机器学习——过采样(OverSampling),解决类别不平衡问题,案例:逻辑回归 信用卡欺诈检测
下采样:机器学习——下采样(UnderSampling),解决类别不平衡问题,案例:逻辑回归 信用卡欺诈检测-CSDN博客
(完整代码在底部)
解决样本不平衡问题:SMOTE 过采样实战讲解
在实际的机器学习任务中,数据集往往存在类别不平衡问题,尤其是在信用卡欺诈检测、疾病预测等场景中,正样本极其稀少。若不处理这一问题,模型更倾向于预测多数类,导致 recall 值(召回率)偏低。
SMOTE 的核心思想是:不是复制已有少数类样本,而是通过“插值”方式生成新的样本点,使少数类样本在特征空间中更丰富、分布更自然。
说白了就是在每两个同类别的点之间插入新点,也同样是此类别
工作步骤如下:
-
找邻居:
对于每一个少数类样本,SMOTE 会在该类中找到若干个(如 5 个)“最近邻”样本。 -
随机选择邻居:
从这几个邻居中,随机选一个作为“参考点”。 -
插值生成新样本:
在原样本和参考点之间,以一定比例随机插值,生成一个“新的样本点”。
也就是说,这个新样本不是原样本的复制,也不是邻居样本的复制,而是它们之间的“连线上的点”。数学公式如下(对特征向量来说):
-
xi:原始少数类样本
-
xn:其某个邻居
-
λ:0~1之间的随机数,决定插值比例
-
-
重复以上过程,直到少数类样本数量和多数类一样多,达到“类别平衡”。
本篇文章将通过信用卡欺诈检测数据集(creditcard.csv),演示如何使用 SMOTE 方法对训练集进行过采样,并对比模型在过采样前后的性能表现差异。
一、引入所需模块
import numpy as np
import pandas as pd
from sklearn import metrics
from sklearn.linear_model import LogisticRegression
from sklearn.model_selection import train_test_split
from sklearn.preprocessing import StandardScaler
# from imblearn.over_sampling import SMOTE # 过采样
二、数据预处理
信用卡欺诈检测数据集 creditcard.csv
-
数据来源信用卡欺诈检测实战数据集_数据集-阿里云天池
https://tianchi.aliyun.com/dataset/101562?accounttraceid=c1258603818f44d6a57fe125248cc969rkgu
-
样本总数:284,807 条
-
特征数:30(28个匿名特征 + 金额
Amount
+ 时间Time
) -
目标变量:
Class
(0=正常交易,1=欺诈交易)
读取数据并标准化金额列
# 读取信用卡欺诈数据集
data = pd.read_csv('creditcard.csv')# 对“Amount”金额列进行标准化(均值为0,标准差为1)
scaler = StandardScaler()
data['Amount'] = scaler.fit_transform(data[['Amount']])# 删除无关的“Time”列
data = data.drop("Time", axis=1)
特征与标签划分
# 分离特征和目标变量
X = data.drop("Class", axis=1) # 特征变量
y = data.Class # 目标变量(是否欺诈)
划分训练集与测试集
# 将数据划分为训练集和测试集,比例为7:3
X_train, X_test, y_train, y_test = train_test_split(X, y, test_size=0.3, random_state=100)
三、原始数据模型训练与评估
# 使用原始训练数据训练逻辑回归模型
model = LogisticRegression()
model.fit(X_train, y_train)
查看原始类别分布
# 查看原始数据中正负样本(欺诈/正常交易)的数量
labels_count = pd.value_counts(data['Class'])
print("原数据组成:\n", labels_count)
结果:
在原始测试集上评估模型
# 在原始测试集上评估原始模型的性能
print("‘原数据训练得到的模型’ 对 ‘原数据的测试集’ 的测试:")
predictions_test = model.predict(X_test)
print(metrics.classification_report(y_test, predictions_test))
这种情况下,模型可能在类别为“1”(欺诈)的召回率较低,常见表现为:
-
precision 较高,但 recall 偏低
-
模型倾向于预测为正常交易
四、使用 SMOTE 进行过采样
进行训练集过采样
# ======================== 使用 SMOTE 过采样 ========================
from imblearn.over_sampling import SMOTE
# 对训练数据进行过采样,使正负样本数量相同
oversampler = SMOTE(random_state=100)
X_train_os, y_train_os = oversampler.fit_resample(X_train, y_train)
⚠ 注意:测试集不要过采样,保持原始分布才能真实评估模型性能。
查看过采样后的数据组成
# 查看过采样后训练数据中正负样本的数量
labels_count_under = pd.value_counts(y_train_os)
print("下采样数据组成:\n", labels_count_under)
示例输出:199014为284807的%70
原理步骤:
-
对每个少数类样本,找到其最近的 k 个同类邻居(默认 k=5)
-
随机选取若干邻居点
-
在原样本和邻居之间插值生成“合成样本”(就是在两两之间多插入几个数据)
-
不是复制已有样本,而是生成新的样本点,更丰富、更自然
五、使用过采样数据训练模型
# 使用过采样后的数据重新训练逻辑回归模型
model_os = LogisticRegression(C=10, penalty='l2', max_iter=1000)
model_os.fit(X_train_os, y_train_os)
在原始测试集上评估新模型
# 在原始测试集上评估过采样模型的性能
print("‘过采样数据训练得到的模型’ 对 ‘原数据的测试集’ 的测试:")
predictions_test = model_os.predict(X_test)
print(metrics.classification_report(y_test, predictions_test))
评估对比与说明
指标/类别 | 原模型 | 过采样模型 | 解读 |
---|---|---|---|
类别 1 Recall(召回率) | 0.65 | 0.88 ✅ | 过采样显著提高了识别能力 |
类别 1 Precision(精准率) | 0.77 ✅ | 0.06 ❌ | 误报大幅增加,很多 0 被错判为 1(宁杀错) |
类别 1 F1-score(平衡指标) | 0.70 ✅ | 0.11 ❌ | 整体准确性下降 |
类别 0 Recall | 1.00 ✅ | 0.98 | 正常交易略受影响 |
总体 Accuracy | 1.00 ✅ | 0.98 | 正确率略下降 |
Macro avg Recall | 0.82 | 0.93 ✅ | 两类平均召回提升 |
六、结果对比分析
指标 | 原始模型 | SMOTE过采样模型 |
---|---|---|
Precision(1类) | 较高 | 较高 |
Recall(1类) | 较低 | 明显提升 |
F1-score(1类) | 偏低 | 显著提升 |
Accuracy | 接近1 | 可能略有下降 |
🎯 结论:SMOTE 有效提高了模型对少数类(欺诈行为)的识别能力,在保持总体准确率的同时显著改善了 recall 和 F1-score,是处理不平衡问题的有效方法之一。
七、总结与建议
-
SMOTE(Synthetic Minority Over-sampling Technique)通过生成新的少数类样本来平衡数据分布,是一种优于简单复制的过采样方式。
-
在不平衡数据场景中,评估指标应以 recall/F1-score 为主,而非 accuracy。
-
SMOTE 适合用于训练集,而不应作用于测试集。
-
逻辑回归 + SMOTE 是一种简单有效的基线方案,可用于模型初步构建与对比。
完整代码:
import numpy as np
import pandas as pd
from sklearn import metrics
from sklearn.linear_model import LogisticRegression
from sklearn.model_selection import train_test_split, cross_val_score
from sklearn.preprocessing import StandardScaler# 读取信用卡欺诈数据集
data = pd.read_csv('creditcard.csv')# 对“Amount”金额列进行标准化(均值为0,标准差为1)
scaler = StandardScaler()
data['Amount'] = scaler.fit_transform(data[['Amount']])# 删除无关的“Time”列
data = data.drop("Time", axis=1)# 分离特征和目标变量
X = data.drop("Class", axis=1) # 特征变量
y = data.Class # 目标变量(是否欺诈)# 将数据划分为训练集和测试集,比例为7:3
X_train, X_test, y_train, y_test = train_test_split(X, y, test_size=0.3, random_state=100)# 使用原始训练数据训练逻辑回归模型
model = LogisticRegression()
model.fit(X_train, y_train)# 查看原始数据中正负样本(欺诈/正常交易)的数量
labels_count = pd.value_counts(data['Class'])
print("原数据组成:\n", labels_count)# 在原始测试集上评估原始模型的性能
print("‘原数据训练得到的模型’ 对 ‘原数据的测试集’ 的测试:")
predictions_test = model.predict(X_test)
print(metrics.classification_report(y_test, predictions_test))# ======================== 使用 SMOTE 过采样 ========================
from imblearn.over_sampling import SMOTE
# 对训练数据进行过采样,使正负样本数量相同
oversampler = SMOTE(random_state=100)
X_train_os, y_train_os = oversampler.fit_resample(X_train, y_train)# 使用过采样后的数据重新训练逻辑回归模型
model_os = LogisticRegression(C=10, penalty='l2', max_iter=1000)
model_os.fit(X_train_os, y_train_os)# 查看过采样后训练数据中正负样本的数量
labels_count_under = pd.value_counts(y_train_os)
print("过采样数据组成:\n", labels_count_under)# 在原始测试集上评估过采样模型的性能
print("‘过采样数据训练得到的模型’ 对 ‘原数据的测试集’ 的测试:")
predictions_test = model_os.predict(X_test)
print(metrics.classification_report(y_test, predictions_test))