查看全课大纲(第 46 / 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
CNN设计准则
小象实战讲义 · 基于深度学习的计算机视觉
在学习了 AlexNet、VGG、GoogLeNet 和 ResNet 等一系列经典的卷积神经网络后,你是否思考过它们的设计背后是否存在一些共通的规律?为什么有的网络又深又好,有的却难以训练?本节我们将系统性地总结 CNN 的设计准则,这些准则不仅是理解现有网络架构的钥匙,也是未来你尝试设计或改进网络时应当遵循的指导原则。
💡 核心导读
- 避免信息瓶颈:在特征提取过程中,特征图的空间尺寸(高×宽)与通道数的乘积应缓慢减小,防止信息过早、过多地丢失。
- 控制通道数量:输入与输出通道数直接影响模型的参数量和计算复杂度,需将其保持在可控范围内以实现高效计算。
- 扩大感受野:为了捕捉更大范围的上下文信息,需要足够大的感受野,通常通过堆叠多个小卷积核来替代单个大卷积核,以平衡计算量与表达能力。
- 采用分组与分解策略:使用分组卷积、低秩分解(如空间分解为1×n和n×1卷积)等技术,可以显著降低计算复杂度,同时保持甚至增强模型的表达能力。
1. 核心设计准则一:避免信息瓶颈
信息瓶颈(Information Bottleneck)是网络设计中的大忌。它指的是在网络的前向传播过程中,特征信息(如图像的细节、结构等)因网络结构不当而过早、过快地丢失,导致后续层无法获取足够的信息来进行有效的分类或回归。
一个直观的衡量指标是特征图的“信息量”,我们可以粗略地用其三维尺寸的乘积来表示:高(H) × 宽(W) × 通道数(C)。
设计准则:在卷积过程中,应确保 H × W × C 这个乘积是缓慢减小的,而不是骤降。
让我们以 VGG 网络为例来分析。VGG 网络通常由多个卷积块组成,每个块后接一个池化层(通常是最大池化,步长为2)。池化会使空间尺寸(H, W)减半(面积变为原来的 1/4),但为了补偿空间信息的损失,VGG 会在下一个卷积块中显著增加通道数(C)。
import numpy as np
# 模拟 VGG 某几层的特征图尺寸变化
# 初始输入假设为 224x224x3
layers_info = [
("Input", 224, 224, 3),
("After Conv Block 1 & Pool", 112, 112, 64), # 空间减半,通道增至64
("After Conv Block 2 & Pool", 56, 56, 128), # 空间再减半,通道增至128
("After Conv Block 3 & Pool", 28, 28, 256), # 空间再减半,通道增至256
("After Conv Block 4 & Pool", 14, 14, 512), # 空间再减半,通道增至512
("After Conv Block 5 & Pool", 7, 7, 512), # 空间再减半,通道保持512
]
print("模拟 VGG 网络特征图信息量(H*W*C)变化:")
print("-" * 60)
print(f"{'Layer':<30} {'H*W*C':>15} {'相对倍数':>10}")
print("-" * 60)
prev_volume = None
for name, h, w, c in layers_info:
volume = h * w * c
ratio = volume / prev_volume if prev_volume is not None else 1.0
print(f"{name:<30} {volume:>15,} {ratio:>10.2f}")
prev_volume = volume模拟 VGG 网络特征图信息量(H*W*C)变化:
------------------------------------------------------------
Layer H*W*C 相对倍数
------------------------------------------------------------
Input 150,528 1.00
After Conv Block 1 & Pool 802,816 5.33
After Conv Block 2 & Pool 401,408 0.50
After Conv Block 3 & Pool 200,704 0.50
After Conv Block 4 & Pool 100,352 0.50
After Conv Block 5 & Pool 25,088 0.25从输出可以看到,第一次从输入到第一个池化层后,信息量不但没有下降,反而增大到约 5.3 倍(通道数从 3 增至 64,远超空间缩小的损失);此后各层信息量则缩小为原来的 1/2 到 1/4,是一个相对平缓的下降过程。这种“下宽上窄”的结构,类似于埃菲尔铁塔,基础宽大以容纳丰富信息,顶部精细以进行高层抽象,有效避免了信息瓶颈。
2. 核心设计准则二:控制通道数量
卷积层的计算复杂度与输入输出通道数直接相关。给定一个卷积层:
- 输入特征图尺寸:
- 卷积核尺寸:
- 输出通道数:
- 步长(stride),暂不考虑填充(padding)。
该卷积层的参数量为: 每个卷积核有 个参数,共有 个这样的卷积核。
其计算量(浮点运算次数,FLOPs) 近似为: 即,输出特征图每个位置的计算,都需要进行 次乘加运算,共有 个这样的位置。
import numpy as np
def calculate_conv_complexity(H_in, W_in, C_in, H_f, W_f, C_out):
"""计算卷积层的参数量和近似计算量"""
params = H_f * W_f * C_in * C_out
flops = H_in * W_in * H_f * W_f * C_in * C_out
return params, flops
# 示例1:一个典型的中间层卷积
H_in, W_in, C_in = 56, 56, 256 # 输入尺寸
H_f, W_f = 3, 3 # 卷积核尺寸
C_out = 256 # 输出通道数
params, flops = calculate_conv_complexity(H_in, W_in, C_in, H_f, W_f, C_out)
print(f"示例卷积层(3x3, 256->256,输入56x56):")
print(f" 参数量: {params:,}")
print(f" 计算量(FLOPs): {flops:,}")
# 示例2:如果盲目增加输出通道数会怎样?
C_out_double = 512
params_double, flops_double = calculate_conv_complexity(H_in, W_in, C_in, H_f, W_f, C_out_double)
print(f"\n如果将输出通道数从256增加到512:")
print(f" 参数量变为: {params_double:,} (是原来的 {params_double/params:.1f} 倍)")
print(f" 计算量变为: {flops_double:,} (是原来的 {flops_double/flops:.1f} 倍)")示例卷积层(3x3, 256->256,输入56x56):
参数量: 589,824
计算量(FLOPs): 1,849,688,064
如果将输出通道数从256增加到512:
参数量变为: 1,179,648 (是原来的 2.0 倍)
计算量变为: 3,699,376,128 (是原来的 2.0 倍)可以看到,输出通道数 与计算复杂度和参数量呈线性正比关系。无限制地增加通道数会导致模型急剧膨胀,计算开销难以承受。因此,第二个设计准则强调:必须将通道数(即卷积核数量)保持在一个可控的、合理的范围内。后续的许多网络设计(如 GoogLeNet 的 1×1 降维、ResNet 的 Bottleneck 结构)都是为了在增加网络深度或宽度的同时,巧妙地控制通道数,实现“多快好省”。
3. 核心设计准则三:扩大感受野
感受野(Receptive Field)是指卷积神经网络中,输出特征图上的一个像素点,对应输入图像上的区域大小。感受野越大,意味着该像素点能“看到”的原始输入范围越广,从而能捕捉更宏观的语义信息。
这里似乎出现了一个矛盾:
- 准则二启示我们:卷积核尺寸()不能太大,因为参数量和计算量会呈平方级增长(例如,5×5卷积核的参数是3×3的约2.8倍,7×7则是约5.4倍)。
- 分类任务要求我们:需要足够大的感受野来理解图像的整体内容,不能“管中窥豹”。
解决方案:用多个小卷积核堆叠来替代单个大卷积核。
VGG 网络已经实践了这一思想:2个3×3卷积核堆叠的感受野等效于1个5×5卷积核;3个3×3卷积核堆叠的感受野等效于1个7×7卷积核。
import numpy as np
def calculate_receptive_field(kernel_sizes, strides):
"""计算堆叠卷积层的感受野(简化版,假设padding='same'或适当)"""
# 这里我们简化计算,从最后一层向前推
# RF_{i} = (RF_{i-1} - 1) * stride_{i} + kernel_size_{i}
# 初始感受野为1(一个像素)
rf = 1
for k, s in zip(reversed(kernel_sizes), reversed(strides)):
rf = (rf - 1) * s + k
return rf
# 方案A:使用一个7x7卷积核
rf_A = calculate_receptive_field(kernel_sizes=[7], strides=[1])
print(f"方案A(单个7x7卷积)的感受野大小: {rf_A}")
# 方案B:使用三个3x3卷积核堆叠
rf_B = calculate_receptive_field(kernel_sizes=[3, 3, 3], strides=[1, 1, 1])
print(f"方案B(三个3x3卷积堆叠)的感受野大小: {rf_B}")
# 比较参数量(假设输入输出通道数C相同)
C = 64
params_7x7 = 7 * 7 * C * C
params_3x3_stack = 3 * (3 * 3 * C * C) # 三个卷积层,每层输入输出通道都是C
print(f"\n假设输入输出通道数 C = {C}:")
print(f" 方案A(7x7)参数量: {params_7x7:,}")
print(f" 方案B(3x3堆叠)参数量: {params_3x3_stack:,}")
print(f" 参数量比例(B/A): {params_3x3_stack/params_7x7:.2f}")方案A(单个7x7卷积)的感受野大小: 7
方案B(三个3x3卷积堆叠)的感受野大小: 7
假设输入输出通道数 C = 64:
方案A(7x7)参数量: 200,704
方案B(3x3堆叠)参数量: 110,592
参数量比例(B/A): 0.55堆叠小卷积核的优势:
- 参数量更少:如上例,在获得相同感受野(7)的情况下,三个3×3卷积的参数量约为单个7×7的55%。
- 计算可能更高效:虽然操作次数可能相近,但小核的局部性更好,利于硬件优化。
- 引入更多非线性:每个卷积层后都跟有激活函数(如 ReLU)。堆叠三个小卷积核意味着引入了三次非线性变换,而单个大卷积核只引入一次。更多的非线性极大地增强了模型的表达能力,使其能够拟合更复杂的函数。这就像活字印刷(小模块灵活组合)比雕版印刷(一大块固定内容)更强大一样。
4. 高级设计策略:分组与分解
为了进一步降低计算复杂度,研究者们提出了更精细的策略。
4.1 分组卷积(Grouped Convolution)
分组卷积最早在 AlexNet 中被用于在两个 GPU 上并行训练。它将输入和输出的通道分成 组,每组独立进行卷积操作,最后再将结果拼接起来。
- 标准卷积:每个输出通道与所有输入通道相连。
- 分组卷积(组数 ):每个输出通道仅与 个输入通道相连。 其参数量和计算量都减少为原来的 。
import numpy as np
def standard_conv_complexity(C_in, C_out, H_f, W_f):
"""标准卷积参数量"""
return H_f * W_f * C_in * C_out
def grouped_conv_complexity(C_in, C_out, H_f, W_f, groups):
"""分组卷积参数量,假设C_in和C_out能被groups整除"""
assert C_in % groups == 0 and C_out % groups == 0
params_per_group = H_f * W_f * (C_in // groups) * (C_out // groups)
return params_per_group * groups
C_in, C_out = 256, 256
H_f, W_f = 3, 3
params_standard = standard_conv_complexity(C_in, C_out, H_f, W_f)
print(f"标准3x3卷积(256->256)参数量: {params_standard:,}")
for g in [2, 4, 8]:
params_grouped = grouped_conv_complexity(C_in, C_out, H_f, W_f, g)
reduction = (1 - params_grouped/params_standard) * 100
print(f"分组卷积(组数={g})参数量: {params_grouped:,} (减少 {reduction:.1f}%)")标准3x3卷积(256->256)参数量: 589,824
分组卷积(组数=2)参数量: 294,912 (减少 50.0%)
分组卷积(组数=4)参数量: 147,456 (减少 75.0%)
分组卷积(组数=8)参数量: 73,728 (减少 87.5%)分组卷积的极端情况是当组数 等于输入通道数 (且输出通道数也等于 )时,每个通道独立做卷积,这称为深度卷积(Depthwise Convolution);在它之后再加一个 1×1 的逐点卷积(Pointwise Convolution),才构成 MobileNet 等轻量网络中广泛使用的深度可分离卷积(Depthwise Separable Convolution)。ResNeXt 网络则创新性地将“分组”视为网络设计的基数(Cardinality) 维度,与深度、宽度并列,通过增加分支数(即组数)来提升模型性能。
4.2 低秩分解(Low-rank Decomposition)
低秩分解的核心思想是将一个大的卷积核分解为多个小的、秩更低的卷积核的串联或并联。
空间分解:将一个 的卷积分解为一个 卷积和一个 卷积的串联。
- 例如,一个 卷积可以分解为 和 卷积。
- 当输入输出通道数相同(,且中间通道数也为 )时,参数量从 降为 ,减少约三分之一;一般情形下应为 ,并不保证更省,需要按中间通道数具体计算。
- Inception V3 中大量使用了这种非对称分解。
通道分解:这通常通过引入 1×1 卷积 来实现。1×1 卷积有两个重要作用:
- 跨通道的信息整合与交互:对同一空间位置的所有通道值进行线性组合。
- 升降维(Bottleneck):减少或增加通道数,从而控制后续卷积层的计算量。这是 GoogLeNet 和 ResNet 中 Bottleneck 结构的关键。
# 伪代码示意:ResNet Bottleneck 结构
输入 x (H, W, 256)
1. 1x1 卷积,降维至 64 通道 -> 输出 (H, W, 64)
2. 3x3 卷积,处理特征 -> 输出 (H, W, 64)
3. 1x1 卷积,升维回 256 通道 -> 输出 (H, W, 256)
4. 与输入 x 相加(Shortcut Connection)-> 最终输出通过先用1×1卷积将通道数从256降到64,再进行昂贵的3×3卷积,最后再升回256,整个Bottleneck块的参数量和计算量远小于直接进行一个256->256的3×3卷积。
5. 经典CNN的横向对比
综合运用以上准则,我们再来回顾一下经典CNN的发展脉络与性能表现。
import numpy as np
# 基于课件GT表格数据整理
models = [
{"name": "AlexNet (2012)", "layers": 8, "top5_err": 16.4, "params_M": 60, "key_tech": ["ReLU", "Dropout", "LRN(后弃用)", "分组卷积"]},
{"name": "VGG-19 (2014)", "layers": 19, "top5_err": 7.3, "params_M": 144, "key_tech": ["小卷积核堆叠(3x3)", "结构简洁"]},
{"name": "GoogLeNet v1 (2014)", "layers": 22, "top5_err": 6.7, "params_M": 7, "key_tech": ["Inception模块", "1x1卷积(Bottleneck)", "辅助分类器", "全局平均池化"]},
{"name": "ResNet-152 (2015)", "layers": 152, "top5_err": 3.57, "params_M": 60, "key_tech": ["残差连接(Shortcut)", "Bottleneck设计", "BatchNorm"]},
]
print("经典CNN模型对比(数据来源于ILSVRC竞赛)")
print("="*80)
print(f"{'模型':<20} {'层数':>6} {'Top-5错误率(%)':>15} {'参数量(M)':>12} {'关键技术'}")
print("-"*80)
for m in models:
tech_str = ", ".join(m["key_tech"])
print(f"{m['name']:<20} {m['layers']:>6} {m['top5_err']:>15.2f} {m['params_M']:>12} {tech_str}")经典CNN模型对比(数据来源于ILSVRC竞赛)
================================================================================
模型 层数 Top-5错误率(%) 参数量(M) 关键技术
--------------------------------------------------------------------------------
AlexNet (2012) 8 16.40 60 ReLU, Dropout, LRN(后弃用), 分组卷积
VGG-19 (2014) 19 7.30 144 小卷积核堆叠(3x3), 结构简洁
GoogLeNet v1 (2014) 22 6.70 7 Inception模块, 1x1卷积(Bottleneck), 辅助分类器, 全局平均池化
ResNet-152 (2015) 152 3.57 60 残差连接(Shortcut), Bottleneck设计, BatchNorm从对比中我们可以得出一些重要观察:
- 数据增强(Data Augmentation)是标配:所有成功模型都使用了数据增强来弥补训练数据的不足,防止过拟合。
- Dropout 非常有效:作为一种正则化手段,被广泛采用。
- 局部响应归一化(LRN)被淘汰:VGG论文中指出其作用不大;与 VGG 同期的 GoogLeNet v1 仍保留 LRN,自 Batch Normalization 普及后它才基本退出主流。
- 批归一化(Batch Normalization)是里程碑:虽然表中只有ResNet标注使用,但自GoogLeNet v2(Inception V2)提出后,它已成为训练深度网络的关键技术,能稳定训练、加速收敛、允许使用更高的学习率。
- “多快好省”的进化:从AlexNet到ResNet,网络深度急剧增加(8层→152层),Top-5错误率大幅下降(16.4%→3.57%),但ResNet的参数量(60M)却与AlexNet相当,远低于VGG-19(144M)。这完美体现了通过精妙的结构设计(如残差连接、Bottleneck),可以在控制复杂度的同时,实现性能的飞跃。
📝 动手练一练
- 概念辨析:请简述“避免信息瓶颈”和“扩大感受野”这两个设计准则,并说明在VGG网络中是如何体现和平衡这两者的。
- 计算实践:假设一个卷积层的输入是
56x56x128,我们希望输出56x56x256。请计算并对比以下两种方案的参数量,并说明它们在感受野上的差异:- 方案A:直接使用一个
7x7卷积核(感受野为 7)。 - 方案B:使用两个
3x3卷积核堆叠(中间通道数保持 256,感受野为 5)。
- 方案A:直接使用一个
👉 点击查看参考答案
概念辨析:
- 避免信息瓶颈:指在网络前向传播时,特征图总信息量(H×W×C)应缓慢变化,防止信息丢失过快。在VGG中,每次经过步长为2的池化层(H,W减半)后,下一个卷积块会大幅增加通道数(C),使 H×W×C 的下降保持平缓(第一次因通道从 3 增至 64,信息量反而上升)。
- 扩大感受野:指网络深层神经元应对输入图像有足够大的视野,以理解全局语义。VGG通过堆叠多个3×3小卷积核来替代大卷积核(如2个3×3等效5×5感受野,3个等效7×7),在扩大感受野的同时,控制了参数量并引入了更多非线性。
- 平衡:VGG用“小核堆叠”策略,在逐步扩大感受野的过程中(每层感受野增加2),通过池化层逐步压缩空间尺寸并增加通道数,实现了感受野扩大与信息瓶颈避免之间的平衡。
计算实践:
- 方案A(7x7卷积):参数量 = ,感受野为 7。
- 方案B(两个3x3卷积堆叠):
- 第一个3x3卷积:输入128通道,输出256通道。参数量 =
- 第二个3x3卷积:输入256通道,输出256通道。参数量 =
- 总参数量 = ,感受野为 。
- 对比:方案B的参数量约为方案A的 55%,但感受野只有 5,小于 7。若要严格达到感受野 7,需要堆叠三个 3×3 卷积(第三层 256→256 再增加 589,824),总参数量为 ,约为 7×7 方案的 92%。这说明“小核堆叠”的优势在于相同或相近感受野下参数更少、非线性更多,而不是在任何情形下都绝对更省参数。
本章小结
本节我们系统梳理了卷积神经网络(CNN)的四大核心设计准则:
- 避免信息瓶颈:确保特征图尺寸(H, W)与通道数(C)的乘积缓慢变化,维持信息流。
- 控制通道数量:审慎设计输入输出通道数,以管理模型复杂度和计算成本。
- 扩大感受野:通过堆叠小卷积核来等效大卷积核的感受野,从而以更少的参数和更多的非线性来捕捉大范围上下文。
- 采用分组与分解策略:利用分组卷积、空间/通道分解(尤其是1×1卷积)等技术,在几乎不损失性能的前提下,大幅降低模型的参数量和计算量。
这些准则并非凭空而来,而是从AlexNet、VGG、GoogLeNet到ResNet等一系列成功模型的实践中总结出的宝贵经验。它们共同指向一个目标:设计出表达能力强、训练稳定、且计算高效的网络结构。
放到今天看,这四条准则依然成立,但实现手段已经演进:分组卷积发展为深度可分离卷积与通道混洗(MobileNet、ShuffleNet),人工设计逐渐让位于神经架构搜索(NAS)与 EfficientNet 的复合缩放;而 Vision Transformer 用自注意力直接获得全局感受野,绕开了“堆叠小卷积核扩大感受野”这条路径。理解这些准则,仍然是判断一个新架构“为什么这样设计”的共同语言。
📋 行动清单
- 回顾对比:重新浏览一遍AlexNet、VGG、GoogLeNet和ResNet的结构图,用本节学到的四个准则去分析它们各自的设计亮点。
- 手动计算:任选一个经典网络(如VGG16)的某个阶段,手动计算其经过卷积和池化后,特征图尺寸与通道数的变化,验证“信息量”是否缓慢下降。
- 思考应用:如果让你为一个移动设备设计一个轻量级图像分类网络,你会优先考虑运用本节提到的哪几条准则?为什么?(提示:考虑计算资源限制)
—— 小象教研组