或许大家都习惯了使用Adam和AdamW,但是我们真的有了解这些优化器的内部原理吗?
优化器的基本思想是梯度下降法,梯度下降法的公式非常简单,
故能修改的地方只有学习率和梯度值,由此催生出3类优化器:
- 1 修改梯度:SGD
- 2 修改学习率:Adagrad,RMSProp
- 3 修改梯度和学习率:Adam,AdamW
详细进化表如下:
1 梯度下降法
1.1 基本思想
先设定一个学习率$\iota$,参数沿梯度的反方向移动。假设需要更新的参数为$\omega$,梯度为g,更新策略为:
$$ \omega \gets \omega -\iota *g $$
1.2 梯度下降法的三种形式
- BGD:批量梯度下降,每次参数更新使用全部的样本
- SGD:随机梯度下降,每次参数更新使用一个样本
- MBGD:小批量梯度下降,每次参数更新使用一部分样本
参数优化步骤:求梯度 → 求梯度平均值 → 更新权重
1.3 优缺点
优点:
- 算法简介,当学习率合适的时候,可以收敛到全局最优(凸函数)
缺点:
- 1 对超参数比较敏感;过小导致收敛速度慢,过大导致越过极值点
- 2 学习率容易在迭代过程中保持不变,被卡在按点
- 3 容易陷入局部极小值
1.4 一维梯度下降法
以目标函数$f(x) = x^2$为例子,看梯度下降法如何工作。
import numpy as np
import matplotlib.pyplot as plt
lrs = [0.05, 0.1, 0.2, 0.4, 0.6, 0.8, 1.0, 1.2]
result = []
for lr in lrs:
x = 10
temp = [x]
for i in range(10):
x -= lr * 2 * x
temp.append(x)
result.append(temp)
fx = np.arange(-10, 10, 0.1)
fig, ax = plt.subplots(2, 4, figsize=(20, 8))
for i in range(2):
for j in range(4):
ax[i][j].plot(fx, [x*x for x in fx])
ax[i][j].plot(result[2*i+j], [x*x for x in result[2*i+j]], "-o")
ax[i][j].set_title(f"learning rate is {lrs[2*i + j]}")
ax[i][j].set_xlabel("x")
ax[i][j].set_ylabel("f(x)")
plt.show()
1.5 多维梯度下降法
以目标函数$f(x) = x_{1}^2+2x_{2}^2$为例子,使用梯度下降法,观察x1,x2如何从初始位置[-5, -2]的更新轨迹。
import numpy as np
import matplotlib.pyplot as plt
def loss_func(x1, x2):
return x1**2 + 2*x2**2
x1, x2 = -5, -2
eta = 0.4
num_epochs = 20
result = [(x1, x2)]
for epoch in range(num_epochs):
grad_1 = 2 * x1
grad_2 = 4 * x2
x1 -= eta * grad_1
x2 -= eta * grad_2
result.append((x1, x2))
plt.figure(figsize=(8, 4))
plt.plot(*zip(*result), "-o")
x1, x2 = np.meshgrid(np.arange(-5.5, 1.0, 0.1), np.arange(-3.0, 1.0, 0.1))
C = plt.contour(x1, x2, loss_func(x1, x2), colors="red")
plt.clabel(C, inline=True, fontsize=10)
# ax1 = plt.axes(projection='3d')
# ax1.scatter3D(x1, x2, loss_func(x1, x2), cmap='Blues')
plt.show()
2 动量
2.1 基本思想
让参数的更新具有惯性,每一步更新都是由前面梯度的累计v和当前梯度g组合而成。
2.2 优缺点
优点:使用梯度的惯性使节点能突破极值点。
import numpy as np
import matplotlib.pyplot as plt
def loss_func(x1, x2):
return x1**2 + 2*x2**2
x1, x2 = -5, -2
v1, v2 = 0, 0
eta, alpha = 0.4, 0.5
num_epochs = 20
result = [(x1, x2)]
for epoch in range(num_epochs):
grad_1 = 2 * x1
grad_2 = 4 * x2
v1 = alpha * v1 + (1-alpha) * grad_1
v2 = alpha * v2 + (1-alpha) * grad_2
x1 -= eta * v1
x2 -= eta * v2
result.append((x1, x2))
plt.figure(figsize=(8, 4))
plt.plot(*zip(*result), "-o")
x1, x2 = np.meshgrid(np.arange(-5.5, 1.0, 0.1), np.arange(-3.0, 1.0, 0.1))
C = plt.contour(x1, x2, loss_func(x1, x2), colors="red")
plt.clabel(C, inline=True, fontsize=10)
# ax1 = plt.axes(projection='3d')
# ax1.scatter3D(x1, x2, loss_func(x1, x2), cmap='Blues')
plt.show()
3 Adagrad
3.1 基本思想
自适应学习率优化算法
思想:随机梯度下降法,针对所有的参数,都是使用相同的固定的学习率进行优化,但是不同的采纳数的梯度差异可能很大,使用相同的学习率效果不会很好,针对不同的参数,设置不同的学习率。
3.2 优缺点
import numpy as np
import matplotlib.pyplot as plt
def loss_func(x1, x2):
return x1**2 + 2*x2**2
x1, x2 = -5, -2
r1, r2 = 0, 0
eta = 0.4
num_epochs = 20
result = [(x1, x2)]
for epoch in range(num_epochs):
grad_1 = 2 * x1
grad_2 = 4 * x2
r1 = r1 + grad_1**2
r2 = r2 + grad_2**2
x1 -= (eta / np.sqrt(r1 + 1e-10)) * grad_1
x2 -= (eta / np.sqrt(r2 + 1e-10))* ( grad_2)
print(f"x1 lr ", (eta / np.sqrt(r1 + 1e-10)))
print(f"x2 lr ", (eta / np.sqrt(r2 + 1e-10)))
result.append((x1, x2))
plt.figure(figsize=(8, 4))
plt.plot(*zip(*result), "-o")
x1, x2 = np.meshgrid(np.arange(-5.5, 1.0, 0.1), np.arange(-3.0, 1.0, 0.1))
C = plt.contour(x1, x2, loss_func(x1, x2), colors="red")
plt.clabel(C, inline=True, fontsize=10)
# ax1 = plt.axes(projection='3d')
# ax1.scatter3D(x1, x2, loss_func(x1, x2), cmap='Blues')
plt.show()
4 RMSProp
import numpy as np
import matplotlib.pyplot as plt
def loss_func(x1, x2):
return x1**2 + 2*x2**2
x1, x2 = -5, -2
r1, r2 = 0, 0
eta, alpha = 0.4, 0.5
num_epochs = 20
result = [(x1, x2)]
for epoch in range(num_epochs):
grad_1 = 2 * x1
grad_2 = 4 * x2
r1 = alpha * r1 + (1 - alpha) * grad_1**2
r2 = alpha * r2 + (1 - alpha) * grad_2**2
x1 -= (eta / np.sqrt(r1 + 1e-10)) * grad_1
x2 -= (eta / np.sqrt(r2 + 1e-10))* ( grad_2)
# print(f"x1 lr ", (eta / np.sqrt(r1 + 1e-10)))
# print(f"x2 lr ", (eta / np.sqrt(r2 + 1e-10)))
result.append((x1, x2))
plt.figure(figsize=(8, 4))
plt.plot(*zip(*result), "-o")
x1, x2 = np.meshgrid(np.arange(-5.5, 1.0, 0.1), np.arange(-3.0, 1.0, 0.1))
C = plt.contour(x1, x2, loss_func(x1, x2), colors="red")
plt.clabel(C, inline=True, fontsize=10)
# ax1 = plt.axes(projection='3d')
# ax1.scatter3D(x1, x2, loss_func(x1, x2), cmap='Blues')
plt.show()
5 Adam
import numpy as np
import matplotlib.pyplot as plt
def loss_func(x1, x2):
return x1**2 + 2*x2**2
x1, x2 = -5, -2
r1, r2 = 0, 0
eta, alpha = 0.4, 0.5
num_epochs = 20
result = [(x1, x2)]
for epoch in range(num_epochs):
grad_1 = 2 * x1
grad_2 = 4 * x2
r1 = alpha * r1 + (1 - alpha) * grad_1**2
r2 = alpha * r2 + (1 - alpha) * grad_2**2
x1 -= (eta / np.sqrt(r1 + 1e-10)) * grad_1
x2 -= (eta / np.sqrt(r2 + 1e-10))* ( grad_2)
# print(f"x1 lr ", (eta / np.sqrt(r1 + 1e-10)))
# print(f"x2 lr ", (eta / np.sqrt(r2 + 1e-10)))
result.append((x1, x2))
plt.figure(figsize=(8, 4))
plt.plot(*zip(*result), "-o")
x1, x2 = np.meshgrid(np.arange(-5.5, 1.0, 0.1), np.arange(-3.0, 1.0, 0.1))
C = plt.contour(x1, x2, loss_func(x1, x2), colors="red")
plt.clabel(C, inline=True, fontsize=10)
# ax1 = plt.axes(projection='3d')
# ax1.scatter3D(x1, x2, loss_func(x1, x2), cmap='Blues')
plt.show()