书里用支持向量机,这里换成一个更好懂的算法——最近邻:跟训练集里每个样本比一比,看谁最像,就认成谁。
12.5 确认了数据没问题,这一节真正跑一遍识别——也是这一章的动手重头戏。
一、换一个好懂的算法
书里用的是支持向量机(12.2 讲过)。这里换个更容易上手的:最近邻(k-NN)。 它的流程只有四步: 拿到一个新样本 → 跟训练集里每一个比一比、算有多像 → 挑出最像的 k 个 → 谁最多就判成谁。 它连【训练】这一步都没有——训练集本身就是模型。 但道理和 SVM 一模一样:都是从数据里找规律,而不是人写规则。
二、实测的结果
按 80/20 分好数据,k 取 3,跑一遍: 训练样本 32 条、测试样本 8 条、判对 7 条——准确率 87.5%。 认错的那一条:正确答案是 3,它认成了 2。 这个结果正好印证了上一节的观察——3 和 2 都有横有弯,是最容易混的一对。 所以这个错误不是【程序坏了】,是【这两个东西本来就长得像】。
三、为什么不能只看这个分数
87.5% 听起来不错,但先别急着庆祝。 测试样本只有 8 条——对一条就是 12.5 个百分点,波动大得吓人。 换句话说:换个切法,这个数字可能变成 75%,也可能变成 100%。 这就是下一节要解决的问题:单次成绩到底能信几分。
四、为什么这类算法能认字
根本原因不神秘:同一个数字的写法,彼此都很像。 同一类的样本在 64 维空间里挤成一团,不同类之间隔得比较远——所以【找最近的邻居】就行得通。 这也解释了它为什么错:当两个数字的写法真的接近时,那一团就挨到一起了。 提高的办法也很直白:更多样本、更好的特征、更强的算法——但都绕不开【数据得先像样】。
五、动手 Demo:通过机器学习识别手写数字
书里用的是支持向量机,这里换个更好懂的算法:最近邻。 跟训练集里每个样本比一比,看谁最像就认成谁。
xs, ys = make_dataset(per_digit=8)
cut = int(len(xs) * 0.8)
trx, tryy, ttx, tty = xs[:cut], ys[:cut], xs[cut:], ys[cut:]
right = 0
for q, t in zip(ttx, tty):
if knn_predict(trx, tryy, q, 3) == t:
right += 1
print("准确率", right / len(ttx) * 100, "%")
实跑输出:
--------------------------------------------------------------------
通过机器学习识别手写数字
--------------------------------------------------------------------
书里用的是支持向量机,**这里换个更好懂的算法:最近邻(k-NN)。**
拿到一个新样本
跟训练集里每一个样本比一比,算出有多像
挑出最像的 k 个
它们里哪个标签最多,就判成哪个
**它没有任何【训练】过程——训练集就是模型本身。**
但道理和 SVM 一样:**都是从数据里找规律,而不是人写规则。**
训练样本 32 条
测试样本 8 条
k 取 3
判对 7 条
准确率 87.5%
认错的:正确答案 3,它认成 2。
**准确率高不高,先别急着庆祝——样本才几十条,波动很大。**
这就引出了下一节要解决的事:**单次结果到底能信几分。**
实测:训练 32 条、测试 8 条、判对 7 条,准确率 87.5%。 认错的那条:正确答案 3 被认成 2——正好是上一节点名的【最容易混】的那一对。
30 秒实验
- 把 k 从 3 改成 1 再跑一次,看准确率是升还是降。
- 把训练数据减少到只有 2 条每个数字,重新感受一下分数会掉多少。
常见误区
- 以为准确率越高越好、越高越可信。样本太少时,分数本身就靠不住。
- 以为认错就是程序有 bug。3 和 2 本来就容易混,这是数据的问题。
自测三问(点开对答案)
1. 最近邻算法的流程是什么? —— 把新样本跟训练集里每个样本比距离,取最近的 k 个,看哪个标签最多就判成哪个。 2. 这次实测的准确率是多少、错在哪? —— 87.5%(8 条错 1 条);错的是把 3 认成了 2。 3. 为什么这类算法能识别手写数字? —— 同一个数字的不同写法彼此很像,同类样本聚在一起,找最近的邻居就能判断。- ☐ 下一节(12.7 尝试交叉验证)
相关笔记
- 尝试交叉验证 — 单次分数靠不住怎么办
- 查看手写数字数据的内容 — 那个被认错的数字早有预兆
- 支持向量机 — 书里用的那个算法
- ⌨️ 编程原理 · 索引 — 把编程全貌理一遍
- ⚙️ 程序是怎样跑起来的 · 索引