核心要点
卷积 = 卷积核在输入上滑窗,对每个窗口做元素乘积再求和。
先按 padding 在输入四周补零,再按 stride 控制窗口移动步长。
输出尺寸 H_out = (H + 2P − K) / S + 1(向下取整),宽同理。
易错点:边界越界、stride 整除、padding 后才取窗口;多通道需在通道维一并求和。
标准回答
一、先把结论讲清楚
可以先说直觉:2D 卷积就是卷积核在图像上滑动,每个窗口做元素乘积再求和。
一、实现顺序是什么?
先根据 padding 给输入四周补零,再按 stride 移动窗口。每到一个位置,取出和 kernel 同大小的窗口,做 window * kernel,再 sum,得到输出上的一个点。
二、拆开机制和判断点
二、输出尺寸怎么算?
如果输入高是 H,卷积核大小是 K,padding 是 P,stride 是 S,那么输出高是 floor((H + 2P - K) / S) + 1,宽同理。这个 +1 很容易漏。
**三、多通道时怎么理解?
三、补上例子、边界和取舍
**
单通道是窗口和 kernel 相乘求和;多通道时,kernel 会覆盖所有输入通道,先在 H、W、C_in 上一起求和,每个输出通道对应一个自己的卷积核。下面给出单输入/单核的直观滑窗实现:
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。 这会直接少算一行或一列。
误区二:先取窗口再补 padding。 padding 必须先作用在输入上,否则边缘信息已经丢了。
追问
追问 1:复杂度是多少?工程上如何加速?
朴素单核复杂度是 O(H_out·W_out·K²),多通道多核还要乘 C_in 和 C_out。工程上常用 im2col + GEMM、Winograd 或 FFT 来加速。
追问 2:深度学习里卷积和数学定义的卷积有何区别?
数学卷积会把核翻转 180°,但深度学习框架通常做的是 互相关:不翻转核,直接滑窗相乘求和。因为卷积核是学出来的,翻不翻转本质上只是参数表示不同。
🔗 相似问题
同一考点的不同问法,换着练更稳
延伸学习
按主题分类的相关资源,便于系统复习
