摘要:關於度量指標,本模型選擇f1_macro score,它是正常交易與欺詐交易f1_score的平均值。可增加類別1(欺詐交易)的f1_score,並通過f1_score的宏觀平均值,確定該模型的性能。

全文共9825字,預計學習時長40分鐘或更長


尼日利亞王子發來郵件?Python告訴你:假的

圖片來源:me.me


在撒哈拉以南的非洲多地,銀行普及率低是普遍現象。這是由(但不限於)以下因素造成:當地居民出於歷史原因對銀行機構的不信任、低且不穩定的居民收入和身份檔案的不完善。此外,不少居民主要生活在偏遠的農村,那裏實體銀行基礎設施建設普遍匱乏。


鑑於上述問題,無數金融科技競相湧現,旨在利用日趨升高的手機普及率與性價比來建立數字經濟,爲撒哈拉以南非洲人民面臨的困境提供良方。


人們相信,這些金融科技會帶來淨正面效益,尤其是促進移動交易/無現金交易量的增加,降低攜帶現金的安全風險,並提高交易的便利性,進而促進經濟活動的總體增加。對於非正式貿易較多的國家而言,移動交易會讓逃稅行爲降至最低,增加商業運作透明度。


在津巴布韋,多虧了移動支付,政府才能向非正規企業徵稅,即通過Ecocash(津巴布韋頭號移動支付平臺)對一切貿易活動徵稅。稅爲國之本,一個國家可以在別的領域一無是處,但絕不能疏於稅收。


尼日利亞王子發來郵件?Python告訴你:假的

圖片來源:9gag


尼日利亞王子發來郵件?Python告訴你:假的

做出設想


移動支付的益處非常多,但有個問題不能忽略:試想在一個非洲國家,比方說瓦坎達,有一家支付公司致力於促進多方支付。就在便捷無縫的移動支付發展得如火如荼時,移動支付的一個弊端暴露了出來——網絡詐騙。


想象一下,有一天你收到一封自稱爲尼日利亞王子的來信,並表示可以讓你幫個小忙(比如給他匯一筆小錢)之後就能得到一筆鉅額遺產……你該相信嗎?也許你覺得是天上掉餡餅了,豈不知你早已被詐騙犯視爲一塊肥肉。


爲了甄別網絡詐騙,人們需要建立一個算法模型來預測給定交易是否涉嫌詐騙。鑑於此,本文以Kaggle上的信用卡欺詐偵測數據集爲基礎,利用機器學習來判斷既定交易是否涉嫌欺詐。


數據集傳送門:https://www.kaggle.com/mlg-ulb/creditcardfraud


尼日利亞王子發來郵件?Python告訴你:假的

採用數據集


首先,從正常交易與欺詐交易的數據可視化入手,探索這些數據具體如何呈現。


#loading the dataset
location = '/Users/emmanuels/Downloads/creditcard.csv'
dat = pd.read_csv(location)
#visualising transactions
ax = dat.groupby('Class').size().transform(lambda x:
(x/sum(x)*100)).plot.bar()
for a in ax.patches:
ax.text(a.get_x()+.06,a.get_height()+.5,\
str('{}%'.format(round(a.get_height(),3))),fontsize=24,
color='black')
old = [0,1]
new = ['Normal','Fraudulent']
ax.set_xticks(old)
ax.set_xticklabels(new,rotation=0,fontsize=28)


上傳數據後,按類別對數據集進行分組(0爲正常交易,1爲欺詐交易),並返回類別總數。雖然apply(https://pandas.pydata.org/pandas-docs/stable/reference/api/pandas.DataFrame.apply.html)操作會讓任何lambda函數調用於已分組的數據集,但在這個情況下,使用transform(https://pandas.pydata.org/pandas-docs/stable/reference/api/pandas.DataFrame.transform.html)指令能保證所得返回值會與輸入值規模相同。接下來在lambda函數中將其轉化爲佔交易總量的百分比,並繪製出標有相關百分比的條形圖。


尼日利亞王子發來郵件?Python告訴你:假的

正常交易與欺詐交易的百分比


正如所料,絕大多數交易都是非欺詐性的。但這出現了個小問題,畢竟衡量機器學習模型的性能優劣僅靠準確率是不夠的。例如,要是預測每樁交易都是正常的,那準確率便是99.827%,這聽上去很贊,但若真是如此,那便會因未能甄別出欺詐交易而造成金錢損失。因此,無論使用何種指標來衡量算法性能,關鍵還是在於衡量該算法正確甄別欺詐交易的能力。


尼日利亞王子發來郵件?Python告訴你:假的

時間-交易量可視化

接下來,根據交易達成時間研究交易量。鑑於該數據集並不顯示時間數據,因此我們只能得到自首次成交紀錄以來所記錄的秒數。

plt.plot( 'Time', 'Amount', data=dat, marker='o', color='blue',
linewidth=2, linestyle='dashed')
plt.xlabel('Seconds since first transaction')
plt.ylabel('Size of transaction')
plt.rcParams["figure.figsize"] = (30,20)
plt.rcParams["font.size"] = "20"



尼日利亞王子發來郵件?Python告訴你:假的

時間-成交量可視化


按時間將交易量可視化後,似乎能夠得出某種模式。交易量在25000秒開始走高,約20000秒後達到峯值,之後持續下降。該週期持續約500000秒(約14個小時),同樣的模式在125000秒處再次出現。這大概就是一天的週期,交易量先從當天的開始逐漸走高,在中間某個時刻達到峯值,之後在打烊前衰減。可以想見,關於欺詐交易何時更易發生這個問題,或許能在該模式中找到蛛絲馬跡。

dat['Hours'] = dat['Time']/3600


爲了在分析中囊括上述信息,我們可以創建一個柱狀圖,指出一樁交易發生時的節點(小時)。再次申明,該推測可能會成爲有效判斷交易是否涉嫌欺詐的重要特徵。


尼日利亞王子發來郵件?Python告訴你:假的

時間-欺詐交易佔比可視化


接下來,根據上一段所做的假設,我們將按時間可視化欺詐交易佔交易總量的比例,以探究欺詐交易是否集聚在某個特定時間段內。

#visualising amount spent per hour by class
dat_grouped =
dat.groupby(['Hours','Class'])['Amount'].sum()
ax_three = dat_grouped.groupby(level=0).apply(lambda
x:round(100*x/x.sum(),3)).unstack().plot.bar(stacked=True)
for i in ax_three.patches:
width,height=i.get_width(),i.get_height()
x,y = i.get_xy()
horiz_offset=1
vert_offset=1
labels = ['Normal','Fraudulent']
ax_three.legend(bbox_to_anchor=(horiz_offset,vert_offset),labels=labels)
if height > 0:
ax_three.annotate('{:.2f} %'.format(height),
(i.get_x()+.15*width,
i.get_y()+.5*height),
rotation=90)


爲了實現可視化,數據集按小時與類別予以分類,並返回每小時進行的正常交易與欺詐交易的總和。接着調用lambda函數將這兩個類別的數量轉化爲百分比,創建堆積條形圖並註釋相關的百分比,並定位百分比以此確保圖表可讀性。


尼日利亞王子發來郵件?Python告訴你:假的

Data Viz 實際數據可視化專家100%致力於他們的工作


尼日利亞王子發來郵件?Python告訴你:假的

時間-欺詐交易佔比可視化


鑑於數據僅涉及48小時內所達成的交易,任何觀察得出的模式都可以解釋成偶然現象。但我們認爲,將發生特定交易的一小時內欺詐交易佔比作爲一個特徵添加進來會很有意思。據推測,交易時間段這一特徵(聯合其他特徵)所包含的相關信息,可能有助於判斷交易是否涉嫌欺詐。欺詐交易在一天內某些時段可能更易發生。

a= dat.groupby(['Hours','Class'])['Amount'].sum()
b =pd.DataFrame(a.groupby(level=0).apply(lambda
x:round(100*x/x.sum(),3)).unstack())
b=b.reset_index()
b.columns = ['Hours','Normal','Fraudulent']
dat = pd.merge(dat,b[['Hours','Fraudulent']],on='Hours', how='inner')


爲了做出該柱狀圖,首先根據小時計算出正常交易與欺詐交易各自佔比,再將數據的行轉換成列,以此將類別信息以柱狀圖予以展現。

接着合併數據幀,從剛剛創建的數據幀中提取出欺詐交易列。和用SQL一樣,兩個數據幀按照共有特徵/鍵——小時數進行合併。於是,數據幀有了一個新的數據列,限制特定交易小時內欺詐交易的佔比。


尼日利亞王子發來郵件?Python告訴你:假的

機器學習


要想分類,首先從邏輯迴歸開始。簡要回顧下,該算法即logit函數執行變換操作,迴歸出某事件的發生概率。


尼日利亞王子發來郵件?Python告訴你:假的


統計學=機器學習


dat.columns[df.isnull().any()]
dat['Fraudulent'] = dat['Fraudulent'].fillna(0)
from sklearn.linear_model import LogisticRegression
from sklearn.model_selection import train_test_split
#from sklearn.feature_selection import RFE
from sklearn.metrics import confusion_matrix
from sklearn.metrics import classification_report
log_reg = LogisticRegression(penalty='l1')
y = dat['Class']
X = dat.drop(['Class'],axis=1)
X_train,X_test,y_train,y_test = train_test_split(X,y,test_size=0.3,random_state=100)
log_reg.fit(X_train,y_train)
#predicting with models
predicts = log_reg.predict(X_test)
#f1 score is harmonic mean of recall and precision scoreconfusion_matrix(y_test,predicts)
####RESULTS####
array([[85276, 25],
[ 46, 96]])


接下來覈查NAN值,並在必要情況下將所有NAN值用0替換。清理過程後導入分析所需的數據包,將其分爲訓練數據與測試數據,再將前者導入邏輯迴歸模型。該模型專門使用l1正則化過程(套索迴歸-最小絕對收縮與選擇算子)(https://ai.stanford.edu/~ang/papers/aaai06-l1logisticregression.pdf)。L2最小化平方和,l1則最小化目標值與預期值的絕對差值之和。該正則化過程旨在防止係數的過擬合,以此提高該模型學習歸納新數據的能力。

如前文所述,準確度不是衡量該模型性能優劣的有效指標。因此我們先使用混淆矩陣(誤差矩陣)來測試該模型對於正常交易與欺詐交易的分類精度——尤其考量模型能否正確甄別出欺詐交易。

根據混淆矩陣,該模型對正常交易的分類準確度爲99.974%,對欺詐交易的分類準確度爲67.60%。

print(classification_report(y_test,predicts))
####RESULTS####
precision recall f1-score support
0 1.00 1.00 1.00 85301
1 0.79 0.68 0.73 142
micro avg 1.00 1.00 1.00 85443
macro avg 0.90 0.84 0.86 85443
weighted avg 1.00 1.00 1.00 85443


爲便於比較,可以打印出所預測的分類報告。該報告返回f1-score,這對保證比較一致性來說很重要。f1-score算是個加權數,是精確率(即檢索結果中真陽性樣本佔所有真陽性預測的比例)與召回率(即檢索結果中真陽性樣本佔所有實際真陽性樣本的比例)的調和均值,可顯示該模型正確分類正常交易與欺詐交易的性能。可增加類別1(欺詐交易)的f1_score,並通過f1_score的宏觀平均值,確定該模型的性能。


尼日利亞王子發來郵件?Python告訴你:假的

遞歸特徵消除


並非所有特徵同樣重要。有些特徵不會爲該模型性能增值,甚至會起到反作用。當然,這一切主要與所採用模型有關。某些諸如決策樹與隨機森林之類的模型可自行選出最相關的特徵,因此無須專門進行特徵選擇。


特徵選擇的原理在於選擇能優化某個分數的特徵。主要方法有正向選擇(從一個特徵開始不斷添加新的特徵直到得出最優解)與反向選擇(正向選擇的逆過程)。就本文而言,更傾向於遞歸特徵消除。這是一種反向選擇,去除模型中最無關緊要的特徵,該方法的關鍵在於衡量重要性的指標。

from sklearn.feature_selection import RFECV
selector = RFECV(log_reg,step=3,cv=5,scoring='f1_macro')
selector = selector.fit(X,y)


本文專門選擇帶有交叉驗證的遞歸特徵消除,即用五倍交叉驗證得出特徵的最優數。關於度量指標,本模型選擇f1_macro score,它是正常交易與欺詐交易f1_score的平均值。將迭代設置爲3,因此每次迭代將刪去3個特徵。

#showing the name of the features that have been selectedcols = list(X.columns)
temp =pd.Series(selector.support_,index=cols)
selected_features = temp[temp==True].index
selected_features
#### RESULTS ####
Index(['V1', 'V3', 'V4', 'V5', 'V6', 'V7', 'V8', 'V9', 'V10', 'V11', 'V12',
'V13', 'V14', 'V15', 'V16', 'V17', 'V19', 'V20', 'V21', 'V22', 'V23',
'V24', 'V25', 'V27', 'V28', 'Fraudulent'],
dtype='object')


爲了掌握每次遞歸特徵消除後所返回的特徵,筆者創建了一個pandas series, 將選定特徵映射於相關列標題。最終,這次過程消除了V2,V15,V18,小時,量與時間這幾列。

X_rfecv = dat[selected_features].values
X_train,X_test,y_train,y_test =
train_test_split(X_rfecv,y,test_size=0.3,random_state=100)
log_reg.fit(X_train,y_train)
#predicting with models
predicts = log_reg.predict(X_test)
#f1 score is harmonic mean of recall and precision score
confusion_matrix(y_test,predicts)
#### RESULTS ####
array([[85279, 22],
[ 48, 94]])


使用rfe過程過濾出來的特徵被導入到邏輯迴歸模型後,發現模型預測正常交易的性能略有提高,而欺詐交易的預測性能卻有所衰減。不過,宏f1_score卻與所有列作爲特徵時毫無變化。


尼日利亞王子發來郵件?Python告訴你:假的

另覓良方


本文決定採用決策樹、隨機森林與隨機搜索交叉驗證來獲得最優參數的近似值,並利用隨機森林將宏f1_score平均值提升到0.88。

random_forest = RandomForestClassifier()
param_grid = {"criterion":['gini','entropy'],
'n_estimators':range(20,200,20),
'max_depth':range(20,200,20),
}
tuned_rfc =
RandomizedSearchCV(random_forest,param_grid,cv=5,scoring='f1')
tuned_rfc.fit(X_train,y_train)
tuned_preds = tuned_rfc.predict(X_test)
confusion_matrix(y_test,tuned_preds)
#### RESULTS ####
array([[85288, 13],
[ 35, 107]])


隨機搜索包可用來預設筆者想測試的形參(形式參數),以此得到本模型最優形參。這一原理即在一切可能的形參中找到最優組合。顧名思義,與一一檢驗所有指明形參的排列不同,該方法所創建的組合是隨機的,使得此類超參數調整法比其他諸如網格搜索的方法更爲迅捷。畢竟,後者的運行時間長得令人煎熬,跟看一部DC電影有得一拼。


尼日利亞王子發來郵件?Python告訴你:假的

DC電影好看——個鬼


憑藉隨機搜索與f1衡量參數,本文將最優參數與訓練數據予以擬合,得出測試結果,並將其返至混淆矩陣。如結果所示,正常交易與欺詐交易的準確度均有所提升。

print(classification_report(y_test,tuned_preds))
#### RESULTS ####
precision recall f1-score support
0 1.00 1.00 1.00 85301
1 0.89 0.75 0.82 142
micro avg 1.00 1.00 1.00 85443
macro avg 0.95 0.88 0.91 85443
weighted avg 1.00 1.00 1.00 85443


更棒的是,宏f1平均值也有所升高,該模型終於可甄別出更多的欺詐交易了。

尼日利亞王子發來郵件?Python告訴你:假的

XGBoost! (極限梯度提升)


最後,我們決定採用飽受讚譽的XGBoost分類器,並通過交叉驗證的隨機搜索予以微調。XGBoost是極限梯度提升的英文縮寫。根據各項Kaggle競賽結果來看,該機器學習功能強大,性能優越。更有人說,就連洗過衣服後離奇失蹤的襪子究竟去往何方,XGBoost都能幫你做出推測。


尼日利亞王子發來郵件?Python告訴你:假的

襪子軍隊集結!


據官方“白皮書”,XGBoost是在梯度提升框架下運行的梯度提升樹算法。簡言之,它將預測不出(或效果不佳)理想結果的弱學習器/樹進行強化。這意味着新樹可校正先前決策樹預測的殘留錯誤(即糾錯)。只要做出明確指定,該過程將一直延續,具體由模型所用參數決定。決策樹做出的每個校正都會併入模型中,只要對每次校正賦予加權,便能控制所用模型對訓練數據的適應速度。


決策樹的工作原理與XGBoost有相通之處。在決策樹中,數據分爲多個子集/葉子,並給每一片葉子賦值(信息增益),並構建更多的枝杈(即進一步分割數據),不斷選擇擁有最高值的葉子,直到達到預定水平/縱深。對整體而言(如隨機森林),不同決策樹運行相同過程,彼此互不干擾。這與提升樹類似,但兩者區別在於模型的訓練方式。研究不同觀點與調研後,XGBoost在訓練速度方面似乎比梯度提升更爲優越,甚至還超越了隨機森林,此外XGBoost的數據結果往往也更好。


from xgboost import XGBClassifier
y = dat['Class']
X = dat.drop(['Class'],axis=1)
X_train,X_test,y_train,y_test =
train_test_split(X,y,test_size=0.3,random_state=100)
xgb = XGBClassifier()
#fine tuning XGB parameters
params = {'min_child_weight':[5,15],
'subsample':[0.6,0.8,1.0],
'gamma':[1,5,10,15],
'learning_rate': [0.01,0.05,0.1],
'colsample_bytree':[0.6,0.8,1.0],
'max_depth':[2,3,5,10],
'n-estimaors': range(50,1000,50)
}
#randomized search
random_search = RandomizedSearchCV(xgb,params,cv=5,n_iter=5,scoring='f1',random_state=100)
random_search.fit(X_train,y_train)
confusion_matrix(y_test,opt_predicts)
#### RESULTS ####
array([[85290, 11],
[ 31, 111]])


在閱讀了關於gamma基準與學習率後,我們決定再次使用隨機搜索得出最優參數,將模型與訓練數據擬合,使模型得到輕微的改進。如今,該模型對正常交易的分類準確率爲99.987%,對欺詐交易的分類準確率爲78.16%。

print(classification_report(y_test,opt_predicts))
#### RESULTS ####
precision recall f1-score support
0 1.00 1.00 1.00 85301
1 0.91 0.78 0.84 142
micro avg 1.00 1.00 1.00 85443
macro avg 0.95 0.89 0.92 85443
weighted avg 1.00 1.00 1.00 85443


更棒的是,宏F1平均值也略有提高。在這其中,最有趣的是實現性能飛躍的功臣不是選用了不同的模型,而是特徵工程。可以想見,倘若對所取數據有更深的理解與感悟,模型的可行性將大幅提高。例如,作爲一家支付平臺,研究員對給定用戶的數據平均規模,量與交易時間會有更深的認識。與單單從原始數據中歸納出模棱兩可的模式相比,所獲得的總特徵要更好。


尼日利亞王子發來郵件?Python告訴你:假的


提升雖小,但聊勝於無


尼日利亞王子發來郵件?Python告訴你:假的

交叉驗證


最後一步,對XGBoost數據進行交叉驗證。本文案例採用十倍分層交叉驗證,其原理爲每一層均能很好地體現整個數據集各層的數據,而非僅囊括一類(例如僅包括0類,即正常交易)。對於本文案例而言,這尤爲重要,畢竟其中一個類別佔多於99%。


strat = StratifiedKFold(n_splits=5, shuffle=True)
strat.get_n_splits(X)
model_score = []
for train_index, test_index in strat.split(X,y):
X_train,X_test,y_train,y_test = X.iloc[train_index],
X.iloc[test_index],y.iloc[train_index],y.iloc[test_index]
random_search.fit(X_train, y_train)
xgb_predicts=random_search.predict(X_test)
model_score.append(f1_score(y_test,xgb_predicts,average='macro',labels=np.unique(xgb_predicts)))
scores_table = pd.DataFrame({"F1 Score" :model_score})
scores_table


用交叉驗證專門針對宏f1值進行驗證。據初步測試顯示,該值約爲0,92.


尼日利亞王子發來郵件?Python告訴你:假的



接着將5個測試添加到dataframe(數據幀)中,所得結果在某種程度上對初步測試是個佐證。

尼日利亞王子發來郵件?Python告訴你:假的

留言 點贊 關注

我們一起分享AI學習與發展的乾貨

歡迎關注全平臺AI垂類自媒體 “讀芯術”

相關文章