実装関連事項

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

Theano は深層学習に使える微分のライブラリ.Python から利用することができる.若干のとっつきにくさがあるが,慣れると煩雑な書き方をたくさん覚えなくて良いという点から,他のフレームワークより簡単に使うことができる.Theano の基本的な使い方は以下にまとめることで大体全部で,これらを抑えたら複雑なネットワークも何とかなる.

Theanoのインストール

特に何も考えずに以下のコマンド一発でインストールできる.

$ pip3 install theano

Theanoの起動方法

スクリプト名を theano-script.py とした場合,以下のよう打つと CPU のみを使って実行できる.

$ THEANO_FLAGS=mode=FAST_RUN,device=cpu,floatX=float32 python3 theano-script.py

以下のよう打つと GPU を使って実行できる.環境変数 THEANO_FLAGS や floatX 等は設定ファイルに記述しておいても良い.

$ THEANO_FLAGS=mode=FAST_RUN,device=gpu,floatX=float32 python3 theano-script.py

シンボル

Theano では,シンボルと呼ばれる変数を使う.これは,Python の変数とは明らかに異なる.以下のように書くと,32ビット小数点のスカラーシンボル a,ベクトルシンボル b,マトリックスシンボル c,32ビット整数のスカラーシンボル x,ベクトルシンボル y,マトリックスシンボル z が生成される.f は float,i は int を意味する.name というところにはわかり易い名前を付ける.深層学習を行う場合は大体以下のシンボルさえ使えたら十分.むしろ,fmatrix と imatrix くらいしか使うことはない.

#!/usr/bin/env python3
# -*- coding: utf-8 -*-
import numpy as np
import theano
import theano.tensor as T

def main():
	a=T.fscalar(name="a")
	b=T.fvector(name="b")
	c=T.fmatrix(name="c")
	x=T.iscalar(name="x")
	y=T.ivector(name="y")
	z=T.imatrix(name="z")
	print(a,b,c,x,y,z)

if __name__ == '__main__':
	main()

これを実行した結果は以下のようになる.何も起こらない.このシンボルというものは10とか200とかの実際の値は持たない.シンボルはこれだけで使うことはなく,以下で示す関数と組み合わせて使う.

a b c x y z

関数

Theano では関数と呼ばれるものを生成することができる.以下のように,theano.function() で作る.inputs のところには何らかのシンボルをリストで指定する.これに対して,outputs のところにはそれらのシンボルを使って表現したい関数を指定する.すなわち,以下のようにすると,f(x,y)=x+y という関数が使えるようになる.ここで,dscalar の d は64ビット小数点を表す double.

#!/usr/bin/env python3
# -*- coding: utf-8 -*-
import numpy as np
import theano
import theano.tensor as T

def main():
	x=T.dscalar(name="x")
	y=T.dscalar(name="y")
	f=theano.function(inputs=[x,y],outputs=[x+y])
	print(f(1,2)[0])

if __name__ == '__main__':
	main()

これを実行した結果は以下のようになる.f(x,y)=x+y に対して,f(1.0,2.0) を計算したため出力は 3.0 となる.ここで,f(1,2)[0] のように [0] を指定しているのは,outputs=[] で指定したように,この関数の出力がリストであり,そのリストの最初の値を表示させたいため.

3.0

関数を間接的な変数で処理する

関数の引数に givens を指定すると,その関数を inputs で指定した変数から定義する新たな変数で扱うことができる.以下の場合,c+2 が x,c2 が y に代入され,その x と y を用いた式を outputs に指定することができるようになる.

#!/usr/bin/env python3
# -*- coding: utf-8 -*-
import numpy as np
import theano
import theano.tensor as T

def main():
	x=T.dscalar(name="x")
	y=T.dscalar(name="y")
	c=T.dscalar(name="c")
	f=theano.function(inputs=[c],outputs=[x**2+y],givens=[(x,c+2),(y,c*c)]) # この場合,c+2がx,c*cがyとなる
	print(f(2))

if __name__ == '__main__':
	main()

結果は以下のようになる.これは,f(x,y)=x2+y,x=c+2,y=c2 に対して,c=2 を計算した結果である.

20

以上のプログラムは以下のように書き換えることができる.こっちの方がわかり易い.実際,givens を使うことは滅多にない.

#!/usr/bin/env python3
# -*- coding: utf-8 -*-
import numpy as np
import theano
import theano.tensor as T

def main():
	c=T.dscalar(name="c")
	x=c+2
	y=c*c
	f=theano.function(inputs=[c],outputs=[x**2+y])
	print(f(2)[0])

if __name__ == '__main__':
	main()

結果は以下のように上と同じになる.こういう書き方は普通のプログラミング言語の常識からかけ離れており,少し違和感がある.

20

勾配の計算

勾配を自動で計算ができるのが Theano の最も優れている点である.勾配は T.grad() で計算する.中身の cost には微分の対象の値を指定し,wrt には何について微分するかを指定する.wrt は with regard to の略.以下のように書くと,z=(x+4y)3 を x について微分したもの (gzx) と,y について微分したもの (gzy) の値を計算することができる.

#!/usr/bin/env python3
# -*- coding: utf-8 -*-
import numpy as np
import theano
import theano.tensor as T

def main():
	x=T.dscalar(name="x")
	y=T.dscalar(name="y")
	z=(x+4*y)**3
	gzx=T.grad(cost=z,wrt=x)
	gzy=T.grad(cost=z,wrt=y)
	fgzx=theano.function(inputs=[x,y],outputs=[gzx])
	fgzy=theano.function(inputs=[x,y],outputs=[gzy])
	print(fgzx(8,2)[0])
	print(fgzy(4,2)[0])

if __name__ == '__main__':
	main()

結果は以下のようになる.勾配が得られた.

768.0
1728.0

共有変数

Theano には共有変数と呼ばれる仕組みがある.theano.shared() で生成する.計算の途中で値を保持しておいたり,値を変更したりしたいときに用いる.この共有変数こそが,普通の Python 等のプログラミング言語の普通の変数に近い変数である.共有変数はシンボルと異なり,初期化の段階で value に実際の値を割り当てる.以下のように書くと,[2,4,6,8] が格納された共有変数c を用いた関数,f(x)=cx に対して f(2) を計算した値を出力した後に,c を set_value() を用いて c=[100,200,300] で書き換えた値を使って,f(2) の解を出力できる.

#!/usr/bin/env python3
# -*- coding: utf-8 -*-
import numpy as np
import theano
import theano.tensor as T

def main():
	x=T.dscalar(name="x")
	c=theano.shared(value=np.array([2,4,6,8]),name="c")
	f=theano.function(inputs=[x],outputs=[c*x])
	print(f(2)[0])
	c.set_value(np.array([100,200,300]))
	print(f(2)[0])

if __name__ == '__main__':
	main()

結果は以下のようになる.主にこの共有変数と勾配を求める方法と以下の値を更新する方法を駆使することで深層学習のプログラムを書くことができる.

[  4.   8.  12.  16.]
[ 200.  400.  600.]

値の更新

上では,共有変数c の値の書き換え (更新) を set_value() を用いて行ったが,これと同じことを,関数を実行するときに updates を使って実現することができる.以下のように updates=[(c,c+1)] と書くと,関数が1回実行される度に c←c+1 という更新がされる.updates はリストで指定するが,リストの各要素はタプル () で表されたペアの変数を指定する.タプルでなくて,標準ライブラリ collenctions にある collenctions.OrderedDisct() でも良い.以下のように書くと,関数 f() が合計6回実行されるが,その都度,c の値は更新 (1ずつ増加) され,それに対して c2 が計算,出力される.

#!/usr/bin/env python3
# -*- coding: utf-8 -*-
import numpy as np
import theano
import theano.tensor as T

def main():
	c=theano.shared(value=np.array([0]),name="c")
	f=theano.function(inputs=[],outputs=[c*c],updates=[(c,c+1)])
	print(c.get_value(),f()[0])
	print(c.get_value(),f()[0])
	print(c.get_value(),f()[0])
	print(c.get_value(),f()[0])
	print(c.get_value(),f()[0])
	print(c.get_value(),f()[0])

if __name__ == '__main__':
	main()

結果は以下のようになる.

[0] [0]
[1] [1]
[2] [4]
[3] [9]
[4] [16]
[5] [25]

勾配降下法

Theano を用いると勾配降下法を実装することができる.以下のような簡単な例の場合,スクラッチで書いた方が絶対に簡潔に書けるが,大きな問題を扱う場合には威力を発揮する.ここでは,以下のようなベクトル $\boldsymbol{x}$ を考える.

\begin{eqnarray*}\boldsymbol{x}&=&(x_1,x_2,x_3,x_4)\\&=&(1,2,3,4)\tag{1}\end{eqnarray*}

これに対して,以下の $y$ を考える.

\begin{eqnarray*}y=1+\sum_{i=1}^{4}(x_i-w_i)\tag{2}\end{eqnarray*}

この $y$ を最小化するベクトル $\boldsymbol{w}=(w_1,w_2,w_3,w_4)$ を勾配降下法を使って求めたい.式2は展開すると以下のようになるので,求めたい解 $\boldsymbol{w}$ は当然 $\boldsymbol{w}=(1,2,3,4)$ である.そのときの $y$ の最小値は1となる.

\begin{eqnarray*}y=1+(1-w_1)+(2-w_2)+(3-w_3)+(4-w_4)\tag{3}\end{eqnarray*}

勾配降下法を用いて,これらの値が得られたら良いということになる.そのためのプログラムは以下のように書ける.$\boldsymbol{w}$ には最初は乱数を使って適当な値をアサインする.これは,np.random.random(4) でできる.こう書くことで,ランダムな値が格納された4次元ベクトルを生成できる.式2は Theano の関数 T.sum() を使って簡単に書くことができる.実直に書いても良い.gyw は y の勾配.ここで,lr は学習率 (learning rate) とかステップサイズα と呼ばれる値であり,勾配降下法で値を更新する際にどれくらいの大きさで値を更新するかを決定するためのパラメーターである.以下の場合は適当に0.4を与える.これが大きすぎるといつまでたっても極小値に値が到達しないし,小さすぎると極小値に到達するために長い時間が必要となる.次の f が学習の本体であり,この updates に $\boldsymbol{w}$ の更新式を指定する.最後に,for でその更新式を30回実行する.その最中に,5回更新の度に,学習率に0.9を掛けて,少しずつ小さくしていく.

#!/usr/bin/env python3
# -*- coding: utf-8 -*-
import numpy as np
import theano
import theano.tensor as T

def main():
	x=T.dvector(name="x")
	w=theano.shared(value=np.random.random(4),name="w")
	y=T.sum((x-w)**2)+1
	gyw=T.grad(cost=y,wrt=w)
	lr=theano.shared(value=np.float32(0.4),name="lr")
	f=theano.function(inputs=[x],outputs=[y],updates=[(w,w-lr*gyw)])
	for i in range(30):
		print("Epoch: ",i+1," y=",f([1,2,3,4])[0],"\tw=",w.get_value(),sep="")
		if (i+1)%5==0: lr.set_value(lr.get_value()*np.float32(0.9))

if __name__ == '__main__':
	main()

上のプログラムを実行した結果は以下のようになる.エポックが進むにつれて,目標の値に近づき,最終的に理論値と等しい最初値と $\boldsymbol{w}$ の値が得られる.

Epoch: 1 y=20.53888326876192    w=[ 0.909762706164  1.743037888591  2.520552703792  3.308976677788]
Epoch: 2 y=1.7815552375818242   w=[ 0.981952542309  1.948607580781  2.904110546474  3.861795343795]
Epoch: 3 y=1.0312622057765273   w=[ 0.996390508677  1.989721516769  2.980822110438  3.972359070407]
Epoch: 4 y=1.0012504880819912   w=[ 0.999278101778  1.997944303476  2.996164422316  3.994471814411]
Epoch: 5 y=1.0000500195173168   w=[ 0.999855620364  1.99958886072   2.999232884509  3.998894362948]
Epoch: 6 y=1.0000020007804542   w=[ 0.999959573698  1.999884880989  2.999785207639  3.999690421591]
Epoch: 7 y=1.0000001568612222   w=[ 0.999988680634  1.999967766673  2.999939858132  3.999913318036]
Epoch: 8 y=1.0000000122979225   w=[ 0.999996830577  1.999990974668  2.999983160275  3.999975729047]
Epoch: 9 y=1.0000000009641574   w=[ 0.999999112562  1.999997472907  2.999995284877  3.999993204133]
Epoch: 10 y=1.00000000007559    w=[ 0.999999751517  1.999999292414  2.999998679765  3.999998097157]
Epoch: 11 y=1.0000000000059261  w=[ 0.999999912534  1.99999975093   2.999999535277  3.999999330199]
Epoch: 12 y=1.0000000000007343  w=[ 0.999999969212  1.999999912327  2.999999836418  3.99999976423 ]
Epoch: 13 y=1.000000000000091   w=[ 0.999999989163  1.999999969139  2.999999942419  3.999999917009]
Epoch: 14 y=1.0000000000000113  w=[ 0.999999996185  1.999999989137  2.999999979731  3.999999970787]
Epoch: 15 y=1.0000000000000013  w=[ 0.999999998657  1.999999996176  2.999999992865  3.999999989717]
Epoch: 16 y=1.0000000000000002  w=[ 0.99999999944   1.999999998406  2.999999997026  3.999999995714]
Epoch: 17 y=1.0 w=[ 0.999999999767  1.999999999336  2.999999998761  3.999999998214]
Epoch: 18 y=1.0 w=[ 0.999999999903  1.999999999723  2.999999999483  3.999999999255]
Epoch: 19 y=1.0 w=[ 0.999999999959  1.999999999885  2.999999999785  3.99999999969 ]
Epoch: 20 y=1.0 w=[ 0.999999999983  1.999999999952  2.99999999991   3.999999999871]
Epoch: 21 y=1.0 w=[ 0.999999999992  1.999999999977  2.999999999957  3.999999999939]
Epoch: 22 y=1.0 w=[ 0.999999999996  1.999999999989  2.99999999998   3.999999999971]
Epoch: 23 y=1.0 w=[ 0.999999999998  1.999999999995  2.99999999999   3.999999999986]
Epoch: 24 y=1.0 w=[ 0.999999999999  1.999999999998  2.999999999995  3.999999999993]
Epoch: 25 y=1.0 w=[ 1.              1.999999999999  2.999999999998  3.999999999997]
Epoch: 26 y=1.0 w=[ 1.              1.999999999999  2.999999999999  3.999999999998]
Epoch: 27 y=1.0 w=[ 1.              2.              2.999999999999  3.999999999999]
Epoch: 28 y=1.0 w=[ 1.  2.  3.  4.]
Epoch: 29 y=1.0 w=[ 1.  2.  3.  4.]
Epoch: 30 y=1.0 w=[ 1.  2.  3.  4.]

デバッグ補助

Theano が他のプログラミング言語やフレームワークと比べて,とっつきにくいのは実際の値が見えない点にある.普通,コードはトライアンドエラーで少しずつ修正を加えられながら完成される.Theano はここが難しい.その点を若干改善する方法として,eval() という仕組みがある.以下のように eval() を使用すると,ループの中でどのように値が変化しているかを知ることができる.この機能を使うことで,変な値が出ているところに目星を付けてコードを改善する.

#!/usr/bin/env python3
# -*- coding: utf-8 -*-
import numpy as np
import theano
import theano.tensor as T

def main():
	lr=theano.shared(value=np.float32(0.4),name="lr")
	for i in range(30):
		print(lr,lr.eval())
		if (i+1)%5==0: lr.set_value(lr.get_value()*np.float32(0.9))

if __name__ == '__main__':
	main()

これを実行した結果は以下のようになる.

lr 0.4000000059604645
lr 0.35999998450279236
lr 0.32399997115135193
lr 0.29159995913505554
lr 0.2624399662017822
lr 0.23619596660137177
lr 0.21257635951042175
lr 0.19131872057914734
lr 0.17218683660030365
lr 0.15496814250946045
Hatena Google+