Kaggle機器學習之泰坦尼克號生還預測

寫在前面的話

  • 去年八月開始決心轉行數據分析到現在為止也有一段時間了,從最開始的包括現在一直進行的python語言方面的學習,最初主要在實驗樓學習相關知識,比如Mysql,Linux相方面的,到現在慢慢的轉向Kaggle想要深入機器學習領域,感覺自己由淺入深慢慢的在敲開數據分析的大門,之前在門縫裡就已經窺見裡面世界之大,現在推開門之後,愈發覺得裡面的世界竟如此的驚艷。先前這個開通這個專欄也是想記錄下自己自學數據分析的過程,但是遲遲沒有更新任何文章,原因有很多(主要是lan),不多說了。
  • 這篇文章主要記錄我在Kaggle上學習Titanic:Machine Learning from Disaster,輸出一份數據分析報告,作為自己學習的記錄,同時也以此為契機,做到後續每一周至少更新一篇數據分析報告,作為學習的輸出。
  • 此文主要引用Kaggle上大神Yassine Ghouzam的實踐項目Titanic Top 4% with ensemble modeling,所有代碼自己均跑過一遍,並進行理解注釋,受用匪淺。

主要內容

1 簡單說明和介紹

  • 1.1 項目簡介
  • 1.2 數據集說明

2 導入數據和數據預覽

  • 2.1 導出數據
  • 2.2 檢查異常數據
  • 2.3 合併訓練集和測試集
  • 2.4 檢查空值和缺失值

3 數據特徵列分析

  • 3.1 數值類型數據
  • 3.2 非數值類型數據

4 補充缺失值

  • 4.1 年齡缺失值補充

5 特徵列分析轉換處理

  • 5.1 姓名列分析
  • 5.2 家庭成員規模分析
  • 5.3 船艙號分析
  • 5.4 票號分析

6 模型的比較和選擇

  • 6.1 單一模型訓練

6.1.1 交叉驗證

6.1.2 模型最優參數調整

6.1.3 繪製不同模型學習曲線

6.1.4 查看不同模型使用特徵值權重

  • 6.2 組合最優模型

6.2.1 組合模型

  • 6.3 預測分析

6.3.1 預測並提交預測結果


Part 1 簡單說明和介紹

1.1項目簡介

Titanic:Machine Learning from Disaster 作為kaggle入門項目,首先是通過使用機器學習演算法對已有的訓練集數據建立模型,然後進行模型訓練,最終把訓練好的模型應用在測試集上同時輸出測試結果,在建立模型之前需要對數據進行分析清洗,異常數值處理,缺失值的補充,然後是模型的選擇和參數優化,每個過程處理方式不同,都會導致最終預測結果的準確性受到影響。

本項目中最終的使用的組合模型,也就是首先單個比較主要的機器學習演算法,主要有:SVC,決策樹,隨機森林,K近鄰,邏輯回歸,隨機梯度提升,投票演算法,提升演算法。對單個模型根據預測結果進行可視化呈現,選擇效果較好的模型並分別對模型進行最佳參數調整,最終組合成一個模型,該組合模型的預測結果最終排名Top 4%。

1.2數據集說明

此數據集主要包含兩部分,一部分是Train訓練集,另一部分是Test測試集,Test測試集中沒有Survived列,其餘部分和Train一致,需要我們根據機器學習演算法訓練模型,最終應用到Test測試集中預測乘客生還結果。

Train訓練集中主要主要包含以下列:

  • Survived(生還與否,0=No,1=Yes)
  • Pclass(艙位信息,1=一等艙,2=二等艙,3=三等艙)
  • Sex(性別)
  • Age(年齡)
  • SibSp(親屬/配偶人數)
  • Parch(父母/子女人數)
  • Ticket(船票信息)
  • Fare(票價)
  • Cabin(船艙號)
  • Embarked(出發地,C=Cherbourg,Q=Queenstown,S=Southampton)

Part 2 導入數據和數據預覽

2.1 導出數據

#導入基本的庫import pandas as pdimport numpy as npimport matplotlib.pyplot as pltimport seaborn as sns%matplotlib inlinefrom collections import Counter#導入機器學習演算法庫from sklearn.ensemble import RandomForestClassifier, AdaBoostClassifier, GradientBoostingClassifier, ExtraTreesClassifier, VotingClassifierfrom sklearn.discriminant_analysis import LinearDiscriminantAnalysisfrom sklearn.linear_model import LogisticRegressionfrom sklearn.neighbors import KNeighborsClassifierfrom sklearn.tree import DecisionTreeClassifierfrom sklearn.neural_network import MLPClassifierfrom sklearn.svm import SVCfrom sklearn.model_selection import GridSearchCV, cross_val_score, StratifiedKFold, learning_curve#設置sns樣式sns.set(style=white, context=notebook, palette=deep)#導入訓練集以及測試集數據,訓練集train.csv 以及測試集test.csv 均可以在kaggle下載train = pd.read_csv("train.csv")test = pd.read_csv("test.csv")#獲取測試集的乘客ID號,用於最後提交預測數據IDtest = test["PassengerId"]

2.2 檢查異常數據

異常數據對於最終結果的預測有這很大的影響,為了保證預測結果不受異常值的影響,需要對其進行處理,對於異常數據我們通常的理解是那些看起來很離譜的數據,但是如何定量的去判斷該數據是否離譜,這裡運用了Tukey method(Tukey JW.1977),先定義一個區間IQR,該區間是用75百分位數Q3減去25百分位數Q1,對於數值列Age,SibSp,Parch,Fare中的數據,如果各列有數據的值大於(Q3+1.5*IQR)或者小於(Q1-1.5*IQR),則可以判定為異常數值,記錄下該數據對應的行索引,當每行中超過兩條異常數據值,可以考慮將此行刪除,以下是具體的代碼實現如下:

#定義一個函數,對異常數值檢測判定def detect_outliers(df,n,features): outlier_indices=[] for col in features: #遍歷所有的特徵列 Q1=np.percentile(df[col],25) #獲取第25百分位數值 Q3 =np.percentile(df[col],75) #獲取第75百分位數值 IQR =Q3-Q1 #獲取25百分位數值和75百分位數值範圍 outlier_step =1.5*IQR #異常數值的判定依據 #獲取當前特徵列小於(Q1-outlier_step)或者大於(Q3+outlier_step)的行索引 outlier_list_col = df[(df[col] < Q1 - outlier_step) | (df[col] > Q3 + outlier_step )].index #把通過以上方法獲取到的異常數值加入outliers_indices列表中 outlier_indices.extend(outlier_list_col) # 把超過規定數量異常值的行索引找出來 outlier_indices = Counter(outlier_indices) multiple_outliers = list( k for k, v in outlier_indices.items() if v > n ) return multiple_outliers#使用異常值檢測函數,對Age,Sibsp,Parch 和Fare進行遍歷,找出超過兩個異常數值對應的行索引Outliers_to_drop = detect_outliers(train,2,["Age","SibSp","Parch","Fare"])#把訓練集中的異常行索引刪除,並對行索引進行重置.train = train.drop(Outliers_to_drop, axis = 0).reset_index(drop=True)

2.3 合併訓練集和測試集

#2.3 把訓練集和測試集數據合併起來,方便後續在對特徵列進行轉化後能夠確保數據的一致性train_len = len(train)dataset = pd.concat(objs=[train,test],axis=0).reset_index(drop=True)

2.4 檢查空值和缺失值

dataset =dataset.fillna(np.nan) #首先先把數據集所有的空值和NaN統一填充為NaNdataset.isnull().sum() #檢測下空值的總數輸出結果如下:Age 256Cabin 1007Embarked 2Fare 1Name 0Parch 0PassengerId 0Pclass 0Sex 0SibSp 0Survived 418Ticket 0dtype: int64

以上明顯看出,Age列和Cabin列具有比較多的缺失值。

#接著查看下訓練集整體缺失值情況train.info()train.isnull().sum()輸出結果如下:<class pandas.core.frame.DataFrame>RangeIndex: 881 entries, 0 to 880Data columns (total 12 columns):PassengerId 881 non-null int64Survived 881 non-null int64Pclass 881 non-null int64Name 881 non-null objectSex 881 non-null objectAge 711 non-null float64SibSp 881 non-null int64Parch 881 non-null int64Ticket 881 non-null objectFare 881 non-null float64Cabin 201 non-null objectEmbarked 879 non-null objectdtypes: float64(2), int64(5), object(5)memory usage: 65.4+ KBPassengerId 0Survived 0Pclass 0Name 0Sex 0Age 170SibSp 0Parch 0Ticket 0Fare 0Cabin 680Embarked 2dtype: int6train.head() #查看下train訓練集前五列信息train.dtypes #查看下train訓練集各列的數據類型train.describe() #對每列數據進行統計匯總,這裡自動識別具有int類型的列

通過運行以上代碼,可以大致了解整個數據集的總體情況,接著便是對數據集各個特徵列進行具體分析。

Part 3 數據特徵列分析

3.1 數值類型數據

通過圖表可以很直觀的呈現不同列之間的相關性係數,一般相關係數越大,表明數據相關性越強,進而表示該列數據對預測結果的權重比較大。

#下面我們利用熱力圖具體可以看下數值列(SibSp,Parch, Age ,Fare)和Survived之間的關係

g = sns.heatmap(train[["Survived","SibSp","Parch","Age","Fare"]].corr(), annot=True, fmt = ".2f", cmap = "coolwarm")

圖3-1

分析圖3-1,乍一看似乎只有Fare和Survived列具有一定的相關性,但是並不表明其他列就是沒有用處的,接著我們便來具體的分析每一列和Survived之間的關係。

#查看下SibSp列和Survived之間的關係

g=sns.factorplot(x=SibSp,y=Survived,data=train,kind=bar ,size=6,palette=muted)g.despine(left=True)g=g.set_ylabels("Survival probability")

圖3-2

從上圖3-2分析可以看出,乘客親屬的數量越多,對應生存的概率越低,對於單個的乘客或者帶有1到2個親屬的乘客比帶有3到4個的乘客更容易生還下來。

#接著再來分析下Parch和Survived之間對應的關係

g = sns.factorplot(x="Parch",y="Survived",data=train,kind="bar", size = 6 , palette = "muted")g.despine(left=True)g = g.set_ylabels("survival probability")

圖3-3

從圖3-3整體分析可知,相比較單個成員家庭以及家庭成員3-4和家庭成員5-6的家庭來說,家庭成員為1-2人的家庭生還的概率較大,同時注意在這裡,Parch家庭成員為3的乘客可能出現偏差。

#使用密度函數來探索Age和Survived之間的關係

g = sns.FacetGrid(train, col=Survived)g = g.map(sns.distplot, "Age")

圖3-4

上圖3-4分析得出,無論生還者還是未生還者的乘客中,年齡整體分布都類似於高斯分析,同時在右圖生還者概率分布中,特別小的孩子和年齡在20-40之間的人具有更多的生還機會,而相對於年齡在60-80的人群,生還率較小。

#下面接著利用核密度估計再來探究下年齡的分布

g = sns.kdeplot(train["Age"][(train["Survived"] == 0) & (train["Age"].notnull())], color="Red", shade = True)g = sns.kdeplot(train["Age"][(train["Survived"] == 1) & (train["Age"].notnull())], ax =g, color="Blue", shade= True)g.set_xlabel("Age")g.set_ylabel("Frequency")g = g.legend(["Not Survived","Survived"])

圖3-5

上圖3-5進一步證實,年齡特別小的乘客,大概0-5歲的小孩呈現了一個生還高峰。

# 接著探索下Fare和Survived之間的聯繫

dataset[Fare].isnull().sum() #查看Fare中的缺失值#把缺失值填充為此列其他數值的中位數dataset["Fare"] = dataset["Fare"].fillna(dataset["Fare"].median()) #利用直方圖展示下Fare的分布情況g = sns.distplot(dataset["Fare"], color="m", label="Skewness : %.2f"%(dataset["Fare"].skew()))g = g.legend(loc="best")

圖3-6

觀察圖3-6,可以發現數據的分布很不均勻,代表著數據分散程度的參數Skewness也高達4.51,很明顯Fare的分布偏離正態分布,從而導致整提數據權重分布不均勻,在這種情況下,可已考慮對Fare列取對數,從而使得數值分布更加均勻。以下是具體代碼:

dataset["Fare"] = dataset["Fare"].map(lambda i: np.log(i) if i > 0 else 0)#對Fare進行對數化處理後,在對Fare進行處理g = sns.distplot(dataset["Fare"], color="b", label="Skewness : %.2f"%(dataset["Fare"].skew()))g = g.legend(loc="best")

圖3-7

經過對數化處理後,Fare列數值分布變得更加均勻,偏離度降低。

3.2 非數值類型數據

以上是對數值連續變化int類型列進行分析,接下來分析字元串類型的分類數據,比如Sex,Pclass,Embarked列

#首先是性別Sex列,可以利用簡單的柱狀圖來看下性別和生還之間的關係

g = sns.barplot(x="Sex",y="Survived",data=train)g = g.set_ylabel("Survival Probability")

圖3-8

train[["Sex","Survived"]].groupby(Sex).mean()輸出結果: SurvivedSex female 0.747573male 0.190559

根據上圖3-8和數據可以明顯看出,女性的生還率遠遠高於男性,所以我們可以認為在預測生還率過程,性別是非常重要的因素。

#分析完Sex之後,再來考慮Pclass和Survived之間的關係

g = sns.factorplot(x="Pclass",y="Survived",data=train,kind="bar", size = 6 , palette = "muted")g.despine(left=True)g = g.set_ylabels("survival probability")

圖3-9

Pclass=1 代表著頭等艙,Pclass=2,代表著2等艙,Pclass=3代表著三等艙,毫無疑問在上圖3-9中,頭等艙的乘客具有更高的生還率。

#上面圖3-8中表示女性的生還可能遠遠高於男性,那麼在不同的艙位裡面是否也是這樣的?

g = sns.factorplot(x="Pclass", y="Survived", hue="Sex", data=train,size=6, kind="bar", palette="muted")g.despine(left=True)g = g.set_ylabels("survival probability")

圖3-10

結果並沒有偏離我們的猜測,在不同的艙位包括頭等艙中,依舊是女性乘客的生還率遠遠的高於男性乘客,這似乎也讓我想起電影里的一句台詞--Women and children first。

#最後再來對Embarked參數進行分析,看看從不同地方上船的乘客是否會有不一樣結果。

#首先查看下數據的缺失值dataset["Embarked"].isnull().sum()輸出結果:2dataset["Embarked"].describe()輸出結果:count 1297unique 3top Sfreq 904Name: Embarked, dtype: object#考慮到只有兩個缺失值,我們可以把缺失值填充為最常出現的Sdataset["Embarked"] = dataset["Embarked"].fillna("S")#通過圖表看下不同Embarked(出發地)和Survived之間的情況g = sns.factorplot(x="Embarked", y="Survived", data=train,size=6, kind="bar", palette="muted")g.despine(left=True)g = g.set_ylabels("survival probability")

圖3-11

上圖3-11看起來來自C(Cherbourg)的乘客似乎有更高的生還率,我想可以在這裡可以做一個假設,有可能是因為來自Cherbourg的乘客比起來自Q(Queenstown),S(Southampton)的乘客,頭等艙的比例比較高,所以接著可以驗證下來自不同地方的乘客艙位分布情況。

g = sns.factorplot("Pclass", col="Embarked", data=train,size=6, kind="count", palette="muted")g.despine(left=True)g = g.set_ylabels("Count")

圖3-12

上圖3-12可以明顯看出,三等艙乘客多數來自Southampton(S)和Queenstown(Q),相對於來自Cherbourg的乘客,頭等艙的比例最高,所以驗證了我上面的推論,就是因為來自Cherbourg的乘客頭等艙的比例較高,所以生還率更高,也許我們會想為甚麼來自頭等艙的乘客具有更高的生還率呢?我想是因為他們可能在撤離的時候享有優先權吧!

Part 4 補充缺失值

4.1 年齡缺失值補充

#先來看下Age和Sex,Parch,Pclass 還有SibSP之間的關係

g = sns.factorplot(y="Age",x="Sex",data=dataset,kind="box")g = sns.factorplot(y="Age",x="Sex",hue="Pclass", data=dataset,kind="box")g = sns.factorplot(y="Age",x="Parch", data=dataset,kind="box")g = sns.factorplot(y="Age",x="SibSp", data=dataset,kind="box")

圖4-1

圖4-2

圖4-3

圖4-4

根據圖4-1分析可知,性別對於年齡的分布沒有太多影響,所以不作為預測年齡的主要參考,圖4-2分析,一等艙年齡大於二等艙,二等艙年齡大於三等艙,圖4-3表明,家裡有父母和小孩的乘客自身年齡傾向於更大,圖4-4說明有更多親屬/配偶的乘客自身年齡傾向于越小。

#把Sex列中的女性用1來表示,男性用0來表示

dataset["Sex"] = dataset["Sex"].map({"male": 0, "female":1})

#繪製熱力圖,查看下Age和Sex,SibSp,Parch以及Pclass之間的關係

g = sns.heatmap(dataset[["Age","Sex","SibSp","Parch","Pclass"]].corr(), cmap="BrBG",annot=True)

圖4-5

根據相關性表格4-5顯示說明,Age和Sex之間沒有太多的關係,但同時相對Parch也是個例外,因為在在此圖中Age和Parch是負相關的,但是根據之前繪製的圖,家裡有父母和小孩的乘客自身年齡傾向於更大,兩者之間結論剛好相反,綜上所述,我們可以利用考慮SibSp,Parch和Pclass來進行Age缺失值的填充,填充的策略是根據缺失值所在行對應的SibSp,Parch和Pclass的情況來對應選擇有著類似SibSp,Parch和Pclass的Age值其他行數值的中位數。以下是具體的代碼實現:

#先獲取控制對應行的indexindex_NaN_age = list(dataset["Age"][dataset["Age"].isnull()].index)#遍歷每一個空值行for i in index_NaN_age: #取除空值以外所有值的中位數 age_med = dataset["Age"].median() #取和第i行具有相同的SibSp,Parch,Pclass的其他所有行對應的Age的中位數 age_pred =dataset["Age"][((dataset[SibSp] == dataset.iloc[i]["SibSp"]) & (dataset[Parch] == dataset.iloc[i]["Parch"]) & (dataset[Pclass] == dataset.iloc[i]["Pclass"]))].median() #判斷age_pred是否為空,如果為空,則選擇age_med if not np.isnan(age_pred): dataset[Age].iloc[i] = age_pred else: dataset[Age].iloc[i] = age_med

執行完成以上代碼之後,可以再來查看下Age列和Survived之間的關係。

g = sns.factorplot(x="Survived", y = "Age",data = train, kind="box")g = sns.factorplot(x="Survived", y = "Age",data = train, kind="violin")

圖4-6

圖4-7

圖4-6僅僅能看出生還乘客的年齡主要是集中在20-40歲之間,但是通過圖4-7,除了可以看出,生還乘客的年齡除了集中在20-40之間,同時在0-5歲的年齡區間出現了一個小的峰值,這和我們之前得到結論是一致,說明填充的缺失年齡值是符合規律的。

Part 5 特徵列分析轉換處理

5.1 姓名列分析

#查看下「Name」列中前幾行具體數值

dataset["Name"].head()輸出結果:0 Braund, Mr. Owen Harris1 Cumings, Mrs. John Bradley (Florence Briggs Th...2 Heikkinen, Miss. Laina3 Futrelle, Mrs. Jacques Heath (Lily May Peel)4 Allen, Mr. William HenryName: Name, dtype: object

根據以上結果展現,可以考慮把Name列中的姓名稱謂如:Mr,Miss,Mrs作為單獨的一列Title。具體實現代碼如下:

#從原來的Name列中取出稱呼前綴dataset_title = [i.split(",")[1].split(".")[0].strip() for i in dataset["Name"]]#構造新的Title列dataset["Title"]=pd.Series(dataset_title)dataset["Title"].head()輸出結果:0 Mr1 Mrs2 Miss3 Mrs4 MrName: Title, dtype: object

以上代碼完成之後,可以看下新增列Title中各個數值的分布情況

g = sns.countplot(x="Title",data=dataset)g = plt.setp(g.get_xticklabels(), rotation=45)

圖5-1

圖5-1表示,此數據集中乘客的稱謂一共有17種,但是多數的數量非常少,由此我們可以考慮只把Title歸結為四個種類,先把數量比較少的稱謂比如"Lady","the Countess","Countess", "Capt","Col"......統一替換成"Rare"。替換代碼如下:

dataset["Title"] = dataset["Title"].replace([Lady, the Countess,Countess, Capt, Col,Don, Dr, Major, Rev, Sir, Jonkheer, Dona], Rare)#接著把主要的列對應替換成為數字0,1,2,3dataset["Title"] = dataset["Title"].map({"Master":0, "Miss":1, "Ms" : 1 , "Mme":1, "Mlle":1, "Mrs":1, "Mr":2, "Rare":3})#轉化為整型dataset["Title"] = dataset["Title"].astype(int)

替換好之後,我們可以看下最後的分布情況

g = sns.countplot(dataset["Title"])g = g.set_xticklabels(["Master","Miss/Ms/Mme/Mlle/Mrs","Mr","Rare"])

圖5-2

#接著看看新增的Title列和Survived之間的對應關係

g = sns.factorplot(x="Title",y="Survived",data=dataset,kind="bar")g = g.set_xticklabels(["Master","Miss-Mrs","Mr","Rare"])g = g.set_ylabels("survival probability")

圖5-3

結論總是驚人的相似,圖5-3很清楚表示女性乘客的中生還者比例更高。

#通過以上將Name轉化為Title的處理,我們就直接可以把Name列刪除掉了。

dataset.drop(labels = ["Name"], axis = 1, inplace = True)

5.2 家庭成員規模分析

首先我們推測一下,家庭的大小會很大程度的影響生還的可能,因為家庭人數越多,我們在逃生的過程中就更容易去尋找其他的家庭成員,從而導致最佳逃生時間被耽誤,因此,我們可以考慮新增一列Fsize(family size),主要是根據SibSp,Parch和乘客本人相加,即Fsize=SibSp+Parch+1

dataset["Fsize"] = dataset["SibSp"] + dataset["Parch"] + 1

#新增Fsize列之後,我們來看下Fsize列和Survived之間的關係

g = sns.factorplot(x="Fsize",y="Survived",data = dataset)g = g.set_ylabels("Survival Probability")

圖5-4

觀察圖5-4,可以明顯看出,家庭成員越多的乘客,生還的可能性越小,在此可以考慮根據家庭成員的個數新增四列不同的特徵列,單個的乘客歸類為「Single」,具有兩個家庭成員的乘客歸類為「SmallF」,具有三個到四個家庭的乘客歸類為「MedF」,具有五個及以上的乘客歸類為「LargeF」,以下是具體代碼實現:

dataset[Single] = dataset[Fsize].map(lambda s: 1 if s == 1 else 0)dataset[SmallF] = dataset[Fsize].map(lambda s: 1 if s == 2 else 0)dataset[MedF] = dataset[Fsize].map(lambda s: 1 if 3 <= s <= 4 else 0)dataset[LargeF] = dataset[Fsize].map(lambda s: 1 if s >= 5 else 0)

#接著我們來看下新增的四列和Survived之間的關係

g = sns.factorplot(x="Single",y="Survived",data=dataset,kind="bar")g = g.set_ylabels("Survival Probability")g = sns.factorplot(x="SmallF",y="Survived",data=dataset,kind="bar")g = g.set_ylabels("Survival Probability")g = sns.factorplot(x="MedF",y="Survived",data=dataset,kind="bar")g = g.set_ylabels("Survival Probability")g = sns.factorplot(x="LargeF",y="Survived",data=dataset,kind="bar")g = g.set_ylabels("Survival Probability")

圖5-5

圖5-6

圖5-7

圖5-8

以上四張圖很清楚的表明,小型和中型的家庭乘客的生還率明顯高於單個乘客和家庭成員比較多的乘客。

#接著對剛才新增的Title列和已經有的Embarked列進行處理,因為Title列中是由0,1,2,3組成,而Embarked列是由C,Q,S組成,所有我們可以使用Pandas中的get_dummies方法對這兩列進行One-hot編碼,具體代碼實現如下:

dataset = pd.get_dummies(dataset, columns = ["Title"])dataset = pd.get_dummies(dataset, columns = ["Embarked"], prefix="Em")

運行好以上代碼之後,Title也將被分成Title_0,Title_1,Title_2,Title_3列,Embarked列則被分成Em_C,Em_Q,Em_S。

5.3 船艙號分析

在分析Cabin船艙號數據之前,我們可以大體先看下數據的結構組成

dataset["Cabin"].head()輸出結果:0 NaN1 C852 NaN3 C1234 NaNName: Cabin, dtype: object#查看下匯總信息dataset["Cabin"].describe()輸出結果:count 292unique 186top G6freq 5Name: Cabin, dtype: object#缺失值dataset["Cabin"].isnull().sum()輸出結果:1007

以上分析得出,在Cabin列,一共有292條非空值,1007條是空值

#查看下Cabin列非空值的情況

dataset["Cabin"][dataset["Cabin"].notnull()].head()輸出結果:1 C853 C1236 E4610 G611 C103Name: Cabin, dtype: object

#考慮把Cabin列中空值列替換成為"X",如果不是空值,則取該值的首位i[0]

dataset["Cabin"] = pd.Series([i[0] if not pd.isnull(i) else X for i in dataset[Cabin] ])

#以上步驟完成後,可以查看下目前Cabin列不同參數的分布情況

g = sns.countplot(dataset["Cabin"],order=[A,B,C,D,E,F,G,T,X])

圖5-9

#接著我們可以再看下Cabin列中不同參數值對應Survived的情況

g = sns.factorplot(y="Survived",x="Cabin",data=dataset,kind="bar", order=[A,B,C,D,E,F,G,T,X])g = g.set_ylabels("Survival Probability")

圖5-10

根據圖5-9可知,由於擁有Cabin的乘客相對於沒有Cabin的乘客的數量少很多,所以在判斷生還可能的時候可能會出現一定偏差,因此我們無法根據乘客所在的不同位置情況來判定生還的可能性,但是我們從上圖5-10依然可以得出結論,對於擁有Cabin的乘客比沒有Cabin的乘客生還的可能性更高。

#使用get_dummies演算法對Cabin列進行處理

dataset = pd.get_dummies(dataset, columns = ["Cabin"],prefix="Cabin")

5.4 票號分析

相同的步驟,在分析之前先看看Ticket的數據結構組成。

dataset["Ticket"].head()輸出結果:0 A/5 211711 PC 175992 STON/O2. 31012823 1138034 373450Name: Ticket, dtype: object

我們可以分析猜測對於具有相同前綴的Ticket,他們對應的Cabin應該是相同的,由此也可以推斷,具體相同Tickets前綴的乘客的生還率應該是接近的,根據以上判斷我們可以考慮把Ticket列根據Ticket前綴進行劃分,提取Ticket的前綴信息,如果沒有則返回None,代碼如下:

Ticket=[]for i in list(dataset.Ticket): #首先判定Ticket是否帶有前綴,也就是判定是否是純數字 if not i.isdigit(): #把所有的點「.」和「/」替換成空格後進行劃分後,取第一個數 Ticket.append(i.replace(".","").replace("/","").strip().split( )[0]) else: Ticket.append("X")dataset["Ticket"]=Ticket#看下最終處理的效果dataset["Ticket"].head()輸出結果:0 A51 PC2 STONO23 X4 XName: Ticket, dtype: object

#以上處理好之後,就可以使用get_dummies進行one-hot編碼了

dataset = pd.get_dummies(dataset, columns = ["Ticket"], prefix="T")

#最後處理Pclass列,考慮先把Pclass列轉換成為"category",然後使用get_dummies進行處理。

dataset["Pclass"] = dataset["Pclass"].astype("category")dataset = pd.get_dummies(dataset, columns = ["Pclass"],prefix="Pc")#去掉無關的特徵列"passengerId"dataset.drop(labels = ["PassengerId"], axis = 1, inplace = True)

通過以上的步驟就完成了數據的清洗,特徵值的分析,已經缺失值、空值的填充,最後我們可以利用處理好的數據集進行模型的訓練了

#總體呈先下我們處理好的數據集

dataset.head()

Part 6 模型的比較和選擇

6.1 單一模型訓練

在最開始的時候,我把訓練集和測試集合併進行統一的特徵值處理,現在我們需要把處理好之後的dataset分開成為原來的Train和Test數據集,train數據集用來訓練模型,test就是我們最終模型的驗證。

train = dataset[:train_len]test = dataset[train_len:]test.drop(labels=["Survived"],axis = 1,inplace=True)

#區分train訓練集中訓練數據特徵列和結果列

train["Survived"] = train["Survived"].astype(int)Y_train = train["Survived"]X_train = train.drop(labels = ["Survived"],axis = 1)

6.1.1 交叉驗證

在單個的模型訓練中,主要考慮使用以下常用的機器學習演算法:

  • SVC
  • Decision Tree
  • AdaBoost
  • Random Forest
  • Extra Trees
  • Gradient Boosting
  • Multiple layer perceprton (neural network)
  • KNN
  • Logistic regression
  • Linear Discriminant Analysis

#設置kfold

kfold = StratifiedKFold(n_splits=10)

#匯總不同模型演算法,同時進行初始化

random_state = 2classifiers = []classifiers.append(SVC(random_state=random_state))classifiers.append(DecisionTreeClassifier(random_state=random_state))classifiers.append(AdaBoostClassifier(DecisionTreeClassifier (random_state=random_state),random_state=random_state,learning_rate=0.1))classifiers.append(RandomForestClassifier(random_state=random_state))classifiers.append(ExtraTreesClassifier(random_state=random_state))classifiers.append(GradientBoostingClassifier(random_state=random_state))classifiers.append(MLPClassifier(random_state=random_state))classifiers.append(KNeighborsClassifier())classifiers.append(LogisticRegression(random_state = random_state))classifiers.append(LinearDiscriminantAnalysis())

#不同機器學習交叉驗證結果匯總

cv_results = []for classifier in classifiers : cv_results.append(cross_val_score(classifier, X_train, y = Y_train, scoring = "accuracy", cv = kfold, n_jobs=4))

#取每個模型獲取的cv_results的平均數以及標準偏差

cv_means=[]cv_std=[]for cv_result in cv_results: cv_means.append(cv_result.mean()) cv_std.append(cv_result.std())#集中在一張Dataframe的表格當中cv_res = pd.DataFrame({"CrossValMeans":cv_means,"CrossValerrors": cv_std, "Algorithm":["SVC","DecisionTree","AdaBoost", "RandomForest","ExtraTrees","GradientBoosting","MultipleLayerPerceptron", "KNeighboors","LogisticRegression","LinearDiscriminantAnalysis"]})

#查看下cv_res

cv_res輸出結果: Algorithm CrossValMeans CrossValerrors0 SVC 0.822944 0.0451551 DecisionTree 0.799183 0.0425292 AdaBoost 0.804865 0.0517383 RandomForest 0.807022 0.0416584 ExtraTrees 0.800268 0.0371455 GradientBoosting 0.829724 0.0419596 MultipleLayerPerceptron 0.822944 0.0317167 KNeighboors 0.795710 0.0408378 LogisticRegression 0.822932 0.0337659 LinearDiscriminantAnalysis 0.822932 0.038751

#通過圖表來看下不同演算法的表現情況

g = sns.barplot("CrossValMeans","Algorithm",data = cv_res, palette="Set3",orient = "h",**{xerr:cv_std})g.set_xlabel("Mean Accuracy")g = g.set_title("Cross validation scores")

圖6-1

綜合以上模型表現,考慮選擇SVC, AdaBoost, RandomForest , ExtraTrees 和 the GradientBoosting classifiers 應用到組合模型當中去。

6.1.2 模型最優參數調整

先考慮對Adaboost模型進行最優參數優化

DTC = DecisionTreeClassifier()#設置AdaBoost的參數adaDTC = AdaBoostClassifier(DTC, random_state=7)ada_param_grid = {"base_estimator__criterion" : ["gini", "entropy"], "base_estimator__splitter" : ["best", "random"], "algorithm" : ["SAMME","SAMME.R"], "n_estimators" :[1,2], "learning_rate": [0.0001, 0.001, 0.01, 0.1, 0.2, 0.3,1.5]}gsadaDTC = GridSearchCV(adaDTC,param_grid = ada_param_grid, cv=kfold, scoring="accuracy", n_jobs= 4, verbose = 1)gsadaDTC.fit(X_train,Y_train)ada_best = gsadaDTC.best_estimator_

#優化ExtraTrees 的參數

ExtC = ExtraTreesClassifier()# 優化決策樹演算法參數設置ex_param_grid = {"max_depth": [None], "max_features": [1, 3, 10], "min_samples_split": [2, 3, 10], "min_samples_leaf": [1, 3, 10], "bootstrap": [False], "n_estimators" :[100,300], "criterion": ["gini"]}gsExtC = GridSearchCV(ExtC,param_grid = ex_param_grid, cv=kfold, scoring="accuracy", n_jobs= 4, verbose = 1)#應用到訓練集上去gsExtC.fit(X_train,Y_train)ExtC_best = gsExtC.best_estimator_

#優化RandomForest的參數

RFC = RandomForestClassifier()#設置網格參數rf_param_grid = {"max_depth": [None], "max_features": [1, 3, 10], "min_samples_split": [2, 3, 10], "min_samples_leaf": [1, 3, 10], "bootstrap": [False], "n_estimators" :[100,300], "criterion": ["gini"]}#模型初始化gsRFC = GridSearchCV(RFC,param_grid = rf_param_grid, cv=kfold, scoring="accuracy", n_jobs= 4, verbose = 1)#模型訓練gsRFC.fit(X_train,Y_train)RFC_best = gsRFC.best_estimator_

#優化GradientBoosing的參數

GBC = GradientBoostingClassifier()gb_param_grid = {loss : ["deviance"], n_estimators : [100,200,300], learning_rate: [0.1, 0.05, 0.01], max_depth: [4, 8], min_samples_leaf: [100,150], max_features: [0.3, 0.1] }gsGBC = GridSearchCV(GBC,param_grid = gb_param_grid, cv=kfold, scoring="accuracy", n_jobs= 4, verbose = 1)gsGBC.fit(X_train,Y_train)GBC_best = gsGBC.best_estimator_

#優化SVC的參數

SVMC = SVC(probability=True)svc_param_grid = {kernel: [rbf], gamma: [ 0.001, 0.01, 0.1, 1], C: [1, 10, 50, 100,200,300, 1000]}gsSVMC = GridSearchCV(SVMC,param_grid = svc_param_grid, cv=kfold, scoring="accuracy", n_jobs= 4, verbose = 1)gsSVMC.fit(X_train,Y_train)SVMC_best = gsSVMC.best_estimator_

以上便是對選擇出來的機器學習演算法的優化,通過參數的優化,目前模型已經達到最佳效果。

6.1.3 繪製不同模型學習曲線

通過模型學習曲線查看,我們可以更好的了解模型最終呈現的效果,首先我們先定義一個繪製模型曲線的繪製函數

def plot_learning_curve(estimator, title, X, y, ylim=None, cv=None, n_jobs=-1, train_sizes=np.linspace(.1, 1.0, 5)): plt.figure() plt.title(title) if ylim is not None: plt.ylim(*ylim) plt.xlabel("Training examples") plt.ylabel("Score") train_sizes, train_scores, test_scores = learning_curve( estimator, X, y, cv=cv, n_jobs=n_jobs, train_sizes=train_sizes) train_scores_mean = np.mean(train_scores, axis=1) train_scores_std = np.std(train_scores, axis=1) test_scores_mean = np.mean(test_scores, axis=1) test_scores_std = np.std(test_scores, axis=1) plt.grid() plt.fill_between(train_sizes, train_scores_mean - train_scores_std, train_scores_mean + train_scores_std, alpha=0.1, color="r") plt.fill_between(train_sizes, test_scores_mean - test_scores_std, test_scores_mean + test_scores_std, alpha=0.1, color="g") plt.plot(train_sizes, train_scores_mean, o-, color="r", label="Training score") plt.plot(train_sizes, test_scores_mean, o-, color="g", label="Cross-validation score") plt.legend(loc="best") return plt

#利用上面函數對不同演算法模型進行學習曲線的繪製

g = plot_learning_curve(gsRFC.best_estimator_,"RF mearning curves", X_train,Y_train,cv=kfold)g = plot_learning_curve(gsExtC.best_estimator_,"ExtraTrees learning curves", X_train,Y_train,cv=kfold)g = plot_learning_curve(gsSVMC.best_estimator_,"SVC learning curves", X_train,Y_train,cv=kfold)g = plot_learning_curve(gsadaDTC.best_estimator_,"AdaBoost learning curves", X_train,Y_train,cv=kfold)g = plot_learning_curve(gsGBC.best_estimator_,"GradientBoosting learning curves", X_train,Y_train,cv=kfold)

圖6-2

圖6-3

圖6-4

圖6-5

圖6-6

6.1.4 查看不同模型使用特徵值權重

通過繪製圖表來具體看下不同的演算法對應不同特徵值的權重

#利用plt 繪製四張子圖nrows = ncols = 2fig, axes = plt.subplots(nrows = nrows, ncols = ncols, sharex="all", figsize=(15,15))#指明需要分類的names_classifiers = [("AdaBoosting", ada_best),("ExtraTrees",ExtC_best), ("RandomForest",RFC_best),("GradientBoosting",GBC_best)]nclassifier = 0for row in range(nrows): for col in range(ncols): name = names_classifiers[nclassifier][0] classifier = names_classifiers[nclassifier][1] #先根據classifier.feature_importances_的值進行降序排列,然後取前40 indices = np.argsort(classifier.feature_importances_)[::-1][:40] #繪製柱狀圖,y軸對應特徵列的名稱,x軸則表明特徵列所佔的重要程度比例 g = sns.barplot(y=X_train.columns[indices][:40],x = classifier.feature_importances_[indices][:40] , orient=h,ax=axes[row][col]) g.set_xlabel("Relative importance",fontsize=12) g.set_ylabel("Features",fontsize=12) g.tick_params(labelsize=9) g.set_title(name + " feature importance") nclassifier += 1

圖6-7

根據上圖6-7亦可以得出結論,不同的演算法模型對應的比重最大的特徵列是不相同的,也就是說,不同模型預測的的依據是不同的,但是觀察所佔比例靠前的特徵列,可以發現基本是集中在「Fare」,「Title_2","Age",以及「sex」當中,進一步分析Title_2可知,Mrs/Mlle/Mme/Miss/Ms category也是和sex高度相關的。

由此我們可以簡單做個總結:

  1. Pc_1,Pc_2,Pc_3 以及Fare 特徵列代表著乘客的社會地位相關
  2. Sex 以及 Title_(Mrs/Mlle/Mme/Miss/Ms) 和 Title_3(Mr)特徵列代表著性別
  3. Age和Title_1(Master) 和乘客的年齡相關
  4. Fsize,LargeF,MedF,Single特徵列和乘客家庭規模相關的

根據以上的總結,我們可以知道,預測一個乘客的生還與否,相對於乘客當時身處的位置而言,年齡,性別,家庭大小和社會地位佔有更多的權重。

#在使用各個模型組合之前,可以先看下最優參數下模型表現

test_Survived_RFC = pd.Series(RFC_best.predict(test), name="RFC")test_Survived_ExtC = pd.Series(ExtC_best.predict(test), name="ExtC")test_Survived_SVMC = pd.Series(SVMC_best.predict(test), name="SVC")test_Survived_AdaC = pd.Series(ada_best.predict(test), name="Ada")test_Survived_GBC = pd.Series(GBC_best.predict(test), name="GBC")#把對應的預測結果粘貼起來ensemble_results = pd.concat([test_Survived_RFC,test_Survived_ExtC, test_Survived_AdaC,test_Survived_GBC, test_Survived_SVMC],axis=1)#查看不同結果的相關性g= sns.heatmap(ensemble_results.corr(),annot=True)

圖6-8

通過圖6-8分析得出,除了Adaboost以外,其他模型之間都有類似相關的預測結果。

6.2 組合最優模型

6.2.1 組合模型

#傳入參數soft,把五個模型組合起來

votingC = VotingClassifier(estimators=[(rfc, RFC_best), (extc, ExtC_best),(svc, SVMC_best), (adac,ada_best),(gbc,GBC_best)], voting=soft, n_jobs=4)votingC = votingC.fit(X_train, Y_train)

以上便是通過單個模型的組合,對訓練集進行訓練,最終可以應用到test數據集上進行預測

6.3 預測分析

6.3.1 預測並提交預測結果

# 使用組合模型對測試集進行預測test_Survived = pd.Series(votingC.predict(test), name="Survived")results = pd.concat([IDtest,test_Survived],axis=1)results.to_csv("ensemble_python_voting.csv",index=False)

最後"ensemble_python_voting.csv"刷到Kaggle,排名進入Top4%。


推薦閱讀:

分析競爭力,數字時代的差異化競爭優勢
4 種最搶手的數據分析職業,你會選擇?
數據篇(1):數據分析

TAG:機器學習 | 數據分析 | Python |