核心要点
卷积 = 卷积核在输入上滑窗,对每个窗口做元素乘积再求和。
先按 padding 在输入四周补零,再按 stride 控制窗口移动步长。
输出尺寸 H_out = (H + 2P − K) / S + 1(向下取整),宽同理。
易错点:边界越界、stride 整除、padding 后才取窗口;多通道需在通道维一并求和。
标准回答
2D 卷积本质是卷积核在输入特征图上滑动,每滑到一个位置就把覆盖的局部区域与卷积核做逐元素相乘再求和,得到输出的一个像素。实现要点有三:先用 padding 在输入四周补零以控制输出尺寸与保留边缘信息;用 stride 决定窗口每次移动的步长;输出尺寸由公式 (H + 2P − K)/S + 1 决定。下面给出单输入/单核的直观滑窗实现(含 stride 与 padding):
import numpy as np
def conv2d(x, kernel, stride=1, padding=0):
# x: (H, W) 输入;kernel: (K, K) 卷积核
K = kernel.shape[0]
# 四周补零 padding
xp = np.pad(x, ((padding, padding), (padding, padding)), mode='constant')
H, W = xp.shape
H_out = (H - K) // stride + 1 # 输出高
W_out = (W - K) // stride + 1 # 输出宽
out = np.zeros((H_out, W_out))
for i in range(H_out):
for j in range(W_out):
hs, ws = i * stride, j * stride
window = xp[hs:hs + K, ws:ws + K] # 取出局部窗口
out[i, j] = np.sum(window * kernel) # 元素积求和
return out
if __name__ == '__main__':
x = np.arange(25, dtype=float).reshape(5, 5)
k = np.ones((3, 3)) # 求和核
y = conv2d(x, k, stride=1, padding=1)
print(y.shape) # (5, 5),padding=1 保持尺寸常见误区
⚠️ 常见踩坑
输出尺寸公式忘记 +1 或写成 (H−K+2P)/S 取整后再处理边界会差一行/列;padding 必须在取窗口之前完成,否则边缘像素被丢弃。
追问
追问 1:复杂度是多少?工程上如何加速?
单核朴素实现为 O(H_out·W_out·K²),多通道多核再乘以 C_in·C_out。工程上用 im2col 把每个滑窗展平成列、再用一次大矩阵乘 (GEMM) 完成所有卷积,充分利用 BLAS/GPU;小核常用 Winograd,大核或 FFT 卷积进一步降复杂度。
追问 2:深度学习里卷积和数学定义的卷积有何区别?
数学卷积要把核翻转(180°)再滑动,而深度学习框架(如 PyTorch 的 Conv2d)实际做的是「互相关」——不翻转核直接滑窗相乘。因为核是学出来的,翻转与否只是参数的镜像,效果等价,所以业界统一称作卷积。
延伸学习
与本题相关的知识库文章、术语、工具与行业资讯。
🛠️ AI 工具
- Pytorch
Meta 开源的深度学习框架,100K+ stars。以动态计算图和 Pythonic 风格著称,在学术界和工业界都有广泛应用,支持分布式训练、移动端部署和 ONNX 导出