- 🍨 本文为🔗365天深度学习训练营 中的学习记录博客
- 🍖 原作者:K同学啊
一、前期准备
python
import torch
import torch.nn as nn
import torch.optim as optim
二、定义残差块
python
class ResidualBlockV2(nn.Module):
expansion = 4
def __init__(self, in_channels, out_channels, stride=1):
super(ResidualBlockV2, self).__init__()
# 预激活(Pre-activation)
self.bn1 = nn.BatchNorm2d(in_channels)
self.relu = nn.ReLU(inplace=True)
# 主干道:三层卷积
self.conv1 = nn.Conv2d(in_channels, out_channels, kernel_size=1, stride=stride, bias=False)
self.bn2 = nn.BatchNorm2d(out_channels)
self.conv2 = nn.Conv2d(out_channels, out_channels, kernel_size=3, padding=1, bias=False)
self.bn3 = nn.BatchNorm2d(out_channels)
self.conv3 = nn.Conv2d(out_channels, out_channels * self.expansion, kernel_size=1, bias=False)
# 捷径:如果形状对不上,就用1x1卷积修一下
self.shortcut = nn.Sequential()
if stride != 1 or in_channels != out_channels * self.expansion:
self.shortcut = nn.Conv2d(in_channels, out_channels * self.expansion, kernel_size=1, stride=stride, bias=False)
def forward(self, x):
# 先激活,再分叉
pre_act = self.relu(self.bn1(x))
shortcut = self.shortcut(pre_act)
out = self.conv1(pre_act)
out = self.conv2(self.relu(self.bn2(out)))
out = self.conv3(self.relu(self.bn3(out)))
# 直接相加,后面没有激活函数,这就是V2
return out + shortcut
三、组装完整的ResNet-50V2模型
python
class ResNet50V2(nn.Module):
def __init__(self, num_classes=2):
super(ResNet50V2, self).__init__()
self.in_channels = 64
# 开头的特征提取层
self.conv1 = nn.Conv2d(3, 64, kernel_size=7, stride=2, padding=3, bias=False)
self.bn1 = nn.BatchNorm2d(64)
self.relu = nn.ReLU(inplace=True)
self.maxpool = nn.MaxPool2d(kernel_size=3, stride=2, padding=1)
# 堆叠50层的四个阶段
self.layer1 = self._make_layer(64, 3, stride=1)
self.layer2 = self._make_layer(128, 4, stride=2)
self.layer3 = self._make_layer(256, 6, stride=2)
self.layer4 = self._make_layer(512, 3, stride=2)
# 最后的输出层
self.bn = nn.BatchNorm2d(2048)
self.avgpool = nn.AdaptiveAvgPool2d((1, 1))
self.fc = nn.Linear(2048, num_classes)
def _make_layer(self, out_channels, blocks, stride):
layers = [ResidualBlockV2(self.in_channels, out_channels, stride)]
self.in_channels = out_channels * 4
for _ in range(1, blocks):
layers.append(ResidualBlockV2(self.in_channels, out_channels))
return nn.Sequential(*layers)
def forward(self, x):
x = self.maxpool(self.relu(self.bn1(self.conv1(x))))
x = self.layer4(self.layer3(self.layer2(self.layer1(x))))
x = self.fc(torch.flatten(self.avgpool(self.relu(self.bn(x))), 1))
return x
四、测试模型
python
if __name__ == "__main__":
print("正在组装 ResNet-50 V2")
model = ResNet50V2(num_classes=2)
print("正在生成一张虚拟的医学 X 光片送入模型...")
dummy_input = torch.randn(2, 3, 224, 224)
output = model(dummy_input)
print("PyTorch 代码翻译成功!模型结构正确!")
print(f"模型的输出形状为: {output.shape} (预期看到的是 torch.Size([2, 2]))")

五、总结
本周我们完成了两大任务:
- 理论升级 :理解 ResNet-V2 架构为何优于 V1。
- 框架迁移 :将模型代码从 TensorFlow 翻译成 PyTorch 风格。
一、 理论篇:ResNet-V1 vs ResNet-V2 的区别
这两代网络的核心区别,在于激活函数(ReLU)和归一化(Batch Normalization, BN)放置的位置。
| 对比维度 | ResNet-V1 (后激活 Post-activation) | ResNet-V2 (预激活 Pre-activation) |
|---|---|---|
| 操作顺序 | 卷积(Conv) -> 归一化(BN) -> 激活(ReLU) | 归一化(BN) -> 激活(ReLU) -> 卷积(Conv) |
| 合并点后 | 捷径(Shortcut)与主路相加后,还要经过一次 ReLU。 | 捷径(Shortcut)与主路相加后,直接输出,无 ReLU。 |
| 反向传播 | 梯度(Gradient)回传时,容易被最后的 ReLU 阻挡,导致梯度消失。 | 梯度可以通过捷径无损回传,畅通无阻。 |
| 最终效果 | 网络深度通常局限在 100-200 层左右。 | 彻底打破深度限制,可以训练 1000 层以上 的超深网络。 |
V2 的精髓就是"预激活(Pre-activation)",把处理数据的操作提前。它的终极目的就是为了保持"捷径(Shortcut/Skip Connection)"的绝对纯净。只要捷径上没有激活函数拦路,模型犯错后的"纠错信号(梯度)"就能完美传回前面的层。
二、 代码篇:PyTorch 的"造车逻辑"
从 TensorFlow 换到 PyTorch,最大的感受是代码变得更加"面向对象(基于 Class 类)"。写一个 PyTorch 模型的标准动作分为两步:
__init__(初始化函数) ------ 准备零件库:
在这里,我们把所有要用到的层(卷积层Conv2d、归一化层BatchNorm2d、激活函数ReLU、全连接层Linear)全部定义好。注意:这里只是把零件买回来,还没有组装。forward(前向传播函数) ------ 流水线组装:
在这里,我们规定数据x进入模型后,先经过哪个零件,再经过哪个零件,最后如何输出。这也是为什么捷径的加法out + shortcut是写在forward里面的。
三、 工程篇:工业级架构测试技巧
- 虚拟输入(Dummy Input) :在工程实践中,刚写完一个庞大的模型(如 50 层的 ResNet),我们不会立刻用真实数据集去训练(Training)。
- 结构验证(Architecture Validation) :我们会生成一个形状一致的随机张量(例如
torch.randn(2, 3, 224, 224),代表 2 张 224x224 的 3 通道图片),将其送入模型进行一次前向传播(Forward Pass)。 - 结论 :如果代码能在 1 秒钟内跑通,并且输出的张量形状(Tensor Shape)符合预期(如
[2, 2],代表 2 张图,2 个分类概率),这就证明我们的网络架构在逻辑上 100% 正确,没有维度不匹配的 Bug。
四、 拓展资料:V2 思想的跨领域迁移
ResNet-V2 的"预激活(Pre-activation)"思想,其本质是:为了防止深度网络退化,必须清除恒等映射路径(主通道)上的阻碍。
这个思想不仅在图像识别(CV)界称王,还跨界统治了自然语言处理(NLP)界:
- 当今最火的大语言模型(如 GPT、BERT 内部的 Transformer 架构),早期使用的是 Post-LN(后置层归一化),网络很难加深。
- 后来全面改用 Pre-LN(前置层归一化),这与 ResNet-V2 的预激活思想如出一辙,从而造就了今天几百上千层、参数量千亿级别的超级 AI。