Going Faraway

渡辺遼遠の雑記帳。技術ネタと読んだ本の紹介。

読書メモ:Deep Learning with Pythonを読む

以前、軽く流し読んだフランソワ・ショレ氏の「Deep Learning with Python」ですが、ちゃんとサンプルを動かしながら勉強していこうと思います。

なお、この記事は機械学習素人が勉強の過程をメモするためのであり、内容の正しさについては保証しません。(ちゃんと知りたい人は上掲書を読みましょう)

2.1. はじめてのニューラルネット

最初は機械学習のサンプルとしてよくあるMNISTの分類を行う。MNISTは、0〜9までの手書き文字の画像で、訓練データが6万件、テストデータが1万件存在する。
f:id:liaoyuan:20180118211402p:plain
図:MNISTのサンプル

なお、MNISTのデータセットへのインターフェイスはKeras自体に付属しているので、以下のコードでデータセットを読み込める。(初回ロード時にはデータをダウンロードしに行くので、多少時間が必要。)

from keras.datasets import mnist

(train_images, train_labels), (test_images, test_labels) = mnist.load_data()
original_test_labels = test_labels
original_test_images = test_images

ニューラルネットにこのデータを入力して学習させる前に、まずはニューラルネット(tensorflow) が受け付けられる形式にデータを整形する必要がある。

train_images = train_images.reshape((60000, 28 * 28))
train_images = train_images.astype('float32') / 255

test_images = test_images.reshape((10000, 28 * 28))
test_images = test_images.astype('float32') / 255

train_labels = to_categorical(train_labels)
test_labels = to_categorical(test_labels)

MNISTのデータは28x28ピクセルを表現した二次元配列であり、各ピクセルは0〜255 (グレースケール) の値を取る。各データを一次元の配列で0〜1までの実数(浮動小数点数) に変更する

データのラベルは0〜9までの整数で表現されているので、ワンホットベクトル形式に変更する。ワンホットベクトルとは、以下の形式の表現。

0 => [1,0,0,0,0,0,0,0,0,0] 
1 => [0,1,0,0,0,0,0,0,0,0]
2 => [0,0,1,0,0,0,0,0,0,0]
# 10個の要素を持つリストで、数字の「2」であれば3番目の要素だけが1で他は0

この変換をしてくれるkerasの関数がある。(keras.utils.to_categorical)

カテゴリをワンホットベクトル形式で表現する必要性については、このサイトの説明が分かりやすかった。(多分、線形演算でカテゴリの分類を扱うための技法ということらしい。) この程度の変換は良きにはからって勝手にやってくれと思わんでもない。

ニューラルネットのモデル定義

次から、ニューラルネットのモデルを定義する。

models.Sequencial は、その名の通り「順番に」 ニューラルネットの層を重ねるモデルである。Dense というレイヤーは、密結合(全結合)ネットワークで、前のノードとの間に全てエッジが引かれる。最初の引数はその層のノード数を表し、activationは活性関数を表す。

network = models.Sequential()
network.add(layers.Dense(512, activation='relu', input_shape=(28 * 28,)))
network.add(layers.Dense(10, activation='softmax'))

最初はニューラルネットのモデルがあまり分からなかったので図にしてみた。こんな感じだと思われる。

f:id:liaoyuan:20180118212624p:plain

Layerはその名の通り「層」を表すものだけど、むしろ層と層を繋ぐ方法を指定するものだとイメージしたほうが分かりやすいかもしれない。

学習させる前に、compile関数でモデルの準備を行う。このとき、最適化関数、損失関数、メトリクスを定義する。それぞれの役割と詳細はここでは省略する。

network.compile(optimizer='rmsprop',
                loss='categorical_crossentropy',
                metrics=['accuracy'])

学習実行

訓練データに対して、学習を10回実行する。epochsで訓練回数を指定。batch_size は訓練バッチのサイズを指定するもので、ここで指定したサンプル数ごとに勾配の更新を行う。今回はCPUのみで学習を実行したが、この程度であれば数分もかからず学習が完了した。

network.fit(train_images, train_labels, epochs=10, batch_size=128)

そして、evaluate関数でテストデータを用いて訓練したモデルを評価する。テストデータに対する正答率は97.9%くらい。

test_loss, test_acc = network.evaluate(test_images, test_labels)
print(test_acc) # => 0.9793

 

予測と結果の表示

書籍のサンプルとしてはここまでだけど、単に学習させるだけで終わっても面白くないので、簡単に結果を確認してみたい。

まずは結果概要を確認するために混同行列を表示する。縦の列が本来の値で、横の行がニューラルネットによる推測値である。対角線上の値が正解数を意味する。

[[ 970    1    0    1    1    0    4    1    2    0]
 [   0 1123    4    1    0    1    3    1    2    0]
 [   1    0 1013    1    2    0    3    7    5    0]
 [   0    0    4  990    0    4    2    3    3    4]
 [   0    0    2    0  964    0    8    2    0    6]
 [   2    0    0    7    0  874    5    1    1    2]
 [   3    2    1    1    1    2  948    0    0    0]
 [   1    3    9    1    0    0    0 1003    3    8]
 [   1    0    3    3    3    2    2    2  953    5]
 [   0    2    0    1    8    3    1    5    3  986]]

厳密に統計的な分析をしたわけではないけど、6, 8, 9などの丸を含む数字は苦手なのだろうか、という印象。

更に、誤った予測をされた数字の画像を出力させたいので、テストデータに対して予測を行う。ここで使うのは predict_classes メソッドで、テストイメージのNumpy配列を受け取り、クラス(カテゴリ)の予測結果、つまり0〜9の値が入った配列を返す。

predicted_classes = network.predict_classes(test_images)

ニューラルネットの予測のうち、誤答だったものを画像で100件出力してみる。左上の青文字が正解のラベルで、赤字がニューラルネットの予測値 (誤答)

f:id:liaoyuan:20180118214029p:plain

0と6の混同など、文字が乱雑すぎて人間でも同じ間違いをするだろうという感じの文字もあるし、2と8のように何故こんな不可解な間違いをするんだっていう予測もある。今回は2次元の画像データを1次元のベクトルに変換しており、縦方向の形状を活用できる処理をしていないからこうなるのでしょう。(ちなみに、本書ではCNNは5章で扱われる予定)

一応今回のコードは以下のgistに掲載した。
"Deep Learning with Python" のMNISTサンプル · GitHub

今回は以上です。