
文章目录
1. 课前导读
1.1 本节课学习目标
- 理解卷积核尺寸(1×1、3×3、5×7等)对感受野、参数量和特征提取能力的影响。
- 掌握小卷积核堆叠替代大卷积核的理论依据(如两个3×3等效一个5×5,参数量更少)。
- 学会根据输入分辨率和任务需求选择步距(stride)和填充(padding)策略,计算输出尺寸并分析边界信息损失。
- 理解网络深度与宽度的权衡:过浅欠拟合,过深退化(梯度消失/爆炸)和过拟合。
- 掌握深度可分离卷积(Depthwise Separable Convolution)的原理及其在轻量化网络中的应用。
- 能够通过实验对比不同配置下的模型性能,并基于验证集指标调优。
1.2 知识重难点
| 类别 | 内容 |
|---|---|
| 重点 | 小卷积核堆叠的优势;步距与填充对特征图尺寸和感受野的量化影响;网络深度与宽度的实验对比方法 |
| 难点 | 感受野的递推计算;深度可分离卷积的参数量节省原理;网络退化问题(非梯度原因)与残差连接的初步概念 |
| 易混淆点 | 步距(stride)在卷积层和池化层中的不同语义;填充same在步距>1时输出尺寸的计算;1×1卷积的作用(通道降维/升维) |
1.3 学习前置条件
- 已掌握CNN基本结构(第21课)和经典模型(第23课)。
- 能够使用TensorFlow搭建CNN并训练(第22、24课)。
- 熟悉模型评估方法(第18课)。
1.4 学完可掌握能力
- 独立设计CNN时科学选择卷积核尺寸和数量。
- 通过调整步距和填充控制特征图下采样速度,平衡感受野和分辨率。
- 判断给定任务所需的网络深度范围,避免无效的层数堆叠。
- 应用深度可分离卷积压缩模型,适合移动端部署。
1.5 行业应用场景
- 移动端视觉:使用深度可分离卷积(如MobileNet)减少计算量。
- 高分辨率图像:通过步距和填充控制内存占用。
- 医学影像:小目标检测需小卷积核、小步距保留细节。
- 实时系统:优化卷积核组合降低FLOPs。
2. 核心理论精讲
2.1 卷积核选型:尺寸与数量
2.1.1 卷积核尺寸
常见卷积核尺寸:1×1、3×3、5×5、7×7。选择原则:
- 小核(3×3):参数量少,计算快,堆叠后感受野大,非线性更强。VGG系列证明两个3×3等效一个5×5,三个3×3等效一个7×7,且参数量更少。
- 大核(5×5以上):感受野大,适合捕捉大范围纹理,但参数量大,易过拟合。通常只在第一层使用(如AlexNet的11×11)。
- 1×1卷积:不改变空间尺寸,用于跨通道信息整合(通道降维/升维),在Inception、ResNet中广泛应用。
参数量对比:
- 一个5×5卷积,输入通道C_in,输出C_out:参数量 = 5×5×C_in×C_out。
- 两个3×3卷积(中间无池化):参数量 = 3×3×C_in×C_mid + 3×3×C_mid×C_out。若C_mid合理(例如C_mid = C_out),则约为2×9×C_in×C_out = 18×C_in×C_out,而5×5卷积为25×C_in×C_out。因此两个3×3参数量更少,且多一次非线性,表达能力更强。
2.1.2 卷积核数量(通道数)
- 浅层:核数量较少(如32、64),提取低级特征。
- 深层:核数量翻倍(如128、256),增加抽象特征多样性。
- 瓶颈结构:使用1×1卷积先降维再升维,控制计算量(如ResNet的bottleneck)。
2.2 步距与填充
2.2.1 步距(Stride)
步距定义卷积核滑动的像素数。步距>1可降低输出尺寸,相当于下采样。步距过大可能丢失细粒度信息。通常卷积层步距默认为1,通过池化层下采样;也可在卷积层直接使用步距2代替池化(如Strider CNN)。
2.2.2 填充(Padding)
- Valid:不填充,输出尺寸减小,边界信息使用次数少。
- Same:填充使输出尺寸 = 输入尺寸 / 步距(向上取整),保留边界信息。
公式回顾:
[
O = \left\lfloor \frac{H + 2P - K}{S} \right\rfloor + 1
]
对于same填充,通常取 ( P = \lfloor (K-1)/2 \rfloor ) 使输出与输入尺寸在步距1时相同。
选择策略:若希望保留边缘信息且不损失尺寸,使用same填充;若需剧烈下采样且边界不重要,可用valid。
2.3 网络深度与宽度
2.3.1 深度(层数)
- 欠拟合:网络太浅,无法学习复杂模式。
- 过拟合:网络太深,训练数据不足时容易过拟合。
- 退化问题:网络过深时,训练误差不降反升(非梯度消失引起),ResNet通过残差连接解决。
一般建议:对于CIFAR-10/100,10~20层卷积足够;ImageNet需50层以上。
2.3.2 宽度(通道数)
增加宽度可提升模型容量,但参数量线性增长。宽度与深度的权衡:相同参数下,深度比宽度更重要(但太深会导致优化困难)。
2.3.3 学习率与深度关系
深层网络对学习率更敏感,需使用更小的学习率或学习率预热。
2.4 感受野计算
感受野(Receptive Field)是输出特征图上一个像素对应输入图像的区域大小。递推公式:
[
RF_{l} = RF_{l-1} + (K_l - 1) \times \prod_{i=1}^{l-1} S_i
]
其中 ( K_l ) 为第l层核大小,( S_i ) 为第i层步距。感受野应逐步增大至覆盖整个图像。
2.5 深度可分离卷积
标准卷积:每个输出通道与所有输入通道卷积,参数量 = ( K^2 \times C_{in} \times C_{out} )。
深度可分离卷积分为两步:
- 深度卷积:每个输入通道单独卷积,输出通道数 = ( C_{in} ),参数量 = ( K^2 \times C_{in} )。
- 逐点卷积:1×1卷积,将深度卷积输出组合成 ( C_{out} ) 通道,参数量 = ( C_{in} \times C_{out} )。
总参数量 = ( K^2 \times C_{in} + C_{in} \times C_{out} ),相比标准卷积减少约 ( 1/C_{out} + 1/K^2 ) 倍。在MobileNet中广泛应用。
3. 环境搭建与工具配置
沿用第24课环境。需安装matplotlib和seaborn用于绘图。
conda activate tf213
python
导入模块:
import tensorflow as tf
import numpy as np
import matplotlib.pyplot as plt
import seaborn as sns
from tensorflow.keras import layers, models, datasets, callbacks
import time
4. 代码实战教学
4.1 不同卷积核尺寸对比实验
在CIFAR-10上构建三个网络,分别使用5×5、3×3堆叠、7×7卷积核,保持参数量相近。
(x_train, y_train), (x_test, y_test) = datasets.cifar10.load_data()
x_train = x_train.astype(np.float32) / 255.0
x_test = x_test.astype(np.float32) / 255.0
y_train = tf.keras.utils.to_categorical(y_train, 10)
y_test = tf.keras.utils.to_categorical(y_test, 10)
def build_model_with_kernel(kernel_sizes, use_bn=True):
model = models.Sequential()
model.add(layers.Input(shape=(32,32,3)))
for i, ks in enumerate(kernel_sizes):
model.add(layers.Conv2D(32 if i==0 else 64, ks, padding='same'))
if use_bn:
model.add(layers.BatchNormalization())
model.add(layers.Activation('relu'))
if i < len(kernel_sizes)-1:
model.add(layers.MaxPooling2D(2))
model.add(layers.GlobalAveragePooling2D())
model.add(layers.Dense(10, activation='softmax'))
return model
# 配置1: 单层5x5卷积(等效感受野5)
model_5x5 = build_model_with_kernel([5])
# 配置2: 两个3x3卷积堆叠(等效感受野5)
model_3x3_stack = build_model_with_kernel([3,3])
# 配置3: 单层7x7卷积(更大感受野)
model_7x7 = build_model_with_kernel([7])
models_dict = {'5x5': model_5x5, '3x3x2': model_3x3_stack, '7x7': model_7x7}
histories = {}
for name, m in models_dict.items():
print(f"Training {name}")
m.compile(optimizer='adam', loss='categorical_crossentropy', metrics=['accuracy'])
# 使用少量epoch快速对比
h = m.fit(x_train, y_train, validation_split=0.1, epochs=15, batch_size=64, verbose=0)
histories[name] = h
plt.figure()
for name, h in histories.items():
plt.plot(h.history['val_accuracy'], label=name)
plt.xlabel('Epoch')
plt.ylabel('Val Accuracy')
plt.legend()
plt.title('Kernel Size Comparison')
plt.show()
结论:两个3×3堆叠通常优于单个5×5,因为更多非线性。
4.2 步距与填充实验
对比步距1和2,以及填充same/valid对输出尺寸和性能的影响。
def build_model_with_stride_padding(stride, padding):
model = models.Sequential([
layers.Conv2D(32, 3, strides=stride, padding=padding, input_shape=(32,32,3)),
layers.BatchNormalization(),
layers.Activation('relu'),
layers.Conv2D(64, 3, strides=1, padding='same'),
layers.BatchNormalization(),
layers.Activation('relu'),
layers.MaxPooling2D(2),
layers.GlobalAveragePooling2D(),
layers.Dense(10, activation='softmax')
])
return model
configs = [
('stride1_same', 1, 'same'),
('stride2_same', 2, 'same'),
('stride1_valid', 1, 'valid'),
('stride2_valid', 2, 'valid')
]
for name, s, p in configs:
model = build_model_with_stride_padding(s, p)
# 打印第一层输出形状
dummy = tf.zeros((1,32,32,3))
out = model.layers[0](dummy)
print(f"{name}: first conv output shape = {out.shape}")
# 快速训练
model.compile(optimizer='adam', loss='categorical_crossentropy', metrics=['accuracy'])
model.fit(x_train, y_train, validation_split=0.1, epochs=10, batch_size=64, verbose=0)
test_loss, test_acc = model.evaluate(x_test, y_test, verbose=0)
print(f"{name} test acc: {test_acc:.4f}")
观察:步距2导致尺寸减半,信息丢失可能降低准确率;valid填充边界信息丢失,小图像上影响明显。
4.3 网络深度实验
构建不同深度的CNN(2、4、6个卷积块),对比性能。
def build_deep_cnn(num_blocks, base_filters=32):
model = models.Sequential()
model.add(layers.Input(shape=(32,32,3)))
filters = base_filters
for i in range(num_blocks):
model.add(layers.Conv2D(filters, 3, padding='same'))
model.add(layers.BatchNormalization())
model.add(layers.Activation('relu'))
model.add(layers.Conv2D(filters, 3, padding='same'))
model.add(layers.BatchNormalization())
model.add(layers.Activation('relu'))
model.add(layers.MaxPooling2D(2))
filters *= 2 # 每块后翻倍
model.add(layers.GlobalAveragePooling2D())
model.add(layers.Dense(10, activation='softmax'))
return model
depths = [2, 4, 6] # 卷积块数量
hist_depth = {}
for d in depths:
print(f"Training depth {d} blocks")
model = build_deep_cnn(d)
model.compile(optimizer='adam', loss='categorical_crossentropy', metrics=['accuracy'])
h = model.fit(x_train, y_train, validation_split=0.1, epochs=25, batch_size=64, verbose=0)
hist_depth[d] = h
test_acc = model.evaluate(x_test, y_test, verbose=0)[1]
print(f"Test acc: {test_acc:.4f}")
# 绘制验证准确率曲线
for d, h in hist_depth.items():
plt.plot(h.history['val_accuracy'], label=f'{d} blocks')
plt.xlabel('Epoch')
plt.ylabel('Val Accuracy')
plt.legend()
plt.title('Effect of Network Depth')
plt.show()
通常4块比2块好,但6块可能因退化导致提升不大(若无残差连接)。
4.4 深度可分离卷积实现与对比
实现一个标准卷积块和一个深度可分离卷积块,比较参数量和准确率。
def separable_cnn():
model = models.Sequential([
layers.SeparableConv2D(32, 3, padding='same', activation='relu', input_shape=(32,32,3)),
layers.BatchNormalization(),
layers.MaxPooling2D(2),
layers.SeparableConv2D(64, 3, padding='same', activation='relu'),
layers.BatchNormalization(),
layers.MaxPooling2D(2),
layers.SeparableConv2D(128, 3, padding='same', activation='relu'),
layers.BatchNormalization(),
layers.GlobalAveragePooling2D(),
layers.Dense(10, activation='softmax')
])
return model
def standard_cnn():
model = models.Sequential([
layers.Conv2D(32, 3, padding='same', activation='relu', input_shape=(32,32,3)),
layers.BatchNormalization(),
layers.MaxPooling2D(2),
layers.Conv2D(64, 3, padding='same', activation='relu'),
layers.BatchNormalization(),
layers.MaxPooling2D(2),
layers.Conv2D(128, 3, padding='same', activation='relu'),
layers.BatchNormalization(),
layers.GlobalAveragePooling2D(),
layers.Dense(10, activation='softmax')
])
return model
sep_model = separable_cnn()
std_model = standard_cnn()
print("Separable CNN params:", sep_model.count_params())
print("Standard CNN params:", std_model.count_params())
sep_model.compile(optimizer='adam', loss='categorical_crossentropy', metrics=['accuracy'])
std_model.compile(optimizer='adam', loss='categorical_crossentropy', metrics=['accuracy'])
history_sep = sep_model.fit(x_train, y_train, validation_split=0.1, epochs=15, batch_size=64, verbose=0)
history_std = std_model.fit(x_train, y_train, validation_split=0.1, epochs=15, batch_size=64, verbose=0)
plt.plot(history_sep.history['val_accuracy'], label='Separable')
plt.plot(history_std.history['val_accuracy'], label='Standard')
plt.legend()
plt.title('Separable vs Standard Conv')
plt.show()
可分离卷积参数量少,但准确率可能略低。
4.5 感受野计算工具
编写函数计算给定网络配置的感受野。
def compute_receptive_field(layers_info):
"""layers_info: list of (kernel_size, stride)"""
rf = 1
for k, s in reversed(layers_info):
rf = rf + (k - 1) * s
return rf
# 例如:两个3x3卷积,步距1,第一个池化步距2
layers = [(3,1), (3,1), (2,2)] # 池化层核大小2步距2
rf = compute_receptive_field(layers)
print(f"Receptive field: {rf}") # 计算得到 1 + (3-1)*1 + (3-1)*1*? 需正确递推。
# 更准确使用递推: 从最后一层向前
def rf_calc(layers):
rf = 1
stride_product = 1
for k, s in reversed(layers):
rf = rf + (k - 1) * stride_product
stride_product *= s
return rf
print(rf_calc(layers)) # 正确计算
5. 案例实操演练
案例:在CIFAR-10上优化CNN超参数,提升准确率至90%以上
我们将系统调整卷积核尺寸、步距填充、深度、深度可分离等,并采用数据增强和正则化。
5.1 基线模型
定义一个包含3个卷积块(每个块2个卷积+BN+ReLU+池化)的模型,使用3×3核,步距1,same填充,全局平均池化后接Dense层。
def baseline_model():
model = models.Sequential()
model.add(layers.Input(shape=(32,32,3)))
# Block1
model.add(layers.Conv2D(32, 3, padding='same'))
model.add(layers.BatchNormalization())
model.add(layers.ReLU())
model.add(layers.Conv2D(32, 3, padding='same'))
model.add(layers.BatchNormalization())
model.add(layers.ReLU())
model.add(layers.MaxPooling2D(2))
# Block2
model.add(layers.Conv2D(64, 3, padding='same'))
model.add(layers.BatchNormalization())
model.add(layers.ReLU())
model.add(layers.Conv2D(64, 3, padding='same'))
model.add(layers.BatchNormalization())
model.add(layers.ReLU())
model.add(layers.MaxPooling2D(2))
# Block3
model.add(layers.Conv2D(128, 3, padding='same'))
model.add(layers.BatchNormalization())
model.add(layers.ReLU())
model.add(layers.Conv2D(128, 3, padding='same'))
model.add(layers.BatchNormalization())
model.add(layers.ReLU())
model.add(layers.GlobalAveragePooling2D())
model.add(layers.Dense(10, activation='softmax'))
return model
5.2 调优实验
- 核尺寸优化:比较3×3、5×5、以及混合。
- 步距优化:将部分池化层替换为步距2的卷积层,观察是否提升。
- 深度优化:增加一个卷积块(4块),比较性能。
- 深度可分离:使用
SeparableConv2D替代标准卷积,减少参数量。
5.3 最终模型与训练
综合最佳配置,加入数据增强,使用余弦退火学习率。
# 数据增强
data_aug = tf.keras.Sequential([
layers.RandomFlip("horizontal"),
layers.RandomRotation(0.1),
layers.RandomZoom(0.1),
])
def preprocess_train(x, y):
x = data_aug(x)
return x, y
train_ds = tf.data.Dataset.from_tensor_slices((x_train, y_train)).shuffle(50000).batch(128).map(preprocess_train).prefetch(tf.data.AUTOTUNE)
val_ds = tf.data.Dataset.from_tensor_slices((x_test, y_test)).batch(128).prefetch(tf.data.AUTOTUNE) # 简化,实际应从训练集划分验证
# 最终模型(使用深度可分离卷积 + 适当深度)
final_model = models.Sequential([
layers.SeparableConv2D(32, 3, padding='same', input_shape=(32,32,3)),
layers.BatchNormalization(),
layers.ReLU(),
layers.SeparableConv2D(32, 3, padding='same'),
layers.BatchNormalization(),
layers.ReLU(),
layers.MaxPooling2D(2),
layers.SeparableConv2D(64, 3, padding='same'),
layers.BatchNormalization(),
layers.ReLU(),
layers.SeparableConv2D(64, 3, padding='same'),
layers.BatchNormalization(),
layers.ReLU(),
layers.MaxPooling2D(2),
layers.SeparableConv2D(128, 3, padding='same'),
layers.BatchNormalization(),
layers.ReLU(),
layers.SeparableConv2D(128, 3, padding='same'),
layers.BatchNormalization(),
layers.ReLU(),
layers.GlobalAveragePooling2D(),
layers.Dense(10, activation='softmax')
])
final_model.compile(optimizer=tf.keras.optimizers.Adam(0.001),
loss='categorical_crossentropy', metrics=['accuracy'])
# 回调
callbacks = [
callbacks.EarlyStopping(patience=10, restore_best_weights=True),
callbacks.ReduceLROnPlateau(factor=0.5, patience=3),
callbacks.ModelCheckpoint('best_cifar.h5', save_best_only=True)
]
history_final = final_model.fit(train_ds, validation_data=val_ds, epochs=100, callbacks=callbacks, verbose=1)
test_loss, test_acc = final_model.evaluate(x_test, y_test, verbose=0)
print(f"Final test accuracy: {test_acc:.4f}")
6. 常见坑点与排错总结
6.1 卷积核选型坑点
-
坑1:盲目使用大卷积核(如7×7),导致参数量激增且易过拟合。
- 解决:优先堆叠3×3,仅在第一层用稍大核(如5×5)。
-
坑2:忽略1×1卷积的通道降维作用,导致模型参数冗余。
6.2 步距与填充坑点
-
坑3:步距设置不当导致输出尺寸为0或负值,程序崩溃。
- 解决:使用
model.summary()检查每层输出尺寸,必要时调整。
- 解决:使用
-
坑4:
padding='same'在步距>1时输出尺寸为ceil(H/s),可能不是整数倍,影响后续连接。通常没问题,但需注意全局平均池化前尺寸不宜过小。
6.3 深度与宽度坑点
-
坑5:网络过深(>20层)且无残差连接,训练误差上升。
- 解决:添加残差块(第31课),或使用预训练模型。
-
坑6:宽度(通道数)增加过快(如每层翻倍),导致后半部分特征图通道数过大,参数量爆炸。
- 建议:每池化一次翻倍,保持参数量增长可控。
6.4 深度可分离卷积坑点
-
坑7:误认为深度可分离卷积总是优于标准卷积,实际上在小型数据集上可能因表达能力下降而准确率降低。
- 建议:在移动端或大模型压缩时使用,小模型慎用。
-
坑8:
SeparableConv2D的默认参数:depth_multiplier=1,即每个输入通道只生成一个深度通道。可增大该值提升容量。
7. 知识点总结 + 课后作业
7.1 核心知识点梳理
- 卷积核选型:小核堆叠优于大核,1×1用于通道调整。
- 步距与填充:控制特征图尺寸和边界信息保留。
- 深度与宽度:增加深度提升性能但存在退化,需配合残差;宽度增加容量但参数量大。
- 感受野计算:递推公式有助于理解网络视野。
- 深度可分离卷积:减少参数量,适用于轻量网络。
7.2 基础作业
- 计算一个5层网络(卷积核3×3,步距1,池化层每两层一次,核大小2步距2)的最后一层感受野。
- 在CIFAR-10上对比使用3×3卷积和5×5卷积(单层,不堆叠)的模型,记录参数量和准确率。
- 实现一个函数,给定模型,自动计算所有卷积层的理论感受野(假设输入无限)。
7.3 进阶实操作业
任务:设计一个轻量级CNN用于CIFAR-10,要求参数量小于50万,准确率高于85%
要求:
- 使用深度可分离卷积或分组卷积。
- 控制网络深度在10层以内。
- 使用数据增强和正则化。
- 训练并报告测试准确率、参数量、FLOPs(可使用
tf.keras.utils.model_to_dot或第三方库)。
7.4 思考拓展题
-
为什么在深层网络中使用1×1卷积能够减少计算量?以输入56×56×256,输出56×56×128为例,比较直接使用3×3卷积与先1×1降维再3×3的参数量和FLOPs。
-
对于小目标检测(如遥感图像中的微小物体),步距和填充应该如何选择?为什么?
-
近年来的研究(如ConvNeXt)又重新使用大卷积核(7×7)并取得良好效果,这与传统小核堆叠的观点矛盾吗?谈谈你的理解。
下一课预告:循环神经网络RNN原理——我们将迈入序列数据处理领域,学习RNN如何建模时间依赖,为文本、语音、时序预测任务打下基础。
🔗《TensorFlow2.x: 深度学习入门到高阶实战教程》系列课程导航
第一部分:基础入门(1-10 课)
第二部分:神经网络核心(11-25 课)
第三部分:进阶网络与框架高阶(26-40 课)
第四部分:企业实战与项目落地(41-50 课)
🌟 感谢您耐心阅读到这里!
💡 如果本文对您有所启发欢迎:
👍 点赞📌 收藏 📤 分享给更多需要的伙伴。
🗣️ 期待在评论区看到您的想法, 共同进步。
🔔 关注我,持续获取更多干货内容~
🤗 我们下篇文章见~

1266

被折叠的 条评论
为什么被折叠?



