李沐《动手学深度学习》第三章报错?手把手教你手动给d2l.torch添加train_ch3函数
深度学习实践指南:手动修复d2l.torch缺失函数实战
最近在跟着李沐老师的《动手学深度学习》课程学习时,不少同学反馈遇到了"module 'd2l.torch' has no attribute 'train_ch3'"的报错。这个问题通常是由于d2l库版本更新导致的函数变动,而课程示例代码使用的是旧版函数。本文将带你深入理解问题本质,并提供一套跨平台的解决方案,让你不必纠结于版本回退,直接通过源码修改解决问题。
1. 问题诊断与环境准备
遇到"AttributeError: module 'd2l.torch' has no attribute 'train_ch3'"这类错误时,首先要明确问题的根源。d2l库作为深度学习教学工具包,会随着PyTorch等框架的更新而不断迭代,这就可能导致旧版教程中的部分函数在新版本中被重构或移除。
验证问题步骤:
- 确认已安装d2l库:
pip show d2l - 检查当前版本:
python -c "import d2l; print(d2l.__version__)" - 尝试导入函数:
python -c "from d2l.torch import train_ch3"
如果确实遇到缺失函数的问题,传统解决方案是安装指定旧版本:
pip install d2l==0.17.5
但这种方法可能引发更多依赖冲突。更优雅的解决方案是手动添加缺失函数到当前安装的d2l包中。
2. 定位d2l包安装路径
不同操作系统下,Python包的安装路径有所差异。以下是各平台的查找方法:
2.1 通用方法:使用Python命令查找
无论使用哪种操作系统,都可以通过Python的site模块查找包路径:
python -m site
输出示例:
sys.path = [
'/opt/miniconda3/envs/dl/lib/python39.zip',
'/opt/miniconda3/envs/dl/lib/python3.9',
'/opt/miniconda3/envs/dl/lib/python3.9/lib-dynload',
'/opt/miniconda3/envs/dl/lib/python3.9/site-packages',
]
其中site-packages目录就是第三方包的安装位置。
2.2 各操作系统特有路径
| 操作系统 | 典型路径 | 说明 |
|---|---|---|
| Windows | C:\Users\<用户名>\AppData\Local\Programs\Python\Python39\Lib\site-packages |
默认Python安装路径 |
| macOS | /opt/miniconda3/envs/<环境名>/lib/python3.9/site-packages |
Conda环境路径 |
| Linux | /usr/local/lib/python3.9/site-packages |
系统级Python安装 |
提示:如果使用虚拟环境,路径会包含环境名。使用
conda info --envs查看所有环境列表。
3. 编辑d2l/torch.py文件
找到site-packages目录后,进入d2l子目录,找到torch.py文件。这是我们需要修改的核心文件。
3.1 文件编辑步骤
- 导航到d2l目录:
cd /path/to/site-packages/d2l
- 备份原始文件(重要!):
cp torch.py torch.py.bak
- 使用编辑器打开文件:
- Vim/Neovim:
vim torch.py - VS Code:
code torch.py - 其他GUI编辑器直接双击打开
- Vim/Neovim:
3.2 添加缺失函数代码
在torch.py文件中找到合适的位置(通常在类似功能的函数附近),添加以下代码:
def evaluate_accuracy(net, data_iter): #@save
"""计算在指定数据集上模型的精度"""
if isinstance(net, torch.nn.Module):
net.eval() # 将模型设置为评估模式
metric = Accumulator(2) # 正确预测数、预测总数
with torch.no_grad():
for X, y in data_iter:
metric.add(accuracy(net(X), y), y.numel())
return metric[0] / metric[1]
def train_epoch_ch3(net, train_iter, loss, updater): #@save
"""训练模型一个迭代周期(定义见第3章)"""
# 将模型设置为训练模式
if isinstance(net, torch.nn.Module):
net.train()
# 训练损失总和、训练准确度总和、样本数
metric = Accumulator(3)
for X, y in train_iter:
# 计算梯度并更新参数
y_hat = net(X)
l = loss(y_hat, y)
if isinstance(updater, torch.optim.Optimizer):
# 使用PyTorch内置的优化器和损失函数
updater.zero_grad()
l.mean().backward()
updater.step()
else:
# 使用定制的优化器和损失函数
l.sum().backward()
updater(X.shape[0])
metric.add(float(l.sum()), accuracy(y_hat, y), y.numel())
# 返回训练损失和训练精度
return metric[0] / metric[2], metric[1] / metric[2]
def train_ch3(net, train_iter, test_iter, loss, num_epochs, updater): #@save
"""训练模型(定义见第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
3.3 编辑器操作指南
不同编辑器的保存方式:
-
Vim:
- 按
i进入插入模式 - 粘贴代码
- 按
Esc退出插入模式 - 输入
:wq保存并退出
- 按
-
VS Code:
- 直接编辑文件
Ctrl+S保存- 关闭窗口
-
记事本/TextEdit:
- 注意保存为.py格式
- 确保编码为UTF-8
4. 验证修改结果
完成编辑后,需要验证修改是否生效:
- 在Python交互环境中测试:
from d2l.torch import train_ch3
print(train_ch3.__doc__) # 查看函数文档字符串
-
重新运行课程第三章的示例代码,确认不再报错。
-
如果遇到
Animator或Accumulator未定义错误,同样需要在torch.py中添加这些辅助类:
class Accumulator: #@save
"""在n个变量上累加"""
def __init__(self, n):
self.data = [0.0] * n
def add(self, *args):
self.data = [a + float(b) for a, b in zip(self.data, args)]
def reset(self):
self.data = [0.0] * len(self.data)
def __getitem__(self, idx):
return self.data[idx]
class Animator: #@save
"""在动画中绘制数据"""
def __init__(self, xlabel=None, ylabel=None, legend=None, xlim=None,
ylim=None, xscale='linear', yscale='linear',
fmts=('-', 'm--', 'g-.', 'r:'), nrows=1, ncols=1,
figsize=(3.5, 2.5)):
# 增量地绘制多条线
if legend is None:
legend = []
self.fig, self.axes = plt.subplots(nrows, ncols, figsize=figsize)
if nrows * ncols == 1:
self.axes = [self.axes, ]
# 使用lambda函数捕获参数
self.config_axes = lambda: set_axes(
self.axes[0], xlabel, ylabel, xlim, ylim, xscale, yscale, legend)
self.X, self.Y, self.fmts = None, None, fmts
def add(self, x, y):
# 向图表中添加多个数据点
if not hasattr(y, "__len__"):
y = [y]
n = len(y)
if not hasattr(x, "__len__"):
x = [x] * n
if not self.X:
self.X = [[] for _ in range(n)]
if not self.Y:
self.Y = [[] for _ in range(n)]
for i, (a, b) in enumerate(zip(x, y)):
if a is not None and b is not None:
self.X[i].append(a)
self.Y[i].append(b)
self.axes[0].cla()
for x, y, fmt in zip(self.X, self.Y, self.fmts):
self.axes[0].plot(x, y, fmt)
self.config_axes()
display.display(self.fig)
display.clear_output(wait=True)
5. 高级技巧与注意事项
5.1 函数位置选择策略
在torch.py中添加新函数时,位置选择很重要:
- 按功能分组:将训练相关函数放在一起
- 按章节排序:跟随教材章节顺序排列
- 依赖关系:被调用的函数应放在调用它的函数之前
5.2 版本兼容性处理
为避免未来更新覆盖修改,可以考虑:
-
创建本地副本:将d2l目录复制到项目文件夹,修改sys.path优先导入本地副本
import sys sys.path.insert(0, '/path/to/local/d2l') -
创建补丁文件:将修改单独保存为patch文件,方便重复应用
diff -u original.py modified.py > d2l_patch.diff patch torch.py < d2l_patch.diff
5.3 常见问题排查
-
权限问题:在Linux/macOS上可能需要sudo权限
sudo chmod -R 777 /path/to/site-packages -
缓存问题:Python可能会缓存模块,修改后重启解释器
-
编码问题:确保文件保存为UTF-8编码,特别是Windows系统
这种手动修改源码的方法不仅适用于d2l库,对于其他开源库的类似问题也同样有效。掌握了这项技能,你就能更灵活地应对各种版本兼容性问题,而不必被版本依赖所束缚。
更多推荐
所有评论(0)