
简介这是一份以龙良曲PyTorch课程为主线、覆盖多个经典深度学习模型复现的完整代码包适合正在系统学习PyTorch框架、希望从基础语法过渡到模型实现与训练实践的开发者。资源中可见Unet、Vision Transformer、DDPM、MAE等代表性模型的实现也涉及无监督学习AutoEncoder等内容基本按学习进度和模型类别组织代码便于对照课程章节逐一击破。压缩包共259个文件约18.88MB以145个py源码文件为主体辅以ipynb演示笔记、png/jpg训练可视化截图、xml配置文件及少量csv数据与db日志文件整体目录结构清晰既能看代码也能对照输出图检查训练效果。目前已有186人学习下载说明该资源具有一定参考价值。对想快速上手模型复现、梳理PyTorch训练流程的读者来说这份代码包可以省去大量重复造轮子的时间直接基于已有工程修改结构、调整参数并运行实验。 其实这个项目是我一边刷B站一边动手码出来的跟着龙良曲老师的PyTorch系列教程走完全部代码再从中挑一个模型自己重新复现最后选了Unet。整个仓库不算大但是把PyTorch从数据加载、网络搭建到训练推理这条路完整趟了一遍。这篇文章就记录我当时为什么这么规划、复现Unet的关键模块怎么拆、环境怎么搭、训练里踩了哪些坑以及一些网上教程不会明说的细节。想入门PyTorch、或者正在纠结怎么复现第一个分割模型的朋友可以直接对照着走。1. 从课程代码到第一个复现模型我这样拆解目标1.1 学习代码怎么用才不浪费龙良曲老师的这套课程代码定位是课堂教学和动手练习不是工业级部署项目。它的优点恰好在于结构干净、依赖少数据加载、模型定义、训练循环这些代码都能和课程视频里的知识点一一对上。我拿到代码之后没有急着跑而是干了几件整理工作第一步把仓库按自己的习惯重新组织成dataset、model、loss、train、utils五个模块第二步每个模块对照视频过一遍搞清楚每个函数为什么存在第三步找一个不太大的模型来完整复现。前两步很快第三步才是真正花时间的。这里我建议不要边看视频边敲代码效率不高。更推荐先看一小节理解了整体思路再关上视频用注释和代码结构反向推导实现。推不出来再回头翻课程印象会深很多。1.2 复现目标为什么挑Unet而不是更炫的模型很多入门者一上来就复现ResNet、YOLO、Transformer这类模型结果往往是结构还没吃透就卡在训练技巧上最后变成了调包侠。Unet是我见过最适合作为第一个复现目标的分割模型理由很朴实结构模块化极强双卷积、下采样、上采样、跳跃连接可以被拆成独立组件。计算量可控单张消费级显卡就能训练不需要分布式。数据集容易找医学图像分割、道路分割、遥感分割、工业质检都能用到。说到底复现模型不是为了在排行榜上刷分而是为了把模型看懂、改得动、能迁移。Unet这个模型同时覆盖了卷积、特征金字塔、长跳跃连接等几个高频考点它就是最合适的练手对象。2. Unet网络结构拆解每一层都要心里有数2.1 编码器压缩路径到底在干什么Unet左侧的编码器任务是把输入的图像逐步压缩成高维特征图。每次下采样之后特征图的尺寸减半通道数翻倍。这样做的原因很直接随着感受野越来越大网络能够从局部纹理过渡到全局语义这和我们人眼看图的过程是类似的先看整体轮廓再注意细节纹理。以输入尺寸512x512x3为例第一层双卷积输出512x512x64池化后变成256x256x64第二层双卷积输出256x256x128池化后变成128x128x128第三层、第四层依次类推。特征图分辨率越来越低但通道里携带的语义信息越来越丰富。写代码的时候我把双卷积块封装成了DoubleConv类。顺序是Conv2d - BatchNorm2d - ReLU - Conv2d - BatchNorm2d - ReLU。BatchNorm放在卷积和激活之间是实践出来的标配比卷积后直接接ReLU收敛更稳定。2.2 解码器、跳跃连接以及Unet名字的由来解码器在结构上正好和编码器对称负责把压缩后的低分辨率特征图逐步恢复回原图尺寸。每次上采样后还要把编码器对应层输出的同尺寸特征图在通道维度上拼接起来这就是跳跃连接skip connection。为什么这里用拼接而不是相加因为拼接保留了底层纹理细节和深层语义信息两路证据让网络自己学习如何融合最终分割边界会精细很多。Unet名字就来自这个U形结构左侧编码器右侧解码器中间用横杠连接。我在实验里试过把跳跃连接直接屏蔽掉效果立竿见影验证集mIoU掉了超过10个点。所以如果你发现自己的Unet分割结果边缘很糊第一反应应该检查跳跃连接有没有生效而不是盲目加数据增强。3. 环境搭建与Unet核心实现照着做就行3.1 PyTorch环境配置里最容易被忽略的版本匹配环境问题是被问得最多的我在这一步也踩过坑。建议一律用Anaconda创建独立虚拟环境不要直接往base环境里塞包。创建并激活环境的命令很朴素conda create -n torch_unet python3.10 conda activate torch_unet接下来装PyTorch我强烈建议去PyTorch官网复制对应CUDA版本的安装命令不要随便pip install torch碰运气。版本匹配的核心原则是系统显卡驱动的CUDA版本必须大于等于你安装的PyTorch编译时使用的CUDA版本。装完后用一段小代码验证import torch print(torch.__version__) print(torch.cuda.is_available())如果输出中cuda.is_available()是True说明GPU可用可以继续。如果你的显卡比较老比如MX150这类低功耗卡CUDA版本不要追新装11.8或12.1就够用。离线环境比如CentOS服务器则需要提前下载好对应Python版本的whl包和依赖再用pip install --no-index离线安装。3.2 Unet的代码骨架双卷积和上采样是关键我复现代的Unet没有用复杂的转置卷积上采样而是用Upsample 3x3卷积的组合。这样做的原因是转置卷积容易产生棋盘格伪影而先插值再卷积不仅参数量小分割边缘也更平滑。完整核心代码大概长这样import torch import torch.nn as nn class DoubleConv(nn.Module): def __init__(self, in_ch, out_ch): super().__init__() self.conv nn.Sequential( nn.Conv2d(in_ch, out_ch, 3, padding1), nn.BatchNorm2d(out_ch), nn.ReLU(inplaceTrue), nn.Conv2d(out_ch, out_ch, 3, padding1), nn.BatchNorm2d(out_ch), nn.ReLU(inplaceTrue) ) def forward(self, x): return self.conv(x) class Unet(nn.Module): def __init__(self, in_ch3, out_ch1): super().__init__() self.enc1 DoubleConv(in_ch, 64) self.enc2 DoubleConv(64, 128) self.enc3 DoubleConv(128, 256) self.enc4 DoubleConv(256, 512) self.pool nn.MaxPool2d(2) self.bridge DoubleConv(512, 1024) self.up4 nn.Upsample(scale_factor2, modebilinear, align_cornersTrue) self.dec4 DoubleConv(1024 512, 512) self.up3 nn.Upsample(scale_factor2, modebilinear, align_cornersTrue) self.dec3 DoubleConv(512 256, 256) self.up2 nn.Upsample(scale_factor2, modebilinear, align_cornersTrue) self.dec2 DoubleConv(256 128, 128) self.up1 nn.Upsample(scale_factor2, modebilinear, align_cornersTrue) self.dec1 DoubleConv(128 64, 64) self.out nn.Conv2d(64, out_ch, 1) def forward(self, x): e1 self.enc1(x) e2 self.enc2(self.pool(e1)) e3 self.enc3(self.pool(e2)) e4 self.enc4(self.pool(e3)) b self.bridge(self.pool(e4)) d4 self.dec4(torch.cat([self.up4(b), e4], dim1)) d3 self.dec3(torch.cat([self.up3(d4), e3], dim1)) d2 self.dec2(torch.cat([self.up2(d3), e2], dim1)) d1 self.dec1(torch.cat([self.up1(d2), e1], dim1)) return self.out(d1)代码里最需要注意的就是torch.cat那一行通道拼接前必须保证两个特征图的宽高完全一致。所以我习惯在定义模型后用随机张量跑一次forward输出每一层的shape并逐行检查而不是直接去训练。3.3 训练细节损失函数、评价指标和模型保存分割任务和分类任务的一个显著区别是评价指标不能只看accuracy。我主要看两个指标mIoU和Dice系数。损失函数则用BCEWithLogitsLoss加上辅助的Dice Loss二者按一定比例相加这样在小目标分割场景下收敛更快。训练循环里有几个很不起眼但容易丢分的细节model.train()和model.eval()必须成对出现否则BatchNorm在验证时会用训练集的统计量推理结果会有偏差。保存模型建议只保存state_dict不要直接保存整个model对象。后者在Python环境和PyTorch版本变化后经常加载失败。每次跑新实验之前先归一化输入数据再检查一下数据增强有没有对image和mask做同样的变换否则mask和图像错位模型再强也白搭。4. 实操中遇到的典型问题与排查速查我把自己在复现过程中遇到的问题整理成了一张速查表基本覆盖了新手复现Unet会碰到的绝大部分问题典型问题大概率原因解决方法torch.cat行报尺寸不匹配输入图像宽高不是16的倍数随机裁剪成512x512或在网络前加Padding显存溢出OOMbatch size过大或输入分辨率过高调小batch size开启梯度累积显卡驱动警告出现cu130字样系统驱动CUDA和PyTorch编译版本不匹配按官网命令重装匹配的PyTorch训练loss不降或震荡学习率过大、数据未归一化、mask错位检查数据配对学习率降到1e-4验证mIoU低但训练acc高类别不平衡小目标被忽略改用Dice Loss或调整权重数据加载很慢GPU利用率不高num_workers设置不对或数据在机械盘设4~8个worker数据放固态盘4.1 特征图尺寸对不上的问题这是复现Unet时出现频率最高的问题没有之一。Unet里一共有4次下采样所以输入图像的宽高必须是16的倍数否则最后上采样出来的尺寸和编码器对应的特征图尺寸对不上拼接时就会报错。解决思路分两种一是数据预处理阶段直接随机裁剪成512x512简单粗暴一劳永逸二是在网络输入层加一个自适应padding模块把宽高补齐到16的倍数。我选的是第一种因为还能顺带起到数据增强的作用。4.2 显卡显存不足的两种解法刚上手的时候我把batch size设成8输入1024x1024结果8G显存直接OOM。后来把输入降到512x512batch size降到4问题就解决了。如果这样还爆显存可以用梯度累积的办法每2个batch再更新一次参数效果上等价于变相扩大了batch size。核心代码思路是把loss.backward()之后的optimizer.step()延后先积累几个batch的梯度accumulation_steps 2 optimizer.zero_grad() for i, (imgs, masks) in enumerate(train_loader): loss criterion(model(imgs), masks) loss loss / accumulation_steps loss.backward() if (i 1) % accumulation_steps 0: optimizer.step() optimizer.zero_grad()4.3 训练时看到CUDA优化警告先别慌网上很多人问的“warning: you need pytorch with cu130 or higher to use optimized cuda operations”我也遇到过。这个警告的意思是当前安装的PyTorch编译版本要求的CUDA计算能力高于当前驱动提供的支持。PyTorch在安装时自带的CUDA运行库和系统的显卡驱动CUDA版本不是一回事。处理方式两种如果显卡不算太老升级驱动如果不想动驱动就换成对应CUDA版本的PyTorch。4.4 训练loss不下降时先怀疑数据再怀疑模型我曾经调了一整个晚上网络结构loss就是死气沉沉不下降。第二天冷静下来检查数据发现是归一化时把image的像素范围除以255后又减了错误的均值等于把输入信号压得太厉害网络根本学不动。这类问题有个排查顺序打印几个batch的image和mask确认图像内容正常且mask对应位置正确再看归一化参数接着把学习率降到1e-4最后才去怀疑网络结构。5. 复现完之后的一些个人体会这个项目做完之后我最大的感受是复现Unet并不是终点它把环境搭建、数据准备、模型定义、训练验证、问题排查这条pipeline完整地串了起来。以后换SegNet、DeepLabV3、或者加注意力机制的Unet改进版都是替换这条流水线里的某一个环节而已。有一点想特别提醒正在学PyTorch的朋友不要高估看视频的收获也不要低估亲手敲代码的作用。跟着课程代码敲一遍你会发现很多细节——比如某个层输出维度是多少、inplaceTrue什么情况下不能用、align_corners该设什么值——这些在视频里可能一句话带过但自己动手时会卡很久。我现在写模型前有个习惯先用随机张量打印每一层输出的shape确认无误再开始训练。这个习惯就是从这个项目开始养成的节省了后期大量的调试时间。我现在的计划是继续往这个仓库里补充SegNet、带注意力门的Unet改进版以及一个视频动作分类的小实验。这个项目已经变成了我的一个“实验玩具箱”很多新想法都直接在它的基础上改。如果你也正在学PyTorch真的建议不要只盯着视频看趁热打铁选一个模型开始复现吧。本文还有配套的精品资源点击获取