1. 程式人生 > >deeplearning系列(五)實現一個簡單的深度神經網路

deeplearning系列(五)實現一個簡單的深度神經網路

1. 深度學習概覽

淺層神經網路的介紹中,實現了包含一個隱藏層的淺層神經網路,對於這樣的淺層網路,在網路訓練過程中可以通過反向傳播演算法得到較優的網路引數值。然而,因為只有一個隱藏層,限制了模型的表達能力。

在本節中,開始介紹包含多個隱藏層的深度神經網路,經過多個隱藏層對上一層的非線性變換,深度神經網路有遠超過淺層網路的表達能力。

但訓練深度神經網路並不是一件十分輕鬆的工作,淺層神經網路的訓練經驗不能直接移植過來。這其中主要存在一下幾方面原因:

  1. 資料量大小。淺層神經網路的訓練依賴於有標籤的資料。深度網路,因其遠超過淺層網路的引數量,需要更多的有標籤資料來訓練,而通常這樣的資料是很難獲取的。
  2. 區域性極值。神經網路是一個非凸的優化問題,對於淺層網路來說,可以通過訓練使引數收斂到合理的區域性極值。而深度網路是一個高度非凸的問題,存在很多的壞的區域性極值,使用梯度下降法一般不能收斂到合理的引數值。
  3. 梯度彌散。使用反向傳播計算梯度時,當網路層次很多時,網路前幾層梯度幅值很小。使用梯度下降時,前幾層引數更新速度也因此變得緩慢,這些層不能從樣本中有效學習。

那麼有沒有可以解決這些問題的方案,從而使深度網路的訓練是可行的呢?採用逐層貪婪訓練得到每層引數,然後再使用解決淺層神經網路的演算法(例如:BP+L-BFGS)對引數微調是一個比較可行的解決方案。

2. 棧式自編碼神經網路

棧式自編碼神經網路是一個由多層稀疏自編碼器組成的神經網路,前一層自編碼器的輸出作為後一層的輸入。棧式自編碼神經網路引數是通過逐層貪婪訓練獲得的。以一個包含2個隱藏層,輸出層為softmax的神經網路為例,其訓練過程可以表示為:

  1. 用原始輸入x訓練第一個自編碼器,學習原始輸入的一階特徵h(1),如下圖(左)所示;
  2. 將所有訓練資料輸入上面第一個自編碼器,得到其一階特徵h(1),然後作為第二個自編碼器的輸入,學習原始輸入的二階特徵h(2)如下圖(中)所示;
  3. 將所有一階特徵輸入到訓練好的第二個自編碼器,得到所有的二階特徵h(2),作為softmax分類器的輸入,訓練分類器的引數。

這裡寫圖片描述

3. 引數微調

在上述預訓練結束之後,將上面三層結合起來得到包含兩個隱藏層和一個softmax輸出層的棧式自編碼網路,如下圖所示。


圖片名稱

然後採用反向傳播演算法調整所有層的引數,這個過程稱為微調。微調過程中,網路所有層的全部引數都被優化,經過微調後,可以大幅提高神經網路的分類效能。

4. 程式碼實現

程式碼結構為:

  • STEP:0-1是引數設定及訓練資料的獲取部分;
  • STEP:2-4是棧式自編碼訓練部分。包括兩個自編碼器和一個softmax迴歸訓練部分,經過這樣的訓練,可以得到一個適合微調的引數初始值;
  • STEP:5是引數的微調部分,包括使用反向傳播計算梯度和用L-BFGS優化引數。
  • STEP:6用經過訓練後的網路引數對測試資料集中的資料進行測試。
%% STEP 0: Here we provide the relevant parameters values 
inputSize = 28 * 28;
numClasses = 10;
hiddenSizeL1 = 200;    % Layer 1 Hidden Size
hiddenSizeL2 = 200;    % Layer 2 Hidden Size
sparsityParam = 0.1;   % desired average activation of the hidden units.
lambda = 3e-3;         % weight decay parameter       
beta = 3;              % weight of sparsity penalty term

%% STEP 1: Load data from the MNIST database
trainData = loadMNISTImages('mnist/train-images.idx3-ubyte');
trainLabels = loadMNISTLabels('mnist/train-labels.idx1-ubyte');
trainLabels(trainLabels == 0) = 10; % Remap 0 to 10 since our labels need to start from 1

%% STEP 2: Train the first sparse autoencoder
sae1Theta = initializeParameters(hiddenSizeL1, inputSize);
addpath minFunc/
options.Method = 'lbfgs'; % Here, we use L-BFGS to optimize our cost function. 
options.maxIter = 400;    % Maximum number of iterations of L-BFGS to run 
options.display = 'on';
[sae1OptTheta, cost] = minFunc( @(p) sparseAutoencoderCost(p,inputSize, hiddenSizeL1, 
                                   lambda, sparsityParam,beta, trainData), 
                                   sae1Theta, options);

%% STEP 3: Train the second sparse autoencoder
[sae1Features] = feedForwardAutoencoder(sae1OptTheta, hiddenSizeL1,inputSize, trainData);
sae2Theta = initializeParameters(hiddenSizeL2, hiddenSizeL1);
[sae2OptTheta, cost] = minFunc( @(p) sparseAutoencoderCost(p, ...
                                   hiddenSizeL1, hiddenSizeL2, ...
                                   lambda, sparsityParam, ...
                                   beta, sae1Features), ...
                                   sae2Theta, options);

%% STEP 4: Train the softmax classifier
[sae2Features] = feedForwardAutoencoder(sae2OptTheta, hiddenSizeL2, ...
                                        hiddenSizeL1, sae1Features);
saeSoftmaxTheta = 0.005 * randn(hiddenSizeL2 * numClasses, 1);
softmaxModel = softmaxTrain(hiddenSizeL2, numClasses, lambda, ...
                            sae2Features, trainLabels, options);
saeSoftmaxOptTheta = softmaxModel.optTheta(:);

%% STEP 5: Finetune softmax model
stack = cell(2,1);
stack{1}.w = reshape(sae1OptTheta(1:hiddenSizeL1*inputSize), ...
                     hiddenSizeL1, inputSize);
stack{1}.b = sae1OptTheta(2*hiddenSizeL1*inputSize+1:2*hiddenSizeL1*inputSize+hiddenSizeL1);
stack{2}.w = reshape(sae2OptTheta(1:hiddenSizeL2*hiddenSizeL1), ...
                     hiddenSizeL2, hiddenSizeL1);
stack{2}.b = sae2OptTheta(2*hiddenSizeL2*hiddenSizeL1+1:2*hiddenSizeL2*hiddenSizeL1+hiddenSizeL2);
[stackparams, netconfig] = stack2params(stack);
stackedAETheta = [ saeSoftmaxOptTheta ; stackparams ];
[stackedAEOptTheta, cost] = minFunc( @(p) stackedAECost(p, ...
                                   inputSize, hiddenSizeL2, ...
                                   numClasses,netconfig,lambda, ...
                                   trainData, trainLabels), ...
                                   stackedAETheta, options);

%% STEP 6: Test 
testData = loadMNISTImages('mnist/t10k-images.idx3-ubyte');
testLabels = loadMNISTLabels('mnist/t10k-labels.idx1-ubyte');
testLabels(testLabels == 0) = 10; % Remap 0 to 10
[pred] = stackedAEPredict(stackedAETheta, inputSize, hiddenSizeL2, ...
                          numClasses, netconfig, testData);
acc = mean(testLabels(:) == pred(:));
fprintf('Before Finetuning Test Accuracy: %0.3f%%\n', acc * 100);
[pred] = stackedAEPredict(stackedAEOptTheta, inputSize, hiddenSizeL2, ...
                          numClasses, netconfig, testData);
acc = mean(testLabels(:) == pred(:));
fprintf('After Finetuning Test Accuracy: %0.3f%%\n', acc * 100);

用L-BFGS優化引數,需要提供一個輸入是網路引數:theta,輸出是網路輸出:cost和引數梯度:grad的函式。其中函式梯度是用反向傳播演算法得到的,程式碼如下:

function [ cost, grad ] = stackedAECost(theta, inputSize, hiddenSize, ...
                                              numClasses, netconfig, ...
                                              lambda, data, labels)
softmaxTheta = reshape(theta(1:hiddenSize*numClasses), numClasses, hiddenSize);
stack = params2stack(theta(hiddenSize*numClasses+1:end), netconfig);
softmaxThetaGrad = zeros(size(softmaxTheta));
stackgrad = cell(size(stack));
for d = 1:numel(stack)
    stackgrad{d}.w = zeros(size(stack{d}.w));
    stackgrad{d}.b = zeros(size(stack{d}.b));
end
m = size(data, 2);
groundTruth = full(sparse(labels, 1:m, 1));

% Forward propagation
z2 = stack{1}.w*data + repmat(stack{1}.b,1,m);
a2 = sigmoid(z2);
z3 = stack{2}.w*a2 + repmat(stack{2}.b,1,m);
a3 = sigmoid(z3);
z4 = softmaxTheta*a3;
z4 = bsxfun(@minus, z4, max(z4, [], 1));
a4 = exp(z4);
a4 = bsxfun(@rdivide, a4, sum(a4));

% Back propagation
delta4 = -(groundTruth-a4);
delta3 = (softmaxTheta'*delta4).*sigmoidGrad(z3);
delta2 = (stack{2}.w'*delta3).*sigmoidGrad(z2);
softmaxThetaGrad = 1./m *delta4*a3';
stackgrad{2}.w = 1./m *delta3*a2';
stackgrad{2}.b = 1./m *sum(delta3,2);
stackgrad{1}.w = 1./m *delta2*data';
stackgrad{1}.b = 1./m *sum(delta2,2);
softmaxThetaGrad = softmaxThetaGrad+lambda*softmaxTheta;
stackgrad{2}.w = stackgrad{2}.w+lambda*stack{2}.w;
stackgrad{1}.w = stackgrad{1}.w+lambda*stack{1}.w;

% cost calculation
cost = -(1./m)*sum(sum(groundTruth.*log(a4))) + lambda/2.0*sum(sum(theta.^2));

%% Roll gradient vector
grad = [softmaxThetaGrad(:) ; stack2params(stackgrad)];
end

分別使用下面兩種引數值對測試資料進行分類:

  • 僅使用預訓練得到的引數;
  • 預訓練加微調後的引數;

在測試資料上的分類結果為:

Before Finetuning Test Accuracy: 91.950%
After Finetuning Test Accuracy: 98.280%

可以看出,微調後的結果將分類準確率提高了6.3個百分點。