本文为365天深度学习训练营 中的学习记录博客 原作者K同学啊一、前期准备importtorchimporttorch.nnasnnimporttorch.optimasoptim二、定义残差块classResidualBlockV2(nn.Module):expansion4def__init__(self,in_channels,out_channels,stride1):super(ResidualBlockV2,self).__init__()# 预激活Pre-activationself.bn1nn.BatchNorm2d(in_channels)self.relunn.ReLU(inplaceTrue)# 主干道三层卷积self.conv1nn.Conv2d(in_channels,out_channels,kernel_size1,stridestride,biasFalse)self.bn2nn.BatchNorm2d(out_channels)self.conv2nn.Conv2d(out_channels,out_channels,kernel_size3,padding1,biasFalse)self.bn3nn.BatchNorm2d(out_channels)self.conv3nn.Conv2d(out_channels,out_channels*self.expansion,kernel_size1,biasFalse)# 捷径如果形状对不上就用1x1卷积修一下self.shortcutnn.Sequential()ifstride!1orin_channels!out_channels*self.expansion:self.shortcutnn.Conv2d(in_channels,out_channels*self.expansion,kernel_size1,stridestride,biasFalse)defforward(self,x):# 先激活再分叉pre_actself.relu(self.bn1(x))shortcutself.shortcut(pre_act)outself.conv1(pre_act)outself.conv2(self.relu(self.bn2(out)))outself.conv3(self.relu(self.bn3(out)))# 直接相加后面没有激活函数这就是V2returnoutshortcut三、组装完整的ResNet-50V2模型classResNet50V2(nn.Module):def__init__(self,num_classes2):super(ResNet50V2,self).__init__()self.in_channels64# 开头的特征提取层self.conv1nn.Conv2d(3,64,kernel_size7,stride2,padding3,biasFalse)self.bn1nn.BatchNorm2d(64)self.relunn.ReLU(inplaceTrue)self.maxpoolnn.MaxPool2d(kernel_size3,stride2,padding1)# 堆叠50层的四个阶段self.layer1self._make_layer(64,3,stride1)self.layer2self._make_layer(128,4,stride2)self.layer3self._make_layer(256,6,stride2)self.layer4self._make_layer(512,3,stride2)# 最后的输出层self.bnnn.BatchNorm2d(2048)self.avgpoolnn.AdaptiveAvgPool2d((1,1))self.fcnn.Linear(2048,num_classes)def_make_layer(self,out_channels,blocks,stride):layers[ResidualBlockV2(self.in_channels,out_channels,stride)]self.in_channelsout_channels*4for_inrange(1,blocks):layers.append(ResidualBlockV2(self.in_channels,out_channels))returnnn.Sequential(*layers)defforward(self,x):xself.maxpool(self.relu(self.bn1(self.conv1(x))))xself.layer4(self.layer3(self.layer2(self.layer1(x))))xself.fc(torch.flatten(self.avgpool(self.relu(self.bn(x))),1))returnx四、测试模型if__name____main__:print(正在组装 ResNet-50 V2)modelResNet50V2(num_classes2)print(正在生成一张虚拟的医学 X 光片送入模型...)dummy_inputtorch.randn(2,3,224,224)outputmodel(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。