机器学习相关操作分享(二)

<h1>目录<span class="tocSkip"></span></h1>
<div class="toc"><ul class="toc-item"><li><span><a href="#1-数据说明与导入" data-toc-modified-id="1-数据说明与导入-1">1 数据说明与导入</a></span></li><li><span><a href="#2-数据初步探索" data-toc-modified-id="2-数据初步探索-2">2 数据初步探索</a></span><ul class="toc-item"><li><span><a href="#2.1-数据信息检查" data-toc-modified-id="2.1-数据信息检查-2.1">2.1 数据信息检查</a></span></li><li><span><a href="#2.2-数据清洗" data-toc-modified-id="2.2-数据清洗-2.2">2.2 数据清洗</a></span></li><li><span><a href="#2.3-数据可视化与分析" data-toc-modified-id="2.3-数据可视化与分析-2.3">2.3 数据可视化与分析</a></span><ul class="toc-item"><li><span><a href="#2.3.1-数据分布情况" data-toc-modified-id="2.3.1-数据分布情况-2.3.1">2.3.1 数据分布情况</a></span></li><li><span><a href="#2.3.2-结合违约情况查看数据分布" data-toc-modified-id="2.3.2-结合违约情况查看数据分布-2.3.2">2.3.2 结合违约情况查看数据分布</a></span></li><li><span><a href="#2.3.3-展示数据相关性" data-toc-modified-id="2.3.3-展示数据相关性-2.3.3">2.3.3 展示数据相关性</a></span></li></ul></li></ul></li><li><span><a href="#3-分类建模" data-toc-modified-id="3-分类建模-3">3 分类建模</a></span><ul class="toc-item"><li><span><a href="#3.1-KNN模型" data-toc-modified-id="3.1-KNN模型-3.1">3.1 KNN模型</a></span></li><li><span><a href="#3.2-决策树" data-toc-modified-id="3.2-决策树-3.2">3.2 决策树</a></span></li></ul></li><li><span><a href="#4-引入新评价指标" data-toc-modified-id="4-引入新评价指标-4">4 引入新评价指标</a></span></li><li><span><a href="#5-集成学习" data-toc-modified-id="5-集成学习-5">5 集成学习</a></span><ul class="toc-item"><li><span><a href="#5.1-XGBoost" data-toc-modified-id="5.1-XGBoost-5.1">5.1 XGBoost</a></span></li><li><span><a href="#5.2-CATBOOST" data-toc-modified-id="5.2-CATBOOST-5.2">5.2 CATBOOST</a></span></li></ul></li><li><span><a href="#6-结论" data-toc-modified-id="6-结论-6">6 结论</a></span></li></ul></div>
# 1 数据说明与导入
本案例使用的数据集为台湾地区信用卡客户数据,具体的数据包括:每位客户案例的编号、客户的信用卡额度、性别、受教育程度、婚姻状况、年龄、在记录数据期间(2005年4-9月)每个月的还款情况、账单记录和支付记录,以及该客户在接下来的一个月中违约与否的记录。具体数据字段的表格信息记录如下:
| 列名 | 含义说明 | 数据类型 |
|:----|:----------:|----:|
| ID | 客户ID。 | int64 |
| LIMIT_BAL |    银行给予客户的信用额度,包括个人信用额度和客户的家庭信用额度。 | float64 |
| SEX |    客户的性别。男性记为1,女性记为2。 | int64 |
| EDUCATION |    客户的教育水平。研究生及以上记为1,大学记为2,高中记为3,其它记为0。 | int64 |
| MARRIAGE |    客户的婚姻状况。未婚记为2,已婚记为1,其它记为0。 | int64|
| AGE |    客户的年龄。 | int64 |
| PAY_1~PAY_6 |    这六个变量是2005年4月到9月每月的还款记录,取值为-1~9,代表累计逾期月数。 | int64 |
| BILL_AMT1~BILL_AMT6 |    这六个变量是2005年4月到9月每月的账单记录,即每月用信用卡消费记录。 | float64 |
| PAY_AMT1~PAY_AMT6 |    这六个变量是2005年4月到9月每月的支付记录,包括还账单金额和存入信用卡的金额。 | float64 |
| default.payment.next.month |    代表客户下个月是否违约,违约记为1,未违约则记为0。 | int64 |
我们首先导入整个案例过程中需要用到的部分包,主要包括Pandas、NumPy、可视化需要的Matplotlib、Seaborn以及一些机器学习包。
import pandas as pd
import numpy as np
import matplotlib.pyplot as plt
import seaborn as sns
import warnings
%matplotlib inline

import sklearn
from sklearn.model_selection import train_test_split
from sklearn.neighbors import KNeighborsClassifier 
from sklearn import metrics 
from sklearn.metrics import precision_recall_curve
from sklearn.metrics import confusion_matrix
from sklearn import preprocessing

import warnings
warnings.filterwarnings('ignore')
我们使用`read_csv`函数导入数据,并将数据储存为`df`。
df = pd.read_csv('./input/data.csv')
# 2 数据初步探索
## 2.1 数据信息检查
首先,我们查看数据的前五行,检查数据读取情况。
df.head(5)
下面我们通过`shape`查看数据的大小。
df.shape
接下来我们使用`info`检查数据的格式等基本信息。
df.info()
可以看到,数据的大小为30000x25,且其中不存在任何的缺失值,非常完整。
我们还可以通过`describe`查看数据字段的基本统计信息,以获取数据的进一步信息。
df.describe()
一共有30000名信用卡持卡人的信息,这些持卡人信用卡平均额度`LIMIT_BAL`为167484,但是标准差达到了129747,信用卡最大额度达到100万新台币,额度最小的仅有1000新台币,跨度非常大;我们已知2代表女性,1代表男性,性别`SEX`的平均值在1.6以上,说明女性在该样本中的占比明显高于男性。同时我们也发现与描述不符的异常数据,教育程度`EDUCATION`的最大值和最小值均在给定的字段描述之外,婚姻状况`MARRIAGE`有标记为3的点,`PAY_1`到`PAY_6`的还款记录中出现了描述之外的-2,我们应逐一进行理解与清洗。
## 2.2 数据清洗
首先对教育程度的字段用`value_counts`进行查看:
df['EDUCATION'].value_counts()
df.groupby(['EDUCATION'])['default.payment.next.month'].mean()
教育程度`EDUCATION`标记为4,5,6的值都可以理解为"其他",代表低于高中的学历。为了将学历按低到高进行排序,还需将标签为1和标签为3的数据互换标签。我们使用`replace`完成上述替换,`inplace`参数设置为`True`表示替换原数据。
df['EDUCATION'].replace([1,3,4,5,6],[3,1,0,0,0],inplace=True)
df['EDUCATION'].value_counts()
性别`SEX`的数据中,男性和女性标记为1和2,我们将其替换为0和1比较符合编程习惯。
df['SEX'].replace([1,2],[0,1],inplace=True)
df['SEX'].value_counts()
我们对婚姻状况`MARRIAGE`一列也同样进行查看。
df['MARRIAGE'].value_counts()
婚姻状况标记为3可以理解为"其他",我们将其改为"其他"所对应的值0。
df['MARRIAGE'].replace(3,0,inplace=True)
df['MARRIAGE'].value_counts()
df.groupby(['MARRIAGE'])['default.payment.next.month'].mean()
   最后,我们对标记还款状况的列 `PAY_1` 到`PAY_6` 也同样进行查看。首先取 `PAY_1` 一列为例子。
df['PAY_6'].value_counts()
df.groupby(['PAY_6'])['default.payment.next.month'].mean()
还款为-2,-1,0均应视为还清债务,我们将其统一标记为0。经过验证`PAY_1`到`PAY_6`的数据具有同样特征,我们用`iloc`定位我们要替换的列,进行替换操作。
df.iloc[:,[6,7,8,9,10,11]] = df.iloc[:,[6,7,8,9,10,11]].replace([-2,-1],[0,0])
df.iloc[:,[6,7,8,9,10,11]].describe()
样本标签应该只预测为0与1,我们对不同标签的比例比较感兴趣,利用`value_counts`返回的Series进行计算。
df['default.payment.next.month'].value_counts()[0]/df['default.payment.next.month'].value_counts()[1]
df['default.payment.next.month'].value_counts(normalize=True)
可以看到两种样本标签比例在3.5左右,数据存在类别不平衡问题。
## 2.3 数据可视化与分析
我们还可以通过可视化分析,来获取更多有关于数据的特征和数据特征相关性的信息。
### 2.3.1 数据分布情况
首先我们展示一些数据的分布情况,例如对于连续型变量`LIMIT_BAL`,查看数据分布状况。对于离散型变量`SEX`、`EDUCATION`,`MARRIAGE`,查看其中不同类型数据的占比情况。
sns.set(rc={'figure.figsize':(10,5),"font.size":15,"axes.titlesize":15,"axes.labelsize":15})
plt.title('信用额度分布图')

sns.set_color_codes("pastel")
sns.distplot(df['LIMIT_BAL'],kde=True,bins=200, color="blue")
plt.xlabel("信用额度")

plt.rcParams['figure.dpi'] = 1200 #为了使图片更加清晰
plt.show()
df['LIMIT_BAL'].value_counts()
可以看到信用额度的分布很不均匀,少数人群的信用额度非常高,大部分人的信用额度集中在较低的值。
sns.set(rc={'figure.figsize':(10,5),"font.size":15,"axes.titlesize":15,"axes.labelsize":15})
plt.title('客户年龄分布图')

sns.set_color_codes("pastel")
sns.distplot(df['AGE'],kde=True,bins=59, color="green")
plt.xlabel("年龄")
plt.show()
从图中能够发现,客户的年龄主要集中在20-40岁之间。
fig = plt.subplots(1,3,figsize=(16,12))
sns.set(rc={'figure.figsize':(10,5),"font.size":15,"axes.titlesize":15,"axes.labelsize":15})

plt.subplot(1,3,1)
sex_count = df['SEX'].value_counts()
sex_count.plot(kind='pie', labels=['女', '男'], autopct='%1.1f%%')
plt.ylabel("")
plt.title('性别分布图')

plt.subplot(1,3,2)
edu_count = df['EDUCATION'].value_counts()
edu_count.plot(kind='pie', labels=['本科', '研究生或以上','高中','其他'], autopct='%1.1f%%')
plt.title('教育情况分布图')
plt.ylabel("")

plt.subplot(1,3,3)
mar_count = df['MARRIAGE'].value_counts()
mar_count.plot(kind='pie', labels=['未婚', '已婚','其他'], autopct='%1.1f%%')
plt.title('婚姻状况分布图')
plt.ylabel("")

plt.show()
从本图中可以看出性别的分布不均匀,女性占比显著高于男性。教育情况上,72%的客户教育程度都在本科或以上。婚姻状况则显示已婚和未婚客户的数目差距不多。
### 2.3.2 结合违约情况查看数据分布
得到了数据分布信息后,我们再结合对应的标签查看这些数据的特征,来考察对于不同的性别,年龄和教育情况,客户的违约情况是否有着比较显著的区别。
sns.set(rc={'figure.figsize':(13,5),"font.size":15,"axes.titlesize":15,"axes.labelsize":15})
plt.figure()
fig, ax = plt.subplots(1,3,figsize=(12,5))

plt.subplot(1,3,1)
ax=sns.countplot(x='SEX',hue='default.payment.next.month',data = df)
ax.set_xticklabels(['男', '女'],fontsize=12)
plt.xlabel("性别")
plt.ylabel("")
plt.title('不同性别违约情况')

plt.subplot(1,3,2)
ax=sns.countplot(x='EDUCATION',hue='default.payment.next.month',data = df)
ax.set_xticklabels(['其他', '高中','本科','研究生及以上'],fontsize=12)
plt.xlabel("教育程度")
plt.ylabel("")
plt.title('不同教育程度违约情况')


plt.subplot(1,3,3)
ax=sns.countplot(x='MARRIAGE',hue='default.payment.next.month',data = df)
ax.set_xticklabels(['其他', '已婚','未婚'], fontsize=12)
plt.xlabel("婚姻状况")
plt.ylabel("")
plt.title('不同婚姻状况违约情况')

plt.show()
从图中可以看出,虽然男性的数量远低于女性,但违约人数并没有明显低于女性违约人数。对于婚姻状况也存在类似情况,未婚的人数比已婚人数多,但违约人数和已婚的违约人数相差无几,也说明了未婚人数的违约概率可能要大一些。我们再考察对于年龄和信用卡额度的违约情况。
t0 = df[df['default.payment.next.month'] == 0]
t1 = df[df['default.payment.next.month'] == 1]
plt.figure()
fig, ax = plt.subplots(1,2,figsize=(12,6))

plt.subplot(1,2,1)
sns.distplot(t0['AGE'],kde_kws={"color":"blue","label":"未违约"})
sns.distplot(t1['AGE'],kde_kws={"color":"orange","label":"违约"})
plt.xlabel('年龄', fontsize=12)
locs, labels = plt.xticks()
plt.title('不同年龄违约情况')
plt.tick_params(axis='both', which='major', labelsize=12)

plt.subplot(1,2,2)
sns.distplot(t0["LIMIT_BAL"],kde_kws={"color":"blue","label":"未违约"})
sns.distplot(t1["LIMIT_BAL"],kde_kws={"color":"orange","label":"违约"})
plt.xlabel('信用卡额度', fontsize=12)
locs, labels = plt.xticks()
plt.title('不同信用卡额度违约情况')
plt.tick_params(axis='both', which='major', labelsize=12)

plt.show()
仔细观察上图,我们可以获取更多有趣的信息。例如30岁前,客户的违约概率更大;30-40岁之间是客户信用最高的区间,40岁之后再次下降;信用卡额度较低的客户违约概率更高。
### 2.3.3 展示数据相关性
为了观察变量间的相关性,我们也可以为连续型变量绘制一个热力图。
plt.figure(figsize = (12,12))
df1 = df.iloc[:,[1,6,7,8,9,10,11,12,13,14,15,16,17,18,19,20,21,22,23,24]].copy()
plt.title('连续型变量皮尔逊相关系数热力图',fontsize=20)
sns.heatmap(df1.corr(),annot = True,cmap="Blues",annot_kws={'fontsize':9})
可以看到每个用户不同月的消费记录之间具有强线性相关性,颜色十分深。还款记录也具有一定相关性,相对而言支付记录的相关性则比较低。还应该对离散型变量`SEX`,`EDUCATION`,`MARRIAGE`,`default.payment.next.month`进行检验。我们用Sklearn特征选择中的`chi2`输出离散特征的卡方统计量,并通过`score`和`pvalue`来评价这些特征和违约与否之间的相关性。
from sklearn.feature_selection import SelectKBest
from sklearn.feature_selection import chi2

y = df['default.payment.next.month']
X = df[['PAY_1','PAY_2','SEX','EDUCATION','MARRIAGE']]

chi2_selector = SelectKBest(chi2, k=4)
chi2_selector.fit(X, y)
chi2_selector.scores_
chi2_selector.pvalues_
`scores`越高,则说明特征和违约与否之间的相关性越强,反之则越弱;`pvalues`的值反之。
相对而言,性别的影响最大,婚姻情况的影响则最小。
# 3 分类建模
我们首先尝试使用一些基本的机器学习模型对数据直接进行分类建模,并给出分类的预测结果。我们选用比较经典的KNN和决策树来观察效果。
## 3.1 KNN模型
在KNN模型中,由于涉及到欧氏空间的距离概念,因此对于无序离散型变量,应该先对其做哑变量编码处理。我们对无序离散变量`SEX`,`MARRIAGE`进行哑变量编码。
str_columns = ['SEX','MARRIAGE']
df_new = pd.get_dummies(df, columns=str_columns)
df_new
我们将数据集划分为标签列`y`和特征列`X`,为后面的处理做好准备。
y = df_new['default.payment.next.month']
X = df_new.drop(columns=['ID','default.payment.next.month'])
对于数值型的变量,不同变量定义的数值变化范围差的非常多,我们还应对这部分数据进行标准化处理。由于部分变量在总体中的最大最小值未知(例如客户的信用卡额度`LIMIT_BAL`),因此我们选用ZScore标准化对特征列进行标准化处理,使得处理过后的每一列数据都是均值为0,标准差为1的数据。
X.iloc[:,0:20] = preprocessing.scale(X.iloc[:,0:20])
X.sample(5)
我们选取某一列数据,打印出它的均值和标准差,验证ZScore标准化的效果。
print ('均值为:{a}'.format(a = X.LIMIT_BAL.mean()))
print ('标准差为:{a}'.format(a = X.LIMIT_BAL.std()))
可以看到均值逼近0,标准差逼近1,说明标准化成功,接下来我们对数据进行训练集和测试集的划分。选取测试集大小为0.3,`random_state`参数指伪随机数的状态参数)。
X_train, X_test,\
y_train, y_test \
= train_test_split(X, y, test_size=0.3, random_state=0)
划分训练集测试集后,打印出标签为1和0的值在训练集和测试集中的占比,测试划分效果。
print (y_train.value_counts()/len(y_train))
print (y_test.value_counts()/len(y_test))
下面,我们使用sklearn中的`KNeighborsClassifier`对训练集进行分类训练。当不输入参数时,默认KNN所选取的`n_neighbors`参数取值为5。
knn_model = KNeighborsClassifier(n_neighbors=12,weights='distance')
knn_model.fit(X_train, y_train)
得到模型`knn_model`后,我们在测试集上验证其分类效果,用`predict`方法获取模型对每一个测试集样本标签的预测结果`knn_pred`。用`predict_proba`方法获取模型把每一个样本划分为正类的概率`knn_score`。
knn_score = knn_model.predict_proba(X_test)[:,1]
knn_pred = knn_model.predict(X_test)
为了展示分类模型的效果,我们需要获取模型的AUC得分(`accuracy_score`),分类报告以及混淆矩阵。为了方便后续使用,我们定义一个函数`Get_report`来获取这些信息。
def Get_report(testers , predictors):
    print ('模型的AUC Score为:{a}'.format(a=metrics.accuracy_score(testers, predictors)))
    print ('模型的分类报告展示如下:')
    print (metrics.classification_report(testers, predictors))
    print ('模型的混淆矩阵展示如下:')
    plt.figure(figsize=(8,4))
    ConfMatrix = confusion_matrix(testers, predictors)
    sns.heatmap(ConfMatrix,annot=True, cmap="Blues", fmt="d", 
            xticklabels = ['未违约', '违约'], 
            yticklabels = ['未违约', '违约'])
    plt.ylabel('真实标签')
    plt.xlabel('预测标签')
    plt.title("混淆矩阵")
我们向这个函数中传入之前得到的`y_test`和`knn_pred`,展示KNN模型的分类效果报告。
Get_report(y_test , knn_pred)
Get_report(y_test , knn_pred)
我们发现,虽然总体预测的精准度尚可,违约样本的实际召回率只有0.27,能够综合展示正类样本的准确率和召回率的`f1-score`只有0.36。我们再尝试其他模型进行对比。
## 3.2 决策树
我们再引入决策树算法,直接进行模型训练,并展示模型训练结果,与KNN得到的结果进行对比。
from sklearn import tree
from sklearn.tree import DecisionTreeClassifier
tree_model = DecisionTreeClassifier(max_depth=4, max_features=0.8, min_samples_leaf=50, random_state=1, class_weight={1:2,0:1})
tree_model.fit(X_train, y_train)
tree_score = tree_model.predict_proba(X_test)[:,1]
tree_pred = tree_model.predict(X_test)
Get_report(y_test,tree_pred)
tree_score = tree_model.predict_proba(X_test)[:,1]
tree_pred = tree_model.predict(X_test)
Get_report(y_test,tree_pred)
可以看到,决策树相比KNN,在召回率和`f1-score`上有所提升,但还是不能令人满意。
# 4 引入新评价指标
同时,针对不平衡样本的评价指标也需要进行相应的更新,我们可以引入`Precision_Recall_curve`、`AP`值、`ROC_curve`、`G-means`,`G-means`指标最大化时的阈值`thresholds`来更加全面的评价模型得到的分类效果。
我们定一个`Get_curve`函数,对某一个得到的分类模型,展示它的`Precision_Recall_curve`,`AP`值,`ROC_curve`,`G-means`随阈值变化的曲线,并给出一系列对应的数值。
def Get_curve (tester , scorer , predictionvalues):
    precision, recall, thresholds = precision_recall_curve(tester , scorer)
    fpr,tpr,thresholds1 = metrics.roc_curve(tester , scorer)
    print(precision)
    print(recall)
    f1 = []
    gmeans = []
    for i in range(len(precision)):
        f1.append(2*(precision[i]*recall[i])/(precision[i]+recall[i]))
    for i in range(len(fpr)):
        gmeans.append(np.sqrt(tpr[i]*(1-fpr[i])))

    plt.figure()
    fig, ax = plt.subplots(2,2,figsize=(12,3))
    plt.subplot(1,3,1)
    plt.plot(recall,precision)
    plt.title('精准率-召回率曲线')
    plt.xlabel('召回率')
    plt.ylabel('精准率')
    plt.xlim(0,1)

    plt.subplot(1,3,2)
    plt.plot(fpr,tpr)
    plt.title('ROC曲线')
    plt.xlabel('FPR')
    plt.ylabel('TPR')

    plt.subplot(1,3,3)
    plt.plot(thresholds1,gmeans)
    plt.title('Gmeans曲线')
    plt.xlabel('阈值')
    plt.ylabel('gmeans值')
    plt.xlim(0,1)

    plt.show()

    apscore = sklearn.metrics.average_precision_score(tester, scorer, average='macro', sample_weight=None)
    print("AP值:{name}".format(name=apscore))
    best_threshold = thresholds1[np.argmax(gmeans)]
    best_thresholds = thresholds[np.argmax(f1)]
    print('F1最大对应阈值 = {a}'.format(a = best_thresholds))
    print('Gmeans最大对应阈值 = {a}'.format(a = best_threshold))
    print('AUC得分= {a}'.format(a = metrics.accuracy_score(tester, predictionvalues)))
    print('最大Gmeans = {a}'.format(a = max(gmeans)))
    print('最大F1 = {a}'.format(a = max(f1)))
我们传入上文中利用决策树模型得到的参数`y_test`,`tree_score`,`tree_pred`来测试这个函数的效果。
Get_curve(y_test,tree_score,tree_pred)
tree_pred
# 载入算法类
from sklearn.ensemble import RandomForestClassifier
from sklearn.metrics import roc_auc_score

# 设置基模型个数列表
grid_n = [20, 40, 60, 80, 100, 120, 200, 400, 500]

# 计算在不同基模型个数下的袋外样本得分
oob_score = []
for item in grid_n:
    model = RandomForestClassifier(n_estimators=item, random_state=10, oob_score=True)
    model.fit(X_train, y_train)
    pred = model.predict_proba(X_test)[:,1]
    oob_score.append(roc_auc_score(y_test,pred))

oob_score
# 5 集成学习
我们再尝试使用XGBoost,CatBoost等集成学习算法,看是否能够达到更好的效果。
## 5.1 XGBoost
下面我们引入一种强力的算法-XGBoost,作为Boosting的代表之一,它是对GBDT(Gradient Boosting Decision Tree)的一种改进,我们在对数据进行重采样后进行学习。
y = df['default.payment.next.month']
X = df.drop(columns=['ID','default.payment.next.month'])
X_train, X_test,\
y_train, y_test \
= train_test_split(X, y, test_size=0.3, random_state=0)
from lightgbm import LGBMClassifier,LGBMRegressor
model = LGBMClassifier(boost_type='gbdt',n_jobs=-1,learning_rate=0.01,
                      colsample_bylevel=0.8,subsample=0.8,
                      scale_pos_weight=1,n_estimators=10000,max_depth=8,
                       alpha=5,reg_lambda=1)
eval_set = [(X_test, y_test)]
model.fit(X_train, y_train, early_stopping_rounds=100, 
          eval_metric="logloss", eval_set=eval_set, verbose=50)

y_lgb_pred = model.predict_proba(X_test)[:,1]
y_lgb_pred
from xgboost import XGBClassifier
model = XGBClassifier(booster='gbtree',n_jobs=-1,learning_rate=0.01,gamma=1,
                      colsample_bylevel=0.8,subsample=0.8,
                      scale_pos_weight=1,n_estimators=1000,max_depth=6,alpha=5,reg_lambda=1)
eval_set = [(X_test, y_test)]
model.fit(X_train, y_train, early_stopping_rounds=50, eval_metric="auc", eval_set=eval_set, verbose=50)

`learning_rate`参数代表学习率,决定着目标函数能否收敛到局部最小值以及何时收敛到最小值。`alpha`和`reg_lambda`分别对应L1,L2正则项系数。
# y_pred = model.predict(X_test)
# y_socre = model.predict_proba(X_test)[:,1]
# predictions = [round(value) for value in y_pred]
# Get_report(y_test,y_pred)
y_xgb_pred = model.predict_proba(X_test)[:,1]
y_xgb_pred
   可以看到AUC值相较于随机森林又提高了1%左右,可以在此基础上引入`GridSearchCV`方法进行网格搜索,可以在指定参数内遍历,继续寻找最佳参数。调到较好的参数之后,我们调用`Get_curve`函数考察最终的结果。
# Get_curve(y_test,y_socre,y_pred)
可以看到,XGBoost比随机森林在`F1`和`Gmeans`最大值上有一定的提升。
## 5.2 CATBOOST
   我们最后再使用一种在处理类别特征时通常效果更好的算法:CatBoost。它的基本原理类似于常规的Gradient Boosting算法,它一方面可以自动处理分类特征,并且在训练过程中处理这些类别特征有优势;另一方面,使用了一种新的方式计算leaf-values,可降低过拟合。
由于可以自动处理分类特征,我们可以直接使用未进行哑变量编码的数据进行计算。
categorical_features_indices = ['LIMIT_BAL','PAY_1','PAY_2','PAY_3','PAY_4','PAY_5','PAY_6','BILL_AMT1','BILL_AMT2','BILL_AMT3','BILL_AMT4','BILL_AMT5','BILL_AMT6','PAY_AMT1','PAY_AMT2','PAY_AMT3','PAY_AMT4','PAY_AMT5','PAY_AMT6']

for f in categorical_features_indices:
    df[f] = df[f].astype(str)

y = df['default.payment.next.month']
X = df.drop(columns=['ID','default.payment.next.month'])
X_train, X_test,\
y_train, y_test \
= train_test_split(X, y, test_size=0.3, random_state=0)
from sklearn.model_selection import GridSearchCV
from catboost import CatBoostClassifier

model = CatBoostClassifier(iterations=10000, depth=6,cat_features = categorical_features_indices,loss_function='Logloss',
                            logging_level='Verbose')
model.fit(X_train,y_train,eval_set=(X_test, y_test),early_stopping_rounds=100, verbose=100)
   经验证,如果使用重采样后的数据集反而导致模型分类预测效果下降,这可能是由于算法学习能力较强导致过拟合导致的。接下来我们输出CatBoost算法得到的混淆矩阵等结果。
# cb_pred = model.predict(X_test)
# cb_socre = model.predict_proba(X_test)[:,1]
# predictions = [round(value) for value in cb_pred]
y_cat_pred = model.predict_proba(X_test)[:,1]
y_cat_pred
y_lgb_pred
y_xgb_pred
roc_auc_score(y_test, (y_xgb_pred+y_lgb_pred+y_cat_pred)/3)
from sklearn.metrics import log_loss
log_loss(y_test, (y_xgb_pred+y_lgb_pred+y_cat_pred)/3)
rh_pred = y_xgb_pred*0.2 + y_lgb_pred*0.5 + y_cat_pred*0.3
pd.DataFrame(rh_pred)[0].apply(lambda x: 1 if x>=0.2 else 0)
from sklearn.metrics import f1_score

for thr in [0.2,0.25,0.3,0.35,0.4,0.5]:
    pred = pd.DataFrame(rh_pred)[0].apply(lambda x: 1 if x>=thr else 0).values
    score = f1_score(y_test, np.array(pred), average='macro')
    print('{}: f1_score = {}'.format(str(thr), str(score)))
    
# Get_report(y_test,rh_pred)
   我们通过CatBoost内置的`feature_importances`,还可以查看特征的重要度的排序。
fea = model.feature_importances_
fea_name = model.feature_names_
plt.figure(figsize=(10, 10))
plt.title('特征重要性')
plt.barh(fea_name,fea,height =0.5)
   我们再获取使用CatBoost得到的评价指标,与之前的情况做对比。
# Get_curve(y_test, cb_socre,cb_pred)
   综合来看,CatBoost取得了较好的效果,但我们发现`Gmeans`的最大值和`F1`的最大值并不能同时取到,二者之间存在取舍关系,在处理实际问题时,需要根据对正类样本的分类需求来决定。是更加追求正类样本的召回率,还是整体判别的精确性?此处我们分别给出`Gmeans`最大时的模型和F1最大时的模型,仅供参考。
THRESHOLD = 0.25519412686347887
y_pred = np.where(model.predict_proba(X_test)[:,1] >= THRESHOLD, 1, 0)
Get_report(y_test,y_pred)
   这是追求F1最大时的效果,此时设置分类阈值为0.25519412686347887 , F1值达到了0.55。
THRESHOLD = 0.18889976065560052
y_pred = np.where(model.predict_proba(X_test)[:,1] >= THRESHOLD, 1, 0)
Get_report(y_test,y_pred)
   这是追求`Gmeans`最大时的效果,此时设置分类阈值为0.18889976065560052,`F1`值有一定的下降,但是正类样本的召回率达到了0.68。根据实际问题的不同需求,两种结果具有不同的实际意义。例如,如果我们认为放过违约样本的代价较大,我们可以选择以`Gmeans`最大化作为算法的评价指标。
# 6 结论
   对于不平衡样本的分类问题,本案例一共使用了六种不同的分类模型算法,其中CatBoost实现了最好的分类效果。

   然而,本案例最终得到的分类效果差强人意,最好的AUC值为0.825,正类样本得到的最高`F1`值为0.55,对于信用卡违约这样一个对正类样本召回率要求较高的场景,往往需要更高的召回率和准确率。然而受数据限制,本数据集较难在最终的结果上实现很好的分类效果,但对于不同算法和指标的处理和参考是值得复习的。

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值