找回密码
立即注册
搜索
热搜: Java Python Linux Go
发回帖 发新帖

1615

积分

1

好友

227

主题
发表于 4 小时前 | 查看: 2| 回复: 0

在实际的优化问题中,我们经常面对高维、非凸、具有复杂景观的目标函数最优化。经典的梯度下降(GD)与随机梯度下降(SGD)虽然在许多场景下有效,但也存在一些局限:

  • 固定学习率选择困难:学习率过大会导致震荡或发散,过小则收敛缓慢。
  • 参数维度的异方差性:不同参数或维度的梯度尺度差异显著,固定学习率难以适配。
  • 非凸目标复杂地形:如深度网络、Rosenbrock函数等具有峡谷、平坦区、鞍点等,优化路径需要“方向动量”与“尺度自适应”。

为此,工业界与学术界提出了动量、自适应学习率及其结合——Adam优化算法,它在实践中表现出色,尤其在深度学习领域广泛应用,能有效提升模型训练效率。

Adam算法原理

设参数向量为θ,在第t步的损失函数为L_t(也可设为期望损失),对应的梯度为g_t。

最基本的梯度下降更新为:

θ_{t+1} = θ_t - η g_t

其中η为学习率。

动量与RMSProp回顾

动量法通过对梯度做指数滑动平均求“速度”:

v_t = β_1 v_{t-1} + (1 - β_1) g_t

其中β_1为典型值0.9。它能在一致方向上加速,对振荡方向平滑。

RMSProp通过对梯度的平方做指数滑动平均来刻画维度尺度:

s_t = β_2 s_{t-1} + (1 - β_2) g_t^2

其中g_t^2为逐元素平方,β_2为典型值0.999,ϵ为数值稳定项。RMSProp能对不同维度调整步长,使得梯度大的维度步长减小,梯度小的维度步长增大。

Adam:动量与自适应的结合

Adam将上述两者结合,分别对梯度的一阶矩与二阶矩做指数滑动平均,并进行偏差修正:

  1. 一阶矩(动量,均值估计)

    m_t = β_1 m_{t-1} + (1 - β_1) g_t
  2. 二阶矩(方差,平方均值估计)

    v_t = β_2 v_{t-1} + (1 - β_2) g_t^2
  3. 偏差修正(关键步骤,改善早期估计):

    \hat{m}_t = m_t / (1 - β_1^t)
    \hat{v}_t = v_t / (1 - β_2^t)
  4. 最终参数更新

    θ_{t+1} = θ_t - η \hat{m}_t / (\sqrt{\hat{v}_t} + ϵ)

超参数典型取值:β_1=0.9, β_2=0.999, ϵ=1e-8。

偏差修正详解

以m_t为例(v_t类似)。定义递推关系:

m_t = β_1 m_{t-1} + (1 - β_1) g_t

在期望层面展开(忽略噪声相关性),若梯度期望E[g_t]在训练早期近似为常数,则有:

E[m_t] ≈ E[g] (1 - β_1^t)

可见,m_t的期望因零初始化而存在偏差。通过除以(1 - β_1^t)进行修正,得到无偏估计\hat{m}_t。同理,\hat{v}_t修正二阶矩偏差。这显著提升了训练初期的稳定性。

案例一:非凸函数Rosenbrock优化

Rosenbrock函数是经典的非凸测试函数,具有狭长“谷底”与弯曲轨迹,适合观察优化器行为:

f(x,y) = (a - x)^2 + b (y - x^2)^2

全局最优解为(1,1)。我们使用PyTorch实现Adam与SGD,从同一初始点优化,对比轨迹与收敛性能。

import torch
import numpy as np
import matplotlib.pyplot as plt

# 固定随机种子
torch.manual_seed(42)
np.random.seed(42)

def rosenbrock_torch(xy, a=1.0, b=100.0):
    x, y = xy[0], xy[1]
    return (a - x)**2 + b * (y - x**2)**2

def rosenbrock_np(X, Y, a=1.0, b=100.0):
    return (a - X)**2 + b * (Y - X**2)**2

# 手动实现Adam优化
def optimize_rosenbrock_adam(init=(-1.5, 2.0), lr=0.02, betas=(0.9, 0.999), eps=1e-8, steps=2000):
    xy = torch.tensor(init, dtype=torch.float32, requires_grad=True)
    m = torch.zeros_like(xy)
    v = torch.zeros_like(xy)
    beta1, beta2 = betas
    path, grad_norms, dists = [], [], []

    for t in range(1, steps + 1):
        loss = rosenbrock_torch(xy)
        loss.backward()
        g = xy.grad.detach()

        m = beta1 * m + (1 - beta1) * g
        v = beta2 * v + (1 - beta2) * (g * g)
        m_hat = m / (1 - beta1 ** t)
        v_hat = v / (1 - beta2 ** t)

        step = lr * m_hat / (torch.sqrt(v_hat) + eps)
        with torch.no_grad():
            xy -= step
        xy.grad.zero_()

        path.append(xy.detach().numpy().copy())
        grad_norms.append(torch.norm(g).item())
        dists.append(torch.norm(xy.detach() - torch.tensor([1.0, 1.0])).item())

    return np.array(path), np.array(grad_norms), np.array(dists)

# SGD优化对比
def optimize_rosenbrock_sgd(init=(-1.5, 2.0), lr=1e-3, steps=2000):
    xy = torch.tensor(init, dtype=torch.float32, requires_grad=True)
    path, grad_norms, dists = [], [], []

    for t in range(steps):
        loss = rosenbrock_torch(xy)
        loss.backward()
        g = xy.grad.detach()
        with torch.no_grad():
            xy -= lr * g
        xy.grad.zero_()

        path.append(xy.detach().numpy().copy())
        grad_norms.append(torch.norm(g).item())
        dists.append(torch.norm(xy.detach() - torch.tensor([1.0, 1.0])).item())

    return np.array(path), np.array(grad_norms), np.array(dists)

# 运行优化
path_adam, grad_adam, dist_adam = optimize_rosenbrock_adam()
path_sgd, grad_sgd, dist_sgd = optimize_rosenbrock_sgd()

# 可视化:等高线与优化轨迹
X = np.linspace(-2, 2, 400)
Y = np.linspace(-1, 3, 400)
XX, YY = np.meshgrid(X, Y)
ZZ = rosenbrock_np(XX, YY)

plt.figure(figsize=(8, 6))
plt.contourf(XX, YY, ZZ, levels=60, cmap='inferno')
plt.colorbar()
plt.plot(path_adam[:, 0], path_adam[:, 1], color='#00FFFF', lw=2.5, label='Adam路径')
plt.plot(path_sgd[:, 0], path_sgd[:, 1], color='#FF00FF', lw=2.0, label='SGD路径')
plt.scatter([-1.5], [2.0], c='yellow', s=80, edgecolors='black', label='起始点')
plt.scatter([1.0], [1.0], c='lime', s=80, edgecolors='black', label='全局最优点')
plt.title('Rosenbrock函数等高线与优化轨迹')
plt.xlabel('x'); plt.ylabel('y')
plt.legend()
plt.tight_layout()
plt.show()

# 可视化:梯度范数与距离变化
fig, ax1 = plt.subplots(figsize=(8, 5))
ax1.plot(grad_adam, color='#00CED1', lw=2.0, label='Adam梯度范数')
ax1.plot(grad_sgd, color='#FF1493', lw=2.0, label='SGD梯度范数')
ax1.set_yscale('log')
ax1.set_xlabel('迭代次数')
ax1.set_ylabel('梯度范数(对数)')
ax1.legend(loc='upper right')

ax2 = ax1.twinx()
ax2.plot(dist_adam, color='#7FFF00', lw=2.0, label='Adam到(1,1)距离')
ax2.plot(dist_sgd, color='#FFA500', lw=2.0, label='SGD到(1,1)距离')
ax2.set_ylabel('到最优点的距离')
fig.suptitle('梯度范数与到最优点距离的变化')
fig.tight_layout()
plt.show()

结果分析

  • 等高线与优化轨迹:Adam沿谷底更快对齐并收敛;SGD路径曲折,易震荡。
    图片
  • 梯度范数与距离曲线:Adam的梯度范数下降更迅速,到最优点距离缩小更快,显示其动量与自适应步长提升收敛效率。
    图片

在非凸、峡谷状地形上,Adam通过一阶矩快速对齐下降方向,利用二阶矩抑制梯度尺度差异,从而稳定靠近全局最优。

案例二:多类螺旋数据分类

我们构造一个3类二维螺旋数据集,具有高度非线性与类间交织,用于检验分类器与优化器能力。使用小型MLP,并以Adam优化训练。

模型配置:两层隐藏层MLP(64-64)+ ReLU + Softmax。
优化器:Adam,学习率0.01,β_1=0.9, β_2=0.999。

import torch
import torch.nn as nn
import torch.optim as optim
import numpy as np
import matplotlib.pyplot as plt
from sklearn.manifold import TSNE

torch.manual_seed(123)
np.random.seed(123)

def make_spiral(n_per_class=2000, n_classes=3, noise=0.2):
    X, y = [], []
    for k in range(n_classes):
        theta = np.linspace(0, 4*np.pi, n_per_class)
        r = np.linspace(0.0, 1.0, n_per_class)
        offset = k * 2*np.pi / n_classes
        xs = r * np.sin(theta + offset) + np.random.randn(n_per_class) * noise
        ys = r * np.cos(theta + offset) + np.random.randn(n_per_class) * noise
        X.append(np.vstack([xs, ys]).T)
        y.append(np.full(n_per_class, k))
    X = np.concatenate(X, axis=0)
    y = np.concatenate(y, axis=0)
    idx = np.random.permutation(len(y))
    return X[idx], y[idx]

X, y = make_spiral()
X_t = torch.tensor(X, dtype=torch.float32)
y_t = torch.tensor(y, dtype=torch.long)

class Net(nn.Module):
    def __init__(self, in_dim=2, hidden=64, out_dim=3):
        super().__init__()
        self.net = nn.Sequential(
            nn.Linear(in_dim, hidden),
            nn.ReLU(),
            nn.Linear(hidden, hidden),
            nn.ReLU(),
            nn.Linear(hidden, out_dim)
        )
        self.layer1 = self.net[0]
        self.relu1 = self.net[1]
        self.layer2 = self.net[2]
        self.relu2 = self.net[3]
        self.layer3 = self.net[4]

    def forward(self, x, return_hidden=False):
        h1 = self.relu1(self.layer1(x))
        h2 = self.relu2(self.layer2(h1))
        logits = self.layer3(h2)
        if return_hidden:
            return logits, h2
        return logits

model = Net()
criterion = nn.CrossEntropyLoss()
optimizer = optim.Adam(model.parameters(), lr=0.01, betas=(0.9, 0.999))

def accuracy(logits, y_true):
    preds = torch.argmax(logits, dim=1)
    return (preds == y_true).float().mean().item()

epochs = 200
batch_size = 128
loss_hist, acc_hist = [], []
for epoch in range(epochs):
    perm = torch.randperm(X_t.size(0))
    X_shuff = X_t[perm]
    y_shuff = y_t[perm]
    for i in range(0, X_t.size(0), batch_size):
        xb = X_shuff[i:i+batch_size]
        yb = y_shuff[i:i+batch_size]
        optimizer.zero_grad()
        logits = model(xb)
        loss = criterion(logits, yb)
        loss.backward()
        optimizer.step()
    with torch.no_grad():
        logits = model(X_t)
        loss_hist.append(criterion(logits, y_t).item())
        acc_hist.append(accuracy(logits, y_t))

# 决策边界可视化
x_min, x_max = X[:,0].min()-0.5, X[:,0].max()+0.5
y_min, y_max = X[:,1].min()-0.5, X[:,1].max()+0.5
xx, yy = np.meshgrid(np.linspace(x_min, x_max, 400),
                     np.linspace(y_min, y_max, 400))
grid = np.c_[xx.ravel(), yy.ravel()]
grid_t = torch.tensor(grid, dtype=torch.float32)
with torch.no_grad():
    logits = model(grid_t)
    probs = torch.softmax(logits, dim=1).numpy()
    preds = np.argmax(probs, axis=1)
Z = preds.reshape(xx.shape)

plt.figure(figsize=(8,6))
plt.contourf(xx, yy, Z, levels=3, cmap='Spectral', alpha=0.7)
plt.scatter(X[:,0], X[:,1], c=y, cmap='rainbow', s=12, edgecolor='k', alpha=0.85)
plt.title('Adam优化下的决策边界(3类螺旋数据)')
plt.xlabel('x1'); plt.ylabel('x2')
plt.colorbar(label='类别')
plt.tight_layout()
plt.show()

# 训练曲线
fig, ax1 = plt.subplots(figsize=(8,5))
ax1.plot(loss_hist, color='#00FF7F', lw=2.0, label='训练损失')
ax1.set_xlabel('Epoch')
ax1.set_ylabel('Loss')
ax2 = ax1.twinx()
ax2.plot(acc_hist, color='#1E90FF', lw=2.0, label='训练准确率')
ax2.set_ylabel('Accuracy')
lines1, labels1 = ax1.get_legend_handles_labels()
lines2, labels2 = ax2.get_legend_handles_labels()
ax1.legend(lines1+lines2, labels1+labels2, loc='lower right')
fig.suptitle('Adam训练损失与准确率曲线')
fig.tight_layout()
plt.show()

# t-SNE隐藏层表征可视化
with torch.no_grad():
    _, hidden = model(X_t, return_hidden=True)
hidden_np = hidden.numpy()
tsne = TSNE(n_components=2, perplexity=30, learning_rate=200, random_state=123)
hidden_2d = tsne.fit_transform(hidden_np)

plt.figure(figsize=(8,6))
plt.scatter(hidden_2d[:,0], hidden_2d[:,1], c=y, cmap='rainbow', s=10, alpha=0.9)
plt.title('Adam训练后隐藏层表征的t-SNE可视化')
plt.xlabel('TSNE-1'); plt.ylabel('TSNE-2')
plt.tight_layout()
plt.show()

结果分析

  • 决策边界:Adam训练的分类器形成非线性分割面,有效区分螺旋数据。
    图片
  • 训练曲线:损失稳步下降,准确率上升,显示Adam在复杂数据上的良好收敛性。
    图片
  • t-SNE可视化:隐藏层表征在嵌入空间中呈现清晰聚类,说明Adam助力网络学习可分特征。
    图片

Adam通过自适应矩估计平衡梯度尺度,依托动量稳定更新,在多类螺旋数据上快速收敛至有效解。

总结

Adam核心思想:结合一阶矩(动量)与二阶矩(自适应)的指数滑动平均,经偏差修正后实现逐元素步长调整。更新公式概括为:

θ_{t+1} = θ_t - η \hat{m}_t / (\sqrt{\hat{v}_t} + ϵ)

关键优势

  • 案例一(Rosenbrock):在非凸地形中更稳健、高效,量化曲线显示收敛速度优势。
  • 案例二(螺旋数据):在高非线性分类任务中快速学习有效决策边界与可分表征。

实践建议:优先考虑AdamW变体;调参关注学习率η、动量β_1、二阶矩平滑β_2及稳定项ϵ;结合学习率调度、梯度裁剪以改善收敛;混合精度训练中注意数值稳定。Adam作为高效优化算法,对于提升模型训练效率至关重要。




上一篇:基于Python、Py4j与ReAct架构的自我编程Agent:Coding驱动的自主决策实践
下一篇:全面解析Unity AnimatorController状态机设计与高级动画应用
您需要登录后才可以回帖 登录 | 立即注册

手机版|小黑屋|网站地图|云栈社区 ( 苏ICP备2022046150号-2 )

GMT+8, 2025-12-24 19:01 , Processed in 0.308907 second(s), 37 queries , Gzip On.

Powered by Discuz! X3.5

© 2025-2025 云栈社区.

快速回复 返回顶部 返回列表