ニューラルネットワークを使った画像分類（VGG16）の基本的な手順を解説する

#1　はじめに
ディープラーニングによる画像分類の基本的な考え方や計算の内容については、<a href ="https://qiita.com/ykoji/items/b0f36d649c2af9d3222c">別記事</a>を書いたので、そちらを参照してください。今回は、これを踏まえて、実践的な画像分類の方法について、TensorFlowの<a href="https://keras.io/ja/">Keras</a>を使いながら解説をしていきたいと思います。
TensorFlow・Kerasは、機械学習モジュールの１つで、最も人気のあるものと言って良いでしょう。わざわざ計算の数式から書かなくても、主なアルゴリズムはあらかじめ定義されているので、このモジュールから呼び出すだけですぐにディープラーニングのモデルを作ることができます。
ここでは、VGG16の転移学習（一部ファインチューニング）をこのTensorFlow・Kerasを使って実践してみたいと思います。プログラムの実行や内容の利用についてはあくまで自己責任でお願いします。

#２　今回やること
猫、犬、猿の画像をたくさん集めます。それぞれ、cat, dog, monkeyとラベルづけ（それぞれフォルダを分けて保存）をしておきます。そして、これらの画像をディープラーニング（VGG16）で学習します。その後、学習には使っていない猫の画像を見せて猫として判別できるか試してみます。

#３　全体の流れ
<h3>（１）学習データ（とテストデータ）の準備</h3>
①Google画像検索などで、猫、犬、猿の画像をたくさんダウンロードし、フォルダを分けて保存します。フォルダ名はそれぞれcat,dog,monkeyとしておきましょう。
②それぞれのフォルダに保存されたファイルについて、猫は０、犬は１、猿は２としてインデックスをつけた上で、すべてのファイル名を取得します。
③②のそれぞれのファイル名ごとにファイルを開いてPILモジュールを使ってRGBの画像に変換し、２２４×２２４のサイズにリサイズをします。そして、Numpyモジュールを使って画像を配列に変換します。
④リストXには、画像をnumpy配列に変換したデータを、リストYには、インデックス（猫は０、犬は１、猿は２）を追加（アペンド）していきます。そして、最後にX、YともにArrayとして行列に変換します。
⑤X、Yをmodel_selection.train_test_splitモジュールを使って、「学習用のデータ」と「テスト用のデータ」に分割します。このモジュールはデフォルトではその分割サイズが０．７５：０．２５になります。出力としては、X_train、X_test、Y_train、Y_testの行列ができます。
⑥np.saveで⑤の行列を保存します。

<h3>（２）出力データ（Y）、入力データ（X）の加工</h3>
①出力データ（Y_trainとY_test）について、0:cat 1:dog 2:monkeyとなっていますので、これをニューラルネットワークの出力として扱えるよう、catのとき(1,0,0) dogのとき（0,1,0)　monkeyのとき(0,0,1)に変換します。
②入力データ （X_trainとX_test)について、各要素のデータ型を浮動小数点型に変換します（計算ができるようにするために不可欠なプロセスです）。そして、０〜２５５諧調のデータを０〜１に正規化するため、２５５で割ります。

<h3>（３）ニューラルネットワークのモデル（VGGモデル）の定義</h3>
①Kerasであらかじめ用意されているVGG16のモデルを読み込みます。全結合層は自作するので不要です。
②Flatten（平準化）と全結合層（ここでは、ユニットサイズ（出力空間の次元）として、２５６と３を設定します。）を定義します。全結合層の間に０．５のドロップアウトを挿入します。
③①と②のモデルを組み合わせて（結合して）モデルをセットします。
※モデルがセットできたら、サマリーメソッドを使ってモデルの内容を確認しておきます。

<h3>（４）学習</h3>
①今回は一部ファインチューニングなので、０〜１５のレイヤについては、重み付けをフリーズする設定をします。
②モデルをコンパイルします。モデルを最小化しようとする目的関数（これを損失関数といいます）はcategorical_crossentropyに、最適化アルゴリズムは、Adamに、評価関数のリストは、['accuracy']に設定をしておきます。
③モデルを使ってデータを読み込み訓練をします。ここでバッチサイズ（例えば３２）とエポック数（１５〜２０ぐらい）を設定します。
※バッチサイズとは、１回の学習で処理するデータの数のことです。データが大きすぎてコンピュータでデータをすべていっぺんに読み込むことができないので、データを分割して入力します。例えば、バッチサイズ３２で全データ数が３２００の場合、３２×１００回の繰り返しをしてから重みを更新します。この１００回のことを反復（イタレーション：データセットに含まれるデータが少なくとも１回の学習に用いられるために必要な学習回数）と呼びます。
※エポック数とは、３２×１００回の学習のサイクルを何回まわすか、その回数のことをいいます。
④学習後に、学習済みのモデルを拡張子h５のファイルとして保存をします。

<h3>（５）新しい画像で分類にチャレンジ</h3>
①（４）④で保存した学習済みモデル（h5ファイル）を読み込みます。
②画像データのサイズ等を調整して、np.array形式にしてX（入力データ）として定義します。
③model.predictを使って推定します。推定結果がサイズ１×３の行列で返されるので、確率（０〜１で表現されているので、パーセンテージに直すためには、１００倍が必要です）が最も大きいところが画像の推定結果となります。

#４ コード
上記の流れをコードにしていきます。ここでは、GPUが簡単に使えるGoogle Colabで動かせるコードを書いておきます。
注１：GoogleDriveのMy Driveフォルダを使っていますので、Google ColabからGoogle Driveにアクセスできるように設定する必要があります。
注２：高速に計算を行うため、ランタイムをGPUに設定する必要があります。
注３：用意するファイルは、cat,dog,monkeyの名前のフォルダとその中の画像ファイル（可能な限りたくさん用意します）、cat_test.jpegというファイル名の分類テスト用の新しい画像ファイルです。

<h3>（１）のコード</h3>

```python

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

classes = ["cat","dog","monkey"]
num_classes = len(classes)
image_size = 224

X = [] 
Y = [] 

for index, classlabel in enumerate(classes):
    photos_dir = "./" + classlabel
    files = glob.glob("./drive/My Drive/" + photos_dir + "/*.jpg")
    for i, file in enumerate(files):
        image = Image.open(file)
        image = image.convert("RGB")
        image = image.resize((image_size,image_size))
        data = np.asarray(image) 
        X.append(data)
        Y.append(index)

X = np.array(X)
Y = np.array(Y)

X_train, X_test, y_train, y_test = model_selection.train_test_split(X, Y)
xy = (X_train, X_test, y_train, y_test)
np.save("/content/drive/MyDrive/data_vgg/animal.npy", xy)

```

<h3>（２）〜（４）のコード</h3>

```python
import numpy as np
from tensorflow import keras
from tensorflow.keras.models import Sequential, Model
from tensorflow.keras.layers import Dense, Dropout, Flatten
from tensorflow.keras.layers import Conv2D, MaxPooling2D
from tensorflow.keras.optimizers import SGD, Adam
from tensorflow.python.keras.utils import np_utils
from tensorflow.keras.applications import VGG16
from keras.applications.inception_resnet_v2 import InceptionResNetV2

classes = ["cat","dog","monkey"]
num_classes = len(classes)
image_size = 224

#(2)
X_train, X_test, y_train, y_test = np.load("/content/drive/MyDrive/data_vgg/animal.npy",allow_pickle=True)
y_train = np_utils.to_categorical(y_train, num_classes)
y_test = np_utils.to_categorical(y_test, num_classes)
X_train = X_train.astype("float") / 255.0
X_test = X_test.astype("float") /255.0

#(3)
model = VGG16(weights='imagenet', include_top=False, input_shape=(image_size,image_size,3))

print("VGG16の全結合層抜きモデル")
model.summary()

top_model = Sequential()
top_model.add(Flatten(input_shape=model.output_shape[1:]))
top_model.add(Dense(256, activation='relu'))
top_model.add(Dropout(0.5))
top_model.add(Dense(num_classes, activation='softmax'))

print("Sequentialモデル")
top_model.summary()

model = Model(inputs=model.input, outputs=top_model(model.output))

print("統合モデル")
model.summary()

#(4)
for layer in model.layers[:13]:
    layer.trainable = False

opt = Adam(learning_rate=0.0001)
model.compile(loss='categorical_crossentropy', optimizer=opt,metrics=['accuracy'])
model.fit(X_train, y_train, batch_size=32, epochs=17)

score = model.evaluate(X_test, y_test, batch_size=32)

model.save("/content/drive/MyDrive/data_vgg/animal_cnn.h5")
```

<h3>（５）のコード</h3>

```python
import numpy as np
from tensorflow import keras
from tensorflow.keras.models import Sequential, Model,load_model
from PIL import Image
import sys

classes = ["cat","dog","monkey"]
num_classes = len(classes)
image_size = 224

#cat_test.jpegというファイル名を用意してフォルダに保存しておきます
image = Image.open("cat_test.jpeg")
image = image.convert("RGB")
image = image.resize((image_size,image_size))
data = np.asarray(image) / 255.0
X = []
X.append(data)
X = np.array(X)

model = load_model('./animal_cnn.h5')

result = model.predict([X])[0]
predicted = result.argmax()
percentage = int(result[predicted] * 100)

print(classes[predicted], percentage)
```