1. 程式人生 > >MATLAB 深度學習:自編碼(含MATLAB)

MATLAB 深度學習:自編碼(含MATLAB)

本節將研究深度學習網路權值設計的重要思想之一:自編碼思想,在正式介紹之前先以一個簡單的介紹一篇,一層隱含層網路的自編碼學習問題。

那麼這種網路有什麼實際應用呢?自動編碼器(Autoencoder)是當前深度學習研究的熱點之一,有很多重要的應用領域,這裡僅舉一個有趣的例子,大家還記得前一段時間百度推出的上傳你的照片,系統幫你找到與你像的明星嗎?其實這個功能就可以用自編碼器(Autoencoder)來實現,首先,我們將已經訓練好的自動編碼器(Autoencoder)的輸入層和中間層組成的網路拿出來,將所有明星的臉進行壓縮,得到一個人臉向量,儲存起來,然後,當普通使用者上傳了自己的照片後,系統將使用者照片輸入自動編碼器(Autoencoder)的輸入層,從中間層得到該使用者的人臉向量,最後拿這個使用者的人臉向量與明星們的人臉向量進行比較,找出向量距離最近的一個或幾個明星,將明星的照片作為與使用者最像的明星照片,返回給使用者。由於百度這項服務推出的時間較早,應該不是基於自動編碼器實現的,但是使用自動編碼器,完全可以實現這個功能,有興趣的讀者完全可以試一下。

當然去噪自動編碼機(Denosing Autoencoder)更大的應用是作為深度學習中的一層,堆疊在一起形成深度學習網路。


自編碼是一種非常簡單的BP神經網路,是一種無監督學習演算法。

在一般BP神經網路中,進行資料訓練時,對一組輸入資料,就會對應一組輸出資料,通過調整預測輸出資料跟真事輸出資料的差值(表示成代價函式的形式),對權值進行微調,從而得到各層網路的權值。但是對於某些輸入來說,其輸出 是得不到的,那麼如何訓練這些資料,得出這些資料的特性?就是通過使輸入等於輸出。

以一個簡單三層網路為例如下: 
這裡寫圖片描述 
這裡我們假設輸出等於輸入來訓練這個網路引數(可能訓練好的網路引數不可能讓輸出百分百等於輸入,至少會非常接近吧)。那麼這個網路在輸入確定了以後(這時輸出也就確定了吧),唯一需要確定的就是隱含層的個數了吧,以上述這個圖為例,我們可以看到,網路的輸入與輸出都是一個6維的向量,而隱含層是3個,也就是3維。

那麼這種自編碼有什麼意義呢?它又有什麼用呢?可以看到,自編碼可以使得網路通過學習轉化成一組另外的量,這組量又可以通過譯碼恢復成原始的量,這個過程就相當於先求函式,再求反函式的過程,如果通過網路求得的函式(也就是圖中輸入和隱層之間的對應關係)其對應的反函式能夠最大限度的反應出最原始的輸入,說明這個函式能夠真實體現出輸入資料的特性,是原始資料的另外一種表達形式。也就是說,原始的量可以通過編碼對映成另一組量吧,這一組量既然可以通過譯碼恢復成原始的量,說明了什麼?中間這一層的輸出是不是就是原始輸入的另外一種表達了?是的。這就好比一個人,你看得到時候直接看就是一個人,當你看不到人的時候,比如說你聽到了他的名字,你也知道這個名字表示的就是這個人。所以這個名字就是這個人的另一種條件下的新特徵,而往往這種新特徵更能去分這個人是誰。


  1. 好了再看看上面這個圖,輸入6維,隱含層以後變成了3維,輸出還是6維,我們單看到隱含層發現了什麼?是不是輸入從6維降到了3維?但是這3為在某種意義上還是原始資料的典型特徵吧,言外之意是不是相當於降維了。如果知道主成分分析法(PCA)的人應該瞭解,pca方法其實就是實現資料降維的,那麼在這裡我們通過這種自編碼,規定隱含層神經元的個數以後,通過自編碼的訓練,讓網路的輸出儘可能的等於輸入,待自編碼完成後(也就是誤差達到可接受範圍),那麼輸入通過隱含層的輸出就相當於降維了吧(前提是隱含層的神經元個數要小於輸入維數,這樣才叫降維,否則的話叫升維),說到升維,瞭解PCA的朋友你們有沒有試過PCA升維呢?PCA能不能升維呢?哈哈,貌似不能,沒試過。但是理論上是可以的。那麼升維相當於將資訊複雜化,這種操作有沒有用呢?可能還是有用的吧,瞭解SVM的朋友知道,SVM裡面就有將非線性資料通過升維以後可以線上性範圍內可分,那麼這裡的升維是不是也能將原始非線性的資料變到線性呢?可以去試試看,應該有那麼個意思。則這個問題就變成了一個線性代數中的主成份分析問題了。假設中間層有k個節點,就變成由輸入訊號x的前k個主成份項,來近似表示原始輸入訊號。關於這個問題,我們會在深度學習數學基礎的線性代數章節中,詳細分析這一過程。

因為y可以視為x的有失真壓縮形式,通過我們的優化演算法,可以對訓練樣本產生很好的壓縮效果,同時在測試樣本集上有很好的表現,但是我們並不能保證網路可以所有樣本都有好的壓縮效果。
隨著對自動編碼器的研究,研究者們發現,對於採用非線性編碼器,隨機梯度下降演算法時,當中間層神經元個數大於輸入層神經元個數時,網路誤差和泛化的效果會更好一些。
這是什麼原因呢?因為我們採用的是隨機梯度下降加早期終止優化演算法,我們的自動編碼器編碼層非線性神經元的連線權值需要非常小,才能模擬出線性變換的效果,同時,對於解碼網路,又需要非常大的權值,才能有效的恢復原始訊號(可以想像成編碼層是乘以一個數,而解碼層是乘以這個數的倒數),而我們的優化演算法,很難產生特別大的連線權值。因此,我們的自動編碼器只能擬合於我們的訓練樣本集。增加中間層神經元的數量,可以在一定程度上解決這個問題。
但是,如果我們把自動編碼器的編碼層視為對輸入訊號的壓縮,如果中間層的神經元數量多於輸入層,那麼不僅沒有實現壓縮,反而使訊號擴大了。這就未免太搞笑了。為此,很多研究人員提出,可以通過向中間層神經元引入稀疏性,使大部分神經元為0或者接近為0,從而達到訊號壓縮的目的。從實踐上來看,這種方法的效果還不錯。這種方法為去噪自編碼

我們在這裡介紹的是另一種方法,來解決自動編碼器編解碼效能問題。與上面增加中間層神經元數量的方法不同,在這裡我們通過向編解碼過程中新增隨機噪音來解決這一問題,這也是我們為什麼把今天所介紹的網路稱為去噪自動編碼機(dA)的原因。

這個方法的核心思想很簡單,就是隨機的將原始訊號的一些維度數值變為0,有時甚至可以達到一半左右,將這個加入了隨機噪音的訊號輸入我們的去噪自動編碼器,將得到的輸出與原始輸入訊號之間計算誤差,還是採用前面的隨機梯度下降演算法,對權值進行調整,使誤差達到最小。如果網路可以做到這一點,就可以視為網路自動將我們人為加入了隨機噪音去掉了。
我們自然會問,為什麼要這樣做呢?實際上,對於如影象訊號而言,很多訊號內容是冗餘的,去掉之後不影響資訊量,例如當影象訊號有一些雪花時,我們還是可以識別出影象內容的。這說明讓自動編解碼機去噪是可能的。至於加入噪音的原因,是因為我們人對影象內容的理解,完全可以在某些資訊缺失的情況下得出結論,如影象中物體被遮蓋、破損的情況下,我們依然可以識別影象中的物體。去噪自動編碼機(dA)通過加入隨機噪聲,試圖使神經網路也具有與人類似的能力



好了說了這麼多,我們還是來看降維的情況,通過自編碼實現資料的降維思想最初是2006年深度學習領域大牛Hinton想出來的,並且發表在頂級期刊Science上,文章的出處在這裡:

該篇經典之作也被視為深度學習的開山之作,自此以後深度學習火了起來,並且逐漸打敗傳統模式識別領域的淺層學習演算法。我們知道,機器學習或者模式識別,對資料的主要工作在做什麼?無非提取資料的主要特徵,那麼以前可能所有的特徵要麼是人為設計出來,要麼是淺層學習出來的,像PCA,他們雖然一定程度上有用,但是相對於深度學習這種將資料的各個層次的特徵都學習出來了的相比自然弱了不少,這也是深度學習的最強大之處。

瞭解了自編碼,下面我們來實際看看這種自編碼的效果。 
這裡以matlab平臺實驗,假設我們選取一系列小塊影象(至於小塊選多大,這裡考慮速度原因,單檯筆記本的計算原因,選擇6*6的小塊影象,其實這是很小了,所以效果不是很明顯,大概這個意思)作為輸出來進行三層網路的自編碼學習,然後看看隱含層學習到的特徵是什麼。選擇matlab是因為它自帶神經網路的建構函式便於實驗,關於其神經網路工具箱的使用介紹,看這篇

好了開始實驗吧,首先我們需要找到自編碼的輸入樣本,這裡我們以每個6*6的小塊影象作為一個樣本,先準備好很多這樣的小塊影象,假設準備1000個吧,至於怎麼準備,你可以讓每個小塊假設是從某個大影象上隨機擷取的好了。

clc
clear
%選擇塊影象的大小
patch = [6,6];
%選擇塊影象的個數
num_pic = 1000;
data = zeros(num_pic,patch(1)*patch(2));
for i = 1:20
    pic_name = [num2str(i),'.jpg'];
    I = imread(pic_name);
    I = rgb2gray(I);
    for j = 1:50
        %隨機選擇塊的位置
        choose_row = round((size(I,1) - 50)*rand);
        choose_col = round((size(I,2) - 50)*rand);
        %提取塊並存起來
        I_patch = I(choose_row+1:(choose_row+6),choose_col+1:(choose_col+6));
        data((i-1)*50+j,:) = I_patch(:);
    end
end
%歸一化到0-1
data = mapminmax(data, 0, 1);
save data.mat data        
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11
  • 12
  • 13
  • 14
  • 15
  • 16
  • 17
  • 18
  • 19
  • 20
  • 21
  • 22
  • 23
  • 24

然後把這些樣本既當成輸入,也當成輸出輸送到神經網路中,比如這裡我們就得到了1000*36的樣本,每一行一個6*6的塊,1000個這樣的塊,輸出也是1000*36,就這樣。接著就可以訓練了,這裡我們再取100個隱含層。那麼假設訓練好了。我們怎麼視覺化這個結果呢?因為我們需要看的是它自編碼的編碼部分,而不是譯碼部分,所以我們只關心編碼部分的權值,像下面這個: 
這裡寫圖片描述

比如x1經過隱含層的對映會有三個輸出,那麼這三個輸出就是編碼結果,同理其他的。而我們是需要把這個對映結果顯示出來,也就是把x1對映的三個值顯示出來,同理其他的。因為這裡只有三個值,根本沒辦法視覺化。所以像上面我們取了100個隱含層,把每個輸入每個單元對映100個輸出,再把100變成10*10影象,我們就可以顯示了,並且這裡我們也只是顯示這些權值,把輸出預設為全1看看。

好了,那麼採用matlab工具箱,上述data選擇完後,構造神經網路模型,簡單的幾行程式碼:


clc
clear
load data
% data = data(1:100,:);
%% 神經網路的構建與訓練
% 構造神經網路(包含100個隱藏層的節點)
net = feedforwardnet(100);
net.layers{2}.transferFcn = 'tansig';% 輸出的對映方法,預設purelin--線性對映
% 訓練網路
net = train(net,data',data');
% 視覺化權值
show_result(net);
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11
  • 12
  • 13

這樣matlab自帶它的訓練模型就出來了: 
這裡寫圖片描述

視覺化的函式也很簡單:

function show_result(net)
% 將輸入到隱含層的權值提出來並顯示
W1 = net.IW{1};
[~,n] = size(W1);
for i = 1:n
    im = W1(:,i);
    im = reshape(im,10,10);
    subplot(6,6,i);
    imshow(im,[]);
end
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11

最終得到結果如下: 
這裡寫圖片描述

可以看到輸出的編碼樣子,有點像卻很不明顯,原因就是這是由36到100的升維,大了電腦帶不動,在matlab的這個自帶工具箱下。

所以,即使我們選擇的塊只有6*6,1000個塊,然而這個訓練過程卻很漫長,為什麼?就是matlab自帶的神經網路並不是適合這種自編碼的訓練,而是淺層的輸入到輸出的訓練,真正的自編碼訓練需要特定的設計才能保證速度,這裡只是強制使用著看看,同時自編碼還可以優化,比如加入稀疏變成稀疏自編碼等等,後面介紹一種工具箱來重新實驗這種自編碼。