1、SGD原理介绍

随机梯度下降(Stochastic Gradient Descent,简称SGD)是基于梯度的一种优化算法,用于寻找损失函数最小化的参数配置。SGD通过计算每个样本的梯度来更新参数,并在每次更新中随机选择一个或一批样本。SGD的原理相对简单,它通过计算损失函数对每个训练样本的梯度来更新参数。具体步骤如下:

1、随机选择一个训练样本;
2、计算该样本的梯度;
3、使用梯度值和学习率来更新参数;
4、重复以上步骤,直至达到收敛条件或达到指定迭代次数。

SGD在深度学习中广泛应用于模型的训练过程,特别是在大规模数据集和复杂模型的情况下。由于SGD的简单性和高效性,它成为了优化算法领域的基准方法。

2、SGD参数介绍

torch.optim.SGD(params, lr=<required parameter>, momentum=0, dampening=0, weight_decay=0, nesterov=False)

params(必须参数): 这是一个包含了需要优化的参数(张量)的迭代器,例如模型的参数 model.parameters()。

lr(必须参数): 学习率(learning rate)。它是一个正数,控制每次参数更新的步长。较小的学习率会导致收敛较慢,较大的学习率可能导致震荡或无法收敛。

momentum(默认值为 0): 动量(momentum)是一个用于加速 SGD 收敛的参数。它引入了上一步梯度的指数加权平均。通常设置在 0 到 1 之间。当 momentum 大于 0 时,算法在更新时会考虑之前的梯度,有助于加速收敛。

dampening(默认值为 0): 阻尼项,用于减缓动量的速度。在某些情况下,为了防止动量项引起的震荡,可以设置一个小的 dampening 值。

weight_decay(默认值为 0): 权重衰减,也称为 L2 正则化项。它用于控制参数的幅度,以防止过拟合。通常设置为一个小的正数。

nesterov(默认值为 False): Nesterov 动量。当设置为 True 时,采用 Nesterov 动量更新规则。Nesterov 动量在梯度更新之前先进行一次预测,然后在计算梯度更新时使用这个预测。

3、SGD应用介绍

import torch
import torch.optim as optim
 
# 定义模型和损失函数
model = torch.nn.Linear(10, 1)
criterion = torch.nn.MSELoss()
 
# 定义优化器
optimizer = optim.SGD(model.parameters(), lr=0.01, momentum=0.9, weight_decay=0.0001)
 
# 在训练循环中使用优化器
for epoch in range(epochs):
    # Forward pass
    output = model(input_data)
    loss = criterion(output, target)
 
    # Backward pass and optimization
    optimizer.zero_grad()
    loss.backward()
    optimizer.step()

在上述示例中,创建了一个线性模型,使用均方误差损失,并使用 torch.optim.SGD 作为优化器。在训练循环中,通过执行前向传播、反向传播和优化步骤来更新模型参数。

4、SGD手动实现

PyTorch中SGD的源码比较复杂,很多C++底层的优化,我们可以手动实现一个简化版的SGD,从而加深对SGD优化器的理解。

import torch
from .optimizer import Optimizer, required


class SGD(Optimizer):
   
    def __init__(self, params, lr=required, momentum=0, dampening=0,
                 weight_decay=0, nesterov=False):
        if lr is not required and lr < 0.0:
            raise ValueError("Invalid learning rate: {}".format(lr))
        if momentum < 0.0:
            raise ValueError("Invalid momentum value: {}".format(momentum))
        if weight_decay < 0.0:
            raise ValueError("Invalid weight_decay value: {}".format(weight_decay))

        defaults = dict(lr=lr, momentum=momentum, dampening=dampening,
                        weight_decay=weight_decay, nesterov=nesterov)
        if nesterov and (momentum <= 0 or dampening != 0):
            raise ValueError("Nesterov momentum requires a momentum and zero dampening")
        super(SGD, self).__init__(params, defaults)

    def __setstate__(self, state):
        super(SGD, self).__setstate__(state)
        for group in self.param_groups:
            group.setdefault('nesterov', False)

    def step(self, closure=None):
        """Performs a single optimization step.
        Arguments:
            closure (callable, optional): A closure that reevaluates the model
                and returns the loss.
        """
        loss = None
        if closure is not None:
            loss = closure()

        for group in self.param_groups:
            weight_decay = group['weight_decay']
            momentum = group['momentum']
            dampening = group['dampening']
            nesterov = group['nesterov']

            for p in group['params']:
                if p.grad is None:
                    continue
                d_p = p.grad.data
                if weight_decay != 0:
                    d_p = d_p.add(weight_decay, p.data)
                if momentum != 0:
                    param_state = self.state[p]
                    if 'momentum_buffer' not in param_state:
                        buf = param_state['momentum_buffer'] = torch.clone(d_p).detach()
                    else:
                        buf = param_state['momentum_buffer']
                        buf.mul_(momentum).add_(1 - dampening, d_p)
                    if nesterov:
                        d_p = d_p.add(momentum, buf)
                    else:
                        d_p = buf

                p.data.add_(-group['lr'], d_p)

        return loss

5、SGD的改进

凡事都有两面性,SGD每次仅使用一个样本或一批样本进行模型参数更新,相比于使用全部样本的批量梯度下降(BGD),计算成本更低。但是,由于SGD每次更新只使用一个样本或一批样本,更新方向可能受噪声的干扰而波动较大。

对于SGD,合适的学习率的选择至关重要。学习率过大可能导致振荡,学习率过小则收敛速度缓慢。为了克服SGD的缺点,研究者们提出了一系列改进的优化方法:

1、动量法(Momentum):引入一个动量项,使更新方向在梯度变化较大的维度上具有一定的惯性,从而加速收敛并减少震荡。
2、学习率衰减(Learning Rate Decay):随着训练的进行,逐渐减小学习率,有助于在接近损失函数最小值时平稳收敛。
3、Adagrad:自适应学习率的方法,根据参数在每次迭代中的梯度大小自适应地调整学习率。
4、RMSprop:对Adagrad进行改进,通过平均梯度的平方来调整学习率,从而缓解训练过程中的震荡。
5、Adam:结合了Momentum和Adagrad的优点,并进行了一定的改进和修正,是目前最流行和常用的优化算法之一。