Theano Tutorial 1 - Variables and Function

Jan 29, 2017   #Python  #Machine Learning 

はじめに

 theano 0.8のチュートリアル(Basics)を読んだので、すぐに陳腐化すると思うけど簡単にまとめた。
 この記事では、変数と関数についてまとめた。
 尚、theanoは、多次元配列を含む数式を効率よく定義、最適化、評価可能なPythonライブラリである。

シンボル変数とtheano関数

 theanoでは、シンボル変数(symbolic variable)を組み合わて、関数(theano.function)を構築する(コンパイル)。 以下の例ではまず、T.dvector, T.dmatrix, T.dscalarで、3種類のシンボル変数を定義している。シンボル変数は、次元と型を組み合わせた複数の種類がある1。 次に、theano.functionを使ってシンプルなtheano関数を定義している。様々なオプションがあるが、第一引数に入力シンボル変数のlistを、第二引数で戻り値(シンボル変数z)を指定している。戻り値にlistを使用することで、複数の戻り値も指定できる。

import numpy as np
from theano import tensor as T
from theano import function
from theano import pp
from theano import In

# double型のベクタ変数を定義
X = T.dvector('X')
# double型のマトリクス変数を定義
# name属性を明示的に指定
W = T.dmatrix(name='W')
# double型のスカラ変数を定義
b = T.dscalar('b')

assert b.type is T.dscalar # TensorType(float64, scalar)
# dscalarはclassではなく、TensorVariableのインスタンス
assert isinstance(b, T.TensorVariable)

z = (1/2) * X * W + b
# pp(pretty-print)で変数の情報を出力できる
# ここでは、変数のname属性の値が使われる
assert pp(z) == '(((TensorConstant{0.5} * X) * W) + b)'

# 引数が[X, W, b]で、戻り値がzの関数を定義
# tensor.Inで、デフォルト値の設定も可能。
f = function([X, W, In(b, value=0.1)], z)
expect = np.array([[0.1, 1.6], [0.1, 0.6]])
# numpy.arrayを引数として直接取ることができる
real = f(np.array([0, 1]), [[2, 3], [3, 1]])
assert (expect == real).all()

共有変数と乱数生成

 共有変数(shared variable)は、theano関数の内部状態を表現できる。共有変数は、初期値を持ち、更新可能で、複数の関数で共有することができる。 以下の例では、内部状態stateを持つ、theano関数を定義している。 updatesパラメタは、状態の更新式を設定するもので、tuple((共有変数, 更新式))のlistを渡す。
 さて、theano関数のその他の振る舞いとして、givensパラメタとcopyメソッドがある。 このgivensパラメタは、特定の関数においてシンボル変数(共有変数に限らない)の値を置き換えたい時に(代入ではない)使用できる。 またcopyメソッドは、類似する関数で異なる状態を保持したいときに利用できる。
 最後に、乱数生成の簡単な例を書いている。

import numpy as np
from theano import tensor as T
from theano import function
from theano import pp
from theano import In
from theano import shared
from theano.tensor.shared_randomstreams import RandomStreams


state = shared(0, name='state')
inc = T.iscalar('inc')
accumulator = function([inc],
                       state,
                       updates=[(state, state+inc)])

# 共有変数には、setterとgetterがある。
assert state.get_value() == 0
accumulator(3)
assert state.get_value() == 3
state.set_value(-1)
assert state.get_value() == -1

base = T.scalar(dtype=state.dtype, name='base')
offset = T.iscalar('offset')
z = state + offset
count_from = function([base, offset],
                      z,
                      givens=[(state, base)])
assert count_from(1000, 10) == 1010
# 代入されたわけではない。
assert state.get_value() == -1

# swapパラメタで、対応する共有変数にマップする。
another_state = shared(0)
another_accumulator = accumulator.copy(swap={state: another_state})
accumulator(11)
assert state.get_value() == 10
another_accumulator(100)
assert another_state.get_value() == 100


srng = RandomStreams(seed=234)
# 乱数生成器にnormal(正規分布)を指定。
rv_n = srng.normal((2,2))
# no_default_updatesをTrueに指定すると内的状態は更新されない(この場合同じ乱数が生成される)。
f = function([], rv_n, no_default_updates=True)
assert f().all() == f().all()

ロジスティック回帰

 以上を踏まえて、以下にtheanoでのロジスティック回帰の例を示す。ここでは、theanoが持つ基本的な数値計算関数や、自動微分を使用している。

import numpy
import theano
import theano.tensor as T
rng = numpy.random

sample_N = 400
feature_N = 784
training_steps = 1000
threshold = 0.5

train_X, train_y = (rng.randn(sample_N, feature_N),
                    rng.randint(size=sample_N, low=0, high=2))

X = T.dmatrix("X")
y = T.dvector("y")
w = theano.shared(rng.randn(feature_N), name="w")
b = theano.shared(0., name="b")

# Construct Theano expression graph
p_1 = 1 / (1 + T.exp(-T.dot(X, w) - b))
prediction = p_1 > threshold
xent = -y * T.log(p_1) - (1-y) * T.log(1-p_1)
cost = xent.mean() + 0.01 * (w ** 2).sum()
gw, gb = T.grad(cost, [w, b])

train = theano.function(
          inputs=[X,y],
          outputs=[prediction, xent],
          updates=((w, w - 0.1 * gw), (b, b - 0.1 * gb)))
predict = theano.function(inputs=[X], outputs=prediction)

# Train
for i in range(training_steps):
    pred, err = train(train_X, train_y)

pred_y = predict(train_X)
print("model params:")
print('w:', w.get_value())
print('b:', b.get_value())
print("target values for D:")
print(train_y)
print("prediction on D:")
print(pred_y)
print('accuracy:', (train_y == pred_y).sum() / len(pred_y))

おわりに

 theanoの自動微分は便利だが、それ以外にもチュートリアルにはヤコビアン、ヘッセ行列の計算や、scipyの疎行列との統合、マルチGPU対応などもある。次回(Theano Tutorial 2 - Derivatives, Conditions, Loop) はその辺をまとめてみる予定。

参考

関連記事