【theano-windows】学习笔记六——theano中的循环函数scan

来源:互联网 发布:少儿编程前景 编辑:程序博客网 时间:2024/05/26 17:46

前言

ScanTheano中最基础的循环函数, 官方教程主要是通过大量的例子来说明用法. 不过在学习的时候我比较习惯先看看用途, 然后是参数说明, 最后再是研究实例.

国际惯例, 参考网址

官网关于Scan的11个例子

官网更全面的介绍

简介

用途

  • 递归的一般形式, 可以被用于循环
  • scan有两个特殊的案例Reductionmap
  • scan可以按照某个输入序列执行一个函数, 在每个时间戳都提供一个输出, 可以被函数的下一次执行所看到
  • 可以看到之前执行函数在前K步的情况
  • sum()操作可以通过在一个列表上scan函数z+x(i), 初始状态是z=0
  • 通常for循环可以使用scan()搞定, 而且scan()Theano处理循环的最接近方法
  • 使用scan()进行循环的好处
    • 迭代次数可以成为符号图的一部分
    • 最小化GPU转移
    • 有序计算梯度
    • python的中的for循环稍微快点
    • 通过检测实际内存需要, 因而能够降低总内存使用

参考手册

两个特殊的案例

  • 一个reduce操作可以被用于返回scan的最后一个输出
  • 一个map操作可以被用于让函数忽视之前步骤的输出

调用以下几个函数都会使用Scan操作:

theano.map(fn, sequences, non_sequences=None, truncate_gradient=-1, go_backwards=False, mode=None, name=None)

参数说明(只提供部分参数说明, 具体可戳第二个参考博客):

  • fn是每一步迭代应用的函数
  • sequence是迭代序列列表
  • non_sequence是传入fn的参数, 这些参数不会被迭代
  • go_backwardsbool型参数, 如果为True就说明sequence是从列表的最后一个向着列表开头传入迭代
theano.reduce(fn, sequences, outputs_info, non_sequences=None, go_backwards=False, mode=None, name=None)

参数说明:

  • fn是每步迭代应用的函数
  • sequence是迭代序列列表
  • outputs_inforeduce输出的字典列表
  • non_sequences传入fn的参数列表,这些参数都不会参与迭代
  • go_backwardsbool型参数, 如果是True就说明sequence是从列表的最后一个向着列表开头传入迭代
theano.foldl(fn, sequences, outputs_info, non_sequences=None, mode=None, name=None)theano.foldr(fn, sequences, outputs_info, non_sequences=None, mode=None, name=None)

参数说明(关于foldlfoldr的说明可以戳这里1,这里2, 这里3):

  • fn是每次循环执行的函数
  • sequence是跌迭序列列表
  • outputs_info输出的字典列表
  • non_sequences 迭代中不会传入fn的参数列表
#scan函数的参数列表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, return_list=False)

参数说明:

  • fn是每一步scan都会执行的操作, 它需要构建一个变量去描述每次迭代的输出, 输入到theano的变量期望能够代表输入序列的所有切片和之前的输出值, non_sequences也会被丢给scan. 输入到fn的变量顺序如下:

    • 第一个序列的所有时间片
    • 第二个序列的所有时间片
    • 最后一个序列的所有时间片
    • 第一个输出的所有过去片
    • 第二个输出的所有过去片(顺便吐槽一下theano的文档错别字真多,output都能写成otuput)
    • 最后一个输出的过去片
    • 其他的参数(non_sequences提供的序列)
      序列的顺序与丢给scansequence列表一样, 输出的顺序与outputs_info的序列一样

    关于输入输出的顺序, 官网给了一个例子:

    #加入调用scan函数的参数如下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])

    那么fn接收参数的顺序如下:

    #scan中fn接收的参数顺序Sequence1[t-3]Sequence1[t+2]Sequence1[t-1]Sequence2[t]Sequence3[t+3]Output1[t-3]Output1[t-5]Output3[t-1]Argument1Argument2

    non_sequences列表中可以包含共享变量, 虽然scan自己可以指出它们, 因而可以跳过, 但是为了代码的清晰, 还是建议提供它们(这些共享变量). 当然scan也可以断定其他的non_sequences(非共享的), 即使它们没有被传递给scan, 一个简单的例子如下:

    import theano.tensor as TTW   = TT.matrix()W_2 = W**2def f(x):  return TT.dot(x,W_2)

    scan函数希望返回两个东西

    • 一个是输出列表, 输出顺序与outputs_info一样, 不同在于每一个输出初始状态必须仅有一个输出变量(既然它没有用)
    • 另一个是fn需要返回一个更新字典(告诉如何在每次迭代以后更新共享变量), 字典可以是元组列表.

    这两个返回的列表没有顺序限制,fn可以返回(output_list,update_dictionary)或者(update_dictionary,output_list)或者仅仅输出一个(在这种情况下,另一个就是空)

    为了将scan作为while循环使用, 还需要返回一个停止条件, 在until类中加入, 这个条件应该被当做第三个元素返回, 比如

    return [y1_t, y2_t], {x:x+1}, theano.scan_module.until(x < 50)
  • sequences是描述scan迭代的Theano变量或者字典的列表, 如果提供的是字典, 那么一系列的可选信息可以被提供, 字典需要包含如下keys:

    • input(强制性的): 代表序列的Theano变量
    • taps: fn所需要的序列的时间拍子. 作为一组整数列表提供, 值k表示第t步迭代会将t+k时间片数据传递给fn, 默认值是0

    在列表sequences中任何的Theano变量都被自动地包装到字典中, 其中taps设置为0

  • output_info是描述循环计算的输出的初始状态的Theano变量或者字典列表, 当初始状态作为字典给出以后, 关于输出对应的初始状态的可选信息可以被提供. 这个字典应该包含:

    • initial: 代表给定输出初始状态的Theano变量, 如果输出不是递归计算且不需要初始状态, 那么这部分可以被忽略.
    • taps: 传递给fn的时间拍子, 是负整数列表, 值k代表第t次迭代将会传递t+k片给fn

    如果output_info是空列表或者None,scan会假设任何的输出都没有使用拍子. 如果仅仅提供了输出的自己, 那么会报错(因为没有任何的约定去只是如何将提供的信息映射到fn的输出)

  • non_sequences 是传递给fn的参数列表, 可以选择排除列表中传递给fn的变量, 但是不建议这么做

  • n_steps是迭代次数

  • truncate_gradient是截断BPTT(Backpropagation Through Time)算法的迭代次数,这个应该是与RNN有关的梯度更新时候需要使用的

  • go_backwards: 标志着scan是否需按照序列反向取值. 如果每个序列是按时间索引, 那么这个值是True的时候, 那么就从最后到0行进

  • name: 当分析scan的时候, 为scan的任意实例提供一个名字很重要, 这个分析器能够提供你的代码的整体描述, 而且可以分析实例每一步的计算.

  • mode: 建议将这个参数置为None

  • profile: 暂时不了解先

  • allow_gc暂时不了解先

  • strict如果是true,那么要求fn中的共享变量必须作为non_sequences或者sequences的一部分被提供

  • return_list: 如果是true, 那么即使只有一个输出, 也会返回一个列表

返回值是以元组的形式返回(outputs,updates):

  • outputstheano变量或者theano变量列表, 与outputs_info顺序相同
  • updates 是字典子集, 指定了共享变量的更新方法, 这个字典需要被传递到theano.function中. 与一般的字典不同的是, keys是共享变量, 这些字典的添加是一致的
theano.scan_checkpoints(fn, sequences=[], outputs_info=None, non_sequences=[], name='checkpointscan_fn', n_steps=None, save_every_N=10, padding=True)

描述

更加节省空间的Scan函数, 但是使用更加严格, 在scan()中, 对每个输入计算关于输出的梯度, 你需要存储每步的中间结果, 这很费内存. 而这个scan_checkpoints()允许save_every_n步前向计算, 而不去存储中间结果, 也允许在梯度计算期间重新计算它们.

参数说明:

  • fn: 迭代函数
  • sequences: theano变量或者字典列表, 描述scan迭代所需序列, 每个序列必须相同长度
  • outputs_info: 循环计算的输出的初始状态, 是theano变量或者字典列表
  • non_sequences: 是传递给fn的参数列表
  • n_steps:是迭代次数
  • save_every_N: 不需要存储scan计算的步骤数
  • padding : 如果序列的长度不是save_every_N的准备横竖被, 那么就填充0, 以保证scan正常运行

输出:与scan()一样,输出(outputs,updates)元组形式, 区别在于: 它仅仅包含每save_every_N步的输出. 没有被函数返回的时间步将会在梯度计算中重新计算

实例

一下实例的书写都要先引入模块

import theanoimport theano.tensor as T

1.计算A**K

如果在python中用循环写,可以是这样:

#计算A**kk=2A=3result=1for i in range(k):    result=result*Aprint result

分析一下需要三件事情被处理: result的初始值、result的累积结果、不变量A. 那么不变量就存在non_sequences中, 初始化就存在outputs_info中, 累积操作是自动发生的:

k=T.iscalar('k')A=T.vector('A')#其实按照习惯,最好是写T.dvector之类的result, updates = theano.scan(fn=lambda prior_result,A : prior_result * A, #迭代使用函数                             outputs_info=T.ones_like(A),#丢给prior_result                             non_sequences=A,#丢给A                                n_steps=k)#迭代次数

上面代码中注意scan()固定的接收参数顺序: 输出先验(初始值)、non_sequence; 但是由于scan()返回的是每次迭代的结果, 所以只需要取出最后一次结果

final_result = result[-1]

然后放到function中去编译, 返回相关结果

power=theano.function(inputs=[A,k],outputs=final_result,updates=updates)#放到函数中编译

然后返回0~9的平方的结果

print power(range(10),2)#[  0.   1.   4.   9.  16.  25.  36.  49.  64.  81.]

2. 主维度迭代: 多项式计算

除了按照固定次数的迭代, scan()也可以按照主维度去迭代, 类似于第一个实例用的是for i in range(k),而本实例关注的是for iter in a_list. 这时候提供的循环tensors需要使用sequences关键字

本实例演示的是从一个系数列表中构建符号计算:

f=c[1]x0+c[2]x1+c[3]x2

按照上面的步骤, 同样先定义两个变量分别指示系数c和输入变量x, 指数是从0~inf的, 那么就用arange取值就行了, 关于输出值的初始状态就不需要了, 因为输出值并没有被迭代计算:

coefficients=T.dvector('coefficients')#系数x=T.dscalar('x')#变量max_coefficients_supported=10000#指数

定义完所需变量后, 按照lambda定义的fn中定义顺序, 传递sequences指定系数和指数, 然后使用outputs_info初始化输出, 因为输出无需初始化或者迭代计算, 所以为None,其实也可以省略这个参数不写. 最后在non_sequences中传递变量, 一定要注意传递给fn的参数顺序是sequencesoutputs_infonon_sequences

components, updates=theano.scan(lambda coefficients,power,free_variable: coefficients*(free_variable**power),                               outputs_info=None,                               sequences=[coefficients,T.arange(max_coefficients_supported)],                               non_sequences=x)

分析一下: 先把sequences中的coefficients丢给lambda中的coefficients, T.arange(max_coefficients_support)定义的指数丢给power,然后因为outputs_infoNone, 说明它相当于没有, 可以忽视它继续看后面的将non_sequences丢给free_variable, 接下来计算加和及在function中编译, 最后测试

polynomial=components.sum()calculate_ploynomial=theano.function(inputs=[coefficients,x],outputs=polynomial)#testtest_coefficients=np.asarray([1,0,2],dtype=np.float32)test_value=3print 'use scan result:',calculate_ploynomial(test_coefficients,test_value)print 'use normal calc:',(1.0 * (3 ** 0) + 0.0 * (3 ** 1) + 2.0 * (3 ** 2))#use scan result: 19.0#use normal calc: 19.0

有几个有趣的事情注意一下:

  • 首先生成系数, 然后把它们加和起来. 其实也可以沿途计算加和, 然后取最后一个值, 这更具内存效率

  • 第二就是结果没有累积状况, 将outputs_info=None , 这表明scan不会将先验结果传递给fn, 注意参数传递顺序:

    sequences (if any), prior result(s) (if needed), non-sequences (if any)

  • 第三就是有一个便捷操作, 利用thenao.tensor.arangesequences中, 为什么长度不一样也能丢到fn中呢?看第四条

  • 第四就是如果给定的多个sequences不是同一长度, scan会截断它们为最短的长度.也就是说指数本来是0~9999, 但是按照coefficients,T.arange(max_coefficients_supported)中最短的那个截断.

随后我们自己用中间变量写一次试试, 累加和写到输出先验results变量中, 存储在scan()函数的outputs_info参数中

#尝试自己用累加和写一遍results=np.array([0],dtype='int32')c=T.vector('c',dtype='int32')x=T.scalar('x',dtype='int32')components, updates=theano.scan(lambda i,results,c,x: results+c[i]*(x**i),                               sequences=T.arange(c.shape[0],dtype='int32'),                               outputs_info= results,                               non_sequences=[c,x])final_res=components[-1]cal_poly=theano.function(inputs=[c,x],outputs=final_res)test_c=np.asarray([1,0,2],dtype=np.int32)test_value=3print cal_poly(test_c,test_value)#19

【PS】鬼知道我写的对不对, 各位亲们如果感觉哪里出问题希望多多交流,目前结果反正是对的,比较坑的是一定要注意传入fn的参数一定要是相同类型, 我刚开始直接声明results=0, 才发现这个是int8类型, 结果一直报错, 坑

3. 简单的标量加法, 剔除lambda表达式

上面的例子的表达式都是在theano.scan中用lambda表达式写的, 有一件事一定要注意: 提供的初始状态, 也就是outputs_info必须与每次迭代的输出变量的形状大小相同

下面计算的是

results=n(n+1)2=1+2+3++n

先定义变量n, 以及使用def外部定义乘法函数

#定义nup_to=T.iscalar('up_to')#定义加法操作,这只是上一个结果加上下一个数字, 所以在scan中需要循环def accumulate_by_adding(arrange_val,sum_to_date):    return sum_to_date+arrange_val#返回值给scan的outputs_info参数seq=T.arange(up_to)

定义scan中的循环

#定义scan操作outputs_info=T.as_tensor_variable(np.array(0,seq.dtype))scan_result, scan_updates=theano.scan(accumulate_by_adding,                                     sequences=seq,#传给arrange_val                                     outputs_info=outputs_info,#传给sum_to_date                                     non_sequences=None)triangular_sequence=theano.function(inputs=[up_to],outputs=scan_result)

测试一下:

#testsome_num=15print(triangular_sequence(some_num))print [n * (n + 1) // 2 for n in range(some_num)]#[  0   1   3   6  10  15  21  28  36  45  55  66  78  91 105]#[0, 1, 3, 6, 10, 15, 21, 28, 36, 45, 55, 66, 78, 91, 105]

4.设置指定索引值

此例子是定义一个全零矩阵, 然后对指定索引出赋值, 如(1,1)处把0改为42, 把(2,3)赋值为50

先定义三个变量

location=T.imatrix('location')#位置values=T.vector('values')#位置对应的赋值output_model=T.matrix('output_model')#输出矩阵

然后定义替换函数, 注意使用theano.tensorset_subtensor函数可以替换值, 这个在博客《【theano-windows】学习笔记五——theano中张量部分函数》中有提到过

#定义替换函数def set_value_at_position(a_location,a_value,output_model):    zeros=T.zeros_like(output_model)    zeros_subtensor=zeros[a_location[0],a_location[1]]    return T.set_subtensor(zeros_subtensor,a_value)#替换值

然后设计scan函数, 以及使用function编译

#设计scanresult, updates = theano.scan(fn=set_value_at_position,                              outputs_info=None,                              sequences=[location, values],                              non_sequences=output_model)assign_values_at_positions=theano.function(inputs=[location,values,output_model],                                          outputs=result)

测试

#testtest_locations=np.asarray([[1,1],[2,3]],dtype=np.int32)test_values=np.asarray([42,50],dtype=np.float32)test_output_model=np.zeros((5,5),dtype=np.float32)print assign_values_at_positions(test_locations,test_values,test_output_model)'''[[[  0.   0.   0.   0.   0.]  [  0.  42.   0.   0.   0.]  [  0.   0.   0.   0.   0.]  [  0.   0.   0.   0.   0.]  [  0.   0.   0.   0.   0.]] [[  0.   0.   0.   0.   0.]  [  0.   0.   0.   0.   0.]  [  0.   0.   0.  50.   0.]  [  0.   0.   0.   0.   0.]  [  0.   0.   0.   0.   0.]]]  '''

5. 共享变量——吉布斯采样

例子: 进行十次吉布斯采样

expressionloop:P(h|v),P(v|h),P(h|v),,P(v|h)wrt.P(h|v)=sigmoid(wv+bh)P(v|h)=sigmoid(wh+bv)

定义三个变量: 权重、可见层偏置、隐藏层偏置

W=theano.shared(W_values)#权重bvis=theano.shared(bvis_values)#可见层偏置bhid=theano.shared(hvis_values)#隐藏层偏置

计算概率,并采样

#随机流trng=T.shared_randomstreams.RandomStreams(1234)#一次吉布斯采样def OneStep(vsample):    hmean=T.nnet.sigmoid(theano.dot(vsample,W)+bhid)#从v到h,激活概率    hsample=trng.binomial(size=hmean.shape,n=1,p=hmean)#采样    vmean=T.nnet.sigmoid(theano.dot(hsample,W.T)+bvis)#从h到v激活概率    return trng.binomial(size=vsample.shape,n=1,p=vmean,dtype=thenao.config.floatX)#采样

scan中循环十次, 用function激活参数更新

sample=T.vector()values,updates=theano.scan(Onestep,                          sequences=None,                          outputs_info=sample,                          nstep=10)gibbs10=theano.function([sample],values[-1],updates=updates)

【注】这个代码暂时运行不了, 后面用theano构建受限玻尔兹曼机RBM的时候再细究

这里需要注意两个问题:

  • 第一个 就是更新字典的重要性. 它将k步后的更新值与共享变量链接起来. 它指出十次迭代之后随机流是如何更新的. 如果不将更新字典传递给function, 那么会得到十组相同的随机数. 比如

    a = theano.shared(1)b=T.dscalar('b')c=T.dscalar('c')values, updates = theano.scan(lambda :{a: a+1}, n_steps=10)b = a + 1c = updates[a] + 1f = theano.function([], [b, c], updates=updates)print f()#[array(2), array(12)]print a.get_value()#11print f()#[array(12), array(22)]print a.get_value()#21

    【注】这个例子的官方文档书写可能有问题, 可以参考我的改改,但是我写的不一定对嘛

    我们可以发现这个例子中, 更新bc的区别在于, 一个用updates, 而另一个没有, 因而使用了updates的变量可以在每次迭代中获取到a的更新值11, 而没有使用updates更新规则的函数中,a的值始终是1,这就是为什么看到了两个结果1+1=211+1=12

  • 第二个就是如果使用了共享变量, 但是不想对他们进行迭代, 你可以不将他们传递为参数. 但是还是建议传递到Scan, 因为可以省去scan查找它们并放入到中的时间, 然后把它们给non_sequences参数.那么就可以再写一遍Gibbs采样

    W=theano.scan(W_values)bvis=theano.shared(bvis_values)bhid=theano.shared(bhid_values)trng=T.shared_randomstreams.RandomStreams(1234)def OneStep(vsample,W,bvis,bhid):  hmean=T.nnet.sigmoid(theano.dot(vsample,W)+bhid)  hsample=trng.binomial(size=hmean.shape,n=1,p=hmean)  vmean=T.nnet.sigmoid(theano.dot(hsample,W.T)+bvis)  return trng.binomial(size=vsample.shape, n=1, p=vmean,                      dtype=theano.config.floatX)sample=T.vector()values,updates=theano.scan(fn=OneStep,                        sequences=None,                        outputs_info=sample,                        non_sequences=[W,bvis,bhid])gibbs10=theano.function([sample],values[-1],updates=updates)

    上面说将共享变量传入scan可以简化计算图, 这可以提高优化以及执行速度. 一个比较好的记住使用scan中传递每一个共享变量的方法是使用strict标志. 当我们把它设置为True的时候, scan会检查在fn中所有必要的共享变量是否被传显示传递给fn,这必须由用户保证, 否则报错

    然后我们又可以写一次Gibbs采样, 设置strict=True

    def OneStep(vsample) :  hmean = T.nnet.sigmoid(theano.dot(vsample, W) + bhid)  hsample = trng.binomial(size=hmean.shape, n=1, p=hmean)  vmean = T.nnet.sigmoid(theano.dot(hsample, W.T) + bvis)  return trng.binomial(size=vsample.shape, n=1, p=vmean,                       dtype=theano.config.floatX)#设置strict=Truevalues, updates = theano.scan(OneStep,                            outputs_info=sample,                            n_steps=10,                            strict=True)#没有传递共享变量,会报错

    ↑↑↑↑↑↑上面这个写法会报错, 因为缺少共享变量的传递信息,错误信息如下:

    Traceback (most recent call last):...MissingInputError: An input of the graph, used to computeDimShuffle{1,0}(<TensorType(float64, matrix)>), was not provided andnot given a value.Use the Theano flag exception_verbosity='high',formore information on this error.

    加入non_sequences参数就对了

    def OneStep(vsample) :  hmean = T.nnet.sigmoid(theano.dot(vsample, W) + bhid)  hsample = trng.binomial(size=hmean.shape, n=1, p=hmean)  vmean = T.nnet.sigmoid(theano.dot(hsample, W.T) + bvis)  return trng.binomial(size=vsample.shape, n=1, p=vmean,                       dtype=theano.config.floatX)#设置strict=Truevalues, updates = theano.scan(OneStep,                            sequences=None,                            outputs_info=sample,                            non_sequences=[W,bvis,bhid],                            n_steps=10,                            strict=True)

6.Scan的条件结束

Scan结束循环, 我们可以使用除了上面指定迭代次数n_steps以外, 还能用条件去提前结束循环, 类似于while(condition), 比如我们计算指数, 如果它大于设置的max_value阈值就停止

“`python
def power_of_2(previous_power,max_value):
return previous_power*2,theano.scan_module.until(previous_power*2>max_value)

max_value=T.dscalar()
values,_ = theano.scan(power_of_2,
sequences=None,
outputs_info=T.constant(1.),
non_sequences=max_value,
n_steps=1024)
f=theano.function([max_value],values)

print f(45)
#[ 2. 4. 8. 16. 32. 64.]
“`

注意, 这个theano.scan()中迭代会在outputs_info的基础上继续迭代, 所以运行结果是12222

可以发现为了提前终止循环, 在函数内部进行了条件控制, 而使用的参数被包含在类theano.scan_module.until

7.多输出, 多时间拍-RNN

上面都是简单的scan实例, 然而scan不仅支持先验结果和当前序列值, 还能够向后看不止一步. 比如我们设计RNN的时候, 假设RNN的定义如下:

这里写图片描述

【注】这个网络与经典RNN相去甚远, 可能没什么用,主要是为了清除阐述scan的向后看特点, 我们后续会跟进RNN的实现

这个例子中,我们有一个序列, 需要迭代u和两个输出x,y,计算一步迭代:

#RNNdef oneStep(u_tm4,u_t,x_tm3,x_tm1,y_tm1,W,W_in_1,W_in_2,W_feedback,W_out):    x_t=T.tanh(theano.dot(x_tm1,W)+\              theano.dot(u_t,   W_in_1) + \              theano.dot(u_tm4, W_in_2) + \              theano.dot(y_tm1, W_feedback))    y_t = theano.dot(x_tm3, W_out)    return [x_t,y_t]

之前我们介绍过scansequencesoutputs_info中的一个参数叫taps,可以控制向后移动的结果长度, 这里为了获取各种时间的结果值, 就要用到它

W = T.matrix()W_in_1 = T.matrix()W_in_2 = T.matrix()W_feedback = T.matrix()W_out = T.matrix()u = T.matrix() x0 = T.matrix()y0 = T.vector() ([x_vals, y_vals], updates) = theano.scan(fn=oneStep,                                          sequences=dict(input=u, taps=[-4,-0]),                                          outputs_info=[dict(initial=x0, taps=[-3,-1]), y0],                                          non_sequences=[W, W_in_1, W_in_2, W_feedback, W_out],                                          strict=True)

现在x_valsy_vals就是在u上迭代以后生成的指向序列x和y的符号变量, 其中sequences_tapsoutputs_taps指出哪个切片是明确需要的. 注意如果我们想使用x[t-k], 我们并非总需要x[t-(k-1)],x[t-(k-2)],..., 但是使用编译的函数时, 表示它的numpy阵列将会足够大去包含这个值. 假设我们编译了上述函数, 就会将u作为uvals=[0,1,2,3,4,5,6,7,8]给出, 而scan会将uvals[0]当做u[-4],将会从uvals[4]向后遍历. 关于这个建议看官方文档的reference

暂时还没涉及到RNN的搭建, 不过要知道scan可以想后看好几步的结果, 使用的是taps即可, 后面到实例搭建的时候, 用到了自然就理解了

简单的实战实例

可能我写的稍微改动了一下官网的源码, 但是结果应该是对的, 可以对照看看添加了什么,方便掌握theano的各种参数的基本操作.

1.逐元素计算

tanh(Wx+b)

#逐元素计算tanh(x(t).dot(W) + b)#定义三个变量X=T.matrix('X')W=T.matrix('W')b_sym=T.vector('b_sym')#使用scan计算results,updates=theano.scan(lambda v,W,b_sym: T.tanh(T.dot(v,W)+b_sym),                           sequences=X,                           outputs_info=None,                           non_sequences=[W,b_sym])compute_elementwise=theano.function(inputs=[X,W,b_sym],outputs=results)#测试x = np.eye(2, dtype=theano.config.floatX)w = np.ones((2, 2), dtype=theano.config.floatX)b = np.ones((2), dtype=theano.config.floatX)b[1] = 2print compute_elementwise(x, w, b)#计算结果print np.tanh(x.dot(w)+b)'''[[ 0.96402758  0.99505478] [ 0.96402758  0.99505478]][[ 0.96402758  0.99505478] [ 0.96402758  0.99505478]] '''

2.计算序列,只涉及到一步结果

x(t)=tanh(Wx(t1)+Uy(t)+Vp(Tt))

注意这个式子中x(t1)在实现的时候, 由于scan本身当前次迭代就是在上一次迭代的结果进行的, 所以不需要使用taps=[-1]取值, 后面的tTt分别表示按顺序取值和逆序取值

#计算序列 x(t) = tanh(x(t - 1).dot(W) + y(t).dot(U) + p(T - t).dot(V))#定义参数X = T.vector("X")W = T.matrix("W")U = T.matrix("U")Y = T.matrix("Y")V = T.matrix("V")P = T.matrix("P")#在scan中迭代results,updates=theano.scan(lambda y,p,x_tm1: T.tanh( T.dot(x_tm1,W)+T.dot(y,U)+T.dot(p,V) ),                           sequences=[Y,P[::-1]],                           outputs_info=[X],                           non_sequences=None)#function编译compute_seq=theano.function([X,W,Y,U,P,V],outputs=results)#测试x=np.zeros((2),dtype=theano.config.floatX)x[1]=1w=np.ones((2,2),dtype=theano.config.floatX)y=np.ones((5,2),dtype=theano.config.floatX)y[0,:]=-3u=np.ones((2,2),dtype=theano.config.floatX)p=np.ones((5,2),dtype=theano.config.floatX)p[0,:]=3v=np.ones((2,2),dtype=theano.config.floatX)print (compute_seq(x,w,y,u,p,v))#用numpy测试结果x_res=np.zeros((5,2),dtype=theano.config.floatX)x_res[0]=np.tanh(x.dot(w)+y[0].dot(u)+p[4].dot(v))for i in range(1,5):    x_res[i]=np.tanh(x_res[i-1].dot(w)+y[i].dot(u)+p[4-i].dot(v))print x_res'''[[-0.99505478 -0.99505478] [ 0.96471971  0.96471971] [ 0.99998587  0.99998587] [ 0.99998772  0.99998772] [ 1.          1.        ]][[-0.99505478 -0.99505478] [ 0.96471971  0.96471971] [ 0.99998587  0.99998587] [ 0.99998772  0.99998772] [ 1.          1.        ]]'''

3.按行(列)计算X的范数

#按行计算X=T.dmatrix('X')results,updates=theano.scan(lambda x: T.sqrt((x**2).sum()),                           sequences=[X],                           outputs_info=None,                           non_sequences=None)computer_norm_lines=theano.function(inputs=[X],outputs=results)#测试x=np.diag(np.arange(1,6,dtype=theano.config.floatX),1)print computer_norm_lines(x)#[ 1.  2.  3.  4.  5.  0.]#用numpy得出结果看看print np.sqrt((x**2).sum(1))#[ 1.  2.  3.  4.  5.  0.]
#按列计算X=T.dmatrix('X')results,updates=theano.scan(lambda x: T.sqrt((x**2).sum()),                           sequences=[X.T],                           outputs_info=None,                           non_sequences=None)computer_norm_lines=theano.function(inputs=[X],outputs=results)#测试x=np.diag(np.arange(1,6,dtype=theano.config.floatX),1)print computer_norm_lines(x)#[ 0.  1.  2.  3.  4.  5.]#用numpy得出结果看看print np.sqrt((x**2).sum(0))#[ 0.  1.  2.  3.  4.  5.]

4. 计算矩阵的迹

其实就是矩阵主对角线元素和, 主要是要对行列都进行遍历, 从而取到每个元素值

floatX='float32'X=T.matrix('X')results,_=theano.scan(lambda i,j,traj: T.cast(X[i,j]+traj,floatX),                      sequences=[T.arange(X.shape[0]),T.arange(X.shape[1])],                      outputs_info=np.asarray(0.,dtype=floatX),                      non_sequences=None)results=results[-1]compute_traj=theano.function(inputs=[X],outputs=results)#测试x=np.eye(5,dtype=theano.config.floatX)x[0]=np.arange(5,dtype=theano.config.floatX)print compute_traj(x)#4.0#用numpy计算结果print np.diagonal(x).sum()#4.0

5.计算序列,涉及到两步结果

x(t)=Ux(t2)+Vx(t1)+tanh(Wx(t1)+b)

这个例子就涉及到对x的前两步结果的提取了, 用taps, 建议再去刷一遍前面《参考手册》的关于outputs_info中设置taps后传递参数到fn那部分

U,V,W=T.matrices('U','V','W')X=T.matrix('X')b_sym=T.vector('b_sym')n_sym=T.iscalar('n_sym')#更新results,_=theano.scan(lambda x_tm2, x_tm1: T.dot(x_tm2,U)+T.dot(x_tm1,V)+T.tanh(T.dot(x_tm1,W)+b_sym),                     sequences=None,                     outputs_info=[dict(initial=X,taps=[-2,-1])],                     non_sequences=None,                     n_steps=n_sym)compute_seq2=theano.function(inputs=[X,U,V,W,b_sym,n_sym],outputs=results)#测试x = np.zeros((2, 2), dtype=theano.config.floatX) # the initial value must be able to return x[-2]x[1, 1] = 1w = 0.5 * np.ones((2, 2), dtype=theano.config.floatX)u = 0.5 * (np.ones((2, 2), dtype=theano.config.floatX) - np.eye(2, dtype=theano.config.floatX))v = 0.5 * np.ones((2, 2), dtype=theano.config.floatX)n = 10b = np.ones((2), dtype=theano.config.floatX)print(compute_seq2(x, u, v, w, b, n))'''[[  1.40514827   1.40514827] [  2.88898897   2.38898897] [  4.34018326   4.34018326] [  6.53463173   6.78463173] [  9.82972336   9.82972336] [ 14.22203922  14.09703922] [ 20.07440186  20.07440186] [ 28.12292099  28.18542099] [ 39.19137192  39.19137192] [ 54.28408051  54.25283051]]'''

6.计算雅可比式

y=tanh(Ax)yx=?

import theanoimport theano.tensor as Timport numpy as np# 定义参数v = T.vector()A = T.matrix()y = T.tanh(T.dot(v, A))#利用grad计算一阶导results, updates = theano.scan(lambda i: T.grad(y[i], v), sequences=[T.arange(y.shape[0])])compute_jac_t = theano.function([A, v], results) # shape (d_out, d_in)# 测试x = np.eye(5, dtype=theano.config.floatX)[0]w = np.eye(5, 3, dtype=theano.config.floatX)w[2] = np.ones((3), dtype=theano.config.floatX)print(compute_jac_t(w, x))# 与numpy结果对比print(((1 - np.tanh(x.dot(w)) ** 2) * w).T)'''[[ 0.4199743  0.         0.4199743  0.         0.       ] [ 0.         1.         1.         0.         0.       ] [ 0.         0.         1.         0.         0.       ]][[ 0.41997433  0.          0.41997433  0.          0.        ] [ 0.          1.          1.          0.          0.        ] [ 0.          0.          1.          0.          0.        ]]'''

7. 在循环时做累加

主要注意使用共享变量, 直接在function中用scan返回的updates更新共享变量即可

#在循环过程中累加k=theano.shared(0)n_sym=T.iscalar('n_sym')results,updates=theano.scan(lambda: {k:(k+1)},                     sequences=None,                     outputs_info=None,                     non_sequences=None,                     n_steps=n_sym)accumulator=theano.function(inputs=[n_sym],updates=updates)print k.get_value()#0accumulator(5)print k.get_value()#5

8.乘以二项分布

tanh(Wv+b)d,where.dbinomial

#定义变量W=T.matrix('W')V=T.matrix('V')b_sym=T.vector('b_sym')#定义一个二项分布trng=T.shared_randomstreams.RandomStreams(1234)d=trng.binomial(size=W[1].shape)#定义乘法操作results,updates=theano.scan(lambda v: T.tanh(T.dot(v,W)+b_sym)*d,                           sequences=V,                           outputs_info=None,                           non_sequences=None)#放到function中编译compute_with_bnoise=theano.function(inputs=[V,W,b_sym],outputs=results,updates=updates)#测试一下x = np.eye(10, 2, dtype=theano.config.floatX)w = np.ones((2, 2), dtype=theano.config.floatX)b = np.ones((2), dtype=theano.config.floatX)print(compute_with_bnoise(x, w, b))'''[[ 0.96402758  0.        ] [ 0.          0.96402758] [ 0.          0.        ] [ 0.76159418  0.76159418] [ 0.76159418  0.        ] [ 0.          0.76159418] [ 0.          0.76159418] [ 0.          0.76159418] [ 0.          0.        ] [ 0.76159418  0.76159418]]'''

9.计算幂

Ak=?

分析:可以利用每上一次的结果继续计算下一次的结果

k=T.iscalar('k')A=T.vector('A')#上一次结果乘以底数def inner_fct(prior_result,B):    return prior_result*B#使用scan循环获取结果results,updates=theano.scan(inner_fct,                           sequences=None,                           outputs_info=T.ones_like(A),                           non_sequences=A,                           n_steps=k)#用function编译final_result=results[-1]# power=theano.function(inputs=[A,k],outputs=final_result,updates=updates)#不用updates也行,貌似final_result已经包含更新方法了power=theano.function(inputs=[A,k],outputs=final_result)#测试print power(range(10),2)#[  0.   1.   4.   9.  16.  25.  36.  49.  64.  81.]

10.计算多项式

f=c[1]x0+c[2]x1+c[3]x2

参考上面的实例2,这里贴一遍自己写的那个代码, 累加和写到输出先验results变量中, 存储在scan()函数的outputs_info参数中

#尝试自己用累加和写一遍results=np.array([0],dtype='int32')c=T.vector('c',dtype='int32')x=T.scalar('x',dtype='int32')components, updates=theano.scan(lambda i,results,c,x: results+c[i]*(x**i),                               sequences=T.arange(c.shape[0],dtype='int32'),                               outputs_info= results,                               non_sequences=[c,x])final_res=components[-1]cal_poly=theano.function(inputs=[c,x],outputs=final_res)test_c=np.asarray([1,0,2],dtype=np.int32)test_value=3print cal_poly(test_c,test_value)#19

官方代码:

coefficients = theano.tensor.vector("coefficients")x = T.scalar("x")max_coefficients_supported = 10000# Generate the components of the polynomialfull_range=theano.tensor.arange(max_coefficients_supported)components, updates = theano.scan(fn=lambda coeff, power, free_var:                                   coeff * (free_var ** power),                                outputs_info=None,                                sequences=[coefficients, full_range],                                non_sequences=x)polynomial = components.sum()calculate_polynomial = theano.function(inputs=[coefficients, x],                                     outputs=polynomial)test_coeff = numpy.asarray([1, 0, 2], dtype=numpy.float32)print(calculate_polynomial(test_coeff, 3))

突然发现官方文档最后的Exercise也是要求改编这个写法, 这里把练习题的写法也贴过来, 它的通用性更强, 因为我上面的power幂刚好就是迭代次数, 而习题的代码是提出来这一项的

X=T.scalar('X')coefficients=T.vector('coefficients')max_coefficients=10000full_range=T.arange(max_coefficients)out_info=T.as_tensor_variable(np.asarray(0,'float64'))components,updates=theano.scan(lambda coeff,power,prior_val,free_var:                              prior_val+(coeff*(free_var**power)),                              sequences=[coefficients,full_range],                              outputs_info=out_info,                              non_sequences=X)ploynomial=components[-1]calculate_polynomial=theano.function(inputs=[coefficients,X],outputs=ploynomial)test_coeff = np.asarray([1, 0, 2], dtype=np.float32)print(calculate_polynomial(test_coeff, 3))#19.0

【注】突然发现很多funtion中都不需要把updates添加进去都可以计算出正确结果, 难道原因是resultsupdates是存在dict中, 传递resultsfunction的输出的同时也已经把其更新规则传递进去了?好像这样理解也没什么不对, 毕竟前面我们发现function的输出可以是表达式, 也可以是表达式返回值

code:链接: https://pan.baidu.com/s/1o8wVGjo 密码: 59pg

阅读全文
0 0
原创粉丝点击