检测类别不平衡:类别分布与基线陷阱
您将计算类别频率,揭示不平衡数据集中的虚拟分类器陷阱,并确认准确率在此场景下是具有误导性的指标。
检测类别不平衡:类别分布与基线陷阱 是 CoddyKit 上的免费 Machine Learning Academy 课时。 这是第 1 节课,共 4 节。 你可以在下方免费阅读本课时的完整内容 — 然后在浏览器中使用内置代码编辑器和全天候 AI 导师进行实践。 这是 Machine Learning Academy 学习路径的一部分,你的进度在网页和 CoddyKit 应用中同步。 Machine Learning Academy 课程共包含 4 节课。
什么是类别不平衡?
当某个类别在数据集中占据绝大多数时,就会出现类别不平衡。在欺诈检测中,欺诈交易可能只占所有记录的 0.1%;在医学诊断中,罕见疾病可能每 1000 名患者中仅影响 1 人。如果模型始终预测多数类,它可以达到 99.9% 的准确率,却完全忽略罕见但关键的类别,因此在这些场景中,准确率会成为极具误导性的指标。
衡量类别分布
训练任何模型之前,请使用 pd.Series(y).value_counts() 或 np.bincount(y) 检查类别分布。一个实用的汇总统计量是不平衡比率,即多数类样本数与少数类样本数之比。比率高于 10:1 通常被视为不平衡;高于 100:1 则属于严重不平衡,需要使用专门的技术。
import numpy as np
import pandas as pd
# Simulate imbalanced binary classification dataset
np.random.seed(0)
y = np.array([0] * 950 + [1] * 50) # 95% negative, 5% positive
counts = pd.Series(y).value_counts()
print('Class counts:\n', counts)
print()
print('Class proportions:\n', counts / len(y))
print()
print('Imbalance ratio:', counts[0] / counts[1])不平衡数据中的准确率陷阱
在负类样本占 95% 的数据集中,一个始终预测多数类(负类)的模型无需学到任何有用信息,就能达到 95% 的准确率。这就是准确率陷阱。模型完全没有检测正类样本(也就是您真正关心的类别)的能力,但它的准确率看起来却很高。因此,不平衡数据集需要使用准确率以外的指标。
from sklearn.dummy import DummyClassifier
from sklearn.metrics import accuracy_score, classification_report
import numpy as np
from sklearn.model_selection import train_test_split
from sklearn.datasets import make_classification
X, y = make_classification(n_samples=1000, weights=[0.95, 0.05],
random_state=42, n_features=10)
X_train, X_test, y_train, y_test = train_test_split(X, y, random_state=42)
# Always predict majority class
dummy = DummyClassifier(strategy='most_frequent')
dummy.fit(X_train, y_train)
y_pred = dummy.predict(X_test)
print('Accuracy:', accuracy_score(y_test, y_pred).round(4))
print()
print(classification_report(y_test, y_pred))为什么 DummyClassifier 基线很重要
使用 strategy='most_frequent' 的 DummyClassifier 是最低可接受基线。任何真正的模型都必须在有实际意义的层面上超过它——不仅要超过其准确率,还要在重要指标(精确率、召回率、F1 或 AUC-ROC)上胜出。如果您的真实模型只比这个基线略好,那么它很可能也学会了忽略少数类。
from sklearn.linear_model import LogisticRegression
from sklearn.dummy import DummyClassifier
from sklearn.metrics import classification_report
from sklearn.preprocessing import StandardScaler
from sklearn.datasets import make_classification
from sklearn.model_selection import train_test_split
X, y = make_classification(n_samples=1000, weights=[0.95, 0.05], random_state=42)
X_train, X_test, y_train, y_test = train_test_split(X, y, random_state=42)
X_train_s = StandardScaler().fit_transform(X_train)
X_test_s = StandardScaler().fit_transform(X_test)
lr = LogisticRegression().fit(X_train_s, y_train)
print('--- Logistic Regression ---')
print(classification_report(y_test, lr.predict(X_test_s)))不平衡数据中的精确率和召回率
对于不平衡问题,最有信息量的两个指标是:精确率 = TP / (TP + FP),即所有预测为正类的样本中,实际为正类的有多少?召回率 = TP / (TP + FN),即所有实际为正类的样本中,模型捕获了多少?在欺诈检测中,召回率至关重要(不要漏掉欺诈);在垃圾邮件过滤中,精确率更重要(不要误判正常邮件)。
from sklearn.metrics import precision_score, recall_score, f1_score
from sklearn.linear_model import LogisticRegression
from sklearn.preprocessing import StandardScaler
from sklearn.datasets import make_classification
from sklearn.model_selection import train_test_split
X, y = make_classification(n_samples=1000, weights=[0.95, 0.05], random_state=42)
X_train, X_test, y_train, y_test = train_test_split(X, y, random_state=42)
X_train_s = StandardScaler().fit_transform(X_train)
X_test_s = StandardScaler().fit_transform(X_test)
lr = LogisticRegression().fit(X_train_s, y_train)
y_pred = lr.predict(X_test_s)
print(f'Precision: {precision_score(y_test, y_pred):.4f}')
print(f'Recall: {recall_score(y_test, y_pred):.4f}')
print(f'F1-Score: {f1_score(y_test, y_pred):.4f}')ROC-AUC:与阈值无关的评估
ROC-AUC 会在所有可能的决策阈值下评估模型,并衡量所得曲线下的面积。AUC 为 0.5 表示随机水平,1.0 表示完美,0.9 以上表示优秀。与准确率不同,AUC 不受类别不平衡影响,因为它评估的是模型的排序能力,而不是在固定阈值下进行预测的能力。对于大多数不平衡二分类任务,AUC 都是推荐优先使用的主要指标。
from sklearn.metrics import roc_auc_score
from sklearn.linear_model import LogisticRegression
from sklearn.preprocessing import StandardScaler
from sklearn.datasets import make_classification
from sklearn.model_selection import train_test_split
X, y = make_classification(n_samples=1000, weights=[0.95, 0.05], random_state=42)
X_train, X_test, y_train, y_test = train_test_split(X, y, random_state=42)
sc = StandardScaler()
X_train_s = sc.fit_transform(X_train)
X_test_s = sc.transform(X_test)
lr = LogisticRegression().fit(X_train_s, y_train)
proba = lr.predict_proba(X_test_s)[:, 1]
print('ROC-AUC:', roc_auc_score(y_test, proba).round(4))可视化类别不平衡
简单的类别频数条形图就能直观地向利益相关者展示不平衡情况。请将其作为标准实践,包含在探索性数据分析笔记本中。如果您使用 Pandas DataFrame,还应检查是否存在由数据收集偏差造成的隐藏不平衡,而不是现实世界中的真实稀有性。
import matplotlib.pyplot as plt
import numpy as np
y = np.array([0] * 950 + [1] * 50)
classes, counts = np.unique(y, return_counts=True)
plt.bar(['Negative (0)', 'Positive (1)'], counts, color=['#2196F3', '#F44336'])
plt.ylabel('Count')
plt.title('Class Distribution (95:5 imbalance)')
for i, c in enumerate(counts):
plt.text(i, c + 5, str(c), ha='center', fontweight='bold')
plt.show()使用分层拆分保留比例
拆分不平衡数据集时,请在 train_test_split 中使用 stratify=y。如果不进行分层,随机因素可能会将所有少数类样本分到同一个子集中,使训练或评估失去意义。分层拆分可以确保训练集和测试集具有与原始数据集相同的类别比例。
from sklearn.model_selection import train_test_split
import numpy as np
y = np.array([0] * 950 + [1] * 50)
X = np.random.randn(1000, 5)
X_train, X_test, y_train, y_test = train_test_split(
X, y, test_size=0.2, stratify=y, random_state=42
)
print('Train class ratio:', (y_train == 1).mean().round(4))
print('Test class ratio:', (y_test == 1).mean().round(4))
print('Expected ratio:', (y == 1).mean().round(4))用于交叉验证的分层 K 折
同样地,请使用 StratifiedKFold(或者只需向 cross_val_score 传入 cv=5——scikit-learn 会对分类器自动使用 StratifiedKFold),以确保每个折叠都保持原始类别比例。对于少数类样本较少的情况,普通 KFold 可能会生成不含任何正类样本的折叠,从而导致错误或产生误导性指标。
from sklearn.model_selection import StratifiedKFold, cross_val_score
from sklearn.linear_model import LogisticRegression
from sklearn.preprocessing import StandardScaler
from sklearn.pipeline import Pipeline
import numpy as np
y = np.array([0] * 950 + [1] * 50)
X = np.random.randn(1000, 5)
pipe = Pipeline([('sc', StandardScaler()), ('lr', LogisticRegression())])
skf = StratifiedKFold(n_splits=5)
scores = cross_val_score(pipe, X, y, cv=skf, scoring='roc_auc')
print(f'Stratified CV AUC: {np.mean(scores):.4f} +/- {np.std(scores):.4f}')精确率-召回率曲线
对于高度不平衡的数据,精确率-召回率(PR)曲线通常比 ROC 曲线提供更多信息。PR 曲线展示所有阈值下 y 轴上的精确率和 x 轴上的召回率。PR 曲线下面积(AP 分数)范围为 0 到 1,并且能够敏锐反映少数类的表现;当多数类占绝对优势时,ROC-AUC 可能会掩盖这一点。
from sklearn.metrics import precision_recall_curve, average_precision_score
from sklearn.linear_model import LogisticRegression
from sklearn.preprocessing import StandardScaler
from sklearn.datasets import make_classification
from sklearn.model_selection import train_test_split
import matplotlib.pyplot as plt
X, y = make_classification(n_samples=1000, weights=[0.95, 0.05], random_state=42)
X_train, X_test, y_train, y_test = train_test_split(X, y, stratify=y, random_state=42)
sc = StandardScaler()
X_train_s = sc.fit_transform(X_train)
X_test_s = sc.transform(X_test)
lr = LogisticRegression().fit(X_train_s, y_train)
proba = lr.predict_proba(X_test_s)[:, 1]
precision, recall, _ = precision_recall_curve(y_test, proba)
ap = average_precision_score(y_test, proba)
plt.plot(recall, precision, label=f'AP={ap:.3f}')
plt.xlabel('Recall')
plt.ylabel('Precision')
plt.title('Precision-Recall Curve')
plt.legend()
plt.show()建模前记录不平衡情况
最佳实践是:在每个分类项目开始时创建一份不平衡报告。记录各类别的数量、比例以及基线虚拟分类器的准确率。这样可以设定合理预期,并促使团队在训练任何模型之前,就成功指标达成一致。一个准确率为 95% 的欺诈检测模型通常毫无价值——相关方需要提前了解这一点。
import numpy as np
from sklearn.dummy import DummyClassifier
from sklearn.metrics import accuracy_score, roc_auc_score
y = np.array([0] * 950 + [1] * 50)
X = np.random.randn(1000, 5)
dummy = DummyClassifier(strategy='most_frequent').fit(X, y)
dummy_acc = accuracy_score(y, dummy.predict(X))
dummy_auc = roc_auc_score(y, dummy.predict_proba(X)[:, 1])
print('=== Imbalance Report ===')
print(f'Class 0: {(y==0).sum()} ({(y==0).mean():.1%})')
print(f'Class 1: {(y==1).sum()} ({(y==1).mean():.1%})')
print(f'Imbalance ratio: {(y==0).sum()/(y==1).sum():.0f}:1')
print(f'Dummy accuracy: {dummy_acc:.4f}')
print(f'Dummy AUC: {dummy_auc:.4f}')
print('Recommended metric: ROC-AUC or Precision-Recall AUC')快速检查
检验您对本课所讲类别不平衡和基线陷阱的理解。
课程回顾
在本课中,您学到了:类别不平衡会使准确率成为具有误导性的指标——只预测多数类的模型也能获得非常高的准确率;始终与 DummyClassifier 基线进行比较,确保模型学到的不只是最简单的结果;对于不平衡数据集,应使用 ROC-AUC 或精确率-召回率 AUC 作为主要指标。接下来,我们将应用 SMOTE 和随机过采样来增加少数类样本。
用 AI 导师学习 Python — 免费
在浏览器中编写并运行真实代码,获得全天候 AI 导师的即时帮助,并在网页或应用中继续学习。
- 课程
- 30
- 课程
- 120
常见问题解答
「检测类别不平衡:类别分布与基线陷阱」课时是免费的吗?
是的 — 「检测类别不平衡:类别分布与基线陷阱」的完整文本可在网页上免费阅读。要进行交互式练习(内置代码编辑器和全天候 AI 导师)并解锁 Machine Learning Academy 课程的其余内容,请升级到 CoddyKit PRO。 Machine Learning Academy 课程共包含 4 节课。
「检测类别不平衡:类别分布与基线陷阱」这节课中我会学到什么?
您将计算类别频率,揭示不平衡数据集中的虚拟分类器陷阱,并确认准确率在此场景下是具有误导性的指标。 你通过在浏览器中直接运行的动手代码来练习 Machine Learning Academy,全天候 AI 导师会在你学习这节课的过程中回答你的问题。
学习 Machine Learning Academy 需要有经验吗?
无需任何先前经验。CoddyKit 上的 Machine Learning Academy 课程适合初学者到高级学习者,你可以从这里开始或从头开始,按照自己的节奏学习。 这是第 1 节课,共 4 节。
「检测类别不平衡:类别分布与基线陷阱」课时需要多长时间?
大多数 CoddyKit 课程大约需要 5–10 分钟。每节课都很精短且互动,所以你能稳步进步,并在网页和应用中从离开的地方继续。
我能在这节 Machine Learning Academy 课中编写并运行代码吗?
能。每节 Machine Learning Academy 课都包含内置代码编辑器,你可以在浏览器中直接编写并运行真实代码,并获得即时 AI 反馈 — 无需本地设置。
此课程中的所有课时
- 检测类别不平衡:类别分布与基线陷阱
- 随机过采样与 SMOTE
- 随机欠采样与 ClusterCentroids
- 类别权重与阈值移动