關於鐵達尼號誰活著之類的事

其實是某次的作業,打蠻認真的就順手po出來。

摘要

網址

https://www.kaggle.com/c/nlp-hw2

成果

Score: 0.96440

程式語言

Python 3.6.4

機器學習、資料處理相關Library

資料特徵欄位

採用

[id, pclass, name, sex, age, sibsp, parch, fare, embarked, boat, body]

未採用

[cabin, survived, ticket, home.dest]

資料讀取與預處理

首先,讀入資料並拋棄不必用到的欄位後,我將 training data 和 testing data 串接在一起,同時進行處理,以避免造成兩者資料欄位數不一致的狀況。

# read data
train_df = pd.read_excel("training data(1000).xlsx")
test_df = pd.read_excel("testing data.xlsx")

# concat training data & testing data
df = pd.concat(
    [
        train_df.drop(['cabin', 'survived', 'ticket', 'home.dest'], axis=1), 
        test_df.drop(['cabin', 'survived', 'ticket', 'home.dest'], axis=1)
    ],
    ignore_index=True
)

自定義function

由於之後會常用到需要將資料one hot編碼的狀況,因此先自訂幾個function,分別是對一般單個資料以及list格式的資料作處理。

def encode_one_hot(df, column):
    return pd.get_dummies(data=df, columns=[column])

def encode_one_hot_list(df, column):
    return df.drop([column], axis=1).join(
        pd.get_dummies(
            pd.DataFrame(df[column].tolist()).stack(),
            prefix=column
        ).astype(int).sum(level=0)
    )

補空值

需要補的欄位有:age, fare, body。

age和fare我採用mean()填入平均值。先前有測試過median()取中位數的方式,但發現兩這改成中位數後都會使準確率確實降低。 對於body的空欄位我則直接填0,避免錯誤的預測。

df['age'] = df['age'].fillna(df['age'].mean())
df['fare'] = df['fare'].fillna(df['fare'].mean())
df['body'] = df['body'].fillna(0)

性別欄位

將female用0代替,male則用1。

df['sex'] = df['sex'].map({'female': 0, 'male': 1}).astype(int)

處理name欄位

name欄位包含了姓名、稱謂等等的資訊,其中稱謂算是頗有用的資料,可能反映出該乘客的年齡、性別與婚姻狀況,因此我將對name欄位進行處理,取出稱謂。 根據自行統計後,發現稱謂欄位有 ['Miss', 'Mlle', 'Ms', 'Mrs', 'Mme', 'Lady', 'the Countess', 'Dona', 'Mr', 'Dr', 'Major', 'Jonkheer', 'Col', 'Rev', 'Capt', 'Sir', 'Don', 'Master'] 這18種。 而其中有些出現次數甚少,這種狀況將不利於分析,因此我將其簡化成 ['Miss', 'Mrs', 'Mr', 'Master'] 4類。

def to_title(name):
    title = re.findall(r',(.*?)\.', name)[0].strip()
    if title in ['Miss', 'Mlle', 'Ms']:
        return 'Miss'
    elif title in ['Mrs', 'Mme', 'Lady', 'the Countess', 'Dona']:
        return 'Mrs'
    elif title in ['Mr', 'Dr', 'Major', 'Jonkheer', 'Col', 'Rev', 'Capt', 'Sir', 'Don']:
        return 'Mr'
    else:
        return title

df['title'] = df['name'].map(to_title)

接著將其編碼成one hot格式

df = encode_one_hot(df, 'title')

處理boat欄位

boat欄位格式是一個字串,若有多個boat則以空白分開。因此我透過split將其欄位先轉換成list格式,存有各個boat。

def to_boat_id(boat):
    boat = str(boat).split(" ")
    return boat
df['boat'] = df['boat'].map(to_boat_id)

接著,我也把他轉換成one hot格式。另外,空值的部分會直接encode成boat_nan。

df = encode_one_hot_list(df, 'boat')

家庭欄位

與家庭相關的欄位有:

我將其合併為單一一個familySize欄位,結果也不受其影響,故直接將其簡化。

df['familySize'] = df['sibsp'] + df['parch']

其他one hot處理

df = encode_one_hot(df, 'pclass')
df = encode_one_hot(df, 'embarked')

其他嘗試紀錄

  1. 我曾對cabin欄位進行處理過,但是我發現對準確率並無助益,推測是因為test資料中有cabin欄位的資料不多,因此反而會造成誤判。
  2. 原本並沒有對pclass進行處理,但我想pclass並不完全算是漸進的資料欄位,三種倉等是截然不同的。因此,我採用one hot的形式將其完全分開來看,準確率確實有提升。

最終處理

result = df.drop(['name', 'sibsp', 'parch'],
                 axis=1).as_matrix()

將其已不必要的欄位刪除,並將dataframe轉為matrix形式儲存。

minmax_scale = preprocessing.MinMaxScaler(feature_range=(0, 1))

train_feature = minmax_scale.fit_transform(result[:train_count])
test_feature = minmax_scale.fit_transform(result[train_count:])

將其數值標準化為0~1

train_label = train_df['survived']

train_count = len(train_df)

Model

架構總覽

model = Sequential()
model.add(Dense(10, input_dim=train_feature.shape[1], activation='relu'))
model.add(Dropout(0.3))
model.add(Dense(7, activation='relu'))
model.add(Dropout(0.4))
model.add(Dense(4, activation='relu'))
model.add(Dropout(0.3))
model.add(Dense(1, activation='sigmoid'))
rmsprop = RMSprop(lr=0.001, rho=0.9, epsilon=1e-08, decay=0.0)
model.compile(optimizer=rmsprop, loss='binary_crossentropy',
              metrics=['accuracy'])
model.fit(train_feature, train_label, epochs=50, batch_size=5)

疊了四層layer,並在每層中間分別dropout掉0.3, 0.4, 0.3。 訓練時的epochs設為50,batch_size設為5。

fit參數調整

註:此處Dropout並未調整成最佳狀態。

大略嘗試調整了4次

epochs batch_size Score
50 10 0.95469
50 5 0.95792
50 2 0.95792
50 1 0.95469

似乎沒有明顯影響,參數的調整只改變了一筆測試資料的結果而已。

但順帶一提,我也曾試著把epochs直接調到10000給電腦跑個10幾分鐘,結果分數反而超低,僅僅拿到0.54692 我猜想,大概是如下所述的過度擬合問題吧?

過度擬合

過度擬合會導致預測成果反而不準確,而Dropout函數可以解決此問題。 一開始並沒有在每層中間加入Dropout,正確率約為0.93上下,而之後加入Dropout後,最高可提升到0.96以上,也就是現在的成果。 以下是Dropout調參數的紀錄:

Dropout 2 Dropout 1 Dropout 3 Score
0.3 0.3 0.3 0.95469
0.3 0.3 0.2 0.95145
0.4 0.3 0.3 0.96440 (Highest)
0.4 0.4 0.4 0.94498
0.4 0.4 0.3 0.95469

結語

  1. 我覺得整理資料蠻好玩的,一直在找奇怪的特徵出來玩
  2. 猜參數蠻麻煩的,要有點耐心,不過這次資料是還好啦,比較小

喵。(ˊ・ω・ˋ)

Discussion and feedback