【机器学习】(20)—— 类别不平衡

文章目录

    • [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. 训练难在哪:小批量里看不见少数类

训练时模型要同时学两件事:

  1. 每一类长什么样(特征与标签的对应)
  2. 每一类有多常见(真实比例)

普通训练把两件事缠在一起。轻度不平衡时,每个 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、早停放回完整图景里。复杂度过高会放大不平衡场景下的「背少数噪声样本」风险,和本篇的加权策略需要一起看。

系列导航


如果本篇对你有帮助,欢迎点赞、收藏、关注博主,机器学习专栏持续更新中,下次更新不迷路。

相关推荐
Bode_20021 小时前
知识捕获工具与业务流程模型
人工智能·制造
yyyyyuanxian1 小时前
一次 ChatGPT Plus 订阅经历:网页端支付受限后,我在 iPhone 内购页完成了订阅
人工智能·chatgpt·iphone·chatgpt plus
明志数科1 小时前
具身智能数据标准化:从碎片化接口到统一工作组的技术路径
人工智能·算法·机器学习
IT_陈寒1 小时前
Vite的静态资源路径搞得我头秃,原来这样配才对
前端·人工智能·后端
xd1855785551 小时前
SQL语句生成-基于鸿蒙的AI SQL语句生成应用开发实践
人工智能·sql·华为·harmonyos·鸿蒙
快乐非自愿1 小时前
2026 IDC认证低代码平台TOP7|制造业/政务场景可直接复用
人工智能·低代码·制造·政务
旖-旎1 小时前
LeetCode 494:目标和(动态规划/01背包问题)—— 题解
c++·算法·leetcode·动态规划·01背包
土星云SaturnCloud1 小时前
智慧港口边缘计算:基于土星云边缘计算设备的三级架构设计与实战
服务器·人工智能·ai·边缘计算
XS0301061 小时前
Spring AI第一课
数据库·人工智能·spring