一吸占、背景
從去年暑假到今年2月份曾在兩家互金公司實習癌压,接觸了很多金融信貸場景下的知識嫡霞,其中最基礎(chǔ)的當屬評分卡的開發(fā)了衙荐。接下來將結(jié)合我在實習中的工作講一下評分卡的開發(fā)全流程捞挥,有興趣的童鞋可以看一下,有任何不對的地方希望大家批評指正忧吟。
二砌函、正文
首先評分卡的開發(fā)流程大致包含下面這些步驟:
(一)定義問題
確定兩類重要的問題:觀察期和表現(xiàn)期、好壞客戶的定義溜族。
觀察期相當于X變量讹俊,即搜集用戶的行為;表現(xiàn)期相當于Y變量煌抒,即用戶的表現(xiàn)仍劈,在評分卡中通常就是好壞兩類。觀察期從觀察時點起向前回溯的最遠時長寡壮,表現(xiàn)期從觀察時點起根據(jù)賬齡分析來決定贩疙。
從圖中可看出,在9個月以后的壞賬率趨于穩(wěn)定况既,說明壞客戶重復(fù)暴露的時間為9個月这溅,那么就定義表現(xiàn)期為9個月。
好壞客戶通過滾動率和遷移率來分析棒仍。滾動率分析就是從某個觀察點之前的一段時間(稱為觀察期)的最壞的狀態(tài)向觀察點之后的一段時間(稱為表現(xiàn)期)的最壞狀態(tài)的發(fā)展變化情況悲靴,如下所示:
從上看出,在觀察期正常還款的在表現(xiàn)期還正常還款的概率為96%莫其,在觀察期逾期3個月的客戶在表現(xiàn)期逾期4個月的概率為61%癞尚,說明M3的用戶有很大概率會更壞變?yōu)镸4,故定義M3為壞客戶乱陡,即逾期60-90的人否纬。
(二)數(shù)據(jù)準備和預(yù)處理
貫穿全文的包---scorecardpy
根據(jù)定義的觀察期和表現(xiàn)期拉取數(shù)據(jù),預(yù)處理主要是對異常值蛋褥、缺失值临燃,有些情況還需要特征縮放即歸一化和標準化處理,比如時間和金額等等和其他字段的取值相差很大時,可以利用已有的方法也可以自定義膜廊,如下所示:
#相當于數(shù)據(jù)預(yù)處理, 設(shè)定要刪除的規(guī)則乏沸,主要是指缺失率,iv<0.02等等
dt_s = sc.var_filter(data, y="flagy")
print("變量預(yù)處理前后變化:",data.shape[1],"->",dt_s.shape[1])
(三)特征工程
可以從數(shù)據(jù)來源爪瓜、業(yè)務(wù)類型蹬跃、時間范圍和統(tǒng)計單位4個維度來衍生,將4個維度作笛卡爾積交叉組合特征铆铆。
除此之外維度指標有頻次(近3個月的f貸款申請次數(shù))蝶缀、總和、平均薄货、占比翁都、一致性(身份證和手機號所在的省份是否一致)、距離谅猾、波動柄慰、占比趨勢和對比趨勢等。還有一些可以結(jié)合具體的業(yè)務(wù)來税娜,如在信用卡業(yè)務(wù)中對于還款有近1坐搔、3、6敬矩、12概行、24個月的還款次數(shù)、最大單筆還款距今時長弧岳、最后一次還款距今時長等等凳忙。
(四)變量篩選
在評分卡中由于需要業(yè)務(wù)具有很強的解釋性,通常入模的變量為8~16個缩筛,需要篩選變量消略。變量篩選的方式有以下幾種:
如果不使用預(yù)測模型,有監(jiān)督的可以使用信息值瞎抛、卡方統(tǒng)計量和gini系數(shù)艺演;無監(jiān)督的可以使用相關(guān)性、聚類和PCA等
若使用預(yù)測模型桐臊,可以使用逐步回歸胎撤、正則化(AIB,BIC,lasso)、交叉驗證和機器學習如xgb等自帶篩選特征断凶,在經(jīng)過預(yù)處理后保留下來的變量有707個伤提,故必須要篩選變量,采用了兩種方法lasso和xgb认烁,xgb的方法如下:
# 進行Xgboost之前需要處理數(shù)據(jù),要預(yù)先處理數(shù)據(jù) 都要轉(zhuǎn)化成float類型,而且要沒有缺失值
import re
from sklearn.model_selection import cross_val_score
from sklearn.model_selection import KFold
from sklearn.model_selection import train_test_split
def float_1(x):
? ? try:
? ? ? ? return float(x)
? ? except:
? ? ? ? return np.nan
y = dt_s['flagy']
col = set(dt_s.columns)
X = dt_s.ix[:, [x for x in col if not re.search('flagy', x)]]
X = X.applymap(float_1)
X = X.astype(np.float)
X = X.fillna(0)
X_train, X_test, y_train, y_test = train_test_split(X, y, test_size=0.3, random_state=10)
選擇合適的參數(shù)
import xgboost as xgb
dtrain = xgb.DMatrix(X_train, y_train)
params={
? ? 'booster':'gbtree',
? ? 'objective': 'binary:logistic',
? ? ? ? 'eval_metric': 'auc',
? ? 'max_depth': 3,
? ? 'lambda': 5,
? ? ? ? 'subsample': 0.8,
? ? ? ? 'colsample_bytree': 0.8,
? ? ? ? 'min_child_weight': 20,
? ? ? ? 'eta': 0.025,
? ? 'nthread': 8
? ? ? ? }
# 交叉驗證選出test-auc-mean最高時對應(yīng)的迭代次數(shù)
cv_log = xgb.cv(params,dtrain,num_boost_round=500,
? ? ? ? ? ? ? ? nfold=5,
? ? ? ? ? ? ? ? metrics='auc',
? ? ? ? ? ? ? ? early_stopping_rounds=10)
bst_auc = cv_log['test-auc-mean'].max()
cv_log['nb'] = cv_log.index
cv_log.index = cv_log['test-auc-mean']
nround = cv_log.nb.to_dict()[bst_auc]
lasso方法如下:
# 交叉驗證擬合Lasso模型
from sklearn.linear_model import LassoCV
lassocv = LassoCV()
lassocv.fit(X,y)
# 交叉驗證選擇的參數(shù)alpha
print(lassocv.alpha_)
# 最終Lasso模型中的變量系數(shù)
print(lassocv.coef_[:10])
# Lasso細篩出的變量個數(shù)
print(np.sum(lassocv.coef_ > 0))
然后可以將兩者的特征交集作為初步的變量肿男。
#將兩種方法取交集介汹,得到的變量入模
features_chosen = ['pc_rcnt_income', 'location_cell_stab', 'location_cell_use_days',
? ? ? ? ? ? ? ? ? 'als_m6_id_nbank_cons_allnum', 'ir_id_x_cell_notmat_days',
? ? ? ? ? ? ? ? ? 'pc_business_type', 'als_m12_id_caoff_orgnum', 'pc_noincome_lst_mons',
? ? ? ? ? ? ? ? ? 'stab_mail_num', 'pc_long_income', 'als_m12_id_cooff_orgnum',
? ? ? ? ? ? ? ? ? 'pc_mobile_cons', 'pc_regincome_sta', 'als_fst_cell_nbank_inteday',
? ? ? ? ? ? ? ? ? 'location_online_fre', 'als_m6_cell_nbank_p2p_allnum',
? ? ? ? ? ? ? ? ? 'als_m6_cell_bank_min_inteday', 'als_m12_id_nbank_max_inteday',
? ? ? ? ? ? ? ? ? 'location_id_attr', 'als_m12_cell_nbank_nsloan_allnum',
? ? ? ? ? ? ? ? ? 'cons_max_m12_pay', 'cons_tot_m3_visits', 'cons_tot_m3_pay', 'als_m12_id_nbank_allnum'
]
(五)相關(guān)性分析
可以利用皮爾森相關(guān)系數(shù)>0.7,和方差擴大因子vif>3來篩選變量舶沛。
#將vif>3的去掉嘹承,statsmodels不允許有缺失值
col = np.array(data[features_chosen])
from statsmodels.stats.outliers_influence import variance_inflation_factor as vif
data[features_chosen] = data[features_chosen].fillna(0)
for i in range(len(features_chosen)):
? ? print('{} VIF是{}'.format(features_chosen[i],vif(col,i)))
(六)WOE/IV
在計算WOE和IV值之前先要劃分訓練集、測試集和跨時間驗證樣本如庭,比例為5:2:3叹卷,接下來是分箱,分箱應(yīng)該在訓練集上而不是整個數(shù)據(jù)上坪它,因為如果在整個數(shù)據(jù)集上先分箱骤竹,然后再用其中的一部分作為測試集,將會導(dǎo)致過擬合往毡。
dt_var = data[features_chosen]
dt_var['flagy'] = data['flagy']
train, test = sc.split_df(dt_var,'flagy').values()
print("訓練集蒙揣、測試集劃分比例為",train.shape[0],":",test.shape[0])
然后接下來分箱
#bins是對訓練集作的分箱
bins_train = sc.woebin(train, y="flagy",save_breaks_list=True)
#測試集以在訓練集上分箱的情況作為標準
bins_test = sc.woebin(test, y="flagy",breaks_list=breaks_list)
train_woe = sc.woebin_ply(train, bins_train)
test_woe = sc.woebin_ply(test, bins_test)
y_train = train_woe.loc[:,'flagy']
X_train = train_woe.loc[:,train_woe.columns != 'flagy']
y_test = test_woe.loc[:,'flagy']
X_test = test_woe.loc[:,train_woe.columns != 'flagy']
X_train.to_csv('X_train_final.csv',index=False,sep=',',header=None)
y_train.to_csv('y_train_final.csv',index=False,sep=',',header=None)
計算的IV值
分箱的結(jié)果,對于非單調(diào)的可以手動分箱:
(七)模型構(gòu)建
用到LR和LightGBM卖擅,部分代碼如下:
LR代碼:
from sklearn.model_selection import GridSearchCV
from sklearn.linear_model import LogisticRegression
#需要調(diào)優(yōu)的參數(shù)
penaltys = ['l1','l2']
Cs = [0.001, 0.01, 0.1, 1, 10, 100, 1000]
tuned_parameters = dict(penalty = penaltys, C = Cs)
lr_penalty= LogisticRegression()
# GridSearchCV(estimator, param_grid, ... cv=None, ...)
# estimator: 模型鸣奔, param_grid:字典類型的參數(shù)墨技, cv:k折交叉驗證
grid= GridSearchCV(lr_penalty, tuned_parameters, cv=5)
grid.fit(X_train,y_train) # 網(wǎng)格搜索訓練
grid.cv_results_ #訓練的結(jié)果
# examine the best model
print(grid.best_score_)? # 最好的分數(shù)
print(grid.best_params_) # 最好的參數(shù)
from sklearn.linear_model import LogisticRegression
lr = LogisticRegression(penalty='l1',C=0.1,solver='saga',n_jobs=-1)
lr.fit(X_train,y_train)
對于變量還要計算其顯著性惩阶,如P值、Z值扣汪,然后將P>|Z|的變量去掉
from scipy import stats
def stat_model(x, y, y_pred, const, coef):
? ? '''
? ? '''
? ? x = x.copy()
? ? x.reset_index(drop=True,inplace=True)
? ? y = y.copy()
? ? y.reset_index(drop=True,inplace=True)
? ? newX = pd.DataFrame({"Constant": np.ones(len(x))}).join(pd.DataFrame(x))
? ? MSE = (sum((y - y_pred)**2))/(len(newX)-len(newX.columns))#分母應(yīng)該是N
? ? params = np.append(const,coef)
? ? var_b = MSE*(np.linalg.inv(np.dot(newX.T,newX)).diagonal())
? ? st_error = np.sqrt(var_b)
? ? t_v = params/st_error
? ? p_values = [2*(1-stats.t.cdf(np.abs(i),len(newX)-1)) for i in t_v]
? ? result = pd.DataFrame({'Coefficients':params,
? ? ? ? ? ? ? ? ? ? ? ? ? 'Standard Errors':st_error,
? ? ? ? ? ? ? ? ? ? ? ? ? 't values':t_v,
? ? ? ? ? ? ? ? ? ? ? ? ? 'P_values':p_values}) \
? ? ? ? ? ? ? ? ? ? ? ? .apply(lambda x: round(x,3)) \
? ? ? ? ? ? ? ? ? ? ? ? .assign(Variable = newX.columns.tolist())
? ? return result
#intercept_是截距断楷,coef_是訓練后的輸入端模型系數(shù),如果label有兩個崭别,即y值有兩列冬筒。
summary = stat_model(X_train,y_train,train_pred,lr.intercept_,lr.coef_)
summary[['Variable','Coefficients','P_values']]
#得到的constant是截距項,不用管
LightGBM代碼(找不到了...):
(八)模型評估
模型評估使用ks茅主、auc和psi來評價
train_perf = sc.perf_eva(y_train, train_pred, title = "train")
test_perf = sc.perf_eva(y_test, test_pred, title = "test")
最終模型的分值分布如下:
(九)模型部署
在風控后臺上配置模型規(guī)則舞痰,對于復(fù)雜的模型還需要將模型文件作轉(zhuǎn)換,封裝成類,由其他代碼來調(diào)用
(十)模型監(jiān)控
前期主要監(jiān)控模型的穩(wěn)定性psi诀姚,變量的psi响牛,也要比較模型每日的拒絕率和線下拒絕率
后期等到用戶有一定表現(xiàn)后可以用auc、ks和iv等來對比線上和線下的區(qū)別赫段。
三呀打、結(jié)語
在互金實習了兩段,最終還是選擇了互聯(lián)網(wǎng)糯笙,這也是對自己實習部分工作的回顧和總結(jié)贬丛。評分卡建模流程基本如上,歡迎大家指正交流给涕。