機器學習——用邏輯回歸及隨機森林實現泰坦尼克號的生存預測


1.實驗背景

本次實驗是Kaggle上的一個入門比賽——Titanic: Machine Learning from Disaster。比賽選擇了泰坦尼克號海難作為背景,並提供了樣本數據及測試數據,要求我們根據樣本數據內容建立一個預測模型,對於測試數據中每個人是否獲救做個預測。樣本數據包括891條乘客信息及獲救情況,測試數據有418條乘客信息。樣本數據的樣例如下:

  • Passenger:乘客唯一識別id
  • Survived:是否存活,0為否,1為是   
  • Pclass:船艙等級,1、2、3等 
  • Name:姓名
  • Sex:性別
  • Age:年齡 
  • SibSp:和該乘客一起旅行的兄弟姐妹和配偶的數量
  • Parch:和該乘客一起旅行的父母和孩子的數量
  • Ticket:船票號
  • Fare:船票價格
  • Cabin:船艙號
  • Embarked:登船港口 S=英國南安普頓Southampton(起航點) C=法國 瑟堡市Cherbourg(途經點) Q=愛爾蘭 昆士Queenstown(途經點) 

我們的目標就是根據上述字段信息以及乘客的獲救情況,實現一個預測乘客是否存活的模型。下面我們來看一下用到的模型。

2.模型簡介

通過分析案例,我們可以看出這是一個很明顯的二分類問題,即判斷乘客是否遇難,關於二分類的模型很多,這里用到了三種模型:線性回歸模型、邏輯回歸模型跟隨機森林模型。本次實驗也參考了 寒小陽的CSDN博客以及網頁雲課堂的免費公開課。下面就對這三個模型進行介紹。

線性回歸模型

線性回歸模型很簡單,可以看成多項式方程的擬合問題。只有一個自變量,稱為一元線性回歸;有多於一個的變量,稱為多元線性回歸。對於一元線性回歸,經常采用最小二乘的方法擬合出一條最逼近各點的曲線,如下圖所示:

多元線性回歸自變量不止一個,形式如式子:Y = a1X1+a2X2+a3X3+a4X4+a5X5+.....+anXn。一元線性回歸是找一條擬合直線,而對於多元線性回歸則是找到一個超平面,使這個超平面距離各點的距離最小。

其實無論是一元線性回歸還是多元線性回歸,它們的通式是Y = ω‘X+b。通過訓練數據,找到最合適的w‘和b,也就實現了模型的求解。

這樣,我們輸入不同的自變量,就可以找到對應的因變量,達到預測的目的。

邏輯回歸模型

線性回歸存在一個最大的問題就是,它的自變量是連續變化的,是區間變量,而現實生活中很多變量不是連續的,例如屬性變量或者序列變量。在這里,像我們樣本數據中的年齡屬性就是序列變量,因為年齡正常情況下不存在小數,不可能我們說一個人12.25歲;數據中的船艙等級、性別、登船港口等都是屬性變量,這些變量的取值都是固定的。

所以對於現實生活中很多實例,線性回歸模型不再很適用,這時候就要考慮邏輯回歸模型。

其實邏輯回歸可以看做廣義的線性回歸,只不過它通過函數L把 w‘X+b對應一個隱狀態p,p = L(ω‘X+b),相當於對於結果Y又用一個函數L進行修飾得到L(Y)。如果沒有函數L則是線性回歸,如果函數L是多項式函數,就是多項式回歸,而如果L是logistic函數,就是logistic回歸。

logistic回歸的因變量可以是二分類的,也可以是多分類的,但是二分類的更為常用,也更加容易解釋,多類可以使用softmax方法進行處理。實際中最為常用的就是二分類的logistic回歸。

我們通常用0和1來表示二分類的結果,而ω‘X+b得到的值可能不是0-1范圍內,我們需要找到一個函數對於ω‘X+b結果進行處理,使其值在[0,1]里面。於是便找到Sigmoid Function作為我們的L函數,它的函數式如下:

 

Sigmoid Function的函數圖像如下,它的繪制函數是:

 1 import matplotlib.pyplot as plt
 2 import numpy as np
 3  
 4 def Sigmoid(x):
 5     return 1.0 / (1.0 + np.exp(-x))
 6  
 7 x= np.arange(-10, 10, 0.1)
 8 #Sigmoid函數
 9 h = Sigmoid(x)            
10 plt.plot(x, h)
11 #坐標軸上加一條豎直的線(0位置)
12 plt.axvline(0.0, color='k')   
13 plt.axhspan(0.0, 1.0, facecolor='1.0', alpha=1.0, ls='dotted')  
14 plt.axhline(y=0.5, ls='dotted', color='k') 
15 #y軸標度
16 plt.yticks([0.0, 0.5, 1.0]) 
17 #y軸范圍
18 plt.ylim(-0.1, 1.1)       
19 plt.show()  

 

 

 

我們又把L函數叫做激活函數,激活函數不只Sigmoid Function一種,還有tanh函數、ReLU函數等等,函數的選擇要根據適用場景來定。

我們通過Sigmoid Function把結果值變為[0,1]之間的數值,那么怎么才能求出最合適的ω'跟b呢?

對於單個樣本,這里就是每一個乘員數據,我們定義了損失函數(Loss Functon)來評價預測的結果。它的形式是這樣的:Ŀ(a,y),這里的a就是L(ω‘X+b),y則是預測的真值。Ŀ是一個衡量預測值與真值大小的函數,最常用的就是對數形式的函數,為:Ŀ(a,y) = -[yloga +(1-y)log(l-y)]。

而成本函數(Cost Function)則是所有樣本函數的加權求平均。我們要想求出最合適的ω'跟b,就要是成本函數的值最小,首先對ω'跟b賦予初值,這時候我們對成本函數求導,利用反向傳播,可以得到一次dω'跟db。

通過梯度下降法ω' = ω'+dω',b = b+db就得到更新后的ω'跟b。最后通過一步步的梯度下降,使得成本函數C的值最小,這時候我們就找到了最優的ω'跟b。如下圖所示,通過不斷調整ω'跟b,使得損失函數C到達最低點。

 

  

當然,我們的實現過程很簡單,只需要幾行代碼就可以搞定。

隨機森林模型

在談隨機森林模型前,需要理解決策樹模型。最簡單的一個決策樹只有一個分支,例如考到60分以上是及格,60分以下是不及格。當影像因素很多事,會根據這些因素建立很多決策層,最終得到結果。如下圖所示,就是一個決策樹:

決策樹模型很容易產生過擬合現象,模型泛化能力很弱。基於決策樹模型,又出現了隨機森林模型,通過選擇選擇任意數量的決策樹,通過從樣本數據數據中有放回的隨機抽取一些樣本去訓練這些決策樹,最終的結果是綜合所有決策樹的判斷給出最合理的決策。

說了這么多模型,其實模型的實現過程很簡單,因為python的sklearn庫已經把上面這些函數通通封裝好了,只需要調用即可。

3.數據預處理

在進行實驗之前,這里先說明一下用到的庫函數。pandas、sklearn、numpy以及繪圖庫函數matplotlib

機器學習的絕大多數運算是矩陣運算,需要輸入的數據是數值型。而我們這里很多數據是字符型,我們首先需要對於數據進行預處理。

首先讀入我們的數據:

 1 # 正則表達式模塊
 2 import re
 3 
 4 # 由於年齡中有空值,需要先用平均值對年齡的缺失值進行填充,因為矩陣運算只能是數值型,不能是字符串
 5 titanic['Age'] = titanic['Age'].fillna(titanic['Age'].mean())
 6 # 同理,由於Embarked(登船地點)里面也有空值,所以也需要用出現最多的類型對它進行一個填充
 7 titanic['Embarked'] = titanic['Embarked'].fillna('S')
 8 
 9 # 對於性別中的male與female,用0和1來表示。首先看性別是否只有兩個值
10 # 對於登船地點的三個值S C Q,也用0 1 2分別表示
11 # print(titanic['Sex'].unique())
12 # print(titanic['Embarked'].unique())
13 titanic.loc[titanic['Sex'] == 'male', 'Sex'] = 0
14 titanic.loc[titanic['Sex'] == 'female', 'Sex'] = 1
15 
16 titanic.loc[titanic['Embarked'] == 'S', 'Embarked'] = 0
17 titanic.loc[titanic['Embarked'] == 'C', 'Embarked'] = 1
18 titanic.loc[titanic['Embarked'] == 'Q', 'Embarked'] = 2
19 
20 # 加上其余的屬性特性
21 titanic["FamilySize"] = titanic["SibSp"] + titanic["Parch"]
22 
23 # 姓名的長度
24 titanic["NameLenght"] = titanic["Name"].apply(lambda x: len(x))
25 
26 
27 # 定義提取姓名中Mr以及Mrs等屬性
28 def get_title(name):
29     title_search = re.search(' ([A-Za-z]+)\.', name)
30     if title_search:
31         return title_search.group(1)
32     return ""
33 
34 
35 titles = titanic["Name"].apply(get_title)
36 # 對於姓名中的一些稱呼賦予不同的數值
37 title_mapping = {'Mr': 1, 'Miss': 2, 'Mrs': 3, 'Master': 4, 'Dr': 5, 'Rev': 6, 'Major': 7, 'Mlle': 8, 'Col': 9,
38                  'Capt': 10, 'Ms': 11, 'Don': 12, 'Jonkheer': 13, 'Countess': '14', 'Lady': 15, 'Sir': 16, 'Mme': 17}
39 for k,v in title_mapping.items():
40     titles[titles == k] = v
41 titanic['Titles'] = titles

這時候,我們需要畫圖看一下這些數據對於最后獲救的結果影響到底有多大。

首先是乘客船艙等級獲救情況統計:

 1 # 導入圖表函數
 2 import matplotlib.pyplot as plt
 3 from pylab import *
 4 # 圖表漢字正常顯示
 5 mpl.rcParams['font.sans-serif'] = ['SimHei']
 6 # 圖表負值正常顯示
 7 matplotlib.rcParams['axes.unicode_minus'] = False
 8 
 9 # 查看各等級乘客等級的獲救情況
10 fig = plt.figure()
11 # 設置圖表顏色的alpha參數
12 fig.set(alpha=0.2)
13 
14 Suvived_0 = titanic.Pclass[titanic.Survived == 0].value_counts()
15 Suvived_1 = titanic.Pclass[titanic.Survived == 1].value_counts()
16 df = pandas.DataFrame({u"獲救": Suvived_1, u"未獲救": Suvived_0})
17 df.plot(kind='bar', stacked=True)
18 plt.title(u'各乘客等級的獲救情況')
19 plt.xlabel(u'乘客等級')
20 plt.ylabel(u'人數')
21 plt.show()

再看一下不同性別的獲救情況:

 1 # 按性別分組
 2 fig = plt.figure()
 3 fig.set(alpha=0.2)
 4 
 5 Survived_m = titanic.Survived[titanic.Sex == 0].value_counts()
 6 Survived_f = titanic.Survived[titanic.Sex == 1].value_counts()
 7 df = pandas.DataFrame({u'男性': Survived_m, u'女性': Survived_f})
 8 df.plot(kind='bar', stacked=True)
 9 plt.title(u'不同性別獲救情況')
10 plt.xlabel(u'性別')
11 plt.ylabel(u'人數')
12 plt.show()

還有不同年齡的獲救情況統計:

1 # 不同年齡獲救情況
2 fig = plt.figure()
3 fig.set(alpha=0.2)
4 plt.scatter(titanic.Survived, titanic.Age)
5 plt.ylabel(u'年齡')
6 plt.grid(b=True, which='major', axis='y')
7 plt.title(u'不同年齡的獲救情況(1為獲救)')
8 plt.show()

不同港口登錄乘客的獲救情況:

 1 # 不同港口登錄乘客獲救情況
 2 fig = plt.figure()
 3 fig.set(alpha=0.2)
 4 Survived_0 = titanic.Embarked[titanic.Survived == 0].value_counts()
 5 Survived_1 = titanic.Embarked[titanic.Survived == 1].value_counts()
 6 df = pandas.DataFrame({u'獲救': Survived_1, u'未獲救': Survived_0})
 7 df.plot(kind='bar', stacked=True)
 8 plt.xlabel(u'登錄港口')
 9 plt.ylabel(u'人數')
10 plt.title(u'不同港口登錄乘客獲救情況')

 

 

 

 好吧,通過圖表分析,我們得知其中的一些要素對於是否獲救確實存在影像。此時我們已經獲得了10個影響屬性,分別是:Pclass", "Sex", "Age", "SibSp", "Parch", "Fare", "Embarked", "Titles", "FamilySize", "NameLenght"。

那么我們有必要對這10個字段分別計算一下重要度,也就是對於結果的影像。我們通過對於每個字段,隨機添加一些噪音值,通過對於結果值的影像來判斷相應的重要程度。例如一個屬性添加了很多噪音值,但是對於結果預測的准確率沒有太大影響,我們就認為這個屬性重要程度相對較低,反之亦然,統計代碼及結果如下圖所示:

 1 # 各特征屬性的重要程度
 2 from sklearn.feature_selection import SelectKBest, f_classif
 3 
 4 
 5 selector = SelectKBest(f_classif, k=5)
 6 selector.fit(titanic[presictors], titanic["Survived"])
 7 # 獲取每個數據的重要值
 8 scores = -np.log10(selector.pvalues_)
 9 
10 # 畫圖表示,看看哪一些屬性對結果影響較大,即重要值高
11 plt.bar(range(len(presictors)), scores)
12 plt.xticks(range(len(presictors)), presictors, rotation='vertical')
13 
14 plt.show()

我們發現,船艙等級(Pclass)、性別(Sex)、船票價格(Fare)、名字稱謂(Title)以及名字長短(NameLength)的重要性很大,其它的重要性則不是很高(數值越大重要性越大)。頭等艙的確實有一定的優勢,女性比男性更有優勢(女人跟小孩優先)以及姓名長度跟稱謂也有影響(姓名長度影響座位分配?),船票價格跟船艙等級是正相關,所以也很重要。

 4.模型訓練

好了,模型我們也已經初步了解,數據也已經經過預處理,那么我們就開始訓練模型吧。

在此之前,先討論一下交叉驗證。由於我們的測試數據是沒有存活數據的,也就是我們需要根據樣本數據訓練出來的模型給出測試數據對應的存活結果,再在Kaggle平台上提交結果。但是,我們又怎么知道我們訓練模型的效果呢?有種方法叫做交叉驗證,它的思想就是對於訓練樣本分成幾份,例如三份,然后分別取出不同的兩份作為訓練樣本,另一份作為測試樣本,共取3次,最后取平均就得到了最終的最終的精度。最終的結果值就是預測出正確值數量/測試樣本的數量。sklearn里面也提供了交叉驗證的模型,所以很方便就可以測試我們的模型是否准確啦。

首先我們看一下線性回歸模型的訓練及測試結果:

 1 # 導入線性回歸模型跟邏輯回歸模型
 2 from sklearn.linear_model import LinearRegression, LogisticRegression
 3 
 4 presictors = ["Pclass", "Sex", "Age", "SibSp", "Parch", "Fare", "Embarked", "Titles", "FamilySize", "NameLenght"]
 5 alg = LinearRegression()
 6 # 在訓練集上進行三次交叉驗證
 7 # kf = KFold(titanic.shape[0], n_folds=3, random_state=1)
 8 kf = KFold(n_splits=2)
 9 predictions = []
10 for train, test in kf.split(titanic):
11     # 從train中取出分割后的訓練數據
12     train_predictors = titanic[presictors].iloc[train, :]
13     # 取出存活數量作為訓練目標
14     train_target = titanic["Survived"].iloc[train]
15     # 使用訓練樣本跟訓練目標訓練回歸函數
16     alg.fit(train_predictors, train_target)
17     # 我們現在可以在測試集上做預測
18     test_predictions = alg.predict(titanic[presictors].iloc[test, :])
19     predictions.append(test_predictions)
20 
21 # 檢查回歸的效果
22 predictions = np.concatenate(predictions, axis=0)
23 
24 predictions[predictions > .5] = 1
25 predictions[predictions <= .5] = 0
26 accuracy = sum(predictions == titanic["Survived"])/len(predictions)
27 print(accuracy)

好啦,然后我們可以看我們模型的預測准確率是0.792368125701459。畢竟這是用線性回歸做的,我們可以再試一下邏輯回歸,看一下效果是否會更好。

1 alg = LogisticRegression(random_state=1)
2 scores = cross_val_score(alg, titanic[presictors], titanic["Survived"], cv=3)
3 print(scores.mean())

結果出來了,模型的預測准確度是0.8103254769921437,確實比線性回歸的效果要好,那么我們再試一下隨機森林的結果。

1 # 導入隨機森林模型
2 from sklearn.ensemble import RandomForestClassifier
3 
4 alg = RandomForestClassifier(random_state=1, n_estimators=100, min_samples_split=4, min_samples_leaf=2)
5 # presictors = ["Pclass", "Sex", "Age", "SibSp", "Parch", "Fare", "Embarked", "Titles", "FamilySize", "NameLenght"]
6 kf = KFold(n_splits=3)
7 scores = cross_val_score(alg, titanic[presictors], titanic["Survived"], cv=kf.split(titanic))
8 print(scores.mean())

模型的預測准確度是0.8294051627384961,精度再一次提高。

到目前為止,我們已經嘗試了線性回歸、邏輯回歸以及隨機森林實現了泰坦尼克號生存預測。這時候,我們在會考慮,模型精度能不能進一步提高呢?模型融合可以是機器學習中提高精度的一大殺器。我們試着把邏輯回歸跟隨機森林這兩種模型結合到一起,綜合兩種模型的預測得出更合理的結果,同時,因為隨機森林的精度更高,所以我們對於隨機森林賦予權重為3,邏輯回歸模型的權重賦予1,下面我們看一下實現過程:

 1 algorithms = [
 2     [RandomForestClassifier(random_state=1, n_estimators=100, min_samples_split=4, min_samples_leaf=2), ["Pclass", "Sex",
 3                                      "Age", "SibSp", "Parch", "Fare", "Embarked", "Titles", "FamilySize", "NameLenght"]],
 4     [LogisticRegression(random_state=1),  ["Pclass", "Sex", "Age", "SibSp", "Parch", "Fare", "Embarked", "Titles",
 5                                            "FamilySize", "NameLenght"]]
 6 ]
 7 # 交叉驗證
 8 kf = KFold(n_splits=3)
 9 predictions = []
10 for train, test in kf.split(titanic):
11     train_target = titanic["Survived"].iloc[train]
12     full_test_predictions = []
13     # 對於每一個測試集都做出預測
14     for alg, predictors in algorithms:
15         alg.fit(titanic[predictors].iloc[train, :], train_target)
16         test_predictions = alg.predict_proba(titanic[predictors].iloc[test, :].astype(float))[:, 1]
17         full_test_predictions.append(test_predictions)
18     test_predictions = (full_test_predictions[0]*3 + full_test_predictions[1])/4
19     test_predictions[test_predictions <= .5] = 0
20     test_predictions[test_predictions > .5] = 1
21     predictions.append(test_predictions)
22 
23 # 把所有的預測結果放到集合當中
24 predictions = np.concatenate(predictions, axis=0)
25 
26 # 計算與訓練數據真值比較的精度
27 accuracy = sum(predictions == titanic["Survived"])/len(predictions)
28 print(accuracy)

這時候我們得到最終的精度為0.8249158249158249,好吧對比單純的隨機森林精度並沒有提高。

5.結束語

同時我們還可以對模型進行進一步的優化,例如上面有一些字段屬性的重要程度很低,那么我們就可以綜合取舍,這樣不僅可以提高我們模型的精度,還可以提高我們模型的泛化能力。同時,我們還可以嘗試其它的模型,以及多種模型的組合,以此來提高我們預測的精度。

當然,這次模型訓練的數據非常少,因為泰塔尼克號全部的乘客約2200人,模型的訓練可能需要更多數據的訓練,在模型精度和泛化能力之間尋找一個合理平衡,也就是防止過擬合跟欠擬合的問題。

本次實驗采用的python版本是3.6.0,實驗用到的數據以及源代碼在我的GitHub中(首頁左上角點擊即可進入),大家可以下載實驗。

這次實驗就講到這里啦,歡迎大家提出寶貴的意見!

 


免責聲明!

本站轉載的文章為個人學習借鑒使用,本站對版權不負任何法律責任。如果侵犯了您的隱私權益,請聯系本站郵箱yoyou2525@163.com刪除。



 
粵ICP備18138465號   © 2018-2025 CODEPRJ.COM