一、图像在计算机中的本质
图像在计算机中以像素(Pixel)矩阵的形式存储,每个像素代表图像中一个最小的颜色单元,其核心是“数值化的颜色信息”。具体表现为:
-
灰度图:单通道矩阵,每个像素值用0-255的整数表示(0为纯黑,255为纯白),矩阵尺寸即图像分辨率(如28×28表示高28像素、宽28像素)。

-
彩色图:多通道矩阵,常见为RGB三通道(红、绿、蓝),每个通道均为独立的灰度矩阵,单个像素的颜色由三个通道的像素值组合决定(如RGB(255,0,0)为纯红),存储维度为“高度×宽度×通道数”(如224×224×3)。

-
本质:图像的本质是“离散化的数值矩阵”,计算机处理图像本质是对这些数值矩阵进行运算。
二、全连接神经网络存在的问题——为什么解决不了图像问题?
全连接神经网络(FCN)的核心是“每个输入节点与所有输出节点完全连接”,这种结构在处理图像时存在三大致命问题:
-
参数爆炸问题:图像分辨率稍高时,输入维度会急剧增加,导致连接权重参数数量呈指数级增长。例如:输入为224×224×3的彩色图,展开后输入节点数为224×224×3=150528,若第一个全连接层设1000个输出节点,仅该层权重参数就达150528×1000≈1.5×10⁸个,远超模型承载能力,且易过拟合。
-
丢失空间信息:图像的核心特征(如边缘、纹理、形状)依赖像素的空间位置关系(如“上方像素与下方像素的灰度差构成边缘”),但全连接网络需将二维像素矩阵展开为一维向量输入,彻底破坏了像素间的空间关联性,无法捕捉图像的空间特征。
-
缺乏平移不变性:若图像中目标的位置发生微小偏移(如猫从图像左侧移到右侧),展开后的一维向量中对应像素的位置会大幅变化,全连接网络会将其识别为完全不同的输入,导致模型对目标位置敏感,泛化能力极差。
三、卷积运算过程
卷积运算(Convolution)是CNN的核心操作,其核心是“用卷积核(Filter/Kernel)对图像的局部区域进行滑动计算,提取局部特征”,具体过程如下:

-
准备输入与卷积核:输入为二维特征图(如灰度图)或多通道特征图;卷积核为小型二维矩阵(如3×3、5×5),其尺寸远小于输入,每个元素为可学习的权重参数。
-
滑动卷积核:将卷积核的左上角与输入特征图的左上角对齐,以固定步长(见“步幅”)在输入特征图上沿水平和垂直方向滑动,每次滑动后停留的位置称为“感受野”(卷积核覆盖的局部区域)。
-
元素相乘求和:在每个感受野内,将卷积核的每个元素与输入对应位置的像素值相乘,再将所有乘积结果求和,得到输出特征图上对应位置的一个像素值。
-
激活函数处理:将求和结果输入激活函数(如ReLU),引入非线性,增强模型对复杂特征的表达能力,最终得到该位置的输出像素。
关键特性:局部连接(仅连接局部区域像素)、权值共享(同一卷积核在整个输入上重复使用,减少参数)。
单通道卷积运算代码实现
import numpy as np
def single_channel_convolution(input_feature, kernel, stride=1, padding=0):
"""
实现单通道卷积运算
参数:
input_feature: 输入单通道特征图,形状为(H, W),H为高度,W为宽度
kernel: 卷积核,形状为(K, K),K为卷积核尺寸
stride: 步幅,默认1
padding: 填充量,默认0(Valid Padding)
返回:
output_feature: 输出单通道特征图
"""
# 1. 对输入特征图进行零填充
# pad_width为(上下填充, 左右填充),(padding, padding)表示上下左右各填充padding层
padded_input = np.pad(input_feature, ((padding, padding), (padding, padding)), mode='constant', constant_values=0)
# 2. 获取输入和卷积核的尺寸
H_in, W_in = padded_input.shape # 填充后的输入尺寸
K, _ = kernel.shape # 卷积核尺寸(假设为正方形)
# 3. 计算输出特征图的尺寸(使用公式)
H_out = (H_in - K) // stride + 1
W_out = (W_in - K) // stride + 1
# 4. 初始化输出特征图
output_feature = np.zeros((H_out, W_out))
# 5. 滑动卷积核进行卷积运算
for i in range(H_out): # 沿高度方向滑动
for j in range(W_out): # 沿宽度方向滑动
# 确定当前感受野的区域(卷积核覆盖的输入部分)
receptive_field = padded_input[i*stride : i*stride+K, j*stride : j*stride+K]
# 元素相乘并求和(卷积核心计算)
conv_sum = np.sum(receptive_field * kernel)
# 激活函数(ReLU)处理,引入非线性
output_feature[i, j] = max(0, conv_sum)
return output_feature
# 测试代码
if __name__ == "__main__":
# 模拟输入:5×5的单通道灰度图(像素值0-255)
input_image = np.array([[10, 20, 30, 20, 10],
[20, 40, 60, 40, 20],
[30, 60, 90, 60, 30],
[20, 40, 60, 40, 20],
[10, 20, 30, 20, 10]])
# 3×3卷积核(边缘检测核,可提取水平边缘)
edge_kernel = np.array([[-1, -2, -1],
[0, 0, 0],
[1, 2, 1]])
# 执行卷积:步幅1,填充1(Same Padding)
output = single_channel_convolution(input_image, edge_kernel, stride=1, padding=1)
print("输入特征图形状:", input_image.shape)
print("输出特征图形状:", output.shape)
print("输出特征图(边缘检测结果):\n", output.round(2))
四、步幅
步幅(Stride)是卷积运算中卷积核沿输入特征图滑动的“每次移动的像素数”,分为水平步幅(Sₕ)和垂直步幅(Sᵥ),通常设为相同值(如S=1、S=2)。
-
作用:控制输出特征图的尺寸和计算效率。步幅越大,卷积核滑动次数越少,输出特征图尺寸越小,计算速度越快;步幅越小,输出尺寸越大,特征保留越完整,但计算量越大。
-
示例:输入尺寸为5×5,卷积核尺寸为3×3,步幅S=1时,水平/垂直滑动次数为5-3+1=3次,输出尺寸为3×3;步幅S=2时,滑动次数为(5-3)/2 +1=2次,输出尺寸为2×2。
五、填充
填充(Padding)是在输入特征图的边缘(上下左右)添加额外像素(通常为0,称为“零填充”),目的是“保持输出特征图的尺寸”或“保留边缘特征”。
-
核心问题:无填充时,卷积核每次滑动会“舍弃”边缘像素,导致输出尺寸小于输入(如5×5输入用3×3卷积核,输出3×3),且边缘特征(如物体轮廓)因仅被卷积一次而提取不充分。
-
常见填充方式:
有效填充(Valid Padding):不添加任何像素,输出尺寸最小,无边缘信息补充。 -
相同填充(Same Padding):添加像素使输出尺寸与输入尺寸相同,计算公式为填充量P=(K-1)/2(K为卷积核尺寸,需为奇数,如K=3时P=1,在边缘各加1层像素)。
六、输出特征图计算公式
输出特征图的尺寸(仅考虑二维单通道情况,设输出高度为Hₒᵤₜ、宽度为Wₒᵤₜ)由输入尺寸(Hᵢₙ、Wᵢₙ)、卷积核尺寸(Kₕ、Kᵥ,通常Kₕ=Kᵥ=K)、步幅(Sₕ、Sᵥ,通常Sₕ=Sᵥ=S)、填充量(Pₕ、Pᵥ,通常Pₕ=Pᵥ=P)共同决定,公式如下:
Hₒᵤₜ = ⌊(Hᵢₙ + 2Pₕ - Kₕ) / Sₕ⌋ + 1
Wₒᵤₜ = ⌊(Wᵢₙ + 2Pᵥ - Kᵥ) / Sᵥ⌋ + 1
-
符号说明:⌊·⌋表示向下取整(若计算结果为小数,舍弃小数部分)。
-
示例:输入Hᵢₙ=224、Wᵢₙ=224,K=3,S=1,P=1(Same Padding),则Hₒᵤₜ=(224+2×1-3)/1 +1=224,输出尺寸与输入一致。
输出特征图尺寸计算代码实现(含注释)
import math
def calculate_output_size(H_in, W_in, K, S=1, P=0):
"""
计算卷积后输出特征图的尺寸
参数:
H_in: 输入特征图高度
W_in: 输入特征图宽度
K: 卷积核尺寸(正方形,K×K)
S: 步幅(默认1,水平和垂直步幅相同)
P: 填充量(默认0,上下左右填充量相同)
返回:
H_out: 输出特征图高度
W_out: 输出特征图宽度
"""
# 直接套用公式,使用math.floor实现向下取整
H_out = math.floor((H_in + 2 * P - K) / S) + 1
W_out = math.floor((W_in + 2 * P - K) / S) + 1
return H_out, W_out
# 测试代码(覆盖不同场景)
if __name__ == "__main__":
# 场景1:Valid Padding(无填充),5×5输入,3×3卷积,步幅1
H1, W1 = calculate_output_size(H_in=5, W_in=5, K=3, S=1, P=0)
print("场景1(Valid Padding)输出尺寸:", (H1, W1)) # 预期(3,3)
# 场景2:Same Padding(填充1),5×5输入,3×3卷积,步幅1
H2, W2 = calculate_output_size(H_in=5, W_in=5, K=3, S=1, P=1)
print("场景2(Same Padding)输出尺寸:", (H2, W2)) # 预期(5,5)
# 场景3:大步幅(步幅2),5×5输入,3×3卷积,无填充
H3, W3 = calculate_output_size(H_in=5, W_in=5, K=3, S=2, P=0)
print("场景3(步幅2)输出尺寸:", (H3, W3)) # 预期(2,2)
# 场景4:经典CNN输入(224×224),3×3卷积,步幅1,Same Padding
H4, W4 = calculate_output_size(H_in=224, W_in=224, K=3, S=1, P=1)
print("场景4(224×224输入)输出尺寸:", (H4, W4)) # 预期(224,224)
七、多通道卷积运算
多通道场景分为“输入多通道”(如RGB彩色图3通道)和“输出多通道”(提取多种特征),运算核心是“通道对应卷积,结果累加,多卷积核实现多输出”。
-
输入多通道,输出单通道:
卷积核通道数=输入通道数(如输入3通道,卷积核为3个3×3矩阵,即3×3×3)。 -
运算过程:每个通道的输入特征图与对应通道的卷积核单独做卷积运算,得到3个单通道中间结果,再将这3个结果对应位置像素值相加,得到1个单通道输出特征图。
-
输入多通道,输出多通道:
输出通道数=卷积核的个数(如要输出16通道特征,需设置16个独立的3×3×3卷积核)。 -
运算过程:每个卷积核分别与输入多通道特征图做“单输出通道”运算,16个卷积核对应得到16个单通道输出特征图,组合后形成16通道的输出特征图。
意义:不同通道的卷积核可提取不同类型的特征(如边缘、纹理、颜色块),多输出通道实现多种特征的并行提取。
八、池化操作
池化(Pooling)是CNN中用于“降维、减少参数、增强鲁棒性”的操作,通常紧跟卷积层之后,核心是“对局部区域像素进行聚合计算”,不改变通道数。
- 核心目的:
降维:减少特征图尺寸和参数数量,降低计算量,防止过拟合。
import numpy as np
def multi_channel_convolution(input_feature, kernels, stride=1, padding=0):
"""
实现多通道卷积运算(输入多通道,输出多通道)
参数:
input_feature: 输入多通道特征图,形状为(C_in, H, W),C_in为输入通道数
kernels: 卷积核组,形状为(C_out, C_in, K, K),C_out为输出通道数,K为卷积核尺寸
stride: 步幅,默认1
padding: 填充量,默认0
返回:
output_feature: 输出多通道特征图,形状为(C_out, H_out, W_out)
"""
C_in, H_in, W_in = input_feature.shape
C_out, _, K, _ = kernels.shape
# 对输入特征图的空间维度进行填充(通道维度不填充)
padded_input = np.pad(input_feature, ((0, 0), (padding, padding), (padding, padding)),
mode='constant', constant_values=0)
# 计算输出特征图的空间尺寸
H_out = (padded_input.shape[1] - K) // stride + 1
W_out = (padded_input.shape[2] - K) // stride + 1
# 初始化输出特征图
output_feature = np.zeros((C_out, H_out, W_out))
# 遍历每个输出通道(对应一个卷积核组)
for c_out in range(C_out):
# 当前输出通道对应的卷积核(形状:C_in×K×K)
current_kernel = kernels[c_out]
# 遍历空间维度进行卷积
for i in range(H_out):
for j in range(W_out):
# 提取当前感受野(形状:C_in×K×K)
receptive_field = padded_input[:, i*stride:i*stride+K, j*stride:j*stride+K]
# 通道对应相乘后求和(C_in个通道的卷积结果累加)
conv_sum = np.sum(receptive_field * current_kernel)
# ReLU激活
output_feature[c_out, i, j] = max(0, conv_sum)
return output_feature
# 测试代码
if __name__ == "__main__":
# 模拟输入:3通道(RGB)、5×5的彩色图(通道维度在前)
input_rgb = np.array([
# 红色通道
[[1, 2, 3, 2, 1], [2, 3, 4, 3, 2], [3, 4, 5, 4, 3], [2, 3, 4, 3, 2], [1, 2, 3, 2, 1]],
# 绿色通道
[[0, 1, 2, 1, 0], [1, 2, 3, 2, 1], [2, 3, 4, 3, 2], [1, 2, 3, 2, 1], [0, 1, 2, 1, 0]],
# 蓝色通道
[[2, 1, 0, 1, 2], [1, 0, 1, 0, 1], [0, 1, 2, 1, 0], [1, 0, 1, 0, 1], [2, 1, 0, 1, 2]]
])
# 卷积核组:2个输出通道,每个通道对应3个输入通道的3×3卷积核
kernels = np.array([
# 输出通道1的卷积核(模拟提取边缘)
[
[[-1, -1, -1], [0, 0, 0], [1, 1, 1]], # 红色通道核
[[-1, -1, -1], [0, 0, 0], [1, 1, 1]], # 绿色通道核
[[-1, -1, -1], [0, 0, 0], [1, 1, 1]] # 蓝色通道核
],
# 输出通道2的卷积核(模拟提取纹理)
[
[[1, 0, -1], [2, 0, -2], [1, 0, -1]], # 红色通道核
[[1, 0, -1], [2, 0, -2], [1, 0, -1]], # 绿色通道核
[[1, 0, -1], [2, 0, -2], [1, 0, -1]] # 蓝色通道核
]
])
# 执行多通道卷积:步幅1,填充1
output = multi_channel_convolution(input_rgb, kernels, stride=1, padding=1)
print("输入特征图形状(C_in, H, W):", input_rgb.shape) # 预期(3,5,5)
print("输出特征图形状(C_out, H, W):", output.shape) # 预期(2,5,5)
print("输出通道1特征图:\n", output[0].round(2))
print("输出通道2特征图:\n", output[1].round(2))
多通道卷积运算代码实现
- 增强鲁棒性:对微小的位置偏移不敏感(如目标轻微移动后,聚合结果不变),提升模型泛化能力。
常见类型:
最大池化(Max Pooling):用局部区域内的最大值作为输出像素(如2×2池化核,取4个像素中的最大值),能有效保留边缘、纹理等强特征,应用最广泛。
平均池化(Average Pooling):用局部区域内的平均值作为输出像素,能保留整体灰度信息,但特征提取能力弱于最大池化,常用于网络后期或分类层前。
运算逻辑:与卷积类似,池化核(如2×2)以固定步幅(通常与池化核尺寸相同,如2×2池化步幅=2)滑动,对每个局部区域做聚合计算。
九、卷积神经网络中的池化操作
在CNN架构中,池化操作是“卷积层的重要补充”,其位置和作用具有明确的设计逻辑,核心是“配合卷积层实现特征的逐步抽象”。
- 典型位置:通常位于卷积层之后(Conv→Pool→Conv→Pool…),形成“卷积提取特征-池化降维”的交替结构,部分网络(如ResNet)会在 bottleneck 结构中嵌入池化。
池化操作代码实现
import numpy as np
def pooling(input_feature, pool_size=2, stride=2, mode='max'):
"""
实现池化操作(支持最大池化和平均池化,多通道兼容)
参数:
input_feature: 输入特征图,形状为(C, H, W),C为通道数
pool_size: 池化核尺寸(正方形,默认2×2)
stride: 步幅(默认2,与池化核尺寸相同,避免重叠)
mode: 池化模式,'max'为最大池化,'avg'为平均池化(默认max)
返回:
output_feature: 输出池化后的特征图,形状为(C, H_out, W_out)
"""
C, H_in, W_in = input_feature.shape
# 计算输出特征图的空间尺寸
H_out = (H_in - pool_size) // stride + 1
W_out = (W_in - pool_size) // stride + 1
# 初始化输出特征图
output_feature = np.zeros((C, H_out, W_out))
# 遍历每个通道(池化不改变通道数,通道独立运算)
for c in range(C):
# 遍历空间维度进行池化
for i in range(H_out):
for j in range(W_out):
# 提取当前池化区域
pool_region = input_feature[c, i*stride:i*stride+pool_size, j*stride:j*stride+pool_size]
# 根据模式计算池化结果
if mode == 'max':
output_feature[c, i, j] = np.max(pool_region)
elif mode == 'avg':
output_feature[c, i, j] = np.mean(pool_region)
else:
raise ValueError("仅支持'max'和'avg'两种池化模式")
return output_feature
# 测试代码
if __name__ == "__main__":
# 模拟输入:2通道、4×4的特征图(来自多通道卷积的输出)
input_feature = np.array([
# 通道1
[[1.2, 0.8, 2.1, 1.5],
[0.5, 1.9, 0.7, 2.3],
[1.1, 0.3, 1.7, 0.9],
[0.6, 1.4, 0.4, 1.8]],
# 通道2
[[0.9, 1.3, 0.5, 1.7],
[1.6, 0.4, 1.2, 0.8],
[0.7, 1.5, 0.6, 1.1],
[1.0, 0.3, 1.4, 0.2]]
])
# 执行最大池化(2×2池化核,步幅2)
max_pool_output = pooling(input_feature, pool_size=2, stride=2, mode='max')
# 执行平均池化(2×2池化核,步幅2)
avg_pool_output = pooling(input_feature, pool_size=2, stride=2, mode='avg')
print("输入特征图形状(C, H, W):", input_feature.shape) # 预期(2,4,4)
print("最大池化输出形状:", max_pool_output.shape) # 预期(2,2,2)
print("最大池化结果:\n", max_pool_output.round(2))
print("平均池化输出形状:", avg_pool_output.shape) # 预期(2,2,2)
print("平均池化结果:\n", avg_pool_output.round(2))
-
关键作用:
特征层级提升:卷积层提取低层次特征(如边缘、线条),池化后特征图尺寸缩小,后续卷积层可捕捉更大范围的高层特征(如纹理、形状、目标部件)。 -
控制过拟合:通过减少参数数量和对位置的敏感性,降低模型对训练数据细节的依赖,提升泛化能力。
-
保持计算效率:若仅用卷积层降维(增大步幅),会丢失过多特征;池化在降维的同时能更好保留关键特征,平衡性能与效率。
-
注意事项:池化层无可学习参数,仅通过固定规则聚合像素;现代网络中,有时用“步幅大于1的卷积层”替代池化层,以在降维时保留更多特征信息。
深度解析卷积神经网络原理
1万+

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



