Hatena::ブログ(Diary)

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

2016-03-27

深層学習用言語Deel誕生 そして超人工生命ハッカソン、参加者100名突破!したのでさらに募集枠を増やしました。 07:58

https://i.gyazo.com/704e33d41136199dd65ab0b405412347.png

http://connpass.com/event/28982/

 ドワンゴ人工知能研究所主催の超人工生命ハッカソンの参加希望者が100人を突破したので、会場をさらに広くして150人まで募集受付することにしました。


 しかし突然不安になったのは、「果たしてこれだけの人が本当にChainerを使いこなせるのだろうか」ということ。いや、もちろんChainer Meetupには毎回200人くらいが参加しているので、日本のChainerユーザはおそらく1000人以上はいるのだと思うがしかし・・・Chainerミートアップは話を聞いたり出会いの場としての機能が強いわけだけど、ハッカソンとなると実装しなきゃならないので全く分からないという人が来ても申し訳ない。



 ということで、作りました。

 初心者でも使える深層学習用言語。その名もDeel(ディール) まだ開発中だけど、ハッカソンには間に合わせたい。

https://github.com/uei/deel/raw/master/deel.png


 具体的には

 分類器が次のように書けます。

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倍の効率化ですよ。言ってみれば。これぞプログラミングの力やわー。文明の利器やわー。


 「こんなに簡単に書いて、カスタマイズとかどうすんじゃい」と思うかもしれませんが、ニューラルネットワークの細かいカスタマイズなどはそれがご専門の先生方に任せておけばよろしいのです。


 これくらい簡単に書けると、もっと複雑なこともやってみようかなという気持ちになります。

https://i.gyazo.com/3b2661b617ce10b26e1f2093d69df872.png

http://www.chokaigi.jp/2016/booth/jiyukenkyu.html


 たとえば、ニコニコ超会議のイベント、超自由研究で開催される「超コメント生成AIコンテスト」では、画像からコメントを生成するAIをバトルさせるんだけど、正直言ってかなり複雑なプログラムを書かなくてはならない。まあid:Hi-Kingさんが作ってくれたサンプルをそのまま動かすだけでも相当面白いんだけど

https://i.gyazo.com/9e9622e543ad6c2f42aa2d1bc4ade657.png

 上が、見せた画像で、下が生成されたコメントです。

 新しい水着の着方って・・・

 面白いなあ。AI


 ニコニコ静画の画像とコメントを学習させているので、まるで生きている人間がコメントしているかのよう。

 ちなみに軍艦の写真を見せると・・・

https://i.gyazo.com/7371cf105ceae4668d8eaf109994f564.png


 完全に艦これと間違われるわけです。んなアホな。凄くない?コレ。


 しかしこれを実現するプログラムはけっこう難しい。

 あと、これは実は生成してるんじゃなくて検索してるんだよね。


 生成するなら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

 でも念のため言っておくけどソースは汚いし、まだいきなり動くところまでは行ってないよ



 まだソースが汚いのでアレですが、とりあえずハッカソンに向けて動くように持って行きたいと思っています。間に合うかなー?


 というわけで間口も広げて会場も広くなった超人工生命ハッカソンへのお申し込みはこちら。

超人工生命ハッカソン - connpass

http://connpass.com/event/28982/