文章目录
- Python 梯度下降法(六):Nadam Optimize
- 一、数学原理
- 1.1 介绍
- 1.2 符号定义
- 1.3 实现流程
- 二、代码实现
- 2.1 函数代码
- 2.2 总代码
- 三、优缺点
- 3.1 优点
- 3.2 缺点
- 四、相关链接
Python 梯度下降法(六):Nadam Optimize
一、数学原理
1.1 介绍
Nadam(Nesterov-accelerated Adaptive Moment Estimation)优化算法是 Adam 优化算法的改进版本,结合了 Nesterov 动量(Nesterov Momentum)和 Adam 算法的优点。
Nadam 在 Adam 算法的基础上引入了 Nesterov 动量的思想。Adam 算法通过计算梯度的一阶矩估计(均值)和二阶矩估计(未中心化的方差)来自适应地调整每个参数的学习率。而 Nesterov 动量则是在计算梯度时,考虑了参数在动量作用下未来可能到达的位置的梯度,从而让优化过程更具前瞻性。
1.2 符号定义
设置一下超参数:
参数 | 说明 |
---|---|
η \eta η | 学习率,控制参数更新的步长 |
m m m | 一阶矩估计,梯度均值 |
β 1 \beta_{1} β1 | 一阶矩指数衰减率,通常取 0.9 0.9 0.9 |
v v v | 二阶矩估计,梯度未中心化方差 |
β 2 \beta_{2} β2 | 二阶矩指数衰减率,通常取 0.999 0.999 0.999 |
ϵ \epsilon ϵ | 无穷小量,用于避免分母为零, 1 0 − 8 10^{-8} 10−8 |
g t g_{t} gt | 在 t t t时刻位置的梯度 |
θ \theta θ | 需要进行拟合的参数 |
1.3 实现流程
- 初始化参数: θ n × 1 \theta_{n\times 1} θn×1、 m 0 ⃗ n × 1 = 0 \vec{m_{0}}_{n\times 1}=0 m0n×1=0、 v 0 ⃗ n × 1 = 0 \vec{v_{0}}_{n\times 1}=0 v0n×1=0
- 更新一阶矩估计 m t m_{t} mt: m t = β 1 m t − 1 + ( 1 − β 1 ) g t m_{t}=\beta_{1}m_{t-1}+(1-\beta_{1})g_{t} mt=β1mt−1+(1−β1)gt
- 更新二阶矩估计 v t v_{t} vt: v t = β 2 v t − 1 + ( 1 − β 2 ) g t 2 v_{t}=\beta_{2}v_{t-1}+(1-\beta_{2})g_{t}^{2} vt=β2vt−1+(1−β2)gt2
- 偏差修正:由于 m 0 , v 0 = 0 m_{0},v_{0}=0 m0,v0=0,在训练初期会存在偏差,需要进行修正: m ^ t = m t 1 − β 1 t , v ^ t = v t 1 − β 2 t \hat{m}_{t}=\frac{m_{t}}{1-\beta_{1}^{t}},\hat{v}_{t}=\frac{v_{t}}{1-\beta_{2}^{t}} m^t=1−β1tmt,v^t=1−β2tvt
- 计算预估一阶矩: m ~ t = β 1 m ^ t + ( 1 − β 1 ) g t 1 − β 1 t \widetilde{m}_{t}=\beta_{1}\hat{m}_{t}+\frac{(1-\beta_{1})g_{t}}{1-\beta_{1}^{t}} m t=β1m^t+1−β1t(1−β1)gt
- 更新模型参数 θ t \theta_{t} θt: θ t = θ t − 1 − η v t ^ + ϵ ⊙ m ~ t \theta_{t}=\theta_{t-1}-\frac{\eta}{\sqrt{ \hat{v_{t}} }+\epsilon}\odot\widetilde{m}_{t} θt=θt−1−vt^+ϵη⊙m t
二、代码实现
2.1 函数代码
python"># 定义 Nadam 函数
def nadam_optimizer(X, y, eta, num_iter=1000, beta1=0.9, beta2=0.999, epsilon=1e-8, threshold=1e-8):
"""
X: 数据 x mxn,可以在传入数据之前进行数据的归一化
y: 数据 y mx1
eta: 学习率
num_iter: 迭代次数
beta: 衰减率
epsilon: 无穷小
threshold: 阈值
"""
m, n = X.shape
theta, mt, vt = np.random.randn(n, 1), np.zeros((n, 1)), np.zeros((n, 1)) # 初始化数据
loss_ = []
for t in range(1, num_iter + 1):
# 计算梯度
h = X.dot(theta)
err = h - y
loss_.append(np.mean(err ** 2) / 2)
g = (1 / m) * X.T.dot(err)
# 一阶矩估计
mt = beta1 * mt + (1 - beta1) * g
# 二阶矩估计
vt = beta2 * vt + (1 - beta2) * g ** 2
# 先计算偏差修正,后面需要使用到,并且去除负数
m_hat, v_hat = mt / (1 - pow(beta1, t)), np.maximum(vt / (1 - pow(beta2, t)), 0)
# 计算预估一阶矩
m_pre = beta1 * m_hat + (1 - beta1) * g / (1 - pow(beta1, t))
# 更新参数
theta = theta - np.multiply((eta / (np.sqrt(v_hat) + epsilon)), m_pre)
# 检查是否收敛
if t > 1 and abs(loss_[-1] - loss_[-2]) < threshold:
print(f"Converged at iteration {t}")
break
return theta.flatten(), loss_
2.2 总代码
python">import numpy as np
import matplotlib.pyplot as plt
# 设置 matplotlib 支持中文
plt.rcParams['font.sans-serif'] = ['Microsoft YaHei']
plt.rcParams['axes.unicode_minus'] = False
# 定义 Nadam 函数
def nadam_optimizer(X, y, eta, num_iter=1000, beta1=0.9, beta2=0.999, epsilon=1e-8, threshold=1e-8):
"""
X: 数据 x mxn,可以在传入数据之前进行数据的归一化
y: 数据 y mx1
eta: 学习率
num_iter: 迭代次数
beta: 衰减率
epsilon: 无穷小
threshold: 阈值
"""
m, n = X.shape
theta, mt, vt = np.random.randn(n, 1), np.zeros((n, 1)), np.zeros((n, 1)) # 初始化数据
loss_ = []
for t in range(1, num_iter + 1):
# 计算梯度
h = X.dot(theta)
err = h - y
loss_.append(np.mean(err ** 2) / 2)
g = (1 / m) * X.T.dot(err)
# 一阶矩估计
mt = beta1 * mt + (1 - beta1) * g
# 二阶矩估计
vt = beta2 * vt + (1 - beta2) * g ** 2
# 先计算偏差修正,后面需要使用到,并且去除负数
m_hat, v_hat = mt / (1 - pow(beta1, t)), np.maximum(vt / (1 - pow(beta2, t)), 0)
# 计算预估一阶矩
m_pre = beta1 * m_hat + (1 - beta1) * g / (1 - pow(beta1, t))
# 更新参数
theta = theta - np.multiply((eta / (np.sqrt(v_hat) + epsilon)), m_pre)
# 检查是否收敛
if t > 1 and abs(loss_[-1] - loss_[-2]) < threshold:
print(f"Converged at iteration {t}")
break
return theta.flatten(), loss_
# 生成一些示例数据
np.random.seed(42)
X = 2 * np.random.rand(100, 1)
y = 4 + 3 * X + np.random.randn(100, 1)
# 添加偏置项
X_b = np.c_[np.ones((100, 1)), X]
# 超参数
eta = 0.1
# 运行 Nadam 优化器
theta, loss_ = nadam_optimizer(X_b, y, eta)
print("最优参数 theta:")
print(theta)
# 绘制损失函数图像
plt.plot(range(len(loss_)), loss_, label="损失函数图像")
plt.title("损失函数图像")
plt.xlabel("迭代次数")
plt.ylabel("损失值")
plt.legend() # 显示图例
plt.grid(True) # 显示网格线
plt.show()
三、优缺点
3.1 优点
自适应学习率:NAdam 继承了 Adam 的自适应学习率特性,能够根据梯度的一阶矩(均值)和二阶矩(方差)动态调整每个参数的学习率。这使得 NAdam 在处理不同尺度的参数时更加高效,尤其适合稀疏梯度问题。
Nesterov 动量:NAdam 引入了 Nesterov 动量,能够在更新参数时先根据当前动量预测参数的未来位置,再计算梯度。这种“前瞻性”的更新方式使得 NAdam 能够更准确地调整参数,从而加速收敛。
快速收敛:由于结合了 Adam 的自适应学习率和 Nesterov 动量的前瞻性更新,NAdam 在大多数优化问题中能够比 Adam 和传统梯度下降法更快地收敛。特别是在非凸优化问题中,NAdam 的表现通常优于其他优化算法。
鲁棒性:NAdam 对超参数的选择相对鲁棒,尤其是在学习率和动量参数的选择上。这使得 NAdam 在实际应用中更容易调参。
适合大规模数据:NAdam 能够高效处理大规模数据集和高维参数空间,适合深度学习中的大规模优化问题。
3.2 缺点
计算复杂度较高:由于 NAdam 需要同时维护一阶矩和二阶矩估计,并计算 Nesterov 动量,其计算复杂度略高于传统的梯度下降法。虽然现代深度学习框架(如 PyTorch、TensorFlow)已经对 NAdam 进行了高效实现,但在某些资源受限的场景下,计算开销仍然是一个问题。
对初始学习率敏感:尽管 NAdam 对超参数的选择相对鲁棒,但初始学习率的选择仍然对性能有较大影响。如果初始学习率设置不当,可能会导致收敛速度变慢或无法收敛。
可能陷入局部最优:在某些复杂的非凸优化问题中,NAdam 可能会陷入局部最优解,尤其是在损失函数存在大量鞍点或平坦区域时。
内存占用较高:NAdam 需要存储一阶矩和二阶矩估计,这会增加内存占用。对于非常大的模型(如 GPT-3 等),内存占用可能成为一个瓶颈。
理论分析较少:相比于 Adam 和传统的梯度下降法,NAdam 的理论分析相对较少。虽然实验结果表明 NAdam 在大多数任务中表现优异,但其理论性质仍需进一步研究。
四、相关链接
Python 梯度下降法合集:
- Python 梯度下降法(一):Gradient Descent-CSDN博客
- Python 梯度下降法(二):RMSProp Optimize-CSDN博客
- Python 梯度下降法(三):Adagrad Optimize-CSDN博客
- Python 梯度下降法(四):Adadelta Optimize-CSDN博客
- Python 梯度下降法(五):Adam Optimize-CSDN博客
- Python 梯度下降法(六):Nadam Optimize-CSDN博客
- Python 梯度下降法(七):Summary-CSDN博客