実装関連事項

各種プログラミング言語の基本的な書き方やソフトウェア等の使用方法について.

ニューラルネットワークのアーキテクチャのひとつである長短期記憶 (LSTM) は入力ベクトルの文脈を考慮して学習を行うことができる構造.入力には配列情報を用いる.LSTMを用いると配列上の現在の状態とそれ以前,または,それ以降の情報を基に予測を行うことができる.用いられる文脈は時系列や位置等,様々である.

実装するLSTMユニット

Theanoを用いて以下のLSTMユニットを実装する.

iml_theano_lstm_01.svg

ここで,$\boldsymbol{u}$ はLSTMユニットへの入力ベクトル,$\boldsymbol{v}$ はLSTMユニットからの出力ベクトル,$\boldsymbol{h}$ は以前の出力,$\boldsymbol{s}$ は重み消失または爆発を防ぐためのシステムであるCEC (Constant Error Carousel) を実現するためのベクトル,$\times$ は行列積を計算する記号,$+$ は行列の和を計算する記号,ドットはアダマール積を計算する記号,$\tau$ はハイパボリックタンジェント,$\sigma$ はシグモイド関数,$\boldsymbol{w}_a$,$\boldsymbol{w}_b$ および $\boldsymbol{b}$ は学習されるパラメーターをあらわす.LSTMユニットでは,最初,入力されたベクトルに $\boldsymbol{w}_a$ を乗じることで4倍の長さに拡張する,これらに対して $\boldsymbol{w}_b$ と以前の出力を乗じたものとバイアス項 $\boldsymbol{b}$ を加える.その後,ベクトルを4分割し元の長さに戻し,$\tau$ および,3つの $\sigma$ からなるゲートを通過させる.$\sigma$ であらわされるゲートは上から入力,忘却,出力ゲートと呼ぶ.これらのゲートを通過後,その出力は再び $\tau$ を通過し,最終的に $\boldsymbol{v}$ として次の層へと伝播される.このとき,出力ベクトルは $\boldsymbol{h}$ として保持され,次の入力の計算に利用される.これがLSTMの記憶の正体である.以下の実際のプログラムでは,LSTM層の後にさらに全結合層を1層加えた計算を実行する.

学習させるデータの構造1

以下のようなデータを考える.これは2つの入力データ.アトリビュートの数は3であり,文脈の長さは6である.すなわち,最初のインスタンスの最初の値は,[ 0.08653088 -0.08423756 -0.12672345] で文脈2番目の値は [-0.08154392 -0.15250891 -0.07280673] で,文脈の最後の値は [-0.07257242 0.06696415 -0.14728086] である.

[[[ 0.08653088 -0.08423756 -0.12672345]
  [-0.08154392 -0.15250891 -0.07280673]
  [-0.0342948  -0.17434101  0.07698885]
  [ 0.02664058 -0.09384421  0.00929922]
  [-0.16242379  0.0303786   0.17171848]
  [-0.07257242  0.06696415 -0.14728086]]

 [[ 0.03460518 -0.19195698  0.13157602]
  [-0.08154392 -0.15250891 -0.07280673]
  [-0.0342948  -0.17434101  0.07698885]
  [ 0.02664058 -0.09384421  0.00929922]
  [-0.16242379  0.0303786   0.17171848]
  [-0.07257242  0.06696415 -0.14728086]]]

これに対して,対応付けたいデータ,すなわち,ターゲットベクトルは以下のデータ.インスタンスと文脈の長さは当然それぞれ2つと6であり,アトリビュートの数も3である.アトリビュートのサイズは入力データと同じである必要はない.入力データの最初のインスタンスの最初の値,[ 0.08653088 -0.08423756 -0.12672345] に対して出力させたい値は [-0.08154392 -0.15250891 -0.07280673] であり,文脈の2番目の値 [-0.08154392 -0.15250891 -0.07280673] に対して出力させたい値は [-0.0342948 -0.17434101 0.07698885] で,文脈の最後の値 [-0.07257242 0.06696415 -0.14728086] に対して出力させたい値は [ 0.08653088 -0.08423756 -0.12672345] である.

[[[-0.08154392 -0.15250891 -0.07280673]
  [-0.0342948  -0.17434101  0.07698885]
  [ 0.02664058 -0.09384421  0.00929922]
  [-0.16242379  0.0303786   0.17171848]
  [-0.07257242  0.06696415 -0.14728086]
  [ 0.08653088 -0.08423756 -0.12672345]]

 [[-0.08154392 -0.15250891 -0.07280673]
  [-0.0342948  -0.17434101  0.07698885]
  [ 0.02664058 -0.09384421  0.00929922]
  [-0.16242379  0.0303786   0.17171848]
  [-0.07257242  0.06696415 -0.14728086]
  [ 0.03460518 -0.19195698  0.13157602]]]

コード1

以上のデータを学習させるためのコードは以下のようになる.

#!/usr/bin/env python3
# -*- coding: utf-8 -*-
import sys,re,os,subprocess
import numpy as np
import theano
import theano.tensor as T
np.random.seed(0)
np.set_printoptions(threshold=np.inf,linewidth=np.inf,suppress=True,precision=8)

def main():
	# Hyperparameter
	SAMPLESIZE=2
	EMBEDSIZE=3
	INPUTLENGTH=5
	LSTMUNITSIZE=10
	MAXEPOCH=3000
	
	# Generating data
	lix=np.asarray(np.random.randn(SAMPLESIZE,INPUTLENGTH,EMBEDSIZE),dtype=theano.config.floatX)
	lit=np.asarray(np.random.randn(SAMPLESIZE,INPUTLENGTH,EMBEDSIZE),dtype=theano.config.floatX)
	
	MIDLENGTH=5
	lidata=np.ndarray((SAMPLESIZE,MIDLENGTH+2),dtype=np.int32)
	for i in range(SAMPLESIZE):
		for j in range(MIDLENGTH+2):
			if i==0:
				lidata[i][0]=lidata[i][-1]=MIDLENGTH+0
			else:
				lidata[i][0]=lidata[i][-1]=MIDLENGTH+1
			lidata[i][j-1]=j-2
	embed=np.asarray(0.2*np.random.uniform(-1.0,1.0,(MIDLENGTH+2,3)),dtype=theano.config.floatX)
	lidata=embed[lidata]
	lix=lidata[:,:-1,:]
	lit=lidata[:,1:,:]
	
	# Input variable
	x=T.fmatrix('x')
	t=T.fmatrix('t')
	
	# Parameter for LSTM layer
	W1aa=theano.shared(value=np.asarray(np.random.normal(loc=0,scale=0.05,size=(EMBEDSIZE,LSTMUNITSIZE)),dtype=theano.config.floatX),name="W1aa")
	W1ai=theano.shared(value=np.asarray(np.random.normal(loc=0,scale=0.05,size=(EMBEDSIZE,LSTMUNITSIZE)),dtype=theano.config.floatX),name="W1ai")
	W1af=theano.shared(value=np.asarray(np.random.normal(loc=0,scale=0.05,size=(EMBEDSIZE,LSTMUNITSIZE)),dtype=theano.config.floatX),name="W1af")
	W1ao=theano.shared(value=np.asarray(np.random.normal(loc=0,scale=0.05,size=(EMBEDSIZE,LSTMUNITSIZE)),dtype=theano.config.floatX),name="W1ao")
	b1a=theano.shared(value=np.asarray(np.random.normal(loc=0,scale=0.5,size=(LSTMUNITSIZE)),dtype=theano.config.floatX),name="b1a")
	b1i=theano.shared(value=np.asarray(np.random.normal(loc=0,scale=0.5,size=(LSTMUNITSIZE)),dtype=theano.config.floatX),name="b1i")
	b1f=theano.shared(value=np.asarray(np.random.normal(loc=0,scale=0.5,size=(LSTMUNITSIZE)),dtype=theano.config.floatX),name="b1f")
	b1o=theano.shared(value=np.asarray(np.random.normal(loc=0,scale=0.5,size=(LSTMUNITSIZE)),dtype=theano.config.floatX),name="b1o")
	W1ba=theano.shared(value=np.asarray(np.random.normal(loc=0,scale=0.05,size=(LSTMUNITSIZE,LSTMUNITSIZE)),dtype=theano.config.floatX),name="W1ba")
	W1bi=theano.shared(value=np.asarray(np.random.normal(loc=0,scale=0.05,size=(LSTMUNITSIZE,LSTMUNITSIZE)),dtype=theano.config.floatX),name="W1bi")
	W1bf=theano.shared(value=np.asarray(np.random.normal(loc=0,scale=0.05,size=(LSTMUNITSIZE,LSTMUNITSIZE)),dtype=theano.config.floatX),name="W1bf")
	W1bo=theano.shared(value=np.asarray(np.random.normal(loc=0,scale=0.05,size=(LSTMUNITSIZE,LSTMUNITSIZE)),dtype=theano.config.floatX),name="W1bo")
	vs=theano.shared(value=np.asarray(np.zeros(shape=(LSTMUNITSIZE)),dtype=theano.config.floatX),name="vs")
	vh=theano.shared(value=np.asarray(np.zeros(shape=(LSTMUNITSIZE)),dtype=theano.config.floatX),name="vh")
	
	# Parameter for fully connected layer
	W2=theano.shared(value=np.asarray(np.random.normal(loc=0,scale=0.05,size=(LSTMUNITSIZE,EMBEDSIZE)),dtype=theano.config.floatX),name="W2")
	b2=theano.shared(value=np.asarray(np.random.normal(loc=0,scale=0.5,size=(EMBEDSIZE)),dtype=theano.config.floatX),name="b2")
	
	# Network calculation
	liparameter=[W1aa,W1ai,W1af,W1ao,b1a,b1i,b1f,b1o,W1ba,W1bi,W1bf,W1bo]
	[vh,vs],liscanupdate=theano.scan(fn=lstmforward,sequences=x,truncate_gradient=-1,outputs_info=[vh,vs],non_sequences=liparameter)
	y=T.dot(vh,W2)+b2
	tscost=((t-y)**2).sum()
	
	# Calculating gradient
	liparameter.append(W2)
	liparameter.append(b2)
	ligradient=T.grad(cost=tscost,wrt=liparameter)
	
	# Optimization setting
	liupdate=adam(liparameter,ligradient)
	
	# Generating function
	trainer=theano.function(inputs=[x,t],outputs=[tscost],updates=liupdate)
	predictor=theano.function(inputs=[x],outputs=[y,vs])
	
	# Learning
	for i in range(MAXEPOCH):
		costvalue=0
		for j in range(SAMPLESIZE):
			costvalue+=trainer(lix[j],lit[j])[0]
		costvalue=costvalue/SAMPLESIZE
		if (i+1)%100==0:
			print("Epoch {0:8d}: Training cost={1:9.5f}".format(i+1,float(costvalue)))
			print("T:","\n",lit,"\n",sep="",end="")
			for j in range(SAMPLESIZE):
				[result,cellvalue]=predictor(lix[j])
				print("Y",j,":","\n",result,"\n",sep="",end="")

def adam(liparameter,ligradient,a=0.001,b1=0.9,b2=0.999,e=1e-6):
	liupdate=[]
	t=theano.shared(value=np.float32(1),name="t")
	liupdate.append((t,t+1))
	for pc,gc in zip(liparameter,ligradient):
		mc=theano.shared(value=np.zeros(pc.get_value().shape,dtype=theano.config.floatX),name='mc')
		vc=theano.shared(value=np.zeros(pc.get_value().shape,dtype=theano.config.floatX),name='vc')
		mn=b1*mc+(1-b1)*gc
		vn=b2*vc+(1-b2)*gc**2
		mh=mn/(1-b1**t)
		vh=vn/(1-b2**t)
		pn=pc-(a*mh)/(T.sqrt(vh+e))
		liupdate.append((mc,mn))
		liupdate.append((vc,vn))
		liupdate.append((pc,pn))
	return liupdate

def lstmforward(x,vh,vs,W1aa,W1ai,W1af,W1ao,b1a,b1i,b1f,b1o,W1ba,W1bi,W1bf,W1bo):
	va=T.tanh(T.dot(x,W1aa)+b1a+T.dot(vh,W1ba))
	vi=T.nnet.sigmoid(T.dot(x,W1ai)+b1i+T.dot(vh,W1bi))
	vf=T.nnet.sigmoid(T.dot(x,W1af)+b1f+T.dot(vh,W1bf))
	vo=T.nnet.sigmoid(T.dot(x,W1ao)+b1o+T.dot(vh,W1bo))
	vs=va*vi+vf*vs
	vh=vo*T.tanh(vs)
	return vh,vs

if __name__ == '__main__':
	main()

コード中,40行目から54行目まででLSTMの計算に用いる変数を生成する.

	# Parameter for LSTM layer
	W1aa=theano.shared(value=np.asarray(np.random.normal(loc=0,scale=0.05,size=(EMBEDSIZE,LSTMUNITSIZE)),dtype=theano.config.floatX),name="W1aa")
	W1ai=theano.shared(value=np.asarray(np.random.normal(loc=0,scale=0.05,size=(EMBEDSIZE,LSTMUNITSIZE)),dtype=theano.config.floatX),name="W1ai")
	W1af=theano.shared(value=np.asarray(np.random.normal(loc=0,scale=0.05,size=(EMBEDSIZE,LSTMUNITSIZE)),dtype=theano.config.floatX),name="W1af")
	W1ao=theano.shared(value=np.asarray(np.random.normal(loc=0,scale=0.05,size=(EMBEDSIZE,LSTMUNITSIZE)),dtype=theano.config.floatX),name="W1ao")
	b1a=theano.shared(value=np.asarray(np.random.normal(loc=0,scale=0.5,size=(LSTMUNITSIZE)),dtype=theano.config.floatX),name="b1a")
	b1i=theano.shared(value=np.asarray(np.random.normal(loc=0,scale=0.5,size=(LSTMUNITSIZE)),dtype=theano.config.floatX),name="b1i")
	b1f=theano.shared(value=np.asarray(np.random.normal(loc=0,scale=0.5,size=(LSTMUNITSIZE)),dtype=theano.config.floatX),name="b1f")
	b1o=theano.shared(value=np.asarray(np.random.normal(loc=0,scale=0.5,size=(LSTMUNITSIZE)),dtype=theano.config.floatX),name="b1o")
	W1ba=theano.shared(value=np.asarray(np.random.normal(loc=0,scale=0.05,size=(LSTMUNITSIZE,LSTMUNITSIZE)),dtype=theano.config.floatX),name="W1ba")
	W1bi=theano.shared(value=np.asarray(np.random.normal(loc=0,scale=0.05,size=(LSTMUNITSIZE,LSTMUNITSIZE)),dtype=theano.config.floatX),name="W1bi")
	W1bf=theano.shared(value=np.asarray(np.random.normal(loc=0,scale=0.05,size=(LSTMUNITSIZE,LSTMUNITSIZE)),dtype=theano.config.floatX),name="W1bf")
	W1bo=theano.shared(value=np.asarray(np.random.normal(loc=0,scale=0.05,size=(LSTMUNITSIZE,LSTMUNITSIZE)),dtype=theano.config.floatX),name="W1bo")
	vs=theano.shared(value=np.asarray(np.zeros(shape=(LSTMUNITSIZE)),dtype=theano.config.floatX),name="vs")
	vh=theano.shared(value=np.asarray(np.zeros(shape=(LSTMUNITSIZE)),dtype=theano.config.floatX),name="vh")

56から58行目まではLSTM層の次の層である全結合層のための変数を生成するためのもの.LSTMは再帰的な構造を持つアーキテクチャであるが,Theanoにおいてこれを実現するシステムは theano.scan() である.outputs_info で指定した vh と vs が,関数 lstmforward() で繰り返し更新される.このとき,LSTMのパラメーターは,non_sequences に指定されているため,theano.scan() の繰り返し計算で更新されるのではなく,91行目からのオプティマイザー adam() によって更新される.最終的な出力は vh であり,これが次の層,57行目の計算に渡される.

	# Parameter for fully connected layer
	W2=theano.shared(value=np.asarray(np.random.normal(loc=0,scale=0.05,size=(LSTMUNITSIZE,EMBEDSIZE)),dtype=theano.config.floatX),name="W2")
	b2=theano.shared(value=np.asarray(np.random.normal(loc=0,scale=0.5,size=(EMBEDSIZE)),dtype=theano.config.floatX),name="b2")
	
	# Network calculation
	liparameter=[W1aa,W1ai,W1af,W1ao,b1a,b1i,b1f,b1o,W1ba,W1bi,W1bf,W1bo]
	[vh,vs],liscanupdate=theano.scan(fn=lstmforward,sequences=x,truncate_gradient=-1,outputs_info=[vh,vs],non_sequences=liparameter)
	y=T.dot(vh,W2)+b2
	tscost=((t-y)**2).sum()

以上のプログラムの名前を lstm-simple.py とすると,これは以下のようなコマンドで実行することができる.

$ THEANO_FLAGS=mode=FAST_RUN,device=cpu,floatX=float32 lstm-simple.py

結果は以下のようになる.学習の初期段階では出力と教師データの類似度は低いが,学習の最終段階では,2つの出力データはそれぞれ2つの教師データと類似している.

Epoch      100: Training cost=  0.27196
T:
[[[-0.08154392 -0.15250891 -0.07280673]
  [-0.0342948  -0.17434101  0.07698885]
  [ 0.02664058 -0.09384421  0.00929922]
  [-0.16242379  0.0303786   0.17171848]
  [-0.07257242  0.06696415 -0.14728086]
  [ 0.08653088 -0.08423756 -0.12672345]]

 [[-0.08154392 -0.15250891 -0.07280673]
  [-0.0342948  -0.17434101  0.07698885]
  [ 0.02664058 -0.09384421  0.00929922]
  [-0.16242379  0.0303786   0.17171848]
  [-0.07257242  0.06696415 -0.14728086]
  [ 0.03460518 -0.19195698  0.13157602]]]
Y0:
[[ 0.12997298 -0.12816769  0.2257674 ]
 [ 0.0458257  -0.09537494  0.12289979]
 [-0.01749319 -0.06912208  0.04372281]
 [-0.05167246 -0.05485708 -0.00118935]
 [-0.07519376 -0.0442379  -0.0331631 ]
 [-0.07767847 -0.04404005 -0.03925303]]
Y1:
[[ 0.11746319 -0.12239502  0.21132547]
 [ 0.03859967 -0.09215678  0.11380288]
 [-0.02234909 -0.06696483  0.03742144]
 [-0.05493155 -0.05341873 -0.00549483]
 [-0.0774149  -0.04326688 -0.03613317]
 [-0.07918319 -0.04338718 -0.04128495]]
.
.
.
Epoch     3000: Training cost=  0.00317
T:
[[[-0.08154392 -0.15250891 -0.07280673]
  [-0.0342948  -0.17434101  0.07698885]
  [ 0.02664058 -0.09384421  0.00929922]
  [-0.16242379  0.0303786   0.17171848]
  [-0.07257242  0.06696415 -0.14728086]
  [ 0.08653088 -0.08423756 -0.12672345]]

 [[-0.08154392 -0.15250891 -0.07280673]
  [-0.0342948  -0.17434101  0.07698885]
  [ 0.02664058 -0.09384421  0.00929922]
  [-0.16242379  0.0303786   0.17171848]
  [-0.07257242  0.06696415 -0.14728086]
  [ 0.03460518 -0.19195698  0.13157602]]]
Y0:
[[-0.08514881 -0.14413828 -0.07130915]
 [-0.0248866  -0.16882963  0.06702732]
 [ 0.01571253 -0.07165423 -0.00313702]
 [-0.18557674  0.04484013  0.1765048 ]
 [-0.05348092  0.06230876 -0.1626899 ]
 [ 0.08268815 -0.10409838 -0.11242718]]
Y1:
[[-0.0759373  -0.15430401 -0.0743376 ]
 [-0.04356647 -0.18152104  0.0804119 ]
 [ 0.02606758 -0.10608211  0.01962999]
 [-0.13059545  0.00584829  0.17076756]
 [-0.08819377  0.06825462 -0.13039929]
 [ 0.03594881 -0.17074329  0.11827008]]

学習させるデータの構造2

次に以下のようなデータを考える.これは10個のインスタンスからなるデータで,各列が各インスタンスである.文脈の長さは12で,アトリビュートの次元は1である.各インスタンスにおいて一番最初の文脈と最後の文脈の値が等しく,中間の10文脈は各インスタンスで互いに等しい.よって,これをちゃんと学習できるのならば,LSTMが文脈を学習することができて,その記憶力は少なくとも11文脈は継続することの例証となる.

[[0 0 1 2 3 4 5 6 7 8 9 0]
 [1 0 1 2 3 4 5 6 7 8 9 1]
 [2 0 1 2 3 4 5 6 7 8 9 2]
 [3 0 1 2 3 4 5 6 7 8 9 3]
 [4 0 1 2 3 4 5 6 7 8 9 4]
 [5 0 1 2 3 4 5 6 7 8 9 5]
 [6 0 1 2 3 4 5 6 7 8 9 6]
 [7 0 1 2 3 4 5 6 7 8 9 7]
 [8 0 1 2 3 4 5 6 7 8 9 8]
 [9 0 1 2 3 4 5 6 7 8 9 9]]

実際の学習では以下のような入力データを用いる.

[[0 0 1 2 3 4 5 6 7 8 9]
 [1 0 1 2 3 4 5 6 7 8 9]
 [2 0 1 2 3 4 5 6 7 8 9]
 [3 0 1 2 3 4 5 6 7 8 9]
 [4 0 1 2 3 4 5 6 7 8 9]
 [5 0 1 2 3 4 5 6 7 8 9]
 [6 0 1 2 3 4 5 6 7 8 9]
 [7 0 1 2 3 4 5 6 7 8 9]
 [8 0 1 2 3 4 5 6 7 8 9]
 [9 0 1 2 3 4 5 6 7 8 9]]

実際の学習では以下のような入力データを用いる.これに対して教師データは以下である.

[[0 1 2 3 4 5 6 7 8 9 0]
 [0 1 2 3 4 5 6 7 8 9 1]
 [0 1 2 3 4 5 6 7 8 9 2]
 [0 1 2 3 4 5 6 7 8 9 3]
 [0 1 2 3 4 5 6 7 8 9 4]
 [0 1 2 3 4 5 6 7 8 9 5]
 [0 1 2 3 4 5 6 7 8 9 6]
 [0 1 2 3 4 5 6 7 8 9 7]
 [0 1 2 3 4 5 6 7 8 9 8]
 [0 1 2 3 4 5 6 7 8 9 9]]

コード2

以上のデータを学習させるためのコードは以下のようになる.これはデータにおける数値を量的値と考え,回帰を行うものである.

#!/usr/bin/env python3
# -*- coding: utf-8 -*-
import sys,re,os,subprocess
import numpy as np
import theano
import theano.tensor as T
np.random.seed(0)
np.set_printoptions(threshold=np.inf,linewidth=np.inf,suppress=True,precision=8)

def main():
	# Hyperparameter
	LENGTH=10
	SAMPLESIZE=LENGTH
	EMBEDUNIT=1
	OUTPUTSIZE=EMBEDUNIT
	LSTMUNITSIZE=100
	MAXEPOCH=1000
	MINIBATCHSIZE=5
	MINIBATCHNUMBER=SAMPLESIZE//MINIBATCHSIZE
	
	# Generating data
	lidata=np.ndarray((SAMPLESIZE,LENGTH+2),dtype=np.float32)
	for i in range(SAMPLESIZE):
		for j in range(LENGTH+2):
			lidata[i][0]=lidata[i][-1]=i
			lidata[i][j-1]=j-2
	lidata=lidata.reshape((SAMPLESIZE,LENGTH+2,1))
	lix=lidata[:,:-1]
	lit=lidata[:,1:]
	
	# Input variable
	x=T.ftensor3('x')
	t=T.ftensor3('t')
	
	# Parameter for LSTM layer
	W1aa=theano.shared(value=np.asarray(np.random.uniform(low=-0.2,high=0.2,size=(EMBEDUNIT,LSTMUNITSIZE)),dtype=theano.config.floatX),name="W1aa")
	W1ai=theano.shared(value=np.asarray(np.random.uniform(low=-0.2,high=0.2,size=(EMBEDUNIT,LSTMUNITSIZE)),dtype=theano.config.floatX),name="W1ai")
	W1af=theano.shared(value=np.asarray(np.random.uniform(low=-0.2,high=0.2,size=(EMBEDUNIT,LSTMUNITSIZE)),dtype=theano.config.floatX),name="W1af")
	W1ao=theano.shared(value=np.asarray(np.random.uniform(low=-0.2,high=0.2,size=(EMBEDUNIT,LSTMUNITSIZE)),dtype=theano.config.floatX),name="W1ao")
	b1a=theano.shared(value=np.asarray(np.random.uniform(low=-0.2,high=0.2,size=(LSTMUNITSIZE)),dtype=theano.config.floatX),name="b1a")
	b1i=theano.shared(value=np.asarray(np.random.uniform(low=-0.2,high=0.2,size=(LSTMUNITSIZE)),dtype=theano.config.floatX),name="b1i")
	b1f=theano.shared(value=np.asarray(np.random.uniform(low=-0.2,high=0.2,size=(LSTMUNITSIZE)),dtype=theano.config.floatX),name="b1f")
	b1o=theano.shared(value=np.asarray(np.random.uniform(low=-0.2,high=0.2,size=(LSTMUNITSIZE)),dtype=theano.config.floatX),name="b1o")
	W1ba=theano.shared(value=np.asarray(np.random.uniform(low=-0.2,high=0.2,size=(LSTMUNITSIZE,LSTMUNITSIZE)),dtype=theano.config.floatX),name="W1ba")
	W1bi=theano.shared(value=np.asarray(np.random.uniform(low=-0.2,high=0.2,size=(LSTMUNITSIZE,LSTMUNITSIZE)),dtype=theano.config.floatX),name="W1bi")
	W1bf=theano.shared(value=np.asarray(np.random.uniform(low=-0.2,high=0.2,size=(LSTMUNITSIZE,LSTMUNITSIZE)),dtype=theano.config.floatX),name="W1bf")
	W1bo=theano.shared(value=np.asarray(np.random.uniform(low=-0.2,high=0.2,size=(LSTMUNITSIZE,LSTMUNITSIZE)),dtype=theano.config.floatX),name="W1bo")
	vs=theano.shared(value=np.asarray(np.zeros(shape=(MINIBATCHSIZE,LSTMUNITSIZE)),dtype=theano.config.floatX),name="vs")
	vh=theano.shared(value=np.asarray(np.zeros(shape=(MINIBATCHSIZE,LSTMUNITSIZE)),dtype=theano.config.floatX),name="vh")
	
	# Parameter for fully connected layer
	W2=theano.shared(value=np.asarray(np.random.normal(loc=0,scale=0.05,size=(LSTMUNITSIZE,OUTPUTSIZE)),dtype=theano.config.floatX),name="W2")
	b2=theano.shared(value=np.asarray(np.random.normal(loc=0,scale=0.5,size=(OUTPUTSIZE)),dtype=theano.config.floatX),name="b2")
	
	# Network calculation
	mx=x.dimshuffle((1,0,2))
	mt=t.dimshuffle((1,0,2))
	liparameter=[W1aa,W1ai,W1af,W1ao,b1a,b1i,b1f,b1o,W1ba,W1bi,W1bf,W1bo]
	[vh,vs],liscanupdate=theano.scan(fn=lstmforward,sequences=mx,truncate_gradient=-1,outputs_info=[vh,vs],non_sequences=liparameter)
	y=T.dot(vh,W2)+b2
	tscost=((mt-y)**2).sum()
	
	# Calculating gradient
	liparameter.append(W2)
	liparameter.append(b2)
	ligradient=T.grad(cost=tscost,wrt=liparameter)
	
	# Optimization setting
	liupdate=adam(liparameter,ligradient)
	
	# Generating function
	trainer=theano.function(inputs=[x,t],outputs=[tscost],updates=liupdate)
	my=y.dimshuffle((1,0,2))
	predictor=theano.function(inputs=[x],outputs=[my,vs])
	
	# Learning
	for i in range(MAXEPOCH):
		liindex=np.random.permutation(SAMPLESIZE)
		costvalue=0
		for j in range(MINIBATCHNUMBER):
			start=j*MINIBATCHSIZE
			end=(j+1)*MINIBATCHSIZE
			costvalue+=trainer(lix[liindex[start:end],:,:],lit[liindex[start:end],:,:])[0]
		costvalue=costvalue/MINIBATCHNUMBER
		if (i+1)%100==0:
			print("Epoch {0:8d}: Training cost={1:9.5f}".format(i+1,float(costvalue)))
			for j in range(MINIBATCHNUMBER):
				[result,cellvalue]=predictor(lix[j*MINIBATCHSIZE:(j+1)*MINIBATCHSIZE,:,:])
				result=result.reshape((MINIBATCHSIZE,LENGTH+1))
				print(np.hstack((lix[j*MINIBATCHSIZE:(j+1)*MINIBATCHSIZE,0,:],np.round(result))).astype(int))

def adam(liparameter,ligradient,a=0.001,b1=0.9,b2=0.999,e=1e-6):
	liupdate=[]
	t=theano.shared(value=np.float32(1),name="t")
	liupdate.append((t,t+1))
	for pc,gc in zip(liparameter,ligradient):
		mc=theano.shared(value=np.zeros(pc.get_value().shape,dtype=theano.config.floatX),name='mc')
		vc=theano.shared(value=np.zeros(pc.get_value().shape,dtype=theano.config.floatX),name='vc')
		mn=b1*mc+(1-b1)*gc
		vn=b2*vc+(1-b2)*gc**2
		mh=mn/(1-b1**t)
		vh=vn/(1-b2**t)
		pn=pc-(a*mh)/(T.sqrt(vh+e))
		liupdate.append((mc,mn))
		liupdate.append((vc,vn))
		liupdate.append((pc,pn))
	return liupdate

def lstmforward(x,vh,vs,W1aa,W1ai,W1af,W1ao,b1a,b1i,b1f,b1o,W1ba,W1bi,W1bf,W1bo):
	va=T.tanh(T.dot(x,W1aa)+b1a+T.dot(vh,W1ba))
	vi=T.nnet.sigmoid(T.dot(x,W1ai)+b1i+T.dot(vh,W1bi))
	vf=T.nnet.sigmoid(T.dot(x,W1af)+b1f+T.dot(vh,W1bf))
	vo=T.nnet.sigmoid(T.dot(x,W1ao)+b1o+T.dot(vh,W1bo))
	vs=va*vi+vf*vs
	vh=vo*T.tanh(vs)
	return vh,vs

if __name__ == '__main__':
	main()

コード中,48行目と49行目のLSTMの内部の値の行列はミニバッチのサイズによって形を変更する必要がある.

	vs=theano.shared(value=np.asarray(np.zeros(shape=(MINIBATCHSIZE,LSTMUNITSIZE)),dtype=theano.config.floatX),name="vs")
	vh=theano.shared(value=np.asarray(np.zeros(shape=(MINIBATCHSIZE,LSTMUNITSIZE)),dtype=theano.config.floatX),name="vh")

56行目と57行目でデータの形を変更している.

	mx=x.dimshuffle((1,0,2))
	mt=t.dimshuffle((1,0,2))

これは,theano.scan() の計算をミニバッチで計算を行うために加える.本来の入力データは以下である.もしこのデータをそのまま繰り返し計算に供すると,以下の1から11行目までを最初の文脈の値,12から22行目までを次の文脈,というように学習が行われてしまい,これはミニバッチでの学習とは異なる.

[[[0.],
  [0.],
  [1.],
  [2.],
  [3.],
  [4.],
  [5.],
  [6.],
  [7.],
  [8.],
  [9.]],

 [[1.],
  [0.],
  [1.],
  [2.],
  [3.],
  [4.],
  [5.],
  [6.],
  [7.],
  [8.],
  [9.]],

 [[2.],
  [0.],
  [1.],
  [2.],
  [3.],
  [4.],
  [5.],
  [6.],
  [7.],
  [8.],
  [9.]],

 [[3.],
  [0.],
  [1.],
  [2.],
  [3.],
  [4.],
  [5.],
  [6.],
  [7.],
  [8.],
  [9.]],

 [[4.],
  [0.],
  [1.],
  [2.],
  [3.],
  [4.],
  [5.],
  [6.],
  [7.],
  [8.],
  [9.]],

 [[5.],
  [0.],
  [1.],
  [2.],
  [3.],
  [4.],
  [5.],
  [6.],
  [7.],
  [8.],
  [9.]],

 [[6.],
  [0.],
  [1.],
  [2.],
  [3.],
  [4.],
  [5.],
  [6.],
  [7.],
  [8.],
  [9.]],

 [[7.],
  [0.],
  [1.],
  [2.],
  [3.],
  [4.],
  [5.],
  [6.],
  [7.],
  [8.],
  [9.]],

 [[8.],
  [0.],
  [1.],
  [2.],
  [3.],
  [4.],
  [5.],
  [6.],
  [7.],
  [8.],
  [9.]],

 [[9.],
  [0.],
  [1.],
  [2.],
  [3.],
  [4.],
  [5.],
  [6.],
  [7.],
  [8.],
  [9.]]]

本来学習したい文脈は以下であり,それを実現するのが56行目と57行目の記述である.

[[[0.],
  [1.],
  [2.],
  [3.],
  [4.],
  [5.],
  [6.],
  [7.],
  [8.],
  [9.]],
 
 [[0.],
  [0.],
  [0.],
  [0.],
  [0.],
  [0.],
  [0.],
  [0.],
  [0.],
  [0.]],
 
 [[1.],
  [1.],
  [1.],
  [1.],
  [1.],
  [1.],
  [1.],
  [1.],
  [1.],
  [1.]],
 
 [[2.],
  [2.],
  [2.],
  [2.],
  [2.],
  [2.],
  [2.],
  [2.],
  [2.],
  [2.]],
 
 [[3.],
  [3.],
  [3.],
  [3.],
  [3.],
  [3.],
  [3.],
  [3.],
  [3.],
  [3.]],
 
 [[4.],
  [4.],
  [4.],
  [4.],
  [4.],
  [4.],
  [4.],
  [4.],
  [4.],
  [4.]],
 
 [[5.],
  [5.],
  [5.],
  [5.],
  [5.],
  [5.],
  [5.],
  [5.],
  [5.],
  [5.]],
 
 [[6.],
  [6.],
  [6.],
  [6.],
  [6.],
  [6.],
  [6.],
  [6.],
  [6.],
  [6.]],
 
 [[7.],
  [7.],
  [7.],
  [7.],
  [7.],
  [7.],
  [7.],
  [7.],
  [7.],
  [7.]],
 
 [[8.],
  [8.],
  [8.],
  [8.],
  [8.],
  [8.],
  [8.],
  [8.],
  [8.],
  [8.]],
 
 [[9.],
  [9.],
  [9.],
  [9.],
  [9.],
  [9.],
  [9.],
  [9.],
  [9.],
  [9.]]]

以上のプログラムの名前を lstm-regression.py とすると,これは以下のようなコマンドで実行することができる.

$ THEANO_FLAGS=mode=FAST_RUN,device=cpu,floatX=float32 lstm-regression.py

結果は以下のようになる.ちゃんと文脈が学習できている.

Epoch      100: Training cost= 83.97422
[[0 0 0 1 2 4 5 6 7 6 6 6]
 [1 0 0 1 2 4 5 6 7 7 6 6]
 [2 0 1 1 3 4 6 6 7 7 7 6]
 [3 0 1 2 3 4 6 7 7 7 7 6]
 [4 0 1 2 3 4 5 6 7 7 7 7]]
[[5 0 1 2 3 4 5 6 7 7 7 7]
 [6 0 1 2 3 4 5 6 7 7 7 7]
 [7 1 1 2 3 4 5 6 7 7 7 7]
 [8 1 1 2 3 4 5 6 7 7 7 7]
 [9 1 1 2 3 4 5 6 7 7 7 7]]
.
.
.
Epoch     1000: Training cost=  0.15725
[[0 0 1 2 3 4 5 6 7 8 9 0]
 [1 0 1 2 3 4 5 6 7 8 9 1]
 [2 0 1 2 3 4 5 6 7 8 9 2]
 [3 0 1 2 3 4 5 6 7 8 9 3]
 [4 0 1 2 3 4 5 6 7 8 9 4]]
[[5 0 1 2 3 4 5 6 7 8 9 5]
 [6 0 1 2 3 4 5 6 7 8 9 6]
 [7 0 1 2 3 4 5 6 7 8 9 7]
 [8 0 1 2 3 4 5 6 7 8 9 8]
 [9 0 1 2 3 4 5 6 7 8 9 9]]

コード3

以上のデータを回帰ではなくて分類問題として学習させるには以下のプログラムを使う.

#!/usr/bin/env python3
# -*- coding: utf-8 -*-
import sys,re,os,subprocess
import numpy as np
import theano
import theano.tensor as T
np.random.seed(0)
np.set_printoptions(threshold=np.inf,linewidth=np.inf,suppress=True,precision=8)

def main():
	# Hyperparameter
	LENGTH=10
	SAMPLESIZE=LENGTH
	EMBEDUNIT=50
	OUTPUTSIZE=LENGTH
	LSTMUNITSIZE=200
	MAXEPOCH=1000
	MINIBATCHSIZE=5
	MINIBATCHNUMBER=SAMPLESIZE//MINIBATCHSIZE
	
	# Generating data
	lidata=np.ndarray((SAMPLESIZE,LENGTH+2),dtype=np.int32)
	for i in range(SAMPLESIZE):
		for j in range(LENGTH+2):
			lidata[i][0]=lidata[i][-1]=i
			lidata[i][j-1]=j-2
	lix=lidata[:,:-1]
	lit=lidata[:,1:]
	
	# Input variable
	x=T.imatrix('x')
	t=T.imatrix('t')
	
	# Embedding
	embed=theano.shared(value=np.asarray(0.2*np.random.uniform(-1.0,1.0,(LENGTH,EMBEDUNIT)),dtype=theano.config.floatX))
	
	# Parameter for LSTM layer
	W1aa=theano.shared(value=np.asarray(np.random.normal(loc=0,scale=0.05,size=(EMBEDUNIT,LSTMUNITSIZE)),dtype=theano.config.floatX),name="W1aa")
	W1ai=theano.shared(value=np.asarray(np.random.normal(loc=0,scale=0.05,size=(EMBEDUNIT,LSTMUNITSIZE)),dtype=theano.config.floatX),name="W1ai")
	W1af=theano.shared(value=np.asarray(np.random.normal(loc=0,scale=0.05,size=(EMBEDUNIT,LSTMUNITSIZE)),dtype=theano.config.floatX),name="W1af")
	W1ao=theano.shared(value=np.asarray(np.random.normal(loc=0,scale=0.05,size=(EMBEDUNIT,LSTMUNITSIZE)),dtype=theano.config.floatX),name="W1ao")
	b1a=theano.shared(value=np.asarray(np.random.normal(loc=0,scale=0.5,size=(LSTMUNITSIZE)),dtype=theano.config.floatX),name="b1a")
	b1i=theano.shared(value=np.asarray(np.random.normal(loc=0,scale=0.5,size=(LSTMUNITSIZE)),dtype=theano.config.floatX),name="b1i")
	b1f=theano.shared(value=np.asarray(np.random.normal(loc=0,scale=0.5,size=(LSTMUNITSIZE)),dtype=theano.config.floatX),name="b1f")
	b1o=theano.shared(value=np.asarray(np.random.normal(loc=0,scale=0.5,size=(LSTMUNITSIZE)),dtype=theano.config.floatX),name="b1o")
	W1ba=theano.shared(value=np.asarray(np.random.normal(loc=0,scale=0.05,size=(LSTMUNITSIZE,LSTMUNITSIZE)),dtype=theano.config.floatX),name="W1ba")
	W1bi=theano.shared(value=np.asarray(np.random.normal(loc=0,scale=0.05,size=(LSTMUNITSIZE,LSTMUNITSIZE)),dtype=theano.config.floatX),name="W1bi")
	W1bf=theano.shared(value=np.asarray(np.random.normal(loc=0,scale=0.05,size=(LSTMUNITSIZE,LSTMUNITSIZE)),dtype=theano.config.floatX),name="W1bf")
	W1bo=theano.shared(value=np.asarray(np.random.normal(loc=0,scale=0.05,size=(LSTMUNITSIZE,LSTMUNITSIZE)),dtype=theano.config.floatX),name="W1bo")
	vs=theano.shared(value=np.asarray(np.zeros(shape=(MINIBATCHSIZE,LSTMUNITSIZE)),dtype=theano.config.floatX),name="vs")
	vh=theano.shared(value=np.asarray(np.zeros(shape=(MINIBATCHSIZE,LSTMUNITSIZE)),dtype=theano.config.floatX),name="vh")
	
	# Parameter for fully connected layer
	W2=theano.shared(value=np.asarray(np.random.normal(loc=0,scale=0.05,size=(LSTMUNITSIZE,OUTPUTSIZE)),dtype=theano.config.floatX),name="W2")
	b2=theano.shared(value=np.asarray(np.random.normal(loc=0,scale=0.5,size=(OUTPUTSIZE)),dtype=theano.config.floatX),name="b2")
	
	# Network calculation
	u=embed[x]
	mu=u.dimshuffle((1,0,2))
	mt=t.dimshuffle((1,0))
	liparameter=[W1aa,W1ai,W1af,W1ao,b1a,b1i,b1f,b1o,W1ba,W1bi,W1bf,W1bo]
	[vh,vs],liscanupdate=theano.scan(fn=lstmforward,sequences=mu,truncate_gradient=-1,outputs_info=[vh,vs],non_sequences=liparameter)
	mu=T.dot(vh,W2)+b2
	(ud1,ud2,ud3)=mu.shape
	y=T.nnet.softmax(mu.reshape((ud1*ud2,ud3))).reshape((ud1,ud2,ud3))
	(td1,td2)=mt.shape
	tscost=T.mean(T.nnet.categorical_crossentropy(T.nnet.softmax(mu.reshape((ud1*ud2,ud3))),mt.reshape((td1*td2,))).reshape((ud1,ud2)))
	
	# Calculating gradient
	liparameter.append(W2)
	liparameter.append(b2)
	ligradient=T.grad(cost=tscost,wrt=liparameter)
	
	# Optimization setting
	liupdate=adam(liparameter,ligradient)
	
	# Generating function
	trainer=theano.function(inputs=[x,t],outputs=[tscost],updates=liupdate)
	my=T.argmax(y.reshape((ud1*ud2,ud3)),axis=1).reshape((ud1,ud2)).dimshuffle((1,0))
	predictor=theano.function(inputs=[x],outputs=[my,vs])
	
	# Learning
	for i in range(MAXEPOCH):
		liindex=np.random.permutation(SAMPLESIZE)
		costvalue=0
		for j in range(MINIBATCHNUMBER):
			start=j*MINIBATCHSIZE
			end=(j+1)*MINIBATCHSIZE
			costvalue+=trainer(lix[liindex[start:end],:],lit[liindex[start:end],:])[0]
		costvalue=costvalue/MINIBATCHNUMBER
		if (i+1)%100==0:
			print("Epoch {0:8d}: Training cost={1:9.5f}".format(i+1,float(costvalue)))
			for j in range(MINIBATCHNUMBER):
				[result,cellvalue]=predictor(lix[j*MINIBATCHSIZE:(j+1)*MINIBATCHSIZE,:])
				print(np.hstack((lix[j*MINIBATCHSIZE:(j+1)*MINIBATCHSIZE,0:1],result)))

def adam(liparameter,ligradient,a=0.001,b1=0.9,b2=0.999,e=1e-6):
	liupdate=[]
	t=theano.shared(value=np.float32(1),name="t")
	liupdate.append((t,t+1))
	for pc,gc in zip(liparameter,ligradient):
		mc=theano.shared(value=np.zeros(pc.get_value().shape,dtype=theano.config.floatX),name='mc')
		vc=theano.shared(value=np.zeros(pc.get_value().shape,dtype=theano.config.floatX),name='vc')
		mn=b1*mc+(1-b1)*gc
		vn=b2*vc+(1-b2)*gc**2
		mh=mn/(1-b1**t)
		vh=vn/(1-b2**t)
		pn=pc-(a*mh)/(T.sqrt(vh+e))
		liupdate.append((mc,mn))
		liupdate.append((vc,vn))
		liupdate.append((pc,pn))
	return liupdate

def lstmforward(x,vh,vs,W1aa,W1ai,W1af,W1ao,b1a,b1i,b1f,b1o,W1ba,W1bi,W1bf,W1bo):
	va=T.tanh(T.dot(x,W1aa)+b1a+T.dot(vh,W1ba))
	vi=T.nnet.sigmoid(T.dot(x,W1ai)+b1i+T.dot(vh,W1bi))
	vf=T.nnet.sigmoid(T.dot(x,W1af)+b1f+T.dot(vh,W1bf))
	vo=T.nnet.sigmoid(T.dot(x,W1ao)+b1o+T.dot(vh,W1bo))
	vs=va*vi+vf*vs
	vh=vo*T.tanh(vs)
	return vh,vs

if __name__ == '__main__':
	main()

以上のプログラムの名前を lstm-classification.py とすると,これは以下のようなコマンドで実行することができる.

$ THEANO_FLAGS=mode=FAST_RUN,device=cpu,floatX=float32 lstm-classification.py

結果は以下のようになる.ちゃんと文脈が学習できている.

Epoch      100: Training cost=  0.36392
[[0 0 1 2 3 4 5 6 7 8 9 9]
 [1 0 1 2 3 4 5 6 7 8 9 1]
 [2 0 1 2 3 4 5 6 7 8 9 1]
 [3 0 1 2 3 4 5 6 7 8 9 1]
 [4 0 1 2 3 4 5 6 7 8 9 1]]
[[5 0 1 2 3 4 5 6 7 8 9 9]
 [6 0 1 2 3 4 5 6 7 8 9 9]
 [7 0 1 2 3 4 5 6 7 8 9 9]
 [8 0 1 2 3 4 5 6 7 8 9 9]
 [9 0 1 2 3 4 5 6 7 8 9 9]]
.
.
.
Epoch     1000: Training cost=  0.00692
[[0 0 1 2 3 4 5 6 7 8 9 0]
 [1 0 1 2 3 4 5 6 7 8 9 1]
 [2 0 1 2 3 4 5 6 7 8 9 2]
 [3 0 1 2 3 4 5 6 7 8 9 3]
 [4 0 1 2 3 4 5 6 7 8 9 4]]
[[5 0 1 2 3 4 5 6 7 8 9 5]
 [6 0 1 2 3 4 5 6 7 8 9 6]
 [7 0 1 2 3 4 5 6 7 8 9 7]
 [8 0 1 2 3 4 5 6 7 8 9 8]
 [9 0 1 2 3 4 5 6 7 8 9 9]]
Hatena Google+