2016-03-27
■深層学習用言語Deel誕生 そして超人工生命ハッカソン、参加者100名突破!したのでさらに募集枠を増やしました。 
http://connpass.com/event/28982/
ドワンゴ人工知能研究所主催の超人工生命ハッカソンの参加希望者が100人を突破したので、会場をさらに広くして150人まで募集受付することにしました。
しかし突然不安になったのは、「果たしてこれだけの人が本当にChainerを使いこなせるのだろうか」ということ。いや、もちろんChainer Meetupには毎回200人くらいが参加しているので、日本のChainerユーザはおそらく1000人以上はいるのだと思うがしかし・・・Chainerミートアップは話を聞いたり出会いの場としての機能が強いわけだけど、ハッカソンとなると実装しなきゃならないので全く分からないという人が来ても申し訳ない。
ということで、作りました。
初心者でも使える深層学習用言語。その名もDeel(ディール) まだ開発中だけど、ハッカソンには間に合わせたい。
具体的には
分類器が次のように書けます。
CNN = GoogLeNet() #Caffeプリトレインモデルを読み込み Input("deel.png") #deel.pngを読み込み CNN.classify() #画像認識 Show() #認識結果をテキストで表示
わかりますか?この簡潔さ。
同じようなことをChainerやTensorFlowでやろうとするとなんと手間の掛かることか。
いやまあガチの機械学習屋さんはそれでいいのかもしれませんがね。
我々アプリケーションやサービス側のプログラマーからしたら、あれはちょっと複雑にすぎるわけですよ先生。
これ、Chainerで普通に書くと、同じことでもこんなに↓長くなってしまう。
from __future__ import print_function import argparse import datetime import json import multiprocessing import random import sys import threading import time import numpy as np from PIL import Image import six #import six.moves.cPickle as pickle import cPickle as pickle from six.moves import queue import chainer import matplotlib.pyplot as plt import numpy as np import math import chainer.functions as F import chainer.links as L from chainer.links import caffe from matplotlib.ticker import * from chainer import serializers parser = argparse.ArgumentParser( description='Image inspection using chainer') parser.add_argument('image', help='Path to inspection image file') parser.add_argument('--model','-m',default='model', help='Path to model file') parser.add_argument('--mean', default='mean.npy', help='Path to the mean file (computed by compute_mean.py)') args = parser.parse_args() def read_image(path, center=False, flip=False): image = np.asarray(Image.open(path)).transpose(2, 0, 1) if center: top = left = cropwidth / 2 else: top = random.randint(0, cropwidth - 1) left = random.randint(0, cropwidth - 1) bottom = model.insize + top right = model.insize + left image = image[:, top:bottom, left:right].astype(np.float32) image -= mean_image[:, top:bottom, left:right] image /= 255 if flip and random.randint(0, 1) == 0: return image[:, :, ::-1] else: return image import nin mean_image = pickle.load(open(args.mean, 'rb')) model = nin.NIN() serializers.load_hdf5("gpu1out.h5", model) cropwidth = 256 - model.insize model.to_cpu() def predict(net, x): h = F.max_pooling_2d(F.relu(net.mlpconv1(x)), 3, stride=2) h = F.max_pooling_2d(F.relu(net.mlpconv2(h)), 3, stride=2) h = F.max_pooling_2d(F.relu(net.mlpconv3(h)), 3, stride=2) h = net.mlpconv4(F.dropout(h, train=net.train)) h = F.reshape(F.average_pooling_2d(h, 6), (x.data.shape[0], 1000)) return F.softmax(h) #setattr(model, 'predict', predict) img = read_image(args.image) x = np.ndarray( (1, 3, model.insize, model.insize), dtype=np.float32) x[0]=img x = chainer.Variable(np.asarray(x), volatile='on') score = predict(model,x) #score=cuda.to_cpu(score.data) categories = np.loadtxt("labels.txt", str, delimiter="\t") top_k = 20 prediction = zip(score.data[0].tolist(), categories) prediction.sort(cmp=lambda x, y: cmp(x[0], y[0]), reverse=True) for rank, (score, name) in enumerate(prediction[:top_k], start=1): print('#%d | %s | %4.1f%%' % (rank, name, score * 100))
https://github.com/shi3z/chainer_imagenet_tools/blob/master/inspection.py
これは単にニューラルネットで画像を解釈するだけのプログラムなんだけど、無茶苦茶複雑に見えるじゃないですか。やってることがヘボい割には。
もう一度、Deelでの記述を見てみましょう
CNN = GoogLeNet() #Caffeプリトレインモデルを読み込み Input("deel.png") #deel.pngを読み込み CNN.classify() #画像認識 Show() #認識結果をテキストで表示
ほら短い!
やったね!大勝利!
さらにややこしい、バッチによる学習はこんな感じで
nin = NetworkInNetwork() InputBatch(train="data/train.txt", val="data/test.txt") def workout(x,t): nin.classify(x) return nin.backprop(t) BatchTrain(workout)
簡単!スッキリ!高利回り!
これ、バッチによる学習プログラムをChainerまんまで書くとどうなるかは、こちらを御覧ください→https://github.com/pfnet/chainer/blob/master/examples/imagenet/train_imagenet.py
まあ昔に比べれば本来これも短い方だけどね。330行あるわけですよ。
それが10行未満で書けるようになる。33倍の効率化ですよ。言ってみれば。これぞプログラミングの力やわー。文明の利器やわー。
「こんなに簡単に書いて、カスタマイズとかどうすんじゃい」と思うかもしれませんが、ニューラルネットワークの細かいカスタマイズなどはそれがご専門の先生方に任せておけばよろしいのです。
これくらい簡単に書けると、もっと複雑なこともやってみようかなという気持ちになります。
http://www.chokaigi.jp/2016/booth/jiyukenkyu.html
たとえば、ニコニコ超会議のイベント、超自由研究で開催される「超コメント生成AIコンテスト」では、画像からコメントを生成するAIをバトルさせるんだけど、正直言ってかなり複雑なプログラムを書かなくてはならない。まあid:Hi-Kingさんが作ってくれたサンプルをそのまま動かすだけでも相当面白いんだけど
上が、見せた画像で、下が生成されたコメントです。
新しい水着の着方って・・・
面白いなあ。AI
ニコニコ静画の画像とコメントを学習させているので、まるで生きている人間がコメントしているかのよう。
ちなみに軍艦の写真を見せると・・・
完全に艦これと間違われるわけです。んなアホな。凄くない?コレ。
しかしこれを実現するプログラムはけっこう難しい。
あと、これは実は生成してるんじゃなくて検索してるんだよね。
生成するならLSTMに学習させないと。
しかし面倒くさい。まあできるとは思うが骨の髄まで面倒くさい。
そこで同じようなことをもっと簡単に記述できないかと考えた。
CNN = GoogLeNet() RNN = LSTM(units=10,layers=5) InputBatch(train='train.tsv', test='test.tsv') def workout(x,t): Input(x) CNN.classify() RNN.forward() return RNN.backprop(t) BatchTrain(epoch=500)
画像を見せて、その結果をRNN(LSTM)に入力し、RNNをバックプロパゲーションして学習させるというわけだ。
これなら出来そうな気がしなくもない。LSTMのハイパーバラメータをいじるだけで誰でも簡単にAIプログラミングを試せるのではないか。
まだLSTMの部分は作ってないけど、まあそう苦労せずに作れるだろう。
さらに、超人工生命ハッカソンではUnityとChainerを接続しているが、このプログラムは人類には複雑すぎる。やってることは単純なのに!
そこで、将来的にはこんな感じで書けるといいのではないかと思っている。
CNN = GoogLeNet() DQN = DeepQLearning(output=4) def workout(): #Realtime input image from Unity InputUnity('unity.png') CNN.classify() DQN.forward() OutputUnity( { 0:'left, 1:'right, 2:'up', 3:'down', 4:'space'}) #Get score or loss from Unity game t = InputVarsFromUnity() DQN.reinforcement(t) StreamTrain(workout)
ほらこれ。なんかできそうな気がしない?
実際にこの中身を見ようと思ったら、まあ見る気しませんよ。
まあ深層学習用言語と名づけましたが、実際にはPythonの単なるラッパーです。
ミソは、各ニューラルネットワークをブラックボックスにして、コンテキスト依存性を持たせてプログラムをスッキリさせたところ。
さらに、このコンテキストは、Chainerに依存しないテンソルクラスなので、たとえばCNNをChainer、LSTMをTensorFlowとかにもできちゃうようにしたい。
Chainer等のフレームワークからDeelで使えるようなラッパークラスを書くだけで取り込めることを目指している。
ラッパーも、このくらい簡単に書ける
''' Network in Network by Chainer ''' import model.nin class NetworkInNetwork(ImageNet): def __init__(self,mean='data/mean.npy',labels='misc/labels.txt',optimizer=None): super(NetworkInNetwork,self).__init__('NetworkInNetwork',in_size=227) self.func = model.nin.NIN() ImageNet.mean_image = pickle.load(open(mean, 'rb')) self.labels = np.loadtxt(labels, str, delimiter="\t") if Deel.gpu>=0: self.func.to_gpu() if optimizer == None: self.optimizer = optimizers.MomentumSGD(lr=0.01, momentum=0.9) self.optimizer.setup(self.func) def forward(self,x): y = self.func.forward(x) return y def classify(self,x=None): if x==None: x=Tensor.context x = x.content result = self.forward(x) t = ChainerTensor(result) t.owner=self t.use() return t def train(self,x,t): _x = x.content _t = t.content loss= self.func(_x,_t) print("backward") loss.backward() print("backward-end") self.optimizer.update() print('loss', loss.data) t.content.loss =loss t.content.accuracy=self.func.accuracy return t def backprop(self,t): global optimizer_lr x=Tensor.context self.optimizer.lr = optimizer_lr self.optimizer.zero_grads() loss,accuracy = self.func.getLoss(x.content,t.content) t.content.loss =loss t.content.accuracy=accuracy loss.backward() self.optimizer.update() return t
まあちょっとグローバル変数使ったりしてるところがダサいといえばダサいんだけど。
僕は新しい技術が普及するためには、第一に簡単にならないといけないと思っていて、Deelは簡単にする、シンプルにする、という目的に対して最適化されたものだ。
というわけでgithubはここ→ https://github.com/uei/deel
でも念のため言っておくけどソースは汚いし、まだいきなり動くところまでは行ってないよ
まだソースが汚いのでアレですが、とりあえずハッカソンに向けて動くように持って行きたいと思っています。間に合うかなー?
というわけで間口も広げて会場も広くなった超人工生命ハッカソンへのお申し込みはこちら。
- 31 https://t.co/jJwQnBDUCT
- 8 https://www.facebook.com/
- 6 https://www.google.co.jp/
- 2 http://m.facebook.com
- 2 http://www.google.co.jp/
- 2 http://www.google.co.jp/url?sa=t&rct=j&q=&esrc=s&source=web&cd=1&ved=0CDMQFjAB&url=http://d.hatena.ne.jp/shi3z/20160327/1459033117&ei=4BX3VonFAdawwwaNFA&usg=AFQjCNGbtT9SUzyDzCNolfhdA-tD_JDCAg
- 1 http://b.hatena.ne.jp/
- 1 http://b.hatena.ne.jp/entry/http:/www.nihongenki.net/2016/03/26/post-1346/
- 1 http://b.hatena.ne.jp/entrylist
- 1 http://d.hatena.ne.jp/kawatan/20070911