一起养成写作习惯!这是我参与「掘金日新计划 · 4 月更文挑战」的第19天,点击查看活动详情。
前言
在金融服务行业中,某些客户的违约是导致收入损失的主要来源之一。但是,只有极少数客户会违约。因此,这一分类问题的关键在于识别稀有事件。
任务与模型分析
数据集
在本节中,我们使用 Kaggle
中 Give Me Some Credit 数据集,其中包括 250000
个用户的历史数据,可以用于预测用户在未来两年内遇到财务困境的可能性,更新用户的信用评分,下载数据集后,可以看到其中包含用户年龄、收入、债务等信息:
信用预测任务分析
在本节中,我们将分析在给定时间点跟踪客户收入、支出等属性的数据集,并尝试预测客户是否存在违约的可能性。作为企业,可能希望关注更可能违约的客户——为他们提供替代的付款方式或降低信用额度等。因此,我们用来预测客户违约的任务需求如下:
- 目标:以较高的概率识别更有可能违约的客户。
- 衡量标准:将计算得到的测试数据集中违约概率最大的前
10%
用户作为候选违约用户,查看两年后实际违约的客户数量,以此衡量模型性能。这是由于绝大多数用户都不会违约,我们的关键在于识别违约的用户,如果模型将所有用户均预测为不会违约,就可以获得很高的准确率,但是这样是没有实际意义的,因此我们不能仅仅考虑模型的准确率,而要进一步考察模型将用户预测为违约用户后,这些用户的实际违约状况。
神经网络模型分析
我们计算每个用户违约概率的网络模型策略如下:
- 考虑所有用户的历史数据
- 构造可以用于识别可能违约客户的输入变量:
- 以收入/债务比 (
DebtRatio
) 作为指示用户是否可能违约的主要指标 - 使用与之相似的其他一些变量
- 以收入/债务比 (
- 接下来,创建输出变量:
- 查看在成员未来两年内是否有严重违约行为 (
SeriousDlqin2yrs
),以获取在未来两年内实际违约的成员
- 查看在成员未来两年内是否有严重违约行为 (
- 分类结果是二元变量,因此模型最小化二进制交叉熵损失
- 模型在输入层和输出层之间包含隐藏层
- 最后,计算测试数据集中违约概率最大的前10%用户中实际违约的成员数量
我们假设测试数据具有代表性,可以评估其在训练时没有见到的数据集上的性能,即假定该模型在一个看不见的数据集上的性能可以很好地指示该模型在未来数据上的表现。
使用神经网络实现信用预测
本节,我们将编写代码,实现上述神经网络模型。
- 导入相关的包和数据集:
import pandas as pd
# 读取下载的文件
data = pd.read_csv('cs-train.csv')
# 只需要使用指定列
data = data[['SeriousDlqin2yrs','age', 'DebtRatio', 'MonthlyIncome']]
我们已经在第 1 节中查看了数据集中用户包含的特征,这里我们只是用其中的一部分特征作为演示:用户两年后是否有重大违约情况 SeriousDlqin2yrs
,用户年龄 age
,收入/债务比 DebtRatio
以及每月收入MonthlyIncome
。
上图是原始数据集中变量的子集,SeriousDlqin2yrs
的变量是我们基于数据集中其余变量需要预测的输出变量。
- 查看汇总数据集以更好地理解变量:
print(data.describe())
数据汇总情况输出:
SeriousDlqin2yrs age DebtRatio MonthlyIncome
count 150000.000000 150000.000000 150000.000000 1.202690e+05
mean 0.066840 52.295207 353.005076 6.670221e+03
std 0.249746 14.771866 2037.818523 1.438467e+04
min 0.000000 0.000000 0.000000 0.000000e+00
25% 0.000000 41.000000 0.175074 3.400000e+03
50% 0.000000 52.000000 0.366508 5.400000e+03
75% 0.000000 63.000000 0.868254 8.249000e+03
max 1.000000 109.000000 329664.000000 3.008750e+06
查看输出后,可以观察到以下问题:
- 某些变量的范围较小 (
age
),而某些变量的范围较大 (MonthlyIncome
) - 某些变量缺失一些值 (
MonthlyIncome
) - 某些变量具有异常值 (
DebtRatio
)
- 接下来,我们纠正先前标记的所有问题,首先用变量的中位数代替变量中的缺失值:
vars = data.columns[1:]
import numpy as np
for var in vars:
data[var]= np.where(data[var].isnull(),data[var].median(),data[var])
除了第一个变量,因为它是我们要尝试预测的变量,然后将缺失值替换为该变量的中位数。
- 消除输入变量中的离群值:
for var in vars:
x=data[var].quantile(0.95)
data[var+"outlier_flag"]=np.where(data[var]>x,1,0)
data[var]=np.where(data[var]>x,x,data[var])
在前面的代码中,我们计算了每个变量的第 95
个百分位数并存入新变量 x
中,如果该行包含异常值,则使用一个标记位将其值设为 1
,否则为 0
。此外,我们将变量值的上限设置为原始值的第 95
个百分位数。
Note:
个数据按大小排列,处于 位置的值称第 百分位数。
- 最后,我们限制
DebtRatio
的范围为[0, 1]
:
data['DebtRatio_outlier'] = np.where(data['DebtRatio']>1, 1, 0)
data['DebtRatio'] = np.where(data['DebtRatio']>1, 1, data['DebtRatio'])
- 将所有变量归一化为相同的标度,以得到介于 0 和 1 之间的值:
for var in vars:
data[var]= data[var]/data[var].max()
在前面的代码中,通过将每个输入变量值除以输入变量列的最大值,我们将所有变量限制为相似范围,即 [0, 1]
。
- 创建输入和输出数据集:
x = data.iloc[:, 1:]
y = data['SeriousDlqin2yrs']
- 将数据集分为训练和测试数据集:
from sklearn.model_selection import train_test_split
x_train, x_test, y_train, y_test = train_test_split(x, y, test_size=0.3)
使用 train_test_split
方法将输入和输出数组拆分为训练和测试数据集,其中测试数据集占数据总数的 30%
。
9. 创建数据集之后,定义神经网络模型,如下所示:
model = Sequential()
model.add(Dense(1000, input_dim=x_train.shape[1], activation='relu'))
model.add(Dense(1, activation='sigmoid'))
model.summary()
该模型的结构信息如下:
Model: "sequential"
_________________________________________________________________
Layer (type) Output Shape Param #
=================================================================
dense (Dense) (None, 1000) 8000
_________________________________________________________________
dense_1 (Dense) (None, 1) 1001
=================================================================
Total params: 9,001
Trainable params: 9,001
Non-trainable params: 0
_________________________________________________________________
模型将输入变量连接到具有 1000
个隐藏单元的隐藏层。
- 编译模型,使用二进制交叉熵,因为输出变量只有两个类,将指定优化器为
adam
优化:
model.compile(loss='binary_crossentropy', optimizer='adam', metrics=['acc'])
- 拟合模型:
history = model.fit(x_train,
y_train,
validation_data=(x_test, y_test),
epochs=50,
batch_size=512,
verbose=1)
训练和测试损失的变化,以及随着 epoch
的推移准确率的变化如下:
from matplotlib import pyplot as plt
history_dict = history.history
loss_values = history_dict['loss']
val_loss_values = history_dict['val_loss']
acc_values = history_dict['acc']
val_acc_values = history_dict['val_acc']
epochs = range(1, len(val_loss_values) + 1)
plt.subplot(211)
plt.plot(epochs, loss_values, marker='x', label='Traing loss')
plt.plot(epochs, val_loss_values, marker='o', label='Test loss')
plt.title('Training and test loss')
plt.xlabel('Epochs')
plt.ylabel('Loss')
plt.legend()
plt.subplot(212)
plt.plot(epochs, acc_values, marker='x', label='Training accuracy')
plt.plot(epochs, val_acc_values, marker='o', label='Test accuracy')
plt.title('Training and test accuracy')
plt.xlabel('Epochs')
plt.ylabel('Accuracy')
plt.legend()
plt.show()
- 对测试数据集进行预测:
pred = model.predict(x_test)
- 按概率递减的顺序排列,检查在测试数据集中,模型预测违约概率最高的前
10%
的用户中实际违约的用户数量:
test_data = pd.DataFrame([y_test]).T
test_data['pred'] = pred
test_data = test_data.reset_index(drop='index')
test_data = test_data.sort_values(by='pred', ascending=False)
print(test_data[:int(len(test_data) * 0.1)]['SeriousDlqin2yrs'].sum())
在前面的代码中,将预测值与实际值拼接在一起,然后按输出的预测概率对数据集进行了排序。我们统计了在测试数据集的违约预测概率前 10%
(即前 4500
个)的用户中违约者的实际数量。可以看到,通过对 4500
个高概率违约用户与其真实标签进行对比后,我们识别了 1625
个实际违约者,我们可以说这模型具有很好的预测性能,因为在整个数据集中大约只有 6%
的客户违约。