当前位置: 首页 > article >正文

深度学习每周学习总结J7(对ResNeXt-50 算法的思考)

  • 🍨 本文为🔗365天深度学习训练营 中的学习记录博客
  • 🍖 原作者:K同学啊 | 接辅导、项目定制

目录

    • 问题描述
    • tensorflow版本问题推理
      • 问题解析
      • 逐步推理和验证
        • 1. 通道数变化逻辑
        • 2. 分组卷积部分的作用
        • 3. 为什么没有报错
        • 4. 是否存在代码问题
        • 5. 修正代码
        • 6. 进一步验证和实验
      • 总结
    • pytorch版本代码问题推理
      • 逐步分析
        • 1. 残差分支 `shortcut` 的定义
        • 2. 主分支的通道数变化
        • 3. 残差连接 `x += shortcut`
        • 4. 验证问题是否存在
          • 第一层(conv_shortcut=True)
          • 后续层(conv_shortcut=False)
        • 5. 代码是否正确?
        • 6. 进一步验证
      • 总结

问题描述

现在有一个问题如下:残差单元里,如果conv_shotcut=False,那么在执行“x=Add()…”语句时,通道数不一致的,为什么不报错。这个代码是否有错?展示相关资料、逐步推理模型写下对应的思考过程。

现在有TnesorFlow复现ResNext-50模型如下:import numpy as np
from tensorflow.keras.preprocessing.image import ImageDataGenerator
from tensorflow.keras.utils import to_categorical
from tensorflow.keras.models import Sequential
from tensorflow.keras.layers import Input, Dense, Dropout, Conv2D, MaxPool2D, Flatten, GlobalAvgPool2D, concatenate, \
BatchNormalization, Activation, Add, ZeroPadding2D, Lambda
from tensorflow.keras.layers import ReLU
from tensorflow.keras.optimizers import Adam
import matplotlib.pyplot as plt
from tensorflow.keras.callbacks import LearningRateScheduler
from tensorflow.keras.models import Model

# 定义分组卷积
def grouped_convolution_block(init_x, strides, groups, g_channels):
    group_list = []
    # 分组进行卷积
    for c in range(groups):
        # 分组取出数据
        x = Lambda(lambda x: x[:, :, :, c * g_channels:(c + 1) * g_channels])(init_x)
        # 分组进行卷积
        x = Conv2D(filters=g_channels, kernel_size=(3, 3),strides=strides, padding='same', use_bias=False)(x)
        # 存入list
        group_list.append(x)
    # 合并list中的数据
    group_merage = concatenate(group_list, axis=3)
    x = BatchNormalization(epsilon=1.001e-5)(group_merage)
    x = ReLU()(x)
    return x

# 定义残差单元
def block(x, filters, strides=1, groups=32, conv_shortcut=True):

    if conv_shortcut:
        shortcut = Conv2D(filters * 2, kernel_size=(1, 1), strides=strides, padding='same', use_bias=False)(x)
        # epsilon为BN公式中防止分母为零的值
        shortcut = BatchNormalization(epsilon=1.001e-5)(shortcut)
    else:
        # identity_shortcut
        shortcut = x
        
    # 三层卷积层
    x = Conv2D(filters=filters, kernel_size=(1, 1), strides=1, padding='same', use_bias=False)(x)
    x = BatchNormalization(epsilon=1.001e-5)(x)
    x = ReLU()(x)
    # 计算每组的通道数
    g_channels = int(filters / groups)
    # 进行分组卷积
    x = grouped_convolution_block(x, strides, groups, g_channels)

    x = Conv2D(filters=filters * 2, kernel_size=(1, 1), strides=1, padding='same', use_bias=False)(x)
    x = BatchNormalization(epsilon=1.001e-5)(x)
    x = Add()([x, shortcut])
    x = ReLU()(x)
    return x

# 堆叠残差单元
def stack(x, filters, blocks, strides, groups=32):
    # 每个stack的第一个block的残差连接都需要使用1*1卷积升维
    x = block(x, filters, strides=strides, groups=groups)
    for i in range(blocks):
        x = block(x, filters, groups=groups, conv_shortcut=False)
    return x

# 定义ResNext50(32*4d)网络
def ResNext50(input_shape, num_classes):
    inputs = Input(shape=input_shape)
    # 填充3圈0,[224,224,3]->[230,230,3]
    x = ZeroPadding2D((3, 3))(inputs)
    x = Conv2D(filters=64, kernel_size=(7, 7), strides=2, padding='valid')(x)
    x = BatchNormalization(epsilon=1.001e-5)(x)
    x = ReLU()(x)
    # 填充1圈0
    x = ZeroPadding2D((1, 1))(x)
    x = MaxPool2D(pool_size=(3, 3), strides=2, padding='valid')(x)
    # 堆叠残差结构
    x = stack(x, filters=128, blocks=2, strides=1)
    x = stack(x, filters=256, blocks=3, strides=2)
    x = stack(x, filters=512, blocks=5, strides=2)
    x = stack(x, filters=1024, blocks=2, strides=2)
    # 根据特征图大小进行全局平均池化
    x = GlobalAvgPool2D()(x)
    x = Dense(num_classes, activation='softmax')(x)
    # 定义模型
    model = Model(inputs=inputs, outputs=x)
    return model

model=ResNext50(input_shape=(224,224,3),num_classes=1000)
model.summary()

tensorflow版本问题推理

在深度学习模型中,尤其是带有残差结构的模型(如 ResNet 或 ResNeXt),通道数的一致性是一个非常重要的细节。我们来逐步分析你的代码并解答问题。


问题解析

在你的代码中,block 函数定义了一个残差单元,关键部分是:

if conv_shortcut:
    shortcut = Conv2D(filters * 2, kernel_size=(1, 1), strides=strides, padding='same', use_bias=False)(x)
    shortcut = BatchNormalization(epsilon=1.001e-5)(shortcut)
else:
    shortcut = x

如果 conv_shortcut=False,那么 shortcut 是直接从输入 x 赋值过来的。这意味着 shortcut 的通道数与输入 x 的通道数一致,未经过处理。而在后面的代码中,有一行:

x = Add()([x, shortcut])

Add() 要求两个张量在通道数(最后一个维度)上必须一致,否则会报错。那么问题来了:

  • conv_shortcut=False,为什么没有报错?
  • 代码逻辑是否有问题?

逐步推理和验证

我们从代码结构上一步步分析。

1. 通道数变化逻辑

观察到:

  1. 输入 x 的通道数是动态的,可能会随着每个 Conv2D 操作改变。

  2. block 函数中,通道数在以下两处被改变:

    • x = Conv2D(filters=filters, ...),通道数变为 filters
    • x = Conv2D(filters=filters * 2, ...),通道数变为 filters * 2

    此时,如果 conv_shortcut=Trueshortcut 也会被升维至 filters * 2 通道。

  3. 如果 conv_shortcut=Falseshortcut 直接取值为输入 x,其通道数保持不变,与当前 x 的通道数(filters * 2)可能不一致。


2. 分组卷积部分的作用

分组卷积不会影响这一问题,因为它仅对输入的通道进行分组处理,最终输出的通道数还是符合卷积核参数设置。关键问题还是残差连接部分的通道对齐。


3. 为什么没有报错

conv_shortcut=FalseAdd() 的两个输入张量通道数必须相同。如果没有报错,可能是以下两种情况之一:

  1. 隐含假设:输入通道数已经对齐
    在调用 block 函数时,确保输入张量的通道数已经等于 filters * 2。例如:

    x = block(x, filters=128, groups=32, conv_shortcut=False)
    

    此时,x 的通道数已经通过前一个 block 调整到 filters * 2

  2. 框架默认广播机制
    如果输入的 shortcutx 的通道数不一致,理论上应该报错。但在某些特殊情况下,框架可能会尝试通过广播机制补齐通道维度。例如,将较小的张量自动扩展以匹配较大的张量。这种情况在 TensorFlow 中通常不推荐,且需要确认是否存在。


4. 是否存在代码问题

从理论上分析,这段代码在 conv_shortcut=False 的情况下确实可能出现通道数不一致的问题。如果输入张量的通道数与 filters * 2 不一致,那么 Add() 将无法正常执行。因此,代码逻辑在设计上可能存在问题,建议对其进行修改。


5. 修正代码

可以通过显式检查通道数来解决这一问题。例如:

  1. conv_shortcut=False 的情况下,添加通道对齐逻辑:

    if not conv_shortcut and x.shape[-1] != filters * 2:
        shortcut = Conv2D(filters * 2, kernel_size=(1, 1), strides=1, padding='same', use_bias=False)(x)
        shortcut = BatchNormalization(epsilon=1.001e-5)(shortcut)
    else:
        shortcut = x
    
  2. 或者在 stack 函数中确保输入张量的通道数已经对齐:

    x = block(x, filters, strides=strides, groups=groups, conv_shortcut=True)
    for i in range(blocks - 1):  # 修正blocks循环次数
        x = block(x, filters, groups=groups, conv_shortcut=False)
    

6. 进一步验证和实验

在实际实验中,可以通过以下方式验证通道数是否一致:

  1. 打印各层的输出形状:

    print(x.shape)
    print(shortcut.shape)
    

    确保通道数一致。

  2. 编写测试用例检查 block 函数输出是否符合预期:

    test_input = np.random.randn(1, 56, 56, 64)  # 假设输入通道数为64
    test_output = block(test_input, filters=128, conv_shortcut=False)
    print(test_output.shape)  # 应该输出 (1, 56, 56, 256)
    

总结

  1. conv_shortcut=False 时,代码中未对通道数进行显式对齐,存在潜在问题。
  2. 如果通道数确实一致,可能是前面的代码已经对输入通道数做了隐式约束。
  3. 最好在代码中显式处理通道对齐问题,确保逻辑自洽,避免因意外输入导致错误。

pytorch版本代码问题推理

你的 PyTorch 版本的代码需要同样关注 残差分支 (shortcut)主分支 (x) 在通道数上的一致性问题。我们将逐步分析这段代码,验证是否存在与 TensorFlow 版本类似的问题。


逐步分析

1. 残差分支 shortcut 的定义

ResNeXtBlock 中:

if conv_shortcut:
    self.shortcut = nn.Sequential(
        nn.Conv2d(in_channels, filters * 2, kernel_size=1, stride=stride, bias=False),
        nn.BatchNorm2d(filters * 2),
    )
else:
    self.shortcut = nn.Identity()
  • 如果 conv_shortcut=Trueshortcut 使用一个 1x1 卷积升维(或降维)来使通道数从 in_channels 变为 filters * 2
  • 如果 conv_shortcut=Falseshortcutnn.Identity(),即直接复制输入数据,不会对输入张量的通道数进行任何修改。

这意味着当 conv_shortcut=False 时,shortcut 的通道数与输入 x 的通道数是完全相同的。


2. 主分支的通道数变化

主分支的结构如下:

  1. 第一层 1x1 卷积:

    self.conv1 = nn.Sequential(
        nn.Conv2d(in_channels, filters, kernel_size=1, stride=1, bias=False),
        nn.BatchNorm2d(filters),
        nn.ReLU(inplace=True)
    )
    

    这里将输入通道数从 in_channels 转换为 filters

  2. 分组卷积:

    self.grouped_conv = GroupedConvBlock(filters, groups, self.g_channels, stride)
    

    GroupedConvBlock 将输入张量分为 groups 个分组,每组的通道数为 g_channels = filters // groups。分组卷积的输出张量的通道数仍然是 filters

  3. 最后一层 1x1 卷积:

    self.conv3 = nn.Sequential(
        nn.Conv2d(filters, filters * 2, kernel_size=1, stride=1, bias=False),
        nn.BatchNorm2d(filters * 2),
    )
    

    这里将通道数从 filters 转换为 filters * 2

最终主分支的输出通道数为 filters * 2


3. 残差连接 x += shortcut

主分支的输出和 shortcut 分支的输出在通道数上必须一致,才能执行 x += shortcut

关键代码:

shortcut = self.shortcut(x)
x = self.conv1(x)
x = self.grouped_conv(x)
x = self.conv3(x)
x += shortcut

潜在问题:

  • conv_shortcut=False 时,shortcut 是输入张量的直接复制,因此其通道数为 in_channels
  • 主分支的输出通道数为 filters * 2
  • 如果 in_channels != filters * 2,此处会报错,因为 PyTorch 不支持通道数不一致的张量相加。

4. 验证问题是否存在

检查 ResNeXtBlock 的输入通道数和输出通道数是否对齐。

第一层(conv_shortcut=True)

_make_layer 中创建的第一个 ResNeXtBlock

layers = [ResNeXtBlock(in_channels, filters, stride=stride)]

此处 conv_shortcut=True(默认值),所以 shortcut 分支会自动调整通道数到 filters * 2,不会有问题。

后续层(conv_shortcut=False)

后续的 ResNeXtBlock

for _ in range(1, blocks):
    layers.append(ResNeXtBlock(filters * 2, filters, stride=1))

此时的输入通道数为 filters * 2,主分支和 shortcut 分支的输出通道数也都为 filters * 2,不会有问题。


5. 代码是否正确?

根据分析,你的 PyTorch 版本没有问题,原因如下:

  • 第一个 ResNeXtBlockconv_shortcut=True 的情况下,会对 shortcut 分支进行通道数对齐。
  • 后续的 ResNeXtBlock 保证输入通道数与输出通道数一致,因此即使 conv_shortcut=False,也不会有通道数不一致的情况。

这一设计比 TensorFlow 版本更加清晰和稳健,因为它显式地定义了输入输出的通道数。


6. 进一步验证

可以通过打印每个模块的输入和输出形状来验证是否存在通道数不一致的问题。例如:

model = ResNeXt50()
input_tensor = torch.randn(1, 3, 224, 224)  # 假设输入为 3 通道图像
output = model(input_tensor)  # 前向传播
print("Output shape:", output.shape)

另外,也可以打印每个 ResNeXtBlock 的输入输出通道数:

for name, layer in model.named_modules():
    if isinstance(layer, ResNeXtBlock):
        print(f"{name}: in_channels={layer.conv1[0].in_channels}, out_channels={layer.conv3[0].out_channels}")

总结

  1. TensorFlow 版本存在潜在问题:未显式保证残差连接中 shortcut 和主分支的通道数一致。
  2. PyTorch 版本没有问题:通过 conv_shortcut_make_layer 的逻辑,确保了每个 ResNeXtBlock 的输入和输出通道数一致性。
  3. 建议:在模型调试阶段打印中间张量的形状,确保通道数逻辑清晰。

http://www.kler.cn/a/417723.html

相关文章:

  • 移除元素-双指针(下标)
  • 2. K8S集群架构及主机准备
  • 86.(2)攻防世界 WEB PHP2
  • GPT与Deepseek等数据驱动AI的缺点
  • Spring Boot 实例解析:配置文件
  • M|哪吒之魔童闹海
  • 洛谷P1443 马的遍历
  • 电路基础——相量法
  • 积鼎科技携手西北工业大学动力与能源学院共建复杂多相流仿真联合实验室
  • VUE 入门级教程:开启 Vue.js 编程之旅
  • 008静态路由-特定主机路由
  • 矩阵乘法实现填充矩阵F.padding
  • C语言模拟实现简单链表的复盘
  • 【机器学习算法】XGBoost原理
  • 【故障处理系列--业务官网无法打开】
  • 0017. shell命令--tac
  • Milvus的索引类型
  • 【经典论文阅读】Transformer(多头注意力 编码器-解码器)
  • 打造高质量技术文档的关键要素(结合MATLAB)
  • selinux、firewalld
  • JD - HotKey:缓存热 Key 管理的高效解决方案
  • Vue + Vite + Element Plus 与 Django 进行前后端对接
  • 【系统架构设计师】高分论文:论敏捷软件开发方法及其成用
  • TYUT设计模式大题
  • 架构04-透明多级分流系统
  • LeetCode-315. Count of Smaller Numbers After Self