機械学習によるギター画像の分類 その1

## サマリ

* CNN（ResNet）によるギター画像の分類で99%超の精度を達成した。
* ImageNetの一般画像分類モデルを元にした転移学習による学習の高速化・高精度化・汎化性能の向上を確認した。

## はじめに

本記事は、アラフォー・エンジニアによる、夏休みの自由研究の記録です。
CNNを使った、ギター画像の分類にチャレンジしました。
技術的に目新しい話はあまりないですが、ギターを題材にした事例は意外にもなさそうだったので、なんとなく結果を公開します。

## 環境

自宅のパソコンです。

```
Intel(R) Core(TM) i7-2600 CPU @ 3.40GHz
Memory: 32GB
Geforce GTX 1080 （Founders Edition）
Ubuntu 16.04
Python 3.5.3
Keras（backend: Tensorflow）
```

## データセットの準備

データセットは、Web検索からスクレイピングにより調達し、手動でちまちまとラベルを修正しました。ラベルは以下の13種です。

* Stratocaster
* Telecaster
* Jazzmaster
* Jaguar
* Mustang
* LesPaul
* SG
* FlyingV
* Explorer
* Firebird
* SemiHollow
* Hollow
* Acoustic

ラベルがフェンダー/ギブソンのソリッドモデルに偏っているのは、たまたまラベル付き画像の調達が容易だったためで、特に深い意味はないです。

今回は、各クラス250枚の画像を用意し、そこからランダムに選んだ50枚を検証サンプル、残り200枚を学習サンプルとします。


## モデルの構築

Kerasでは50層のResNetがプリセット（？）として用意されているので、とりあえずそれを使うことにしました。
そのままだと1000クラスの分類モデルとなっているので、全結合層を切除（```include_top=False```）し、所望の分類のための全結合層を接ぎ木します。接ぎ木部分はミニマムに、全結合1層 + Softmaxです。

```python
resnet = ResNet50(include_top=False, input_shape=(224, 224, 3), weights="imagenet")
h = Flatten()(resnet.output)
model_output = Dense(len(classes), activation="softmax")(h)
model = Model(resnet.input, model_output)
```

ここで、`weights="imagenet"`とすると、ImageNetを使った学習結果が重みの初期値として設定されます。この状態から学習を開始することで、ファインチューニングすなわち転移学習となります。ちなみに、今回は学習済レイヤのフリーズはせず、学習時に全レイヤの重みを更新します。

`weights=None`とすると、重みは乱数で初期化されます。つまり、転移なしでゼロから学習することになります。

今回は、転移のあり・なし両方で実験してみました。


## 学習

サンプル画像が比較的少ないので、学習に際してはデータの水増しが必要です。今回は、KerasのImageDataGeneratorを使ってData Augumentationを実施しています。

```python
train_gen = ImageDataGenerator(
    rotation_range=45.,
    width_shift_range=0.2,
    height_shift_range=0.2,
    zoom_range=0.2,
    horizontal_flip=True,
    vertical_flip=True)

train_flow = train_gen.flow_from_directory(
    directory="./image/train",
    batch_size=32,
    target_size=(224, 224),
    classes=[_["key"] for _ in classes]
)
```

アフィン変換による学習データの水増しがとても簡単にできます。<font color="DarkRed">~~しかも、画像ファイルのロード＋Augumentationは学習とは並列・非同期で走るという素敵仕様です。個人的には、これだけでもKeras使う価値があると思いました。~~</font>

**追記・訂正：**
Kerasにおいてデータの前処理が学習と並列・非同期で走るのは、[Kerasの学習エンジンの機能（fit_generator内でのOrderedEnqueuer/GeneratorEnqueuerの利用による）](https://github.com/fchollet/keras/blob/135efd66d00166ca6df32f218d086f98cc300f1e/keras/engine/training.py#L1834-L2096)で、ImageDataGeneratorの提供する機能ではありませんでした。私の勘違いから誤解を招きうる表現となっていたので、訂正します。



最適化は、[ResNetのオリジナル論文](https://arxiv.org/abs/1512.03385)に倣ってMomentum+SGDを採用しました。

```python
optimizer = SGD(decay=1e-6, momentum=0.9, nesterov=True)
```

一応Adam等も試しましたが、巷で言われている通り、ResNetに関してはMomentum+SGDが優秀でした。

今回は、32サンプルのミニバッチ学習1000回を1ステップとし、ステップ毎に検証の精度を確認していきます。検証の精度が収束したら学習を停止します。（Early Stopping）

```python
es_cb = EarlyStopping(patience=20)
```



## 学習結果

結果を見てみましょう。

まず、転移学習の場合。精度の推移はこんな感じです。
![trans.PNG](https://qiita-image-store.s3.amazonaws.com/0/78866/d195ba2a-6a89-9d13-8f4c-9d9ffb00809d.png)

青が学習曲線、橙が検証曲線です。

検証の精度がバタつきますが、36ステップで学習精度99.9%、検証精度100%をマークします。その後も精度は上がったり下がったりしますが、とりあえず、この36ステップ目のスナップショットを成果物として採用します。ちなみに、36ステップまでの所要時間は約5時間でした。

一方、転移なしの場合。
![non_trans.PNG](https://qiita-image-store.s3.amazonaws.com/0/78866/aed99a04-6c5d-ed87-d3cc-9e415c2173aa.png)

転移学習と比べると、学習の進みが遅いうえに、精度もいまいちです。ベストスコアは学習精度が99%、検証精度が84%。学習と検証で精度の乖離が大きく、モデルの汎化性能が低いことがわかります。

転移学習の効果は絶大ですね。


## あれこれ推論させてみる

手持ちのギターの写真を撮って、推論させてみました。
使ったのは、転移ありの方のモデルです。

**Jazzmaster**
![jm.jpg](https://qiita-image-store.s3.amazonaws.com/0/78866/a6a55800-9864-9c89-b6e2-eb5468419152.jpeg)

**LesPaul**
![lp.jpg](https://qiita-image-store.s3.amazonaws.com/0/78866/9e23ae2e-7d34-da8d-12ba-5ff7020e6752.jpeg)

**アコギ**
![ac.jpg](https://qiita-image-store.s3.amazonaws.com/0/78866/c7b21b34-cc9e-190f-dd60-6fa3ac6868a6.jpeg)

ちゃんと機能しているようです。

学習データにない形のギターではどうでしょうか。


**Duo Sonic**
![ds.jpg](https://qiita-image-store.s3.amazonaws.com/0/78866/5232f365-d9da-9c0f-64ec-cfe11c0311b0.jpeg)

ムスタングと同じステューデントモデルということで、納得の結果です。


**謎のスピーカー内蔵ギター（Crews製・型式不明）**
![xx.jpg](https://qiita-image-store.s3.amazonaws.com/0/78866/5ac91319-6cc5-e7db-1250-42354a032cfc.jpeg)

、、、ジャガーには見えないなあ。
じゃあ何に似てるかと言われると困りますが、レスポールとかテレキャスターの方がまだ近いような気がします。特徴のとらえ方が人間とは少し違いそうです。

最後にちょっといたずらしてみます。

Jazzmasterにペイントツールでちょっとだけ落書きすると、、、
![jm2.jpg](https://qiita-image-store.s3.amazonaws.com/0/78866/42962c1a-524f-ed94-938f-09d624d3ab2b.jpeg)

なぜかフライングVに。うーん。。
どこら辺を見てそう思ったのかが気になります。


## 感想

Deep Learningでの画像認識は初めて試してみましたが、予想以上の精度が出ました。
楽器のECサイトやオークションサイトで、商品のタグ付・分類が間違っているケースは多々あるので、こういうのでチェックかけた方がいいと思います。

また、一般画像分類モデルからの転移学習が、特定ドメインの画像分類に効果的であることも確認できました。

一方で、出力の根拠・判断基準がわからず、誤分類の補正が難しいという、Deep Learningの課題も現認できました。
今回のモデルは、理想的な入力（ノイズのない画像）では高精度の分類が可能な一方、僅かなノイズで判断が大きく変化してしまうという不安定さを見せました。学習時の入力に意図的にノイズを加えることで、もっと頑健なモデルを生成できると思われるので、時間があれば試してみたいです。（⇒[やってみました](http://qiita.com/coz-a/items/b8f27f452677ec80a40a)。）
モデルの着目箇所を推定するGrad-CAMというテクニックもあるようなので、併せて試して、変化を見てみたいですね。

今回はモデルにResNet-50を使いましたが、このくらいの分類タスクはもっと軽量なモデルでも行けそうな予感がする（なんとなく）ので、浅めのNetwork in Networkあたりを使って、蒸留によるモデルの縮小にもチャレンジしてみたいです。