こんにちは。アプリケーション共同開発部エンジニアの石島です。
先月 10 月に Google Home が発表されました。音声で家電等が操作できる、調べ物ができるなど人の生活を補助するツールとしてこれから益々着目されると思います。この音声認識に用いられているのが Deep Learning と言われる技術です。そこで Deep Learning を手軽に実装できる Keras を用いて画像認識を試してみようと思います!
今回は Deep Learning の一種である Convolutional Neural Network(CNN)を Keras を用いて実装し、2種類の Droid くんを認識させようと思います。対象となる Droid くんは以下の Droid くんです。では早速行っていきます!
Kerasとは
Kerasは,Pythonで書かれた,TensorFlowまたはCNTK,Theano上で実行可能な高水準のニューラルネットワークライブラリです. Kerasは,迅速な実験を可能にすることに重点を置いて開発されました. アイデアから結果に到達するまでのリードタイムをできるだけ小さくすることが,良い研究をするための鍵になります.
公式のドキュメントでは上記のように示されています。 TensorFlow と比べると簡潔に実装でき、直感的に理解しやすいコードになると思います。
モデル構成
まずは構築するネットワークモデルを考えます。今回は以下のようなモデルを構築しようと思います。
事前準備
開発環境は Python 3 で Keras のバージョンは 2.0.8 です。はじめに学習に用いる Droid くん画像を集めます。今回はノーマルな Droid くんとクイーンバージョン Droid くんを撮影し、各 70 枚ほど準備しました。
データ作成
はじめに list_pictures で指定したパスから画像ファイルのリストを取得します。次に load_img で画像を読み込みます。通常の画像サイズでは大きく、処理時間がかかってしまうので target_size で 64 x 64 にリサイズするように指定します。画像は Pillow の形式で読み込まれるのでこれを Numpy の形式に合わせるように変換します。これは img_to_array を使用することで簡単に変換できます。上記のメソッドは Keras で用意されています。
学習したモデルを評価するために用意したデータの2割ほどを評価用のデータとし、残りを学習用のデータとします。以下では学習用データの X_train と Y_train を用いてモデルの学習を行います。そして学習したモデルに対して X_test を入力し、出力データと Y_test を比較し、一致しているかどうかで学習したモデルの評価を行います。
データを分ける前に訓練データと教師データの正規化を行います。画像の画素値は 0 ~ 255 までの値のため、これを 0 ~ 1 の範囲に変換します。また教師データについては2種類の認識のため「0」と「1」のバイナリ表現でも問題ないのですが、あえて one-hot 表現という方法で2種類の教師データを作成します。例えば次の3種類の Droid くんを認識したい場合、ノーマル Droid くんが「0」、クイーン Droid くんが「1」、第3の Droid くんが「2」という教師データを「0」→「1,0,0」、「1」→「0,1,0」、「2」→「0,0,1」として表現します。これは to_categorical を使用することで表現できます。学習データの形式は(画像数、画像の幅、画像の高さ、チャンネル数)となっています。教師データの形式は(画像数、クラス数)となっています。
def make_data(img_dir_list): # 訓練データリスト X = [] # 教師データリスト Y = [] for i, path in enumerate(img_dir_list): print(path) for picture in list_pictures(path): # 画像を行列変換 img = img_to_array(load_img(picture, target_size=(64, 64))) X.append(img) Y.append(i) # リストを Numpy の array に変換 X = np.asarray(X) Y = np.asarray(Y) # 正規化(自作関数) X = normalization(X) # 教師データを one-hot 表現に変換 Y = np_utils.to_categorical(Y, len(img_dir_list)) print("Train Shape :", X.shape) print("Label Shape :", Y.shape) # 学習データとテストデータを作成 X_train, X_test, Y_train, Y_test = train_test_split(X, Y, test_size=0.2, random_state=0) return X_train, X_test, Y_train, Y_test
モデル構築&学習
ではモデルを構築して学習してみましょう。注意点は入力層である畳み込み層 Conv2D では入力するデータの形式、最後の出力層 Dense ではクラス数(分類数)を指定することです。前者は画像の幅(width)、高さ(height)、チャンネル数(channel)を指定し、後者は今回2種類の Droid くんを認識するので 2を指定します。あとは中間層(隠れ層)を自由に積み重ねてネットワークを深くしていきます。
畳み込み層では畳み込み演算と呼ばれる計算を行います。この演算を行うと出力されるサイズ(幅、高さ)が縮小してしまい、最終的にサイズが 1 x 1 になると畳み込み演算が適用できません。そのため padding=same を指定することで出力されるサイズを入力と同様のものにしています。畳み込み層の次にあるのはプーリング層で入力のサイズ(幅、高さ)を小さくする演算を行います。この演算によって入力データのズレを吸収できるため、対象物体の位置変化に対して頑強になります。この畳み込みとプーリングによって画像から自動的に特徴を抽出しているのが CNN の大きな特徴です。次に Dropout 層を設けることでニューロンをランダムに削除しています。これは過学習を防ぐためです。
モデルの構築には様々な引数が存在します。引数でパラメータや関数、手法などが選択でき、手軽に試せる点も Keras のメリットだと思います。また、 ImageDataGenerator を用いることで自前のデータを加工して増やすことができます。 EarlyStopping を用いることで過学習が起きる前に学習を止めることができます。この他にもエポックごとに学習したモデルを保存したり、学習を途中から行う機能なども用意されています。
def build_model(input_shape): model = Sequential() # 畳み込み層 model.add(Conv2D(32, # フィルタ数 (3, 3), # フィルタサイズ activation="relu", # 活性化関数 padding="same", # パディング input_shape=input_shape.shape[1:], # 入力 kernel_initializer="he_normal")) # 重みの初期化 # プーリング層 model.add(MaxPooling2D(pool_size=(2, 2))) # ドロップアウト model.add(Dropout(0.25)) model.add(Conv2D(64, (3, 3), activation="relu", padding="same", kernel_initializer="he_normal")) model.add(MaxPooling2D(pool_size=(2, 2))) model.add(Dropout(0.25)) # 平坦化( 16 x 16 x 64 の3次元データを 16 x 16 x 64 = 16384 の1次元に変換) model.add(Flatten()) model.add(Dense(256, activation="relu", kernel_initializer="he_normal")) model.add(Dropout(0.5)) model.add(Dense(CATEGORY_NUM, activation="softmax")) # 最適化手法の選択(自作関数) # 普通に "adam" , "sgd" など文字列で指定してもよい(パラメータはデフォルトになる) optimizer = select_optimizer(2) model.compile(loss="categorical_crossentropy", optimizer=optimizer, metrics=["accuracy"]) return model
if __name__ == '__main__': img_dir_list = get_img_dir_path() X_train, X_test, Y_train, Y_test = make_data(img_dir_list) data_generator = ImageDataGenerator(rotation_range=180, # 画像の回転 width_shift_range=0.2, # 水平方向の移動 height_shift_range=0.2, # 垂直方向の移動 shear_range=0.78, # シアー変換 zoom_range=0.5, # ズーム horizontal_flip=True, # 水平方向の反転 vertical_flip=True) # 垂直方向の反転 data_generator.fit(X_train) model = build_model(X_train) early_stopping = EarlyStopping(monitor="val_loss", patience=1, verbose=1, mode="auto") history = model.fit_generator(data_generator.flow(X_train, Y_train, batch_size=25), steps_per_epoch=500, # 水増しさせるサンプル数 epochs=10, # エポック数 verbose=1, # ログ出力モード validation_data=(X_test, Y_test), callbacks=[early_stopping]) score = model.evaluate(X_test, Y_test) print("loss=", score[0]) print("accuracy=", score[1]) plot_model(model, to_file=MODEL_IMAGE, show_shapes=True, show_layer_names=True) model.save(MODEL_DATA) print("Complete!")
plot_model を用いることで構築したモデルを図として保存し、確認することができます。今回は下図のようになりました。モデル構成図と比較するとほぼ同じだと思います。 Tensorboard を使って可視化もできるようです。また構築し、学習したモデルを save を呼び出すことで保存できます。
認識
では実際に学習したモデルを用いて Droid くん認識を行います。学習したモデルを読み込みを行うとモデルの構築だけでなく、計算済みの重みも読み込まれます。今回のモデルへの入力形式は幅が 64 、高さが 64 、チャンネル数が3となっているので入力する画像をデータの作成時と同様に 64 × 64 にリサイズし、モデルに入力します。predict_proba メソッドは各クラス(カテゴリ)に属する確率を出力します。以下では2種類の確率の高い方のインデックスを取得し、どちらのクラス(カテゴリ)に属するかを出力するようにしています。
from keras.models import load_model from keras.preprocessing.image import img_to_array, load_img, list_pictures import os, sys import numpy as np # モデルデータ MODEL_DATA = "droids_model.hdf5" # テスト画像ディレクトリ IMAGE_DIR = "./images/droids/test_images/test1" # カテゴリー CATEGORIES = ["Normal Droid", "Queen Droid"] """ 画素値の正規化 @param[in] data 画像データリスト @return data 画像データリスト """ def normalization(data): data = data.astype("float32") data = data / 255.0 return data if __name__ == '__main__': X = [] file_path_list = [] for picture in list_pictures(IMAGE_DIR): file_path_list.append(picture) # 画像の読み込み img = load_img(picture, target_size=(64, 64)) img = img_to_array(img) X.append(img) # Numpy の array に変換 X = np.asarray(X) # 正規化 X = normalization(X) # モデルの読み込み model = load_model(MODEL_DATA) # 予測(分類) predict = model.predict_proba(X) for i, pre in enumerate(predict): idx = np.argmax(pre) rate = pre[idx] * 100 print(file_path_list[i] + " : " + str(CATEGORIES[idx]) \ + " : " + str(round(rate, 5)))
結果
学習したモデルを用いてノーマルな Droid くんとクイーンバージョン Droid くんを各3枚ずつ、計6枚を試しに認識させてみました。以下に実行した結果を示します。結果は画像パス、認識結果、所属確率の順で表示されます。6枚全てにおいて正しく認識されました。 6枚の画像は Droid くんのみが写っていて、比較的に認識しやすい画像なのでうまく認識されました。しかし、例えば Droid くんの一部が何かに重なっていて見えなくなっていたり、背景がもっと乱雑な場合では結果が変わるかもしれません。
感想
Keras を用いることで CNN が比較的簡単に記述できました。今回は CPU のみを用いた計算のため、それほど層を深くしませんでした。しかし、さらに層を深くして計算したい場合には GPU を用意するのがオススメです。現在では AWS などに環境構築済みのインスタンスが用意されているのでそちらを使用するのもいいと思います。これから Deep Learning をはじめてみたいという方や理論はともかく実装したいという方は Keras を使ってみてはいかがでしょうか。
*Keras のバージョンによりメソッド名や引数名が多少異なる場合があります。また、使い方が間違っているものもあるかもしれません。
フェンリルのオフィシャル Twitter アカウントでは、フェンリルプロダクトの最新情報などをつぶやいています。よろしければフォローしてください!
フェンリルの Facebook ページでは、最新トピックをお知らせしています。よろしければいいね!してください!