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

如深度学习中代码:

复制代码
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
相关推荐
Mintopia1 天前
OpenClaw 对软件行业产生的影响
人工智能
陈广亮1 天前
构建具有长期记忆的 AI Agent:从设计模式到生产实践
人工智能
会写代码的柯基犬1 天前
DeepSeek vs Kimi vs Qwen —— AI 生成俄罗斯方块代码效果横评
人工智能·llm
Mintopia1 天前
OpenClaw 是什么?为什么节后热度如此之高?
人工智能
爱可生开源社区1 天前
DBA 的未来?八位行业先锋的年度圆桌讨论
人工智能·dba
叁两1 天前
用opencode打造全自动公众号写作流水线,AI 代笔太香了!
前端·人工智能·agent
前端付豪1 天前
LangChain记忆:通过Memory记住上次的对话细节
人工智能·python·langchain
strayCat232551 天前
Clawdbot 源码解读 7: 扩展机制
人工智能·开源
程序员打怪兽1 天前
详解Visual Transformer (ViT)网络模型
深度学习