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

前言

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]
    Argument1
    Argument2

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

    import theano.tensor as TT
    W   = TT.matrix()
    W_2 = W**2
    def 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 theano
import theano.tensor as T

1.计算A**K

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

#计算A**k
k=2
A=3
result=1
for i in range(k):result=result*A
print 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_resultnon_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]x2f=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)
#test
test_coefficients=np.asarray([1,0,2],dtype=np.float32)
test_value=3
print '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=3
print 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++nresults=n∗(n+1)2=1+2+3+⋯+n

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

#定义n
up_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_valoutputs_info=outputs_info,#传给sum_to_datenon_sequences=None)
triangular_sequence=theano.function(inputs=[up_to],outputs=scan_result)

测试一下:

#test
some_num=15
print(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编译

#设计scan
result, 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)

测试

#test
test_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)expressionloop:P(h|v),P(v|h),P(h|v),⋯,P(v|h)wrt.P(h|v)=sigmoid(w∗v+bh)P(v|h)=sigmoid(w∗h+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 + 1
    c = updates[a] + 1
    f = theano.function([], [b, c], updates=updates)
    print f()#[array(2), array(12)]
    print a.get_value()#11
    print 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 compute
    DimShuffle{1,0}(<TensorType(float64, matrix)>), was not provided and
    not given a value.Use the Theano flag exception_verbosity='high',for
    more 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的基础上继续迭代, 所以运行结果是122221∗2∗2∗2⋯∗2

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

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

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

这里写图片描述

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

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

#RNN
def 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⁡(W∗x+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(t)=tanh⁡(W∗x(t−1)+U∗y(t)+V∗p(T−t))

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

#计算序列 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]=1
w=np.ones((2,2),dtype=theano.config.floatX)
y=np.ones((5,2),dtype=theano.config.floatX)
y[0,:]=-3
u=np.ones((2,2),dtype=theano.config.floatX)
p=np.ones((5,2),dtype=theano.config.floatX)
p[0,:]=3
v=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(t)=U∗x(t−2)+V∗x(t−1)+tanh⁡(W∗x(t−1)+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] = 1
w = 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 = 10
b = 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=?y=tanh⁡(A∗x)∂y∂x=?

import theano
import theano.tensor as T
import 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()#0
accumulator(5)
print k.get_value()#5

8.乘以二项分布

tanh(Wv+b)d,where.dbinomialtanh⁡(W∗v+b)∗d,where.d∈binomial

#定义变量
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=?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]x2f=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=3
print 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 polynomial
full_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=10000
full_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

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.mzph.cn/news/246640.shtml

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈email:809451989@qq.com,一经查实,立即删除!

相关文章

【theano-windows】学习笔记九——softmax手写数字分类

前言 上一篇博客折腾了数据集的预备知识, 接下来按照官方的Deep learning 0.1 documentation一步步走, 先折腾softmax, 关于softmax和logistic回归分类的联系, 我在之前写过一个小博客 国际惯例, 参考博客走一波: Classifying MNIST digits using Logistic Regression soft…

【theano-windows】学习笔记十——多层感知机手写数字分类

前言 上一篇学习了softmax, 然后更进一步就是学习一下基本的多层感知机(MLP)了. 其实多层感知机同时就是w*xb用某个激活函数激活一下, 得到的结果作为下一层神经元的输入x, 类似于 output⋯f3(f2(f1(x∗w1b2)∗w2b2)∗w3b3)⋯output=\cdots f^3(f^2(f^1(x*w^1+b^2)*w^2+b^2)*…

【theano-windows】学习笔记十一——theano中与神经网络相关函数

前言 经过softmax和MLP的学习, 我们发现thenao.tensor中除了之前的博客【theano-windows】学习笔记五——theano中张量部分函数提到的张量的定义和基本运算外, 还有一个方法称为nnet, 如果自己实现过前面两篇博客中的代码就会发现用到了theano.tensor.nnet.sigmoid和thenao.te…

【caffe-windows】全卷积网络特征图分析

前言 突然就想分析一下全卷积网络的转置卷积部分了, 就是这么猝不及防的想法, 而且这个网络对图片的输入大小无要求&#xff0c;这么神奇的网络是时候分析一波了&#xff0c;我个人的学习方法调试代码&#xff0c;然后对照论文看理论 本次分析主要针对每层的权重大小和特征图…

【theano-windows】学习笔记十二——卷积神经网络

前言 按照进度, 学习theano中的卷积操作 国际惯例, 来一波参考网址 Convolutional Neural Networks (LeNet) 卷积神经网络如何应用在彩色图像上&#xff1f; 卷积小知识 三大特性&#xff1a;局部感知(稀疏连接), 权值共享, 池化 上图很重要, 描述的是前一个隐层m-1具有四…

【theano-windows】学习笔记十三——去噪自编码器

前言 上一章节学习了卷积的写法,主要注意的是其实现在theano.tensor.nnet和theano.sandbox.cuda.dnn中都有对应函数实现, 这一节就进入到无监督或者称为半监督的网络构建中. 首先是自编码器(Autoencoders)和降噪自编码器(denoising Autoencoders) 国际惯例, 参考网址: Denoi…

【theano-windows】学习笔记十四——堆叠去噪自编码器

前言 前面已经学习了softmax,多层感知器,CNN&#xff0c;AE&#xff0c;dAE&#xff0c;接下来可以仿照多层感知器的方法去堆叠自编码器 国际惯例&#xff0c;参考文献&#xff1a; Stacked Denoising Autoencoders (SdA) Greedy Layer-Wise Training of Deep Networks 理…

【theano-windows】学习笔记十五——受限玻尔兹曼机

前言 终于到了最喜欢的模型: 受限玻尔兹曼机(RBM)了, 发现关于RBM是如何从能量模型发展过来的介绍非常不错, 而关于详细理论证明, 可以去看我前面的受限玻尔兹曼机的一系列博客. 国际惯例, 参考博客,超级强推第二个博客, 证明过程很给力: Restricted Boltzmann Machines (R…

【Ogre-windows】环境配置

前言 由于工程原因, 学习一下Ogre面向对象图形渲染开源引擎, 慢慢爬坑吧。首先还是环境的配置问题哎. 其实最重要的是要预先编译三方库, 虽然官方说可以自动编译, 但是在自己电脑上还是出现了无法解析外部符号之类的问题, 正常情况下我就认为是三方库的lib出现了问题, 最后额外…

【theano-windows】学习笔记十六——深度信念网络DBN

前言 前面学习了受限玻尔兹曼机(RBM)的理论和搭建方法, 如果稍微了解过的人, 肯定知道利用RBM可以堆叠构成深度信念网络(deep belief network, DBN)和深度玻尔兹曼机(deep Boltzmann machine), 这里就先学习一下DBN. 国际惯例, 参考博文: Deep Belief Networks A fast lear…

【Ogre-windows】实例配置

前言 折腾了好久才搞定教程实例, 主要是因为上一篇博客安装的具体版本是Ogre1.10.9, 而官方的Ogre Wiki Tutorial Framework没有指定具体版本, 如果单纯下载Ogre Wiki Tutorial Framework 1.10 - (Windows line endings, updated 2015-10-15) 运行, 基本会血崩. 所以, 在经过仔…

【Ogre-windows】旋转矩阵及位置解析

前言 这篇博客主要针对三种问题 如何创建动画帧如何获取全局位置如何计算全局旋转矩阵 仿真环境为VS2013Ogre1.10.9与matlab验证 创建动画帧 这里只做一个简单的实验: 将自带的人物模型Jaiqua的run运动给新创建的运动myrun中并播放&#xff0c;直接贴代码了 void JaiQua:…

BP推导——续

前言 之前有证明过一次人工神经网络——【BP】反向传播算法证明 &#xff0c;但是回头看的时候&#xff0c;有很多地方非常不严谨&#xff0c;特此拿出来再单独证明一次BP&#xff0c;并严格保证其严谨性。如果想看看粗略的证明&#xff0c;可以去看我之前的博客&#xff0c;毕…

matlab学习——强连通分量

前言 最近motion graph相关实验&#xff0c;发现实现运动过渡需要构建运动图&#xff0c;而为了避免运动过渡陷入死胡同&#xff0c;需要对图结构进行裁剪&#xff0c;方法就是计算图模型的极大强联通分量&#xff0c;但是自己懒得去实现&#xff0c;所以就去搜了一下matlab中…

【音频处理】离散傅里叶变换

前言 最近复现音乐驱动舞蹈的文章《Dancing-to-Music Character Animation》&#xff0c;用到了与傅里叶变换很相似的称为常Q变换的方法去分割音乐&#xff0c;所以对傅里叶变换做了一个小了解&#xff0c;本文不深入各种乱糟糟的理论&#xff0c;比如什么蝶形算法啥的&#x…

【音频处理】短时傅里叶变换

前言 上一篇博客讲了离散傅里叶变换&#xff0c;里面的实例是对整个信号进行计算&#xff0c;虽然理论上有N点傅里叶变换(本博客就不区分FFT和DFT了&#xff0c;因为它俩就是一个东东&#xff0c;只不过复杂度不同)&#xff0c;但是我个人理解是这个N点是信号前面连续的N个数值…

【theano-windows】学习笔记十九——循环神经网络

前言 前面已经介绍了RBM和CNN了&#xff0c;就剩最后一个RNN了&#xff0c;抽了一天时间简单看了一下原理&#xff0c;但是没细推RNN的参数更新算法BPTT&#xff0c;全名是Backpropagation Through Time。 【注】严谨来说RNN有两个称呼&#xff1a;①结构上递归的recursive n…

【theano-windows】学习笔记二十——LSTM理论及实现

前言 上一篇学习了RNN&#xff0c;也知道了在沿着时间线对上下文权重求梯度的时候&#xff0c;可能会导致梯度消失或者梯度爆炸&#xff0c;然后我们就得学习一波比较常见的优化方法之LSTM 国际惯例&#xff0c;参考网址&#xff1a; LSTM Networks for Sentiment Analysis …

刚体运动学——欧拉角、四元数、旋转矩阵

前言 刚体运动旋转一般用&#xff1a;欧拉角、四元数、轴角对等表示&#xff0c;在对某个坐标旋转的时候&#xff0c;只需将欧拉角或四元数转换为旋转矩阵&#xff0c;并与原始坐标相乘&#xff0c;便可得到旋转以后的坐标。这里主要看看欧拉角、四元数和旋转矩阵。 国际惯例…

刚体运动学-四元数插值

前言 之前对写了一篇关于刚体运动学相关知识博客&#xff1a;刚体运动学——欧拉角、四元数、旋转矩阵&#xff0c;本篇博客就举例来说明&#xff0c;如何在运动捕捉数据中进行四元数插值。 国际惯例&#xff0c;参考博客&#xff1a; 探讨&#xff1a;向量&#xff08;方向…