僕の世界観を変えてみる

文系男子が趣味でプログラミングを勉強していくブログです。他にも日常で起きたどうでもいいことや愚痴を書いていきたいです。座右の銘は和を以て貴しとなすです。仲良くやろうよ。

【ディープラーニング】文系男子がpythonとkerasを使ってMNISTをやってみた

f:id:htmllifehack:20180817230635j:plain

ディープラーニングって難しいイメージがあったので手を出さなかったんですけど、

なんとなくディープラーニングのフレームワーク調べてたら思ってたよりコードが少なくて

あれこれって意外といけるんじゃね?って軽いノリで初めてみました。

やったことは大したことないんですがすごく楽しいです。

転職に活かせたらいいなと思い書きます。



ところで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])

f:id:htmllifehack:20180819231708p:plain

おどろおどろしい色の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')

f:id:htmllifehack:20180819231932p:plain

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関数で活性化させて終了です。

図にするとこんなイメージ。

f:id:htmllifehack:20180820222713p:plain

なんでDense32にするのかわからないんですけど、確か32とか128とか決まった数字があったはずです。

とりあえずわからなければこの数字を使って変換するといいいよって何かで読みました。

kerasのドキュメントだったか書籍だったか、うーん思い出せない。

Home - Keras Documentation

compileで学習の条件を指定する

最後にどのように学習させるのか指定します。

model.compile(optimizer='rmsprop',
                            loss='binary_crossentropy',
                            metrics=['accuracy'])

optimizerは最適化の意味で引数に勾配法を指定します。

rmspropのほかにSGDなどいくつか種類がありますが今回は二値分類で使われるrmspropにしました。

確率的勾配降下法はうっすらわかりますが計算式などはわからないので詳しく知りたい方は各々調べてください。

最適化 - Keras Documentation

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を使います。

学習の時間がとられなくて済むので便利です。

コード

まとめ

てなわけで今回はディープラーニング入門としてkerasを使ってmnistをやってみました。

kerasを使うと簡単にディープラーニングができることがわかりました。

結構webでkerasの使い方やmnistはでていますけどやっぱり書籍を読んだほうがいいですね。

予習しておくのてしないのじゃ全然違います。

ちなみに僕は簡単な書籍を読んで予習したあとにUdemyで動画を見ながら手を動かしています。

¥2,000しないくらいで購入できるのでおすすめですよ!