了解信用审批业务的基本流程了解ABC记分卡是什么,有什么区别,了解风控建模的流程,掌握记分卡模型,正负样本定义方法,知道如何构建特征,如何评估特征,授信审批业务的基本流程,四因素认证:银行卡持卡人姓名、身份证号码、 银行卡号、手机号码
互联网金融风控系统主要由用户数据三部分组成:用户基本信息、用户行为信息、用户授权信息、外部访问信息。 数据采集会涉及埋点和爬虫技术,行业内的数据基本大同小异。免费运营商数据、安卓可抓取手机内部信息(APP名称、移动设备信息、部分APP内容信息)、充电信用数据、各种信息验证、外部黑名单等现金贷款和消费金融等特定场景都会有自己的数据可供使用,如阿里、京东.com自有电商数据、滴滴司机数据、 顺丰中通快递资料、用户基本信息(联系方式、通讯录、教育背景等)。 用户行为信息(操作应用程序、注册、点击位置时的行为。 用户授权信息(运营商、学术信息网络、设备IMEI...外部访问信息(P2P信用、芝麻信用等其他金融机构。 政策系统:收集用户信息后,将用户信息录入策略引擎、欺诈规则、访问规则(年龄、地区、通讯录、行为规则)、运营商规则(呼叫规则)、风险列表(黑名单、失信名单、法院名单)、网贷(长、白)。机器学习模型:欺诈检测模型、准入模型、信用模型、风险定价、配额管理、流失预警、损失修复。
风控模型包括一张B卡。 该模型可以使用相同的算法,一般使用逾期天数来区分正负样本,即目标值y(0或1)贷款前申请记分卡中的贷款行为记分卡beh**ior记分卡贷后催收记分卡c卡由于用途不同, Y的值可能不同,公司有内部提醒,也有外部提醒。对外收款率低,单价贵,C卡的Y可以根据内部是否召回来定义。 准备明确的需求、模型设计、业务抽象、分类、回归问题定义、标签(目标值)样本、设计特征、工程数据处理,选择合适的样本,并将所有信息作为基本特征进行匹配,构建特征评估模型、模型训练、模型评估、模型调优、在线操作、模型交付、模型部署、模型监控项目准备期特征工程、模型构建、在线运营。
定义您的需求目标群体:新客户、优质老客户、逾期老客户产品:额度、利率市场策略:冷启动、开拓市场、提高收益时限:应急使用、长期部署实例企业需要为新客户开通小额现金贷产品,抢占新市场高风险瘦数据新客户应用记分卡模型设计业务抽象成分类回归问题风控场景下的问题通常可以转化为二元分类问题:信用评分模型预期用于用户是否会逾期,逾期用户1营销模型预期用于用户被营销后是否会来借贷, 而用户1未借贷的模型,有望用于用户是否会与用户1失去联系在风控业务中,只有欺诈检测不是二元问题。 由于样本量不足,它可能是无监督学习模型模型 算法 规则模型 逻辑回归 集成学习 融合模型 模型输入:数据源 时间跨度 Y 标签定义 在构建信用评分模型时,原始数据中只有每个人当前的逾期情况,没有负样本,负样本需要人工构建 通常选择一个截止点(阈值), 当逾期超过一定阈值时,将样本视为负样本,以后不会还钱,例如,逾期15天是正负样本的标记阈值,逾期超过15天的客户为逾期。在 15 天 y = 1 时,y=0 如何定义只有按时付款的人和逾期较少的人才会被标记为 0。例如,将逾期 5 天的 5 天和未逾期 5 <至 15 天的数据(灰色样本)从样本中删除,去除“灰色样本”会使样本分布趋于二项式,更有利于模型学习。 “灰色样本”通常放置在测试集中,以确保模型在训练后能够区分样本的那部分。 样本选择的代表性:样本必须充分代表总体。 例如,消费贷款客户的数据不能直接用于小额现金贷款的充足性:样本集的数量必须满足一定的要求。 记分卡建模通常需要至少 1500 个正负样本。 随着样本量的增加,模型的效果将显著提高时效性:当样本量足够时,通常要求样本的观察周期尽可能接近实际应用时间节点。 例如,在客户群稳定的场景中,例如银行,观察期可能长达一年半到两年。 排除:虽然建模样本需要具有整体代表性,但部分法律规定,当前样本集不应包含不满足特定场景借贷需求的用户,如行为记分卡用户、不还款用户、欺诈用户等。 记分卡建模通常要求正负样本数“=1500,但当总样本量超过50000时,许多模型的性能不再随着样本量的增加而显著提高,数据处理和模型训练过程通常很耗时。 如果样本量过大,会给训练过程增加不必要的负担,需要对样本进行二次抽样。 由于阴性样本通常很少,因此只有阳性样本不足。 常见的欠采样方法分为:随机欠采样:直接对正样本进行欠采样,达到预期比值。 分层抽样:抽样后,开发样本、验证样本和超时样本中正负样本的比例相同。 等比例抽样:正样本与正负样本的比例相等,即正样本量与负样本量之比为1:1。 需要注意的是,取样后需要为阳性样本添加权重。 如果将阳性样本作为原始样本进行采样,则采样后阳性样本的权重为4,阴性样本的权重保持在1。 因为后续的计算模型在测试指标和预期坏账时,需要将权重带入计算逻辑中,以还原指标在实际情况下的估计值,否则预期结果与实际部署结果之间会有明显的偏差。 当负样本较少时,需要进行成本敏感的加权或过样处理,观察期和履约期是指用户申请信用产品之前的时间段,履约期是定义好坏标签的时间窗,如果在此时间窗内触发不良定义, 这是一个糟糕的样本,反之亦然。例如,如果要建立卡模型,观察期为12个月,履约期为3个月,以用户在借贷前12个月的历史行为表现作为变量进行后续建模,如果用户在到期后3个月内未还款,则认为该用户为负样本, 那么性能周期说是3个月,在建模之前,需要将训练数据测试数据划分数据集分成3个子集: 开发样本(develop): 开发样本和验证样本采用分层抽样方式划分,确保两个数据集中的负样本占验证样本(估值)的比例相同: 开发样本与验证样本的比例为 6:4 的时间外 (OOT):整个建模样本中的最新数据通常为 u例子:
客户群体样本设计选择:新客户、未逾期老客户、逾期老客户培训集测试集 1 月 2 月 3 月 4 月 5 月 6 月 7 月 8 月 合计 100200300400500600700800 坏 366815121424 坏 %3% 3% 2% 2% 2% 2% 2% 3% 客户群体描述:首单用户,内部数据丰富,消除高危职业,收入范围在xxxx客户群 标签: 好:fpd<=5 坏:fpd>15,(5,15)灰度样本,不参加训练,参与测试和评估数据研究,明确目标人群可用的数据,明确数据采集逻辑。
阐明数据的质量、覆盖率和稳定性。
对特征构建的误区:获取数据后,立即进行特征构建,在特征之前需要明确:数据源对应的具体数据表,绘制ER图的样本集对特征进行评估,B卡的样本集不能包含逾期数据,C卡的样本集不能包含按时还款的数据特征框架, 确保数据使用维度综合考虑,确定思维框架,与组内其他人讨论,明确数据源对应的具体数据表,明确数据来源:(de data engineer Data Warehouse Engineer)。
数据分析师获取的数据可能如下:数据仓库的原始表、数据仓库的重构表、数据仓库的原始表、数据仓库的重构表可能因为更新时间不同而具有不同的数据量!尽量使用数据仓库工程师处理的重构表,确保逻辑统一、实时**,确保生产库和数据仓库的数据一致(难)画出类似er的图数据关系 一对一、一对多、多对多。
编写 SQL 查询时需要从用户列表开始,加入其他表时无法选择distinctuser_idfrom订单表清晰评估特征样本集新申请人无内部信用数据无逾期老客户当期无逾期信息逾期客户和未逾期客户的还款数据一定有很大差异如何从原始数据构建特征:指定特征框架,确保充分考虑数据使用维度每个属性都可以由r(新近度)f(频率)M(货币)三个维度的思维, 构建GPS特征的经纬度是最近的GPS省市,应用时间GPS位于GPS出现的省市,在gGPS出现的省市中,MGPS最多的省市出现在GDP最多的省市, 人口, 坏账率, 其他统计资料
设计实验、模型训练和模型评估。
在设计实验训练模型时,影响模型效果的可能因素很多,我们需要通过设计实验来验证哪些因素会提高模型的性能。基尼报告1:差异化,在不同细分市场中捕捉坏人的能力坏人总数,坏人率,KS [300, 550] [500, 600] [600, 700] [700, 750] [750, 800] [800, 850] [850, 950] 报告 2:跨时间稳定性得分段测试集 1 测试集 2 在线 1 在线 2 周期。300, 550]10%10%[500, 600]20%20%[600, 700]20%20%[700, 750]25%25%[750, 800]20%20%[800, 850]4%[850, 950]1%1%决策点占比50%50%50%用户总数30002000平均分730725psi-0。01模型交付、模型部署、模型监控。
模型交付流程: 1 提交特征和模型报告2 离线结果质量审查(无遗漏、无重复、存储位置正确、文件名规范)3 保存模型文件,确定版本号,提交时间4 老板审批,通知业务方5 在线部署、案例分析、持续监控 功能报告1 功能项目需求2 功能项目任务列表3 功能项目进度表4 类 ER 图5 设计示例6 功能框架7 每周开发进度和成果8 每周讨论反馈和改进说明9 特点项目交付说明10 特点项目摘要模型报告1 模型项目要求2 模型项目任务列表3 模型项目时间表4 模型设计5 样品设计6 模型训练过程和实验设计7 每周开发进度和结果8 每周讨论、反馈和改进说明9 模型 P5%的“驾贷”产品逾期30天,5%的人期望解决方案现有风控架构稳定,希望快速开发上线,解决问题,尽量不要使用复杂的方法,考虑使用已有数据,挖掘合适的业务规则和数据字典
加载数据。
import pandas as pdimport numpy as npdata = pd.read_excel('data/rule_data.xlsx')data.head()
显示结果:查看新课程uidoil_actv_dtcreate_dttotal_oil_cntpay_amount_totalclass_newbad_indoil_amountdiscount_amountsale_amountamountpay_amountcoupon_amountpayment_coupon_amountchannel_codeoil_codescenesource_appcall_source0a82177102018-08-192018-08-17275.048295495.4b03308.561760081.01796001.01731081.08655401.01.01.0132031a82177102018-08-192018-08-16275.048295495.4b04674.682487045.02537801.02437845.012189221.01.01.0132032a82177102018-08-192018-08-15275.048295495.4b01873.06977845.0997801.0961845.04809221.01.01.0122033a82177102018-08-192018-08-14275.048295495.4b04837.782526441.02578001.02484441.012422201.01.01.0122034a82177102018-08-192018-08-13275.048295495.4b02586.381350441.01378001.01328441.06642201.01.01.012203
data.class_new.unique()
显示结果:原始数据的特征太少,考虑到一些新特征是在原有特征的基础上派生出来的,将特征分为三类,分别处理数值类型变量:根据id分组后,采用多种方式聚合推导新的特征分类类型变量, 并且根据 ID 分组后,聚合查询条目数,并派生出新的特征array(['b', 'e', 'c', 'a', 'd', 'f'], dtype=object)
org_list = ['uid','create_dt','oil_actv_dt','class_new','bad_ind']agg_list = ['oil_amount','discount_amount','sale_amount','amount','pay_amount','coupon_amount','payment_coupon_amount']count_list = ['channel_code','oil_code','scene','source_app','call_source']
创建数据副本,保留底部表,并查看缺少的内容df = data[org_list].copy()df[agg_list] = data[agg_list].copy()df[count_list] = data[count_list].copy()df.isna().sum()
显示结果:查看数值变量的分布uid 0 create_dt 4944 oil_actv_dt 0 class_new 0 bad_ind 0 oil_amount 4944 discount_amount 4944 sale_amount 4944 amount 4944 pay_amount 4944 coupon_amount 4944 payment_coupon_amount 4946 channel_code 0 oil_code 0 scene 0 source_app 0 call_source 0 dtype: int64
df.describe()
显示结果:填写缺失值完成CRAT DT,使用Oil Actv DT填写拦截申请时间和不超过6个月的贷款发放时间数据(考虑数据的时效性)。bad_indoil_amountdiscount_amountsale_amountamountpay_amountcoupon_amountpayment_coupon_amountchannel_codeoil_codescenesource_appcall_sourcecount50609.00000045665.0000004.566500e+044.566500e+044.566500e+044.566500e+0445665.00000045663.00000050609.00000050609.00000050609.00000050609.00000050609.000000mean0.017764425.3761071.832017e+051.881283e+051.808673e+059.043344e+050.576853149.3953971.4763781.6178941.9065190.3060722.900729std0.132093400.5962442.007574e+052.048742e+051.977035e+059.885168e+050.494064605.1388231.5114703.0741660.3672800.8936820.726231min0.0000001.0000000.000000e+000.000000e+001.000000e+005.000000e+000.0000000.0000000.0000000.0000000.0000000.0000000.00000025%0.000000175.4400006.039100e+046.200100e+045.976100e+042.988010e+050.0000001.0000001.0000000.0000002.0000000.0000003.00000050%0.000000336.1600001.229310e+051.279240e+051.209610e+056.048010e+051.0000001.0000001.0000000.0000002.0000000.0000003.00000075%0.000000557.6000002.399050e+052.454010e+052.360790e+051.180391e+061.000000100.0000001.0000000.0000002.0000000.0000003.000000max1.0000007952.8200003.916081e+063.996001e+063.851081e+061.925540e+071.00000050000.0000006.0000009.0000002.0000003.0000004.000000
def time_isna(x,y): if str(x) == 'nat': x = y return xdf2 = df.sort_values(['uid','create_dt'],ascending = false)df2['create_dt'] = df2.apply(lambda x: time_isna(x.create_dt,x.oil_actv_dt),axis = 1)df2['dtn'] = (df2.oil_actv_dt - df2.create_dt).apply(lambda x :x.days)df = df2[df2['dtn']<180]df.head()
显示结果:按 ID 号对用户进行排序,并保留最近一次请求的时间,以确保每个用户都有记录uidcreate_dtoil_actv_dtclass_newbad_indoil_amountdiscount_amountsale_amountamountpay_amountcoupon_amountpayment_coupon_amountchannel_codeoil_codescenesource_appcall_sourcedtn50608b964363919850357032018-10-082018-10-08b0nannannannannannannan69234050607b964363919846933972018-10-112018-10-11e0nannannannannannannan69234050606b964363919772174682018-10-172018-10-17b0nannannannannannannan69234050605b964363919764808922018-09-282018-09-28b0nannannannannannannan69234050604b964363919721060432018-10-192018-10-19a0nannannannannannannan692340
base = df[org_list]base['dtn'] = df['dtn']base = base.sort_values(['uid','create_dt'],ascending = false)base = base.drop_duplicates(['uid'],keep = 'first')base.shape
显示结果:连续统计变量特征推导的函数聚合方法包括对历史特征值进行计数、查找大于 0 的历史特征值的数量、求和、平均值、最大值、最小值、方差和范围
gn = pd.dataframe()for i in agg_list: tp = df.groupby('uid').apply(lambda df:len(df[i]))reset_index() tp.columns = ['uid',i + '_cnt'] if gn.empty: gn = tp else: gn = pd.merge(gn,tp,on = 'uid',how = 'left'求大于 0 tp = df 的历史特征值的个数groupby('uid').apply(lambda df:np.where(df[i]>0,1,0).sum())reset_index() tp.columns = ['uid',i + '_num'] if gn.empty: gn = tp else: gn = pd.merge(gn,tp,on = 'uid',how = 'left') 总和 tp = dfgroupby('uid').apply(lambda df:np.nansum(df[i]))reset_index() tp.columns = ['uid',i + '_tot'] if gn.empty: gn = tp else: gn = pd.merge(gn,tp,on = 'uid',how = 'left') 是平均 tp = dfgroupby('uid').apply(lambda df:np.nanmean(df[i]))reset_index() tp.columns = ['uid',i + '_**g'] if gn.empty: gn = tp else: gn = pd.merge(gn,tp,on = 'uid',how = 'left') 求最大 TP = dfgroupby('uid').apply(lambda df:np.nanmax(df[i]))reset_index() tp.columns = ['uid',i + '_max'] if gn.empty: gn = tp else: gn = pd.merge(gn,tp,on = 'uid',how = 'left') 求最小值 tp = dfgroupby('uid').apply(lambda df:np.nanmin(df[i]))reset_index() tp.columns = ['uid',i + '_min'] if gn.empty: gn = tp else: gn = pd.merge(gn,tp,on = 'uid',how = 'left') 求方差 tp = dfgroupby('uid').apply(lambda df:np.nanvar(df[i]))reset_index() tp.columns = ['uid',i + '_var'] if gn.empty: gn = tp else: gn = pd.merge(gn,tp,on = 'uid',how = 'left'求值域 tp = dfgroupby('uid').apply(lambda df:np.nanmax(df[i]) np.nanmin(df[i]) reset_index() tp.columns = ['uid',i + '_ran'] if gn.empty: gn = tp else: gn = pd.merge(gn,tp,on = 'uid',how = 'left')
查看派生结果gn.columns
显示结果:查找 dstc lst 变量的非重复数index(['uid', 'oil_amount_cnt', 'oil_amount_num', 'oil_amount_tot', 'oil_amount_**g', 'oil_amount_max', 'oil_amount_min', 'oil_amount_var_x', 'oil_amount_var_y', 'discount_amount_cnt', 'discount_amount_num', 'discount_amount_tot', 'discount_amount_**g', 'discount_amount_max', 'discount_amount_min', 'discount_amount_var_x', 'discount_amount_var_y', 'sale_amount_cnt', 'sale_amount_num', 'sale_amount_tot', 'sale_amount_**g', 'sale_amount_max', 'sale_amount_min', 'sale_amount_var_x', 'sale_amount_var_y', 'amount_cnt', 'amount_num', 'amount_tot', 'amount_**g', 'amount_max', 'amount_min', 'amount_var_x', 'amount_var_y', 'pay_amount_cnt', 'pay_amount_num', 'pay_amount_tot', 'pay_amount_**g', 'pay_amount_max', 'pay_amount_min', 'pay_amount_var_x', 'pay_amount_var_y', 'coupon_amount_cnt', 'coupon_amount_num', 'coupon_amount_tot', 'coupon_amount_**g', 'coupon_amount_max', 'coupon_amount_min', 'coupon_amount_var_x', 'coupon_amount_var_y', 'payment_coupon_amount_cnt', 'payment_coupon_amount_num', 'payment_coupon_amount_tot', 'payment_coupon_amount_**g', 'payment_coupon_amount_max', 'payment_coupon_amount_min', 'payment_coupon_amount_var_x', 'payment_coupon_amount_var_y'], dtype='object')
gc = pd.dataframe()for i in count_list: tp = df.groupby('uid').apply(lambda df: len(set(df[i]))reset_index() tp.columns = ['uid',i + '_dstc'] if gc.empty: gc = tp else: gc = pd.merge(gc,tp,on = 'uid',how = 'left')
将变量组合在一起fn = pd.merge(base,gn,on= 'uid')fn = pd.merge(fn,gc,on= 'uid') fn.shape
显示结果:在合并过程中可能会出现缺失值,并且会填充缺失值
fn = fn.fillna(0)fn.head(100)
显示结果:训练决策树模型uidcreate_dtoil_actv_dtclass_newbad_inddtnoil_amount_cntoil_amount_numoil_amount_totoil_amount_**g...payment_coupon_amount_maxpayment_coupon_amount_minpayment_coupon_amount_var_xpayment_coupon_amount_var_ypayment_coupon_amount_varchannel_code_dstcoil_code_dstcscene_dstcsource_app_dstccall_source_dstc0b964363919850357032018-10-082018-10-08b00100.000.00...0.00.00.00.00.0111111b964363919846933972018-10-112018-10-11e00100.000.00...0.00.00.00.00.0111112b964363919772174682018-10-172018-10-17b00100.000.00...0.00.00.00.00.0111113b964363919764808922018-09-282018-09-28b00100.000.00...0.00.00.00.00.011111
100 rows × 74 columns
x = fn.drop(['uid','oil_actv_dt','create_dt','bad_ind','class_new'],axis = 1)y = fn.bad_ind.copy()from sklearn import treedtree = tree.decisiontreeregressor(max_depth = 2,min_samples_leaf = 500,min_samples_split = 5000)dtree = dtree.fit(x,y)
要输出决策树图像并做出决策,需要安装 Graphviz 软件,并且需要安装两个 python 库 pip install graphvizpip install pydotplusimport pydotplus from ipython.display import imagefrom six import stringioimport os# os.environ["path"] += os.pathsep + 'c:/program files (x86)/graphviz2.38/bin/'# with open("dt.dot", "w") as f:# tree.export_graphviz(dtree, out_file=f)dot_data = stringio()tree.export_graphviz(dtree, out_file=dot_data, feature_names=x.columns, class_names=['bad_ind'], filled=true, rounded=true, special_characters=true)graph = pydotplus.graph_from_dot_data(dot_data.getvalue())image(graph.create_png())
显示结果:使用结果对用户进行细分
group_1 = fn.loc[(fn.amount_tot>48077.5)&(fn.amount_cnt>3.5)].copy()group_1['level'] = 'past_a'group_2 = fn.loc[(fn.amount_tot>48077.5)&(fn.amount_cnt<=3.5)].copy()group_2['level'] = 'past_b'group_3 = fn.loc[fn.amount_tot<=48077.5].copy()group_3['level'] = 'past_c'
如果您拒绝过去的 C 客户,则可以将总体负样本百分比降低到 0021 如果过去 B 也被否定,则整体负样本比例可以降低到 0012 对于过去A、过去B和过去C的实际策略,应根据利率进行线性规划,从而实现风险定价信贷审批业务的基本流程:申请、审批、支付、还款、重新申请、再贷款审批、规则模型、逾期催收规则、模型。
ABC记分卡A应用,B行为,C集合针对不同的客户群体,不同的可用数据,Y定义不同的风控建模流程项目准备 特征工程建模 在线操作 明确需求 数据处理 模型训练 模型交付模型设计 特征构建 模型评估 模型部署 样本设计 功能评估模型 调优模型 监控记分卡模型 正负样本定义方法 一般习惯y = 1 对于不良用户(默认值) y = 1 选择:使用 DPD30、DPD15...。根据具体业务情况),删除灰色部分的用户没有逾期,或者逾期不足5天,作为一个好的用户,如何构建特征,如何评估特征特征,构建E-R图,知道哪些表保存了数据,知道表之间的关系,知道哪些数据可以与单个特征一起使用, 从三个维度、RFM考虑、生成新特征、用户时间段、特征、用户时间序列、特征、特征、评估覆盖率、稳定性、psi、鉴别度、iv单特征、AUCKS规则引擎如何工作,使用一系列判断逻辑来区分客户群体,不同群体的逾期风险存在显著差异,机器学习模型可以辅助规则挖掘。