初识神经网络04——构建神经网络2
系列文章目录
初识神经网络01——认识PyTorch
初识神经网络02——认识神经网络
初识神经网络03——构建神经网络
文章目录
- 系列文章目录
- 一、参数初始化
- 1.1 固定值初始化
- 1.2 随机初始化
- 1.3 Xavier初始化
- 1.4 He初始化
- 1.5 总结
- 二、损失函数
- 2.1 线性损失函数
- 2.2 CrossEntropyLoss
- 2.2.1 信息量
- 2.2.2 信息熵
- 2.2.3 KL散度
- 2.2.4 交叉熵
- 2.3 BCELoss
- 2.4 总结
- 三、反向传播
- 3.1 前向传播
- 3.2 梯度下降算法存在问题
- 3.3 算法的改进
- 3.3.1 Momentum
- 3.3.2 AdaGrad
- 3.3.3 RMSProp
- 3.3.4 Adam
- 3.4 总结
- 总结
一、参数初始化
神经网络的参数初始化是训练深度学习模型的关键步骤之一。初始化参数(通常是权重和偏置)会对模型的训练速度、收敛性以及最终的性能产生重要影响。下面是关于神经网络参数初始化的一些常见方法及其相关知识点。
1.1 固定值初始化
固定值初始化是指在神经网络训练开始时,将所有权重或偏置初始化为一个特定的常数值。这种初始化方法虽然简单,但在实际深度学习应用中通常并不推荐。
-
全0初始化
将神经网络中的所有权重参数初始化为0,但会导致对称性破坏,每个神经元在每一层中都会执行相同的计算,模型无法学习。通常不用来初始化权重,但可以用来初始化偏置。 -
全1初始化
全1初始化会导致网络中每个神经元接收到相同的输入信号,进而输出相同的值,这就无法进行学习和收敛。所以全1初始化只是一个理论上的初始化方法,但在实际神经网络的训练中并不适用。 -
任意常数初始化
将所有参数初始化为某个非零的常数(如 0.1,-1 等)。虽然不同于全0和全1,但这种方法依然不能避免对称性破坏的问题。
对称性问题
- 现象:同一层的所有神经元具有完全相同的初始权重和偏置。
- 后果:在反向传播时,所有神经元会收到相同的梯度,导致权重更新完全一致。无论训练多久,同一层的神经元本质上会保持相同的功能(相当于“一个神经元”的多个副本),极大降低模型的表达能力。所有输入特征被同等对待,无法学习特征间的不同重要性。
1.2 随机初始化
方法:将权重初始化为随机的小值,通常从正态分布或均匀分布中采样。
应用场景:这是最基本的初始化方法,通过随机初始化避免对称性破坏。
1.3 Xavier初始化
Xavier 初始化(由 Xavier Glorot 在 2010 年提出)是一种自适应权重初始化方法,专门为解决神经网络训练初期的梯度消失或爆炸问题而设计。Xavier 初始化也叫做Glorot初始化。Xavier 初始化的核心思想是根据输入和输出的维度来初始化权重,使得每一层的输出的方差保持一致。具体来说,权重的初始化范围取决于前一层的神经元数量(输入维度)和当前层的神经元数量(输出维度)。
解决深度神经网络在训练初期可能遇到的梯度消失(Vanishing Gradients)和梯度爆炸(Exploding Gradients)问题。它通过精心设置初始权重值的方差,试图确保网络中各层的输入信号(正向传播时)和梯度信号(反向传播时)的方差在层与层之间保持相对稳定。
方法:根据输入和输出神经元的数量来选择权重的初始值。
这里简单讲解一下该方法的原理;
前置知识:
均匀分布的概率密度函数(PDF):
p(x)={1b−a如果a≤x≤b0其他情况p(x)=\begin{cases}\frac{1}{b−a} & 如果 a≤x≤b\\ 0 & 其他情况 \end{cases} p(x)={b−a10如果a≤x≤b其他情况
计算期望值(均值),也即是对(a,b)区间中的所有x加权求和:
E[X]=∫abx⋅p(x)dx=∫abx⋅1b−adx=a+b2E[X]=∫_a^bx⋅p(x) dx=∫_a^bx⋅\frac{1}{b−a} dx=\frac{a+b}{2} E[X]=∫abx⋅p(x) dx=∫abx⋅b−a1 dx=2a+b
计算方差(二阶矩减去均值的平方):
Var(X)=E(X−E(X))2=E[X2]−(E[X])2Var(X)=E(X-E(X))^2=E[X^2]−(E[X])^2 Var(X)=E(X−E(X))2=E[X2]−(E[X])2
-
先计算 E[X2]E[X^2]E[X2]:
E[X2]=∫abx2⋅1b−adx=b3−a33(b−a)=a2+ab+b23E[X^2]=∫_a^bx^2⋅\frac{1}{b−a} dx=\frac{b^3−a^3}{3(b−a)}=\frac{a^2+ab+b^2}{3} E[X2]=∫abx2⋅b−a1 dx=3(b−a)b3−a3=3a2+ab+b2 -
代入方差公式:
Var(X)=a2+ab+b23−(a+b2)2=(b−a)212Var(X)=\frac{a^2+ab+b^2}{3}−(\frac{a+b}{2})^2=\frac{(b−a)^2}{12} Var(X)=3a2+ab+b2−(2a+b)2=12(b−a)2
数学原理:
(1) 前向传播的方差一致性
假设输入 x 的均值为 0,方差为 σx2σ_x^2σx2,权重 W的均值为 0,方差为 σW2σ_W^2σW2,则输出 z=Wxz=Wxz=Wx的方差为:
Var(z)=nin⋅Var(W)⋅Var(x)Var(z)=n_{in}⋅Var(W)⋅Var(x) Var(z)=nin⋅Var(W)⋅Var(x)
为了使 Var(z)=Var(x),需要:
nin⋅Var(W)=1⟹Var(W)=1ninn_{in}⋅Var(W)=1 ⟹ Var(W)=\frac{1}{n_{in}} nin⋅Var(W)=1 ⟹ Var(W)=nin1
其中 ninn_{in}nin是输入维度。这里乘以 ninn_{in}nin 的原因是,输出 z 是由 ninn_{in}nin 个输入 x 的线性组合得到的,每个输入 x 都与一个权重 W 相乘。因此,输出 z 的方差是 ninn_{in}nin 个独立的 WxW_xWx 项的方差之和。
(2) 反向传播的梯度方差一致性
在反向传播过程中,梯度 ∂L∂x\frac{∂L}{∂x}∂x∂L 是通过链式法则计算得到的,其中 L 是损失函数,x 是输入,z 是输出。梯度∂L∂x\frac{∂L}{∂x}∂x∂L可以表示为:
∂L∂x=∂L∂z.∂z∂x\frac{∂L}{∂x}=\frac{∂L}{∂z}.\frac{∂z}{∂x} ∂x∂L=∂z∂L.∂x∂z
假设 z=Wx,其中 W 是权重矩阵,那么 ∂z∂x=W\frac{∂z}{∂x}=W∂x∂z=W。因此,梯度 ∂L∂x\frac{∂L}{∂x}∂x∂L可以写为: ∂L∂x=∂L∂zW\frac{∂L}{∂x}=\frac{∂L}{∂z}W∂x∂L=∂z∂LW
反向传播时梯度 ∂L∂x\frac{∂L}{∂x}∂x∂L 的方差应与 ∂L∂z\frac{∂L}{∂z}∂z∂L 相同,因此:
nout⋅Var(W)=1⟹Var(W)=1noutn_{out}⋅Var(W)=1 ⟹ Var(W)=\frac{1}{n_{out}} nout⋅Var(W)=1 ⟹ Var(W)=nout1
其中 noutn_{out}nout是输出维度(fan_out)。为了保持梯度的方差一致性,我们需要确保每个输入维度 ninn_{in}nin 的梯度方差与输出维度 noutn_{out}nout的梯度方差相同。因此,我们需要将 W 的方差乘以 noutn_{out}nout,以确保梯度的方差在反向传播过程中保持一致。
(3) 综合考虑
为了同时平衡前向传播和反向传播,Xavier 采用:
Var(W)=2nin+noutVar(W)=\frac{2}{n_{in}+n_{out}} Var(W)=nin+nout2
权重从以下分布中采样:
均匀分布:
W∼U(−6nin+nout,6nin+nout)W\sim\mathrm{U}\left(-\frac{\sqrt{6}}{\sqrt{n_\mathrm{in}+n_\mathrm{out}}},\frac{\sqrt{6}}{\sqrt{n_\mathrm{in}+n_\mathrm{out}}}\right) W∼U(−nin+nout6,nin+nout6)
在Xavier初始化中,我们选择 a=−6nin+nouta=−\sqrt{\frac{6}{n_{in}+n_{out}}}a=−nin+nout6 和 b=6nin+noutb=\sqrt{\frac{6}{n_{in}+n_{out}}}b=nin+nout6,这样方差为:
Var(W)=(b−a)212=(26nin+nout)212=4⋅6nin+nout12=2nin+noutVar(W)=\frac{(b−a)^2}{12}=\frac{(2\sqrt{\frac{6}{n_{in}+n_{out}}})^2}{12}=\frac{4⋅\frac{6}{nin+nout}}{12}=\frac{2}{n_{in}+n_{out}} Var(W)=12(b−a)2=12(2nin+nout6)2=124⋅nin+nout6=nin+nout2
正态分布:
W∼N(0,2nin+nout)N(0,std2)W\sim\mathrm{N}\left(0,\frac{2}{n_\mathrm{in}+n_\mathrm{out}}\right)\\\mathcal{N}(0, \text{std}^2) W∼N(0,nin+nout2)N(0,std2)
其中 ninn_{\text{in}}nin 是当前层的输入神经元数量,noutn_{\text{out}}nout是输出神经元数量。
在前向传播中,输出的方差受 ninn_{in}nin 影响。在反向传播中,梯度的方差受 noutn_{out}nout 影响。
优点:平衡了输入和输出的方差,适合SigmoidSigmoidSigmoid 和 TanhTanhTanh 激活函数。
应用场景:常用于浅层网络或使用SigmoidSigmoidSigmoid 、TanhTanhTanh 激活函数的网络。
1.4 He初始化
也叫kaiming 初始化。He 初始化的核心思想是调整权重的初始化范围,使得每一层的输出的方差保持一致。与 Xavier 初始化不同,He 初始化专门针对 ReLU 激活函数的特性进行了优化。(torch默认的对权重的初始化方法,而使用uniform对偏置初始化)
数学推导
(1) 前向传播的方差一致性
对于 ReLU 激活函数,输出的方差为:
Var(z)=12nin⋅Var(W)⋅Var(x)Var(z)=\frac{1}{2}n_{in}⋅Var(W)⋅Var(x) Var(z)=21nin⋅Var(W)⋅Var(x)
(乘12\frac{1}{2}21是因为 ReLU 使一半神经元输出为 0,方差减半)
为使 Var(z)=Var(x),需:
12nin⋅Var(W)=1⟹Var(W)=2nin\frac{1}{2}n_{in}⋅Var(W)=1 ⟹ Var(W)=\frac{2}{n_{in}} 21nin⋅Var(W)=1 ⟹ Var(W)=nin2
(2) 反向传播的梯度一致性
类似地,反向传播时梯度方差需满足:
Var(∂L∂x)=12nout⋅Var(W)⋅Var(∂L∂z)Var(\frac{∂L}{∂x})=\frac{1}{2}n_{out}⋅Var(W)⋅Var(\frac{∂L}{∂z}) Var(∂x∂L)=21nout⋅Var(W)⋅Var(∂z∂L)
因此:
Var(W)=2noutVar(W)=\frac{2}{n_{out}} Var(W)=nout2
(3) 两种模式
fan_in
模式(默认):优先保证前向传播稳定,方差 2nin\frac{2}{n_{in}}nin2。fan_out
模式:优先保证反向传播稳定,方差2nout\frac{2}{n_{out}}nout2。
方法:专门为 ReLU 激活函数设计。权重从以下分布中采样:
均匀分布:
W∼U(−6nin,6nin)W\sim\mathrm{U}\left(-\frac{\sqrt{6}}{\sqrt{n_\mathrm{in}}},\frac{\sqrt{6}}{\sqrt{n_\mathrm{in}}}\right) W∼U(−nin6,nin6)
正态分布:
W∼N(0,2nin)W\sim\mathrm{N}\left(0,\frac{2}{n_\mathrm{in}}\right) W∼N(0,nin2)
其中 ninn_{\text{in}}nin 是当前层的输入神经元数量。
优点:适用于ReLUReLUReLU 和 LeakyReLULeaky ReLULeakyReLU 激活函数。
应用场景:深度网络,尤其是使用 ReLU 激活函数时。
1.5 总结
pytorch默认对权重的初始化方法为He方法,默认使用均匀分布的随机初始化方式对偏置进行初始化。其他方式请查看官方文档。这里给出一份示例代码:
import torch
import torch.nn as nn# 创建模型实例
model = nn.Linear(100, 200) # ----------------------------
# 1.1 固定值初始化
# ----------------------------print("固定值初始化结果:")
nn.init.constant_(model.weight, 0.1)
print(model.weight[0, :5]) # 输出前5个权重值# ----------------------------
# 1.2 随机初始化
# ----------------------------print("\n随机均匀分布初始化结果:")
nn.init.uniform_(model.weight, -0.1, 0.1)
print(model.weight[0, :5])
print("\n随机正态分布初始化结果:")
nn.init.normal_(model.weight, mean=0, std=0.1)
print(model.weight[0, :5])# ----------------------------
# 1.3 Xavier初始化 (Glorot)
# ----------------------------print("\nXavier初始化结果:")
nn.init.xavier_normal_(model.weight)
print(model.weight[0, :5])
print("权重均值: {:.4f}, 标准差: {:.4f}".format(model.weight.mean().item(), model.weight.std().item()))# ----------------------------
# 1.4 He初始化 (Kaiming)
# ----------------------------print("\nHe初始化结果:")
nn.init.kaiming_normal_(model.weight)
print(model.weight[0, :5])
print("权重均值: {:.4f}, 标准差: {:.4f}".format(model.weight.mean().item(), model.weight.std().item()))
二、损失函数
2.1 线性损失函数
在先前机器学习的文章当中以及提及了损失函数。但当时对损失函数的讲解更接近原理,所使用的损失函数不过是线性损失函数的一种,叫均方差损失(MSE损失),也叫L2损失,其被广泛应用在神经网络中。MSE的公式如下:
MSE=1n∑i=1n(yi−y^i)2\text{MSE} = \frac{1}{n} \sum_{i=1}^{n} \left( y_i - \hat{y}_i \right)^2 MSE=n1i=1∑n(yi−y^i)2
其中:
- nnn 是样本的总数。
- $ y_i $ 是第 iii 个样本的真实值。
- $ \hat{y}_i $ 是第 iii 个样本的预测值。
- (yi−y^i)2\left( y_i - \hat{y}_i \right)^2(yi−y^i)2 是真实值和预测值之间的误差平方。
特点:
- 平方惩罚:因为误差平方,MSE 对较大误差施加更大惩罚,所以 MSE 对异常值更为敏感。
- 凸性:MSE 是一个凸函数(国际的叫法,国内叫凹函数),这意味着它具有一个唯一的全局最小值,有助于优化问题的求解。
还有另一种线性损失函数,MAE(Mean Absolute Error,平均绝对误差)通常也被称为 L1-Loss,通过对预测值和真实值之间的绝对差取平均值来衡量他们之间的差异。通常用于需要对误差进行线性度量的情况,尤其是当数据中可能存在异常值时,MAE可以避免对异常值的过度惩罚。MAE的公式如下:
MAE=1n∑i=1n∣yi−y^i∣\text{MAE} = \frac{1}{n} \sum_{i=1}^{n} \left| y_i - \hat{y}_i \right| MAE=n1i=1∑n∣yi−y^i∣
其中:
- nnn 是样本的总数。
- $ y_i $ 是第 iii 个样本的真实值。
- $ \hat{y}_i$ 是第 iii 个样本的预测值。
- ∣yi−y^i∣\left| y_i - \hat{y}_i \right|∣yi−y^i∣ 是真实值和预测值之间的绝对误差。
特点:
- 鲁棒性:与均方误差(MSE)相比,MAE对异常值(outliers)更为鲁棒,因为它不会像MSE那样对较大误差平方敏感。
- 物理意义直观:MAE以与原始数据相同的单位度量误差,使其易于解释。
这里给出一份简单的代码示例:
import torch
import torch.nn as nn# 初始化MAE损失函数
mae_loss = nn.L1Loss()
# 初始化MSE损失函数
mse_loss = nn.MSELoss()# 假设 y_true 是真实值, y_pred 是预测值
y_true = torch.tensor([3.0, 5.0, 2.5])
y_pred = torch.tensor([2.5, 5.0, 3.0])# 计算MAE
loss1 = mae_loss(y_pred, y_true)
print(f'MAE Loss: {loss1.item()}')
# 计算MSE
loss2 = mse_loss(y_pred, y_true)
print(f'MSE Loss: {loss2.item()}')
2.2 CrossEntropyLoss
在先前机器学习中的决策树算法中我们已经简要介绍了信息熵概念与计算方式,这里我们将从信息量开始,逐步深入推导到交叉熵的概念。
2.2.1 信息量
信息量用于衡量一个事件所包含的信息的多少。信息量的定义基于事件发生的概率:事件发生的概率越低,其信息量越大。其量化公式:
对于一个事件x,其发生的概率为 P(x),信息量I(x) 定义为:
I(x)=−logP(x)I(x)=−logP(x) I(x)=−logP(x)
性质
- 非负性:I(x)≥0。
- 单调性:P(x)越小,I(x)越大。
2.2.2 信息熵
信息熵是信息量的期望值。熵越高,表示随机变量的不确定性越大;熵越低,表示随机变量的不确定性越小。
公式由数学中的期望推导而来:
H(X)=−∑i=1nP(xi)logP(xi)H(X)=−∑_{i=1}^n P(x_i)logP(x_i) H(X)=−i=1∑nP(xi)logP(xi)
其中−logP(xi)-logP(x_i)−logP(xi)是信息量,P(xi)P(x_i)P(xi)是信息量对应的概率
2.2.3 KL散度
KL散度用于衡量两个概率分布之间的差异。它描述的是用一个分布 Q来近似另一个分布 P时,所损失的信息量。KL散度越小,表示两个分布越接近。
对于两个离散概率分布 P和 Q,KL散度定义为:
DKL(P∣∣Q)=∑iP(xi)logP(xi)Q(xi)=∑iP(xi)logP(xi)−∑iP(xi)logQ(xi)D_{KL}(P||Q)=∑_iP(x_i)log\frac{P(x_i)}{Q(x_i)}=∑_iP(x_i)log{P(x_i)}-∑_iP(x_i)log{Q(x_i)} DKL(P∣∣Q)=i∑P(xi)logQ(xi)P(xi)=i∑P(xi)logP(xi)−i∑P(xi)logQ(xi)
其中:P 是真实分布,Q是近似分布。
2.2.4 交叉熵
对KL散度公式展开:
DKL(P∣∣Q)=∑iP(xi)logP(xi)Q(xi)=∑iP(xi)[logP(xi)−logQ(xi)]=∑iP(xi)logP(xi)−∑iP(xi)logQ(xi)=−(−∑iP(xi)logP(xi))+(−∑iP(xi)logQ(xi))=−H(P)+(−∑iP(xi)logQ(xi))=H(P,Q)−H(P)D_{KL}(P||Q)=∑_iP(x_i)log\frac{P(x_i)}{Q(x_i)}=∑_iP(x_i)[log{P(x_i)}-log{Q(x_i)}]\\ =∑_iP(x_i)log{P(x_i)}-∑_iP(x_i)log{Q(x_i)}=-(-∑_iP(x_i)log{P(x_i)})+(-∑_iP(x_i)log{Q(x_i)})\\ =-H(P)+(-∑_iP(x_i)log{Q(x_i)})\\ =H(P,Q)-H(P) DKL(P∣∣Q)=i∑P(xi)logQ(xi)P(xi)=i∑P(xi)[logP(xi)−logQ(xi)]=i∑P(xi)logP(xi)−i∑P(xi)logQ(xi)=−(−i∑P(xi)logP(xi))+(−i∑P(xi)logQ(xi))=−H(P)+(−i∑P(xi)logQ(xi))=H(P,Q)−H(P)
由上述公式可知,P是真实分布,H( P )是常数,所以KL散度可以用H(P,Q)来表示;H(P,Q)叫做交叉熵。
交叉熵=散度+熵
如果将P换成y(真实值),Q换成y^\hat{y}y^(预测值),则交叉熵公式为:
CrossEntropyLoss(y,y^)=−∑i=1Cyilog(y^i)\text{CrossEntropyLoss}(y, \hat{y}) = - \sum_{i=1}^{C} y_i \log(\hat{y}_i) CrossEntropyLoss(y,y^)=−i=1∑Cyilog(y^i)
其中:
- CCC 是类别的总数。
- yyy 是真实标签的one-hot编码向量,表示真实类别。
- y^\hat{y}y^ 是模型的输出(经过 softmax 后的概率分布)。
- yiy_iyi 是真实类别的第 iii 个元素(0 或 1)。
- y^i\hat{y}_iy^i 是预测的类别概率分布中对应类别 iii 的概率。
特点:
-
概率输出:CrossEntropyLoss 通常与 softmax 函数一起使用,使得模型的输出表示为一个概率分布(即所有类别的概率和为 1)。PyTorch 的 nn.CrossEntropyLoss 已经内置了 Softmax 操作。如果我们在输出层显式地添加 Softmax,会导致重复应用 Softmax,从而影响模型的训练效果。
-
惩罚错误分类:该损失函数在真实类别的预测概率较低时,会施加较大的惩罚,这样模型在训练时更注重提升正确类别的预测概率。
-
多分类问题中的标准选择:在大多数多分类问题中,CrossEntropyLoss 是首选的损失函数。
应用场景:
CrossEntropyLoss 广泛应用于各种分类任务,包括图像分类、文本分类等,尤其是在神经网络模型中。
交叉熵损失函数原理:
由交叉熵公式可知:
Loss(y,y^)=−∑i=1Cyilog(y^i)\text{Loss}(y, \hat{y}) = - \sum_{i=1}^{C} y_i \log(\hat{y}_i) Loss(y,y^)=−i=1∑Cyilog(y^i)
因为yiy_iyi是one-hot编码,其值不是1便是0,又是乘法,所以只要知道1,也即是真实类别对应的index就可以了,展开后:Loss(y,y^)=−log(y^m)\text{Loss}(y, \hat{y}) = - \log(\hat{y}_m)Loss(y,y^)=−log(y^m),即预测为真实类别的信息量。
其中,m表示真实类别。
因为神经网络最后一层分类总是接softmax,所以可以把y^m\hat{y}_my^m直接看为是softmax后的结果。
Loss(i)=−log(softmax(xi))\text{Loss}(i) = - \log(softmax(x_i)) Loss(i)=−log(softmax(xi))
所以,CrossEntropyLoss
实质上是两步的组合:Cross Entropy = Log-Softmax + NLLLoss
- Log-Softmax:对输入 logits 先计算对数 softmax:
log(softmax(x))
。 - NLLLoss(Negative Log-Likelihood):对 log-softmax 的结果计算负对数似然损失。简单理解就是求负数。原因是概率值通常在 0 到 1 之间,取对数后会变成负数。为了使损失值为正数,需要取负数。
关于softmax函数,在先前文章中讲解神经网络中的损失函数的部分有所提及。
示例代码:
import torch
import torch.nn as nn# 假设有三个类别,模型输出是未经softmax的logits
logits = torch.tensor([[1.5, 2.0, 0.5], [0.5, 1.0, 1.5]])# 真实的标签
labels = torch.tensor([1, 2]) # 第一个样本的真实类别为1,第二个样本的真实类别为2# 初始化CrossEntropyLoss
# 参数:reduction,即所有样本损失值的 mean-平均值(默认),sum-总和
criterion = nn.CrossEntropyLoss()# 计算损失
loss = criterion(logits, labels)
print(f'Cross Entropy Loss: {loss.item()}')
2.3 BCELoss
二分类交叉熵损失函数,使用在输出层使用sigmoid激活函数进行二分类时,需显式调用sigmoid。
由交叉熵公式:
CELoss(y,y^)=−∑i=1Cyilog(y^i)\text{CELoss}(y, \hat{y}) = - \sum_{i=1}^{C} y_i \log(\hat{y}_i) CELoss(y,y^)=−i=1∑Cyilog(y^i)
对于二分类问题,真实标签 y的值为(0 或 1),假设模型预测为正类的概率为 y^\hat{y}y^,则:
{y如果y=11−y如果y=0\begin{cases} {y} & 如果 y=1\\ 1−{y} & 如果 y=0 \end{cases} {y1−y如果y=1如果y=0
所以:
CELoss(y,y^)=−[ylog(y^)+(1−y)log(1−y^)]\text{CELoss}(y, \hat{y}) = -[ylog(\hat{y}) + (1-y)log(1-\hat{y})] CELoss(y,y^)=−[ylog(y^)+(1−y)log(1−y^)]
这里给出示例代码:
import torch
import torch.nn as nn# 假设有三个样本,模型输出是sigmoid函数的输出,
logits = torch.tensor([[0.5], [0.9], [0.1]])# 真实的标签
labels = torch.tensor([[0], [1], [0]],dtype=torch.float)# 计算损失方式一:
bceLoss = nn.BCELoss()
loss1 = criterion(logits, labels)#计算损失方式二: 两种方式结果相同
loss2 = nn.functional.binary_cross_entropy(logits, labels)print(loss1, loss2)
2.4 总结
- 当输出层使用softmax多分类时,使用交叉熵损失函数;
- 当输出层使用sigmoid二分类时,使用二分类交叉熵损失函数, 比如在逻辑回归中使用;
- 当功能为线性回归时,使用均方差损失-L2 loss;
三、反向传播
3.1 前向传播
前向传播(Forward Propagation)把输入数据经过各层神经元的运算并逐层向前传输,一直到输出层为止。本质即为输入特征获得预测值。
前向传播的主要作用是:
- 计算神经网络的输出结果,用于预测或计算损失。
- 在反向传播中使用,通过计算损失函数相对于每个参数的梯度来优化网络。
3.2 梯度下降算法存在问题
梯度下降算法,在先前已有介绍梯度下降的文章已经专门介绍过这里不再赘述;而SGD,BGD,MBGD在先前的文章中也已有介绍这里同样不再赘述。
目前的梯度下降算法有如下问题:
-
收敛速度慢:BGD和MBGD使用固定学习率,太大会导致震荡,太小又收敛缓慢。
-
局部最小值和鞍点问题:SGD在遇到局部最小值或鞍点时容易停滞,导致模型难以达到全局最优。
-
训练不稳定:SGD中的噪声容易导致训练过程中不稳定,使得训练陷入震荡或不收敛。
3.3 算法的改进
回到梯度下降算法的公式:wijnew=wijold−α∂E∂wijw_{ij}^{new}= w_{ij}^{old} - \alpha \frac{\partial E}{\partial w_{ij}}wijnew=wijold−α∂wij∂E,其中能够进行优化的部分只有学习率α\alphaα以及梯度部分∂E∂wij\frac{\partial E}{\partial w_{ij}}∂wij∂E。
通过引入对梯度部分的优化可引出今天的第一种优化方式:
3.3.1 Momentum
动量(Momentum)是对梯度下降的优化方法,可以更好地应对梯度变化和梯度消失问题,从而提高训练模型的效率和稳定性。它通过引入 指数加权平均 来积累历史梯度信息,从而在更新参数时形成“动量”,帮助优化算法更快地越过局部最优或鞍点。优化的是梯度
梯度更新算法包括两个步骤:
a. 更新动量项
首先计算当前的动量项 vtv_tvt: vt=βvt−1+(1−β)∇θJ(θt)v_{t} = \beta v_{t-1} + (1 - \beta) \nabla_\theta J(\theta_t)vt=βvt−1+(1−β)∇θJ(θt) 其中:
- vt−1v_{t-1}vt−1 是之前的动量项;
- β\betaβ 是动量系数(通常为 0.9);
- ∇θJ(θt)\nabla_\theta J(\theta_t)∇θJ(θt) 是当前的梯度;
b. 更新参数
利用动量项更新参数:
vt=βvt−1+(1−β)∇θJ(θt)θt=θt−1−ηvtv_{t}=\beta v_{t-1}+(1-\beta)\nabla_\theta J(\theta_t) \\ \theta_{t}=\theta_{t-1}-\eta v_{t} vt=βvt−1+(1−β)∇θJ(θt)θt=θt−1−ηvt
特点:
-
惯性效应: 该方法加入前面梯度的累积,这种惯性使得算法沿着当前的方向继续更新。如遇到鞍点,也不会因梯度逼近零而停滞。
-
减少震荡: 该方法平滑了梯度更新,减少在鞍点附近的震荡,帮助优化过程稳定向前推进。
-
加速收敛: 该方法在优化过程中持续沿着某个方向前进,能够更快地穿越鞍点区域,避免在鞍点附近长时间停留。
在方向上的作用:
(1)梯度方向一致时
- 如果梯度在多个连续时刻方向一致(例如,一直指向某个方向),Momentum 会逐渐积累动量,使更新速度加快。
- 例如,假设梯度在多个时刻都是正向的,动量 vtv_tvt 会逐渐增大,从而加速参数更新。
(2)梯度方向不一致时
- 如果梯度方向在不同时刻不一致(例如,来回震荡),Momentum 会通过积累的历史梯度信息部分抵消这些震荡。
- 例如,假设梯度在一个时刻是正向的,下一个时刻是负向的,动量 vtv_tvt 会平滑这些变化,使更新路径更加稳定。
(3)局部最优或鞍点附近
- 在局部最优或鞍点附近,梯度可能会变得很小,导致标准梯度下降法停滞。
- Momentum 通过积累历史梯度信息,可以帮助参数更新越过这些平坦区域。
动量方向与梯度方向一致
(1)梯度方向一致时
- 如果梯度在多个连续时刻方向一致(例如,一直指向某个方向),动量会逐渐积累,动量方向与梯度方向一致。
- 例如,假设梯度在多个时刻都是正向的,动量 vtv_tvt 会逐渐增大,从而加速参数更新。
(2)几何意义
- 在优化问题中,如果损失函数的几何形状是 平滑且单调 的(例如,一个狭长的山谷),梯度方向会保持一致。
- 在这种情况下,动量方向与梯度方向一致,Momentum 会加速参数更新,帮助算法更快地收敛。
动量方向与梯度方向不一致
(1)梯度方向不一致时
- 如果梯度方向在不同时刻不一致(例如,来回震荡),动量方向可能会与当前梯度方向不一致。
- 例如,假设梯度在一个时刻是正向的,下一个时刻是负向的,动量 vtv_tvt 会平滑这些变化,使更新路径更加稳定。
(2)几何意义
- 在优化问题中,如果损失函数的几何形状是 复杂且非凸 的(例如,存在多个局部最优或鞍点),梯度方向可能会在不同时刻发生剧烈变化。
- 在这种情况下,动量方向与梯度方向可能不一致,Momentum 会通过积累的历史梯度信息部分抵消这些震荡,使更新路径更加平滑。
总结:
- 动量项更新:利用当前梯度和历史动量来计算新的动量项。
- 权重参数更新:利用更新后的动量项来调整权重参数。
- 梯度计算:在每个时间步计算当前的梯度,用于更新动量项和权重参数。
Momentum 算法是对梯度值的平滑调整,但是并没有对梯度下降中的学习率进行优化。这里给出一份示例代码:
# 定义模型和损失函数
model = nn.Linear(10, 1)
criterion = nn.MSELoss()
optimizer = torch.optim.SGD(model.parameters(), lr=0.01, momentum=0.9) # PyTorch 中 momentum 直接作为参数# 模拟数据
X = torch.randn(100, 10)
y = torch.randn(100, 1)# 训练循环
for epoch in range(100):optimizer.zero_grad()outputs = model(X)loss = criterion(outputs, y)loss.backward()optimizer.step()print(f'Epoch {epoch}, Loss: {loss.item():.4f}')
3.3.2 AdaGrad
AdaGrad(Adaptive Gradient Algorithm)是一种调节学习率的优化方式,通过为每个参数引入独立的学习率,它根据历史梯度的平方和来调整这些学习率。具体来说,对于频繁更新的参数,其学习率会逐渐减小;而对于更新频率较低的参数,学习率会相对较大。AdaGrad避免了统一学习率的不足,更多用于处理稀疏数据和梯度变化较大的问题。
AdaGrad流程:
-
初始化:
- 初始化参数 $ \theta_0 $ 和学习率 $ \eta $。
- 将梯度累积平方的向量 $ G_0$ 初始化为零向量。
-
梯度计算:
- 在每个时间步 ttt,计算损失函数$ J(\theta)$对参数 θ\thetaθ 的梯度gt=∇θJ(θt)g_t = \nabla_\theta J(\theta_t)gt=∇θJ(θt)。
-
累积梯度的平方:
-
对每个参数 iii累积梯度的平方:
Gt=Gt−1+gt2G_{t} = G_{t-1} + g_{t}^2\\ Gt=Gt−1+gt2
其中GtG_{t}Gt 是累积的梯度平方和,$ g_{t}$ 是第$ i 个参数在时间步个参数在时间步个参数在时间步t$ 的梯度。推导:
Gt=Gt−1+gt2=Gt−2+gt−12+gt2=...=g12+...+gt−12+gt2G_{t} = G_{t-1} + g_{t}^2=G_{t-2} + g_{t-1}^2 + g_{t}^2 = ... = g_{1}^2 + ... + g_{t-1}^2 + g_{t}^2 Gt=Gt−1+gt2=Gt−2+gt−12+gt2=...=g12+...+gt−12+gt2
-
-
参数更新:
-
利用累积的梯度平方来更新参数:
θt=θt−1−ηGt+ϵgt\theta_{t} = \theta_{t-1} - \frac{\eta}{\sqrt{G_{t} + \epsilon}} g_{t} θt=θt−1−Gt+ϵηgt -
其中:
- η\etaη 是全局的初始学习率。
- ϵ\epsilonϵ 是一个非常小的常数,用于避免除零操作(通常取10−810^{-8}10−8)。
- ηGt+ϵ\frac{\eta}{\sqrt{G_{t} + \epsilon}}Gt+ϵη 是自适应调整后的学习率。
-
AdaGrad 为每个参数分配不同的学习率:
- 对于梯度较大的参数,Gt较大,学习率较小,从而避免更新过快。
- 对于梯度较小的参数,Gt较小,学习率较大,从而加快更新速度。
可以将 AdaGrad 类比为:
- 梯度较大的参数:类似于陡峭的山坡,需要较小的步长(学习率)以避免跨度过大。
- 梯度较小的参数:类似于平缓的山坡,可以采取较大的步长(学习率)以加快收敛。
优点:
- 自适应学习率:由于每个参数的学习率是基于其梯度的累积平方和 Gt,iG_{t,i}Gt,i 来动态调整的,这意味着学习率会随着时间步的增加而减少,对梯度较大且变化频繁的方向非常有用,防止了梯度过大导致的震荡。
- 适合稀疏数据:AdaGrad 在处理稀疏数据时表现很好,因为它能够自适应地为那些较少更新的参数保持较大的学习率。
缺点:
- 学习率过度衰减:随着时间的推移,累积的时间步梯度平方值越来越大,导致学习率逐渐接近零,模型会停止学习。
- 不适合非稀疏数据:在非稀疏数据的情况下,学习率过快衰减可能导致优化过程早期停滞。
AdaGrad是一种有效的自适应学习率算法,然而由于学习率衰减问题,我们会使用改 RMSProp 或 Adam 来替代。
示例:
# 定义模型和损失函数model = torch.nn.Linear(10, 1)criterion = torch.nn.MSELoss()optimizer = torch.optim.Adagrad(model.parameters(), lr=0.01)# 模拟数据X = torch.randn(100, 10)y = torch.randn(100, 1)# 训练循环for epoch in range(100):optimizer.zero_grad()outputs = model(X)loss = criterion(outputs, y)loss.backward()optimizer.step()print(f'Epoch {epoch}, Loss: {loss.item()}')
3.3.3 RMSProp
虽然 AdaGrad 能够自适应地调整学习率,但随着训练进行,累积梯度平方 GtG_tGt会不断增大,导致学习率逐渐减小,最终可能变得过小,导致训练停滞。
RMSProp(Root Mean Square Propagation)是一种自适应学习率的优化算法,在时间步中,不是简单地累积所有梯度平方和,而是使用指数加权平均来逐步衰减过时的梯度信息。旨在解决 AdaGrad 学习率单调递减的问题。它通过引入 指数加权平均 来累积历史梯度的平方,从而动态调整学习率。
公式为:
st=β⋅st−1+(1−β)⋅gt2θt+1=θt−ηst+ϵ⋅gts_t=β⋅s_{t−1}+(1−β)⋅g_t^2\\θ_{t+1}=θ_t−\frac{η}{\sqrt{s_t+ϵ}}⋅gt st=β⋅st−1+(1−β)⋅gt2θt+1=θt−st+ϵη⋅gt
其中:
- sts_tst是当前时刻的指数加权平均梯度平方。
- β是衰减因子,通常取 0.9。
- η是初始学习率。
- ϵ是一个小常数(通常取 10−810^{−8}10−8),用于防止除零。
- gtg_tgt是当前时刻的梯度。
优点
-
适应性强:RMSProp自适应调整每个参数的学习率,对于梯度变化较大的情况非常有效,使得优化过程更加平稳。
-
适合非稀疏数据:相比于AdaGrad,RMSProp更加适合处理非稀疏数据,因为它不会让学习率减小到几乎为零。
-
解决过度衰减问题:通过引入指数加权平均,RMSProp避免了AdaGrad中学习率过快衰减的问题,保持了学习率的稳定性
缺点
依赖于超参数的选择:RMSProp的效果对衰减率 γ\gammaγ 和学习率 η\etaη 的选择比较敏感,需要一些调参工作。
示例:
# 定义模型和损失函数model = nn.Linear(10, 1)criterion = nn.MSELoss()optimizer = torch.optim.RMSprop(model.parameters(), lr=0.01, alpha=0.9, eps=1e-8)# 模拟数据X = torch.randn(100, 10)y = torch.randn(100, 1)# 训练循环for epoch in range(100):optimizer.zero_grad()outputs = model(X)loss = criterion(outputs, y)loss.backward()optimizer.step()print(f'Epoch {epoch}, Loss: {loss.item():.4f}')
3.3.4 Adam
Adam(Adaptive Moment Estimation)算法将动量法和RMSProp的优点结合在一起:
- 动量法:调整梯度,通过一阶动量(即梯度的指数加权平均)来加速收敛,尤其是在有噪声或梯度稀疏的情况下。
- RMSProp:通过二阶动量(即梯度平方的指数加权平均)来调整学习率,使得每个参数的学习率适应其梯度的变化。
Adam过程
-
初始化:
- 初始化参数 θ0\theta_0θ0 和学习率η\etaη。
- 初始化一阶动量估计 m0=0m_0 = 0m0=0 和二阶动量估计v0=0v_0 = 0v0=0。
- 设定动量项的衰减率 β1\beta_1β1 和二阶动量项的衰减率β2\beta_2β2,通常 β1=0.9\beta_1 = 0.9β1=0.9,β2=0.999\beta_2 = 0.999β2=0.999。
- 设定一个小常数ϵ\epsilonϵ(通常取10−810^{-8}10−8),用于防止除零错误。
-
梯度计算:
- 在每个时间步 ttt,计算损失函数J(θ)J(\theta)J(θ) 对参数θ\thetaθ 的梯度gt=∇θJ(θt)g_t = \nabla_\theta J(\theta_t)gt=∇θJ(θt)。
-
一阶动量估计(梯度的指数加权平均):
- 更新一阶动量估计:
mt=β1mt−1+(1−β1)gtm_t = \beta_1 m_{t-1} + (1 - \beta_1) g_t mt=β1mt−1+(1−β1)gt
其中,mtm_tmt 是当前时间步 ttt 的一阶动量估计,表示梯度的指数加权平均。
- 更新一阶动量估计:
-
二阶动量估计(梯度平方的指数加权平均):
- 更新二阶动量估计:
vt=β2vt−1+(1−β2)gt2v_t = \beta_2 v_{t-1} + (1 - \beta_2) g_t^2 vt=β2vt−1+(1−β2)gt2
其中,vtv_tvt 是当前时间步 ttt 的二阶动量估计,表示梯度平方的指数加权平均。
- 更新二阶动量估计:
-
偏差校正:
由于一阶动量和二阶动量在初始阶段可能会有偏差,以二阶动量为例:
在计算指数加权平均时,初始化 v0=0v_{0}=0v0=0,那么v1=0.999⋅v0+0.001⋅g12v_{1}=0.999\cdot v_{0}+0.001\cdot g_{1}^2v1=0.999⋅v0+0.001⋅g12,得到v1=0.001⋅g12v_{1}=0.001\cdot g_{1}^2v1=0.001⋅g12,显然得到的 v1v_{1}v1 会小很多,导致估计的不准确,以此类推:
根据:v2=0.999⋅v1+0.001⋅g22v_{2}=0.999\cdot v_{1}+0.001\cdot g_{2}^2v2=0.999⋅v1+0.001⋅g22,把 v1v_{1}v1 带入后,
得到:v2=0.999⋅0.001⋅g12+0.001⋅g22v_{2}=0.999\cdot 0.001\cdot g_{1}^2+0.001\cdot g_{2}^2v2=0.999⋅0.001⋅g12+0.001⋅g22,导致 v2v_{2}v2 远小于 g1g_{1}g1 和 g2g_{2}g2,所以 v2v_{2}v2 并不能很好的估计出前两次训练的梯度。所以这个估计是有偏差的,可使用以下公式进行偏差校正:
m^t=mt1−β1tv^t=vt1−β2t\hat{m}_t = \frac{m_t}{1 - \beta_1^t} \\ \hat{v}_t = \frac{v_t}{1 - \beta_2^t} m^t=1−β1tmtv^t=1−β2tvt
其中,m^t\hat{m}_tm^t 和v^t\hat{v}_tv^t 是校正后的一阶和二阶动量估计。假设梯度 gt{g_t}gt是一个平稳的随机变量,其期望为:E(gt)=μE(g_t)=μE(gt)=μ。通过递推可以发现,对于任意时刻 t,mtm_tmt的期望为:
E(mt)=(1−βt)⋅μE(m_t)=(1−β^t)⋅μ E(mt)=(1−βt)⋅μ
通过上述公式我们发现mtm_tmt的期望与梯度期望相差了(1−βt)(1−β^t)(1−βt)倍,我们想要找到一个修正后的mt^\hat{m_t}mt^,使:E(mt^)=μE(\hat{m_t})=μE(mt^)=μ所以:
E(mt)=(1−βt)⋅μ=(1−βt)⋅E(mt^)E(m_t)=(1−β^t)⋅μ=(1−β^t)⋅E(\hat{m_t}) E(mt)=(1−βt)⋅μ=(1−βt)⋅E(mt^)
进行缩放:mt=(1−βt)⋅mt^m_t=(1−β^t)⋅\hat{m_t}mt=(1−βt)⋅mt^,可得出:
mt^=mt1−βt\hat{m_t}=\frac{m_t}{1−β^t} mt^=1−βtmt
vtv_tvt推导同理。 -
参数更新:
- 使用校正后的动量估计更新参数:
θt+1=θt−ηv^t+ϵm^t\theta_{t+1} = \theta_t - \frac{\eta}{\sqrt{\hat{v}_t} + \epsilon} \hat{m}_t θt+1=θt−v^t+ϵηm^t
- 使用校正后的动量估计更新参数:
优点
-
高效稳健:Adam结合了动量法和RMSProp的优势,在处理非静态、稀疏梯度和噪声数据时表现出色,能够快速稳定地收敛。
-
自适应学习率:Adam通过一阶和二阶动量的估计,自适应调整每个参数的学习率,避免了全局学习率设定不合适的问题。
-
适用大多数问题:Adam几乎可以在不调整超参数的情况下应用于各种深度学习模型,表现良好。
缺点
-
超参数敏感:尽管Adam通常能很好地工作,但它对初始超参数(如 β1\beta_1β1、β2\beta_2β2 和 η\etaη)仍然较为敏感,有时需要仔细调参。
-
过拟合风险:由于Adam会在初始阶段快速收敛,可能导致模型陷入局部最优甚至过拟合。因此,有时会结合其他优化算法(如SGD)使用。
示例:
# 定义模型和损失函数model = nn.Linear(10, 1)criterion = nn.MSELoss()optimizer = torch.optim.Adam(model.parameters(), lr=0.01, betas=(0.9, 0.999), eps=1e-8)# 模拟数据X = torch.randn(100, 10)y = torch.randn(100, 1)# 训练循环for epoch in range(100):optimizer.zero_grad()outputs = model(X)loss = criterion(outputs, y)loss.backward()optimizer.step()print(f'Epoch {epoch}, Loss: {loss.item():.4f}')
3.4 总结
梯度下降算法通过不断更新参数来最小化损失函数,是反向传播算法中计算权重调整的基础。在实际应用中,根据数据的规模和计算资源的情况,选择合适的梯度下降方式(批量、随机、小批量)及其变种(如动量法、Adam等)可以显著提高模型训练的效率和效果。
Adam是目前最为流行的优化算法之一,因其稳定性和高效性,广泛应用于各种深度学习模型的训练中。Adam结合了动量法和RMSProp的优点,能够在不同情况下自适应调整学习率,并提供快速且稳定的收敛表现。
总结
本文简要介绍了神经网络中的初始化技术,损失函数,以及反向传播中的优化技术。