本文围绕天池贷款违约预测赛题,介绍零基础入门金融风控的实现过程。使用超过120万条贷款记录数据,对比多种数据不平衡处理方法,以CatBoost为基分类器,经5折交叉验证,结合过采样的模型线上表现较好(AUC 0.7347),SMOTE和BalanceCascade效果欠佳,为相关任务提供参考。
☞☞☞AI 智能聊天, 问答助手, AI 智能搜索, 免费无限量使用 DeepSeek R1 模型☜☜☜
![【金融风控系列】_[1]_贷款违约预测 -](https://img.php.cn/upload/article/202507/21/2025072117310075200.jpg)
零基础入门金融风控-贷款违约预测
该赛题来自 TIANCHI 零基础入门系列,仅用作学习交流
该数据来自某信贷平台的贷款记录,总数据量超过120w,包含47列变量信息,其中15列为匿名变量。为了保证比赛的公平性,将会从中抽取80万条作为训练集,20万条作为测试集A,20万条作为测试集B,同时会对employmentTitle、purpose、postCode和title等信息进行脱敏。
字段表
| Field | Description |
|---|---|
| id | 为贷款清单分配的唯一信用证标识 |
| loanAmnt | 贷款金额 |
| term | 贷款期限(year) |
| interestRate | 贷款利率 |
| installment | 分期付款金额 |
| grade | 贷款等级 |
| subGrade | 贷款等级之子级 |
| employmentTitle | 就业职称 |
| employmentLength | 就业年限(年) |
| homeOwnership | 借款人在登记时提供的房屋所有权状况 |
| annualIncome | 年收入 |
| verificationStatus | 验证状态 |
| issueDate | 贷款发放的月份 |
| purpose | 借款人在贷款申请时的贷款用途类别 |
| postCode | 借款人在贷款申请中提供的邮政编码的前3位数字 |
| regionCode | 地区编码 |
| dti | 债务收入比 |
| delinquency_2years | 借款人过去2年信用档案中逾期30天以上的违约事件数 |
| ficoRangeLow | 借款人在贷款发放时的fico所属的下限范围 |
| ficoRangeHigh | 借款人在贷款发放时的fico所属的上限范围 |
| openAcc | 借款人信用档案中未结信用额度的数量 |
| pubRec | 贬损公共记录的数量 |
| pubRecBankruptcies | 公开记录清除的数量 |
| revolBal | 信贷周转余额合计 |
| revolUtil | 循环额度利用率,或借款人使用的相对于所有可用循环信贷的信贷金额 |
| totalAcc | 借款人信用档案中当前的信用额度总数 |
| initialListStatus | 贷款的初始列表状态 |
| applicationType | 表明贷款是个人申请还是与两个共同借款人的联合申请 |
| earliesCreditLine | 借款人最早报告的信用额度开立的月份 |
| title | 借款人提供的贷款名称 |
| policyCode | 公开可用的策略_代码=1新产品不公开可用的策略_代码=2 |
| n系列匿名特征 | 匿名特征n0-n14,为一些贷款人行为计数特征的处理 |
参考:
[1] https://github.com/caozichuan/TianChi_loadDefault/blob/main/GitHub_loadDefault/model/xgb_github.ipynb
[2] https://tianchi.aliyun.com/notebook-ai/detail?spm=5176.21852664.0.0.4f70379c1gFeCt&postId=129321
[3] https://imbalanced-ensemble.readthedocs.io/en/latest/index.html
[4] https://blog.csdn.net/qq_31367861/article/details/111145816?spm=5176.21852664.0.0.22686e12UZfkmZ
文献4方法线上提交分数为0.7391
[5] https://blog.csdn.net/qq_44694861/article/details/109753004?spm=5176.21852664.0.0.667f4288swn2OQ
本项目主要是对金融风控中常见的数据不平衡方法进行对比,以及对 self-paced-ensemble 包进行学习
使用的方法如下:
- 无处理
- SMOTE算法
SMOTE(Synthetic Minority Oversampling Technique),合成少数类过采样技术.它是基于随机过采样算法的一种改进方案,为了克服随机过采样算法泛化能力差的缺点,SMOTE算法的对少数类样本近邻进行采样,根据少数类样本人工合成新样本添加到数据集中。
美图云修
商业级AI影像处理工具
50 查看详情
![]()
- SPE算法
Liu Z , Cao W , Gao Z , et al. Self-paced Ensemble for Highly Imbalanced Massive Data Classification[J]. 2019.
- 过采样算法
重复正样本,使得正样本与负样本比例接近 1 : 1
- 欠采样算法
从数量多的样本里面随机选择样本进行抛弃,为了避免随机性影响,我们独立训练多个模型进行简单平均
- BalanceCascade算法
Liu, X. Y., Wu, J., & Zhou, Z. H. “Exploratory undersampling for class-imbalance learning.” IEEE Transactions on Systems, Man, and Cybernetics, Part B (Cybernetics) 39.2 (2008): 539-550
注意事项
- 运行主要有两部分,数据处理 和 模型选择 ,根据注释部分,自己进行数据处理和算法组合。默认CBT模型+过采样算法。
#!pip install pandas --user#!pip install lightgbm --user#!pip install xgboost --user!pip install catboost --user !pip install joblib --user !pip install scikit-learn --user !pip install imblearn --user !pip install imbalanced-ensemble --user !pip self-paced-ensemble --userIn [2]
import pandas as pdimport datetimeimport warnings
warnings.filterwarnings('ignore')from sklearn.model_selection import StratifiedKFold#warnings.filterwarnings('ignore')#%matplotlib inlinefrom sklearn.metrics import roc_auc_score## 数据降维处理的from sklearn.model_selection import train_test_split
from catboost import CatBoostClassifierimport imbalanced_ensemble as imbensfrom collections import Counterfrom imbalanced_ensemble.sampler.over_sampling import SMOTE
import numpy as np
train=pd.read_csv("./data/data53042/train.csv")
testA=pd.read_csv("./data/data53042/testA.csv")
numerical_fea = list(train.select_dtypes(exclude=['object']).columns)
category_fea = list(filter(lambda x: x not in numerical_fea,lis
t(train.columns)))
label = 'isDefault'numerical_fea.remove(label)#按照中位数填充数值型特征train[numerical_fea] = train[numerical_fea].fillna(train[numerical_fea].median())
testA[numerical_fea] = testA[numerical_fea].fillna(testA[numerical_fea].median())#按照众数填充类别型特征train[category_fea] = train[category_fea].fillna(train[category_fea].mode())
testA[category_fea] = testA[category_fea].fillna(testA[category_fea].mode())def make_fea(data):
data['issueDate'] = pd.to_datetime(data['issueDate'],format='%Y-%m-%d')
startdate = datetime.datetime.strptime('2007-06-01', '%Y-%m-%d') #构造时间特征
data['issueDateDT'] = data['issueDate'].apply(lambda x: x-startdate).dt.days
data['grade'] = data['grade'].map({'A':1,'B':2,'C':3,'D':4,'E':5,'F':6,'G':7})
data['subGrade'] = data['subGrade'].map({'E2':1,'D2':2,'D3':3,'A4':4,'C2':5,'A5':6,'C3':7,'B4':8,'B5':9,'E5':10, 'D4':11,'B3':12,'B2':13,'D1':14,'E1':15,'C5':16,'C1':17,'A2':18,'A3':19,'B1':20, 'E3':21,'F1':22,'C4':23,'A1':24,'D5':25,'F2':26,'E4':27,'F3':28,'G2':29,'F5':30, 'G3':31,'G1':32,'F4':33,'G4':34,'G5':35})
data = pd.get_dummies(data, columns=['subGrade', 'homeOwnership', 'verificationStatus', 'purpose', 'regionCode'], drop_first=True)
data['employmentLength'] = data['employmentLength'].map({'NaN':-1,'1 year':1,'2 years':2,'3 years':3,'4 years':4,'5 years':5,'6 years':6,'7 years':7,'8 years':8,'9 years':9,'10+ years':10,'< 1 year':0})
data['earliesCreditLine'] = data['earliesCreditLine'].apply(lambda s: int(s[-4:])) for item in ['n0','n1','n2','n2.1','n4','n5','n6','n7','n8','n9','n10','n11','n12','n13','n14']:
data['grade_to_mean_' + item] = data['grade'] / data.groupby([item])['grade'].transform('mean')
data['grade_to_std_' + item] = data['grade'] / data.groupby([item])['grade'].transform('std')
data['n15']=data['n8']*data['n10'] return data# 特征工程 简单处理train = make_fea(train)
testA = make_fea(testA)print(train.shape)print(testA.shape)print("数据预处理完成!")
sub=testA[['id']].copy()
sub['isDefault']=0testA=testA.drop(['id','issueDate'],axis=1)
data_x=train.drop(['isDefault','id','issueDate'],axis=1)
data_y=train[['isDefault']].copy()
col=['grade','subGrade','employmentTitle','homeOwnership','verificationStatus','purpose','postCode','regionCode', 'initialListStatus','applicationType','policyCode']for i in data_x.columns: if i in col:
data_x[i] = data_x[i].astype('str')for i in testA.columns: if i in col:
testA[i] = testA[i].astype('str')
answers = []
mean_score = 0n_folds = 5sk = StratifiedKFold(n_splits=n_folds, shuffle=True, random_state=2019)print(data_x.shape)print(testA.shape)# 在测试集中有部分特征训练集中没有,进行处理COLNAME = data_x.columns
testA = testA[COLNAME]# 处理INF NAN ,替换成 -1 注意:np.inf和-np.inf是不同的两个值,这里经常遇到忘记处理后者引发错误data_x.replace([np.inf, -np.inf, np.nan], -1, inplace=True)
testA.replace([np.inf, -np.inf, np.nan], -1, inplace=True)# -----重复正样本,因为原始数据比例约为3:1 因此对正样本重复三次拼接到原始数据中-----a = [i for i, x in enumerate(data_y.values) if x[0]==1]
all_1 = data_x.iloc[a]
all_y = pd.DataFrame(np.ones(all_1.shape[0],dtype=int),columns=['isDefault'])print(all_y.shape)print(data_x.shape,data_y.shape)for i in range(3):
data_y= pd.concat([data_y,all_y],axis=0)
data_x = pd.concat([data_x,all_1],axis=0) print(data_x.shape,data_y.shape)
data_x.reset_index(drop=True,inplace =True)
data_y.reset_index(drop=True,inplace =True)'''
# -----使用SMOTE算法对负样本进行扩充-----
sm = SMOTE(random_state=42)
data_x, data_y = sm.fit_resample(data_x, data_y)
print('Resampled dataset' , data_y.value_counts())
'''
基础模型定义及训练
使用CatBoost分类模型作为基准模型
In [ ]model=CatBoostClassifier(
loss_function="Logloss",
eval_metric="AUC",
task_type="CPU",
learning_rate=0.1,
iterations=500,
random_seed=2025,
verbose=500,
depth=7)for train, test in sk.split(data_x, data_y):
x_train = data_x.iloc[train]
y_train = data_y.iloc[train]
x_test = data_x.iloc[test]
y_test = data_y.iloc[test] '''
# -----使用SPE 算法对不平衡数据进行处理-----
clf = imbens.ensemble.SelfPacedEnsembleClassifier(
base_estimator = model,#基准分类模型 可以自定义,需要模型包含fit等方法,具体信息请查看参考文献3
n_estimators = 5,
random_state=49
).fit(x_train, y_train)
'''
# -----使用BalanceCascade 算法对不平衡数据进行处理-----
'''
clf = imbens.ensemble.BalanceCascadeClassifier(
base_estimator = model,#基准分类模型 可以自定义,需要模型包含fit等方法,具体信息请查看参考文献3
n_estimators = 5,
random_state=49
).fit(x_train, y_train)
'''
# 基准分类模型
clf = model.fit(x_train,y_train)#, eval_set=(x_test,y_test),cat_features=col)
yy_pred_valid=clf.predict_proba(x_test)[:,-1] print('cat验证的auc:{}'.format(roc_auc_score(y_test, yy_pred_valid)))
mean_score += roc_auc_score(y_test, yy_pred_valid) / n_folds
y_pred_valid = clf.predict_proba(testA)[:,-1]
answers.append(y_pred_valid)print('mean valAuc:{}'.format(mean_score))
cat_pre=sum(answers)/n_folds
sub['isDefault']=cat_pre
sub.to_csv('金融预测.csv',index=False)
欠采样模型训练
In [ ]model=CatBoostClassifier(
loss_function="Logloss",
eval_metric="AUC",
task_type="CPU",
learning_rate=0.1,
iterations=500,
random_seed=2025,
verbose=500,
depth=7)#----- 欠采样模型 -----a = [i for i, x in enumerate(data_y.values) if x[0]==1]
all_1 = data_x.iloc[a]
all_y1 = pd.DataFrame(np.ones(all_1.shape[0]),columns=['isDefault'])
all_y0 = pd.DataFrame(np.ones(all_y.shape[0]),columns=['isDefault'])print(all_y1.shape)#for i in range(4): # 数据比1:4+i= 0all_0 = data_x.iloc[i*all_y1.shape[0]:(i+1)*all_y1.shape[0],:]
data_y_= pd.concat([all_y1,all_y0],axis=0)
data_x_ = pd.concat([all_1,all_0],axis=0)print(data_x_.shape,data_y_.shape,all_0.shape)for train, test in sk.split(data_x, data_y):
x_train = data_x.iloc[train]
y_train = data_y.iloc[train]
x_test = data_x.iloc[test]
y_test = data_y.iloc[test] # 基准分类模型
#clf = model.fit(x_train,y_train)#, eval_set=(x_test,y_test),cat_features=col)
clf = imbens.ensemble.SelfPacedEnsembleClassifier(
base_estimator = model,#基准分类模型 可以自定义,需要模型包含fit等方法,具体信息请查看参考文献3
n_estimators = 5,
random_state=49
).fit(x_train, y_train)
yy_pred_valid=clf.predict_proba(x_test)[:,-1] print('cat验证的auc:{}'.format(roc_auc_score(y_test, yy_pred_valid)))
mean_score += roc_auc_score(y_test, yy_pred_valid) / n_folds
y_pred_valid = clf.predict_proba(testA)[:,-1]
answers.append(y_pred_valid)print('mean valAuc:{}'.format(mean_score))
cat_pre=sum(answers)/(n_folds) # 数据比1:4sub['isDefault']=cat_pre
sub.to_csv('金融预测.csv',index=False)
总结
本项目主要以CBT算法为基分类器对比了几种常用的数据不平衡的处理效果,关于数据特征构建的知识可以查看开头参考文献。
数据的对比提交结果如下:
| 数据集 | TIANCHI-贷款违约预测(ONLine) |
|---|---|
| 比例 | 4.01(80W) |
| 指标 | AUC(线下五折验证/线上) |
| CaTBoost+SPE(5) | 0.7348/0.7328 |
| CaTBoost | 0.7354/0.7338 |
| CaTBoost+SMOTE | 0.9334/0.6949 |
| CaTBoost+过采样 | 0.7477/0.7346 |
| CaTBoost+欠采样 | 0.7354/0.7338 |
| CaTBoost+BalanceCascade(5) | 0.6052/0.6032 |
| CaTBoost+SPE(5)+过采样 | 0.7485/0.7347 |
imbalanced-ensemble库中包含了许多其他的非平衡算法,有兴趣的同学可以关注参考文献[3]
此外,在训练中,随着基模型数量的增加此数据上的分类表现有所下降,这可能是由于此数据失衡仅4:1,不足以体现算法优势。
以上就是【金融风控系列】_[1]_贷款违约预测的详细内容,更多请关注其它相关文章!
# cad
# qq
# ai
# yy
# red
# descript
# type
# git
# 网站优化设计名词解释
# 高州seo优化关键词
# 开州区可靠网站建设
# 渭南富平seo优化
# 昆明长沙seo费用信誉保障
# 武汉推广招人网站排名榜
# 网站建设与管理课程学啥
# 康复学科营销推广方案
# 百度营销推广教程
# 浦江网站建设流程设计
# 安装包
# 对不
# 一键
# 数据处理
# 中文网
# 请查看
# 不平衡
# 自定义
# 线上
# 美图
相关栏目:
【
企业资讯168 】
【
行业动态50218 】
【
媒体报道120512 】
相关推荐:
汽车的type-c接口是什么
万能表上的power是什么意思
固态硬盘内存如何查找
没网环境如何安装typescript
苹果16关闭哪些功能好
苹果16会升级哪些
苹果16更新了哪些版本
typescript入门要多久
苹果16充电方式有哪些
忐忑不安是什么意思
命令行如何运行c
固态硬盘4k如何看
如何使用命令行界面
哪些编程软件需要typescript
春运提前抢票攻略
市盈率292是什么意思
如何用好typescript
高市盈率是什么意思
如何管理员打开cmd命令行窗口
如何测试固态硬盘速度
如何通过命令行聊天
如何创建解压文件命令
ai如何重复使用上一命令
望远镜上power是什么意思
云笔记本电脑有什么用
如何激活固态硬盘
热水器没热水显示power是什么意思
酷狗音乐pc版的每日推荐在哪 酷狗音乐PC版每日推荐查找指南
为什么youtube音乐打不开
折叠屏手机哪款最好
单片机log怎么看
一分钟等于多少秒
破太岁是什么意思
eraser是什么意思
春运抢票技巧攻略
光刻机分类有哪些品牌的
夸克为什么老是投屏失败
笔记本如何选择固态硬盘
哪个品牌有折叠屏手机卖
苹果16新增哪些功能
小屏折叠屏手机有哪些
电动车充电器上的power是什么意思
苹果16系统有哪些缺陷
春运车站抢票和网上抢票
手机nfc功能功能是什么意思
typescript有哪些版本
课程伴侣登不上怎么办
rxjs和typescript什么意思
360f4怎么取消百变壁纸
市盈率为负值是什么意思


美图云修
t(train.columns)))
label = 'isDefault'numerical_fea.remove(label)#按照中位数填充数值型特征train[numerical_fea] = train[numerical_fea].fillna(train[numerical_fea].median())
testA[numerical_fea] = testA[numerical_fea].fillna(testA[numerical_fea].median())#按照众数填充类别型特征train[category_fea] = train[category_fea].fillna(train[category_fea].mode())
testA[category_fea] = testA[category_fea].fillna(testA[category_fea].mode())def make_fea(data):
data['issueDate'] = pd.to_datetime(data['issueDate'],format='%Y-%m-%d')
startdate = datetime.datetime.strptime('2007-06-01', '%Y-%m-%d') #构造时间特征
data['issueDateDT'] = data['issueDate'].apply(lambda x: x-startdate).dt.days
data['grade'] = data['grade'].map({'A':1,'B':2,'C':3,'D':4,'E':5,'F':6,'G':7})
data['subGrade'] = data['subGrade'].map({'E2':1,'D2':2,'D3':3,'A4':4,'C2':5,'A5':6,'C3':7,'B4':8,'B5':9,'E5':10, 'D4':11,'B3':12,'B2':13,'D1':14,'E1':15,'C5':16,'C1':17,'A2':18,'A3':19,'B1':20, 'E3':21,'F1':22,'C4':23,'A1':24,'D5':25,'F2':26,'E4':27,'F3':28,'G2':29,'F5':30, 'G3':31,'G1':32,'F4':33,'G4':34,'G5':35})
data = pd.get_dummies(data, columns=['subGrade', 'homeOwnership', 'verificationStatus', 'purpose', 'regionCode'], drop_first=True)
data['employmentLength'] = data['employmentLength'].map({'NaN':-1,'1 year':1,'2 years':2,'3 years':3,'4 years':4,'5 years':5,'6 years':6,'7 years':7,'8 years':8,'9 years':9,'10+ years':10,'< 1 year':0})
data['earliesCreditLine'] = data['earliesCreditLine'].apply(lambda s: int(s[-4:])) for item in ['n0','n1','n2','n2.1','n4','n5','n6','n7','n8','n9','n10','n11','n12','n13','n14']:
data['grade_to_mean_' + item] = data['grade'] / data.groupby([item])['grade'].transform('mean')
data['grade_to_std_' + item] = data['grade'] / data.groupby([item])['grade'].transform('std')
data['n15']=data['n8']*data['n10'] return data# 特征工程 简单处理train = make_fea(train)
testA = make_fea(testA)print(train.shape)print(testA.shape)print("数据预处理完成!")
sub=testA[['id']].copy()
sub['isDefault']=0testA=testA.drop(['id','issueDate'],axis=1)
data_x=train.drop(['isDefault','id','issueDate'],axis=1)
data_y=train[['isDefault']].copy()
col=['grade','subGrade','employmentTitle','homeOwnership','verificationStatus','purpose','postCode','regionCode', 'initialListStatus','applicationType','policyCode']for i in data_x.columns: if i in col:
data_x[i] = data_x[i].astype('str')for i in testA.columns: if i in col:
testA[i] = testA[i].astype('str')
answers = []
mean_score = 0n_folds = 5sk = StratifiedKFold(n_splits=n_folds, shuffle=True, random_state=2019)print(data_x.shape)print(testA.shape)# 在测试集中有部分特征训练集中没有,进行处理COLNAME = data_x.columns
testA = testA[COLNAME]# 处理INF NAN ,替换成 -1 注意:np.inf和-np.inf是不同的两个值,这里经常遇到忘记处理后者引发错误data_x.replace([np.inf, -np.inf, np.nan], -1, inplace=True)
testA.replace([np.inf, -np.inf, np.nan], -1, inplace=True)# -----重复正样本,因为原始数据比例约为3:1 因此对正样本重复三次拼接到原始数据中-----a = [i for i, x in enumerate(data_y.values) if x[0]==1]
all_1 = data_x.iloc[a]
all_y = pd.DataFrame(np.ones(all_1.shape[0],dtype=int),columns=['isDefault'])print(all_y.shape)print(data_x.shape,data_y.shape)for i in range(3):
data_y= pd.concat([data_y,all_y],axis=0)
data_x = pd.concat([data_x,all_1],axis=0) print(data_x.shape,data_y.shape)
data_x.reset_index(drop=True,inplace =True)
data_y.reset_index(drop=True,inplace =True)'''
# -----使用SMOTE算法对负样本进行扩充-----
sm = SMOTE(random_state=42)
data_x, data_y = sm.fit_resample(data_x, data_y)
print('Resampled dataset' , data_y.value_counts())
'''