元数据卡
- 前置知识:Vol 12 数据处理与特征工程、Vol 10 概率与统计
- 预计时间:45 分钟
- 核心难度:入门到进阶
- 阅读模式:高度专注
- 完成标志:能拆分数据集、理解过拟合与欠拟合、完成一个端到端 ML 流程
你的进度
你从模型工坊的前三张工作台抬起头来,发现它们有一个共同点:每一套系统都需要你亲手写规则。搜索的代价函数是你写的,推理的逻辑规则是你写的,强化学习的奖励函数也是你写的。
你拿起阿花的信重新读了一遍:“它要是能自己学就好了。”
你换了个方向——不给规则,给例子。模型工坊的新工作台上摆着一摞标注好的数据。
你的任务
机器学习的本质是从有限样本中学习一个能推广到未见数据的函数。本章建立基础框架:数据拆分、特征工程、训练验证测试三阶段、过拟合与欠拟合的诊断和应对。
本章分层
- 必读:数据集拆分、偏差-方差困境、过拟合与正则化
- 选读:学习曲线、AIC/BIC
- 进阶:PAC 学习理论直观理解
破局 · 溯源
假设你在教一个小孩认识猫。你不会写一本《猫的识别算法手册》——你只是不断指给他看:"这个是猫,这个不是。"慢慢地他自己学会了。
这是机器学习的直觉。但第一个问题立刻出现:你用来教他的"猫"照片和考试时给他的新照片,是同一批吗?如果是同一批,他只是背下了答案——这叫过拟合。如果照片太少、太模糊,他根本学不会——这叫欠拟合。
数据集拆分
所有 ML 项目的第一步:把数据拆成三份。
- 训练集:模型在上面学习
- 验证集:调超参数、选模型
- 测试集:最终评估(绝对不准碰,直到最后一刻)
from sklearn.model_selection import train_test_split
import numpy as np
# 生成示例数据
X = np.random.randn(1000, 10)
y = (X[:, 0] + X[:, 1] > 0).astype(int)
# 先拆出测试集
X_temp, X_test, y_temp, y_test = train_test_split(
X, y, test_size=0.2, random_state=42
)
# 再拆训练和验证
X_train, X_val, y_train, y_val = train_test_split(
X_temp, y_temp, test_size=0.25, random_state=42
)
print(f"训练: {len(X_train)}, 验证: {len(X_val)}, 测试: {len(X_test)}")为什么要两个步骤?如果你用同一个 train_test_split 反复看着验证集调参数,验证集的信息就泄露到了模型选择上——验证集不再是"未见数据",你实际上在间接过拟合验证集。
特征工程
原始数据很少直接喂给模型。特征工程是把原始数据转化为模型能有效利用的形式:
- 标准化(StandardScaler):减去均值除以标准差,让不同尺度的特征可比
- 归一化(MinMaxScaler):缩放到 [0, 1]
- 类别编码:One-Hot 或 Label Encoding
- 特征交叉:组合特征创造新特征
from sklearn.preprocessing import StandardScaler
scaler = StandardScaler()
X_train_scaled = scaler.fit_transform(X_train)
X_val_scaled = scaler.transform(X_val) # 注意:用训练集的统计量
X_test_scaled = scaler.transform(X_test) # 绝对不能调用 fit过拟合与欠拟合
这是机器学习里最重要的诊断框架。
- 欠拟合:训练误差高 → 模型不够复杂 / 特征不够
- 过拟合:训练误差低但验证误差高 → 模型记住了噪声而非模式
# 在多项式回归上演示过拟合
from sklearn.preprocessing import PolynomialFeatures
from sklearn.linear_model import LinearRegression
from sklearn.metrics import mean_squared_error
np.random.seed(42)
X_small = np.sort(np.random.rand(20, 1), axis=0)
y_small = np.sin(2 * np.pi * X_small).ravel() + np.random.normal(0, 0.1, 20)
for degree in [1, 3, 15]:
poly = PolynomialFeatures(degree)
X_poly = poly.fit_transform(X_small)
model = LinearRegression()
model.fit(X_poly, y_small)
train_pred = model.predict(X_poly)
train_mse = mean_squared_error(y_small, train_pred)
print(f"degree={degree:2d}, train MSE: {train_mse:.4f}")当 degree=1 时,欠拟合(线形太简单);degree=3 刚好;degree=15 完美通过每个训练点(过拟合),在新数据上表现极差。
对抗过拟合的方法:
- 正则化:在损失函数中加惩罚项
- 交叉验证:用多组训练/验证拆分评估
- 早停(Early Stopping):在验证误差开始上升时停止训练
- 数据增强:扩大训练数据规模
- Dropout/噪声:随机丢弃单元或加噪声
# L2 正则化的线性回归(岭回归)
from sklearn.linear_model import Ridge
ridge = Ridge(alpha=1.0)
ridge.fit(X_train, y_train)
print(f"Ridge train: {ridge.score(X_train, y_train):.3f}, "
f"val: {ridge.score(X_val, y_val):.3f}")偏差-方差困境
偏差(bias)衡量模型对真实模式的逼近能力。方差(variance)衡量模型对训练数据变化的敏感度。
- 高偏差 → 欠拟合(假设太强)
- 高方差 → 过拟合(假设太弱,对数据敏感)
两者此消彼长:降低偏差升高方差,反之亦然。好模型就是在两者之间找到临界点。
常见陷阱
- 先看测试集再看测试集再看测试集——当你"不小心"看了测试集上的表现来调参数时,测试集已经不再是评估标准了。
- 标准化必须用训练集的 fit,不能在整体数据上 fit 再拆分(数据泄露)。
- 类别不均衡:如果 99% 是负类,一个永远预测负类的模型准确率 99%——但没用。需要改用 F1、PR 曲线或重采样。
- 时间序列数据不能随机拆分——必须按时间顺序前训练后测试。
通关挑战
- 热身(10 分钟):用 sklearn.datasets.make_classification 生成一个二分类数据集,手动拆分成训练/验证/测试,运行一个逻辑回归,打印三个集合上的准确率。
- 挑战(30 分钟):在 10 个不同的多项式度数(1~20)上训练,绘制训练误差和验证误差随度数变化的曲线——观察过拟合从哪个度数开始。
- 观察:在训练集上加入随机噪声标签(错误标注 10%),看看验证集上的准确率变化趋势。
旅人笔记
机器学习三分模型,七分数据。理解过拟合是理解一切 ML 的核心——从简单的线性回归到万亿参数大语言模型,都在解决同一个问题:从有限样本中学习能推广的知识。
-> 下一站预告
有了数据框架,你开始学习第一个完整的模型家族:线性模型。