Python 梯度下降法(六):Nadam Optimize

文章目录

  • [Python 梯度下降法(六):Nadam Optimize](#Python 梯度下降法(六):Nadam Optimize)

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 实现流程

  1. 初始化参数: θ n × 1 \theta_{n\times 1} θn×1、 m 0 ⃗ n × 1 = 0 \vec{m_{0}}{n\times 1}=0 m0 n×1=0、 v 0 ⃗ n × 1 = 0 \vec{v{0}}_{n\times 1}=0 v0 n×1=0
  2. 更新一阶矩估计 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
  3. 更新二阶矩估计 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
  4. 偏差修正:由于 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
  5. 计算预估一阶矩: 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
  6. 更新模型参数 θ 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 梯度下降法合集:

相关推荐
张太行_40 分钟前
C++中的析构器(Destructor)(也称为析构函数)
开发语言·c++
aiweker2 小时前
Selenium 使用指南:从入门到精通
python·selenium·测试工具
Hello.Reader4 小时前
深入浅出 Rust 的强大 match 表达式
开发语言·后端·rust
dreadp5 小时前
解锁豆瓣高清海报(二) 使用 OpenCV 拼接和压缩
图像处理·python·opencv·计算机视觉·数据分析
Tester_孙大壮5 小时前
第32章 测试驱动开发(TDD)的原理、实践、关联与争议(Python 版)
驱动开发·python·tdd
xrgs_shz6 小时前
MATLAB的数据类型和各类数据类型转化示例
开发语言·数据结构·matlab
小王子10248 小时前
设计模式Python版 组合模式
python·设计模式·组合模式
来恩10039 小时前
C# 类与对象详解
开发语言·c#