【theano-windows】学习笔记六——theano中的循环函数scan
2017-09-05 16:15
447 查看
前言
Scan是
Theano中最基础的循环函数, 官方教程主要是通过大量的例子来说明用法. 不过在学习的时候我比较习惯先看看用途, 然后是参数说明, 最后再是研究实例.
国际惯例, 参考网址
官网关于
Scan的11个例子
官网更全面的介绍
简介
用途递归的一般形式, 可以被用于循环
scan有两个特殊的案例
Reduction和
map
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_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] 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):
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 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_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的参数顺序是
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) #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.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=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+⋯+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_val outputs_info=outputs_info,#传给sum_to_date non_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.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编译
#设计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(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
【注】这个例子的官方文档书写可能有问题, 可以参考我的改改,但是我写的不一定对嘛
我们可以发现这个例子中, 更新
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=True values, 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=True values, 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⋯∗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]
之前我们介绍过
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(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] = 2 print 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−1)在实现的时候, 由于
scan本身当前次迭代就是在上一次迭代的结果进行的, 所以不需要使用
taps=[-1]取值, 后面的t和T−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]=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)=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(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(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=?分析:可以利用每上一次的结果继续计算下一次的结果
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=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添加进去都可以计算出正确结果, 难道原因是
results与
updates是存在
dict中, 传递
results给
function的输出的同时也已经把其更新规则传递进去了?好像这样理解也没什么不对, 毕竟前面我们发现
function的输出可以是表达式, 也可以是表达式返回值
code:链接: https://pan.baidu.com/s/1o8wVGjo 密码: 59pg
相关文章推荐
- 【theano-windows】学习笔记五——theano中张量部分函数
- 【theano-windows】学习笔记十一——theano中与神经网络相关函数
- 【theano-windows】学习笔记二——theano中的函数和共享参数
- Swift学习笔记之字典,循环,可选变量,函数签名,面向对象
- 【theano-windows】学习笔记十四——堆叠去噪自编码器
- Windows内核学习笔记(二)-- IoCallDriver函数与PoCallDriver函数
- 【theano-windows】学习笔记三——theano中的导数
- python 学习笔记day02-python循环、文件、函数、模块
- Scala学习教程笔记一之基础语法,条件控制,循环控制,函数,数组,集合
- 【theano-windows】学习笔记一——theano中的变量
- Scala学习教程笔记一之基础语法,条件控制,循环控制,函数,数组,集合
- 【theano-windows】学习笔记十二——卷积神经网络
- Linux学习笔记:循环,定义函数
- 【theano-windows】学习笔记四——theano中的条件语句
- Theano学习-循环-scan
- 【theano-windows】学习笔记十五——受限玻尔兹曼机
- 函数参数Windows程序设计_Chap01_起步_学习笔记
- python 学习笔记3(循环方式;list初始化;循环对象/生成器/表推导;函数对象;异常处理)
- DELPHI语法基础学习笔记-Windows 句柄、回调函数、函数重载等
- 【极客学院】-iOS学习笔记-5-Swift(变量,循环,控制,函数,面向对象,小项目:呈现网页)