Python分析信用卡反欺詐!騙我程序員,不存在的
前言:
本文研究的是大數據量(284807條數據)下模型選擇的問題,也參考了一些文獻,但大多不夠清晰,因此吐血整理本文,希望對大家有幫助;
本文試著從數據分析師的角度,設想“拿到數據該如何尋找規律、選哪種模型來構建反欺詐模型?”的角度來分析,以業務導向為主,不深究算法原理;
下一篇文章會說明數據結構極度不平衡的情況下,該如何修正數據集、如何調整參數。
數據來源及項目概況
數據是從kaggle上看到的項目,具體鏈接如下:
https://www.kaggle.com/mlg-ulb/creditcardfraud
獲取本例數據的,可在上述項目詳情鏈接中下載數據。
數據集包含歐洲持卡人于2013年9月通過信用卡進行的交易。該數據集提供兩天內發生的交易,其中在284,807筆交易中有492起欺詐行為。
數據集非常不平衡,負面類別(欺詐)占所有交易的0.172%。
它只包含數值輸入變量,這是PCA變換的結果。不幸的是,由于保密問題,我們無法提供有關數據的原始特征和更多背景信息。特征V1,V2,... V28是用PCA獲得的主要組件,唯一沒有用PCA轉換的特征是'Time'和'Amount'。
- “時間”包含每個事務與數據集中第一個事務之間經過的秒數。
- '金額'是交易金額,該特征可以用于依賴于例子的成本敏感性學習。
- “Class”是響應變量,在欺詐的情況下其值為1,否則為0。
2、準備并初步查看數據集
- # 導入包
- import numpy as np
- import pandas as pd
- import matplotlib.pyplot as plt
- import matplotlib.gridspec as gridspec
- import seaborn as sns; plt.style.use('ggplot')
- import sklearn
- from sklearn.preprocessing import StandardScaler
- from sklearn.model_selection import train_test_split
- from sklearn.utils import shuffle
- from sklearn.metrics import confusion_matrix
- from sklearn.manifold import TSNE
- pass
- # 倒入并查看數據
- crecreditcard_data=pd.read_csv('./creditcard.csv')
- crecreditcard_data.shape,crecreditcard_data.info()
- <class 'pandas.core.frame.DataFrame'>
- RangeIndex: 284807 entries, 0 to 284806
- Data columns (total 31 columns):
- Time 284807 non-null float64
- V1 284807 non-null float64
- V2 284807 non-null float64
- V3 284807 non-null float64
- V4 284807 non-null float64
- V5 284807 non-null float64
- V6 284807 non-null float64
- V7 284807 non-null float64
- V8 284807 non-null float64
- V9 284807 non-null float64
- V10 284807 non-null float64
- V11 284807 non-null float64
- V12 284807 non-null float64
- V13 284807 non-null float64
- V14 284807 non-null float64
- V15 284807 non-null float64
- V16 284807 non-null float64
- V17 284807 non-null float64
- V18 284807 non-null float64
- V19 284807 non-null float64
- V20 284807 non-null float64
- V21 284807 non-null float64
- V22 284807 non-null float64
- V23 284807 non-null float64
- V24 284807 non-null float64
- V25 284807 non-null float64
- V26 284807 non-null float64
- V27 284807 non-null float64
- V28 284807 non-null float64
- Amount 284807 non-null float64
- Class 284807 non-null int64
- dtypes: float64(30), int64(1)
- memory usage: 67.4 MB
- ((284807, 31), None)
- crecreditcard_data.describe()
- pass
- crecreditcard_data.head()
- pass
- # 看看欺詐與非欺詐的比例如何
- count_classes=pd.value_counts(crecreditcard_data['Class'],sort=True).sort_index()
- # 統計下具體數據
- count_classes.value_counts()
- # 也可以用count_classes[0],count_classes[1]看分別數據
- 284315 1
- 492 1
- Name: Class, dtype: int64
- count_classes.plot(kind='bar')
- plt.show()
0代表正常,1代表欺詐,二者數量嚴重失衡,極度不平衡,根本不在一個數量級上。
3、欺詐與時間序列分布關系
- # 查看二者的描述性統計,與時間的序列分布關系
- print('Normal')
- print(crecreditcard_data.
- Time[crecreditcard_data.Class == 0].describe())
- print('-'*25)
- print('Fraud')
- print(crecreditcard_data.
- Time[crecreditcard_data.Class == 1].describe())
- Normal
- count 284315.000000
- mean 94838.202258
- std 47484.015786
- min 0.000000
- 25% 54230.000000
- 50% 84711.000000
- 75% 139333.000000
- max 172792.000000
- Name: Time, dtype: float64
- -------------------------
- Fraud
- count 492.000000
- mean 80746.806911
- std 47835.365138
- min 406.000000
- 25% 41241.500000
- 50% 75568.500000
- 75% 128483.000000
- max 170348.000000
- Name: Time, dtype: float64
- f,(ax1,ax2)=plt.subplots(2,1,sharex=True,figsize=(12,6))
- bins=50
- ax1.hist(crecreditcard_data.Time[crecreditcard_data.Class == 1],bins=bins)
- ax1.set_title('欺詐(Fraud))',fontsize=22)
- ax1.set_ylabel('交易量',fontsize=15)
- ax2.hist(crecreditcard_data.Time[crecreditcard_data.Class == 0],bins=bins)
- ax2.set_title('正常(Normal',fontsize=22)
- plt.xlabel('時間(單位:秒)',fontsize=15)
- plt.xticks(fontsize=15)
- plt.ylabel('交易量',fontsize=15)
- # plt.yticks(fontsize=22)
- plt.show()
欺詐與時間并沒有必然聯系,不存在周期性;
正常交易有明顯的周期性,有類似雙峰這樣的趨勢。
4、欺詐與金額的關系和分布情況
- print('欺詐')
- print(crecreditcard_data.Amount[crecreditcard_data.Class ==1].describe())
- print('-'*25)
- print('正常交易')
- print(crecreditcard_data.Amount[crecreditcard_data.Class==0].describe())
- 欺詐
- count 492.000000
- mean 122.211321
- std 256.683288
- min 0.000000
- 25% 1.000000
- 50% 9.250000
- 75% 105.890000
- max 2125.870000
- Name: Amount, dtype: float64
- -------------------------
- 正常交易
- count 284315.000000
- mean 88.291022
- std 250.105092
- min 0.000000
- 25% 5.650000
- 50% 22.000000
- 75% 77.050000
- max 25691.160000
- Name: Amount, dtype: float64
- f,(ax1,ax2)=plt.subplots(2,1,sharex=True,figsize=(12,6))
- bins=30
- ax1.hist(crecreditcard_data.Amount[crecreditcard_data.Class == 1],bins=bins)
- ax1.set_title('欺詐(Fraud)',fontsize=22)
- ax1.set_ylabel('交易量',fontsize=15)
- ax2.hist(crecreditcard_data.Amount[crecreditcard_data.Class == 0],bins=bins)
- ax2.set_title('正常(Normal)',fontsize=22)
- plt.xlabel('金額($)',fontsize=15)
- plt.xticks(fontsize=15)
- plt.ylabel('交易量',fontsize=15)
- plt.yscale('log')
- plt.show()
金額普遍較低,可見金額這一列的數據對分析的參考價值不大。
5、查看各個自變量(V1-V29)與因變量的關系
看看各個變量與正常、欺詐之間是否存在聯系,為了更直觀展示,通過distplot圖來逐個判斷,如下:
- features=[x for x in crecreditcard_data.columns
- if x not in ['Time','Amount','Class']]
- plt.figure(figsize=(12,28*4))
- gs =gridspec.GridSpec(28,1)
- import warnings
- warnings.filterwarnings('ignore')
- for i,cn in enumerate(crecreditcard_data[v_features]):
- ax=plt.subplot(gs[i])
- sns.distplot(crecreditcard_data[cn][crecreditcard_data.Class==1],bins=50,color='red')
- sns.distplot(crecreditcard_data[cn][crecreditcard_data.Class==0],bins=50,color='green')
- ax.set_xlabel('')
- ax.set_title('直方圖:'+str(cn))
- plt.savefig('各個變量與class的關系.png',transparent=False,bbox_inches='tight')
- plt.show()
紅色表示欺詐,綠色表示正常。
- 兩個分布的交叉面積越大,欺詐與正常的區分度最小,如V15;
- 兩個分布的交叉面積越小,則該變量對因變量的影響越大,如V14。
下面我們看看各個單變量與class的相關性分析,為更直觀展示,直接作圖,如下:
- # 各個變量的矩陣分布
- crecreditcard_data.hist(figsize=(15,15),bins=50)
- plt.show()

6、三種方法建模并分析
本部分將應用邏輯回歸、隨機森林、支持向量SVM三種方法建模分析,分別展開如下:
準備數據:
- # 先把數據分為欺詐組和正常組,然后按比例生產訓練和測試數據集
- # 分組
- Fraud=crecreditcard_data[crecreditcard_data.Class == 1]
- Normal=crecreditcard_data[crecreditcard_data.Class == 0]
- # 訓練特征集
- x_train=Fraud.sample(frac=0.7)
- x_train=pd.concat([x_train,Normal.sample(frac=0.7)],axis=0)
- # 測試特征集
- x_test=crecreditcard_data.loc[~crecreditcard_data.index.isin(x_train.index)]
- # 標簽集
- y_train=x_train.Class
- y_test=x_test.Class
- # 去掉特征集里的標簽和時間列
- x_train=x_train.drop(['Class','Time'],axis=1)
- x_test=x_test.drop(['Class','Time'],axis=1)
- # 查看數據結構
- print(x_train.shape,y_train.shape,
- '\n',x_test.shape,y_test.shape)
- (199364, 29) (199364,)
- (85443, 29) (85443,)
6.1 邏輯回歸方法
- from sklearn import metrics
- import scipy.optimize as op
- from sklearn.linear_model import LogisticRegression
- from sklearn.cross_validation import KFold,cross_val_score
- from sklearn.metrics import (precision_recall_curve,
- auc,roc_auc_score,
- roc_curve,recall_score,
- classification_report)
- lrmodel = LogisticRegression(penalty='l2')
- lrmodel.fit(x_train, y_train)
- #查看模型
- print('lrmodel')
- print(lrmodel)
- lrmodel
- LogisticRegression(C=1.0, class_weight=None, dual=False, fit_intercept=True,
- intercept_scaling=1, max_iter=100, multi_class='ovr', n_jobs=1,
- penalty='l2', random_state=None, solver='liblinear', tol=0.0001,
- verbose=0, warm_start=False)
- #查看混淆矩陣
- ypred_lr=lrmodel.predict(x_test)
- print('confusion_matrix')
- print(metrics.confusion_matrix(y_test,ypred_lr))
- confusion_matrix
- [[85284 11]
- [ 56 92]]
- #查看分類報告
- print('classification_report')
- print(metrics.classification_report(y_test,ypred_lr))
- classification_report
- precision recall f1-score support
- 0 1.00 1.00 1.00 85295
- 1 0.89 0.62 0.73 148
- avg / total 1.00 1.00 1.00 85443
- #查看預測精度與決策覆蓋面
- print('Accuracy:%f'%(metrics.accuracy_score(y_test,ypred_lr)))
- print('Area under the curve:%f'%(metrics.roc_auc_score(y_test,ypred_lr)))
- Accuracy:0.999216
- Area under the curve:0.810746
6.2 隨機森林模型
- from sklearn.ensemble import RandomForestClassifier
- rfmodel=RandomForestClassifier()
- rfmodel.fit(x_train,y_train)
- #查看模型
- print('rfmodel')
- rfmodel
- rfmodel
- RandomForestClassifier(bootstrap=True, class_weight=None, criterion='gini',
- max_depth=None, max_features='auto', max_leaf_nodes=None,
- min_impurity_decrease=0.0, min_impurity_split=None,
- min_samples_leaf=1, min_samples_split=2,
- min_weight_fraction_leaf=0.0, n_estimators=10, n_jobs=1,
- oob_score=False, random_state=None, verbose=0,
- warm_start=False)
- #查看混淆矩陣
- ypred_rf=rfmodel.predict(x_test)
- print('confusion_matrix')
- print(metrics.confusion_matrix(y_test,ypred_rf))
- confusion_matrix
- [[85291 4]
- [ 34 114]]
- #查看分類報告
- print('classification_report')
- print(metrics.classification_report(y_test,ypred_rf))
- classification_report
- precision recall f1-score support
- 0 1.00 1.00 1.00 85295
- 1 0.97 0.77 0.86 148
- avg / total 1.00 1.00 1.00 85443
- #查看預測精度與決策覆蓋面
- print('Accuracy:%f'%(metrics.accuracy_score(y_test,ypred_rf)))
- print('Area under the curve:%f'%(metrics.roc_auc_score(y_test,ypred_rf)))
- Accuracy:0.999625
- Area under the curve:0.902009
6.3支持向量機SVM
- # SVM分類
- from sklearn.svm import SVC
- svcmodel=SVC(kernel='sigmoid')
- svcmodel.fit(x_train,y_train)
- #查看模型
- print('svcmodel')
- svcmodel
- SVC(C=1.0, cache_size=200, class_weight=None, coef0=0.0,
- decision_function_shape='ovr', degree=3, gamma='auto', kernel='sigmoid',
- max_iter=-1, probability=False, random_state=None, shrinking=True,
- tol=0.001, verbose=False)
- #查看混淆矩陣
- ypred_svc=svcmodel.predict(x_test)
- print('confusion_matrix')
- print(metrics.confusion_matrix(y_test,ypred_svc))
- confusion_matrix
- [[85197 98]
- [ 142 6]]
- #查看分類報告
- print('classification_report')
- print(metrics.classification_report(y_test,ypred_svc))
- classification_report
- precision recall f1-score support
- 0 1.00 1.00 1.00 85295
- 1 0.06 0.04 0.05 148
- avg / total 1.00 1.00 1.00 85443
- #查看預測精度與決策覆蓋面
- print('Accuracy:%f'%(metrics.accuracy_score(y_test,ypred_svc)))
- print('Area under the curve:%f'%(metrics.roc_auc_score(y_test,ypred_svc)))
- Accuracy:0.997191
- Area under the curve:0.519696
7、小結
- 通過三種模型的表現可知,隨機森林的誤殺率最低;
- 不應只盯著精度,有時候模型的精度高并不能說明模型就好,特別是像本項目中這樣的數據嚴重不平衡的情況。舉個例子,我們拿到有1000條病人的數據集,其中990人為健康,10個有癌癥,我們要通過建模找出這10個癌癥病人,如果一個模型預測到了全部健康的990人,而10個病人一個都沒找到,此時其正確率仍然有99%,但這個模型是無用的,并沒有達到我們尋找病人的目的;
- 建模分析時,遇到像本例這樣的極度不平衡數據集,因采取下采樣、過采樣等辦法,使數據平衡,這樣的預測才有意義,下一篇文章將針對這個問題進行改進;
- 模型、算法并沒有高低、好壞之分,只是在不同的情況下有不同的發揮罷了,這點應正確的看待。