核心要点

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

  • 先按 padding 在输入四周补零,再按 stride 控制窗口移动步长。

  • 输出尺寸 H_out = (H + 2P − K) / S + 1(向下取整),宽同理。

  • 易错点:边界越界、stride 整除、padding 后才取窗口;多通道需在通道维一并求和。

标准回答

2D 卷积本质是卷积核在输入特征图上滑动,每滑到一个位置就把覆盖的局部区域与卷积核做逐元素相乘再求和,得到输出的一个像素。实现要点有三:先用 padding 在输入四周补零以控制输出尺寸与保留边缘信息;用 stride 决定窗口每次移动的步长;输出尺寸由公式 (H + 2P − K)/S + 1 决定。下面给出单输入/单核的直观滑窗实现(含 stride 与 padding):

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 或写成 (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 导出