七、BP算法
多层神经网络的学习本领比单层网络强得多。想要练习多层网络,需要更强大的学习算法。误差反向传播算法(Back Propagation)是其中最杰出的代表,它是目前最成功的神经网络学习算法。实际任务利用神经网络时,大多是在利用 BP 算法举行练习,值得指出的是 BP 算法不但可用于多层前馈神经网络,还可以用于其他类型的神经网络。通常说 BP 网络时,一样平常是指用 BP 算法练习的多层前馈神经网络。
误差反向传播算法(BP)的根本步骤:
- 前向传播:正向盘算得到猜测值。
- 盘算损失:通过损失函数 L ( y pred , y true ) L(y_{\text{pred}}, y_{\text{true}}) L(ypred,ytrue) 盘算猜测值和真实值的差距。
- 梯度盘算:反向传播的核心是盘算损失函数 L L L 对每个权重和偏置的梯度。
- 更新参数:一旦得到每层梯度,就可以利用梯度降落算法来更新每层的权重和偏置,使得损失逐渐减小。
- 迭代练习:将前向传播、梯度盘算、参数更新的步骤重复多次,直到损失函数收敛或到达预定的停止条件。
1. 前向传播
前向传播(Forward Propagation)把输入数据经过各层神经元的运算并逐层向前传输,不停到输出层为止。
下面是一个简单的三层神经网络(输入层、隐蔽层、输出层)前向传播的根本步骤分析。
1.1输入层到隐蔽层
给定输入 x x x 和权重矩阵 W 1 W_1 W1 及偏置向量 b 1 b_1 b1,隐蔽层的输出(激活值)盘算如下:
z ( 1 ) = W 1 ⋅ x + b 1 z^{(1)} = W_1 \cdot x + b_1 z(1)=W1⋅x+b1
将 z ( 1 ) z^{(1)} z(1) 通过激活函数 σ \sigma σ举行激活:
a ( 1 ) = σ ( z ( 1 ) ) a^{(1)} = \sigma(z^{(1)}) a(1)=σ(z(1))
1.2隐蔽层到输出层
隐蔽层的输出 a ( 1 ) a^{(1)} a(1) 通过输出层的权重矩阵 W 2 W_2 W2和偏置 b 2 b_2 b2 天生最终的输出:
z ( 2 ) = W 2 ⋅ a ( 1 ) + b 2 z^{(2)} = W_2 \cdot a^{(1)} + b_2 z(2)=W2⋅a(1)+b2
输出层的激活值 a ( 2 ) a^{(2)} a(2) 是最终的猜测结果:
y pred = a ( 2 ) = σ ( z ( 2 ) ) y_{\text{pred}} = a^{(2)} = \sigma(z^{(2)}) ypred=a(2)=σ(z(2))
前向传播的主要作用是:
- 盘算神经网络的输出结果,用于猜测或盘算损失。
- 在反向传播中利用,通过盘算损失函数相对于每个参数的梯度来优化网络。
2. 反向传播
反向传播(Back Propagation,简称BP)通过盘算损失函数相对于每个参数的梯度来调解权重,使模型在练习数据上的表现逐渐优化。反向传播结合了链式求导法则和梯度降落算法,是神经网络模型练习过程中更新参数的关键步骤。
2.1 原理
利用链式求导法则对每一层举行求导,直到求出输入层x的导数,然后利用导数值举行梯度更新
2.2. 链式法则
链式求导法则(Chain Rule)是微积分中的一个重要法则,用于求复合函数的导数。在深度学习中,链式法则是反向传播算法的基础,如许就可以通过分层的盘算求得损失函数相对于每个参数的梯度。以下面的复合函数为例:
f ( x ) = 1 1 + e − ( w x + b ) \mathrm{f(x)=\frac{1}{1+e^{-(wx+b)}}} f(x)=1+e−(wx+b)1
其中 x x x 是输入数据, w w w 是权重, b b b 是偏置。
2.2.1 函数分解
可以将该复合函数分解为:
函数导数我们假设 w=0, b=0, x=1 h 1 = x × w h_1 = x \times w h1=x×w ∂ h 1 ∂ w = x , ∂ h 1 ∂ x = w \frac{\partial h_1}{\partial w} = x, \quad \frac{\partial h_1}{\partial x} = w ∂w∂h1=x,∂x∂h1=w h 1 = x × w = 0 h_1 = x \times w = 0 h1=x×w=0 h 2 = h 1 + b h_2 = h_1 + b h2=h1+b ∂ h 2 ∂ h 1 = 1 , ∂ h 2 ∂ b = 1 \frac{\partial h_2}{\partial h_1} = 1, \quad \frac{\partial h_2}{\partial b} = 1 ∂h1∂h2=1,∂b∂h2=1 h 2 = h 1 + b = 0 + 0 = 0 h_2 = h_1 + b = 0 + 0 = 0 h2=h1+b=0+0=0 h 3 = h 2 × − 1 h_3 = h_2 \times -1 h3=h2×−1 ∂ h 3 ∂ h 2 = − 1 \frac{\partial h_3}{\partial h_2} = -1 ∂h2∂h3=−1 h 3 = h 2 × − 1 = 0 × − 1 = 0 h_3 = h_2 \times -1=0 \times -1 = 0 h3=h2×−1=0×−1=0 h 4 = e x p ( h 3 ) h_4 = exp(h_3) h4=exp(h3) ∂ h 4 ∂ h 3 = e x p ( h 3 ) \frac{\partial h_4}{\partial h_3} = exp(h_3) ∂h3∂h4=exp(h3) h 4 = e x p ( h 3 ) = e x p ( 0 ) = 1 h_4 = exp(h_3) = exp(0)=1 h4=exp(h3)=exp(0)=1 h 5 = h 4 + 1 h_5 = h_4 + 1 h5=h4+1 ∂ h 5 ∂ h 4 = 1 \frac{\partial h_5}{\partial h_4} = 1 ∂h4∂h5=1 h 5 = h 4 + 1 = 1 + 1 = 2 h_5 = h_4 + 1 = 1 + 1 = 2 h5=h4+1=1+1=2 h 6 = 1 / h 5 h_6 = 1/h_5 h6=1/h5 ∂ h 6 ∂ h 5 = − 1 h 5 2 \frac{\partial h_6}{\partial h_5} = -\frac{1}{h^2_5} ∂h5∂h6=−h521 h 6 = 1 / h 5 = 1 / 2 = 0.5 h_6 = 1/h_5 = 1 / 2 = 0.5 h6=1/h5=1/2=0.5 2.2.2 链式求导
复合函数数学过程如下:
∂ f ( x ; w , b ) ∂ w = ∂ f ( x ; w , b ) ∂ h 6 ∂ h 6 ∂ h 5 ∂ h 5 ∂ h 4 ∂ h 4 ∂ h 3 ∂ h 3 ∂ h 2 ∂ h 2 ∂ h 1 ∂ h 1 ∂ w ∂ f ( x ; w , b ) ∂ b = ∂ f ( x ; w , b ) ∂ h 6 ∂ h 6 ∂ h 5 ∂ h 5 ∂ h 4 ∂ h 4 ∂ h 3 ∂ h 3 ∂ h 2 ∂ h 2 ∂ b \frac{\partial f(x;w,b)}{\partial w}=\frac{\partial f(x;w,b)}{\partial h_6}\frac{\partial h_6}{\partial h_5}\frac{\partial h_5}{\partial h_4}\frac{\partial h_4}{\partial h_3}\frac{\partial h_3}{\partial h_2}\frac{\partial h_2}{\partial h_1}\frac{\partial h_1}{\partial w} \\ \\ \frac{\partial f(x;w,b)}{\partial b}=\frac{\partial f(x;w,b)}{\partial h_6}\frac{\partial h_6}{\partial h_5}\frac{\partial h_5}{\partial h_4}\frac{\partial h_4}{\partial h_3}\frac{\partial h_3}{\partial h_2}\frac{\partial h_2}{\partial b} ∂w∂f(x;w,b)=∂h6∂f(x;w,b)∂h5∂h6∂h4∂h5∂h3∂h4∂h2∂h3∂h1∂h2∂w∂h1∂b∂f(x;w,b)=∂h6∂f(x;w,b)∂h5∂h6∂h4∂h5∂h3∂h4∂h2∂h3∂b∂h2
可以得到:
∂ f ( x ; w , b ) ∂ w ∣ x = 1 , w = 0 , b = 0 = ∂ f ( x ; w , b ) ∂ h 6 ∂ h 6 ∂ h 5 ∂ h 5 ∂ h 4 ∂ h 4 ∂ h 3 ∂ h 3 ∂ h 2 ∂ h 2 ∂ h 1 ∂ h 1 ∂ w = 1 × − 0.25 × 1 × 1 × − 1 × 1 × 1 = 0.25. \begin{aligned} \frac{\partial f(x;w,b)}{\partial w}|_{x=1,w=0,b=0}& =\frac{\partial f(x;w,b)}{\partial h_6}\frac{\partial h_6}{\partial h_5}\frac{\partial h_5}{\partial h_4}\frac{\partial h_4}{\partial h_3}\frac{\partial h_3}{\partial h_2}\frac{\partial h_2}{\partial h_1}\frac{\partial h_1}{\partial w} \\ &=1\times-0.25\times1\times1\times-1\times1\times1 \\ &=0.25. \end{aligned} ∂w∂f(x;w,b)∣x=1,w=0,b=0=∂h6∂f(x;w,b)∂h5∂h6∂h4∂h5∂h3∂h4∂h2∂h3∂h1∂h2∂w∂h1=1×−0.25×1×1×−1×1×1=0.25.
2.2.3 代码实现
我们通过代码来实现以上过程:
- import torch
- import torch.nn as nn
- def test006():
- x = torch.tensor(1.0)
- w = torch.tensor(0.0, requires_grad=True)
- b = torch.tensor(0.0, requires_grad=True)
- # 计算函数
- y = (torch.exp(-(w * x + b)) + 1) ** -1
- # 自动微分
- y.backward()
- # 梯度打印
- print(w.grad)
- if __name__ == "__main__":
- test006()
复制代码 打印结果:
2.4 重要性
反向传播算法极大地提高了多层神经网络练习的效率,使得练习深度模型成为可能。通过链式法则逐层盘算梯度,反向传播可以有用地处置惩罚复杂的网络结构,确保每一层的参数都能得到合理的调解。
2.5 案例助解
这里我们通过一个实际的案例,去理解反向传播整个过程~
2.5.1 数据预备
整体网络结构及神经元数据和权重参数如下图所示:
数据解释如下:
- i 1 = 0.05 , i 2 = 0.10 i_1=0.05, i_2=0.10 i1=0.05,i2=0.10 代表输入层输入数据的2个特征;
- w 1 = 0.15 , w 2 = 0.20 w_1=0.15, w_2=0.20 w1=0.15,w2=0.20代表的是输入数据映射到 h 1 h_1 h1的权重参数;
- w 3 = 0.25 , w 4 = 0.30 w_3=0.25, w_4=0.30 w3=0.25,w4=0.30代表的是输入数据映射到 h 2 h_2 h2的权重参数;
- b 1 = 0.35 , b 2 = 0.60 b1=0.35,b2=0.60 b1=0.35,b2=0.60分别代表输入层到隐蔽层、隐蔽层到输出层的偏执;
- w 5 = 0.40 , w 6 = 0.45 w_5=0.40, w_6=0.45 w5=0.40,w6=0.45代表的是隐蔽层的神经元映射到 o 1 o_1 o1的权重参数;
- w 7 = 0.50 , w 8 = 0.55 w_7=0.50, w_8=0.55 w7=0.50,w8=0.55代表的是隐蔽层的神经元映射到 o 2 o_2 o2的权重参数;
- o 1 o_1 o1下面标注的 0.01 0.01 0.01表示target为 0.01 0.01 0.01, o 2 o_2 o2下面标注的 0.99 0.99 0.99表示target为 0.99 0.99 0.99;
2.5.2 神经元盘算
以是,我们可以得到如下数据:
盘算h1的相干数据:
h 1 = w 1 ∗ i 1 + w 2 ∗ i 2 + b 1 = 0.15 ∗ 0.05 + 0.20 ∗ 0.10 + 0.35 = 0.3775 k 1 = s i g m o i d ( h 1 ) = s i g m o i d ( 0.3775 ) = 0.5933 \mathrm{h}_{1}=\mathrm{w}_{1}*\mathrm{i}_{1}+\mathrm{w}_{2}*\mathrm{i}_{2}+\mathrm{b}_{1}\quad =0.15 * 0.05 + 0.20 * 0.10 + 0.35 =0.3775 \\ k_{1}=sigmoid(h1)=sigmoid(0.3775)=0.5933 h1=w1∗i1+w2∗i2+b1=0.15∗0.05+0.20∗0.10+0.35=0.3775k1=sigmoid(h1)=sigmoid(0.3775)=0.5933
盘算h2的相干数据:
h 2 = w 3 ∗ i 1 + w 4 ∗ i 2 + b 1 = 0.25 ∗ 0.05 + 0.30 ∗ 0.10 + 0.35 = 0.3925 k 2 = s i g m o i d ( h 2 ) = s i g m o i d ( 0.3925 ) = 0.5969 \mathrm{h}_{2}=\mathrm{w}_{3}*\mathrm{i}_{1}+\mathrm{w}_{4}*\mathrm{i}_{2}+\mathrm{b}_{1}\quad =0.25 * 0.05 + 0.30 * 0.10 + 0.35 =0.3925 \\ k_{2}=sigmoid(h2)=sigmoid(0.3925)=0.5969 h2=w3∗i1+w4∗i2+b1=0.25∗0.05+0.30∗0.10+0.35=0.3925k2=sigmoid(h2)=sigmoid(0.3925)=0.5969
盘算o1的相干数据:
o 1 = w 5 ∗ k 1 + w 6 ∗ k 2 + b 2 = 0.40 ∗ 0.5933 + 0.45 ∗ 0.5969 + 0.60 = 1.1059 m 1 = s i g m o i d ( o 1 ) = s i g m o i d ( 1.1059 ) = 0.7514 \mathrm{o}_{1}=\mathrm{w}_{5}*\mathrm{k}_{1}+\mathrm{w}_{6}*\mathrm{k}_{2}+\mathrm{b}_{2}\quad =0.40 * 0.5933 + 0.45 * 0.5969 + 0.60 =1.1059 \\ m_{1}=sigmoid(o1)=sigmoid(1.1059)=0.7514 o1=w5∗k1+w6∗k2+b2=0.40∗0.5933+0.45∗0.5969+0.60=1.1059m1=sigmoid(o1)=sigmoid(1.1059)=0.7514
盘算o2的相干数据:
o 2 = w 7 ∗ k 1 + w 8 ∗ k 2 + b 2 = 0.50 ∗ 0.5933 + 0.55 ∗ 0.5969 + 0.60 = 1.2249 m 2 = s i g m o i d ( o 2 ) = s i g m o i d ( 1.2249 ) = 0.7729 \mathrm{o}_{2}=\mathrm{w}_{7}*\mathrm{k}_{1}+\mathrm{w}_{8}*\mathrm{k}_{2}+\mathrm{b}_{2}\quad =0.50 * 0.5933 + 0.55 * 0.5969 + 0.60 =1.2249 \\ m_{2}=sigmoid(o2)=sigmoid(1.2249)=0.7729 o2=w7∗k1+w8∗k2+b2=0.50∗0.5933+0.55∗0.5969+0.60=1.2249m2=sigmoid(o2)=sigmoid(1.2249)=0.7729
以是,最终的猜测结果分别为: 0.7514、0.7729
2.5.3 损失盘算
猜测值和真实值(target)举行比较盘算损失:
M S E L o s s = 1 2 ( ( m 1 − t a r g e t 1 ) 2 + ( ( m 2 − t a r g e t 2 ) 2 ) = 1 2 ( ( 0.7514 − 0.01 ) 2 + ( ( 0.7729 − 0.99 ) 2 ) = 0.2984 MSELoss = \frac{1}{2}((\mathrm{m}_{1}\mathrm{-target}_{1})^{2}+((\mathrm{m}_{2}\mathrm{-target}_{2})^{2}) \\ = \frac{1}{2}((0.7514-0.01)^{2}+((0.7729-0.99)^{2}) =0.2984 MSELoss=21((m1−target1)2+((m2−target2)2)=21((0.7514−0.01)2+((0.7729−0.99)2)=0.2984
得到损失是:0.2984
2.5.4 梯度盘算
接下来,我们举行梯度盘算和参数更新
盘算 w5 权重的梯度
∂ L ∂ w 5 = ∂ L ∂ o 1 1 ∗ ∂ m 1 ∂ o 1 ∗ ∂ o 1 ∂ w 5 = ( m 1 − t a r g e t 1 ) ∗ s i g m o i d ( o 1 ) ∗ ( 1 − s i g m o i d ( o 1 ) ) ∗ k 1 = ( 0.7514 − 0.01 ) ∗ s i g m o i d ( 1.1059 ) ∗ ( 1 − s i g m o i d ( 1.1059 ) ) ∗ 0.5933 = 0.0822 \begin{aligned} \frac{\partial\mathrm{L}}{\partial\mathrm{w}_{5}}& =\frac{\partial\mathrm{L}}{\partial\mathrm{o1}_{1}}*\frac{\partial\mathrm{m}_{1}}{\partial\mathrm{o}_{1}}*\frac{\partial\mathrm{o}_{1}}{\partial\mathrm{w}_{5}} \\ &=(\mathrm{m}_{1}-\mathrm{target}_{1})*\mathrm{sigmoid}(\mathrm{o}_{1})*\left(1-\mathrm{sigmoid}(\mathrm{o}_{1})\right)*\mathrm{k}_{1} \\ &=(0.7514-0.01)*sigmoid(1.1059)*\left(1-sigmoid(1.1059)\right)*0.5933 \\ &=0.0822 \end{aligned} ∂w5∂L=∂o11∂L∗∂o1∂m1∗∂w5∂o1=(m1−target1)∗sigmoid(o1)∗(1−sigmoid(o1))∗k1=(0.7514−0.01)∗sigmoid(1.1059)∗(1−sigmoid(1.1059))∗0.5933=0.0822
盘算 w7 权重的梯度
∂ L ∂ w 7 = ∂ L ∂ m 2 ∗ ∂ m 2 ∂ o 2 ∗ ∂ o 2 ∂ w 7 = ( m 2 − t a r g e t 2 ) ∗ s i g m o i d ( o 2 ) ∗ ( 1 − s i g m o i d ( o 2 ) ) ∗ k 1 = ( 0.7729 − 0.99 ) ∗ s i g m o i d ( 1.2249 ) ∗ ( 1 − s i g m o i d ( 1.2249 ) ) ∗ 0.5933 = − 0.0226 \begin{aligned} \frac{\partial\mathrm{L}}{\partial\mathrm{w}_7}& =\frac{\partial\mathrm{L}}{\partial\mathrm{m}_2}*\frac{\partial\mathrm{m}_2}{\partial\mathrm{o}_2}*\frac{\partial\mathrm{o}_2}{\partial\mathrm{w}_7} \\ &=(\mathrm{m}_{2}-\mathrm{target}_{2})*\mathrm{sigmoid}(\mathrm{o}_{2})*\left(1-\mathrm{sigmoid}(\mathrm{o}_{2})\right)*\mathrm{k}_{1} \\ &=(0.7729-0.99)*sigmoid(1.2249)*\left(1-sigmoid(1.2249)\right)*0.5933 \\ &=-0.0226 \end{aligned} ∂w7∂L=∂m2∂L∗∂o2∂m2∗∂w7∂o2=(m2−target2)∗sigmoid(o2)∗(1−sigmoid(o2))∗k1=(0.7729−0.99)∗sigmoid(1.2249)∗(1−sigmoid(1.2249))∗0.5933=−0.0226
盘算 w1 权重的梯度
2.5.5 参数更新
现在就可以举行权重更新了:假设学习率是0.5
w 5 = 0.40 − 0.5 ∗ 0.0822 = 0.3589 w 7 = 0.50 + 0.5 ∗ 0.0226 = 0.5113 w 1 = 0.15 − 0.5 ∗ 0.0004 = 0.1498 w_5=0.40-0.5*0.0822=0.3589 \\ w_7=0.50+0.5*0.0226=0.5113 \\ w_1=0.15-0.5*0.0004=0.1498 w5=0.40−0.5∗0.0822=0.3589w7=0.50+0.5∗0.0226=0.5113w1=0.15−0.5∗0.0004=0.1498
2.5.6 代码实现
参考代码如下:
- import torch
- import torch.nn as nn
- import torch.optim as optim
- class Net(nn.Module):
- def __init__(self):
- super(Net, self).__init__()
- self.linear1 = nn.Linear(2, 2)
- self.linear2 = nn.Linear(2, 2)
- # 网络参数初始化
- self.linear1.weight.data = torch.tensor([[0.15, 0.20], [0.25, 0.30]])
- self.linear2.weight.data = torch.tensor([[0.40, 0.45], [0.50, 0.55]])
- self.linear1.bias.data = torch.tensor([0.35, 0.35])
- self.linear2.bias.data = torch.tensor([0.60, 0.60])
- def forward(self, x):
- x = self.linear1(x)
- x = torch.sigmoid(x)
- x = self.linear2(x)
- x = torch.sigmoid(x)
- return x
- if __name__ == "__main__":
- inputs = torch.tensor([[0.05, 0.10]])
- target = torch.tensor([[0.01, 0.99]])
- # 获得网络输出值
- net = Net()
- output = net(inputs)
- # 计算误差
- loss = torch.sum((output - target) ** 2) / 2
- # 优化方法
- optimizer = optim.SGD(net.parameters(), lr=0.5)
- # 梯度清零
- optimizer.zero_grad()
- # 反向传播
- loss.backward()
- # 打印(w1-w8)观察w5、w7、w1 的梯度值是否与手动计算一致
- print(net.linear1.weight.grad.data)
- print(net.linear2.weight.grad.data)
- #更新梯度
- optimizer.step()
-
- # 打印更新后的网络参数
- print(net.state_dict())
复制代码 打印结果:
- tensor([[0.0004, 0.0009],
- [0.0005, 0.0010]])
- tensor([[ 0.0822, 0.0827],
- [-0.0226, -0.0227]])
- OrderedDict([('linear1.weight', tensor([[0.1498, 0.1996],
- [0.2498, 0.2995]])), ('linear1.bias', tensor([0.3456, 0.3450])), ('linear2.weight', tensor([[0.3589, 0.4087],
- [0.5113, 0.5614]])), ('linear2.bias', tensor([0.5308, 0.6190]))])
复制代码 另一种写法(常用)
- import torch
- import torch.nn as nn
- import torch.optim as optim
- import os
- class MyModel(nn.Module):
- def __init__(self, input_size, output_size):
- super(MyModel, self).__init__()
- # 定义网络结构
- # 输入层到隐藏层
- self.hidden=nn.Sequential(nn.Linear(input_size, 2),nn.Sigmoid())
- # 初始化隐藏层权重和偏置(默认自动,这里手动是为了测试案例中运算的数字)
- self.hidden[0].weight.data = torch.tensor([[0.15, 0.20], [0.25, 0.30]])
- self.hidden[0].bias.data = torch.tensor([0.35, 0.35])
- # 隐藏层到输出层
- self.out = nn.Sequential(nn.Linear(2, output_size),nn.Sigmoid())
- self.out[0].weight.data = torch.tensor([[0.40, 0.45], [0.50, 0.55]])
- self.out[0].bias.data = torch.tensor([0.60, 0.60])
- def forward(self, x):
- x = self.hidden(x)
- output = self.out(x)
- return output
- def train(epochs=10):
- # 模型
- model = MyModel(input_size=2, output_size=2)
- # 优化器
- optimizer = optim.SGD(model.parameters(), lr=0.5)
- #损失函数
- criterion = nn.MSELoss()
- # 输入数据
- input = torch.tensor([[0.05, 0.10]])
- target = torch.tensor([[0.01, 0.99]])
- #前向传播
- output = model(input)
- #计算损失
- loss = criterion(output, target)
- # 梯度清零
- optimizer.zero_grad()
- # 反向传播
- loss.backward()
- # 更新权重参数:让损失尽可能小
- optimizer.step()
- #更新后的模型参数
- state_dict=model.state_dict()
- print("更新后的模型参数:",state_dict)
- #保存模型参数
- filepath = os.path.relpath(os.path.join(os.path.dirname(__file__),"weights/model.pth"))
-
-
- def detect():
- # 加载模型参数
- filepath = os.path.relpath(os.path.join(os.path.dirname(__file__),"weights/model.pth"))
- model = MyModel(input_size=2, output_size=2)
- model.load_state_dict(torch.load(filepath))
- input = torch.tensor([[0.05, 0.10]])
- output = model(input)
- print("预测推理",output)
-
-
- if __name__=="__main__":
- train()
- detect()
复制代码 3. BP之梯度降落
梯度降落算法的目的是找到使损失函数 L ( θ ) L(\theta) L(θ) 最小的参数 θ \theta θ,其核心是沿着损失函数梯度的负方向更新参数,以逐步逼近局部或全局最优解,从而使模型更好地拟合练习数据。
3.1 数学描述
简单回顾下数学知识。
3.1.1 数学公式
w i j n e w = w i j o l d − α ∂ E ∂ w i j w_{ij}^{new}= w_{ij}^{old} - \alpha \frac{\partial E}{\partial w_{ij}} wijnew=wijold−α∂wij∂E
其中, α \alpha α是学习率:
- 学习率太小,每次练习之后的结果太小,增加时间和算力成本。
- 学习率太大,大概率会跳过最优解,进入无限的练习和震荡中。
- 解决的方法就是,学习率也需要随着练习的举行而变化。
3.1.2 过程论述
- 初始化参数:随机初始化模型的参数 θ \theta θ,如权重 W W W和偏置 b b b。
- 盘算梯度:损失函数 L ( θ ) L(\theta) L(θ)对参数 θ \theta θ 的梯度 ∇ θ L ( θ ) \nabla_\theta L(\theta) ∇θL(θ),表示损失函数在参数空间的变化率。
- 更新参数:按照梯度降落公式更新参数: θ : = θ − α ∇ θ L ( θ ) \theta := \theta - \alpha \nabla_\theta L(\theta) θ:=θ−α∇θL(θ),其中, α \alpha α 是学习率,用于控制更新步长。
- 迭代更新:重复【盘算梯度和更新参数】步骤,直到某个停止条件(如梯度靠近0、不再收敛、完成迭代次数等)。
3.2 传统降落方式
根据盘算梯度时数据量不同,常见的方式有:
3. 2.1 批量梯度降落
Batch Gradient Descent BGD
- 特点:
- 优点:
- 收敛稳定,能正确地沿着损失函数的真实梯度方向降落。
- 实用于小型数据集。
- 缺点:
- 对于大型数据集,盘算量巨大,更新速率慢。
- 需要大量内存来存储整个数据集。
- 公式:
θ : = θ − α 1 m ∑ i = 1 m ∇ θ L ( θ ; x ( i ) , y ( i ) ) \theta := \theta - \alpha \frac{1}{m} \sum_{i=1}^{m} \nabla_\theta L(\theta; x^{(i)}, y^{(i)}) θ:=θ−αm1i=1∑m∇θL(θ;x(i),y(i))
其中, m m m 是练习集样本总数, x ( i ) , y ( i ) x^{(i)}, y^{(i)} x(i),y(i)是第 i i i 个样本及其标签。
3.2.2 随机梯度降落
Stochastic Gradient Descent, SGD
- 特点:
- 优点:
- 更新频率高,盘算快,适合大规模数据集。
- 能够跳出局部最小值,有助于找到全局最优解。
- 缺点:
- 收敛不稳定,轻易震荡,由于每个样本的梯度可能都不完全代表整体方向。
- 需要较小的学习率来缓解震荡。
- 公式:
θ : = θ − α ∇ θ L ( θ ; x ( i ) , y ( i ) ) \theta := \theta - \alpha \nabla_\theta L(\theta; x^{(i)}, y^{(i)}) θ:=θ−α∇θL(θ;x(i),y(i))
其中, x ( i ) , y ( i ) x^{(i)}, y^{(i)} x(i),y(i) 是当前随机抽取的样本及其标签。
3.2.3 小批量梯度降落
Mini-batch Gradient Descent MGBD
- 特点:
- 每次更新参数时,利用一小部分练习集(小批量)来盘算梯度。
- 优点:
- 在盘算效率和收敛稳定性之间取得均衡。
- 能够利用向量化加速盘算,适合现代硬件(如GPU)。
- 缺点:
- 选择适当的批量大小比较困难;批量太小则靠近SGD,批量太大则靠近批量梯度降落。
- 通常会根据硬件算力设置为32\64\128\256等2的次方。
- 公式:
θ : = θ − α 1 b ∑ i = 1 b ∇ θ L ( θ ; x ( i ) , y ( i ) ) \theta := \theta - \alpha \frac{1}{b} \sum_{i=1}^{b} \nabla_\theta L(\theta; x^{(i)}, y^{(i)}) θ:=θ−αb1i=1∑b∇θL(θ;x(i),y(i))
其中, b b b 是小批量的样本数量,也就是 b a t c h _ s i z e batch\_size batch_size。
3.3 存在的问题
- 收敛速率慢:BGD和MBGD利用固定学习率,太大会导致震荡,太小又收敛缓慢。
- 局部最小值和鞍点问题:SGD在碰到局部最小值或鞍点时轻易停滞,导致模型难以到达全局最优。
- 练习不稳定:SGD中的噪声轻易导致练习过程中不稳定,使得练习陷入震荡或不收敛。
3.4 优化梯度降落方式
传统的梯度降落优化算法中,可能会碰到以下环境:
碰到平缓地区,梯度值较小,参数优化变慢 碰到 “鞍点” ,梯度为 0,参数无法优化 碰到局部最小值 对于这些问题, 出现了一些对梯度下
降算法的优化方法,例如:Momentum、AdaGrad、RMSprop、Adam 等.
3.4.1 指数加权平均
我们最常见的算数平均指的是将全部数加起来除以数的个数,每个数的权重是相同的。
加权平均指的是给每个数赋予不同的权重求得平均数。
移动平均数,指的是盘算近来邻的 N 个数来获得平均数。
指数移动加权平均(Exponential Moving Average简称EMA)则是参考各数值,而且各数值的权重都不同,间隔越远的数字对平均数盘算的贡献就越小(权重较小),间隔越近则对平均数的盘算贡献就越大(权重越大)。
比如:明气候温怎么样,和昨气候温有很大关系,而和一个月前的气温关系就小一些。
盘算公式可以用下面的式子来表示:
其中:
- St 表示指数加权平均值(EMA);
- Yt 表示 t 时候的值;
- β \beta β 是平滑系数,取值范围为 0 ≤ β < 1 0\leq \beta < 1 0≤β<1。 β \beta β 越靠近 1 1 1,表示对历史数据依赖性越高;越靠近 0 0 0 则越依赖当前数据。该值越大平均数越平缓
代码演示:
- import torch
- import matplotlib.pyplot as plt
- ELEMENT_NUMBER = 30
- # 1. 实际平均温度
- def test01():
- # 固定随机数种子
- torch.manual_seed(0)
- # 产生30天的随机温度
- temperature = torch.randn(size=[ELEMENT_NUMBER,]) * 10
- print(temperature)
- # 绘制平均温度
- days = torch.arange(1, ELEMENT_NUMBER + 1, 1)
- plt.plot(days, temperature, color='r')
- plt.scatter(days, temperature)
- plt.show()
- # 2. 指数加权平均温度
- def test02(beta=0.9):
- # 固定随机数种子
- torch.manual_seed(0)
- # 产生30天的随机温度
- temperature = torch.randn(size=[ELEMENT_NUMBER,]) * 10
- print(temperature)
- exp_weight_avg = []
- for idx, temp in enumerate(temperature):
- # 第一个元素的的 EWA 值等于自身
- if idx == 0:
- exp_weight_avg.append(temp)
- continue
- # 第二个元素的 EWA 值等于上一个 EWA 乘以 β + 当前气温乘以 (1-β)
- new_temp = exp_weight_avg[-1] * beta + (1 - beta) * temp
- exp_weight_avg.append(new_temp)
- days = torch.arange(1, ELEMENT_NUMBER + 1, 1)
- plt.plot(days, exp_weight_avg, color='r')
- plt.scatter(days, temperature)
- plt.show()
- if __name__ == '__main__':
- test01()
- test02(0.5)
- test02(0.9)
复制代码 执行结果:
3.4.2 Momentum
a.特点
动量(Momentum)是对梯度降落的优化方法,可以更好地应对梯度变化和梯度消散问题,从而提高练习模型的效率和稳定性。
- 惯性效应: 该方法参加前面梯度的累积,这种惯性使得算法沿着当前的方向继承更新。如碰到鞍点,也不会因梯度逼近零而停滞。
- 淘汰震荡: 该方法平滑了梯度更新,淘汰在鞍点附近的震荡,资助优化过程稳定向前推进。
- 加速收敛: 该方法在优化过程中持续沿着某个方向前进,能够更快地穿越鞍点地区,避免在鞍点附近长时间停顿。
b.梯度盘算公式
梯度盘算公式:Dt = β * St-1 + (1- β) * Dt
- St-1 表示历史梯度移动加权平均值
- wt 表示当前时候的梯度值
- β 为权重系数
举个例子,假设:权重 β 为 0.9,例如:
- 第一次梯度值:s1 = d1 = w1
- 第二次梯度值:s2 = 0.9 + s1 + d2 * 0.1
- 第三次梯度值:s3 = 0.9 * s2 + d3 * 0.1
- 第四次梯度值:s4 = 0.9 * s3 + d4 * 0.1
- - w 表示初始梯度
- - d 表示当前轮数计算出的梯度值
- - s 表示历史梯度值
复制代码 梯度降落公式中梯度的盘算,就不再是当前时候 t 的梯度值,而是历史梯度值的指数移动加权平均值。
公式修改为: W t + 1 = W t − α ∗ D t W_{t+1}=W_t-α*D_t Wt+1=Wt−α∗Dt
c.原理
那么,Monmentum 优化方法是如何肯定程度上克服 “平缓”、”鞍点”、”峡谷” 的问题呢?
当处于鞍点位置时,由于当前的梯度为 0,参数无法更新。但是 Momentum 动量梯度降落算法已经在先前积聚了一些梯度值,很有可能使得跨过鞍点。
由于 mini-batch 平凡的梯度降落算法,每次选取少数的样本梯度确定前进方向,可能会出现震荡,使得练习时间变长。Momentum 利用移动加权平均,平滑了梯度的变化,使得前进方向更加平缓,有利于加速练习过程。肯定程度上有利于低落 “峡谷” 问题的影响。
峡谷问题:就是会使得参数更新出现剧烈震荡.
Momentum 算法可以理解为是对梯度值的一种调解,我们知道梯度降落算法中还有一个很重要的学习率,Momentum 并没有学习率举行优化。
d.API
- optimizer = optim.SGD(model.parameters(), lr=0.6, momentum=0.9) # 学习率和动量值可以根据实际情况调整,momentum 参数指定了动量系数,默认为0。动量系数通常设置为 0 到0.5 之间的一个值,但也可以根据具体的应用场景调整
复制代码 e.总结:
- 动量项更新:利用当前梯度和历史动量来盘算新的动量项。
- 权重参数更新:利用更新后的动量项来调解权重参数。
- 梯度盘算:在每个时间步盘算当前的梯度,用于更新动量项和权重参数。
Momentum 算法是对梯度值的平滑调解,但是并没有对梯度降落中的学习率举行优化。
3.4.3 AdaGrad
AdaGrad(Adaptive Gradient Algorithm)为每个参数引入独立的学习率,它根据历史梯度的平方和来调解这些学习率,如许就使得参数具有较大的历史梯度的学习率减小,而参数具有较小的历史梯度的学习率保持较大,从而实现更有用的学习。AdaGrad避免了统一学习率的不敷,更多用于处置惩罚希奇数据和梯度变化较大的问题。
AdaGrad流程:
- 初始化学习率 α、初始化参数 θ、小常数 σ = 1e-6
- 初始化梯度累积变量 s = 0
- 从练习集中采样 m 个样本的小批量,盘算梯度 g
- 累积平方梯度 s = s + g ⊙ g,⊙ 表示各个分量相乘
- 学习率 α 的盘算公式如下:
- 参数更新公式如下:
- 其中:
- α \alpha α 是全局的初始学习率。
- σ \sigma σ 是一个非常小的常数,用于避免除零操作(通常取 1 0 − 8 10^{-8} 10−8)。
- α s + σ \frac{\alpha}{\sqrt{s }+\sigma} s +σα 是自适应调解后的学习率。
优点:
- 自适应学习率:由于每个参数的学习率是基于其梯度的累积平方和 来动态调解的,这意味着学习率会随着时间步的增加而淘汰,对梯度较大且变化频仍的方向非常有用,防止了梯度过大导致的震荡。
- 适合希奇数据:AdaGrad 在处置惩罚希奇数据时表现很好,由于它能够自适应地为那些较少更新的参数保持较大的学习率。
缺点:
- 学习率过分衰减:随着时间的推移,累积的时间步梯度平方值越来越大,导致学习率逐渐靠近零,模型会停止学习。
- 不适合非希奇数据:在非希奇数据的环境下,学习率过快衰减可能导致优化过程早期停滞。
AdaGrad是一种有用的自适应学习率算法,然而由于学习率衰减问题,我们会利用改 RMSProp 或 Adam 来替换。
API
- optimizer = optim.Adagrad(model.parameters(), lr=0.9) # 设置学习率
复制代码 3.4.4 RMSProp
RMSProp(Root Mean Square Propagation)在时间步中,不是简单地累积全部梯度平方和,而是利用指数加权平均来逐步衰减过期的梯度信息。这种方法专门用于解决AdaGrad在练习过程中学习率过分衰减的问题。
RMSProp过程
- 初始化学习率 α、初始化参数 θ、小常数 σ = 1e-8( 用于防止除零操作(通常取 1 0 − 8 10^{-8} 10−8))。
- 初始化参数 θ
- 初始化梯度累计变量 s=0
- 从练习集中采样 m 个样本的小批量,盘算梯度 g
- 利用指数移动平均累积历史梯度,公式如下:
- 学习率 α 的盘算公式如下:
- 参数更新公式如下:
优点
- 适应性强:RMSProp自适应调解每个参数的学习率,对于梯度变化较大的环境非常有用,使得优化过程更加平稳。
- 适合非希奇数据:相比于AdaGrad,RMSProp更加适合处置惩罚非希奇数据,由于它不会让学习率减小到险些为零。
- 解决过分衰减问题:通过引入指数加权平均,RMSProp避免了AdaGrad中学习率过快衰减的问题,保持了学习率的稳定性
缺点
- 依赖于超参数的选择:RMSProp的结果对衰减率 β \beta β 和学习率 α \alpha α 的选择比较敏感,需要一些调参工作。
需要注意的是:AdaGrad 和 RMSProp 都是对于不同的参数分量利用不同的学习率,如果某个参数分量的梯度值较大,则对应的学习率就会较小,如果某个参数分量的梯度较小,则对应的学习率就会较大一些
API
- optimizer = optim.RMSprop(model.parameters(), lr=0.7, momentum=0.9) # 设置学习率和动量
复制代码 3.4.5 Adam
Adam(Adaptive Moment Estimation)算法将动量法和RMSProp的优点结合在一起:
- 动量法:通过一阶动量(即梯度的指数加权平均)来加速收敛,尤其是在有噪声或梯度希奇的环境下。
- RMSProp:通过二阶动量(即梯度平方的指数加权平均)来调解学习率,使得每个参数的学习率适应其梯度的变化。
- Momentum 利用指数加权平均盘算当前的梯度值、AdaGrad、RMSProp 利用自适应的学习率,Adam 结合了 Momentum、RMSProp 的优点,利用:移动加权平均的梯度和移动加权平均的学习率。使得能够自适应学习率的同时,也能够利用 Momentum 的优点。
优点
- 高效妥当:Adam结合了动量法和RMSProp的上风,在处置惩罚非静态、希奇梯度和噪声数据时表现出色,能够快速稳定地收敛。
- 自适应学习率:Adam通过一阶和二阶动量的估计,自适应调解每个参数的学习率,避免了全局学习率设定不符合的问题。
- 实用大多数问题:Adam险些可以在不调解超参数的环境下应用于各种深度学习模型,表现良好。
缺点
- 超参数敏感:只管Adam通常能很好地工作,但它对初始超参数(如 β 1 \beta_1 β1、 β 2 \beta_2 β2 和 η \eta η)仍然较为敏感,有时需要仔细调参。
- 过拟合风险:由于Adam会在初始阶段快速收敛,可能导致模型陷入局部最优甚至过拟合。因此,有时会结合其他优化算法(如SGD)利用。
API
- optimizer = optim.Adam(model.parameters(), lr=0.05) # 设置学习率
复制代码 3.5 总结
梯度降落算法通过不停更新参数来最小化损失函数,是反向传播算法中盘算权重调解的基础。在实际应用中,根据数据的规模和盘算资源的环境,选择符合的梯度降落方式(批量、随机、小批量)及其变种(如动量法、Adam等)可以显著提高模型练习的效率和结果。
Adam是目前最为流行的优化算法之一,因其稳定性和高效性,广泛应用于各种深度学习模型的练习中。Adam结合了动量法和RMSProp的优点,能够在不同环境下自适应调解学习率,并提供快速且稳定的收敛表现。
[code][/code]
免责声明:如果侵犯了您的权益,请联系站长,我们会及时删除侵权内容,谢谢合作!更多信息从访问主页:qidao123.com:ToB企服之家,中国第一个企服评测及商务社交产业平台。 |