核心要点

  • 卷积 = 卷积核在输入上滑窗,对每个窗口做元素乘积再求和。

  • 先按 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 上一起求和,每个输出通道对应一个自己的卷积核。下面给出单输入/单核的直观滑窗实现:

python
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°,但深度学习框架通常做的是 互相关:不翻转核,直接滑窗相乘求和。因为卷积核是学出来的,翻不翻转本质上只是参数表示不同。

🔗 相似问题

同一考点的不同问法,换着练更稳

延伸学习

按主题分类的相关资源,便于系统复习