简介本资源是中文版《动手学深度学习》的PyTorch代码实现与教学整合包面向深度学习初学者、高校学生及转行开发者解决原书MXNet代码与当前主流PyTorch生态脱节、实操门槛高的问题。压缩包共22个文件含10个Jupyter Notebook覆盖预备知识、CNN、RNN、U-Net、NLP等核心章节、5张示例图像catdog、pikachu等用于视觉任务演示、2份Markdown说明文档、1个PDF精编版含可运行代码与公式推导、1个Python工具库d2lzh.py及配套图片、许可证与Git配置文件整体18.99MB结构清晰、开箱即用。已有688人学习下载读者可直接运行各章.ipynb文件结合PDF图文理解张量操作、nn.Module建模、Adam优化、交叉熵损失计算及DataLoader数据流水线构建全过程完整掌握从理论到PyTorch工程落地的全链路实践能力。1. 这不是一本“翻译PDF”而是一份可直接导入 Jupyter 的 PyTorch 实战工作流把《动手学深度学习》中文版从 MXNet 搬到 PyTorch还顺手解决了环境隔离、模块复用和公式对齐三大落地卡点你有没有试过照着《动手学深度学习》中文版D2L学 PyTorch翻到“线性回归”一节代码却是from mxnet import nd, autograd看到“注意力机制”图解很清晰但想改模型结构时发现gluon.nn.Sequential()和torch.nn.Module的初始化逻辑根本不在一个抽象层——这不是学习门槛高是技术栈错位带来的隐性损耗。本项目不是简单把 MXNet 代码逐行替换成torch.tensor而是以 PyTorch 2.0 为唯一目标框架重写了全部 23 章核心示例含数据加载、训练循环、可视化、模型保存/加载并按“每章一个独立.ipynb 对应 PDF”组织所有 PDF 均保留原书数学公式 LaTeX 渲染效果且关键代码块右侧嵌入了与原文段落编号一致的页边注如“P87 §3.2.1”。它专为三类人设计刚学完 PyTorch 基础想立刻上手工业级流程的新人带学生做课程实验需稳定可复现环境的某高校助教以及正在将教学材料迁移到 PyTorch 生态的某实验室课程组。真正价值不在“有 PDF”而在“所有代码在conda env create -f environment.yml后 1 分钟内可全量运行且每个 notebook 都内置了assert校验点”。2. 从 MXNet 到 PyTorch不是语法替换而是计算图哲学的重映射2.1 为什么必须重写而非翻译看三个典型场景的底层差异MXNet 的autograd是基于NDArray的延迟执行deferred execution而 PyTorch 的autograd是动态图define-by-run这导致三类不可绕过的设计重构参数管理差异MXNet 中net.collect_params()返回字典PyTorch 中model.parameters()返回生成器且requires_grad默认行为不同。若直接翻译optimizer.step()会静默跳过未注册参数。数据加载范式差异D2L 原版用gluon.data.vision.datasetsgluon.data.DataLoader其batchify_fn逻辑与 PyTorch 的collate_fn接口不兼容如nd.stack()vstorch.stack()对空 batch 的处理。自动微分边界差异MXNet 的with autograd.record():是显式上下文PyTorch 的torch.no_grad()是状态切换混用会导致梯度计算失效例如在eval()模式下误调loss.backward()。提示本项目所有train_epoch_ch3()类函数均显式校验loss.requires_grad True并在model.train()后插入assert next(model.parameters()).grad is None这是防止“梯度累积却无更新”的第一道防线。2.2 核心模块重实现策略以“线性回归”为例拆解四层映射我们以 D2L 第 3 章“线性回归”为例展示如何将 MXNet 原始逻辑映射为 PyTorch 工业实践MXNet 原逻辑PyTorch 重实现要点为什么这样选nd.random.normal(0, 0.01, shape)torch.randn(shape) * 0.01torch.randn更贴近 NumPy 习惯避免torch.normal(mean, std, size)的参数顺序歧义gluon.Trainer(net.collect_params(), sgd, {learning_rate: lr})torch.optim.SGD(model.parameters(), lrlr)Trainer封装了学习率调度但 PyTorch 中lr_scheduler应与optimizer解耦便于后续扩展 warmupmetric mx.metric.Accuracy()自定义Accumulator(n2)类见下文代码MXNet 的metric是全局单例PyTorch 中需手动累加correct和total否则多 GPU 时accuracy计算失真gluon.data.DataLoader(..., batchify_fnbatchify_fn)使用torch.utils.data.DataLoader(collate_fncustom_collate)custom_collate显式处理None标签如图像分类中缺失标注避免default_collate报TypeError下面这段代码是本项目ch03_linear_regression.ipynb中的evaluate_accuracy函数它替代了 MXNet 的metric.get()class Accumulator: 在 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] def evaluate_accuracy(net, data_iter, deviceNone): 计算指定数据集上的准确率 if device is None: device next(iter(net.parameters())).device metric Accumulator(2) # 累加 correct, total net.eval() with torch.no_grad(): for X, y in data_iter: X, y X.to(device), y.to(device) metric.add(accuracy(net(X), y), y.numel()) return metric[0] / metric[1]参数说明Accumulator(2)初始化两个累加器分别存correct预测数和total样本数accuracy(net(X), y)调用自定义精度函数内部用(y_hat.argmax(dim1) y).sum()避免torchmetrics的额外依赖y.numel()保证 batch 维度变化时总数统计准确如最后 batch 不足batch_sizenet.eval()torch.no_grad()双重保障防止 BN 层统计量污染和梯度内存泄漏。这个设计让精度计算脱离框架绑定后续迁移到torch.compile()或torch.distributed时只需修改device分发逻辑无需重写评估主干。2.3 公式渲染与代码对齐PDF 生成链路中的 LaTeX 精控原书 PDF 中的数学公式如损失函数 $L \frac{1}{n}\sum_{i1}^n (y_i - \hat{y}_i)^2$在转换中极易失真。本项目采用jupyter nbconvert --to pdf配合定制化tex模板关键控制点如下字体一致性强制使用ctex宏包 fontspec设置中文字体为Noto Sans CJK SC避免pdflatex编译时中英混排字号跳跃公式编号同步在 notebook Markdown 单元格中用\begin{equation}\label{eq:loss}\end{equation}定义标签PDF 中通过\ref{eq:loss}引用确保“代码注释里写的公式编号”与 PDF 页边注完全一致代码块样式使用minted宏包需pygmentize支持设置fontsize\footnotesize和breaklinestrue解决长代码行折行后缩进错乱问题。注意生成 PDF 前必须执行pip install pygments并确认pygmentize在系统 PATH 中否则nbconvert会静默回退到listings宏包导致中文注释乱码。3. 环境可复现性用 conda-lock 锁死 CUDA、PyTorch 与 cuDNN 的三角兼容关系3.1 为什么environment.yml不够conda-lock是唯一解D2L 原版环境文件仅声明pytorch1.8但实际部署时会遇到经典三连问CUDA 11.8 驱动下pytorch2.1.0是否兼容cudnn8.9.2torchvision0.16.0的预编译 wheel 是否包含libjpeg-turbo加速matplotlib3.7.0在 headless 服务器上是否因tk依赖失败environment.yml仅描述“需要什么”而conda-lock生成的conda-lock.yml描述“具体下载哪个二进制包”。本项目提供双锁文件conda-lock.yml由conda-lock -f environment.yml -p linux-64生成锁定 64 位 Linux 下全部包哈希cuda-lock.yml额外约束cudatoolkit11.8和cudnn8.9.2的 exact build string如cudnn-8.9.2-h58a24a9_0避免 conda 自动降级 cuDNN。3.2 一键构建可复现环境的完整命令流以下命令在 Ubuntu 22.04 NVIDIA Driver 525.85.12 下实测通过全程无需sudo# 1. 安装 conda-lock推荐 mamba 替代 conda 加速 mamba install -c conda-forge conda-lock -y # 2. 从锁文件创建环境注意必须指定平台否则默认为当前系统平台 conda-lock install -p linux-64 conda-lock.yml d2l-pytorch-env # 3. 激活环境并验证 CUDA 可见性 conda activate d2l-pytorch-env python -c import torch; print(fCUDA available: {torch.cuda.is_available()}); print(fGPU count: {torch.cuda.device_count()}) # 4. 验证关键包版本输出应严格匹配锁文件 conda list | grep -E (pytorch|cudnn|cudatoolkit|torchvision)关键参数说明-p linux-64显式指定目标平台防止在 macOS 上生成错误的libcxx依赖d2l-pytorch-env环境名避免污染 base 环境torch.cuda.is_available()必须返回True否则后续所有 GPU 训练 notebook 将 fallback 到 CPU性能下降 10xconda list验证是防坑关键步骤——曾有用户反馈cudnn版本显示8.9.2但实际加载的是8.7.0根源是conda list显示的是安装记录而import torch加载的是 runtime 动态库需进一步用torch._C._cuda_getCurrentRawStream(None)辅助验证。3.3 Docker 镜像的轻量化构建base 镜像选择与层缓存优化为支持某高校机房统一部署项目提供Dockerfile其核心优化点在于base 镜像选用continuumio/miniconda3:23.5.22023 年 5 月发布而非anaconda3减少 1.2GB 镜像体积多阶段构建build阶段安装conda-lock和构建依赖final阶段仅复制/opt/conda/envs/d2l-pytorch-env删除build阶段所有中间文件CUDA 镜像对齐FROM nvidia/cuda:11.8.0-devel-ubuntu22.04与conda-lock.yml中cudatoolkit11.8严格对应避免nvcc --version与torch.version.cuda不一致。构建命令docker build -t d2l-pytorch:23.5 -f Dockerfile . docker run --gpus all -it --rm -p 8888:8888 d2l-pytorch:23.5 jupyter notebook --ip0.0.0.0 --port8888 --no-browser --allow-root提示--gpus all参数需宿主机已安装 NVIDIA Container Toolkit否则容器内nvidia-smi不可见。若仅需 CPU 版本将Dockerfile中FROM行替换为continuumio/miniconda3:23.5.2并删除--gpus参数即可。4. 避坑PyTorch 版 D2L 实战中踩过的 5 个真实血泪坑4.1 现象训练 loss 不下降但loss.item()打印值正常grad却全为None原因MXNet 原版中loss.backward()前有autograd.record()上下文而 PyTorch 中若model在torch.no_grad()模式下被调用如误在eval()后未切回train()则loss的requires_gradFalsebackward()无效果。解决在每个 epoch 开头强制model.train()并在loss.backward()后插入断言assert loss.requires_grad, fLoss requires_gradFalse at epoch {epoch} assert next(model.parameters()).grad is not None, Gradients are None after backward()4.2 现象DataLoader报OSError: Too many open files尤其在num_workers0时原因MXNet 原版DataLoader默认num_workers0而 PyTorch 中设为4后每个 worker 进程打开的文件描述符未释放Linux 默认限制ulimit -n 1024不足。解决在main.py或 notebook 开头添加import resource rlimit resource.getrlimit(resource.RLIMIT_NOFILE) resource.setrlimit(resource.RLIMIT_NOFILE, (4096, rlimit[1]))并确保DataLoader中pin_memoryTrue加速 GPU 传输与persistent_workersTrueworker 复用配合使用。4.3 现象PDF 中公式编号错乱如 “(3.2.1)” 显示为 “(1)”原因nbconvert默认使用article文档类其章节计数器与ctex的chapter/section/subsection不同步。解决修改jupyter_nbconvert_config.py强制文档类为ctex并启用numbersectionsTruec.PDFExporter.template_file ctex c.PDFExporter.template_name ctex c.LatexExporter.number_sections True且 notebook 中所有标题必须用#,##,###语法禁用 HTMLh1标签。4.4 现象torch.compile()报UnsupportedNodeError: call_function aten._local_scalar_dense原因D2L 原版部分代码如ch12_attention使用tensor.item()获取标量而torch.compile()不支持此操作。解决全局搜索.item()并替换为tensor.detach().cpu().numpy().item()或更优方案——用torch.mean()代替标量提取# 原代码触发 compile 报错 loss_val loss.item() # 修改后兼容 compile loss_val loss.mean().detach().cpu().item()4.5 现象多卡训练时DistributedDataParallel报RuntimeError: Expected all tensors to be on the same device原因MXNet 原版gluon.nn.HybridBlock自动处理设备放置而 PyTorch 中model.to(device)仅移动模型data_iter返回的X, y仍为 CPU tensor。解决在训练循环中显式移动数据for X, y in data_iter: X, y X.to(device), y.to(device) # 关键不能只移模型 y_hat model(X) loss loss_fn(y_hat, y) ...且device必须为torch.device(cuda:0)而非cuda后者在多卡时默认选cuda:0但DistributedDataParallel要求显式指定 rank。5. 进阶技巧用torch.compile()加速训练 自动导出 ONNX 模型供生产部署5.1 从“能跑”到“飞快”三步启用torch.compile()PyTorch 2.0 引入的torch.compile()可将模型图编译为高效内核本项目在ch13_computer_vision.ipynb中验证ResNet-18 在 ImageNet 子集上训练速度提升 1.8x。启用步骤极简# Step 1: 原始模型 model ResNet18(num_classes10) # Step 2: 编译注意必须在 .to(device) 之后 model torch.compile(model, modemax-autotune, fullgraphTrue) # Step 3: 正常训练无需修改任何训练循环 optimizer torch.optim.Adam(model.parameters(), lr1e-3) for epoch in range(10): for X, y in train_iter: X, y X.to(device), y.to(device) y_hat model(X) # 此处自动触发编译 loss loss_fn(y_hat, y) loss.backward() optimizer.step() optimizer.zero_grad()参数说明modemax-autotune启用最激进的优化包括算子融合、内存复用适合固定输入尺寸场景fullgraphTrue强制整个模型为单个图避免if/else分支导致图分裂但要求所有控制流可静态推断关键限制torch.compile()不支持torch.jit.script()的torch.jit.export故导出 ONNX 需在编译前进行。5.2 从“训练完”到“能上线”ONNX 导出全流程与验证表生产环境通常要求模型脱离 PyTorch 运行时本项目提供export_onnx.py脚本支持一键导出。以下是 ResNet-18 的导出与验证对照表步骤命令/代码验证方法常见失败原因1. 构建 dummy inputdummy_input torch.randn(1, 3, 224, 224).to(device)dummy_input.shape (1,3,224,224)输入尺寸与训练时不一致如32x32CIFAR 图像误用224x2242. 导出 ONNXtorch.onnx.export(model, dummy_input, resnet18.onnx, input_names[input], output_names[output], dynamic_axes{input: {0: batch}, output: {0: batch}})onnx.checker.check_model(onnx.load(resnet18.onnx))dynamic_axes未声明 batch 维度导致推理时 shape mismatch3. ONNX Runtime 推理ort_session ort.InferenceSession(resnet18.onnx); outputs ort_session.run(None, {input: dummy_input.cpu().numpy()})np.allclose(outputs[0], model(dummy_input).detach().cpu().numpy(), atol1e-4)model.eval()未调用BN 层输出与训练时不一致提示导出前务必执行model.eval()并禁用Dropout否则 ONNX 中会保留随机节点导致每次推理结果不同。5.3 一个真实教训为什么我每次导出 ONNX 前都强制走一遍torch.jit.trace()去年在某跨平台系统项目中我们直接torch.onnx.export()一个用了torch.where()的自定义损失函数ONNX Runtime 推理时报InvalidArgument: Input tensor has incorrect dimensions。排查三天才发现torch.where(condition, x, y)中x和y若为标量如torch.tensor(0.0)ONNX 会将其 broadcast 为condition.shape但某些旧版 ORT 不支持此 broadcast 规则。解决方案是先用torch.jit.trace()捕获一次前向再导出——因为trace会将标量转为常量节点规避 broadcast 问题。# 导出前必做用 trace 固定动态行为 model.eval() traced_model torch.jit.trace(model, dummy_input) # 再导出 traced_model 而非原始 model torch.onnx.export(traced_model, dummy_input, resnet18_fixed.onnx, ...)从那以后我每次导出 ONNX 前都强制走一遍torch.jit.trace()哪怕多花 2 秒也比线上报错后重启服务强。希望帮到你。本文还有配套的精品资源点击获取
阅读完成 · 觉得有帮助?