草稿
混淆矩阵
对二分类垃圾邮件分类结果计数,不仅看整体正确率,还要区分误报和漏报。
代入问题:评估垃圾邮件过滤器
你有一个模型,每封邮件被标记为 spam 或 not-spam。
你需要知道的不仅是“有多少封邮件是对的”,还要知道“错在哪里”。
这个固定数据集有 12 封邮件:
立即领取奖品
明显的中奖诱饵,被过滤器拦下。
项目笔记
普通工作邮件,被留在收件箱。
收据已附上
真实收据被错误标成垃圾邮件。
账户提醒
伪造提醒漏进了收件箱。
限时优惠
促销垃圾邮件被正确拦截。
团队午餐
团队日常邮件被正确保留。
密码重置
用户请求的重置邮件正常送达。
紧急转账
诈骗邮件被过滤器漏掉。
航班变更
有用的出行更新被误报。
加密币奖励
可疑奖励垃圾邮件被正确拦截。
发票已批准
业务发票被正确接收。
验证钱包
钓鱼式钱包邮件被漏判。
第一个朴素想法
最直接想到的分数是总正确率:
正确 / 总数。
对这 12 封邮件是 7 / 12 = 58.3%。
但这会把不同错误混在一起:
计数:3
e1, e5, e10
计数:4
e2, e6, e7, e11
正确 = TP + TN = 7
错误 = FP + FN = 5
错误为什么不一样痛
e3 和 e4 都是错,但语义完全不同:
e3:真实不是垃圾邮件却被拦截(误报),e4:真实是垃圾邮件却放过了(漏报)。
e3
收据已附上
真实收据被错误标成垃圾邮件。
真实: 非垃圾邮件(not-spam), 预测: 垃圾邮件(spam) (FP: 假正例(false positive))
e4
账户提醒
伪造提醒漏进了收件箱。
真实: 垃圾邮件(spam), 预测: 非垃圾邮件(not-spam) (FN: 假负例(false negative))
核心发明
把判断拆成两个是/否问题:
- 真实标签
actual是不是正类; - 预测标签
predicted是不是正类。
这就得到四个格子。
种子示例:e1→TP,e3→FP,e2→TN,e4→FN。
| 真实 | 预测=垃圾邮件 | 预测=非垃圾邮件 |
|---|---|---|
| 真实=垃圾邮件 | 1 (e1) | 1 (e4) |
| 真实=非垃圾邮件 | 1 (e3) | 1 (e2) |
两类真实标签构成行,两个预测标签构成列。
交互演示
逐步处理 12 条邮件,观察每一步只给一个格子加一。
第 1/12 步:将 e1 计入 真正例(true positive)(TP)。
真实: 垃圾邮件; 预测: 垃圾邮件.
e1: 立即领取奖品,真实为“spam”(正类),预测为“spam”(正类),因此属于 真正例(true positive)(TP)。
TP=1, FP=0, TN=0, FN=0
总计 = 1
TP 变更:0 → 1。
TP + FP + TN + FN = 12
正确 = TP + TN = 7
错误 = FP + FN = 5
| 真实 \ 预测 | 正类 1 | 负类 0 |
|---|---|---|
| 真实=正类 | TP: 1 | FN: 0 |
| 真实=负类 | FP: 0 | TN: 0 |
| 当前步 | Step | Id | 主题 | 真实 | 预测 | Cell |
|---|---|---|---|---|---|---|
| 当前 | 1 | e1 | 立即领取奖品 | 垃圾邮件 | 垃圾邮件 | TP: 真正例(true positive) |
| 2 | e2 | 项目笔记 | 非垃圾邮件 | 非垃圾邮件 | TN: 真负例(true negative) | |
| 3 | e3 | 收据已附上 | 非垃圾邮件 | 垃圾邮件 | FP: 假正例(false positive) | |
| 4 | e4 | 账户提醒 | 垃圾邮件 | 非垃圾邮件 | FN: 假负例(false negative) | |
| 5 | e5 | 限时优惠 | 垃圾邮件 | 垃圾邮件 | TP: 真正例(true positive) | |
| 6 | e6 | 团队午餐 | 非垃圾邮件 | 非垃圾邮件 | TN: 真负例(true negative) | |
| 7 | e7 | 密码重置 | 非垃圾邮件 | 非垃圾邮件 | TN: 真负例(true negative) | |
| 8 | e8 | 紧急转账 | 垃圾邮件 | 非垃圾邮件 | FN: 假负例(false negative) | |
| 9 | e9 | 航班变更 | 非垃圾邮件 | 垃圾邮件 | FP: 假正例(false positive) | |
| 10 | e10 | 加密币奖励 | 垃圾邮件 | 垃圾邮件 | TP: 真正例(true positive) | |
| 11 | e11 | 发票已批准 | 非垃圾邮件 | 非垃圾邮件 | TN: 真负例(true negative) | |
| 12 | e12 | 验证钱包 | 垃圾邮件 | 非垃圾邮件 | FN: 假负例(false negative) |
切换正类到 not-spam 时(为方便对照)当前步会改到:
TP=0, FP=0, TN=1, FN=0
这个示例是确定性的:同一封邮件始终带来同一步迁移。
正式定义
设 y 是真实标签,\hat{y} 是模型预测。
在本节点中,正类明确声明为:
1 = spam,0 = not spam。
以下用行列法定义矩阵(行是实际,列是预测):
1 表示“正类”这一约定,而不是“更正确”。
TP、FP、TN、FN 是这四个格子的计数。
| 预测为正类 | 预测为负类 | |
|---|---|---|
| 真实为正类 | TP | FN |
| 真实为负类 | FP | TN |
不变量为:
n 是已评估邮件数。
共享的数据给出:
TP = 3FP = 2TN = 4FN = 3correct = TP + TN = 7wrong = FP + FN = 5total = 12
实现草图
每条邮件只会进入一个格子并更新一个计数器:
if (actual === positiveLabel && predicted === positiveLabel) {
tp += 1;
} else if (actual !== positiveLabel && predicted === positiveLabel) {
fp += 1;
} else if (actual !== positiveLabel && predicted !== positiveLabel) {
tn += 1;
} else {
fn += 1;
}
| 若真实是正类? | 若预测是正类? | 更新 | 含义 |
|---|---|---|---|
| 真实正类 | 预测正类 | TP++ | 真正例(true positive) |
| 真实负类 | 预测正类 | FP++ | 假正例(false positive) |
| 真实负类 | 预测负类 | TN++ | 真负例(true negative) |
| 真实正类 | 预测负类 | FN++ | 假负例(false negative) |
正确性直觉
两个布尔问题是穷尽且互斥的。
对同一个正类定义,每条邮件只属于这四种组合之一:
- 实际正类且预测正类
- 实际负类且预测正类
- 实际负类且预测负类
- 实际正类且预测负类
因此每条邮件只进入一个格子。处理 9 条后是:
TP = 2,FP = 2,TN = 3,FN = 2,总计 9。
到全部 12 条结束后是:
TP = 3,FP = 2,TN = 4,FN = 3,总计 12。
TP=2, FP=2, TN=3, FN=2
total = 9
formula: TP + FP + TN + FN = 9
TP=3, FP=2, TN=4, FN=3
总数 = 12
不变量:每条样本恰好映射到一个单元。
可写成前缀不变量:
到末尾时,已处理样本数即 12。
复杂度
这类扫描是单次遍历:
每封邮件做固定次数比较和一次计数器更新。
常见误解
当前表:正类是 spam(1 表示垃圾邮件)。
| 预测=垃圾邮件 | 预测=非垃圾邮件 | |
|---|---|---|
| 真实=垃圾邮件 | 3 (TP) | 3 (FN) |
| 真实=非垃圾邮件 | 2 (FP) | 4 (TN) |
若正类改成 not-spam,不同单元格含义发生重分配。
| 预测=非垃圾邮件 | 预测=垃圾邮件 | |
|---|---|---|
| 真实=非垃圾邮件 | 4 (TP) | 2 (FN) |
| 真实=垃圾邮件 | 3 (FP) | 3 (TN) |
再看一个“换正类”练习,保持真实/预测标签不变,改 positiveLabel = "not-spam":
TP = 4FP = 3TN = 3FN = 2
这不改变预测本身,只改变了我们问“正类是什么”的问题。
图谱关联
这会是 precision、recall、f1-score 的基础节点;这些节点目前计划中尚未全部上线。
已实现
计划后续
计划后续
计划后续
静态追踪账本(无 JS 回退)
下面这张账本完全静态,JS 关闭也可查看,并且与交互轨迹一致:
| 步 | 邮件 | 主题 | 真实 | 预测 | 格子 | TP | FP | TN | FN |
|---|---|---|---|---|---|---|---|---|---|
| 1 | e1 | 立即领取奖品 | spam | spam | TP(真正例(true positive)) | 1 | 0 | 0 | 0 |
| 2 | e2 | 项目笔记 | not-spam | not-spam | TN(真负例(true negative)) | 1 | 0 | 1 | 0 |
| 3 | e3 | 收据已附上 | not-spam | spam | FP(假正例(false positive)) | 1 | 1 | 1 | 0 |
| 4 | e4 | 账户提醒 | spam | not-spam | FN(假负例(false negative)) | 1 | 1 | 1 | 1 |
| 5 | e5 | 限时优惠 | spam | spam | TP(真正例(true positive)) | 2 | 1 | 1 | 1 |
| 6 | e6 | 团队午餐 | not-spam | not-spam | TN(真负例(true negative)) | 2 | 1 | 2 | 1 |
| 7 | e7 | 密码重置 | not-spam | not-spam | TN(真负例(true negative)) | 2 | 1 | 3 | 1 |
| 8 | e8 | 紧急转账 | spam | not-spam | FN(假负例(false negative)) | 2 | 1 | 3 | 2 |
| 9 | e9 | 航班变更 | not-spam | spam | FP(假正例(false positive)) | 2 | 2 | 3 | 2 |
| 10 | e10 | 加密币奖励 | spam | spam | TP(真正例(true positive)) | 3 | 2 | 3 | 2 |
| 11 | e11 | 发票已批准 | not-spam | not-spam | TN(真负例(true negative)) | 3 | 2 | 4 | 2 |
| 12 | e12 | 验证钱包 | spam | not-spam | FN(假负例(false negative)) | 3 | 2 | 4 | 3 |
不变量校验:TP + FP + TN + FN = 12。
练习
e9的格子是什么?- 把正类改成
not-spam后,e9落入哪个格子? - 在本页定义里,如果真实为
not-spam但预测为spam,对应哪个术语? - 为什么只报错率会遗漏误报/漏报差异?
图谱连接 : 混淆矩阵