五个例子掌握theano.scan函数

一、theano的工作原理

    在theano编程中,Graph是指导theano如何对变量进行操作的唯一途径,theano变量和theano Ops(操作)是Graph的两个基本构成元素。Graph只能由theano变量(包括shared变量)或常数组成。如图所示: 

五个例子掌握theano.scan函数

 

    通常可以按如下步骤构造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

上一篇:优达学城深度学习之三(上)——卷积神经网络


下一篇:石头剪刀布