評分卡開發(fā)流程全解析---python版

一吸占、背景

從去年暑假到今年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é)贬丛。評分卡建模流程基本如上,歡迎大家指正交流给涕。

最后編輯于
?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請聯(lián)系作者
  • 序言:七十年代末豺憔,一起剝皮案震驚了整個濱河市额获,隨后出現(xiàn)的幾起案子,更是在濱河造成了極大的恐慌恭应,老刑警劉巖咪啡,帶你破解...
    沈念sama閱讀 206,214評論 6 481
  • 序言:濱河連續(xù)發(fā)生了三起死亡事件,死亡現(xiàn)場離奇詭異暮屡,居然都是意外死亡撤摸,警方通過查閱死者的電腦和手機,發(fā)現(xiàn)死者居然都...
    沈念sama閱讀 88,307評論 2 382
  • 文/潘曉璐 我一進店門褒纲,熙熙樓的掌柜王于貴愁眉苦臉地迎上來准夷,“玉大人,你說我怎么就攤上這事莺掠∩狼叮” “怎么了?”我有些...
    開封第一講書人閱讀 152,543評論 0 341
  • 文/不壞的土叔 我叫張陵彻秆,是天一觀的道長楔绞。 經(jīng)常有香客問我,道長唇兑,這世上最難降的妖魔是什么酒朵? 我笑而不...
    開封第一講書人閱讀 55,221評論 1 279
  • 正文 為了忘掉前任,我火速辦了婚禮扎附,結(jié)果婚禮上蔫耽,老公的妹妹穿的比我還像新娘。我一直安慰自己留夜,他們只是感情好匙铡,可當我...
    茶點故事閱讀 64,224評論 5 371
  • 文/花漫 我一把揭開白布。 她就那樣靜靜地躺著碍粥,像睡著了一般鳖眼。 火紅的嫁衣襯著肌膚如雪。 梳的紋絲不亂的頭發(fā)上嚼摩,一...
    開封第一講書人閱讀 49,007評論 1 284
  • 那天钦讳,我揣著相機與錄音,去河邊找鬼低斋。 笑死蜂厅,一個胖子當著我的面吹牛,可吹牛的內(nèi)容都是我干的膊畴。 我是一名探鬼主播掘猿,決...
    沈念sama閱讀 38,313評論 3 399
  • 文/蒼蘭香墨 我猛地睜開眼,長吁一口氣:“原來是場噩夢啊……” “哼唇跨!你這毒婦竟也來了稠通?” 一聲冷哼從身側(cè)響起衬衬,我...
    開封第一講書人閱讀 36,956評論 0 259
  • 序言:老撾萬榮一對情侶失蹤,失蹤者是張志新(化名)和其女友劉穎改橘,沒想到半個月后滋尉,有當?shù)厝嗽跇淞掷锇l(fā)現(xiàn)了一具尸體,經(jīng)...
    沈念sama閱讀 43,441評論 1 300
  • 正文 獨居荒郊野嶺守林人離奇死亡飞主,尸身上長有42處帶血的膿包…… 初始之章·張勛 以下內(nèi)容為張勛視角 年9月15日...
    茶點故事閱讀 35,925評論 2 323
  • 正文 我和宋清朗相戀三年狮惜,在試婚紗的時候發(fā)現(xiàn)自己被綠了。 大學時的朋友給我發(fā)了我未婚夫和他白月光在一起吃飯的照片碌识。...
    茶點故事閱讀 38,018評論 1 333
  • 序言:一個原本活蹦亂跳的男人離奇死亡碾篡,死狀恐怖,靈堂內(nèi)的尸體忽然破棺而出筏餐,到底是詐尸還是另有隱情开泽,我是刑警寧澤,帶...
    沈念sama閱讀 33,685評論 4 322
  • 正文 年R本政府宣布魁瞪,位于F島的核電站穆律,受9級特大地震影響,放射性物質(zhì)發(fā)生泄漏导俘。R本人自食惡果不足惜峦耘,卻給世界環(huán)境...
    茶點故事閱讀 39,234評論 3 307
  • 文/蒙蒙 一、第九天 我趴在偏房一處隱蔽的房頂上張望趟畏。 院中可真熱鬧贡歧,春花似錦滩租、人聲如沸赋秀。這莊子的主人今日做“春日...
    開封第一講書人閱讀 30,240評論 0 19
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽猎莲。三九已至,卻和暖如春技即,著一層夾襖步出監(jiān)牢的瞬間著洼,已是汗流浹背。 一陣腳步聲響...
    開封第一講書人閱讀 31,464評論 1 261
  • 我被黑心中介騙來泰國打工而叼, 沒想到剛下飛機就差點兒被人妖公主榨干…… 1. 我叫王不留身笤,地道東北人。 一個月前我還...
    沈念sama閱讀 45,467評論 2 352
  • 正文 我出身青樓葵陵,卻偏偏與公主長得像液荸,于是被迫代替她去往敵國和親。 傳聞我的和親對象是個殘疾皇子脱篙,可洞房花燭夜當晚...
    茶點故事閱讀 42,762評論 2 345