1. 程式人生 > >Kaggle競賽(1)——Tantic泰坦尼克之災

Kaggle競賽(1)——Tantic泰坦尼克之災

1.引言

先說一句,年末雙十一什麼的一來,真是非(mang)常(cheng)歡(gou)樂(le)!然後push自己抽出時間來寫這篇blog的原因也非常簡單:

  • 寫完前兩篇邏輯迴歸的介紹和各個角度理解之後,我們討論群(戳我入群)的小夥伴們紛紛表示『好像很高階的樣紙,but 然並卵 啊!你們倒是拿點實際資料來給我們看看,這玩意兒 有!什!麼!用!啊!』
  • talk is cheap, show me the code!
  • no example say a jb!

OK,OK,這就來了咯,同學們彆著急,我們先找個簡單的實際例子,來看看,所謂的資料探勘或者機器學習實際應用到底是怎麼樣一個過程。

『喂,那幾個說要看大資料上機器學習應用的,對,就是說你們!彆著急好麼,我們之後拉點大一點實際資料用liblinear或者spark,MLlib跑給你們看,行不行?咱們先拿個例項入入門嘛』

好了,我是一個嚴肅的技術研究和分享者,咳咳,不能廢話了,各位同學繼續往下看吧!

2.背景

2.1 關於Kaggle

  • 親,逼格這麼高的地方,你一定聽過對不對?是!這就是那個無數『資料探勘先驅』們,在回答”槍我有了,哪能找到靶子練練手啊?”時候的答案!
  • 這是一個要資料有資料,要實際應用場景有場景,要一起在資料探勘領域high得不要不要的小夥伴就有小夥伴的地方啊!!!

艾瑪,逗逼模式開太猛了。恩,不鬧,不鬧,說正事,Kaggle是一個數據分析建模的應用競賽平臺,有點類似

KDD-CUP(國際知識發現和資料探勘競賽),企業或者研究者可以將問題背景、資料、期望指標等釋出到Kaggle上,以競賽的形式向廣大的資料科學家徵集解決方案。而熱愛數(dong)據(shou)挖(zhe)掘(teng)的小夥伴們可以下載/分析資料,使用統計/機器學習/資料探勘等知識,建立演算法模型,得出結果並提交,排名top的可能會有獎金哦!

2.2 關於泰坦尼克號之災

  • 帶大家去該問題頁面溜達一圈吧

    • 下面是問題背景頁
      泰坦尼克號問題背景頁
    • 下面是可下載Data的頁面
      Data頁面
    • 下面是小夥伴們最愛的forum頁面,你會看到各種神級人物厲(qi)害(pa)的資料處理/建模想法,你會直視『世界真奇妙』。
      論壇頁面
  • 泰坦尼克號問題之背景

    • 就是那個大家都熟悉的『Jack and Rose』的故事,豪華遊艇倒了,大家都驚恐逃生,可是救生艇的數量有限,無法人人都有,副船長髮話了『lady and kid first!』,所以是否獲救其實並非隨機,而是基於一些背景有rank先後的

    • 訓練和測試資料是一些乘客的個人資訊以及存活狀況,要嘗試根據它生成合適的模型並預測其他人的存活狀況

    • 對,這是一個二分類問題,是我們之前討論的logistic regression所能處理的範疇。

3.說明

接觸過Kaggle的同學們可能知道這個問題,也可能知道RandomForest和SVM等等演算法,甚至還對多個模型做過融合,取得過非常好的結果,那maybe這篇文章並不是針對你的,你可以自行略過。

我們因為之前只介紹了Logistic Regression這一種分類演算法。所以本次的問題解決過程和優化思路,都集中在這種演算法上。其餘的方法可能我們之後的文章裡會提到。

說點個人的觀點。不一定正確。
『解決一個問題的方法和思路不止一種』
『沒有所謂的機器學習演算法優劣,也沒有絕對高效能的機器學習演算法,只有在特定的場景、資料和特徵下更合適的機器學習演算法。』

4.怎麼做?

手把手教程馬上就來,先來兩條我看到的,覺得很重要的經驗。

  1. 印象中Andrew Ng老師似乎在coursera上說過,應用機器學習,千萬不要一上來就試圖做到完美,先擼一個baseline的model出來,再進行後續的分析步驟,一步步提高,所謂後續步驟可能包括『分析model現在的狀態(欠/過擬合),分析我們使用的feature的作用大小,進行feature selection,以及我們模型下的bad case和產生的原因』等等。

  2. Kaggle上的大神們,也分享過一些experience,說幾條我記得的哈:

    • 『對資料的認識太重要了!』
    • 『資料中的特殊點/離群點的分析和處理太重要了!』
    • 『特徵工程(feature engineering)太重要了!在很多Kaggle的場景下,甚至比model本身還要重要』
    • 『要做模型融合(model ensemble)啊啊啊!』

更多的經驗分享請加討論群,具體方式請聯絡作者,或者參見《“ML學分計劃”說明書》

5.初探資料

先看看我們的資料,長什麼樣吧。在Data下我們train.csv和test.csv兩個檔案,分別存著官方給的訓練和測試資料。

import pandas as pd #資料分析
import numpy as np #科學計算
from pandas import Series,DataFrame

data_train = pd.read_csv("/Users/Hanxiaoyang/Titanic_data/Train.csv")
data_train
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6

pandas是常用的python資料處理包,把csv檔案讀入成dataframe各式,我們在ipython notebook中,看到data_train如下所示:

訓練資料

這就是典型的dataframe格式,如果你沒接觸過這種格式,完全沒有關係,你就把它想象成Excel裡面的列好了。
我們看到,總共有12列,其中Survived欄位表示的是該乘客是否獲救,其餘都是乘客的個人資訊,包括:

  • PassengerId => 乘客ID
  • Pclass => 乘客等級(1/2/3等艙位)
  • Name => 乘客姓名
  • Sex => 性別
  • Age => 年齡
  • SibSp => 堂兄弟/妹個數
  • Parch => 父母與小孩個數
  • Ticket => 船票資訊
  • Fare => 票價
  • Cabin => 客艙
  • Embarked => 登船港口

逐條往下看,要看完這麼多條,眼睛都有一種要瞎的趕腳。好吧,我們讓dataframe自己告訴我們一些資訊,如下所示:

data_train.info()
  • 1

看到了如下的資訊:
資料資訊

上面的資料說啥了?它告訴我們,訓練資料中總共有891名乘客,但是很不幸,我們有些屬性的資料不全,比如說:

  • Age(年齡)屬性只有714名乘客有記錄
  • Cabin(客艙)更是隻有204名乘客是已知的

似乎資訊略少啊,想再瞄一眼具體資料數值情況呢?恩,我們用下列的方法,得到數值型資料的一些分佈(因為有些屬性,比如姓名,是文字型;而另外一些屬性,比如登船港口,是類目型。這些我們用下面的函式是看不到的):

數值型資料基本資訊

我們從上面看到更進一步的什麼資訊呢?
mean欄位告訴我們,大概0.383838的人最後獲救了,2/3等艙的人數比1等艙要多,平均乘客年齡大概是29.7歲(計算這個時候會略掉無記錄的)等等…

6.資料初步分析

每個乘客都這麼多屬性,那我們咋知道哪些屬性更有用,而又應該怎麼用它們啊?說實話這會兒我也不知道,但我們記得前面提到過

  • 『對資料的認識太重要了!』
  • 『對資料的認識太重要了!』
  • 『對資料的認識太重要了!』

重要的事情說三遍,恩,說完了。僅僅最上面的對資料瞭解,依舊無法給我們提供想法和思路。我們再深入一點來看看我們的資料,看看每個/多個 屬性和最後的Survived之間有著什麼樣的關係呢。

6.1 乘客各屬性分佈

腦容量太有限了…數值看花眼了。我們還是統計統計,畫些圖來看看屬性和結果之間的關係好了,程式碼如下:

import matplotlib.pyplot as plt
fig = plt.figure()
fig.set(alpha=0.2)  # 設定圖表顏色alpha引數

plt.subplot2grid((2,3),(0,0))             # 在一張大圖裡分列幾個小圖
data_train.Survived.value_counts().plot(kind='bar')# 柱狀圖 
plt.title(u"獲救情況 (1為獲救)") # 標題
plt.ylabel(u"人數")  

plt.subplot2grid((2,3),(0,1))
data_train.Pclass.value_counts().plot(kind="bar")
plt.ylabel(u"人數")
plt.title(u"乘客等級分佈")

plt.subplot2grid((2,3),(0,2))
plt.scatter(data_train.Survived, data_train.Age)
plt.ylabel(u"年齡")                         # 設定縱座標名稱
plt.grid(b=True, which='major', axis='y') 
plt.title(u"按年齡看獲救分佈 (1為獲救)")


plt.subplot2grid((2,3),(1,0), colspan=2)
data_train.Age[data_train.Pclass == 1].plot(kind='kde')   
data_train.Age[data_train.Pclass == 2].plot(kind='kde')
data_train.Age[data_train.Pclass == 3].plot(kind='kde')
plt.xlabel(u"年齡")# plots an axis lable
plt.ylabel(u"密度") 
plt.title(u"各等級的乘客年齡分佈")
plt.legend((u'頭等艙', u'2等艙',u'3等艙'),loc='best') # sets our legend for our graph.


plt.subplot2grid((2,3),(1,2))
data_train.Embarked.value_counts().plot(kind='bar')
plt.title(u"各登船口岸上船人數")
plt.ylabel(u"人數")  
plt.show()
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11
  • 12
  • 13
  • 14
  • 15
  • 16
  • 17
  • 18
  • 19
  • 20
  • 21
  • 22
  • 23
  • 24
  • 25
  • 26
  • 27
  • 28
  • 29
  • 30
  • 31
  • 32
  • 33
  • 34
  • 35
  • 36

資料基本資訊圖示

bingo,圖還是比數字好看多了。所以我們在圖上可以看出來,被救的人300多點,不到半數;3等艙乘客灰常多;遇難和獲救的人年齡似乎跨度都很廣;3個不同的艙年齡總體趨勢似乎也一致,2/3等艙乘客20歲多點的人最多,1等艙40歲左右的最多(→_→似乎符合財富和年齡的分配哈,咳咳,別理我,我瞎扯的);登船港口人數按照S、C、Q遞減,而且S遠多於另外倆港口。

這個時候我們可能會有一些想法了:

  • 不同艙位/乘客等級可能和財富/地位有關係,最後獲救概率可能會不一樣
  • 年齡對獲救概率也一定是有影響的,畢竟前面說了,副船長還說『小孩和女士先走』呢
  • 和登船港口是不是有關係呢?也許登船港口不同,人的出身地位不同?

口說無憑,空想無益。老老實實再來統計統計,看看這些屬性值的統計分佈吧。

6.2 屬性與獲救結果的關聯統計

#看看各乘客等級的獲救情況
fig = plt.figure()
fig.set(alpha=0.2)  # 設定圖表顏色alpha引數

Survived_0 = data_train.Pclass[data_train.Survived == 0].value_counts()
Survived_1 = data_train.Pclass[data_train.Survived == 1].value_counts()
df=pd.DataFrame({u'獲救':Survived_1, u'未獲救':Survived_0})
df.plot(kind='bar', stacked=True)
plt.title(u"各乘客等級的獲救情況")
plt.xlabel(u"乘客等級") 
plt.ylabel(u"人數") 
plt.show()
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11
  • 12

各乘客等級的獲救情況

嘖嘖,果然,錢和地位對艙位有影響,進而對獲救的可能性也有影響啊←_←
咳咳,跑題了,我想說的是,明顯等級為1的乘客,獲救的概率高很多。恩,這個一定是影響最後獲救結果的一個特徵。

#看看各性別的獲救情況
fig = plt.figure()
fig.set(alpha=0.2)  # 設定圖表顏色alpha引數

Survived_m = data_train.Survived[data_train.Sex == 'male'].value_counts()
Survived_f = data_train.Survived[data_train.Sex == 'female'].value_counts()
df=pd.DataFrame({u'男性':Survived_m, u'女性':Survived_f})
df.plot(kind='bar', stacked=True)
plt.title(u"按性別看獲救情況")
plt.xlabel(u"性別") 
plt.ylabel(u"人數")
plt.show()
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11
  • 12

各乘客等級的獲救情況

歪果盆友果然很尊重lady,lady first踐行得不錯。性別無疑也要作為重要特徵加入最後的模型之中。

再來個詳細版的好了。


 #然後我們再來看看各種艙級別情況下各性別的獲救情況
fig=plt.figure()
fig.set(alpha=0.65) # 設定影象透明度,無所謂
plt.title(u"根據艙等級和性別的獲救情況")

ax1=fig.add_subplot(141)
data_train.Survived[data_train.Sex == 'female'][data_train.Pclass != 3].value_counts().plot(kind='bar', label="female highclass", color='#FA2479')
ax1.set_xticklabels([u"獲救", u"未獲救"], rotation=0)
ax1.legend([u"女性/高階艙"], loc='best')

ax2=fig.add_subplot(142, sharey=ax1)
data_train.Survived[data_train.Sex == 'female'][data_train.Pclass == 3].value_counts().plot(kind='bar', label='female, low class', color='pink')
ax2.set_xticklabels([u"未獲救", u"獲救"], rotation=0)
plt.legend([u"女性/低階艙"], loc='best')

ax3=fig.add_subplot(143, sharey=ax1)
data_train.Survived[data_train.Sex == 'male'][data_train.Pclass != 3].value_counts().plot(kind='bar', label='male, high class',color='lightblue')
ax3.set_xticklabels([u"未獲救", u"獲救"], rotation=0)
plt.legend([u"男性/高階艙"], loc='best')

ax4=fig.add_subplot(144, sharey=ax1)
data_train.Survived[data_train.Sex == 'male'][data_train.Pclass == 3].value_counts().plot(kind='bar', label='male low class', color='steelblue')
ax4.set_xticklabels([u"未獲救", u"獲救"], rotation=0)
plt.legend([u"男性/低階艙"], loc='best')

plt.show()
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11
  • 12
  • 13
  • 14
  • 15
  • 16
  • 17
  • 18
  • 19
  • 20
  • 21
  • 22
  • 23
  • 24
  • 25
  • 26
  • 27
  • 28

各性別和艙位的獲救情況

恩,堅定了之前的判斷。

我們看看各登船港口的獲救情況。

fig = plt.figure()
fig.set(alpha=0.2)  # 設定圖表顏色alpha引數

Survived_0 = data_train.Embarked[data_train.Survived == 0].value_counts()
Survived_1 = data_train.Embarked[data_train.Survived == 1].value_counts()
df=pd.DataFrame({u'獲救':Survived_1, u'未獲救':Survived_0})
df.plot(kind='bar', stacked=True)
plt.title(u"各登入港口乘客的獲救情況")
plt.xlabel(u"登入港口") 
plt.ylabel(u"人數") 

plt.show()
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11
  • 12

各登船港口的獲救情況

下面我們來看看 堂兄弟/妹,孩子/父母有幾人,對是否獲救的影響。


g = data_train.groupby(['SibSp','Survived'])
df = pd.DataFrame(g.count()['PassengerId'])
print df

g = data_train.groupby(['SibSp','Survived'])
df = pd.DataFrame(g.count()['PassengerId'])
print df
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9

堂兄弟/妹影響

父母/孩子影響

好吧,沒看出特別特別明顯的規律(為自己的智商感到捉急…),先作為備選特徵,放一放。


#ticket是船票編號,應該是unique的,和最後的結果沒有太大的關係,先不納入考慮的特徵範疇把
#cabin只有204個乘客有值,我們先看看它的一個分佈
data_train.Cabin.value_counts()
  • 1
  • 2
  • 3
  • 4
  • 5

部分結果如下:
Cabin分佈

這三三兩兩的…如此不集中…我們猜一下,也許,前面的ABCDE是指的甲板位置、然後編號是房間號?…好吧,我瞎說的,別當真…

關鍵是Cabin這鬼屬性,應該算作類目型的,本來缺失值就多,還如此不集中,註定是個棘手貨…第一感覺,這玩意兒如果直接按照類目特徵處理的話,太散了,估計每個因子化後的特徵都拿不到什麼權重。加上有那麼多缺失值,要不我們先把Cabin缺失與否作為條件(雖然這部分資訊缺失可能並非未登記,maybe只是丟失了而已,所以這樣做未必妥當),先在有無Cabin資訊這個粗粒度上看看Survived的情況好了。


fig = plt.figure()
fig.set(alpha=0.2)  # 設定圖表顏色alpha引數

Survived_cabin = data_train.Survived[pd.notnull(data_train.Cabin)].value_counts()
Survived_nocabin = data_train.Survived[pd.isnull(data_train.Cabin)].value_counts()
df=pd.DataFrame({u'有':Survived_cabin, u'無':Survived_nocabin}).transpose()
df.plot(kind='bar', stacked=True)
plt.title(u"按Cabin有無看獲救情況")
plt.xlabel(u"Cabin有無") 
plt.ylabel(u"人數")
plt.show()
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11
  • 12
  • 13

有無Cabin記錄影響

咳咳,有Cabin記錄的似乎獲救概率稍高一些,先這麼著放一放吧。

7.簡單資料預處理

大體資料的情況看了一遍,對感興趣的屬性也有個大概的瞭解了。
下一步幹啥?咱們該處理處理這些資料,為機器學習建模做點準備了。

對了,我這裡說的資料預處理,其實就包括了很多Kaggler津津樂道的feature engineering過程,灰常灰常有必要!

『特徵工程(feature engineering)太重要了!』
『特徵工程(feature engineering)太重要了!』
『特徵工程(feature engineering)太重要了!』

恩,重要的事情說三遍。

先從最突出的資料屬性開始吧,對,Cabin和Age,有丟失資料實在是對下一步工作影響太大。

先說Cabin,暫時我們就按照剛才說的,按Cabin有無資料,將這個屬性處理成Yes和No兩種型別吧。

再說Age:

通常遇到缺值的情況,我們會有幾種常見的處理方式

  • 如果缺值的樣本佔總數比例極高,我們可能就直接捨棄了,作為特徵加入的話,可能反倒帶入noise,影響最後的結果了
  • 如果缺值的樣本適中,而該屬性非連續值特徵屬性(比如說類目屬性),那就把NaN作為一個新類別,加到類別特徵中
  • 如果缺值的樣本適中,而該屬性為連續值特徵屬性,有時候我們會考慮給定一個step(比如這裡的age,我們可以考慮每隔2/3歲為一個步長),然後把它離散化,之後把NaN作為一個type加到屬性類目中。
  • 有些情況下,缺失的值個數並不是特別多,那我們也可以試著根據已有的值,擬合一下資料,補充上。

本例中,後兩種處理方式應該都是可行的,我們先試試擬合補全吧(雖然說沒有特別多的背景可供我們擬合,這不一定是一個多麼好的選擇)

我們這裡用scikit-learn中的RandomForest來擬合一下缺失的年齡資料(注:RandomForest是一個用在原始資料中做不同取樣,建立多顆DecisionTree,再進行average等等來降低過擬合現象,提高結果的機器學習演算法,我們之後會介紹到)


from sklearn.ensemble import RandomForestRegressor

### 使用 RandomForestClassifier 填補缺失的年齡屬性
defset_missing_ages(df):

    # 把已有的數值型特徵取出來丟進Random Forest Regressor中
    age_df = df[['Age','Fare', 'Parch', 'SibSp', 'Pclass']]

    # 乘客分成已知年齡和未知年齡兩部分
    known_age = age_df[age_df.Age.notnull()].as_matrix()
    unknown_age = age_df[age_df.Age.isnull()].as_matrix()

    # y即目標年齡
    y = known_age[:, 0]

    # X即特徵屬性值
    X = known_age[:, 1:]

    # fit到RandomForestRegressor之中
    rfr = RandomForestRegressor(random_state=0, n_estimators=2000, n_jobs=-1)
    rfr.fit(X, y)

    # 用得到的模型進行未知年齡結果預測
    predictedAges = rfr.predict(unknown_age[:, 1::])

    # 用得到的預測結果填補原缺失資料
    df.loc[ (df.Age.isnull()), 'Age' ] = predictedAges 

    return df, rfr

defset_Cabin_type(df):
    df.loc[ (df.Cabin.notnull()), 'Cabin' ] = "Yes"
    df.loc[ (df.Cabin.isnull()), 'Cabin' ] = "No"
    return df

data_train, rfr = set_missing_ages(data_train)
data_train = set_Cabin_type(data_train)
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11
  • 12
  • 13
  • 14
  • 15
  • 16
  • 17
  • 18
  • 19
  • 20
  • 21
  • 22
  • 23
  • 24
  • 25
  • 26
  • 27
  • 28
  • 29
  • 30
  • 31
  • 32
  • 33
  • 34
  • 35
  • 36
  • 37
  • 38
  • 39

處理Cabin和Age之後

恩。目的達到,OK了。

因為邏輯迴歸建模時,需要輸入的特徵都是數值型特徵,我們通常會先對類目型的特徵因子化。
什麼叫做因子化呢?舉個例子:

以Cabin為例,原本一個屬性維度,因為其取值可以是[‘yes’,’no’],而將其平展開為’Cabin_yes’,’Cabin_no’兩個屬性

  • 原本Cabin取值為yes的,在此處的”Cabin_yes”下取值為1,在”Cabin_no”下取值為0
  • 原本Cabin取值為no的,在此處的”Cabin_yes”下取值為0,在”Cabin_no”下取值為1

我們使用pandas的”get_dummies”來完成這個工作,並拼接在原來的”data_train”之上,如下所示。


dummies_Cabin = pd.get_dummies(data_train['Cabin'], prefix= 'Cabin')

dummies_Embarked = pd.get_dummies(data_train['Embarked'], prefix= 'Embarked')

dummies_Sex = pd.get_dummies(data_train['Sex'], prefix= 'Sex')

dummies_Pclass = pd.get_dummies(data_train['Pclass'], prefix= 'Pclass')

df = pd.concat([data_train, dummies_Cabin, dummies_Embarked, dummies_Sex, dummies_Pclass], axis=1)
df.drop(['Pclass', 'Name', 'Sex', 'Ticket', 'Cabin', 'Embarked'], axis=1, inplace=True)
df
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11
  • 12
  • 13

離散/因子化之後

bingo,我們很成功地把這些類目屬性全都轉成0,1的數值屬性了。

這樣,看起來,是不是我們需要的屬性值都有了,且它們都是數值型屬性呢。

有一種臨近結果的寵寵欲動感吧,莫急莫急,我們還得做一些處理,仔細看看Age和Fare兩個屬性,乘客的數值幅度變化,也忒大了吧!!如果大家瞭解邏輯迴歸與梯度下降的話,會知道,各屬性值之間scale差距太大,將對收斂速度造成幾萬點傷害值!甚至不收斂! (╬▔皿▔)…所以我們先用scikit-learn裡面的preprocessing模組對這倆貨做一個scaling,所謂scaling,其實就是將一些變化幅度較大的特徵化到[-1,1]之內。

import sklearn.preprocessing as preprocessing
scaler = preprocessing.StandardScaler()
age_scale_param = scaler.fit(df['Age'])
df['Age_scaled'] = scaler.fit_transform(df['Age'], age_scale_param)
fare_scale_param = scaler.fit(df['Fare'])
df['Fare_scaled'] = scaler.fit_transform(df['Fare'], fare_scale_param)
df
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7

scaling

恩,好看多了,萬事俱備,只欠建模。馬上就要看到成效了,哈哈。我們把需要的屬性值抽出來,轉成scikit-learn裡面LogisticRegression可以處理的格式。

8.邏輯迴歸建模

我們把需要的feature欄位取出來,轉成numpy格式,使用scikit-learn中的LogisticRegression建模。

from sklearn import linear_model

# 用正則取出我們要的屬性值
train_df = df.filter(regex='Survived|Age_.*|SibSp|Parch|Fare_.*|Cabin_.*|Embarked_.*|Sex_.*|Pclass_.*')
train_np = train_df.as_matrix()

# y即Survival結果
y = train_np[:, 0]

# X即特徵屬性值
X = train_np[:, 1:]

# fit到RandomForestRegressor之中
clf = linear_model.LogisticRegression(C=1.0, penalty='l1', tol=1e-6)
clf.fit(X, y)

clf
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11
  • 12
  • 13
  • 14
  • 15
  • 16
  • 17

good,很順利,我們得到了一個model,如下:
modeling

先淡定!淡定!你以為把test.csv直接丟進model裡就能拿到結果啊…騷年,圖樣圖森破啊!我們的”test_data”也要做和”train_data”一樣的預處理啊!!


data_test = pd.read_csv("/Users/Hanxiaoyang/Titanic_data/test.csv")
data_test.loc[ (data_test.Fare.isnull()), 'Fare' ] = 0
# 接著我們對test_data做和train_data中一致的特徵變換
# 首先用同樣的RandomForestRegressor模型填上丟失的年齡
tmp_df = data_test[['Age','Fare', 'Parch', 'SibSp', 'Pclass']]
null_age = tmp_df[data_test.Age.isnull()].as_matrix()
# 根據特徵屬性X預測年齡並補上
X = null_age[:, 1:]
predictedAges = rfr.predict(X)
data_test.loc[ (data_test.Age.isnull()), 'Age' ] = predictedAges

data_test = set_Cabin_type(data_test)
dummies_Cabin = pd.get_dummies(data_test['Cabin'], prefix= 'Cabin')
dummies_Embarked = pd.get_dummies(data_test['Embarked'], prefix= 'Embarked')
dummies_Sex = pd.get_dummies(data_test['Sex'], prefix= 'Sex')
dummies_Pclass = pd.get_dummies(data_test['Pclass'], prefix= 'Pclass')


df_test = pd.concat([data_test, dummies_Cabin, dummies_Embarked, dummies_Sex, dummies_Pclass], axis=1)
df_test.drop(['Pclass', 'Name', 'Sex', 'Ticket', 'Cabin', 'Embarked'], axis=1, inplace=True)
df_test['Age_scaled'] = scaler.fit_transform(df_test['Age'], age_scale_param)
df_test['Fare_scaled'] = scaler.fit_transform(df_test['Fare'], fare_scale_param)
df_test
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11
  • 12
  • 13
  • 14
  • 15
  • 16
  • 17
  • 18
  • 19
  • 20
  • 21
  • 22
  • 23
  • 24
  • 25

modeling

不錯不錯,資料很OK,差最後一步了。
下面就做預測取結果吧!!

test = df_test.filter(regex='Age_.*|SibSp|Parch|Fare_.*|Cabin_.*|Embarked_.*|Sex_.*|Pclass_.*')
predictions = clf.predict(test)
result = pd.DataFrame({'PassengerId':data_test['PassengerId'].as_matrix(), 'Survived':predictions.astype(np.int32)})
result.to_csv("/Users/Hanxiaoyang/Titanic_data/logistic_regression_predictions.csv", index=False)
  • 1
  • 2
  • 3
  • 4

預測結果

嘖嘖,挺好,格式正確,去make a submission啦啦啦!

在Kaggle的Make a submission頁面,提交上結果。如下:
Kaggle排名

0.76555,恩,結果還不錯。畢竟,這只是我們簡單分析處理過後出的一個baseline模型嘛。

9.邏輯迴歸系統優化

9.1 模型係數關聯分析

親,你以為結果提交上了,就完事了?
我不會告訴你,這只是萬里長征第一步啊(淚牛滿面)!!!這才剛擼完baseline model啊!!!還得優化啊!!!

看過Andrew Ng老師的machine Learning課程的同學們,知道,我們應該分析分析模型現在的狀態了,是過/欠擬合?,以確定我們需要更多的特徵還是更多資料,或者其他操作。我們有一條很著名的learning curves對吧。

不過在現在的場景下,先不著急做這個事情,我們這個baseline系統還有些粗糙,先再挖掘挖掘。

  • 首先,Name和Ticket兩個屬性被我們完整捨棄了(好吧,其實是因為這倆屬性,幾乎每一條記錄都是一個完全不同的值,我們並沒有找到很直接的處理方式)。

  • 然後,我們想想,年齡的擬合本身也未必是一件非常靠譜的事情,我們依據其餘屬性,其實並不能很好地擬合預測出未知的年齡。再一個,以我們的日常經驗,小盆友和老人可能得到的照顧會多一些,這樣看的話,年齡作為一個連續值,給一個固定的係數,應該和年齡是一個正相關或者負相關,似乎體現不出兩頭受照顧的實際情況,所以,說不定我們把年齡離散化,按區段分作類別屬性會更合適一些。

上面只是我瞎想的,who knows是不是這麼回事呢,老老實實先把得到的model係數和feature關聯起來看看。

pd.DataFrame({"columns":list(train_df.columns)[1:], "coef":list(clf.coef_.T)})
  • 1

LR模型係數

首先,大家回去前兩篇文章裡瞄一眼公式就知道,這些係數為正的特徵,和最後結果是一個正相關,反之為負相關。

我們先看看那些權重絕對值非常大的feature,在我們的模型上:

  • Sex屬性,如果是female會極大提高最後獲救的概率,而male會很大程度拉低這個概率。
  • Pclass屬性,1等艙乘客最後獲救的概率會上升,而乘客等級為3會極大地拉低這個概率。
  • 有Cabin值會很大程度拉昇最後獲救概率(這裡似乎能看到了一點端倪,事實上從最上面的有無Cabin記錄的Survived分佈圖上看出,即使有Cabin記錄的乘客也有一部分遇難了,估計這個屬性上我們挖掘還不夠)
  • Age是一個負相關,意味著在我們的模型裡,年齡越小,越有獲救的優先權(還得回原資料看看這個是否合理
  • 有一個登船港口S會很大程度拉低獲救的概率,另外倆港口壓根就沒啥作用(這個實際上非常奇怪,因為我們從之前的統計圖上並沒有看到S港口的獲救率非常低,所以也許可以考慮把登船港口這個feature去掉試試)。
  • 船票Fare有小幅度的正相關(並不意味著這個feature作用不大,有可能是我們細化的程度還不夠,舉個例子,說不定我們得對它離散化,再分至各個乘客等級上?)

噢啦,觀察完了,我們現在有一些想法了,但是怎麼樣才知道,哪些優化的方法是promising的呢?

因為test.csv裡面並沒有Survived這個欄位(好吧,這是廢話,這明明就是我們要預測的結果),我們無法在這份資料上評定我們演算法在該場景下的效果…

而『每做一次調整就make a submission,然後根據結果來判定這次調整的好壞』其實是行不通的…

9.2 交叉驗證

重點又來了:

『要做交叉驗證(cross validation)!』
『要做交叉驗證(cross validation)!』
『要做交叉驗證(cross validation)!』

恩,重要的事情說三遍。我們通常情況下,這麼做cross validation:把train.csv分成兩部分,一部分用於訓練我們需要的模型,另外一部分資料上看我們預測演算法的效果。

我們用scikit-learn的cross_validation來幫我們完成小資料集上的這個工作。

先簡單看看cross validation情況下的打分

from sklearn import cross_validation

 #簡單看看打分情況
clf = linear_model.LogisticRegression(C=1.0, penalty='l1', tol=1e-6)
all_data = df.filter(regex='Survived|Age_.*|SibSp|Parch|Fare_.*|Cabin_.*|Embarked_.*|Sex_.*|Pclass_.*')
X = all_data.as_matrix()[:,1:]
y = all_data.as_matrix()[:,0]
print cross_validation.cross_val_score(clf, X, y, cv=5)
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8

結果是下面醬紫的:
[0.81564246 0.81005587 0.78651685 0.78651685 0.81355932]

似乎比Kaggle上的結果略高哈,畢竟用的是不是同一份資料集評估的。

等等,既然我們要做交叉驗證,那我們乾脆先把交叉驗證裡面的bad case拿出來看看,看看人眼稽核,是否能發現什麼蛛絲馬跡,是我們忽略了哪些資訊,使得這些乘客被判定錯了。再把bad case上得到的想法和前頭係數分析的合在一起,然後逐個試試。

下面我們做資料分割,並且在原始資料集上瞄一眼bad case:

# 分割資料,按照 訓練資料:cv資料 = 7:3的比例
split_train, split_cv = cross_validation.train_test_split(df, test_size=0.3, random_state=0)
train_df = split_train.filter(regex='Survived|Age_.*|SibSp|Parch|Fare_.*|Cabin_.*|Embarked_.*|Sex_.*|Pclass_.*')
# 生成模型
clf = linear_model.LogisticRegression(C=1.0, penalty='l1', tol=1e-6)
clf.fit(train_df.as_matrix()[:,1:], train_df.as_matrix()[:,0])

# 對cross validation資料進行預測

cv_df = split_cv.filter(regex='Survived|Age_.*|SibSp|Parch|Fare_.*|Cabin_.*|Embarked_.*|Sex_.*|Pclass_.*')
predictions = clf.predict(cv_df.as_matrix()[:,1:])

origin_data_train = pd.read_csv("/Users/HanXiaoyang/Titanic_data/Train.csv")
bad_cases = origin_data_train.loc[origin_data_train['PassengerId'].isin(split_cv[predictions != cv_df.as_matrix()[:,0]]['PassengerId'].values)]
bad_cases
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11
  • 12
  • 13
  • 14