先看效果:手写数字识别

在下方画布上手写一个数字(0-9),右侧 CNN 实时给出每个数字的置信度。模型准确率 ~99%,完全在浏览器中运行。

在这里手写数字
识别结果
?
置信度

01 核心原理(大白话版)

你认出一张猫的照片,不是因为你记住了图片里每一个像素的颜色——而是因为你看到了尖耳朵、胡须、毛茸茸的轮廓这些特征。

CNN 做的事情和你一样:先从图像里找局部特征,再把特征组合成更高级的概念,最后得出判断。

为什么普通神经网络不够用?

一张 224×224 的彩色图片有 150,528 个像素值。如果用普通全连接层,第一层每个神经元就要连接 150,528 个输入——参数量爆炸,而且完全无视了像素之间的空间关系。

关键洞察:图像里有用的信息是局部的——边缘、角点、纹理都只占图像的一小块区域。CNN 就利用这个特性,用小窗口扫描图像,而不是一次性看全部。

CNN 的三个核心操作

1
卷积(Convolution)—— 找特征

用一个小窗口(卷积核,比如 3×3)在图像上滑动,每滑到一个位置就做一次加权求和,输出一个数。滑完全图得到一张特征图,记录了"这个卷积核负责的特征在哪里有响应"。
多个不同的卷积核 = 同时检测多种特征(横边缘、竖边缘、斜线…)。

2
池化(Pooling)—— 缩图

特征图太大了,用最大池化(Max Pooling)把 2×2 的小块缩成 1 个数(取最大值),图像尺寸减半,但重要特征保留。好处是:减少计算量,同时带来一点平移不变性——特征稍微挪了几个像素,结论不变。

3
全连接(FC)—— 下判断

把最后一层特征图"拍平"成一维向量,送进普通的全连接层。此时特征已经足够抽象(不再是像素,而是"有没有耳朵"这样的高级概念),全连接层负责组合这些特征、输出分类概率。

CNN 的两大关键特性

局部感受野

每个神经元只看图像的一小块区域(感受野),而不是全图。这符合图像的自然结构:有意义的特征总是局部的。

权重共享

同一个卷积核在图像上滑动时,用的是同一套权重。检测"竖线"的卷积核,不管竖线在图像左侧还是右侧,都用同一个核去找。参数量从百万降到几十。

层叠起来,特征越来越抽象

CNN 通常堆叠多个"卷积 + 池化"组合:

  • 第1层卷积:学到边缘、颜色渐变等底层特征
  • 第2层卷积:把边缘组合成角、圆弧等中层特征
  • 更深的层:组合出眼睛、轮子、文字等高层语义特征

这就是深度学习"深"的意义:不是人工设计特征,而是让网络自己一层一层地把像素抽象成概念。

一步步构建 CNN

从像素绘制到分类训练,逐步搭建。

第一步 绘制 16×16 像素图形

用程序在 16×16 画布上画圆、方块、三角形,每个像素值 0 或 1。

第二步 生成数据集

批量生成 180 个样本,整理成 CNN 需要的四维张量格式 [N, H, W, C]。

第三步 搭建卷积网络

两层卷积(提取特征)→ 展平 → 全连接层(分类)。

第四步 训练并观察精度

用 adam 优化器训练,每 epoch 打印 loss 和 accuracy。

02 代码

03 学术性讲解

卷积操作的数学定义

对于输入特征图 X(高 H × 宽 W × 通道数 C_in)和卷积核 K(高 k_H × 宽 k_W × C_in),输出特征图第 (i, j) 位置的值为:

Y[i, j] = Σ_c Σ_m Σ_n X[i·s+m, j·s+n, c] · K[m, n, c] + b

其中 s 为步长(stride),b 为偏置。使用多个卷积核(filters)时,每个核产生一张特征图,所有特征图叠在一起形成下一层的输入。

卷积操作示意

输入 5×5 卷积核 3×3 w₁ w₂ w₃ w₄ w₅ w₆ w₇ w₈ w₉ 特征图 3×3 y₀₀ 输出尺寸(无padding): H_out = (H - k_H) / s + 1 W_out = (W - k_W) / s + 1 如:(5-3)/1+1 = 3 padding=same 时输出同尺寸

参数量计算

卷积层参数只有卷积核的权重和偏置,与输入图像尺寸无关:

参数量 = k_H × k_W × C_in × C_out + C_out(偏置)

本演示模型(第1层 Conv2D):3 × 3 × 1 × 8 + 8 = 80 个参数。对比全连接层处理 16×16 输入到 8 个神经元需要 16×16×8 + 8 = 2056 个参数,卷积层效率高得多。

网络结构

本演示模型结构为 Conv(8) → Pool → Conv(16) → Pool → Flatten → Dense(32) → Dense(3)

  • Conv2D(8, 3×3, ReLU, same):输入 (batch, 16, 16, 1),输出 (batch, 16, 16, 8),8个卷积核各检测一种特征,参数量 80
  • MaxPooling2D(2×2):输出 (batch, 8, 8, 8),尺寸减半,保留最强响应
  • Conv2D(16, 3×3, ReLU, same):输出 (batch, 8, 8, 16),在第1层特征基础上组合更复杂结构,参数量 1168
  • MaxPooling2D(2×2):输出 (batch, 4, 4, 16)
  • Flatten:输出 (batch, 256)
  • Dense(32, ReLU):输出 (batch, 32),参数量 8224
  • Dense(3, Softmax):输出 (batch, 3),对应圆/方/三角三类概率,参数量 99

池化层

最大池化(Max Pooling)在每个池化窗口内取最大值:

y[i, j] = max_{m,n ∈ window} x[i·s+m, j·s+n]

最大池化保留特征的位置不变性:即使特征稍微平移,最大值仍被保留。平均池化(Average Pooling)则取均值,信息更平滑但边缘特征可能被稀释。

激活函数

卷积后通常跟 ReLU 激活,引入非线性:

ReLU(x) = max(0, x)

ReLU 让负响应归零("这个位置没有这个特征"),正响应保留。计算简单、梯度不消失,是 CNN 最常用的激活函数。

反向传播与权重更新

CNN 同样通过反向传播计算梯度,但卷积层的梯度需要通过转置卷积(也叫反卷积)传回输入层。由于权重共享,同一个卷积核在不同位置的梯度会累加

∂L/∂K[m,n] = Σ_{i,j} ∂L/∂Y[i,j] · X[i·s+m, j·s+n]

权重更新公式与 MLP 相同:K ← K − η × ∂L/∂K,偏置 b ← b − η × ∂L/∂b

卷积特征图可视化

观察不同卷积核在图像上滑动的过程,以及产生的特征图:

输入图像 (8x8)
卷积核滑动
特征图 (6x6)

经典 CNN 架构演进

LeNet-5(1998)

最早的实用 CNN,用于手写数字识别。结构:2个卷积层 + 3个全连接层,奠定了 CNN 的基本范式。

AlexNet(2012)

ImageNet 竞赛冠军,将 CNN 带入深度学习时代。引入 ReLU、Dropout、数据增强,Top-5 错误率降至 15.3%。

VGG(2014)

用统一的 3×3 小卷积核堆叠更深的网络(16-19层),证明网络深度是性能的关键。

ResNet(2015)

引入残差连接(skip connection),解决了深层网络的梯度消失问题,将网络深度推向 152 层,Top-5 错误率降至 3.57%。