LWLeon.
LEON / ML / EN + ZH

机器学习

通过十六个循序渐进的章节学习机器学习。从行与标签开始,构建小模型、检查误差,再走向文本过滤、推荐与可靠性检查。每章都有原创故事、带答案的引导问题、两个练习案例与可运行 Python。

16 / 故事 · 概念 · 应用 · 公式

LEON / LEARNING MAP

先看学习路线

按箭头顺序学习。点击一个阶段进入第一节;完整知识框架列出了每一个主题。

  1. 01

    1 · 基础:准备可信表格

    4 个主题 ↗
  2. 02

    2 · 核心:拟合并衡量预测

    4 个主题 ↗
  3. 03

    3 · 应用:选择有用方法

    4 个主题 ↗
  4. 04

    4 · 进阶:结构、排序与可靠性

    4 个主题 ↗
LEON / FROM IDEAS TO IMPLEMENTATION

继续深入:完整案例

先读对应章节,再跟着故事预测输出、运行完整程序、核对解释。每个案例都在原有概念上增加一个实际问题。

  1. 01

    用刻意简单的记忆器暴露泄漏

    让评估边界匹配部署问题。

    故事 · 代码 · 推理
    先读对应章节: 划分数据之前,先定义问题
  2. 02

    用明确错误代价选择决策阈值

    计算错误、代价与概率决策规则。

    故事 · 代码 · 推理
    先读对应章节: 区分概率估计与决策
阅读字号
刚接触 Python 或这些数学知识?从这里开始 ↗下载全部 Python 示例 ↓
第一次运行 Python?按这四步操作
  1. 安装 Python 3.12 或更新版本。Python 官方下载页 ↗
  2. 点击示例旁的“下载 Python 代码”,得到一个 .py 文件。它是包含程序文字的文件。
  3. 打开终端,在下载目录中运行 python 文件名.py,并把“文件名”换成实际名称。在部分系统上,命令是 python3。终端是输入命令并查看文字结果的窗口。
  4. 把运行结果与页面的“预期输出”逐项比较。先改一个输入,再观察变化。

部分示例使用 NumPy,这是处理数字和数组的工具包。若代码中出现 import numpy as np,请先用同一 Python 安装一次:python -m pip install numpy。这行 import 加载 NumPy,并把它简称为 np。NumPy 官方安装说明 ↗

示例无需下载数据集或模型权重。安装 Python 和所需工具包后即可离线运行。页面上的代码文字可以复制;运行结果会显示在你的终端中。

把知识连接起来

四阶段覆盖数据准备、回归与分类、近邻与文本模型、树与提升、聚类与 PCA、推荐、验证、校准和监测。所有数据与故事均为原创合成教学示例。Python 只使用标准库或 NumPy。

开始前需要了解

  • 能阅读表格,使用加法、分数与平方。
  • 先阅读共享的 Python 与数学入门。每章都会解释新术语。
  • 无需机器学习库使用经验。

你将学到什么

  • 把预测问题转为特征、标签与合理划分。
  • 计算预测和误差,为每个主题运行小型 Python 示例。
  • 选择并解释用于文本、数值表格与推荐的简单方法。
  • 用验证、校准与新标签判断模型可靠性。

知识框架

点击一个概念,从故事走向原理与数学。

ML / 01Leon

从行、特征、标签与基线开始

入门

01 / 从一个完整故事开始

凯有一份学校社团消息记录。他想区分公告与普通聊天。首先,他规定一个样本就是一条消息,再记录两个输入:词数和链接数。标签由人提供,是模型要学习的答案,因此不能同时作为输入。

选择算法之前,凯先对每条新消息预测训练中最常见的标签。只有数据含有有用信息时,才有机会超过这条简单基线。他保存基线分数。后续模型都必须在相同留出消息上证明额外复杂度值得。

02 / 理解核心概念

数据集是一组样本。表格中每行是一个样本,每个输入列是一个特征。标签是目标答案。分类预测类别,回归预测数值。

学习就是从训练样本中选择模型参数。基线是刻意保持简单的比较对象。分类可始终预测训练中最常见的类别。使用平方损失的回归可预测训练目标均值。两者都应在独立样本上评估。

先认识这些词

特征
作出预测时已经可用的输入。
标签
用于训练或评估监督模型的目标答案。
基线
用于判断改进的简单参考预测规则。

03 / 如何运用这个概念

垃圾消息过滤先定义一条消息及一致的标签规则。打印耗时预测先定义一个任务及其秒数。在输入旁写上单位与允许取值。增加特征或模型之前先保留基线。

04 / 官方教程与研究中的应用

官方虚拟估计器文档介绍常数与类别频率基线。阅读时区分比较规则与真正利用信息特征的模型。这里的消息表格是原创教学示例。

05 / 公式与逐项解析

X ∈ ℝ^(n×d), y = [y₁, …, yₙ]
c* = argmax_c count_train(c)
accuracy = correct / n_test

X 是实数输入表,有 n 行、d 个特征列。ℝ 表示实数,× 分开两个维度。y 列出标签,yᵢ 中的 i 是行号。c 是可能的类别,count_train(c) 统计其训练样本数。argmax 选择计数最大的类别,记为 c*。n_test 是测试行数。

06 / 一步步算出结果

训练标签为 [0,0,0,1]。类别零出现三次,因此基线预测零。测试标签为 [0,1,1,0]。四次预测中两次正确,准确率为 2/4=0.50。训练中 75% 的多数比例并不保证测试准确率也为 75%。

一步步想清楚

  1. 消息类别能同时放入 X 与 y 吗?

    如果目标就是预测消息类别,就不能。这等于把答案交给模型。输入必须在答案未知时可用。

  2. 为什么测试准确率为 0.50 而不是 0.75?

    规则由训练标签选择,但准确率按测试标签计算。四个测试标签中只有两个为零。

  3. 如果两个训练类别计数相同呢?

    选择并记录确定性的平局规则。本例选择较小类别编号。不要查看测试标签来打破平局。

运行一个完整示例 · 01

构建多数标签基线

用训练标签选规则,再用独立测试标签评分。

Python 3下载源代码 ↓

长代码行可左右滚动。复制或下载会保留原始代码。

from collections import Counter
train_y = [0, 0, 0, 1]
test_y = [0, 1, 1, 0]
counts = Counter(train_y)
baseline = min(counts, key=lambda label: (-counts[label], label))
predictions = [baseline] * len(test_y)
correct = sum(pred == true for pred, true in zip(predictions, test_y))
print('baseline class:', baseline)
print('predictions:', predictions)
print(f'test accuracy: {correct / len(test_y):.2f}')
在本地运行
python features-labels-baselines-ml-majority-baseline.py

预期输出

baseline class: 0
predictions: [0, 0, 0, 0]
test accuracy: 0.50

逐步读懂代码

Counter 统计训练标签。min 按负计数排序,因此最大计数排前;类别编号用于处理平局。列表重复为每个测试行生成预测。zip 配对预测和标签,sum 把为真的比较按一计数。输出只是基线,不能证明词数或链接有用。

这些知识可以用在哪里

消息分类

写三条自拟记录,包含词数、链接数及独立的公告标签。标明消息到达时哪些输入可用。训练前先计算多数标签基线。

数值基线

训练打印耗时为 [2,4,6] 秒,预测其均值 4 秒。在 [3,5] 上测试,平方误差均为 1,因此测试 MSE 为 1。

类比的边界与注意事项. 有用类别稀少时,基线分数可能看起来很高。标签也可能不一致或错误。可信分数需要清楚任务、可靠标签与合适的留出数据。
记住这一点

先定义样本与答案,再保留一个简单比较,让复杂模型证明改进。

↑ 返回知识框架
ML / 02Leon

划分数据之前,先定义问题

入门

01 / 从一个完整故事开始

米拉想提前判断野外传感器是否会在下次记录中失效。表格包含十二台设备的多次记录,还包含故障之后才写的维护笔记。随机按行划分后,分数很高。可是模型在新设备上失败了。

米拉检查每个输入何时可用。她删除事后笔记,把完整设备分别放入训练集、验证集和测试集。分数下降了,却终于回答了真正的问题:模型能否用于陌生硬件?她在每个结果旁写下预测时点和划分规则。

02 / 理解核心概念

训练数据用于学习模型参数。验证数据帮助选择设置。测试数据检查最终选择。三者的职责要分开。

泄漏指使用真正预测时无法获得的信息,事后维护笔记就是一例。相互关联的行也可能让测试过于容易。若目标是预测新设备,就隔离完整设备。若目标是预测已知设备的明天,就保持时间先后顺序。

先认识这些词

划分
把样本分到承担不同任务的集合。
数据泄漏
学习或选择过程用到了预期预测场景中不可用的信息。
分组
相关记录的集合,例如同一设备的全部记录。

03 / 如何运用这个概念

用一句话写清预测单位、目标时间范围和可用特征。画出从原始记录到最终分数的流程。在拟合缺失值填补、缩放或特征选择之前划分数据,并在每个训练折内拟合这些步骤。检查重复记录和跨集合共享的实体。保留一个简单基线,并记录划分规则,让后续模型比较回答同一个问题。

04 / 官方教程与研究中的应用

所链接的 Google 数据集划分教程解释了训练、验证和测试数据各自的作用。所链接的 scikit-learn 常见陷阱指南演示了预处理如何泄漏信息,并使用流水线把数据转换与估计器结合起来。可以把这些已有文档中的流程用于自行构建的传感器表格:检查特征的时间戳,留出适当的单位,并确认每个需要学习的转换步骤只能看到训练行。

05 / 公式与逐项解析

D = D_train ⊔ D_val ⊔ D_test
R_test = (1 / n_test) Σ_{i=1}^{n_test} I[f(xᵢ) ≠ yᵢ]

D 表示完整数据集;D_train、D_val 和 D_test 分别表示训练、验证和测试子集。符号 ⊔ 表示记录互不重叠的并集,同时还必须满足所需的分组隔离或时间隔离。n_test 是测试样本数量。下标 i 指定一个测试样本,xᵢ 是其可用输入,yᵢ 是其真实类别。f 是已经训练并固定的分类器。I 是指示函数:预测错误时为一,否则为零。Σ 把这些错误相加,R_test 则是错误的平均值。

06 / 一步步算出结果

构造十二台设备的 120 条记录,每台设备对应十次任务。把八台完整设备用于训练,两台用于验证,两台用于测试。得到的记录数分别为 80、20 和 20,合计 120。假设固定后的分类器正确预测了二十条测试记录中的十八条,那么有两次错误,因此 R_test = 2 / 20 = 0.10,即错误率为 10%。另一个刻意采用不恰当按行划分的示例可能在二十条记录中预测正确十九条,得到 5% 的错误率。如果设备身份携带信息,这个更小的数字评估的是另一个受到污染的比较。两个结果都不能证明模型适用于所有未来硬件。此外,留出的其实是两台设备,而不是二十台独立设备,这也限制了这个微型演示值得给予的信心。

一步步想清楚

  1. 为什么删除预测力很强的维护笔记?

    笔记在故障后才出现。任务开始前的预警无法使用它。预测力强不代表当时可用。

  2. 八台训练设备为什么对应 80 行?

    每台提供十次记录,所以计算 8×10。同一设备的十行始终放在一起。

  3. 看完测试分数后能否调整模型?

    可以开始新的开发周期,但这个集合不再是未使用的最终测试集。修改后的模型需要新的独立测试证据。

  4. 重复测量数据上的高分一定是假的吗?

    不一定。它可能回答了合理的熟悉组任务。问题是把未留出完整组的评估,解释成对新组的泛化能力。

运行一个完整示例 · 01

把完整设备放在一起

不用随机库调用,构建这个 120 行的划分。

Python 3下载源代码 ↓

长代码行可左右滚动。复制或下载会保留原始代码。

groups = [device for device in range(12) for _ in range(10)]
train = [i for i, device in enumerate(groups) if device < 8]
valid = [i for i, device in enumerate(groups) if 8 <= device < 10]
test = [i for i, device in enumerate(groups) if device >= 10]
train_devices = {groups[i] for i in train}
test_devices = {groups[i] for i in test}
print('rows:', len(train), len(valid), len(test))
print('shared devices:', sorted(train_devices & test_devices))
print('test error:', 2 / len(test))
在本地运行
python framing-splits-leakage-ml-group-split.py

预期输出

rows: 80 20 20
shared devices: []
test error: 0.1

逐步读懂代码

第一行把每个设备编号重复十次。enumerate 提供行号。三个条件分配完整分组。集合去掉重复编号,& 查找共有编号。空列表表示训练和测试没有共享设备。最后一行假定出现两次错误;它没有训练或评估真实传感器模型。

运行一个完整示例 · 02

用刻意简单的记忆器暴露泄漏

米娜为四台设备各记录两次读数。她每台拿一条训练、另一条测试,只记住设备编号的模型就得到满分。改用完全没见过的设备测试,分数降到一半。模型并不是突然变差了:第一次划分检验的是熟悉设备,第二次问的才是新设备。

Python 3下载源代码 ↓

长代码行可左右滚动。复制或下载会保留原始代码。

rows = [(device, label) for device, label in
        [("A", 0), ("B", 1), ("C", 0), ("D", 1)] for _ in range(2)]

def evaluate(train, test):
    memory = {device: label for device, label in train}
    fallback = int(sum(label for _, label in train) > len(train) / 2)
    predictions = [memory.get(device, fallback) for device, _ in test]
    accuracy = sum(p == label for p, (_, label) in zip(predictions, test)) / len(test)
    overlap = {d for d, _ in train} & {d for d, _ in test}
    return accuracy, sorted(overlap)

row_train, row_test = rows[::2], rows[1::2]
group_train = [row for row in rows if row[0] in {"A", "B"}]
group_test = [row for row in rows if row[0] in {"C", "D"}]
row_score, overlap = evaluate(row_train, row_test)
group_score, clean_overlap = evaluate(group_train, group_test)
assert row_score == 1.0 and group_score == 0.5 and not clean_overlap
print(f"row split: accuracy={row_score:.2f}; shared devices={overlap}")
print(f"group split: accuracy={group_score:.2f}; shared devices={clean_overlap}")
在本地运行
python framing-splits-leakage-memorizer-leakage-audit.py

预期输出

row split: accuracy=1.00; shared devices=['A', 'B', 'C', 'D']
group split: accuracy=0.50; shared devices=[]

按执行顺序理解

  1. 构造同设备标签固定的重复测量,用人工数据把捷径暴露出来。

  2. 只在训练行拟合记忆表和多数类后备规则;平票时选零。

  3. 比较按行划分与设备互斥划分,同时报告共享编号和准确率。

逐步读懂代码

准确率=测试预测正确数/测试行数。按行测试时四条全对;按设备测试时四条中两条正确,因为未见设备都得到只由训练集确定的后备标签零,而只有 C 的标签是零。

分离单位取决于部署目标。要面对新患者、新设备或新视频,就应把整个组留出;若预测已知设备的未来事件,则应采用合适的时间划分。只做组间隔离还不能阻止预处理中的未来信息泄漏。每一折的缺失值填补、缩放和特征选择都只能用训练部分拟合。

这只是诊断玩具,不是某个真实模型准确率为 50% 的证据。刻意简单的基线有时比复杂模型更容易暴露捷径。scikit-learn 官方交叉验证指南提供按组划分工具,但分组变量要从真正想回答的问题出发选择。

这些知识可以用在哪里

新录音设备

把六条自拟录音按设备 A、B、C 分组。用 A 和 B 训练,保留 C。在查看标签前,先确认两个设备集合没有重叠。

明天的传感器读数

列出第 1 到 10 天的测量值。第 1–6 天训练,第 7–8 天验证,第 9–10 天测试。不要用未来读数填补较早的缺口。

类比的边界与注意事项. 记录互不重叠并不保证证据独立。相关的人、地点、录音或设备可能跨越不同集合,即使记录并非字面上的重复。干净的测试集仍可能与未来数据不同。分组数量很少时,不确定性尤其大;反复依据测试结果选择模型,会把测试集变成另一个验证集。
记住这一点

只有明确预测什么、信息何时可用,以及划分代表哪一种未见案例之后,分数才有意义。

↑ 返回知识框架
ML / 03Leon

填补缺口前先检查表格

入门

01 / 从一个完整故事开始

梅记录教室传感器温度。有些单元格空白,还有一个值为 999,实际上是设备错误码。最初计算平均时,她把 999 当作真实温度,结果异常偏高。她查看记录说明,把错误码标为缺失。

填补之前,梅先统计缺口。她用已观测训练读数的中位数作简单替代,并保留缺失标记。表格现在可用于计算,但替代值不是新的测量。以后比较读数时,她仍能区分原始观测与填充值。

02 / 理解核心概念

探索性数据分析 EDA 指建模前检查数据内容。检查单位、范围、重复与缺失数量。缺失代表未知,并不自动等于零。

填补按明确规则替代缺失项。中位数是观测值排序后的中间值。只用训练数据学习替代值,再重复使用。单独的缺失指示保留“没有测量”这一事实。

先认识这些词

探索性数据分析
建模前检查数据结构、范围与质量。
缺失值填补
用明确的估计值或占位值替代未知项。
中位数
排序后的中间值;个数为偶数时取中间两值的平均。

03 / 如何运用这个概念

传感器表格和文档元数据常有缺口。选择替代方式前先调查原因。比较原本完整与原本缺失行的错误率。将拟合的替代规则与模型一起保存。

04 / 官方教程与研究中的应用

scikit-learn 填补指南介绍均值、中位数、常数替代及缺失指示。这里的小型 NumPy 示例借鉴这些机制,并不假设所有模型都需要填补。

05 / 公式与逐项解析

m = median(observed training values)
x̃ᵢ = m if xᵢ is missing, otherwise xᵢ
r = missing_count / n

m 是替代用的中位数。xᵢ 是第 i 行原值,x̃ᵢ 是填补后的值。n 为行数,r 为该列缺失比例。波浪号表示变换后的值。

06 / 一步步算出结果

训练值 [2,缺失,6,4] 四项中缺一项,r=0.25。观测值排序为 [2,4,6],中位数为 4。训练填补结果为 [2,4,6,4]。测试值 [缺失,10] 仍用 4,得到 [4,10],不要重新计算测试中位数 10。

一步步想清楚

  1. 为什么不把所有缺失都替换为零?

    零可能是真实测量值,含义完全不同。缺口意味着不知道。应按特征含义选择规则。

  2. 缺失标记 [0,1,0,0] 保留了什么?

    它记录第二个值填补前是未知的。单看填补值 4 无法判断这一点。

  3. 能用测试行学习中位数吗?

    不能。这会让留出数据影响准备过程。应在训练行拟合,再将保存的规则用于其他数据。

运行一个完整示例 · 01

拟合中位数,再重复使用

本例用 NaN 表示未知数值。

Python 3下载源代码 ↓

长代码行可左右滚动。复制或下载会保留原始代码。

import numpy as np
train = np.array([2., np.nan, 6., 4.])
test = np.array([np.nan, 10.])
missing = np.isnan(train)
median = np.median(train[~missing])
filled_train = np.where(missing, median, train)
filled_test = np.where(np.isnan(test), median, test)
print(f'training missing fraction: {missing.mean():.2f}')
print('training flag:', missing.astype(int).tolist())
print('filled training:', filled_train.tolist())
print('filled test:', filled_test.tolist())
在本地运行
python missing-data-eda-ml-median-imputation.py

预期输出

training missing fraction: 0.25
training flag: [0, 1, 0, 0]
filled training: [2.0, 4.0, 6.0, 4.0]
filled test: [4.0, 10.0]

逐步读懂代码

isnan 标出缺失项。~ 反转布尔掩码,使索引选出已观测训练值。median 取中位数。where 只在缺失位置选择替代值。astype(int) 把标记显示成零和一。测试使用相同的中位数。代码准备数值数据,并不能说明填补改善了预测。

这些知识可以用在哪里

传感器错误码

对 [18,19,999,20],先确认 999 是错误码,再标为缺失。观测中位数为 19。保留填充值与缺失标记。

全空列

尝试没有任何观测值的训练列。在中位数规则产生 NaN 前停止。决定删除该列,或使用有记录的常数与缺失标记。

类比的边界与注意事项. 填补会掩盖不确定性。缺失原因可能与未观测值本身有关。整列训练数据全缺失时没有观测中位数,需要明确处理。
记住这一点

先弄清空白的含义,再用训练信息填补,并保留缺失记录。

↑ 返回知识框架
ML / 04Leon

把类别变成明确的列

入门

01 / 从一个完整故事开始

美术社按纸、木、金属记录物品材料。琳想预测每件物品适合的收纳盒。她把材料编码为 1、2、3,距离模型就把金属到纸的距离视为木到纸的两倍。材料名称并没有这样的间距。

琳改为每种材料建立一列,用一标记所属类别,其他列为零。她还提前规定如何处理未见材料。新表格只表达已知信息,不编造顺序。她把列名与模型一起保存。

02 / 理解核心概念

类别特征表示组别。有些类别确有顺序,例如小、中、大;材料名称则没有。独热编码为每个已知类别建立独立指示列。

词表应由训练数据确定,以后使用相同列顺序。未见类别需要明确规则,例如未知列或拒绝处理。本例加入未知列,能保持固定形状,却不能让模型理解新类别的含义。

先认识这些词

类别特征
用取值标识组别或名称的输入。
独热编码
用多列零或一来标识所属类别。
词表
保存的已知类别列表及其列顺序。

03 / 如何运用这个概念

可用类别编码表示表格中的设备型号、文件格式或材料种类。数值缩放与类别编码作为不同准备步骤,并在每个训练折内验证整个流程。

04 / 官方教程与研究中的应用

官方预处理指南解释独热编码与顺序编码,也记录未知类别的处理选项。这里的编码器很小,可以逐行检查。

05 / 公式与逐项解析

hⱼ(c) = I[c = vocabularyⱼ]
h_unknown(c) = I[c is absent from the training vocabulary]

c 是观测类别,vocabularyⱼ 是第 j 列对应的已知类别。I 在条件成立时为一,否则为零。hⱼ 是该列的指示值,h_unknown 是额外的未知类别指示。

06 / 一步步算出结果

列为 [纸,木,未知] 时,木变为 [0,1,0],纸变为 [1,0,0],未见金属变为 [0,0,1]。每行之和为一。两种已知类别在此表示中的距离为 √2,而不是人为编造的数值顺序。

一步步想清楚

  1. 为什么 1、2、3 会误导距离模型?

    这些数值暗含顺序与间距,无序名称并不支持这种几何关系。独热列消除这一特定假设。

  2. 为什么保存列顺序?

    学习到的权重对应某一列。以后交换纸与木的列,即使形状不变,也改变了权重含义。

  3. 未知列是否是适用于所有新类别的已学解法?

    不是。它只是标明情况。有效应对仍需证据、后备规则或后续带标签样本。

运行一个完整示例 · 01

编码已知与未见类别

明确使用最后一列记录未知类别。

Python 3下载源代码 ↓

长代码行可左右滚动。复制或下载会保留原始代码。

vocabulary = ['paper', 'wood']
def encode(value):
    return [int(value == name) for name in vocabulary] + [int(value not in vocabulary)]
print('columns:', vocabulary + ['unknown'])
for value in ['wood', 'paper', 'metal']:
    row = encode(value)
    print(value, row, 'sum:', sum(row))
在本地运行
python categorical-preprocessing-ml-category-columns.py

预期输出

columns: ['paper', 'wood', 'unknown']
wood [0, 1, 0] sum: 1
paper [1, 0, 0] sum: 1
metal [0, 0, 1] sum: 1

逐步读懂代码

列表推导把取值与每个已保存名称比较。int 将比较结果转为零或一。最后一项检查它是否不在整个词表中。打印行和可确认恰有一个指示被激活。编码器只准备特征,既未训练分类器,也未学会怎样正确处理金属。

这些知识可以用在哪里

文件格式

用 png 和 txt 建立训练词表。用明确的未知列编码新 wav 文件。检查每个输出长度相同,且列名保持固定。

有序尺寸

比较材料名称与小、中、大。尺寸等级有顺序,但间距未必相同。决定顺序编码还是实测尺寸更适合任务。

类比的边界与注意事项. 类别很多时会产生很多列。稀有类别的训练证据很少。如果训练行未使用未知列,它可能没有学到有效权重。应选择后备方案并评估其行为。
记住这一点

编码要表达类别含义,固定列顺序,并明确处理未见值。

↑ 返回知识框架
ML / 05Leon

线性回归:用直线预测数值

核心

01 / 从一个完整故事开始

俊记录窗边幼苗的高度。他想用简单规则把高度与生长时间联系起来。纸上的两条直线似乎都合理。一条偏离高幼苗,另一条靠近高幼苗,却偏离几株小苗。俊需要公平的比较方法。

他把预测与实测高度并排写下,逐项相减、平方,再取平均。他再开平方,把单位恢复为厘米。直线有了明确分数,失败的位置也清楚了。他把直线作为基线,并计划下一步测量光照。

02 / 理解核心概念

直线先把输入乘以斜率,再加上截距,得到数值预测。输入增加一个单位时,预测改变多少由斜率决定。输入为零时的预测就是截距。

本章把预测残差定义为预测减实测。平方让正负误差都作正贡献。均方误差 MSE 是这些平方的平均值,大误差影响更大。均方根误差 RMSE 再开平方,使单位与目标一致。拟合直线描述关联,本身不能证明因果。

先认识这些词

斜率
输入增加一个单位时,预测值的变化。
截距
输入为零时直线的预测。
残差
按本章约定,为预测减去实测目标。

03 / 如何运用这个概念

先使用常数预测,例如训练目标的均值,再在相同的留出样本上比较线性模型。在每个特征和系数旁标注测量单位。检查残差相对于预测值及重要输入的图形。如果大误差主导结果,先检查其来源,再考虑更换损失函数。传达误差大小时,报告均方根误差这类保留原始单位的指标。

04 / 官方教程与研究中的应用

所链接的 Google 损失教程比较了回归中的绝对误差与平方误差,并解释这种选择如何改变大残差的影响。这里引用它是为了支持损失的定义,幼苗故事和数值并非来自该教程。可以把教程中的比较作为阅读提示,然后针对自己的测量值计算两种指标,并说明每种指标更强调哪些错误。

05 / 公式与逐项解析

ŷᵢ = b + w xᵢ
MSE = (1 / n) Σᵢ₌₁ⁿ (ŷᵢ − yᵢ)²
RMSE = √MSE

下标 i 表示观测,n 是参与评估的观测数量。xᵢ 是输入,yᵢ 是实测目标,ŷᵢ 是模型预测;帽子符号用于区分估计值与测量值。b 是截距,单位与目标相同。w 是斜率,单位为每单位输入对应的目标单位。差值 ŷᵢ − yᵢ 是有正负号的残差。Σ 表示对所有观测求和,上标 2 表示对每个残差平方,√ 表示开平方。MSE 使用目标单位的平方,RMSE 则恢复为目标单位。

06 / 一步步算出结果

使用三个合成观测:输入天数为 1、2、3,对应高度为 3、5、8 厘米。候选直线取 b = 1、w = 2,预测为 3、5、7。其残差为 0、0、−1 厘米;平方误差之和为 1,因此 MSE = 1 / 3 ≈ 0.333,RMSE ≈ 0.577 厘米。若常数预测等于目标均值 16 / 3,则平方误差为 49 / 9、1 / 9 和 64 / 9,其 MSE 为 38 / 9 ≈ 4.222。候选直线在这次比较中更好,但这里并未声称它是最小二乘最优解。这三个训练点也不能独立估计未来的预测质量。

一步步想清楚

  1. 为什么先平方再相加?

    直接求和时,+1 与 −1 会抵消。平方和为 2。误差为 4 的平方是 16,其影响是误差为 1 的十六倍。

  2. 0.577 厘米如何得到?

    平方误差为 0、0、1,平均为 1/3 平方厘米。开平方约为 0.577 厘米。

  3. 训练 MSE 更低是否证明未来预测更好?

    不证明。训练行参与了规则选择。要在相同留出行上比较,才能检查改进能否迁移。

运行一个完整示例 · 01

比较直线与常数

用相同三行演示计算。

Python 3下载源代码 ↓

长代码行可左右滚动。复制或下载会保留原始代码。

import numpy as np
x = np.array([1., 2., 3.])
y = np.array([3., 5., 8.])
prediction = 1 + 2 * x
residual = prediction - y
mse = np.mean(residual ** 2)
baseline_mse = np.mean((y.mean() - y) ** 2)
print('prediction:', prediction.tolist())
print(f'MSE: {mse:.6f}; RMSE: {np.sqrt(mse):.6f}')
print(f'constant MSE: {baseline_mse:.6f}')
在本地运行
python linear-regression-mse-ml-line-loss.py

预期输出

prediction: [3.0, 5.0, 7.0]
MSE: 0.333333; RMSE: 0.577350
constant MSE: 4.222222

逐步读懂代码

np.array 保存有序数值。乘法和减法按对应位置计算。**2 平方每个残差,mean 求和再除以三,sqrt 恢复原单位。y.mean 给出常数基线。这些行仅用于比较计算。所选直线既不是声称求出的最小二乘最优解,也不是独立测试结果。

这些知识可以用在哪里

幼苗基线

运行示例,再把最后的高度由 8 改为 10。直线仍预测 7。MSE 变为 9/3=3,显示大误差如何改变平方损失。

打印耗时

在自拟打印任务中,用页数作输入、秒数作目标。斜率单位为秒/页。用截距表示准备耗时,再检查不同长度的留出任务。

类比的边界与注意事项. 直线可能无法表达非线性关系、交互作用和突变。平方损失对极端残差敏感,无论这些残差反映的是有意义的事件还是测量错误。超出已观测特征范围的外推可能不可靠。相关输入可能使单个系数不稳定,即使预测仍然有用。
记住这一点

线性回归是透明的基线;损失函数决定哪些错误更重要,残差则揭示直线尚未解释的部分。

↑ 返回知识框架
ML / 06Leon

用可控步长和可比尺度学习

核心

01 / 从一个完整故事开始

阿莎测量机器人小车的行驶距离。一个输入是以秒计的脉冲时间,另一个是数千量级的编码器计数。训练中损失突然上升。她起初想增加网络层数,后来发现一个权重比其他权重变化快得多。

阿莎只用训练记录计算缩放参数,并调低学习率。更新变得容易追踪。她再用两个自拟点手算一步,理解原因。现在她能区分更新规则不合适与输入信息不足。训练损失下降很有用,但仍需验证。

02 / 理解核心概念

损失是衡量预测误差的数值。梯度描述当前权重附近损失怎样变化。一维时,可以把它想成山坡的斜率。梯度下降沿斜率的反方向移动。学习率乘以梯度,决定本次更新。

标准化先减去训练均值,再除以训练标准差。均值表示中心,标准差表示分散程度。这样更容易处理不同输入尺度。常数列的离散程度为零,需要单独处理,不能除以零。

先认识这些词

梯度
损失相对于模型参数的局部斜率。
学习率
控制梯度对参数改变幅度的乘数。
标准差
衡量离散程度;这里是与均值之差的平方均值再开平方。

03 / 如何运用这个概念

在训练数据上拟合缩放统计量,并原样用于验证和测试。绘制损失随更新次数变化的曲线,检查数值是否爆炸或出现非有限值;若更新不稳定,可尝试更小的学习率。在决定延长训练之前,先比较训练曲线与验证曲线。对于标准差为零的常数特征,应明确处理。把拟合好的转换与最终模型一起保存。

04 / 官方教程与研究中的应用

所链接的 Google 梯度下降教程追踪连续的参数更新,并展示训练损失。它的另一篇归一化教程讨论了特征尺度及其对优化的影响。两篇资料共同提供了一条有文档支持的路径,从损失定义走向实际训练诊断。可以用下面的小车示例重建其中的逻辑,再解释为何输入单位改变后,相同数值的学习率未必具有相同的表现。

05 / 公式与逐项解析

zᵢ = (xᵢ − μ_train) / s_train
L(w,b) = (1 / n) Σᵢ₌₁ⁿ (w zᵢ + b − yᵢ)²
g_w = (2 / n) Σᵢ₌₁ⁿ (w zᵢ + b − yᵢ) zᵢ
g_b = (2 / n) Σᵢ₌₁ⁿ (w zᵢ + b − yᵢ)
w_new = w − η g_w; b_new = b − η g_b

xᵢ 是第 i 个原始输入;μ_train 和 s_train 是训练均值与总体标准差,其中 s_train 为正。zᵢ 是标准化输入,yᵢ 是目标。n 是训练样本数量,Σ 表示对它们求和。w 和 b 是当前斜率与截距。L 是均方损失。g_w 和 g_b 分别是它关于这两个参数的偏导数。η 是正的学习率。后缀 new 表示更新后的值。在应用任一更新之前,两个梯度都必须在同一组旧参数上计算。

06 / 一步步算出结果

取原始输入 2、6,对应目标为 1、3。训练均值为 4,总体标准差为 2,因此标准化输入为 −1、1。令初始参数 w = 0、b = 0。两个预测值都是零,得到 L = (1 + 9) / 2 = 5。权重梯度为 (2 / 2)[(−1)(−1) + (−3)(1)] = −2,截距梯度为 −4。学习率取 0.1,同时更新得到 w_new = 0.2、b_new = 0.4。新预测值为 0.2、0.6,因此新损失为 (0.64 + 5.76) / 2 = 3.2。这一步使损失下降了 1.8,但既没有完成优化,也不能证明泛化能力。

一步步想清楚

  1. 为什么 2 和 6 变成 −1 和 1?

    均值为 4,与 4 的差为 −2 和 2。总体标准差为 2,因此将两者除以 2。

  2. 负梯度为什么让权重增加?

    更新要减去学习率乘梯度。减去负数等于增加正数,从局部方向看是朝较低损失移动。

  3. 学习率很大时会怎样?

    大步可能越过低点,使损失上升。要比较更新前后的损失,不能假定每一步都成功。

运行一个完整示例 · 01

标准化后更新一步

下面所有梯度都使用更新前的参数。

Python 3下载源代码 ↓

长代码行可左右滚动。复制或下载会保留原始代码。

import numpy as np
x = np.array([2., 6.]); y = np.array([1., 3.])
z = (x - x.mean()) / x.std()
w, b, rate = 0., 0., 0.1
error = w * z + b - y
gw = 2 * np.mean(error * z)
gb = 2 * np.mean(error)
w, b = w - rate * gw, b - rate * gb
print('scaled inputs:', z.tolist())
print(f'gradients: {gw:.1f}, {gb:.1f}')
print(f'weights: {w:.1f}, {b:.1f}')
print(f'loss: {np.mean((w * z + b - y) ** 2):.3f}')
在本地运行
python gradient-descent-scaling-ml-gradient-step.py

预期输出

scaled inputs: [-1.0, 1.0]
gradients: -2.0, -4.0
weights: 0.2, 0.4
loss: 3.200

逐步读懂代码

mean 和 std 计算训练统计量;这里 NumPy 的 std 使用总体约定。error 保存旧残差。乘以 z 得到权重导数中的输入贡献。均值乘二来自平方的求导。元组赋值让两个参数都使用旧梯度更新。最后重新计算损失。这只是微型线性模型的一次精确更新,并非完整训练流程。

这些知识可以用在哪里

小车模型的一步更新

把学习率从 0.1 改为 1.1,重新计算损失。旧梯度不变,但步长越过低点,得到损失 7.2。

恒定传感器列

尝试输入 [4,4],标准差为零。除法前先识别这种情况。可删除无信息的列,或用明确保存的规则把它映射为零。

类比的边界与注意事项. 缩放无法修复错误标签,也无法补上缺失的解释变量。标准差可能受到离群值的强烈影响。即使目标函数是凸函数,梯度下降仍需要合适的步长;凸性本身不能让每次更新都稳定。更复杂的模型可能具有非凸损失,收敛行为也会不同。
记住这一点

把更新规则、输入单位和损失曲线放在一起检查;稳定优化是学习的必要条件,却不是泛化能力的证据。

↑ 返回知识框架
ML / 07Leon

区分概率估计与决策

核心

01 / 从一个完整故事开始

里奥想把夜空图像分为晴朗和多云两类。模型给下一张图像的晴朗分数为 0.60,他立即准备望远镜。朋友问为什么以 0.50 为界,里奥暂时答不上来。

他把两件事分开:模型估计概率,阈值把估计变成行动。提高阈值会减少准备提醒。他固定模型,用验证图像比较不同阈值。现在这些数字描述的是可解释的选择,而不是藏在模型里的规则。

02 / 理解核心概念

二分类任务有两个标签,这里是晴朗和多云。逻辑回归先生成线性分数,再用 sigmoid 函数将它映射到零与一之间,作为正类概率的估计。

阈值是另一个决策规则:估计达到界限就预测正类。移动阈值不会改变权重或概率估计。单独使用 sigmoid 并不能保证概率准确。后面学习的校准会检查预测概率与观测频率是否相符。

先认识这些词

正类
选择用一表示的标签,不一定意味着好事。
Sigmoid 函数
把任意有限实数分数平滑映射到零与一之间的函数。
阈值
把概率估计转为类别选择的界限。

03 / 如何运用这个概念

在训练之前明确正类,并在各项指标中保持定义一致。根据预期行动的代价或人工审核能力,使用验证数据比较阈值。在选定的工作点同时检查假阳性与假阴性。如果概率本身会影响决策,除分类准确率外还应检查校准情况。在最终测试之前固定阈值,并记录概率恰好等于阈值时如何处理。

04 / 官方教程与研究中的应用

所链接的 Google sigmoid 教程解释了线性分数如何变成概率估计。其阈值教程随后把概率分界点与混淆矩阵中的结果联系起来。可以把它们理解为分类器的两个不同阶段。在这个原创天文示例中,模型可以保持不变,而准备器材的规则可以改变;官方资料支持这种区分,并不意味着存在相应的天文部署,也没有验证这个虚构数据集。

05 / 公式与逐项解析

z = b + w x
p = 1 / (1 + exp(−z))
ŷ = I[p ≥ τ]

x 是一个测量特征,w 是学到的对应系数,b 是截距。z 是得到的线性分数,可解释为模型对正类的对数优势。exp 是以 e 为底的指数函数,e 约等于 2.71828。p 是正类的估计概率。τ 是选定的决策阈值,位于零与一之间。ŷ 是预测的二元标签。括号内条件成立时,I 等于一,否则等于零;这一约定把恰好等于阈值的情况归入正类。

06 / 一步步算出结果

假设一个示范模型使用 b = −1.2、w = 0.8,特征值 x = 2。线性分数为 z = −1.2 + 0.8 × 2 = 0.4。由于 exp(−0.4) ≈ 0.6703,估计的正类概率为 1 / 1.6703 ≈ 0.5987。阈值取 0.50 时,模型预测正类;阈值取 0.65 时,模型预测负类。概率和系数都没有改变。作为对照,x = 0 时,z = −1.2,p ≈ 0.2315。这些都是模型输出,并非已观测频率,也不是对某一个夜晚的保证。若要证明任一阈值合理,利奥都需要带标签的验证案例,并明确如何权衡不必要的准备与错失合适观测机会。

一步步想清楚

  1. 0.5987 已经是类别标签吗?

    不是,它是概率估计。与 0.50 比较得到类别一,与 0.65 比较得到类别零。

  2. 为什么 sigmoid(0) 等于 0.5?

    exp(0) 等于 1。分母为 1+1=2,所以结果为 1/2。

  3. 提高阈值会改善所有指标吗?

    不会。正类预测会减少,部分误报可能消失,也可能漏掉部分真正例。要在验证数据上同时统计两种变化。

  4. 为什么不在最终测试集上选阈值?

    这样报告的测试分数也包含了针对同一批结果优化的选择。应先固定阈值,再做未参与选择的最终评估。

运行一个完整示例 · 01

同一个概率,两种决策

使用示意性的固定权重,不是真实训练的夜空分类器。

Python 3下载源代码 ↓

长代码行可左右滚动。复制或下载会保留原始代码。

from math import exp
x, w, b = 2., 0.8, -1.2
score = b + w * x
probability = 1 / (1 + exp(-score))
print(f'score: {score:.4f}; probability: {probability:.4f}')
for threshold in [0.50, 0.65]:
    label = int(probability >= threshold)
    print(f'threshold {threshold:.2f}: class {label}')
在本地运行
python logistic-thresholds-ml-sigmoid-cutoffs.py

预期输出

score: 0.4000; probability: 0.5987
threshold 0.50: class 1
threshold 0.65: class 0

逐步读懂代码

第一项计算生成线性分数。exp 实现 sigmoid 中的指数。循环重复使用同一个概率。>= 与各阈值比较,int 将真假变成一或零。输出类别改变,但分数不变。没有使用数据拟合这些权重,也没有数据证明哪种阈值更合适。

运行一个完整示例 · 02

用明确错误代价选择决策阈值

学校机器把损坏的练习零件送去检查。漏掉坏件的代价记为四个工作单位,误查好件记为一个。米娜先用常见的 0.5 阈值,再在小验证集上列出两种错误。降低阈值能补获一个坏件,只多一次误查。她在查看最终测试集之前固定阈值。

Python 3下载源代码 ↓

长代码行可左右滚动。复制或下载会保留原始代码。

scores = [0.10, 0.35, 0.45, 0.60, 0.80]
truth = [0, 1, 0, 1, 1]

def cost_at(threshold):
    predicted = [int(score >= threshold) for score in scores]
    fp = sum(p == 1 and y == 0 for p, y in zip(predicted, truth))
    fn = sum(p == 0 and y == 1 for p, y in zip(predicted, truth))
    return fp + 4 * fn, fp, fn

candidates = [0.30, 0.50, 0.70, 1.10]
for threshold in candidates:
    cost, fp, fn = cost_at(threshold)
    print(f"threshold={threshold:.2f} FP={fp} FN={fn} cost={cost}")
best = min(candidates, key=lambda t: (cost_at(t)[0], t))
assert best == 0.30 and cost_at(best) == (1, 1, 0)
print(f"selected on validation: {best:.2f}")
在本地运行
python logistic-thresholds-validation-cost-threshold.py

预期输出

threshold=0.30 FP=1 FN=0 cost=1
threshold=0.50 FP=0 FN=1 cost=4
threshold=0.70 FP=0 FN=2 cost=8
threshold=1.10 FP=0 FN=3 cost=12
selected on validation: 0.30

按执行顺序理解

  1. 比较阈值前先规定代价;本例优化 FP+4×FN,而不是准确率。

  2. 每个候选阈值都用 score>=threshold 产生预测,再统计两种错误。

  3. 最小化验证代价并说明平局规则;最终测试集不参与选择。

逐步读懂代码

阈值 0.30 时预测为 [0,1,1,1,1],一个假正例、零个假负例,总代价为 1。阈值 0.50 时没有假正例,却有一个假负例,代价为 4。全预测负类的候选 1.10 代价为 12。若比较不同规模验证集,可除以五,报告每个样本的平均代价。

若 p 是校准后的概率,假正例代价为 C_FP、假负例代价为 C_FN,正确决策代价为零,则 C_FP×(1-p)≤C_FN×p 时预测正类。整理得 p≥C_FP/(C_FP+C_FN),本例为 0.2。这是总体期望代价规则,与用五个已观测标签从有限网格选阈值不是同一件事。模型分数也未必是校准概率。

部署后,代价、类别比例和校准情况都可能变化。调阈值不能修复很差的排序或无效的数据划分。这只是小例子,实际结论需要足够有代表性的验证数据及不确定性检查。

这些知识可以用在哪里

夜空图像复核

对概率 [0.3,0.6,0.9] 比较阈值 0.5 与 0.8,正类预测数分别为二和一。决定哪个阈值更好之前,需要加入真实标签。

垃圾消息复核队列

用阈值选择需要人工复核的消息。降低阈值会增加队列长度。同时衡量有效检出与不必要复核;队列长度本身不代表质量。

类比的边界与注意事项. sigmoid 输出不会自动成为校准良好的概率。如果特征没有表达重要交互作用,线性决策边界就可能遗漏它们。类别比例、行动代价或可用能力改变时,合适的阈值也可能改变。在测试集上选择阈值,与在那里选择模型参数一样,都会污染最终评估。
记住这一点

把训练概率模型与选择决策阈值视为相关但独立的步骤,再用未参与选择的数据评估完整规则。

↑ 返回知识框架
ML / 08Leon

统计任务真正关心的错误

核心

01 / 从一个完整故事开始

诺拉在一组音频片段里寻找鸟叫。每一百段中只有十段包含鸟叫。模型每次都说“没有鸟叫”,准确率也能达到 90%。分数听起来不错,却没有找出任何想听的内容。

诺拉检查第二个检测器。它找出八段鸟叫,也把十二段背景声送去复核。准确率较低,结果却可能更有用。她用小表格记录四种结果。现在她能同时讨论找回多少鸟叫,以及还要听多少片段。单一分数不再掩盖取舍。

02 / 理解核心概念

混淆矩阵统计每类的正确与错误决策。真正例表示找到了真实鸟叫,假正例表示把背景标为鸟叫,假反例表示漏掉鸟叫,真反例表示正确排除背景。

准确率用所有正确决策除以总片段数。精确率用找到的鸟叫除以全部标记片段。召回率用找到的鸟叫除以全部真实鸟叫。F1 用调和平均结合精确率与召回率。它们回答不同问题,因此既报告比例,也报告计数。分母为零时需要明确约定。

先认识这些词

精确率
预测为正的样本中,真实为正的比例。
召回率
真实为正的样本中,被找到的比例。
类别不平衡
一个类别的样本数量远多于另一个类别。

03 / 如何运用这个概念

报告指标时,同时写明正类定义、正类比例、阈值和混淆矩阵的四项计数。与简单基线比较,适当时也包括始终预测负类的分类器。使用验证数据检查不同阈值下的精确率和召回率。分别检查重要录音条件,例如安静与多风的夜晚。如果分母为零,应说明该量未定义,或明确采用的报告约定,而不是默默把它当成出色表现。

04 / 官方教程与研究中的应用

所链接的 Google 分类指标教程定义了准确率、精确率、召回率和 F1,并讨论不同错误的重要性如何影响指标选择。可以用这个官方框架审查原创的鸟鸣检测器示例。在作出结论之前,根据四项计数重新构建各个指标,并解释为什么高准确率可能与一次成功检测都没有同时出现。教程是事实依据;录音情境和计数均为原创。

05 / 公式与逐项解析

N = TP + TN + FP + FN
Accuracy = (TP + TN) / N
Precision = TP / (TP + FP)
Recall = TP / (TP + FN)
F1 = 2 TP / (2 TP + FP + FN)

TP 统计真阳性,即实际鸟鸣被标为鸟鸣。TN 统计真阴性,即背景片段被标为背景。FP 统计假阳性,即背景片段被错误地标为鸟鸣。FN 统计假阴性,即检测器漏掉的实际鸟鸣。N 是被评估片段的总数。Accuracy、Precision、Recall 和 F1 分别表示公式中的四个无量纲指标。2 TP 表示二乘以 TP。除法之前必须检查每个分母;例如,TP + FP 为零时,精确率未定义,因为没有任何片段被预测为正类。

06 / 一步步算出结果

构造一百个带标签的片段:十个含有鸟鸣,九十个是背景。假设检测器找到八个鸟鸣、漏掉两个,并把十二个背景片段标为鸟鸣。那么 TP = 8、FN = 2、FP = 12、TN = 78。准确率为 (8 + 78) / 100 = 86%。精确率为 8 / 20 = 40%,召回率为 8 / 10 = 80%,F1 为 16 / 30 ≈ 53.3%。始终预测负类的基线可以达到 90% 的准确率,因为它排除了全部九十个背景片段,但其召回率为零。由于它没有预测任何正类,其精确率未定义。因此,检测器虽然准确率较低,却找回了基线漏掉的八个鸟鸣。十二次误报是否可以接受,取决于预期的听音流程。

一步步想清楚

  1. 为什么 90% 准确率可能无法帮助寻找鸟叫?

    九十段是背景。拒绝全部片段能判对这九十段,却漏掉所有鸟叫,召回率为零。

  2. 为什么精确率和召回率的分母不同?

    精确率检查二十段标记片段,即 8/20。召回率检查十段真实鸟叫,即 8/10。两者都使用八次正确检出。

  3. 如果没有片段被预测为正类呢?

    精确率变成 0/0,数学上未定义。某些软件按约定报告零。要说明约定,并保留原始计数。

运行一个完整示例 · 01

从计数计算四个指标

这些计数描述自拟评估集上一个阈值的结果。

Python 3下载源代码 ↓

长代码行可左右滚动。复制或下载会保留原始代码。

tp, tn, fp, fn = 8, 78, 12, 2
total = tp + tn + fp + fn
accuracy = (tp + tn) / total
precision = tp / (tp + fp)
recall = tp / (tp + fn)
f1 = 2 * tp / (2 * tp + fp + fn)
for name, value in [('accuracy', accuracy), ('precision', precision),
                    ('recall', recall), ('F1', f1)]:
    print(f'{name}: {value:.3f}')
print('clips to review:', tp + fp)
在本地运行
python metrics-imbalance-ml-confusion-counts.py

预期输出

accuracy: 0.860
precision: 0.400
recall: 0.800
F1: 0.533
clips to review: 20

逐步读懂代码

四个计数恰好覆盖一百段片段。每个除法对应不同问题。F1 直接用计数计算;在精确率和召回率有定义时,它等于两者的调和平均。循环把比例保留三位小数。本例假设分母非零。代码只计算指标,并未训练音频检测器。

这些知识可以用在哪里

听音工作量

使用 TP=8、FP=12。需要复核二十段,其中八段有用。若一次只能复核十段,就评估更严格的阈值,不要用准确率掩盖工作量。

稀有划痕检查

构造五个有划痕、九十五个无划痕的合成样本。始终预测无划痕的规则有 95% 准确率,但召回率为零。先建立这条基线,再评估检测器。

类比的边界与注意事项. 混淆矩阵描述的是某个评估集上某一阈值的表现。即使其他行为看起来相似,正类比例改变时,精确率也可能改变。F1 无法表达所有代价偏好。正类数量很少会使召回率估计不稳定,而汇总分数可能掩盖特定条件下的糟糕表现。
记住这一点

让混淆矩阵始终伴随主要分数;合适的指标必须揭示预期用途真正关心的错误。

↑ 返回知识框架
ML / 09Leon

近邻法:向附近样本借用证据

应用

01 / 从一个完整故事开始

绫把短音频分为两种乐器组。每段有两个测量特征。她先不拟合直线,而是画出带标签训练片段,找出离新片段最近的点,让最近三个标签投票。

最初结果很奇怪:一个特征是数千量级,另一个在零到一之间。绫用训练统计量缩放,再在验证片段上比较不同邻居数。她发现“近”本身就是建模选择。方法容易解释,但答案依赖用哪些特征定义这张地图。

02 / 理解核心概念

k 近邻分类器保存带标签训练样本。对新输入计算距离,选择最小的 k 个,再统计标签。本例是二分类,超过半数投一则预测类别一。

欧氏距离就是普通直线距离:坐标差平方、求和,再开平方。给邻居排序时,平方距离顺序相同,可以省去开方。缩放必须在训练数据上拟合。通过验证选择 k,而不是使用最终测试。

先认识这些词

邻居
在所选距离下,靠近查询的训练样本。
欧氏距离
由坐标差的平方计算的直线距离。
超参数
在参数拟合之外选择的设置,例如邻居数 k。

03 / 如何运用这个概念

近邻法可为小型特征表或相似搜索提供直观基线。展示取回样本以检查错误。相信局部投票前,先检查查询是否离所有训练点都很远。

04 / 官方教程与研究中的应用

scikit-learn 近邻指南解释监督近邻预测与距离选择。以下示例仅对微型数值表实现等权投票。

05 / 公式与逐项解析

d²(x,q) = Σⱼ₌₁ᵈ (xⱼ−qⱼ)²
ŷ = I[(1/k) Σᵢ∈Nₖ(q) yᵢ > 1/2]

q 是查询向量,x 是训练向量。j 遍历 d 个特征坐标。d² 表示平方距离,其中上标 2 表示平方,而求和上界 d 是特征数。Nₖ(q) 是最近 k 行的索引集合,yᵢ 是零或一标签,ŷ 是预测标签,I 是指示函数。

06 / 一步步算出结果

点为 [0,0]、[1,0]、[2,0]、[4,0],标签为 [0,0,1,1]。查询 [1.6,0] 得到平方距离 [2.56,0.36,0.16,5.76]。最近三个索引为 [2,1,0],标签为 [1,0,0]。均值为 1/3,因此预测零。若 k=1,则预测一。

一步步想清楚

  1. 为什么可以不开平方就排序?

    对非负值,平方根保持顺序。平方最小,平方根也最小。

  2. 为什么 k=3 预测零?

    三个标签为 [1,0,0],两票为零、一票为一,类别一比例为 1/3。

  3. k=1 最近,是否总更准确?

    不是。一个错误标签或噪声点就能控制答案。较大的 k 平滑决策,却可能混合不同区域。应比较验证误差。

运行一个完整示例 · 01

检查每一张近邻投票

表格为合成数据,两个特征已经采用可比尺度。

Python 3下载源代码 ↓

长代码行可左右滚动。复制或下载会保留原始代码。

import numpy as np
x = np.array([[0.,0.], [1.,0.], [2.,0.], [4.,0.]])
y = np.array([0,0,1,1])
query = np.array([1.6,0.])
squared = np.sum((x - query) ** 2, axis=1)
order = np.argsort(squared, kind='stable')
print('squared distances:', np.round(squared, 2).tolist())
for k in [1,3]:
    labels = y[order[:k]]
    print('k:', k, 'labels:', labels.tolist(), 'prediction:', int(labels.mean() > 0.5))
在本地运行
python nearest-neighbours-ml-knn-vote.py

预期输出

squared distances: [2.56, 0.36, 0.16, 5.76]
k: 1 labels: [1] prediction: 1
k: 3 labels: [1, 0, 0] prediction: 0

逐步读懂代码

减去 query 时,每行都使用同一查询。sum(axis=1) 在每行内加总特征贡献。argsort 按距离从小到大返回行索引,稳定排序让等距离按原行顺序处理。[:k] 选取邻居,标签均值就是一的比例。这是实际可运行的微型近邻分类器,但没有真实音频准确率的证据。

这些知识可以用在哪里

改变邻居数

对同一查询运行 k=1 与 k=3。列出所选标签,解释答案为何改变。不要同时修改训练表。

片段相似搜索

用两个标明单位的自拟音频特征,取回三个邻近片段检查。改变一个特征的尺度,检查取回集合是否改变。

类比的边界与注意事项. 无关特征占主导时,距离失去意义。大型训练集使直接搜索昂贵。平局需要规则,投票比例也不自动是校准概率。
记住这一点

近邻预测是否合理,取决于距离、特征尺度与保存的样本。

↑ 返回知识框架
ML / 10Leon

朴素贝叶斯:用词频分类短文本

应用

01 / 从一个完整故事开始

博收到许多社团短消息,想先做一个可检查的垃圾消息过滤器。他先统计词频。在训练例子中,“offer” 常出现在垃圾消息里,“meeting” 常出现在普通消息里。

一条新消息含有“offer”。博把类别频率与该词在各类中的频率结合起来。除法前加上小计数,避免未见词让分数直接归零。这个微型例子给出清楚答案,但博仍测试正常使用“offer”的消息,因为一个词只是证据,不是证明。

02 / 理解核心概念

分词把文本拆成词等单位。计数向量记录词表中每个词出现多少次。多项式朴素贝叶斯用各类别的词频为向量评分。“朴素”指条件独立假设:类别固定后,模型忽略词之间的许多依赖。

加一平滑使词表中每个词都有正计数。把词概率与类别先验相乘,也可等价地把对数相加。对数防止长乘积变得过小。对最终两类分数归一化,可得到模型概率估计。

先认识这些词

词元
按选定切分规则产生的一个文本单位。
先验
使用查询词语之前的类别概率。
平滑
增加小计数,避免词表内的词得到零概率。

03 / 如何运用这个概念

朴素贝叶斯可作为垃圾消息过滤与文档分类的基线。词表和词频只在训练文本上拟合。相信总体准确率之前,检查误报、罕见词与标点处理。

04 / 官方教程与研究中的应用

官方朴素贝叶斯指南介绍多项式文本分类与平滑。这里的文本计数是原创微型示例,不能据此建立可部署的垃圾消息过滤器。

05 / 公式与逐项解析

θ_cj = (N_cj + 1)/(N_c + V)
s_c = log π_c + Σⱼ xⱼ log θ_cj
P(c|x) = exp(s_c) / Σ_a exp(s_a)

c、a 表示类别,j 表示词。N_cj 统计类别 c 中词 j 的次数,N_c 是该类总词数,V 是词表大小。θ_cj 是平滑后的词概率,π_c 是类别先验,xⱼ 是查询中词 j 的次数,s_c 是对数分数。log 是自然对数,exp 是其逆运算。

06 / 一步步算出结果

词表为 [offer,meeting]。垃圾类计数 [3,1],普通类 [1,3],各有四次词出现。加一平滑后,垃圾类词概率为 [4/6,2/6],普通类为 [2/6,4/6]。类别先验相等、查询为 [1,0] 时,未归一化分数为 1/3 与 1/6,总和为 1/2,因此垃圾类概率为 2/3。

一步步想清楚

  1. 为什么分母加 V?

    V 个词的计数各加一,总计数也增加 V,这样概率之和仍为一。

  2. 为什么查询是 [1,0]?

    它含 offer 一次、meeting 零次,向量遵循已保存词表顺序。

  3. 如果两个词各出现一次呢?

    在此对称计数与相等先验下,两类分数相同。真实语境可能不同,但这个计数模型看不到。

运行一个完整示例 · 01

计算双词分类器

用预设训练词频总计展示各个概率。

Python 3下载源代码 ↓

长代码行可左右滚动。复制或下载会保留原始代码。

import numpy as np
counts = np.array([[3.,1.], [1.,3.]])
word_prob = (counts + 1) / (counts.sum(axis=1, keepdims=True) + 2)
query = np.array([1.,0.])
log_scores = np.log([0.5,0.5]) + np.log(word_prob) @ query
weights = np.exp(log_scores - log_scores.max())
probabilities = weights / weights.sum()
print('word probabilities:', np.round(word_prob, 3).tolist())
print('class probabilities:', np.round(probabilities, 3).tolist())
print('class:', ['spam','ordinary'][int(np.argmax(probabilities))])
在本地运行
python text-naive-bayes-ml-naive-bayes-counts.py

预期输出

word probabilities: [[0.667, 0.333], [0.333, 0.667]]
class probabilities: [0.667, 0.333]
class: spam

逐步读懂代码

行代表类别、列代表词。sum 配合 keepdims 为每行保留列形状的分母。加一实现平滑。@ 把按查询计数加权的对数概率相加。减去最大对数分数能保持指数计算稳定,而不改变归一化结果。argmax 选择较高分数。分类器只依赖这些计数,不是训练好的语言模型。

这些知识可以用在哪里

检查误报

写一条含 offer 的普通消息,例如邀请提供帮助。玩具分类器仍偏向垃圾类。解释计数表示遗漏了哪些语境。

未见词

加入一个不在保存词表中的查询词。评估前选定未知词元或忽略规则。不要利用测试标签扩充词表。

类比的边界与注意事项. 词计数丢失顺序和大量语境。独立性假设常不成立。即使分类有用,概率也可能校准很差。未见词需要固定的词表处理规则。
记住这一点

统计词频、平滑计数、结合证据,再检查语境在哪些地方打破简单假设。

↑ 返回知识框架
ML / 11Leon

建立规则,再理解组合规则会改变什么

应用

01 / 从一个完整故事开始

陶记录小风筝能否持续飞行,输入包括风速和拉线角度。一条直线规则忽略了交互:合适的角度取决于风速。他画出决策树,每个分叉问一个问题。

这棵树能处理小表格,但更深的版本开始记住异常试验。陶比较浅树与由不同树组成的森林,并检查森林如何合并概率。组合结果不再过度依赖某一棵树,但仍需留出试验验证。清楚的分支是有用的建模选择,不是因果证明。

02 / 理解核心概念

决策树连续提问,例如“风速是否小于 3?”每个回答决定走哪条分支。叶子是最终分组,并给出预测。选择分裂时,比较分裂前后标签混杂程度。纯分组的基尼不纯度为零,混杂分组更高。

森林组合多棵带随机性的树。这里平均它们的正类概率。不同的树可以降低对单个噪声样本的敏感度。后面学习的提升法依次学习修正,与森林的训练逻辑不同。

先认识这些词

叶子
决策树中提供预测的最终区域。
基尼不纯度
衡量标签混杂的分数;二分类时为 2p(1−p)。
集成
组合多个模型得到预测。

03 / 如何运用这个概念

先拟合浅树,并对照数据检查其条件。在验证过程中调整深度与最小叶节点样本数,然后使用相同划分和指标比较森林或提升树。检查在合理的训练变动下结果是否稳定。进行解释时,应区分有用的预测划分与因果解释。记录集成模型组合的是类别投票还是概率,因为两者可能产生不同决策。

04 / 官方教程与研究中的应用

所链接的 scikit-learn 决策树指南演示了分类树与回归树,并解释与深度相关的过拟合。其集成指南区分了随机化森林与提升法,并说明其森林分类器对类别概率取平均。这些细节支持了风筝模型比较以及下面的平均计算,也解释了为什么把所有森林实现都描述为多数投票,可能掩盖实际使用的预测规则。

05 / 公式与逐项解析

G(p) = 2 p (1 − p)
ΔG = G(p) − (n_L / n) G(p_L) − (n_R / n) G(p_R)
p_forest = (1 / T) Σₜ₌₁ᵀ qₜ

p 是父节点中正类所占的比例,G 是其二元基尼不纯度。n 是父节点样本数;n_L 和 n_R 分别是送往左、右子节点的样本数,因此 n = n_L + n_R。p_L 和 p_R 是两个子节点的正类比例。ΔG 是该划分带来的不纯度下降。T 是树的数量,t 指定一棵树,qₜ 是这棵树对同一输入预测的正类概率。Σ 把这些概率相加;p_forest 是它们的未加权平均值。

06 / 一步步算出结果

父节点包含六次成功飞行与四次失败,因此 p = 0.6,G = 2 × 0.6 × 0.4 = 0.48。一个候选划分把四次成功和一次失败送到左侧,把两次成功和三次失败送到右侧。两个子节点的不纯度分别为 0.32 和 0.48。每个子节点都有五个样本,因此加权不纯度为 0.5 × 0.32 + 0.5 × 0.48 = 0.40。不纯度下降了 0.08,这是局部改进,并不能证明测试性能更好。另一个独立示例中,假设三棵树对一次新飞行预测的正类概率分别为 0.2、0.7、0.9。其平均值为 1.8 / 3 = 0.6,在阈值为 0.5 时作出正类决策。这里明确使用的集成规则就是概率平均。

一步步想清楚

  1. 为什么按子节点大小加权不纯度?

    含九个样本的子节点应比只含一个样本的子节点贡献更大。加权后,分数对应从父节点随机选一个样本的情况。

  2. 分裂增益如何得到 0.08?

    父节点不纯度为 0.48。子节点加权值为 0.5×0.32+0.5×0.48=0.40。相减得到 0.08。

  3. 三棵相似的树会犯同样的错误吗?

    会。平均无法消除所有树都学到的误导模式。数据质量与独立验证仍然重要。

运行一个完整示例 · 01

计算分裂分数与树的平均

计算局部分裂准则,不构建整棵树。

Python 3下载源代码 ↓

长代码行可左右滚动。复制或下载会保留原始代码。

def gini(positive, total):
    p = positive / total
    return 2 * p * (1 - p)
parent = gini(6, 10)
children = 0.5 * gini(4, 5) + 0.5 * gini(2, 5)
probabilities = [0.2, 0.7, 0.9]
print(f'parent: {parent:.2f}; children: {children:.2f}')
print(f'gain: {parent - children:.2f}')
print(f'forest probability: {sum(probabilities) / len(probabilities):.2f}')
在本地运行
python trees-ensembles-ml-gini-forest.py

预期输出

parent: 0.48; children: 0.40
gain: 0.08
forest probability: 0.60

逐步读懂代码

函数先把计数转成正类比例,再计算二分类基尼不纯度。子节点一样大,因此权重均为 0.5。相减衡量局部增益。sum 除以 len 得到树概率的平均。数值只展示一个候选分裂与一种集成规则,没有进行树搜索或森林训练。

这些知识可以用在哪里

纯叶子

把左子节点改为五次成功、零次失败。正类比例为一,基尼值为零。还要检查分裂后是否有足够样本可靠评估。

森林的合并规则

比较概率 [0.49,0.49,0.99],平均约为 0.657,高于 0.5。但先阈值化再多数投票会得到负类。要说明使用哪种合并规则。

类比的边界与注意事项. 贪心划分优化的是局部选择,不一定得到全局最优的树。如果许多树共享同一种误导性信号,森林仍可能失败。大型集成模型需要更多资源,也更难检查。特征重要性描述的是特定假设下的模型行为,并不能证明改变某个特征就会改变结果。
记住这一点

树模型把交互作用转化为明确区域;集成方法可以稳定预测,但每增加一层复杂度,都仍需针对预期任务进行验证。

↑ 返回知识框架
ML / 12Leon

提升法:学习下一次修正

应用

01 / 从一个完整故事开始

云预测简单机器人任务的时长。常数估计对短任务和长任务产生相反方向的误差。她把误差写在预测下方,再用小树拟合剩余误差。这棵树负责预测修正量,而不是完整时长。

云只加入一部分修正,训练损失下降了。她谨慎重复这个过程,同时观察验证误差。增加树可以继续改善训练拟合,却未必改善新任务。她理解了提升法:一连串有针对性的修正,其数量与幅度都要由证据决定。

02 / 理解核心概念

集成组合多个模型。平方误差梯度提升先建立简单预测,再用小模型拟合残差。这里残差定义为目标减当前预测。它是前面章节预测误差的相反数,因此加上它会朝所需修正方向移动。将小模型输出乘学习率后加入预测。

下一模型处理前次更新后剩余的误差。这不同于森林对随机化树取平均。其他损失使用负梯度,而不一定是这里的残差。树深度、步长与轮数共同控制灵活性。

先认识这些词

残差学习器
学习预测剩余修正量的小模型。
树桩
只有一个分裂、两个叶子的决策树。
提升轮次
增加一个新修正模型的一次操作。

03 / 如何运用这个概念

经过合适预处理后,提升树是数值与类别表格的常见选择。应与线性基线比较。通过验证数据选择停止轮数,并沿用其他模型相同的划分规则。

04 / 官方教程与研究中的应用

官方集成指南解释梯度提升及其与损失梯度的联系。代码把一个固定分裂树桩拟合到残差,展示一次实际修正,并不是完整提升库。

05 / 公式与逐项解析

rᵢ = yᵢ − F₀(xᵢ)
F₁(xᵢ) = F₀(xᵢ) + η h(xᵢ)

i 表示训练行,yᵢ 是目标。F₀ 是当前预测器,F₁ 是更新后预测器。rᵢ 是需要的修正量,h 是拟合这些修正的小模型,η 是学习率,这里取零到一之间。

06 / 一步步算出结果

目标 [1,1,3,3] 的均值为 2。从 2 开始,残差为 [−1,−1,1,1],MSE 为 1。树桩把前两行与后两行分开,恰好预测这些残差。η=0.5 时,预测变为 [1.5,1.5,2.5,2.5]。所有平方误差为 0.25,因此新 MSE 为 0.25。

一步步想清楚

  1. 为什么修正使用目标减预测?

    预测过高时,该差为负。加上负修正会降低预测,符号与所需修复一致。

  2. MSE 0.25 如何得到?

    每项预测距目标仍差 0.5。平方均为 0.25,因此平均也是 0.25。

  3. 为什么不总取 η=1?

    这个简单例子会一步拟合好。但噪声数据中,完整修正可能过快拟合偶然模式。应通过验证选择学习率和轮数。

运行一个完整示例 · 01

拟合一个残差树桩

固定以 x<2 分裂,便于检查两个叶子均值。

Python 3下载源代码 ↓

长代码行可左右滚动。复制或下载会保留原始代码。

import numpy as np
x = np.array([0.,1.,2.,3.]); y = np.array([1.,1.,3.,3.])
prediction = np.full_like(y, y.mean())
residual = y - prediction
left = x < 2
correction = np.where(left, residual[left].mean(), residual[~left].mean())
updated = prediction + 0.5 * correction
print('residual:', residual.tolist())
print('updated:', updated.tolist())
print(f'MSE before: {np.mean((prediction-y)**2):.2f}')
print(f'MSE after: {np.mean((updated-y)**2):.2f}')
在本地运行
python boosting-residuals-ml-residual-stump.py

预期输出

residual: [-1.0, -1.0, 1.0, 1.0]
updated: [1.5, 1.5, 2.5, 2.5]
MSE before: 1.00
MSE after: 0.25

逐步读懂代码

full_like 为每个目标生成常数初始预测。布尔 left 掩码定义两个叶子。各叶子取残差均值,它能最小化叶内残差平方误差。where 为每行分配对应修正。加上一半修正即完成一次提升。代码不含分裂搜索、多轮训练或独立验证。

这些知识可以用在哪里

更小的一步

取 η=0.25,预测变为 [1.75,1.75,2.25,2.25],MSE 为 0.5625。修正方向不变,但每轮前进更少。

有噪声的时长

把最后目标从 3 改为 8,重新计算均值与叶子修正。先解释为何一个异常测量会强烈影响平方误差模型,再判断它是否有效数据。

类比的边界与注意事项. 重复修正可能追逐噪声。训练损失小不能替你选择轮数。这里的固定分裂特别简单,不能衡量真实任务时长的性能。
记住这一点

提升法让下一个模型修复当前预测仍遗漏的部分。

↑ 返回知识框架
ML / 13Leon

寻找结构,不凭空赋予标签

进阶

01 / 从一个完整故事开始

伊玛尼测量河边捡到的石头,记录长、宽、质量和纹理,却没有岩石类型标签。聚类程序返回三组,她差点把它们命名为三种岩石。随后,她发现把克改为千克就改变了分组。

伊玛尼意识到距离依赖单位。她明确选择缩放规则,并检查各组代表石头。她还用 PCA 观察变化最大的方向。紧凑的图帮助她提出更好问题,却不会补出缺失的标签。她使用中性组名,在提出地质结论前寻找外部证据。

02 / 理解核心概念

聚类不使用目标标签,提出候选分组。K-means 把点分给较近中心,再把中心移到组内均值。目标函数加总点到中心的平方距离。增加组数可以降低该数值,却不一定改善解释。

PCA 回答另一个问题:减去均值后,哪个方向的离散程度最大?它将点投影到这些方向。协方差矩阵概括坐标如何共同变化。特征向量是矩阵作用后保持方向的向量,特征值表示该方向上的方差。这些是几何描述,不是发现的类别标签。

先认识这些词

质心
一个分组内各坐标分别求平均得到的中心。
投影
沿选定方向测量一个点得到的坐标。
解释方差
选定 PCA 方向保留的总离散程度比例。

03 / 如何运用这个概念

根据希望进行的比较选择特征与缩放方式,并保留该转换以处理后续观测。检查各簇的代表性成员,比较几个合理的簇数或初始化方式。对于 PCA,应报告保留的方差,并检查主成分载荷。如果这种表示将输入监督模型,应在训练折内部拟合它。解释分组时使用领域证据,不要把簇编号当成已经发现的真相。

04 / 官方教程与研究中的应用

所链接的 scikit-learn 聚类指南描述了 K 均值目标,以及它不擅长处理的形状。所链接的分解指南用鸢尾花测量值的低维表示说明 PCA,并指出 PCA 会中心化输入,但不会自动逐特征缩放。这些官方示例澄清了分组与投影承担的不同任务。河边石头故事和下面的矩形数据集,都是说明这些区别的原创示例。

05 / 公式与逐项解析

J = Σᵢ₌₁ⁿ ‖xᵢ − μ_cᵢ‖²
S = (1 / n) Σᵢ₌₁ⁿ (xᵢ − x̄)(xᵢ − x̄)ᵀ
S vⱼ = λⱼ vⱼ
r₁ = λ₁ / Σⱼ₌₁ᵈ λⱼ

n 是观测数量,i 指定其中一个观测。xᵢ 是含 d 个特征的向量;cᵢ 表示其所属簇,μ_cᵢ 是该簇的质心。平方范数 ‖·‖² 对各坐标差的平方求和,J 是簇内平方距离总和。x̄ 是均值向量。S 是此处以 n 为分母的协方差矩阵。上标 T 表示向量转置,使该乘积成为外积。vⱼ 是第 j 个主方向,λⱼ 是对应方差,按从大到小排列。Σ 表示求和。在总方差为正的前提下,r₁ 是第一主成分保留的方差比例。

06 / 一步步算出结果

考虑四个二维点:(0,0)、(0,2)、(4,0)、(4,2),两个坐标轴已经使用可比单位。按第一个坐标分为两个簇后,质心为 (0,1) 和 (4,1)。每个点到所属质心的平方距离都是 1,因此 J = 4。如果只使用位于 (2,1) 的单个质心,则每个点的平方距离都是 5,J = 20。进行 PCA 时,先减去均值 (2,1)。总体协方差矩阵为对角矩阵,对角元素为 4 和 1,因此第一主方向为 (1,0),方向符号可以任意反转。它保留了 4 / (4 + 1) = 80% 的总方差。只保留一个主成分会舍弃竖直方向上的区别。更低的聚类损失与更高的保留方差描述的是几何性质,而不是想象出来的类别标签是否正确。

一步步想清楚

  1. 为什么两组的损失为 4?

    每个点与中心的竖直距离为一,平方距离为 1。四个点合计为 4。

  2. 保留 80% 方差意味着什么?

    水平方差为 4,竖直方差为 1。保留水平方向得到 4/(4+1)。这不代表分类准确率为 80%。

  3. 方差小的方向为什么仍可能重要?

    少见但有用的差别,整体离散程度可能很小。PCA 没有标签来告诉它这个差别对后续任务重要。

运行一个完整示例 · 01

计算分组与 PCA 方差

矩形数据让两种计算都容易看清。

Python 3下载源代码 ↓

长代码行可左右滚动。复制或下载会保留原始代码。

import numpy as np
x = np.array([[0., 0.], [0., 2.], [4., 0.], [4., 2.]])
centers = np.array([[0., 1.], [4., 1.]])
assignment = np.array([0, 0, 1, 1])
loss = np.sum((x - centers[assignment]) ** 2)
centered = x - x.mean(axis=0)
covariance = centered.T @ centered / len(x)
values = np.linalg.eigvalsh(covariance)
print(f'cluster loss: {loss:.1f}')
print('covariance:', covariance.tolist())
print(f'first variance share: {values[-1] / values.sum():.2f}')
在本地运行
python clustering-pca-ml-cluster-pca-geometry.py

预期输出

cluster loss: 4.0
covariance: [[4.0, 0.0], [0.0, 1.0]]
first variance share: 0.80

逐步读懂代码

centers[assignment] 为每行选一个中心。平方再求和得到分组损失。mean(axis=0) 计算每列均值。.T 交换轴,@ 矩阵乘法构建总体协方差。eigvalsh 按升序返回特征值,因此最后一个最大。代码衡量已选分组和方差,没有发现岩石类别,也没有运行迭代 K-means 拟合。

这些知识可以用在哪里

石头的几何结构

把示例中的竖直坐标乘三。竖直方差变为 9,水平方差仍为 4。第一主成分方向改变,说明单位的重要性。

音频片段浏览

对时长、能量等自拟片段特征聚类。命名之前听取代表片段。一组大音量片段可能只是录音增益相近,不一定属于同一声音类型。

类比的边界与注意事项. K 均值可能难以处理不规则形状、不同密度和离群值;允许更多簇时,其目标值也会下降。PCA 捕捉线性方差,而方差可能来自干扰变化,并非有用信号。缩放选择会改变两种分析,投影图中看起来分离也不能证明存在有意义的科学区别。
记住这一点

用聚类提出分组,用 PCA 压缩变化,再用这两种方法本身没有提供的证据检验解释。

↑ 返回知识框架
ML / 14Leon

推荐:按明确的相似度排序

进阶

01 / 从一个完整故事开始

芮想为科学社推荐图书馆视频。每个视频有两个手工特征:天文与机器人。她用相同两维表示观众主动表达的兴趣,再用点积给视频评分。

芮发现标签很多的视频可能只因向量更长而得高分,于是比较去除长度影响的余弦相似度。排序现在体现特征空间的方向。她展示推荐原因并收集反馈。这是透明的内容推荐,但无法发现这两个特征从未描述的兴趣。

02 / 理解核心概念

基于内容的推荐器在相同空间中比较物品特征与用户画像。点积把对应坐标相乘后相加。余弦相似度再除以两个向量长度,衡量方向一致程度,而非大小。

排序按分数排列候选物品。候选集是排序前考虑的列表。任务需要时,移除不可用或已看过内容。相似分数不是喜欢的概率。应按明确反馈流程评估相关性与多样性。

先认识这些词

用户画像
描述主动表达或推断兴趣的特征向量。
余弦相似度
点积除以双方长度,用于衡量方向一致程度。
候选集
排序决定顺序之前考虑的物品集合。

03 / 如何运用这个概念

可用于相关阅读、视频建议或相似文档搜索。先用容易理解的标签,再比较更丰富的学习特征。为无画像的新观众与无标签物品准备后备规则。

04 / 官方教程与研究中的应用

Google 推荐课程介绍基于内容的过滤与点积相似度。这里的标签视频示例加入明确的余弦计算,便于检查,不声称具有生产推荐质量。

05 / 公式与逐项解析

score(u,v) = (u·v)/(‖u‖‖v‖)
u·v = Σⱼ uⱼvⱼ; ‖u‖ = √Σⱼ uⱼ²

u 是兴趣向量,v 是物品向量,j 表示对应特征位置。· 是点积,‖u‖ 是欧氏向量长度。√ 表示平方根,Σ 表示求和。计算余弦相似度时,两个长度都必须非零。

06 / 一步步算出结果

画像为 [1,1]。天文 [1,0] 与机器人 [0,1] 均得 1/√2≈0.707。混合视频 [1,1] 得 2/(√2×√2)=1。将其缩放为 [2,2],余弦仍为 1;点积则从 2 升到 4。

一步步想清楚

  1. 为什么两个单主题视频都得 0.707?

    画像对两个主题权重相同。点积都是一,长度分别为 √2 与一。

  2. 分数为 1 是否表示肯定喜欢?

    不是。它只表示当前表示的方向相同。特征可能遗漏节奏、难度或观众当前需求。

  3. 画像为 [0,0] 时怎么办?

    除以零长度未定义。可以收集兴趣,或使用明确标注的通用后备规则,不要编造相似度。

运行一个完整示例 · 01

给三个标签视频排序

标签为手工设定;示例不学习嵌入。

Python 3下载源代码 ↓

长代码行可左右滚动。复制或下载会保留原始代码。

import numpy as np
names = ['astronomy', 'robotics', 'mixed']
items = np.array([[1.,0.], [0.,1.], [1.,1.]])
profile = np.array([1.,1.])
scores = (items @ profile) / (np.linalg.norm(items, axis=1) * np.linalg.norm(profile))
order = np.argsort(-scores, kind='stable')
for index in order:
    print(f'{names[index]}: {scores[index]:.3f}')
在本地运行
python content-recommendations-ml-cosine-recommendations.py

预期输出

mixed: 1.000
astronomy: 0.707
robotics: 0.707

逐步读懂代码

@ 为每个物品计算点积。norm 计算向量长度,axis=1 分别保留物品长度。除法去掉长度影响。对负分排序使高分在前,平局按输入顺序处理。在这些标签下混合视频排第一。这是可检查的评分规则,不是经验证的观众偏好模型。

这些知识可以用在哪里

相关阅读

给三篇自拟文章标两个主题特征。对画像 [1,0] 排序,再与 [0,1] 比较,用坐标解释顺序变化。

更长的标签向量

把一个物品所有坐标乘二。点积加倍,但余弦不变。判断在你的推荐任务中,向量大小是否包含有用证据。

类比的边界与注意事项. 零向量没有可定义的余弦方向。狭窄画像可能重复推荐相似物品。手工标签可能不完整。相似度本身不能衡量质量、新颖性或观众是否真的喜欢。
记住这一点

选择符合推荐问题的表示与分数,再用反馈检验排序。

↑ 返回知识框架
ML / 15Leon

验证模型选择,为场景变化做好准备

进阶

01 / 从一个完整故事开始

任用多个传感器读数预测温室湿度。灵活模型几乎完全拟合第一个月,却在后来的日期表现很差。他尝试惩罚大权重,并按时间顺序的验证时段比较不同强度。训练拟合最好的模型不再胜出。

他在未使用的最终时段上测试所选模型。后来通风设备更换,温室情况发生变化。任没有把旧分数当作永久承诺,而是收集新标签再次检查性能。验证帮助他为某种环境选模型,监测还要确认现实是否仍符合这种环境。

02 / 理解核心概念

过拟合是学到只帮助训练数据、却无法迁移的细节。L2 正则化把权重平方和乘以指定强度,再加到训练目标中,从而抑制过大系数。它可能改善泛化,但需要验证来判断。

交叉验证在称为“折”的多个划分上重复拟合与评估。所有需要学习的预处理都应在每个折内部完成。时间与分组依赖仍要考虑。分布变化指未来数据或输入与目标的关系改变。正则化无法防御所有这类变化。

先认识这些词

过拟合
学到只适用于训练数据、不能帮助新样本的细节。
正则化
通过约束或惩罚改变对拟合解的偏好。
折
交叉验证中一次训练与评估的划分。

03 / 如何运用这个概念

定义数量适中的候选设置,并在相同验证折上比较。把所有需要学习的预处理步骤放到每个折内部。任务需要时,使用分组或按时间划分,并保留最终评估时间段,不让它参与选择。记录输入范围、适用时的类别比例,以及误差模式。部署条件改变后,应获取新的带标签证据,不要把只监测输入当成性能测量。

04 / 官方教程与研究中的应用

所链接的 scikit-learn 交叉验证指南说明了考虑分组和时间顺序的划分器。其线性模型指南介绍岭回归,以及正则化强度的验证。所链接的 Google 数据集指南也讨论了评估数据与后续使用场景不匹配的问题。这些资料支持了所述流程,温室叙事则为原创。请注意,下方平均损失目标的惩罚归一化方式,与 scikit-learn Ridge 所采用的求和损失约定不同。 在其他约定一致的情况下,Ridge 的惩罚参数等于训练样本数量乘以 λ。

05 / 公式与逐项解析

L_λ(w,b) = (1 / n) Σᵢ₌₁ⁿ (b + wᵀxᵢ − yᵢ)² + λ Σⱼ₌₁ᵈ wⱼ²
CV(λ) = (1 / K) Σₖ₌₁ᴷ MSEₖ(λ)

n 是训练样本数;i 是观测下标,j 是 d 个特征中的特征下标。xᵢ 是输入向量,yᵢ 是目标,w 是系数向量,b 是不受惩罚的截距。上标 T 表示转置,因此 wᵀxᵢ 是点积。λ 是非负惩罚强度,L_λ 是带惩罚的训练目标。Σ 表示求和。K 是验证折的数量,k 指定其中一折。MSEₖ 是使用该折对应的训练部分拟合后,在该验证折上得到的不含惩罚项的误差。CV 是这些误差的未加权平均值;折大小不同时,若要得到按样本汇总的均值,需要加权。

06 / 一步步算出结果

在这里明确采用的平均损失约定下,比较两个合成系数向量。候选 A 的训练 MSE 为 1.0,系数平方和为 9;候选 B 的训练 MSE 为 1.4,系数平方和为 1。当 λ = 0.1 时,带惩罚的目标分别为 1.9 和 1.5,因此 B 虽然原始训练拟合较差,目标值却更小。这个比较只是说明惩罚项的作用,并未声称任一向量是优化后的解。另一个独立验证示例中,假设 λ = 0 在三个等大折上的误差为 1.0、1.4、1.2,而 λ = 0.1 时为 0.9、1.0、1.1。两者均值分别为 1.2 和 1.0。根据验证选择后一个设置,在开发数据上重新拟合,然后在保留的最终时间段上评估一次。

一步步想清楚

  1. 为什么训练 MSE 更差,目标值反而更小?

    候选 B 的 MSE 增加 0.4,但惩罚减少 0.8。总值为 1.4+0.1×1=1.5,低于 A 的 1.9。

  2. 应该用哪些数值选择惩罚强度?

    使用验证误差,这里折均值为 1.2 与 1.0。训练目标用于拟合权重,不是未来误差的独立度量。

  3. 输入均值不变是否证明模型仍准确?

    不证明。即使均值相近,输入与标签的关系也可能改变。需要新的带标签样本来测量误差。

运行一个完整示例 · 01

区分拟合目标与验证

用候选摘要演示两种不同的比较。

Python 3下载源代码 ↓

长代码行可左右滚动。复制或下载会保留原始代码。

candidates = {'A': (1.0, 9), 'B': (1.4, 1)}
strength = 0.1
for name, (mse, squared_weights) in candidates.items():
    print(f'{name} objective: {mse + strength * squared_weights:.1f}')
folds = {0.0: [1.0, 1.4, 1.2], 0.1: [0.9, 1.0, 1.1]}
means = {value: sum(errors) / len(errors) for value, errors in folds.items()}
for value, score in means.items():
    print(f'lambda {value:.1f}: validation MSE {score:.1f}')
print('chosen lambda:', min(means, key=means.get))
在本地运行
python validation-regularization-shift-ml-regularization-validation.py

预期输出

A objective: 1.9
B objective: 1.5
lambda 0.0: validation MSE 1.2
lambda 0.1: validation MSE 1.0
chosen lambda: 0.1

逐步读懂代码

每个候选保存训练 MSE 与权重平方和。第一个循环加上惩罚。folds 字典存放另一组示意性的验证摘要。接着对等大小折取平均。min 的 key=means.get 选择验证误差最小的强度,不按惩罚后的训练损失选择。这些是预设摘要,不是实际拟合模型的结果。

这些知识可以用在哪里

选择惩罚强度

比较代码中两组验证误差。在查看测试集之前选择均值较低者。用开发数据重新拟合所选设置,再报告一次最终测试结果。

录音房间改变

声音分类器从安静房间移到大厅。保留大厅近期的带标签样本。在判断适应训练是否有帮助前,比较错误类型,而不只是平均音量。

类比的边界与注意事项. 反复搜索大量设置可能使验证数据也发生过拟合。对于相互依赖的观测或未来预测,交叉验证并非自动有效。正则化依赖特征尺度,也无法提供缺失信息。发现输入变化并不能证明准确性下降;输入汇总统计稳定,也不能保证与目标的关系保持稳定。
记住这一点

用验证选择复杂度,用保留测试评估已经完成的选择;只要预期预测场景改变,就收集新的证据。

↑ 返回知识框架
ML / 16Leon

检查置信程度并监测变化

进阶

01 / 从一个完整故事开始

阿丽的垃圾消息过滤器常给出 0.8 概率。她起初把它理解成每条消息都有 80% 的成功保证。后来她把近期带标签、概率相近的消息分组。四条预测约为 0.8 的消息中,只有两条是垃圾消息。在这个小组里模型过于自信。

阿丽记录组大小,并收集更多数据重复检查。她还监测缺失输入和新词。它们回答不同问题:输入变化提示条件可能改变,新标签揭示预测是否仍有用。她计划用独立数据重新校准,而不是从最终测试集改写概率。

02 / 理解核心概念

校准检查相似案例中,估计概率是否与观测频率一致。可靠性分箱把相近估计分组,比较组内平均概率与正类比例。小组噪声较大,应保留计数。

布里尔分数平均概率与零或一标签之差的平方。该指标越低越好,但它结合预测质量的多个方面,并非纯校准指标。监测还检查输入有效性、缺失率与随时间变化的带标签错误率。重新校准需要与最终评估分离的数据。

先认识这些词

校准
相似案例中预测概率与观测频率的一致程度。
可靠性分箱
预测概率相近的一组样本。
布里尔分数
预测概率与二元结果之差的平方均值。

03 / 如何运用这个概念

垃圾消息复核队列既需要有效排序,也需要可解释的置信程度。用近期带标签消息检查概率分箱,同时监测新类别与缺失特征。调查变化后,再在独立时段比较更新模型。

04 / 官方教程与研究中的应用

官方校准指南解释可靠性图与恰当评分规则。Google 监测指南介绍数据检查与上线后的带标签质量检查。本例只是微型诊断,样本不足以决定重新校准策略。

05 / 公式与逐项解析

p̄_b = (1/n_b) Σᵢ∈b pᵢ
f_b = (1/n_b) Σᵢ∈b yᵢ
Brier = (1/n) Σᵢ₌₁ⁿ (pᵢ−yᵢ)²

b 是非空概率分箱,n_b 是箱内数量。pᵢ 是预测正类概率,yᵢ 是零或一标签。p̄_b 为平均预测概率,f_b 为观测正类比例。n 是全部评估样本数,Σ 表示相加。

06 / 一步步算出结果

概率为 [0.8,0.8,0.8,0.8],标签为 [1,1,0,0]。平均预测为 0.8,正类比例却为 2/4=0.5。平方误差为 [0.04,0.04,0.64,0.64],均值为 1.36/4=0.34。四个案例提出了疑问,却不能精确确定总体校准误差。

一步步想清楚

  1. 0.8 是否表示一条消息有部分是垃圾消息?

    不是。标签仍为零或一。概率描述不确定性,校准针对许多相似消息的频率。

  2. 这里负标签为何贡献 0.64?

    y=0 时差为 0.8−0=0.8,平方为 0.64。自信但错误的概率受到较大惩罚。

  3. 没有标签的监测能证明准确率吗?

    不能。它能发现格式或输入变化,但衡量正确性需要可靠结果或标签。

运行一个完整示例 · 01

检查一个概率分箱

四个例子都属于概率约为 0.8 的同一分箱。

Python 3下载源代码 ↓

长代码行可左右滚动。复制或下载会保留原始代码。

import numpy as np
probability = np.array([0.8,0.8,0.8,0.8])
label = np.array([1.,1.,0.,0.])
brier = np.mean((probability - label) ** 2)
print('bin count:', len(label))
print(f'mean probability: {probability.mean():.2f}')
print(f'observed positive fraction: {label.mean():.2f}')
print(f'Brier score: {brier:.2f}')
在本地运行
python calibration-monitoring-ml-calibration-bin.py

预期输出

bin count: 4
mean probability: 0.80
observed positive fraction: 0.50
Brier score: 0.34

逐步读懂代码

数组把每个概率与真实标签配对。相减、平方、取平均实现布里尔分数。标签为零或一,因此均值就是正类比例。打印数量提醒我们四个案例的证据有限。脚本诊断预设预测,没有拟合校准器,也没有监控真实系统。

这些知识可以用在哪里

更谨慎的概率

把四个概率都改为 0.5,此组标签上的布里尔分数变成 0.25。这个小样本分数改善了;任何学到的重新校准仍需独立数据验证。

新的文件来源

文档导入器改变前后监测特征缺失率。若升高,先检查解析。判断分类准确率是否下降前,收集新标签。

类比的边界与注意事项. 标签延迟或缺失限制监测。分箱方式会改变图示。分布变化后校准可能退化。特征均值改变只是警讯,不证明预测误差增加。
记住这一点

置信程度需要频率检查。模型质量需要新标签,尤其是在输入或环境变化时。

↑ 返回知识框架

知识速查

01 / 从行、特征、标签与基线开始

先定义样本与答案,再保留一个简单比较,让复杂模型证明改进。

02 / 划分数据之前,先定义问题

只有明确预测什么、信息何时可用,以及划分代表哪一种未见案例之后,分数才有意义。

03 / 填补缺口前先检查表格

先弄清空白的含义,再用训练信息填补,并保留缺失记录。

04 / 把类别变成明确的列

编码要表达类别含义,固定列顺序,并明确处理未见值。

05 / 线性回归:用直线预测数值

线性回归是透明的基线;损失函数决定哪些错误更重要,残差则揭示直线尚未解释的部分。

06 / 用可控步长和可比尺度学习

把更新规则、输入单位和损失曲线放在一起检查;稳定优化是学习的必要条件,却不是泛化能力的证据。

07 / 区分概率估计与决策

把训练概率模型与选择决策阈值视为相关但独立的步骤,再用未参与选择的数据评估完整规则。

08 / 统计任务真正关心的错误

让混淆矩阵始终伴随主要分数;合适的指标必须揭示预期用途真正关心的错误。

09 / 近邻法:向附近样本借用证据

近邻预测是否合理,取决于距离、特征尺度与保存的样本。

10 / 朴素贝叶斯:用词频分类短文本

统计词频、平滑计数、结合证据,再检查语境在哪些地方打破简单假设。

11 / 建立规则,再理解组合规则会改变什么

树模型把交互作用转化为明确区域;集成方法可以稳定预测,但每增加一层复杂度,都仍需针对预期任务进行验证。

12 / 提升法:学习下一次修正

提升法让下一个模型修复当前预测仍遗漏的部分。

13 / 寻找结构,不凭空赋予标签

用聚类提出分组,用 PCA 压缩变化,再用这两种方法本身没有提供的证据检验解释。

14 / 推荐:按明确的相似度排序

选择符合推荐问题的表示与分数,再用反馈检验排序。

15 / 验证模型选择,为场景变化做好准备

用验证选择复杂度,用保留测试评估已经完成的选择;只要预期预测场景改变,就收集新的证据。

16 / 检查置信程度并监测变化

置信程度需要频率检查。模型质量需要新标签,尤其是在输入或环境变化时。

官方教程与原始研究

故事、讲解与计算例子由 Leon 原创编写。链接中的教程和研究属于各自作者;本资料为独立学习笔记,并非官方译本或官方认可课程。

  1. 数据集:划分原始数据集 ↗Google · Official tutorial
  2. 常见陷阱与推荐实践 ↗scikit-learn · Official tutorial
  3. 线性回归:损失 ↗Google · Official tutorial
  4. 线性回归:梯度下降 ↗Google · Official tutorial
  5. 数值数据:归一化 ↗Google · Official tutorial
  6. 逻辑回归:使用 sigmoid 函数计算概率 ↗Google · Official tutorial
  7. 阈值与混淆矩阵 ↗Google · Official tutorial
  8. 分类:准确率、召回率、精确率与相关指标 ↗Google · Official tutorial
  9. 决策树 ↗scikit-learn · Official tutorial
  10. 集成:梯度提升、随机森林、装袋、投票与堆叠 ↗scikit-learn · Official tutorial
  11. 聚类 ↗scikit-learn · Official tutorial
  12. 把信号分解为成分:主成分分析 ↗scikit-learn · Official tutorial
  13. 交叉验证:评估估计器性能 ↗scikit-learn · Official tutorial
  14. 线性模型:岭回归与分类 ↗scikit-learn · Official tutorial
  15. 虚拟估计器与基线比较 ↗scikit-learn · Official documentation
  16. 缺失值填补 ↗scikit-learn · Official documentation
  17. 预处理:类别特征 ↗scikit-learn · Official documentation
  18. 最近邻 ↗scikit-learn · Official documentation
  19. 朴素贝叶斯 ↗scikit-learn · Official documentation
  20. 概率校准 ↗scikit-learn · Official documentation
  21. 基于内容的过滤 ↗Google · Official tutorial
  22. 监测流水线 ↗Google · Official tutorial
  23. 交叉验证:评估估计器性能 ↗scikit-learn · Official reference
  24. 调整分类决策阈值 ↗scikit-learn · Official reference