diff --git a/docs/chapter4/4.2.md b/docs/chapter4/4.2.md index 1f31357..7c06679 100644 --- a/docs/chapter4/4.2.md +++ b/docs/chapter4/4.2.md @@ -387,3 +387,72 @@ tensor([ 5.5010, -18.3851], requires_grad=True)
图4.13 训练(蓝色)和验证(红色)损失曲线图。(A)训练和验证损失没有下降;由于数据中没有信息或模型拟合能力不足,该模型无法学习。(B)训练损失下降而验证损失增加(过拟合)。(C)训练和验证损失同步减少;由于模型并非处于过拟合的极限,因此性能可能会进一步提高。(D)训练和验证损失具有不同的绝对值但趋势相似;过拟合在可控之内。
+## 4.2.3 不需要时关闭`autograd` + +从训练循环中,你注意到只能在`train_loss`上调用`backward`。因此,误差只会根据训练集来进行反向传播。验证集用于在未用于训练的数据上对模型输出的准确性进行独立的评估。 + +好奇的读者此时可能会有一个问题。我们对模型进行了两次评估(一次在`train_t_u`上,一次在`val_t_u`上),然后调用一次`backward`。这不会使`autograd`变得混乱吗?验证集评估过程中生成的值不会影响`backward`吗? + +幸运的是,事实并非如此。训练循环中的第一行在`train_t_u`上对模型进行评估以产生`train_t_p`。然后用`train_t_p`计算`train_loss`,创建一个链接从`train_t_u`到`train_t_p`再到`train_loss`的计算图。当在`val_t_u`上再次评估模型然后生成`val_t_p`和`val_loss`时,将创建一个单独的计算图,该图链接从`val_t_u`到`val_t_p`再到`val_loss`。单独的张量通过相同的函数`model`和`loss_fn`运行,生成了单独的计算图,如图4.14所示。 + +
+4.14 +
+
图4.14 本图显示了如果计算图有两个损失,对其中一个调用`.backward`时梯度如何在计算图中传播
+ +这两个图唯一的共同点是参数。当在`train_loss`上调用`backward`时,`backward`函数将在第一个图上运行。换句话说,你是基于`train_t_u`来累积`train_loss`相对于参数的导数的。 + +如果你还(错误地)对`val_loss`调用了`backward`,那么你将在相同叶节点上累积`val_loss`相对于参数的导数值。还记得`zero_grad`吗,除非你明确地将梯度清零,否则每次调用`backward`时,梯度都会进行累积?这里就会发生类似的事情:对`val_loss`调用`backward`会导致梯度累积在`trainsloss.backward()`执行期间生成的梯度之上。此时,你将在整个数据集(训练集加上验证集)上有效地训练模型,因为梯度将取决于两者。真是有趣。 + +还有一个点需要讨论:因为你永远不会对`val_loss`调用`backward`,所以为什么要构建图形?实际上,此时你完全可以将`model`和`loss_fn`当作普通函数而无需追踪计算历史。无论经过怎样的优化,追踪计算历史都会带来额外的代价,所以你应该在验证过程中避免这些代价,尤其是当模型具有数百万个参数时。 + +为了解决这个问题,PyTorch允许你通过使用`torch.no_grad`上下文管理器在不需要时关闭`autograd`。虽然就小规模问题而言,在速度或内存消耗方面没有任何有意义的优势。但是对于较大的问题,差别可能会很明显。你可以通过检查`val_loss`张量上`require_grad`属性的值来确保此上下文管理器正常工作: + +``` python +def training_loop(n_epochs, optimizer, params, + train_t_u, val_t_u, train_t_c, val_t_c): + for epoch in range(1, n_epochs + 1): + train_t_p = model(train_t_u, *params) + train_loss = loss_fn(train_t_p, train_t_c) + + with torch.no_grad(): + val_t_p = model(val_t_u, *params) + val_loss = loss_fn(val_t_p, val_t_c) + assert val_loss.requires_grad == False + + optimizer.zero_grad() + train_loss.backward() + optimizer.step() +``` + +使用相关的`set_grad_enabled`上下文管理器,你还可以根据布尔表达式(通常表示在训练还是在推理中)来调节代码在启用或禁用`autograd`的情况下运行。你可以定义一个`calc_forward`函数,该函数接受输入中的数据,并根据布尔值`is_train`参数运行带或不带`autograd`的`model`和`loss_fn`: + +``` python +def calc_forward(t_u, t_c, is_train): + with torch.set_grad_enabled(is_train): + t_p = model(t_u, *params) + loss = loss_fn(t_p, t_c) + return loss +``` + + +## 小结 + +本章从一个大问题开始:机器如何从示例中学习?我们在本章的其余部分中描述了优化模型以拟合数据的机制。我们选择坚持使用简单的模型来显示所有必须的部件而去掉不必要的复杂性。 + +## 练习 +* 将模型重新定义为 $w_2 t^2_u+w_1 t_u + b$: + * 必须更改训练循环的哪些部分等以适应此重新定义?哪些部分是与模型无关的? + * 训练后的损失值变得更高还是更低?结果更好还是更差? + +## 总结 +* 线性模型是用于拟合数据的合理的最简单的模型; +* 凸优化技术可以用于线性模型,但不能推广到神经网络,因此本章重点介绍参数估计。 +* 深度学习可用于通用模型,这些通用模型不是为解决特定任务而设计的,而是可以自动调整以专门解决眼前的问题。 +* 学习算法等于根据观察结果优化模型的参数。损失函数是对执行任务中的错误的一种度量,例如预测输出值和测量值之间的误差。目标就是使损失函数值尽可能低。 +* 损失函数关于模型参数的变化率可用于在减少损失的方向上更新该参数。 +* PyTorch中的`optim`模块提供了一组现成的优化器,用于更新参数和最小化损失函数。 +* 优化器使用PyTorch的`autograd`来计算每个参数的梯度,而梯度具体取决于该参数对最终输出的贡献程度。`autograd`允许用户在复杂的前向通过过程中依赖于动态计算图。 +* 诸如`torch.no_grad()`的上下文管理器可用于控制是否需要自动求导。 +* 数据通常划分为独立的训练集和验证集,从而可以在未训练的数据(即验证集)上进行模型评估。 +* 当模型的性能在训练集上继续提高但在验证集上下降时,模型就发生了过拟合。这种情况通常发生在模型无法泛化(到训练集之外的数据)而是记住了训练集所需的输出时。 diff --git a/docs/img/chapter4/4.14.png b/docs/img/chapter4/4.14.png new file mode 100644 index 0000000..efe5133 Binary files /dev/null and b/docs/img/chapter4/4.14.png differ