深度学习优化器进化史:从SGD到AdamW的原理与选择

点击 "AladdinEdu,同学们用得起的【H卡】算力平台",注册即送-H卡级别算力80G大显存按量计费灵活弹性顶级配置学生更享专属优惠


引言:优化器------深度学习的引擎

在深度学习的宏伟殿堂中,优化器扮演着至关重要的角色,它如同模型的引擎,决定了学习过程的效率和最终性能。从最基础的随机梯度下降(SGD)到如今广泛使用的Adam及其变体,优化器的发展历程见证了深度学习领域的飞速进步。

优化器的核心任务是调整模型参数以最小化损失函数,这个过程看似简单,实则充满挑战:如何平衡收敛速度和稳定性?如何处理不同参数的不同学习需求?如何避免陷入局部最优或鞍点?这些问题推动着优化器技术的不断演进。

本文将深入探讨优化器的发展历程,从经典的SGD到现代的自适应优化器,通过理论分析、代码实现和实验对比,帮助读者全面理解各种优化器的原理和特点,并掌握根据具体任务选择合适优化器的能力。

第一部分:优化器基础与数学原理

1.1 优化问题基础

深度学习中的优化问题可以形式化为:

minθ L(θ) = 𝔼(x,y)∼𝒟[ℓ(f(x;θ), y)]

其中θ是模型参数,L是损失函数,𝒟是数据分布,ℓ是损失函数,f是模型函数。

1.2 梯度下降的核心思想

梯度下降法基于一个简单而强大的思想:沿着梯度反方向更新参数,因为梯度方向是函数增长最快的方向。

批量梯度下降更新公式

θt+1 = θt - η∇L(θt)

其中η是学习率。

python 复制代码
import numpy as np
import matplotlib.pyplot as plt
from matplotlib.animation import FuncAnimation
from IPython.display import HTML

# 定义测试函数
def quadratic_function(x, y):
    """二次函数,用于优化演示"""
    return x**2 + 2*y**2 + x*y + 3*x - 2*y + 5

def quadratic_gradient(x, y):
    """二次函数的梯度"""
    dx = 2*x + y + 3
    dy = 4*y + x - 2
    return np.array([dx, dy])

# 梯度下降可视化
def visualize_gradient_descent():
    # 创建网格数据
    x = np.linspace(-5, 5, 100)
    y = np.linspace(-5, 5, 100)
    X, Y = np.meshgrid(x, y)
    Z = quadratic_function(X, Y)
    
    # 初始参数
    params = np.array([-4.0, 4.0])
    learning_rate = 0.1
    trajectory = [params.copy()]
    
    # 执行梯度下降
    for i in range(50):
        grad = quadratic_gradient(params[0], params[1])
        params = params - learning_rate * grad
        trajectory.append(params.copy())
    
    trajectory = np.array(trajectory)
    
    # 绘制等高线图
    plt.figure(figsize=(10, 8))
    contour = plt.contour(X, Y, Z, 50, cmap='viridis')
    plt.colorbar(contour)
    plt.plot(trajectory[:, 0], trajectory[:, 1], 'ro-', markersize=4)
    plt.title('Gradient Descent Optimization Path')
    plt.xlabel('x')
    plt.ylabel('y')
    plt.show()
    
    return trajectory

# 运行可视化
trajectory = visualize_gradient_descent()

第二部分:经典优化器详解

2.1 随机梯度下降(SGD)

SGD是深度学习中最基础的优化器,它使用单个样本或小批量的梯度来更新参数。

SGD更新公式

θt+1 = θt - η∇L(θt; x(i), y(i))

python 复制代码
import torch
import torch.nn as nn
import torch.optim as optim

class SGDOptimizer:
    def __init__(self, params, lr=0.01):
        """
        手动实现SGD优化器
        
        Args:
            params: 模型参数
            lr: 学习率
        """
        self.params = list(params)
        self.lr = lr
    
    def zero_grad(self):
        """清零梯度"""
        for param in self.params:
            if param.grad is not None:
                param.grad.zero_()
    
    def step(self):
        """执行参数更新"""
        for param in self.params:
            if param.grad is not None:
                param.data = param.data - self.lr * param.grad.data

# 使用示例
def test_sgd_optimizer():
    # 创建简单模型
    model = nn.Linear(10, 1)
    
    # 使用自定义SGD优化器
    optimizer = SGDOptimizer(model.parameters(), lr=0.01)
    
    # 模拟训练步骤
    for epoch in range(10):
        # 模拟输入和目标
        inputs = torch.randn(32, 10)
        targets = torch.randn(32, 1)
        
        # 前向传播
        outputs = model(inputs)
        loss = nn.MSELoss()(outputs, targets)
        
        # 反向传播
        optimizer.zero_grad()
        loss.backward()
        
        # 参数更新
        optimizer.step()
        
        print(f'Epoch {epoch+1}, Loss: {loss.item():.4f}')

# 测试SGD优化器
test_sgd_optimizer()

2.2 带动量的SGD(Momentum SGD)

动量法通过引入速度变量来加速SGD,并减少振荡。

动量SGD更新公式

vt+1 = γvt + η∇L(θt)

θt+1 = θt - vt+1

python 复制代码
class MomentumSGD:
    def __init__(self, params, lr=0.01, momentum=0.9):
        """
        手动实现带动量的SGD优化器
        
        Args:
            params: 模型参数
            lr: 学习率
            momentum: 动量系数
        """
        self.params = list(params)
        self.lr = lr
        self.momentum = momentum
        self.velocities = [torch.zeros_like(param) for param in self.params]
    
    def zero_grad(self):
        """清零梯度"""
        for param in self.params:
            if param.grad is not None:
                param.grad.zero_()
    
    def step(self):
        """执行参数更新"""
        for i, param in enumerate(self.params):
            if param.grad is not None:
                # 更新速度
                self.velocities[i] = self.momentum * self.velocities[i] + self.lr * param.grad.data
                # 更新参数
                param.data = param.data - self.velocities[i]

# 动量SGD效果对比
def compare_sgd_vs_momentum():
    """对比SGD和动量SGD的优化路径"""
    # 定义测试函数
    def himmelblau(x, y):
        return (x**2 + y - 11)**2 + (x + y**2 - 7)**2
    
    def himmelblau_gradient(x, y):
        dx = 4*x*(x**2 + y - 11) + 2*(x + y**2 - 7)
        dy = 2*(x**2 + y - 11) + 4*y*(x + y**2 - 7)
        return np.array([dx, dy])
    
    # 初始化参数
    x_sgd, y_sgd = 0.0, 0.0
    x_momentum, y_momentum = 0.0, 0.0
    lr = 0.01
    momentum = 0.9
    
    # 记录轨迹
    sgd_trajectory = [(x_sgd, y_sgd)]
    momentum_trajectory = [(x_momentum, y_momentum)]
    
    # 速度初始化
    vx, vy = 0.0, 0.0
    
    # 优化过程
    for i in range(1000):
        # SGD
        grad_sgd = himmelblau_gradient(x_sgd, y_sgd)
        x_sgd = x_sgd - lr * grad_sgd[0]
        y_sgd = y_sgd - lr * grad_sgd[1]
        sgd_trajectory.append((x_sgd, y_sgd))
        
        # Momentum SGD
        grad_momentum = himmelblau_gradient(x_momentum, y_momentum)
        vx = momentum * vx + lr * grad_momentum[0]
        vy = momentum * vy + lr * grad_momentum[1]
        x_momentum = x_momentum - vx
        y_momentum = y_momentum - vy
        momentum_trajectory.append((x_momentum, y_momentum))
    
    # 可视化
    x = np.linspace(-5, 5, 100)
    y = np.linspace(-5, 5, 100)
    X, Y = np.meshgrid(x, y)
    Z = himmelblau(X, Y)
    
    plt.figure(figsize=(12, 5))
    
    plt.subplot(1, 2, 1)
    plt.contour(X, Y, Z, 50, cmap='viridis')
    sgd_traj = np.array(sgd_trajectory)
    plt.plot(sgd_traj[:, 0], sgd_traj[:, 1], 'ro-', markersize=2, label='SGD')
    plt.title('SGD Optimization Path')
    plt.xlabel('x')
    plt.ylabel('y')
    plt.legend()
    
    plt.subplot(1, 2, 2)
    plt.contour(X, Y, Z, 50, cmap='viridis')
    momentum_traj = np.array(momentum_trajectory)
    plt.plot(momentum_traj[:, 0], momentum_traj[:, 1], 'bo-', markersize=2, label='Momentum SGD')
    plt.title('Momentum SGD Optimization Path')
    plt.xlabel('x')
    plt.ylabel('y')
    plt.legend()
    
    plt.tight_layout()
    plt.show()

# 运行对比
compare_sgd_vs_momentum()

2.3 Nesterov加速梯度(NAG)

NAG是动量法的改进版本,它先根据动量方向进行预更新,然后计算梯度。

NAG更新公式

vt+1 = γvt + η∇L(θt - γvt)

θt+1 = θt - vt+1

python 复制代码
class NAGOptimizer:
    def __init__(self, params, lr=0.01, momentum=0.9):
        """
        手动实现NAG优化器
        
        Args:
            params: 模型参数
            lr: 学习率
            momentum: 动量系数
        """
        self.params = list(params)
        self.lr = lr
        self.momentum = momentum
        self.velocities = [torch.zeros_like(param) for param in self.params]
    
    def zero_grad(self):
        """清零梯度"""
        for param in self.params:
            if param.grad is not None:
                param.grad.zero_()
    
    def step(self):
        """执行参数更新"""
        for i, param in enumerate(self.params):
            if param.grad is not None:
                # 保存当前参数
                current_param = param.data.clone()
                
                # 预更新参数
                param.data = param.data - self.momentum * self.velocities[i]
                
                # 这里应该重新计算梯度,但为了简化,我们使用已计算的梯度
                # 在实际实现中,需要在前向传播前进行预更新
                
                # 恢复参数
                param.data = current_param
                
                # 更新速度
                self.velocities[i] = self.momentum * self.velocities[i] + self.lr * param.grad.data
                
                # 更新参数
                param.data = param.data - self.velocities[i]

# NAG与动量SGD对比
def compare_momentum_vs_nag():
    """对比动量SGD和NAG的优化路径"""
    # 定义测试函数
    def rastrigin(x, y):
        """Rastrigin函数,多局部极小值点"""
        A = 10
        return A * 2 + (x**2 - A * np.cos(2 * np.pi * x)) + (y**2 - A * np.cos(2 * np.pi * y))
    
    def rastrigin_gradient(x, y):
        dx = 2 * x + 10 * 2 * np.pi * np.sin(2 * np.pi * x)
        dy = 2 * y + 10 * 2 * np.pi * np.sin(2 * np.pi * y)
        return np.array([dx, dy])
    
    # 初始化参数
    x_momentum, y_momentum = 2.5, 2.5
    x_nag, y_nag = 2.5, 2.5
    lr = 0.01
    momentum = 0.9
    
    # 记录轨迹
    momentum_trajectory = [(x_momentum, y_momentum)]
    nag_trajectory = [(x_nag, y_nag)]
    
    # 速度初始化
    vx_m, vy_m = 0.0, 0.0
    vx_n, vy_n = 0.0, 0.0
    
    # 优化过程
    for i in range(500):
        # Momentum SGD
        grad_m = rastrigin_gradient(x_momentum, y_momentum)
        vx_m = momentum * vx_m + lr * grad_m[0]
        vy_m = momentum * vy_m + lr * grad_m[1]
        x_momentum = x_momentum - vx_m
        y_momentum = y_momentum - vy_m
        momentum_trajectory.append((x_momentum, y_momentum))
        
        # NAG
        # 预更新位置
        x_nag_lookahead = x_nag - momentum * vx_n
        y_nag_lookahead = y_nag - momentum * vy_n
        
        # 计算预更新位置的梯度
        grad_n = rastrigin_gradient(x_nag_lookahead, y_nag_lookahead)
        
        # 更新速度
        vx_n = momentum * vx_n + lr * grad_n[0]
        vy_n = momentum * vy_n + lr * grad_n[1]
        
        # 更新参数
        x_nag = x_nag - vx_n
        y_nag = y_nag - vy_n
        nag_trajectory.append((x_nag, y_nag))
    
    # 可视化
    x = np.linspace(-3, 3, 100)
    y = np.linspace(-3, 3, 100)
    X, Y = np.meshgrid(x, y)
    Z = rastrigin(X, Y)
    
    plt.figure(figsize=(12, 5))
    
    plt.subplot(1, 2, 1)
    plt.contour(X, Y, Z, 50, cmap='viridis', levels=np.logspace(-1, 3, 20))
    momentum_traj = np.array(momentum_trajectory)
    plt.plot(momentum_traj[:, 0], momentum_traj[:, 1], 'ro-', markersize=2, label='Momentum SGD')
    plt.title('Momentum SGD Optimization Path')
    plt.xlabel('x')
    plt.ylabel('y')
    plt.legend()
    
    plt.subplot(1, 2, 2)
    plt.contour(X, Y, Z, 50, cmap='viridis', levels=np.logspace(-1, 3, 20))
    nag_traj = np.array(nag_trajectory)
    plt.plot(nag_traj[:, 0], nag_traj[:, 1], 'bo-', markersize=2, label='NAG')
    plt.title('NAG Optimization Path')
    plt.xlabel('x')
    plt.ylabel('y')
    plt.legend()
    
    plt.tight_layout()
    plt.show()

# 运行对比
compare_momentum_vs_nag()

第三部分:自适应学习率优化器

3.1 AdaGrad

AdaGrad为每个参数自适应地调整学习率,适合处理稀疏数据。

AdaGrad更新公式

gt,i = ∇L(θt,i)

Gt,ii = Gt-1,ii + gt,i²

θt+1,i = θt,i - (η/√(Gt,ii + ε)) · gt,i

python 复制代码
class AdaGradOptimizer:
    def __init__(self, params, lr=0.01, epsilon=1e-8):
        """
        手动实现AdaGrad优化器
        
        Args:
            params: 模型参数
            lr: 学习率
            epsilon: 小常数,防止除零
        """
        self.params = list(params)
        self.lr = lr
        self.epsilon = epsilon
        self.G = [torch.zeros_like(param) for param in self.params]
    
    def zero_grad(self):
        """清零梯度"""
        for param in self.params:
            if param.grad is not None:
                param.grad.zero_()
    
    def step(self):
        """执行参数更新"""
        for i, param in enumerate(self.params):
            if param.grad is not None:
                # 累积平方梯度
                self.G[i] = self.G[i] + param.grad.data ** 2
                
                # 计算自适应学习率
                adaptive_lr = self.lr / (torch.sqrt(self.G[i]) + self.epsilon)
                
                # 更新参数
                param.data = param.data - adaptive_lr * param.grad.data

# AdaGrad优化器测试
def test_adagrad_optimizer():
    """测试AdaGrad优化器在不同稀疏性数据上的表现"""
    # 创建测试数据
    torch.manual_seed(42)
    
    # 密集数据
    dense_features = torch.randn(1000, 50)
    dense_targets = torch.randn(1000, 1)
    
    # 稀疏数据(90%为零)
    sparse_features = dense_features.clone()
    mask = torch.rand_like(sparse_features) > 0.9
    sparse_features = sparse_features * mask.float()
    
    # 创建简单模型
    model_dense = nn.Linear(50, 1)
    model_sparse = nn.Linear(50, 1)
    
    # 使用相同的初始权重
    model_sparse.load_state_dict(model_dense.state_dict())
    
    # 优化器
    optimizer_dense = AdaGradOptimizer(model_dense.parameters(), lr=0.1)
    optimizer_sparse = AdaGradOptimizer(model_sparse.parameters(), lr=0.1)
    
    # 训练记录
    losses_dense = []
    losses_sparse = []
    
    # 训练过程
    for epoch in range(100):
        # 密集数据训练
        outputs_dense = model_dense(dense_features)
        loss_dense = nn.MSELoss()(outputs_dense, dense_targets)
        
        optimizer_dense.zero_grad()
        loss_dense.backward()
        optimizer_dense.step()
        
        losses_dense.append(loss_dense.item())
        
        # 稀疏数据训练
        outputs_sparse = model_sparse(sparse_features)
        loss_sparse = nn.MSELoss()(outputs_sparse, dense_targets)
        
        optimizer_sparse.zero_grad()
        loss_sparse.backward()
        optimizer_sparse.step()
        
        losses_sparse.append(loss_sparse.item())
    
    # 绘制训练曲线
    plt.figure(figsize=(10, 6))
    plt.plot(losses_dense, label='Dense Data')
    plt.plot(losses_sparse, label='Sparse Data')
    plt.xlabel('Epoch')
    plt.ylabel('Loss')
    plt.title('AdaGrad Performance on Different Data Sparsity')
    plt.legend()
    plt.grid(True)
    plt.show()

# 运行测试
test_adagrad_optimizer()

3.2 RMSProp

RMSProp改进了AdaGrad,通过指数移动平均来累积梯度,避免学习率过早衰减。

RMSProp更新公式

E[g²]t = βE[g²]t-1 + (1-β)gt²

θt+1 = θt - (η/√(E[g²]t + ε)) · gt

python 复制代码
class RMSPropOptimizer:
    def __init__(self, params, lr=0.01, alpha=0.99, epsilon=1e-8):
        """
        手动实现RMSProp优化器
        
        Args:
            params: 模型参数
            lr: 学习率
            alpha: 衰减率
            epsilon: 小常数,防止除零
        """
        self.params = list(params)
        self.lr = lr
        self.alpha = alpha
        self.epsilon = epsilon
        self.avg_sq_grad = [torch.zeros_like(param) for param in self.params]
    
    def zero_grad(self):
        """清零梯度"""
        for param in self.params:
            if param.grad is not None:
                param.grad.zero_()
    
    def step(self):
        """执行参数更新"""
        for i, param in enumerate(self.params):
            if param.grad is not None:
                # 计算指数移动平均的平方梯度
                self.avg_sq_grad[i] = self.alpha * self.avg_sq_grad[i] + (1 - self.alpha) * param.grad.data ** 2
                
                # 计算自适应学习率
                adaptive_lr = self.lr / (torch.sqrt(self.avg_sq_grad[i]) + self.epsilon)
                
                # 更新参数
                param.data = param.data - adaptive_lr * param.grad.data

# RMSProp与AdaGrad对比
def compare_adagrad_vs_rmsprop():
    """对比AdaGrad和RMSProp在非平稳问题上的表现"""
    # 定义非平稳目标函数
    def non_stationary_function(x, iteration):
        """随时间变化的非平稳函数"""
        return (x - 0.5 * np.sin(iteration / 50)) ** 2
    
    def non_stationary_gradient(x, iteration):
        """非平稳函数的梯度"""
        return 2 * (x - 0.5 * np.sin(iteration / 50))
    
    # 初始化参数
    x_adagrad, x_rmsprop = 2.0, 2.0
    lr = 0.1
    epsilon = 1e-8
    
    # AdaGrad变量
    G_adagrad = 0
    
    # RMSProp变量
    avg_sq_grad = 0
    alpha = 0.99
    
    # 记录轨迹
    adagrad_trajectory = [x_adagrad]
    rmsprop_trajectory = [x_rmsprop]
    target_trajectory = [0.5 * np.sin(0)]  # 初始目标值
    
    # 优化过程
    for iteration in range(1, 1000):
        # 当前目标值
        current_target = 0.5 * np.sin(iteration / 50)
        target_trajectory.append(current_target)
        
        # AdaGrad
        grad_adagrad = non_stationary_gradient(x_adagrad, iteration)
        G_adagrad = G_adagrad + grad_adagrad ** 2
        adaptive_lr_adagrad = lr / (np.sqrt(G_adagrad) + epsilon)
        x_adagrad = x_adagrad - adaptive_lr_adagrad * grad_adagrad
        adagrad_trajectory.append(x_adagrad)
        
        # RMSProp
        grad_rmsprop = non_stationary_gradient(x_rmsprop, iteration)
        avg_sq_grad = alpha * avg_sq_grad + (1 - alpha) * grad_rmsprop ** 2
        adaptive_lr_rmsprop = lr / (np.sqrt(avg_sq_grad) + epsilon)
        x_rmsprop = x_rmsprop - adaptive_lr_rmsprop * grad_rmsprop
        rmsprop_trajectory.append(x_rmsprop)
    
    # 可视化
    plt.figure(figsize=(12, 6))
    
    iterations = range(1000)
    plt.plot(iterations, adagrad_trajectory, 'r-', label='AdaGrad', alpha=0.7)
    plt.plot(iterations, rmsprop_trajectory, 'b-', label='RMSProp', alpha=0.7)
    plt.plot(iterations, target_trajectory, 'g--', label='Moving Target', alpha=0.7)
    
    plt.xlabel('Iteration')
    plt.ylabel('Parameter Value')
    plt.title('AdaGrad vs RMSProp on Non-stationary Problem')
    plt.legend()
    plt.grid(True)
    plt.show()

# 运行对比
compare_adagrad_vs_rmsprop()

3.3 Adam(Adaptive Moment Estimation)

Adam结合了动量法和RMSProp的优点,是当前最流行的优化器之一。

Adam更新公式

mt = β₁mt-1 + (1-β₁)gt # 一阶矩估计

vt = β₂vt-1 + (1-β₂)gt² # 二阶矩估计

t = mt/(1-β₁ᵗ) # 偏差校正

t = vt/(1-β₂ᵗ) # 偏差校正

θt+1 = θt - (η/√(v̂t + ε)) · m̂t

python 复制代码
class AdamOptimizer:
    def __init__(self, params, lr=0.001, beta1=0.9, beta2=0.999, epsilon=1e-8):
        """
        手动实现Adam优化器
        
        Args:
            params: 模型参数
            lr: 学习率
            beta1: 一阶矩衰减率
            beta2: 二阶矩衰减率
            epsilon: 小常数,防止除零
        """
        self.params = list(params)
        self.lr = lr
        self.beta1 = beta1
        self.beta2 = beta2
        self.epsilon = epsilon
        self.t = 0
        
        # 初始化矩估计
        self.m = [torch.zeros_like(param) for param in self.params]
        self.v = [torch.zeros_like(param) for param in self.params]
    
    def zero_grad(self):
        """清零梯度"""
        for param in self.params:
            if param.grad is not None:
                param.grad.zero_()
    
    def step(self):
        """执行参数更新"""
        self.t += 1
        for i, param in enumerate(self.params):
            if param.grad is not None:
                # 更新一阶矩估计
                self.m[i] = self.beta1 * self.m[i] + (1 - self.beta1) * param.grad.data
                
                # 更新二阶矩估计
                self.v[i] = self.beta2 * self.v[i] + (1 - self.beta2) * param.grad.data ** 2
                
                # 偏差校正
                m_hat = self.m[i] / (1 - self.beta1 ** self.t)
                v_hat = self.v[i] / (1 - self.beta2 ** self.t)
                
                # 更新参数
                param.data = param.data - self.lr * m_hat / (torch.sqrt(v_hat) + self.epsilon)

# Adam优化器全面测试
def comprehensive_adam_test():
    """全面测试Adam优化器在不同场景下的表现"""
    # 测试1: 不同学习率的影响
    print("测试不同学习率对Adam的影响...")
    
    # 定义测试函数
    def test_function(x):
        return x ** 4 - 3 * x ** 3 + 2 * x ** 2 - x + 5
    
    def test_gradient(x):
        return 4 * x ** 3 - 9 * x ** 2 + 4 * x - 1
    
    # 测试不同学习率
    learning_rates = [0.001, 0.01, 0.1, 0.5]
    results = {}
    
    for lr in learning_rates:
        x = torch.tensor([5.0], requires_grad=True)  # 初始点
        optimizer = AdamOptimizer([x], lr=lr)
        
        trajectory = [x.item()]
        for step in range(1000):
            # 计算损失
            loss = test_function(x)
            
            # 反向传播
            optimizer.zero_grad()
            loss.backward()
            optimizer.step()
            
            trajectory.append(x.item())
        
        results[f'LR={lr}'] = trajectory
    
    # 可视化不同学习率的效果
    plt.figure(figsize=(12, 6))
    for label, trajectory in results.items():
        plt.plot(trajectory, label=label)
    
    plt.xlabel('Iteration')
    plt.ylabel('Parameter Value')
    plt.title('Adam with Different Learning Rates')
    plt.legend()
    plt.grid(True)
    plt.show()
    
    return results

# 运行全面测试
adam_results = comprehensive_adam_test()

3.4 AdamW:解耦权重衰减

AdamW改进了Adam,将权重衰减与梯度更新解耦,解决了Adam在某些任务上的泛化问题。

AdamW更新公式

与Adam相同,但权重衰减单独处理:

θt+1 = θt - (η/√(v̂t + ε)) · m̂t - ηλθt

python 复制代码
class AdamWOptimizer:
    def __init__(self, params, lr=0.001, beta1=0.9, beta2=0.999, epsilon=1e-8, weight_decay=0.01):
        """
        手动实现AdamW优化器
        
        Args:
            params: 模型参数
            lr: 学习率
            beta1: 一阶矩衰减率
            beta2: 二阶矩衰减率
            epsilon: 小常数,防止除零
            weight_decay: 权重衰减系数
        """
        self.params = list(params)
        self.lr = lr
        self.beta1 = beta1
        self.beta2 = beta2
        self.epsilon = epsilon
        self.weight_decay = weight_decay
        self.t = 0
        
        # 初始化矩估计
        self.m = [torch.zeros_like(param) for param in self.params]
        self.v = [torch.zeros_like(param) for param in self.params]
    
    def zero_grad(self):
        """清零梯度"""
        for param in self.params:
            if param.grad is not None:
                param.grad.zero_()
    
    def step(self):
        """执行参数更新"""
        self.t += 1
        for i, param in enumerate(self.params):
            if param.grad is not None:
                # 应用权重衰减(解耦)
                param.data = param.data - self.lr * self.weight_decay * param.data
                
                # 更新一阶矩估计
                self.m[i] = self.beta1 * self.m[i] + (1 - self.beta1) * param.grad.data
                
                # 更新二阶矩估计
                self.v[i] = self.beta2 * self.v[i] + (1 - self.beta2) * param.grad.data ** 2
                
                # 偏差校正
                m_hat = self.m[i] / (1 - self.beta1 ** self.t)
                v_hat = self.v[i] / (1 - self.beta2 ** self.t)
                
                # 更新参数
                param.data = param.data - self.lr * m_hat / (torch.sqrt(v_hat) + self.epsilon)

# Adam vs AdamW对比
def compare_adam_vs_adamw():
    """对比Adam和AdamW在深度学习任务上的表现"""
    # 使用CIFAR-10数据集
    transform = transforms.Compose([
        transforms.ToTensor(),
        transforms.Normalize((0.5, 0.5, 0.5), (0.5, 0.5, 0.5))
    ])
    
    trainset = torchvision.datasets.CIFAR10(root='./data', train=True, download=True, transform=transform)
    trainloader = torch.utils.data.DataLoader(trainset, batch_size=128, shuffle=True)
    
    # 创建相同结构的两个模型
    model_adam = torchvision.models.resnet18(pretrained=False, num_classes=10)
    model_adamw = torchvision.models.resnet18(pretrained=False, num_classes=10)
    
    # 使用相同的初始权重
    model_adamw.load_state_dict(model_adam.state_dict())
    
    # 优化器
    optimizer_adam = AdamOptimizer(model_adam.parameters(), lr=0.001)
    optimizer_adamw = AdamWOptimizer(model_adamw.parameters(), lr=0.001, weight_decay=0.01)
    
    criterion = nn.CrossEntropyLoss()
    
    # 训练记录
    losses_adam = []
    losses_adamw = []
    accuracies_adam = []
    accuracies_adamw = []
    
    # 训练过程
    for epoch in range(10):  # 简化训练轮数
        # Adam训练
        model_adam.train()
        running_loss_adam = 0.0
        correct_adam = 0
        total_adam = 0
        
        for i, (inputs, labels) in enumerate(trainloader):
            optimizer_adam.zero_grad()
            outputs = model_adam(inputs)
            loss = criterion(outputs, labels)
            loss.backward()
            optimizer_adam.step()
            
            running_loss_adam += loss.item()
            _, predicted = outputs.max(1)
            total_adam += labels.size(0)
            correct_adam += predicted.eq(labels).sum().item()
        
        losses_adam.append(running_loss_adam / len(trainloader))
        accuracies_adam.append(100. * correct_adam / total_adam)
        
        # AdamW训练
        model_adamw.train()
        running_loss_adamw = 0.0
        correct_adamw = 0
        total_adamw = 0
        
        for i, (inputs, labels) in enumerate(trainloader):
            optimizer_adamw.zero_grad()
            outputs = model_adamw(inputs)
            loss = criterion(outputs, labels)
            loss.backward()
            optimizer_adamw.step()
            
            running_loss_adamw += loss.item()
            _, predicted = outputs.max(1)
            total_adamw += labels.size(0)
            correct_adamw += predicted.eq(labels).sum().item()
        
        losses_adamw.append(running_loss_adamw / len(trainloader))
        accuracies_adamw.append(100. * correct_adamw / total_adamw)
        
        print(f'Epoch {epoch+1}: Adam Loss: {losses_adam[-1]:.4f}, Acc: {accuracies_adam[-1]:.2f}% | '
              f'AdamW Loss: {losses_adamw[-1]:.4f}, Acc: {accuracies_adamw[-1]:.2f}%')
    
    # 可视化结果
    plt.figure(figsize=(12, 5))
    
    plt.subplot(1, 2, 1)
    plt.plot(losses_adam, 'b-', label='Adam')
    plt.plot(losses_adamw, 'r-', label='AdamW')
    plt.xlabel('Epoch')
    plt.ylabel('Loss')
    plt.title('Training Loss Comparison')
    plt.legend()
    plt.grid(True)
    
    plt.subplot(1, 2, 2)
    plt.plot(accuracies_adam, 'b-', label='Adam')
    plt.plot(accuracies_adamw, 'r-', label='AdamW')
    plt.xlabel('Epoch')
    plt.ylabel('Accuracy (%)')
    plt.title('Training Accuracy Comparison')
    plt.legend()
    plt.grid(True)
    
    plt.tight_layout()
    plt.show()
    
    return losses_adam, losses_adamw, accuracies_adam, accuracies_adamw

# 运行对比
adam_loss, adamw_loss, adam_acc, adamw_acc = compare_adam_vs_adamw()

第四部分:优化器选择指南与实战建议

4.1 优化器性能综合对比

python 复制代码
def comprehensive_optimizer_comparison():
    """综合对比各种优化器的性能"""
    # 定义复杂的测试函数
    def complex_function(x, y):
        """具有多个局部极小值的复杂函数"""
        return (1.5 - x + x * y) ** 2 + (2.25 - x + x * y ** 2) ** 2 + (2.625 - x + x * y ** 3) ** 2
    
    def complex_gradient(x, y):
        """复杂函数的梯度"""
        term1 = 2 * (1.5 - x + x * y) * (-1 + y)
        term2 = 2 * (2.25 - x + x * y ** 2) * (-1 + y ** 2)
        term3 = 2 * (2.625 - x + x * y ** 3) * (-1 + y ** 3)
        dx = term1 + term2 + term3
        
        term1 = 2 * (1.5 - x + x * y) * x
        term2 = 2 * (2.25 - x + x * y ** 2) * (2 * x * y)
        term3 = 2 * (2.625 - x + x * y ** 3) * (3 * x * y ** 2)
        dy = term1 + term2 + term3
        
        return np.array([dx, dy])
    
    # 优化器列表
    optimizers = {
        'SGD': {'lr': 0.01},
        'Momentum': {'lr': 0.01, 'momentum': 0.9},
        'AdaGrad': {'lr': 0.1},
        'RMSProp': {'lr': 0.01, 'alpha': 0.99},
        'Adam': {'lr': 0.001, 'beta1': 0.9, 'beta2': 0.999}
    }
    
    # 初始化参数
    initial_point = np.array([-1.0, -1.5])
    trajectories = {}
    final_values = {}
    
    # 对每个优化器进行测试
    for opt_name, opt_params in optimizers.items():
        print(f"测试 {opt_name} 优化器...")
        
        # 初始化参数
        params = initial_point.copy()
        trajectory = [params.copy()]
        
        # 优化器特定的变量初始化
        if opt_name == 'SGD':
            # 无需额外变量
            pass
        elif opt_name == 'Momentum':
            velocity = np.zeros_like(params)
        elif opt_name == 'AdaGrad':
            G = np.zeros_like(params)
        elif opt_name == 'RMSProp':
            avg_sq_grad = np.zeros_like(params)
        elif opt_name == 'Adam':
            m = np.zeros_like(params)
            v = np.zeros_like(params)
            t = 0
        
        # 优化过程
        for iteration in range(1000):
            grad = complex_gradient(params[0], params[1])
            
            if opt_name == 'SGD':
                update = opt_params['lr'] * grad
            
            elif opt_name == 'Momentum':
                velocity = opt_params['momentum'] * velocity + opt_params['lr'] * grad
                update = velocity
            
            elif opt_name == 'AdaGrad':
                G = G + grad ** 2
                update = opt_params['lr'] * grad / (np.sqrt(G) + 1e-8)
            
            elif opt_name == 'RMSProp':
                avg_sq_grad = opt_params['alpha'] * avg_sq_grad + (1 - opt_params['alpha']) * grad ** 2
                update = opt_params['lr'] * grad / (np.sqrt(avg_sq_grad) + 1e-8)
            
            elif opt_name == 'Adam':
                t += 1
                m = opt_params['beta1'] * m + (1 - opt_params['beta1']) * grad
                v = opt_params['beta2'] * v + (1 - opt_params['beta2']) * grad ** 2
                
                # 偏差校正
                m_hat = m / (1 - opt_params['beta1'] ** t)
                v_hat = v / (1 - opt_params['beta2'] ** t)
                
                update = opt_params['lr'] * m_hat / (np.sqrt(v_hat) + 1e-8)
            
            # 更新参数
            params = params - update
            trajectory.append(params.copy())
        
        trajectories[opt_name] = np.array(trajectory)
        final_values[opt_name] = complex_function(params[0], params[1])
        print(f"  {opt_name} 最终损失: {final_values[opt_name]:.6f}")
    
    # 可视化结果
    x = np.linspace(-4.5, 4.5, 100)
    y = np.linspace(-4.5, 4.5, 100)
    X, Y = np.meshgrid(x, y)
    Z = complex_function(X, Y)
    
    plt.figure(figsize=(15, 12))
    
    # 绘制等高线图
    contour = plt.contour(X, Y, Z, 50, cmap='viridis', alpha=0.6)
    plt.colorbar(contour)
    
    # 绘制各优化器的轨迹
    colors = ['red', 'blue', 'green', 'orange', 'purple']
    for i, (opt_name, trajectory) in enumerate(trajectories.items()):
        plt.plot(trajectory[:, 0], trajectory[:, 1], color=colors[i], marker='o', 
                markersize=2, linewidth=1.5, label=opt_name)
    
    plt.title('Optimizer Trajectories on Complex Function')
    plt.xlabel('x')
    plt.ylabel('y')
    plt.legend()
    plt.grid(True)
    plt.show()
    
    # 绘制收敛曲线
    plt.figure(figsize=(12, 6))
    for opt_name, trajectory in trajectories.items():
        losses = [complex_function(p[0], p[1]) for p in trajectory]
        plt.plot(losses, label=opt_name, linewidth=2)
    
    plt.yscale('log')
    plt.xlabel('Iteration')
    plt.ylabel('Loss (log scale)')
    plt.title('Optimizer Convergence Comparison')
    plt.legend()
    plt.grid(True)
    plt.show()
    
    return trajectories, final_values

# 运行综合对比
trajectories, final_values = comprehensive_optimizer_comparison()

4.2 优化器选择指南

根据理论分析和实验结果,我们总结出以下优化器选择指南:

优化器 适用场景 优点 缺点 推荐参数
SGD 凸优化、简单问题 理论保证、可解释性强 收敛慢、易陷入局部最优 lr=0.01
Momentum 中等复杂度问题 加速收敛、减少振荡 需要调整动量参数 lr=0.01, momentum=0.9
AdaGrad 稀疏数据、自然语言处理 自适应学习率、适合稀疏特征 学习率过早衰减 lr=0.1
RMSProp 非平稳目标、RNN 处理非平稳问题效果好 对超参数敏感 lr=0.001, alpha=0.99
Adam 大多数深度学习任务 快速收敛、自适应学习率 可能泛化不如SGD lr=0.001, beta1=0.9, beta2=0.999
AdamW 计算机视觉、需要正则化的任务 更好的泛化性能 超参数更多 lr=0.001, weight_decay=0.01

4.3 实战建议与调参技巧

python 复制代码
def practical_optimizer_tips():
    """
    优化器实战建议和调参技巧
    """
    tips = {
        '学习率设置': [
            '从默认学习率开始(Adam: 0.001, SGD: 0.01)',
            '使用学习率预热(warmup)策略',
            '实施学习率衰减(step decay或cosine annealing)',
            '对于小批量数据,使用较小的学习率'
        ],
        '批量大小': [
            '大批量:使用更大学习率,加快训练速度',
            '小批量:使用更小学习率,可能获得更好泛化',
            '常见批量大小:32, 64, 128, 256'
        ],
        '优化器选择': [
            'CV任务:Adam/AdamW通常是不错的选择',
            'NLP任务:AdaGrad/Adam适合稀疏特征',
            '简单任务:SGD可能获得更好泛化',
            '不稳定训练:尝试减小学习率或换用SGD'
        ],
        '超参数调优': [
            '使用网格搜索或随机搜索',
            '关注验证集性能而非训练集损失',
            '考虑使用自动化超参数优化工具(如Optuna)'
        ],
        '高级技巧': [
            '梯度裁剪(clip gradients)防止梯度爆炸',
            '使用SWA(Stochastic Weight Averaging)提高泛化',
            '尝试Lion、AdaBelief等新兴优化器'
        ]
    }
    
    print("优化器实战建议与调参技巧")
    print("=" * 50)
    
    for category, advice_list in tips.items():
        print(f"\n{category}:")
        for i, advice in enumerate(advice_list, 1):
            print(f"  {i}. {advice}")
    
    return tips

# 学习率搜索示例
def learning_rate_search_example():
    """学习率搜索的代码示例"""
    # 定义测试函数
    def test_model():
        return nn.Sequential(
            nn.Linear(10, 50),
            nn.ReLU(),
            nn.Linear(50, 1)
        )
    
    # 测试不同学习率
    learning_rates = [0.1, 0.01, 0.001, 0.0001]
    results = {}
    
    for lr in learning_rates:
        model = test_model()
        optimizer = AdamOptimizer(model.parameters(), lr=lr)
        
        # 简单训练循环
        losses = []
        for step in range(100):
            # 模拟数据
            inputs = torch.randn(32, 10)
            targets = torch.randn(32, 1)
            
            # 前向传播
            outputs = model(inputs)
            loss = nn.MSELoss()(outputs, targets)
            
            # 反向传播
            optimizer.zero_grad()
            loss.backward()
            optimizer.step()
            
            losses.append(loss.item())
        
        results[lr] = losses
    
    # 可视化结果
    plt.figure(figsize=(10, 6))
    for lr, losses in results.items():
        plt.plot(losses, label=f'LR={lr}')
    
    plt.xlabel('Step')
    plt.ylabel('Loss')
    plt.title('Learning Rate Search Results')
    plt.legend()
    plt.yscale('log')
    plt.grid(True)
    plt.show()
    
    return results

# 运行示例
lr_results = learning_rate_search_example()
practical_tips = practical_optimizer_tips()

第五部分:新兴优化器与未来展望

5.1 新兴优化器介绍

python 复制代码
class LionOptimizer:
    def __init__(self, params, lr=0.0001, beta1=0.9, beta2=0.99, weight_decay=0.0):
        """
        Lion优化器实现(EvoLved Sign Momentum)
        参考:https://arxiv.org/abs/2302.06675
        
        Args:
            params: 模型参数
            lr: 学习率
            beta1: 一阶矩衰减率
            beta2: 二阶矩衰减率
            weight_decay: 权重衰减系数
        """
        self.params = list(params)
        self.lr = lr
        self.beta1 = beta1
        self.beta2 = beta2
        self.weight_decay = weight_decay
        self.t = 0
        
        # 初始化矩估计
        self.m = [torch.zeros_like(param) for param in self.params]
    
    def zero_grad(self):
        """清零梯度"""
        for param in self.params:
            if param.grad is not None:
                param.grad.zero_()
    
    def step(self):
        """执行参数更新"""
        self.t += 1
        for i, param in enumerate(self.params):
            if param.grad is not None:
                # 更新矩估计
                self.m[i] = self.beta1 * self.m[i] + (1 - self.beta1) * param.grad.data
                
                # 计算更新方向(使用符号函数)
                update = torch.sign(self.m[i])
                
                # 应用权重衰减
                if self.weight_decay > 0:
                    param.data = param.data - self.lr * self.weight_decay * param.data
                
                # 更新参数
                param.data = param.data - self.lr * update

class AdaBeliefOptimizer:
    def __init__(self, params, lr=0.001, beta1=0.9, beta2=0.999, epsilon=1e-8):
        """
        AdaBelief优化器实现
        参考:https://arxiv.org/abs/2010.07468
        
        Args:
            params: 模型参数
            lr: 学习率
            beta1: 一阶矩衰减率
            beta2: 二阶矩衰减率
            epsilon: 小常数,防止除零
        """
        self.params = list(params)
        self.lr = lr
        self.beta1 = beta1
        self.beta2 = beta2
        self.epsilon = epsilon
        self.t = 0
        
        # 初始化矩估计
        self.m = [torch.zeros_like(param) for param in self.params]
        self.s = [torch.zeros_like(param) for param in self.params]  # 二阶矩估计
    
    def zero_grad(self):
        """清零梯度"""
        for param in self.params:
            if param.grad is not None:
                param.grad.zero_()
    
    def step(self):
        """执行参数更新"""
        self.t += 1
        for i, param in enumerate(self.params):
            if param.grad is not None:
                # 更新一阶矩估计
                self.m[i] = self.beta1 * self.m[i] + (1 - self.beta1) * param.grad.data
                
                # 更新二阶矩估计(基于梯度与一阶矩的偏差)
                grad_diff = param.grad.data - self.m[i]
                self.s[i] = self.beta2 * self.s[i] + (1 - self.beta2) * (grad_diff ** 2)
                
                # 偏差校正
                m_hat = self.m[i] / (1 - self.beta1 ** self.t)
                s_hat = self.s[i] / (1 - self.beta2 ** self.t)
                
                # 更新参数
                param.data = param.data - self.lr * m_hat / (torch.sqrt(s_hat) + self.epsilon)

# 测试新兴优化器
def test_emerging_optimizers():
    """测试新兴优化器的性能"""
    # 定义测试函数
    def difficult_function(x, y):
        """难以优化的函数"""
        return torch.sin(5 * x) * torch.cos(5 * y) + 0.5 * (x ** 2 + y ** 2)
    
    # 初始化参数
    x_lion = torch.tensor([2.0], requires_grad=True)
    x_adabelief = torch.tensor([2.0], requires_grad=True)
    
    # 优化器
    lion_optimizer = LionOptimizer([x_lion], lr=0.0001)
    adabelief_optimizer = AdaBeliefOptimizer([x_adabelief], lr=0.001)
    
    # 训练记录
    lion_losses = []
    adabelief_losses = []
    
    # 优化过程
    for step in range(1000):
        # Lion优化器
        lion_loss = difficult_function(x_lion, torch.tensor([1.0]))
        lion_optimizer.zero_grad()
        lion_loss.backward()
        lion_optimizer.step()
        lion_losses.append(lion_loss.item())
        
        # AdaBelief优化器
        adabelief_loss = difficult_function(x_adabelief, torch.tensor([1.0]))
        adabelief_optimizer.zero_grad()
        adabelief_loss.backward()
        adabelief_optimizer.step()
        adabelief_losses.append(adabelief_loss.item())
    
    # 可视化结果
    plt.figure(figsize=(10, 6))
    plt.plot(lion_losses, label='Lion')
    plt.plot(adabelief_losses, label='AdaBelief')
    plt.xlabel('Step')
    plt.ylabel('Loss')
    plt.title('Emerging Optimizers Performance')
    plt.legend()
    plt.grid(True)
    plt.yscale('log')
    plt.show()
    
    return lion_losses, adabelief_losses

# 运行测试
lion_loss, adabelief_loss = test_emerging_optimizers()

5.2 未来发展方向

优化器技术仍在快速发展中,未来的研究方向包括:

  1. 自动化优化器:自动调整超参数和选择优化算法
  2. 理论突破:更深入理解优化器的理论性质和收敛保证
  3. 硬件感知优化:针对特定硬件架构优化的算法
  4. 联邦学习优化:分布式环境下的高效优化算法
  5. 元学习优化器:学习如何优化的人工智能方法

结论

通过本文的全面探讨,我们对深度学习优化器的发展历程、原理机制和实践应用有了深入的理解。从最基础的SGD到现代的自适应优化器,每一种算法都有其独特的优势和适用场景。

关键收获

  1. 没有万能优化器:不同任务需要不同的优化器,需要根据具体问题选择
  2. 超参数至关重要:学习率、动量等参数对优化效果有极大影响
  3. 自适应优化器主导:Adam及其变体在大多数深度学习任务中表现优异
  4. 新兴技术不断涌现:Lion、AdaBelief等新优化器展现出巨大潜力

实践建议

  1. 从Adam开始:对于大多数任务,Adam是一个不错的起点
  2. 尝试SGD:如果追求最佳泛化性能,可以尝试SGD with momentum
  3. 学习率调优:始终关注学习率的影响,使用学习率调度策略
  4. 监控训练过程:密切关注训练和验证损失,及时调整策略

优化器选择是深度学习实践中的艺术与科学的结合。通过理解各种优化器的原理和特性,结合实际问题的特点,我们可以做出更明智的选择,从而训练出更高效、更强大的深度学习模型。

随着技术的不断发展,我们期待看到更多创新的优化算法出现,进一步推动深度学习领域的前进。


点击 "AladdinEdu,同学们用得起的【H卡】算力平台",注册即送-H卡级别算力80G大显存按量计费灵活弹性顶级配置学生更享专属优惠

相关推荐
axban2 小时前
QT M/V架构开发实战:QStandardItemModel介绍
开发语言·数据库·qt
dlraba8022 小时前
Python 实战:票据图像自动矫正技术拆解与落地教程
人工智能·opencv·计算机视觉
哈哈很哈哈2 小时前
构建高性能网络应用
网络
猿究院-赵晨鹤2 小时前
String、StringBuffer 和 StringBuilder 的区别
java·开发语言
emojiwoo2 小时前
HTTP 状态码背后的逻辑:从请求到响应的完整流程解析(含完整流程图)
网络·网络协议·http
I'm a winner2 小时前
第五章:Python 数据结构:列表、元组与字典(一)
开发语言·数据结构·python
葵野寺2 小时前
【RelayMQ】基于 Java 实现轻量级消息队列(九)
java·开发语言·rabbitmq·java-rabbitmq
过河卒_zh15667662 小时前
9.13AI简报丨哈佛医学院开源AI模型,Genspark推出AI浏览器
人工智能·算法·microsoft·aigc·算法备案·生成合成类算法备案
青草地溪水旁2 小时前
Linux epoll 事件模型终极指南:深入解析 epoll_event 与事件类型
linux·epoll