阿里雲的金融風控-貸款違約預測_特征工程


特征工程

項目地址: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)連續箱單調
  1. 固定寬度分箱

當數值橫跨多個數量級時,最好按照 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']))
  1. 分位數分箱
data['loanAmnt_bin3'] = pd.qcut(data['loanAmnt'], 10, labels=False)
  1. 卡方分箱及其他分箱方法的嘗試
  • 這一部分屬於進階部分,學有余力的同學可以自行搜索嘗試

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

 


免責聲明!

本站轉載的文章為個人學習借鑒使用,本站對版權不負任何法律責任。如果侵犯了您的隱私權益,請聯系本站郵箱yoyou2525@163.com刪除。



 
粵ICP備18138465號   © 2018-2025 CODEPRJ.COM