《Lending Club—構(gòu)建貸款違約預(yù)測模型》點評

python金融風(fēng)控評分卡模型和數(shù)據(jù)分析微專業(yè)課:http://dwz.date/b9vv

博主介紹: 持牌照消費金融模型專家,和中科院,中科大教授保持長期項目合作;和同盾,聚信立等外部數(shù)據(jù)源公司有項目對接。熟悉消費金融場景業(yè)務(wù),線上線下業(yè)務(wù),包括現(xiàn)金貸,商品貸,醫(yī)美,反欺詐,汽車金融等等。模型項目200+,擅長Python機(jī)器學(xué)習(xí)建模,對于變量篩選,衍生變量構(gòu)造,變量缺失率高,正負(fù)樣本不平衡,共線性高,多算法比較,調(diào)參等疑難問題有良好解決方法。
之前看到一篇不錯文章《lendingclub-構(gòu)建貸款違約預(yù)測模型》,分享給各位同學(xué)。原文鏈接如下https://blog.csdn.net/arsenal0435/article/details/80446829。此文章總結(jié)整體構(gòu)建思路不錯,但每個環(huán)節(jié)都還有改善地方。例如特征篩選時,是否使用標(biāo)準(zhǔn)化方法縮放數(shù)據(jù)要看算法和最終驗證結(jié)果。熱編碼方法hot-coding是一種分類數(shù)據(jù)預(yù)處理方法,但并非最優(yōu),且熱編碼后數(shù)據(jù)擴(kuò)張多個維度,極大占用內(nèi)存。
算法方面,邏輯回歸在數(shù)據(jù)預(yù)處理,特別是woe分箱需要耗費大量時間和精力。目前woe分箱方法有卡方分箱,kmean分箱,樹分箱,但沒有統(tǒng)一標(biāo)準(zhǔn)方法。一般腳本自動分箱后,最好人為檢查一下,再做做粗分箱微調(diào)。
模型驗證結(jié)果來看,auc=0.66,效果也很一般,模型還有提升空間?;煜仃嘑1分?jǐn)?shù),sensitivity,precision等結(jié)果很奇怪,都是0.66。仔細(xì)觀察,是博主用了smote方法處理非平衡數(shù)據(jù),把正負(fù)樣本5:5開。
SMOTE方法來對正負(fù)樣本比例設(shè)定不是5:5這么簡單,5:5只是一個純理想化模型訓(xùn)練狀態(tài),但實際模型表現(xiàn)不一定最好。
另外grid_search網(wǎng)格調(diào)參方法對于lendingclub數(shù)據(jù)來說,速度太慢。lendingclub超過12萬數(shù)據(jù),grid_search網(wǎng)格調(diào)參會消耗大量時間。
缺失數(shù)據(jù)處理方面,woe分箱會把缺失數(shù)據(jù)分為一箱,不用再單獨用平均數(shù),中位數(shù),或眾數(shù)來填充缺失數(shù)據(jù),這顯得畫蛇添足。很有很多細(xì)節(jié)就不一一點評了。
總之文章整體結(jié)構(gòu)還是不錯,只是機(jī)器學(xué)習(xí)是一門非常專業(yè)學(xué)科,細(xì)分很多領(lǐng)域。歡迎各位同學(xué)訪問我的教學(xué)主頁學(xué)習(xí):http://dwz.date/bwes
文章具體內(nèi)容如下:
1.本項目需解決的問題
????本項目通過利用P2P平臺Lending Club的貸款數(shù)據(jù),進(jìn)行機(jī)器學(xué)習(xí),構(gòu)建貸款違約預(yù)測模型,對新增貸款申請人進(jìn)行預(yù)測是否會違約,從而決定是否放款。
2.建模思路
????以下為本次項目的工作流程。

3.場景解析
? ? 貸款申請人向Lending Club平臺申請貸款時,Lending Club平臺通過線上或線下讓客戶填寫貸款申請表,收集客戶的基本信息,這里包括申請人的年齡、性別、婚姻狀況、學(xué)歷、貸款金額、申請人財產(chǎn)情況等信息,通常來說還會借助第三方平臺如征信機(jī)構(gòu)或FICO等機(jī)構(gòu)的信息。通過這些信息屬性來做線性回歸 ,生成預(yù)測模型,Lending Club平臺可以通過預(yù)測判斷貸款申請是否會違約,從而決定是否向申請人發(fā)放貸款。
1)首先,我們的場景是通過用戶的歷史行為(如歷史數(shù)據(jù)的多維特征和貸款狀態(tài)是否違約)來訓(xùn)練模型,通過這個模型對新增的貸款人“是否具有償還能力,是否具有償債意愿”進(jìn)行分析,預(yù)測貸款申請人是否會發(fā)生違約貸款。這是一個監(jiān)督學(xué)習(xí)的場景,因為已知了特征以及貸款狀態(tài)是否違約(目標(biāo)列),我們判定貸款申請人是否違約是一個二元分類問題,可以通過一個分類算法來處理,這里選用邏輯斯蒂回歸(Logistic Regression)。
2)觀察數(shù)據(jù)集發(fā)現(xiàn)部分?jǐn)?shù)據(jù)是半結(jié)構(gòu)化數(shù)據(jù),需要進(jìn)行特征抽象。
? ? 現(xiàn)對該業(yè)務(wù)場景進(jìn)行總結(jié)如下:
? ? 根據(jù)歷史記錄數(shù)據(jù)學(xué)習(xí)并對貸款是否違約進(jìn)行預(yù)測,監(jiān)督學(xué)習(xí)場景,選擇邏輯斯蒂回歸(Logistic Regression)算法。
數(shù)據(jù)為半結(jié)構(gòu)化數(shù)據(jù),需要進(jìn)行特征抽象。
4.數(shù)據(jù)預(yù)處理(Pre-Processing Data)
? ? ?本次項目數(shù)據(jù)集來源于Lending Club Statistics,具體為2018年第一季Lending Club平臺發(fā)生借貸的業(yè)務(wù)數(shù)據(jù)。
?數(shù)據(jù)預(yù)覽

查看每列屬性缺失值的比例
check_null = data.isnull().sum().sort_values(ascending=False)/float(len(data))?
print(check_null[check_null > 0.2]) # 查看缺失比例大于20%的屬性。

? ? 從上面信息可以發(fā)現(xiàn),本次數(shù)據(jù)集缺失值較多的屬性對我們模型預(yù)測意義不大,例如id和member_id以及url等。因此,我們直接刪除這些沒有意義且缺失值較多的屬性。此外,如果缺失值對屬性來說是有意義的,還得細(xì)分缺失值對應(yīng)的屬性是數(shù)值型變量或是分類類型變量。
thresh_count = len(data)*0.4 # 設(shè)定閥值
data = data.dropna(thresh=thresh_count, axis=1) #若某一列數(shù)據(jù)缺失的數(shù)量超過閥值就會被刪除
再將處理后的數(shù)據(jù)轉(zhuǎn)化為csv
data.to_csv('loans_2018q1_ml.csv', index = False)
loans = pd.read_csv('loans_2018q1_ml.csv')?
loans.dtypes.value_counts() # 分類統(tǒng)計數(shù)據(jù)類型

loans.shape
(107866, 103)
同值化處理???
????如果一個變量大部分的觀測都是相同的特征,那么這個特征或者輸入變量就是無法用來區(qū)分目標(biāo)時間。
loans = loans.loc[:,loans.apply(pd.Series.nunique) != 1]
loans.shape
(107866, 96)
缺失值處理——分類變量? ?
objectColumns = loans.select_dtypes(include=["object"]).columns
loans[objectColumns].isnull().sum().sort_values(ascending=False)

loans[objectColumns]
loans['int_rate'] = loans['int_rate'].str.rstrip('%').astype('float')
loans['revol_util'] = loans['revol_util'].str.rstrip('%').astype('float')
objectColumns = loans.select_dtypes(include=["object"]).columns
? 我們可以調(diào)用missingno庫來快速評估數(shù)據(jù)缺失的情況。
msno.matrix(loans[objectColumns]) ?# 缺失值可視化

?從圖中可以直觀看出變量“l(fā)ast_pymnt_d”、“emp_title”、“emp_length”缺失值較多。
????這里我們先用‘unknown’來填充。
objectColumns = loans.select_dtypes(include=["object"]).columns
loans[objectColumns] = loans[objectColumns].fillna("Unknown")
缺失值處理——數(shù)值變量
numColumns = loans.select_dtypes(include=[np.number]).columns
pd.set_option('display.max_columns', len(numColumns))
loans[numColumns].tail()

loans.drop([107864, 107865], inplace =True)
這里使用可sklearn的Preprocessing模塊,參數(shù)strategy選用most_frequent,采用眾數(shù)插補(bǔ)的方法填充缺失值
imr = Imputer(missing_values='NaN', strategy='most_frequent', axis=0)? #? axis=0? 針對列來處理
imr = imr.fit(loans[numColumns])
loans[numColumns] = imr.transform(loans[numColumns])
?這樣缺失值就已經(jīng)處理完。

數(shù)據(jù)過濾
print(objectColumns)

????將以上重復(fù)或?qū)?gòu)建預(yù)測模型沒有意義的屬性進(jìn)行刪除。
drop_list = ['sub_grade', 'emp_title', 'issue_d', 'title', 'zip_code', 'addr_state', 'earliest_cr_line',
? ? ? ?'initial_list_status', 'last_pymnt_d', 'next_pymnt_d', 'last_credit_pull_d', 'disbursement_method']
loans.drop(drop_list, axis=1, inplace=True)
loans.select_dtypes(include = ['object']).shape
(107866, 8)
5.特征工程(Feature Engineering)
特征衍生
? ? Lending Club平臺中,"installment"代表貸款每月分期的金額,我們將'annual_inc'除以12個月獲得貸款申請人的月收入金額,然后再把"installment"(月負(fù)債)與('annual_inc'/12)(月收入)相除生成新的特征'installment_feat',新特征'installment_feat'代表客戶每月還款支出占月收入的比,'installment_feat'的值越大,意味著貸款人的償債壓力越大,違約的可能性越大。
loans['installment_feat'] = loans['installment'] / ((loans['annual_inc']+1) / 12)
特征抽象(Feature Abstraction)
def coding(col, codeDict):
? ? colCoded = pd.Series(col, copy=True)
? ? for key, value in codeDict.items():
? ? ? ? colCoded.replace(key, value, inplace=True)
? ? return colCoded
#把貸款狀態(tài)LoanStatus編碼為違約=1, 正常=0:
loans["loan_status"] = coding(loans["loan_status"], {'Current':0,'Issued':0,'Fully Paid':0,'In Grace Period':1,'Late (31-120 days)':1,'Late (16-30 days)':1,'Charged Off':1})
print( '\nAfter Coding:')
pd.value_counts(loans["loan_status"])

貸款狀態(tài)可視化

loans.select_dtypes(include=["object"]).head()

?首先,我們對變量“emp_length”、"grade"進(jìn)行特征抽象化。
# 有序特征的映射
mapping_dict = {
? ? "emp_length": {
? ? ? ? "10+ years": 10,
? ? ? ? "9 years": 9,
? ? ? ? "8 years": 8,
? ? ? ? "7 years": 7,
? ? ? ? "6 years": 6,
? ? ? ? "5 years": 5,
? ? ? ? "4 years": 4,
? ? ? ? "3 years": 3,
? ? ? ? "2 years": 2,
? ? ? ? "1 year": 1,
? ? ? ? "< 1 year": 0,
? ? ? ? "Unknown": 0
? ? },
? ? "grade":{
? ? ? ? "A": 1,
? ? ? ? "B": 2,
? ? ? ? "C": 3,
? ? ? ? "D": 4,
? ? ? ? "E": 5,
? ? ? ? "F": 6,
? ? ? ? "G": 7
? ? }
}
loans = loans.replace(mapping_dict)?
loans[['emp_length','grade']].head()?

?再對剩余特征進(jìn)行One-hot編碼。
n_columns = ["home_ownership", "verification_status", "application_type","purpose", "term"]?
dummy_df = pd.get_dummies(loans[n_columns]) # 用get_dummies進(jìn)行one hot編碼
loans = pd.concat([loans, dummy_df], axis=1) #當(dāng)axis = 1的時候,concat就是行對齊,然后將不同列名稱的兩張表合并
?再清除掉原來的屬性。
loans = loans.drop(n_columns, axis=1)
loans.info()

? ? 這樣,就已經(jīng)將所有類型為object的變量作了轉(zhuǎn)化。
col = loans.select_dtypes(include=['int64','float64']).columns
col = col.drop('loan_status') #剔除目標(biāo)變量
loans_ml_df = loans? # 復(fù)制數(shù)據(jù)至變量loans_ml_df
特征縮放(Feature Scaling)
????我們采用的是標(biāo)準(zhǔn)化的方法,調(diào)用scikit-learn模塊preprocessing的子模塊StandardScaler。
sc =StandardScaler()? # 初始化縮放器
loans_ml_df[col] =sc.fit_transform(loans_ml_df[col])? #對數(shù)據(jù)進(jìn)行標(biāo)準(zhǔn)化
特征選擇(Feature Selecting)
????目的:首先,優(yōu)先選擇與目標(biāo)相關(guān)性較高的特征;其次,去除不相關(guān)特征可以降低學(xué)習(xí)的難度。
#構(gòu)建X特征變量和Y目標(biāo)變量
x_feature = list(loans_ml_df.columns)
x_feature.remove('loan_status')
x_val = loans_ml_df[x_feature]
y_val = loans_ml_df['loan_status']
len(x_feature) # 查看初始特征集合的數(shù)量
103
????首先,選出與目標(biāo)變量相關(guān)性較高的特征。這里采用的是Wrapper方法,通過暴力的遞歸特征消除 (Recursive Feature Elimination)方法篩選30個與目標(biāo)變量相關(guān)性最強(qiáng)的特征,逐步剔除特征從而達(dá)到首次降維,自變量從103個降到30個。
# 建立邏輯回歸分類器
model = LogisticRegression()
# 建立遞歸特征消除篩選器
rfe = RFE(model, 30) #通過遞歸選擇特征,選擇30個特征
rfe = rfe.fit(x_val, y_val)
# 打印篩選結(jié)果
print(rfe.n_features_)
print(rfe.estimator_ )
print(rfe.support_)
print(rfe.ranking_) #ranking 為 1代表被選中,其他則未被代表未被選中

col_filter = x_val.columns[rfe.support_] #通過布爾值篩選首次降維后的變量
col_filter

Filter
?在第一次降維的基礎(chǔ)上,通過皮爾森相關(guān)性圖譜找出冗余特征并將其剔除;同時,可以通過相關(guān)性圖譜進(jìn)一步引導(dǎo)我們選擇特征的方向。
colormap = plt.cm.viridis
plt.figure(figsize=(12,12))
plt.title('Pearson Correlation of Features', y=1.05, size=15)
sns.heatmap(loans_ml_df[col_filter].corr(),linewidths=0.1,vmax=1.0, square=True, cmap=colormap, linecolor='white', annot=True)

drop_col = ['funded_amnt', 'funded_amnt_inv', 'out_prncp', 'out_prncp_inv', 'total_pymnt_inv', 'total_rec_prncp',
? ? ? ? ? ?'num_actv_rev_tl', 'num_rev_tl_bal_gt_0', 'home_ownership_RENT', 'application_type_Joint App',
? ? ? ? ? ? 'term_ 60 months', 'purpose_debt_consolidation', 'verification_status_Source Verified', 'home_ownership_OWN',?
? ? ? ? ? ? ?'verification_status_Verified',]
col_new = col_filter.drop(drop_col) #剔除冗余特征

len(col_new) ?# 特征子集包含的變量從30個降維至15個。
15
Embedded
? ? 下面需要對特征的權(quán)重有一個正確的評判和排序,可以通過特征重要性排序來挖掘哪些變量是比較重要的,降低學(xué)習(xí)難度,最終達(dá)到優(yōu)化模型計算的目的。這里,我們采用的是隨機(jī)森林算法判定特征的重要性,工程實現(xiàn)方式采用scikit-learn的featureimportances 的方法。
names = loans_ml_df[col_new].columns
clf=RandomForestClassifier(n_estimators=10,random_state=123) #構(gòu)建分類隨機(jī)森林分類器
clf.fit(x_val[col_new], y_val) #對自變量和因變量進(jìn)行擬合
for feature in zip(names, clf.feature_importances_):
? ? print(feature)

plt.style.use('ggplot')
?
## feature importances 可視化##
importances = clf.feature_importances_
feat_names = names
indices = np.argsort(importances)[::-1]
fig = plt.figure(figsize=(20,6))
plt.title("Feature importances by RandomTreeClassifier")
plt.bar(range(len(indices)), importances[indices], color='lightblue',? align="center")
plt.step(range(len(indices)), np.cumsum(importances[indices]), where='mid', label='Cumulative')
plt.xticks(range(len(indices)), feat_names[indices], rotation='vertical',fontsize=14)
plt.xlim([-1, len(indices)])
plt.show()
?
# 下圖是根據(jù)特征在特征子集中的相對重要性繪制的排序圖,這些特征經(jīng)過特征縮放后,其特征重要性的和為1.0。
# 由下圖我們可以得出的結(jié)論:基于決策樹的計算,特征子集上最具判別效果的特征是“total_pymnt”。

6.模型訓(xùn)練
處理樣本不均衡
? ? 前面已提到,目標(biāo)變量“l(fā)oans_status”正常和違約兩種類別的數(shù)量差別較大,會對模型學(xué)習(xí)造成困擾。我們采用過采樣的方法來處理樣本不均衡問題,具體操作使用的是SMOTE(Synthetic Minority Oversampling Technique),SMOET的基本原理是:采樣最鄰近算法,計算出每個少數(shù)類樣本的K個近鄰,從K個近鄰中隨機(jī)挑選N個樣本進(jìn)行隨機(jī)線性插值,構(gòu)造新的少數(shù)樣本,同時將新樣本與原數(shù)據(jù)合成,產(chǎn)生新的訓(xùn)練集。
# 構(gòu)建自變量和因變量
X = loans_ml_df[col_new]
y = loans_ml_df["loan_status"]
?
n_sample = y.shape[0]
n_pos_sample = y[y == 0].shape[0]
n_neg_sample = y[y == 1].shape[0]
print('樣本個數(shù):{}; 正樣本占{:.2%}; 負(fù)樣本占{:.2%}'.format(n_sample,
? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ?n_pos_sample / n_sample,
? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ?n_neg_sample / n_sample))
print('特征維數(shù):', X.shape[1])

# 處理不平衡數(shù)據(jù)
sm = SMOTE(random_state=42)? ? # 處理過采樣的方法
X, y = sm.fit_sample(X, y)
print('通過SMOTE方法平衡正負(fù)樣本后')
n_sample = y.shape[0]
n_pos_sample = y[y == 0].shape[0]
n_neg_sample = y[y == 1].shape[0]
print('樣本個數(shù):{}; 正樣本占{:.2%}; 負(fù)樣本占{:.2%}'.format(n_sample,
? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ?n_pos_sample / n_sample,
? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ?n_neg_sample / n_sample))

構(gòu)建分類器訓(xùn)練
? ? 本次項目我們采用交叉驗證法劃分?jǐn)?shù)據(jù)集,將數(shù)據(jù)劃分為3部分:訓(xùn)練集(training set)、驗證集(validation set)和測試集(test set)。讓模型在訓(xùn)練集進(jìn)行學(xué)習(xí),在驗證集上進(jìn)行參數(shù)調(diào)優(yōu),最后使用測試集數(shù)據(jù)評估模型的性能。
? ? 模型調(diào)優(yōu)我們采用網(wǎng)格搜索調(diào)優(yōu)參數(shù)(grid search),通過構(gòu)建參數(shù)候選集合,然后網(wǎng)格搜索會窮舉各種參數(shù)組合,根據(jù)設(shè)定評定的評分機(jī)制找到最好的那一組設(shè)置。
構(gòu)建分類器訓(xùn)練
? ? 本次項目我們采用交叉驗證法劃分?jǐn)?shù)據(jù)集,將數(shù)據(jù)劃分為3部分:訓(xùn)練集(training set)、驗證集(validation set)和測試集(test set)。讓模型在訓(xùn)練集進(jìn)行學(xué)習(xí),在驗證集上進(jìn)行參數(shù)調(diào)優(yōu),最后使用測試集數(shù)據(jù)評估模型的性能。
? ? 模型調(diào)優(yōu)我們采用網(wǎng)格搜索調(diào)優(yōu)參數(shù)(grid search),通過構(gòu)建參數(shù)候選集合,然后網(wǎng)格搜索會窮舉各種參數(shù)組合,根據(jù)設(shè)定評定的評分機(jī)制找到最好的那一組設(shè)置。
X_train, X_test, y_train, y_test = train_test_split(X, y, test_size = 0.3, random_state = 0) # random_state = 0 每次切分的數(shù)據(jù)都一樣
# 構(gòu)建參數(shù)組合
param_grid = {'C': [0.01,0.1, 1, 10, 100, 1000,],
? ? ? ? ? ? ? ? ? ? ? ? ? ? 'penalty': [ 'l1', 'l2']}
# C:Inverse of regularization strength; must be a positive float. Like in support vector machines, smaller values specify stronger regularization.
grid_search = GridSearchCV(LogisticRegression(),? param_grid, cv=10) # 確定模型LogisticRegression,和參數(shù)組合param_grid ,cv指定10折
grid_search.fit(X_train, y_train) # 使用訓(xùn)練集學(xué)習(xí)算法

print("Best parameters: {}".format(grid_search.best_params_))
print("Best cross-validation score: {:.5f}".format(grid_search.best_score_))

print("Best estimator:\n{}".format(grid_search.best_estimator_)) # grid_search.best_estimator_ 返回模型以及他的所有參數(shù)(包含最優(yōu)參數(shù))

現(xiàn)在使用經(jīng)過訓(xùn)練和調(diào)優(yōu)后的模型在測試集上測試。
y_pred = grid_search.predict(X_test)
print("Test set accuracy score: {:.5f}".format(accuracy_score(y_test, y_pred,)))
Test set accuracy score: 0.66064
print(classification_report(y_test, y_pred))

roc_auc = roc_auc_score(y_test, y_pred)
print("Area under the ROC curve : %f" % roc_auc)
Area under the ROC curve : 0.660654

總結(jié)
????最后結(jié)果不太理想,實際工作中還要做特征分箱處理,計算IV值和WOE編碼也是需要的。模型評估方面也有不足,這為以后的工作提供了些經(jīng)驗。
up主教學(xué)主頁 ?
https://study.163.com/provider/400000000398149/index.htm?share=2&shareId=400000000398149
