逻辑回归之信用卡
背景:有人利用信用卡欺诈,数据给出了28W多个样本,每一个样本有20多个因素数据和最终是否欺诈的结论。
1 import numpy as np 2 import pandas as pd 3 import matplotlib.pyplot as plt 4 #导入相关库文件 5 6 data = pd.read_csv('creditcard.csv') #读取文件 7 print(data.shape) #打印下文件内容是几行几列 8 count_class = pd.value_counts(data['Class'],sort = True).sort_index() 9 #pd.value_counts()对数据集中的['Class']列进行数学统计,统计其中每一类数据出现的个数。 10 #sort_index()是根据索引进行排序,在这个数据集中好像没有什么意义 11 print (count_class)
1 from sklearn.preprocessing import StandardScaler 2 # 导入sklearn数据预处理模块,sklearn是机器学期很重的一个库,有时间还是需要着重学习一下 3 data['normAmount'] = StandardScaler().fit_transform(data['Amount'].values.reshape(-1,1)) # -1表示系统自动计算得到的行,1表示1列 4 #StandardScaler().fit_transform是sklearn里可将数据标准化的函数,和scale()的功能类似,都可以将数据标准化,具体可以百度 5 #https://blog.csdn.net/weixin_38278334/article/details/82971752 .fit_transform的应用 6 #.values是将前边的字典转成数组,也就是转成list。.reshape(-1,1)是将前边的.list转成n行,1列,-1是不指定行数,自动计算得出n 7 #再将reshape之后的list赋值给data['normAmount'],等于为数据集多增加了一列。 8 data = data.drop(['Time','Amount'],axis = 1) 9 #观察数据发现,“Time”这一列没有什么作用,所以删除,“Amount”每个样本之间差距比较大,应将其标准化, 10 #结合上一行,实际就是把['Amount']一列进行标准化,并改名为normAmount。
通过观察数据发现,Class=0的样本有284315个,Class=1的样本有492个,典型的样本不均衡现象。处理方法有两种:过采样和下采样。过采样:增加Class=1的样本数量,增加至和Class=0一样多;下采样:减少Class=0的样本数量,减少至和Class=1的数量相同。
1 X = data.loc[:,data.columns != 'Class'] #选取非“Class”列,目的是将Class视为标签 2 #注意.loc和.iloc的区别。iloc主要使用数字来索引数据,不能使用字符型的标签来索引数据。 3 #loc只能使用字符型标签来索引数据,不能使用数字来索引数据。特殊情况:当dataframe的行标签或列标签为数字时,loc就可以来索引 4 #如果对数据很熟悉 5 y = data.loc[:,data.columns == 'Class'] # y 为标签,即类别 6 number_records_fraud = len(data[data.Class==1]) #统计异常值的个数 7 #print (number_records_fraud) # 492 个 8 fraud_indices = np.array(data[data.Class == 1].index) 9 #fraud_indices值是取原数据所有Class=1的样本的索引,并转为array的形式。 10 #fraud_indices是所有Class=1的行索引 11 normal_indices = data[data.Class == 0].index # 记录正常值的索引 12 normal_indices = np.array(normal_indices) 13 #normal_indices是取原数据所有Class=0的样本的索引,并转为array的格式。 14 random_normal_indices =np.random.choice(normal_indices,number_records_fraud,replace = False) 15 #目的是从正常值的索引中,选择和异常值相等个数的样本。 16 #np.random.choice()功能是在第一个参数normal_indices内随机去第二个参数number_records_fraud个数据,number_records_fraud是Class=1的样本数量 17 #random_normal_indices = np.array(random_normal_indices) 18 under_sample_indices = np.concatenate([fraud_indices,random_normal_indices]) 19 #np.concatenate是将两个array类型拼接成一个array,类似于append,但是适合大批量数据拼接.注意参数外有一个括号,可以help查询使用方法。 20 under_sample_data = data.iloc[under_sample_indices,:] 21 #under_sample_indices是组合好的新的数据的索引,是一个array,iloc是根据索引找到该数据 22 #under_sample_data最终的值是组合好的数据的新的数据集,是一个数据集 23 X_undersample = under_sample_data.iloc[:, under_sample_data.columns != 'Class'] # 下采样后的训练集 24 #找到新数据集under_sample_data中Class=1的所有列 25 y_undersample = under_sample_data.iloc[:, under_sample_data.columns == 'Class'] # 下采样后的标签 26 #找到新数据集under_sample_data中Class=0的所有列 27 print(len(under_sample_data[under_sample_data.Class == 1]) / len(under_sample_data)) # 正负样本的比例都是 0.5 28 #under_sample_data.Class == 1是个布尔值的array,under_sample_data[]取到under_sample_data中Class=1的所有数据
其实不难发现,上边的代码很多,其实就作了一件事,下采样。随机取Class=0中的若干个样本(数量等于Class=1的数量),重新组合数据。
1 from sklearn.model_selection import train_test_split 2 #新版本的要用sklearn.model_selection,网上很多用的是老版本的库,会报错 3 # from sklearn.cross_validation import train_test_split 4 # ModuleNotFoundError: No module named 'sklearn.cross_validation' 5 X_train,X_test,y_train,y_test = train_test_split(X,y,test_size = 0.3,random_state = 0) 6 #将原始数据按照参数test_size比例切割为训练集和测试集。 7 X_train_undersample, X_test_undersample, y_train_undersample, y_test_undersample = train_test_split( 8 X_undersample, y_undersample, test_size=0.3, random_state=0) 9 #将下采样后的数据按照参数test_size比例切割为训练集和测试集。 10 #这是数据分析预测很重要的一部。将数据分为两部分,第一部分是预测模型用的,第二部分是模型确定后,用来拟合数据,评估模型好坏的。
下一部分代码是比较核心的一部分,用sklearn库里的函数来建立模型。
1 def printing_Kfold_scores(x_train_data,y_train_data): 2 fold = KFold(5,shuffle=False) 3 #KFold就是建立一个交叉验证的模型。没有将数据集代入参数。5的意思是切成5部分,其中4部分和1部分各位1组。 4 c_param_range = [0.01,0.1,1,10,100] 5 #惩罚项,在逻辑回归中可以设置惩罚项,分为l1和l2两种。简单来说,l1可以防止过拟合现象(过拟合就是训练接表现很好,但是测试集表现很差) 6 #l2可以突出某一个参数的权重。惩罚项是需要人为设定的,这里设置多个惩罚项参数,可以对比哪一更好。 7 results_table = pd.DataFrame(index = range(len(c_param_range),2),columns = ['C_parameter','Mean recall score']) 8 #建立一个新的DataFrame,行索引是[0,1,2,3,4],列是['C_parameter','Mean recall score'], 9 #'C_parameter'储存惩罚项参数,'Mean recall score'储存在这一惩罚项参数下的平均recall值 10 #recall值反应预测是否准确。例如10个病人,预测出来了8个,recall=80% 11 #其实index = range(len(c_param_range),2)并没有什么用,因为具体有多少行是由c_param_range内容确定的。 12 results_table['C_parameter'] = c_param_range 13 j = 0 14 for c_param in c_param_range: 15 #此循环的内容是惩罚项参数,也就是计算一种交叉验证情况下的,5种惩罚项参数的recall值 16 print('------------------------------------------') 17 print('C parameter:', c_param) 18 print('-------------------------------------------') 19 print('') 20 recall_accs = []#建立新的array,后后续计算每一种惩罚项参数下的recall做准备 21 for iteration, indices in enumerate(fold.split(x_train_data),start=1): 22 #iteration=[1,2,3,4,5],enumerate有一个参数(start=1),从1开始数 23 lr = LogisticRegression(C=c_param, penalty='l1', solver='liblinear', max_iter=10000) 24 # 使用特定的C参数调用逻辑回归模型 25 # 参数 solver=’liblinear’ 消除警告 26 # 出现警告:模型未能收敛 ,请增加收敛次数 27 # ConvergenceWarning: Liblinear failed to converge, increase the number of iterations. 28 # "the number of iterations.", ConvergenceWarning) 29 # 增加参数 max_iter 默认1000 30 lr.fit(x_train_data.iloc[indices[0], :], y_train_data.iloc[indices[0], :].values.ravel()) 31 #例如,交叉验证,分为5组数据[0,1,2,3,4],可以分为[0,1,2,3]和[4],[4,1,2,3]和[0],[0,4,2,3]和[1],[0,1,4,3]和[2],[0,1,2,4]和[3] 32 #indices[0]是[[0,1,2,3],[4,1,2,3],[0,4,2,3],[0,1,4,3]],indices[1]是[[4],[0],[1],[2]] 33 #注意,0、1、2、3、4只是索引,KFold分的是索引! 34 # Use the training data to fit the model. In this case, we use the portion 35 # of the fold to train the model with indices[0], We then predict on the 36 # portion assigned as the 'test cross validation' with indices[1] 37 38 y_pred_undersample = lr.predict(x_train_data.iloc[indices[1], :].values) 39 #利用模型预测数据。输入是x_train_data.iloc[indices[1],也就是前边分好的,在交叉验证里每组数据的第二部分。 40 #predict返回的是一个大小为n的一维数组,第i个预测样本的标签; 41 # Predict values using the test indices in the training data 42 43 # Calculate the recall score and append it to a list for recall scores 44 # representing the current c_parameter 45 recall_acc = recall_score(y_train_data.iloc[indices[1], :].values, y_pred_undersample) 46 #计算召回率,y_train_data.iloc[indices[1], :]是真实的数据,y_pred_undersample是预测的数据 47 recall_accs.append(recall_acc) 48 #把刚刚计算完的一种交叉验证情况下的召回率,储存,一会要计算5种交叉验证的召回率的均值 49 print('Iteration ', iteration, ': recall score = ', recall_acc) 50 # the mean value of those recall scores is the metric we want to save and get 51 # hold of. 52 results_table.loc[j, 'Mean recall score'] = np.mean(recall_accs) 53 j += 1 54 print('') 55 print('Mean recall score ', np.mean(recall_accs)) 56 print('') 57 best_c = results_table.loc[results_table['Mean recall score'].astype('float64').idxmax()]['C_parameter'] 58 #best_c内容是所有的惩罚项参数中,recall值最大的参数。 59 return best_c
将前边下采样的数据集代入printing_Kfold_scores函数,计算打印先关数据。需要注意,results_table['Mean recall score']是一个object,需要转为浮点型,才能比大小。
best_c = printing_Kfold_scores(X_train_undersample,y_train_undersample)
打印结果
------------------------------------------ C parameter: 0.01 ------------------------------------------- Iteration 1 : recall score = 0.9726027397260274 Iteration 2 : recall score = 0.9315068493150684 Iteration 3 : recall score = 1.0 Iteration 4 : recall score = 0.9594594594594594 Iteration 5 : recall score = 0.9848484848484849 Mean recall score 0.9696835066698082 ------------------------------------------ C parameter: 0.1 ------------------------------------------- Iteration 1 : recall score = 0.8493150684931506 Iteration 2 : recall score = 0.863013698630137 Iteration 3 : recall score = 0.9491525423728814 Iteration 4 : recall score = 0.9459459459459459 Iteration 5 : recall score = 0.8939393939393939 Mean recall score 0.9002733298763017 ------------------------------------------ C parameter: 1 ------------------------------------------- Iteration 1 : recall score = 0.863013698630137 Iteration 2 : recall score = 0.9041095890410958 Iteration 3 : recall score = 0.9830508474576272 Iteration 4 : recall score = 0.9459459459459459 Iteration 5 : recall score = 0.9242424242424242 Mean recall score 0.924072501063446 ------------------------------------------ C parameter: 10 ------------------------------------------- Iteration 1 : recall score = 0.9178082191780822 Iteration 2 : recall score = 0.8904109589041096 Iteration 3 : recall score = 0.9830508474576272 Iteration 4 : recall score = 0.9324324324324325 Iteration 5 : recall score = 0.9242424242424242 Mean recall score 0.929588976442935 ------------------------------------------ C parameter: 100 ------------------------------------------- Iteration 1 : recall score = 0.9178082191780822 Iteration 2 : recall score = 0.8904109589041096 Iteration 3 : recall score = 0.9830508474576272 Iteration 4 : recall score = 0.9594594594594594 Iteration 5 : recall score = 0.9242424242424242 Mean recall score 0.9349943818483405
很容易发现,惩罚项参数=0.01的时候,recall值是最低的,但是这并不代表最好的。
下边从混淆矩阵的角度,说明问题。
1 import seaborn as sns #导入seaboard库 2 def plot_confusion_matrix(cnf_matrix): #定义画图函数 3 sns.heatmap(cnf_matrix, fmt="d", annot=True, cmap='Blues') 4 ##cnf_matrix是计算好的混淆矩阵,没有fmt="d"热力图中会出现科学计数法e,cmap设置颜色 5 plt.ylabel("True label") #纵坐标label 6 plt.xlabel("Predicted label")#横坐标label 7 8 lr = LogisticRegression(C=best_c, penalty='l1', solver='liblinear') 9 #用刚才惩罚项中recall值最大的参数 10 lr.fit(X_train_undersample, y_train_undersample.values.ravel()) 11 #用下采样的训练数据,建立模型。其实是把刚才的工作又做了一遍。刚才是通过交叉验证,计算出哪一个惩罚项参数的recall值最大,现在确定你了,又重新算 12 y_pred_undersample = lr.predict(X_test_undersample.values) 13 #用建立好的模型,预测y 14 y_pred_undersample = y_pred_undersample.tolist() 15 #转成list,因为cnf_matrix函数参数要求是list 16 y_test_undersample = y_test_undersample['Class'].tolist() 17 #同上 18 cnf_matrix = confusion_matrix(y_test_undersample, y_pred_undersample) 19 #cnf_matrix返回值是一个二维数组,也就是说是个ndarray类型的 20 print("Recall metric in the testing dataset:", 21 cnf_matrix[1, 1] / (cnf_matrix[1, 0] + cnf_matrix[1, 1])) 22 #打印recall值 23 plot_confusion_matrix(cnf_matrix) 24 #画出混淆矩阵,原来博主写的看不太懂,我觉得用heatmap更加简单些 25 plt.show() 26 #打印
输出:
召回率也还可以,0.93多,但是由于采取的是下采样,样本数量是总样本的一部分,具有片面性。另外,发现有8个人,真实为1,预测为0,等于说没预测到。
best_c = printing_Kfold_scores(X_train, y_train)
输出如下:
------------------------------------------ C parameter: 0.1 ------------------------------------------- Iteration 1 : recall score = 0.5671641791044776 Iteration 2 : recall score = 0.6164383561643836 Iteration 3 : recall score = 0.6833333333333333 Iteration 4 : recall score = 0.5846153846153846 Iteration 5 : recall score = 0.525 Mean recall score 0.5953102506435158 ------------------------------------------ C parameter: 1 ------------------------------------------- Iteration 1 : recall score = 0.5522388059701493 Iteration 2 : recall score = 0.6164383561643836 Iteration 3 : recall score = 0.7166666666666667 Iteration 4 : recall score = 0.6153846153846154 Iteration 5 : recall score = 0.5625 Mean recall score 0.612645688837163 ------------------------------------------ C parameter: 10 ------------------------------------------- Iteration 1 : recall score = 0.5522388059701493 Iteration 2 : recall score = 0.6164383561643836 Iteration 3 : recall score = 0.7333333333333333 Iteration 4 : recall score = 0.6153846153846154 Iteration 5 : recall score = 0.575 Mean recall score 0.6184790221704963 ------------------------------------------ C parameter: 100 ------------------------------------------- Iteration 1 : recall score = 0.5522388059701493 Iteration 2 : recall score = 0.6164383561643836 Iteration 3 : recall score = 0.7333333333333333 Iteration 4 : recall score = 0.6153846153846154 Iteration 5 : recall score = 0.575 Mean recall score 0.6184790221704963 Process finished with exit code 0
发现虽然下采样模型的recall比较高,但是用下采样的模型预测原始数据,recall才0.61多,所以存在局限性。
#再绘制混淆矩阵看看 lr = LogisticRegression(C=best_c, penalty='l1', solver='liblinear') lr.fit(X_train, y_train.values.ravel()) y_pred = lr.predict(X_test.values) y_pred = y_pred.tolist() y_test = y_test['Class'].tolist() cnf_matrix = confusion_matrix(y_test, y_pred) print("Recall metric in the testing dataset:", cnf_matrix[1, 1] / (cnf_matrix[1, 0] + cnf_matrix[1, 1])) #打印recall值 print('cnf_matrix[1, 1]=',cnf_matrix[1, 1],'cnf_matrix[1, 0]=',cnf_matrix[1, 0],'cnf_matrix[1, 1]=',cnf_matrix[1, 1]) plot_confusion_matrix(cnf_matrix) plt.show()
输出混淆矩阵:
发现问题,如果从准确率的角度看,很高,但是从recall值看,很低,recall才0.61多。真实值为1的,预测值为0的,有56个人,这些人是检测不出来的。印证了刚才所说的下采样的片面性。
从另外一个角度讨论,如果修改预测0或1的阈值,结果怎么样?lr.predict函数默认阈值是0.5,如果修改阈值,需要使用lr.predict_proba函数。
lr = LogisticRegression(C=0.01, penalty='l1', solver='liblinear') lr.fit(X_train_undersample, y_train_undersample.values.ravel()) y_pred_undersample_proba = lr.predict_proba(X_test_undersample.values) #predict返回的是0或1,predict_proba返回的是0或1的概率值,所以y_pred_undersample_proba.shape=(296, 2) #第0列是预测值为0的概率,第1列是预测值为1的概率。在这个背景下,预测结果只有0和1,在其他背景下,可能有0、1、2等等。 #所以每一行相加的概率值为1 thresholds = [0.1, 0.2, 0.3, 0.4, 0.5, 0.6, 0.7, 0.8, 0.9] # 阈值列表 #目的是画出,不同阈值下的recall值和图像。比如阈值=0.1,大于0.1的意思是有10%的概率认为是确诊,就归为确定病例。 plt.figure(figsize=(10, 10)) #设定图的长宽 j = 1 for i in thresholds: #遍历几种情况下的阈值 y_test_predictions_high_recall = y_pred_undersample_proba[:, 1] > i # 如果预测的概率值大于阈值,就返回True,所以y_test_predictions_high_recall的值是一个bool型的list ax = plt.subplot(3, 3, j) #设定子图画板 ax.set_title('Threshold >= %s' % i) j += 1 cnf_matrix = confusion_matrix(y_test_undersample, y_test_predictions_high_recall) print("Recall metric in the testing dataset:", cnf_matrix[1, 1] / (cnf_matrix[1, 0] + cnf_matrix[1, 1])) plot_confusion_matrix(cnf_matrix) plt.show()
输出图像:
很容易看出来,阈值是0.5或0.6的情况下,recall值会好点。
再换种角度分析问题,既然下采样预测原始数据的效果不好,采用过采样呢?过采样就要增加样本数量,也就是Class=1的样本数量。
from imblearn.over_sampling import SMOTE #导入SMOTE算法,第一次用需要安装,在anaconda Prompt下pip install imblearn就可以 credit_cards = pd.read_csv('creditcard.csv') columns = credit_cards.columns features_columns = columns.delete(len(columns) - 1) features = credit_cards[features_columns] labels = credit_cards['Class'] features['normAmount'] = StandardScaler().fit_transform(features['Amount'].values.reshape(-1,1)) print(features) features = features.drop(['Time','Amount'],axis = 1) print(features) features_train, features_test, labels_train, labels_test = train_test_split( features, labels, test_size=0.2, random_state=0) oversampler = SMOTE(random_state=0) os_features,os_labels = oversampler.fit_sample(features_train,labels_train) os_features = pd.DataFrame(os_features) os_labels = pd.DataFrame(os_labels)
注意SMOTE.fit_sample(features_train,labels_train)
features_train:特征集
labels_train:标签集
该函数自动判断哪个是少数不平衡的类别,所以输入时将整体的训练集输入即可,不用将少数集预先提出。
输出:
------------------------------------------ C parameter: 0.01 ------------------------------------------- Iteration 1 : recall score = 0.8838709677419355 Iteration 2 : recall score = 0.8947368421052632 Iteration 3 : recall score = 0.9668916675888016 Iteration 4 : recall score = 0.9561886547740737 Iteration 5 : recall score = 0.9569360635737132 Mean recall score 0.9317248391567574 ------------------------------------------ C parameter: 0.1 ------------------------------------------- Iteration 1 : recall score = 0.8838709677419355 Iteration 2 : recall score = 0.8947368421052632 Iteration 3 : recall score = 0.9688170853159235 Iteration 4 : recall score = 0.9586836812081644 Iteration 5 : recall score = 0.9592002725843858 Mean recall score 0.9330617697911343 ------------------------------------------ C parameter: 1 ------------------------------------------- Iteration 1 : recall score = 0.8838709677419355 Iteration 2 : recall score = 0.8947368421052632 Iteration 3 : recall score = 0.9687064291247095 Iteration 4 : recall score = 0.9590683769138612 Iteration 5 : recall score = 0.9597498378782383 Mean recall score 0.9332264907528016 ------------------------------------------ C parameter: 10 ------------------------------------------- Iteration 1 : recall score = 0.8838709677419355 Iteration 2 : recall score = 0.8947368421052632 Iteration 3 : recall score = 0.9689498727453801 Iteration 4 : recall score = 0.9591343247491234 Iteration 5 : recall score = 0.959486046537189 Mean recall score 0.9332356107757782 ------------------------------------------ C parameter: 100 ------------------------------------------- Iteration 1 : recall score = 0.8838709677419355 Iteration 2 : recall score = 0.8947368421052632 Iteration 3 : recall score = 0.9689941352218656 Iteration 4 : recall score = 0.9588815247139513 Iteration 5 : recall score = 0.9595629856783284 Mean recall score 0.9332092910922688
从结果来看,每个惩罚系数都是差不多的。
lr = LogisticRegression(C = best, penalty = 'l1', solver='liblinear') lr.fit(os_features,os_labels.values.ravel()) y_pred = lr.predict(features_test.values) cnf_matrix = confusion_matrix(labels_test,y_pred) plot_confusion_matrix(cnf_matrix) plt.show()
输出的混淆矩阵:
从输出的混淆矩阵来看,效果不错,至少recall=95/(95+6)=0.94多,只有6个人患病,但是没有预测到。比下采样好多了。
总结:除了学会处理样本不均衡的方法外,对于应用来说,不用过于关注算法具体如何实现的,只需要知道算法调用的函数名称、参数配置就可以。但是如果是写论文来说,想优化算法,就需要搞清楚算法的本质,用自己的代码去实现算法,再做优化。
本博客参考的是唐宇迪的机器学期视频,以及https://www.cnblogs.com/xiaoyh/p/11209909.html朋友的博客。