深度学习中,用损失的均值或者总和反向传播的区别

如深度学习中代码:

def train_epoch_ch3(net, train_iter, loss, updater):
    """The training loop defined in Chapter 3."""
    # Set the model to training mode
    if isinstance(net, torch.nn.Module):
        net.train()
    # Sum of training loss, sum of training accuracy, no. of examples
    metric = Accumulator(3)
    for X, y in train_iter:
        # Compute gradients and update parameters
        y_hat = net(X)
        l = loss(y_hat, y)
        if isinstance(updater, torch.optim.Optimizer):
            # Using PyTorch in-built optimizer & loss criterion
            updater.zero_grad()
            l.backward()
            updater.step()
            metric.add(float(l) * len(y), accuracy(y_hat, y), y.numel())
        else:
            # Using custom built optimizer & loss criterion
            l.sum().backward()
            updater(X.shape[0])
            metric.add(float(l.sum()), accuracy(y_hat, y), y.numel())
    # Return training loss and training accuracy
    return metric[0] / metric[2], metric[1] / metric[2]

对于循环for X, y in train_iter:每一次迭代都是一次小批量训练

当用torch里的优化器来更新参数时,第一个if语句执行:

1.此时由于torch默认累计梯度,所以每次循环都得梯度置零

2.然后torch进行反向传播(如果是使用了torch定义的loss,那么得到的l是标量,即平均损失,次此时直接反向传播即可)。

loss = nn.CrossEntropyLoss(reduction='mean'),reduction='mean'意味着损失会返回均值,reduction理解成降维

loss = nn.CrossEntropyLoss(reduction='None'),reduction='None'意味着损失会返回原来的形式,即矢量

为什么不用总和形式呢?均值会好一点,因为每次训练有不同的batch_size(如果我把样本分成三个批量,一次训练中,首先用批量一训练,然后批量二,批量三,然后批量都训练完后,再进行新的一轮训练,直到收敛),用均值的话会默认使用l.sum()/batch_size,batch_size由内部求出,这个不用自己写,就实现了batch_size的解耦,不容易出错,更新参数时形式就变成了,因为此时的损失是均值了

3.进行参数更新

4.统计损失与精度,为什么要用float(l)*len(y)呢

因为l=loss(y_hat,y),这里的损失函数是从外部传进来的,所以如果传进来的是torch自己定义的损失函数,应该计算的是平均损失,那么要变成整体损失就得float(l)*len(y)。

如果loss是自己定义的,可能只是计算了损失,没求平均(看上面的函数,此时的loss应该是矢量,所以才会有l.sum())

当自己实现优化器来更新参数时,else语句执行:

1.此时自己可能没实现累计梯度,所以不用梯度清零

2.反向传播,但由于此时自己实现了loss函数,可能loss是矢量,要转成标量来反向传播,一个矢量转标量的好方法就是求和,即l.sum().backward(),

3.更新参数,注意到updater传入了一个参数X.shape[0],即样本数量batch_size,外边要注意更新参数时为,因为此时的l是总和

4.统计损失与精度

sum和mean其实主要影响了"梯度的大小",反向传播时,依据损失求梯度,如果是sum,则梯度会比mean大n倍,那么在学习率不变的情况下,步子会迈得很长,体现到图形上就是正确率提升不了。所以需要缩小学习率。

l.mean().backward()和l.sum().backward()的区别在于它们计算梯度的方式。l.mean().backward()计算的是平均损失对权重的梯度,而l.sum().backward()计算的是总损失对权重的梯度。

在实践中,这两种方式通常会得到相似的结果,因为它们都是在尝试最小化损失。然而,使用l.mean().backward()可能会使得梯度的大小更稳定,因为它不会因为批量大小的变化而变化。这可能会使得训练过程更稳定,特别是在批量大小可能变化的情况下。

另一方面,使用l.sum().backward()可能会使得梯度的大小更大,这可能会导致训练过程更快,但也可能导致训练过程更不稳定。

总的来说,哪种方式更好取决于你的具体情况。如果你的批量大小是固定的,那么你可能会发现l.sum().backward()和l.mean().backward()在实践中没有太大的区别。如果你的批量大小可能变化,那么你可能会发现l.mean().backward()在实践中更稳定

补:总训练函数:

def train_ch3(net, train_iter, test_iter, loss, num_epochs, updater):
    """Train a model (defined in Chapter 3)."""
    animator = Animator(xlabel='epoch', xlim=[1, num_epochs], ylim=[0.3, 0.9],
                        legend=['train loss', 'train acc', 'test acc'])
    for epoch in range(num_epochs):
        train_metrics = train_epoch_ch3(net, train_iter, loss, updater)
        test_acc = evaluate_accuracy(net, test_iter)
        animator.add(epoch + 1, train_metrics + (test_acc,))
    train_loss, train_acc = train_metrics
    assert train_loss < 0.5, train_loss
    assert train_acc <= 1 and train_acc > 0.7, train_acc
    assert test_acc <= 1 and test_acc > 0.7, test_acc
相关推荐
JD技术委员会1 分钟前
Rust 语法噪音这么多,是否适合复杂项目?
开发语言·人工智能·rust
liruiqiang056 分钟前
机器学习 - 投票感知器
人工智能·算法·机器学习
刘什么洋啊Zz3 小时前
MacOS下使用Ollama本地构建DeepSeek并使用本地Dify构建AI应用
人工智能·macos·ai·ollama·deepseek
奔跑草-4 小时前
【拥抱AI】GPT Researcher 源码试跑成功的心得与总结
人工智能·gpt·ai搜索·deep research·深度检索
禁默5 小时前
【第四届网络安全、人工智能与数字经济国际学术会议(CSAIDE 2025】网络安全,人工智能,数字经济的研究
人工智能·安全·web安全·数字经济·学术论文
boooo_hhh6 小时前
深度学习笔记16-VGG-16算法-Pytorch实现人脸识别
pytorch·深度学习·机器学习
AnnyYoung6 小时前
华为云deepseek大模型平台:deepseek满血版
人工智能·ai·华为云
INDEMIND7 小时前
INDEMIND:AI视觉赋能服务机器人,“零”碰撞避障技术实现全天候安全
人工智能·视觉导航·服务机器人·商用机器人
慕容木木7 小时前
【全网最全教程】使用最强DeepSeekR1+联网的火山引擎,没有生成长度限制,DeepSeek本体的替代品,可本地部署+知识库,注册即可有750w的token使用
人工智能·火山引擎·deepseek·deepseek r1
南 阳7 小时前
百度搜索全面接入DeepSeek-R1满血版:AI与搜索的全新融合
人工智能·chatgpt