特征 / 标签 / 过拟合
机器学习的三个最基础术语——特征、标签、过拟合——理解了它们,你就理解了 ML 的"地基"。这一篇不讲故事,直接拆概念。
先把术语翻译成人话
这一篇的三个词——特征、标签、过拟合——听着玄,说白了都是学生时代天天经历的事。先摆一张对照表,后面看正文会顺很多。
| 术语 | 换成大白话 | 生活里对应什么 |
|---|---|---|
| 特征(Feature) | 用来判断一件事的那几项已知信息 | 医院体检单上的身高、血压、血糖那几栏 |
| 特征工程(Feature Engineering) | 把乱七八糟的原始信息整理成模型能吃的样子 | 厨师在案板上把食材切丝、切丁、腌好 |
| 特征缩放(Scaling) | 先把单位统一了再比大小 | 一个标"元/斤"一个标"元/公斤",得先换算 |
| 标签(Label) | 人给这条数据写上的标准答案 | 作业本后面的答案页 |
| 标签噪声(Label Noise) | 答案页本身就印错了几道 | 参考答案有错,你越较真错得越离谱 |
| 过拟合(Overfitting) | 只把练习册背下来了,换个数字就傻眼 | 只会做例题的学生,考试一变形就崩 |
| 欠拟合(Underfitting) | 压根没学会,练习册都做不对 | 没复习,平时作业就一塌糊涂 |
| 偏差 / 方差 | 偏差=准星装歪了;方差=手抖,每次落点都不一样 | 打靶——一个是偏,一个是散 |
| 正则化(Regularization) | 给模型加点约束,别让它太由着自己来 | 老师限时交卷、随机抽查,逼你别死记 |
| Dropout | 训练时随便关掉一些神经元 | 教室里随机点人回答,谁都得会 |
| 早停(Early Stopping) | 一看开始背题了就叫停 | 复习到边际收益为零就该睡了 |
| 交叉验证(Cross Validation) | 换几种切法多考几次,别靠一次运气 | 做五套模拟卷取平均,而不是只看一次成绩 |
| 数据泄漏(Data Leakage) | 偷看了答案还以为自己会做 | 拿高考真卷当练习册,分数一文不值 |
特征 · Feature
特征是喂给模型的"输入维度"——每个样本用一组数字来描述。挑西瓜时"响声、颜色、瓜蒂"就是三个特征。
你去医院体检,报告上有:身高、体重、血压、血糖、胆固醇、心率、白细胞……
每一项就是一个特征。医生根据这些特征判断你"健康 / 亚健康 / 有病"。
机器学习也一样——每个样本用一组特征描述,模型根据特征做判断。
体检报告这个类比还能再往下推一层,暴露出特征最关键的一个性质:特征必须是"数字",或者能被转成数字。身高体重天生是数字,好办;但"性别""血型""是否吸烟"不是,必须先编码。更麻烦的是"主诉症状:最近三周持续咳嗽,夜间加重"这种自由文本——它信息量最大,但也最难变成模型能吃的形状。特征工程的全部工作,就是把现实世界里五花八门的信息统一压成一个数值向量。
另一个必须建立的直觉是特征的维度和样本数量的关系。假设你只有 200 份体检报告,却往模型里塞了 500 个指标——这时候模型的自由度超过了数据能约束的程度,它一定能找到某个"完美区分这 200 人"的组合,但那个组合纯属巧合。这个现象叫维度灾难,Bellman 在 1957 年就命名了它。粗略的经验法则是:简单模型至少要让样本数是特征数的 10 倍以上,否则宁可先做特征筛选或降维。
"自由度超过数据能约束的程度"这句话换成大白话:条件给多了,什么结论都能编出来。好比全班只有 200 个人,你却拿了 500 个指标去找规律——总能找出一条"生日在三月且左手写字的人成绩更好"这种巧合。想象一下用 500 把钥匙去开一把锁,试到最后总有一把碰巧转开了,可这不代表那把是钥匙。
特征工程 · 最难也最重要的事
特征工程(Feature Engineering)是"从原始数据里提取有用特征"的过程。传统 ML 时代,80% 的工作量在特征工程上。
| 原始数据 | 提取的特征 | 为什么有用 |
|---|---|---|
| 用户注册时间 | 注册距今多少天 | 新用户和老用户行为不同 |
| 交易时间戳 | 是否周末、是否凌晨 | 盗刷多在凌晨 |
| 一段文本 | 长度、感叹号数量、关键词 | 垃圾邮件通常很长、很多感叹号 |
| 一张图片 | 颜色直方图、边缘密度 | 猫和狗的纹理不同 |
特征工程 = 做菜——你把生食材(原始数据)切成丝、片、丁(特征),厨师(模型)才能炒出好菜。
深度学习 = 自动做菜——你直接把整只鸡扔进锅,神经网络自己学会"怎么切"。
这就是深度学习最大的突破:它把特征工程自动化了。
四类原始数据的实战处理手法
把特征工程拆开看,现实中的原始数据基本就四类:数值、类别、时间、文本。每一类有自己的一套标准动作,这套动作在银行、电商、广告、风控里天天在用。
这四套动作说白了就是厨房里的四种备菜手法:数值要"改刀改成一样大小"(缩放),类别要"分门别类装进不同的碗"(编码),时间要"看清是早市还是夜市"(拆成星期几、是否凌晨),文本要"从一大堆话里挑出关键那几味"。备菜做得好,后面随便什么锅都能炒出味;备菜马虎,再好的锅也救不回来。
数值特征的处理有四个常见动作。缺失值填补:最简单是填均值或中位数(有离群点时用中位数),更好的做法是额外加一个"是否缺失"的标记列——因为"缺失"这件事本身常常有信息,一个不填写收入的申请人可能真的有隐情。离群点截断:把超过 99 分位的值统一压到 99 分位(winsorize),避免一个录入错误的"年龄 999"毁掉整个模型。分箱(Binning):把连续的年龄切成"18–25/26–35/36–50/50+",这会丢失一些信息,但能让模型捕捉非线性关系,而且在风控评分卡里更利于业务解释。取对数:收入、房价、点击量这类长尾右偏的分布,取 log 之后会接近正态,对线性模型帮助巨大。
类别特征是最需要技巧的一类。最基础的是独热编码(One-Hot):把"北京/上海/广州"变成三列 0/1。它的问题是类别数一多就爆炸——用户 ID 有一千万个,独热编码后就是一千万列的稀疏矩阵,内存直接撑爆。所以高基数类别特征要换招:
| 编码方式 | 怎么做 | 适合 | 风险 |
|---|---|---|---|
| 独热 One-Hot | 每个类别一列 0/1 | 类别少(< 15 个)、线性模型 | 高基数时维度爆炸 |
| 标签编码 Label | 直接映射成 0,1,2,3... | 只能用于树模型 | 线性模型会误以为 3 > 1 有大小关系 |
| 序数编码 Ordinal | 按真实顺序映射(小学=1,中学=2,大学=3) | 类别本身有序 | 顺序定错就引入假信息 |
| 频次编码 | 用该类别出现的次数替代 | 高基数、类别热度有意义 | 不同类别频次相同会撞车 |
| 目标编码 Target | 用该类别下标签的均值替代 | 高基数、效果常最好 | 极易数据泄漏,必须交叉折内计算 |
| 嵌入 Embedding | 学一个 d 维稠密向量 | 超高基数(用户/商品 ID) | 需要神经网络和足够数据 |
| 哈希技巧 Hashing | 哈希到固定的 N 个桶 | 类别数未知或会持续新增 | 不同类别可能撞到同一个桶 |
嵌入(Embedding)值得多说一句,因为它是独热编码的现代替代品,也是理解深度学习的关键概念。独热编码里"北京"是 [1,0,0]、"上海"是 [0,1,0],两个向量的距离和"上海"与"广州"的距离完全一样——编码本身不携带任何语义。嵌入的做法是给每个类别学一个稠密向量(比如 32 维),这个向量是训练出来的,因此语义相近的类别会自动靠近。
独热编码 vs 嵌入
独热:城市数 = 1000 → 1000 维稀疏向量
北京 [1,0,0,0,...,0] ← 只有一个 1,其余 999 个 0
上海 [0,1,0,0,...,0] ← 任意两城市的余弦相似度都是 0
任何两个城市之间"一样远",模型学不到"北京和上海都是一线城市"
嵌入:城市数 = 1000 → 查一张 1000×32 的表,得到 32 维稠密向量
北京 [ 0.82, -0.31, 0.55, ..., 0.07]
上海 [ 0.79, -0.28, 0.61, ..., 0.11] ← 和北京很近
鹤岗 [-0.44, 0.66, -0.23, ..., 0.52] ← 离得远
这张表的每一个数都是模型训练出来的参数。
训练完成后,向量的距离自动编码了"城市有多像"。
参数量对比(1000 个类别):
独热 + 线性层(输出128):1000 × 128 = 128,000 个参数
嵌入(32) + 线性层(128): 1000×32 + 32×128 = 36,096 个参数
← 参数更少,效果通常更好,还顺带得到可复用的语义向量
时间特征是最容易被浪费的一类。直接把"2026-08-01 14:30:00"这个字符串丢进模型完全没用,必须手工拆解出业务含义:周期特征(星期几、几点钟、是否周末、月份、是否月初月末)、节假日标记(是否法定假日、是否大促、距最近节假日几天)、时间差特征(注册距今多少天、距上次购买多少天——这个在流失预测里几乎总是最强特征)、滑动窗口聚合(近 7 天/30 天的均值、最大值、标准差、趋势斜率)。
周期特征还有一个技术细节容易踩坑:"小时"这个特征不能直接当数字用。因为 23 点和 0 点在数值上差 23,但实际只差 1 小时。正确做法是做周期性编码:
# 时间的周期性编码(把"环状"结构还给模型)
import numpy as np
hour_sin = np.sin(2 * np.pi * hour / 24)
hour_cos = np.cos(2 * np.pi * hour / 24)
# 这样 23 点和 0 点在 (sin, cos) 平面上就是相邻的两个点
# 同理适用于:星期(周期 7)、月份(周期 12)、一年中第几天(周期 365)
dow_sin = np.sin(2 * np.pi * day_of_week / 7)
dow_cos = np.cos(2 * np.pi * day_of_week / 7)
# 滑动窗口特征(时序建模的主力)
df['amt_mean_7d'] = df.groupby('uid')['amt'].rolling(7).mean().values
df['amt_std_7d'] = df.groupby('uid')['amt'].rolling(7).std().values
df['amt_ratio'] = df['amt'] / (df['amt_mean_7d'] + 1e-6) # 本次是否异常
# 严重警告:所有滑动窗口必须只用"当前时刻之前"的数据,
# 用了未来数据就是数据泄漏,线下会好得离谱,上线立刻崩
文本特征的处理经历了三代演进。第一代是词袋(Bag of Words)和 TF-IDF:统计每个词出现的次数,再用 TF-IDF 给"在本文档常见但在全语料罕见"的词更高权重。它完全忽略词序("猫追狗"和"狗追猫"编码一样),但在文档分类上出奇地好用,至今是很多场景的强基线。第二代是词向量(Word2Vec, 2013;GloVe, 2014):每个词一个稠密向量,第一次让"国王 − 男人 + 女人 ≈ 女王"这种向量运算成为可能。第三代是上下文相关的表示(BERT 之后):同一个词在不同句子里有不同的向量,"苹果手机"和"吃苹果"里的"苹果"终于被区分开了。今天做文本任务的默认做法是直接调一个预训练模型拿句向量,手工文本特征工程基本退场了——这是深度学习真正彻底取代特征工程的少数领域之一。
特征缩放:为什么必须做,什么时候可以不做
这是新手最常忽略、后果最直接的一步。考虑一个数据集:年龄范围 18–80,年收入范围 30000–2000000。这两个特征的数值量级差了将近五个数量级。会发生什么?
为什么不缩放会出问题:
【问题一:距离被大数字垄断】
两个人 A(年龄30, 收入100000) 和 B(年龄60, 收入100050)
欧氏距离² = (60-30)² + (100050-100000)² = 900 + 2500
→ "收入只差 50 元"贡献的距离,比"年龄差 30 岁"还大!
受害算法:KNN、K-Means、SVM、任何基于距离的方法
【问题二:梯度下降之字形震荡】
损失曲面在"收入"方向极其陡峭、在"年龄"方向极其平缓,
等高线变成又长又扁的椭圆。
梯度下降会在陡峭方向来回弹跳、在平缓方向蜗牛爬行,
收敛慢几十倍甚至根本不收敛。
受害算法:所有用梯度下降的模型(线性回归、逻辑回归、神经网络)
【问题三:正则化被扭曲】
L1/L2 惩罚的是权重的大小。为了让"收入"起作用,
它的权重必然极小(比如 0.000003);"年龄"的权重可能是 0.5。
同一个正则化强度对这两个权重的实际约束完全不对等。
| 缩放方式 | 公式 | 结果范围 | 什么时候用 |
|---|---|---|---|
| 标准化 Standardization | (x − 均值) / 标准差 | 均值 0、标准差 1(无固定上下界) | 默认首选;数据近似正态;有离群点时比归一化稳 |
| 归一化 Min-Max | (x − min) / (max − min) | [0, 1] | 需要固定区间时(图像像素、某些神经网络输入) |
| 稳健缩放 Robust | (x − 中位数) / 四分位距 | 无固定界 | 离群点很多时——中位数和 IQR 都不怕极值 |
| 对数变换 Log | log(1 + x) | 压缩长尾 | 右偏严重的分布:收入、房价、播放量 |
| L2 归一化 | x / ‖x‖₂ | 向量长度为 1 | 文本 TF-IDF 向量、embedding 相似度检索 |
那什么时候可以不做缩放?树模型(决策树、随机森林、XGBoost、LightGBM)完全不需要。原因很清晰:树的每次分裂只问"这个特征大于某个阈值吗",这是一个纯粹的序关系判断,把收入的单位从元换成万元,排序完全不变,分裂点也只是等比例缩放,树的结构一模一样。这也解释了为什么树模型在真实表格数据上那么好用——它省掉了一整类预处理麻烦,也天然不怕离群点。
但有一条铁律不能违反:缩放的参数必须只从训练集算,然后应用到验证集和测试集上。用全量数据算均值和标准差是最隐蔽也最普遍的数据泄漏形式。
# 错误做法(数据泄漏!)
scaler = StandardScaler()
X_all_scaled = scaler.fit_transform(X_all) # ← 用了全量数据的统计量
X_train, X_test = split(X_all_scaled) # 测试集信息已经泄漏进来了
# 正确做法
X_train, X_test = split(X_all)
scaler = StandardScaler()
X_train_scaled = scaler.fit_transform(X_train) # fit 只看训练集
X_test_scaled = scaler.transform(X_test) # test 只做 transform
# 最佳实践:用 Pipeline 从代码层面强制这个纪律
from sklearn.pipeline import Pipeline
pipe = Pipeline([
('scaler', StandardScaler()),
('model', LogisticRegression()),
])
cross_val_score(pipe, X, y, cv=5)
# ← 交叉验证时,scaler 会在每一折的训练部分单独 fit,绝不会跨折泄漏
标签 · Label
标签是你希望模型学到的"正确答案"。监督学习里,每个样本必须带标签。
| 任务 | 标签长什么样 |
|---|---|
| 分类 | "猫" / "狗" / "垃圾" / "正常" |
| 回归 | 412.5(万元)/ 23.7(℃)/ 156(ms) |
| 目标检测 | 每个物体的类别 + 位置框 |
| 语义分割 | 每个像素的类别 |
标签的成本
标注是 ML 最贵的环节之一。一张医疗影像要资深医生标,一张 5 块钱;一辆自动驾驶汽车的路测数据,要人工框出每一帧的行人、车辆、红绿灯——一帧几毛钱,一天几十万帧。
这就是为什么自监督学习(§ 4.4)这么火——它绕开了标注。
标签噪声:连 ImageNet 都有 5.8% 的标签是错的
这一节本质上就是在讲一件让人不太舒服的事:你手里那本"标准答案",本身印错了几道。打个比方,相当于你做作业时对照的参考答案有 6% 是错的——你越是老实照着改,就越是被带歪。而更麻烦的是:你用同一本错答案去给两个学生打分,排名都可能是反的。
有一个残酷的事实:你以为的"标准答案"里,有相当一部分是错的。2021 年 MIT 的一项研究系统检查了十个最常用的机器学习基准数据集,用一套叫 confident learning 的方法找出疑似错标样本再交给人复核,结论是:测试集的平均错标率约 3.4%,其中 ImageNet 验证集约 5.8%、QuickDraw 高达 10.1%。这些是全世界被引用最多、被反复清洗过的数据集。
标签噪声的后果不只是"准确率天花板降低",它有三个更隐蔽的影响:
- ① 它是过拟合的直接燃料干净标签下模型学到的是规律;有噪声标签时,模型如果容量足够大,会把那些错标样本一个个"背"下来。有一项经典实验(Zhang et al., 2017)证明:把 CIFAR-10 的标签完全随机打乱,一个标准的深度网络依然能把训练集准确率训到 100%——这说明深度网络的记忆容量大到足以背下任何随机噪声,它之所以在真实数据上能泛化,靠的是数据里真实规律的存在和各种正则化手段的约束。
- ② 它会误导模型选择如果测试集里有 6% 的标签是错的,那么两个真实准确率分别是 96% 和 97% 的模型,在这个测试集上的排名可能完全颠倒。MIT 那项研究的一个具体发现是:在噪声较大的测试集上,某些较小的模型的排名会被系统性低估——也就是说,我们可能一直在按错误的排行榜选模型。
- ③ 它会污染指标解读模型准确率卡在 94% 上不去,你以为是模型不行,反复调参加层,其实是标签本身只有 94% 是对的。不去查标签质量,这几周的调参全是白费。
应对标签噪声有一套手段。数据层面:多人标注取共识、用 Kappa 系数监控一致性、对模型损失特别大的样本人工复查(这些往往就是错标样本)、用交叉验证的预测结果反查异常样本。损失函数层面:用对噪声更鲁棒的损失(比如 MAE 类损失比交叉熵更抗噪,因为交叉熵对"高置信度的错误"惩罚趋于无穷,一个错标样本能产生极大的梯度)、或者用 Label Smoothing——把 one-hot 标签 [0,0,1,0] 软化成 [0.02,0.02,0.94,0.02],明确告诉模型"不要 100% 确信任何标签"。训练策略层面:早停(噪声通常在训练后期才被记住)、Co-teaching(两个网络互相挑选"对方看起来学得动"的小损失样本来训练)。
顺带说一个反直觉的正面例子:适度的标签噪声有时反而起到正则化作用,因为它阻止模型对训练集过于自信。但这有严格前提——噪声必须是随机的。如果噪声是系统性的(比如某个标注员总是把"中性"标成"正面"),那它就变成了偏见,会被模型忠实学习并放大,危害远大于随机噪声。
想象一下教室里坐着两个学生,期中考试都考了 100 分。
甲把这学期发的三本练习册从头到尾背了个滚瓜烂熟,连第 47 页第 3 题答案是 B 都记得;乙只做了一半的题,但每道错题都在笔记本上写清了"为什么错"。在练习册上,甲比乙强;一到期末换了套卷子,甲全崩,乙照样及格。甲就是过拟合,乙就是泛化。
关键在于:你光看练习册上的分数,是分不出甲和乙的——这就是为什么必须留一份从没见过的卷子。而"训练误差一路降、验证误差却开始往上走"这条曲线,翻译成人话就是:这孩子从某一刻起不再是在学方法了,而是在背题号。
顺着这个场景,防过拟合的那几招也全能对上:
加数据——题量从 300 道涨到 3 万道,想背也背不完,只能学方法;
数据增强——同一道题把数字换掉、把条件换个说法,背题这条路直接断掉;
早停——看到他开始背题号就叫停,别让他再刷了;
Dropout——教室里随机点人、随机遮住黑板上的一部分,逼他不能只靠某一条路径记答案;
L1/L2 正则——不许他给某一条偏门技巧押上全部身家,每项本事都得摊薄一点。
说白了:过拟合不是"学多了",是"学错了东西"——他把答案本身当成了知识。
过拟合 · Overfitting
过拟合是 ML 里最经典、最致命的问题——模型在训练集上表现完美,遇到新数据就傻。
一个学生,你把过去 10 年的高考题给他背。他背得滚瓜烂熟,你随便抽一道他都能默写出答案。
你很开心,觉得他数学满分稳了。
结果高考那天,题目稍微变了个形式——他一道都不会。
他不是"学会了数学",他是"背会了那 100 道题"。
这就是过拟合。
过拟合 vs 欠拟合
| 欠拟合 · Underfit | 过拟合 · Overfit | |
|---|---|---|
| 训练集表现 | 差 | 完美 |
| 测试集表现 | 差 | 差 |
| 原因 | 模型太简单,学不到规律 | 模型太复杂,把噪音当规律 |
| 类比 | 学生没复习,考砸了 | 学生只背题,换题就傻 |
| 解决 | 加特征、换复杂模型 | 加数据、减复杂度、正则化 |
怎么判断过拟合了
训练过程中画两条曲线——训练误差和验证误差:
- 训练误差 ↓ 验证误差 ↓正常——模型在学真规律。
- 训练误差 ↓ 验证误差 ↑过拟合了——模型在"背"训练集。
- 两条都高欠拟合——模型太简单。
光有三条规则不够,得会看图。学习曲线有两种画法,用途完全不同,混淆它们是常见错误。
【第一种:训练进度曲线】横轴 = epoch(训练轮数)
用来决定"什么时候该停"
误差
│ \ 验证误差
│ \_ __--‾‾
│ \_________---‾‾
│ ↑最低点 ← 过了这里就开始过拟合
│ \
│ \_______________ 训练误差(一直降)
└──────────────────────────────── epoch
早停应该停在验证误差的最低点
【第二种:样本量曲线】横轴 = 训练集大小
用来决定"该加数据还是该换模型"
情况 A:高方差(过拟合) 情况 B:高偏差(欠拟合)
误差│ 误差│
│ \ 验证误差 │______ 验证误差
│ \__ │______ 训练误差
│ \___ │ 两条线早早贴在一起
│ __--‾‾ 训练误差 │ 且都停在很高的水平
└────────────── 样本量 └────────────── 样本量
两条线之间有明显缺口, 两条线没缺口但都很高,
且缺口随数据增加在收窄 加数据完全没用
→ 加数据有效! → 必须换更强的模型/加特征
第二种曲线的实用价值极高,因为它能在你花钱买数据之前,先告诉你花这个钱有没有用。做法很简单:分别用 10%、20%、40%、70%、100% 的训练数据各训一个模型,把训练误差和验证误差都画出来。如果两条线之间的缺口在持续收窄,说明再加数据还能继续涨;如果两条线已经贴在一起且都停在高位,那就是模型能力不够,加十倍数据也是白花钱。
还有一个必须知道的现代补充:双下降现象(Double Descent)。传统教材画的是一条 U 形曲线——模型复杂度增加,测试误差先降后升。但 2019 年之后的研究发现,在超参数化的深度模型上,测试误差在到达那个"插值阈值"(模型刚好能完美记住训练集的临界点)之后会第二次下降,而且可能降到比第一个谷底更低。这解释了一个困扰传统统计学的悖论:为什么参数量远超样本量的巨型神经网络反而泛化得更好。这个现象至今没有完全被理论解释清楚,但它是"大模型为什么有效"的一块重要拼图。
偏差-方差权衡:为什么这是一对矛盾
两个词先翻译:偏差(Bias)=准星装歪了,每枪都往同一个方向偏;方差(Variance)=手抖,平均落点在靶心但散得一片。换成大白话——偏差是"你的想法本来就错了",方差是"你没主见,换一批数据就换一套结论"。好比做菜:偏差是菜谱抄错了,每次都咸;方差是没有菜谱,全凭手感,今天咸明天淡。
过拟合和欠拟合背后有一个统一的数学框架:任何模型的期望误差都能分解成三块。
总误差 = 偏差² + 方差 + 不可约噪声
偏差 Bias
= 模型的"平均预测"和"真实规律"之间的系统性偏离
← 高偏差意味着模型的假设本身就错了(比如用直线拟合抛物线)
← 对应欠拟合
方差 Variance
= 换一批训练数据,模型的预测会波动多大
← 高方差意味着模型对训练集的具体样本过于敏感
← 对应过拟合
不可约噪声 Irreducible Error
= 数据本身的随机性 + 标签噪声
← 这部分谁也降不下去,它是你的性能天花板
打靶类比:
高偏差 + 低方差:弹孔都很集中,但整体偏离靶心 → 准星装歪了
低偏差 + 高方差:弹孔平均在靶心附近,但散得很开 → 手抖
高偏差 + 高方差:又偏又散 → 最糟
低偏差 + 低方差:又准又稳 → 目标状态
为什么这是"权衡"而不是"两者都优化"?因为传统上二者由同一个旋钮控制——模型复杂度。让模型更复杂,它的表达能力变强、偏差下降,但同时它能拟合的东西也变多了、对训练集的具体样本更敏感、方差上升。这个跷跷板关系催生了一整套"在中间找最优点"的方法论,而这个中间点就是所有正则化技术要瞄准的目标。
| 手段 | 对偏差的影响 | 对方差的影响 | 说明 |
|---|---|---|---|
| 加深/加宽模型 | 降低 | 升高 | 能力更强但更容易背题 |
| 加更多训练数据 | 不变 | 降低 | 唯一"只有好处"的手段 |
| 加强正则化(L1/L2) | 升高 | 降低 | 牺牲一点拟合能力换稳定 |
| Bagging / 随机森林 | 基本不变 | 大幅降低 | 多个高方差模型平均,误差相互抵消 |
| Boosting / XGBoost | 大幅降低 | 可能升高 | 串行纠错,所以必须配合正则和早停 |
| 特征筛选 | 可能升高 | 降低 | 去掉噪声特征减少可背的东西 |
看这张表你会发现一个重要结论:只有"加数据"是纯粹的免费午餐,其他手段全是在做交易。这也回答了一个常被问的问题——"为什么大厂的模型效果好"。不完全是因为算法更聪明,很大一部分原因是他们有更多数据,从而能承受更低偏差的复杂模型而不付出方差代价。
怎么防止过拟合
更多数据
数据多了,模型没法"全背下来"——被迫学真规律。这是最根本的方法。
正则化 · Regularization
给模型参数加"惩罚",不让它变得太极端。L1 / L2 是经典方法。
Dropout
训练时随机"关掉"一部分神经元——强迫模型不依赖单一路径。深度学习标配。
早停 · Early Stopping
验证误差开始上升就停——不让模型继续"背"下去。
老师防学生死记硬背的方法:
① 出更多题(更多数据)——题量大了背不完;
② 出变形题(数据增强)——换个角度考,背题就失效;
③ 限时交卷(早停)——不给你无限时间磨;
④ 随机抽查(Dropout)——不知道考哪题,必须全会。
六种正则化手段的原理与代价
上面四张卡片只是入门版。真正干活时你会用到六类手段,每一类的机制和代价都不同,值得逐个说清。
"正则化"这个词最不像人话,其实就是"给模型加规矩,别让它太由着自己来"。好比装修时定的规矩——不许一面墙刷成七种颜色、承重墙一律不许砸。L2 相当于"每样东西都可以有,但都别太夸张";L1 相当于"用不上的直接扔掉,别占货架";Dropout 相当于"随机让几个工人今天请假,活照样得干完,谁都不能是唯一会干的人"。
① L2 正则化(权重衰减 / 岭回归):在损失函数后面加上所有权重的平方和乘以一个系数 λ。它的效果是把所有权重往零的方向压,但不会真的压到零——因为平方项的梯度是 2w,w 越接近零梯度越小,压不动了。几何上它相当于把权重约束在一个球体内。
② L1 正则化(Lasso):加的是权重绝对值之和。它的关键区别是会让一部分权重精确变成 0,从而自动完成特征选择。原因在于绝对值的梯度是常数 ±λ,不管 w 多小这个"推力"都不衰减,所以能一路把它推到零。几何上它的约束区域是一个菱形,菱形的尖角正好落在坐标轴上,最优解容易落到尖角处(某个坐标为 0)。
L1 vs L2 的直观区别
原始损失: L(w)
加 L2 之后: L(w) + λ · Σ wᵢ² ← 岭回归 Ridge
加 L1 之后: L(w) + λ · Σ |wᵢ| ← Lasso
两个都加: L(w) + λ₁·Σ|wᵢ| + λ₂·Σwᵢ² ← 弹性网 Elastic Net
梯度对比:
L2 的惩罚梯度 = 2λw → w 越小,推力越小 → 无限接近 0 但不为 0
L1 的惩罚梯度 = λ·sign(w) → 推力恒定 → 能把 w 精确推到 0
约束区域的几何形状(二维情况):
L2:圆形 ○ L1:菱形 ◇
最优解落在圆上任意点 最优解容易落在菱形的尖角
(两个权重都非零) (尖角在坐标轴上 → 一个权重为 0)
实践选择:
想做特征筛选、想要稀疏解、特征里有大量无用的 → L1
特征都有点用、只想让权重别太极端 → L2(默认首选)
高维且特征之间高度相关 → Elastic Net
λ 太大 → 欠拟合(权重被压得太狠);λ 太小 → 没效果。用验证集调。
③ Dropout(Hinton 组 2012 年提出,2014 年正式发表):训练时每一次前向传播都随机让一部分神经元输出置零(常用比例 0.2–0.5),测试时全部启用但把输出按比例缩放。它的机制有两种解读,都很有启发性。第一种:它迫使网络不依赖任何单一路径——因为任何神经元随时可能"缺席",网络必须建立冗余的、分布式的表示。第二种更漂亮:Dropout 相当于在训练指数级多个共享权重的子网络,测试时对它们做了一次近似的集成平均。所以它本质上是一个极其廉价的集成学习。
Dropout 有两个使用细节。它不能加在输出层(会直接破坏预测)。它和 BatchNorm 一起用时常出问题——因为 Dropout 改变了激活值的方差统计,会和 BatchNorm 的统计量打架,实践中常见的做法是二者只用一个,或者把 Dropout 放在 BatchNorm 之后。另外在 Transformer 里 Dropout 依然是标配(注意力权重和前馈层都会加)。
④ 早停(Early Stopping):监控验证集指标,连续 N 轮(patience)不再改善就停止训练,并回滚到最好的那个权重。它是性价比最高的正则化手段——零额外计算、零额外超参数调优、还顺带省了训练时间。有一个漂亮的理论结果:在某些条件下,早停在数学上等价于 L2 正则化。它的唯一要求是必须有一个干净的验证集,而且要注意不要用测试集来做早停——那就是在测试集上过拟合。
⑤ 数据增强(Data Augmentation):不改模型,而是"造"出更多训练数据。图像上是翻转、旋转、裁剪、色彩抖动、Mixup(把两张图按比例混合,标签也按同比例混合)、CutMix(把一张图的一块贴到另一张上)。文本上是同义词替换、回译(中→英→中)、随机删词、用大模型改写。语音上是加噪声、变速、变调。它的本质是把你对任务的先验知识注入模型——你做水平翻转,是在告诉模型"猫左右翻转还是猫"。所以增强必须符合任务语义:数字识别里绝不能水平翻转,因为 2 翻过来不是 2;医学影像里绝不能随意旋转,因为器官的解剖朝向是有意义的诊断信息。
⑥ 集成(Ensemble):训多个模型取平均或投票。理论依据很硬:如果 M 个模型的误差彼此独立,平均之后方差会降到原来的 1/M。实践中误差不可能完全独立,但只要模型之间有差异(不同随机种子、不同架构、不同数据子集、不同特征子集),就能拿到实实在在的提升。这是 Kaggle 竞赛的必杀技,也是集成方法几乎垄断竞赛榜首的原因。它的代价很直白:推理成本乘以 M,所以线上服务常用知识蒸馏——先训一个集成的"教师",再让一个小模型学习教师的软标签输出,最终部署那个小模型。
| 手段 | 核心机制 | 额外训练成本 | 额外推理成本 | 要不要调超参 |
|---|---|---|---|---|
| 更多数据 | 让模型没法全背下来 | 高(要买/要标) | 无 | 不用 |
| L1 正则 | 把无用权重精确压成 0 | 几乎为零 | 无(还可能变快) | 要调 λ |
| L2 正则 | 限制权重幅度不要太极端 | 几乎为零 | 无 | 要调 λ |
| Dropout | 随机失活,等效廉价集成 | 略增(收敛变慢) | 无 | 要调比例 |
| 早停 | 在过拟合发生前刹车 | 负的(省时间) | 无 | 只需 patience |
| 数据增强 | 注入不变性先验 | 中(每轮要做变换) | 无 | 要选增强策略 |
| 集成 | 多模型误差相互抵消 | ×M | ×M | 要选模型组合 |
| Label Smoothing | 不让模型对标签 100% 自信 | 零 | 无 | 要调平滑系数 |
| BatchNorm | 稳定分布,附带轻微正则效果 | 略增 | 略增 | 基本不用 |
实操上的推荐顺序是:先做早停(免费)→ 再加 L2 和数据增强(便宜且几乎总有效)→ 深度模型加 Dropout → 还不够就想办法搞更多数据 → 最后才考虑集成。反过来的顺序(一上来就堆集成)是新手常犯的低效路径。
交叉验证:让评估结论不靠运气
这件事简单说就是:只考一次的成绩说明不了什么,得多考几次取平均。0.4% 的差距在 300 个样本上相当于一道题的分差——好比两个学生一次考试差了半分,你能说谁更强吗?换套卷子结论可能就反了。
假设你有 2000 条数据,切出 300 条当测试集。你训了两个模型,A 得 87.3%,B 得 86.9%。A 更好吗?这 0.4% 的差距在 300 个样本上只相当于 1 个样本的差别,完全可能是运气。换一个随机种子重切一次,结论可能翻转。这就是交叉验证要解决的问题:把"一次评估"变成"多次评估取平均加标准差"。
常见的交叉验证变体,用错了会得到完全错误的结论
【K-Fold】最基础:均分 K 份,轮流拿 1 份验证
适用:独立同分布的普通数据
【Stratified K-Fold】分层:保证每折里各类别比例和总体一致
适用:分类任务,特别是类别不平衡时 —— 分类任务应该默认用这个
反例:正样本占 2%,普通 5 折可能某一折里正样本只有个位数
【Group K-Fold】按组切:同一组的样本绝不跨折
适用:同一个病人有多张片子、同一个用户有多条记录
为什么必须:如果同一病人的片子一部分在训练、一部分在验证,
模型可以靠"认出这是张三的骨骼形状"作弊 → 分数虚高
【TimeSeriesSplit】时序切:训练集始终在验证集之前
第 1 折 [训练:1-3月 ][验证:4月]
第 2 折 [训练:1-4月 ][验证:5月]
第 3 折 [训练:1-5月 ][验证:6月]
适用:任何有时间顺序的数据
为什么必须:随机切分会让"未来"进入训练集 = 用未来预测过去
【Nested CV】嵌套:外层评估性能,内层调超参
适用:数据很少且必须给出无偏的性能估计(论文、医学研究)
代价:K_outer × K_inner 次训练,非常贵
报告结果的正确姿势:
不要写 "准确率 87.3%"
要写 "5 折交叉验证准确率 86.8% ± 1.4%"
← 那个 ±1.4% 才是告诉你"87.3 和 86.9 谁更好"这个问题有没有意义
K 该取几?K=5 或 K=10 是业界惯例。K 越大,每次的训练集越大、偏差越小,但计算成本线性上升,且各折之间的重叠度越高、方差估计反而不准。极端情况 K=n(每次只留一个样本验证)叫留一法(LOOCV),偏差最小但计算量爆炸,且方差估计很差,只在样本极少时用。
数据泄漏:最隐蔽、最致命、最难自查
四个字翻译成人话:你偷看了答案,还以为自己会做。打个比方,用"这个人已经在医院住院了"去预测"这个人会不会生病"——线下准得离谱,一上线就归零,因为做预测的那一刻他还没去挂号。判断法则只有一句:预测的那一刻,这个字段真的已经填上了吗?
如果只能记住这一节的一句话,就记这句:线下指标好得离谱时,第一反应必须是"我是不是泄漏了",而不是"我真牛"。数据泄漏(Data Leakage)指的是训练过程中用到了预测时刻不可能获得的信息,它的症状是验证集和测试集分数都极高,上线后效果断崖式下跌。它之所以致命,是因为所有常规的检查手段(交叉验证、独立测试集)都无法发现它——因为泄漏同时污染了训练集和测试集。
| 泄漏类型 | 具体例子 | 为什么是泄漏 |
|---|---|---|
| 标签泄漏(最经典) | 用"是否已退款"预测"是否会退货";用"账号是否已封禁"预测"是否欺诈" | 这些字段是结果发生之后才被写入的,预测时刻还是空的 |
| 时间穿越 | 随机打乱时序数据切分;用"未来 7 天均值"当特征 | 训练集包含了验证期之后的信息 |
| 预处理泄漏 | 先用全量数据算均值/标准差/PCA/编码映射,再切分 | 测试集的统计信息进入了训练流程 |
| 重复样本泄漏 | 同一条记录因数据合并而重复出现,一份在训练一份在测试 | 模型见过一模一样的题目,等于开卷考试 |
| 分组泄漏 | 同一病人的多张 CT、同一用户的多条日志被拆到训练和测试两边 | 模型靠"认出这是同一个个体"作弊 |
| 目标编码泄漏 | 用全量数据算"该类别下的标签均值"当特征 | 特征里直接编码了标签信息,等于把答案抄进题目 |
| 元数据泄漏 | 正样本图片都来自 A 医院(有水印/特定分辨率),负样本来自 B 医院 | 模型学的是"认医院"而不是"认病灶" |
| 测试集反复使用 | 用测试集试了 50 组超参数挑最高分 | 你成了那条把测试集信息搬进模型的信道 |
自查泄漏有一套可操作的检查清单,建议每个项目都过一遍:
【泄漏自查清单】
① 时间点测试(最有力的一招)
对每一个特征问:做预测的那一刻,这个字段真的已经有值了吗?
写下每个字段的"可获得时间",和预测时刻做对比。
② 单特征 AUC 扫描
逐个特征单独训一个模型看 AUC。
如果某单个特征的 AUC 就超过 0.9 —— 高度可疑,去查它的来源。
③ 特征重要性异常检查
如果某个你没预料到会很重要的特征排名第一,去查它。
典型:一个叫 "status_code" 或 "update_time" 的字段莫名其妙成了头号特征。
④ 线下线上一致性对比
线下 AUC 0.95、线上 0.68 —— 这个巨大落差 99% 是泄漏或
训练-服务偏斜,不是"线上环境比较复杂"。
⑤ 重复样本检查
对所有特征做 hash 去重,看训练集和测试集有没有交集。
数据合并(join)之后特别容易产生重复。
⑥ 用 Pipeline 而不是手写预处理
把 scaler、encoder、imputer 全部装进 Pipeline,
让框架保证每一折内独立 fit —— 从代码层面消灭预处理泄漏。
最后讲一个真实教训的类型化案例,它同时包含了多种泄漏。2020 年之后有大量"用 CT/X 光筛查新冠"的研究被发表,其中相当一部分后来被系统性综述认定为不可用于临床。原因五花八门但高度雷同:阳性样本来自疫情期成人住院数据,阴性样本来自疫情前的儿童数据集——模型学的是"分辨成人和儿童的骨骼";阳性和阴性来自不同医院的不同设备——模型学的是"认设备的成像风格";同一病人的多张片子被拆到训练和测试两边——分组泄漏。这些模型的报告准确率都在 95% 以上,全部无法在真实临床中复现。它是这一节所有内容最好的注脚:过拟合和数据泄漏不是学术界的细枝末节,它们是决定一个 AI 系统到底有用还是有害的分水岭。
三个术语的关系
把这三个词串起来,就是 ML 的完整工作流:
1. 收集数据
每个样本 = 特征(输入)+ 标签(答案)。
2. 选模型、训练
模型从特征里学规律,尽量预测对标签。
3. 评估
在没见过的数据上测试——如果表现差,可能是过拟合了。
4. 调优
加数据 / 减复杂度 / 正则化——直到模型在训练集和测试集上都表现好。
特征是输入,标签是答案,过拟合是"背题不学真"。这三个词是 ML 的 ABC——后面所有章节都会用到。
第 4 章 · 机器学习篇到此结束。下一章我们进入"神经网络"——深度学习的心脏。