神经网络欧式距离损失函数和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) 的定义直接一起带进去然后对w 和b 进行求导来得到
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)的导数。回忆一下我们刚才的
notation,Softmax-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不断增加,最终接近于1。Loss_2和loss_3的值不断增加。x1和x3的概率值变量np1和np3不断减小,最终接近于0。
图1 x1,x2,x3的损失曲线图 (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 ->stackTheta,netconfig保存一些结构参数。
[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 ->stackTheta,netconfig保存一些结构参数。
[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,来权衡损失函数和正则项,减小参数规模,达到模型简化的目的,从而使模型具有更好的泛化能力。
我们之前已经看过一个完整的神经网络,是怎么样通过神经元和连接搭建起来的,以及如何对数据做预处理。在训练神经网络之前,我们还有一个任务要做,那就是初始化参数。
很小的随机数,其实我们依旧希望初始的权重是较小的数,趋于0,但是就像我们刚刚讨论过的一样,不要真的是0。综合上述想法,在实际场景中,我们通常会把初始权重设定为非常小的数字,然后正负尽量一半一半。这样,初始的时候权重都是不一样的很小随机数,然后迭代过程中不会再出现迭代一致的情况。举个例子,我们可能可以这样初始化一个权重矩阵W=0.0001*np.random.randn(D,H)。这个初始化的过程,使得每个神经元的权重向量初始化为多维高斯中的随机采样向量,所以神经元的初始权重值指向空间中的随机方向。
特别说明:其实不一定更小的初始值会比大值有更好的效果。还要具体问题具体分析。
方差归一化,上面提到的建议有一个小问题,对于随机初始化的神经元参数下的输出,其分布的方差随着输入的数量,会增长。我们实际上可以通过除以总输入数目的平方根,归一化每个神经元的输出方差到1。也就是说,我们倾向于初始化神经元的权重向量为w = np.random.randn(n) / sqrt(n),其中n为输入数。
在前一节里我们说了我们要通过正则化来控制神经网络,使得它不那么容易过拟合。有几种正则化的类型供选择:
L2正则化,我们在损失函数里,加入对每个参数的惩罚度。也就是说,对于每个权重w,我们在损失函数里加入一项12λw2,其中λ是我们可调整的正则化强度。顺便说一句,这里在前面加上1/2的原因是,求导/梯度的时候,刚好变成λw而不是2λw。L2正则化理解起来也很简单,它对于特别大的权重有很高的惩罚度,以求让权重的分配均匀一些,而不是集中在某一小部分的维度上。我们再想想,加入L2正则化项,其实意味着,在梯度下降参数更新的时候,每个权重以W += -lambda*W的程度被拉向0。
L1正则化,这也是一种很常见的正则化形式。在L1正则化中,我们对于每个权重w的惩罚项为λ|w|。有时候,你甚至可以看到大神们混着L1和L2正则化用,也就是说加入惩罚项λ1∣w∣+λ2w2,L1正则化有其独特的特性,它会让模型训练过程中,权重特征向量逐渐地稀疏化,这意味着到最后,我们只留下了对结果影响最大的一部分权重,而其他不相关的输入(例如『噪声』)因为得不到权重被抑制。所以通常L2正则化后的特征向量是一组很分散的小值,而L1正则化只留下影响较大的权重。在实际应用中,如果你不是特别要求只保留部分特征,那么L2正则化通常能得到比L1正则化更好的效果
最大范数约束,另外一种正则化叫做最大范数约束,它直接限制了一个上行的权重边界,然后约束每个神经元上的权重都要满足这个约束。实际应用中是这样实现的,我们不添加任何的惩罚项,就按照正常的损失函数计算,只不过在得到每个神经元的权重向量w⃗ 之后约束它满足∥w⃗ ∥2<c。在神经网络训练学习率设定很高的时候,它也能很好地约束住权重更新变化,不至于直接挂掉。
Dropout: 在训练过程中,我们对每个神经元,都以概率p保持它是**状态,1-p的概率直接关闭它。