本案例使用sklearn中的DecisionTreeClassifier算法来对德国贷款数据建立决策树模型,并通过改变数据标签的代价权重来提升模型性能。 本案例主要包括以下内容 1. 数据源 2. 数据探索和预处理 划分训练集和测试集 3. 模型训练 4. 模型性能评估 5. 模型性能提升

使用决策树进行个人信用风险评估

2007-2008年的全球金融危机凸显了透明度和严密性在银行业务中的重要性。由于信贷供应收到了限制,所以银行正日益紧缩其贷款体系,转向机器学习来更准确地识别高风险贷款。

决策树模型准确性高且可解释性好,所以被广泛地应用在银行业。在很多国家,政府机构会密切监控贷款业务,所以银行需要明确地解释为什么一个申请者的贷款申请被拒绝或者批准。这种可解释性对于贷款申请者也是很重要的,申请者需要知道为什么自己的信用级别不符合银行的要求。

通过构建自动化的信用评分模型,以在线方式进行即时的信贷审批能够为银行节约很多人工成本。 本案例,我们将使用C5.0决策树建立一个简单的个人信用风险评估模型。

1 数据源

使用UCI上的德国信用数据集。该数据集包含了1000个贷款信息,每一个贷款有20个自变量和一个类变量记录该笔贷款是否违约。 我们将使用该数据集构建模型来预测贷款是否违约。

2 数据探索和预处理

首先,使用pandas的read_csv()函数读入数据。

In [1]:
import pandas as pd
import numpy as np
import matplotlib.pyplot as plt

credit = pd.read_csv("./input/credit.csv")
credit.head(5)
Out[1]:
checking_balance months_loan_duration credit_history purpose amount savings_balance employment_length installment_rate personal_status other_debtors ... property age installment_plan housing existing_credits job dependents telephone foreign_worker default
0 < 0 DM 6 critical radio/tv 1169 unknown > 7 yrs 4 single male none ... real estate 67 none own 2 skilled employee 1 yes yes 1
1 1 - 200 DM 48 repaid radio/tv 5951 < 100 DM 1 - 4 yrs 2 female none ... real estate 22 none own 1 skilled employee 1 none yes 2
2 unknown 12 critical education 2096 < 100 DM 4 - 7 yrs 2 single male none ... real estate 49 none own 1 unskilled resident 2 none yes 1
3 < 0 DM 42 repaid furniture 7882 < 100 DM 4 - 7 yrs 2 single male guarantor ... building society savings 45 none for free 1 skilled employee 2 none yes 1
4 < 0 DM 24 delayed car (new) 4870 < 100 DM 1 - 4 yrs 3 single male none ... unknown/none 53 none for free 2 skilled employee 2 none yes 2

5 rows × 21 columns

可以看到,该数据集包含1000个样本和21个变量。变量类型同时包括因子变量和数值变量。 使用value_counts()函数对支票余额变量check_balance和储蓄账户余额变量savings_balance进行查看。

In [2]:
credit.checking_balance.value_counts()
Out[2]:
unknown       394
< 0 DM        274
1 - 200 DM    269
> 200 DM       63
Name: checking_balance, dtype: int64
In [3]:
credit.savings_balance.value_counts()
Out[3]:
< 100 DM         603
unknown          183
101 - 500 DM     103
501 - 1000 DM     63
> 1000 DM         48
Name: savings_balance, dtype: int64

上述两个变量的单位都是德国马克(Deutsche Mark, DM)。 直观来看,支票余额和储蓄账户余额越大,贷款违约的可能性越小。

该贷款数据集还有一些数值型变量,例如贷款期限(months_loan_duration)和贷款申请额度(amount)。

In [4]:
credit[["months_loan_duration","amount"]].describe()
Out[4]:
months_loan_duration amount
count 1000.000000 1000.000000
mean 20.903000 3271.258000
std 12.058814 2822.736876
min 4.000000 250.000000
25% 12.000000 1365.500000
50% 18.000000 2319.500000
75% 24.000000 3972.250000
max 72.000000 18424.000000
In [5]:
credit[["months_loan_duration","amount"]].median()
Out[5]:
months_loan_duration      18.0
amount                  2319.5
dtype: float64

可见,贷款期限为4~72个月,中位数为18个月。贷款申请额度在250~18420马克之间,中位数为2320马克。

变量default表示贷款是否违约,也是我们需要预测的目标变量。 在1000个贷款中,30%贷款申请者有违约行为。

In [6]:
credit.default.value_counts()
Out[6]:
1    700
2    300
Name: default, dtype: int64

银行不太希望贷款给违约率高的客户,因为这些客户会给银行带来损失。 如果建模的目标是识别可能违约的客户,从未减少违约数量。

3 划分训练集和测试集

在正式建模之前,我们需要将数据集分为训练集和测试集两部分。其中训练集用来构建决策树模型,测试集用来评估模型性能。 我们将使用70%数据作为训练数据,30%作为训练数据。 在划分数据之前,我们还需要将数据中字符串形式的变量使用整数进行编码。

In [7]:
col_dicts = {}
cols = ['checking_balance','credit_history', 'purpose', 'savings_balance', 'employment_length', 'personal_status', 
        'other_debtors','property','installment_plan','housing','job','telephone','foreign_worker']

col_dicts = {'checking_balance': {'1 - 200 DM': 2,
  '< 0 DM': 1,
  '> 200 DM': 3,
  'unknown': 0},
 'credit_history': {'critical': 0,
  'delayed': 2,
  'fully repaid': 3,
  'fully repaid this bank': 4,
  'repaid': 1},
 'employment_length': {'0 - 1 yrs': 1,
  '1 - 4 yrs': 2,
  '4 - 7 yrs': 3,
  '> 7 yrs': 4,
  'unemployed': 0},
 'foreign_worker': {'no': 1, 'yes': 0},
 'housing': {'for free': 1, 'own': 0, 'rent': 2},
 'installment_plan': {'bank': 1, 'none': 0, 'stores': 2},
 'job': {'mangement self-employed': 3,
  'skilled employee': 2,
  'unemployed non-resident': 0,
  'unskilled resident': 1},
 'other_debtors': {'co-applicant': 2, 'guarantor': 1, 'none': 0},
 'personal_status': {'divorced male': 2,
  'female': 1,
  'married male': 3,
  'single male': 0},
 'property': {'building society savings': 1,
  'other': 3,
  'real estate': 0,
  'unknown/none': 2},
 'purpose': {'business': 5,
  'car (new)': 3,
  'car (used)': 4,
  'domestic appliances': 6,
  'education': 1,
  'furniture': 2,
  'others': 8,
  'radio/tv': 0,
  'repairs': 7,
  'retraining': 9},
 'savings_balance': {'101 - 500 DM': 2,
  '501 - 1000 DM': 3,
  '< 100 DM': 1,
  '> 1000 DM': 4,
  'unknown': 0},
 'telephone': {'none': 1, 'yes': 0}}

for col in cols:
    credit[col] = credit[col].map(col_dicts[col])
    
credit.head(5)
Out[7]:
checking_balance months_loan_duration credit_history purpose amount savings_balance employment_length installment_rate personal_status other_debtors ... property age installment_plan housing existing_credits job dependents telephone foreign_worker default
0 1 6 0 0 1169 0 4 4 0 0 ... 0 67 0 0 2 2 1 0 0 1
1 2 48 1 0 5951 1 2 2 1 0 ... 0 22 0 0 1 2 1 1 0 2
2 0 12 0 1 2096 1 3 2 0 0 ... 0 49 0 0 1 1 2 1 0 1
3 1 42 1 2 7882 1 3 2 0 1 ... 1 45 0 1 1 2 2 1 0 1
4 1 24 2 3 4870 1 2 3 0 0 ... 2 53 0 1 2 2 2 1 0 2

5 rows × 21 columns

现在,我们从数据集中随机选取其中70%作为训练数据,30%作为测试数据。

In [8]:
from sklearn import model_selection

y = credit['default']
#del credit['default']
X  = credit.loc[:,'checking_balance':'foreign_worker']
X_train, X_test, y_train, y_test = model_selection.train_test_split(X, y, test_size=0.3, random_state=0)

我们验证一下训练集和测试集中,违约贷款的比例是否接近。

In [9]:
print y_train.value_counts()/len(y_train)
print y_test.value_counts()/len(y_test)
1    0.694286
2    0.305714
Name: default, dtype: float64
1    0.713333
2    0.286667
Name: default, dtype: float64

可见,训练集和测试集中违约贷款比例均接近30%。

4 模型训练

我们将使用Scikit-learn中的DecisionTreeClassifier算法来训练决策树模型。 DecisionTreeClassifier算法位于sklearn.tree包,首先将其导入,然后调用fit方法进行模型训练。

In [10]:
from sklearn import tree
from sklearn.tree import DecisionTreeClassifier
credit_model = DecisionTreeClassifier(min_samples_leaf = 6)
credit_model.fit(X_train, y_train)
Out[10]:
DecisionTreeClassifier(class_weight=None, criterion='gini', max_depth=None,
            max_features=None, max_leaf_nodes=None,
            min_impurity_decrease=0.0, min_impurity_split=None,
            min_samples_leaf=6, min_samples_split=2,
            min_weight_fraction_leaf=0.0, presort=False, random_state=None,
            splitter='best')

现在,credit_model就是我们训练得到的决策树模型。可以通过可视化将其展示出来。

注:绘制决策树模型的图形时,请注意pydot的版本(这是一个深坑)

In [11]:
from sklearn.externals.six import StringIO  
from IPython.display import Image
import pydot
dot_data = StringIO()  
tree.export_graphviz(credit_model, out_file = dot_data,  
                         feature_names = X_train.columns,
                         class_names=['no default','default'],
                         filled = True, rounded = True,  
                         special_characters = True) 
(graph,) = pydot.graph_from_dot_data(dot_data.getvalue()) # pydot 1.2.0 以上版本
#graph = pydot.graph_from_dot_data(dot_data.getvalue()) # pydot 1.0 版本以下
Image(graph.create_png()) 
Out[11]:

5 模型性能评估

为了将我们训练好的决策树模型应用于测试数据,我们使用predict()函数,代码如下:

In [12]:
credit_pred = credit_model.predict(X_test)

现在,我们得到了决策树模型在测试数据上的预测结果,通过将预测结果和真实结果进行对比可以评估模型性能。 可以使用sklearn.metrics包中的classification_report()和confusion_matrix()函数,展示模型分类结果:

In [13]:
from sklearn import metrics
print metrics.classification_report(y_test, credit_pred)
print metrics.confusion_matrix(y_test, credit_pred)
print metrics.accuracy_score(y_test, credit_pred)
             precision    recall  f1-score   support

          1       0.78      0.81      0.80       214
          2       0.49      0.44      0.46        86

avg / total       0.70      0.71      0.70       300

[[174  40]
 [ 48  38]]
0.706666666667

在300个贷款申请测试数据中,模型的预测正确率(Accuracy)为70.7%。 216个未违约贷款中,模型正确预测了81%。 84个违约贷款中,模型正确预测出了44%。 下面,我们看看是否能够进一步改善模型的性能。

6 模型性能提升

在实际应用中,模型的预测正确率不高,很难将其应用到实时的信贷评审过程。 在本案例中,如果一个模型将所有的贷款都预测为“未违约”,此时模型的正确率将为72%,而该模型是一个完全无用的模型。 上节中我们建立的模型,正确率为70.7%,但是对于违约贷款的识别性能很差。 我们可以通过创建一个代价矩阵定义模型犯不同错误时的代价。 假设我们认为一个贷款违约者给银行带来的损失是银行错过一个不违约的贷款带来损失的4倍,则未违约和违约的代价权重可以定义为:

In [14]:
class_weights = {1:1, 2:4}
credit_model_cost = DecisionTreeClassifier(max_depth=6,class_weight = class_weights)
credit_model_cost.fit(X_train, y_train)
credit_pred_cost = credit_model_cost.predict(X_test)

print metrics.classification_report(y_test, credit_pred_cost)
print metrics.confusion_matrix(y_test, credit_pred_cost)
print metrics.accuracy_score(y_test, credit_pred_cost)
             precision    recall  f1-score   support

          1       0.87      0.47      0.61       214
          2       0.39      0.83      0.53        86

avg / total       0.73      0.57      0.59       300

[[101 113]
 [ 15  71]]
0.573333333333

可见,模型的整体正确率下降为58%,但是此时的模型能将86个违约贷款中的72个正确识别,识别率为84%。