1. 概述
注: 此項(xiàng)目原地址在我的kaggle主頁上,可以點(diǎn)擊https://www.kaggle.com/acphart/titanic-with-scikit-learn進(jìn)去直接Fork食用效果更佳,這里是直接把我在Jupyter Notebook中寫的.ipynb文件直接轉(zhuǎn)成md格式粘貼到這里的,而且這里不支持交互操作,輸出的格式也比較亂,所以把所有的輸出都clear了,在我的GitHub主頁上,點(diǎn)擊https://github.com/acphart/Kaggle_Titanic_with_sklearn可以下載到完整的.ipynb文件,下載之后在Jupyter Notebook中打開就可以進(jìn)行交互式操作了 ,如果喜歡還可以在GitHub上送個(gè)Star哦。如果不嫌麻煩也可以把這里的的代碼一個(gè)一個(gè)粘貼到Jupyter Notebook的cell中 ~~ :)
1.1 項(xiàng)目介紹
- 我們需要先在Titanic的Data頁面看一下數(shù)據(jù)集的介紹,以及對(duì)結(jié)果的要求,如果沒看先回去看一下。
- Data中有三個(gè)文件,train.csv、test.csv和gender_submission.csv,分別是訓(xùn)練集、測(cè)試集和結(jié)果提交示例
- train.csv比test.csv多了一列Survived標(biāo)簽,我們的工作就是用訓(xùn)練集訓(xùn)練算法之后預(yù)測(cè)出測(cè)試集的Survived標(biāo)簽,之后再提交
1.2 工作思路
- 對(duì)于工作流程,其實(shí)個(gè)人有個(gè)人的喜好,我還是比較喜歡Ng在機(jī)器學(xué)習(xí)課程中描述的思路:
- 如果你準(zhǔn)備研究機(jī)器學(xué)習(xí)的東西,或者構(gòu)造機(jī)器學(xué)習(xí)應(yīng)用程序,最好的實(shí)踐方法不是建立一個(gè)非常復(fù)雜的系統(tǒng),擁有多么復(fù)雜的變量;
而是構(gòu)建一個(gè)簡(jiǎn)單的算法,這樣你可以很快地實(shí)現(xiàn)它。- 每當(dāng)我研究機(jī)器學(xué)習(xí)的問題時(shí),我最多只會(huì)花一天的時(shí)間,就是字面意義上的 24 小時(shí),來試圖很快的把結(jié)果搞出來,即便效果不好。
坦白的說,就是根本沒有用復(fù)雜的系統(tǒng),但是只是很快的得到結(jié)果。即便運(yùn)行得不完美,但是也把它運(yùn)行一遍,最后通過交叉驗(yàn)證來檢
驗(yàn)數(shù)據(jù)。- 一旦做完,你可以畫出學(xué)習(xí)曲線,通過畫出學(xué)習(xí)曲線,以及檢驗(yàn)誤差,來找出你的算法是否有高偏差和高方差的問題,或者別的問題。
在這樣分析之后,再來決定用更多的數(shù)據(jù)訓(xùn)練,或者加入更多的特征變量是否有用。- 這么做的原因是:這在你剛接觸機(jī)器學(xué)習(xí)問題時(shí)是一個(gè)很好的方法,你并不能提前知道你是否需要復(fù)雜的特征變量,或者你是否需要更
多的數(shù)據(jù),還是別的什么。提前知道你應(yīng)該做什么,是非常難的,因?yàn)槟闳鄙僮C據(jù),缺少學(xué)習(xí)曲線。因此,你很難知道你應(yīng)該把時(shí)間花在什
么地方來提高算法的表現(xiàn)。但是當(dāng)你實(shí)踐一個(gè)非常簡(jiǎn)單即便不完美的方法時(shí),你可以通過畫出學(xué)習(xí)曲線來做出進(jìn)一步的選擇。你可以用這種
方式來避免一種電腦編程里的過早優(yōu)化問題,這種理念是:- 我們必須用證據(jù)來領(lǐng)導(dǎo)我們的決策,怎樣分配自己的時(shí)間來優(yōu)化算法,而不是僅僅憑直覺,憑直覺得出的東西一般總是錯(cuò)誤的。
- 除了畫出學(xué)習(xí)曲線之外,一件非常有用的事是誤差分析,通過檢查交叉驗(yàn)證集中被錯(cuò)誤預(yù)測(cè)的數(shù)據(jù),你可以發(fā)現(xiàn)某些系統(tǒng)性的規(guī)律:
什么類型的特征總是容易出錯(cuò)。 經(jīng)常地這樣做之后, 這個(gè)過程能啟發(fā)你構(gòu)造新的特征變量, 或者告訴你: 現(xiàn)在這個(gè)系統(tǒng)的短處,然后啟
發(fā)你如何去提高它。
- 當(dāng)然,這個(gè)項(xiàng)目只是一個(gè)入門級(jí)的小項(xiàng)目,并不需要過多的優(yōu)化,因?yàn)椋篢itanic號(hào)幸存的人雖然有一些系統(tǒng)性的特征,
但是否存活有很大的偶然性,優(yōu)化過多其實(shí)意義不大,能夠把預(yù)測(cè)正確率達(dá)到80%以上就可以了,進(jìn)一步的學(xué)習(xí)應(yīng)該轉(zhuǎn)戰(zhàn)其他有大量數(shù)據(jù)集的項(xiàng)目。 - 至于排行榜中一些達(dá)到90%以上的,其特征變量的確夠復(fù)雜的,有興趣可以觀摩一下。
1.3 我的流程
- 依照Ng推薦的流程,先快速實(shí)現(xiàn)一個(gè)簡(jiǎn)單的預(yù)測(cè)系統(tǒng),再進(jìn)行分析和優(yōu)化
1.4 導(dǎo)入數(shù)據(jù)分析相關(guān)庫(kù)
注: 這里我設(shè)置了IPython.core中cell交互的方式,所以下面cell中所有單行存在的變量都可以自動(dòng)輸出
沒有設(shè)置的話,只有最后一個(gè)單行存在的變量可以自動(dòng)輸出
import numpy as np
import pandas as pd
import matplotlib.pyplot as plt
import warnings
from IPython.core.interactiveshell import InteractiveShell
# 為了輸出美觀一點(diǎn),忽略警告,因?yàn)楹竺嬗袛?shù)組除數(shù)組會(huì)出現(xiàn)除0的情況,但并不影響操作,可以暫時(shí)把下面這句注釋掉
warnings.filterwarnings('ignore')
# 下面這句是為了讓cell中所有單行存在的變量都能輸出,默認(rèn)情況下是只輸出最后一個(gè)單行存在的變量
InteractiveShell.ast_node_interactivity = 'all'
1.5 讀入訓(xùn)練數(shù)據(jù)和測(cè)試數(shù)據(jù)
- 這是第一步,不管后面要干什么,最好先看一下自己的數(shù)據(jù)是什么樣子的。
- 輸出信息還是比較多的,能看出train和test數(shù)據(jù)形式是一樣的,除了train的第二列是Survived標(biāo)簽
- 其中還有一些數(shù)據(jù)有缺失,如Age、Cabin、Embarked,還有test里Fare列缺一個(gè)數(shù)據(jù),這些之后需要相應(yīng)的處理
train = pd.read_csv('train.csv')
test = pd.read_csv('test.csv')
if True:
for data in train, test:
data.head() # 輸出前5行數(shù)據(jù)
data.describe() # 輸出數(shù)值型數(shù)據(jù)的基本統(tǒng)計(jì)描述信息
data.describe(include=[np.object, 'category']) # 輸出類別型數(shù)據(jù)的基本統(tǒng)計(jì)描述信息
2. 初步數(shù)據(jù)特征探索
- 這里我們觀察每一個(gè)特征,以及這個(gè)特征與存活率的相關(guān)性
- 這里需要注意一點(diǎn),當(dāng)我們將單個(gè)特征分組之后計(jì)算每組的存活率時(shí),要看一下各組總的個(gè)體數(shù)量,從而評(píng)估得出來的相關(guān)性會(huì)有多大程度是偶然性導(dǎo)致的,從而指導(dǎo)我們更改分組的方式
2.1 Pclass 船票的等級(jí)
- 從結(jié)果可以看出,船票等級(jí)是一個(gè)相當(dāng)有分量的特征,等級(jí)的高低和存活率有明顯的相關(guān)性
train['Pclass'].value_counts()
train[['Pclass', 'Survived']].groupby(['Pclass'], as_index=False).mean()
2.2 Name 姓名
- 名字891個(gè)沒有重復(fù),輸出幾個(gè)名字來看一下
- 其實(shí)一般來講名字不能算特征,但這里面的名字還真有一些特征:
- 名字中有諸如Mr、Miss、Mrs、Master等等標(biāo)識(shí)乘客身份和性別的特征
- 名字中的姓氏也可能隱含著乘客之間的親屬關(guān)系
- 名字中其他部分含有的信息我也不了解,對(duì)外國(guó)人的名字就了解這么多了 :(
- 不過基于我們的思路,這個(gè)特征可取也可不取,反正暫時(shí)不管它。
train['Name'].unique().size
train['Name'].head(10)
2.3 Sex
- 輸出很完美,性別果然是第一大特征
train[['Sex', 'Survived']].groupby(['Sex'], as_index=False).mean()
2.4 Age
- 這里先看一下年齡的整體分布情況,以及存活者的年齡分布情況
n_bins = 40
fig, ax = plt.subplots(1, 1)
all_n, all_bins, all_patches = ax.hist(train['Age'].dropna(), bins=n_bins, color='r')
svd_n, svd_bins, svd_patches = ax.hist(train.loc[train['Survived']==1, 'Age'].dropna(), bins=n_bins, color='g')
- 然后再看一下年齡和存活率的關(guān)系
- 結(jié)果看起來還是有相關(guān)性的,但表現(xiàn)得不夠完美,后面需要處理
svd_rate = svd_n/all_n
fig, ax = plt.subplots(1, 1)
_ = ax.plot(all_bins[:-1], svd_rate)
2.5 SibSp 兄弟姐妹和配偶(在船上的同輩親屬總數(shù))
- 分組結(jié)果顯示出來這個(gè)特征和存活率還是有相關(guān)性的,但相關(guān)性的表現(xiàn)還不夠完美,后面需要處理
train['SibSp'].value_counts() # 各組的總?cè)藬?shù)
train[['SibSp', 'Survived']].groupby(['SibSp'], as_index=False).mean() #各組的存活率
2.6 Parch 在船上的父母和子女總數(shù)
- 和上面一樣,分組結(jié)果顯示出來這個(gè)特征和存活率還是有相關(guān)性的,但相關(guān)性的表現(xiàn)還不夠完美,后面需要處理
train['Parch'].value_counts() # 各組的總?cè)藬?shù)
train[['Parch', 'Survived']].groupby(['Parch'], as_index=False).mean() # 各組的存活率
2.7 Ticket 船票編號(hào)
- 看不懂船票編號(hào)有什么特征, 跳過 ~~~
train['Ticket'].unique().size
train['Ticket'].head(10)
2.8 Fare 票價(jià)
- 查看各個(gè)票價(jià)區(qū)間的人數(shù)及相應(yīng)存活的人數(shù)
- 結(jié)果很明顯,都不用算比率了,能看出票價(jià)越高存活率越高
n_bins = 30
fig, ax = plt.subplots(1, 1)
_ = ax.hist(train['Fare'], bins=n_bins, color='r')
_ = ax.hist(train.loc[train['Survived']==1, 'Fare'], bins=n_bins, color='g')
2.9 Cabin 船艙編號(hào)
- 這個(gè)特征數(shù)據(jù)缺失的有點(diǎn)嚴(yán)重啊,缺了將近80%的數(shù)據(jù)
- 測(cè)試集那邊也是缺了將近80%,所以這個(gè)特征暫時(shí)不用
train['Cabin'].count()/891
test['Cabin'].count()/418
2.10 Embarked 登船港口
- 這上船的港口和存活率也有較強(qiáng)的相關(guān)性,應(yīng)該是這三個(gè)地方的經(jīng)濟(jì)情況不一樣,我們可以驗(yàn)證一下
- 結(jié)果如下,和猜想差不多,C港口的人平均票價(jià)和存活率明顯高于另外兩個(gè)地方,至于Q、S港口沒有符合猜想,可能是因?yàn)镼港口的人比較少導(dǎo)致偶然性誤差比較大
# 各個(gè)港口登船的人數(shù)及相應(yīng)的存活率
train['Embarked'].value_counts()
train[['Embarked', 'Survived']].groupby(['Embarked'], as_index=False).mean()
# 看一下登船港口的平均票價(jià)
train[['Embarked', 'Fare']].groupby(['Embarked'], as_index=False).mean()
3. 快速實(shí)現(xiàn)一個(gè)簡(jiǎn)單的預(yù)測(cè)系統(tǒng)
3.1 選取特征和處理數(shù)據(jù)
- 為了快速得到結(jié)果,先選直接能用的:Pclass、Sex
- 其中Sex是字符型數(shù)據(jù),需要將其處理成數(shù)值型數(shù)據(jù)
# 將字符型數(shù)據(jù)轉(zhuǎn)換成數(shù)值型數(shù)據(jù),這里順便也把測(cè)試集也轉(zhuǎn)換
train['Sex'] = train['Sex'].map({'female': 0, 'male': 1}).astype(int)
test['Sex'] = test['Sex'].map({'female': 0, 'male': 1}).astype(int)
# 選取特征
quick_feature = ['Pclass', 'Sex']
3.2 切分訓(xùn)練集和測(cè)試集
- 切分比例是train:qtest = 7:3
- 切分前最好先打亂數(shù)據(jù),這樣可以最大程度上避免切分之后的數(shù)據(jù)有系統(tǒng)性的特征偏差
np.random.seed(20180818)
Qtest_n = 630
# 打亂數(shù)據(jù)
train = train.sample(frac = 1.0)
train = train.reset_index()
# 切分?jǐn)?shù)據(jù)
X_train = train.loc[0:Qtest_n, quick_feature].values
y_train = train.loc[0:Qtest_n, 'Survived'].astype(int).values
X_Qtest = train.loc[Qtest_n+1:891, quick_feature].values
y_Qtest = train.loc[Qtest_n+1:891, 'Survived'].astype(int).values
3.3 訓(xùn)練算法并查看性能
- 這里選兩個(gè)算法:邏輯回歸和支持向量機(jī)
from sklearn import linear_model, svm
lgr = linear_model.LogisticRegression()
svc = svm.SVC()
for clf in lgr, svc:
_ = clf.fit(X_train, y_train)
y_ = clf.predict(X_Qtest)
# 計(jì)算準(zhǔn)確率,其實(shí)對(duì)于二分類也可以直接用clf.score(X_Qtest, y_Qtest)得出準(zhǔn)確率
(y_ == y_Qtest).sum() / y_Qtest.size
感覺見證了奇跡一般,其實(shí)數(shù)據(jù)根本沒有改動(dòng)一下,而且只用了兩個(gè)特征,正確率就達(dá)到了0.7885
4. 分析結(jié)果
4.1 正確率究竟是多少
- 雖然一下子就得到了結(jié)果,但一定要冷靜,需要分析這個(gè)正確率究竟是怎樣的
- 為什么說這個(gè)呢,舉個(gè)很簡(jiǎn)單的例子:
假設(shè)一個(gè)識(shí)別中國(guó)人民族的算法,它不用訓(xùn)練,拿到數(shù)據(jù)直接輸出漢族,準(zhǔn)確率也能到92%,但這樣的算法能信嗎?
- 我們先看一下如果全部預(yù)測(cè)死亡,正確率有多少
1 - y_Qtest.sum()/y_Qtest.size
- 全蒙死亡, 預(yù)測(cè)正確率是0.6577,說明我們的學(xué)習(xí)算法還是有點(diǎn)料子的,接下來我們要精確評(píng)價(jià)我們的預(yù)測(cè)性能
- 通過計(jì)算真陽性率(靈敏度)和真陰性率(特異度),看看咱們的正確率究竟幾分是真的
- 真陽性率(True Positive Rate):真正活著的人中預(yù)測(cè)存活的比例
- 真陰性率(True Negative Rate):真正去世的人中預(yù)測(cè)死亡的比例
- 約登指數(shù)可以評(píng)價(jià)整體的預(yù)測(cè)性能
- 約登(Youden)指數(shù):真陽性率 + 真陰性率 - 1
# 1 & 1 =1 1 | 1 = 1
# 1 & 0 =0 1 | 0 = 1
# 0 & 1 =0 0 | 1 = 1
# 0 & 0 =0 0 | 0 = 0
Qtest_svd = y_Qtest.sum()
Qtest_die = y_Qtest.size - y_Qtest.sum()
TPR = (y_ & y_Qtest).sum() / Qtest_svd
TNR = (y_Qtest.size - (y_ | y_Qtest).sum()) / Qtest_die
print("survived : {0} \t TPR : {1:.4f}".format(Qtest_svd, TPR))
print(" dead : {0} \t TNR : {1:.4f}".format(Qtest_die, TNR))
print("Yd_index : {0:.4f}".format(TPR+TNR-1))
- 結(jié)果表明正確預(yù)測(cè)死亡能達(dá)到0.8187,正確預(yù)測(cè)生存能達(dá)到0.7303
- 說明咱們的算法的正確率基本是真的(別被這句話繞進(jìn)去了 ~~~),但是預(yù)測(cè)性能不夠好
- 比作考試的話,就是說我們的算法的分是靠自己做出來的,就是分不夠高,還需努力
- 這里還有一點(diǎn)就是,可是多改幾次前面打亂數(shù)據(jù)時(shí)設(shè)置的隨機(jī)種子,看看不同的隨機(jī)切分對(duì)結(jié)果的影響,由于目前的算法還很簡(jiǎn)單,這些就到后面再弄吧。
5. 下一步要做的事(步入正題)
5.1 我們快速實(shí)現(xiàn)的預(yù)測(cè)系統(tǒng)存在的問題及相應(yīng)的改進(jìn)方向
- 當(dāng)然,第一個(gè)就是預(yù)測(cè)正確率不高,而且靈敏度與特異度差異有點(diǎn)大
- 特征變量過少,因?yàn)榍懊嬗幸恍┟黠@相關(guān)的特征變量并沒有選入,所以需要增加特征變量
- 目前我們還沒有關(guān)注算法訓(xùn)練過程,所以后面還需要檢測(cè)算法訓(xùn)練過程及進(jìn)行相應(yīng)的優(yōu)化
- 不過目前的結(jié)果也提示,抓住主要特征能夠快速得到一個(gè)不錯(cuò)的結(jié)果
6. 進(jìn)一步探索數(shù)據(jù)特征
在初步探索特征中,我們已經(jīng)能看到Pclass、Sex、Age、SibSp、Parch、Fare和Embarked和存活率有相關(guān),其中Pclass和Sex已經(jīng)可以直接使用了
接下來就是要對(duì)剩余的幾個(gè)進(jìn)行處理,這里要注意的是,train數(shù)據(jù)集和test數(shù)據(jù)集要做同樣的處理
6.1 補(bǔ)全數(shù)據(jù)
- 在初步探索中我們已經(jīng)看到了各個(gè)特征的整體情況,有幾個(gè)特征變量數(shù)據(jù)有缺失,所以先要補(bǔ)全數(shù)據(jù)
- 其中年齡缺失的比較多,就按已有年齡的分布進(jìn)行填充,F(xiàn)are和Embarked缺失數(shù)據(jù)都在個(gè)位數(shù),就直接以均值或眾數(shù)填充,Cabin暫時(shí)放棄
np.random.seed(20180818)
for data in train, test:
# 這里去除掉NaN,并將年齡轉(zhuǎn)換成整數(shù),得到一列包含已有年齡的數(shù)組
age_list = (data['Age'].dropna() + 0.5).astype(int)
# 從已有年齡中隨機(jī)抽取相應(yīng)個(gè)數(shù)的年齡
fillage = []
for i in range(len(data['Age']) - data['Age'].count()):
fillage.append(int(np.random.choice(age_list.values, 1)))
# 補(bǔ)全數(shù)據(jù),因?yàn)镕are和Embarked都是極個(gè)別數(shù)據(jù)缺失,就直接以均值和眾數(shù)進(jìn)行補(bǔ)全
data['Age'][pd.isna(data['Age'])] = fillage
data['Fare'][pd.isna(data['Fare'])] = data['Fare'].mean()
data['Embarked'][pd.isna(data['Embarked'])] = 'S'
6.2 Age
- 年齡數(shù)據(jù)已經(jīng)補(bǔ)全,年齡與存活率的相關(guān)性不夠顯著,可以選擇重新分組
- 但是怎么分呢,分多少組呢? 這里嘗試兩種分組的方式,按年齡或人數(shù)分組,分多少組就得多試幾個(gè)了
# 按年齡分組
for n in range(2, 10):
train['SortAge'] = pd.cut(train['Age'], n)
train[['SortAge', 'Survived']].groupby(['SortAge'], as_index=False).mean()
# 按人數(shù)分組
for n in range(2, 10):
train['SortAge'] = pd.qcut(train['Age'], n)
train[['SortAge', 'Survived']].groupby(['SortAge'], as_index=False).mean()
- 兩種分組的結(jié)果中能看出,單純地按人數(shù)或者年齡來分組表現(xiàn)得都不讓人滿意,所以也可以嘗試一下手動(dòng)分組
- 這些分組方式都試一下,后面看看那個(gè)效果最好
- 第一種:按年齡分,由于將近90%的人都在50歲以前,所以組數(shù)不能太多,而且需要重點(diǎn)關(guān)注50歲以前的組別之間的差異度,那么上面分6組表現(xiàn)不錯(cuò)
- 第二種:按人數(shù)分,從上面結(jié)果看可以分5組
- 第三種:手動(dòng)分,從上面結(jié)果可以按年齡0-17、17-21、21-30、30-36、36-80分5組
for data in train, test:
data['SortAge_1'] = 0
data['SortAge_2'] = 0
data['SortAge_3'] = 0
# SortAge_1
data.loc[data['Age'] <= 14, 'SortAge_1'] = 0
data.loc[(data['Age'] > 14) & (data['Age'] <= 27), 'SortAge_1'] = 1
data.loc[(data['Age'] > 27) & (data['Age'] <= 40), 'SortAge_1'] = 2
data.loc[(data['Age'] > 40) & (data['Age'] <= 53), 'SortAge_1'] = 3
data.loc[(data['Age'] > 53) & (data['Age'] <= 66), 'SortAge_1'] = 4
data.loc[ data['Age'] > 66, 'SortAge_1'] = 5
# SortAge_2
data.loc[data['Age'] <= 19, 'SortAge_2'] = 0
data.loc[(data['Age'] > 19) & (data['Age'] <= 25), 'SortAge_2'] = 1
data.loc[(data['Age'] > 25) & (data['Age'] <= 31), 'SortAge_2'] = 2
data.loc[(data['Age'] > 31) & (data['Age'] <= 41), 'SortAge_2'] = 3
data.loc[ data['Age'] > 41, 'SortAge_2'] = 4
# SortAge_3
data.loc[data['Age'] <= 17, 'SortAge_3'] = 0
data.loc[(data['Age'] > 17) & (data['Age'] <= 21), 'SortAge_3'] = 1
data.loc[(data['Age'] > 21) & (data['Age'] <= 30), 'SortAge_3'] = 2
data.loc[(data['Age'] > 30) & (data['Age'] <= 36), 'SortAge_3'] = 3
data.loc[ data['Age'] > 36, 'SortAge_3'] = 4
train['SortAge_3'].value_counts()
train[['SortAge_3', 'Survived']].groupby(['SortAge_3'], as_index=False).mean()
- 手動(dòng)分組效果不錯(cuò),各組間有明顯的差異,而且各組人數(shù)也夠多
6.2 SibSp、Parch
- 這兩個(gè)親屬人數(shù)特征前面已經(jīng)簡(jiǎn)單看了一下和存活率的關(guān)系,我們這里再看一下
train['SibSp'].value_counts() # 各組的總?cè)藬?shù)
train[['SibSp', 'Survived']].groupby(['SibSp'], as_index=False).mean() #各組的存活率
train['Parch'].value_counts() # 各組的總?cè)藬?shù)
train[['Parch', 'Survived']].groupby(['Parch'], as_index=False).mean() #各組的存活率
- SibSp中從各組人數(shù)及相應(yīng)的存活率來看,基本可以按有、無SibSp分兩組進(jìn)行處理
- Parch中從各組人數(shù)及相應(yīng)的存活率來看,也可以按有、無Parch分兩組進(jìn)行處理
- 這樣的話其實(shí)我們也可以把這兩個(gè)加起來,按有沒有親屬分成兩組
- 下面都試一下,看看哪一種法子效果更好一點(diǎn)
train['had_SibSp'] = 0
train['had_Parch'] = 0
train.loc[train['SibSp'] >0, 'had_SibSp'] = 1
train.loc[train['Parch'] >0, 'had_Parch'] = 1
train[['had_SibSp', 'Survived']].groupby(['had_SibSp'], as_index=False).mean()
train[['had_Parch', 'Survived']].groupby(['had_Parch'], as_index=False).mean()
train['FamilySize'] = train['SibSp'] + train['Parch']
train['IsAlone'] = 0
train.loc[train['FamilySize'] >0, 'IsAlone'] = 1
train[['IsAlone', 'Survived']].groupby(['IsAlone'], as_index=False).mean()
- 結(jié)果證明按是否有家屬來分組處理效果更好一些,那么我們的測(cè)試數(shù)據(jù)也要進(jìn)行相應(yīng)的處理
test['FamilySize'] = test['SibSp'] + test['Parch']
test['IsAlone'] = 0
test.loc[test['FamilySize'] >0, 'IsAlone'] = 1
6.3 Fare
- 和年齡一樣,我們也是金額和人數(shù)兩種分組都試一下
for n in range(2, 11):
train['SortFare'] = pd.cut(train['Fare'], n)
train[['SortFare', 'Survived']].groupby(['SortFare'], as_index=False).mean()
for n in range(2, 11):
train['SortFare'] = pd.qcut(train['Fare'], n)
train[['SortFare', 'Survived']].groupby(['SortFare'], as_index=False).mean()
- 這里可以看出按金額分組的效果很差,按人數(shù)來分組效果還不錯(cuò),其中分成2、3、4組都有不錯(cuò)的表現(xiàn)
- 可以都試一下,然后看看那個(gè)效果好
- test集Fare缺了一個(gè)數(shù)據(jù),所以先補(bǔ)齊
test['Fare'] = test['Fare'].fillna(test['Fare'].mean())
for data in train, test:
data['SortFare_2'] = 0
data['SortFare_3'] = 0
data['SortFare_4'] = 0
# SortFare_2
data.loc[data['Fare'] <= 14.45, 'SortFare_2'] = 0
data.loc[ data['Fare'] > 14.45, 'SortFare_2'] = 1
# SortFare_3
data.loc[data['Fare'] <= 8.66, 'SortFare_3'] = 0
data.loc[(data['Fare'] > 8.66) & (data['Fare'] <= 26), 'SortFare_3'] = 1
data.loc[ data['Fare'] > 26, 'SortFare_3'] = 2
# SortFare_4
data.loc[data['Fare'] <= 7.91, 'SortFare_4'] = 0
data.loc[(data['Fare'] > 7.91) & (data['Fare'] <= 14.45), 'SortFare_4'] = 1
data.loc[(data['Fare'] > 14.45) & (data['Fare'] <= 31), 'SortFare_4'] = 2
data.loc[ data['Fare'] > 31, 'SortFare_4'] = 3
6.4 Embarked
- Embarked數(shù)據(jù)有若干個(gè)缺失,直接用眾數(shù)'S'補(bǔ)全即可
- 而且在初步探索中已經(jīng)看到了三組的存活率的差異,這里直接將字符轉(zhuǎn)換成數(shù)值數(shù)據(jù)就可以了
train['Embarked'] = train['Embarked'].fillna('S')
test['Embarked'] = test['Embarked'].fillna('S')
train['Embarked'].count()
test['Embarked'].count()
train['Embarked'] = train['Embarked'].map({'S': 0, 'C': 1, 'Q': 2}).astype(int)
test['Embarked'] = test['Embarked'].map({'S': 0, 'C': 1, 'Q': 2}).astype(int)
再次檢查數(shù)據(jù),除Cabin外數(shù)據(jù)已經(jīng)齊全
for data in train, test:
data.describe()
data.describe(include=[np.object, 'category'])
7. 進(jìn)一步訓(xùn)練算法
7.1 使用已確定的特征
- 上面的特征處理中,有一些已經(jīng)處理完全了:Pclass、Sex、IsAlone和Embarked,先用這四個(gè),看看和之前的性能對(duì)比
- 打亂數(shù)據(jù)并切分就和前面的一樣了,為避免隨機(jī)誤差,這里用和前面一樣的隨機(jī)種子
np.random.seed(2018082401)
# 打亂數(shù)據(jù)
train = train.sample(frac = 1.0)
train = train.reset_index()
train = train.drop(columns=['index'])
# 切分?jǐn)?shù)據(jù)
train_n = 630
X_train = train.loc[0:train_n, :]
y_train = train.loc[0:train_n, 'Survived'].astype(int)
X_cv = train.loc[train_n+1:891, :]
y_cv = train.loc[train_n+1:891, 'Survived'].astype(int)
base_feature = ['Pclass', 'Sex', 'IsAlone', 'Embarked']
7.2 選擇算法,并比較各個(gè)算法的正確率
from sklearn.neighbors import KNeighborsClassifier
from sklearn.svm import SVC
from sklearn.tree import DecisionTreeClassifier
from sklearn.ensemble import RandomForestClassifier, AdaBoostClassifier, GradientBoostingClassifier
from sklearn.naive_bayes import GaussianNB
from sklearn.discriminant_analysis import LinearDiscriminantAnalysis, QuadraticDiscriminantAnalysis
from sklearn.linear_model import LogisticRegression
clf_set = [ KNeighborsClassifier(),
SVC(),
DecisionTreeClassifier(),
RandomForestClassifier(),
AdaBoostClassifier(),
GradientBoostingClassifier(),
GaussianNB(),
LinearDiscriminantAnalysis(),
QuadraticDiscriminantAnalysis(),
LogisticRegression()]
bf = base_feature
for clf in clf_set:
_ = clf.fit(X_train[bf].values, y_train.values)
print("{0:>30s} : {1:<.4f}".format(clf.__class__.__name__, clf.score(X_cv[bf].values, y_cv.values)))
- 結(jié)果表明我們進(jìn)一步的特征處理還是有一點(diǎn)效果的,準(zhǔn)確率比之前有了提升
7.3 確定Age和Fare哪種分組比較適合
sortage_set = ['SortAge_1', 'SortAge_2', 'SortAge_3']
for sortage in sortage_set:
af = ['Pclass', 'Sex', 'IsAlone', 'Embarked']
af.append(sortage)
print("{:>41s}".format(sortage))
for clf in clf_set:
_ = clf.fit(X_train[af].values, y_train.values)
print("{0:>30s} : {1:7.4f}".format(clf.__class__.__name__, clf.score(X_cv[af].values, y_cv.values)))
sortfare_set = ['SortFare_2', 'SortFare_3', 'SortFare_4']
for sortfare in sortfare_set:
ff = ['Pclass', 'Sex', 'IsAlone', 'Embarked']
ff.append(sortfare)
print("{:>41s}".format(sortfare))
for clf in clf_set:
_ = clf.fit(X_train[ff].values, y_train.values)
print("{0:>30s} : {1:7.4f}".format(clf.__class__.__name__, clf.score(X_cv[ff].values, y_cv.values)))
走到這其實(shí)發(fā)現(xiàn)數(shù)據(jù)的偶然性偏差已經(jīng)對(duì)算法有影響了,雖然在這上面看到效果還不錯(cuò),但是嘗試著多改變幾次前面切分?jǐn)?shù)據(jù)的隨機(jī)種子,就會(huì)發(fā)現(xiàn)后面的訓(xùn)練結(jié)果都會(huì)波動(dòng),各種分組方式并不能準(zhǔn)確地判斷優(yōu)劣
這也就是為什么在開始的時(shí)候說不需要在這個(gè)項(xiàng)目上進(jìn)行過多的優(yōu)化
然后試了多次隨機(jī)種子,在結(jié)果中還是能發(fā)現(xiàn)一些東西的,那就是SVC(支持向量機(jī))的score基本都比較高
然后再試一下把SortAge_1和SortFare_2同時(shí)加進(jìn)去
feature = ['Pclass', 'Sex', 'IsAlone', 'Embarked', 'SortAge_1', 'SortFare_2']
for clf in clf_set:
_ = clf.fit(X_train[ff].values, y_train.values)
print("{0:>30s} : {1:7.4f}".format(clf.__class__.__name__, clf.score(X_cv[ff].values, y_cv.values)))
- 最后還是發(fā)現(xiàn),年齡和費(fèi)用加進(jìn)去并沒有有效改善結(jié)果,悲傷 :(
最后可以選SVC算法,然后特征可以把上面的base_feature和feature都試一下
另外關(guān)于Name的特征處理可以參考一下其他的notebook,然后我的就到這里了
clf = SVC()
_ = clf.fit(X_train[feature].values, X_train['Survived'])
y_ = clf.predict(test[feature])