(左:Keras、右:MXnet)
Kaggle Masterの間ではMXnetよりさらに人気なDeep Learningフレームワークというかラッパーが、@fchollet氏の手によるKeras。
結構苦心したのですが、ようやく手元のPython環境で走るようになったので、試してみました。なおKerasの概要と全体像についてはid:aidiaryさんが詳細な解説を書いて下さっているので、そちらの方を是非お読み下さい。
一応こちらでも簡単に解説しておくと、KerasはバックエンドにTensorFlowもしくはTheanoの好きな方を選べば良い、というDeep Learningフレームワークのラッパーです。開発者の@fchollet氏とちょっと個人的にやり取りしたことがあるんですが、彼が語っていたのは「Theanoはもはや煩雑過ぎるし、TensorFlowは使いやすいが計算グラフの理解のところが難しくて万人向けではない」というコメント。「出来るだけ多くの人にDeep Learningに触れてもらいたい」というのがその開発理念だそうです。
ちなみに、彼がTwitterで呟いていたのをチラッとご覧になった方もいるかもしれませんが、Kerasドキュメントの日本語化プロジェクトが目下進行中のようです*1。MXnetが英語以外では中国語ドキュメントのみが整備されていて中国語圏で人気が高いことを鑑みるに、いずれKerasも日本人に最も馴染み深いDeep Learningフレームワークの一つになるかもしれませんね。
インストール
先にTensorFlowを入れておく必要があります。
詳しくはTensorFlowのドキュメントを見てもらいたいのですが、環境によって入れ方が結構異なる点に要注意。また既存のNumPyが原因でコケるケースがあるので、その場合の対処法もチェックしておきましょう。一応、ドキュメントに従うとPython 2系の場合は
# Ubuntu/Linux 64-bit $ sudo apt-get install python-pip python-dev # Mac OS X $ sudo easy_install pip $ sudo easy_install --upgrade six
の後に
# Ubuntu/Linux 64-bit, CPU only, Python 2.7 $ export TF_BINARY_URL=https://storage.googleapis.com/tensorflow/linux/cpu/tensorflow-0.9.0rc0-cp27-none-linux_x86_64.whl # Ubuntu/Linux 64-bit, GPU enabled, Python 2.7 # Requires CUDA toolkit 7.5 and CuDNN v4. For other versions, see "Install from sources" below. $ export TF_BINARY_URL=https://storage.googleapis.com/tensorflow/linux/gpu/tensorflow-0.9.0rc0-cp27-none-linux_x86_64.whl # Mac OS X, CPU only, Python 2.7: $ export TF_BINARY_URL=https://storage.googleapis.com/tensorflow/mac/tensorflow-0.9.0rc0-py2-none-any.whl
として
# Python 2 $ sudo pip install --upgrade $TF_BINARY_URL
で入る、ということになっています。他にもドキュメントにはVirtualEnv, Anaconda, Dockerで入れる方法が書いてありますがここでは割愛します。
また、別の問題として事前にcondaでNumPyを入れてある場合はこれがコケてしまうというケースが最近あるようです*2。この場合は仕方ないので、NumPyだけ別に入れ直す必要がありそうです。
他にもNumPy周りでコケるケースは結構あるようで、僕が実際に見た範囲だとこういうエラーもあるみたいです*3。リンク先の通りにすれば解決する。。。と思いますが、僕の環境にはいくつか制約があってまだ試してませんorz
その後でKerasを入れます。インストール方法は普通にKerasのドキュメントに書いてある通りで、ソースをgit cloneしてきてから
$ sudo python setup.py install
でも良いですし、普通にpipで
sudo pip install keras
でも入ります。入ったら今度はTensorFlowバックエンドで動かすことを想定しているので、Kerasのドキュメントに従って設定ファイル
~/.keras/keras.json
を以下のように書き換えます(一度起動すると生成されるJSONファイルだが面倒なら最初からエディタで書いてしまっても良い)。
# before {"epsilon": 1e-07, "floatx": "float32", "backend": "theano"} # after {"epsilon": 1e-07, "floatx": "float32", "backend": "tensorflow"}
これで一応Kerasが動くようになるはずです。
KerasのCNNでMNIST短縮版の分類をやってみる
基本的にはKerasのドキュメントに出ているサンプルコードをほぼそのまま当てはめただけです。が、元のコードだと若干パフォーマンスが下がるので、僕の方で適当にチューニングし直しています。MNIST短縮版のCSVファイル2つは以下の僕のGitHubから取ってきてください。
一応、TensorFlowバックエンドかどうかを確認しておきましょう。
import keras
Using TensorFlow backend. Using TensorFlow backend.
その後は以下のように組めば、基本的には走るはずです。
from __future__ import print_function from keras.layers.convolutional import Convolution2D from keras.layers.convolutional import MaxPooling2D from keras.layers.core import Activation from keras.layers.core import Dense from keras.layers.core import Dropout from keras.layers.core import Flatten from keras.models import Sequential from keras.utils import np_utils import numpy from pandas import DataFrame from pandas import read_csv data_train = read_csv("short_prac_train.csv") data_test = read_csv("short_prac_test.csv") batch_size = 100 nb_classes = 10 nb_epoch = 10 # input image dimensions img_rows, img_cols = 28, 28 # number of convolutional filters to use nb_filters = 20 # size of pooling area for max pooling nb_pool = 2 # convolution kernel size nb_conv = 5 # the data, shuffled and split between tran and test sets x_train = numpy.asarray([data_train.ix[i][1:] for i in range(len(data_train))]) x_test = numpy.asarray([data_test.ix[i][1:] for i in range(len(data_test))]) y_train = data_train['label'] y_test = data_test['label'] x_train = x_train.reshape(x_train.shape[0], img_rows, img_cols, 1) x_test = x_test.reshape(x_test.shape[0], img_rows, img_cols, 1) x_train = x_train.astype("float32") x_test = x_test.astype("float32") x_train /= 255 x_test /= 255 print("x_train shape:", x_train.shape) print(x_train.shape[0], "train samples") print(x_test.shape[0], "test samples") # convert class vectors to binary class matrices y_train = np_utils.to_categorical(y_train, nb_classes) y_test = np_utils.to_categorical(y_test, nb_classes) model = Sequential() model.add(Convolution2D(nb_filters, nb_conv, nb_conv, border_mode="valid", input_shape=(img_rows, img_cols, 1))) 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.5)) model.add(Flatten()) model.add(Dense(128)) model.add(Activation("relu")) model.add(Dropout(0.5)) model.add(Dense(nb_classes)) model.add(Activation("softmax")) model.compile(loss="categorical_crossentropy", optimizer="adadelta", metrics=["accuracy"]) model.fit(x_train, y_train, batch_size=batch_size, nb_epoch=nb_epoch, verbose=1, validation_data=(x_test, y_test)) score = model.evaluate(x_test, y_test, show_accuracy=True, verbose=0) print("Test score:", score[0]) print("Test accuracy:", score[1])
x_train shape: (5000, 28, 28, 1) 5000 train samples 1000 test samples Train on 5000 samples, validate on 1000 samples Epoch 1/10 5000/5000 [==============================] - 5s - loss: 0.9321 - acc: 0.6978 - val_loss: 0.1817 - val_acc: 0.9340 # ... # Epoch 10/10 5000/5000 [==============================] - 13s - loss: 0.0895 - acc: 0.9710 - val_loss: 0.0468 - val_acc: 0.9890 Test score: 0.0467970174281 Test accuracy: 0.989
ということで、MXnetがほぼ同じパラメータ設定で出したTest accuracy 0.987(後述)とほぼ同じ結果になりました。
MXnetのCNNでMNIST短縮版の分類をやってみる
以前の過去記事で既にお見せした通りですが、一応再掲しておきます。
# Data preparation > train<-read.csv('https://github.com/ozt-ca/tjo.hatenablog.samples/raw/master/r_samples/public_lib/jp/mnist_reproduced/short_prac_train.csv') > test<-read.csv('https://github.com/ozt-ca/tjo.hatenablog.samples/raw/master/r_samples/public_lib/jp/mnist_reproduced/short_prac_test.csv') > train<-data.matrix(train) > test<-data.matrix(test) > train.x<-train[,-1] > train.y<-train[,1] > train.x<-t(train.x/255) > test<-test[,-1] > test<-t(test/255) # Model > data <- mx.symbol.Variable("data") > devices<-mx.cpu() > # first conv > conv1 <- mx.symbol.Convolution(data=data, kernel=c(5,5), num_filter=20) > tanh1 <- mx.symbol.Activation(data=conv1, act_type="relu") > pool1 <- mx.symbol.Pooling(data=tanh1, pool_type="max", + kernel=c(2,2), stride=c(2,2)) > drop1 <- mx.symbol.Dropout(data=pool1,p=0.5) > # second conv > conv2 <- mx.symbol.Convolution(data=drop1, kernel=c(5,5), num_filter=50) > tanh2 <- mx.symbol.Activation(data=conv2, act_type="relu") > pool2 <- mx.symbol.Pooling(data=tanh2, pool_type="max", + kernel=c(2,2), stride=c(2,2)) > drop2 <- mx.symbol.Dropout(data=pool2,p=0.5) > # first fullc > flatten <- mx.symbol.Flatten(data=drop2) > fc1 <- mx.symbol.FullyConnected(data=flatten, num_hidden=500) > tanh4 <- mx.symbol.Activation(data=fc1, act_type="relu") > drop4 <- mx.symbol.Dropout(data=tanh4,p=0.5) > # second fullc > fc2 <- mx.symbol.FullyConnected(data=drop4, num_hidden=10) > # loss > lenet <- mx.symbol.SoftmaxOutput(data=fc2) > train.array <- train.x > dim(train.array) <- c(28, 28, 1, ncol(train.x)) > test.array <- test > dim(test.array) <- c(28, 28, 1, ncol(test)) > mx.set.seed(0) > tic <- proc.time() > model <- mx.model.FeedForward.create(lenet, X=train.array, y=train.y, + ctx=devices, num.round=60, array.batch.size=100, + learning.rate=0.05, momentum=0.9, wd=0.00001, + eval.metric=mx.metric.accuracy, + epoch.end.callback=mx.callback.log.train.metric(100)) Start training with 1 devices [1] Train-accuracy=0.0975510204081633 [2] Train-accuracy=0.0906 [3] Train-accuracy=0.09 # ... # [59] Train-accuracy=0.978 [60] Train-accuracy=0.9822 > print(proc.time() - tic) ユーザ システム 経過 784.666 3.767 677.921 > preds <- predict(model, test.array) > pred.label <- max.col(t(preds)) - 1 > table(test_org[,1],pred.label) pred.label 0 1 2 3 4 5 6 7 8 9 0 99 0 0 0 0 0 1 0 0 0 1 0 99 0 0 1 0 0 0 0 0 2 0 0 98 0 0 0 0 1 1 0 3 0 0 0 98 0 1 0 0 1 0 4 0 2 0 0 97 0 1 0 0 0 5 0 0 0 0 0 99 1 0 0 0 6 0 0 0 0 0 0 100 0 0 0 7 0 0 0 0 0 0 0 99 1 0 8 0 0 0 0 0 0 0 0 100 0 9 0 0 0 0 2 0 0 0 0 98 > sum(diag(table(test_org[,1],pred.label)))/1000 [1] 0.987
上述のように、MXnetではtest accuracy 0.987ということでKerasの方がMXnetを上回っていますが、その差は僅か0.002。事実上ほぼ同等の性能と見て良いでしょう。そしてKeras / MXnetともまだチューニングの余地が多くあることを鑑みるに、精度面では互角と言って良いかと思います。
一方、スピードの方はMXnetではこのaccuracyを出すのに60 epochsを費やして全体で677秒。Kerasだと10 epochsで200秒前後。MXnetでベストチューニングをすればまた異なるかもしれませんが、今回の例ではKerasの方が優れているという結果になっています。
MXnetとKerasの比較(特にモデル記述部分)
ところで、個人的にKeras / MXnetともに大きなアドバンテージだなと思っているのは、その「直感的なインタフェース」です。基本的には、青本『深層学習』を読めば身につく知識をそのまま以下のようなCNN設計部分に記述すれば、走ってくれます。
MXnetの場合
> data <- mx.symbol.Variable("data") > # first conv > conv1 <- mx.symbol.Convolution(data=data, kernel=c(5,5), num_filter=20) > tanh1 <- mx.symbol.Activation(data=conv1, act_type="relu") > pool1 <- mx.symbol.Pooling(data=tanh1, pool_type="max", + kernel=c(2,2), stride=c(2,2)) > drop1 <- mx.symbol.Dropout(data=pool1,p=0.5) > # second conv > conv2 <- mx.symbol.Convolution(data=drop1, kernel=c(5,5), num_filter=50) > tanh2 <- mx.symbol.Activation(data=conv2, act_type="relu") > pool2 <- mx.symbol.Pooling(data=tanh2, pool_type="max", + kernel=c(2,2), stride=c(2,2)) > drop2 <- mx.symbol.Dropout(data=pool2,p=0.5) > # first fullc > flatten <- mx.symbol.Flatten(data=drop2) > fc1 <- mx.symbol.FullyConnected(data=flatten, num_hidden=500) > tanh4 <- mx.symbol.Activation(data=fc1, act_type="relu") > drop4 <- mx.symbol.Dropout(data=tanh4,p=0.5) > # second fullc > fc2 <- mx.symbol.FullyConnected(data=drop4, num_hidden=10) > # loss > lenet <- mx.symbol.SoftmaxOutput(data=fc2) > train.array <- train.x > dim(train.array) <- c(28, 28, 1, ncol(train.x)) > test.array <- test > dim(test.array) <- c(28, 28, 1, ncol(test)) > mx.set.seed(0) > tic <- proc.time() > model <- mx.model.FeedForward.create(lenet, X=train.array, y=train.y, + ctx=devices, num.round=60, array.batch.size=100, + learning.rate=0.05, momentum=0.9, wd=0.00001, + eval.metric=mx.metric.accuracy, + epoch.end.callback=mx.callback.log.train.metric(100))
Kerasの場合
batch_size = 100 nb_classes = 10 nb_epoch = 20 # input image dimensions img_rows, img_cols = 28, 28 # number of convolutional filters to use nb_filters = 20 # size of pooling area for max pooling nb_pool = 2 # convolution kernel size nb_conv = 5 model = Sequential() model.add(Convolution2D(nb_filters, nb_conv, nb_conv, border_mode="valid", input_shape=(img_rows, img_cols, 1))) 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.5)) model.add(Flatten()) model.add(Dense(128)) model.add(Activation("relu")) model.add(Dropout(0.5)) model.add(Dense(nb_classes)) model.add(Activation("softmax")) model.compile(loss="categorical_crossentropy", optimizer="adadelta", metrics=["accuracy"]) model.fit(x_train, y_train, batch_size=batch_size, nb_epoch=nb_epoch, verbose=1, validation_data=(x_test, y_test))
構成としてはKerasもMXnetもよく似ています。モデル記述部分は各層の中身(例えばユニット数・カーネルサイズ・プーリングサイズ・活性化関数・Dropout率など)をただベタッと書いてただひたすらメソッドを順番に書き足していけば良いだけで、後で全体のモデルを計算するメソッドを走らせるだけです。OOPで書かれている分、Kerasの方がスッキリしたスクリプトになっている印象がありますね。一方でMXnetの方がもう少し細かく調整できる感じもありまして、ここはお互いに一長一短と言ったところでしょうか。
他にも、例えばチューニングに拘りたいユーザーなら気になる最適化手法の選択のところも、Kerasであればcompileメソッドのoptimizer引数で、MXnetであればmx.model.FeedForward.createメソッドのoptimizer引数で、お好みのものを選ぶことができます。Theanoで書くと面倒臭いことこの上ないらしい*4ので、そういう点でもあらゆる点で組み上げるのが非常に楽だなと感じています。
Deep Learningのみならず機械学習のdemocratization(民主化)へ
最近、業界で時々耳にするキーワードが「機械学習のdemocratization(民主化)」。言い換えると「一握りのエキスパートだけでなく、多くの新規参入者が気軽にDeep Learningのような先進的な機械学習手法を実践できるようになる」ということです。
実際問題、ちょっと前までの機械学習業界*5はどちらかというとゴリゴリ何もかもスクラッチから組めるコーディング力と機械学習の学識の双方に長けた人材でないと手が出せない世界でしたし、その後例えばscikit-learnのようなパッケージ・ライブラリが広く普及するようになった後も、Deep Learningを初めとした先進的なモデル・アルゴリズムの実践には依然として高い壁が立ちはだかっているという印象がありました。
特にDeep Learningはしばらくの間GPUで動かすのがほぼ前提とされていた時期もあり、またTheanoで組むとなるとスクラッチから組むよりは遥かにマシなもののそれでも結構煩雑だというのもあって、正直手を出しづらいなぁと個人的には感じていました。以下のリンク先にあるのはid:aidiaryさんの手によるTheanoを用いたCNN実装ですが、上記のKeras / MXnetによる実装と比べるとやはりかなりの労作になるというのがお分かりになるかと思います。
これらに比べると、Keras / MXnetのシンプルさとスクリプトの分かりやすさ、そしてスクリプトが分かりやすいにもかかわらずチューニングが容易にできるという便利さが特に際立って見て取れると思います。
そしてKerasの場合はバックエンドにTensorFlowを利用できるということで、そのネームバリューとシステムセットアップの簡便さから今後広く普及することが確実視されるTensorFlowがインストールされている環境であれば気軽に使える、というのもKerasの利点と言えるでしょう。言い換えると、TensorFlowが持つCPU / GPU切り替えの容易さ、分散環境への拡張の容易さなどなどにそのまま乗っかることができるので、さらなる発展も望めます。
もしかしたらKeras / MXnetよりもさらにさらに容易にDeep Learningを組めるシンプルなフレームワークが登場するかもしれませんが*6、今後これらのフレームワークによるDeep Learningそして機械学習そのもののdemocratizationが進んでいくものと期待しております。