系列文章:
从零开始学习深度学习库-1:前馈网络
从零开始学习深度学习库-2:反向传播
欢迎来到本系列的第三部分,在这里,我们将从零开始构建一个深度学习库。
在这篇文章中,我们将向我们的库中添加更多的优化函数和损失函数。
这里是这个系列的Github仓库:https://github.com/ashwins-code
优化函数
优化函数的目标是调整网络参数以最小化神经网络的损失。
它通过计算参数相对于损失的梯度,并使用这个梯度来更新参数来实现这一点。
不同的损失函数以不同的方式使用梯度,这会加速训练过程!
如果我们将损失函数绘制成图形,如上图所示,优化器的目标是改变神经网络的参数,以产生最小的损失值,即图中最低的凹陷处。
优化器在训练过程中的路径由黑色球体表示。
动量
动量是一种优化函数,它扩展了梯度下降算法(我们在上一篇文章中讨论过)。
它旨在加速训练过程,这意味着它可以在更少的迭代中最小化损失。如果我们考虑我们的“黑球”,动量会使这个黑球快速加速朝向最低点,就像从山顶上滚下一个球。
动量累积了在前几个迭代中计算出的梯度,这有助于它确定为了最小化损失而应该前进的方向。
它用于更新参数的公式如下
d
t
=
β
⋅
d
t
−
1
+
l
⋅
g
p
t
+
1
=
p
t
−
d
t
d_t = \beta \cdot d_{t-1} + l \cdot g \\ p_{t+1} = p_t - d_t
dt=β⋅dt−1+l⋅gpt+1=pt−dt
p
t
p_t
pt 是在时刻
t
t
t 的参数值
d
t
d_t
dt 是在时刻
t
t
t 需要前进的“方向”,由之前时刻的梯度计算得出。它初始化为0。
l
l
l 是学习率
β
\beta
β 是一个预先确定的值(通常选择为0.9)
g
g
g 是参数相对于该损失的梯度
这里是我们对这个优化器的Python实现
#optim.py
#...
class Momentum:
def __init__(self, lr = 0.01, beta=0.9):
self.lr = lr
self.beta = beta
def momentum_average(self, prev, grad):
return (self.beta * prev) + (self.lr * grad)
def __call__(self, model, loss):
grad = loss.backward()
for layer in tqdm.tqdm(model.layers[::-1]):
grad = layer.backward(grad)
if isinstance(layer, layers.Layer):
if not hasattr(layer, "momentum"):
layer.momentum = {
"w": 0,
"b": 0
}
layer.momentum["w"] = self.momentum_average(layer.momentum["w"], layer.w_gradient)
layer.momentum["b"] = self.momentum_average(layer.momentum["b"], layer.b_gradient)
layer.w -= layer.momentum["w"]
layer.b -= layer.momentum["b"]
Adam
Adam(Adaptive Moment Estimation)是一种用于深度学习中的梯度下降优化算法,它被广泛用于更新神经网络的权重。Adam结合了两种扩展的梯度下降算法:Momentum 和 RMSProp。
在Momentum算法中,引入了概念“动量”,可以看作是物理中的惯性,让优化算法能够在相关方向上加速学习,并且能够减少震荡。
RMSProp(Root Mean Square Propagation)算法则是一种适应性学习率方法,它会调整每个参数的学习率,通过使用参数最近梯度的平方的移动平均来做。
Adam算法结合了这两个算法的优点:
1.它计算每个参数的梯度的指数移动平均,类似于Momentum中的做法,这帮助算法在最优解方向上加速。
2.它也计算了这些梯度的平方的指数移动平均,类似于RMSProp中的做法,这使得算法能够适应每个参数的响应能力。
Adam 还包括了偏置修正,这是为了在算法的初始阶段对梯度的估计和梯度平方的估计进行校准,使得它们更加准确。
由于这些特点,Adam通常被认为是一个效果好且计算效率高的优化算法,尤其适用于大规模和高维度的优化问题。Adam的另一个优势是它的超参数通常需要的调整较少,它的默认值往往就能在很多问题上表现良好。
这里是更新方程式…
v
t
=
β
1
⋅
v
t
−
1
+
(
1
−
β
1
)
⋅
g
s
t
=
β
2
⋅
s
t
−
1
+
(
1
−
β
2
)
⋅
g
2
Δ
p
t
=
l
⋅
v
t
s
t
+
ϵ
p
t
+
1
=
p
t
−
Δ
p
t
v_t = \beta_1 \cdot v_{t-1} + (1 - \beta_1) \cdot g \\ s_t = \beta_2 \cdot s_{t-1} + (1 - \beta_2) \cdot g^2 \\ \Delta p_t = l \cdot \frac{v_t}{\sqrt{s_t} + \epsilon} \\ p_{t+1} = p_t - \Delta p_t
vt=β1⋅vt−1+(1−β1)⋅gst=β2⋅st−1+(1−β2)⋅g2Δpt=l⋅st+ϵvtpt+1=pt−Δpt
p
t
p_t
pt 是在时刻
t
t
t 的参数值
v
t
v_t
vt 是先前梯度的指数移动平均值。它初始化为0。
s
t
s_t
st 是先前梯度的指数移动平方平均值。它初始化为0。
l
l
l 是学习率
β
1
\beta_1
β1 是一个预先确定的值(通常选择为0.9)
β
2
\beta_2
β2 是一个预先确定的值(通常选择为0.999)
g
g
g 是参数相对于该损失的梯度
ϵ
\epsilon
ϵ 是一个预先确定的值,用来避免除以0。通常设为
1
0
−
10
10^{-10}
10−10
这是我们的Python实现:
#optim.py
#...
class Adam:
def __init__(self, lr = 0.01, beta1=0.9, beta2=0.999, epsilon=10**-8):
self.lr = lr
self.beta1 = beta1
self.beta2 = beta2
self.epsilon = epsilon
def rms_average(self, prev, grad):
return (self.beta2 * prev) + (1 - self.beta2) * (grad ** 2)
def momentum_average(self, prev, grad):
return (self.beta1 * prev) + ((1 - self.beta1) * grad)
def __call__(self, model, loss):
grad = loss.backward()
for layer in tqdm.tqdm(model.layers[::-1]):
grad = layer.backward(grad)
if isinstance(layer, layers.Layer):
if not hasattr(layer, "adam"):
layer.adam = {
"w": 0,
"b": 0,
"w2": 0,
"b2": 0
}
layer.adam["w"] = self.momentum_average(layer.adam["w"], layer.w_gradient)
layer.adam["b"] = self.momentum_average(layer.adam["b"], layer.b_gradient)
layer.adam["w2"] = self.rms_average(layer.adam["w2"], layer.w_gradient)
layer.adam["b2"] = self.rms_average(layer.adam["b2"], layer.b_gradient)
w_adjust = layer.adam["w"] / (1 - self.beta1)
b_adjust = layer.adam["b"] / (1 - self.beta1)
w2_adjust = layer.adam["w2"] / (1 - self.beta2)
b2_adjust = layer.adam["b2"] / (1 - self.beta2)
layer.w -= self.lr * (w_adjust / np.sqrt(w2_adjust) + self.epsilon)
layer.b -= self.lr * (b_adjust / np.sqrt(b2_adjust) + self.epsilon)
使用我们的新优化器!
这就是我们如何在我们的库中使用新优化器来训练模型,解决我们在上一篇文章中描述的相同问题(异或门)。
import layers
import loss
import optim
import numpy as np
x = np.array([[0, 1], [0, 0], [1, 1], [1, 0]])
y = np.array([[0, 1], [1, 0], [1, 0], [0, 1]])
net = layers.Model([
layers.Linear(8),
layers.Linear(4),
layers.Sigmoid(),
layers.Linear(2),
layers.Softmax()
])
net.train(x, y, optim=optim.RMSProp(lr=0.02), loss=loss.MSE(), epochs=200)
print (net(x))
epoch 190 loss 0.00013359948998165245
100%|████████████████████████████████████████████████████████████████████████████████████████████| 5/5 [00:00<?, ?it/s]
epoch 191 loss 0.00012832321751534635
100%|████████████████████████████████████████████████████████████████████████████████████████████| 5/5 [00:00<?, ?it/s]
epoch 192 loss 0.0001232564322705172
100%|████████████████████████████████████████████████████████████████████████████████████████████| 5/5 [00:00<?, ?it/s]
epoch 193 loss 0.00011839076882215646
100%|██████████████████████████████████████████████████████████████████████████████████| 5/5 [00:00<00:00, 5018.31it/s]
epoch 194 loss 0.00011371819900553786
100%|████████████████████████████████████████████████████████████████████████████████████████████| 5/5 [00:00<?, ?it/s]
epoch 195 loss 0.00010923101808808603
100%|████████████████████████████████████████████████████████████████████████████████████████████| 5/5 [00:00<?, ?it/s]
epoch 196 loss 0.00010492183152425807
100%|████████████████████████████████████████████████████████████████████████████████████████████| 5/5 [00:00<?, ?it/s]
epoch 197 loss 0.00010078354226798005
100%|████████████████████████████████████████████████████████████████████████████████████████████| 5/5 [00:00<?, ?it/s]
epoch 198 loss 9.680933861835338e-05
100%|████████████████████████████████████████████████████████████████████████████████████████████| 5/5 [00:00<?, ?it/s]
epoch 199 loss 9.299268257548828e-05
100%|████████████████████████████████████████████████████████████████████████████████████████████| 5/5 [00:00<?, ?it/s]
epoch 200 loss 8.932729868441197e-05
[[0.00832775 0.99167225]
[0.98903246 0.01096754]
[0.99082742 0.00917258]
[0.00833392 0.99166608]]
正如你所看到的,与上一篇文章相比,由于我们的新优化器,我们的模型训练得更好了!
感谢阅读!在下一篇文章中,我们将把目前为止的库应用到一个更高级的问题上(手写数字识别!)