no-image

AlphaNyamo製作記 #01 MNIST

全然開発していなかったので

将棋プログラマーとか書きながら何もやっていなかったのでやることにします。

他の将棋プログラムと違ってディープラーニングを利用するAlphaNyamoは特に難しい事を考える必要はありません。

大事なのは訓練できる「環境」・「訓練用のデータ」・「訓練を何千回と繰り返せるリソース」・「ネットワーク構成や定義」の四つになります。

基本的にはAlphaZeroと同じ形式で進めようと思うので、Alpha-BetaサーチではなくMCTSを使おうと思います。

今回はチュートリアルとしてMNISTをやりたいと思います。MNISTというのは手書き文字認識で、画像認識で最もよく使われているチュートリアルですね。

一度TensorFlow単独で実行したことがあるのですが、今回はKerasを使ってどのくらいかんたんに実装できるか試してみることにしました。

MNISTってなんなのん

簡単に言うと画像を使った文字認識。「タッチパネルに書かれた文字が何かを当てる」みたいなやつ。MNISTの場合はもっとシンプルで書かれた数字を当てるっていうやつです。

なので答えは0~9の10通り。どんなクソコードを書いても10%くらいの正解率はあるわけです。これをニューラルネットワークを使ってどこまで上げていけるかという問題。

ちなみに今回作るネットワークモデルでは98.4%程度の正解率が見込めます

TensorFlowのセットアップ

前回の記事では省略してしまったので。

本来はGPUを使わないと学習部がとんでもなく遅くなるのですが、ここではCPUバージョンのセットアップについて解説します。

まずは仮想環境から整えます。

$ sudo apt-get install python3-pip python3-dev python-virtualenv # for Python 3.n

まずはpython3をインストールします。2を使っていると「お前は織田信長か?」という文句が飛んで来るので必ず3を使うようにしましょう。

$ mkdir alphanyamo

今回はalphanyamoというディレクトリを作成し、そこで作業します。

$ virtualenv --system-site-packages -p python3 targetDirectory # for Python 3.n

virtualenvをセットアップします。こうしないとすべてのプロジェクトでpython3が使われてしまうためです。targetDirectoryの引数は自分が作業したいディレクトリを指定します(今回の場合はalphanyamo)。ちなみに”./”を指定するとセグメンテーションエラーが発生しました。

$ cd alphanyamo

ディレクトリを移動します。”ls”でディレクトリ内を除くといろんなファイルが増えていることがわかります。

$ source bin/activate

アクティベートします。これで、現環境ではpythonを指定するとpython3が動くようになりました。

(alphanyamo)$ 

のように表示されれば仮想環境が正常に動作しています。

ようやくTensorflowのセットアップに入ります。

(alphanyamo)$ pip3 install --upgrade tensorflow # for Python 3.n

70MBくらいのデータをダウンロードするので、通信環境が良い状態で実行してください。

Successfully installed absl-py-0.1.10 astor-0.6.2 bleach-1.5.0 gast-0.2.0 grpcio-1.10.0 html5lib-0.9999999 markdown-2.6.11 numpy-1.14.1 protobuf-3.5.1 six-1.11.0 tensorboard-1.6.0 tensorflow-1.6.0 termcolor-1.1.0 werkzeug-0.14.1

のように表示されればインストール成功です。アンインストールする場合はディレクトリごと削除すれば大丈夫です。

Kerasのセットアップ

TensorFlowを便利に扱うためのライブラリ、Kerasをインストールします。

virtualenvが有効の状態でコマンドを叩きます。

(alphanyamo)$ pip3 install keras

MNISTのコード

ここのコードを参考にしてみました!というか、全く同じコードを使用しています!なので完全にここの丸コピでオッケーです!

このページはここのコードがどのようなことをしているのかを考えるページなので!!

最初のおまじない

# encoding: utf-8
from __future__ import print_function
import keras
from keras.datasets import mnist
from keras.models import Sequential
from keras.layers import Dense, Dropout
from keras.optimizers import RMSprop

最初の部分で使うモジュールを決定します。mnistのデータを拾ってくるのでそれらを書いたりいろいろ。他にどのようなデータが使えるかはまた調べられるっぽい。

バッチサイズの定義

書いておいて何なのですが、いまいち意味がわかっていません。epochsはエポックと呼ばれるもので、学習の繰り返し回数を意味する変数で良かったはず。

バッチサイズは勾配(バイアス)を更新するサンプル数のようです。指定しなければ32になります。num_classesは出力ですね。MNISTは手書きの数字を認識するためのチュートリアルなので出力は0/1ではなく「書かれている数字は0~9のどれか」を推定するので出力は10通りないといけません。

batch_size = 128
num_classes = 10
epochs = 20

データを読み込む

おまじないの部分で勝手にデータセットをダウンロードしてくれるので、あとはそれを読み込みます。

変数名mnistにデータセットが入っているのでそれを読み込むだけなのですが、たったこれだけのコードで読み込みができてしまうらしい。なんと便利…

(x_train, y_train), (x_test, y_test) = mnist.load_data()

データの整形

ダウンロードしてきたデータを学習しやすいように変換します。さて、このreshapeという関数なのですが、一体どんな働きをしてくれるんでしょうか(汗)

Tensorflowでやったときはデータセットが28×28ピクセルのデータで与えられるので、それを784×1のデータに変換したのですが…

調べてみるとreshapeの第一引数はデータ数のようです。なので60000枚と10000枚のデータをそれぞれ784×1の行列に変換するようです。

で、ここでデータセットを学習用とテスト用に分けます。

x_train = x_train.reshape(60000, 784)
x_test = x_test.reshape(10000, 784)
x_train /= 255
x_test /= 255

そしてそれらのデータは256値のグレースケール画像なので2値の白黒画像に変えてしまいます。こうすることで学習が効率的になります。この変換は画素値の値を255で割ることで実現できます。

データの変換

コメントによるとベクトルをバイナリの行列に変換するそうです。よくわからんのですが、そうしなきゃいけないっぽいのでします。

y_train = keras.utils.to_categorical(y_train, num_classes)
y_test = keras.utils.to_categorical(y_test, num_classes)

ネットワーク定義

どんなネットワークで学習するかを定義します。ネットワーク定義はケースバイケースでネットワークモデルと呼ばれることもありますね。

やる気になればいくらでも中間層は増やすことができるのですが、ここではシンプルに中間層が1層のモデルを考えたいと思います。

また、ニューラルネットワークとして多層パーセプトロン(MLP)を使用します。

model = Sequential()
model.add(Dense(512, activation='relu', input_shape=(784,)))
model.add(Dropout(0.2))
model.add(Dense(512, activation='relu'))
model.add(Dropout(0.2))
model.add(Dense(num_classes, activation='softmax'))

Sequential (系列)モデルは層を積み重ねたものらしい。レイヤーを積み重ねるのは.add()メソッドで簡単に行なえます。

そして重要なことは最初のレイヤーにだけは入力のshapeを与える必要があります。これはいくつかの指定方法がありますが、以下の2つのコードは全く等価になるようです。

model.add(Dense(32, input_shape=(784,)))
model.add(Dense(32, input_dim=784))

Denseっていうのは今のところよくわかりません。何が密なんだ?って感じです。

Dropoutは過学習を防ぐためのおまじないのようですね。こうすることでニューラルネットワークが訓練データにだけ正解率が上がって、実際のテストの結果と乖離してしまうような、訓練データ特化型のネットワークになることを防いでくれます。引数としては0以上1以下の浮動小数点であればなんでも良いようです。

activationっていうのについての解説は後述します。

ここまで

入力層には28×28の画像を784×1の二値行列に変換したものを与えました。

中間層には512×1のものを1層用意しました。

出力は10通りなので10×1の出力層を用意しました。

各層の間には活性化関数があるはずなので、ネットワーク構成を図にすると以下のような感じになります。

いやいや、それ計算できんやろ。(1,784)x(784,512)=(1,512)だから書き方逆でしょ!っていうツッコミはご遠慮ください!数字をわかりやすくしたかったので、でっかい方を先に書いちゃっただけなのです!!


それぞれの層を繋ぐシナプスは行列で表現できるはずで、それぞれ784×512の行列と512×10の行列になります。これは指定しなくてもkerasが自動で判定してくれるのでわざわざコードに書く必要はありません。

コーディングする人が考えなければいけないのは、各層を繋ぐ活性化関数にどのようなものを利用するかということです。ただし、重要な事があって活性化関数には非線形なものを選ばなくてはいけません

というのは、仮に線形な活性化関数g(x), h(x)を選んでしまうと、層をいくら増やしたとしてもその線形性からf(x)=g(h(x))なる線形な関数f(x)が存在してしまうためです。

ということは上手くバイアスを選べば「最初からそんなに層いらんかったやん。活性化関数も一つでいいじゃん」って事になってしまうわけです。

活性化関数

じゃあどんな活性化関数ならいいんだ?という話になります。

よく使われているのがReLuと呼ばれる関数(赤い曲線)。ランプ関数って言うらしいのですが…

ちなみに青い方はソフトプラスと呼ばれる活性化関数です。

2011年、Xavier Glorot らは隠れ層の活性化関数として max(0, x) を使った方が tanh やソフトプラスよりも改善するということを発表した[4]。一般的にはこれはランプ関数と呼ばれるが、ニューラルネットワークの世界では ReLU (英: Rectified Linear Unit, Rectifier, 正規化線形関数)と呼ばれる。Yann LeCun やジェフリー・ヒントンらが雑誌ネイチャーに書いた論文では、2015年5月現在これが最善であるとしている[1]。発想としては区分線形関数を使った1次スプライン補間である。線形補間を参照。

とにかく、偉い人たちが(三年前とはいえ)これが最善と言っているのだからこれを使うしかない。

つまり、activationとは活性化関数にReLuを用いるという意味だったわけです。

ちなみにKerasで利用可能な活性化関数はドキュメントを参照。

コンパイル

実はもうほとんどコーディングは終わっていて、最後はネットワーク定義を確定させるだけです。

model.summary()
model.compile(loss='categorical_crossentropy',
optimizer=RMSprop(),
metrics=['accuracy'])

最初の一行は例のごとくおまじない。

lossは損失関数のこと。利用可能な損失関数の種類についてはドキュメントを参照(のちのちまとめます)

今回はcategorical_crossentropyを使用します。これはマルチクラスloglessとして知られているそうな。

この目的関数を使うには,ラベルがバイナリ配列であり,その形状が(nb_samples, nb_classes)であることが必要です。

optimizerは最適アルゴリズムのこと。利用可能なoptimizerについてはやはりドキュメントを参照(のちのちまとめます)

RMSPropオプティマイザは基本的にはデフォルトパラメータで使用すれば良いとのこと。

そしてmetricsはネットワークの性能評価です。どんな値をもってしてネットワークの優劣をつけるかというところです。今回は文字認識なので、単純に解率が高いネットワークを良いネットワークとしていいはずです。なのでパラメータには正解率であるaccuracyを設定します。

訓練・テスト

最後に訓練を行い、テスト結果を表示する部分を作成します。

history = model.fit(x_train, y_train,
batch_size=batch_size,
epochs=epochs,
verbose=1,
validation_data=(x_test, y_test))
score = model.evaluate(x_test, y_test, verbose=0)
print('Test loss:', score[0])
print('Test accuracy:', score[1])

fitというやつが、訓練するパラメータをセットする箇所です。

バッチサイズ、エポック、テストデータ等を設定します。なんだかややこしそうなので、のちのちしっかりとまとめたいところ。

最後にprintで結果を出力して終了します。

まとめ

家にあったサーバで動かしてみたところ、3分ほどで学習が終了しました。

60000/60000 [==============================] - 10s 162us/step - loss: 0.0180 - acc: 0.9953 - val_loss: 0.1073 - val_acc: 0.9822
Test loss: 0.107272329241
Test accuracy: 0.9822

正解率は98.22%で、コードの解説にあったようにおよそ98.4%の正解率が達成できました。
これを畳み込みニューラルネットワーク(CNN)を使えば12epochsでおよそ99.25%の正解率が見込めるとかなんとか。ただしGRID K520っていうGPUを使用して1epochsに16秒くらいかかるらしいのでCPUだとすっごく時間がかかりそう。

この辺は畳み込み積分がCPUが苦手なのでしょーがないのかなといった気もします。

転移畳み込みニューラルネットワークというネットワークを使えば更に正解率を上げることができて、たったの5epochsで99.8%くらいまで正解率をあげられるらしいです。(はえーすっごい)

今回、Kerasを初めて触ってみて、TensorFlow単独よりもコーディングが非常に簡単だと感じました。コードを書くだけなら30分もあれば可能で、それで正解率98%を超えてくるとなると

本当にここ数年の技術と理論の目覚ましい進歩を感じずにはいられませんね。

次は別のネットワーク構成でMNISTにチャレンジしたいと思います。

以上です。