【ディープラーニング】文系男子がpythonとkerasを使ってMNISTをやってみた
ディープラーニングって難しいイメージがあったので手を出さなかったんですけど、
なんとなくディープラーニングのフレームワーク調べてたら思ってたよりコードが少なくて
あれこれって意外といけるんじゃね?って軽いノリで初めてみました。
やったことは大したことないんですがすごく楽しいです。
転職に活かせたらいいなと思い書きます。
- ところでkerasってなに
- kerasを使ってMNISTをやってみる
- matplotlibを使ってmnistを画像表示
- reshapeで配列を変換する
- 正解ラベルをone hot 表現に変える
- モデルの作成
- compileで学習の条件を指定する
- fitメソッドで学習させてみる
- evaluateメソッドでテストしてみる
- modelの保存
- コード
- まとめ
ところでkerasってなに
kerasってのはディープラーニング初心者用のライブラリと考えていいかと。
TensorFlowでは沢山のコードを書かないといけないのに対してkerasでは少量のコードで実装できます。
TensorFlowのラッパーなのでkerasを使うにはTensorFlowをインストールする必要があります。
kerasを使ってMNISTをやってみる
まずTensorFlowとkerasをインストールします。
$ sudo pip install keras tensorflow
kerasと学習に必要なモジュールとmnistをインポートします。
import keras from keras.models import Sequential from keras.layers import Dense, Activation from keras.datasets import mnist
次にmnistのデータをトレーニング用のデータ(x_train)と答え(y_train)、テスト用のデータ(x_test)と答え(y_test)の4つに分割します。
(x_train, y_train), (x_test, y_test) = mnist.load_data()
それぞれ中身をshapeを使って見てみます。
print(x_train.shape, y_train.shape, x_test.shape, y_test.shape)
out : (60000, 28, 28) (60000,) (10000, 28, 28) (10000,)
x_trainは28×28の行列データが60000個入っています。
y_trainは答えが60000個ですね。
testデータは10000個のようです。
28×28の行列と言われても想像しにくいので実際に表示してみます。
x_train[0]
out : array([[ 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0], [ 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0], [ 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0], [ 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0], [ 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0], [ 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 3, 18, 18, 18, 126, 136, 175, 26, 166, 255, 247, 127, 0, 0, 0, 0], [ 0, 0, 0, 0, 0, 0, 0, 0, 30, 36, 94, 154, 170, 253, 253, 253, 253, 253, 225, 172, 253, 242, 195, 64, 0, 0, 0, 0],
というようにリストが沢山入っています。
行が折り返されて見づらいので整列させてみます。
numpyのset_printoptions引数でlinewidthを与えると表示範囲を広げられます。
import numpy as np np.set_printoptions(linewidth=150) print(x_train[0])
out : [[ 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0] [ 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0] [ 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0] [ 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0] [ 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0] [ 0 0 0 0 0 0 0 0 0 0 0 0 3 18 18 18 126 136 175 26 166 255 247 127 0 0 0 0] [ 0 0 0 0 0 0 0 0 30 36 94 154 170 253 253 253 253 253 225 172 253 242 195 64 0 0 0 0] [ 0 0 0 0 0 0 0 49 238 253 253 253 253 253 253 253 253 251 93 82 82 56 39 0 0 0 0 0] [ 0 0 0 0 0 0 0 18 219 253 253 253 253 253 198 182 247 241 0 0 0 0 0 0 0 0 0 0] [ 0 0 0 0 0 0 0 0 80 156 107 253 253 205 11 0 43 154 0 0 0 0 0 0 0 0 0 0] [ 0 0 0 0 0 0 0 0 0 14 1 154 253 90 0 0 0 0 0 0 0 0 0 0 0 0 0 0] [ 0 0 0 0 0 0 0 0 0 0 0 139 253 190 2 0 0 0 0 0 0 0 0 0 0 0 0 0] [ 0 0 0 0 0 0 0 0 0 0 0 11 190 253 70 0 0 0 0 0 0 0 0 0 0 0 0 0] [ 0 0 0 0 0 0 0 0 0 0 0 0 35 241 225 160 108 1 0 0 0 0 0 0 0 0 0 0] [ 0 0 0 0 0 0 0 0 0 0 0 0 0 81 240 253 253 119 25 0 0 0 0 0 0 0 0 0] [ 0 0 0 0 0 0 0 0 0 0 0 0 0 0 45 186 253 253 150 27 0 0 0 0 0 0 0 0] [ 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 16 93 252 253 187 0 0 0 0 0 0 0 0] [ 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 249 253 249 64 0 0 0 0 0 0 0] [ 0 0 0 0 0 0 0 0 0 0 0 0 0 0 46 130 183 253 253 207 2 0 0 0 0 0 0 0] [ 0 0 0 0 0 0 0 0 0 0 0 0 39 148 229 253 253 253 250 182 0 0 0 0 0 0 0 0] [ 0 0 0 0 0 0 0 0 0 0 24 114 221 253 253 253 253 201 78 0 0 0 0 0 0 0 0 0] [ 0 0 0 0 0 0 0 0 23 66 213 253 253 253 253 198 81 2 0 0 0 0 0 0 0 0 0 0] [ 0 0 0 0 0 0 18 171 219 253 253 253 253 195 80 9 0 0 0 0 0 0 0 0 0 0 0 0] [ 0 0 0 0 55 172 226 253 253 253 253 244 133 11 0 0 0 0 0 0 0 0 0 0 0 0 0 0] [ 0 0 0 0 136 253 253 253 212 135 132 16 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0] [ 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0] [ 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0] [ 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0]]
うっすら見ると5っぽいですよね笑
linewidthのデフォルトの数値は75です。
ちなみにこれで5っぽく見えない場合は桁合わせをすると見えるようになるかもしれません。
np.set_printoptions(formatter={'int':'{:3d}'.format}, linewidth=150)
じゃ答えを見てみますか。
y_train[0]
5が出ましたか?
こんな風にxには行列で表された数字が入っています。
0〜255の数字ってのはRGBですね。
0に近づくと黒っぽく、255に近づくと白っぽくなります。
matplotlibを使ってmnistを画像表示
次はmnistをmatplotlibを使って画像表示してみます。
画像表示にはmatplotlibのimshowメソッドを使います。
import matplotlib.pyplot as plt %matplotlib inline plt.imshow(x_train[0])
おどろおどろしい色の5が浮かび上がりました?
cmapでカラーを変更できます。みなさんgrayが多いみたいですね。
plt.figure(figsize=(15,15)) for i in range(1,10): plt.subplot(3,3,i) plt.imshow(x_train[i], cmap='hot')
figsizeで画像の大きさを指定しています。
forで回して10枚分の画像をプロットしています。
subplotでは縦3枚、横3枚で何番目に並べるか画像の配列を指定しています。
imshowの引数でcmapを与えて、今回はhotで表示してみました、熱そうですね。
ちなみに有効なカラーマップ(cmap)は以下の通りです。
Accent, Accent_r, Blues, Blues_r, BrBG, BrBG_r, BuGn, BuGn_r, BuPu, BuPu_r, CMRmap, CMRmap_r, Dark2, Dark2_r, GnBu, GnBu_r, Greens, Greens_r, Greys, Greys_r, OrRd, OrRd_r, Oranges, Oranges_r, PRGn, PRGn_r, Paired, Paired_r, Pastel1, Pastel1_r, Pastel2, Pastel2_r, PiYG, PiYG_r, PuBu, PuBuGn, PuBuGn_r, PuBu_r, PuOr, PuOr_r, PuRd, PuRd_r, Purples, Purples_r, RdBu, RdBu_r, RdGy, RdGy_r, RdPu, RdPu_r, RdYlBu, RdYlBu_r, RdYlGn, RdYlGn_r, Reds, Reds_r, Set1, Set1_r, Set2, Set2_r, Set3, Set3_r, Spectral, Spectral_r, Wistia, Wistia_r, YlGn, YlGnBu, YlGnBu_r, YlGn_r, YlOrBr, YlOrBr_r, YlOrRd, YlOrRd_r, afmhot, afmhot_r, autumn, autumn_r, binary, binary_r, bone, bone_r, brg, brg_r, bwr, bwr_r, cividis, cividis_r, cool, cool_r, coolwarm, coolwarm_r, copper, copper_r, cubehelix, cubehelix_r, flag, flag_r, gist_earth, gist_earth_r, gist_gray, gist_gray_r, gist_heat, gist_heat_r, gist_ncar, gist_ncar_r, gist_rainbow, gist_rainbow_r, gist_stern, gist_stern_r, gist_yarg, gist_yarg_r, gnuplot, gnuplot2, gnuplot2_r, gnuplot_r, gray, gray_r, hot, hot_r, hsv, hsv_r, inferno, inferno_r, jet, jet_r, magma, magma_r, nipy_spectral, nipy_spectral_r, ocean, ocean_r, pink, pink_r, plasma, plasma_r, prism, prism_r, rainbow, rainbow_r, seismic, seismic_r, spring, spring_r, summer, summer_r, tab10, tab10_r, tab20, tab20_r, tab20b, tab20b_r, tab20c, tab20c_r, terrain, terrain_r, viridis, viridis_r, winter, winter_r
reshapeで配列を変換する
28×28だと2次元になってしまうので1次元にするためにreshapeを使います。
28×28=784です。
x_train = x_train.reshape(60000, 784) x_test = x_test.reshape(10000,784)
別に掛け算しているわけではなくて縦と横あったものを一つにまとめたってだけですね。
これだと16とか243とかの数値のままなのでこれを0~1の数値に変換します。
なぜならそのほうが計算しやすいから、なんですって。
ってことでそれぞれの数値を最大値である255で割ります。
x_test = x_test.astype('float32') x_train = x_train.astype('float32') x_test /= 255 x_train /= 255 print(x_test[0]) print(x_train[0])
それぞれのxのデータをfloat32に変換します。(なんでfloat32なのかわかりません。64でもいいのに。もしかしてメモリ関係してる?)
余談ですが、小数点の桁数を変えるときはprecisionを使います。
import numpy as np np.set_printoptions(precision=3) x_train[0]
こうすると小数点第3位まで表示することができます。
正解ラベルをone hot 表現に変える
正解ラベルも0と1で表するためにone hot 表現を使用します。
one hot表現とは正解を1、その他を0で表す方法です。
例えば1~5までの数値の中で正解が3だとしたら[0,0,1,0,0]ってなふうに表示されます。
y_train = keras.utils.to_categorical(y_train, 10) y_test = keras.utils.to_categorical(y_test, 10) print(y_train[0]) print(y_test[0])
keras.utilsのto_categoricalというメソッドを使い引数に何を変換して全部で何個あるのかを指定します。
one hot表現に変えたあとどうなったか見てみましょい。
y_train[0]は5、y_test[0]は7です。
[ 0. 0. 0. 0. 0. 1. 0. 0. 0. 0.] [ 0. 0. 0. 0. 0. 0. 0. 1. 0. 0.]
こんな感じです、わかりますかね?こんな説明で。
モデルの作成
入力するデータがいくつあって、それをどんな関数を使って答えを判断するのか、などなどそれらを指定するための入れ物であるモデルを作成します。
model = Sequential([ Dense(32, input_shape=(784,)), Activation('relu'), Dense(10), Activation('softmax'), ])
784個のデータを32個のノードに変換します。
その後relu関数を使って活性化させます。
さらに32個のノードを10個のノードに変換して最終的にsoftmax関数で活性化させて終了です。
図にするとこんなイメージ。
なんでDense32にするのかわからないんですけど、確か32とか128とか決まった数字があったはずです。
とりあえずわからなければこの数字を使って変換するといいいよって何かで読みました。
kerasのドキュメントだったか書籍だったか、うーん思い出せない。
compileで学習の条件を指定する
最後にどのように学習させるのか指定します。
model.compile(optimizer='rmsprop', loss='binary_crossentropy', metrics=['accuracy'])
optimizerは最適化の意味で引数に勾配法を指定します。
rmspropのほかにSGDなどいくつか種類がありますが今回は二値分類で使われるrmspropにしました。
確率的勾配降下法はうっすらわかりますが計算式などはわからないので詳しく知りたい方は各々調べてください。
lossは損失関数のことで、二値分類なのでbinary_crossentropyを選びました。
binaryは2進数のことで01で表されるので二値分類のときなどに使わるそうです。
metricsは評価関数のことでこれはよくわからないので精度を表すためにaccuracyにしました。
というより二値分類なので精度がわかれば十分です。
kerasのドキュメントにもこの組み合わせで書かれています。
fitメソッドで学習させてみる
機械学習と同様に学習させるときはfitを使います。
model.fit(x_train, y_train, epochs=10, batch_size=32)
引数にxとyのデータを与えてます。
epochsとは学習させる回数のことです。
batch_sizeとは入力データを何個に分けるかを指定しています。
この場合だと60,000個の入力データを32個ずつに分けて学習し、それを10回繰り返すという意味になります。
たぶん。
そうした場合の結果がこちら
Epoch 1/10 60000/60000 [==============================] - 6s 98us/step - loss: 0.0587 - acc: 0.9803 Epoch 2/10 60000/60000 [==============================] - 5s 83us/step - loss: 0.0344 - acc: 0.9887 Epoch 3/10 60000/60000 [==============================] - 5s 80us/step - loss: 0.0278 - acc: 0.9911 Epoch 4/10 60000/60000 [==============================] - 5s 86us/step - loss: 0.0237 - acc: 0.9925: 0s - loss: 0.023 Epoch 5/10 60000/60000 [==============================] - 5s 76us/step - loss: 0.0212 - acc: 0.9932 Epoch 6/10 60000/60000 [==============================] - 5s 80us/step - loss: 0.0194 - acc: 0.9939: 0s - loss: 0.0193 - ac Epoch 7/10 60000/60000 [==============================] - ETA: 0s - loss: 0.0179 - acc: 0.994 - 5s 80us/step - loss: 0.0180 - acc: 0.9943 Epoch 8/10 60000/60000 [==============================] - 5s 78us/step - loss: 0.0169 - acc: 0.9948 Epoch 9/10 60000/60000 [==============================] - 5s 77us/step - loss: 0.0160 - acc: 0.9952 Epoch 10/10 60000/60000 [==============================] - 4s 73us/step - loss: 0.0154 - acc: 0.9951
エポック1のときはloss:0.0587でacc:0.9803になっています。
正解率が98%、損失が5%です。なぜ100を超えるのか不明です。
エポック10になると正解率が99.5%になり、学習を重ねるごとに正解率があがっていることがわかります。
evaluateメソッドでテストしてみる
学習が終わったのでテストデータを使ってちゃんと正解をだせるかやってみます。
score = model.evaluate(x_test, y_test, verbose=1) print('Loss : ', score[0]) print('Accuracy : ', score[1])
scikit-learnのときはpredictでしたがkerasではevaluateを使います。
verboseはログを表示するかどうかの設定で、1はログを表示し、0は表示なしです。
10000/10000 [==============================] - 0s 49us/step Loss : 0.0220060412049 Accuracy : 0.993419999313
lossとaccuracyがリストで入っているので[0]と[1]でそれぞれ分けて表示しています。
modelの保存
学習には時間がかかります。
今回はエポック10でやりましたけど何百回って回したら相当な時間がかかります。
次回続きをやりたいときにまた何百回って学習させるのだと時間の無駄なので、モデルを保存しておきます。
続きからやるときはモデルを呼び出すことでevaluateから始められます。
model.save('mnist_test.h5') model = keras.models.load_model('mnist_test.h5')
saveメソッドでファイル名を指定して保存します。
kerasのドキュメントによればモデルは.h5という拡張子で保存するようです。
読み込むときはload_modelを使います。
学習の時間がとられなくて済むので便利です。