DBN深度置信網絡的實現

深度置信網絡(DBN)是由一系列的玻爾茲曼機(RBM)進行疊加組成的。

代碼實現DBN的過程,請參考matlab的深度學習工具箱:DeepLearnToolbox  。

而關於深度置信網絡的原理部分,請參考大神 peghoty的博客:http://blog.csdn.net/itplus/article/details/19168937

那麼接下來就是自己利用deeplearntoolbox來編寫自己的深度置信網絡(DBN)了。

DBN函數,包含功能:初始化DBN參數,並進行訓練DBN網絡,之後將DBN擴展爲NN,並對NN進行了相應的初始化,訓練以及測試

function DBN(train_x,train_y,test_x,test_y)
%單純的DBN只是一個NN網絡,它返回的是一個訓練好的網絡,而不是對測試樣本的一個評估
%所以,在這個程序中,我們是沒有看到輸出的結果的
%要進行預測,必須要有邏輯迴歸或者softmax迴歸才行,因爲,這樣才能夠對測試樣本進行評估
%初始化參數,層數
x = double(train_x)/255;
opts.numepochs = 1;%迭代次數
opts.batchsize = 100;%批次處理的大小
opts.momentum = 0;%動量(調整梯度)
opts.learn_r = 1;%學習率
n = size(x,2);%輸入的節點數
dbn.layers = [100 100];%隱層的層數以及節點數
dbn.layers = [n,dbn.layers];%輸入層+隱層

%對每層的權重和偏置進行初始化
for u = 1:numel(dbn.layers)-1 %u表示隱層層數
    dbn.rbm{u}.learn_r = opts.learn_r;
    dbn.rbm{u}.momentum = opts.momentum;
    
    dbn.rbm{u}.W  = zeros(dbn.layers(u+1),dbn.layers(u));%784*100的權重矩陣
    dbn.rbm{u}.vW = zeros(dbn.layers(u+1),dbn.layers(u));%更新參數用
    
    dbn.rbm{u}.b  = zeros(dbn.layers(u), 1);%b爲可見層的偏置
    dbn.rbm{u}.vb = zeros(dbn.layers(u), 1);
    
    dbn.rbm{u}.c  = zeros(dbn.layers(u + 1), 1);%c爲隱層的偏置
    dbn.rbm{u}.vc = zeros(dbn.layers(u + 1), 1);
end
%初始化參數完畢 

%訓練
u = numel(dbn.rbm);%隱層的玻爾茲曼機數
dbn.rbm{1} = rbmtrain1(dbn.rbm{1},x,opts);%訓練第一層rbm

for i = 2:u
    P =  repmat(dbn.rbm{i - 1}.c', size(x, 1), 1) + x * dbn.rbm{i - 1}.W';
    x = 1./(1+exp(-P));
    dbn.rbm{i} = rbmtrain1(dbn.rbm{i},x,opts);
end
figure; visualize(dbn.rbm{1}.W');   %  Visualize the RBM weights

%訓練完畢,並打印特徵圖

%展開爲nn,利用BP算法進行參數微調
outputsize = 10; %MNIS數據庫,所以最後一層輸出只能有10個
nn.layers = [dbn.layers,outputsize];%nn是DBN展開的,不過還需要其他的一些參數.nn的size表示層數
nn.n = numel(nn.layers);
nn.activation_function              = 'tanh_opt';   %  激活函數
nn.learningRate                     = 2;            %  學習速率
nn.momentum                         = 0.5;          %  動量,與梯度有關
nn.scaling_learningRate             = 1;            %  Scaling factor for the learning rate (each epoch)
nn.weightPenaltyL2                  = 0;            %  L2 regularization
nn.nonSparsityPenalty               = 0;            %  Non sparsity penalty
nn.sparsityTarget                   = 0.05;         %  Sparsity target
nn.inputZeroMaskedFraction          = 0;            %  Used for Denoising AutoEncoders
nn.testing                          = 0;            %  Internal variable. nntest sets this to one.
nn.output                           = 'sigm';       %  輸出是線性、softmax、還是sigm?
nn.dropoutFraction                  = 0;            %  Dropout level (http://www.cs.toronto.edu/~hinton/absps/dropout.pdf)
for i = 2:nn.n
    nn.W{i - 1} = (rand(nn.layers(i),nn.layers(i-1)+1)-0.5)*2*4*sqrt(6/(nn.layers(i)+nn.layers(i-1)));
    %注意,這兒必須進行權重初始化,因爲輸出層的權重並沒有設置,而且在後面會W會被DBN訓練好的權重覆蓋
    nn.vW{i - 1} = zeros(size(nn.W{i - 1}));
    nn.p{i} = zeros(1,nn.layers(i));%該參數是用來進行稀疏的
end

for i =1: numel(dbn.rbm)%利用了DBN調整的權重
    nn.W{i} = [dbn.rbm{i}.c dbn.rbm{i}.W];%注意,我們已經將偏置和權重一塊放入nn.W{i}中
end
nn.activation_function = 'sigm';

%到此,DBN擴展爲NN並且進行了初始化 

%接着進行訓練

x1 = double(train_x) / 255;
test_x  = double(test_x)  / 255;
y1 = double(train_y);
test_y  = double(test_y);

nn = nntrain1(nn, x1, y1, opts);
%訓練完畢
%進行樣本測試
labels = nnpredict1(nn,test_x);
[dummy,expected] = max(test_y,[],2);
bad = find(labels~= expected);
er = numel(bad) / size(test_x,1);

assert(er < 0.10, 'Too big error');
end

rbmtrain1函數:這個過程就是對DBN進行訓練的過程,要注意的是,對DBN的訓練僅僅只是讓DBN進行特徵學習,而這個過程DBN是無法進行決策,判斷的,其訓練過程中參數的更新主要依賴樣本的變化,返回的是一個進過訓練的網絡(這個網絡還沒有輸出)。

function rbm = rbmtrain1(rbm,x,opts)
assert(isfloat(x),'x must be a float');
assert(all(x(:)>=0) && all(x(:)<=1),'all data in x must be in [0,1]');

m =size(x,1); %返回x的行數,即樣本數量
numbatches = m/opts.batchsize;%每 batchsize個樣本作爲一組

assert(rem(numbatches,1)==0,'numbatches not int');

for i = 1:opts.numepochs
    seq = randperm(m);%seq 是1-m的隨機數序列
    err = 0 ;%誤差
    for l = 1:numbatches
        batch = x(seq((l-1)*opts.batchsize +1:l*opts.batchsize),:);%取x的100個樣本
        %下面的過程是進行GIBBS採樣,也算是CD-k算法的實現
        v1 = batch;%v1表示可見層的初始化,共100個樣本
         P1 = repmat(rbm.c', opts.batchsize, 1) + v1 * rbm.W';
        h1 = double(1./(1+exp(-P1)) > rand(size(P1)));
         P2 = repmat(rbm.b', opts.batchsize, 1) + h1 * rbm.W;
        v2 = double(1./(1+exp(-P2)) > rand(size(P2)));
         P3 = repmat(rbm.c', opts.batchsize, 1) + v2 * rbm.W';
        h2 = 1./(1+exp(-P3));
        %參數的更新
        c1 = h1' * v1;
        c2 = h2' * v2;
        
        rbm.vW = rbm.momentum * rbm.vW + rbm.learn_r * (c1 - c2)     / opts.batchsize;
        rbm.vb = rbm.momentum * rbm.vb + rbm.learn_r * sum(v1 - v2)' / opts.batchsize;
        rbm.vc = rbm.momentum * rbm.vc + rbm.learn_r * sum(h1 - h2)' / opts.batchsize;
        
         rbm.W = rbm.W + rbm.vW;
         rbm.b = rbm.b + rbm.vb;
         rbm.c = rbm.c + rbm.vc;
         
         err = err + sum(sum((v1 - v2) .^ 2)) / opts.batchsize;
    end
    
     disp(['epoch ' num2str(i) '/' num2str(opts.numepochs)  '. Average reconstruction error is: ' num2str(err / numbatches)]);
        
end


nntrain1函數:主要包含了前饋傳播,後向傳播(BP算法實現),參數更新,以及性能評估等過程

function nn = nntrain1( nn, x, y, opts,val_x,val_y)
assert(nargin == 4 || nargin == 6,'number of input arguments must be 4 or 6');

loss.train.e = [];%%保存的是對訓練數據進行前向傳遞,根據得到的網絡輸出值計算損失,並保存 
%在nneval那裏有改變,loss.train.e(end + 1) = nn.L;  

loss.train.e_frac = []; %保存的是:對分類問題,用訓練數據對網絡進行測試,  
%首先用網絡預測得到預測分類,用預測分類與實際標籤進行對比,保存錯分樣本的個數  

loss.val.e = [];%有關驗證集 

loss.val.e_frac = [];

opts.validation = 0;
if nargin == 6%nargin表示參數個數,4個或者6個(val_x,val_y是可選項)
    opts.validation =1;
end

fhandle = [];
if isfield(opts,'plot') && opts.plot == 1
    fhandle = figure();
end

m = size(x,1);

batchsize = opts.batchsize;%批次處理的數目 爲100
numepochs = opts.numepochs;%迭代次數  爲1

numbatches = m/batchsize;%批次處理的次數

assert(rem(numbatches,1) == 0,'numbatches must be int');

L = zeros(numepochs * numbatches,1);%L用來存儲每個訓練小批量的平方誤差
n = 1;%n作爲L的索引

for i=1:numepochs
    tic;% tic用來保存當前時間,而後使用toc來記錄程序完成時間,
    %差值爲二者之間程序運行的時間,單位:s    
    seq = randperm(m);
    %每次選擇一個batch進行訓練,每次訓練都講更新網絡參數和誤差,由nnff,nnbp,nnapplygrads實現:
    for l = 1 : numbatches
        batch_x = x(seq((l-1) * batchsize +1 : l * batchsize),:);
        %每100個爲一組進行處理
        %添加噪聲
        if(nn.inputZeroMaskedFraction ~= 0)%nn參數設置中有,設置爲0
            batch_x = batch_x.*(rand(size(batch_x)) > nn.inputZeroMaskedFraction);
        end
        batch_y = y(seq((l-1) * batchsize +1 : l * batchsize),:);
        
        nn = nnff1(nn,batch_x,batch_y);%進行前向傳播
        nn = nnbp1(nn);%後向傳播
        nn = nnapplygrads1(nn);%進行梯度下降
        
        L(n) = nn.L;%記錄批次的損失,n作爲下標
        n = n+1;
    end
    t = toc;
    %用nneval和訓練數據,評價網絡性能 
    if opts.validation == 1
        loss = nneval1(nn, loss, x, y, val_x, val_y);
        str_perf = sprintf('; Full-batch train mse = %f, val mse = %f', loss.train.e(end), loss.val.e(end));
    else
        %在nneval函數裏對網絡進行評價,繼續用訓練數據,並得到錯分的樣本數和錯分率,都存在了loss裏
        loss = nneval1(nn, loss, x, y);
        str_perf = sprintf('; Full-batch train err = %f', loss.train.e(end));
    end
    %下面是畫圖函數
    if ishandle(fhandle)
        nnupdatefigures1(nn, fhandle, loss, opts, i);
    end
        
    disp(['epoch ' num2str(i) '/' num2str(opts.numepochs) '. Took ' num2str(t) ' seconds' '. Mini-batch mean squared error on training set is ' num2str(mean(L((n-numbatches):(n-1)))) str_perf]);
    nn.learningRate = nn.learningRate * nn.scaling_learningRate;
end
end

nnff1函數:進行前饋傳播,即就是有輸入的數據進行計算隱層節點,輸出節點的輸出

function nn = nnff1( nn,x,y )
%前饋傳播
n = nn.n;%網絡層數
m = size(x,1);%樣本個數:應該爲100

x = [ones(m,1) x];%應該是100*785數組,第一列全爲1,後784列爲樣本值
nn.a{1} = x;%nn.a{i}表示第i層的輸出值,所以a{n}也就表示輸出層的結果

for i = 2 :n-1
    switch nn.activation_function
        case 'sigm'
            P = nn.a{i - 1} * nn.W{i - 1}';%注意:這兒已經把偏置計算進去了
            %因爲在前面展開的時候,將偏置放在了nn.W{i} = [dbn.rbm{i}.c dbn.rbm{i}.W];中
            %並且對訓練樣本添加了一列,這一列就對應這偏置的計算
            nn.a{i} = 1./(1+exp(-P));
        case 'tanh_opt'
            P = nn.a{i - 1} * nn.W{i - 1}';
            nn.a{i} = 1.7159*tanh(2/3.*P);
    end
    
    %dropout
    if(nn.dropoutFraction > 0)
        if(nn.testing)
            nn.a{i} = aa.a{i}.* (1 - nn.dropoutFraction);
        else
            nn.dropOutMask{i} = (rand(size(nn.a{i}))>nn.dropoutFraction);
            nn.a{i} = nn.a{i}.*nn.dropOutMask{i};   
        end
    end
            
    %計算使用稀疏性的指數級激活
    if(nn.nonSparsityPenalty>0)
        nn.p{i} = 0.99 * nn.p{i} + 0.01 * mean(nn.a{i}, 1);
    end
    nn.a{i} = [ones(m,1) nn.a{i}];
end

switch nn.output
    case 'sigm'
        P = nn.a{n - 1} * nn.W{n - 1}';
        nn.a{n} = 1./(1+exp(-P));
    case 'linear'
        nn.a{n} = nn.a{n-1} * nn.W{n-1}';
    case 'softmax'
        nn.a{n} = nn.a{n - 1} * nn.W{n - 1}';
        nn.a{n} = exp(bsxfun(@minus, nn.a{n}, max(nn.a{n},[],2)));
        nn.a{n} = bsxfun(@rdivide, nn.a{n}, sum(nn.a{n}, 2));  
end

%損失
nn.e= y-nn.a{n};%損失,差值
switch nn.output%除m是爲了平均誤差-也可以認爲是單個樣本的誤差
    case {'sigm','linear'}
        nn.L = 1/2 * sum(sum(nn.e .^2)) /m;
    case 'softmax'
        nn.L = -sum(sum(y .* log(nn.a{n}))) /m;
end
end

nnbp1函數:後向傳播即就是BP算法的實現:
function nn= nnbp1( nn )
%進行後向傳播算法
n = nn.n;
sparsityError = 0;
switch nn.output%本步驟表示輸出層單個節點的差值(-(yi-ai)*f'(zi)),即就是BP第二步,第一步是前饋傳播,計算各節點的輸出值
    case 'sigm'
        d{n} = -nn.e .*(nn.a{n} .* (1-nn.a{n}));
    case {'softmax','linear'}
        d{n} = -nn.e;
end

for i = (n-1):-1:2%d_act表示激活函數的導數
    switch nn.activation_function
        case 'sigm'
            d_act = nn.a{i} .*(1-nn.a{i});
        case 'tanh-opt'
            d_act = 1.7159 * 2/3 * (1 - 1/(1.7159)^2 * nn.a{i}.^2);
    end
    
    if(nn.nonSparsityPenalty>0)%稀疏時有用
        pi = repmat(nn.p{i}, size(nn.a{i}, 1), 1);
        sparsityError = [zeros(size(nn.a{i},1),1) nn.nonSparsityPenalty * (-nn.sparsityTarget ./ pi + (1 - nn.sparsityTarget) ./ (1 - pi))];
    end        
    
    if i+1 == n%BP算法的第三步
        d{i} = (d{i + 1} * nn.W{i} + sparsityError) .* d_act;
    else
        d{i} = (d{i + 1}(:,2:end) * nn.W{i} + sparsityError) .* d_act;
        %注意,在這兒第一列是偏置,所以要進行移除
    end
    
    if(nn.dropoutFraction>0)
        d{i} = d{i} .* [ones(size(d{i},1),1) nn.dropOutMask{i}];
    end
end
for i = 1 : (n-1)%由於每層的節點數是不一樣的,所以需要用除以size(d{i])來求平均節點誤差
    if i+1 == n%dW表示的是梯度
        nn.dW{i} = (d{i + 1}' * nn.a{i}) / size(d{i + 1}, 1);
    else
        nn.dW{i} = (d{i + 1}(:,2:end)' * nn.a{i}) / size(d{i + 1}, 1); 
    end
end
end

nnapplygras1d函數:在BP算法後,進行參數的更新,該函數和nnbp1在整個DBN過程中可以認爲是微調階段

function nn = nnapplygrads1(nn)
%更新參數的函數
for i=1:(nn.n-1)%的W本身包括偏置和權重
    if(nn.weightPenaltyL2>0)%L2處罰用
        dW = nn.dW{i} + nn.weightPenaltyL2 * [zeros(size(nn.W{i},1),1) nn.W{i}(:,2:end)];
    else
        dW = nn.dW{i};
    end
    
    dW = nn.learningRate * dW;
    
    if(nn.momentum > 0)
        nn.vW{i} = nn.momentum * nn.vW{i} + dW;
        dW = nn.vW{i};
    end
    nn.W{i} = nn.W{i} - dW;
end
end

nneval1函數:實現性能的評估
function [ loss ] = nneval1(nn, loss, x, y, val_x, val_y)
%評估網路性能
assert(nargin ==4 || nargin == 6,'Wrong number of argument');

nn.testing = 1;
%訓練性能
nn = nnff1(nn,x,y);
loss.train.e(end+1) = nn.L;
%驗證性能
if nargin == 6 
    nn = nnff1(nn,val_x,val_y);
    loss.val.e(end+1) = nn.L;
end

nn.testing = 0;
%錯分類率
if strcmp(nn.output,'softmax')
    [er_train, dummy]         = nntest(nn, train_x, train_y);
    loss.train.e_frac(end+1)    = er_train;
    
    if nargin == 6
        [er_val, dummy]             = nntest(nn, val_x, val_y);
        loss.val.e_frac(end+1)  = er_val;
    end
end    
end


最後就是進行數據的測試

function labels = nnpredict1(nn,x)
    nn.testing = 1;
    nn = nnff1(nn, x, zeros(size(x,1), nn.layers(end)));
    nn.testing = 0;
    
    [dummy, i] = max(nn.a{end},[],2);
    labels = i;

end


到此,整個代碼的實現過程結束

代碼運行結果:3層隱層,各100個節點,numepochs=1


2層隱層,各100 節點,numepochs=10:


總結:DBN在運行過程中只是進行特徵學習,而無法進行決策,所以在進行DBN訓練完成後,需要將DBN擴展爲NN,即添加輸出層(根據分類結果天天輸出層的節點數)。然後,用訓練好的DBN的參數初始化NN的參數,進而在進行傳統的NN訓練,就是進行前饋傳播,後向傳播等,這樣的過程就是完成了DBN的預訓練-微調過程。這樣之後纔可以進行判斷,分類等。


以上是我個人在學習DBN過程的一點小心得,若有不足,懇請指正。

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章