查看全课大纲(第 45 / 93 节)
- 1.计算机视觉概述
- 2.计算机视觉应用场景
- 3.困难与挑战
- 4.开源库介绍
- 5.图像显示与存储
- 6.CLAHE
- 7.腐蚀膨胀与开闭运算
- 8.滤波与边缘填充
- 9.各种滤波
- 10.图像金字塔
- 11.傅里叶变换
- 12.短时傅里叶变换与小波变换
- 13.颜色特征与颜色直方图
- 14.几何特征_边缘
- 15.几何特征-角点与斑点
- 16.局部特征-SIFT特征描述子
- 17.局部特征-SURF特征描述子
- 18.局部特征-ORB特征描述子
- 19.其他特征描述子
- 20.传统图像分割技术
- 21.GraphCut
- 22.GrabCut
- 23.人脸检测的传统方法
- 24.行人检测的传统方法HOG+SVM
- 25.行人检测DPM
- 26.人工神经网络的架构
- 27.目标函数与梯度下降
- 28.误差反向传播
- 29.可手算的神经网络
- 30.改进目标函数与克服梯度消散
- 31.神经网络之传统神经网络与深度网络的差异
- 32.各种梯度下降算法
- 33.如何选择合适的梯度下降算法
- 34.避免过适应
- 35.CNN的架构
- 36.CNN的误差反向传播_卷积层
- 37.CNN的误差反向传播_池化层
- 38.分类所用的数据集
- 39.Alexnet详解
- 40.VGG详解
- 41.GoogLeNet_v1
- 42.BatchNormalization
- 43.GoogLeNet_v3v4
- 44.ResNet
- 45.ResNeXt
- 46.CNN设计准则
- 47.图像检索与推土机距离
- 48.基于颜色相似性
- 49.基于形状相似性
- 50.基于纹理相似性
- 51.基于局部特征相似性
- 52.DL的二值哈希编码
- 53.高维空间中检索KDTree
- 54.高维空间中检索LSH
- 55.目标识别任务综述
- 56.RCNN
- 57.SPP-Net
- 58.Fast R-CNN
- 59.Faster R-CNN
- 60.R-CNN系列模型回顾
- 61.R-FCN的设计思路
- 62.R-FCN的实现
- 63.YOLOv1
- 64.YOLOv2_9000
- 65.YOLOv3
- 66.图像分割应用场景与数据集
- 67.上采样的实现方式
- 68.FCN网络架构的由来
- 69.FCN构建细节
- 70.DeepLabv1
- 71.CRF
- 72.DeepLabv2
- 73.DeepLabv3
- 74.特定的医学影像分割任务
- 75.U-Net
- 76.3D U-Net
- 77.V-Net
- 78.DenseNet
- 79.病理切片分析任务及数据集
- 80.病理切片分析
- 81.图说任务及其传统处理方法
- 82.深度语言模型与RNN
- 83.LSTM及其变种
- 84.NIC模型
- 85.SAT模型
- 86.图说的性能指标
- 87.判别模型与生成模型
- 88.KL散度与JS散度
- 89.VAE
- 90.GAN的基本原理
- 91.DCGAN与CGAN
- 92.Wasserstein GAN
- 93.SRGAN
ResNeXt
小象实战讲义 · 基于深度学习的计算机视觉
在ResNet成功解决了极深网络训练难题后,研究者们开始思考:除了将网络做得更深(增加层数)或更宽(增加每层通道数),是否还存在第三条提升模型性能的路径?ResNeXt网络给出了肯定的答案。本节我们将学习这种巧妙的设计,它通过引入“基数”(Cardinality)这一新维度,在几乎不增加参数量的前提下,显著提升了模型的表达能力,并在ILSVRC-2016竞赛中取得了优异成绩。
💡 核心导读
- 核心思想:打破“更深”或“更宽”的常规思路,引入“基数”作为网络设计的第三个维度,即并行变换路径的数量。
- 关键策略:采用“分割-变换-聚合”策略,把同一个输入送入多个低维度的并行分支进行特征变换,最后通过加法合并。
- 结构创新:将 AlexNet 的分组卷积与 GoogLeNet 的多分支并行思想同 ResNet 的残差结构相结合,构建了具有多个并行瓶颈分支的残差块。
- 性能优势:在参数量和计算量相近的情况下,ResNeXt比同等深度的ResNet具有更强的表达能力和更低的分类错误率。
从ResNet到ResNeXt:寻找新维度
在ResNet之后,研究者们继续探索提升卷积神经网络性能的途径。传统的思路主要集中在两个维度上:
- 深度(Deeper):增加网络的层数,如从ResNet-50到ResNet-101、ResNet-152。
- 宽度(Wider):增加每一层卷积核的数量(即输出通道数),以增强单层的特征提取能力。
然而,单纯地增加深度或宽度会带来参数量的急剧膨胀和计算成本的飙升,且性能提升会逐渐饱和,甚至引发退化问题。
ResNeXt由 Saining Xie、何恺明等人于 2017 年提出,他们另辟蹊径,提出了网络设计的第三个维度——基数(Cardinality)。基数指的是在一个模块内部,并行、同构的变换路径的数量。增加基数,意味着在模块内部引入更多的、结构相同的并行计算分支。
这种设计思想的核心是一种经典的策略:分割-变换-聚合(Split-Transform-Aggregate)。
- 分割(Split):把同一个输入送入多个并行的同构分支(每个分支接收完整输入)。
- 变换(Transform):在每个分支上独立进行一系列的特征变换(如卷积、非线性激活)。
- 聚合(Aggregate):将所有分支变换后的结果合并起来,得到最终输出。
这很像算法设计中的“分治法”,也让我们联想到AlexNet中为了适应双GPU而做的通道分组,以及GoogLeNet中Inception模块的多路径并行思想。ResNeXt正是将这种多路径并行的思想,以一种更简洁、更模块化的方式,引入到了ResNet的残差块中。
ResNeXt模块详解:多路径残差学习
为了理解ResNeXt,我们先回顾一下标准的ResNet瓶颈残差块(Bottleneck Block)。
ResNet瓶颈块回顾
一个典型的ResNet-50/101/152中的瓶颈块结构如下(以输入/输出通道为256为例):
- 一个1×1卷积,用于降维(例如,将256维降到64维)。
- 一个3×3卷积,在低维空间进行核心特征提取。
- 一个1×1卷积,用于升维(将64维恢复回256维),以匹配捷径连接(Shortcut)的维度。
- 最后,将变换后的结果与捷径连接传来的原始输入进行逐元素相加(Add)。
其设计目标是:在3×3卷积层保持较小计算量的前提下,通过两端的1×1卷积来控制整体的通道数,形成一个“瓶颈”结构。
ResNeXt模块结构
ResNeXt模块对中间的“变换”部分进行了革命性的改造。它不再使用单一路径的“降维-卷积-升维”,而是将其复制了多次,形成多条并行的路径。
我们以一个具体的配置为例:32x4d。这表示该模块有32个(Cardinality=32)并行路径,每个路径的瓶颈宽度(Bottleneck width)为4(即降维后的通道数d=4)。
一个32x4d的ResNeXt模块工作流程如下:
- 输入:假设输入特征图尺寸为
[H, W, 256]。 - 变换:同一个输入被送入32个完全相同的并行分支(每个分支接收完整的256维输入)。每个分支内部进行如下操作:
- 1×1卷积(降维):将256维输入降至极低的4维(
d=4)。这一步降维比ResNet(降至64维)更彻底,旨在极大减少每个分支的计算量。 - 3×3卷积(特征提取):在4维空间进行3×3卷积。
- 1×1卷积(升维):将4维特征升回256维,确保所有分支输出维度一致。
- 1×1卷积(降维):将256维输入降至极低的4维(
- 聚合(Aggregate):将32个分支输出的
[H, W, 256]特征图进行逐元素相加,得到一个[H, W, 256]的特征图。这是模块内部的第一次关键“加法”。 - 残差连接(Shortcut):将步骤3聚合的结果,与原始的输入(通过恒等映射或1×1卷积调整维度后)进行第二次逐元素相加。这是模块外部的第二次关键“加法”,构成了残差学习。
通过这种设计,ResNeXt模块内部结构变得比ResNet更复杂(32条路径),但每条路径的计算负担却轻得多(通道数仅4)。这种“化整为零”的策略,是提升模型表达能力的关键。
import numpy as np
# 为了清晰演示ResNeXt的“分割-变换-聚合”思想,我们模拟一个极度简化的过程。
# 假设我们有一个微型“特征向量”(代表某个空间位置的所有通道),而不是整张图。
# 输入特征: 一个8维的向量 (模拟简化版的256维)
input_feature = np.array([1.0, 2.0, 3.0, 4.0, 5.0, 6.0, 7.0, 8.0])
print("输入特征向量:", input_feature)
# 假设我们采用 `4x2d` 配置:4个分支(Cardinality=4),每个分支瓶颈宽度d=2。
# 1. 为演示分组计算,这里把8维输入切成4段,每个分支各得2维。
# 注意:真实 ResNeXt 的每个分支接收完整的输入通道,通道分组发生在瓶颈层内部的
# 分组卷积中;这里只是为了用数组切片演示“分组”的计算形态。
branches_input = [
input_feature[0:2], # 分支1得到 [1., 2.]
input_feature[2:4], # 分支2得到 [3., 4.]
input_feature[4:6], # 分支3得到 [5., 6.]
input_feature[6:8], # 分支4得到 [7., 8.]
]
print("\n分割后各分支输入:")
for i, inp in enumerate(branches_input):
print(f" 分支{i+1}: {inp}")
# 2. 变换:每个分支进行独立的“变换”。
# 为了极度简化,我们假设每个分支的“变换”就是将其输入乘以一个分支特定的权重矩阵W_i (2x2),
# 然后加一个偏置b_i (2,),最后用ReLU激活。
# 注意:这里W_i和b_i是每个分支独立学习的参数。
np.random.seed(42) # 固定随机种子,确保结果可复现
branch_outputs = []
for i, inp in enumerate(branches_input):
# 为每个分支随机生成权重和偏置 (模拟学习到的参数)
W = np.random.randn(2, 2) * 0.1
b = np.random.randn(2,) * 0.1
# 进行“变换”: y = ReLU(W * x + b)
transformed = np.maximum(0, np.dot(W, inp) + b)
branch_outputs.append(transformed)
print(f" 分支{i+1}变换后输出: {transformed}")
# 3. 聚合:将所有分支的输出在通道维度上拼接起来。
# 注意:在真实的ResNeXt中,每个分支已通过1×1卷积升回256维,输出维度与总输出相同,
# 因此聚合是“逐元素相加”。这里每个分支输出只有2维,为了得到8维结果,先用“拼接”模拟
# “特征整合”;更接近真实情况的“相加”聚合见下一个代码块。
aggregated_by_concat = np.concatenate(branch_outputs)
print(f"\n聚合结果(拼接): {aggregated_by_concat}")
print(f"聚合后维度: {aggregated_by_concat.shape}")输入特征向量: [1. 2. 3. 4. 5. 6. 7. 8.]
分割后各分支输入:
分支1: [1. 2.]
分支2: [3. 4.]
分支3: [5. 6.]
分支4: [7. 8.]
分支1变换后输出: [0. 0.34596113]
分支2变换后输出: [0.73439597 0.02960873]
分支3变换后输出: [0. 0.]
分支4变换后输出: [0. 0.70285828]
聚合结果(拼接): [0. 0.34596113 0.73439597 0.02960873 0. 0.
0. 0.70285828]
聚合后维度: (8,)上面的代码用“拼接”模拟了聚合。实际上,在ResNeXt模块中,每个分支的最终输出维度(经过升维后)与模块的最终输出维度相同(例如都是256维),因此聚合操作是逐元素相加。下面的代码模拟这种更真实的情况:
import numpy as np
# 模拟更接近真实ResNeXt的“相加”聚合方式。
# 假设:输入输出维度为4(简化版256),基数C=2,每个分支瓶颈宽度d=1(简化版4)。
# 每个分支完成“降维(d=1)->变换->升维(回4)”的操作,输出一个4维向量,然后多个分支的4维输出相加。
input_dim = 4
cardinality = 2
bottleneck_width = 1
# 模拟输入: 一个4维特征向量
x = np.array([0.5, -0.2, 0.8, 0.1])
print(f"输入特征 x: {x}")
# 模拟两个分支的变换。每个分支需要:
# W1_down: (1,4) 的矩阵,将4维降至1维。
# 在1维空间进行变换(这里简化,假设为恒等变换)。
# W1_up: (4,1) 的矩阵,将1维升回4维。
np.random.seed(123)
branch_outputs_for_sum = []
for c in range(cardinality):
W_down = np.random.randn(bottleneck_width, input_dim) * 0.1
W_up = np.random.randn(input_dim, bottleneck_width) * 0.1
# 分支变换: output = W_up * (W_down * x)
# 注意:这里省略了偏置和非线性激活以简化演示
z = np.dot(W_down, x) # 降维
output = np.dot(W_up, z) # 升维
branch_outputs_for_sum.append(output.flatten()) # 展平为向量
print(f" 分支{c}输出: {output.flatten()}")
# 聚合:将所有分支的输出相加
aggregated_by_sum = np.sum(branch_outputs_for_sum, axis=0)
print(f"\n聚合结果(相加): {aggregated_by_sum}")
# 模拟捷径连接(假设是恒等映射)
shortcut = x
# 残差相加
final_output = aggregated_by_sum + shortcut
print(f"与捷径连接相加后最终输出: {final_output}")输入特征 x: [ 0.5 -0.2 0.8 0.1]
分支0输出: [ 0.00385655 -0.01100734 0.01617457 0.00285884]
分支1输出: [ 0.0037842 -0.00162113 -0.00112654 -0.00110211]
聚合结果(相加): [ 0.00764075 -0.01262846 0.01504803 0.00175673]
与捷径连接相加后最终输出: [ 0.50764075 -0.21262846 0.81504803 0.10175673]参数量分析与性能对比
ResNeXt最引人注目的优势在于,它在参数量几乎不变的情况下,通过增加基数(内部结构复杂度)获得了更强的性能。
参数量对比
我们以输入/输出通道为256的模块为例进行估算。
ResNet瓶颈块参数量(忽略偏置):
- 1×1卷积(256->64):权重尺寸
1*1*256*64 = 16,384 - 3×3卷积(64->64):权重尺寸
3*3*64*64 = 36,864 - 1×1卷积(64->256):权重尺寸
1*1*64*256 = 16,384 - 总计:
16,384 + 36,864 + 16,384 = 69,632 ≈ 70K
- 1×1卷积(256->64):权重尺寸
ResNeXt瓶颈块参数量(
32x4d配置):- 共有
C=32个分支。 - 每个分支:
- 1×1卷积(256->4):
1*1*256*d - 3×3卷积(4->4):
3*3*d*d - 1×1卷积(4->256):
1*1*d*256
- 1×1卷积(256->4):
- 单分支参数量:
256*d + 9*d*d + 256*d = 512*d + 9*d^2 - 总参数量:
C * (512*d + 9*d^2) - 代入
C=32, d=4:32 * (512*4 + 9*16) = 32 * (2048 + 144) = 32 * 2192 = 70,144 ≈ 70K
- 共有
可以看到,32x4d 的ResNeXt模块与标准的ResNet瓶颈模块参数量非常接近。
性能对比
在ImageNet数据集上的实验表明,在控制参数量和计算量相近的条件下,增加基数能持续提升模型性能。
例如:
- ResNet-50(约 25.6M 参数)的 Top-1 错误率约为 23.9%。
- 参数量相近的 ResNeXt-50 (32x4d)(约 25.0M 参数),其 Top-1 错误率降至约 22.2%。
- 同样,ResNet-101(约 44.5M 参数)的错误率约为 22.0%,而 ResNeXt-101 (32x4d)(约 44.2M 参数)的错误率可降至约 21.2%。
论文中的实验系统性地表明,在总参数量受限的条件下,增加基数(C)同时减小每个分支的宽度(d),比单纯增加深度或宽度能更有效地降低错误率。这验证了“基数”作为网络设计新维度的有效性。
核心总结:基数维度的意义
ResNeXt的成功,打破了之前“非深即宽”的常规网络设计思路。它揭示出,通过增加网络模块内部并行、同构变换路径的数量(即基数),可以作为一种更高效的方式来提升模型的表达能力。
这种设计的本质是:
- 结构化稀疏性:多个并行分支可以看作是对一个密集的全通道卷积操作的一种高效、结构化的近似,它引入了归纳偏置,让模型更容易学习到有效的特征组合。
- 分治效率:将复杂的特征变换任务分解到多个低计算成本的分支中并行处理,最后汇总,在保证表达力的同时控制了计算复杂度。
- 与残差的协同:多分支聚合后的结果再与捷径连接相加,这种“聚合加”与“残差加”的两级加法结构,是ResNeXt性能优异的关键。
因此,ResNeXt可以简单理解为 “使用了分组卷积的ResNet” 或 “具有残差连接的、多分支Inception模块”。它继承了ResNet的易于优化性,同时吸收了多路径结构的强大表达能力,成为深度学习模型设计中的一个经典范例。
📝 动手练一练
- 概念辨析:ResNeXt中提出的“基数”(Cardinality)维度,与网络的“深度”和“宽度”维度有何本质区别?请用“分割-变换-聚合”策略来解释。
👉 点击查看参考答案
深度 指的是网络中层(Layer)的数量,它决定了特征从低级到高级的抽象层次。 宽度 通常指某一层中卷积核的数量(输出通道数),它决定了该层能同时提取多少种不同类型的特征。 基数 指的是在一个模块(Block)内部,并行、同构的变换路径的数量。它不增加网络的层数,也不直接增加单层的通道数,而是通过“分割-变换-聚合”策略,在模块内部引入多个并行的计算流。每个流处理输入特征的一个子集(分组),进行独立的低维变换,最后将所有流的结果合并。增加基数,相当于增加了模块内部结构的复杂性和多样性,从而提升了模块整体的特征变换能力。
- 参数计算:假设一个ResNeXt模块配置为
16x8d(基数C=16,每个分支瓶颈宽度d=8),输入和输出通道数均为512。请计算该模块中所有3×3卷积层的参数总量(忽略偏置项,仅计算权重)。
👉 点击查看参考答案
每个分支中,3×3卷积层的输入输出通道数均为 d=8。 因此,单个分支的3×3卷积参数量为: 3 * 3 * 8 * 8 = 576。 总共有 C=16 个分支,且所有分支的3×3卷积层参数是独立的(不是共享的)。 所以,该模块所有3×3卷积层的参数总量为: 16 * 576 = 9,216。
本章小结
本节我们深入学习了ResNeXt网络,它标志着卷积神经网络设计思想的一次重要演进。
- 核心突破:ResNeXt提出了网络设计的第三个维度——基数(Cardinality),即模块内部并行路径的数量,打破了单纯追求更深或更宽的传统思路。
- 关键结构:采用 “分割-变换-聚合” 策略,构建了具有多个并行瓶颈分支的残差模块。每个分支在低维度进行计算,最后通过加法聚合,并与捷径连接相加。
- 卓越效率:通过在参数量和计算量基本不变的条件下增加基数(如
32x4d),ResNeXt获得了比同等复杂度ResNet更强大的表达能力和更优的分类精度(如在ImageNet上错误率显著下降)。 - 设计哲学:ResNeXt体现了结构化设计和模型效率的重要性。它表明,精心设计的模块内部结构(多路径、分组计算)是提升模型性能的有效途径,而不仅仅是堆叠更多的层或通道。
今天,ResNeXt 本身已不再是分类任务的首选,但它提出的分组卷积与基数思想被后续模型广泛吸收:分组卷积在 MobileNet、ShuffleNet 中演化为深度可分离卷积与通道混洗,ResNeXt 也长期作为检测、分割任务的高性价比骨干(如 Faster R-CNN、Mask R-CNN 的常见 backbone);“在固定算力下调整分支数而非一味加深加宽”的思路,则与 EfficientNet 的复合缩放、NAS 搜索一脉相承。
📋 行动清单
- 在脑海中绘制并对比ResNet瓶颈块与ResNeXt (
32x4d) 模块的结构图,明确两个“加法”操作的位置与意义。 - 理解“基数”的定义,并能用自己的话解释为什么增加基数可以提升模型性能而不显著增加参数量。
- 尝试根据公式估算不同配置(如
64x2d)下ResNeXt模块的参数量,并与标准ResNet模块进行比较。
—— 小象教研组