天天看點

Kaggle筆記:Porto Seguro’s Safe Driver Prediction(2)Porto Seguro’s Safe Driver Prediction

Porto Seguro’s Safe Driver Prediction

4.特征工程

上一部分完成了對資料的清洗與分析工作,完成這些準備工作之後,接下來準備進行特征工程。特征工程包括對原始資料特征進行檢測、變形、篩選,以及構築新的可能對建立模型有幫助的特征,是機器學習的重要步驟。

首先,讀入必要的資料與子產品:

import pandas as pd
import numpy as np

test = pd.read_csv('D:\\CZM\\Kaggle\\FinalWork\\train_after_EDA.csv')
train = pd.read_csv('D:\\CZM\\Kaggle\\FinalWork\\test_after_EDA.csv')
len_train = len(train)
len_test = len(test)
           

4.1 基于缺失值的特征工程

在對缺失值進行分析時,我們發現一部分特征的缺失情況之間存在關聯,如下圖所示:

Kaggle筆記:Porto Seguro’s Safe Driver Prediction(2)Porto Seguro’s Safe Driver Prediction

2.2已經分析過,ps_ind_02_cat與ps_ind_04_cat、ps_car_01_cat;ps_car_07_cat和ps_ind_05_cat;ps_03_cat和ps_05_cat,這三組特征每組内的特征缺失情況彼此之間存在關聯。基于此,嘗試構築以下三個特征:

MissingTotal_1:樣本在ps_ind_02_cat、ps_ind_04_cat、ps_car_01_cat這三個特征出現缺失值的個數之和。

MissingTotal_2:樣本在ps_car_07_cat和ps_ind_05_cat這兩個特征中出現缺失值的個數之和。

MissingTotal_3:樣本在ps_car_03_cat和ps_car_05_cat這兩個特征中出現缺失值的個數之和。

代碼如下:

#基于缺失值構築特征:
train['MissingTotal_1'] = np.zeros(len_train)
train['MissingTotal_2'] = np.zeros(len_train)
train['MissingTotal_3'] = np.zeros(len_train)
##MissingTotal_1:ps_ind_02_cat、ps_ind_04_cat、ps_ind_04_cat
train['MissingTotal_1'][train.ps_ind_02_cat==-1] += 1
train['MissingTotal_1'][train.ps_ind_04_cat==-1] += 1
train['MissingTotal_1'][train.ps_car_01_cat==-1] += 1
##MissingTotal_2:ps_car_07_cat和ps_ind_05_cat
train['MissingTotal_2'][train.ps_car_07_cat==-1] += 1
train['MissingTotal_2'][train.ps_ind_05_cat==-1] += 1
##MissingTotal_3:ps_car_03_cat和ps_car_05_cat
train['MissingTotal_3'][train.ps_car_03_cat==-1] += 1
train['MissingTotal_3'][train.ps_car_05_cat==-1] += 1

##對測試集進行相同的處理:
test['MissingTotal_1'] = np.zeros(len_test)
test['MissingTotal_2'] = np.zeros(len_test)
test['MissingTotal_3'] = np.zeros(len_test)
##MissingTotal_1:ps_ind_02_cat、ps_ind_04_cat、ps_ind_04_cat
test['MissingTotal_1'][test.ps_ind_02_cat==-1] += 1
test['MissingTotal_1'][test.ps_ind_04_cat==-1] += 1
test['MissingTotal_1'][test.ps_car_01_cat==-1] += 1
##MissingTotal_2:ps_car_07_cat和ps_ind_05_cat
test['MissingTotal_2'][test.ps_car_07_cat==-1] += 1
test['MissingTotal_2'][test.ps_ind_05_cat==-1] += 1
##MissingTotal_3:ps_car_03_cat和ps_car_05_cat
test['MissingTotal_3'][test.ps_car_03_cat==-1] += 1
test['MissingTotal_3'][test.ps_car_05_cat==-1] += 1
           

接下來,我們要分析新增的變量與Target變量的關系:

##作圖觀察三個特征與Target變量的關系:
k = 0
plt.figure(figsize=(20,10))
for x in Missing_Total:
    k = k+1
    plt.subplot(1,3,k)
    names = [];prop = []
    va = train[x].value_counts().index
    for name in va:
        names.append('feature:'+str(name))
        props = train[train[x]==name].target.value_counts()
        prop_1 = 0;prop_0 = 0
        if 1 in props.index:prop_1 = props[1]
        if 0 in props.index:prop_0 = props[0]
        prop.append(prop_1/(prop_1+prop_0))
    plt.ylabel('Proportion Of target = 1')
    plt.bar(names,prop)
           
Kaggle筆記:Porto Seguro’s Safe Driver Prediction(2)Porto Seguro’s Safe Driver Prediction

計算target=1關于這三個新特征的條件機率,可以看到根據這些新特征的不同取值,target=1的樣本所占的比例是有顯著不同的,說明這些特征可能對target的預測能起到作用。

4.2 基于二分類變量的特征工程

在2.4分析相關系數矩陣時,我們發現了以下的二分類特征之間具有相關性:

  • ps_ind_06_bin、ps_ind_07_ bin、ps_ind_08_bin、ps_ind_09_ bin這四列,互相之間均具有較強的負相關。
  • ps_ind_16_bin、ps_ind_17_bin、ps_ind_18_bin這三列,互相之間均具有較強的負相關。
  • 此外,ps_ind_15和ps_ind_16_bin之間存在較強的負相關;ps_ind_15與ps_ind_16_bin之間也存在一定的正相關。

由于二分類變量的取值是0-1的,是以,這裡可以嘗試對彼此之間相關的二分類特征求和建立新特征:

bin_plus_1:ps_ind_06_bin、ps_ind_07_bin、ps_ind_08_bin、ps_ind_09_ bin這四列相加。

bin_plus_2:ps_ind_16_bin、ps_ind_17_bin、ps_ind_18_bin這三列相加。

##基于bin特征構造特征:
train['bin_plus_1'] = train['ps_ind_06_bin']+train['ps_ind_07_bin']+train['ps_ind_08_bin']+train['ps_ind_09_bin']
train['bin_plus_2'] = train['ps_ind_16_bin']+train['ps_ind_17_bin']+train['ps_ind_18_bin']
           

但是,檢視第一個新特征bin_plus_1的分布,會發現:

In [145]: train.bin_plus_1.value_counts()
Out[145]: 
1    595212
Name: bin_plus_1, dtype: int64
           

所有的樣本這一特征的值都是相同的,這一特征雖然是以不能夠使用了,但是我們提取出了新的資訊:對于每個樣本,這四個特征之和都必定為1,換言之,其中至少有一個特征是沒有必要的。

剔除剛剛新增的bin_plus_1,除此之外,還将ps_ind_09_ bin這一特征列從資料集中删除:

##發現bin_plus_1的值為1,說明這四個特征之和必定為1。
##剔除bin_plus_1和ps_ind_09_bin
train = train.drop('bin_plus_1',axis = 1)
train = train.drop('ps_ind_09_bin',axis = 1)
test = test.drop('ps_ind_09_bin',axis = 1)
           

接下來,因為二分類特征發生了變化,是以,需要重新繪制相關系數矩陣圖:

Kaggle筆記:Porto Seguro’s Safe Driver Prediction(2)Porto Seguro’s Safe Driver Prediction

能夠看到ps_ind_06、ps_ind_07、ps_ind_08彼此之間仍存在一定負相關,但是幅度已經降低不少。那麼,重新用這三個特征的和構築bin_plus_1,另外,也在測試集中構築bin_plus_1與bin_plus_2:

##重新構築:
train['bin_plus_1'] = train['ps_ind_06_bin']+train['ps_ind_07_bin']+train['ps_ind_08_bin']
test['bin_plus_1'] = test['ps_ind_06_bin']+test['ps_ind_07_bin']+test['ps_ind_08_bin']
test['bin_plus_2'] = test['ps_ind_16_bin']+test['ps_ind_17_bin']+test['ps_ind_18_bin']
           

此外,實際上通過相關系數矩陣圖,也可以發現之前新增的一些特征與其他特征之間存在相關性,不過這裡暫時不考慮這些。

接下來,我們要分析新增的變量與Target變量的關系:

Kaggle筆記:Porto Seguro’s Safe Driver Prediction(2)Porto Seguro’s Safe Driver Prediction

可以看到新增的特征取值對Target的影響不大,可能不是很好的特征,不過,總之也可以先留着,之後再進行篩選。

另外,可以發現這兩個新增的特征都屬于二維變量:

bincol.append('bin_plus_1')
bincol.append('bin_plus_2')
           

4.3 對多分類特征的處理

資料集中存在14個标記為_cat的多分類特征,不過,根據先前的分析我們已經得知,這14個多分類特征中,實際上存在兩個特征:ps_car_08_cat以及ps_ind_04_cat,它們屬于二分類特征,其中ps_ind_04_cat是存在缺失值的,是以也可以視為多分類特征,但是ps_car_08_cat就必須被剔除出多分類變量:

#把ps_car_08_cat剔除出多分類:
catcol.remove('ps_car_08_cat')
bincol.append('ps_car_08_cat')
           

接下來,待分析的多分類變量特征還剩13個。常用的對多分類變量進行的模組化方法是根據多分類特征的取值,将其轉化為多個二維的啞變量,然後再進行模組化。然而這樣做有兩個缺點:第一,這會導緻資料維數迅速上升,對本地計算機來說運算壓力過大;第二,可能會導緻本應反映各個取值之間的關系的一定資訊流失。

這篇論文詳細介紹了一種根據變量的各個取值下目标變量的似然機率,将分類變量轉化為連續變量的方法:https://kaggle2.blob.core.windows.net/forum-message-attachments/225952/7441/high%20cardinality%20categoricals.pdf

這裡本項目也将采用這種方法。

但是在執行以前,必須考慮到另一個事實:從先前的分析結果來看,資料集中很可能存在特征被錯誤歸類的情況,有二維特征被歸入了多分類的特征,而且,ps_car_11_cat特征可取的值實在是太多了,有100多個,比起多分類變量,它更像是一個順序或定距變量特征。

在這種情況下,認為現有的分類特征很可能會存在由順序或定距變量誤分過來的特征,是一個合理的推斷。是以,對于多分類特征,本項目作出的處理如下:

  • 為所有多分類特征留存一份副本,将這些變量類型視為連續或順序變量,缺失值也重新插補為中位數。
  • 然後将原本的多分類特征,按照前述方法根據Target似然機率轉化為連續變量特征。
    ##為所有多分類特征留存一份副本,重新插補缺失值,視為順序變量特征:
    cat_to_rank_col = []
    for x in catcol:
        name = x+'_rank'
        co = train[x]
        co = co.replace(-1,np.nan)
        co = co.replace(np.nan,co.median())
        cat_to_rank_col.append(name)
        train[name] = co
    
    for x in catcol:
        name = x+'_rank'
        co = test[x]
        co = co.replace(-1,np.nan)
        co = co.replace(np.nan,co.median())
        test[name] = co
    
    ##接下來,把所有原本的多分類特征,根據貝葉斯機率轉化為連續型特征:
    y = train.target
    nTR = len(y)
    nY = len(y[y==1])
    nYTR = nY/nTR
    for coll in catcol:
        cat1 = train[coll]
        cat2 = train[coll][y==1]
        cat3 = test[coll]
        valuec1 = cat1.value_counts()
        valuec2 = cat2.value_counts()
        catdict = {}
        for i in valuec1.index: 
            ind = i
            ni = valuec1[ind]
            if ind in valuec2.index:
                niY = valuec2[ind] 
            else:
                niY = 0
            lamda = 1/(1+np.exp(20-0.1*ni))
            smoothing = lamda*niY/ni+(1-lamda)*nYTR
            catdict[ind] = smoothing
        #print (catdict)
        for i in valuec1.index: 
            cat1[cat1==i] = catdict[i]
            cat3[cat3==i] = catdict[i]
        train[coll] = cat1
        test[coll] = cat3
               

到此,多分類特征處理完成了,資料集中已經隻剩下二維變量與連續、順序變量。

接下來會對所有的連續或順序變量進行分析。

4.4 剔除訓練集與測試集分布不同的特征

也許是資料提供方刻意為之,也可能純粹是因為資料集足夠大,總之,根據第2、3節的内容,在大多數的特征上,訓練集與測試集的分布十分接近。不過,仍然存在幾個在訓練集和測試集中取值的分布不同的特征,那就是ps_calc_06,ps_calc_12,ps_calc_13,ps_calc_14這四個特征。

但是,直接剔除可能會損失資訊,根據3.3節,ps_calc_06的取值小于2、ps_calc_12的取值大于6、ps_calc_13的取值等于13、ps_calc_14的取值等于19,都可能會對Target=1的條件機率造成比較明顯的影響。

為了保留這些資訊,設定4個新的二維變量特征:

ps_calc_06_bin:ps_calc_06的取值小于2時記為1,否則為0。

ps_calc_12_bin:ps_calc_12的取值大于6時記為1,否則為0。

ps_calc_13_bin:ps_calc_13的取值等于13時記為1,否則為0。

ps_calc_14_bin:ps_calc_14的取值等于19時記為1,否則為0。

然後,剔除原始特征。

##calc_bin:
train['ps_calc_06_bin'] = np.zeros(len_train)
train['ps_calc_12_bin'] = np.zeros(len_train)
train['ps_calc_13_bin'] = np.zeros(len_train)
train['ps_calc_14_bin'] = np.zeros(len_train)
train['ps_calc_06_bin'][train.ps_calc_06<2] += 1
train['ps_calc_12_bin'][train.ps_calc_12>6] += 1
train['ps_calc_13_bin'][train.ps_calc_13==13] += 1
train['ps_calc_14_bin'][train.ps_calc_14==19] += 1
test['ps_calc_06_bin'] = np.zeros(len_test)
test['ps_calc_12_bin'] = np.zeros(len_test)
test['ps_calc_13_bin'] = np.zeros(len_test)
test['ps_calc_14_bin'] = np.zeros(len_test)
test['ps_calc_06_bin'][test.ps_calc_06<2] += 1
test['ps_calc_12_bin'][test.ps_calc_12>6] += 1
test['ps_calc_13_bin'][test.ps_calc_13==13] += 1
test['ps_calc_14_bin'][test.ps_calc_14==19] += 1
train=train.drop('ps_calc_06',axis = 1)
train=train.drop('ps_calc_12',axis = 1)
train=train.drop('ps_calc_13',axis = 1)
train=train.drop('ps_calc_14',axis = 1)
test=test.drop('ps_calc_06',axis = 1)
test=test.drop('ps_calc_12',axis = 1)
test=test.drop('ps_calc_13',axis = 1)
test=test.drop('ps_calc_14',axis = 1)
           

4.5 基于連續或順序特征以及相關系數矩陣的分析

原始資料集中不存在_bin或_cat字尾的特征屬于連續或順序變量,但是,本段所讨論的連續或順序特征不僅是這些,也包括了已經根據貝葉斯機率轉化為連續變量的原本的多分類特征。

由于此時資料中已經不存在多分類特征,是以,重新對整個訓練集的所有特征畫相關系數矩陣圖:

Kaggle筆記:Porto Seguro’s Safe Driver Prediction(2)Porto Seguro’s Safe Driver Prediction

從圖中能夠取得的資訊如下:

  • 即使經過了多次特征工程,我們可以看到calc類的特征依然如先前看到的一樣,不與任何其他特征存在相關關系。
  • 經過連續化處理後的多分類變量整體之間存在一定的正相關關系,且與reg類特征之間也存在一定的正相關關系。
  • ps_ind_04_cat與ps_car_10_cat、ps_car_02_cat,與經過順序化後的它們自己,也就是ps_ind_04_cat_rank和ps_car_10_cat_rank,ps_car_02_cat_rank之間存在較強的相關性,實際上,可以說整個cat特征區域,都與cat_rank區域之間有一定的相關性,這意味着這些多分類特征的特征取值大小順序可能是有意義的。
  • MissingTotal_1與ps_ind_02_cat之間存在較強的正相關,MissingTotal_2與ps_car_07_cat之間正相關,由于MissingTotal是多個特征(包括這裡出現的與之相關的特征)的缺失值數目之和,這可能說明了ps_ind_02_cat的缺失值在構成MissingTotal_1的三個特征中起主要作用,MissingTotal_2與ps_car_07_cat同理。而MissingTotal_3與構成它的兩個特征ps_car_03_cat和ps_car_05_cat均呈現較強的負相關就很好了解了,這兩個特征的缺失值都較多。

以上資訊是與先前對比,新的相關系數矩陣帶來的新内容。

4.6,使用随機森林與XGBoost嘗試對資料模組化

接下來将會對現有的資料集嘗試用随機森林模型進行模組化,這兩種模型作為組合樹模型擁有自動選取特征的能力,它們提供的特征重要性排序,會有助于特征的篩選工作。

首先,使用SKlearn包對整個訓練集建立随機森林模型:

##嘗試模組化,目标是輸出特征重要性
##1.RandomForest
from sklearn.ensemble import RandomForestClassifier
model_fe = RandomForestClassifier()
features = train.columns[2:]
model_fe.fit(train[features],train.target)
###重要性排序
importance = model_fe.feature_importances_
features_sort = features[np.argsort(importance)[::-1]]
importance_sort = importance[np.argsort(importance)[::-1]]
plt.figure(figsize = (20,20))
sorted_idx = np.argsort(importance)[::-1]
pos = np.arange(sorted_idx.shape[0]) + .5
plt.barh(pos, importance[sorted_idx[::-1]],color='r',align='edge')
plt.yticks(pos, features[sorted_idx[::-1]])
           
Kaggle筆記:Porto Seguro’s Safe Driver Prediction(2)Porto Seguro’s Safe Driver Prediction

上圖是對是随機森林輸出的特征重要性進行排序後的示意圖,從圖中我們可以看到,ps_car_13和ps_reg_03無疑是最為重要的兩個特征。先前一直不看好的calc類特征也有很多占據了較高的重要性排序上,而先前字形構造的的ps_calc_06_bin、ps_calc_12_bin、ps_calc_13_bin、ps_calc_14_bin、MissingValue_1、MissingValue_2等幾個特征基本沒有貢獻。

接下來,嘗試使用XGBoost進行模組化:

##2.XGBoost
import xgboost as xgb
from xgboost import XGBClassifier
model_fe = XGBClassifier(    
                        n_estimators=70,
                        max_depth=6,
                        objective="binary:logistic",
                        subsample=.8,
                        min_child_weight=6,
                        colsample_bytree=.8,
                        scale_pos_weight=1.6,
                        gamma=10,
                        reg_alpha=8,
                        reg_lambda=1.3,
                     )
model_fe.fit(train[features],train.target)
###重要性排序
importance = model_fe.feature_importances_
features_sort = features[np.argsort(importance)[::-1]]
importance_sort = importance[np.argsort(importance)[::-1]]
plt.figure(figsize = (20,20))
sorted_idx = np.argsort(importance)[::-1]
pos = np.arange(sorted_idx.shape[0]) + .5
plt.barh(pos, importance[sorted_idx[::-1]],color='r',align='edge')
plt.yticks(pos, features[sorted_idx[::-1]])
           
Kaggle筆記:Porto Seguro’s Safe Driver Prediction(2)Porto Seguro’s Safe Driver Prediction

可以看到,XGBOOST模型的重要性排序認為ps_ind_03才是最重要的特征,ps_car_13和ps_ind_05_cat次之,而和随機森林相比,XGBoost輸出的特征重要性有更多貢獻極低的特征,和随機森林輸出的重要性特征排序不同,XGBoost認為幾乎所有的calc類特征都不會給出什麼貢獻,而先前我們自己構築的MissingTotal_1和MissingTotal_3以及bin_plus_1都擁有不錯的特征重要性。

這裡,把在兩個模型中特征重要性的排序都很低的特征剔除,它們是:

##因重要性過低,将會被剔除的特征:

useless_features = [
 'ps_car_04_cat_rank',
 'ps_car_02_cat',
 'ps_car_08_cat',
 'ps_car_03_cat_rank',
 'ps_car_10_cat_rank',
 'ps_ind_13_bin',
 'ps_calc_06_bin',
 'ps_calc_12_bin',
 'ps_ind_10_bin',
 'ps_ind_11_bin',
 'ps_ind_12_bin',
 'bin_plus_2',
 'ps_ind_14',
 'ps_car_02_cat_rank',
 'ps_ind_18_bin',
 'ps_calc_13_bin',
 'ps_car_14_miss',
 'ps_car_10_cat',
 'ps_calc_14_bin']

train = train.drop(useless_features,axis = 1)
test = test.drop(useless_features,axis = 1)
           

共計19個特征被剔除,剩餘的特征有56個。

4.7 剔除強相關特征

在完成絕大多數的特征工程後,再一次畫出相關系數矩陣:

Kaggle筆記:Porto Seguro’s Safe Driver Prediction(2)Porto Seguro’s Safe Driver Prediction

可以看到,經過幾番特征處理過後,較為嚴重的特征之間的相關性已經很少見了。reg與car類特征之間雖然存在一定的線性相關區域,但是程度也是可以接受的。

接下來,我們要處理掉剩餘存在強線性相關情況的幾個點:MissingTotal_3與ps_car_03_cat和ps_car_05_cat都具有較強的負相關,而MissingTotal_2也與ps_car_07有較強的正相關,此外,ps_ind_04_cat與ps_ind_04_cat_rank之間也存在很強的正相關。

為此,可以選擇删除出現了強線性相關,在模型特征評分中表現也不好的ind_04_cat_rank,MissingTotal_3,MissingTotal_2:

#删除強相關特征:
cor_features = ['ps_ind_04_cat_rank','ps_car_03_cat','MissingTotal_2']
train = train.drop(cor_features,axis = 1)
test = test.drop(cor_features,axis = 1)
           

此外,還能夠發現,target目标變量幾乎不和其他特征存在明顯的線性相關性,是以,應該意識到線性模型在這樣的資料集上,很難有良好的表現。

4.8 總結

至此特征工程告一段落,原始資料被添加和删除了大量特征後,剩餘特征的總數為53,如無意外,這就将會是用于模組化的最終資料。

那麼,把完成了特征工程的資料集儲存好:

##儲存資料

train.to_csv('D:\\CZM\\Kaggle\\FinalWork\\train_after_FeatureEngineer.csv', index=False)
test.to_csv('D:\\CZM\\Kaggle\\FinalWork\\test_after_FeatureEngineer.csv', index=False)
           

繼續閱讀