美女を見分けられない機械はただの機械だ：Pythonによる機械学習用データセット生成

#データセットとは
機械学習におけるデータセットとは、学習に必要な情報がまとまっている「You これで機械学習やっちゃいなよ」セットである。様々なデータセットがネット上に公開されており、
MNISTの手書き数字のデータセットなどはとても有名である。

#データセット生成方法の情報が少ない
web上のデータセットを用いて機械学習の勉強をする際には問題にならないが、勉強を進めていくと「これは自分オリジナルだぜ」的な機械学習を試しにさせてみたくなるのが漢の性である。

しかし、ここで壁にぶち当たる。。。
機械学習自体の情報に比べて、データセット生成に関するが圧倒的に少ない！と拙者は思う。プロフェッショナルな方々は「データセット？そんなん朝飯前に作れるっしょ？」的な高飛車な感じなのかもしれませんが、拙者みたいな「夏です。機械学習始めました。」みたいな初心者にとっては大事なのである。

なのでどうにかこうにか画像判別用データセット生成に辿り着いたので、その方法を一例として公開しようと思ったわけである。

#画像データセット生成までのフロー
拙者は愛すべき美女達(満島ひかり、沢尻エリカ、宮崎あおい、新垣結衣、本田翼)のクラス分類をしたかったので、美女データセットを作成しました。流れは以下になります。

1. 画像収集
2. 顔抽出
3. データセットの形式に変換

##画像の集め方、顔抽出
画像の収集方法、顔抽出方法は

[美女を見分けられない機械はただの機械だ：OpenCV, Python3による画像からの顔抽出](https://qiita.com/Tatsuro64/items/95b0ce48b6bb155bfe29)

に書いてあるのでそちらを参照してください。私は合計1500枚ほどの美女の顔抽出画像を集めました。（照)

#データセットの形式に変換
データセット生成に当たり[画像認識で坂道グループの判別AIの作成](https://qiita.com/tigerz55/items/e4d1d5b8e00f7a771177)を大変参考にさせていただきました。

##データセットの呼び出し方を考える
上記の記事ではデータのNumpy配列オブジェクトをpickle漬けにして保存している。しかし拙者は機械学習を[Pythonではじめる機械学習](https://www.amazon.co.jp/Python%E3%81%A7%E3%81%AF%E3%81%98%E3%82%81%E3%82%8B%E6%A9%9F%E6%A2%B0%E5%AD%A6%E7%BF%92-%E2%80%95scikit-learn%E3%81%A7%E5%AD%A6%E3%81%B6%E7%89%B9%E5%BE%B4%E9%87%8F%E3%82%A8%E3%83%B3%E3%82%B8%E3%83%8B%E3%82%A2%E3%83%AA%E3%83%B3%E3%82%B0%E3%81%A8%E6%A9%9F%E6%A2%B0%E5%AD%A6%E7%BF%92%E3%81%AE%E5%9F%BA%E7%A4%8E-Andreas-C-Muller/dp/4873117984)という本で勉強したので、この本に書いてあるskicit-learnのデータセットの読み出し方がしっくりくる。なのでデータセットを以下のように読み出せるようにプログラムを書いた。

```python
from MyDataSet import beautiful_women

women = beautiful_women.load_beautiful_women()
```

##プログラム
今回私は画像から顔を切り抜いた段階で画像サイズを128*128に統一していたが、サイズを統一していない場合は、2回目のfor文のコメントアウトしてある部分を外してください。

```python:beautiful_women.py

from PIL import Image
import numpy as np
import os, glob

#画像フォルダへの絶対パス
ROOT_DIR = "/Users/tatsuro64/BeautifulWomenCLF/MyDataSet/imgs/"
#各美女の顔画像フォルダ名
WOMEN = ["aoi_ROI", "erika_ROI", "gakky_ROI", "hikari_ROI", "tsubasa_ROI"]

#美女クラス
class BeautifulWomen:
    def __init__(self, data, target, target_names, images):
        self.data = data
        self.target = target
        self.target_names = target_names
        self.images = images

    #キー(インスタンス変数)を取得するメソッド
    def keys(self):
        print("[data, target, target_names, images]")


#画像データをNumpy形式に変換してデータセットを作成
def load_beautiful_women():
    data = []      #画像の一次元データを格納するlist
    target = []    #ラベル(正解)の情報を格納するlist
    target_names = ["aoi", "erika", "gakky", "hikari", "tsubasa"]
    images = []    #画像の2次元データを格納するlist

    for label, woman in enumerate(WOMEN):
        file_dir = ROOT_DIR + woman
        #美女画像を全て取ってくる
        files = glob.glob(file_dir + "/*.jpg")
        print("~~~~~~~~{}の画像をNumpy形式に変換し、Listに格納中~~~~~~~~".format(woman))
        for i, f in enumerate(files):
            img = Image.open(f)
            img = img.convert('L')          #画像をグレースケールに変換
            #img = img.resize((128, 128))    #画像サイズの変更
            imgdata = np.asarray(img)       #Numpy配列に変換
            images.append(imgdata)          #画像データ: 128*128の2次元配列
            data.append(imgdata.flatten())  #画像データ: 16,384の1次元配列
            target.append(label)            #正解ラベルを格納

    print("------------ListをNumpy形式に変換中--------------")
    data = np.array(data)
    target = np.array(target)
    target_names = np.array(target_names)
    images = np.array(images)

    #インスタンスを生成
    beautifulWomen = BeautifulWomen(data, target, target_names, images)
    
    return beautifulWomen
```

##実際に読み出してみる
```python:test.py
from MyDataSet import beautiful_women

women = beautiful_women.load_beautiful_women()
print(women.data.shape)
print(women.keys())

>>> 実行結果
>>> ~~~~~~~~aoi_ROIの画像をNumpy形式に変換し、Listに格納中~~~~~~~~
>>> ~~~~~~~~erika_ROIの画像をNumpy形式に変換し、Listに格納中~~~~~~~~
>>> ~~~~~~~~gakky_ROIの画像をNumpy形式に変換し、Listに格納中~~~~~~~~
>>> ~~~~~~~~hikari_ROIの画像をNumpy形式に変換し、Listに格納中~~~~~~~~
>>> ~~~~~~~~tsubasa_ROIの画像をNumpy形式に変換し、Listに格納中~~~~~~~~
>>> ----------------ListをNumpy形式に変換中------------------
>>> (1456, 16384)
>>> [data, target, target_names, images]
```
##この方法によるメリット・デメリット
pickle漬けで保存する場合は、生成したオブジェクトをバイト列にして保存する。なのでデータセットのNumpy形式に変換するのは一度だけでよく、呼び出し時にはバイト列->オブジェクトの変換をするだけで良い。

一方拙者が選んだ上記の方法は呼び出しごとに、データをNumpy形式に変換してデータセットを生成する必要がある。よって計算コストはpickle漬けに比べて高い。しかし、毎回データセットを画像フォルダから生成するため、後からの画像追加などに対応することが出来てより柔軟である。