《深度学习入门:基于Python的理论与实现》第7章 卷积神经网络笔记
第7章是全书从“全连接神经网络”转向“图像专用深度学习模型”的核心章节,聚焦卷积神经网络(Convolutional Neural Network, CNN)——这一在图像识别、计算机视觉领域不可或缺的模型。本章通过“原理→实现→应用→可视化”的逻辑,层层拆解CNN的核心组件(卷积层、池化层),并结合代码实现与经典网络案例,让读者理解“CNN为何能高效处理图像”。
7.1 CNN的整体结构:与全连接网络的本质区别
在学习CNN细节前,需先明确其层结构框架,理解它与第3-6章全连接网络(Affine-ReLU组合)的差异。
7.1.1 全连接网络的核心问题
全连接网络(如第3章的2层网络)将输入数据(如28×28的图像)拉平为1维向量(784个元素),导致两个关键问题:
- 丢失空间信息:图像的“空间关联性”(如相邻像素的相关性、边缘的连续性)被完全忽视,而这是图像识别的核心特征。
- 参数冗余:若输入为224×224×3的彩色图像,全连接层的权重参数会达到
224×224×3×1000=1.5亿
(假设隐藏层1000个神经元),计算量巨大。
7.1.2 CNN的层结构设计
CNN通过新增卷积层(Convolution层) 和池化层(Pooling层) 解决上述问题,典型的层连接顺序为:
输入 → 卷积层(Conv)→ 激活函数(ReLU)→ 池化层(Pool)→ (重复多次)→ 全连接层(Affine)→ Softmax → 输出
以书中的简单CNN为例(图7-23),具体结构如下:
输入(1×28×28)→ Conv(30个5×5滤波器)→ ReLU → Pool(2×2 Max池化)→ Affine(100个神经元)→ ReLU → Affine(10个神经元)→ Softmax → 输出(10类概率)
这种结构的核心优势:
- 卷积层:保留空间信息,通过“局部感受野”提取边缘、纹理等低级特征。
- 池化层:缩小空间维度,减少参数与计算量,提高对微小位置变化的鲁棒性。
7.2 卷积层:CNN的“特征提取核心”
卷积层是CNN的灵魂,其核心功能是通过滤波器(Filter)提取图像的局部特征(如边缘、斑块、纹理)。本节需掌握卷积运算的原理、关键参数(填充、步幅)及高维数据的处理。
7.2.1 全连接层的问题回顾:为何需要卷积层?
全连接层的本质是“全局连接”——每个神经元与前一层所有神经元连接,导致:
- 忽略图像的“局部相关性”:图像中相邻像素的关联性远大于远距离像素,但全连接层平等对待所有像素。
- 丢失形状信息:输入图像的3D形状(通道×高×宽)被拉平为1D向量,无法利用通道间的关联性(如RGB通道的颜色协同)。
卷积层的设计恰好解决这些问题:它通过“局部连接”和“参数共享”,在保留形状的同时高效提取特征。
7.2.2 2D卷积运算:从“滤波器滑动”到“输出计算”
卷积运算的本质是滤波器(权重矩阵)在输入数据上滑动,对每个局部区域进行“乘积累加”,类似图像处理中的“滤波操作”。
1. 基础示例(单通道输入)
假设输入为4×4的单通道数据,滤波器为3×3,步幅=1,无填充(pad=0),运算过程如下:
- 滤波器滑动:滤波器从输入左上角开始,按步幅1向右、向下滑动,覆盖所有可能的局部区域(共2×2个区域)。
- 乘积累加:对每个局部区域,将输入元素与滤波器元素对应相乘,再求和(如输入区域
[[1,2,3],[0,1,2],[3,0,1]]
与滤波器[[1,0,1],[0,1,0],[1,0,1]]
的乘积累加为1×1 + 2×0 + ... +1×1= 10
)。 - 添加偏置:每个滤波器对应1个偏置,将乘积累加结果加上偏置,得到该位置的输出值。
最终4×4输入经3×3滤波器卷积后,输出为2×2(计算公式见7.2.4)。
2. 关键概念:滤波器(Filter)
- 滤波器是卷积层的“权重”,形状为
(输入通道数C, 滤波器高FH, 滤波器宽FW)
。
例:处理3通道RGB图像的3×3滤波器,形状为(3,3,3)
——3个通道各对应一个3×3权重矩阵,运算时按通道分别卷积后求和。 - 多个滤波器:若使用FN个滤波器,会生成FN个输出通道(每个滤波器提取一种特征,如垂直边缘、水平边缘),输出形状为
(FN, 输出高OH, 输出宽OW)
。
7.2.3 填充(Padding):避免输出维度缩小
1. 问题引入
若输入为4×4,滤波器3×3,步幅1,输出为2×2——每卷积一次,高/宽缩小FH-1=2
。若重复卷积,输入维度会快速缩小至1,无法继续运算。
2. 填充的作用
在输入数据的周围填充固定值(通常为0),调整输入的“有效尺寸”,使输出维度符合预期。
例:对4×4输入填充1层0(变为6×6),经3×3滤波器卷积后,输出为4×4(与原输入维度一致),避免维度过度缩小。
3. 填充的意义
- 保护边缘特征:输入边缘的像素仅被滤波器覆盖1次,中心像素被覆盖多次,填充可增加边缘像素的参与度,避免边缘特征丢失。
- 控制输出维度:通过调整填充大小,可灵活控制输出的高/宽(如保持与输入一致,或按比例缩小)。
7.2.4 步幅(Stride):控制滤波器滑动间隔
步幅是滤波器在输入上滑动的“间隔”,直接影响输出维度。
1. 输出维度计算公式
若输入形状为(C, H, W)
,滤波器形状为(C, FH, FW)
,填充为P,步幅为S,则输出的高(OH)和宽(OW)为:
[
OH = \frac{H + 2P - FH}{S} + 1, \quad OW = \frac{W + 2P - FW}{S} + 1
]
注意:公式结果需为整数,否则框架会报错或自动调整(如补零)。
2. 示例计算
- 输入
H=7, W=7
,滤波器FH=3, FW=3
,P=0,S=2:
(OH=(7+0-3)/2 +1=3),(OW=3),输出为3×3。 - 输入
H=28, W=31
,滤波器FH=5, FW=5
,P=2,S=3:
(OH=(28+4-5)/3 +1=10),(OW=(31+4-5)/3 +1=11),输出为10×11。
7.2.5 3D数据的卷积:处理多通道图像
实际图像是3通道(RGB)的3D数据(C=3, H, W
),3D卷积需满足输入通道数=滤波器通道数,运算过程如下:
- 按通道卷积:滤波器的每个通道(如R通道的3×3权重)与输入的对应通道进行2D卷积,得到3个中间结果。
- 通道求和:将3个中间结果相加,得到1个单通道的特征图(输出通道数=1)。
- 多滤波器生成多通道:若使用FN个3通道滤波器,会生成FN个输出通道,最终输出形状为
(FN, OH, OW)
。
示例(3通道输入)
- 输入:
(3, 4, 4)
(3通道,4×4大小)。 - 滤波器:
(3, 3, 3)
(3通道,3×3大小),共10个滤波器。 - 输出:
(10, 2, 2)
(10个输出通道,2×2大小)。
7.2.6 批处理与4D数据
CNN中各层传递的数据为4D数组,形状为(N, C, H, W)
,其中:
- N:批大小(一次处理的样本数)。
- C:通道数。
- H:高。
- W:宽。
批处理时,卷积运算对N个样本并行处理,滤波器的形状为(FN, C, FH, FW)
(FN为输出通道数),确保每个样本的卷积运算独立进行,不互相干扰。
7.3 池化层:“降维神器”与鲁棒性提升
池化层的核心功能是缩小空间维度(高×宽),减少参数数量和计算量,同时提高模型对输入微小位置变化的鲁棒性(如图像轻微偏移不影响识别)。
7.3.1 常见池化类型:Max池化(主流)
图像识别中最常用的是Max池化——对指定大小的局部区域(如2×2)取最大值,步骤如下:
- 设定池化窗口大小(如2×2)和步幅(通常与窗口大小一致,避免重叠)。
- 窗口按步幅在输入上滑动,对每个窗口取最大值作为输出。
示例(2×2 Max池化,步幅2)
- 输入:
4×4
矩阵[[1,2,1,0],[0,1,2,3],[3,0,1,2],[2,3,0,1]]
。 - 窗口滑动:共覆盖2×2个区域,每个区域取最大值。
- 输出:
2×2
矩阵[[2,3],[3,2]]
。
7.3.2 池化层的3个关键特征
- 无学习参数:池化仅取最大值或平均值,无需像卷积层那样学习权重,参数数量为0。
- 通道数不变:池化按通道独立进行,输入通道数=输出通道数(如3通道输入经池化后仍为3通道)。
- 对位置变化鲁棒:输入微小偏移(如像素左移1个单位),池化结果可能不变,避免模型对位置过度敏感。
7.3.3 其他池化类型(了解)
- Average池化:对局部区域取平均值,平滑效果好,但提取特征的能力弱于Max池化,图像识别中较少使用。
- Global Max/Average池化:对整个特征图取最大值/平均值,输出为1×1大小,常用于替代全连接层(减少参数)。
7.4 卷积层与池化层的实现:im2col技巧
直接用for循环实现卷积/池化会导致效率极低(尤其是4D数据),书中通过im2col(image to column)技巧,将高维数据展开为2D矩阵,通过矩阵乘法简化计算,利用NumPy的高效矩阵运算提升速度。
7.4.1 im2col:将高维输入展开为2D矩阵
im2col的核心思想:将卷积/池化的“局部窗口”横向展开为2D矩阵的列,使卷积运算转化为矩阵乘法。
1. 卷积层的im2col处理(以4D输入为例)
- 输入:
(N, C, H, W)
。 - 处理:对每个样本的每个通道,将所有
(FH, FW)
大小的局部窗口(共OH×OW
个)展开为1列,最终得到2D矩阵(N×OH×OW, C×FH×FW)
。
例:输入(1, 3, 4, 4)
,滤波器(3, 3)
,P=0,S=1:
每个通道有2×2=4
个窗口,3个通道共4×3=12
个元素/窗口,展开后矩阵形状为(4, 3×3×3)= (4,27)
(N=1,OH×OW=4)。
2. 滤波器的展开
滤波器形状为(FN, C, FH, FW)
,将每个滤波器展开为1列,得到2D矩阵(C×FH×FW, FN)
。
3. 卷积运算转化为矩阵乘法
- 展开后的输入矩阵(
N×OH×OW, C×FH×FW
)与滤波器矩阵(C×FH×FW, FN
)相乘,得到(N×OH×OW, FN)
的结果矩阵。 - 将结果矩阵reshape为
(N, FN, OH, OW)
,即卷积层的输出(4D数组)。
7.4.2 卷积层的Python实现(基于im2col)
书中实现了Convolution
类,核心代码如下(关键步骤已标注):
class Convolution:def __init__(self, W, b, stride=1, pad=0):self.W = W # 滤波器:(FN, C, FH, FW)self.b = b # 偏置:(FN,)self.stride = strideself.pad = paddef forward(self, x):FN, C, FH, FW = self.W.shape # 滤波器参数N, C, H, W = x.shape # 输入参数# 1. 计算输出维度out_h = int((H + 2*self.pad - FH) / self.stride) + 1out_w = int((W + 2*self.pad - FW) / self.stride) + 1# 2. im2col展开输入:(N×OH×OW, C×FH×FW)col = im2col(x, FH, FW, self.stride, self.pad)# 3. 展开滤波器:(C×FH×FW, FN)col_W = self.W.reshape(FN, -1).T # reshape(FN,-1)自动计算列数(C×FH×FW)# 4. 矩阵乘法 + 偏置(广播)out = np.dot(col, col_W) + self.b# 5. reshape为4D输出:(N, FN, OH, OW)out = out.reshape(N, out_h, out_w, -1).transpose(0, 3, 1, 2)# transpose调整轴顺序:(N, H, W, C) → (N, C, H, W)return out
im2col
函数:书中common/util.py
实现,核心是遍历输入的每个局部窗口,展开为列。transpose
:因reshape后形状为(N, OH, OW, FN)
,需调整轴顺序为(N, FN, OH, OW)
(通道在前)。
7.4.3 池化层的Python实现(基于im2col)
池化层的im2col处理更简单,展开后只需对每行取最大值,核心代码如下:
class Pooling:def __init__(self, pool_h, pool_w, stride=1, pad=0):self.pool_h = pool_h # 池化窗口高self.pool_w = pool_w # 池化窗口宽self.stride = strideself.pad = paddef forward(self, x):N, C, H, W = x.shape# 1. 计算输出维度out_h = int((H - self.pool_h) / self.stride) + 1out_w = int((W - self.pool_w) / self.stride) + 1# 2. im2col展开输入:(N×C×OH×OW, pool_h×pool_w)# 注意:按通道独立展开,故先reshape为(N×C, 1, H, W),展开后再调整col = im2col(x, self.pool_h, self.pool_w, self.stride, self.pad)col = col.reshape(-1, self.pool_h * self.pool_w) # (-1表示自动计算行数:N×C×OH×OW)# 3. 每行取最大值(axis=1:按行)out = np.max(col, axis=1)# 4. reshape为4D输出:(N, C, OH, OW)out = out.reshape(N, out_h, out_w, C).transpose(0, 3, 1, 2)return out
- 关键:展开时按通道独立处理(
N×C
作为批维度),确保每个通道的池化不互相干扰,通道数不变。
7.5 完整CNN的实现:手写数字识别
结合前面的卷积层、池化层,书中实现了SimpleConvNet
类,用于MNIST手写数字识别,网络结构如下:
输入(1,28,28) → Conv1(30个5×5滤波器,S=1,P=0)→ ReLU1 → Pool1(2×2 Max,S=2)→ Affine1(100个神经元)→ ReLU2 → Affine2(10个神经元)→ SoftmaxWithLoss
7.5.1 网络初始化(核心代码)
class SimpleConvNet:def __init__(self, input_dim=(1,28,28), conv_param={'filter_num':30, 'filter_size':5, 'pad':0, 'stride':1},hidden_size=100, output_size=10, weight_init_std=0.01):# 1. 解析卷积层参数filter_num = conv_param['filter_num']filter_size = conv_param['filter_size']filter_pad = conv_param['pad']filter_stride = conv_param['stride']input_size = input_dim[1]# 2. 计算卷积层输出维度conv_output_size = (input_size - filter_size + 2*filter_pad) / filter_stride + 1# 3. 计算池化层输出维度(2×2 Max池化,S=2,故维度减半)pool_output_size = int(filter_num * (conv_output_size/2) * (conv_output_size/2))# 4. 初始化权重参数self.params = {}# Conv1权重:(filter_num, C, FH, FW),He初始值self.params['W1'] = weight_init_std * np.random.randn(filter_num, input_dim[0], filter_size, filter_size)self.params['b1'] = np.zeros(filter_num)# Affine1权重:(pool_output_size, hidden_size)self.params['W2'] = weight_init_std * np.random.randn(pool_output_size, hidden_size)self.params['b2'] = np.zeros(hidden_size)# Affine2权重:(hidden_size, output_size)self.params['W3'] = weight_init_std * np.random.randn(hidden_size, output_size)self.params['b3'] = np.zeros(output_size)# 5. 构建层(OrderedDict保证顺序)self.layers = OrderedDict()self.layers['Conv1'] = Convolution(self.params['W1'], self.params['b1'], filter_stride, filter_pad)self.layers['ReLU1'] = ReLU()self.layers['Pool1'] = Pooling(pool_h=2, pool_w=2, stride=2)self.layers['Affine1'] = Affine(self.params['W2'], self.params['b2'])self.layers['ReLU2'] = ReLU()self.layers['Affine2'] = Affine(self.params['W3'], self.params['b3'])self.last_layer = SoftmaxWithLoss() # 最后一层单独存储
7.5.2 前向传播与反向传播
# 推理(前向传播)
def predict(self, x):for layer in self.layers.values():x = layer.forward(x)return x# 计算损失
def loss(self, x, t):y = self.predict(x)return self.last_layer.forward(y, t)# 误差反向传播(求梯度)
def gradient(self, x, t):# 1. 前向传播(计算损失)self.loss(x, t)# 2. 反向传播(从最后一层开始)dout = 1dout = self.last_layer.backward(dout)# 3. 逆序遍历层,执行反向传播layers = list(self.layers.values())layers.reverse()for layer in layers:dout = layer.backward(dout)# 4. 提取各层梯度grads = {}grads['W1'] = self.layers['Conv1'].dWgrads['b1'] = self.layers['Conv1'].dbgrads['W2'] = self.layers['Affine1'].dWgrads['b2'] = self.layers['Affine1'].dbgrads['W3'] = self.layers['Affine2'].dWgrads['b3'] = self.layers['Affine2'].dbreturn grads
7.5.3 训练结果
使用MNIST数据集训练后,测试集识别精度约98.96%(训练集精度99.82%),远高于全连接网络(约97%),证明CNN在图像识别上的优势。
7.6 CNN的可视化:理解“特征提取过程”
通过可视化CNN的中间层,可直观理解“模型在学习什么”,书中主要可视化了第1层卷积层的权重和各层特征图。
7.6.1 第1层卷积层的权重可视化
- 学习前:权重随机初始化,灰度分布无规律(杂乱的黑白像素)。
- 学习后:权重呈现明显规律,如:
- 左右渐变的滤波器:对垂直边缘(颜色突变)有响应。
- 上下渐变的滤波器:对水平边缘有响应。
- 块状区域的滤波器:对局部斑块(如数字的“拐角”)有响应。
这说明CNN的浅层(第1层)主要提取“低级特征”——边缘、斑块等基础视觉元素。
7.6.2 分层特征提取规律
根据深度学习可视化研究(如AlexNet的可视化),CNN的特征提取具有分层抽象的特点:
- 浅层(第1-2层):提取边缘、纹理、颜色块等低级特征。
- 中层(第3-5层):组合低级特征,提取“纹理模式”(如网格、条纹)、“物体部件”(如数字的“竖线”“圆圈”)。
- 深层(全连接层前):组合中层特征,提取完整的“物体形状”(如数字的整体轮廓)。
这种从“局部到全局”“低级到高级”的特征提取,与人类识别图像的过程(先看边缘,再拼轮廓,最后识别物体)高度一致。
7.7 代表性CNN网络:LeNet与AlexNet
书中介绍了两个里程碑式的CNN网络,理解它们的结构和创新点,可把握CNN的发展脉络。
7.7.1 LeNet(1998):CNN的“鼻祖”
LeNet是由Yann LeCun提出的首个实用CNN,用于手写数字识别,结构如下:
输入(1,32,32) → Conv1(6个5×5滤波器)→ Sigmoid → 子采样(Average池化)→ Conv2(16个5×5滤波器)→ Sigmoid → 子采样 → Affine1(120个神经元)→ Affine2(84个神经元)→ Gaussian连接 → 输出(10类)
与现代CNN的差异
- 激活函数:使用Sigmoid(现代用ReLU,避免梯度消失)。
- 池化:使用子采样(Average池化,现代用Max池化,提取特征更鲁棒)。
- 无填充:卷积后维度缩小,未使用填充保护边缘。
意义
LeNet首次验证了“卷积+池化”架构在图像识别上的有效性,为后续CNN奠定了基础。
7.7.2 AlexNet(2012):深度学习热潮的“导火索”
AlexNet在2012年ImageNet图像识别竞赛中,以远超传统方法的精度(top-5错误率15.3%,第二名26.2%)引发深度学习热潮,结构如下:
输入(3,224,224) → Conv1(96个11×11滤波器,S=4)→ ReLU → LRN → MaxPool → Conv2(256个5×5滤波器,P=2)→ ReLU → LRN → MaxPool → Conv3(384个3×3滤波器,P=1)→ ReLU → Conv4(384个3×3滤波器,P=1)→ ReLU → Conv5(256个3×3滤波器,P=1)→ ReLU → MaxPool → Affine1(4096个神经元)→ ReLU → Dropout → Affine2(4096个神经元)→ ReLU → Dropout → Affine3(1000个神经元)→ Softmax
核心创新
- ReLU激活函数:替代Sigmoid,解决深层网络的梯度消失问题。
- LRN层(局部响应归一化):对局部区域的神经元输出进行归一化,增强泛化能力(现代CNN中已被Batch Normalization替代)。
- Dropout:抑制过拟合,提高模型泛化能力。
- GPU加速:首次使用GPU并行计算,实现深层网络的训练(AlexNet有8层卷积/全连接,参数约6000万)。
- 数据扩充:通过图像翻转、裁剪、颜色抖动等扩充训练数据,提升鲁棒性。
意义
AlexNet证明了深层CNN在大规模图像识别上的优越性,开启了深度学习在计算机视觉领域的应用热潮。
7.8 本章小结
第7章围绕CNN的核心原理与实现展开,核心要点总结如下:
- CNN的核心优势:通过卷积层保留空间信息、池化层降维,解决全连接网络的参数冗余和空间信息丢失问题。
- 卷积层关键概念:卷积运算(滤波器滑动、乘积累加)、填充(调整输出维度)、步幅(控制滑动间隔)、多通道处理(输入通道=滤波器通道)。
- 池化层关键特征:无参数、通道数不变、对位置变化鲁棒,主流为Max池化。
- 实现技巧:im2col将高维数据展开为2D矩阵,通过矩阵乘法简化卷积/池化计算,提升效率。
- 特征提取规律:浅层提取低级特征(边缘、斑块),深层提取高级特征(物体部件、形状)。
- 代表性网络:LeNet(CNN鼻祖)、AlexNet(深度学习热潮导火索),创新点引领后续CNN发展。
通过本章学习,不仅能掌握CNN的原理与实现,更能理解“为何CNN适合图像识别”——其架构设计贴合图像的空间特性,是“数据驱动”与“结构先验”结合的典范。