数据集及完整代码已上传到github:https://github.com/JIANG54864/ml_exp/tree/main/exp1
信用风险是指银行向用户提供金融服务后,用户不还款的概率。信用风险一直是银行贷款决策中广泛研究的领域。信用风险对银行和金融机构,特别是商业银行来说,起着至关重要的作用,但是一直以来都比较难管理。
本实验以贷款违约为背景,要求使用贝叶斯决策论的相关知识在训练集上构建模型,在测试集上进行贷款违约预测并计算分类准确度。实验目的是使用贝叶斯处理实际问题,不得使用现成工具包直接进行分类。
训练数据集train.csv包含9000条数据,测试数据集test.csv包含1000条数据。注意,训练集和测试集中都有缺失值存在。
以下是字段说明:
字段
描述
loan_id
贷款记录唯一标识
user_id
借款人唯一标识
total_loan
贷款数额
year_of_loan
贷款年份
interest
当前贷款利率
monthly_payment
分期付款金额
grade
贷款级别
employment_type
所在公司类型
industry
工作领域
work_year
工作年限
home_exist
是否有房
censor_status
审核情况
issue_date
贷款发放的月份
use
贷款用途类别
post_code
贷款人申请时邮政编码
region
地区编码
debt_loan_ratio
债务收入比
del_in_18month
借款人过去18个月逾期30天以上的违约事件数
scoring_low
借款人在贷款评分中所属的下限范围
scoring_high
借款人在贷款评分中所属的上限范围
known_outstanding_loan
借款人档案中未结信用额度的数量
known_dero
贬损公共记录的数量
pub_dero_bankrup
公开记录清除的数量
recircle_bal
信贷周转余额合计
recircle_util
循环额度利用率
initial_list_status
贷款的初始列表状态
app_type
是否个人申请
earlies_credit_mon
借款人最早报告的信用额度开立的月份
title
借款人提供的贷款名称
policy_code
公开可用的策略代码=1新产品不公开可用的策略 代码=2
f系列匿名特征
匿名特征f0-f4,为一些贷款人行为计数特征的处理
early_return
借款人提前还款次数
early_return_amount
贷款人提前还款累积金额
early_return_amount_3mon
近3个月内提前还款金额
isDefault
贷款是否违约(预测标签)
首先进行数据预处理。取值唯一、取值各不相同的特征对于预测结果没有意义,因此先将他们删除:
1 2 3 4 5 6 for name in data.columns: unique = data[name].unique().shape[0 ] full = data[name].shape[0 ] if (unique == 1 ) | (unique == full): data = data.drop(name, axis=1 )
查看一下有哪些特征有缺失:
1 2 print (data.isnull().sum (axis=0 ).sort_values(ascending=False ))
用中位数填充空值:
1 2 3 4 5 6 7 8 9 data['f0' ].fillna(int (data['f0' ].median()), inplace=True ) data['f1' ].fillna(int (data['f1' ].median()), inplace=True ) data['f2' ].fillna(int (data['f2' ].median()), inplace=True ) data['f3' ].fillna(int (data['f3' ].median()), inplace=True ) data['f4' ].fillna(int (data['f4' ].median()), inplace=True ) data['work_year' ].fillna('0' , inplace=True )
还有一些与违约没有关系的特征我们手动删除:
1 2 3 4 5 drop_elements=['issue_date' , 'earlies_credit_mon' , 'title' ] for ele in drop_elements: data.drop(ele, axis=1 , inplace=True )
数据中存在大量连续值的属性,不能直接计算似然,需要将连续属性离散化。我使用 pandas.cut()函数来进行分箱。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 def discrete (data ): mydata = pd.read_csv('input/train.csv' ) discreteness = ['total_loan' , 'interest' , 'region' , 'post_code' , 'monthly_payment' , 'debt_loan_ratio' , 'scoring_low' , 'scoring_high' , 'known_outstanding_loan' , 'recircle_b' , 'recircle_u' , 'f0' , 'f2' , 'f3' , 'f4' , 'early_return_amount' , 'early_return_amount_3mon' ] for columnName in discreteness: temp = mydata[columnName].describe() temp1 = data[columnName].describe() if temp['min' ] != temp['25%' ]: dis = pd.cut(data[columnName], bins=[temp1['min' ], temp['25%' ], temp['50%' ], temp['75%' ], temp1['max' ] + 1 ], right=False , include_lowest=True , labels=[0 , 1 , 2 , 3 ]) elif temp['25%' ] != temp['50%' ]: dis = pd.cut(data[columnName], bins=[temp1['min' ], temp['50%' ], temp['75%' ], temp1['max' ] + 1 ], right=False , include_lowest=True , labels=[0 , 1 , 2 ]) else : dis = pd.cut(data[columnName], bins=[temp1['min' ], temp['75%' ], temp1['max' ] + 1 ], right=False , include_lowest=True , labels=[0 , 1 ]) data[columnName] = dis return
统计数据中各列值的出现次数,并分别保存所有数据和非违约数据(isDefault为0)的统计结果。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 def train (data, alldata, positives ): for column in data.columns: datamap = {} line = data[column].value_counts().sort_index(ascending=True ) for index in line.index: datamap[index] = line[index] alldata[column] = datamap positive_data = data.loc[data['isDefault' ] == 0 ] for column in positive_data.columns: datamap = {} line = positive_data[column].value_counts().sort_index(ascending=True ) for index in line.index: datamap[index] = line[index] positives[column] = datamap
重点来了,如何使用贝叶斯公式预测是否违约。
计算基础不违约概率,这里假设所有客户的违约概率相同。
1 probability_0 = alldata['isDefault' ][0 ] / (alldata['isDefault' ][0 ]+alldata['isDefault' ][1 ])
遍历数据集中的每一行数据,计算每一特征对违约概率的影响,注意要避免零概率问题,贝叶斯公式如果乘0就没有意义了。当某个特征值在目标类别中未出现(positive_num=0)时,分子加1保证概率不为零,同时分母对每个可能的特征值都加1次虚拟计数。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 def predict (data, alldata, positives ): lista = [] total_samples = alldata['isDefault' ][0 ] + alldata['isDefault' ][1 ] p_y0 = alldata['isDefault' ][0 ] / total_samples p_y1 = alldata['isDefault' ][1 ] / total_samples for index in data.index: likelihood_y0 = 1.0 likelihood_y1 = 1.0 for column in data.columns: value = data.loc[index, column] unique_count = len (alldata[column].keys()) positive_num = positives[column].get(value, 0 ) prob_y0 = (positive_num + 1 ) / (alldata['isDefault' ][0 ] + unique_count) likelihood_y0 *= prob_y0 global_num = alldata[column].get(value, 0 ) negative_num = global_num - positive_num prob_y1 = (negative_num + 1 ) / (alldata['isDefault' ][1 ] + unique_count) likelihood_y1 *= prob_y1 numerator_y0 = p_y0 * likelihood_y0 numerator_y1 = p_y1 * likelihood_y1 denominator = numerator_y0 + numerator_y1 posterior_y0 = numerator_y0 / denominator if denominator != 0 else 0.5 lista.append(0 if posterior_y0 > 0.5 else 1 ) return lista
说明一下上述代码的贝叶斯公式
先验概率
$ P(y=0) = \frac{\text{alldata[‘isDefault’][0]}}{\text{alldata[‘isDefault’][0]} + \text{alldata[‘isDefault’][1]}} $
$P(y=1) = \frac{\text{alldata[‘isDefault’][1]}}{\text{alldata[‘isDefault’][0]} + \text{alldata[‘isDefault’][1]}} $
条件概率
对每个特征 $x_i$的取值 (v),其在类别 (y=c) 中的条件概率为:
$P(x_i = v | y=c) = \frac{\text{count}(x_i = v | y=c) + 1}{\text{count}(y=c) + K_i} $
后验概率
通过贝叶斯定理计算归一化后的后验概率: $ P(y=0 | x) = \frac{P(y=0) \cdot \prod{i=1}^n P(x_i | y=0)}{P(y=0) \cdot \prod {i=1}^n P(xi | y=0) + P(y=1) \cdot \prod {i=1}^n P(x_i | y=1)} $
$P(y=1 | x) = 1 - P(y=0 | x) $