ここでは、Mac系のOSで深層学習を実施する方法について言及します。はじめに、最も基本的なモデルとして、画像に記載されている数字を当てるという問題を扱います。
こちらをご参照ください。
mnistデータを使用して、0〜9の手書き文字を当てるモデルを構築してみます。まずは、kerasのバックエンドをチェックします。ここで、plaidmlが帰って来ればokです。もしもtensorflowになっている場合は、MacではCPUで深層学習を行うことになるので、相当時間がかかりますので、注意してください。バックエンドが変わらない問題に遭遇したら、上にある「0.準備」の下の方を熟読のこと。
import keras
print(keras.backend.backend())
続いて、mnistのデータを取得します。
from keras.datasets import mnist
(x_train, y_train), (x_test, y_test) = mnist.load_data()
以下の感じで代入できます。教師・テストデータの意味がわからない人は機械学習の項を勉強してください。
shapeで形状を見ると、構造がわかります。
x_train.shape
28x28ピクセルサイズの画像が、6万枚あることを意味しています。y_train[i]と指定することで、x_train[i, :, :]にある画像に書かれている数字の正答値を知ることができます。また、matplotlibのimshow関数を用いることで、画像を可視化できます。
import matplotlib.pyplot as plt
print("0枚目の画像に書かれている数字: ", y_train[0])
plt.imshow(x_train[0, :, :], cmap='gray')
plt.show()
今回は、28x28サイズの画像を、784次元のベクトルに変換し、それを入力する階層型のニューラルネットワークを組んでみます。
処理2は画像データの値の規格化です。グレースケール画像は0が黒、255が白ですが、ニューラルネットワークは値の範囲が規格化されていないとうまく学習できない場合が多いです。したがってここで規格化しておきます。また、処理3を行わないと、エラーが出るので注意が必要です。
import keras
from keras.models import Sequential
from keras.layers import Dense, Dropout
from keras.optimizers import RMSprop
# 各種サイズ取得
num_train = len(y_train) # 教師データ数
num_test = len(y_test) # テストデータ数
print("教師データサイズ: ", num_train)
print("テストデータサイズ: ", num_test)
size_0 = x_train.shape[1] # 横サイズ
size_1 = x_train.shape[2] # 縦サイズ
dim_input_vec = size_0 * size_1
# 画像(行列)をベクトルに変換
x_train = x_train.reshape(num_train, dim_input_vec)
x_test = x_test.reshape(num_test, dim_input_vec)
x_train = x_train.astype('float32')
x_test = x_test.astype('float32')
# 規格化
# グレースケール画像の場合、0が黒、255が白となっている。
# ニューラルは値のレンジが0〜1が望ましいので、そのように規格化
x_train = x_train / 255
x_test = x_test / 255
# クラス分類問題なので、クラスラベルをカテゴリカル変数に変換
num_class = 10
y_train = keras.utils.to_categorical(y_train, num_class)
y_test = keras.utils.to_categorical(y_test, num_class)
今回は、以下の構造を持つ深層学習モデルを定義してみます。
活性化関数はすべてReLuにしました。入力層と出力層の次元は解く問題に依存するので、確定値です。自由には決められないので注意してください。中間層の次元は自由に決められます。なお、出力層の活性化関数は必ずsoftmaxにしなければなりません。これは、0〜9個あるニューロンのうち、最大値となるニューロンを取り出すという処理により、クラス分類を確定させるためです。
compileで記述している部分は、機械学習の理論を読んでいないと理解できないので、ここでは解説しません。ニューラルの基礎理論を理解した人のみ、聴きに来てください。
# モデル構造の定義
model = Sequential()
model.add(Dense(200, activation='relu', input_shape=(dim_input_vec,)))
model.add(Dropout(0.2))
model.add(Dense(100, activation='relu'))
model.add(Dropout(0.2))
model.add(Dense(num_class, activation='softmax'))
model.summary() # 構造出力
# 最適化法などの定義
model.compile(loss='categorical_crossentropy', optimizer=RMSprop(), metrics=['accuracy'])
metal_intel(r) hd graphics 615.0 と出てきました。ここで使用されているCPU/GPUが出てきます。なお今回は、しょぼいintel製GPUが出てきています。
Paramの総数が学習に大きな影響を与えるので、注意してみてください。詳しくはバイアス・バリアンス分解の理論を参照。
ちなみに、dense_1のparamは157000になっています。これは、dim_input_vec次元(784次元)ベクトルを、200次元ベクトルに変換する際、200x784サイズのウェイト行列を、200サイズのバイアスベクトルがパラメータになるためです。200x784+200で157000になります。詳しくは、ニューラルネットワークの理論を読んでいる人しかわからないことですので、理論はほっとく人は気にしないでいいです。
続いて、学習を開始します。深層学習はかなりたくさんのパラメータがあります。詳しくはscikit-learnのニューラルネットワークを解説している項で説明しているので、そちらを参照ください。
# loss が nan になる場合には、batch_sizeを適宜変更しましょう。
# plaidmlの場合、2^n + 1 が推奨らしい? web参照
# 再度やり直す場合には、model = Sequential()のコードを再実行してからにします。
# そうしないと、前の学習からの続きになり、nanからのスタートになってしまいます。
# nan問題は、issue 168を参照 → https://github.com/plaidml/plaidml/issues/168
batch_size = 129
epochs = 20 # 学習回数
learning_process = model.fit(x_train, y_train, # 画像とラベルデータ
batch_size=batch_size, # バッチサイズ
epochs=epochs, # エポック数
verbose=1, # 学習過程を表示する1, しない0
validation_data=(x_test, y_test) # <- 検証データの指定
)
CPUとGPUで処理時間を比べてみると、大前の環境では以下のようになりました。安価なGPUでも、CPUよりも速いことがわかります。
1エポック所要時間
速く終わりたかったので、10エポックにしましたが、精度が悪い場合は100や1000など、大きくしてみてください。エポックとは、勾配法でコスト関数が減少するパラメータの方向を探す際、教師データセットをどのように分割するかに関する話ですが、理論を読んでないとわからないと思うのでこれもここでは説明しません。学習回数くらいの理解で良いと思います。
ここで、validation dataという記載があります。これは、学習に使用していないデータであり、学習過程の「val_acc, val_loss」などがvalidation dataを使用した際の性能になります。ニューラルネットワークにおける学習は、x_train, y_trainの性能を高めることを意味しますが、最終目的はあくまで未知のデータに当てられるかどうかです。したがって、validation dataの性能を逐次チェックし、学習に使用していないデータでも当てられるのか?ということを随時確認しているわけです。
ただし、本来、validation dataにテストデータを割り当てるのはNGな行為なので注意してください。テストデータは構築したAIの性能を測るためのデータなので、学習時にチラチラ見るのは、本来はいけないことです。きちんとやりたい場合は、テストデータをvalidation dataにするのではなく、教師データの一部をvalidation dataにしましょう。
続いて、学習結果を可視化してみます。可視化する際は、learning_processをprintしてみて、何が格納されているのかみてから行うとわかりやすいです。
import numpy as np
plt.figure()
plt.plot(learning_process.epoch, np.array(learning_process.history['acc']),label='Train acc')
plt.plot(learning_process.epoch, np.array(learning_process.history['val_acc']),label='Validation acc')
plt.xlabel('Epoch')
plt.ylabel('Acc')
plt.grid()
plt.legend()
plt.figure()
plt.plot(learning_process.epoch, np.array(learning_process.history['loss']),label='Loss')
plt.plot(learning_process.epoch, np.array(learning_process.history['val_loss']),label='Validation loss')
plt.xlabel('Epoch')
plt.ylabel('Loss')
plt.grid()
plt.legend()
エポックが進むごとに、精度が向上していることを確認できました。重要なのはバリデーションの精度ですので、注意してください。もし、Loss/Accが改善しているのに、Validation loss/accが悪くなっている場合は、過学習が疑われます。
精度は以下のコードで算出できます。
score = model.evaluate(x_test, y_test, verbose=1, batch_size=129) # バッチサイズには2^n+1
print('Loss (test) :', score[0])
print('Accuracy (test) :', score[1])
score = model.evaluate(x_train, y_train, verbose=1, batch_size=129) # バッチサイズには2^n+1
print('Loss (test) :', score[0])
print('Accuracy (test) :', score[1])
入力された画像に対する予測値を得るには、以下のように記載します。 テストデータのi番目の画像に対する推定値を得ています。
i = 0
test_pred = model.predict(x_test[i:i+1,:], batch_size=32+1) # バッチサイズには2^n+1
res = np.argmax(test_pred[i]) # <- 何番目のニューロンの値が一番大きいか?
print("Predict: ", res)
print("True value: ", y_test[i])
推定値が7で、正答値も7番目のニューロンが大きいです。したがって、正しく回答できていることがわかりました。本当にそうなのか、みてみます。28x28サイズの画像を784次元にしているので、28x28サイズにリシェイプし、描画してみます。
img = np.reshape(x_test[i, :], [size_0, size_1])
plt.imshow(img, cmap="gray")
確かに7が出てきました。本当に正しいことがわかりました。
何かしらに組み込む場合は、開発したモデルを保存し、ロードしながら使用することができねばなりません。エラーが出る場合は、pip3 install h5pyが必要かもしれません。
保存は以下の通りです。
model_json_str = model.to_json()
open('mnist_model.json', 'w').write(model_json_str)
model.save_weights('mnist_weights.h5')
ロードは以下の通りです。
from keras.models import model_from_json
# モデルのロード
load_mod = model_from_json(open('mnist_model.json').read())
# 学習済みパラメータの書き込み
load_mod.load_weights('mnist_weights.h5')
ロードしたモデルに予測させてみます。
i=0
res_load = load_mod.predict(x_test[i:i+1,:], batch_size=32+1) # バッチサイズには2^n+1
print(np.argmax(res_load))
一度保存させたモデルも、0番目の画像は7だと答えてくれました。