§ 4.5 · Section

特征 / 标签 / 过拟合

Features, Labels & Overfitting

机器学习的三个最基础术语——特征、标签、过拟合——理解了它们,你就理解了 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% 的工作量在特征工程上。

原始数据提取的特征为什么有用
用户注册时间注册距今多少天新用户和老用户行为不同
交易时间戳是否周末、是否凌晨盗刷多在凌晨
一段文本长度、感叹号数量、关键词垃圾邮件通常很长、很多感叹号
一张图片颜色直方图、边缘密度猫和狗的纹理不同
Analogy · 做菜 vs 吃菜

特征工程 = 做菜——你把生食材(原始数据)切成丝、片、丁(特征),厨师(模型)才能炒出好菜。
深度学习 = 自动做菜——你直接把整只鸡扔进锅,神经网络自己学会"怎么切"。
这就是深度学习最大的突破:它把特征工程自动化了

四类原始数据的实战处理手法

把特征工程拆开看,现实中的原始数据基本就四类:数值、类别、时间、文本。每一类有自己的一套标准动作,这套动作在银行、电商、广告、风控里天天在用。

这四套动作说白了就是厨房里的四种备菜手法:数值要"改刀改成一样大小"(缩放),类别要"分门别类装进不同的碗"(编码),时间要"看清是早市还是夜市"(拆成星期几、是否凌晨),文本要"从一大堆话里挑出关键那几味"。备菜做得好,后面随便什么锅都能炒出味;备菜马虎,再好的锅也救不回来。

数值特征的处理有四个常见动作。缺失值填补:最简单是填均值或中位数(有离群点时用中位数),更好的做法是额外加一个"是否缺失"的标记列——因为"缺失"这件事本身常常有信息,一个不填写收入的申请人可能真的有隐情。离群点截断:把超过 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 都不怕极值
对数变换 Loglog(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%。这些是全世界被引用最多、被反复清洗过的数据集。

标签噪声的后果不只是"准确率天花板降低",它有三个更隐蔽的影响:

应对标签噪声有一套手段。数据层面:多人标注取共识、用 Kappa 系数监控一致性、对模型损失特别大的样本人工复查(这些往往就是错标样本)、用交叉验证的预测结果反查异常样本。损失函数层面:用对噪声更鲁棒的损失(比如 MAE 类损失比交叉熵更抗噪,因为交叉熵对"高置信度的错误"惩罚趋于无穷,一个错标样本能产生极大的梯度)、或者用 Label Smoothing——把 one-hot 标签 [0,0,1,0] 软化成 [0.02,0.02,0.94,0.02],明确告诉模型"不要 100% 确信任何标签"。训练策略层面:早停(噪声通常在训练后期才被记住)、Co-teaching(两个网络互相挑选"对方看起来学得动"的小损失样本来训练)。

顺带说一个反直觉的正面例子:适度的标签噪声有时反而起到正则化作用,因为它阻止模型对训练集过于自信。但这有严格前提——噪声必须是随机的。如果噪声是系统性的(比如某个标注员总是把"中性"标成"正面"),那它就变成了偏见,会被模型忠实学习并放大,危害远大于随机噪声。

Analogy · 死记硬背 vs 真懂了

想象一下教室里坐着两个学生,期中考试都考了 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大幅降低可能升高串行纠错,所以必须配合正则和早停
特征筛选可能升高降低去掉噪声特征减少可背的东西

看这张表你会发现一个重要结论:只有"加数据"是纯粹的免费午餐,其他手段全是在做交易。这也回答了一个常被问的问题——"为什么大厂的模型效果好"。不完全是因为算法更聪明,很大一部分原因是他们有更多数据,从而能承受更低偏差的复杂模型而不付出方差代价。

怎么防止过拟合

01

更多数据

数据多了,模型没法"全背下来"——被迫学真规律。这是最根本的方法。

02

正则化 · Regularization

给模型参数加"惩罚",不让它变得太极端。L1 / L2 是经典方法。

03

Dropout

训练时随机"关掉"一部分神经元——强迫模型不依赖单一路径。深度学习标配。

04

早停 · Early Stopping

验证误差开始上升就停——不让模型继续"背"下去。

Analogy · 防过拟合 = 防死记硬背

老师防学生死记硬背的方法:
出更多题(更多数据)——题量大了背不完;
出变形题(数据增强)——换个角度考,背题就失效;
限时交卷(早停)——不给你无限时间磨;
随机抽查(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. 调优

加数据 / 减复杂度 / 正则化——直到模型在训练集和测试集上都表现好。

Recap · 收束

特征是输入,标签是答案,过拟合是"背题不学真"。这三个词是 ML 的 ABC——后面所有章节都会用到
第 4 章 · 机器学习篇到此结束。下一章我们进入"神经网络"——深度学习的心脏。

☰ 主页
Xue Hai Wu Ya · § 4.5 · Features, Labels & Overfitting