Kerasは相撲取りとアイドルを見分けられるかやってみた


Keras（バックエンドはtensorflow）で、この度横綱に昇進した稀勢の里と、千年に一人の逸材の橋本環奈を見分けられるのかやってみた。で、その途中でアイドル同士を見分けることってできるんだろうかと思って広瀬すずも追加して三つ巴でやってみた。
[Kerasによる、ものすごくシンプルな画像分類（りんごとオレンジ）](http://qiita.com/hiroeorz@github/items/ecb39ed4042ebdc0a957)では読み込んだ画像をフラットな１次元配列に変換してモデルの学習に使ったけど、今回はより本格的に畳み込み層(Convolution2D)を追加してやってみます。

[Kerasのドキュメント](https://keras.io/ja/getting-started/sequential-model-guide/)を読むと、Convolution2Dに喰わせる前に、画像を読み込んだ配列の次元をいじる必要があるようです。例えば `縦:100` x `横:100` x `RGB:3` の画像を読み込んだ場合、100x100x3の配列として読み込まれますが、これを 3x100x100の形に直してからConvolution2Dに与えろってことらしいです。


これらからまず、

```
./data/train/kisenosato/(稀勢の里の画像)
            /hashimoto/(橋本環奈の画像)
            /hirose/(広瀬すずの画像)

./data/test/kisenosato/(稀勢の里の画像)
           /hashimoto/(橋本環奈の画像)
           /hirose/(広瀬すずの画像)
```

として、 `data/train` 以下の画像は学習用、 `data/test` 以下の画像は学習後のテスト用として使用する。学習用の画像はそれぞれ30枚づつぐらい、Google画像検索で手動で集めたです....

コードはこんな感じ

```python:kisenosato.py
from keras.models import Sequential
from keras.layers import Activation, Dense, Dropout, Convolution2D, Flatten, MaxPooling2D
from keras.utils.np_utils import to_categorical
from keras.optimizers import Adagrad
from keras.optimizers import Adam
import numpy as np
from PIL import Image
import os

# 学習用のデータを作る.
image_list = []
label_list = []

# ./data/train 以下のorange,appleディレクトリ以下の画像を読み込む。
for dir in os.listdir("data/train"):
    if dir == ".DS_Store":
        continue

    dir1 = "data/train/" + dir 
    label = 0

    if dir == "kisenosato":    # 稀勢の里はラベル0
        label = 0
    elif dir == "hashimoto":   # 橋本環奈はラベル1
        label = 1
    elif dir == "hirose":      # 広瀬すずはラベル2
        label = 2
        
    for file in os.listdir(dir1):
        if file != ".DS_Store":
            # 配列label_listに正解ラベルを追加(稀勢の里:0 橋本環奈:1 広瀬すず:2)
            label_list.append(label)
            filepath = dir1 + "/" + file
            # 画像を100x100pixelに変換し、1要素が[R,G,B]3要素を含む配列の100x100の２次元配列として読み込む。
            # [R,G,B]はそれぞれが0-255の配列。
            image = np.array(Image.open(filepath).resize((100, 100)))
            print(filepath)
            # 配列を変換し、[[Redの配列],[Greenの配列],[Blueの配列]] のような形にする。
            image = image.transpose(2, 0, 1)
            print(image.shape)
            # 出来上がった配列をimage_listに追加。
            image_list.append(image / 255.)

# kerasに渡すためにnumpy配列に変換。
image_list = np.array(image_list)

# ラベルの配列を1と0からなるラベル配列に変更
# 0 -> [1,0,0], 1 -> [0,1,0] という感じ。
Y = to_categorical(label_list)

# モデルを生成してニューラルネットを構築
model = Sequential()
model.add(Convolution2D(32, 3, 3, border_mode='same', input_shape=(3, 100, 100)))
model.add(Activation("relu"))
model.add(Convolution2D(32, 3, 3))
model.add(Activation("relu"))
model.add(MaxPooling2D(pool_size=(2, 2), border_mode=("same")))
model.add(Dropout(0.25))

model.add(Flatten())

model.add(Dense(200))
model.add(Activation("relu"))
model.add(Dropout(0.2))

model.add(Dense(200))
model.add(Activation("relu"))
model.add(Dropout(0.2))

model.add(Dense(3))
model.add(Activation("softmax"))

# オプティマイザにAdamを使用
opt = Adam(lr=0.0001)
# モデルをコンパイル
model.compile(loss="categorical_crossentropy", optimizer=opt, metrics=["accuracy"])
# 学習を実行。10%はテストに使用。
model.fit(image_list, Y, nb_epoch=1000, batch_size=25, validation_split=0.1)

# テスト用ディレクトリ(./data/train/)の画像でチェック。正解率を表示する。
total = 0.
ok_count = 0.

for dir in os.listdir("data/test"):
    if dir == ".DS_Store":
        continue

    dir1 = "data/test/" + dir 
    label = 0

    if dir == "kisenosato":
        label = 0
    elif dir == "hashimoto":
        label = 1
    elif dir == "hirose":
        label = 2
        
    for file in os.listdir(dir1):
        if file != ".DS_Store":
            label_list.append(label)
            filepath = dir1 + "/" + file
            image = np.array(Image.open(filepath).resize((100, 100)))
            print(filepath)
            image = image.transpose(2, 0, 1)
            result = model.predict_classes(np.array([image / 255.]))
            print("label:", label, "result:", result[0])
            
            total += 1.
            
            if label == result[0]:
                ok_count += 1.

print("seikai: ", ok_count / total * 100, "%")
```

```text:2017/02/15追記
ニューラルネットワークにConvolution2Dを1層追加して合わせて2層とし、加えてMaxPooling2D層を追加したところ識別率が上がりました。
```

さて、結果の出力ですが、上記のスクリプトを実行すると学習プロセスが表示されて、最終的に

```
Epoch 1000/1000
108/108 [==============================] - 0s - loss: 2.8859e-05 - acc: 1.0000 - val_loss: 0.6708 - val_acc: 0.7500


data/test/hashimoto/1032kanna-top_1200x.jpg
1/1 [==============================] - 0s
label: 1 result: 1
data/test/hashimoto/4616_original-e1444887883222.jpg
1/1 [==============================] - 0s
label: 1 result: 1
data/test/hashimoto/d724e07900b770b9f78eaa1b111c8a2f.jpg
1/1 [==============================] - 0s
label: 1 result: 1
data/test/hashimoto/JLU_4kBV.jpeg
1/1 [==============================] - 0s
label: 1 result: 1
data/test/hashimoto/wam1022-kanna-003_x480.jpg
1/1 [==============================] - 0s
label: 1 result: 1
data/test/hashimoto/WS000043.jpg
1/1 [==============================] - 0s
label: 1 result: 1
data/test/hashimoto/橋本環奈.jpg
1/1 [==============================] - 0s
label: 1 result: 2
data/test/hirose/20170110hirose.jpg
1/1 [==============================] - 0s
label: 2 result: 2
data/test/hirose/2e2d42fcee97871743e9a52f54c0fabb_319.jpeg
1/1 [==============================] - 0s
label: 2 result: 2
data/test/hirose/m_E5BA83E780ACE38199E3819A.jpg
1/1 [==============================] - 0s
label: 2 result: 2
data/test/hirose/mig.jpeg
1/1 [==============================] - 0s
label: 2 result: 2
data/test/hirose/uV3Uh8toqAmGDHQ_Inuz4_109.png
1/1 [==============================] - 0s
label: 2 result: 2
data/test/kisenosato/2012062514095159c.jpg
1/1 [==============================] - 0s
label: 0 result: 0
data/test/kisenosato/20161226-00000028-tospoweb-000-1-view.jpg
1/1 [==============================] - 0s
label: 0 result: 0
data/test/kisenosato/images.jpeg
1/1 [==============================] - 0s
label: 0 result: 0
data/test/kisenosato/kise.jpg
1/1 [==============================] - 0s
label: 0 result: 0
data/test/kisenosato/o03780450m0023152112.jpg
1/1 [==============================] - 0s
label: 0 result: 0
seikai:  94.11764705882352 %
```

実行結果の出力は上記の通り。

結果を見やすく表にすると...


![1032kanna-top_1200x.jpg](https://qiita-image-store.s3.amazonaws.com/0/13835/79e09ff2-cd6a-851f-beb5-8c735e0db1ae.jpeg "1032kanna-top_1200x.jpg")
判定：橋本環奈

![4616_original-e1444887883222.jpg](https://qiita-image-store.s3.amazonaws.com/0/13835/cd261cc6-3ebc-9610-d3f3-1060f05b0ee0.jpeg "4616_original-e1444887883222.jpg")
判定：橋本環奈 

![d724e07900b770b9f78eaa1b111c8a2f.jpg](https://qiita-image-store.s3.amazonaws.com/0/13835/f37e8c84-de27-a976-3cf8-8c3ef50f0d1c.jpeg "d724e07900b770b9f78eaa1b111c8a2f.jpg")
判定：橋本環奈 

![JLU_4kBV.jpeg](https://qiita-image-store.s3.amazonaws.com/0/13835/c7c402bc-d9e1-a1f8-393b-7fd2acf85fde.jpeg "JLU_4kBV.jpeg")
判定：橋本環奈

![wam1022-kanna-003_x480.jpg](https://qiita-image-store.s3.amazonaws.com/0/13835/9d58c79b-98dd-a7e5-a5f5-9488bcb680ee.jpeg "wam1022-kanna-003_x480.jpg")
判定：橋本環奈

![WS000043.jpg](https://qiita-image-store.s3.amazonaws.com/0/13835/e9775760-f261-5cd2-4253-8ce0cd2ce832.jpeg "WS000043.jpg")
判定：橋本環奈

![橋本環奈.jpg](https://qiita-image-store.s3.amazonaws.com/0/13835/456bbcc6-23eb-2cee-6e15-23f108473d99.jpeg "橋本環奈.jpg")
判定：広瀬すず（不正解！）

![2e2d42fcee97871743e9a52f54c0fabb_319.jpeg](https://qiita-image-store.s3.amazonaws.com/0/13835/16d1ced3-c7c4-2491-3ea1-66c0374954e9.jpeg "2e2d42fcee97871743e9a52f54c0fabb_319.jpeg")
判定：広瀬すず

![20170110hirose.jpg](https://qiita-image-store.s3.amazonaws.com/0/13835/b4f2cb34-775e-27d2-9c80-2849ed688588.jpeg "20170110hirose.jpg")
判定：広瀬すず

![m_E5BA83E780ACE38199E3819A.jpg](https://qiita-image-store.s3.amazonaws.com/0/13835/f0990f8e-2ad7-01b5-2bfc-31b9f6765ee7.jpeg "m_E5BA83E780ACE38199E3819A.jpg")
判定：広瀬すず

![mig.jpeg](https://qiita-image-store.s3.amazonaws.com/0/13835/4fe26e68-f23f-b7ca-c024-bc095eb8b74f.jpeg "mig.jpeg")
判定：広瀬すず

![uV3Uh8toqAmGDHQ_Inuz4_109.png](https://qiita-image-store.s3.amazonaws.com/0/13835/06f46f8e-e468-8be2-7a01-08ad368ba93a.png "uV3Uh8toqAmGDHQ_Inuz4_109.png")
判定：広瀬すず

![20161226-00000028-tospoweb-000-1-view.jpg](https://qiita-image-store.s3.amazonaws.com/0/13835/6b249dd9-9637-a7b7-2e71-3ac4aae006c7.jpeg "20161226-00000028-tospoweb-000-1-view.jpg")
判定：稀勢の里

![2012062514095159c.jpg](https://qiita-image-store.s3.amazonaws.com/0/13835/598cbd79-38a0-4f12-e613-6f2547fa497d.jpeg "2012062514095159c.jpg")
判定：稀勢の里

![images.jpeg](https://qiita-image-store.s3.amazonaws.com/0/13835/03735760-6482-c79f-4936-77b33fe481f4.jpeg "images.jpeg")
判定：稀勢の里

![kise.jpg](https://qiita-image-store.s3.amazonaws.com/0/13835/067271d9-ea31-8d0d-35f5-c0a859d4a59b.jpeg "kise.jpg")
判定：稀勢の里

![o03780450m0023152112.jpg](https://qiita-image-store.s3.amazonaws.com/0/13835/322078d6-7828-59ec-79e6-55ad07a9a578.jpeg "o03780450m0023152112.jpg")
判定：稀勢の里

というわけで識別率は94.11%となりました。一枚が不正解、他の全ては正解です。素材の画像が少ない状態であることを考えると結構いい確率なのでは。
あと、ラベルは `0が稀勢の里` `1が橋本環奈` `2が広瀬すず` なんですが、稀勢の里とアイドルを間違えてないところがさすがです笑 橋本環奈と広瀬すずは一箇所だけ間違えてますね。正直なところ、相撲取りとアイドルは見分けられても、アイドル同士はこの学習用画像数では難しいのでは、って思ってたら結構見分けられるんですねえ。

とりあえず画像の分類については一旦ここで置いて、今度はRNNをやってみようと思います。