数据集是来自kaggle上的信用卡进行交易的数据。此数据集显示两天内发生的交易,其中284,807笔交易中有492笔被盗刷。数据集非常不平衡,被盗刷占所有交易的0.172%。其中数据特征v1,v2....v28是某些特征,银行为了保密,并没有提供具体代表的内容,Class是响应变量,如果发生被盗刷,则取值1,否则为0。Amount为消费金额。
首先来看下具体数据内容:
data = pd.read_csv("/Users/weillschang/Desktop/jupyter notebook/creditcard_fraud/creditcard.csv")
data.head()
输出内容
看下class值的分布情况
data['Class'].value_counts()
其中class =0的有284315 class=1也就是属于欺诈类型的有492,两者比例超过500,典型的样本分布不均衡。
对于机器学习常见的分类问题,从训练模型的角度来看,比较理想的情况下是正类和负类样本的数量相差不多,如果某类的样本数量很少,那么自然所能提供的信息就很少,用这些不平衡的数据训练出来的模型,其预测结果偏向训练数据数据比较多的哪一类,比如在正负样本比例为9:1是,当预测精度90%时,即使模型将结果全部划分为90%的那一类,其准确度也有90%,而这是没有意义的,自然我们需要对其进行处理
样本不均衡问题
对样本不均衡的处理通常有以下三种方式,这里有参考这个这篇博客数据不均衡的处理
- 欠采样
抛弃数据集中样本数量较多的类别,来缓解不平衡问题,,缺点是会丢失多数类样本中的一些重要信息。 - 过采样
对训练集里面过少的样本进行新的数据合成,来达到数据平衡的问题,这里比较经典的算法是SMOTE算法,他会从相近的几个样本中,加入随机噪声,随机扰动一个特征,来生成新的数据实例 - 权重值的调整
也就是说调整权重值,将少数样本权重设置为一个较大权重,多数样本设置一个较小权重。
对于信用卡欺诈这个问题,面对不平衡超过500,如果用欠采样的话,会丢弃20多万条数据,这很可能会导致丢失很多重要的信息,而权重调整这里也并不容易找到合适的权重值,这里采用过采样来合成新数据
数据预处理
在合成数据之前我们需要对数据进行常规的预处理,将可能的特征属性进行标准化处理,因为算法都假设所有数据集的所有特征集中在0附近,并且有相同的方差,如果某个特征方差远大于其他特征方差,那么该特征可能在目标函数中占得权重更大,而且差距太大的话,这会对收敛速度产生很大的影响,甚至可能不收敛,这里采用sk-learn自带的StandardScaler来进行处理
from sklearn.preprocessing import StandardScaler
data['Amount'] = StandardScaler().fit_transform(data['Amount'].reshape(-1, 1))
data.drop(['Time'],axis=1)
处理后该列数据会变成均值为0,方差为1的一列数据。
使用SMOTE 算法进行数据合成
SMOTE算法很简单,可以说是K 近邻算法的逆操作,以欧氏距离为标准计算它到少数类样本集中所有样本的距离,得到其k近邻后,在从其K 近邻中随机选择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)
这样数据合成之后,二者便会有相同的样本数了,接下来便可以进行逻辑回归生成模型
模型的生成与调参
对于有监督学习算法,过拟合比欠拟合有时更难处理,尤其是过多的特征与过少的数据,最会容易导致过拟合问题
解决过拟合问题,通常有增加数据集,和减少模型复杂度(比如减少学习特征,让某个特征不被模型学习到),而正则化则是减少模型复杂度的一种方法
正则化中我们将保留所有的特征变量,但是会减小特征变量的数量级(参数数值的大小θ(j))。
sk_learn 逻辑回归算法,提供了c值也就是正则化系数倒数供我们选择,c值越小,对应越强的正则化,越强的正则化越能得到一个更简单的假设曲线,也就越能减少过拟合的风险(可以,这很“奥体姆剃刀”),下面使用不同的c值进行准确率的计算
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参数选择l1正则化范式,比较适合模型的特征非常多,同时希望将一些不重要的特征系数归零,从而让模型系数更加稀疏,权重值更低
这里衡量精度采用recall_score,也就是召回率,而不是准确率和正确率。
召回率=TP/(TP+FN)
也就是真正为正例的样本中,正确预测为正例的比例
这里以欺诈为正例。
也就是说假设真的欺诈数目有10个,我们正确预测到了其中9个,其召回率也就是0.9。
如上图最终输出结果为
.....
('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
接下来,把预测结果的结果精度显示在一个混淆矩阵里面,看下当前预测结果,(绘制混淆矩阵代码,参考自网上)
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个的误杀(即将正常的判断为欺诈),接下来考虑设置不同的判断阈值来做更严格的划分并画出混淆矩阵,默认的是0.5 即将将结果划分为超过50%一侧的值。
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)
结果如下
很明显 随着判断阈值越大(即判断的要求更严格),召回率是下降趋势的,但是误杀的概率则明显下降,综合来看0.8附近是个相对比较合理的值。