文章目录
前言
信用卡欺诈是典型的类别极不平衡的分类问题:正常交易(负样本)远多于欺诈交易(正样本)。如果直接用原始数据训练模型,模型会倾向于把所有样本都预测为"正常",导致召回率极低。本文使用下采样(Undersampling)平衡正负样本,并用逻辑回归建模,最后通过调整分类阈值提升欺诈交易的召回率。
一、运行环境
需要安装以下 Python 库:
c
pip install pandas matplotlib numpy scikit-learn
pandas:用于读取、处理表格数据
matplotlib :本文未绘图,但保留通用导入格式
numpy :用于数组、数学计算
pylab:导入绘图中文支持库
数据集:creditcard.csv(可从 Kaggle 获取,包含时间、金额、V1~V28 PCA 特征以及类别标签 Class)
二、完整代码
c
import pandas as pd
import numpy as np
from sklearn.model_selection import train_test_split,cross_val_score
from sklearn.linear_model import LogisticRegression
from sklearn import metrics
from sklearn.preprocessing import StandardScaler
data = pd.read_csv(r'./creditcard.csv')
scaler = StandardScaler()
a = data[['Amount']]
data['Amount'] = scaler.fit_transform(data[['Amount']])
data = data.drop(['Time'],axis=1)
X_whole = data.drop('Class',axis=1)
y_whole = data.Class
x_train_w ,x_text_w , y_train_w , y_text_w = \
train_test_split(X_whole,y_whole,test_size=0.2,random_state=0)
x_train_w['Class'] = y_train_w
data_train = x_train_w
# 进行下采样
pg = data_train[data_train['Class'] == 0]
ng = data_train[data_train['Class'] == 1]
pg = pg.sample(len(ng))
data_c = pd.concat([ pg, ng])
X = data_c.drop('Class',axis=1)
y = data_c.Class
scores = []
c_param_range = [0.01,0.1,1,10,100]
for i in c_param_range:
lr = LogisticRegression(C = i,solver='lbfgs',max_iter=1000)
score = cross_val_score(lr, X, y, cv=5,scoring='recall')
score_mean = sum(score)/len(score)
scores.append(score_mean)
print(score_mean)
best_c = c_param_range[np.argmax(scores)]
lr = LogisticRegression(C= best_c,max_iter=1000)
lr.fit(X,y)
train_predicted = lr.predict(X)
print(metrics.classification_report(y,train_predicted))
test_predicted_big = lr.predict(x_text_w)
print(metrics.classification_report(y_text_w,test_predicted_big))
thresholds = [0.1,0.2,0.3,0.4,0.5,0.6,0.7,0.8,0.9]
recalls = []
for i in thresholds:
y_predict_proba = lr.predict_proba(x_text_w)
y_predict_proba = pd.DataFrame(y_predict_proba)
y_predict_proba = y_predict_proba.drop([0],axis=1)
y_predict_proba[y_predict_proba[[1]] > i] = 1
y_predict_proba[y_predict_proba[[1]] <= i] = 0
recall = metrics.recall_score(y_text_w,y_predict_proba[1])
recalls.append(recall)
print("{} Recall metric in the testing dataset:{:.3f}".format(i,recall))
三、代码逐行解释
- 导入所需库
c
import pandas as pd # 数据处理
import numpy as np # 数组计算
from sklearn.model_selection import train_test_split, cross_val_score # 划分数据集 + 交叉验证
from sklearn.linear_model import LogisticRegression # 逻辑回归模型
from sklearn import metrics # 模型评估指标
from sklearn.preprocessing import StandardScaler # 标准化
- 加载数据
c
data = pd.read_csv(r'./creditcard.csv') # 读取 CSV 文件到 DataFrame
- 特征工程:标准化 Amount 列
c
scaler = StandardScaler() # 创建标准化器对象
a = data[['Amount']] # 取出 Amount 列(注意双括号保持二维形状)
data['Amount'] = scaler.fit_transform(data[['Amount']]) # 拟合 + 转换,将 Amount 变为均值为0、方差为1的分布
为什么标准化?
因为Amount 和其他特征(V1~V28)尺度差异大,影响梯度下降收敛速度和正则化效果。
- 删除不需要的特征
c
data = data.drop(['Time'], axis=1)
删除 Time 列(axis=1 表示列),因为时间与欺诈相关性弱且噪声大
- 划分特征矩阵 X 和标签 y
c
X_whole = data.drop('Class', axis=1) # 删除 Class 列,剩下的都是特征
y_whole = data.Class # Class 列为标签(1-欺诈,0-正常)
- 划分训练集和测试集(原始不平衡数据)
c
x_train_w, x_text_w, y_train_w, y_text_w = train_test_split(
X_whole, y_whole, test_size=0.2, random_state=0
)
# 训练集占80%,测试集占20%;random_state 保证每次运行划分相同
- 构造带标签的训练集,用于下采样
c
x_train_w['Class'] = y_train_w # 将标签列添加到训练特征中,方便按类别筛选
data_train = x_train_w # 重命名为 data_train
- 下采样(Undersampling)------平衡正负样本
c
pg = data_train[data_train['Class'] == 0] # 筛选出正常交易(多数类)
ng = data_train[data_train['Class'] == 1] # 筛选出欺诈交易(少数类)
pg = pg.sample(len(ng)) # 从正常交易中随机抽取与欺诈交易相同数量的样本
data_c = pd.concat([pg, ng]) # 将抽取的正常样本和全部欺诈样本合并成平衡数据集
下采样原理:让正负样本数量相等,避免模型偏向多数类。
- 提取下采样后的特征和标签
c
X = data_c.drop('Class', axis=1) # 平衡数据集的特征
y = data_c.Class # 平衡数据集的标签
- 交叉验证选择最佳正则化参数 C
c
scores = [] # 存储每个 C 对应的平均召回率
c_param_range = [0.01, 0.1, 1, 10, 100] # 待测试的 C 值
for i in c_param_range:
lr = LogisticRegression(C=i, solver='lbfgs', max_iter=1000) # 创建逻辑回归模型,C 为正则化强度的倒数
score = cross_val_score(lr, X, y, cv=5, scoring='recall') # 5折交叉验证,评估指标为召回率
score_mean = sum(score) / len(score) # 计算平均召回率
scores.append(score_mean)
print(score_mean)
C 越大,正则化越弱,模型可能过拟合;C 越小,正则化越强,可能欠拟合。
scoring='recall' 因为欺诈检测更关注"抓住多少真正的欺诈"(召回率)。
- 选出最佳 C 值并用全部平衡数据训练最终模型
c
best_c = c_param_range[np.argmax(scores)] # 找到平均召回率最高的 C
lr = LogisticRegression(C=best_c, max_iter=1000) # 创建最终模型
lr.fit(X, y) # 用全部平衡训练集拟合模型
- 在训练集(平衡数据)上评估模型
c
train_predicted = lr.predict(X) # 预测训练集
print(metrics.classification_report(y, train_predicted)) # 输出精确率、召回率、F1 等
- 在原始测试集(不平衡数据)上评估模型
c
test_predicted_big = lr.predict(x_text_w) # 预测原始测试集
print(metrics.classification_report(y_text_w, test_predicted_big))
原始测试集仍然是不平衡的,模型直接预测时可能召回率偏低。
- 调整分类阈值提升召回率
c
thresholds = [0.1, 0.2, 0.3, 0.4, 0.5, 0.6, 0.7, 0.8, 0.9] # 尝试不同的概率阈值
recalls = []
for i in thresholds:
y_predict_proba = lr.predict_proba(x_text_w) # 获取每个样本属于类别0和1的概率
y_predict_proba = pd.DataFrame(y_predict_proba) # 转为 DataFrame
y_predict_proba = y_predict_proba.drop([0], axis=1) # 只保留类别1(欺诈)的概率列
# 根据阈值 i 将概率转换为类别标签
y_predict_proba[y_predict_proba[[1]] > i] = 1
y_predict_proba[y_predict_proba[[1]] <= i] = 0
recall = metrics.recall_score(y_text_w, y_predict_proba[1]) # 计算召回率
recalls.append(recall)
print("{} Recall metric in the testing dataset:{:.3f}".format(i, recall))
为什么调阈值?
默认阈值 0.5 时,模型只有非常确信时才判断为欺诈。降低阈值(如 0.3)会让更多样本被判为正类,从而提高召回率(但可能误判正常交易)。
四、知识点解读
1.为什么要做下采样?
信用卡欺诈数据是典型的不平衡数据:
正常交易:99.8%
欺诈交易:0.2%
模型会直接全部预测为正常交易,准确率极高,但完全无法检测欺诈。
下采样:减少多数类(正常交易)数量,让两类样本数量相等,让模型学会识别欺诈特征。
- 为什么用召回率 (recall) 评估模型?
欺诈检测的核心要求:宁可误判,不可漏判
召回率:所有真实欺诈中,被模型检测出来的比例
召回率越高,漏判的欺诈交易越少
总结
本文通过一个完整的代码实例,展示了信用卡欺诈检测的完整流程:
数据标准化与清洗--->
下采样处理类别不平衡--->
交叉验证选择逻辑回归的正则化参数--->
模型训练与评估--->
后处理调整阈值优化召回率
每一行代码都配有详细注释,非常适合新手理解机器学习项目的工程实现。你可以在此基础上尝试上采样(SMOTE)、集成方法(XGBoost)或其它阈值调优技巧