Titanic: Machine Learning from Disaster

這是我第一次參加kaggle競賽,雖然只是一個入門記得比賽,但我從中學到很多東西,在這裏我希望記述一下這個比賽帶給我的收穫。

工作環境

  1. 操作系統 : Windows10 64位
  2. 語言: python
  3. 工具箱:numpy,keras,sklearn,pandas

任務概述

相信大家基本都看過或聽說過泰坦尼克號這部影片,顯然我們關注的不是那個悽美的愛情故事,而是希望通過機器學習的方法來預測船上乘客是否能生還。

數據集

訓練集:共891個數據,數據內容如下
這裏寫圖片描述
測試集:共418個待預測數據,數據內容僅出去PassengerId一列,其餘內容想同,這裏不再複述

實現步驟

  1. 清理數據
  2. 分析數據
  3. 建立模型
  4. 挑選超參數


    下面我來對以上四個步驟做詳細說明

    清理數據

    在這一個步驟裏我們會將所得到的891個訓練數據進行清理工作,得到三組不同的數據,(注意這裏說的三組並不是指train,validation,test三個數據集)
    首先我們來看一下數據的總體特徵:
    . 數據類型
    這裏寫圖片描述

    . 是否缺值
    這裏寫圖片描述

    . 數據描述
    這裏寫圖片描述
    通過上面的分析我們對於數據集有了一個充分的瞭解,也就理解了爲什麼要清理數據,我們看到數據有些特徵是不重要的,或是非數值的,或是空的,我們需要根據個人經驗對數據集做處理,以滿足需求。
    現在我們開始清理數據


. 替換空項並去除無用項
通過上面的分析顯然PassengerId,Cabin, Ticket屬於無用項,剩餘的特徵我們替換空項

data_raw = pd.read_csv('train.csv')
data_val  = pd.read_csv('test.csv')
data1 = data_raw.copy(deep = True)
data_cleaner = [data1, data_val]
for dataset in data_cleaner:    
    #complete missing age with median
    dataset['Age'].fillna(dataset['Age'].median(), inplace = True)

    #complete embarked with mode
    dataset['Embarked'].fillna(dataset['Embarked'].mode()[0], inplace = True)

    #complete missing fare with median
    dataset['Fare'].fillna(dataset['Fare'].median(), inplace = True)

#delete the cabin feature/column and others previously stated to exclude in train dataset
drop_column = ['PassengerId','Cabin', 'Ticket']
data1.drop(drop_column, axis=1, inplace = True)
data_val.drop(drop_column, axis=1, inplace = True)
print(data1.isnull().sum())
print("-"*10)
print(data_val.isnull().sum())

這裏寫圖片描述

. 提取新特徵

###CREATE: Feature Engineering for train and test/validation dataset
for dataset in data_cleaner:    
    #Discrete variables
    dataset['FamilySize'] = dataset ['SibSp'] + dataset['Parch'] + 1

    dataset['IsAlone'] = 1 #initialize to yes/1 is alone
    dataset['IsAlone'].loc[dataset['FamilySize'] > 1] = 0 # now update to no/0 if family size is greater than 1

    #quick and dirty code split title from name: http://www.pythonforbeginners.com/dictionary/python-split
    dataset['Title'] = dataset['Name'].str.split(", ", expand=True)[1].str.split(".", expand=True)[0]


    #Continuous variable bins; qcut vs cut: https://stackoverflow.com/questions/30211923/what-is-the-difference-between-pandas-qcut-and-pandas-cut
    #Fare Bins/Buckets using qcut or frequency bins: https://pandas.pydata.org/pandas-docs/stable/generated/pandas.qcut.html
    dataset['FareBin'] = pd.qcut(dataset['Fare'], 4)

    #Age Bins/Buckets using cut or value bins: https://pandas.pydata.org/pandas-docs/stable/generated/pandas.cut.html
    dataset['AgeBin'] = pd.cut(dataset['Age'].astype(int), 5)

#cleanup rare title names
#print(data1['Title'].value_counts())
stat_min = 10 #while small is arbitrary, we'll use the common minimum in statistics: http://nicholasjjackson.com/2012/03/08/sample-size-is-10-a-magic-number/
title_names = (data1['Title'].value_counts() < stat_min) #this will create a true false series with title name as index

#apply and lambda functions are quick and dirty code to find and replace with fewer lines of code: https://community.modeanalytics.com/python/tutorial/pandas-groupby-and-python-lambda-functions/
data1['Title'] = data1['Title'].apply(lambda x: 'Misc' if title_names.loc[x] == True else x)
print(data1['Title'].value_counts())

這裏寫圖片描述

label = LabelEncoder()
for dataset in data_cleaner:    
    dataset['Sex_Code'] = label.fit_transform(dataset['Sex'])
    dataset['Embarked_Code'] = label.fit_transform(dataset['Embarked'])
    dataset['Title_Code'] = label.fit_transform(dataset['Title'])
    dataset['AgeBin_Code'] = label.fit_transform(dataset['AgeBin'])
    dataset['FareBin_Code'] = label.fit_transform(dataset['FareBin'])

這裏寫圖片描述
這裏寫圖片描述

  • 生成行數據集
    我們會生成三個新的數據集,我會將新生成的數據集放到cache字典中保存
    這裏我想再提一下dummy數據集的產生,這個數據集我們通過padas的get_dummy函數將原有數據集中的object,category類型的數據轉換爲one-hot類型
def split_example(data_raw):
    #define y variable aka target/outcome
    Target = ['Survived']

#define x variables for original features aka feature selection
    data_x = ['Sex','Pclass', 'Embarked', 'Title','SibSp', 'Parch', 'AgeBin', 'FareBin_Code', 'FamilySize', 'IsAlone'] #pretty name/values for charts
    data_x_calc = ['Sex_Code','Pclass', 'Embarked_Code', 'Title_Code','SibSp', 'Parch', 'Age', 'Fare'] #coded for algorithm calculation

 #define x variables for original w/bin features to remove continuous variables
    data_x_bin = ['Sex_Code','Pclass', 'Embarked_Code', 'Title_Code', 'FamilySize', 'AgeBin_Code', 'FareBin_Code']

    data_raw_dummy = pd.get_dummies(data_raw[data_x])
    data_x_dummy = data_raw_dummy.columns.tolist()

    train_x_bin, test_x_bin, train_y_bin, test_y_bin = model_selection.train_test_split(data_raw[data_x_bin], data_raw[Target] ,
     test_size = 0.2 ,random_state = 0)
    valid_x_bin, test_x_bin,valid_y_bin,test_y_bin = model_selection.train_test_split(test_x_bin, test_y_bin ,
     test_size = 0.2 ,random_state = 0)
    train_x_dummy, test_x_dummy, train_y_dummy, test_y_dummy = model_selection.train_test_split(data_raw_dummy[data_x_dummy], data_raw[Target],            random_state = 0)
    valid_x_dummy, test_x_dummy,valid_y_dummy,test_y_dummy = model_selection.train_test_split(test_x_dummy, test_y_dummy ,
     test_size = 0.2 ,random_state = 0)
    train_x, test_x, train_y, test_y = model_selection.train_test_split(data_raw[data_x_calc], data_raw[Target], random_state = 0)
    valid_x, test_x,valid_y,test_y = model_selection.train_test_split(test_x, test_y,
     test_size = 0.2 ,random_state = 0)
    cache = {'train_x':train_x,'train_y':train_y,'test_x':test_x,'test_y':test_y,
             'train_x_bin':train_x_bin,'train_y_bin':train_y_bin,'test_x_bin':test_x_bin,'test_y_bin':test_y_bin,
             'train_x_dummy':train_x_dummy,'train_y_dummy':train_y_dummy,'test_x_dummy':test_x_dummy,
             'test_y_dummy':test_y_dummy,'valid_x':valid_x,'valid_y':valid_y,'valid_x_bin':valid_x_bin,
             'valid_y_bin':valid_y_bin,'valid_x_dummy':valid_x_dummy,'valid_y_dummy':valid_y_dummy}
    return cache

我們的數據清理到這裏就告一段落了。


分析數據

在這個步驟我們討論準確率的底線,很明顯,這是一個二分類問題,所以我們即使什麼都不,單單通過產生隨機數的方式也能達到0.5的準確率。
讓我進一步分析我們的數據,採用概率知識來的到最終的準確率底線。
下面我給出數據集中不同特徵與存活率的關係(注意:是與存活率的關係
這裏寫圖片描述
這裏寫圖片描述
這裏寫圖片描述
接下來我給出數據集中不同特徵存活與喪生人數:
這裏寫圖片描述
這裏寫圖片描述
這裏寫圖片描述
這裏寫圖片描述
結合概率論知識,如何我們將所有女性,以及其他特種中存活率最高的人數預測爲全部存活,那我們的準確率大概有0.76.你也可以嘗試使用將所有男性….預測爲存活的方式,到此爲止,我們已經求出了我們的算法至少要達到0.76的準確率。


建立模型

我使用一個含有4個隱含層,一個輸出層的神經網絡建立模型,隱含層每層的輸出相同,爲64個(你也可以嘗試使用更多的隱含層,隱含層含有更多的輸出單元
這裏寫圖片描述
模型結構如上圖所示
優化函數採用Adam優化算法


挑選超參數

在這裏我介紹我挑選超參數的方法,通過我們的模型可以得知,超參數包括:隱含層層數,網絡深度,Adam參數,Dropout層的keep_pro,Regularizer的參數lambd.
爲了方便挑選,我只挑選各層Dropout的keep_pro,lambd我將各層設置爲相同的,按我理解,隱含層+輸出層的lambd加在一起等於原來我們自己使用numpy建立的那個lambd(說的好像很不清楚,只可意會不可言傳,嘻嘻)。
對於這兩個參數,我使用隨機數來挑選合適的lambd以及各層keep_pro。效果還不錯,開始的時候對於每個隨機參數我運行1000個epoch來訓練,我發現效率很低,其中有很多隨機的超參數根本就沒有運行這麼多次的必要,後來,我決定先將50組超參數運行300次,通過valid數據集挑選出準確率高於0.7的超參數在運行600次,然後挑選出準確率高於0.8的超參數運行2000次,這種方式效率還是蠻高的。


擴展

在建立神經網絡的時候我就在想,既然這是一個二分類問題,是否可以使用聚類的方法呢,將存活當成一類,將死亡當成一類,我覺得使用k-means也同樣是可行;
在我的模型產生過擬合後,我想到可否通過Dimensionality Reduction方法將數據中的特徵降維來解決過擬合,希望各位去嘗試一下


感悟

該開始比賽時,我是很急躁的,將注意力全都放到了如何建立模型上去了,沒有仔細的分析數據,對於數據的處理很是粗暴,不過當時可能還是沾沾自喜,直到模型建立好後訓練,發現,我的模型對訓練數據的預測準確率連0.9都到不了,而且還很不穩定,當時我才意識到一定是哪裏出了問題,這次我靜下心來,一點點看別人的kernel,發現是我清理數據這塊出了問題,原來從一開始就本末倒置。到這裏也就該結束了,原來在網上學了很多的可,上面雖然也有編程練習,但都是別人給好了框架,我只要填幾行代碼就完事大吉,這次從清理數據到挑選超參數完全是自己動手,感覺學到不少東西,感謝這次機會,也感謝不斷努力的自己。


清理數據的想法來源於(https://www.kaggle.com/ldfreeman3/a-data-science-framework-to-achieve-99-accuracy)這個kernel。


發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章