intel校企合作合作课程——信用卡交易诈骗检测
AI作业一:信用卡交易诈骗检测
问题描述
2021 年,与信用卡欺诈相关的损失超过 120 亿美元,同比增长近 11%。就重大财务损失、信任和信誉而言,这是银行、客户和商户面临的一个令人担忧的问题。
电子商务相关欺诈一直在以约 13% 的复合年增长率 (CAGR) 增加。由于欺诈性信用卡交易急剧增加,在交易时检测欺诈行为对于帮助消费者和银行非常重要。机器学习可以通过训练信用卡交易模型,然后使用这些模型更快、更准确地检测欺诈交易,在预测欺诈方面发挥至关重要的作用。
预期解决方案
我们期待您参考英特尔的类似实现方案,基于提供的信用卡交易数据,训练一个或多个机器学习模型,有效预测信用卡交易是否为欺诈交易——这里推理时间和二分类准确度(F1分数)将作为评分的主要依据。
要求
需要使用 英特尔® oneAPI AI分析工具包中数据分析和机器学习相关的优化库,基于参考资料的方案,进行信用卡交易欺诈检测的实现,建议跟官方开源的机器学习库运行性能进行分析对比。
数据集
1、该数据集包含欧洲持卡人 2013 年 9 月通过信用卡进行的交易。
2、该数据集显示了两天内发生的交易,其中 284,807 笔交易中有 492 笔欺诈。数据集高度不平衡,正类(欺诈)占所有交易的 0.172%。
3、它仅包含PCA (Principal Component Analysis) 变换结果的数字输入变量。不幸的是,由于保密问题,我们无法提供有关数据的原始特征和更多背景信息。特征 V1、V2、…V28 是通过 PCA 获得的主要成分,唯一未通过 PCA 转换的特征是“时间”和“金额”。特征“时间”包含数据集中每个事务与第一个事务之间经过的秒数。特征“金额”是交易金额,该特征可用于示例相关的成本敏感学习。特征“类别”是响应变量,如果存在欺诈,则取值 1,否则取值 0。
4、考虑到类别不平衡率,我们建议使用精确率-召回率曲线下面积 (Area Under Precision-Recall (PR) Curve即AUPRC) 来测量准确度。混淆矩阵精度对于不平衡分类没有意义。
数据集 (内容相同,压缩格式及分发渠道不同)
Zip格式 https://filerepo.idzcn.com/dataset/assignment_1.zip
Tar格式 https://filerepo.idzcn.com/dataset/assignment_1.tar
百度网盘:https://pan.baidu.com/s/1KNdSIwQHiDrJLT-5K-sPmA 提取码:fly8
项目简介
通过提供的信用卡交易诈骗数据集,对数据首先进行数据探索、数据预处理、利用机器学习建立模型,并进行欺诈数据的检测。
1、数据探索:查看数据集规模、数据类型、缺失值情况以及统计性描述。
2、数据预处理:处理缺失值、平衡数据样本
3、利用机器学习建立模型:支持向量机分类、集成学习
数据探索
# !pip install modin dask
import modin.pandas as pd
import os
os.environ["MODIN_ENGINE"] = "dask"
from modin.config import Engine
Engine.put("dask")
Modin是一个Python第三方库,可以通过并行来处理大数据集。其中语法与pandas相近,拥有出色的性能弥补了pandas处理大型数据集的缺陷。
而Dask 是一个用于分析计算的灵活的并行计算库,实现大型多维数据集分析的更快执行以及加速和扩展数据科学制作流程或工作流程的强大工具。
查看数据集
data = pandas.read_csv('./data/creditcard.csv')
print('数据规模:{}\n'.format(data.shape))
display(data.head())
data=data.infer_objects()
data.info()
可以看到目前所有数据的类型都为float64,并且仅有极少部分属性存在缺失。并且如数据集补充中所说的,属性近保留了金额和时间,其他数据均通过主成分分析PCA作了脱敏处理。通过统计正负类样本数量,正常类(Class=0.0)数量为274754,异常类(Class=1.0)数据数量为484,样本极度不平衡,比例为567:1。
查看数据统计性描述
数据可视化
查看数据类别
通过饼状图直观反映数量对比。
import matplotlib.pyplot as plt
def plot_target(target_col):
tmp=data[target_col].value_counts(normalize=True)
target = tmp.rename(index={1:'Class 1',0:'Class 0'})
wedgeprops = {'width':0.5, 'linewidth':10}
plt.figure(figsize=(6,6))
plt.pie(list(tmp), labels=target.index,
startangle=90, autopct='%1.1f%%',wedgeprops=wedgeprops)
plt.title('Label Distribution', fontsize=16)
plt.show()
plot_target(target_col='Class')
箱形图
通过箱形图查看各个属性分散情况。
密度图
密度图的目的是提供数据潜在分布的视觉呈现。它可以帮助您理解数据的形状和分布,并识别任何异常值或离群值。
cat_cols,float_cols=[],['Class']
for col in data.columns:
if data[col].value_counts().count()<10:
cat_cols.append(col)
else:
float_cols.append(col)
l = len(float_cols)
plot_df=data[float_cols]
fig, ax = plt.subplots(10,3, figsize=(30,100))
fig.suptitle('Distribution of Numerical Variables',fontsize=32)
row=0
col=[0,1,2]*10
for i, column in enumerate(plot_df.columns[1:]):
if (i!=0)&(i%3==0):
row+=1
sns.kdeplot(x=column, hue='Class', palette=intel_pal[::-1], hue_order=[1,0],
label=['Class 1','Class 0'], data=plot_df,
fill=True, linewidth=2.5, legend=False, ax=ax[row,col[i]])
ax[row,col[i]].tick_params(left=False, bottom=False)
ax[row,col[i]].set(title='\n\n{}'.format(column), xlabel='', ylabel=('Density' if i%3==0 else ''))
handles, _ = ax[0,0].get_legend_handles_labels()
fig.legend(labels=['Class 1','Class 0'], handles=reversed(handles))
sns.despine(bottom=True, trim=True)
plt.tight_layout()
这里的分布图像没有完全展示,大部分相对比较集中并且成正态分布。
二元分布图
通过二元分布图查看每个特征中所有值的目标概率,从
fig, axs = plt.subplots(10,3, figsize=(30,100))
col=[0,1,2]*10
row=0
# pal=sns.color_palette("GnBu",30)
for i, column in enumerate(data[float_cols].columns[1:]):
if (i!=0)&(i%3==0):
row+=1
df = pd.concat([data[column],data['Class']],axis=1)
df['bins'] = pd.cut(df[column],300)
df['mean'] = df.bins.apply(lambda x: x.mid)
df = df.groupby('mean')[column,'Class'].transform('mean')
df = df.drop_duplicates(subset=[column]).sort_values(by=column)
axs[row,col[i]].plot(df[column],df.Class)
ax[row,col[i]].tick_params(left=False, bottom=False)
ax[row,col[i]].set(title='\n\n{}'.format(column), xlabel='', ylabel=('Target Probabilitity' if i%3==0 else ''))
fig.show()
热力图
通过热力图直观反映属性两两之间的相关性。
corr = plt.subplots(figsize = (30,20),dpi=128)
corr= sns.heatmap(data.corr(method='spearman'),annot=True,square=True)
查看各属性与类别相关性
import plotly.io as pio
pio.renderers.default = "iframe"
def plot_target_corr(corr, target_col):
corr=corr[target_col].sort_values(ascending=False)[1:]
pal=sns.color_palette("RdYlBu",37).as_hex()
pal=[j for i,j in enumerate(pal) if i not in (17,18)]
rgb=['rgba'+str(matplotlib.colors.to_rgba(i,0.8)) for i in pal]
fig=go.Figure()
fig.add_trace(go.Bar(x=corr.index, y=corr, marker_color=rgb,
marker_line=dict(color=pal,width=2),
hovertemplate='%{x} correlation with Target = %{y}',
showlegend=False, name=''))
fig.update_layout(template=temp, title='Feature Correlations with Target (Class)',
yaxis_title='Correlation', margin=dict(b=160), xaxis_tickangle=45)
fig.show()
corr=data.corr()
plot_target_corr(corr=corr, target_col='Class')
从图中可以看到单个属性与类别相关性都不高。
数据预处理
查看重复值和缺失值
处理重复值与缺失值
data_class_na = data[data['Class'].isna()]
na_index = list(data_class_na.index)
data = data.drop(na_index)
data.drop_duplicates(keep='first',inplace=True,ignore_index=True)
missing=data.isna().sum().sum()
duplicates=data.duplicated().sum()
print("\n数据集中有{:,.0f} 缺失值.".format(missing))
print("数据集中有 {:,.0f} 重复值.".format(duplicates))
平衡数据
主要方法
欠采样(Undersampling):从多数类别中随机选择一部分样本,使得多数类别的样本数量与少数类别的样本数量相近。这种方法的优点是简单快捷,但可能会丢失一些有用信息。
过采样(Oversampling):从少数类别中随机复制一些样本,使得少数类别的样本数量与多数类别的样本数量相近。这种方法的优点是可以充分利用数据集,但可能会导致过拟合。
SMOTE(Synthetic Minority Over-sampling Technique)算法:是一种常用的过采样方法,它通过对少数类别样本进行插值生成新的样本来扩充数据集。这种方法可以有效地避免过拟合问题。
混合采样(Mixed Sampling):结合欠采样和过采样的优点,既可以减少数据量,又可以充分利用数据集。可以先进行欠采样,然后再对欠采样后的数据进行过采样。
本文先对异常样本采用了SMOTE算法进行插值生成新的样本来扩充数据集,然后对插值后的异常样本进行了过拟合,对正常样本进行了欠拟合。
SMOTE算法
# 采用smote算法平衡数据
# !pip install imblearn
# !pip install --user imblearn
from imblearn.over_sampling import SMOTE
smote_model = SMOTE(k_neighbors=2,random_state=42,sampling_strategy=1/250)
# imblearn 中过采样接口提供了随机过采样 RandomOverSampler、SMOTE、ADASYN 三种方式,调用方式基本一致。
# SMOTE 只适合处理连续性变量特征,不适合离散型特征。
x_smote,y_smote = smote_model.fit_resample(data.iloc[:,:-1],data['Class'])
df_smote = pd.concat([x_smote, y_smote], axis=1)
df_smote.groupby('Class').count()
未插值之前的异常数据数量为484,通过SMOTE算法进行插值基本上扩充了一倍。
过拟合与欠拟合
先对插值后异常数据进行过拟合然后在单独对正常数据进行欠拟合。
from imblearn.over_sampling import RandomOverSampler
from imblearn.under_sampling import RandomUnderSampler
rus = RandomUnderSampler(random_state=0,sampling_strategy=1/250)
ros = RandomOverSampler(random_state=0,sampling_strategy=1/250)
X_resampled, y_resampled = ros.fit_resample(data.iloc[:,:-1],data['Class'])
df_resampled = pd.concat([X_resampled, y_resampled], axis=1)
X_uresampled, y_uresampled = rus.fit_resample(data.iloc[:,:-1],data['Class'])
df_uresampled = pd.concat([X_uresampled, y_uresampled], axis=1)
df_new = pd.concat([df_smote, df_resampled[df_resampled['Class']==1.0]], axis=0)
df_new = pd.concat([df_new[df_new['Class']==1.0],df_uresampled[df_uresampled['Class']==0.0]])
df_new.groupby('Class').count()
在数据平衡后,正常数据与异常数据的比例大概是53:1。当然还可以尝试不同的平衡策略,并且通过评价指标去选择最好的平衡策略。策略包括选择不同的插值、欠拟合和过拟合比例,还有选择不同插值、欠拟合和过拟合顺序。
模型拟合
解决数据极度不平衡的机器学习问题,我们可以从样本层面去平衡数据,也可以从模型层面采用集成学习去规避样本类别不平衡的问题。在此阶段,我采用了四种方案以f1分数和ROC 曲线下面积 (AUC)作为评价标准进行对比。四种方案为原始数据+SVC,原始数据+XGBClassifier,平衡数据+SVC,平衡数据+XGBClassifier。
首先定义了三个函数,划分数据集、绘制绘制ROC/PR曲线和预测目标分布、绘制预测数据分布
import plotly.io as pio
pio.renderers.default = "iframe"
def prepare_train_test_data(data, target_col, test_size):
scaler = RobustScaler()
X = data.drop(target_col, axis=1)
y = data[target_col]
X_train, X_test, y_train, y_test = train_test_split(X, y, test_size=test_size, random_state=21)
X_train_scaled = scaler.fit_transform(X_train)
X_test_scaled = scaler.transform(X_test)
print("Train Shape: {}".format(X_train_scaled.shape))
print("Test Shape: {}".format(X_test_scaled.shape))
return X_train_scaled, X_test_scaled, y_train, y_test
def plot_model_res(model_name, y_test, y_prob):
intel_pal=['#0071C5','#FCBB13']
color=['#7AB5E1','#FCE7B2']
fpr, tpr, _ = roc_curve(y_test, y_prob)
roc_auc = auc(fpr,tpr)
precision, recall, _ = precision_recall_curve(y_test, y_prob)
auprc = average_precision_score(y_test, y_prob)
fig = make_subplots(rows=1, cols=2,
shared_yaxes=True,
subplot_titles=['Receiver Operating Characteristic<br>(ROC) Curve',
'Precision-Recall Curve<br>AUPRC = {:.3f}'.format(auprc)])
fig.add_trace(go.Scatter(x=np.linspace(0,1,11), y=np.linspace(0,1,11),
name='Baseline',mode='lines',legendgroup=1,
line=dict(color="Black", width=1, dash="dot")), row=1,col=1)
fig.add_trace(go.Scatter(x=fpr, y=tpr, line=dict(color=intel_pal[0], width=3),
hovertemplate = 'True positive rate = %{y:.3f}, False positive rate = %{x:.3f}',
name='AUC = {:.4f}'.format(roc_auc),legendgroup=1), row=1,col=1)
fig.add_trace(go.Scatter(x=recall, y=precision, line=dict(color=intel_pal[0], width=3),
hovertemplate = 'Precision = %{y:.3f}, Recall = %{x:.3f}',
name='AUPRC = {:.4f}'.format(auprc),showlegend=False), row=1,col=2)
fig.update_layout(template=temp, title="{} ROC and Precision-Recall Curves".format(model_name),
hovermode="x unified", width=900,height=500,
xaxis1_title='False Positive Rate (1 - Specificity)',
yaxis1_title='True Positive Rate (Sensitivity)',
xaxis2_title='Recall (Sensitivity)',yaxis2_title='Precision (PPV)',
legend=dict(orientation='v', y=.07, x=.45, xanchor="right",
bordercolor="black", borderwidth=.5))
fig.show()
def plot_distribution(y_prob):
plot_df=pd.DataFrame.from_dict({'Class 0':(len(y_prob[y_prob<=0.5])/len(y_prob))*100,
'Class 1':(len(y_prob[y_prob>0.5])/len(y_prob))*100},
orient='index', columns=['pct'])
fig=go.Figure()
fig.add_trace(go.Pie(labels=plot_df.index, values=plot_df.pct, hole=.45,
text=plot_df.index, sort=False, showlegend=False,
marker=dict(colors=color,line=dict(color=intel_pal,width=2.5)),
hovertemplate = "%{label}: <b>%{value:.2f}%</b><extra></extra>"))
fig.update_layout(template=temp, title='Predicted Target Distribution',width=700,height=450,
uniformtext_minsize=15, uniformtext_mode='hide')
fig.show()
利用原始数据
利用Intel AI Analytics Toolkit中Machine Learning模块中的intel extension for Scikit-learn进行模型拟合。主要是使用了支持向量机分类来进行拟合。
原始数据+SVC
from sklearnex import patch_sklearn
patch_sklearn()
from sklearn.model_selection import train_test_split, StratifiedKFold, GridSearchCV, RandomizedSearchCV
from sklearn.preprocessing import RobustScaler
from sklearn.metrics import roc_auc_score, roc_curve, auc, accuracy_score, f1_score
from sklearn.metrics import precision_recall_curve, average_precision_score
from sklearn.svm import SVC
import warnings
warnings.filterwarnings("ignore")
import time
import warnings
with warnings.catch_warnings():
warnings.simplefilter("ignore")
print("Preparing Train and Test datasets")
X_train, X_test, y_train, y_test = prepare_train_test_data(data=data,
target_col='Class',
test_size=.25)
parameters = {
'class_weight': 'balanced',
'probability': True,
'random_state': 21}
svc = SVC(**parameters)
## Tune Hyperparameters ##
strat_kfold = StratifiedKFold(n_splits=3, shuffle=True, random_state=21)
print("\nTuning hyperparameters..")
grid = {
'C': np.logspace(-1, 1, 5),
'kernel': ['linear', 'poly', 'rbf', 'sigmoid']
}
grid_search = RandomizedSearchCV(svc, param_distributions=grid,
cv=strat_kfold, n_iter=5, scoring='roc_auc',
verbose=1, n_jobs=-1, random_state=21)
start = time.time()
grid_search.fit(X_train, y_train)
end = time.time()
print('模型拟合时间:{:.2f}'.format(end-start))
print("Done!\nBest hyperparameters:", grid_search.best_params_)
print("Best cross-validation AUC: {:.4f}".format(grid_search.best_score_))
svc = grid_search.best_estimator_
svc_prob = svc.predict_proba(X_test)[:,1]
svc_pred = pd.Series(svc.predict(X_test), name='Target')
svc_auc = roc_auc_score(y_test, svc_prob)
svc_f1 = f1_score(y_test, svc_pred)
## Print model results ##
print("\nTest F1 accuracy: {:.2f}%, AUC: {:.5f}".format(svc_f1*100,svc_auc))
plot_model_res(model_name='SVC', y_test=y_test, y_prob=svc_prob)
plot_distribution(svc_prob)
print('预测类别为正常的数量为:{:d}'.format(len(svc_prob[svc_prob<=0.5])))
print('预测类别为异常的数量为:{:d}'.format(len(svc_prob[svc_prob>=0.5])))
原始数据+XGBClassifier
## Prepare Train and Test datasets ##
print("Preparing Train and Test datasets")
X_train, X_test, y_train, y_test = prepare_train_test_data(data=data,
target_col='Class',
test_size=.25)
## Initialize XGBoost model ##
ratio = float(np.sum(y_train == 0)) / np.sum(y_train == 1)
parameters = {'scale_pos_weight': ratio.round(2),
'tree_method': 'hist',
'random_state': 21}
xgb_model = XGBClassifier(**parameters)
## Tune hyperparameters ##
strat_kfold = StratifiedKFold(n_splits=3, shuffle=True, random_state=21)
print("\nTuning hyperparameters..")
grid = {'min_child_weight': [1, 5, 10],
'gamma': [0.5, 1, 1.5, 2, 5],
'max_depth': [3, 4, 5],
}
grid_search = GridSearchCV(xgb_model, param_grid=grid,
cv=strat_kfold, scoring='roc_auc',
verbose=1, n_jobs=-1)
grid_search.fit(X_train, y_train)
print("Done!\nBest hyperparameters:", grid_search.best_params_)
print("Best cross-validation AUC: {:.4f}".format(grid_search.best_score_))
## Convert XGB model to daal4py ##
xgb = grid_search.best_estimator_
daal_model = d4p.get_gbt_model_from_xgboost(xgb.get_booster())
## Calculate predictions ##
daal_prob = d4p.gbt_classification_prediction(nClasses=2,
resultsToEvaluate="computeClassLabels|computeClassProbabilities",
fptype='float').compute(X_test, daal_model).probabilities # or .predictions
xgb_pred = pd.Series(np.where(daal_prob[:,1]>.5, 1, 0), name='Target')
xgb_auc = roc_auc_score(y_test, daal_prob[:,1])
xgb_f1 = f1_score(y_test, xgb_pred)
## Plot model results ##
print("\nTest F1 Accuracy: {:.2f}%, AUC: {:.5f}".format(xgb_f1*100, xgb_auc))
plot_model_res(model_name='XGBoost', y_test=y_test, y_prob=daal_prob[:,1])
plot_distribution(daal_prob[:,1])
print('预测类别为正常的数量为:{:d}'.format(len(daal_prob[:,1][daal_prob[:,1]<=0.5])))
print('预测类别为异常的数量为:{:d}'.format(len(daal_prob[:,1][daal_prob[:,1]>=0.5])))
平衡数据+SVC
平衡数据+XGBClassifier
对比
方案 | F1分数 | AUC |
---|---|---|
原始数据+SVC | 0.2508 | 0.9742 |
原始数据+XGBClassifier | 0.8387 | 0.9808 |
平衡数据+SVC | 0.8786 | 0.9924 |
平衡数据+XGBClassifier | 0.9593 | 0.9972 |
进一步工作
1、可以采用不同的模型拟合
2、采用不同的数据平衡策略,获取更好的模型拟合效果
学习心得
通过本次校企合作课程,我了解到了intel下的oneAPI在机器学习与数据挖掘领域的应用,通过作业实际体验到了intel AI Analytics Toolkit工具实际对于问题解决的提升。在之前参加过intel黑客松比赛,了解了oneAPI在深度学习中的应用,在本次作业中又体验到了oneAPI中的应用。可以说oneAPI完成了人工智能领域的一个巨大贡献,兼顾机器学习和深度学习,为开发者和应用者提供了极大的便利。