棧式自動編碼器(Stacked AutoEncoder)
起源:自動編碼器
單自動編碼器,充其量也就是個強化補丁版PCA,只用一次好不過癮。
於是Bengio等人在2007年的 Greedy Layer-Wise Training of Deep Networks 中,
仿照stacked RBM構成的DBN,提出Stacked AutoEncoder,為非監督學習在深度網絡的應用又添了猛將。
這里就不得不提 “逐層初始化”(Layer-wise Pre-training),目的是通過逐層非監督學習的預訓練,
來初始化深度網絡的參數,替代傳統的隨機小值方法。預訓練完畢后,利用訓練參數,再進行監督學習訓練。
Part I 原理
非監督學習網絡訓練方式和監督學習網絡的方式是相反的。
在監督學習網絡當中,各個Layer的參數W受制於輸出層的誤差函數,因而Layeri參數的梯度依賴於Layeri+1的梯度,形成了"一次迭代-更新全網絡"反向傳播。
但是在非監督學習中,各個Encoder的參數W只受制於當前層的輸入,因而可以訓練完Encoderi,把參數轉給Layeri,利用優勢參數傳播到Layeri+1,再開始訓練。
形成"全部迭代-更新單層"的新訓練方式。這樣,Layeri+1效益非常高,因為它吸收的是Layeri完全訓練奉獻出的精華Input。
Part II 代碼與實現
主要參考 http://deeplearning.net/tutorial/SdA.html
棧式機在構造函數中,構造出各個Layer、Encoder,並且存起來。
Theano在構建棧式機中,易錯點是Encoder、Layer的參數轉移。
我們知道,Python的列表有深淺拷貝一說。Theano所有被shared標記的變量都是淺拷貝。
因而首先有這樣的錯誤寫法:
def __init__(self,rng,input,n_in,n_out,layerSize): ...... for i in xrange(len(layerSize)): ...... da.W=hidenlayer.W da.bout=hidenlayer.b
然后你在外部為da做grad求梯度的時候就報錯了,提示說params和cost函數不符合。
這是因為cost函數的Tensor表達式在寫cost函數時就確定了,這時候da這個對象剛好構造完,因而Tensor表達式中的da.W是構造隨機值。
然后我們在da構造完了之后,手賤把da.W指向的內存改變了(淺拷貝相當於引用),這樣算出的grad根本就不對。
其實這樣寫反了,又改成了這樣
def __init__(self,rng,input,n_in,n_out,layerSize): ...... for i in xrange(len(layerSize)): ...... hidenlayer.W=da.W hidenlayer.b=da.bout
好吧,這樣不會報錯了,而且每訓練一個Encoder,用get_value查看Layer的值確實改變了。但是,訓練Encoderi+1的時候,怎么感覺沒效果?
其實是真的沒效果,因為Layeri的參數根本沒有傳播到Layeri+1去。
Theano采用Python、C雙內存區設計,在C代碼中訓練完Encoderi時,參數並沒有轉到Layeri中。但是我們明明建立了淺拷貝啊?
原來updates函數在C內存區中,根本沒有覺察到淺拷貝關系,因為它在Python內存區中。
正確做法是像教程這樣,在da構造時建立淺拷貝關系,當編譯成C代碼之后,所有Python對象要在C內存區重新構造,自然就在C內存區觸發了淺拷貝。
da=dA(rng,layerInput,InputSize,self.layerSize[i],hidenlayer.W,hidenlayer.b)
或者訓練完Encoderi,強制把Encoderi參數注入到C內存區的Layeri里。
updateModel=function(inputs=[],outputs=[],updates=[(....)], updateModel()
Theano的寫法風格近似於函數式語言,對象、函數中全是數學模型。一旦構造完了之后,就無法顯式賦值。
所以,在Python非構造函數里為對象賦值是愚蠢的,效果僅限於Python內存區。但是大部分計算都在C內存區,所以需要updates手動把值打進C內存區。
updates是溝通兩區的橋梁,一旦發現Python內存區中有建立淺拷貝關系,就會把C內存區中值更新到Python內存區。(有利於Python中保存參數)
但是絕對不會自動把Python內存區值,更新到C內存區當中。(這點必須小心)
這種做法可以擴展到,監督訓練完之后,參數的保存與導入。