ResNet50V2学习笔记

一、前期准备

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]))")

五、总结

本周我们完成了两大任务:

  1. 理论升级 :理解 ResNet-V2 架构为何优于 V1。
  2. 框架迁移 :将模型代码从 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 模型的标准动作分为两步:

  1. __init__ (初始化函数) ------ 准备零件库:
    在这里,我们把所有要用到的层(卷积层 Conv2d、归一化层 BatchNorm2d、激活函数 ReLU、全连接层 Linear)全部定义好。注意:这里只是把零件买回来,还没有组装。
  2. 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。
相关推荐
wljt2 小时前
Spring boot学习笔记六:SpringBoot实用技术整合
spring boot·笔记·学习
叶子野格2 小时前
《C语言学习:数组》11
c语言·开发语言·c++·学习·visual studio
MY_TEUCK2 小时前
【Agent Skills学习笔记】2小时从会用到会造:什么是Skills?怎么用?怎么写?
人工智能·笔记·学习
skilllite作者2 小时前
SkillLite 技术演进笔记:Workspace、沙箱与进化
java·开发语言·前端·笔记·安全·agentskills
水月wwww2 小时前
【OpenClaw学习记录】
学习
明月清了个风2 小时前
libmodbus笔记
笔记·嵌入式软件·libmodbus
xieliyu.2 小时前
Java手搓数据结构:从零模拟实现顺序表增删改查
java·开发语言·数据结构·学习·顺序表
楼田莉子2 小时前
仿muduo库的高并发服务器——正则表达式与any类介绍及其简单模拟实现
linux·服务器·c++·学习·设计模式
xiaoxiaoxiaolll2 小时前
《Nature Communications》:集成热光调制与3D空间并行的光子神经网络芯片
学习