稀疏自編碼器及其實現——如何搞基

自編碼器是什麼?

自編碼器本身就是一種BP神經網絡。它是一種無監督學習算法

我們都知道神經網絡可以從任意精度逼近任意函數,這裏我們讓神經網絡目標值等於輸出值x,也就是模擬一個恆等函數


太無聊了,是嗎?輸入等於輸出,這網絡有什麼意義?但是,當我們把自編碼神經網絡加入某些限制,事情就發生了變化。如圖1所示,這就是一個基本的自編碼神經網絡,可以看到隱含層節點數量要少於輸入層節點數量。


圖1

舉個例子,如果我們輸入一張10*10的圖像,這樣就有100個像素,所以輸入層和輸出層的節點數量就是100。而我們取隱藏層節點數量爲25。注意,這樣就會迫使隱藏層節點學習得到輸入數據的壓縮表示方法,逼得隱藏層要用25維數據重構出100維的數據。這樣也就完成了學習過程。

這和我們學習的過程很像,假設一共有100個考點,但是隻允許你用25個知識點概括所有這些考點,這就是學習的過程。

 

稀疏自編碼器又是什麼?

更一般的,如果隱藏層節點數量很大,甚至比輸入層節點數量還要多時,我們仍然可以使用自編碼算法,但是這時需要加入稀疏性限制。這就是稀疏自編碼器。

 

什麼是稀疏性限制?

簡單說就是要保證隱藏神經元在大多數情況下是被抑制的狀態。具體表現就是sigmoid函數的輸出大多數狀態是0,tanh函數的輸出大多數狀態是-1。這樣有什麼好處?這樣能夠迫使隱藏神經元發揮最大的潛力,在很不利的條件下學習到真正的特徵。

 

怎麼衡量某個隱藏神經元的激活度?

取平均就好了,假設表示在給定輸入x的情況下,隱藏神經元j的激活度,那麼自然就有平均激活度


 

稀疏性懲罰項——相對熵

爲了保證這個稀疏度小到我們希望的那麼多,比如說,即


我們需要在優化目標函數中加入一個額外的懲罰因子,這個罰因子基於相對熵(KLdivergence):


因此這個罰因子會有如下性質,當時(這裏取稀疏性參數爲0.2),等於0,而兩者差異越來越大時,相對熵會快速趨近於無窮大,如圖2所示:

 

圖2

稀疏自編碼器訓練方式與BP神經網絡相對比

稀疏自編碼神經網絡的代價函數是BP神經網絡的代價函數加上一個稀疏性懲罰項:


相應地,殘差迭代公式也要進行修正


 

實現一個稀疏自編碼器


數據概覽

採用的是Andrew著名的UFLDL給出的樣例,其中數據文件叫做IMAGES,這是一個512*512*10的三維數組,裏面存了10張圖片,每張都是262144像素。執行

<span style="font-size:14px;">loadIMAGES;
imagesc(IMAGES(:,:,6))
colormapgray;</span>


可以看看第6張圖的樣子,如圖3所示,是一張關於森林和雪山的圖像。


圖3


數據採樣

由於數據的圖片挺大,我們總不能一上來搞一個每層都2萬多節點的神經網絡訓練吧。我們先採樣,這裏從10張圖片裏面隨機採樣10000個小patch,每個小patch是一個8*8像素小碎片,我們定義訓練集是64*10000的矩陣。每一列就是把剛剛的小patch拉成列向量的結果。

這裏代碼如下

<span style="font-size:14px;">loadIMAGES;    % load images from disk
patchsize= 8;  % we'll use 8x8 patches
%numpatches = 10;
numpatches= 10000;
%Initialize patches with zeros.  Your codewill fill in this matrix--one
%column per patch, 10000 columns.
patches= zeros(patchsize*patchsize, numpatches);
tic
image_size=size(IMAGES);
i=randi(image_size(1)-patchsize+1,1,numpatches);
j=randi(image_size(2)-patchsize+1,1,numpatches);
k=randi(image_size(3),1,numpatches);
fornum=1:numpatches
patches(:,num)=reshape(IMAGES(i(num):i(num)+patchsize-1,j(num):j(num)+patchsize-1,k(num)),1,[]);
end
toc</span>

這裏randi函數,randi(iMax,m,n)在閉區間[1,iMax]生成m*n型隨機矩陣。而reshape函數的作用是把每個小patch拉成一個列向量。

前200個patch的樣子如圖4所示:


圖4

sparseAutoencoderCost.m

這部分代碼是最核心也是最重要的,正如前面所說,現在優化目標函數有了3個部分:均方差項、權重衰減項、稀疏懲罰項。

這裏程序需要一部分一部分地調試,否則非常容易得到看似正確但實際效果沒有正確代碼好的錯誤結果。這裏附上我最新修改版本的代碼,去掉了所有顯式for循環,使用矢量化編程,簡潔了代碼,增強運行效率,但也減弱了可讀性。

<span style="font-size:14px;">numpatches=size(patches,2);
a2=sigmoid(W1*patches+repmat(b1,1,numpatches));
a3=sigmoid(W2*a2+repmat(b2,1,numpatches));
Rho=sum(a2,2)/numpatches;
Penalty=-sparsityParam./Rho+(1-sparsityParam)./(1-Rho);
Delta3=(a3-patches).*a3.*(1-a3);
Delta2=(W2'*Delta3+beta*repmat(Penalty,1,numpatches)).*a2.*(1-a2);
cost1=sumsqr(a3-patches)/numpatches/2;
cost2=(sumsqr(W1)+sumsqr(W2))*lambda/2;
cost3=beta*sum(sparsityParam*log(sparsityParam./Rho)+(1-sparsityParam)*log((1-sparsityParam)./(1-Rho)));
cost=cost1+cost2+cost3;
W2grad=Delta3*a2'/numpatches+lambda*W2;
b2grad=sum(Delta3,2)/numpatches;
W1grad=Delta2*patches'/numpatches+lambda*W1;
b1grad=sum(Delta2,2)/numpatches;</span>

這裏repmat的重複使用是用來複制矩陣的,一定要不遺餘力去掉這部分代碼中所有的顯式for循環,否則執行起來時間會很長。

 

梯度校驗

梯度校驗是代碼調試的大殺器,這一部分我用到了for循環,效率確實非常低了,所以調試的時候要把校驗參數做些調整,令隱藏節點數量爲2,採樣數量爲100,這樣就能大大加快校驗速度。否則要等相當久的時間。

<span style="font-size:14px;">EPSILON=0.0001;
thetaspslion=zeros(size(theta));
fori=1:size(theta)
thetaspslion(i)=EPSILON;
numgrad(i)=(J(theta+thetaspslion)-J(theta-thetaspslion))/2/EPSILON;
thetaspslion(i)=0;
end</span>

注意:最後運行時一定要關掉梯度校驗,不然就要卡死機了。。。

這個梯度校驗對於目標函數的3部分的調試一定要循序漸進,心急吃不了熱豆腐。均方差項調試正確了以後,會得到如圖5所示的圖像


圖5

而當,均方差項和權重衰減項都調試通過後,得到的圖片是這樣的


圖6

而當所有的目標函數都調試通過以後,接下來就是見證奇蹟的時刻


圖7

這就是圖像的基,通過稀疏自編碼器,我們學習得到了25個8*8的基,這些圖像的基就相當於咱們每個視神經細胞所看到的東西,當這些細胞組成陣列,壘成層層疊疊,我們就能看到所有的東西了。

稀疏自編碼器作爲無監督學習的一層基本模塊,這就是我們DeepLearning萬里長城的第一步:搞基

發佈了54 篇原創文章 · 獲贊 86 · 訪問量 47萬+
發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章