卷积操作原理:卷积核、步长、填充
一句话概述
卷积操作是卷积神经网络(CNN)的核心计算,通过一个小的卷积核(滤波器)在输入特征图上滑动,完成局部特征的提取。卷积核的尺寸决定了感受野的范围,步长(stride)控制滑动的间隔,填充(padding)保持特征图的空间尺寸。这三个参数协同决定了卷积层的输出尺寸和特征提取能力,是理解一切CNN架构的起点。
💡 核心要点:①卷积核是小的权重矩阵,在输入上滑动做逐元素乘加运算,实现局部特征检测 ②步长控制每次滑动的距离,影响输出尺寸和下采样率 ③填充通过在边界补值来保持空间分辨率,Same Padding使输出尺寸与输入相同 ④输出尺寸公式:O = (W - K + 2P) / S + 1,是设计CNN的基本工具
教学与演示
一、卷积操作的基本概念
是什么(定义):卷积操作是一种数学运算,将一个小的核矩阵(卷积核/滤波器)在输入特征图上滑动,在每个位置进行逐元素乘积求和,生成输出特征图的一个值。在深度学习中,卷积操作是局部连接和权重共享的完美实现——同一个卷积核在整个输入上滑动,检测相同的特征模式。
大白话 卷积核就像一个小"手电筒",在图像上扫来扫去。每次扫到一个位置,就把手电筒光照到的像素和手电筒里的"模板"做加权求和,计算结果就是该位置"和模板有多像"。如果像——输出就大;不像——输出就小。
为什么(原理):卷积操作的两个核心思想使其在图像处理中极为高效:①局部连接——每个输出像素只与输入的一个小区域(感受野)相关,而非全部像素,大幅减少了参数数量。②权重共享——同一个卷积核在所有位置使用,无论特征出现在图像的哪个位置都能被检测到(平移等变性)。相比之下,全连接层每个输入输出对都有一个独立参数,对图像来说参数量爆炸。
怎么做(实现):
import numpy as np
# ========================================
# 二维卷积操作 —— CNN 的核心计算
# 卷积核在输入上滑动,做逐元素乘加运算
# ========================================
def conv2d(input_feature, kernel):
"""
二维卷积(无填充,步长为1)
参数:
input_feature: 输入特征图,shape (H, W)
kernel: 卷积核,shape (kH, kW)
返回:
输出特征图,shape (H-kH+1, W-kW+1)
"""
H, W = input_feature.shape # 输入的高度和宽度
kH, kW = kernel.shape # 卷积核的高度和宽度
# 计算输出特征图的尺寸
out_H = H - kH + 1 # 输出高度
out_W = W - kW + 1 # 输出宽度
output = np.zeros((out_H, out_W))
# 在输入特征图上滑动卷积核
for i in range(out_H): # 遍历输出的每一行
for j in range(out_W): # 遍历输出的每一列
# 提取当前窗口(卷积核覆盖的局部区域)
window = input_feature[i:i+kH, j:j+kW] # 形状: (kH, kW)
# 逐元素乘加:对应位置相乘后求和
output[i, j] = np.sum(window * kernel)
return output
# --- 演示1: 边缘检测 ---
# 使用 Sobel 算子检测水平边缘
img = np.array([
[10, 10, 10, 10, 10],
[10, 10, 10, 10, 10],
[50, 50, 50, 50, 50], # 这里有一条水平边缘(亮度突变)
[50, 50, 50, 50, 50],
[50, 50, 50, 50, 50],
], dtype=float)
# 水平边缘检测核(Sobel-Y)
sobel_y = np.array([
[-1, -2, -1],
[ 0, 0, 0],
[ 1, 2, 1]
], dtype=float)
print("输入图像(5×5):")
print(img)
print(f"\n卷积核(Sobel-Y,检测水平边缘):")
print(sobel_y)
# 执行卷积
edge_map = conv2d(img, sobel_y)
print(f"\n卷积输出(边缘检测结果):")
print(edge_map)
print(" → 中间一行值最大(20和0的差异处),检测到了水平边缘!")
# --- 演示2: 均值滤波(模糊) ---
print("\n均值滤波(平滑/模糊):")
blur_kernel = np.ones((3, 3)) / 9.0 # 3×3均值滤波器
print(f"卷积核(3×3均值滤波):")
print(blur_kernel)
blurred = conv2d(img, blur_kernel)
print(f"\n模糊后输出:")
print(blurred)
print(" → 每个位置变成了周围3×3区域的平均值")
大白话 卷积就是"滑动窗口里的加权求和"——窗口覆盖的区域中,每个像素乘以核中对应位置的权重,全部加起来就是一个输出值。然后窗口往右移一步再做一次,遍历整张图。
什么用(AI关联):卷积操作是CNN的基础,所有卷积层(Conv2D)都执行此操作。不同的卷积核学习不同的特征:底层核检测边缘和纹理,中层核检测形状和部件,高层核检测完整的物体。在PyTorch中即nn.Conv2d,TensorFlow中即tf.keras.layers.Conv2D。
哪些坑(缺点):①卷积核大小固定,难以捕获尺度变化大的特征。②标准卷积的计算量随核尺寸平方增长。③在没有填充的情况下,每次卷积都会缩小特征图,深层网络中空间信息会迅速丢失。
二、步长(Stride):控制滑动间隔
是什么(定义):步长(Stride)是卷积核在输入特征图上每次滑动的像素数。步长为S意味着卷积核每次移动S个像素。默认步长为1(核每次移动1个像素),步长为2相当于隔一个采一个,实现下采样。
大白话 步长就是"走路的步幅"——步长为1时小碎步走,每个位置都看一遍(精细但慢);步长为2时迈大步走,隔一个看一个(粗糙但快,还能缩小特征图)。
为什么(原理):步长S>1时,输出特征图的尺寸变为原来的约1/S。当S=2时,空间分辨率减半,实现了下采样(类似于池化)。这种"带学习的下采样"(strided convolution)在现代CNN(如ResNet、EfficientNet)中常取代池化层,因为卷积核的参数是可学习的,比固定运算的池化更灵活。
怎么做(实现):
import numpy as np
# ========================================
# 步长对卷积输出的影响
# 步长越大,输出尺寸越小,但特征图越紧凑
# ========================================
def conv2d_with_stride(input_feature, kernel, stride=1):
"""
带步长的二维卷积
输出尺寸 = ⌊(W - K) / stride⌋ + 1
参数:
input_feature: 输入特征图,shape (H, W)
kernel: 卷积核,shape (kH, kW)
stride: 步长,核每次移动的像素数
返回:
输出特征图
"""
H, W = input_feature.shape
kH, kW = kernel.shape
S = stride
# 输出尺寸公式(无填充情况)
out_H = (H - kH) // S + 1
out_W = (W - kW) // S + 1
output = np.zeros((out_H, out_W))
for i in range(out_H):
for j in range(out_W):
# 注意:步长影响窗口的起始位置
start_h = i * S # 窗口在高度方向的起始位置 = i × 步长
start_w = j * S # 窗口在宽度方向的起始位置 = j × 步长
window = input_feature[start_h:start_h+kH, start_w:start_w+kW]
output[i, j] = np.sum(window * kernel)
return output
# --- 输入图像 ---
img_large = np.array([
[1, 2, 3, 0, 1, 2],
[4, 5, 6, 3, 4, 5],
[7, 8, 9, 6, 7, 8],
[2, 3, 4, 9, 1, 2],
[5, 6, 7, 2, 4, 5],
[8, 9, 1, 5, 7, 8],
], dtype=float)
# 3×3 卷积核(检测"大数值聚集区")
kernel = np.array([
[1, 2, 1],
[0, 0, 0],
[-1, -2, -1]
], dtype=float)
print("输入图像 (6×6):")
print(img_large)
print(f"\n卷积核 (3×3):")
print(kernel)
# 不同步长的对比
for stride in [1, 2, 3]:
out = conv2d_with_stride(img_large, kernel, stride)
expected_h = (6 - 3) // stride + 1
expected_w = (6 - 3) // stride + 1
print(f"\n步长 S={stride}:")
print(f" 输出尺寸: {expected_h}×{expected_w}")
print(f" 每个方向滑动了 {expected_h} 个位置")
print(out)
大白话 步长公式理解:输入尺寸W减去不可滑动的范围(K-1),然后每隔S个步长取一个位置。比如6×6图用3×3核步长2——每行能放(6-3)/2+1=2个核,最终输出是2×2。
什么用(AI关联):步长2的卷积(strided convolution)在ResNet、EfficientNet中用于下采样,替代了传统CNN中"卷积+池化"的两步操作。在GAN中,步长为分数的转置卷积(也叫反卷积)用于上采样生成图像。
哪些坑(缺点):①步长过大导致严重的信息丢失(相邻像素之间的信息被跳过)。②当(W-K)不能被S整除时,部分输入信息被丢弃。③strided convolution可能导致棋盘效应(checkerboard artifacts),尤其是在转置卷积中。
三、填充(Padding):保持空间分辨率
是什么(定义):填充(Padding)是在输入特征图的边界外部添加额外的像素(通常为0),使卷积后的输出尺寸不至于过快缩小。主要有三种方式:Valid Padding(P=0,无填充)、Same Padding(填充使输出尺寸等于输入尺寸)、Full Padding(充分填充使每个像素都被等次数地覆盖)。
大白话 填充就像给照片裱一圈框——裱了框的照片经过卷积后,大小和原来差不多(Same Padding);不裱框的照片越卷越小(Valid Padding)。边框的颜色通常是0(黑色),所以叫"零填充"。
怎么做(实现):
import numpy as np
# ========================================
# 填充方式与输出尺寸的关系
# Same Padding: 输出尺寸 = 输入尺寸(步长为1时)
# Valid Padding: 无填充,输出会缩小
# ========================================
def conv2d_with_padding(input_feature, kernel, padding='valid', stride=1):
"""
带填充的二维卷积
参数:
input_feature: 输入特征图,shape (H, W)
kernel: 卷积核,shape (kH, kW)
padding: 'valid'(无填充) 或 'same'(填充后输出尺寸=输入尺寸/stride)
stride: 步长
返回:
输出特征图
"""
H, W = input_feature.shape
kH, kW = kernel.shape
S = stride
# 根据填充方式计算需要的填充量
if padding == 'valid':
pad_h, pad_w = 0, 0 # 不填充
elif padding == 'same':
# Same Padding: 使输出尺寸 = ceil(H / S)
# 需要的总填充量 = max(0, (out_size-1)*S + K - input_size)
pad_total_h = max(0, (H - 1) * S + kH - H)
pad_total_w = max(0, (W - 1) * S + kW - W)
pad_h = pad_total_h // 2 # 上下平分填充
pad_w = pad_total_w // 2 # 左右平分填充
# 对输入进行零填充
if pad_h > 0 or pad_w > 0:
padded = np.pad(input_feature,
((pad_h, pad_h), (pad_w, pad_w)),
mode='constant', constant_values=0)
else:
padded = input_feature
pH, pW = padded.shape
out_H = (pH - kH) // S + 1
out_W = (pW - kW) // S + 1
output = np.zeros((out_H, out_W))
for i in range(out_H):
for j in range(out_W):
start_h, start_w = i * S, j * S
window = padded[start_h:start_h+kH, start_w:start_w+kW]
output[i, j] = np.sum(window * kernel)
return output
# --- 演示 ---
img = np.arange(1, 26).reshape(5, 5).astype(float) # 5×5 图像
kernel = np.ones((3, 3), dtype=float) / 9.0 # 3×3 均值滤波
print("输入图像 (5×5):")
print(img)
# Valid Padding
out_valid = conv2d_with_padding(img, kernel, padding='valid')
print(f"\nValid Padding(无填充)→ 输出 {out_valid.shape[0]}×{out_valid.shape[1]}:")
print(out_valid)
# Same Padding
out_same = conv2d_with_padding(img, kernel, padding='same')
print(f"\nSame Padding(填充后)→ 输出 {out_same.shape[0]}×{out_same.shape[1]}:")
print(out_same)
print(f" → 输出尺寸与输入相同(5×5),保留了空间分辨率!")
大白话 填充就是在图边上"补一圈0"。Same Padding补得刚好让输出和输入一样大(步长1时),比如5×5图用3×3核,补一圈0变成7×7,卷积后回到5×5——就像"借了一圈,用完又还回去"。
什么用(AI关联):Same Padding允许构建更深的CNN(因为不会过早缩小特征图),是现代CNN的标准配置。VGGNet使用Same Padding保持特征图尺寸;ResNet在需要下采样时才用步长2。语义分割(如U-Net)中Same Padding尤为重要,因为需要输出与原图等大。
哪些坑(缺点):①填充区域的信息是"虚假"的(通常为0),边界附近特征的提取质量下降。②在反向传播中,填充区域的梯度也需要正确处理。③TensorFlow中Same Padding在K为偶数时可能导致非对称填充。
四、多通道卷积与3D卷积
是什么(定义):实际图像通常是多通道的(RGB图像有3个通道),卷积层也相应地使用三维卷积核(kH × kW × C_in),每个输入通道有独立的二维核,所有通道的结果相加得到一个输出通道。一个卷积层可以包含多个这样的滤波器,每个滤波器产生一个输出通道(特征图)。
大白话 多通道卷积就像"彩色滤镜"——RGB三个通道各有自己的检测模板(卷积核),三个通道的结果加起来才是一个完整的输出。如果有64个不同的滤镜组,就产生64个不同的特征图,每个捕捉一种模式。
怎么做(实现):
import numpy as np
# ========================================
# 多通道卷积 —— 处理 RGB 等实际图像
# 每个输入通道有独立核,结果跨通道求和
# ========================================
def conv2d_multichannel(input_feature, kernels, stride=1, padding='same'):
"""
多通道二维卷积
参数:
input_feature: 输入特征图,shape (C_in, H, W)
kernels: 卷积核组,shape (C_out, C_in, kH, kW)
stride: 步长
padding: 填充方式
返回:
输出特征图,shape (C_out, out_H, out_W)
"""
C_in, H, W = input_feature.shape
C_out, _, kH, kW = kernels.shape
# 计算填充
if padding == 'same':
pad_h = (kH - 1) // 2
pad_w = (kW - 1) // 2
else:
pad_h, pad_w = 0, 0
out_H = (H + 2*pad_h - kH) // stride + 1
out_W = (W + 2*pad_w - kW) // stride + 1
output = np.zeros((C_out, out_H, out_W))
# 对每个输出通道
for c_out in range(C_out):
# 对每个输入通道
for c_in in range(C_in):
# 当前输入通道
ch = input_feature[c_in]
if pad_h > 0 or pad_w > 0:
ch = np.pad(ch, ((pad_h, pad_h), (pad_w, pad_w)))
# 当前核
k = kernels[c_out, c_in]
# 单通道卷积,结果累加到输出
for i in range(out_H):
for j in range(out_W):
sh, sw = i * stride, j * stride
window = ch[sh:sh+kH, sw:sw+kW]
output[c_out, i, j] += np.sum(window * k)
return output
# --- 演示:RGB图像边缘检测 ---
np.random.seed(42)
# 模拟一个简单的3通道5×5图像
rgb_img = np.random.randint(0, 256, (3, 5, 5)).astype(float)
# 定义2个3通道卷积核(输出2个特征图)
# 每个输出通道有3个不同的核(分别对应R、G、B通道)
kernels = np.array([
# 输出通道0:检测水平边缘(R、G、B各有略微不同的核)
[[[-1,-1,-1],[0,0,0],[1,1,1]], # R通道核
[[-1,-1,-1],[0,0,0],[1,1,1]], # G通道核
[[-1,-1,-1],[0,0,0],[1,1,1]]], # B通道核
# 输出通道1:检测垂直边缘
[[[-1,0,1],[-1,0,1],[-1,0,1]], # R通道核
[[-1,0,1],[-1,0,1],[-1,0,1]], # G通道核
[[-1,0,1],[-1,0,1],[-1,0,1]]], # B通道核
], dtype=float) # shape: (2, 3, 3, 3) = (C_out, C_in, kH, kW)
out = conv2d_multichannel(rgb_img, kernels)
print(f"RGB输入: 3通道 × 5×5")
print(f"卷积核: 2组 × 3通道 × 3×3 = {2*3*3*3} 个参数")
print(f"输出: {out.shape[0]}通道 × {out.shape[1]}×{out.shape[2]}")
print(f"\n特征图0(水平边缘)数值范围: [{out[0].min():.1f}, {out[0].max():.1f}]")
print(f"特征图1(垂直边缘)数值范围: [{out[1].min():.1f}, {out[1].max():.1f}]")
大白话 多通道卷积就像"多组滤镜同时工作"——假设有64组滤镜(输出通道),每组滤镜都要看全RGB三通道(输入通道),每组里的三个子滤镜各看一个颜色通道,三个结果加起来就是该组滤镜的输出。64组滤镜并行产生64张特征图。
什么用(AI关联):多通道卷积是CNN处理图像的核心。输入层通常为3通道(RGB),第一层卷积输出64或更多通道,每个通道捕获一种视觉模式。逐层增加通道数(如64→128→256→512)是经典CNN的通用设计模式。
概念关系图谱
| 概念 | 核心含义 | 与AI的关系 | 关联概念 |
|---|---|---|---|
| 卷积核 | 小的权重矩阵,在输入上滑动检测特征 | CNN的基本特征提取器 | 滤波器、特征检测、感受野 |
| 步长 | 卷积核每次滑动的像素数 | 控制输出尺寸和下采样率 | 下采样、strided convolution |
| 填充 | 输入边界添加像素(通常为0) | 保持空间分辨率,支持更深网络 | Same Padding、Valid Padding |
| 感受野 | 输出像素对应的输入区域大小 | 决定神经元能"看到"的上下文范围 | 空洞卷积、有效感受野 |
| 权重共享 | 同一卷积核在所有位置复用 | 大幅减少参数,提供平移等变性 | 参数效率、平移不变性 |
| 局部连接 | 每个输出只与输入局部区域相连 | 减少计算量,提取局部特征 | 全连接、稀疏连接 |
重点答疑
Q1: 卷积层和全连接层有什么本质区别?
核心区别有三:①连接方式——卷积层是局部连接(每个输出只与输入的局部窗口相关),全连接层是全局连接(每个输出与所有输入相关)。②参数共享——卷积层的同一个卷积核在所有位置复用,全连接层的每个连接有独立参数。③平移等变性——卷积层天然具有平移等变性(输入平移→输出同样平移),全连接层没有。这些区别使卷积层的参数数量和计算量远小于等效的全连接层。例如,处理224×224×3的图像,一个3×3×3×64的卷积层只有1728个参数,而等效的全连接层需要约6亿个参数。
大白话 全连接层是"每个人都认识每个人"——N个人需要N²个关系。卷积层是"每个人都只认识隔壁邻居"——只需要看3×3的窗口,而且所有人共用同一套"认识标准"。
Q2: Same Padding 和 Valid Padding 各用在什么场景?
Valid Padding(无填充):①需要空间压缩时——如分类网络最后的几层;②不希望边界效应干扰特征提取时;③计算资源受限希望减少特征图尺寸时。Same Padding(输出尺寸等于输入尺寸/stride):①网络的主体部分,希望保持空间信息;②需要精确定位的任务——如语义分割、目标检测;③有跳跃连接的架构需要维度匹配。
Q3: 1×1卷积有什么作用?
1×1卷积看起来很"无用"(它只看一个像素点),但在CNN中有三个重要用途:①通道压缩/扩展——通过控制输出通道数,1×1卷积可以改变特征图的通道维度而不改变空间尺寸。②跨通道信息融合——将多个通道的信息非线性地组合起来。③减少计算量(Bottleneck)——先用1×1卷积降维,做正常卷积,再用1×1升维,如ResNet的bottleneck模块。这种"先压缩、再处理、再恢复"的设计大大降低了计算量。
章节单词汇总
| 英文 | 音标 | 术语/释义 |
|---|---|---|
| Convolution | /ˌkɒnvəˈluːʃən/ | 卷积,滑动核做乘加运算 |
| Kernel/Filter | /ˈkɜːrnəl/ /ˈfɪltər/ | 卷积核/滤波器,小的权重矩阵 |
| Stride | /straɪd/ | 步长,卷积核每次滑动的像素数 |
| Padding | /ˈpædɪŋ/ | 填充,在输入边界补值(通常补0) |
| Feature Map | /ˈfiːtʃər mæp/ | 特征图,卷积层的输出 |
| Receptive Field | /rɪˈseptɪv fiːld/ | 感受野,输出像素对应的输入区域 |
| Weight Sharing | /weɪt ˈʃerɪŋ/ | 权重共享,同一核在不同位置复用 |
| Translation Equivariance | /trænzˈleɪʃən ɪˈkwɪvərɪəns/ | 平移等变性,输入平移→输出同样平移 |
| Strided Convolution | /ˈstraɪdɪd ˌkɒnvəˈluːʃən/ | 步长卷积,步长>1的卷积 |
| Channel | /ˈtʃænəl/ | 通道,特征图的第三维度 |
面试练习
Q1 [单选] 输入尺寸为32×32,使用3×3卷积核、步长2、Valid Padding,输出尺寸是多少?
- A. 15×15
- B. 16×16
- C. 32×32
- D. 17×17
解答:O = ⌊(32-3)/2⌋ + 1 = ⌊14.5⌋ + 1 = 14 + 1 = 15。
Q2 [单选] 使用3×3卷积核、Same Padding、步长1时,单侧需要补多少零?
- A. 1
- B. 2
- C. 0
- D. 3
解答:Same Padding需要O=W,即(W-3+2P)/1+1=W,解得2P=2,P=1。
Q3 [多选] 以下关于卷积操作的描述,哪些是正确的?
- A. 卷积核在整个输入上共享权重
- B. 卷积操作具有平移等变性
- C. 卷积核的参数量与输入尺寸成正比
- D. 每个输出值只与输入的局部区域相关
解答:卷积核的参数量= C_out × C_in × kH × kW,与输入尺寸无关(这正是权重共享的好处)。A、B、D都是卷积的核心特性。
Q4 [单选] 一个卷积层的输入是3通道224×224图像,使用64个3×3卷积核,需要多少参数?
- A. 64 × 3 × 3 × 3 = 1728(忽略偏置)
- B. 64 × 224 × 224
- C. 3 × 3 × 3 = 27
- D. 64 × 3 × 224 × 224
解答:参数数 = C_out × C_in × kH × kW = 64 × 3 × 3 × 3 = 1728。如加上偏置再加64。
Q5 [单选] 1×1卷积主要用于什么目的?
- A. 改变通道数(降维或升维)
- B. 增加感受野
- C. 检测大尺度特征
- D. 替代池化层
解答:1×1卷积不改变空间尺寸,仅影响通道维度。常用于channel-wise的降维/升维和跨通道信息融合。
Q6 [多选] 以下哪些因素会影响卷积层的输出尺寸?
- A. 输入尺寸
- B. 卷积核大小
- C. 步长
- D. 填充量
解答:输出尺寸公式O=(W-K+2P)/S+1中的四个变量全部影响输出尺寸。
Q7 [单选] 在CNN中,随着网络层数增加,特征图的空间尺寸通常如何变化?
- A. 逐渐减小(通过步长卷积或池化)
- B. 逐渐增大
- C. 保持不变
- D. 先减小后增大
解答:经典CNN通过步长卷积或池化逐步缩小特征图空间尺寸,同时增加通道数,形成"金字塔"结构。
Q8 [多选] 关于填充(Padding),以下哪些说法正确?
- A. Same Padding使输出尺寸等于输入尺寸(stride=1时)
- B. Valid Padding等同于无填充
- C. 填充值只能为0
- D. 填充帮助保持特征图的空间分辨率
解答:填充值通常为0但也可以用其他值(如反射填充、复制填充等),C错误。A、B、D正确。
Q9 [单选] 为什么卷积比等效的全连接层参数少那么多?
- A. 卷积计算使用GPU加速
- B. 权重共享和局部连接
- C. 卷积核尺寸很小
- D. 卷积不需要偏置
解答:权重共享(所有位置用同一组权重)和局部连接(每个输出只看局部窗口)是卷积参数高效的根本原因。
Q10 [单选] 一个输入尺寸为13×13的特征图,经过3×3卷积(stride=2, padding=1),输出尺寸为?
- A. 7×7
- B. 6×6
- C. 8×8
- D. 13×13
解答:O = ⌊(13-3+2×1)/2⌋ + 1 = ⌊12/2⌋ + 1 = 6 + 1 = 7。