机器学习——过采样(OverSampling),解决类别不平衡问题,案例:逻辑回归 信用卡欺诈检测

发布于:2025-08-04 ⋅ 阅读:(24) ⋅ 点赞:(0)

下采样:机器学习——下采样(UnderSampling),解决类别不平衡问题,案例:逻辑回归 信用卡欺诈检测-CSDN博客

(完整代码在底部)


解决样本不平衡问题:SMOTE 过采样实战讲解

在实际的机器学习任务中,数据集往往存在类别不平衡问题,尤其是在信用卡欺诈检测、疾病预测等场景中,正样本极其稀少。若不处理这一问题,模型更倾向于预测多数类,导致 recall 值(召回率)偏低。

SMOTE 的核心思想是:不是复制已有少数类样本,而是通过“插值”方式生成新的样本点,使少数类样本在特征空间中更丰富、分布更自然。

说白了就是在每两个同类别的点之间插入新点,也同样是此类别

工作步骤如下:

  1. 找邻居
    对于每一个少数类样本,SMOTE 会在该类中找到若干个(如 5 个)“最近邻”样本。

  2. 随机选择邻居
    从这几个邻居中,随机选一个作为“参考点”。

  3. 插值生成新样本
    在原样本和参考点之间,以一定比例随机插值,生成一个“新的样本点”。
    也就是说,这个新样本不是原样本的复制,也不是邻居样本的复制,而是它们之间的“连线上的点”。

    数学公式如下(对特征向量来说):

    • xi​:原始少数类样本

    • xn​:其某个邻居

    • λ:0~1之间的随机数,决定插值比例

  4. 重复以上过程,直到少数类样本数量和多数类一样多,达到“类别平衡”。

本篇文章将通过信用卡欺诈检测数据集(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

     

读取数据并标准化金额列

# 读取信用卡欺诈数据集
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

原理步骤:

  1. 对每个少数类样本,找到其最近的 k 个同类邻居(默认 k=5)

  2. 随机选取若干邻居点

  3. 在原样本和邻居之间插值生成“合成样本”(就是在两两之间多插入几个数据

  4. 不是复制已有样本,而是生成新的样本点,更丰富、更自然


五、使用过采样数据训练模型

# 使用过采样后的数据重新训练逻辑回归模型
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))


网站公告

今日签到

点亮在社区的每一天
去签到