文章目录
-
- [1. 平衡与不平衡:差的是标签比例](#1. 平衡与不平衡:差的是标签比例)
- [2. 准确率为什么会骗人](#2. 准确率为什么会骗人)
- [3. 训练难在哪:小批量里看不见少数类](#3. 训练难在哪:小批量里看不见少数类)
- [4. 对策:多数类降采样,再升权](#4. 对策:多数类降采样,再升权)
-
- [4.1 第一步:降采样多数类](#4.1 第一步:降采样多数类)
- [4.2 第二步:给多数类升权](#4.2 第二步:给多数类升权)
- [4.3 工程上更常见的等价做法](#4.3 工程上更常见的等价做法)
- [5. 汽车示例:1000 辆里只有 20 辆高效](#5. 汽车示例:1000 辆里只有 20 辆高效)
- [6. 动手:看清准确率陷阱与 class_weight](#6. 动手:看清准确率陷阱与 class_weight)
- [7. 降采样升权的手算直觉](#7. 降采样升权的手算直觉)
- [8. 能力边界与常见误区](#8. 能力边界与常见误区)
-
- [8.1 适用边界](#8.1 适用边界)
- [8.2 常见误区](#8.2 常见误区)
- [9. 关键术语速查](#9. 关键术语速查)
- [10. 延伸阅读](#10. 延伸阅读)
- [11. 小结](#11. 小结)
摘要 :真实分类很少「正负对半」。欺诈、故障、罕见病、高效车里的极少数样本,都会让数据集变成类别不平衡(Class-imbalanced) 。这时「永远猜多数类」也能刷出极高准确率,模型却什么都没学会。本文讲清平衡与不平衡的差别、训练难在哪、为何不能只看准确率,以及多数类降采样 + 升权等常用对策。延续汽车高效分类,并衔接到第 10、11 篇的 Precision / Recall / AUC。
1. 平衡与不平衡:差的是标签比例
二分类里,标签通常是正类 / 负类。
| 类型 | 含义 | 例子 |
|---|---|---|
| 类别平衡 | 正负数量大致相当 | 235 辆高效 vs 247 辆非高效 |
| 类别不平衡 | 某一类远多于另一类 | 990 辆非高效 vs 10 辆高效 |
现实中,不平衡远比平衡常见:信用卡欺诈可能不到 0.1%;罕见病检测更极端。
约定两个名字:
- 多数类(Majority class):出现更多的那一类
- 少数类(Minority class):出现更少、往往更「值钱」的那一类

专栏前几篇用 7 辆车演示时,高效 / 非高效大致对半,属于轻度场景。本篇故意把比例拉极端,才能看清问题。
2. 准确率为什么会骗人
设 1000 辆车里:非高效 990,高效 10。有一个「笨模型」:永远预测非高效。
Accuracy = 990 1000 = 99 % \text{Accuracy} = \frac{990}{1000} = 99\% Accuracy=1000990=99%
看起来很强,但对业务关键的高效车:
Recall efficient = T P T P + F N = 0 0 + 10 = 0 \text{Recall}_{\text{efficient}} = \frac{TP}{TP+FN} = \frac{0}{0+10} = 0 Recallefficient=TP+FNTP=0+100=0
一辆高效车都没找回来。

第 10 篇已强调「不能只看准确率」;不平衡只是把这句话放大到刺眼。评估时应优先看:
| 指标 | 不平衡时的角色 |
|---|---|
| Precision | 报「高效」时有多准 |
| Recall | 真高效找回了多少 |
| F1 | 二者折中 |
| AUC / PR 曲线 | 看排序能力(第 11 篇) |
| Accuracy | 仅作参考,单独汇报很危险 |

3. 训练难在哪:小批量里看不见少数类
训练时模型要同时学两件事:
- 每一类长什么样(特征与标签的对应)
- 每一类有多常见(真实比例)
普通训练把两件事缠在一起。轻度不平衡时,每个 batch 里通常还能碰到少数类;重度不平衡时就不行了。
例如:多数类 200,少数类 2。若 batch_size=20,很多个 batch 一个少数类都没有 ;即便 batch_size=100,平均也只有约 1 个少数类,信号极弱。

结果是:模型很快学会「猜多数类就好」,梯度几乎不被少数类拉动。
4. 对策:多数类降采样,再升权
一种经典、略反直觉的两步法:对多数类降采样(Downsampling) ,再对多数类升权(Upweighting),把「长什么样」和「有多常见」拆开学。

4.1 第一步:降采样多数类
故意少用一部分多数类样本,让训练集比例不那么悬殊。
例:原始 99% 多数类、1% 少数类。对多数类按因子 25 降采样后,训练集约变成 80% / 20%。每个 batch 更容易见到少数类,模型能更快搞清「少数类长什么样」,收敛也往往更快。
注意:验证集、测试集不要为了好看去人为平衡------评估必须反映真实世界比例。
4.2 第二步:给多数类升权
降采样制造了一个「假世界」:少数类看起来比真实更常见,容易引入预测偏差(Prediction Bias)(第 12 篇相关)。
矫正办法:把多数类样本的损失乘上同一个因子。若降采样因子是 25,则多数类损失 ×25------错分一个多数类,等价于错了 25 次。这样模型在学完「长什么样」之后,仍能感到「多数类其实更常见」。
降采样 / 升权因子当作超参数,像学习率一样在验证集上试。
4.3 工程上更常见的等价做法
| 做法 | 直觉 |
|---|---|
class_weight='balanced' |
按频率自动给少数类更大损失权重 |
| 少数类过采样 / SMOTE | 复制或合成少数类(注意过拟合) |
| 调分类阈值 | 概率模型输出后,不机械用 0.5(第 09 篇) |
| 换评估与业务阈值 | 先定「更怕漏报还是误报」 |
sklearn 里对逻辑回归直接设 class_weight,往往比手写降采样更省事;原理与「改损失权重」同一家族。
和专栏前文怎么拼:第 05~07 篇的逻辑回归仍然适用,只是损失从「每条样本一视同仁」变成「少数类喊得更响」;第 09 篇的阈值 T T T 在不平衡时更要当超参调,而不是默认 0.5。
5. 汽车示例:1000 辆里只有 20 辆高效
构造一个轻度~中度不平衡玩具集(真实欺诈会更极端):
| 集合 | 非高效 | 高效 | 比例 |
|---|---|---|---|
| 全体 | 980 | 20 | 2% 正类 |
| 若乱报准确率 | 永远猜 0 → 98% | --- | 虚高 |
业务若关心「尽可能找出高效车」,应盯 Recall,并接受 Precision 可能下降;若关心「推荐列表别掺水」,则更盯 Precision。没有唯一正确指标,只有与代价匹配的指标。
再极端一点:若 10000 辆里只有 5 辆高效,任何复杂模型都可能先输给「规则 + 人工复核」。机器学习不是不平衡问题的唯一解,有时先把少数类样本采够比调参更值钱------这和第 19 篇「数量够不够」是同一条线。

推荐闭环:
text
统计正负比例 → 选定 Precision/Recall/AUC
→ 仅在训练集做加权或降采样
→ 用验证集调权重因子与阈值
→ 在原始比例的测试集上验收
6. 动手:看清准确率陷阱与 class_weight
python
import numpy as np
from sklearn.datasets import make_classification
from sklearn.model_selection import train_test_split
from sklearn.linear_model import LogisticRegression
from sklearn.metrics import accuracy_score, precision_score, recall_score, f1_score, roc_auc_score
# 合成不平衡数据:约 5% 正类
X, y = make_classification(
n_samples=4000,
n_features=8,
n_informative=5,
weights=[0.95, 0.05],
flip_y=0.01,
random_state=42,
)
X_train, X_test, y_train, y_test = train_test_split(
X, y, test_size=0.25, stratify=y, random_state=0
)
print("pos rate:", y.mean())
# A) 无加权
clf_plain = LogisticRegression(max_iter=1000)
clf_plain.fit(X_train, y_train)
p = clf_plain.predict(X_test)
proba = clf_plain.predict_proba(X_test)[:, 1]
# B) 类别加权(少数类损失更大)
clf_bal = LogisticRegression(max_iter=1000, class_weight="balanced")
clf_bal.fit(X_train, y_train)
p_b = clf_bal.predict(X_test)
proba_b = clf_bal.predict_proba(X_test)[:, 1]
def report(name, y_true, y_pred, y_proba):
print(name, {
"acc": round(accuracy_score(y_true, y_pred), 3),
"precision": round(precision_score(y_true, y_pred, zero_division=0), 3),
"recall": round(recall_score(y_true, y_pred, zero_division=0), 3),
"f1": round(f1_score(y_true, y_pred, zero_division=0), 3),
"auc": round(roc_auc_score(y_true, y_proba), 3),
})
report("plain", y_test, p, proba)
report("balanced", y_test, p_b, proba_b)
# C) 「永远猜多数类」基线
baseline = np.zeros_like(y_test)
print("always_neg acc=", accuracy_score(y_test, baseline))
print("always_neg recall=", recall_score(y_test, baseline, zero_division=0))
你会看到:always_neg 的准确率接近正类稀有度的「镜像」(约 95%),召回却是 0;class_weight='balanced' 通常会抬高召回,精确率可能下降------这是权衡,不是实现写错了。
stratify=y 保证划分后各集合正类比例接近,避免「测试集碰巧几乎没有正类」导致指标乱跳(与第 18、19 篇的同分布要求一致)。
若你想手动模拟「降采样 + 升权」,可以先对多数类随机抽样,再用 sample_weight 把多数类权重乘回因子 k k k。class_weight='balanced' 是同一思想的快捷封装:按训练集频率自动算权重,少写几行样板代码。
7. 降采样升权的手算直觉
设多数类降采样因子 k = 25 k=25 k=25:
- 训练集中多数类条数变为原来的 1 / 25 1/25 1/25
- 每条保留的多数类样本,损失乘 k = 25 k=25 k=25
则「见到少数类的频率」上升,同时「多数类在损失里的总分量」大致回到真实世界的稀缺关系。不必死记公式,记住目标即可:
batch 里要能看见少数类;损失里要记得多数类其实更多。
也可以从预测偏差角度理解:只降采样、不升权,模型会以为正类没那么稀有,输出概率整体偏高或偏低(取决于你把哪一类当正类)。升权就是把被拿掉的多数类「在损失里补回来」,减轻第 12 篇那种系统性偏差。
若 k k k 太大:多数类剩太少,方差变大,模型可能不稳定;若 k k k 太小:batch 里仍很难见到少数类。所以 k k k 要和验证集上的 Recall / F1 一起选,而不是拍脑袋。
8. 能力边界与常见误区
8.1 适用边界
- 轻度不平衡(如 40/60)有时只需换指标 + 调阈值,不必强行重采样。
- 极端稀有事件还可能需要异常检测、规则系统或更多少数类采集,而不是只调
class_weight。 - 多分类不平衡同理,但要按类分别看召回,不能只看总准确率。
8.2 常见误区
| 误区 | 正解 |
|---|---|
| 准确率 99% = 模型很好 | 先看多数类基线与少数类召回 |
| 把测试集也采样成 50/50 | 评估失真;只改训练集 |
| 过采样少数类到爆炸 | 易过拟合重复样本 |
| 阈值永远 0.5 | 不平衡时往往要按验证集重选 T T T |
| 只优化 AUC 不管业务 | AUC 好仍可能在工作点上 Recall 不够 |
| 加权后 Precision 下降就回退 | 先看业务更怕哪类错误 |
| 正类太少就放弃分层划分 | 越不平衡越要用 stratify,否则某折可能零正类 |
「指标好看」和「业务可用」之间,不平衡问题最容易制造错觉:同一模型,你把正类定义翻过来,准确率几乎不变,但 Precision/Recall 的含义完全对调。汇报时务必写清:正类是谁、代价是什么。
9. 关键术语速查
| 术语 | 一句话解释 |
|---|---|
| 类别不平衡 | 各类样本数量差距显著 |
| 多数类 / 少数类 | 更多 / 更少的那一类 |
| 降采样 | 训练时少用部分多数类样本 |
| 升权 | 增大某类样本在损失中的权重 |
| 类别权重 | 按类设置损失乘数(如 class_weight) |
| 分层划分 | stratify,保持各集合类比例接近 |
10. 延伸阅读
| 资源 | 适合看什么 |
|---|---|
| sklearn class_weight | LogisticRegression(..., class_weight='balanced') |
| sklearn 分类指标 | precision / recall / F1 / ROC AUC |
| 专栏第 10 篇 | 为何不能只看准确率 |
| 专栏第 11 篇 | 排序能力与阈值无关的视角 |
| 专栏第 12 篇 | 预测偏差与校准直觉 |
11. 小结
类别不平衡时,准确率很容易被多数类抬上天。先认清多数类 / 少数类,再用 Precision、Recall、F1、AUC 评估;训练上通过降采样、升权或 class_weight 让模型既见得到少数类,又不忘记真实比例。验证与测试保持原始分布,只在训练集上做重平衡。
text
看比例 → 选对指标 → 训练集加权/降采样 → 验证集调阈值 → 原比例测试集验收
下一篇回到本单元的模型侧:讲模型复杂度、过拟合与损失曲线怎么读,并把第 07 篇的 L2、早停放回完整图景里。复杂度过高会放大不平衡场景下的「背少数噪声样本」风险,和本篇的加权策略需要一起看。
系列导航:
- 上一篇:【机器学习】(19)------ 数据特性与标签
- 下一篇(预告):模型复杂度、L2 与损失曲线解读
如果本篇对你有帮助,欢迎点赞、收藏、关注博主,机器学习专栏持续更新中,下次更新不迷路。