自編碼器是什麼?
自編碼器本身就是一種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萬里長城的第一步:搞基!