← 返回首页
实验

在浏览器里,让 CNN 认出你写的数字

从零理解 CNN、卷积核和特征图,再亲手写一个 0–9,看浏览器如何一步步识别它。

先弄清楚:这台机器到底在做什么

这个演示要完成的任务叫做图像分类:输入是一张手写数字图像,输出是 0–9 中的一个数字类别。

你可以把整件事想成一场有标准答案的猜数游戏:

  1. 训练:先给网络看大量带有正确类别标签的手写数字图像。例如,一张图片旁明确写着“正确类别是 3”。网络每预测错误一次,训练程序就调整模型参数——包括卷积核权重、偏置以及后续层的权重——让下一次预测更接近正确类别。
  2. 推理:训练结束后,已经学到的模型参数保持不变。此时输入一张新的手写数字图像,网络只使用这些固定参数,计算它属于 0–9 各类别的概率,不再修改权重。

为避免混淆,本文统一使用以下称呼:

  • 数字类别:答案属于 0–9 中的哪一类,例如“类别 3”。
  • 像素值:图片中每个小格的亮度,本页用 0–1 表示。
  • 模型参数:网络在训练时会调整、在推理时保持不变的数值,主要包括权重和偏置。

这个页面做的是“推理”,不是现场训练。你的笔迹只在当前浏览器中计算,不会上传。

CNN 究竟是什么

CNN 的全名是 Convolutional Neural Network,中文叫卷积神经网络。

先不要被“神经网络”这个名字吓到。在这个演示里,它本质上是一条由许多数学运算组成的流水线:

图片 → 找边缘 → 组合成形状 → 给 0–9 打分 → 选最高分

其中“卷积”是这条流水线最关键的观察方式:它不一次看完整张图,而是拿一个小窗口从左到右、从上到下逐格扫描,先找到边、拐角和笔画方向,再把它们组合成更完整的数字字形。

先认识 4 个词

  1. 像素:图片中最小的格子。这里的黑白图可以看成 28 × 28 = 784 个像素值;0 接近黑,1 接近白。
  2. 层:流水线上的一个工位。每一层只做一类较简单的计算,多层接力后才能完成识别。
  3. 卷积核:一个很小的权重方阵,比如 3 × 3 的 9 个权重。它像一枚“花纹探测器”,专门检查局部区域是否出现某种明暗变化。真实模型中的核不是人手写的规则,而是从训练数据中学出来的。
  4. 特征图:卷积核扫完整张图后产生的新图。哪里与它想找的花纹越像,对应位置的特征值就越大、显示得越亮。

卷积核到底怎么算

它没有在做什么神秘的事,每停在一个位置,只做下面五步:

  1. 用 3 × 3 的卷积核盖住图片上的 3 × 3 个像素。
  2. 将 9 个像素值与 9 个卷积核权重逐项相乘,共做 9 次乘法。
  3. 把 9 个乘积全部加起来,再加上一个叫做偏置的基础分。偏置也是训练出来的,它用来调整这个卷积核“多容易被触发”。
  4. 通过 ReLU:正数原样保留,负数改成 0。可以把它理解成“不像就不点亮,像才点亮”。
  5. 向右移动一格,在新位置重复同样的计算。

把它写成一行就是:

输出 = ReLU(9 个“像素 × 卷积核权重”的和 + 偏置)

不需要背这个公式。只要记住:小窗口负责取数,卷积核负责打分,ReLU 负责把没有匹配到的负分关掉。

为什么这样就能找到边缘?因为卷积核里的正权重表示“这里亮就加分”,负权重表示“这里亮就扣分”。当一块像素的明暗排列恰好与卷积核偏好的花纹一致,总分就会很高。

下方的第一张互动卡用一个简化的“竖直边缘核”演示整个计算。点击“下一格”,可以看到窗口移动后,参与计算的 9 个像素和最终分数如何变化。

卷积核里的权重是谁设定的

先给出短答案:不是人逐格填的,而是模型在训练中一点点学出来的。

人会决定“这一层用 8 个 3 × 3 卷积核”,这叫网络结构;但每个核里的 9 个权重和 1 个偏置应该是多少,由训练来决定。

第一步:先从“乱猜”开始

训练开始时,权重通常会被随机初始化成一组接近 0 的小数。它们此时没有“找边缘”或“找圆弧”的能力,第一次预测几乎是随机猜。

为什么不把所有卷积核都初始化成完全相同的 0?因为那样 8 个核会收到完全相同的修改,永远学成一样的东西。用略有不同的随机数开局,是为了打破这种对称,让不同核有机会分工学习不同特征。

第二步:让当前卷积核先猜一次

一张带有正确类别标签的手写图片进入网络,依次经过卷积、池化和分类层,最后产生属于 0–9 各类别的概率。数据从输入一路流向输出的过程,叫做正向传播。

假设图片的正确答案是 3,模型却把 8 的概率给得最高,这次猜测就是错的。但“错了”还不足以指导修改,还要知道错得多严重。

第三步:用损失函数量化错误程度

损失函数会把“预测”与“正确答案”的差距变成一个数,称为损失。预测越离谱,损失越大;预测越接近答案,损失越小。

训练的目标不是让某一个卷积核“看起来像边缘检测器”,而是让整个网络在大量图片上的平均损失尽可能小。这也是真实卷积核的权重常常不整齐、不能逐个用人类语言命名的原因。

第四步:把错误的责任一层层传回去

最终答案猜错,不只是最后一层的责任。前面的卷积核提取了什么特征,也会影响后面的判断。

反向传播会从最后的损失出发,沿着网络倒着往回算,为每个权重算出一个梯度。梯度可以先理解成一张“责任单”:

  • 这个权重如果变大一点,损失会变大还是变小?
  • 它对这次错误的影响大不大?
  • 它应该向上调还是向下调?

它不是把误差平均分给所有参数。某个权重对本次预测的影响越大,收到的修改信号通常也越大。

第五步:每次只修改一小步

有了梯度后,梯度下降负责真正更新权重:

新权重 = 旧权重 − 学习率 × 梯度

学习率决定每次修改多少。太大可能一步跨过更好的位置,在两边来回震荡;太小则学得非常慢。合适的学习率会让损失逐步下降,而不是期待一次就把答案改对。

下方的第二张互动卡使用一个可以心算的简化例子,展示一个全为 0 的卷积核如何在三次更新中逐渐学成“左负右正”:

训练次数 左列权重 中列权重 右列权重 预测分数 损失
0 0.000 0.000 0.000 0.000 0.5000
1 -0.100 0.000 0.100 0.600 0.0800
2 -0.140 0.000 0.140 0.840 0.0128
3 -0.156 0.000 0.156 0.936 0.0020

可以看到,“学会找边”不是突然产生的,而是预测逐步靠近目标、损失逐步下降的结果。

真实卷积核为什么看起来比这个例子乱

上面的例子直接告诉小卷积核“这是一条边”,只是为了把一次更新讲清楚。真实 CNN 并没有“边缘正确答案”,它只知道整张图最后应该是 3 还是 8。卷积核与其他层一起调整,只要最终分类损失下降就会保留这次改动。

因此,真实结果通常会有三个特点:

  1. 权重通常是 -0.42、0.27 这样的小数,而不是整齐的 -1、0、1。
  2. 每个核只是团队中的一员,单独拿出来未必有完美的人类语义;页面中的“竖边”“斜边”标签只是近似解读。
  3. 换一个随机起点重新训练,最后的具体参数值可能不同,但整体识别效果可以同样好。

训练结束后,这些模型参数才会固定下来。你在本页输入新的手写数字图像时,只在做正向传播,卷积核权重不会再更改。

为什么还要一层层叠加

一个卷积核通常只会找到一种很简单的局部特征。所以 CNN 会让多个核并行工作,再把它们叠成多层:

784 个像素值 → 边缘 / 方向 → 拐角 / 圆弧 → 手写字形的整体结构 → 0–9 各类别的概率

第一层不需要知道“这是 3”,它只要找边。后面的层再把边拼成更大的部件。这就是神经网络“由简单特征逐层组合出复杂概念”的核心思路。

现在再亲手试一次

先用下面的小例子看懂一次卷积计算,然后在画板上写一个 0–9 的数字。模型会把你的笔迹依次变成:

  1. 28 × 28 的像素值矩阵。
  2. 第一层卷积核找到的边缘和笔画方向。
  3. 池化后更小、更抗抖动的特征图。
  4. 第二层卷积组合出的拐角和圆弧。
  5. 展平后的一维特征向量,以及经过 Softmax 换算的类别概率。

画完后向下滚动,可以逐层看真实 CNN 刚才计算出来的中间结果。如果不想手写,也可以直接点击预设数字。

先看懂一次计算,再运行真实模型

卷积核不是黑盒:它只做 9 次乘法

左边是一张只有 0 和 1 的迷你图片,右边是一个寻找“左侧暗、右侧亮”的 3×3 卷积核。 彩色框每次只看 9 个像素;它移动一格,就重新算出一个结果。

迷你图片0 = 暗,1 = 亮
0000000110001100011000000
竖直边缘核正数加分,负数扣分
−101−101−101
窗口 1 / 3

相同位置两两相乘

0 × −1 = 00 × 0 = 01 × 1 = 10 × −1 = 00 × 0 = 01 × 1 = 10 × −1 = 00 × 0 = 01 × 1 = 1
9 项相加3
→ ReLU:负数归零 →
写入特征图3

这里出现了从暗到亮的竖直变化,与这个卷积核想找的左边缘相似,所以得到正分。

这一行特征图:330

再追问一步:这 9 个权重从哪里来

卷积核不是人填的,是一次次改出来的

人只规定“使用 3×3 的小窗口”,不会逐个指定里面该填什么。 训练开始时这些权重通常是接近 0 的随机小数;模型猜一次、量一次错误、改一点点,重复许多次后才得到最终数值。

  1. 1随机初始化先从一组没有意义的小数出发
  2. 2正向传播用当前权重计算预测
  3. 3计算损失量出预测离答案有多远
  4. 4反向传播算清每个权重该往哪边改
  5. 5梯度下降按学习率只修改一小步
可复算的玩具例子

教一个全为 0 的卷积核认识“左暗右亮”

为了能心算,把暗、过渡、亮分别写成 −1、0、1,并告诉模型这块花纹的目标分数是 1。 这里直接给了“边缘答案”只是教学简化;真实 CNN 只拿到整张图片的类别标签(例如“这是 3”),误差会从输出层一路反传到卷积核。

训练样本
−101−101−101
第 0 步的卷积核
0.0000.0000.0000.0000.0000.0000.0000.0000.000
目标分数1.000
−
当前预测0.000
=
误差1.000
→
损失 ½·误差²0.5000
每个权重的改变量0.1(学习率)× 1.000(误差)× 对应像素

卷积核现在全是 0,所以无论看到什么都只能打 0 分。点击“训练一步”开始修改。

回到这个页面的真实模型

为什么真实卷积核不是整齐的 −1、0、1?

右边是模型第 0 个卷积核的真实参数。它不是单独练习“找一条标准边”,而是与另外 7 个卷积核、后面的卷积层和分类层一起,为“最终的数字类别有没有预测正确”负责。 因此优化器只追求整套网络的损失更小,不追求权重值整齐或方便人类解释。

每个权重都是大量样本、许多轮更新共同累积的结果。换一组随机初始值重新训练,最后的参数值通常会不同;只要它与其他卷积核配合后同样能正确识别,就仍然是有效答案。

真实卷积核 0
正在读取模型参数…

用鼠标 / 手指在这里手写一个数字

不会画?点一个试试:

计算机视觉,其实是披着风衣的五层数学结构

你在上面画的数字,会像在一条流水线上一样,依次经过下面 5 个工位。 每个工位都在真的计算、图也真的在变—— 最后由一排“评委”投票,票数最高的就是答案。边画边往下看,图里很多地方还能点一点、悬停一下。

这套顺序不是随便排的——整条流水线在由简到繁地搭积木:

像素值→边 / 角→形状零件→完整字形
0

上料 · 你画的手写数字

在上面画一个数字(0–9)

流水线的第一步:把你的字缩成 28×28 的小格子、摆正。注意——在电脑眼里这根本不是一张“图”,而是 784 个像素值。上面这张图你可以点点看:每个格子都有一个 0~1 的亮度值。

💡 为什么这么做为什么先缩成 28×28、还要摆正?因为后面每个“检查员”都是按固定的大小、固定的位置训练出来的。先把输入统一成同一种格式,它们才认得——就像考试得用统一答题卡。

1

Conv2D(二维卷积)· 找边缘

在上面画一个数字(0–9)

第 1 个工位站着 8 个“检查员”(卷积核),各自只认一种花纹,拿着一个 3×3 的小窗口滑过整张图,看到就在那个位置点亮——于是 1 张图变成了 8 张“这种花纹出现在哪儿”的地图。每张图下面标了那位检查员大致在找什么。(注:网络没被告诉要找什么,这些名字是照它自己练出来的卷积核近似标的,所以有的清晰、有的偏杂。)

💡 为什么这么做为什么第一步先找“边”?因为一个手写数字属于哪个类别,几乎全写在它的轮廓和笔画走向上,而不在于笔画是深是浅、背景是什么。直接盯着每个像素的亮度既啰嗦又脆弱(你写深一点、浅一点,数值全变);而“这儿有没有一条边、朝哪个方向”这种线索才真正稳,才是区分 1 和 7 的关键。所以先把最基础、最通用的积木——边和角——抠出来,后面所有判断都搭在它上面。

2

MaxPool(最大池化)· 缩小去噪

在上面画一个数字(0–9)

第 2 个工位负责“打包压缩”:把每 2×2 一小格只留最亮的那个点,图整整缩小一半。关键亮点全留着,细枝末节和噪声被丢掉——既省力,又让识别不那么挑剔你画得偏一点、大一点。(还是刚才那 8 张检查员的地图,只是每张缩小了一半。把鼠标放到格子上能看到具体数值。)

💡 为什么这么做为什么要缩小去噪?三个好处:① 更抗抖——你把字写偏一点、大一点,边的精确位置会变,但“这一带有条竖边”不变;只留每小块里最强的信号,等于说“我只关心附近有没有这个特征,不纠结它在第几个像素”,识别因此对位置、大小、笔迹都更宽容。② 省算力——图小一半,后面每层计算量降到约四分之一。③ 放大视野——图缩小后,下一层同样的 3×3 窗口能覆盖原图更大一片,正好接着往下拼更大的形状。

3

Conv2D(二维卷积)· 拼出更大的部件

在上面画一个数字(0–9)

第 3 个工位是更资深的 16 个检查员。他们不看原图,而是看上一批人交上来的“花纹地图”,把短横短竖拼成拐角、圆弧这类更大的部件。越往后,识别的东西越接近数字字形的局部结构。(同样可以把鼠标放到格子上看特征值。)

💡 为什么这么做为什么还要再卷一层、拼更大的部件?因为光看“边”还判断不出数字类别:横线竖线到处都是,1、4、7 都有竖线。真正区分它们的是这些边怎么组合——一个圈、一个朝左的开口、一个交叉。第二层站在第一层的“边缘地图”上,把邻近的边拼成更大的形状零件(拐角、圆弧、闭环)。这就是“由简到繁搭积木”:像素→边→部件,越往深,识别的结构越大、越抽象、越接近“这到底是几”。

4

Flatten(展平)· 排成一队

在上面画一个数字(0–9)

第 4 个工位把 16 张二维特征图一张张拆开、首尾接成一条长队(16×11×11 = 1936 个特征值)。这一步不改动任何信息,纯粹是换个队形。

💡 为什么这么做为什么要拍平成一条队?因为做最终判断的“评委”(下一层)只接受一维特征向量,没法直接读取二维特征图。拍平纯粹是为了对接它的输入格式,好让它一次性从头读到尾——信息一个都没丢。

5

Dense(全连接层)+ Softmax(概率换算)· 投票选答案

在上面画一个数字(0–9)

最后一个工位坐着 10 位“评委”,分别代表 0~9 十个数字类别。每位评委把长队里的 1936 个特征值按自己的偏好加权求和,得到一个原始分(可能是 8.7、-1.2 这种,没上限、还能是负的)。原始分不好读,于是用 Softmax:它是一把“换算尺”,把这 10 个分数压成一串加起来正好等于 100% 的概率——分越高,占的比例越大。这样你就能直接读成“我有 92% 的把握是 3”。柱子最高的那个,就是答案。

💡 为什么这么做为什么用投票 + 概率来收尾?前面几层只是“看到了哪些零件、在哪儿”,还没下结论。让 10 位评委各自按“我代表的数字类别应该具有什么形状”去打分,就把“看到了什么”变成“所以最像几”;再用 Softmax 换算成加起来 100% 的概率,是为了让结果好读、能比较——“92% 是 3、5% 是 5”比一堆原始分直观得多。

🧠 它是怎么学会的?

这些“检查员”和“评委”的眼力,不是人一条条规则写出来的。 一开始他们全靠瞎猜、错得一塌糊涂。我们拿 6 万张带有正确类别标签的手写数字图像一张张给它看, 每猜错一次,就把大家的判断往正确方向微调一点点; 这样重复几十万次,他们就慢慢练成了现在的样子(这个 demo 认对的概率约 98.5%)。 所以你换种写法它多半也认得——因为它见过的写法实在太多了。

从人脸解锁、自动驾驶到 Sora,本质上都是这五个工位不断放大、变形、组合。(注:现代视觉大模型还大量用了 Transformer、Diffusion 等结构,不只是 CNN 五层堆叠。)

SEARCH / 搜索

找一篇内容

输入关键词,快速找到教程、实验、随笔和专题。

ESC 关闭