數(shù)據(jù)集是來自kaggle上的信用卡進(jìn)行交易的數(shù)據(jù)。此數(shù)據(jù)集顯示兩天內(nèi)發(fā)生的交易,其中284,807筆交易中有492筆被盜刷。數(shù)據(jù)集非常不平衡,被盜刷占所有交易的0.172%。其中數(shù)據(jù)特征v1,v2....v28是某些特征,銀行為了保密,并沒有提供具體代表的內(nèi)容,Class是響應(yīng)變量,如果發(fā)生被盜刷,則取值1,否則為0。Amount為消費(fèi)金額。
首先來看下具體數(shù)據(jù)內(nèi)容:
data = pd.read_csv("/Users/weillschang/Desktop/jupyter notebook/creditcard_fraud/creditcard.csv")
data.head()
輸出內(nèi)容

看下class值的分布情況
data['Class'].value_counts()
其中class =0的有284315 class=1也就是屬于欺詐類型的有492,兩者比例超過500,典型的樣本分布不均衡。
對于機(jī)器學(xué)習(xí)常見的分類問題,從訓(xùn)練模型的角度來看,比較理想的情況下是正類和負(fù)類樣本的數(shù)量相差不多,如果某類的樣本數(shù)量很少,那么自然所能提供的信息就很少,用這些不平衡的數(shù)據(jù)訓(xùn)練出來的模型,其預(yù)測結(jié)果偏向訓(xùn)練數(shù)據(jù)數(shù)據(jù)比較多的哪一類,比如在正負(fù)樣本比例為9:1是,當(dāng)預(yù)測精度90%時(shí),即使模型將結(jié)果全部劃分為90%的那一類,其準(zhǔn)確度也有90%,而這是沒有意義的,自然我們需要對其進(jìn)行處理
樣本不均衡問題
對樣本不均衡的處理通常有以下三種方式,這里有參考這個這篇博客數(shù)據(jù)不均衡的處理
- 欠采樣
拋棄數(shù)據(jù)集中樣本數(shù)量較多的類別,來緩解不平衡問題,,缺點(diǎn)是會丟失多數(shù)類樣本中的一些重要信息。 - 過采樣
對訓(xùn)練集里面過少的樣本進(jìn)行新的數(shù)據(jù)合成,來達(dá)到數(shù)據(jù)平衡的問題,這里比較經(jīng)典的算法是SMOTE算法,他會從相近的幾個樣本中,加入隨機(jī)噪聲,隨機(jī)擾動一個特征,來生成新的數(shù)據(jù)實(shí)例 - 權(quán)重值的調(diào)整
也就是說調(diào)整權(quán)重值,將少數(shù)樣本權(quán)重設(shè)置為一個較大權(quán)重,多數(shù)樣本設(shè)置一個較小權(quán)重。
對于信用卡欺詐這個問題,面對不平衡超過500,如果用欠采樣的話,會丟棄20多萬條數(shù)據(jù),這很可能會導(dǎo)致丟失很多重要的信息,而權(quán)重調(diào)整這里也并不容易找到合適的權(quán)重值,這里采用過采樣來合成新數(shù)據(jù)
數(shù)據(jù)預(yù)處理
在合成數(shù)據(jù)之前我們需要對數(shù)據(jù)進(jìn)行常規(guī)的預(yù)處理,將可能的特征屬性進(jìn)行標(biāo)準(zhǔn)化處理,因?yàn)樗惴ǘ技僭O(shè)所有數(shù)據(jù)集的所有特征集中在0附近,并且有相同的方差,如果某個特征方差遠(yuǎn)大于其他特征方差,那么該特征可能在目標(biāo)函數(shù)中占得權(quán)重更大,而且差距太大的話,這會對收斂速度產(chǎn)生很大的影響,甚至可能不收斂,這里采用sk-learn自帶的StandardScaler來進(jìn)行處理
from sklearn.preprocessing import StandardScaler
data['Amount'] = StandardScaler().fit_transform(data['Amount'].reshape(-1, 1))
data.drop(['Time'],axis=1)
處理后該列數(shù)據(jù)會變成均值為0,方差為1的一列數(shù)據(jù)。
使用SMOTE 算法進(jìn)行數(shù)據(jù)合成
SMOTE算法很簡單,可以說是K 近鄰算法的逆操作,以歐氏距離為標(biāo)準(zhǔn)計(jì)算它到少數(shù)類樣本集中所有樣本的距離,得到其k近鄰后,在從其K 近鄰中隨機(jī)選擇N個樣本, 在從這些樣本與原來的樣本之間隨機(jī)構(gòu)建生成一個新的數(shù)據(jù)。
如下:
## 先對數(shù)據(jù)集進(jìn)行分割,按30% 劃分
from imblearn.over_sampling import SMOTE
from sklearn.ensemble import RandomForestClassifier
from sklearn.metrics import confusion_matrix
from sklearn.model_selection import train_test_split
from sklearn.cross_validation import KFold, cross_val_score
from sklearn.metrics import confusion_matrix,recall_score,classification_report
X = data.loc[:, data.columns != 'Class']
Y = data.loc[:, data.columns == 'Class']
features_train, features_test, labels_train, labels_test = train_test_split(X, Y, test_size=0.2, random_state=0)
## sample 生成
oversampler=SMOTE(random_state=0)
new_features,new_labels=oversampler.fit_sample(features_train,labels_train)
這樣數(shù)據(jù)合成之后,二者便會有相同的樣本數(shù)了,接下來便可以進(jìn)行邏輯回歸生成模型
模型的生成與調(diào)參
對于有監(jiān)督學(xué)習(xí)算法,過擬合比欠擬合有時(shí)更難處理,尤其是過多的特征與過少的數(shù)據(jù),最會容易導(dǎo)致過擬合問題
解決過擬合問題,通常有增加數(shù)據(jù)集,和減少模型復(fù)雜度(比如減少學(xué)習(xí)特征,讓某個特征不被模型學(xué)習(xí)到),而正則化則是減少模型復(fù)雜度的一種方法
正則化中我們將保留所有的特征變量,但是會減小特征變量的數(shù)量級(參數(shù)數(shù)值的大小θ(j))。
sk_learn 邏輯回歸算法,提供了c值也就是正則化系數(shù)倒數(shù)供我們選擇,c值越小,對應(yīng)越強(qiáng)的正則化,越強(qiáng)的正則化越能得到一個更簡單的假設(shè)曲線,也就越能減少過擬合的風(fēng)險(xiǎn)(可以,這很“奧體姆剃刀”),下面使用不同的c值進(jìn)行準(zhǔn)確率的計(jì)算
from sklearn.linear_model import LogisticRegression
fold = KFold(len(os_labels),5,shuffle=False)
c_param_range = [0.01,0.1,1,10,100]
for c_param in c_param_range:
print('C parameter: ', c_param)
listacc=[]
for iteration, indices in enumerate(fold,start=1):
lr = LogisticRegression(C = c_param, penalty = 'l1')
lr.fit(new_features.iloc[indices[0],:],new_labels.iloc[indices[0],:].values.ravel())
predicted_data=lr.predict(new_features.iloc[indices[1],:].values)
recall_acc = recall_score(new_labels.iloc[indices[1],:].values,predicted_data)
listacc.append(recall_acc)
print" recall score = {}".format(recall_acc)
print "mean_acc:{}".format(float(sum(listacc))/len(listacc))
penalty參數(shù)選擇l1正則化范式,比較適合模型的特征非常多,同時(shí)希望將一些不重要的特征系數(shù)歸零,從而讓模型系數(shù)更加稀疏,權(quán)重值更低
這里衡量精度采用recall_score,也就是召回率,而不是準(zhǔn)確率和正確率。
召回率=TP/(TP+FN)
也就是真正為正例的樣本中,正確預(yù)測為正例的比例
這里以欺詐為正例。
也就是說假設(shè)真的欺詐數(shù)目有10個,我們正確預(yù)測到了其中9個,其召回率也就是0.9。
如上圖最終輸出結(jié)果為
.....
('C parameter: ', 0.01)
recall score = 0.922580645161
recall score = 0.901315789474
recall score = 0.931415292686
recall score = 0.922632197931
recall score = 0.921346215144
mean_acc:0.919858028079
('C parameter: ', 0.1)
recall score = 0.922580645161
recall score = 0.907894736842
recall score = 0.932123492309
recall score = 0.924105032919
recall score = 0.922500302261
mean_acc:0.921840841899
('C parameter: ', 1)
recall score = 0.916129032258
recall score = 0.907894736842
recall score = 0.932167754786
recall score = 0.924324859037
recall score = 0.922687154461
mean_acc:0.920640707477
.......
這里選取recall平均值最大的c值,即為0.1
接下來,把預(yù)測結(jié)果的結(jié)果精度顯示在一個混淆矩陣?yán)锩妫聪庐?dāng)前預(yù)測結(jié)果,(繪制混淆矩陣代碼,參考自網(wǎng)上)
from sklearn.cross_validation import KFold, cross_val_score
from sklearn.metrics import confusion_matrix,recall_score,classification_report
import itertools
from sklearn.linear_model import LogisticRegression
def plot_confusion_matrix(cm, classes,
title='Confusion matrix',
cmap=plt.cm.Blues):
"""
This function prints and plots the confusion matrix.
"""
plt.imshow(cm, interpolation='nearest', cmap=cmap)
plt.title(title)
plt.colorbar()
tick_marks = np.arange(len(classes))
plt.xticks(tick_marks, classes, rotation=0)
plt.yticks(tick_marks, classes)
thresh = cm.max() / 2.
for i, j in itertools.product(range(cm.shape[0]), range(cm.shape[1])):
plt.text(j, i, cm[i, j],
horizontalalignment="center",
color="white" if cm[i, j] > thresh else "black")
plt.tight_layout()
plt.ylabel('True label')
plt.xlabel('Predicted label')

單單看召回率還是不錯,可是右上角的還有1045個的誤殺(即將正常的判斷為欺詐),接下來考慮設(shè)置不同的判斷閾值來做更嚴(yán)格的劃分并畫出混淆矩陣,默認(rèn)的是0.5 即將將結(jié)果劃分為超過50%一側(cè)的值。
lr = LogisticRegression(C = 0.1, penalty = 'l1')
lr.fit(new_features,new_labels.values.ravel())
y_pred_proba = lr.predict_proba(features_test.values)
from __future__ import division
thresholds = [0.6,0.7,0.8,0.9]
plt.figure(figsize=(10,10))
j = 1
for i in thresholds:
y_test_predictions_high_recall = y_pred_proba[:,1] > i
plt.subplot(3,3,j)
j += 1
cnf_matrix = confusion_matrix(labels_test,y_test_predictions_high_recall)
np.set_printoptions(precision=2)
print "Recall:{}".format(cnf_matrix[1,1]/(cnf_matrix[1,0]+cnf_matrix[1,1]))
class_names = [0,1]
plot_confusion_matrix(cnf_matrix
, classes=class_names
, title='Threshold >= %s'%i)
結(jié)果如下

很明顯 隨著判斷閾值越大(即判斷的要求更嚴(yán)格),召回率是下降趨勢的,但是誤殺的概率則明顯下降,綜合來看0.8附近是個相對比較合理的值。