【まとめ】機械学習初心者がTensorflowでネコの画像認識をつくってみたらどうなったか

## Tensorflowでネコを画像認識する仕組みをつくってみました！

機械学習の超初心者がTensorflowを元にいろいろつくってみたので紹介します。

最終的につくったものはこちら↓↓↓

~~ねこチェッカー~~　サービスクローズ済み


## Introduction
### 前提
どんな人がやってみたのか、私のプロフィールを少し詳しく説明します。

- インフラ・運用系エンジニア（約9年）
- 機械学習ははじめてチャレンジ
- 数学や統計学も初心者（文系の人）
- 言語はPHP/JavaScript/Perlなどの軽いものだけやったことがある

まとめると超初心者ですね。
統計とかさっぱりです。

今回試してみたきっかけは、トレンドである機械学習は本当に簡単なのか、もしくは大変なことが多いのか、レベルの高いものは作れるのか、いろいろと自分自身で体感してみたかったからです。

### Themes
この記事でまとめるのはこんなことです。

- 機械学習初心者がTensorflowを試して
- 良かったこと
- 大変だったこと

機械学習の統計とか数学的な話はでてきません。
というか勉強中なので書けません。

主に、何が楽しくて何が大変かを整理してお伝えしていきます。

大変なことのほうが圧倒的に多いのですが、どんな壁が待っているかをみなさんに伝えられたら幸いです。

### Tensolflowとは?
まずTensorflowについて。
ここは割愛します。ググってください。たくさん記事が出てきますよ！

簡単にいうと、機械学習を簡単に利用できるライブラリです。
仕様を意識せずに機械学習を行える反面、細かいカスタマイズはできないようです。

今回は機械学習の効果を体感するのが目的なので、細かいところは一旦無視します。

### やったこと

今回実施したことは以下の3点です。

- ネコの画像をスクレイピングして集める
- 画像を学習させる
- お題となる画像を与えて認識できるかチェックしてみる

### イメージ
これが完成イメージ。

まずはネコの画像データを与えます。

![russian.jpg](https://qiita-image-store.s3.amazonaws.com/0/117147/1bd6e22b-1ba6-fd53-0eac-f51ccbc47c12.jpeg)

このネコの画像（ロシアンブルー）を元に結果を問い合わせるコマンドを実行。

```
python3 /usr/local/tf/label_image.py --image cat1.jpg --graph /usr/local/tf/retrained_graph.pb
```

すると結果が返ってくる！

```
russianblue 0.7167683
americanshorthair 0.15780476
munchkin 0.0629924
mikanecat 0.047680147
persia 0.014754322
```
この場合はrussianblue 0.71...となっています。
ロシアンブルーの確信度が71%ということですね！

### 環境
実施した環境はGCPを使っています。
一番弱いインスタンスです。

- GCP VMインスタンス：n1-standard-1(vCPU x 1、メモリ 3.75 GB)
- OS：Debian 4.9
- Python：3.5.3

<img src="https://qiita-image-store.s3.amazonaws.com/0/117147/b4dae395-afc5-f8d6-602a-608b39f504ea.png" width="200px">


### チュートリアルにそって実装
基本的にチュートリアルにそってコーディングしていきます。

Image recognitionのページです。
[TensorFlow - Image Recognition](https://www.tensorflow.org/tutorials/images/image_recognition)

![tensor.png](https://qiita-image-store.s3.amazonaws.com/0/117147/baed5abb-64ea-2192-aafc-38d6d4e16e65.png)

基本的にすべて英語ですが、コマンドさえわかれば問題ありません。
臆せずチャレンジしましょう。


## まずはスクレイピング

TensorFlowを始める前に、まずは利用する画像データをとってきます。

手でダウンロードしていくのは大変なので、スクレイピングという方法をとります。
今回はgoogleimagesdownloadというコマンドを使います。

```
pip3 install google_images_download
googleimagesdownload -k "Scottish Fold"
```
ダウンロードして実行。
-k のあとのダブルクオート内にサーチするキーワードを入れればOKです。
この場合はスコティッシュフォールドの画像を探しています。

### 画像をとってきた！
うまくいくと以下のような形で画像データがゲットできます。

```
-rw-r--r-- 1 xxx xxx   19369 Sep 16 09:04 18. scottish_fold2.jpg
-rw-r--r-- 1 xxx xxx    5604 Sep 16 09:04 16. 220px-white_scottishfold.jpg
-rw-r--r-- 1 xxx xxx  688222 Sep 16 09:04 99. 6.jpg
-rw-r--r-- 1 xxx xxx   75596 Sep 16 09:04 95. scottish_fold_white_550x367.jpg
-rw-r--r-- 1 xxx xxx  140625 Sep 16 09:04 82. angus.jpg
-rw-r--r-- 1 xxx xxx   10598 Sep 16 09:04 68. scottish334.jpg
-rw-r--r-- 1 xxx xxx  201425 Sep 16 09:04 44. animal023_0702_4k.jpg
-rw-r--r-- 1 xxx xxx   24863 Sep 16 09:04 38. 250px-scottish_fold_cat.jpg
-rw-r--r-- 1 xxx xxx   42854 Sep 16 09:04 35. fabio-petroni-scottish-fold-cat_a-g-13454525-14258384.jpg
-rw-r--r-- 1 xxx xxx   54599 Sep 16 09:04 15. 1.jpg
-rw-r--r-- 1 xxx xxx  117165 Sep 16 09:04 11. 16979752-curious-striped-scottish-fold-kitten.jpg
-rw-r--r-- 1 xxx xxx  715242 Sep 16 09:04 10. scottish-fold-cats-michael-d3f35258_cfa6cf2b.jpg
-rw-r--r-- 1 xxx xxx   17777 Sep 16 09:04 55. scottish-fold-1.jpg
-rw-r--r-- 1 xxx xxx  233039 Sep 16 09:04 50. adult-male-blue-scottish-fold-cat-with-golden-eyes-standing-looking-picture-id505322557.jpg
```

一気にダウンロードできるので相当楽です。便利ですねー。

最初は5種類×60枚で行なっていたのですが、このやり方が楽だったため、30種類（人気順）×100枚とってみました。

が・・・これが大変なことになるきっかけとなっていたのです・・・


## 3つの代表的なエラーケース

### ネコじゃない。。。
まずはひとつめ。
Minuetというネコを取得したくて、キーワードに"Minuet"と指定したケース。

<img src="https://qiita-image-store.s3.amazonaws.com/0/117147/0ce881fc-53a8-45d4-2228-91120384a9fa.gif" width="400px">

曲がヒットしてしまいました。。。
本当はネコが欲しいのに。

このようにネコの名前であっても、一般名詞的な使われ方をする場合があるので注意が必要です。
このような場合、"Minuet cat"というように、ネコであることを強調してスクレイピングしましょう。

### ネコ以外が入っている！
次はネコ以外が入っているケース。

<img src="https://qiita-image-store.s3.amazonaws.com/0/117147/358b7487-3c48-04a0-017c-4847caa9099a.jpeg" width="400px">

かわいい。

かわいいことはかわいいのです。

ただ違う。

イヌは今回は呼んでないのです。

### 顔が見えない！
次はこんな場面。

<img src="https://qiita-image-store.s3.amazonaws.com/0/117147/58c3bcaf-fdec-00b4-5dc2-9096c5a06d0c.jpeg" width="400px">

ネコであることは良いのですが、こっちを向いてくれていません。。。
お願いだから顔を上げて。。。

### ここまでやってみて。。。
 ＿人人人人人人人人人人人＿
 ＞　意外と手作業が大変　＜
 ￣Y^Y^Y^Y^Y^Y^Y^Y^Y^Y￣

でした。スクレイピングしたデータをそのまま使える、と思っていたのですが、ある程度は手でなんとかしなきゃいけないんですね。


## Tensorflowインストールと実行
### Tensorflowのインストール

やっとTensorFlowです。
以下のコマンドでインストール。

```
sudo apt-get install -y git build-essential libssl-dev language-pack-id
pip3 install --upgrade pip
pip3 install tensorflow
```

### Tensorflowの実行テスト

実際にTensorflowの動作確認をしてみます。

```
python3
import tensorflow as tf
hello = tf.constant('Hello, TensorFlow!')
sess = tf.Session()
print(sess.run(hello))
```
’Hello, TensorFlow!' と表示されればOK。


### 学習させる
続いて学習させます。
このコマンドもチュートリアルにあります。

```
python3 retrain.py \
  --bottleneck_dir=bottlenecks \
  --how_many_training_steps=100 \
  --model_dir=inception \
  --summaries_dir=training_summaries/basic \
  --output_graph=retrained_graph.pb \
  --output_labels=retrained_labels.txt \
  --image_dir=gakusyu_data
```

2つポイントがあります。

**--how_many_training_steps=100**
ここには学習回数を指定します。この場合100回です。

**--image_dir=gakusyu_data**
ここにはネコの画像があるディレクトリを指定します。この場合gakusyu_dataです。


さあ、これでコマンドを実行します！


学習が完了したかな？と思ったところ、また問題が・・・


### エラー①：ファイル名 too long
```
OSError: [Errno 36] File name too long: "bottlenecks/British Shorthair/85. pet-cat-mammal-whiskers-vertebrate-british-shorthair-european-shorthair-chartreux-russian-blue-korat-cat-mia-small-to-medium-sized-cats-cat-like-mammal-domestic-short-haired-cat-american-shorthair-blue-cat's-743876.jpg_https~tfhub.dev~google~imagenet~inception_v3~feature_vector~1.txt"
```
ファイル名が長いらしいです。。

よくみると確かに恐ろしい長さです。

mvで短いファイル名に置換してあげましょう。


### エラー②：ファイルサイズ too large
```
RuntimeError: Error during processing file gakusyu_data_max/Singapura cat/4. moonwalker_the_singapura.jpg (Invalid JPEG data or crop window, data size 1671168
	 [[Node: DecodeJpeg = DecodeJpeg[acceptable_fraction=1, channels=3, dct_method="", fancy_upscaling=true, ratio=1, try_recover_truncated=false, _device="/job:localhost/replica:0/task:0/device:CPU:0"](_arg_DecodeJPGInput_0_0)]]
```  
今度はファイルサイズが大きすぎるようです。

よく見てみると解像度が「5120×3200」とかです。

サイズの大きいものは削除しましょう。

### エラー③：メモリエラー
```
2018-09-04 22:38:06.231041: W tensorflow/core/framework/allocator.cc:108] Allocation of 49152000 exceeds 10% of system memory.
Killed
```

こんなエラーも出ました。Killedされてる。。
GCPのインスタンスをケチったのが原因かな？


vmstatでリソースを見てみると・・・

```
procs -----------memory---------- ---swap-- -----io---- -system-- ------cpu-----
 r  b   swpd   free   buff  cache   si   so    bi    bo   in   cs us sy id wa st
 0  1      0 450924  20304 161204    0    0  6956     0  419  723 17  5  1 77  0
 1  0      0 400084  20952 177928    0    0  7512     0  636 1252 27  6  0 67  0
 1  0      0 286564  23868 192108    0    0  5916     0  333  853 80  9  0 11  0
 6  0      0 275664  23872 192704    0    0     8     0  843 1527 93  6  0  1  0
 1  0      0 218664  23872 192772    0    0     0     0  282  108 92  8  0  0  0
 2  0      0 177000  23884 194284    0    0  1508     0  289  142 92  4  0  4  0
 0  1      0  80156  23884 243408    0    0 38980     0  455 1121  9  8  0 83  0
 0  2      0  81912  23884 207460    0    0 32028     0  491 4125 17 15  0 69  0
 1  0      0  75488   8708 208428    0    0 19568     0  638 1390 57  8  0 35  0
 1 21      0 609060    236  50240    0    0 14988    84  670 1367 28 56  0 15  0
```
si/soこそ発生していないものの、純粋なfree memoryは75KBですね。

一時的にスケールアップさせてあげます。

クラウドなのですぐできたのは良かった点。


## やっと画像認識を確認してみる

さあ、これでやっと準備OKです。
ここまで来るのに大変でした。

画像認識は試しにこの画像で実行してみます。

<img src="https://qiita-image-store.s3.amazonaws.com/0/117147/a50b7aa9-ca62-1c25-f639-f7a30c427273.jpeg" width="400px">

実行コマンドは以下のものです。
xxx.jpgに画像ファイルを指定します。

```
python3 label_image.py --xxx.jpg --graph retrained_graph.pb --labels retrained_labels.txt
```


### 判定結果が出力された！！

ついに！判定結果が出力されました！

```
persia: 0.28
bengal: 0.07
american shorthair: 0.05
singapura cat: 0.04
abyssinian: 0.03
```
この場合、ペルシャ猫の確信度が28%ということですね。

あれ？でもちょっとまってください。
30種類インプットしたはずなのに、5種類しか表示されていませんね。

### label_image.pyを改修する

いろいろ試行錯誤や質問してみた結果、label_image.pyのソースにキーはありました。

```
top_k = results.argsort()[-5:][::-1]
```

既存のソースコードだと、上位5件のみをリターンするようになっています。

そのため、以下のように変更する必要があります。

```
  top_k = results.argsort()[::-1]
  labels = load_labels(label_file)
  for i in top_k:
    print('{}: {:.2f}'.format(labels[i], results[i]))
```
こうすると、すべての結果をリターンしてくれるようになります。

実行してみると・・・

```
persia: 0.28
bengal: 0.07
american shorthair: 0.05
singapura cat: 0.04
abyssinian: 0.03
tonkinese: 0.028
birman: 0.025
ragdoll: 0.023
munchkin cat: 0.021
laperm: 0.02
scottish fold: 0.02
ragamuffin: 0.02
...
..
.
```
すべて表示されました！これで思っていた結果になりました。


### 学習回数を増やしてみる
学習回数を変更して実施してみました。

学習回数100回

```
persia 0.124754322
```

学習回数500回

```
persia 0.291819281
```

結構変わりますね。学習回数を上げるほど良い結果がでるようですが、どこまでチューニングすればいいかはまだわかりません。



## まとめ

### Good!
#### 動く！楽しい！
初心者でもここまで動かすことができると本当に楽しいです。

#### 大変さが実感できる。
今回いろいろなエラーケースを書きました。できる人からするとなんでもないことかもしれませんが、初めてやってみるとどれだけ大変かが身をもってわかりました。


### Try
#### 手作業（工夫の余地）が結構多い。
スクレイピングしてそのままのデータを利用できないということがわかりました。
使える形にするスクリプトを作って効率化するかkaggleのようなデータセットをもってくるのが良さそうです。（kaggleはあとで知りました）

#### 似ている種類は判別難しい。
30種類で試してみましたが、似ている種類は間違った結果になることも多かったです。
もっと特徴が全然違うものにするとわかりやすいのかもしれません。

### Webにしてみました
コマンドラインだけで実行していると味気なかったのでWebにしてみました。

![web.png](https://qiita-image-store.s3.amazonaws.com/0/117147/66872765-debe-7d76-9371-6c589159e048.png)

実際のサイトはこちら。

~~ねこチェッカー~~ サービスクローズ済み

画像ファイルをアップロードすると、結果を返してくれます。
ネコじゃなくても必ず結果をかえすのでおもしろいです。


### さいごに
大変なことも多かった、はじめての機械学習。
チュートリアルメインでしたが、ほぼ自力でここまでできました。

色々な種類や方法があふれていますが、Tensorflowは比較的やりやすいと思います。
あまり多くのデータや種類を組み込まなければ、エラーになるケースも少ないかと思います。

興味がある方は、一度試してみても良いかもしれませんね。