特征工程
項目地址:https://github.com/datawhalechina/team-learning-data-mining/tree/master/FinancialRiskControl
3.1 學習目標
- 學習特征預處理、缺失值、異常值處理、數據分桶等特征處理方法
- 學習特征交互、編碼、選擇的相應方法
3.2 內容介紹
- 數據預處理
- 缺失值的填充
- 時間格式處理
- 對象類型特征轉換到數值
- 異常值處理
- 基於3segama原則
- 基於箱型圖
- 數據分箱
- 固定寬度分箱
- 分位數分箱
- 離散數值型數據分箱
- 連續數值型數據分箱
- 卡方分箱(選做作業)
- 特征交互
- 特征和特征之間組合
- 特征和特征之間衍生
- 其他特征衍生的嘗試(選做作業)
- 特征編碼
- one-hot編碼
- label-encode編碼
- 特征選擇
- 1 Filter
- 2 Wrapper (RFE)
- 3 Embedded
3.3 代碼示例
3.3.1 導入包並讀取數據
import pandas as pd import numpy as np import matplotlib.pyplot as plt import seaborn as sns import datetime from tqdm import tqdm #進度條 from sklearn.preprocessing import LabelEncoder #標准化標簽 from sklearn.feature_selection import SelectKBest from sklearn.feature_selection import chi2 #方差特征選擇發 from sklearn.preprocessing import MinMaxScaler #最大最小歸一化 import xgboost as xgb import lightgbm as lgb from catboost import CatBoostRegressor import warnings from sklearn.model_selection import StratifiedKFold, KFold from sklearn.metrics import accuracy_score, f1_score, roc_auc_score, log_loss warnings.filterwarnings('ignore') data_train = pd.read_csv('F:/python/阿里雲金融風控-貸款違約預測/train.csv') data_test_a = pd.read_csv('F:/python/阿里雲金融風控-貸款違約預測/testA.csv')
3.3.2特征預處理
- 數據EDA部分我們已經對數據的大概和某些特征分布有了了解,數據預處理部分一般我們要處理一些EDA階段分析出來的問題,這里介紹了數據缺失值的填充,時間格式特征的轉化處理,某些對象類別特征的處理。
首先我們查找出數據中的對象特征和數值特征
numerical_fea = list(data_train.select_dtypes(exclude=['object']).columns) category_fea = list(filter(lambda x: x not in numerical_fea,list(data_train.columns))) label = 'isDefault' numerical_fea.remove(label)
在比賽中數據預處理是必不可少的一部分,對於缺失值的填充往往會影響比賽的結果,在比賽中不妨嘗試多種填充然后比較結果選擇結果最優的一種; 比賽數據相比真實場景的數據相對要“干凈”一些,但是還是會有一定的“臟”數據存在,清洗一些異常值往往會獲得意想不到的效果。
缺失值填充
-
把所有缺失值替換為指定的值0
data_train = data_train.fillna(0)
-
向用缺失值上面的值替換缺失值
data_train = data_train.fillna(axis=0,method='ffill')
-
縱向用缺失值下面的值替換缺失值,且設置最多只填充兩個連續的缺失值
data_train = data_train.fillna(axis=0,method='bfill',limit=2)
#查看缺失值情況 data_train.isnull().sum()
id 0
loanAmnt 0
term 0
interestRate 0
installment 0
grade 0
subGrade 0
employmentTitle 1
employmentLength 46799
homeOwnership 0
annualIncome 0
verificationStatus 0
issueDate 0
isDefault 0
purpose 0
postCode 1
regionCode 0
dti 239
delinquency_2years 0
ficoRangeLow 0
ficoRangeHigh 0
openAcc 0
pubRec 0
pubRecBankruptcies 405
revolBal 0
revolUtil 531
totalAcc 0
initialListStatus 0
applicationType 0
earliesCreditLine 0
title 1
policyCode 0
n0 40270
n1 40270
n2 40270
n2.1 40270
n4 33239
n5 40270
n6 40270
n7 40270
n8 40271
n9 40270
n10 33239
n11 69752
n12 40270
n13 40270
n14 40270
dtype: int64
#按照平均數填充數值型特征 data_train[numerical_fea] = data_train[numerical_fea].fillna(data_train[numerical_fea].median()) data_test_a[numerical_fea] = data_test_a[numerical_fea].fillna(data_train[numerical_fea].median()) #按照眾數填充類別型特征 data_train[category_fea] = data_train[category_fea].fillna(data_train[category_fea].mode()) data_test_a[category_fea] = data_test_a[category_fea].fillna(data_train[category_fea].mode())
- category_fea:對象型類別特征需要進行預處理,其中['issueDate']為時間格式特征
1、mean翻譯:均值,通常指算術平均,也可以是幾何平均。
敘述:把一組序列中的所有值加zhi起來,然后除以總個數,就是均值。
2、median翻譯:中間值。
敘述:把一組序列按照升序的方式排列,然后取中間的那個值,就是中間值。
3、average翻譯:均值,通常指算術平均。
處理完還有employmentLength 46799 是空值,這個需要特別處理
時間格式處理
#轉化成時間格式 for data in [data_train, data_test_a]: data['issueDate'] = pd.to_datetime(data['issueDate'],format='%Y-%m-%d') startdate = datetime.datetime.strptime('2007-06-01', '%Y-%m-%d') #構造時間特征 data['issueDateDT'] = data['issueDate'].apply(lambda x: x-startdate).dt.days
data_train['employmentLength'].value_counts(dropna=False).sort_index()
1 year 52489
10+ years 262753
2 years 72358
3 years 64152
4 years 47985
5 years 50102
6 years 37254
7 years 35407
8 years 36192
9 years 30272
< 1 year 64237
NaN 46799
Name: employmentLength, dtype: int64
對象類型特征轉換到數值
def employmentLength_to_int(s): if pd.isnull(s): return s else: return np.int8(s.split()[0]) for data in [data_train, data_test_a]: data['employmentLength'].replace(to_replace='10+ years', value='10 years', inplace=True) data['employmentLength'].replace('< 1 year', '0 years', inplace=True) data['employmentLength'] = data['employmentLength'].apply(employmentLength_to_int) data['employmentLength'].value_counts(dropna=False).sort_index() #注意這個是測試集的數據
0.0 15989
1.0 13182
2.0 18207
3.0 16011
4.0 11833
5.0 12543
6.0 9328
7.0 8823
8.0 8976
9.0 7594
10.0 65772
NaN 11742
Name: employmentLength, dtype: int64
- 對earliesCreditLine進行預處理
data_train['earliesCreditLine'].sample(5)
519915 Sep-2002
564368 Dec-1996
768209 May-2004
453092 Nov-1995
763866 Sep-2000
Name: earliesCreditLine, dtype: object
for data in [data_train, data_test_a]: data['earliesCreditLine'] = data['earliesCreditLine'].apply(lambda s: int(s[-4:])) #只留下年份
類別特征處理
# 部分類別特征 cate_features = ['grade', 'subGrade', 'employmentTitle', 'homeOwnership', 'verificationStatus', 'purpose', 'postCode', 'regionCode', \ 'applicationType', 'initialListStatus', 'title', 'policyCode'] for f in cate_features: print(f, '類型數:', data[f].nunique())
grade 類型數: 7
subGrade 類型數: 35
employmentTitle 類型數: 79282
homeOwnership 類型數: 6
verificationStatus 類型數: 3
purpose 類型數: 14
postCode 類型數: 889
regionCode 類型數: 51
applicationType 類型數: 2
initialListStatus 類型數: 2
title 類型數: 12058
policyCode 類型數: 1
像等級這種類別特征,是有優先級的可以labelencode或者自映射
for data in [data_train, data_test_a]: data['grade'] = data['grade'].map({'A':1,'B':2,'C':3,'D':4,'E':5,'F':6,'G':7}) #map,字典形式映射
# 類型數在2之上,又不是高維稀疏的,且純分類特征 for data in [data_train, data_test_a]: data = pd.get_dummies(data, columns=['subGrade', 'homeOwnership', 'verificationStatus', 'purpose', 'regionCode'], drop_first=True)
3.3.3 異常值處理
- 當你發現異常值后,一定要先分清是什么原因導致的異常值,然后再考慮如何處理。首先,如果這一異常值並不代表一種規律性的,而是極其偶然的現象,或者說你並不想研究這種偶然的現象,這時可以將其刪除。其次,如果異常值存在且代表了一種真實存在的現象,那就不能隨便刪除。在現有的欺詐場景中很多時候欺詐數據本身相對於正常數據勒說就是異常的,我們要把這些異常點納入,重新擬合模型,研究其規律。能用監督的用監督模型,不能用的還可以考慮用異常檢測的算法來做。
- 注意test的數據不能刪。
檢測異常的方法一:均方差
在統計學中,如果一個數據分布近似正態,那么大約 68% 的數據值會在均值的一個標准差范圍內,大約 95% 會在兩個標准差范圍內,大約 99.7% 會在三個標准差范圍內。
def find_outliers_by_3segama(data,fea): ''' fea是特征名稱 主要目的是標記異常值和正常值 ''' data_std = np.std(data[fea]) #標准差 data_mean = np.mean(data[fea]) #均值 outliers_cut_off = data_std * 3 #三個標准差范圍內 lower_rule = data_mean - outliers_cut_off upper_rule = data_mean + outliers_cut_off data[fea+'_outliers'] = data[fea].apply(lambda x:str('異常值') if x > upper_rule or x < lower_rule else '正常值') return data
- 得到特征的異常值后可以進一步分析變量異常值和目標變量的關系
data_train = data_train.copy() for fea in numerical_fea: data_train = find_outliers_by_3segama(data_train,fea) print(data_train[fea+'_outliers'].value_counts()) print(data_train.groupby(fea+'_outliers')['isDefault'].sum()) print('*'*10)
正常值 800000 Name: id_outliers, dtype: int64 id_outliers 正常值 159610 Name: isDefault, dtype: int64 ********** 正常值 800000 Name: loanAmnt_outliers, dtype: int64 loanAmnt_outliers 正常值 159610 Name: isDefault, dtype: int64 ********** 正常值 800000 Name: term_outliers, dtype: int64 term_outliers 正常值 159610 Name: isDefault, dtype: int64 ********** 正常值 794259 異常值 5741 Name: interestRate_outliers, dtype: int64 interestRate_outliers 異常值 2916 正常值 156694 Name: isDefault, dtype: int64 ********** 正常值 792046 異常值 7954 Name: installment_outliers, dtype: int64 installment_outliers 異常值 2152 正常值 157458 Name: isDefault, dtype: int64 ********** 正常值 800000 Name: employmentTitle_outliers, dtype: int64 employmentTitle_outliers 正常值 159610 Name: isDefault, dtype: int64 ********** 正常值 799701 異常值 299 Name: homeOwnership_outliers, dtype: int64 homeOwnership_outliers 異常值 62 正常值 159548 Name: isDefault, dtype: int64 ********** 正常值 793973 異常值 6027 Name: annualIncome_outliers, dtype: int64 annualIncome_outliers 異常值 756 正常值 158854 Name: isDefault, dtype: int64 ********** 正常值 800000 Name: verificationStatus_outliers, dtype: int64 verificationStatus_outliers 正常值 159610 Name: isDefault, dtype: int64 ********** 正常值 783003 異常值 16997 Name: purpose_outliers, dtype: int64 purpose_outliers 異常值 3635 正常值 155975 Name: isDefault, dtype: int64 ********** 正常值 798931 異常值 1069 Name: postCode_outliers, dtype: int64 postCode_outliers 異常值 221 正常值 159389 Name: isDefault, dtype: int64 ********** 正常值 799994 異常值 6 Name: regionCode_outliers, dtype: int64 regionCode_outliers 異常值 1 正常值 159609 Name: isDefault, dtype: int64 ********** 正常值 798440 異常值 1560 Name: dti_outliers, dtype: int64 dti_outliers 異常值 466 正常值 159144 Name: isDefault, dtype: int64 ********** 正常值 778245 異常值 21755 Name: delinquency_2years_outliers, dtype: int64 delinquency_2years_outliers 異常值 5089 正常值 154521 Name: isDefault, dtype: int64 ********** 正常值 788261 異常值 11739 Name: ficoRangeLow_outliers, dtype: int64 ficoRangeLow_outliers 異常值 778 正常值 158832 Name: isDefault, dtype: int64 ********** 正常值 788261 異常值 11739 Name: ficoRangeHigh_outliers, dtype: int64 ficoRangeHigh_outliers 異常值 778 正常值 158832 Name: isDefault, dtype: int64 ********** 正常值 790889 異常值 9111 Name: openAcc_outliers, dtype: int64 openAcc_outliers 異常值 2195 正常值 157415 Name: isDefault, dtype: int64 ********** 正常值 792471 異常值 7529 Name: pubRec_outliers, dtype: int64 pubRec_outliers 異常值 1701 正常值 157909 Name: isDefault, dtype: int64 ********** 正常值 794120 異常值 5880 Name: pubRecBankruptcies_outliers, dtype: int64 pubRecBankruptcies_outliers 異常值 1423 正常值 158187 Name: isDefault, dtype: int64 ********** 正常值 790001 異常值 9999 Name: revolBal_outliers, dtype: int64 revolBal_outliers 異常值 1359 正常值 158251 Name: isDefault, dtype: int64 ********** 正常值 799948 異常值 52 Name: revolUtil_outliers, dtype: int64 revolUtil_outliers 異常值 23 正常值 159587 Name: isDefault, dtype: int64 ********** 正常值 791663 異常值 8337 Name: totalAcc_outliers, dtype: int64 totalAcc_outliers 異常值 1668 正常值 157942 Name: isDefault, dtype: int64 ********** 正常值 800000 Name: initialListStatus_outliers, dtype: int64 initialListStatus_outliers 正常值 159610 Name: isDefault, dtype: int64 ********** 正常值 784586 異常值 15414 Name: applicationType_outliers, dtype: int64 applicationType_outliers 異常值 3875 正常值 155735 Name: isDefault, dtype: int64 ********** 正常值 775134 異常值 24866 Name: title_outliers, dtype: int64 title_outliers 異常值 3900 正常值 155710 Name: isDefault, dtype: int64 ********** 正常值 800000 Name: policyCode_outliers, dtype: int64 policyCode_outliers 正常值 159610 Name: isDefault, dtype: int64 ********** 正常值 782773 異常值 17227 Name: n0_outliers, dtype: int64 n0_outliers 異常值 3485 正常值 156125 Name: isDefault, dtype: int64 ********** 正常值 790500 異常值 9500 Name: n1_outliers, dtype: int64 n1_outliers 異常值 2491 正常值 157119 Name: isDefault, dtype: int64 ********** 正常值 789067 異常值 10933 Name: n2_outliers, dtype: int64 n2_outliers 異常值 3205 正常值 156405 Name: isDefault, dtype: int64 ********** 正常值 789067 異常值 10933 Name: n2.1_outliers, dtype: int64 n2.1_outliers 異常值 3205 正常值 156405 Name: isDefault, dtype: int64 ********** 正常值 788660 異常值 11340 Name: n4_outliers, dtype: int64 n4_outliers 異常值 2476 正常值 157134 Name: isDefault, dtype: int64 ********** 正常值 790355 異常值 9645 Name: n5_outliers, dtype: int64 n5_outliers 異常值 1858 正常值 157752 Name: isDefault, dtype: int64 ********** 正常值 786006 異常值 13994 Name: n6_outliers, dtype: int64 n6_outliers 異常值 3182 正常值 156428 Name: isDefault, dtype: int64 ********** 正常值 788430 異常值 11570 Name: n7_outliers, dtype: int64 n7_outliers 異常值 2746 正常值 156864 Name: isDefault, dtype: int64 ********** 正常值 789625 異常值 10375 Name: n8_outliers, dtype: int64 n8_outliers 異常值 2131 正常值 157479 Name: isDefault, dtype: int64 ********** 正常值 786384 異常值 13616 Name: n9_outliers, dtype: int64 n9_outliers 異常值 3953 正常值 155657 Name: isDefault, dtype: int64 ********** 正常值 788979 異常值 11021 Name: n10_outliers, dtype: int64 n10_outliers 異常值 2639 正常值 156971 Name: isDefault, dtype: int64 ********** 正常值 799434 異常值 566 Name: n11_outliers, dtype: int64 n11_outliers 異常值 112 正常值 159498 Name: isDefault, dtype: int64 ********** 正常值 797585 異常值 2415 Name: n12_outliers, dtype: int64 n12_outliers 異常值 545 正常值 159065 Name: isDefault, dtype: int64 ********** 正常值 788907 異常值 11093 Name: n13_outliers, dtype: int64 n13_outliers 異常值 2482 正常值 157128 Name: isDefault, dtype: int64 ********** 正常值 788884 異常值 11116 Name: n14_outliers, dtype: int64 n14_outliers 異常值 3364 正常值 156246 Name: isDefault, dtype: int64 **********
- 例如可以看到異常值在兩個變量上的分布幾乎復合整體的分布,如果異常值都屬於為1的用戶數據里面代表什么呢?
#刪除異常值,暫時不管 for fea in numerical_fea: data_train = data_train[data_train[fea+'_outliers']=='正常值'] data_train = data_train.reset_index(drop=True)
檢測異常的方法二:箱型圖
- 總結一句話:四分位數會將數據分為三個點和四個區間,IQR = Q3 -Q1,下觸須=Q1 − 1.5x IQR,上觸須=Q3 + 1.5x IQR;
3.3.4 數據分桶
-
特征分箱的目的:
- 從模型效果上來看,特征分箱主要是為了降低變量的復雜性,減少變量噪音對模型的影響,提高自變量和因變量的相關度。從而使模型更加穩定。
-
數據分桶的對象:
- 將連續變量離散化
- 將多狀態的離散變量合並成少狀態
-
分箱的原因:
- 數據的特征內的值跨度可能比較大,對有監督和無監督中如k-均值聚類它使用歐氏距離作為相似度函數來測量數據點之間的相似度。都會造成大吃小的影響,其中一種解決方法是對計數值進行區間量化即數據分桶也叫做數據分箱,然后使用量化后的結果。
-
分箱的優點:
- 處理缺失值:當數據源可能存在缺失值,此時可以把null單獨作為一個分箱。
- 處理異常值:當數據中存在離群點時,可以把其通過分箱離散化處理,從而提高變量的魯棒性(抗干擾能力)。例如,age若出現200這種異常值,可分入“age > 60”這個分箱里,排除影響。
- 業務解釋性:我們習慣於線性判斷變量的作用,當x越來越大,y就越來越大。但實際x與y之間經常存在着非線性關系,此時可經過WOE變換。
-
特別要注意一下分箱的基本原則:
- (1)最小分箱占比不低於5%
- (2)箱內不能全部是好客戶
- (3)連續箱單調
- 固定寬度分箱
當數值橫跨多個數量級時,最好按照 10 的冪(或任何常數的冪)來進行分組:09、1099、100999、10009999,等等。固定寬度分箱非常容易計算,但如果計數值中有比較大的缺口,就會產生很多沒有任何數據的空箱子。
# 通過除法映射到間隔均勻的分箱中,每個分箱的取值范圍都是loanAmnt/1000 data['loanAmnt_bin1'] = np.floor_divide(data['loanAmnt'], 1000) ## 通過對數函數映射到指數寬度分箱 data['loanAmnt_bin2'] = np.floor(np.log10(data['loanAmnt']))
- 分位數分箱
data['loanAmnt_bin3'] = pd.qcut(data['loanAmnt'], 10, labels=False)
- 卡方分箱及其他分箱方法的嘗試
- 這一部分屬於進階部分,學有余力的同學可以自行搜索嘗試
3.3.5 特征交互
- 交互特征的構造非常簡單,使用起來卻代價不菲。如果線性模型中包含有交互特征對,那它的訓練時間和評分時間就會從 O(n) 增加到 O(n2),其中 n 是單一特征的數量。
for col in ['grade', 'subGrade']: temp_dict = data_train.groupby([col])['isDefault'].agg(['mean']).reset_index().rename(columns={'mean': col + '_target_mean'}) temp_dict.index = temp_dict[col].values temp_dict = temp_dict[col + '_target_mean'].to_dict() data_train[col + '_target_mean'] = data_train[col].map(temp_dict) data_test_a[col + '_target_mean'] = data_test_a[col].map(temp_dict) # 其他衍生變量 mean 和 std for df in [data_train, data_test_a]: for item in ['n0','n1','n2','n2.1','n4','n5','n6','n7','n8','n9','n10','n11','n12','n13','n14']: df['grade_to_mean_' + item] = df['grade'] / df.groupby([item])['grade'].transform('mean') df['grade_to_std_' + item] = df['grade'] / df.groupby([item])['grade'].transform('std')
這里給出一些特征交互的思路,但特征和特征間的交互衍生出新的特征還遠遠不止於此,拋磚引玉,希望大家多多探索。請學習者嘗試其他的特征交互方法。
3.3.6 特征編碼
labelEncode 直接放入樹模型中
#label-encode:subGrade,postCode,title # 高維類別特征需要進行轉換 for col in tqdm(['employmentTitle', 'postCode', 'title','subGrade']): le = LabelEncoder() le.fit(list(data_train[col].astype(str).values) + list(data_test_a[col].astype(str).values)) data_train[col] = le.transform(list(data_train[col].astype(str).values)) data_test_a[col] = le.transform(list(data_test_a[col].astype(str).values)) print('Label Encoding 完成')
邏輯回歸等模型要單獨增加的特征工程
- 對特征做歸一化,去除相關性高的特征
- 歸一化目的是讓訓練過程更好更快的收斂,避免特征大吃小的問題
- 去除相關性是增加模型的可解釋性,加快預測過程。
# 舉例歸一化過程 #偽代碼 for fea in [要歸一化的特征列表]: data[fea] = ((data[fea] - np.min(data[fea])) / (np.max(data[fea]) - np.min(data[fea])))
3.3.7 特征選擇
- 特征選擇技術可以精簡掉無用的特征,以降低最終模型的復雜性,它的最終目的是得到一個簡約模型,在不降低預測准確率或對預測准確率影響不大的情況下提高計算速度。特征選擇不是為了減少訓練時間(實際上,一些技術會增加總體訓練時間),而是為了減少模型評分時間。
特征選擇的方法:
- 1 Filter
- 方差選擇法
- 相關系數法(pearson 相關系數)
- 卡方檢驗
- 互信息法
- 2 Wrapper (RFE)
- 遞歸特征消除法
- 3 Embedded
- 基於懲罰項的特征選擇法
- 基於樹模型的特征選擇
Filter
- 基於特征間的關系進行篩選
方差選擇法
- 方差選擇法中,先要計算各個特征的方差,然后根據設定的閾值,選擇方差大於閾值的特征
from sklearn.feature_selection import VarianceThreshold #其中參數threshold為方差的閾值 VarianceThreshold(threshold=3).fit_transform(train,target_train)
相關系數法
- Pearson 相關系數 皮爾森相關系數是一種最簡單的,可以幫助理解特征和響應變量之間關系的方法,該方法衡量的是變量之間的線性相關性。 結果的取值區間為 [-1,1] , -1 表示完全的負相關, +1表示完全的正相關,0 表示沒有線性相關。
from sklearn.feature_selection import SelectKBest from scipy.stats import pearsonr #選擇K個最好的特征,返回選擇特征后的數據 #第一個參數為計算評估特征是否好的函數,該函數輸入特征矩陣和目標向量, #輸出二元組(評分,P值)的數組,數組第i項為第i個特征的評分和P值。在此定義為計算相關系數 #參數k為選擇的特征個數 SelectKBest(k=5).fit_transform(train,target_train)
卡方檢驗
- 經典的卡方檢驗是用於檢驗自變量對因變量的相關性。 假設自變量有N種取值,因變量有M種取值,考慮自變量等於i且因變量等於j的樣本頻數的觀察值與期望的差距。 其統計量如下: χ2=∑(A−T)2T,其中A為實際值,T為理論值
- (注:卡方只能運用在正定矩陣上,否則會報錯Input X must be non-negative)
from sklearn.feature_selection import SelectKBest from sklearn.feature_selection import chi2 #參數k為選擇的特征個數 SelectKBest(chi2, k=5).fit_transform(train,target_train)
互信息法
- 經典的互信息也是評價自變量對因變量的相關性的。 在feature_selection庫的SelectKBest類結合最大信息系數法可以用於選擇特征,相關代碼如下:
from sklearn.feature_selection import SelectKBest from minepy import MINE #由於MINE的設計不是函數式的,定義mic方法將其為函數式的, #返回一個二元組,二元組的第2項設置成固定的P值0.5 def mic(x, y): m = MINE() m.compute_score(x, y) return (m.mic(), 0.5) #參數k為選擇的特征個數 SelectKBest(lambda X, Y: array(map(lambda x:mic(x, Y), X.T)).T, k=2).fit_transform(train,target_train)
Wrapper (Recursive feature elimination,RFE)
- 遞歸特征消除法 遞歸消除特征法使用一個基模型來進行多輪訓練,每輪訓練后,消除若干權值系數的特征,再基於新的特征集進行下一輪訓練。 在feature_selection庫的RFE類可以用於選擇特征,相關代碼如下(以邏輯回歸為例):
from sklearn.feature_selection import RFE from sklearn.linear_model import LogisticRegression #遞歸特征消除法,返回特征選擇后的數據 #參數estimator為基模型 #參數n_features_to_select為選擇的特征個數 RFE(estimator=LogisticRegression(), n_features_to_select=2).fit_transform(train,target_train)
Embedded
- 基於懲罰項的特征選擇法 使用帶懲罰項的基模型,除了篩選出特征外,同時也進行了降維。 在feature_selection庫的SelectFromModel類結合邏輯回歸模型可以用於選擇特征,相關代碼如下:
from sklearn.feature_selection import SelectFromModel from sklearn.linear_model import LogisticRegression #帶L1懲罰項的邏輯回歸作為基模型的特征選擇 SelectFromModel(LogisticRegression(penalty="l1", C=0.1)).fit_transform(train,target_train)
- 基於樹模型的特征選擇 樹模型中GBDT也可用來作為基模型進行特征選擇。 在feature_selection庫的SelectFromModel類結合GBDT模型可以用於選擇特征,相關代碼如下:
from sklearn.feature_selection import SelectFromModel from sklearn.ensemble import GradientBoostingClassifier #GBDT作為基模型的特征選擇 SelectFromModel(GradientBoostingClassifier()).fit_transform(train,target_train)
本數據集中我們刪除非入模特征后,並對缺失值填充,然后用計算協方差的方式看一下特征間相關性,然后進行模型訓練
# 刪除不需要的數據 for data in [data_train, data_test_a]: data.drop(['issueDate','id'], axis=1,inplace=True) "縱向用缺失值上面的值替換缺失值" data_train = data_train.fillna(axis=0,method='ffill') x_train = data_train.drop(['isDefault','id'], axis=1) #計算協方差 data_corr = x_train.corrwith(data_train.isDefault) #計算相關性 result = pd.DataFrame(columns=['features', 'corr']) result['features'] = data_corr.index result['corr'] = data_corr.values # 當然也可以直接看圖 data_numeric = data_train[numerical_fea] correlation = data_numeric.corr() f , ax = plt.subplots(figsize = (7, 7)) plt.title('Correlation of Numeric Features with Price',y=1,size=16) sns.heatmap(correlation,square = True, vmax=0.8)
features = [f for f in data_train.columns if f not in ['id','issueDate','isDefault'] and '_outliers' not in f] x_train = data_train[features] x_test = data_test_a[features] y_train = data_train['isDefault']
def cv_model(clf, train_x, train_y, test_x, clf_name): folds = 5 seed = 2020 kf = KFold(n_splits=folds, shuffle=True, random_state=seed) train = np.zeros(train_x.shape[0]) test = np.zeros(test_x.shape[0]) cv_scores = [] for i, (train_index, valid_index) in enumerate(kf.split(train_x, train_y)): print('************************************ {} ************************************'.format(str(i+1))) trn_x, trn_y, val_x, val_y = train_x.iloc[train_index], train_y[train_index], train_x.iloc[valid_index], train_y[valid_index] if clf_name == "lgb": train_matrix = clf.Dataset(trn_x, label=trn_y) valid_matrix = clf.Dataset(val_x, label=val_y) params = { 'boosting_type': 'gbdt', 'objective': 'binary', 'metric': 'auc', 'min_child_weight': 5, 'num_leaves': 2 ** 5, 'lambda_l2': 10, 'feature_fraction': 0.8, 'bagging_fraction': 0.8, 'bagging_freq': 4, 'learning_rate': 0.1, 'seed': 2020, 'nthread': 28, 'n_jobs':24, 'silent': True, 'verbose': -1, } model = clf.train(params, train_matrix, 50000, valid_sets=[train_matrix, valid_matrix], verbose_eval=200,early_stopping_rounds=200) val_pred = model.predict(val_x, num_iteration=model.best_iteration) test_pred = model.predict(test_x, num_iteration=model.best_iteration) # print(list(sorted(zip(features, model.feature_importance("gain")), key=lambda x: x[1], reverse=True))[:20]) if clf_name == "xgb": train_matrix = clf.DMatrix(trn_x , label=trn_y) valid_matrix = clf.DMatrix(val_x , label=val_y) params = {'booster': 'gbtree', 'objective': 'binary:logistic', 'eval_metric': 'auc', 'gamma': 1, 'min_child_weight': 1.5, 'max_depth': 5, 'lambda': 10, 'subsample': 0.7, 'colsample_bytree': 0.7, 'colsample_bylevel': 0.7, 'eta': 0.04, 'tree_method': 'exact', 'seed': 2020, 'nthread': 36, "silent": True, } watchlist = [(train_matrix, 'train'),(valid_matrix, 'eval')] model = clf.train(params, train_matrix, num_boost_round=50000, evals=watchlist, verbose_eval=200, early_stopping_rounds=200) val_pred = model.predict(valid_matrix, ntree_limit=model.best_ntree_limit) test_pred = model.predict(test_x , ntree_limit=model.best_ntree_limit) if clf_name == "cat": params = {'learning_rate': 0.05, 'depth': 5, 'l2_leaf_reg': 10, 'bootstrap_type': 'Bernoulli', 'od_type': 'Iter', 'od_wait': 50, 'random_seed': 11, 'allow_writing_files': False} model = clf(iterations=20000, **params) model.fit(trn_x, trn_y, eval_set=(val_x, val_y), cat_features=[], use_best_model=True, verbose=500) val_pred = model.predict(val_x) test_pred = model.predict(test_x) train[valid_index] = val_pred test = test_pred / kf.n_splits cv_scores.append(roc_auc_score(val_y, val_pred)) print(cv_scores) print("%s_scotrainre_list:" % clf_name, cv_scores) print("%s_score_mean:" % clf_name, np.mean(cv_scores)) print("%s_score_std:" % clf_name, np.std(cv_scores)) return train, test
def lgb_model(x_train, y_train, x_test): lgb_train, lgb_test = cv_model(lgb, x_train, y_train, x_test, "lgb") return lgb_train, lgb_test def xgb_model(x_train, y_train, x_test): xgb_train, xgb_test = cv_model(xgb, x_train, y_train, x_test, "xgb") return xgb_train, xgb_test def cat_model(x_train, y_train, x_test): cat_train, cat_test = cv_model(CatBoostRegressor, x_train, y_train, x_test, "cat")
lgb_train, lgb_test = lgb_model(x_train, y_train, x_test)
************************************ 1 ************************************
Training until validation scores don't improve for 200 rounds
[200] training's auc: 0.749225 valid_1's auc: 0.729679
[400] training's auc: 0.765075 valid_1's auc: 0.730496
[600] training's auc: 0.778745 valid_1's auc: 0.730435
Early stopping, best iteration is:
[455] training's auc: 0.769202 valid_1's auc: 0.730686
[0.7306859913754798]
************************************ 2 ************************************
Training until validation scores don't improve for 200 rounds
[200] training's auc: 0.749221 valid_1's auc: 0.731315
[400] training's auc: 0.765117 valid_1's auc: 0.731658
[600] training's auc: 0.778542 valid_1's auc: 0.731333
Early stopping, best iteration is:
[407] training's auc: 0.765671 valid_1's auc: 0.73173
[0.7306859913754798, 0.7317304414673989]
************************************ 3 ************************************
Training until validation scores don't improve for 200 rounds
[200] training's auc: 0.748436 valid_1's auc: 0.732775
[400] training's auc: 0.764216 valid_1's auc: 0.733173
Early stopping, best iteration is:
[386] training's auc: 0.763261 valid_1's auc: 0.733261
[0.7306859913754798, 0.7317304414673989, 0.7332610441015461]
************************************ 4 ************************************
Training until validation scores don't improve for 200 rounds
[200] training's auc: 0.749631 valid_1's auc: 0.728327
[400] training's auc: 0.765139 valid_1's auc: 0.728845
Early stopping, best iteration is:
[286] training's auc: 0.756978 valid_1's auc: 0.728976
[0.7306859913754798, 0.7317304414673989, 0.7332610441015461, 0.7289759386807912]
************************************ 5 ************************************
Training until validation scores don't improve for 200 rounds
[200] training's auc: 0.748414 valid_1's auc: 0.732727
[400] training's auc: 0.763727 valid_1's auc: 0.733531
[600] training's auc: 0.777489 valid_1's auc: 0.733566
Early stopping, best iteration is:
[524] training's auc: 0.772372 valid_1's auc: 0.733772
[0.7306859913754798, 0.7317304414673989, 0.7332610441015461, 0.7289759386807912, 0.7337723979789789]
lgb_scotrainre_list: [0.7306859913754798, 0.7317304414673989, 0.7332610441015461, 0.7289759386807912, 0.7337723979789789]
lgb_score_mean: 0.7316851627208389
lgb_score_std: 0.0017424259863954693
testA_result = pd.read_csv('../testA_result.csv') roc_auc_score(testA_result['isDefault'].values, lgb_test) #0.7290917729487896