theano_tutorial

2915 days ago by takepwave

Hiroshi TAKEMOTO (take.pwave@gmail.com)

Sageノートブックを使ったTheano入門

theanoの参考サイト

Theanoに限らず機械学習の分野では 人工知能に関する断創録 の素晴らしさがが突出しています。

ここでは、 人工知能に関する断創録 のTheanoに関連する記事をSageのノートブックで実装し、Thenoの修得を試みます。

最近では、誰でも簡単にDeeepLearningを体験できるtensorflowが注目されていますが、 「人工知能に関する断創録」の作者が言われているように 「TheanoはDeep Learning Tutorialをはじめ、実装例が豊富にあり、絶妙な粒度で小回りもきくので手法の勉強にちょうどよいんだよね。」 というのがとても納得できます。

今回は、 深層学習 (機械学習プロフェッショナルシリーズ) をベースに以下の内容をSageのノートブックで試します。

theanoのインストール

Sageでtheanoを使うには、以下の手順でtheanoのインストールが必要です。

$ sage -sh
(sage-sh)  $ easy_install theano
Searching for theano
Reading https://pypi.python.org/simple/theano/
Best match: Theano 0.8.2

途中省略
Installed /Users/take/local/sage-6.9/local/lib/python2.7/site-packages/Theano-0.8.2-py2.7.egg
Processing dependencies for theano
Finished processing dependencies for theano

(sage-sh)  $ exit			
		
  • 私の公開している Sageサーバ にもtheano 0.8.2をインストールしました

Theanoの使い方

必要なライブラリのimport

最初に、theanoを使うのに必要なライブラリをインポートします。

import numpy as np import theano import theano.tensor as T 
       

変数宣言

Sageと同様にTheanoも関数を定義するために、 変数(Theanoではシンボル: TensorVariableと呼ぶらしい)を宣言します。

Theanoの変数宣言は以下の様なルールになっています。

変数 = T.型タイプ()			
		
型には、d: Double、f: Float、l: Longが指定され、 タイプには、scalar: 値、vector: ベクトル、matrix: 行列が指定されます。

Theanoでは、シンボルで定義した関数を実行時にコンパイルします。

それでは、人工知能に関する断創録の例題に沿って、変数の宣言、関数の定義、関数のコンパイルを実行してみましょう。

# シンボルの生成 # xはdoubleのスカラー型 x = T.dscalar('x') print type(x) 
       
<class 'theano.tensor.var.TensorVariable'>
<class 'theano.tensor.var.TensorVariable'>
# シンボルを組み立てて数式を定義(これもまたシンボル) y = x ** 2 print type(y) 
       
<class 'theano.tensor.var.TensorVariable'>
<class 'theano.tensor.var.TensorVariable'>
# シンボルを使って関数を定義 # ここでコンパイルされる f = theano.function(inputs=[x], outputs=y) print type(f) 
       
<class 'theano.compile.function_module.Function'>
<class 'theano.compile.function_module.Function'>
# 関数を使ってxに具体的な値を入れてyを計算 print f(1) print f(2) print f(3) print f(4) 
       
1.0
4.0
9.0
16.0
1.0
4.0
9.0
16.0

テンソル

Theanoでは、スカラー、ベクトル、行列などを抽象化した概念テンソル(Tensor)を導入し、 theano.tensorで定義されており、インポート部でこれをTと宣言し、同じ関数定義を ベクトルや行列に簡単に拡張することができます。

x = T.dvector('x') y = x ** 2 f = theano.function(inputs=[x], outputs=y) print f([1,2,3]) 
       
[ 1.  4.  9.]
[ 1.  4.  9.]
x = T.dmatrix('x') y = x ** 2 f = theano.function(inputs=[x], outputs=y) print f([[1,2,3], [4,5,6]]) 
       
[[  1.   4.   9.]
 [ 16.  25.  36.]]
[[  1.   4.   9.]
 [ 16.  25.  36.]]

シグモイド関数の例

ニューラルネットの活性化関数で使われているシグモイド関数、 $$ s(x) = \frac{1}{1 - e^{-x}} $$ をTheanoで実装します。

その前に、Sageのplot関数を使ってシグモイド関数の形を見てみましょう。

# シグモイド関数をSageでプロット x = var('x') s(x) = 1/(1 + exp(-x)) plot(s(x), [x, -5, 5], figsize=5) 
       

Theanoでの実装は以下の様になります。

入力を行列の形式で与えているので、結果も各入力値に対するシグモイド関数の値が行列として返されます。

# Theanoでシグモイド関数を定義 ## シンボルを定義 x = T.dmatrix('x') ## シンボルを組み合わせて数式を定義 s = 1 / (1 + T.exp(-x)) ## シンボルを使って関数化 sigmoid = theano.function(inputs=[x], outputs=s) # 実際の値を使って計算 print sigmoid([[0, 1], [-1, -2]]) 
       
[[ 0.5         0.73105858]
 [ 0.26894142  0.11920292]]
[[ 0.5         0.73105858]
 [ 0.26894142  0.11920292]]

共有変数

Theanoの共有変数がGPUのメモリと密接に関係しているという説明を読んで、GPUのメモリに合わせてfloat型 (theano.config.floatX)にしたり、値の取得にget_value()というメソッドを使っていることが納得できました。

私の環境ではGPUが使えないため、type(X)の型は、theano.tensor.sharedvar.TensorSharedVariableでした。

# データを共有変数に読み込む data = np.array([[1,2,3], [4,5,6]], dtype=theano.config.floatX) X = theano.shared(data, name='X', borrow=True) print type(X) print X.get_value() 
       
<class 'theano.tensor.sharedvar.TensorSharedVariable'>
[[ 1.  2.  3.]
 [ 4.  5.  6.]]
<class 'theano.tensor.sharedvar.TensorSharedVariable'>
[[ 1.  2.  3.]
 [ 4.  5.  6.]]

線形回帰モデルの例

共有変数の例として、線形回帰のモデル式が使われています。 回帰モデルは以下の式で、Wとbが共有変数としてyを求めてみます。 $$ y = W x + b $$

共有変数は値を取得するときにはget_valueが必要ですが、関数を表す式を定義するときにはシンボルと同様に 使えるのは良く出来ているとなぁと思いました。

# 共有変数の定義 # 具体的な数値で初期化 W = theano.shared(np.array([[1,2,3], [4,5,6]], dtype=theano.config.floatX), name='W', borrow=True) b = theano.shared(np.array([1, 1], dtype=theano.config.floatX), name='b', borrow=True) 
       
# 共有変数の値の取得 print W.get_value() print b.get_value() 
       
[[ 1.  2.  3.]
 [ 4.  5.  6.]]
[ 1.  1.]
[[ 1.  2.  3.]
 [ 4.  5.  6.]]
[ 1.  1.]
# シンボルの生成 x = T.vector('x') # シンボルと共有変数を組み立てて数式を定義 y = T.dot(W, x) + b print type(y) # 関数を定義 f = theano.function([x], y) 
       
<class 'theano.tensor.var.TensorVariable'>
<class 'theano.tensor.var.TensorVariable'>
# 関数の使用 print f([1, 1, 1]) 
       
[  7.  16.]
[  7.  16.]

Theanoの自動微分

Theanoの特徴の一つに自動微分があります。ニューラルネットの計算で、入力値から予測値を計算するフィードフォワードに対し、 予測結果と正解のずれを誤差関数Eとして表し、重みWの値を更新するバックプロパゲーションでは、誤差関数Eの微分が必要になります。 $$ w^{\tau+1)} = w^{(\tau)} - \mu \Delta E(w^{\tau}) $$

PRMLの第5章にある多層パーセプトロン関数近似をSageで試した sage/PRML- 多層パーセプトロン関数近似 では、PRMLの式の変換を手でお復習いして、確認しました。 この作業はとても手間が掛かり、ミスも多くなります。

(注 特にニューラルネットの計算はとても時間が掛かるため、実務でニューラルネットを使った場合、式のミスをリカバリすのはとても大変です。)

例1 2次関数

以下のような2次関数の微分をTheanoの自動微分を使って計算してみましょう。 $$ y = x^2 $$ 高校で習った微分の公式から、以下の様になりますね。 $$ \frac{dy}{dx} = 2 x $$

これをTheanoの自動微分を使って解いてみましょう。

微分は、T.grad()のcostに微分される式のシンボル(y)、wrtに微分する変数のシンボルを与えて計算します。

# シンボルxを宣言する x = T.dscalar('x') # 微分される数式のシンボルを定義 y = x ** 2 # yをxに関して微分 gy = T.grad(cost=y, wrt=x) 
       

微分結果のシンボル(gy)は、そのままでは使えないので、function()で関数かしてから使用します。

微分の結果を表示するには、theano.pp()を使って表示します。

# 微分係数を求める関数を定義 f = theano.function(inputs=[x], outputs=gy) print theano.pp(f.maker.fgraph.outputs[0]) 
       
(TensorConstant{2.0} * x)
(TensorConstant{2.0} * x)
# 具体的なxを与えて微分係数を求める print f(2) print f(3) print f(4) 
       
4.0
6.0
8.0
4.0
6.0
8.0

どんな時も代替手段を持っておくことが大切です。ある方法で上手く行かないとき、 なぜ正しく動かないのかを別の方法の結果と比較することで修正することができます。

Sageの計算は、numpyやTheanoのように速くありませんが、比較的数式に近い形式で計算することができます。

Sageの数式処理を使って上記の2次関数の微分を試してみましょう。diffが数式の微分を行う関数です。

# sageの微分を使って同じ処理を試してみる # 変数xを宣言 x = var('x') # yを表す数式を定義 y = x^2 # yをxで微分し、その結果をf(x)に代入する f(x) = diff(y, x) print f(x) 
       
2*x
2*x
# 具体的なxを与えて微分係数を求める print f(2) print f(3) print f(4) 
       
4
6
8
4
6
8

例3 指数関数

例2の指数関数を省略して、例3のsin(x)を計算します。 $$ y = sin(x) $$ $$ \frac{dy}{dx} = cos(x) $$ を計算します。

# シンボルxを宣言する x = T.dscalar('x') # 微分される数式のシンボルを定義 y = T.sin(x) # yをxに関して微分 gy = T.grad(cost=y, wrt=x) # 微分係数を求める関数を定義 f = theano.function(inputs=[x], outputs=gy) print theano.pp(f.maker.fgraph.outputs[0]) 
       
cos(x)
cos(x)
# 具体的なxを与えて微分係数を求める print f(0) print f(np.pi / 2) print f(np.pi) 
       
1.0
6.12323399574e-17
-1.0
1.0
6.12323399574e-17
-1.0

例4 多項式

今度は、例4の多項式です。 $$ y = (x - 4)(x^2 + 6) $$ $$ \frac{dy}{dx} = 3 x^2 - 8x + 6 $$

# シンボルxを宣言する x = T.dscalar('x') # 微分される数式のシンボルを定義 y = (x - 4) * (x ** 2 + 6) # yをxに関して微分 gy = T.grad(cost=y, wrt=x) # 微分係数を求める関数を定義 f = theano.function(inputs=[x], outputs=gy) print theano.pp(f.maker.fgraph.outputs[0]) 
       
Elemwise{Composite{(i0 + sqr(i1) + (i2 * (i3 + i1) *
i1))}}(TensorConstant{6.0}, x, TensorConstant{2.0},
TensorConstant{-4.0})
Elemwise{Composite{(i0 + sqr(i1) + (i2 * (i3 + i1) * i1))}}(TensorConstant{6.0}, x, TensorConstant{2.0}, TensorConstant{-4.0})
# 具体的なxを与えて微分係数を求める print f(0) print f(1) print f(2) 
       
6.0
1.0
2.0
6.0
1.0
2.0

結果の式のi0, i1, i2, i3は、次の括弧のTensorConstant{6.0}, x, TensorConstant{2.0}, TensorConstant{-4.0}に対応します。 これをxの式に書き直すと以下の様になります。

6 + x**2 + (2*(-4+x)*x)			
		

この式を手計算で整理する代わりにSageで確認してみましょう。dyに上記の式を代入し、expand()関数で展開してshow()で表示するだけです。 このように簡単に結果を確認できますよ。

x = var('x') dy = 6 + x**2 + (2*(-4+x)*x) show(dy.expand()) 
       

例6 偏微分

例5を省略して、例6の偏微分を試してみます。

以下の式zをx, yでそれぞれ偏微分します。 $$ z = (x + 2y)^2 $$ $$ \frac{\partial{z}}{\partial{x}} = 2(x + 2y) $$ $$ \frac{\partial{z}}{\partial{y}} = 4(x + 2y) $$

# シンボルxを宣言する x = T.dscalar('x') y = T.dscalar('y') # 微分される数式のシンボルを定義 z = (x + 2 * y) ** 2 # zをxに関して偏微分 gx = T.grad(cost=z, wrt=x) # zをyに関して偏微分 gy = T.grad(cost=z, wrt=y) # 微分係数を求める関数を定義 fgx = theano.function(inputs=[x, y], outputs=gx) fgy = theano.function(inputs=[x, y], outputs=gy) print theano.pp(fgx.maker.fgraph.outputs[0]) print theano.pp(fgy.maker.fgraph.outputs[0]) 
       
Elemwise{Composite{(i0 * (i1 + (i2 * i3)))}}(TensorConstant{2.0}, x,
TensorConstant{2.0}, y)
Elemwise{Composite{(i0 * (i1 + (i2 * i3)))}}(TensorConstant{4.0}, x,
TensorConstant{2.0}, y)
Elemwise{Composite{(i0 * (i1 + (i2 * i3)))}}(TensorConstant{2.0}, x, TensorConstant{2.0}, y)
Elemwise{Composite{(i0 * (i1 + (i2 * i3)))}}(TensorConstant{4.0}, x, TensorConstant{2.0}, y)
# 具体的な値を与えて偏微分係数を求める print fgx(1, 2) print fgx(2, 2) print fgy(1, 2) print fgy(2, 2) 
       
10.0
12.0
20.0
24.0
10.0
12.0
20.0
24.0

最後にSageで偏微分を試してみましょう。

x, yの変数をvar関数で定義し、zの数式を定義します。偏微分はdiff関数の微分変数を変えるだけです。

非常に簡単にfgx, fgyを計算できるのがお分かり頂けると思います。

# sageの微分を使って同じ処理を試してみる # 変数xを宣言 x, y = var('x y') # yを表す数式を定義 z = (x + 2 * y) ** 2 # yをxで微分し、その結果をfgx, fgyに代入する fgx(x, y) = diff(z, x) fgy(x, y) = diff(z, y) print fgx print fgy 
       
(x, y) |--> 2*x + 4*y
(x, y) |--> 4*x + 8*y
(x, y) |--> 2*x + 4*y
(x, y) |--> 4*x + 8*y
print fgx(1, 2) print fgx(2, 2) print fgy(1, 2) print fgy(2, 2) 
       
10
12
20
24
10
12
20
24