ML - 貸款使用者逾期情況分析5 - 特徵工程2(特徵選擇)
文章目錄
特徵選擇 (判定貸款使用者是否逾期)
給定金融資料,預測貸款使用者是否會逾期。
(status是標籤:0表示未逾期,1表示逾期。)
Task8(特徵工程2 - 特徵選擇) - 分別用IV值和隨機森林挑選特徵,再構建模型,進行模型評估
1. IV值進行特徵選擇
1.1 基本介紹
在二分類問題中,IV值(Information Value)主要用來對輸入變數進行編碼和預測能力評估
。
IV 值的取值範圍是[0, ),其大小表示該變數預測能力的強弱。通常認為:
IV值 | 預測能力 |
---|---|
<0.02 | 無用 |
0.02—0.1 | 弱預測 |
0.1—0.3 | 中等預測 |
0.3—0.5 | 強預測 |
>0.5 | 可疑 |
一般選擇中等和強預測能力的變數用於模型開發,一些學派也只提倡具有中等IV值的變數來進行模型開發。
1.2 計算公式
1)WOE
WOE(weight of evidence,證據權重),是對原始變數的一種編碼形式。
對一個變數進行WOE編碼,首先要把變數進行分組處理(分箱或離散化)。常用離散化的方法有等寬分組,等高分組,或利用決策樹來分組。
分組後,對於第 i 組,WOE的計算公式見下式:
它衡量了"當前分組中響應使用者/所有響應使用者"和"當前分組中未響應使用者/所有未響應使用者"的差異。
2)IV值
IV值的計算以WOE為基礎,相當於是WOE值的一個加權求和。
假設變數分了n個組。對第i組,計算公式如下:
計算了變數各個組的 IV 值之後,我們就可以計算整個變數的 IV 值:
IV值主要用於特徵選擇,如果想對變數的預測能力進行排序,可以按 IV 值從高到低篩選。
IV在WOE前多乘了一個因子:
1)保證了IV的值不是負數;
2)很好的考慮了分組中樣本佔整體的比例(比例越低,這個分組對變數整體預測能力的貢獻越低)。
2. 隨機森林進行特徵選擇
隨機森林提供了兩種特徵選擇的方法:mean decrease impurity和mean decrease accuracy。
2.1 平均不純度減少 mean decrease impurity
利用不純度可以確定節點(最優條件). 對於分類問題,常採用基尼不純度/資訊增益;對於迴歸問題,常採用方差/最小二乘擬合。
訓練決策樹時,可以計算每個特徵減少了多少樹的不純度。對於一個決策樹森林來說,可以算出每個特徵平均減少了多少不純度,並把它平均減少的不純度作為特徵選擇的值。
【缺點】
1)該方法存在偏向, 對具有更多類別的變數更有利;
2)label存在多個關聯特徵(任意一個都可以作為優秀特徵), 則一旦某個特徵被選擇, 其他特徵的重要性會急劇降低。這會造成誤解:錯誤的認為先被選中的特徵是很重要的,而其餘的特徵是不重要的。
2.2 平均精確率減少 Mean decrease accuracy
直接度量每個特徵對模型精確率的影響。
打亂每個特徵的特徵值順序,並且度量順序變動對模型的精確率的影響。
對於不重要的變數來說,打亂順序對模型的精確率影響不會太大,但是對於重要的變數來說,打亂順序就會降低模型的精確率。
3. 程式碼
import pickle
import pandas as pd
from sklearn.model_selection import train_test_split
# 匯入資料
data = pd.read_csv('data.csv')
data.drop_duplicates(inplace=True)
# 載入特徵
with open('feature.pkl', 'rb') as f:
X = pickle.load(f)
# 提取標籤
y = data.status
# 劃分訓練集測試集
X_train, X_test, y_train, y_test = train_test_split(X, y, test_size=0.3,random_state=2333)
# 效能評估
from sklearn.metrics import accuracy_score, roc_auc_score
def model_metrics(clf, X_train, X_test, y_train, y_test):
# 預測
y_train_pred = clf.predict(X_train)
y_test_pred = clf.predict(X_test)
y_train_proba = clf.predict_proba(X_train)[:,1]
y_test_proba = clf.predict_proba(X_test)[:,1]
# 準確率
print('[準確率]', end = ' ')
print('訓練集:', '%.4f'%accuracy_score(y_train, y_train_pred), end = ' ')
print('測試集:', '%.4f'%accuracy_score(y_test, y_test_pred))
# auc取值:用roc_auc_score或auc
print('[auc值]', end = ' ')
print('訓練集:', '%.4f'%roc_auc_score(y_train, y_train_proba), end = ' ')
print('測試集:', '%.4f'%roc_auc_score(y_test, y_test_proba))
3.1 IV值進行特徵選擇
stats.scoreatpercentile(x, 50) # 得到x在50%處的數值
np.in1d(B,A) # 在序列B中尋找與序列A相同的值,並返回一邏輯值(True,False)
處理上述特徵時, 遇到了IV的極端情況, 響應數為0或未響應數為0。
為簡單起見, 我們在程式碼中對極端值進行平滑處理。
import math
import numpy as np
from scipy import stats
from sklearn.utils.multiclass import type_of_target
def woe(X, y, event=1):
res_woe = []
iv_dict = {}
for feature in X.columns:
x = X[feature].values
# 1) 連續特徵離散化
if type_of_target(x) == 'continuous':
x = discrete(x)
# 2) 計算該特徵的woe和iv
# woe_dict, iv = woe_single_x(x, y, feature, event)
woe_dict, iv = woe_single_x(x, y, feature, event)
iv_dict[feature] = iv
res_woe.append(woe_dict)
return iv_dict
def discrete(x):
# 使用5等分離散化特徵
res = np.zeros(x.shape)
for i in range(5):
point1 = stats.scoreatpercentile(x, i * 20)
point2 = stats.scoreatpercentile(x, (i + 1) * 20)
x1 = x[np.where((x >= point1) & (x <= point2))]
mask = np.in1d(x, x1)
res[mask] = i + 1 # 將[i, i+1]塊內的值標記成i+1
return res
def woe_single_x(x, y, feature,event = 1):
# event代表預測正例的標籤
event_total = sum(y == event)
non_event_total = y.shape[-1] - event_total
iv = 0
woe_dict = {}
for x1 in set(x): # 遍歷各個塊
y1 = y.reindex(np.where(x == x1)[0])
event_count = sum(y1 == event)
non_event_count = y1.shape[-1] - event_count
rate_event = event_count / event_total
rate_non_event = non_event_count / non_event_total
if rate_event == 0:
rate_event = 0.0001
# woei = -20
elif rate_non_event == 0:
rate_non_event = 0.0001
# woei = 20
woei = math.log(rate_event / rate_non_event)
woe_dict[x1] = woei
iv += (rate_event - rate_non_event) * woei
return woe_dict, iv
import warnings
warnings.filterwarnings("ignore")
iv_dict = woe(X_train, y_train)
iv = sorted(iv_dict.items(), key = lambda x:x[1],reverse = True)
iv
輸出
[(‘historical_trans_amount’, 2.6975301004625365),
(‘trans_amount_3_month’, 2.5633548887586746),
(‘pawns_auctions_trusts_consume_last_6_month’, 2.343990314630991),
(‘repayment_capability’, 2.31685232254565),
(‘first_transaction_day’, 2.10946672748192),
(‘abs’, 2.048054369415617),
(‘consfin_avg_limit’, 1.8005797778063934),
(‘consume_mini_time_last_1_month’, 1.4570522032774857),
…
3.2 隨機森林挑選特徵
首先網格調參,求得模型引數。
import warnings
warnings.filterwarnings("ignore")
from sklearn.model_selection import GridSearchCV
from sklearn.ensemble import RandomForestClassifier
# 觀察預設引數的效能
rf0 = RandomForestClassifier(oob_score=True, random_state=2333)
rf0.fit(X_train, y_train)
print('袋外分數:', rf0.oob_score_)
model_metrics(rf0, X_train, X_test, y_train, y_test)
輸出
袋外分數: 0.7342951608055305
[準確率] 訓練集: 0.9805 測試集: 0.7744
[auc值] 訓練集: 0.9996 測試集: 0.7289
# 網格法調參, 步驟省略...
"""
param_test = {'n_estimators':range(20,200,20)}
# param_test = {'max_depth':range(3,14,2), 'min_samples_split':range(50,201,20)}
# param_test = {'min_samples_split':range(10,100,20), 'min_samples_leaf':range(10,60,10)}
# param_test = {'max_features':range(3,17,2)}
gsearch = GridSearchCV(estimator = RandomForestClassifier(n_estimators=120, max_depth=9, min_samples_split=50,
min_samples_leaf=20, max_features = 9,random_state=2333),
param_grid = param_test, scoring='roc_auc', cv=5)
gsearch.fit(X_train, y_train)
# gsearch.grid_scores_,
gsearch.best_params_, gsearch.best_score_
"""
最終引數及效能
rf = RandomForestClassifier(n_estimators=120, max_depth=9, min_samples_split=50,
min_samples_leaf=20, max_features = 9,oob_score=True, random_state=2333)
rf.fit(X_train, y_train)
print('袋外分數:', rf.oob_score_)
model_metrics(rf, X_train, X_test, y_train, y_test)
輸出
袋外分數: 0.7844905320108205
[準確率] 訓練集: 0.8115 測試集: 0.7954
[auc值] 訓練集: 0.8946 測試集: 0.7914
3.2.1 平均不純度減少 mean decrease impurity
對於每顆樹,按照impurity(此處是gini指數 )給特徵排序,然後整個森林取平均
rf.fit(X_train, y_train)
feature_impotance1 = sorted(zip(map(lambda x: '%.4f'%x, rf.feature_importances_), list(X_train.columns)), reverse=True)
feature_impotance1[:10]
輸出
[(‘0.1333’, ‘trans_fail_top_count_enum_last_1_month’),
(‘0.0818’, ‘loans_score’),
(‘0.0784’, ‘history_fail_fee’),
(‘0.0623’, ‘apply_score’),
(‘0.0580’, ‘latest_one_month_fail’),
(‘0.0424’, ‘loans_overdue_count’),
(‘0.0307’, ‘trans_fail_top_count_enum_last_12_month’),
(‘0.0237’, ‘trans_fail_top_count_enum_last_6_month’),
(‘0.0194’, ‘trans_day_last_12_month’),
(‘0.0184’, ‘max_cumulative_consume_later_1_month’)]
3.2.2 平均精確率減少 Mean decrease accuracy
打亂每個特徵的特徵值順序,並且度量順序變動對模型的精確率的影響。(也可以measure每個特徵加躁,看對結果的準確率的影響。)
import numpy as np
from collections import defaultdict
from sklearn.model_selection import cross_val_score, ShuffleSplit
scores = defaultdict(list)
rs = ShuffleSplit(n_splits=5, test_size=0.3, random_state=0)
for train_idx, test_idx in rs.split(X_train):
x_train, x_test = X_train.values[train_idx], X_train.values[test_idx]
Y_train, Y_test = y_train.values[train_idx], y_train.values[test_idx]
r = rf.fit(x_train, Y_train)
acc = accuracy_score(Y_test, rf.predict(x_test))
for i in range(x_train.shape[1]):
X_t = x_test.copy()
np.random.shuffle(X_t[:, i])
shuff_acc = accuracy_score(Y_test, rf.predict(X_t))
scores[X_train.columns[i]].append((acc - shuff_acc) / acc)
feature_impotance2=sorted([('%.4f'%np.mean(score), feat) for feat, score in scores.items()], reverse=True)
feature_impotance2[:10<