Scan是Theano中最基础的循环函数, 官方教程主要是通过大量的例子来说明用法. 不过在学习的时候我比较习惯先看看用途, 然后是参数说明, 最后再是研究实例.
国际惯例, 参考网址
官网关于Scan的11个例子
官网更全面的介绍
简介用途
递归的一般形式, 可以被用于循环scan有两个特殊的案例Reduction和mapscan可以按照某个输入序列执行一个函数, 在每个时间戳都提供一个输出, 可以被函数的下一次执行所看到可以看到之前执行函数在前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_backwards是bool型参数, 如果为True就说明sequence是从列表的最后一个向着列表开头传入迭代 theano.reduce(fn, sequences, outputs_info, non_sequences=None, go_backwards=False, mode=None, name=None)参数说明:
fn是每步迭代应用的函数sequence是迭代序列列表outputs_info是reduce输出的字典列表non_sequences传入fn的参数列表,这些参数都不会参与迭代go_backwards是bool型参数, 如果是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)参数说明(关于foldl和foldr的说明可以戳这里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提供的序列) 序列的顺序与丢给scan的sequence列表一样, 输出的顺序与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):
outputs是theano变量或者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_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的参数顺序是sequences、outputs_info、non_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_info是None, 说明它相当于没有, 可以忽视它继续看后面的将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.arange到sequences中, 为什么长度不一样也能丢到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+⋯+nresults=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_valoutputs_info=outputs_info,#传给sum_to_datenon_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.tensor的set_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(w∗v+bh)P(v|h)=sigmoid(w∗h+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 + 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【注】这个例子的官方文档书写可能有问题, 可以参考我的改改,但是我写的不一定对嘛
我们可以发现这个例子中, 更新b和c的区别在于, 一个用updates, 而另一个没有, 因而使用了updates的变量可以在每次迭代中获取到a的更新值11, 而没有使用updates更新规则的函数中,a的值始终是1,这就是为什么看到了两个结果1+1=2和11+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的基础上继续迭代, 所以运行结果是1∗2∗2∗2⋯∗21∗2∗2∗2⋯∗2
可以发现为了提前终止循环, 在函数内部进行了条件控制, 而使用的参数被包含在类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]之前我们介绍过scan中sequences和outputs_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_vals和y_vals就是在u上迭代以后生成的指向序列x和y的符号变量, 其中sequences_taps和outputs_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(W∗x+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(W∗x(t−1)+U∗y(t)+V∗p(T−t))x(t)=tanh(W∗x(t−1)+U∗y(t)+V∗p(T−t))
注意这个式子中x(t−1)x(t−1)在实现的时候, 由于scan本身当前次迭代就是在上一次迭代的结果进行的, 所以不需要使用taps=[-1]取值, 后面的tt和T−tT−t分别表示按顺序取值和逆序取值
#计算序列 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)=U∗x(t−2)+V∗x(t−1)+tanh(W∗x(t−1)+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] = 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(A∗x)∂y∂x=?y=tanh(A∗x)∂y∂x=? 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(W∗v+b)∗d,where.d∈binomialtanh(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=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添加进去都可以计算出正确结果, 难道原因是results与updates是存在dict中, 传递results给function的输出的同时也已经把其更新规则传递进去了?好像这样理解也没什么不对, 毕竟前面我们发现function的输出可以是表达式, 也可以是表达式返回值
code:链接: https://pan.baidu.com/s/1o8wVGjo 密码: 59pg