DeepLearning4Jの問題点とKerasの使い勝手

さて、機械学習ですが、ブログではあんまり書いていないもののしつこくしぶとく続けています。DeepLearning4Jもしばらく使ってみましたが、問題点も良くわかってきました。

まず、ND4Jのインタフェース。これをさわるのはかなりしんどいです。例を挙げると、あるテンソルの任意の要素に値を設定する(というか、この0とか1とかで構築したテンソルに後から値を追加していって目的のテンソルを作る、というmutableな使い方自体がすごく気にくわないのだけどそれはNumpyも同じなのでさておき)操作はインデックスを使用して行います。

以下がScalaで実際に私が過去書いたコードです。

val feature = Nd4j.create(featureSize, vectorSize)
for((v, i) <- vectors.zipWithIndex)
  feature.put(Array(NDArrayIndex.point(i), NDArrayIndex.all()), v.toNDArray)

まず行列を作って、そのあとで一行ずつデータ(ベクトル)を差し込んでいくような感じですね。ND4JはNumpyのAPIに影響を受けつつ作ったそうですが、ちょっとこのあたりにJavaの言語仕様の限界を感じます。

Numpy(Python)だとこんな感じです。

 a = np.zeros((3, 3))
# array([[ 0.,  0.,  0.],
#       [ 0.,  0.,  0.],
#       [ 0.,  0.,  0.]])

a[1] = [1., 2., 3.]
# array([[ 0.,  0.,  0.],
#        [ 1.,  2.,  3.],
#        [ 0.,  0.,  0.]])

とっても直感的ですね。これだけでもPython系ライブラリを使うメリットはかなりあるんじゃないかという気がしてきます。

前述したように、テンソルや行列と言った数学の概念がmutableになってるのがとっても気持ち悪いのですが、その背景にはデカいデータを扱うにはimmutableだとメモリ的にしんどすぎるとか色々理由があると思うのでそこは我慢です。

で、その上に乗ってるDeepLearning4J(DL4J)もまだまだ未実装機能やバグが多いです。何だかんだ言って、まだバージョン0.5が最新のライブラリですからね…。

ネット上であされるサンプルもまだ非常に少なく、私のような機械学習初学者にとっては学習を進めていくこと自体がしんどいという欠点もあります。

しかしながら、JVMの上で動作するというのはそれだけで大きなメリットなので、もっともっと頑張ってほしいと思います。ND4JもScala向けインタフェースをもっと拡充してほしい。でも、頑張って欲しいと思うもののプロジェクトに貢献したいという気持ちにまで至らないのは私の意識が低いためです。ごめんなさい。(でも翻訳くらいだったらやってみたいです)

ともかく、DL4Jは今後すごく良くなってほしい、Python一辺倒な現状を何とかしてほしいという思いがあるものの、現段階で機械学習を使ったなにかを作りたいという場合にDL4Jだけでは不足というのが残念ながら事実であると思います。

そこで新たに学習しようと思ったのがKeras。KerasはDL4JやChainerといったライブラリと同じようにレイヤーを指定してネットワークを構成できる高レベルなAPIをそなえており、「ちょっとした解析をしてみたい」みたいな用途に特化したライブラリとなっています。あと、バックエンドにTheanoとTensorflowの両方を使用できます。

それじゃあKerasをやってみましょう。インストールは普通にVirtualenvを作ってpip install kerasでいけますが、モデルをファイルに保存するときにエラーになり、ちょっとはまりました。

以下のように手動でh5pyをインストールする必要があります。良くわかりませんが、Kerasのdependencyに入っていないみたいです。また、Ubuntuだとlibhdf5-devをインストールしておかないとコンパイルするときにエラーになりました。

$ sudo apt-get install libhdf5-dev
$ pip install h5py

じゃあまずは論理演算(AND)を学習させてみましょう。自分の場合、とりあえず論理演算を学習させてみる癖?があります。分かりやすいので…。

from keras.models import Sequential
from keras.layers import Dense, Activation
import numpy as np

np.set_printoptions(formatter={'float': '{: 0.3f}'.format})

model = Sequential()

model.add(Dense(output_dim=2, input_dim=2))
model.add(Activation("softmax"))

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

x = np.array([[0., 0.], [1., 0.], [0., 1.], [1., 1.]])
y = np.array([[0., 1.], [0., 1.], [0., 1.], [1., 0.]])

model.fit(x, y, nb_epoch=20000, verbose=0)

print model.predict(x)

ここでは論理演算をTrueもしくはFalseの2クラス分類問題と扱っています。

で、結果は次のような感じ。左がTrue、右がFalseだと思ってください。

[[ 0.000  1.000]
 [ 0.048  0.952]
 [ 0.048  0.952]
 [ 0.932  0.068]]

ちゃんと学習できてますね。じゃあXORをやってみましょう。

from keras.models import Sequential
from keras.layers import Dense, Activation
import numpy as np

np.set_printoptions(formatter={'float': '{: 0.3f}'.format})

model = Sequential()

model.add(Dense(output_dim=2, input_dim=2))
model.add(Activation("softmax"))

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

x = np.array([[0., 0.], [1., 0.], [0., 1.], [1., 1.]])
y = np.array([[0., 1.], [1., 0.], [1., 0.], [0., 1.]])

model.fit(x, y, nb_epoch=20000, verbose=0)

print model.predict(x)

結果は以下のようになりました。

[[ 0.500  0.500]
 [ 0.500  0.500]
 [ 0.500  0.500]
 [ 0.500  0.500]]

全然収束しませんでした。じゃあ層をもう一つ増やしてみましょう。

model.add(Dense(output_dim=2, input_dim=2))
model.add(Activation("sigmoid"))

model.add(Dense(output_dim=10, input_dim=2))
model.add(Activation("sigmoid"))
model.add(Dense(output_dim=2))
model.add(Activation("sigmoid"))

とします。結果は、

[[ 0.292  0.708]
 [ 0.584  0.416]
 [ 0.559  0.441]
 [ 0.555  0.445]]

となりました。あれ?学習できてないですね。とりあえずepochを40000にしてみましょうか。

[[ 0.031  0.969]
 [ 0.963  0.037]
 [ 0.962  0.038]
 [ 0.040  0.960]]

うーん。こんなもんでしょうか。80000にしてみましょう。

[[ 0.009  0.991]
 [ 0.991  0.009]
 [ 0.989  0.011]
 [ 0.011  0.989]]

まあこんな感じか。でももうちょっとチューニングすれば学習回数も少なく出来るかもしれませんね。Lerning Rateを思い切ってもうちょっと高めてみましょう。デフォルトは0.01ですが、0.1に上げてみました。論理演算ごときの学習で過学習も極所解もクソもありません。どーんといこうや。Learning Rateはoptimizerのパラメータとして与えるみたいです。optimizerを下記のようにしました。

sgd = SGD(lr=0.1)
model.compile(loss='categorical_crossentropy', optimizer=sgd, metrics=['accuracy'])

Learning rateを10倍ついでにEpoch数も8000にしてみました。単純計算で80000回学習させた時とほぼ同等の結果が出力されるはずです。結果はこちら。

[[ 0.004  0.996]
 [ 0.995  0.005]
 [ 0.994  0.006]
 [ 0.008  0.992]]

だいたい同じような程度学習できてますね。よかったよかった。

まとめ

DL4Jは今後に期待したいところだが、現状DL4Jだけで機械学習の勉強やアプリの実装を進めるのはしんどい。Kerasは簡単に使えて良い。暫くはKerasで様子を見つつ、DL4Jをウォッチしていくのが良さそう。ND4JのScalaラッパーのND4S、がんばれ(他力本願)。