ディープラーニングをKerasというフレームワーク上で行う
はじめに
機械学習、特にディープラーニングが近頃(といってもだいぶ前の話になりますが)盛んになっています。CaffeやChainerといったフレームワークもありますが、特にGoogleがTensorflowでtensorboardと呼ばれる簡単に使える可視化基盤を提供して有名になったのを機に、Tensorflowで機械学習を始めてみたという方も少なくないと思います。
しかしTensorflowは低レイヤーな行列演算、ベクトル演算を記述するには適していますが、機械学習モデルの記述という面では書きにくいことも確かで、何かが動いていることは確かめられたけれど自分で何かをするには敷居が高かったです。例えば、Tensorflowでsoftmax層を定義するには、以下のコードを書く必要があります。
# softmax, i.e. softmax(WX + b) with tf.variable_scope('softmax_linear') as scope: weights = _variable_with_weight_decay('weights', [192, NUM_CLASSES], stddev=1/192.0, wd=0.0) biases = _variable_on_cpu('biases', [NUM_CLASSES], tf.constant_initializer(0.0)) softmax_linear = tf.add(tf.matmul(local4, weights), biases, name=scope.name) _activation_summary(softmax_linear)
(https://github.com/tensorflow/tensorflow/blob/r0.7/tensorflow/models/image/cifar10/cifar10.pyから引用)
もちろん機械学習の研究をする場合や、細かくモデルを定義してチューニングをゴリゴリしたい場合には、これぐらい表現力が高い方がよいかもしれません。しかし数学が苦手だったり、趣味レベルで少し触ってみたいだけだったり、短い時間である程度の成果を上げたかったりする人にとっては、下のようにモデルに追加する層を列挙する形で書けると便利ですよね。
model.add(Activation('softmax'))
ここでKeras(Keras Documentation)というフレームワークを導入します。これは簡単な記法で(短いコードで)機械学習のモデルを書き下せる上、すぐに類似した問題に応用可能なsampleがついています。またバックエンドにTheanoの他Tensorflowを適用できるため、Tensorflowとほとんど変わらない速度で、しかも簡易な記法で記述したモデルを試すことが可能です。ここではKerasを用いてCNNで簡単なカラー画像の分類モデルを組み立て、学習させ、実際に予測をするサービスを作ろうとする上のtipsをメモしておきます。先に断っておきますが、私は機械学習の専門家でも、またそれを専門に学んでいるわけでもないため、ディープラーニングに対する記述には正しくない点も多く含まれていることをご了承下さい。
モデルをどう構築するか
従来の機械学習や統計、例えばSVMや回帰分析では、特徴量は自分で抽出しなければなりませんでした。しかしディープラーニングでは、その特徴量抽出は隠れ層の重みの学習に置き換えられるので、適切な学習データを用意することで特徴量自体の検出を機械に委ねることが可能になりました。従って必要となるのはどういったモデルを作るかということと、どう入力画像を扱うか、さらに言えば解きたい問題をどのように機械学習の枠組みに落とし込むかという3つになるでしょう。
Tensorflowよりモデルの構築は容易になったとはいえ、私のような初学者はいったいどのようにモデルを構築すればいいのか検討もつきませんが、画像を扱うにはいくつかの方法があって、MNISTとよばれるグレースケールの手書き数字認識では(MNIST handwritten digit database, Yann LeCun, Corinna Cortes and Chris Burges)多層パーセプトロンと畳み込みニューラルネットワークのサンプルがありました。kerasのexamplesフォルダにあるmnist_cnn.py
とmnist_mlp.py
を比べると分かりますが、CNNではモデルの中に
model.add(Convolution2D(nb_filters, nb_conv, nb_conv, border_mode='valid', input_shape=(1, img_rows, img_cols))) model.add(Activation('relu')) model.add(Convolution2D(nb_filters, nb_conv, nb_conv)) model.add(Activation('relu')) model.add(MaxPooling2D(pool_size=(nb_pool, nb_pool))) model.add(Dropout(0.25))
このような畳み込み層(CNNのCは畳み込み:Convolutionalのcです)が入力と多層パーセプトロンの間に組み込まれています。それぞれの意味としてはConvolution2Dでが画像にフィルタを掛けて特徴量を抽出し、Max Pooling層が最大の値をサンプリングすることで、その値の位置感度を下げます。そして最後のDropout層によって出力をここでは4分の1にして過学習を防いでいるといった感じでしょうか。またノードからの出力の総和を計算する際、reluと呼ばれる活性化関数が用いられていますが、これはmax(0, x)で定義される関数で、負の入力に対しては0を返しますが、正の入力に対しては線形の出力が返されます。これによってある程度疎な行列が得られるので行列演算にも都合がよく隠れ層でよく使われる一方で、最終的な出力を得る際には表現力が貧弱だということもあり、softmax関数が使っているようです。
実際にサンプルのコメントにもあるように精度はCNNの方が高いですし、Cifar10(CIFAR-10 and CIFAR-100 datasets)とよばれるカラー画像6万枚の10分類のサンプルコードはCNN版しかありません。TensorflowでもCNNによる実装がチュートリアルにあったはずです。恐らくこのようなフルカラーの画像を扱う上では、計算コストはかかりますが畳み込み層を入れたほうがよいのでしょう。更にCNNのよいところは、全層結合のモデルで画像認識させる際に必要となる初期化処理(自己符号化器などを用いて、前もって適当な重みをそれぞれのノードにかけておかなければならない)をせずに学習させることができるところにもあります。
Kerasではこういった一直線のSequentialなモデルは、層を列挙していくだけで簡単に記述することができます。CNNを用いることとした上で、サンプルのモデルを使って数字をいろいろ変えてみてチューニングするといったことをしました。より高度なモデルを作るとしたら、論文を読むとか、強い人のものを参考にするという方法もあります。
例えばこのように公開されているKaggleで用いられたNeural Network Configurationsを参考にして層を増やすといったことは考えられます。ただ一方で、モデルを複雑にしたところでそもそも学習させる画像データが少ない場合、計算時間ばかりかかって満足できる結果を得ることにはつながりませんでした。
入力の扱い
機械学習に食わせるためには、入力を区間[0,1]のn次元配列(テンソル?)にする必要があります。例えば文章であれば文を単語に分けて、同じ単語に対して一意の数字を割り振ったベクトルとして表現できます。デジタル画像では、BMP形式だとわかりやすいですが1ピクセルに対してグレースケールであれば1つの値、カラー画像であればRGBの3つの値で表現することがすでにできているので、それをそのまま入力とすることを考えます。(ただし0〜1の範囲に落とし込むため、255で割る必要があります。逆に255で2回割ってしまうと全く予測結果がおかしなことになるので注意)
Kerasの例をみると、MNISTのようなグレースケールのものは1枚の画像は縦☓横の2次元配列で表されて、それ自体が1つの配列にすべて格納されている形で学習データを構成するので実質は3次元配列が入力全体となります。cifar10ではカラー画像なので1枚の画像は縦x横xRGBの3次元配列で表されて、それを1つの配列に全部格納するので実質4次元配列が入力の形となります。
分類結果は5値分類であれば0,1,2,3,4のように表現されますが、ここではバイナリ配列として扱う([1,0,0,0,0],[0,1,0,0,0],...のように)必要があります。これは関数が用意されているのでそのまま使います。
Y_train = np_utils.to_categorical(y_train, nb_classes) Y_test = np_utils.to_categorical(y_test, nb_classes)
ではcifar10の場合に入力配列をどう生成するかということですが、Tensorflowではバイナリファイルから読み込むスタイルでした。このバイナリファイルの構造は簡単で1行に1画像であり、最初の1バイトが分類値で、次にRGB値が1024バイトごと入っているものでしたが、KerasではPythonのcPickle化されたものを入力として使っていました。サンプルコードをそのまま流用するためには画像をcPickleに落とし込まなければならないのですが、好きな画像群からダンプする場合は下の記事のコードのインスタンス変数名を少し変更することでカラー画像の読み込みを作ることができました。
私はこの時に、上下位置を少しずつずらした画像を作って学習効率の増強に努めました。KerasにもImageDataGenerator
というのがあって画像を適切に加工してくれるようなのですが、私が試した時はデータ数が少ないためもあったのか、うまくいきませんでした。(何度学習させてもlossが減らない、学習中に明らかにおかしいlossの変動がみられるなど)
また入力画像として、あまり関連性のない画像を分類問題として与えるとうまくいかないことが多かったです。例えば画像がアニメキャラのものか実在する人間のものかを二値分類するのはそれなりに精度がよかったのですが、あるアニメの登場人物と別のアニメの登場人物をそれぞれ集合として与えた時、登場人物1人につき1枚ずつの写真が大量にあったとしても、うまくいきませんでした。
予測するには
Tensorflowではセッションに対してrun
を呼ぶとき、学習データと分類値を入れれば学習になるし、feed_dict=
にテストデータを与えれば分類が受け取れるということのようだったのですが、Kerasではpredict
という専用の関数があります。これに学習データを投げると、配列で受け取ることができます。またpredict_classes
を呼ぶと、分類結果のラベルだけを表示してくれます。
学習させた後のモデルを最後にこんな感じのコードを追加すると、architecture.json
にはモデル構造がJSON形式で、weights.h5
にはそれぞれのノードの重みが保存されます。
model_json = model.to_json() open('architecture.json', 'w').write(model_json) model.save_weights('weights.h5', overwrite=True)
予測させたいときには
def load_model(model_def_fname, model_weight_fname): model = model_from_json(open(model_def_fname).read()) model.load_weights(model_weight_fname)
こんな感じで呼べるので、これを再びコンパイルしたものを使ってpredict
すると、1回の学習データをもとにさまざまなデータに対して予測をすることが簡単にできます。
ところでこれはpythonというかnumpyのtipsなのですが、多値分類は結果の値が指数表現になることがあります。こうなってしまったとき小数点以下の値が見にくいときは、
import numpy as np np.set_printoptions(suppress=True) # 小数表現にする np.set_printoptions(precision=3) # 小数点以下3桁まで表示させるようにする
と書くとprint
したときにわかりやすくなるでしょう。
サービス化する
Kerasを使った予測サービスをWeb上で展開しようというとき、金があればいくらでも方法はあるのですが、個人レベルで且つ無料で済ませたい(且つアクセスを期待しない)場合はどうするかという問題があって、検討した結果を記述します。
ここでやりたいのは学習済みのarchitecture.json
、weights.h5
を置き、解析用のコードとwebサーバーを立てておいて、そこに入力のベクトルをPOSTすると予測結果を返すものを作るということです。TensorflowはPythonなので、Webサーバーは同じくPythonのFlaskとかで記述すると書きやすいです。
heroku
herokuは18時間までならタダで動かせるのでこれを使うのが一番王道だと思いますが、dockerコンテナを立てるにはクレジットカードが必要です。そうしない場合はKerasのバックエンドとなるTensorflow、Theanoをどちらもheroku上にインストールすることができませんでした。Theanoをrequirements.txt
に記述してpush時にpipでインストールさせようとしても、まず最新バージョンが落とせない。それにビルドの途中で
Problem occurred during compilation with the command line below: /usr/bin/g++ -shared -g -march=core-avx-i -mcx16 -msahf -mno-movbe -maes -mpclmul -mpopcnt -mno-abm -mno-lwp -mno-fma -mno-fma4 -mno-xop -mno-bmi -mno-bmi2 -mno-tbm -mavx -mno-avx2 -msse4.2 -msse4.1 -mno-lzcnt -mno-rtm -mno-hle -mrdrnd -mf16c -mfsgsbase -mno-rdseed -mno-prfchw -mno-adx -mfxsr -mxsave -mxsaveopt --param l1-cache-size=32 --param l1-cache-line-size=64 --param l2-cache-size=25600 -mtune=core-avx-i -DNPY_NO_DEPRECATED_API=NPY_1_7_API_VERSION -m64 -fPIC -I/app/.heroku/python/lib/python2.7/site-packages/numpy/core/include -I/app/.heroku/python/include/python2.7 -I/app/.heroku/python/lib/python2.7/site-packages/theano/gof -fvisibility=hidden -o /app/.theano/compiledir_Linux-3.13--generic-x86_64-with-debian-jessie-sid-x86_64-2.7.7-64/lazylinker_ext/lazylinker_ext.so /app/.theano/compiledir_Linux-3.13--generic-x86_64-with-debian-jessie-sid-x86_64-2.7.7-64/lazylinker_ext/mod.cpp -L/app/.heroku/python/lib -lpython2.7 /usr/bin/ld: /app/.heroku/python/lib/libpython2.7.a(abstract.o): relocation R_X86_64_32S against `_Py_NotImplementedStruct' can not be used when making a shared object; recompile with -fPIC /app/.heroku/python/lib/libpython2.7.a: error adding symbols: Bad value collect2: error: ld returned 1 exit status
というエラーメッセージが出て駄目でした。Tensorflowは下のページを見る限り動かなさそうでしたし、herokuを予測サーバーに使うのは断念しました。
Openshift Online
これもしばしば無料でWebサーバー立てる際に使うのですが、今回は試していないです。
Arukas.io
さくらインターネットが出しているDockerホスティングサービス。つい最近公開されましたが、Dockerイメージを今の所制約なしにホスティングしてくれるのはかなりありがたいです。TheanoやTensoflowのインストールでこけるぐらいなら、全部入りのDockerイメージをアップロードしてしまうほうがはるかに楽で、Dockerfileにkeras + tensorflow + flaskを入れる設定を書いてDocker hubにアップロードし、それをArukas.ioにデプロイするというフローで拍子抜けするほど簡単に予測サーバーを立てることができました。メモリは512MBに設定していますが予測する分ならほとんど手元のマシンと変わらない速度で動いている感があります。
続き
Kerasを用いて様々な処理を施した所感は以下になります。
Refernces
Neural Networks using Keras on Rescale | Rescale
www.slideshare.net