Hatena::ブログ(Diary)

shi3zの長文日記 RSSフィード Twitter

2017-04-11

後方互換性をバッサリと切り捨てたというふれこみのChainer2(ベータ1)を恐る恐る試す。そして未来 06:31

 ああついにこの時が来てしまった。


 去年本を書いた時は「メジャーバージョンアップは過去の互換性を捨てる!」と高らかに宣言されていたChainer2。嫌だよ怖いよーと思っていたのだが、ついにリリースが目前になってしまった。本に間に合うなら書き直そうかと思っていたけど、ぜんぜん完成が見えないのでとりあえず発売した。タイミング的にはギリギリだったね。


 さて、そもそもChainerは、ただでさえ後方互換性をバッサバッサと切り捨てる、非人道的なフレームワークであることはよく知られている。そのアップデートペースは二週間に一回という過激なものであり、Chainer開発のアルバイトを募集してもなかなか学生が集まらないと嘆かれているのもさもありなんという感じなのだ。怖いじゃん。そんな職場。


 まあとはいえ、自分の愛したフレームワークの最新版であるので、いきなり手のひらを返したように「さよなら、今までニューラルネットをありがとう」とグッバイするわけにもいかないので、おそるおそる・・・ほんとうにおそるおそる、自分が本のために書いたごく簡単なMNISTを試してみた。


 まず、全結合のみのもの

import numpy as np
import chainer
import chainer.functions as F
import chainer.links as L
from chainer import Variable,optimizers,Chain

train, test = chainer.datasets.get_mnist(ndim=3)

class Model(Chain):
    def __init__(self):
        super(Model, self).__init__(
            l1=L.Linear(784, 100),
            l2=L.Linear(100, 100),
            l3=L.Linear(100, 10),
            )
    def __call__(self, x):
        h = F.relu(self.l1(x))
        h = F.relu(self.l2(h))
        return self.l3(h)


model = L.Classifier(Model())
optimizer = optimizers.MomentumSGD(lr=0.01, momentum=0.9)
optimizer.setup(model)


batchsize = 1000

def conv(batch,batchsize):
    x=[]
    t=[]
    for j in range(batchsize):
        x.append(batch[j][0])
        t.append(batch[j][1])
    return Variable(np.array(x)),Variable(np.array(t))

for n in range(20):
    for i in chainer.iterators.SerialIterator(train, batchsize,repeat=False):
        x,t = conv(i,batchsize)
        
        model.cleargrads()
        loss = model(x,t)
        loss.backward()
        optimizer.update()

    i = chainer.iterators.SerialIterator(test,batchsize, repeat=False).next()
    x,t = conv(i,batchsize)
    loss = model(x,t)
    print n,loss.data,model.accuracy.data

 断っておくがこの書き方は2にいくまでもなく古い。

 1の末期に突如導入されたiteratorにムリヤリあわせる形になったからだ。

 後方互換性を謳う1でさえこの勢いでバージョンアップしていたから、もうついていくのが辛い。


 開発方針が行き当たりばったりのように見える。

 そしていつまで経ってもマルチノード対応しないなあと思ったら、マルチノード対応のChainerMNは公開されてない(っていうかハードコードした?)。絵に描いたモチじゃん! TensorFlowより10倍速かろうが、食えない餅より、今食える粟だろう。


 そもそも突然trainerが導入されたのはマルチノードでやるためじゃなかったんかい。


 ま、そういう不満もありつつ、単純なMNISTはそのまま動いて少しホッとした。

 trainerを使った書き方はしてないが、そもそもtrainerの書き方はかなり隠蔽されてしまうので僕が普段やってる仕事にはあまり向いてない。機械学習の勉強にはそれなりに良いのかもしれないけど。そこまで隠蔽するものか(Deelのことは棚に上げ)。


 さて、まあ少しホッとしたところで畳み込みも試してみる

import numpy as np
import chainer
import chainer.functions as F
import chainer.links as L
from chainer import Variable,optimizers,Chain

train, test = chainer.datasets.get_mnist(ndim=3)

#import cupy
#from chainer import cuda
GPU=-1

#cuda.get_device(GPU).use()
#xp = cuda.cupy if GPU >= 0 else np
xp=np

class Model(Chain):
    def __init__(self):
        super(Model, self).__init__(
            conv1=L.Convolution2D(1, 20, 5),
            conv2=L.Convolution2D(20, 50, 5),
            fc1=L.Linear(800, 500), 
            fc2=L.Linear(500, 10),
            )
    def __call__(self, x,train=True):
        cv1 = self.conv1(x)
        relu = F.relu(cv1)
        h = F.max_pooling_2d(relu, 2)
        h = F.max_pooling_2d(F.relu(self.conv2(h)), 2)
        h = F.dropout(F.relu(self.fc1(h)))
        return self.fc2(h)


model = L.Classifier(Model())
#model.to_gpu(GPU)

optimizer = optimizers.MomentumSGD(lr=0.01, momentum=0.9)
optimizer.setup(model)


batchsize = 1000
#print type(train[0][1])
def conv(batch,batchsize):
    x=[]
    t=[]
    for j in range(batchsize):
        x.append(batch[j][0])
        t.append(batch[j][1])
    return Variable(xp.array(x)),Variable(xp.array(t))

for n in range(20):
    for i in chainer.iterators.SerialIterator(train, batchsize,repeat=False):
        x,t = conv(i,batchsize)
        
        model.cleargrads()
        loss = model(x,t)
        loss.backward()
        optimizer.update()
        
    i = chainer.iterators.SerialIterator(test, batchsize).next()
    x,t = conv(i,batchsize)
    loss = model(x,t)
    print n,loss.data

 スタート

 はいエラー出ました。

chainer2 shi3z$ python mnist_cnn.py 
Traceback (most recent call last):
  File "mnist_cnn.py", line 56, in <module>
    loss = model(x,t)
  File "/Users/shi3z/anaconda/lib/python2.7/site-packages/chainer/links/model/classifier.py", line 67, in __call__
    self.y = self.predictor(*x)
  File "mnist_cnn.py", line 30, in __call__
    h = F.dropout(F.relu(self.fc1(h)), train=train)
TypeError: dropout() got an unexpected keyword argument 'train'
chainer2 shi3z$ 

 来ました後方互換性ナシの洗礼。

 dropoutで毎回指定していたtrainはいらなくなったらしい。まあ内部的に処理すればいい話だからな。


 Chainerに限らないんだけど、機械学習系のフレームワークは根本的にコンピュータの動作を理解しないで保守性という錦の御旗でコンピュータのリソースを無駄に使うゆとりプログラミングの時代から、ようやくコンピュータの性能を限界まで引き出すために無駄なパラメータ渡しやメモリをじゃぶじゃぶ使う富豪的プログラミングを脱却するフェーズに移ってるのかもしれない。まあスピード勝負になってるんだから当然だわな。


 Chainerの場合、ただでさえPythonでやるという不利(そりゃCの方がずっと速いけどGPUでの性能差が圧倒的に大きいので見過ごされがち)があるので、真面目に最適化やろうとすると最終的には一部マシン語で書くようになるかもしれない。まあコードの最適化そのものもAIにやらせりゃいいよ最後は。それを最初にやったところが最速のフレームワークになるだろうな。


 Chainer2に移行してこの高血圧社会についていくか、それともややゆとりを持ってPytorchやKerasに移行するか・・・と思っていたら、今度はGoogleからSonnetという、どこかのプロバイダーみたいな名前のTensorFlowラッパーが出てきて大混乱。

 

 この手のものはサンプルが揃っていないとあんまり意味が無いんだよね。計算資源が十分にあって、マルチノードで1ノード8基くらいのGPUがやまほどあれば、Keras+TensorFlowが今のところ一番手軽で最強と言える。


 ちなみにさっきと同じものをKerasで書くとこんな感じ


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


batch_size = 128
num_classes = 10
epochs = 20

# the data, shuffled and split between train and test sets
(x_train, y_train), (x_test, y_test) = mnist.load_data()

x_train = x_train.reshape(60000, 784)
x_test = x_test.reshape(10000, 784)
x_train = x_train.astype('float32')
x_test = x_test.astype('float32')
x_train /= 255
x_test /= 255
print(x_train.shape[0], 'train samples')
print(x_test.shape[0], 'test samples')

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

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

model.summary()

model.compile(loss='categorical_crossentropy',
              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])

 あと、この中ではKerasが一番サンプルが多い。あのややこしい畳込みLSTMまである。Chainerもコミュニティは頑張っているが、pix2pixの移植とかもそうだけどなんか足りてない感じがする。習作って感じなんだよな。手っ取り早く最新の理論をいろいろ試すならKerasはラクだ。本職のニューラルネット研究者になろうというんじゃなければ一番いかかも


 ChainerのメリットであるDefine by Runが活きるのは、よほど特殊なケースなので、Kerasがそのあたりをバッサリ切っているのはクレバーな選択と言える。


 ガチの研究者の場合はChainerくらいの自由度が必要だと思うが、それであったとしても松尾豊先生が提唱するようなマルチエージェントのニューラルネットは実現困難だ(まあこれが実現しやすいフレームワークはない)。


 「マルチエージェントによるDeep Learning」がなんであるかというのはこの論文を読んで欲しい

https://kaigi.org/jsai/webprogram/2016/pdf/953.pdf


 論文としては比較的読みやすいし、これには未来がある気がする。去年の論文だけど。


 今のパーセプトロン(レイヤーの積み重ねによるニューラルネット)の延長としてのニューラルネットの世界は、不自然である、というのがこの論文の主旨で、生物の脳はそんな構造になってないから非同期的なマルチエージェントのニューラルネットとして実装すべきだし、それでも上手くいくはずだ、という内容で、実際に実装したらパーセプトロンと同等の性能が出たから上手くいく、という話である。


 この論文を読むと、なるほど松尾先生がどうして畳み込みに夢を感じられないのかがよりよくわかった。


 要するに、畳込みというのは姑息に見えるわけだ。生物の脳が本来持つ性質を無視した簡易的な実装に見えるし、そんな姑息な方法でたとえ上手く行ったとしても嬉しいか?ということは感覚的には理解できる。


 すげー高い志なわけよ、それって。だから寄附講座の授業でも(いまさらという気がする)ホップフィールドネットワークとか制約ボルツマンマシンについて解説してたのね。要するにあれは退化だと思ってるわけだ。それはそうだよね。そもそも不自然だから。


 マルチエージェントだと、たぶん計算そのものをもっと効率的にして大規模化しないと今の畳込みに似た性能にはならないのではないかと個人的には思う。


 たぶん、畳込みというのは、マルチエージェントとして動作するニューラルネットがある程度の規模でやっていることを大雑把に近似する方法なのではないか。だとすると実用的な畳込みと同等のマルチエージェントネットワークを構成するには今の数百倍の規模の計算が必要なわけで、しかもアーキテクチャ的にGPUが得意なわけではないので全く新しいアーキテクチャを作らなきゃなんない。FPGAとかで実験とかしてるんだろうか。


 個人的にはそういうことはなんとなくできそうだと思うし、出来たらすごく夢あるし、なによりGPU偏重パーセプトロン偏重な世界観に対して、非同期のマルチエージェントニューラルネットがFPGAなりASIC上に実装されてそっちのほうが性能が高い(というところまでいくのに相当な時間がかかりそうだが)のだとしたら、実に痛快じゃないか。


 そうなるとChainerもTensorFlowもどうでもよくなっちゃうわけで。そういう世界が来たら楽しいから松尾先生にはぜひ頑張っていただきたい。FPGAで実装するなら8ビットでReluするだけなら簡単そうだ。むかし1024コアのライフゲームをFPGAに実装した経験を持つ僕としては、すごく暇ならぜひやってみたいのだがそんな仕事なかなか落ちてこないからなあ。


 一方、お仕事としてはChainerもTensorFlowもTorchもKerasも使っている我々としては、そういう夢のある世界が来たらいいなーと思いつつ、いまのところGPUで処理しやすい畳込みに頼らざるをえない現状もある程度は肯定しないとなんないよね。


 というわけでUEIは引き続き深層学習に興味のあるエンジニアや学生アルバイト、インターンを募集しています。いや、もうてんてこ舞いすぎて


 場所は湯島なんで、東大にも秋葉原にも近いよ。

 学生さんの場合、Pythonは初心者でも歓迎

採用情報 | 株式会社UEI

http://www.uei.co.jp/recruit/