Python 梯度下降法(五):Adam Optimize

发布于:2025-02-10 ⋅ 阅读:(35) ⋅ 点赞:(0)

Python 梯度下降法(五):Adam Optimize

一、数学原理

1.1 介绍

Adam 算法结合了 AdagradRMSProp 算法的优点。Adagrad 算法会根据每个参数的历史梯度信息来调整学习率,对于出现频率较低的参数会给予较大的学习率,而对于出现频率较高的参数则给予较小的学习率。RMSProp 算法则是对 Adagrad 算法的改进,它通过使用移动平均的方式来计算梯度的平方,从而避免了 Adagrad 算法中学习率单调下降的问题。

1.2 符号说明

参数 意义
g t = ∇ θ J ( θ t ) g_{t}=\nabla_{\theta}J(\theta_{t}) gt=θJ(θt) t t t时刻的梯度
m t m_{t} mt 梯度的一阶矩(均值)
β 1 \beta_{1} β1 一阶矩衰减率,一般取0.9
v t v_{t} vt 梯度的二阶矩(未中心化的方差)
β 2 \beta_{2} β2 二阶矩衰减率,一般取0.99
θ \theta θ 线性拟合参数
η \eta η 学习率
ϵ \epsilon ϵ 无穷小量,一般取 1 0 − 8 10^{-8} 108

1.3 实现流程

  1. 初始化: θ \theta θ η \eta η m 0 ⃗ = 0 \vec{m_{0}}=0 m0 =0 v 0 ⃗ = 0 \vec{v_{0}}=0 v0 =0
  2. 计算梯度: g t = ∇ θ J ( θ t ) = 1 m X T L g_{t}=\nabla_{\theta}J(\theta_{t})=\frac{1}{m}X^{T}L gt=θJ(θt)=m1XTL
  3. 梯度的一阶矩估计(均值): m t = β 1 m t − 1 + ( 1 − β 1 ) g t m_{t}=\beta_{1}m_{t-1}+(1-\beta_{1})g_{t} mt=β1mt1+(1β1)gt
  4. 梯度的二阶矩估计(未中心化的方差): 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=β2vt1+(1β2)gt2
  5. 偏差修正: 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}} mt^=1β1tmtvt^=1β2tvt
  6. 更新参数: θ t = θ t − 1 − η m t ^ v t ^ + ϵ \theta_{t}=\theta_{t-1}-\frac{\eta \hat{m_{t}}}{\sqrt{ \hat{v_{t}} }+\epsilon} θt=θt1vt^ +ϵηmt^

二、代码实现

2.1 函数代码

# 定义 Adam 函数
def adam_optimizer(X, y, eta, num_iter=1000, beta1=0.8, beta2=0.8, 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, loss_ = np.random.randn(n, 1), np.zeros((n, 1)), np.zeros((n, 1)), []  # 初始化数据
    for iter in range(num_iter):
        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
        # 偏差修正
        mt_ = mt / (1 - pow(beta1, (iter + 1)))  # 得 + 1 不然在 iter = 0 时,分母为零
        vt_ = np.abs(vt / (1 - pow(beta2, (iter + 1))))
        # 更新参数
        theta = theta - (eta * mt_) / (np.sqrt(vt_) + epsilon)
    
        # 检查是否收敛
        if iter > 1 and abs(loss_[-1] - loss_[-2]) < threshold:
            print(f"Converged at iteration {iter + 1}")
            break

    return theta.flatten(), loss_

2.2 总代码

import numpy as np
import matplotlib.pyplot as plt

# 设置 matplotlib 支持中文
plt.rcParams['font.sans-serif'] = ['Microsoft YaHei']
plt.rcParams['axes.unicode_minus'] = False

# 定义 Adam 函数
def adam_optimizer(X, y, eta, num_iter=1000, beta1=0.8, beta2=0.8, 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, loss_ = np.random.randn(n, 1), np.zeros((n, 1)), np.zeros((n, 1)), []  # 初始化数据
    for iter in range(num_iter):
        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
        # 偏差修正
        mt_ = mt / (1 - pow(beta1, (iter + 1)))  # 得 + 1 不然在 iter = 0 时,分母为零
        vt_ = np.abs(vt / (1 - pow(beta2, (iter + 1))))
        # 更新参数
        theta = theta - (eta * mt_) / (np.sqrt(vt_) + epsilon)
    
        # 检查是否收敛
        if iter > 1 and abs(loss_[-1] - loss_[-2]) < threshold:
            print(f"Converged at iteration {iter + 1}")
            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.01

# 运行 Adam 优化器
theta, loss_ = adam_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()

1738332803_d7btmbrnt5.png1738332802724.png

2.3 遇到的问题

当偏差修正为以下算法时,出现报错:

        # 偏差修正
        mt_ = mt / (1 - pow(beta1, (iter)))
        vt_ = np.abs(vt / (1 - pow(beta2, (iter))))

1738332890_bekam4jjvm.png1738332889461.png

进行检验时,我们发现:

1738333012_ujmv5g46fw.png1738333011494.png

mt_,vt_ \text{mt\_,vt\_} mt_,vt_为无穷量,因此考虑分母为零的情况,而当 iter = 0 \text{iter}=0 iter=0时, 1 − β iter = 0 1- \beta^{\text{iter}}=0 1βiter=0,故说明索引不能从0开始,而应该从1开始,因此引入 iter + 1 \text{iter}+1 iter+1,防止分母的无穷大引入。

2.4 算法优化

由于算法过程中,如果数据量太多会引起资源的严重浪费,因此我们引入小批量梯度下降法的类似方法,批量截取数据来进行拟合。

# 定义 Adam 函数
def adam_optimizer(X, y, eta, num_iter=1000, batch_size=32, beta1=0.8, beta2=0.8, epsilon=1e-8, threshold=1e-8):
    """
    X: 数据 x  mxn,可以在传入数据之前进行数据的归一化
    y: 数据 y  mx1
    eta: 学习率
    num_iter: 迭代次数
    batch_size: 小批量分支法的批量数
    beta: 衰减率
    epsilon: 无穷小
    threshold: 阈值
    """
    m, n = X.shape
    theta, mt, vt, loss_ = np.random.randn(n, 1), np.zeros((n, 1)), np.zeros((n, 1)), []  # 初始化数据
    num_batchs = m // batch_size
    for _ in range(num_iter):
        range_shuffle = np.random.permutation(m)
        X_shuffled = X[range_shuffle]
        y_shuffled = y[range_shuffle]
        loss_temp = []
        for iter in range(num_batchs):
            start_index = batch_size * iter
            end_index = start_index + batch_size
            xi = X_shuffled[start_index:end_index]
            yi = y_shuffled[start_index:end_index]
            h = xi.dot(theta)
            err = h - yi
            loss_temp.append(np.mean((err ** 2) / 2))
            g = (1 / m ) * xi.T.dot(err)
            # 一阶矩估计
            mt = beta1 * mt + (1 - beta1) * g
            # 二阶矩估计
            vt = beta2 * vt + (1 - beta2) * g ** 2
            # 偏差修正
            mt_ = mt / (1 - pow(beta1, (iter + 1)))
            vt_ = np.abs(vt / (1 - pow(beta2, (iter + 1))))
            # 更新参数
            theta = theta - (eta * mt_) / (np.sqrt(vt_) + epsilon)

        loss_.append(np.mean(loss_temp))
        # 检查是否收敛
        if _ > 1 and abs(loss_[-1] - loss_[-2]) < threshold:
            print(f"Converged at iteration {iter + 1}")
            break

    return theta.flatten(), loss_

1738333762_rdxih0p4h8.png1738333761148.png

使用小批量进行Adam优化,可以大大节省系统的资源。

三、优缺点

3.1 优点

对不同参数调整学习率:Adam 能够为模型的每个参数自适应地调整学习率。它会根据参数的梯度历史信息,对出现频率较低的参数给予较大的学习率,对出现频率较高的参数给予较小的学习率。这使得模型在训练过程中能够更好地处理不同尺度和变化频率的参数,加速收敛过程。

无需手动精细调整:在很多情况下,Adam 算法提供的默认超参数就能取得不错的效果,不需要像传统优化算法那样进行大量的手动调参,节省了时间和精力。

低内存需求:Adam 只需要存储梯度的一阶矩估计(均值)和二阶矩估计(未中心化的方差),不需要像一些二阶优化方法那样存储复杂的海森矩阵(Hessian matrix),因此内存占用相对较小,适合处理大规模数据集和深度神经网络。

快速收敛:通过结合梯度的一阶矩和二阶矩信息,Adam 能够更准确地估计梯度的方向和大小,从而在大多数情况下比传统的随机梯度下降(SGD)算法更快地收敛到最优解。

利用稀疏信息:在处理稀疏数据(如自然语言处理中的词向量)时,Adam 能够根据数据的稀疏性调整学习率。对于那些很少出现的特征,算法会给予较大的学习率,使得模型能够更有效地学习这些特征,避免因数据稀疏而导致的学习困难

偏差修正机制:Adam 算法引入了偏差修正机制,用于修正一阶矩和二阶矩估计在训练初期的偏差。这使得算法在训练的早期阶段更加稳定,能够避免因初始估计不准确而导致的训练波动或不收敛问题。

3.2 缺点

自适应特性的局限性:虽然 Adam 能够自适应地调整学习率,但在某些情况下,这种自适应特性可能会导致算法陷入局部最优解。由于学习率会随着训练过程自动调整,可能会在接近局部最优解时过早地降低学习率,使得算法难以跳出局部最优区域,从而无法找到全局最优解。

需要一定的调参经验:尽管 Adam 提供了默认的超参数,但在某些复杂的任务或数据集上,这些默认参数可能不是最优的。例如, β \beta β ϵ \epsilon ϵ的取值会影响算法的性能,如果选择不当,可能会导致收敛速度变慢、模型性能下降等问题。因此,在实际应用中,可能仍然需要进行一定的超参数调优。

过度适应训练数据:由于 Adam 算法在训练过程中过于关注梯度的历史信息和自适应调整学习率,可能会导致模型过度适应训练数据,从而降低模型的泛化能力。在某些情况下,使用 Adam 训练的模型在测试集上的表现可能不如使用其他优化算法训练的模型。

四、相关链接

Python 梯度下降法合集:


网站公告

今日签到

点亮在社区的每一天
去签到