機械学習で「令和」Tシャツと「平成」Tシャツを見分ける。

# 目的
2019.4.1.に新元号「令和」が発表されました。
![540px-Yoshihide_Suga_announcing_new_imperial_era_Reiwa_2_(cropped).jpg](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/344880/2e154e21-a969-53cd-0760-9186603efa9e.jpeg)

「人々が美しく心を寄せ合う中で文化が生まれ育つ」という意味が込められているようで、
災害や景気変動の影響が残る平成時代の課題を踏まえ、良い時代を創っていければと切に願います。

一方で、新しい元号にちなんだ商品もちらほら見かけるようになりました。
<img width="152" alt="スクリーンショット 2019-04-13 17.32.36.png" src="https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/344880/ffb410fd-821f-3846-a7c3-67ac5f98a84d.png">

そういえば、平成Tシャツあったな、と思い出しまして、
![heisei1.jpg](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/344880/c9136a63-9e2c-b0bd-e52e-449d904b0add.jpeg)

今回は、これら商品の文字がどの程度機械学習で分類可能か試したいと思います。

# 準備
1.「令和」「平成」の文字画像を用意します。

- 令和 20枚
<img width="240" alt="スクリーンショット 2019-04-14 18.03.28.png" src="https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/344880/c8cce159-29a4-203f-cd75-f2e8d86925f1.png">

- 平成 20枚
<img width="240" alt="スクリーンショット 2019-04-14 18.03.55.png" src="https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/344880/29a41fb5-633d-8805-324d-b3873c3a717f.png">

- 最終評価用 6枚 ※文字の部分は切り出して使います、一部の画像は有名人の方の画像を使わせて頂きます。
<img width="240" alt="スクリーンショット 2019-04-14 18.09.16.png" src="https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/344880/584fdaeb-5675-b516-30cc-d7a62d0a1c2a.png">


2.学習(train)/検証(validation)/最終評価(evaluation)フォルダにそれぞれ格納します。

data-org
- train
　　reiwa ：用意した「令和」のうち16枚をこちらに格納
　　heisei ：用意した「平成」のうち16枚をこちらに格納
- validation
　　reiwa ：：用意した「令和」のうち4枚をこちらに格納
　　heisei ：用意した「平成」のうち4枚をこちらに格納
- evaluation 6枚
　　
3.機械学習用コード準備（後述のlearn.py）
　学習データの数を増やせるのが望ましいですが準備工数削減のため、
　ImageDataGeneratorで、回転/ズーム/などを駆使して枚数増やします。

# コード

```'learn.py'
from tensorflow.python.keras import layers
from tensorflow.python.keras import models
from tensorflow.python.keras import optimizers
from tensorflow.python.keras.preprocessing.image import ImageDataGenerator
import matplotlib.pyplot as plt
import numpy as np
from PIL import Image
import glob

# モデルの定義
model = models.Sequential()
model.add(layers.Conv2D(16, (3, 3), activation='relu', input_shape=(256, 256, 3)))
model.add(layers.Conv2D(16, (3, 3), activation='relu'))
model.add(layers.MaxPooling2D((2, 2)))
model.add(layers.Conv2D(32, (3, 3), activation='relu'))
model.add(layers.MaxPooling2D((2, 2)))
model.add(layers.Flatten())
model.add(layers.Dense(512, activation='relu'))
model.add(layers.Dense(256, activation='relu'))
model.add(layers.Dense(1, activation='sigmoid'))

# モデルのコンパイル
model.compile(optimizer='rmsprop',
              loss='binary_crossentropy',
              metrics=['acc'])

model.summary()

# クラス宣言
train_datagen = ImageDataGenerator(
    rescale=1.0 / 255,
    shear_range=0.2,
    zoom_range=0.2,
    horizontal_flip=True)

test_datagen = ImageDataGenerator(rescale=1.0 / 255)


# フォルダ指定
batch_size=20
train_generator = train_datagen.flow_from_directory(
    'data/train',
    target_size=(256, 256),
    batch_size=batch_size,
    class_mode='binary')

validation_generator = test_datagen.flow_from_directory(
    'data/validation',
    target_size=(256, 256),
    batch_size=batch_size,
    class_mode='binary')

print(train_generator.class_indices)


# 学習
history = model.fit_generator(train_generator,
                              steps_per_epoch=10,
                              epochs=10,
                              validation_data=validation_generator,
                              validation_steps=10)

acc = history.history['acc']
val_acc = history.history['val_acc']
loss = history.history['loss']
val_loss = history.history['val_loss']

epochs = range(1, len(acc) + 1)

print(acc);
print(val_acc);
print(loss);
print(val_loss);

plt.plot(epochs, acc, 'bo', label='Training acc')
plt.plot(epochs, val_acc, 'b', label='Validation acc')
plt.title('Training and validation accuracy')
plt.legend()
plt.savefig("figure-acc.png")
plt.figure()

plt.plot(epochs, loss, 'bo', label='Training loss')
plt.plot(epochs, val_loss, 'b', label='Validation loss')
plt.title('Training and validation loss')
plt.legend()
plt.savefig("figure-loss.png")

# evaluation
paths = glob.glob("./data/evaluation/*.jpg")
for path in paths:
    img = Image.open(path).convert('RGB')
    img = img.resize((256, 256))
    x = np.array(img)
    x = x / 255.
    x = x[None, ...]
    pred_prob = model.predict(x, batch_size=1, verbose=0)

    # 2値分類なので[0,1]の確率を出力する。0.5以上なら1とする。
    pred = np.round(pred_prob, 0)

    print("---{0}---".format(path))
    print(pred_prob)
    print("{0}の予測結果は{1}です．".format(path, pred))

```

# テスト

学習stepのさじ加減は調整必要ですが、とりあえず15epochを指定して学習。
（もっと良いパラメータがあるはず）

```
2019-04-14 17:34:11.610592: I tensorflow/core/platform/cpu_feature_guard.cc:142] Your CPU supports instructions that this TensorFlow binary was not compiled to use: AVX2 FMA
Model: "sequential"
_________________________________________________________________
Layer (type)                 Output Shape              Param #   
=================================================================
conv2d (Conv2D)              (None, 254, 254, 16)      448       
_________________________________________________________________
conv2d_1 (Conv2D)            (None, 252, 252, 16)      2320      
_________________________________________________________________
max_pooling2d (MaxPooling2D) (None, 126, 126, 16)      0         
_________________________________________________________________
conv2d_2 (Conv2D)            (None, 124, 124, 32)      4640      
_________________________________________________________________
max_pooling2d_1 (MaxPooling2 (None, 62, 62, 32)        0         
_________________________________________________________________
flatten (Flatten)            (None, 123008)            0         
_________________________________________________________________
dense (Dense)                (None, 512)               62980608  
_________________________________________________________________
dense_1 (Dense)              (None, 256)               131328    
_________________________________________________________________
dense_2 (Dense)              (None, 1)                 257       
=================================================================
Total params: 63,119,601
Trainable params: 63,119,601
Non-trainable params: 0
_________________________________________________________________
Found 32 images belonging to 2 classes.
Found 8 images belonging to 2 classes.
{'heisei': 0, 'reiwa': 1}
Epoch 1/15
10/10 [==============================] - 18s 2s/step - loss: 8.1931 - acc: 0.4750 - val_loss: 0.8280 - val_acc: 0.5000
Epoch 2/15
10/10 [==============================] - 16s 2s/step - loss: 0.7148 - acc: 0.4875 - val_loss: 0.7022 - val_acc: 0.5000
Epoch 3/15
10/10 [==============================] - 17s 2s/step - loss: 0.7318 - acc: 0.5250 - val_loss: 0.7039 - val_acc: 0.5000
Epoch 4/15
10/10 [==============================] - 18s 2s/step - loss: 0.6831 - acc: 0.5437 - val_loss: 0.7165 - val_acc: 0.5000
Epoch 5/15
10/10 [==============================] - 18s 2s/step - loss: 0.9253 - acc: 0.5375 - val_loss: 0.6806 - val_acc: 0.5375
Epoch 6/15
10/10 [==============================] - 16s 2s/step - loss: 0.6640 - acc: 0.6438 - val_loss: 0.6785 - val_acc: 0.4875
Epoch 7/15
10/10 [==============================] - 17s 2s/step - loss: 0.6093 - acc: 0.6812 - val_loss: 0.7921 - val_acc: 0.4000
Epoch 8/15
10/10 [==============================] - 16s 2s/step - loss: 0.6163 - acc: 0.6875 - val_loss: 0.8923 - val_acc: 0.4500
Epoch 9/15
10/10 [==============================] - 16s 2s/step - loss: 0.5593 - acc: 0.7312 - val_loss: 0.8695 - val_acc: 0.5625
Epoch 10/15
10/10 [==============================] - 17s 2s/step - loss: 0.5669 - acc: 0.7063 - val_loss: 0.8767 - val_acc: 0.3125
Epoch 11/15
10/10 [==============================] - 16s 2s/step - loss: 0.6163 - acc: 0.7188 - val_loss: 0.8409 - val_acc: 0.5000
Epoch 12/15
10/10 [==============================] - 16s 2s/step - loss: 0.5653 - acc: 0.7125 - val_loss: 0.9275 - val_acc: 0.4125
Epoch 13/15
10/10 [==============================] - 17s 2s/step - loss: 0.6229 - acc: 0.7625 - val_loss: 0.8779 - val_acc: 0.3500
Epoch 14/15
10/10 [==============================] - 17s 2s/step - loss: 0.5152 - acc: 0.7625 - val_loss: 0.8519 - val_acc: 0.5125
Epoch 15/15
10/10 [==============================] - 17s 2s/step - loss: 0.5381 - acc: 0.7563 - val_loss: 0.7191 - val_acc: 0.5000
```

![figure-acc.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/344880/3a8059bd-3188-77f1-8f52-0d32917bdcd3.png)

# 実画像評価
最終評価用の画像データ6枚を使用

<img width="207" alt="スクリーンショット 2019-04-14 18.14.37.png" src="https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/344880/f923a5a5-946c-3236-df6f-f4d0195ed9d4.png">
期待値：平成　　予測値：平成

```
---./data_org/evaluation/heisei1-2.jpg---
[[0.04057287]]
./data_org/evaluation/heisei1-2.jpgの予測結果は[[0.]]です．
```

<img width="204" alt="スクリーンショット 2019-04-14 18.14.41.png" src="https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/344880/8f8d7762-446d-0e23-115a-ff1fc0e71847.png">
期待値：平成　　予測値：平成　※スコアぎりぎり

```
---./data_org/evaluation/heisei2-2.jpg---
[[0.4843383]]
./data_org/evaluation/heisei2-2.jpgの予測結果は[[0.]]です．
```


<img width="203" alt="スクリーンショット 2019-04-14 18.14.18.png" src="https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/344880/1df90da9-2250-76ae-bbb0-1835ca14cc78.png">
期待値：令和　　予測値：令和　※スコアぎりぎり

```
---./data_org/evaluation/reiwa1-1.jpg---
[[0.5244837]]
./data_org/evaluation/reiwa1-1.jpgの予測結果は[[1.]]です．
```

<img width="191" alt="スクリーンショット 2019-04-14 18.14.22.png" src="https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/344880/39a6a8bc-589c-4cc3-13a7-2fe2d4b3e1e9.png">

期待値：令和　　予測値：令和

```
---./data_org/evaluation/reiwa2-2.jpg---
[[0.93729514]]
./data_org/evaluation/reiwa2-2.jpgの予測結果は[[1.]]です．
```

<img width="193" alt="スクリーンショット 2019-04-14 18.14.27.png" src="https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/344880/e57f498f-354f-950e-19f2-b33aa2a097c0.png">

期待値：令和　　予測値：令和

```
---./data_org/evaluation/reiwa3-2.jpg---
[[0.9273448]]
./data_org/evaluation/reiwa3-2.jpgの予測結果は[[1.]]です．
```

<img width="190" alt="スクリーンショット 2019-04-14 18.14.30.png" src="https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/344880/d9ed42fb-cc29-7a8e-bb77-401055e9cc8e.png">

期待値：令和　　予測値：令和

```
---./data_org/evaluation/reiwa4-2.jpg---
[[0.9319874]]
./data_org/evaluation/reiwa4-2.jpgの予測結果は[[1.]]です．
```

<img width="200" alt="スクリーンショット 2019-04-14 18.14.34.png" src="https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/344880/59b22f03-cc0d-e6bf-193b-e0704fb169f4.png">

期待値：令和　　予測値：令和　※スコアぎりぎり

```
---./data_org/evaluation/reiwa5-2.jpg---
[[0.6226176]]
./data_org/evaluation/reiwa5-2.jpgの予測結果は[[1.]]です．
```


# 感想

もっと明確に評価スコアが分かれると思いましたが、ぎりぎりのデータが数個ありました。
全体の結果は期待通りとなったものが多いですが、サンプル数が乏しく、本当に性能に問題ないのかは確認継続が必要だと考えています。残課題は下記の通り。
・過学習していないか、学習が足りないのではないか要確認
　　→入力データのノイズがどれくらい影響している？
・ハイパーパラメータ調整
・汎化性能の評価

もし詳しい方がおられれば、ご指導頂ければと思います。

# CodeingError対策

```
OSError: cannot identify image file './data_org/train/showa//.DS_Store'
```
DS_StoreはMacのメタデータのようです。python処理に不要であれば下記コマンド等で消しましょう。
$ find ./data_org/ -name .DS_Store | xargs rm


# 参考
[令和-wikipedia](https://ja.wikipedia.org/wiki/令和![540px-Yoshihide_Suga_announcing_new_imperial_era_Reiwa_2_(cropped).jpg](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/344880/8482ba58-09e6-ca26-cfa6-1e94b8fe1ca3.jpeg)
)
[Python: コマンドライン引数とは？（超基礎）](https://qiita.com/orange_u/items/3f0fb6044fd5ee2c3a37)
[macから.DS_Storeを消しさる](https://qiita.com/supersnack/items/157b69589e36f340bb29)
[ImageDataGeneratorクラス](https://keras.io/ja/preprocessing/image/)
[SequentialモデルでKerasを始めてみよう](https://keras.io/ja/getting-started/sequential-model-guide/)
[そのモデル、過学習してるの？未学習なの？と困ったら](https://chezou.hatenablog.com/entry/2016/05/29/215739)