1. 程式人生 > >神經網路歐式距離損失函式和softmaxwithloss損失函式轉換示例

神經網路歐式距離損失函式和softmaxwithloss損失函式轉換示例

1. 神經網路損失函式說明

神經網路歐式距離損失函式用於連續值訓練樣本的擬合,softmaxwithloss損失函式用於分類訓練樣本的擬合。另外,深度網路對於分類樣本的擬合能力強於對於連續值樣本的擬合能力。同樣的深度網路如果能擬合10分類的樣本,可能只能擬合3連續值的樣本

歐式距離損失函式如下式所示:

 

                       (f1)

它在 Logistic Regression 裡其到的作用是講線性預測值轉化為類別概率:假設(f2)是第i個類別的線性預測結果,帶入 Softmax 的結果其實就是先對每一個 exponential 變成非負,然後除以所有項之和進行歸一化,現在每個

(f3)就可以解釋成觀察到的資料x屬於類別i的概率,或者稱作似然 (Likelihood)

然後 Logistic Regression 目標函式是根據最大似然原則來建立的,假設資料x所對應的類別為y,則根據我們剛才的計算最大似然就是要最大化Oy的值 (通常是使用 negative log-likelihood 而不是 likelihood,也就是說最小化-log(oy)  的值,這兩者結果在數學上是等價的。)。後面這個操作就是 caffe 文件裡說的 Multinomial Logistic Loss,具體寫出來是這個樣子:

代價函式

Softmax-Loss 其實就是把兩者結合到一起,只要把

Oy的定義展開即可

softmaxloss損失函式:

注意,這裡的log代表ln的意思,即:=

                                 (f4)

其中,y為影象所屬於的類別編號,是真實的標籤值;z為影象所對應的資料,這個資料也是輸入softmax層的資料這個資料是深度神經網路輸出的資料

沒有任何 fancy 的東西。比如如果我們要寫一個 Logistic Regression solver,那麼因為要處理的就是這個東西,比較自然地就可以將整個東西合在一起來考慮,或者甚至將(f2)的定義直接一起帶進去然後對wb進行求導來得到 Gradient Descent

update rule,例如我們之前介紹 Gradient Descent 的時候舉的兩類 Logistic Regression 的例子就是這樣做的。

反過來,如果是在設計 Deep Neural Networks 的庫,則可能會傾向於將兩者分開來看待:因為 Deep Learning 的模型都是一層一層疊起來的結構,一個計算庫的主要工作是提供各種各樣的 layer,然後讓使用者可以選擇通過不同的方式來對各種 layer 組合得到一個網路層級結構就可以了。比如使用者可能最終目的就是得到各個類別的概率似然值,這個時候就只需要一個 Softmax Layer,而不一定要進行 Multinomial Logistic Loss 操作;或者是使用者有通過其他什麼方式已經得到了某種概率似然值,然後要做最大似然估計,此時則只需要後面的 Multinomial Logistic Loss 而不需要前面的 Softmax 操作。因此提供兩個不同的 Layer 結構比只提供一個合在一起的 Softmax-Loss Layer 要靈活許多。從程式碼的角度來說也顯得更加模組化。但是這裡自然地就出現了一個問題:numerical stability

讓我們回到 Softmax-Loss 層,由於該層沒有引數,我們只需要計算向後傳遞的導數就可以了,此外由於該層是最頂層,所以不用使用 chain rule 就可以直接計算對於最終輸出(loss)的導數。回憶一下我們剛才的 notationSoftmax-Loss 層合在一起的時候我們用(f5)來表示,它有兩個輸入,一個是 true label,直接來自於最底部的資料層,並且我們不需要對資料層做任何的 gradient descent 引數更新,所以我們不需要像那個輸入進行 back propagation,但是另外一個輸入z則來自於下面的計算層,對於 Logistic Regression 或者普通的 DNNs 下面會是一個全連通的線性內積層,不過具體是什麼我們也不需要關心,只要把(f6)計算出來丟給下面讓他們自己去算後面的就好了。根據普通的微積分知識,我們很容易算出:

; ; ;

K=1,2,…..m m類別;

其中 Softmax-Loss 的中間步驟 Softmax Forward Pass 的計算結果,k=1,…..,m,表示softmax的輸入埠編號

求出之後,令,求出了zk輸出埠應該呈現出來的值。應該進行調整的數量(f7)

                                (f8)

2. Softmax with loss 程式碼示例

clear all

clc

close all

x1=0;

x2=-0.4;

x3=0.2

for k=1:2160

v_k=k

p1=exp(x1);

p2=exp(x2);

p3=exp(x3);

np1=p1/(p1+p2+p3);

np2=p2/(p1+p2+p3);

np3=p3/(p1+p2+p3);

loss_1(k)=-log(np1);

loss_2(k)=-log(np2);

loss_3(k)=-log(np3);

x1=x1-(np1);

x2=x2-(np2-1);  % the probability of x2 should be 1.

x3=x3-(np3);

end

figure

subplot(1,3,1)

plot(log(loss_1))

subplot(1,3,2)

plot(log(loss_2))

subplot(1,3,3)

plot(log(loss_3))

上述示例程式中,隨著x1,x2,x3的更新,loss_2值不斷減小。x2概率值變數np2不斷增加最終接近於1Loss_2loss_3值不斷增加。x1x3的概率值變數np1np3不斷減小最終接近於0

 

1 x1x2x3損失曲線圖 (f9)

3. 歐式距離損失函式示例程式碼

本例包含四組樣本深度網路較好地擬合。網路引數

netsize=[inputsize,5,6,8,7,5,4];

(1) Main_function

clear all

clc

close all

TrainData =[1   2  3   4   5   6  7   8  9  10

          1   9  17  25  33  41  49  57 65  73];

batchsize=4;

TrainData=TrainData(:,1:batchsize);

TrainLabel=[1    0.3   0.8  0.2;

            1.8  0.8   1.2  1.1];

classnum=2;%輸出端數目

%獲取資料的維度

inputsize=size(TrainData ,1);

%獲取資料的數量

datanum=size(TrainData ,2);

% 用一個向量來定義網路的深度,以及每層神經元數目。

netsize=[inputsize,5,6,8,7,5,4];

% netsize=[inputsize,5,6,8,9,9,8,7,5,4];

%網路最後一層神經元數數目,再考慮一個偏置。

lastsize=netsize(end)+1;

%初始化網路引數,以結構體的形式儲存。

stack = initializeNet(netsize);

% 在訓練時,往往需要將引數轉成一列向量,提供給損失函式。

% stack ->stackThetanetconfig儲存一些結構引數。

[stackTheta, netconfig] = stack2params(stack);

% 指定固定的最後一層的初始化的值;

rand('state',2)

lastTheta = 0.0005 * randn(lastsize * classnum, 1);

%最終網路需要的引數

Theta=[ lastTheta ; stackTheta ];

%lastTheta表示深度網路最後一層的權值

% the following part is for the traing epoch.

% batchsize=5;

%%每次訓練的小批量樣本數</span>  

batchnum=floor(size(TrainData,2)/batchsize);  

DataNum=size(TrainData,2);  

alpha=1e-2;

%這是學習率,一般隨著網路的懸念都需要不斷的減小  

lambda = 1e-4; % Weight decay parameter

for epoch=1:16000

    v_epoch=epoch

    if epoch>=16000

        alpha=1e-3;

    end

    idx=randperm(DataNum);  

   for t=1:batchnum  

      subdata=TrainData(:,idx((t-1)*batchsize+1:(t)*batchsize));  

      sublabel=TrainLabel(:,idx((t-1)*batchsize+1:(t)*batchsize));

      [cost(epoch),grad]=ReLUDNNCost(Theta,classnum,lastsize,netconfig,lambda,subdata,sublabel);

      Theta=Theta-alpha*grad;  

   end      

end

plot(log(cost))

(2) drelu

function dre= drelu(x)

  dre=zeros(size(x));

  dre(x>0)=1;

  dre(x==0)=0.5; %這句可以不要

end

(3) relu

function re = relu(x)

re = max(x,0)-1;

end

(4) params2stack

function stack = params2stack(params, netconfig)

depth = numel(netconfig.layersizes);

stack = cell(depth,1);

prevLayerSize = netconfig.inputsize; % the size of the previous layer

curPos = double(1); % mark current position in parameter vector

for d = 1:depth

% Create layer d

stack{d} = struct;

% Extract weights

wlen = double(netconfig.layersizes{d} * prevLayerSize);

stack{d}.w = reshape(params(curPos:curPos+wlen-1), netconfig.layersizes{d}, prevLayerSize);

curPos = curPos+wlen;

% Extract bias

blen = double(netconfig.layersizes{d});

stack{d}.b = reshape(params(curPos:curPos+blen-1), netconfig.layersizes{d}, 1);

curPos = curPos+blen;

% Set previous layer size

prevLayerSize = netconfig.layersizes{d};

end

end

(5) ReLUDNNCost

function [cost,grad] = ReLUDNNCost(theta,numClasses,lasthiddenSize, netconfig,lambda, trainData,trainLabels)

%引數獲取的一些操作

lastTheta = reshape(theta(1:lasthiddenSize*numClasses), numClasses, lasthiddenSize);

%theta向量中抽取網路權值引數並轉化

stack = params2stack(theta(lasthiddenSize*numClasses+1:end), netconfig);

stackgrad = cell(size(stack));

%這裡儲存在應用BP演算法求梯度時需要的資料

PARA=cell(numel(stack),1);

%傳進來的樣本數

datanum=size(trainData,2);

%開始前饋,網路雖然多層,但只是重複而已

data=trainData;

for d = 1:numel(stack)

PARA{d}.a=data;

z2=(stack{d}.w*data)+stack{d}.b*ones(1,datanum);

%ReLU函式

a2=relu(z2);

data=a2;

%ReLU函式的導函式

PARA{d}.daz=drelu(z2);

end

a2=[a2;ones(1,datanum)];

%開始求解損失

groundTruth=trainLabels;

v_groundTruth=groundTruth

M = lastTheta*a2;

v_M=M

% 損失函式,

cost=sum(sum((groundTruth-M).^2))./datanum;

%最後一層神經元的目標函式對lastTheta 的導數,

lastThetaGrad = -1/datanum*((groundTruth-M)*a2')+lambda*lastTheta;

% 輸出層誤差傳導至倒數第二層神經元的值

predelta=-lastTheta'*(groundTruth-M);

predelta=predelta(1:end-1,:);

for d = numel(stack):-1:1

delta=predelta.*PARA{d}.daz;

stackgrad{d}.w=delta*PARA{d}.a'/datanum;%.*PARA{d}.idx

stackgrad{d}.b=sum(delta,2)/datanum;

predelta=stack{d}.w'*delta;

end

grad = [lastThetaGrad(:) ; stack2params(stackgrad)];

end

(6) stack2params

function [params, netconfig] = stack2params(stack)

params = [];

for d = 1:numel(stack)

    params = [params ; stack{d}.w(:) ;

        stack{d}.b(:) ];

end

if nargout > 1

    if numel(stack) == 0

        netconfig.inputsize = 0;

        netconfig.layersizes = {};

    else

        netconfig.inputsize = size(stack{1}.w, 2);

        netconfig.layersizes = {};

        for d = 1:numel(stack)

            netconfig.layersizes = [netconfig.layersizes ; size(stack{d}.w,1)];

        end

    end

end

end

(7) initializeNet

function stack = initializeNet(netsize)

layersize=length(netsize(:));

stack = cell(layersize-1,1);

for l=1:layersize-1

    hiddenSize=netsize(l+1);

    visibleSize=netsize(l);

    r =sqrt(6) / sqrt(hiddenSize+visibleSize+1);

    rand('state',2)

    stack{l}.w= rand(hiddenSize, visibleSize) * 2 * r - r; stack{l}.b= zeros(hiddenSize, 1);

end

end

(8) 執行結果

深度網路收斂,執行結果如下圖所示:

 

  (f10)

輸出標籤值如下:

v_groundTruth =

    0.2000    0.3000    1.0000    0.8000

    1.1000    0.8000    1.8000    1.2000

深度網路實際擬合值如下:

v_M =

    0.2001    0.3001    0.9999    0.7999

1.0999    0.8000    1.7999    1.2000

上面資料比較可見,兩者差距較,深度網路很好地擬合連續值輸出的樣本。

4. softmaxloss損失函式示例程式碼

3部分的程式碼進行修改,使得深度網路的損失函式採用softmaxwithloss損失函式。主要修改的部分為main_function.m 檔案ReLUDNNCost.m檔案示例程式碼可以識別三個不同的種類。樣本5樣本。

(1) Main_function

clear all; clc ; close all

TrainData =[1   2  3   4   5   6  7   8  9  10

          1   9  17  25  33  41  49  57 65  73];

batchsize=5;

TrainData=TrainData(:,1:batchsize);

TrainLabel=[0 1 0 0 1;

          1 0 0 1 0;

          0 0 1 0 0];

classnum=3;%輸出端數目

%獲取資料的維度

inputsize=size(TrainData ,1);

%獲取資料的數量

datanum=size(TrainData ,2);

% 用一個向量來定義網路的深度,以及每層神經元數目。

netsize=[inputsize,5,6,8,7,5,4];

% netsize=[inputsize,5,6,8,9,9,8,7,5,4];

%網路最後一層神經元數數目,再考慮一個偏置。

lastsize=netsize(end)+1;

%初始化網路引數,以結構體的形式儲存。

stack = initializeNet(netsize);

% 在訓練時,往往需要將引數轉成一列向量,提供給損失函式。

% stack ->stackThetanetconfig儲存一些結構引數。

[stackTheta, netconfig] = stack2params(stack);

% 指定固定的最後一層的初始化的值;

rand('state',2)

lastTheta = 0.0005 * randn(lastsize * classnum, 1);

%最終網路需要的引數

Theta=[ lastTheta ; stackTheta ];

%lastTheta表示深度網路最後一層的權值

% the following part is for the traing epoch.

% batchsize=5;

%%每次訓練的小批量樣本數</span>  

batchnum=floor(size(TrainData,2)/batchsize);  

DataNum=size(TrainData,2);  

alpha=1e-2;

%這是學習率,一般隨著網路的懸念都需要不斷的減小  

lambda = 1e-4; % Weight decay parameter

for epoch=1:3600

    v_epoch=epoch

    if epoch>=16000

        alpha=1e-3;

    end

    idx=randperm(DataNum);  

   for t=1:batchnum  

      subdata=TrainData(:,idx((t-1)*batchsize+1:(t)*batchsize));  

      sublabel=TrainLabel(:,idx((t-1)*batchsize+1:(t)*batchsize));

      [cost(epoch),grad]=ReLUDNNCost(Theta,classnum,lastsize,netconfig,lambda,subdata,

sublabel);

      Theta=Theta-alpha*grad;  

   end      

end

plot(log(cost))

(9) ReLUDNNCost

識別三個種類的深度網路。

function [cost,grad] = ReLUDNNCost(theta,numClasses,lasthiddenSize, netconfig,lambda, trainData,trainLabels)

%引數獲取的一些操作

lastTheta = reshape(theta(1:lasthiddenSize*numClasses), numClasses, lasthiddenSize);

%theta向量中抽取網路權值引數並轉化

stack = params2stack(theta(lasthiddenSize*numClasses+1:end), netconfig);

stackgrad = cell(size(stack));

%這裡儲存在應用BP演算法求梯度時需要的資料

PARA=cell(numel(stack),1);

%傳進來的樣本數

datanum=size(trainData,2);

%開始前饋,網路雖然多層,但只是重複而已

data=trainData;

for d = 1:numel(stack)

PARA{d}.a=data;

z2=(stack{d}.w*data)+stack{d}.b*ones(1,datanum);

%ReLU函式

a2=relu(z2);

data=a2;

%ReLU函式的導函式

PARA{d}.daz=drelu(z2);

end

a2=[a2;ones(1,datanum)];

%開始求解損失

groundTruth=trainLabels;

v_groundTruth=groundTruth

M = lastTheta*a2;

v_M=M;

p1=exp(M(1,:));

p2=exp(M(2,:));

p3=exp(M(3,:));

np1=p1./(p1+p2+p3)

np2=p2./(p1+p2+p3)

np3=p3./(p1+p2+p3)

dnp1=-(np1-trainLabels(1,:));

dnp2=-(np2-trainLabels(2,:));

dnp3=-(np3-trainLabels(3,:));

loss_1=log(np1);

loss_2=log(np2);

cost=(loss_1+loss_2)/2;

cost=0;

last_delta=[dnp1;dnp2;dnp3];

% 損失函式,

%最後一層神經元的目標函式對lastTheta 的導數,

% lastThetaGrad = -1/datanum*((groundTruth-M)*a2')+lambda*lastTheta;

lastThetaGrad = -1/datanum*(last_delta*a2')+lambda*lastTheta;

% 輸出層誤差傳導至倒數第二層神經元的值

predelta=-lastTheta'*last_delta;

predelta=predelta(1:end-1,:);

for d = numel(stack):-1:1

delta=predelta.*PARA{d}.daz;

stackgrad{d}.w=delta*PARA{d}.a'/datanum;%.*PARA{d}.idx

stackgrad{d}.b=sum(delta,2)/datanum;

predelta=stack{d}.w'*delta;

end

grad = [lastThetaGrad(:) ; stack2params(stackgrad)];

end

5. 總結

深度網路的資料預處理中,去均值是非常重要的步驟。而歸一化處理反而不是必須採用的步驟。

深度神經網路可以對少量樣本進行很好的擬合,而較多的樣本無法很好擬合的時候,很可能是深度網路的層數不夠深,即深度網路的特徵無法很好地表徵所有樣本的特徵。此時,可以通過加深網路的層次來使得深度網路對較多的樣本進行擬合。

需要記住的是:引數越多,模型越複雜,而越複雜的模型越容易過擬合。過擬合就是說模型在訓練資料上的效果遠遠好於在測試集上的效能。此時可以考慮正則化,通過設定正則項前面的hyper parameter,來權衡損失函式和正則項,減小引數規模,達到模型簡化的目的,從而使模型具有更好的泛化能力。

2.2 權重初始化

我們之前已經看過一個完整的神經網路,是怎麼樣通過神經元和連線搭建起來的,以及如何對資料做預處理。在訓練神經網路之前,我們還有一個任務要做,那就是初始化引數。

很小的隨機數,其實我們依舊希望初始的權重是較小的數,趨於0,但是就像我們剛剛討論過的一樣,不要真的是0。綜合上述想法,在實際場景中,我們通常會把初始權重設定為非常小的數字,然後正負儘量一半一半。這樣,初始的時候權重都是不一樣的很小隨機數,然後迭代過程中不會再出現迭代一致的情況。舉個例子,我們可能可以這樣初始化一個權重矩陣W=0.0001*np.random.randn(D,H)。這個初始化的過程,使得每個神經元的權重向量初始化為多維高斯中的隨機取樣向量,所以神經元的初始權重值指向空間中的隨機方向。

特別說明:其實不一定更小的初始值會比大值有更好的效果。還要具體問題具體分析。

方差歸一化,上面提到的建議有一個小問題,對於隨機初始化的神經元引數下的輸出,其分佈的方差隨著輸入的數量,會增長。我們實際上可以通過除以總輸入數目的平方根,歸一化每個神經元的輸出方差到1。也就是說,我們傾向於初始化神經元的權重向量為w = np.random.randn(n) / sqrt(n),其中n為輸入數。

2.3 正則化

在前一節裡我們說了我們要通過正則化來控制神經網路,使得它不那麼容易過擬合。有幾種正則化的型別供選擇:

L2正則化,我們在損失函式裡,加入對每個引數的懲罰度。也就是說,對於每個權重w,我們在損失函式里加入一項12λw2,其中λ是我們可調整的正則化強度。順便說一句,這裡在前面加上1/2的原因是,求導/梯度的時候,剛好變成λw而不是2λwL2正則化理解起來也很簡單,它對於特別大的權重有很高的懲罰度,以求讓權重的分配均勻一些,而不是集中在某一小部分的維度上。我們再想想,加入L2正則化項,其實意味著,在梯度下降引數更新的時候,每個權重以W += -lambda*W的程度被拉向0

L1正則化,這也是一種很常見的正則化形式。在L1正則化中,我們對於每個權重w的懲罰項為λ|w|。有時候,你甚至可以看到大神們混著L1L2正則化用,也就是說加入懲罰項λ1w+λ2w2L1正則化有其獨特的特性,它會讓模型訓練過程中,權重特徵向量逐漸地稀疏化,這意味著到最後,我們只留下了對結果影響最大的一部分權重,而其他不相關的輸入(例如『噪聲』)因為得不到權重被抑制。所以通常L2正則化後的特徵向量是一組很分散的小值,而L1正則化只留下影響較大的權重。在實際應用中,如果你不是特別要求只保留部分特徵,那麼L2正則化通常能得到比L1正則化更好的效果

最大範數約束,另外一種正則化叫做最大範數約束,它直接限制了一個上行的權重邊界,然後約束每個神經元上的權重都要滿足這個約束。實際應用中是這樣實現的,我們不新增任何的懲罰項,就按照正常的損失函式計算,只不過在得到每個神經元的權重向量w 之後約束它滿足w2<c。在神經網路訓練學習率設定很高的時候,它也能很好地約束住權重更新變化,不至於直接掛掉。

Dropout:在訓練過程中,我們對每個神經元,都以概率p保持它是啟用狀態,1-p的概率直接關閉它。