機器學習——信用卡反欺詐案例


導入類庫

 1 import numpy as np
 2 import pandas as pd
 3 from pandas import Series, DataFrame
 4 import matplotlib.pyplot as plt
 5 from sklearn.preprocessing import StandardScaler
 6 from imblearn.over_sampling import SMOTE
 7 from sklearn.ensemble import GradientBoostingClassifier
 8 from sklearn.model_selection import train_test_split
 9 from sklearn.linear_model import LogisticRegression
10 from sklearn.metrics import confusion_matrix
11 import itertools
12 from sklearn.model_selection import GridSearchCV
13 from sklearn.metrics import auc, roc_curve

作圖函數

 1 def plot_confusion_matrix(cm, classes,
 2                           title='Confusion matrix',
 3                           cmap=plt.cm.Blues):
 4     """
 5     This function prints and plots the confusion matrix.
 6     """
 7     plt.imshow(cm, interpolation='nearest', cmap=cmap)
 8     plt.title(title)
 9     plt.colorbar()
10     tick_marks = np.arange(len(classes))
11     plt.xticks(tick_marks, classes, rotation=0)
12     plt.yticks(tick_marks, classes)
13 
14     threshold = cm.max() / 2.
15     for i, j in itertools.product(range(cm.shape[0]), range(cm.shape[1])):
16         plt.text(j, i, cm[i, j],
17                  horizontalalignment="center",
18                  color="white" if cm[i, j] > threshold else "black")  # 若對應格子上面的數量不超過閾值則,上面的字體為白色,為了方便查看
19 
20     plt.tight_layout()
21     plt.ylabel('True label')
22     plt.xlabel('Predicted label')
23     plt.show()

數據獲取與解析

數據為結構化數據,不需要抽特征轉化, 但特征Time和Amount的數據規格和其他特征不一樣, 需要對其做特征做特征縮放

1 credit = pd.read_csv('./creditcard.csv')
2 
3 print('原始行列 >>>>', credit.shape)  # (284807行, 31列)
4 # print(credit.head())  # 前5行
5 # print(credit.dtypes)  # 查看特征(列)類型。結果:數據類型只有float64和int64
6 # print(credit.isnull().any())    # 判斷是否有缺失值。結果:無缺失值,方便后續處理
7 # print(credit.info())  # 查看數據集詳細信息(類型,占用大小,缺失值,行列等)

特征工程

 

 1 # c_counts = credit['Class'].value_counts()
 2 # print(c_counts, type(c_counts))  # 對Class列分類統計,並判斷類型
 3 # print(c_counts.index, c_counts.values)  # 提取索引和值
 4 '''
 5 結果:
 6 0    284315
 7 1       492
 8 Name: Class, dtype: int64
 9 Name: Class, dtype: int64 <class 'pandas.core.series.Series'>
10 Int64Index([0, 1], dtype='int64') [284315    492]
11 '''
12 
13 # 對c_counts作圖進行分析
14 # plt.figure(figsize=(10, 6))
15 # 餅圖:兩種作圖方式
16 # ax = plt.subplot(121)
17 # c_counts是pandas的Series類型,pandas可以使用plot快速作圖
18 # c_counts.plot(kind='pie', autopct='%0.3f%%', ax=ax)
19 # plt.pie(c_counts, autopct='%0.3f%%')
20 
21 # 柱狀圖:兩種作圖方式
22 # ax = plt.subplot(122)
23 # c_counts.plot(kind='bar', ax=ax)
24 # plt.bar(c_counts.index, c_counts.values)
25 # plt.show()
26 '''
27 存在492例盜刷,占總樣本的0.17%,
28 存在明顯的數據類別不平衡問題,
29 可采用過采樣(增加數據)的方法處理該問題
30 '''

 

特征轉換

將時間從單位每秒化為單位每小時 divmod(7201,3600) 結果:(2, 1) 元組,2為商,1為余數

 

1 credit['Time'] = credit['Time'].map(lambda x: divmod(x, 3600)[0])
2 # print(credit['Time'])  # map高級函數:將Time中的每個元素作用於lambda函數

特征選擇

 

 1 # Class列中值為0的為True,值為1為False,生成的cond0行數不變
 2 # cond0 = credit['Class'] == 0
 3 # Class列中值為0的為False,值為1為True,生成的cond0行數不變
 4 # cond1 = credit['Class'] == 1
 5 # print('cond0 >>>>', len(cond0))
 6 # print('cond1 >>>>', len(cond1))
 7 
 8 # 作圖分析
 9 # credit['V1'][cond0].plot(kind='hist', bins=500)
10 # credit['V1'][cond1].plot(kind='hist', bins=50)
11 # plt.show()
12 
13 # 調試查看用
14 # print("credit['V1'] >>>>", credit['V1'])
15 # print('cond0 >>>>', cond0)
16 # print('cond1 >>>>', cond1)
17 
18 # 篩選出存在於V1列中且在cond0中為True的值(284315)
19 # print("credit['V1'][cond0] >>>>", credit['V1'][cond0])
20 # 篩選出存在於V1列中且在cond0中為True的值(492)
21 # print("credit['V1'][cond1] >>>>", credit['V1'][cond1])
22 
23 ''' 作圖分析:將每一個特征根據Class的真假進行划分, 圖像中兩種圖形的重合度越大說明該特征對Class的影響越小, 所以需要剔除掉無用的特征 '''
24 # cols = ['V1', 'V2', 'V3', 'V4', 'V5', 'V6', 'V7', 'V8', 'V9', 'V10',
25 #         'V11', 'V12', 'V13', 'V14', 'V15', 'V16', 'V17', 'V18', 'V19', 'V20',
26 #         'V21', 'V22', 'V23', 'V24', 'V25', 'V26', 'V27', 'V28']
27 # 作圖:28行,1列,每一行顯示一個特征對應的圖
28 # plt.figure(figsize=(12, 2800))
29 # for i, col in enumerate(cols):
30 #     ax = plt.subplot(28, 1, i + 1)
31 # density(normed)標准化數據:將過大或過小的數據統一標准化
32 #     credit[col][cond0].plot(kind='hist', bins=500, density=True, ax=ax)
33 #     credit[col][cond1].plot(kind='hist', bins=50, density=True, ax=ax)
34 #
35 #     ax.set_title(col)
36 # plt.show()
37 
38 # 待剔除的列(10列)
39 drops = ['V13', 'V15', 'V20', 'V22', 'V23', 'V24', 'V25', 'V26', 'V27', 'V28']
40 # 刪除指定列(axis=1按列,axis=0按行)
41 credit2 = credit.drop(labels=drops, axis=1)
42 print('人眼剔除無用列后 >>>>', credit2.shape)
43 ''' 不同變量在信用卡被盜刷和信用卡正常的不同分布情況, 選擇在不同信用卡狀態下的分布有明顯區別的變量。 
因此剔除變量V13 、V15 、V20 、V22、 V23 、V24 、V25 、V26 、V27 和V28變量
'''

特征縮放

Amount變量和Time變量的取值范圍與其他變量相差較大, 所以要對其進行特征縮放

 1 # print('原Amount數據最大值', credit2['Amount'].max())
 2 # print('原Amount數據最小值', credit2['Amount'].min())
 3 # print('原Time數據最大值', credit2['Time'].max())
 4 # print('原Time數據最小值', credit2['Time'].min())
 5 
 6 # 創建標准化對象
 7 standScaler = StandardScaler()
 8 cols = ['Time', 'Amount']
 9 # 標准化數據
10 credit2[cols] = standScaler.fit_transform(credit2[cols])
11 # print('標准化Amount后最大值 >>>>', credit2['Amount'].max())
12 # print('標准化Amount后最小值 >>>>', credit2['Amount'].min())
13 # print('標准化Time后最大值 >>>>', credit2['Time'].max())
14 # print('標准化Time后最小值 >>>>', credit2['Time'].min())

特征重要性排序

對特征的重要性進行排序,以進一步減少變量 利用GBDT梯度提升決策樹進行特征重要性排序

 1 # 創建GBDT對象
 2 # clf = GradientBoostingClassifier()
 3 # 特征訓練集:前20列
 4 # X_train = credit2.iloc[:, :-1]
 5 # print('X_train.shape >>>>', X_train.shape)
 6 # cols = X_train.columns
 7 # print('X_train.columns >>>>', X_train.columns)
 8 # 目標值訓練集:Class列
 9 # y_train = credit2['Class']  # y_train = credit2.iloc[:,-1]
10 # print('y_train.shape >>>>', y_train.shape)
11 # 訓練數據
12 # clf.fit(X_train, y_train)
13 # 得到特征重要性數據
14 # feature_importances_ = clf.feature_importances_
15 # print('feature_importances_ >>>>', feature_importances_)
16 # 從大到小對特征重要性進行排序,並作圖分析
17 # argsort():對數組排序並返回排序后每個元素對應的未排序時自身所在的索引
18 # index = feature_importances_.argsort()[::-1]
19 # print('從大到小排列特征重要性,返回每個元素的原索引 >>>>', index, len(index))
20 
21 # plt.figure(figsize=(12, 9))
22 # 柱狀圖,第二個參數代表按從大到小排列的特征數據
23 # plt.bar(np.arange(len(index)), feature_importances_[index])
24 # 柱狀圖x坐標:第二個參數是按特征值從大到小排列后的特征名
25 # plt.xticks(np.arange(len(index)), cols[index])
26 # plt.show()
27 # 根據圖像得到要刪除的特征列(最小的后9列)
28 drops = ['V7', 'V21', 'V8', 'V5', 'V4', 'V11', 'V19', 'V1', 'Amount']
29 credit3 = credit2.drop(labels=drops, axis=1)
30 print('通過GBDT分析剔除無用列后 >>>>', credit3.shape)
31 # print('credit3.columns >>>>', credit3.columns)

模型訓練

 

處理樣本不平衡問題
目標變量“Class”正常和被盜刷兩種類別的數量差別較大,會對模型學習造成困擾。
舉例來說,假如有100個樣本,其中只有1個是被盜刷樣本,
其余99個全為正常樣本,那么學習器只要制定一個簡單的方法:
即判別所有樣本均為正常樣本,就能輕松達到99%的准確率。
而這個分類器的決策對我們的風險控制毫無意義。
因此,在將數據代入模型訓練之前,我們必須先解決樣本不平衡的問題。
現對該業務場景進行總結如下:
過采樣(oversampling):
增加正樣本使得正、負樣本數目接近,然后再進行學習。
欠采樣(undersampling):
去除一些負樣本使得正、負樣本數目接近,然后再進行學習。 
本次處理樣本不平衡采用的方法是過采樣,
具體操作使用SMOTE(Synthetic Minority Oversampling Technique),
SMOET的基本原理是:
采樣最鄰近算法,計算出每個少數類樣本的K個近鄰,
從K個近鄰中隨機挑選N個樣本進行隨機線性插值,
構造新的少數樣本,同時將新樣本與原數據合成,產生新的訓練集。

  

 1 # SMOTE 過采樣
 2 X = credit3.iloc[:, :-1]
 3 y = credit3.Class
 4 X_train, X_test, y_train, y_test = train_test_split(X, y, test_size=0.3)
 5 X_train,y_train 作為訓練數據 訓練時,保證樣本均衡,將X_train和y_train樣本過采樣處理 測試時候,可以樣本不均衡
 6 # print('未均衡的y訓練集分類統計(Class) >>>>', y_train.value_counts())
 7 
 8 smote = SMOTE()
 9 # ndarray
10 X_train_new, y_train_new = smote.fit_sample(X_train, y_train)
11 # print('均衡后的x訓練集 >>>>', X_train_new, type(X_train_new))
12 # print('均衡后的y訓練集(Class) >>>>', y_train_new, type(y_train_new), len(y_train_new))
13 # y_train_new類型為numpy.ndarray,需轉化為pandas.Series類型才可分類統計
14 # print('均衡后的y訓練集分類統計(Class) >>>>', Series(y_train_new).value_counts())

求召回率

單獨的邏輯回歸求得查全率Recall rate,Recall也叫召回率

 

 1 # 創建邏輯回歸對象
 2 # logistic = LogisticRegression()
 3 # print(logistic)
 4 '''
 5 LogisticRegression(C=1.0, class_weight=None, dual=False, fit_intercept=True,
 6           intercept_scaling=1, max_iter=100, multi_class='ovr', n_jobs=1,
 7           penalty='l2', random_state=None, solver='liblinear', tol=0.0001,
 8           verbose=0, warm_start=False)
 9 '''
10 # 訓練均衡后的數據
11 # logistic.fit(X_train_new, y_train_new)
12 # 預測
13 # y_ = logistic.predict(X_test)
14 # print('y_test >>>>', y_test)
15 # print('預測的y_ >>>>', y_)
16 # 交叉表
17 # print('交叉表 >>>>', pd.crosstab(y_test, y_, margins=True))
18 
19 # 混合矩陣
20 # cm = confusion_matrix(y_test, y_)
21 # print('混合矩陣 >>>>', cm, type(cm))
22 # Recall------“正確被檢索的正樣本item(TP)"占所有"應該檢索到的item(TP+FN)"的比例
23 # plot_confusion_matrix(cm, [0, 1], title='Recall:%0.3f' % (cm[1, 1] / (cm[1, 0] + cm[1, 1])))

交叉驗證與調優

 

 1 logistic = LogisticRegression()
 2 clf = GridSearchCV(logistic, param_grid={'tol': [1e-3, 1e-4, 1e-5], 'C': [1, 0.1, 10, 100]}, cv=10, iid=False, n_jobs=1)
 3 print(clf.fit(X_train_new, y_train_new))
 4 # print('best_score_ >>>>', clf.best_score_)
 5 # print('best_params_ >>>>', clf.best_params_)
 6 # print('best_index_ >>>>', clf.best_index_)
 7 # print('best_estimator_ >>>>', clf.best_estimator_)
 8 
 9 # 預測
10 # y3_ = clf.best_estimator_.predict(X_test)
11 # print('y3_預測(best_estimator_) >>>>', confusion_matrix(y_test, y3_))
12 
13 # y2_ = clf.predict(X_test)
14 # print('y2_預測 >>>>', confusion_matrix(y_test, y2_))
15 
16 # cm2 = confusion_matrix(y_test, y2_)
17 
18 # 可視化,對比邏輯斯蒂回歸和GridSearchCV結果
19 # plot_confusion_matrix(cm, [0, 1], title='Logistic Recall:%0.3f' % (cm[1, 1] / (cm[1, 0] + cm[1, 1])))
20 # plot_confusion_matrix(cm2, [0, 1], title='GridSearchCV Recall:%0.3f' % (cm2[1, 1] / (cm2[1, 0] + cm2[1, 1])))

模型評估

解決不同的問題,通常需要不同的指標來度量模型的性能。
例如我們希望用算法來預測癌症是否是惡性的,
假設100個病人中有5個病人的癌症是惡性, 
對於醫生來說,盡可能提高模型的查全率(recall)比提高查准率(precision)更為重要,
因為站在病人的角度,發生漏發現癌症為惡性比發生誤 判為癌症是惡性更為嚴重
由此可見就上面的兩個算法而言,明顯lgb過擬合了,
考慮到樣本不均衡問題,
故應該選用簡單一點的算法(邏輯回歸)來減少陷入過擬合的陷阱

  

1 y_proba = clf.predict_proba(X_test)
2 # 預測被盜刷的概率
3 print(y_proba)

 


免責聲明!

本站轉載的文章為個人學習借鑒使用,本站對版權不負任何法律責任。如果侵犯了您的隱私權益,請聯系本站郵箱yoyou2525@163.com刪除。



 
粵ICP備18138465號   © 2018-2025 CODEPRJ.COM