一、theano的工作原理
在theano编程中,Graph是指导theano如何对变量进行操作的唯一途径,theano变量和theano Ops(操作)是Graph的两个基本构成元素。Graph只能由theano变量(包括shared变量)或常数组成。如图所示:
通常可以按如下步骤构造Graph:首先声明theano变量,theano变量在python文件中的作用范围和普通python变量相同;然后用theano的Ops建立变量之间的联系,如T.sum(a,b);最后用theano.function把变量和变量间关系结合起来,构成一个完整的Graph。
假设已经创建了一个function,称为fn,fn=theano.function(…)。Graph中的shared变量已经包含了调用fn时需要的数据,而普通theano变量仅仅是一个占位符,需要在function中作为输入,并且在调用fn时给变量赋具体值(如numpy的array或者常数)。
二、scan函数
scan是theano中构建循环Graph的方法,函数声明如下:
theano.scan(fn, sequences=None, outputs_info=None, non_sequences=None, n_steps=None, truncate_gradient=-1, go_backwards=False, mode=None, name=None, profile=False, allow_gc=None, strict=False)
fn:函数类型,scan的一步执行。除了outputs_info,fn可以返回sequences变量的更新updates。fn的输入变量顺序为sequences中的变量,outputs_info的变量,non_sequences中的变量。如果使用了taps,则按照taps给fn喂变量,taps的详细介绍会在后面的例子中给出。
sequences:scan进行迭代的变量;scan会在T.arange()生成的list上遍历,例如下面的polynomial 例子。
outputs_info:初始化fn的输出变量,和输出的shape一致;如果初始化值设为None表示这个变量不需要初始值。
non_sequences:fn函数用到的其他变量,迭代过程中不可改变(unchange)。
n_steps:fn的迭代次数。
下面通过几个例子解释scan函数的具体使用方法。
例一、A的k次方
#==============================================================================
# A**k
#==============================================================================
k = T . iscalar('k')
A = T . vector( 'A')
outputs, updates = theano.scan(lambda result, A : result * A,
non_sequences = A, outputs_info=T.ones_like(A), n_steps = k)
result = outputs [-1]
fn_Ak = theano . function([A,k ], result, updates=updates )
print fn_Ak( range(10 ), 2 )
结果:
[ 0. 1. 4. 9. 16. 25. 36. 49. 64. 81.]
- 1
- 2
- 3
- 4
- 5
- 6
- 7
- 8
- 9
- 10
- 11
- 12
- 13
- 14
程序输出结果A*A。outputs_info初始化为和A相同大小的全1向量,匿名(lambda)函数的输入依次为outputs_info,non_sequences ,对应于匿名函数的输入result和A。由于scan函数的输出结果会记录每次迭代fn的输出,result = outputs [-1]可以告诉theano只需要取最后一次迭代结果,theano也会对此做相应的优化(不存保存中间几次迭代结果)。
例二、多项式方程
#==============================================================================
# polynomial -- c0*x^0 + c1*x^1 + c2*x^2 + c3*x^3...
#==============================================================================
coefficients=T.vector('coeff')
x = T.iscalar('x')
sum_poly_init = T.fscalar('sum_poly')
result, update = theano.scan(lambda coefficients, power, sum_poly, x: T.cast(sum_poly +
coefficients*(x**power),dtype=theano.config.floatX),
sequences=[coefficients, T.arange(coefficients.size)],
outputs_info=[sum_poly_init],
non_sequences=[x])
poly_fn = theano.function([coefficients,sum_poly_init,x], result, updates=update)
coeff_value = numpy.asarray([1.,3.,6.,5.], dtype=theano.config.floatX)
x_value = 3
poly_init_value = 0.
print poly_fn(coeff_value,poly_init_value, x_value)
结果:
[ 1. 10. 64. 199.]
- 1
- 2
- 3
- 4
- 5
- 6
- 7
- 8
- 9
- 10
- 11
- 12
- 13
- 14
- 15
- 16
- 17
- 18
- 19
- 20
- 21
这个例子主要演示了T.arange()的作用,scan会在T.arange()生成的list上遍历。例如这段代码中T.arange()生成list=[0,1,2,3],在第i次迭代中,scan把coefficients的第i个元素和list的第i个元素喂给fn作为参数。outputs_info作为第三个参数输入给fn,然后是non_sequences的变量。其中outputs_info的初始化大小和类型都要和fn的返回结果相同。打印结果中包含了4次迭代的输出。
例三、theano.scan_module.until
#==============================================================================
# theano.scan_module.until
#==============================================================================
print 'theano.scan_module.until:'
def prod_2(pre_value, max_value):
return pre_value*2, theano.scan_module.until(pre_value*2 > max_value)
max_value = T.iscalar('max_value')
result, update = theano.scan(prod_2, outputs_info=T.constant(1.),
non_sequences=[max_value], n_steps=100)
prod_fn = theano.function([max_value], result, updates=update)
print prod_fn(400)
结果:
[ 2. 4. 8. 16. 32. 64. 128. 256. 512.]
- 1
- 2
- 3
- 4
- 5
- 6
- 7
- 8
- 9
- 10
- 11
- 12
- 13
- 14
- 15
- 16
- 17
theano.scan_module.until必须放在函数的return语句里,当条件满足时,scan停止迭代。
例四、斐波那契数列–taps
taps在有时间序列的迭代程序中比较有用,例如有如下代码:
scan(fn, sequences = [ dict(input= Sequence1, taps = [-3,2,-1])
, Sequence2
, dict(input = Sequence3, taps = 3) ]
, outputs_info = [ dict(initial = Output1, taps = [-3,-5])
, dict(initial = Output2, taps = None)
, Output3 ]
, non_sequences = [ Argument1, Argument2])
- 1
- 2
- 3
- 4
- 5
- 6
- 7
那么fn函数的变量输入顺序是:
1. Sequence1[t-3]
2. Sequence1[t+2]
3. Sequence1[t-1]
4. Sequence2[t]
5. Sequence3[t+3]
6. Output1[t-3]
7. Output1[t-5]
8. Output3[t-1]
9. Argument1
10. Argument2
- 1
- 2
- 3
- 4
- 5
- 6
- 7
- 8
- 9
- 10
- 11
其中sequences默认taps=0;outputs_info默认taps=-1,因为taps=0的结果是当前这一步迭代需要计算的。
#==============================================================================
# taps scalar -- Fibonacci sequence
#==============================================================================
Fibo_arr = T.vector('Fibonacci')
k= T.iscalar('n_steps')
result, update = theano.scan(lambda tm2,tm1: tm2 + tm1,
outputs_info=[dict(initial=Fibo_arr, taps=[-2,-1])],
n_steps=k)
Fibo_fn = theano.function([Fibo_arr,k], result, updates=update)
Fibo_init = numpy.asarray([1,1], dtype=theano.config.floatX)
k_value = 12
print Fibo_fn(Fibo_init, k_value)
结果:
[ 2. 3. 5. 8. 13. 21. 34. 55. 89. 144. 233. 377.]
- 1
- 2
- 3
- 4
- 5
- 6
- 7
- 8
- 9
- 10
- 11
- 12
- 13
- 14
- 15
程序段中首先设置scan的output的初始值为Fibo_arr=[1,1]向量,taps -2和-1分别指向Fibo_arr的第一个元素和第二个元素,并作为匿名函数的输入参数tm2和tm1。例如假设scan迭代到[ 2. 3. 5. 8. 13. 21. 34. ]时,taps=-4对应的值为8,taps=-3对应的值为13,taps=-2对应的值为21,taps=-1对应的值为34,那么在当前迭代中tm2=21, tm1=34,当前迭代结果为21+34=55。
当使用taps时,outputs_info的初始化值中必须包含taps所对应时间点的值。
例五、两个斐波那契数列
#==============================================================================
# taps vector -- two Fibonacci sequences
#==============================================================================
Fibo_mat = T.matrix('Fibo_mat')
k = T.iscalar('n_steps')
result, update = theano.scan(lambda tm2,tm1: tm2 + tm1,
outputs_info=[dict(initial=Fibo_mat, taps=[-2,-1])],
n_steps=k)
Fibo_fn = theano.function([Fibo_mat,k], result, updates=update)
Fibo_init = numpy.asarray([[1,1],[1,2]], dtype=theano.config.floatX)
k_value = 12
print Fibo_fn(Fibo_init, k_value)
结果:
[[ 2. 3.]
[ 3. 5.]
[ 5. 8.]
[ 8. 13.]
[ 13. 21.]
[ 21. 34.]
[ 34. 55.]
[ 55. 89.]
[ 89. 144.]
[ 144. 233.]
[ 233. 377.]
[ 377. 610.]]
- 1
- 2
- 3
- 4
- 5
- 6
- 7
- 8
- 9
- 10
- 11
- 12
- 13
- 14
- 15
- 16
- 17
- 18
- 19
- 20
- 21
- 22
- 23
- 24
- 25
在这段程序中,taps = -2的值为[1,1], taps=-1的值为[1,2],通过迭代求和,得到了两列斐波那契数列。这段代码和例四很相似,只是例四中是标量的迭代,这段代码是向量的迭代,同样可以很容易扩展到矩阵的迭代。
三、参考资料
theano document:http://deeplearning.net/software/theano/library/scan.html#lib-scan-shared-variables