1. 程式人生 > >Titanic分析&預測(二)

Titanic分析&預測(二)

IDE: Jupyter Notebook + Python 3
目標:根據已有資料集中的年齡、性別等屬性和存活與否,建立迴歸模型,並利用測試集提供的資料,進行存活預測。這次實驗參考的是一個投票數最多的kernel,採用的是整合學習(Ensemble Learning)的思想。

整合學習(ensemble learning)會構建並結合多個學習器來完成學習任務,有時也被稱為多分類器系統(multi-classifier system)、基於委員會的學習(committee-based learning)等。 (摘自《機器學習》周志華)

從周老師對整合學習的解釋可以大概看出構建整合學習模型的步驟:

⚪先構建多個學習器,可以是不同型別,如決策樹和神經網路。
⚪將以上的學習器進行結合,就是能夠體現“整合”這一思想的關鍵。

由於單一的學習器在給定資料集之下會體現不同的效能,泛化能力也有強有弱。為了增強模型的泛化能力,將以某種方式將同一資料集下訓練的多個學習器結合起來,從而獲得比單一學習器更好的效能。常見的結合策略有平均法、投票法和學習法(Stacking)。

本次實驗需要用到的包:

import numpy as np
import pandas as pd
import re
import seaborn as sns
import xgboost as xgb
import
matplotlib.pyplot as plt
import warnings warnings.filterwarnings('ignore') from sklearn.ensemble import (RandomForestClassifier, AdaBoostClassifier, GradientBoostingClassifier, ExtraTreesClassifier) from sklearn.svm import SVC from sklearn.model_selection import KFold

載入資料集,顯示資料集的資訊。(圖略)

# 載入資料集
train = pd.read_csv('D:\Dataset
\Titanic_ensembling\\train.csv') test = pd.read_csv('D:\Dataset\Titanic_ensembling\\test.csv') PassengerId = test['PassengerId']

先做資料處理,包括缺失值和型別轉換
缺失值處理

full_data = [train, test]
for dataset in full_data:
    # embarked和Fare缺失值處理
    dataset['Embarked'] = dataset['Embarked'].fillna('S')
    dataset['Fare'] = dataset['Fare'].fillna(train['Fare'].median())
    # 年齡缺失值處理
    age_avg = dataset['Age'].mean()
    age_std = dataset['Age'].std()
    age_null_count = dataset['Age'].isnull().sum()
    age_null_random_list = np.random.randint(age_avg - age_std, age_avg + age_std, size = age_null_count)
    dataset['Age'][np.isnan(dataset['Age'])] = age_null_random_list
    dataset['Age'] = dataset['Age'].astype(int)

特徵工程,對數值型和類別型的資料進行分析,發現新的特徵。
**a.**Sex屬性根據性別不同將資料值改為數值,0(female)/1(male)

for dataset in full_data:
    # Mapping Sex
    dataset['Sex'] = dataset['Sex'].map( {'female': 0, 'male': 1} ).astype(int)

**b.**Cabin屬性根據有無Cabin建立‘Has_Ccabin’特徵,0(無Cabin)/1(有Cabin)

train['Has_Cabin'] = train["Cabin"].apply(lambda x: 0 if type(x) == float else 1)
test['Has_Cabin'] = test["Cabin"].apply(lambda x: 0 if type(x) == float else 1)

**c.**Embarked屬性根據港口不同將資料值改為數值,0(S)/1(C)/2(Q)

for dataset in full_data:
    # Mapping Embarked
    dataset['Embarked'] = dataset['Embarked'].map( {'S': 0, 'C': 1, 'Q': 2} ).astype(int)

d.根據Name屬性的資料長度建立新屬性‘Name_Length’

train['Name_length'] = train['Name'].apply(len)
test['Name_length'] = test['Name'].apply(len)

e.根據SibSp和Parch屬性建立新屬性‘FamilySize’,代表家人數量。猜測家人數量少的生還率可能會偏低,因此建立新屬性‘IsAlone’,0(有家人)/1(無家人)

for dataset in full_data:
    dataset['FamilySize'] = dataset['SibSp'] + dataset['Parch'] + 1

for dataset in full_data:
    dataset['IsAlone'] = 0
    dataset.loc[dataset['FamilySize'] == 1, 'IsAlone'] = 1

f.為了將Age屬性值變成類別型資料,需要將原Age屬性值分成需要的類別數目,再給每一類Age值賦相應的類別標籤。這裡採用pandas裡面的cut()方法,在分類打標籤的時候,使每個類別長度都是相同的(但是其中的資料個數一般不同)

train['CategoricalAge'] = pd.cut(train['Age'], 5)
for dataset in full_data:
    dataset.loc[dataset['Age']<=16, 'Age']=0
    dataset.loc[(dataset['Age']>16)&(dataset['Age']<=32), 'Age']=1
    dataset.loc[(dataset['Age']>32)&(dataset['Age']<=48), 'Age']=2
    dataset.loc[(dataset['Age']>48)&(dataset['Age']<=64), 'Age']=3
    dataset.loc[dataset['Age']>64, 'Age']=4
# 上面的程式碼塊也可簡化成以下方式
pd.cut(train['Age'], label(01234))

g.為了將Fare屬性值變成類別型資料,需要將原Fare屬性值分成需要的類別數目,再給每一類Fare值賦相應的類別標籤。這裡採用pandas裡面的qcut()方法,在分類打標籤的時候,使每個類別中的資料個數都是相同的(但是類別的長度一般不同)

train['CategoricalFare'] = pd.cut(train['Fare'], 4)
for dataset in full_data:
    dataset.loc[dataset['Fare']<=7.91, 'Fare']=0
    dataset.loc[(dataset['Fare']>7.91)&(dataset['Fare']<=14.454), 'Fare']=1
    dataset.loc[(dataset['Fare']>14.454)&(dataset['Fare']<=31), 'Fare']=2
    dataset.loc[dataset['Fare']>31, 'Fare']=3
    dataset['Fare'] = dataset['Fare'].astype(int)
# 上面的程式碼塊也可簡化成以下方式
pd.cut(train['Fare'], label(0123))

h.對於Name屬性,猜測其中有特殊稱號的人會更尊貴,生還率可能會更高一些,因此寫一個獲取稱號的方法get_title(),將獲得的稱號存進‘Title’屬性中,特殊稱呼存為Rare,再將所有有稱呼的值map對應到title_mapping中,沒有title的map為0,程式碼如下:

def get_title(name):
    title_search = re.search('([A-Za-z]+)\.', name)
    if title_search:
        return title_search.group(1)
    return ''

for dataset in full_data:
    dataset['Title'] = dataset['Name'].apply(get_title)
for dataset in full_data:
    dataset['Title'] = dataset['Title'].replace(['Lady', 'Countess', 'Capt', 'Col', 'Don', 'Dr', 'Major',
                                                'Rev', 'Sir', 'Dona', 'Jonkheer'], 'Rare')
    dataset['Title'] = dataset['Title'].replace('Mlle', 'Miss')
    dataset['Title'] = dataset['Title'].replace('Ms', 'Miss')
    dataset['Title'] = dataset['Title'].replace('Mme', 'Mrs')
    title_mapping = {'Mr':1, 'Miss':2, 'Mrs':3, 'Master':4, 'Rare':5}
    dataset['Title'] = dataset['Title'].map(title_mapping)
    dataset['Title'] = dataset['Title'].fillna(0)

最終處理結果展示,程式碼如下:

drop_elements = ['PassengerId', 'Name', 'Ticket', 'Cabin', 'SibSp']
train = train.drop(drop_elements, axis = 1)
# train = train.drop(['CategoricalAge', 'CategoricalFare'], axis = 1)
test = test.drop(drop_elements, axis = 1)

train.head(10)

處理後的資料集

為了檢視特徵工程做的是否合理,這裡採用皮爾森關係熱圖(Pearson Correlation Heatmap),能夠直觀的展示出來各個特徵之間的相關係數,程式碼如下:

colormap = plt.cm.RdBu
plt.figure(figsize = (14,12))
plt.title('Pearson Correlation of Features')
sns.heatmap(train.astype(float).corr(), linewidths = 0.1, vmax = 1.0, square = True, cmap = colormap, linecolor = 'white', annot = True)

熱力圖1

關係熱圖會直觀的體現出特徵之間關聯性。若兩個特徵之間表現出強相關性,那麼就可以考慮移除其中一個;相反如果熱圖之中沒有強相關性,這就代表每個特徵都儲存有它自己最獨特的資訊,應該被保留下來參與建模。

接下來就可以建立學習器進行模型訓練了,最後再將多個學習器整合在一起輸出預測結果。這裡用到的學習模型有以下五個:
a.Random Forest classifier
b.AdaBoost classifer
c.Gradient Boosting classifer
d.Extra Trees classifier
e.Support Vector Machine

由於sklearn classifier 常用的方法如train(),fit()和predict()在訓練時會出現很多次,這裡將其抽象封裝成一個SklearnHelper類,避免了程式碼冗餘,程式碼如下:

NFOLDS = 5
SEED = 0

class SklearnHelper(object):
    def __init__(self, clf, seed = 0, params = None):
        params['random_state'] = seed
        self.clf = clf(**params)

    def train(self, x_train, y_train):
        self.clf.fit(x_train, y_train)

    def predict(self, x):
        return self.clf.predict(x)

    def fit(self, x, y):
        return self.clf.fit(x, y)

    def feature_importances(self, x, y):
        print(self.clf.fit(x,y).feature_importances_)

訓練模型時,不能使用整個訓練集,這樣有可能會使模型泛化能力降低。這裡採用K折交叉驗證,將資料集分成K份,每次用其中的k-1份訓練,用剩下的1份驗證模型精度,以此避免過擬合,增強泛化能力。定義方法get_oof()程式碼如下:

ntrain = train.shape[0]
ntest = test.shape[0]
kf = KFold(n_splits = NFOLDS, shuffle = True, random_state=SEED)

def get_oof(clf, x_train, y_train, x_test):
    oof_train = np.zeros((ntrain,))
    oof_test = np.zeros((ntest,))
    oof_test_skf = np.empty((NFOLDS, ntest))

    for i, (train_index, test_index) in enumerate(kf.split(x_train)):
        x_tr = x_train[train_index]
        y_tr = y_train[train_index]
        x_te = x_train[test_index]

        clf.train(x_tr, y_tr)

        oof_train[test_index] = clf.predict(x_te)
        oof_test_skf[i, :] = clf.predict(x_test)

    oof_test[:] = oof_test_skf.mean(axis=0)
    return oof_train.reshape(-1, 1), oof_test.reshape(-1, 1)

上面這段程式碼主要是get_oof()方法的定義。將分類器、訓練集和測試集傳進來之後,會先構造出一個和訓練測試集大小相同的兩個array,用於存放預測結果,最後會將NFOLDS次訓練的結果存入大小為(NFOLDS, ntest)的oof_test_skf 中,求平均值並返回。
for迴圈會對傳入方法的train和test進行NFOLDS次split(),分為新的x_tr和x_te用以模型訓練和預測,將每次訓練的預測結果存入oof_train[test_index],會有覆蓋但每次都不是完整資料集訓練出來的,達到最大化利用有限的資料目的。x_test的預測結果則存在oof_test_skf[i,:]中,i代表分類器個數。

接下來定義五個學習器的模型引數。其中:
n_jobs:訓練過程所建立的任務數量
n_estimators:分類樹的數量
max_depth:分類樹的最大深度,太大容易使模型複雜出現過擬合狀況
verbose:訓練過程中的一些引數輸出
程式碼如下:

# Random Forest Parameters
rf_params = {
    'n_jobs': -1,
    'n_estimators': 500,
     'warm_start': True, 
     #'max_features': 0.2,
    'max_depth': 6,
    'min_samples_leaf': 2,
    'max_features' : 'sqrt',
    'verbose': 0
}

# Extra Trees Parameters
et_params = {
    'n_jobs': -1,
    'n_estimators':500,
    #'max_features': 0.5,
    'max_depth': 8,
    'min_samples_leaf': 2,
    'verbose': 0
}

# AdaBoost parameters
ada_params = {
    'n_estimators': 500,
    'learning_rate' : 0.75
}

# Gradient Boosting parameters
gb_params = {
    'n_estimators': 500,
     #'max_features': 0.2,
    'max_depth': 5,
    'min_samples_leaf': 2,
    'verbose': 0
}

# Support Vector Classifier parameters 
svc_params = {
    'kernel' : 'linear',
    'C' : 0.025
    }

# 例項化出學習器的物件
rf = SklearnHelper(clf=RandomForestClassifier, seed=SEED, params=rf_params)
et = SklearnHelper(clf=ExtraTreesClassifier, seed=SEED, params=et_params)
ada = SklearnHelper(clf=AdaBoostClassifier, seed=SEED, params=ada_params)
gb = SklearnHelper(clf=GradientBoostingClassifier, seed=SEED, params=gb_params)
svc = SklearnHelper(clf=SVC, seed=SEED, params=svc_params)

將需要訓練的資料放到X_train和y_train中,測試資料放入x_test中,呼叫get_oof()進行模型訓練,程式碼如下:

y_train = train['Survived'].ravel()
train = train.drop(['Survived'], axis=1)
x_train = train.values 
x_test = test.values

et_oof_train, et_oof_test = get_oof(et, x_train, y_train, x_test)
rf_oof_train, rf_oof_test = get_oof(rf,x_train, y_train, x_test)
ada_oof_train, ada_oof_test = get_oof(ada, x_train, y_train, x_test)
gb_oof_train, gb_oof_test = get_oof(gb,x_train, y_train, x_test)
svc_oof_train, svc_oof_test = get_oof(svc,x_train, y_train, x_test) 

print("Training is complete")

這裡得到的五組新的train和test用於在下一階段訓練和測試用。在進行學習器結合之前,先把各個學習器對所有特徵的打分展示出來,程式碼如下:

rf_feature = rf.feature_importances(x_train,y_train)
et_feature = et.feature_importances(x_train, y_train)
ada_feature = ada.feature_importances(x_train, y_train)
gb_feature = gb.feature_importances(x_train,y_train)

分類器打分

將分數跟特徵對應起來

cols = train.columns.values
feature_dataframe = pd.DataFrame( {'features': cols,
     'Random Forest feature importances': rf_feature,
     'Extra Trees  feature importances': et_feature,
      'AdaBoost feature importances': ada_feature,
    'Gradient Boost feature importances': gb_feature
    })

feature_dataframe

不同分類器與特徵的分數關聯

繪出柱狀圖會更加直觀,定義繪圖方法並繪圖,程式碼如下:

def plot_features_importances(features_importances, title, feature_names):
    pos = np.arange(features_importances.shape[0])+1
    plt.figure(figsize=(14, 4))
    plt.bar(pos, features_importances, width=0.5, align = 'center')
    plt.xticks(pos, feature_names)
    plt.ylabel('Importances')
    plt.title(title)
    plt.show()

plot_features_importances(feature_dataframe['Random Forest feature importances'], 'Random Forest feature importances', feature_dataframe['features'])
plot_features_importances(feature_dataframe['Extra Trees  feature importances'], 'Extra Trees  feature importances', feature_dataframe['features'])
plot_features_importances(feature_dataframe['AdaBoost feature importances'], 'AdaBoost feature importances', feature_dataframe['features'])
plot_features_importances(feature_dataframe['Gradient Boost feature importances'], 'Gradient Boost feature importances', feature_dataframe['features']

rf
et
ada
gb

可以明顯看出來特徵的重要性。接下來對以上的五個分類器特徵求平均,繪圖展示結果,程式碼如下:

feature_dataframe['mean'] = feature_dataframe.mean(axis= 1) 
plot_features_importances(feature_dataframe['mean'], 'Mean feature importances', feature_dataframe['features'])

求平均

同樣的繪製熱圖,檢視以上幾種分類器的關聯性,程式碼如下:

base_predictions_train = pd.DataFrame( {'RandomForest': rf_oof_train.ravel(),
     'ExtraTrees': et_oof_train.ravel(),
     'AdaBoost': ada_oof_train.ravel(),
      'GradientBoost': gb_oof_train.ravel()
    })

colormap = plt.cm.RdBu
plt.figure()
plt.title('labelled-heatmap')
sns.heatmap(base_predictions_train.astype(float).corr(), linewidths = 0.1, vmax = 1.0, square = True, cmap = colormap, linecolor = 'white', annot = True)

熱力圖2

將上面五個分類器的訓練集和測試集合串聯到一起,進行第二層模型的結合訓練。第二層採用比較著名的XGBoost,其引數初始化及預測結果如下所示:

x_train = np.concatenate(( et_oof_train, rf_oof_train, ada_oof_train, gb_oof_train, svc_oof_train), axis=1)
x_test = np.concatenate(( et_oof_test, rf_oof_test, ada_oof_test, gb_oof_test, svc_oof_test), axis=1)

gbm = xgb.XGBClassifier(
    #learning_rate = 0.02,
     n_estimators= 2000,
     max_depth= 4,
     min_child_weight= 2,
     #gamma=1,
     gamma=0.9,                        
     subsample=0.8,
     colsample_bytree=0.8,
     objective= 'binary:logistic',
     nthread= -1,
     scale_pos_weight=1).fit(x_train, y_train)

predictions = gbm.predict(x_test)

# 生成預測結果檔案 
StackingSubmission = pd.DataFrame({ 'PassengerId': PassengerId,
                            'Survived': predictions })
StackingSubmission.to_csv('D:\\Dataset\\Titanic_ensembling\\titanic_ensembling.csv', index=False)

Summary:
a.這次對ensembling learning 的過程有一定了解,能夠將單獨的分類器結合起來是它的最大優勢,投票思想也很容易瞭解。但是對xgboost不瞭解,需要翻文章資料學習演算法思想。
**b.**K折交叉驗證應用到單個的模型也是可以的,能夠將有限的資料虛擬化的複製幾遍,提高模型的泛化能力。
c.提交後得分比上次高了0.01左右,但是進步了3k名,平時學到新的“套路”有想法了會回來再提交的