【theano-windows】学习笔记六——theano中的循环函数scan
前言
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是從列表的最后一個向著列表開頭傳入迭代
參數說明:
- fn是每步迭代應用的函數
- sequence是迭代序列列表
- outputs_info是reduce輸出的字典列表
- non_sequences傳入fn的參數列表,這些參數都不會參與迭代
- go_backwards是bool型參數, 如果是True就說明sequence是從列表的最后一個向著列表開頭傳入迭代
參數說明(關于foldl和foldr的說明可以戳這里1,這里2, 這里3):
- fn是每次循環執行的函數
- sequence是跌迭序列列表
- outputs_info輸出的字典列表
- non_sequences 迭代中不會傳入fn的參數列表
參數說明:
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是共享變量, 這些字典的添加是一致的
描述
更加節省空間的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 T1.計算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關鍵字
本實例演示的是從一個系數列表中構建符號計算:
按照上面的步驟, 同樣先定義兩個變量分別指示系數 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必須與每次迭代的輸出變量的形狀大小相同
下面計算的是
先定義變量 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.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. 共享變量——吉布斯采樣
例子: 進行十次吉布斯采樣
定義三個變量: 權重、可見層偏置、隱藏層偏置 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=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的基礎上繼續迭代, 所以運行結果是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,計算一步迭代:
#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?(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]=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.05.計算序列,涉及到兩步結果
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] = 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=?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()#58.乘以二項分布
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=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中的循环函数scan的全部內容,希望文章能夠幫你解決所遇到的問題。
- 上一篇: 浦发美丽女人巧虎卡值得申请吗?卡片评测分
- 下一篇: 警惕!信用卡会影响房贷,想贷款买房最好别