案例概述使用逻辑回归检测信用卡欺诈行为。逻辑回归是最经典,最牛逼的二分类算法,在处理分类问题尤其是二分类问题时,首选逻辑回归算法。算法选择遵循先简单,再复杂的原则。
从本质上来看,回归分析不管是线性回归还是逻辑回归,拟合的都是一条线(二维)或者一个平面(三维)或者更高维度,因此更适合使用连续型的数值型特征进行预测,而对于有太多分类型特征的数据,即使转换为哑变量,也必然会存在拟合不足的现象。
对于信用卡欺诈检测,实际数据中必然存在大量的正常数据及小部分异常数据。对于数据不均衡的情况一般需要先使用过采样或下采样方法对数据进行处理。
过程观察数据,不均衡怎么处理
预处理:特征工程
标准化,是数据浮动区间一样
数据选择操作
通过交叉验证找寻参数
混淆矩阵及模型评估
调整阈值
一、导入并观察数据
1.导入模块
import numpy as np
import pandas as pd
import matplotlib.pyplot as plt
2.导入数据并查看前几条数据
data=pd.read_csv('creditcard.csv')
data.head()查看数据大小及数据类型
data.shape
data.dtypes计算不同的属性值的个数并绘图
#可以了解数据分布情况,从图形可以看到数据分布极其不平衡
count_classes=data['V1'].groupby(data['Class']).count()
count_classes.plot(kind = 'bar')
plt.title("Fraud class histogram")
plt.xlabel("Class")
plt.ylabel("Frequency")
二、数据预处理
1.标准化处理由于这里面数据是已经经过特征化处理过的数据,所以不再需要做其他的处理。但是,Amount字段数字范围与其他字段相差较大,为了避免权重影响,需对其进行标准化处理。
#从sklearn的预处理包导入标准化处理模块
from sklearn.preprocessing import StandardScaler
#fit_transform-对数据进行标准化变换
#增加一个新的特征
data['normAmount']=StandardScaler().fit_transform(data['Amount'].values.reshape(-1,1))
#删去没用的特征
data=data.drop(['Time','Amount'],axis=1)
data.head()
三、选取特征数据由于数据存在严重不平衡的现象,在这里,为了比较不同方法的结果,把数据集分为三种类型:原数据集,下采样数据集,过采样数据集
1.原数据集
#特征数据
X=data.loc[:,data.columns!='Class']
#label列
Y=data.loc[:,data.columns=='Class']
2.下采样数据集
# 异常样本个数
number_records_abnormal=data.loc[data['Class']==1].shape[0]
#异常样本的索引值
abnormal_indexes=np.array(data.loc[data['Class']==1].index)
#正常样本的索引值
normal_indexes=np.array(data.loc[data['Class']==0].index)
# 从正常样本随机取样,数量为异常样本个数
random_normal_indexes=np.random.choice(normal_indexes,number_records_abnormal,replace = False)
random_normal_indexes=np.array(random_normal_indexes)
#合并数据
under_sample_indexes=np.concatenate([abnormal_indexes,random_normal_indexes])
# 下采样数据集
under_sample_data=data.loc[under_sample_indexes]
#特征数据
X_undersample=under_sample_data.loc[:,under_sample_data.columns!='Class']
#label列
Y_undersample=under_sample_data.loc[:,under_sample_data.columns=='Class']
#查看数据
print('下采样正常数据个数:',len(under_sample_data[under_sample_data.Class == 0]))
print('下采样异常数据个数:',len(under_sample_data[under_sample_data.Class == 1]))
3.过采样数据集:SMOTE算法对于少数类中的每一个样本x,以欧式距离为标准计算他到少数类样本集中所有样本的距离,得到其k近邻。
根据样本不平衡的比例设置一个采样比例以确定采样倍率N,从其k近邻中随机选择若干个样本,假设选择的近邻为xn。
对于每一个随机选出的近邻xn,分别与元样本按照如下的公式构建新的样本。 Xnew=x+rand(0,1)*欧氏距离
#向上采样SMOTE算法
from imblearn.over_sampling import SMOTE
#构造特征数据
oversampler=SMOTE(random_state=0)
X_oversample,Y_oversample=oversampler.fit_sample(X,Y.values.reshape(-1,1))
##查看数据
print('异常值个数:',len(Y_oversample[Y_oversample==1]))
print('正常值个数:',len(Y_oversample[Y_oversample==0]))
四、切分数据:训练集(80%)和测试集(20%)
1.原数据集
#导入切分数据模块
from sklearn.cross_validation import train_test_split
#切分原始数据:test_size--测试集比率
#random_state = 0 --每次随机得到的数据集是一样的
X_train,X_test,Y_train,Y_test=train_test_split(X,Y,test_size=0.2,random_state=0)
#查看数据
print('训练集数据个数:',len(X_train))
print('测试集数据个数:',len(X_test))
2.下采样数据集
#导入切分数据模块
from sklearn.cross_validation import train_test_split
#切分原始数据:test_size--测试集比率
#random_state = 0 --每次随机得到的数据集是一样的
X_train_undersample,X_test_undersample,Y_train_undersample,Y_test_undersample=train_test_split(X_undersample,Y_undersample,test_size=.2,random_state=0)
#查看数据
print('下采样训练集数据个数:',len(X_train_undersample))
print('下采样测试集数据个数:',len(X_test_undersample))
3.过采样数据集
#导入切分数据模块
from sklearn.cross_validation import train_test_split
#切分原始数据:test_size--测试集比率
#random_state = 0 --每次随机得到的数据集是一样的
X_train_oversample,X_test_oversample,Y_train_oversample,Y_test_oversample=train_test_split(X_oversample,Y_oversample,test_size=.2,random_state=0)
#查看数据
print('过采样训练集数据个数:',len(X_train_oversample))
print('过采样测试集数据个数:',len(X_test_oversample))
五、交叉验证对比模型
#逻辑回归
from sklearn.linear_model import LogisticRegression
from sklearn.cross_validation import KFold, cross_val_score
from sklearn.metrics import confusion_matrix,recall_score,classification_report
#构造交叉验证函数
def printing_Kfold_scores(x_train_data,y_train_data):
fold = KFold(len(y_train_data),5,shuffle=False)
recall_accs = []
for iteration, indices in enumerate(fold,start=1):
lr = LogisticRegression()
lr.fit(x_train_data.iloc[indices[0],:],y_train_data.iloc[indices[0],:].values.ravel())
y_pred= lr.predict(x_train_data.iloc[indices[1],:].values)
recall_acc = recall_score(y_train_data.iloc[indices[1],:].values,y_pred)
recall_accs.append(recall_acc)
print('Iteration ', iteration,': recall score = ', recall_acc)
mean_recall_score=np.mean(recall_accs)
print('****************************************************************************')
print('Mean recall score ', mean_recall_score)
print('****************************************************************************')
return mean_recall_score
1.原数据集
origin= printing_Kfold_scores(X_train,Y_train)
2.下采样数据集
undersample= printing_Kfold_scores(X_train_undersample,Y_train_undersample)
3.过采样数据集3.过采样数据集
3.过采样数据集
oversample= printing_Kfold_scores(X_train_oversample,Y_train_oversample)
由交叉验证结果可得,使用原始数据得到的模型最差,过采样数据集最好。虽然下采样数据集的平均评价系数也比较高,但是稳定性较差。所以接下来选择过采样数据集进行进一步的分析操作。
六、正则化惩罚项为了避免过度拟合,引入正则化惩罚项;
以前讨论的损失函数只有C0这个部分,在学术上称为“经验风险”,后半部分的损失函数(加入的正则化项的部分)叫做“结构风险”。
所谓的“经验风险”就是指由于拟合结果和样本标签之间的残差总和所产生的经验性差距所带来的风险是欠拟合的风险;“结构风险”就是刚才提到的模型不够“简洁”带来的风险,是不够泛化带来的风险。
#构造交叉验证函数
def printing_Kfold_scores(x_train_data,y_train_data):
fold = KFold(len(y_train_data),5,shuffle=False)
c_param_range = [0.01,0.1,1,10,100]
results_table = pd.DataFrame(index = range(len(c_param_range),2), columns = ['C_parameter','Mean recall score'])
results_table['C_parameter'] = c_param_range
j = 0
for c_param in c_param_range:
print('-------------------------------------------')
print('C parameter: ', c_param)
print('-------------------------------------------')
print('')
recall_accs = []
for iteration, indices in enumerate(fold,start=1):
lr = LogisticRegression(C = c_param, penalty = 'l1')
lr.fit(x_train_data.iloc[indices[0],:],y_train_data.iloc[indices[0],:].values.ravel())
y_pred= lr.predict(x_train_data.iloc[indices[1],:].values)
recall_acc = recall_score(y_train_data.iloc[indices[1],:].values,y_pred)
recall_accs.append(recall_acc)
print('Iteration ', iteration,': recall score = ', recall_acc)
results_table.loc[j,'Mean recall score'] = np.mean(recall_accs)
j += 1
print('')
print('Mean recall score ', np.mean(recall_accs))
print('')
best_c = results_table.loc[results_table['Mean recall score'].idxmax()]['C_parameter']
# Finally, we can check which C parameter is the best amongst the chosen.
print('****************************************************************************')
print('Best model to choose from cross validation is with C parameter = ', best_c)
print('****************************************************************************')
return best_c调用函数
best_c = printing_C_parameter(X_train_oversample,Y_train_oversample)这样,得到当惩罚项系数为1时模型的效果最后,所以之后的回归模型直接使用该参数即可。
七、混淆矩阵:用于进行模型评估混淆矩阵绘制函数
def plot_confusion_matrix(cm, classes,
title='Confusion matrix',
cmap=plt.cm.Blues):
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')构建回归模型并调用混淆矩阵函数
lr = LogisticRegression(C = best_c, penalty = 'l1')
lr.fit(X_train_oversample,Y_train_oversample.values.ravel())
Y_pred = lr.predict(X_test.values)
# Compute confusion matrix
cnf_matrix = confusion_matrix(Y_test,Y_pred)
np.set_printoptions(precision=2)
print("Recall metric in the testing dataset: ", cnf_matrix[1,1]/(cnf_matrix[1,0]+cnf_matrix[1,1]))
# Plot non-normalized confusion matrix
class_names = [0,1]
plt.figure()
plot_confusion_matrix(cnf_matrix
, classes=class_names
, title='Confusion matrix')
plt.show()
八、逻辑回归的阈值对结果的影响
lr = LogisticRegression(C = 0.01, penalty = 'l1')
lr.fit(X_train_oversample,Y_train_oversample.values.ravel())
Y_pred_oversample_proba = lr.predict_proba(X_test_oversample.values)
thresholds = [0.1,0.2,0.3,0.4,0.5,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_oversample_proba[:,1] > i
plt.subplot(3,3,j)
j += 1
# Compute confusion matrix
cnf_matrix = confusion_matrix(Y_test_oversample,Y_test_predictions_high_recall)
np.set_printoptions(precision=2)
print("Recall metric in the testing dataset: ", cnf_matrix[1,1]/(cnf_matrix[1,0]+cnf_matrix[1,1]))
# Plot non-normalized confusion matrix
class_names = [0,1]
plot_confusion_matrix(cnf_matrix
, classes=class_names
, title='Threshold >= %s'%i)随着阈值的增大,recall值越低,误杀率越低(把0判断为1的概率),错放率越高(把1判断为0的概率)。如果不能允许错放率,则其越低越高,当recall值为0.4时为最优解;如果为了降低工作量,想要较低的误杀率,同时保证较低的错放率,则recall值为0.5-0.8均可,实际选择视情况而定。