Kaggle泰坦尼克號船難--邏輯回歸預(yù)測生存率

一、題目

https://www.kaggle.com/c/titanic

1-1.png

二、題意分析

train.csv中有891條泰坦尼克號乘客的數(shù)據(jù),包括這些乘客的一些特征與獲救情況。
test.csv中有418條乘客的數(shù)據(jù),包括這些乘客的一些特征但不包括獲救情況。
根據(jù)train.csv中乘客的特征與獲救情況,預(yù)測test.csv中乘客的獲救概率。

三、編程環(huán)境準(zhǔn)備

(一)操作系統(tǒng):Win 10

(二)編程語言:Python 3.6
Win 10安裝Python 3.6

(三)需要的庫:numpy + pandas + matplotlib + sklearn
Win 10安裝numpy、pandas、scipy、matplotlib和sklearn
Win 10系統(tǒng)matplotlib中文無法顯示的解決方案

(四)交互式編程環(huán)境ipython notebook
Win 10基于Python 3.6安裝IPython Notebook

四、數(shù)據(jù)分析

1. 查看train.csv中的數(shù)據(jù)

import pandas as pd #數(shù)據(jù)分析
import numpy as np #科學(xué)計算
from pandas import Series,DataFrame

data_train = pd.read_csv("train.csv")
data_train
4-1.png

這些數(shù)據(jù)就是train.csv中的原始數(shù)據(jù)了,只不過這里是在ipython notebook環(huán)境中顯示罷了。顯示格式類似excel格式。

2. 查看數(shù)據(jù)的基本信息

data_train.info()
<class 'pandas.core.frame.DataFrame'>
RangeIndex: 891 entries, 0 to 890
Data columns (total 12 columns):
PassengerId    891 non-null int64
Survived       891 non-null int64
Pclass         891 non-null int64
Name           891 non-null object
Sex            891 non-null object
Age            714 non-null float64
SibSp          891 non-null int64
Parch          891 non-null int64
Ticket         891 non-null object
Fare           891 non-null float64
Cabin          204 non-null object
Embarked       889 non-null object
dtypes: float64(2), int64(5), object(5)
memory usage: 83.6+ KB

總共有12列數(shù)據(jù):

PassengerId:乘客編號
Survived:是否生存,1表生存,0表示遇難
Pclass:艙位等級,分為一等艙、二等艙、三等艙
Name:乘客姓名
Sex:性別,Male或Female
Age:年齡
SibSp:兄弟姐妹、堂兄弟姐妹人數(shù)
Parch:父母與子女個數(shù)
Ticket:船票信息(上面記載著座位號)
Fare:票價
Cabin:客艙
Embarked:登船港口

這12列數(shù)據(jù)中,有9列數(shù)據(jù)是完整的,即有891條記錄;
Embarked這一列,數(shù)據(jù)缺失了兩條;
Age這一列,差了一百多條數(shù)據(jù);
Cabin這一列,數(shù)據(jù)很不完整,只有204條記錄。

3. 查看數(shù)據(jù)的描述性統(tǒng)計

data_train.describe()
4-3.png

(1)總共有12列數(shù)據(jù),這里只列出了7列,為什么呢?
因為Name、Ticket和Cabin是文本信息,Sex和Embarked是類目信息,無法統(tǒng)計出來。

(2)pandas中的std為樣本標(biāo)準(zhǔn)差。例:x = {1, 2, 3},則平均數(shù)x' = (1 + 2 + 3) / 3 = 2
樣本方差(無偏)D = [abs(1 - 2) + abs(2 - 2) + abs(3 - 3)] / 2 = 1
樣本標(biāo)準(zhǔn)差(無偏)= sqrt(D) = 1

(3)仍以 x = {1, 2, 3}為例
min值 = 0%值 = 1
25%值 = 1.5
50%值 = 2
75%值 = 2.5
max值 = 100%值 = 3

(4)計算mean的時候,會自動剔除沒有記錄的數(shù)據(jù)。以
x = {10, NaN, 20, NaN, 30}為例,平均值 = (10 + 20 + 30) / 3 = 20

(5)從上表結(jié)果可以看出,生存率平均值為0.383838,說明遇難人數(shù)一大半;Pclass的平均值為2.3,說明坐3等艙的乘客居多,因為通常3等艙的價格最便宜艙位最多;平均年齡29.7歲,結(jié)合表格可以看出,很多成年人帶了年幼的小孩,導(dǎo)致平均年齡較小。

五、數(shù)據(jù)分析

(一)乘客屬性分布

import matplotlib.pyplot as plt
fig = plt.figure()
fig.set_size_inches(12, 12)     #設(shè)置畫布尺寸

plt.subplot2grid((2,2),(0,0))  
data_train.Survived.value_counts().plot(kind='bar')
plt.title(u"獲救情況 (1為獲救)")
plt.ylabel(u"人數(shù)")  

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

plt.subplot2grid((2,2),(1,0))
data_train.Embarked.value_counts().plot(kind='bar')
plt.title(u"各登船口岸上船人數(shù)")
plt.ylabel(u"人數(shù)") 

plt.subplot2grid((2,2),(1,1))
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')

plt.show()
5-1.png

從上面的幾個圖可以看出:
遇難人數(shù)占一大半;
三等艙位的乘客最多,按照出行常識,應(yīng)該是三等艙座位多價格便宜;
多數(shù)人從S口上船,是不是可以推測:S口就是普通登船口,C口和Q口是不是專用登船口?
三等艙人數(shù) > 二等艙人數(shù) > 一等艙人數(shù),頭等艙乘客年齡 > 二等艙乘客年齡 > 三等艙乘客年齡,這是因為年齡越大,財富越多,越傾向于買高檔的艙位。
二、三等艙多數(shù)人的年齡介于20~40之間,并且二三等艙人數(shù)比較多,這可以和平均年齡29.7歲相呼應(yīng)。

(二)獲救與各屬性的關(guān)聯(lián)統(tǒng)計

1. 年齡屬性與獲救的關(guān)聯(lián)

fig = plt.figure()
fig.set_size_inches(12, 12)     # 設(shè)置畫布尺寸

plt.subplot2grid((2,2),(0,0))
plt.scatter(data_train.Survived, data_train.Age)
plt.ylabel(u"年齡")            # 設(shè)定縱坐標(biāo)名稱
plt.grid(b=True, which='major', axis='y') 
plt.title(u"按年齡看獲救分布 (1為獲救)")
plt.show()
5-2.png

上圖中藍(lán)色的點表示有這個年齡。
可以看出,大體上,無論是獲救(x = 1.0)還是未獲救(x = 0.0)都有年齡分布,沒有什么規(guī)律。
但是65~75歲的年齡段沒有獲救的人,但有遇難的人,考慮到這個年齡段的乘客數(shù)量很少,可能說明不了什么問題。

2. 艙位等級與獲救的關(guān)聯(lián)

# 各艙位等級的獲救情況
fig = plt.figure()

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"人數(shù)") 
plt.show()
5-3.png

可以看出,頭等艙的獲救機會 > 二等艙的獲救機會 > 三等艙的獲救機會。說明越有錢,買的艙位越好,獲救概率越高。

3. 性別與獲救的關(guān)聯(lián)

#看看各性別的獲救情況
fig = plt.figure()

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"人數(shù)")
plt.show()
5-4.png

可以看出,女性的獲救概率遠(yuǎn)大于男性。說明Lady first被執(zhí)行得不錯。

4. 艙位等級結(jié)合性別的獲救情況

# 各種艙級別情況下各性別的獲救情況
fig=plt.figure()
fig.set_size_inches(14, 7)
plt.title(u"根據(jù)艙等級和性別的獲救情況")

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=45)
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=45)
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()
5-5.png

明顯可以看出,坐頭等艙的女性獲救的概率接近100%,坐二三等艙的女生獲救概率接近50%。男性無論是坐頭等艙還是二三等艙,獲救概率都比較低,尤其是坐二三等艙獲救的概率更低。

5. 登船港口的獲救情況

fig = plt.figure()

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"人數(shù)") 
plt.show()
5-6.png

看起來,C港口獲救概率要高一點,S港口和Q港口的獲救概率較低。

6. (堂)兄弟姐妹與父母子女?dāng)?shù)量對獲救的影響

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

g = data_train.groupby(['Parch','Survived'])
df = pd.DataFrame(g.count()['PassengerId'])
print (df)
                PassengerId
SibSp Survived             
0     0                 398
      1                 210
1     0                  97
      1                 112
2     0                  15
      1                  13
3     0                  12
      1                   4
4     0                  15
      1                   3
5     0                   5
8     0                   7
                PassengerId
Parch Survived             
0     0                 445
      1                 233
1     0                  53
      1                  65
2     0                  40
      1                  40
3     0                   2
      1                   3
4     0                   4
5     0                   4
      1                   1
6     0                   1

上面的運行結(jié)果,看不出什么規(guī)律。

7. 船艙信息對獲救的影響

#ticket是船票編號,應(yīng)該是unique的,和最后的結(jié)果沒有太大的關(guān)系,先不納入考慮的特征范疇
#cabin只有204個乘客有值,我們先看看它的一個分布
data_train.Cabin.value_counts()
G6             4
C23 C25 C27    4
B96 B98        4
F2             3
F33            3
C22 C26        3
E101           3
D              3
C83            2
C123           2
C92            2
F4             2
B77            2
D20            2
B51 B53 B55    2
C2             2
B22            2
E121           2
D35            2
E8             2
D33            2
B20            2
D36            2
B49            2
D17            2
C78            2
F G73          2
C125           2
E25            2
B28            2
              ..
C148           1
B69            1
E31            1
E46            1
A23            1
E58            1
C32            1
D7             1
D45            1
C111           1
E17            1
A31            1
F E69          1
D10 D12        1
A10            1
D48            1
C95            1
T              1
C106           1
C30            1
E68            1
B102           1
C101           1
D11            1
B101           1
B79            1
D15            1
C128           1
B38            1
E10            1
Name: Cabin, Length: 147, dtype: int64

這些信息很不集中,很難看出問題來。并且也不知道G6, C23, C25這一類的信息是什么意思。只能做個推測:字母代表船艙(房間)號,數(shù)字代表床位(座位)號;或者字母代表樓層號,數(shù)字代表房間號。
注意Cabin只有204條記錄,缺失了700多條記錄。缺失的原因可能有兩種:
(1)本來是有的,可能后來由于什么原因,信息丟失了
(2)本來就沒有這些信息。比如可能只有一等艙二等艙有這種信息,三等艙沒有這種信息(想像一個大屋,沒有固定的座位,大家隨便坐)

咱們從一個較粗的粒度--Cabin信息的有無來試一下:

fig = plt.figure()
fig.set(alpha=0.2)  # 設(shè)定圖表顏色alpha參數(shù)

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"人數(shù)")
plt.show()
5-7.png

可以看出,有Cabin信息的獲救概率明顯大于無Cabin信息,這是不是意味著第二種猜測(即本來就沒有這些信息)才是對的呢?

六、數(shù)據(jù)預(yù)處理

數(shù)據(jù)分析完之后,可以對部分?jǐn)?shù)據(jù)進(jìn)行預(yù)處理。

(一)Cabin和Age預(yù)處理

Cabin和Age缺失較多,咱們可以先對這兩個字段進(jìn)行處理。

Cabin可以按上面的分析,先處理成Yes和No兩種類型。

對于Age,通常遇到缺值的情況,我們會有幾種常見的處理方式:
(1)如果缺值的樣本占總數(shù)比例極高,我們可能就直接舍棄了,作為特征加入的話,可能反倒帶入noise,影響最后的結(jié)果了
(2)如果缺值的樣本適中,而該屬性非連續(xù)值特征屬性(比如說類目屬性),那就把NaN作為一個新類別,加到類別特征中
(3)如果缺值的樣本適中,而該屬性為連續(xù)值特征屬性,有時候我們會考慮給定一個step(比如這里的age,我們可以考慮每隔2/3歲為一個步長),然后把它離散化,之后把NaN作為一個type加到屬性類目中。
(4)有些情況下,缺失的值個數(shù)并不是特別多,那我們也可以試著根據(jù)已有的值,擬合一下數(shù)據(jù),補充上。

本例中,后兩種處理方式應(yīng)該都是可行的,我們先試試按第(4)種方式擬合補全Age數(shù)據(jù)。
我們這里用scikit-learn中的隨機森林方法(RandomForest)來擬合一下缺失的年齡數(shù)據(jù)

# 注意,若第二次運行本程序,會報"ValueError: Found array with 0 sample(s) (shape=(0, 4)) while a minimum of 1 is required.",
# 這是因為在上次運行本段程序時,data_train已經(jīng)發(fā)生了變化 
# 解決方案:不要連續(xù)運行本程序,在再次運行本程序之前,要先運行上面第一段程序,以獲得原data_train的值

from sklearn.ensemble import RandomForestRegressor

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

    # 把已有的數(shù)值型特征取出來丟進(jìn)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即目標(biāo)年齡
    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)

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

    # 用得到的預(yù)測結(jié)果填補原缺失數(shù)據(jù)
    df.loc[df.Age.isnull(), 'Age'] = predictedAges 

    return df, rfr

def set_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)
data_train
6-1.png

可以看到,Age和Cabin的值已處理。

(二)特征因子化

因為邏輯回歸建模時,需要輸入的特征都是數(shù)值型特征,我們通常會先對類目型的特征因子化。
什么叫做因子化呢?舉個例子:
以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

上面的程序是將Cabin處理成Cabin_Yes和Cabin_No,Embarked處理成Embarked_C、Embarked_Q和Embarked_S,Sex處理成Sex_Male和Sex_Female,Pclass處理成Pclass_1、Pclass_2和Pclass_3;
接著用concat函數(shù)將這些新的屬性連接到dataframe中,再通過drop函數(shù)將原先的Pclass、Name、Sex、Ticket、Cabin和Embarked這六個屬性從dataframe中去掉。

6-2.png

(三)數(shù)據(jù)標(biāo)準(zhǔn)化

注意Age和Fare這兩個屬性的數(shù)據(jù)取值范圍太大,這將對邏輯回歸的收斂造成不利的影響。處理方法是將其標(biāo)準(zhǔn)化。
標(biāo)準(zhǔn)化就是將特征數(shù)據(jù)的分布調(diào)整成標(biāo)準(zhǔn)正太分布,也叫高斯分布,也就是使得數(shù)據(jù)的均值為0,方差為1。

import sklearn.preprocessing as preprocessing
scaler = preprocessing.StandardScaler()
age_scale_param = scaler.fit(df['Age'].values.reshape(-1, 1))
df['Age_scaled'] = scaler.fit_transform(df['Age'].values.reshape(-1, 1), age_scale_param)
fare_scale_param = scaler.fit(df['Fare'].values.reshape(-1, 1))
df['Fare_scaled'] = scaler.fit_transform(df['Fare'].values.reshape(-1, 1), fare_scale_param)
df
6-3.png

至此,我們已把需要的屬性值抽出來,轉(zhuǎn)成scikit-learn里面LogisticRegression可以處理的格式。

七、邏輯回歸建模

(一)建立模型

把需要的特征字段取出來,轉(zhuǎn)成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結(jié)果
y = train_np[:, 0]

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

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

clf

生成的模型如下:

LogisticRegression(C=1.0, class_weight=None, dual=False, fit_intercept=True,
          intercept_scaling=1, max_iter=100, multi_class='ovr', n_jobs=1,
          penalty='l1', random_state=None, solver='liblinear', tol=1e-06,
          verbose=0, warm_start=False)

(二)對測試數(shù)據(jù)集進(jìn)行預(yù)處理

測試集預(yù)處理的過程和訓(xùn)練集的預(yù)處理過程一樣

data_test = pd.read_csv("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()
# 根據(jù)特征屬性X預(yù)測年齡并補上
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'].values.reshape(-1, 1), age_scale_param)
df_test['Fare_scaled'] = scaler.fit_transform(df_test['Fare'].values.reshape(-1, 1), fare_scale_param)
df_test
7-1.png

(三)預(yù)測

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("predicted_result.csv", index=False)
result
7-2.png

此時,在項目目錄下,可觀察到新生成了predicted_result.csv文件

(四)提交結(jié)果

將predicted_result.csv提交到kaggle上:

7-3.png

預(yù)測準(zhǔn)確率為76.55%,因為模型比較粗糙,這個結(jié)果算是差強人意。

八、優(yōu)化

(一)模型系數(shù)分析

把模型系數(shù)和特征關(guān)聯(lián)起來

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

從相關(guān)系數(shù)可以看出
(1)有Cabin信息的,獲救概率會大很多。除了從有無這個較粗的維度來分析外,是否可以從Cabin信息本身來進(jìn)一步挖掘比如字母和數(shù)字對獲救概率的影響?
(2)若從S港口登船,獲救概率會明顯降低,但是C港口和Q港口的系數(shù)為0,這與上面的分析是不完全匹配。圖5-6顯示,S港口和Q港口獲救率比較低,C口的獲救率為50%左右。所以C口的相關(guān)系數(shù)為0有道理,但是Q口的相關(guān)系數(shù)為0則不太合理。另外,S、Q和C分別代表什么意思呢?所以說做數(shù)據(jù)挖掘時,熟悉業(yè)務(wù)也是很重要的。
(3)從性別來看,女性極大的提高獲救率,男性較大降低獲救率。
(4)從艙位等級來看,一等艙的獲救率較高,而三等艙獲救率大幅降低。有錢就是好啊。
(5)從年齡來看,負(fù)相關(guān),年齡越小獲救率越高,年齡越大獲救率越低。年齡越小越容易獲救這好理解,但是年齡越大呢?有時候遇到危險時,“小孩、婦女和老人先走”,這里年齡越大是不是有個范圍呢?比如20-60歲年齡越大獲救率越低,但是60歲以上的乘客獲救會不會大一點?是否考慮設(shè)個系數(shù)體現(xiàn)兩頭值的影響?或者按年齡段來分析,比如每隔5歲作為一個年齡段?
(6)票價Fare也有影響。但相關(guān)系數(shù)比較低。相關(guān)系數(shù)比較低,是因為真的影響很小,還是因為挖掘的不夠?按常理頭等艙的票價會更貴,應(yīng)該對獲救概率有所影響。

(二)交叉驗證

交叉驗證通常是把train.csv分成兩部分,一部分用于訓(xùn)練模型,另外一部分?jǐn)?shù)據(jù)用來預(yù)測結(jié)果,然后將預(yù)測結(jié)果與實際結(jié)果比較,這樣就能知道模型的預(yù)測效果。
用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))
[0.81564246 0.81564246 0.78651685 0.78651685 0.81355932]

預(yù)測結(jié)果比先前的結(jié)果要好一點,因為用的是不同的數(shù)據(jù)集。

(三)提取bad case

做數(shù)據(jù)分割并且在原始數(shù)據(jù)集提取bad case

# 分割數(shù)據(jù),按照 訓(xùn)練數(shù)據(jù):cv數(shù)據(jù) = 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數(shù)據(jù)進(jìn)行預(yù)測

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("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

總共提取到50條bad case記錄:

8-2.png

拿到bad cases之后,仔細(xì)分析,有可能會產(chǎn)生新的想法或思路。比如有一部分可能會印證在系數(shù)分析部分的猜測,那這些優(yōu)化的想法的優(yōu)先級可以放高一些。

現(xiàn)在有了”train_df” 和 “vc_df” 兩個數(shù)據(jù)部分,前者用于訓(xùn)練model,后者用于評定和選擇模型。可以考慮做一些優(yōu)化操作,比如:
(1)Age屬性不使用上面的擬合方式,而是根據(jù)名稱中的“Mr”、“Mrs”、“Miss”等的平均值進(jìn)行填充。
(2)Age不做成一個連續(xù)值屬性,而是使用一個步長進(jìn)行離散化,變成離散的類目特征。
(3)Cabin再細(xì)化一些,對于有記錄的Cabin屬性,我們將其分為前面的字母部分(可能是位置和船層之類的信息) 和 后面的數(shù)字部分(應(yīng)該是房間號,有意思的事情是,如果你仔細(xì)看看原始數(shù)據(jù),你會發(fā)現(xiàn),這個值大的情況下,似乎獲救的可能性高一些)。
(4)Pclass和Sex倆太重要了,我們試著用它們?nèi)ソM出一個組合屬性來試試,這也是另外一種程度的細(xì)化。
(5)單加一個Child字段,Age<=12的,設(shè)為1,其余為0(你去看看數(shù)據(jù),確實小盆友優(yōu)先程度很高啊)
(6)如果名字里面有“Mrs”,而Parch>1的,我們猜測她可能是一個母親,應(yīng)該獲救的概率也會提高,因此可以多加一個Mother字段,此種情況下設(shè)為1,其余情況下設(shè)為0
(7)把堂兄弟/兄妹和Parch還有自己 個數(shù)加在一起組一個Family_size字段(考慮到大家族可能對最后的結(jié)果有影響)
(8)Name是一個我們一直沒有觸碰的屬性,我們可以做一些簡單的處理,比如說男性中帶某些字眼的(“Capt”, “Don”, “Major”, “Sir”)可以統(tǒng)一到一個Title,女性也一樣。

(四)過擬合和欠擬合

在訓(xùn)練模型時,經(jīng)常會產(chǎn)生過擬合或欠擬合的問題。

在統(tǒng)計學(xué)或機器學(xué)習(xí)中,擬合指的是逼近目標(biāo)函數(shù)的遠(yuǎn)近程度。統(tǒng)計學(xué)或機器學(xué)習(xí)通常通過用于描述函數(shù)和目標(biāo)函數(shù)逼近的吻合程度來描述擬合的好壞。

當(dāng)某個模型過度的學(xué)習(xí)訓(xùn)練數(shù)據(jù)中的細(xì)節(jié)和噪音,以至于模型在新的數(shù)據(jù)上表現(xiàn)很差,我們稱過擬合發(fā)生了。這意味著訓(xùn)練數(shù)據(jù)中的噪音或者隨機波動也被當(dāng)做特征被模型學(xué)習(xí)了。而問題就在于這些概念不適用于新的數(shù)據(jù),從而導(dǎo)致模型泛化性能變差。

欠擬合就是模型沒有很好地捕捉到數(shù)據(jù)特征,不能夠很好地擬合數(shù)據(jù) 。

當(dāng)訓(xùn)練集和驗證集準(zhǔn)確度都很低時,則一般是欠擬合,(此時訓(xùn)練集和驗證集損失error都比較大)
而當(dāng)訓(xùn)練集準(zhǔn)確度很高而驗證集準(zhǔn)確度很低時,則一般是過擬合(此時訓(xùn)練集損失error比較小而驗證集比較大)。

一開始我們的模型往往是欠擬合的,也正是因為如此才有了優(yōu)化的空間,我們需要不斷的調(diào)整算法來使得模型的預(yù)測能力變得更強。但是優(yōu)化到了一定程度可能會產(chǎn)生過擬合的問題,這時就需要解決過擬合的問題了。

(五)學(xué)習(xí)曲線

import numpy as np
import matplotlib.pyplot as plt
from sklearn.learning_curve import learning_curve

# 用sklearn的learning_curve得到training_score和cv_score,使用matplotlib畫出learning curve
def plot_learning_curve(estimator, title, X, y, ylim=None, cv=None, n_jobs=1, 
                        train_sizes=np.linspace(.05, 1., 20), verbose=0, plot=True):
    """
    畫出data在某模型上的learning curve.
    參數(shù)解釋
    ----------
    estimator : 你用的分類器。
    title : 表格的標(biāo)題。
    X : 輸入的feature,numpy類型
    y : 輸入的target vector
    ylim : tuple格式的(ymin, ymax), 設(shè)定圖像中縱坐標(biāo)的最低點和最高點
    cv : 做cross-validation的時候,數(shù)據(jù)分成的份數(shù),其中一份作為cv集,其余n-1份作為training(默認(rèn)為3份)
    n_jobs : 并行的的任務(wù)數(shù)(默認(rèn)1)
    """
    train_sizes, train_scores, test_scores = learning_curve(
        estimator, X, y, cv=cv, n_jobs=n_jobs, train_sizes=train_sizes, verbose=verbose)

    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)

    if plot:
        plt.figure()
        plt.title(title)
        if ylim is not None:
            plt.ylim(*ylim)
        plt.xlabel(u"訓(xùn)練樣本數(shù)")
        plt.ylabel(u"得分")
        plt.gca().invert_yaxis()
        plt.grid()

        plt.fill_between(train_sizes, train_scores_mean - train_scores_std, train_scores_mean + train_scores_std, 
                         alpha=0.1, color="b")
        plt.fill_between(train_sizes, test_scores_mean - test_scores_std, test_scores_mean + test_scores_std, 
                         alpha=0.1, color="r")
        plt.plot(train_sizes, train_scores_mean, 'o-', color="b", label=u"訓(xùn)練集上得分")
        plt.plot(train_sizes, test_scores_mean, 'o-', color="r", label=u"交叉驗證集上得分")

        plt.legend(loc="best")

        plt.draw()
        plt.gca().invert_yaxis()
        plt.show()

    midpoint = ((train_scores_mean[-1] + train_scores_std[-1]) + (test_scores_mean[-1] - test_scores_std[-1])) / 2
    diff = (train_scores_mean[-1] + train_scores_std[-1]) - (test_scores_mean[-1] - test_scores_std[-1])
    return midpoint, diff

plot_learning_curve(clf, u"學(xué)習(xí)曲線", X, y)
8-3.png

從上圖大致可以看出,訓(xùn)練集和交叉驗證集上的得分曲線走勢還是符合預(yù)期的。
從目前的曲線來看,我們的模型并不處于過擬合的狀態(tài)(過擬合的表現(xiàn)一般是訓(xùn)練集上得分高,而交叉驗證集上要低很多,中間的gap比較大)。因此我們可以再做些特征工程的工作,添加一些新產(chǎn)出的特征或者組合特征到模型中。

九、模型融合

先舉個例子:
你和班上的某個學(xué)霸關(guān)系很好,每次作業(yè)都“模仿”他,于是大多數(shù)情況下,他做對了,你也對了。但是大神一旦做錯了,你也只能跟著一起錯。。
再來看看另外一個場景,你和班上的5學(xué)霸關(guān)系都很好,每次都把他們作業(yè)拿過來,對比一下,再“自己做”,如果哪天某學(xué)霸寫錯了,但是另外四個寫對了,從概率的角度來講,你肯定會選擇另外四個學(xué)霸的答案,對吧?

模型融合也是這樣的道理。
所謂模型融合,是按照不同的思路來組合基礎(chǔ)模型,在保證準(zhǔn)確度的同時也提升了模型防止過擬合的能力。
模型融合的方法有Voting, Averaging, Ranking, Binning, Bagging, Boosting, Stacking, Blending等。

我們現(xiàn)在只講了logistic regression,如果我們還想用這個融合思想去提高我們的結(jié)果,該怎么做呢?
既然這個時候模型沒得選,那咱們就在數(shù)據(jù)上動腦筋。如果模型出現(xiàn)過擬合,一定是在我們的訓(xùn)練上出現(xiàn)擬合過度造成的。
那我們干脆就不要用全部的訓(xùn)練集,每次取訓(xùn)練集的一個子集做訓(xùn)練,這樣,我們雖然用的是同一個機器學(xué)習(xí)算法(邏輯回歸),但是得到的模型卻是不一樣的;同時,因為我們沒有任何一份子數(shù)據(jù)集是全的,因此即使出現(xiàn)過擬合,也是在子訓(xùn)練集上出現(xiàn)過擬合,而不是全體數(shù)據(jù)上,這樣做一個融合,可能對最后的結(jié)果有一定的幫助。這種方法就是Bagging方法。

我們用scikit-learn里面的Bagging來完成上面的思路。

from sklearn.ensemble import BaggingRegressor

train_df = df.filter(regex='Survived|Age_.*|SibSp|Parch|Fare_.*|Cabin_.*|Embarked_.*|Sex_.*|Pclass.*|Mother|Child|Family|Title')
train_np = train_df.as_matrix()

# y即Survival結(jié)果
y = train_np[:, 0]

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

# fit到BaggingRegressor之中
clf = linear_model.LogisticRegression(C=1.0, penalty='l1', tol=1e-6)
bagging_clf = BaggingRegressor(clf, n_estimators=20, max_samples=0.8, max_features=1.0, bootstrap=True, bootstrap_features=False, n_jobs=-1)
bagging_clf.fit(X, y)

test = df_test.filter(regex='Age_.*|SibSp|Parch|Fare_.*|Cabin_.*|Embarked_.*|Sex_.*|Pclass.*|Mother|Child|Family|Title')
predictions = bagging_clf.predict(test)
result = pd.DataFrame({'PassengerId':data_test['PassengerId'].as_matrix(), 'Survived':predictions.astype(np.int32)})
result.to_csv("predicted_bagging_result.csv", index=False)

運行之后,在項目的路徑下就會生成predicted_bagging_reslult.csv。

將predicted_bagging_result.csv提交到kaggle上

9-1.png

可以看到,預(yù)測的準(zhǔn)確度略有提升。
雖然提升的不多,但是模型融合的思路是初學(xué)者必須掌握的。

十、Github代碼下載

https://github.com/zhenghaishu/Kaggle

十一、參考資料

https://blog.csdn.net/han_xiaoyang/article/details/49797143


了解小朋友學(xué)編程請加QQ307591841(微信與QQ同號),或QQ群581357582。
關(guān)注公眾號請掃描二維碼


qrcode_for_kidscode_258.jpg
最后編輯于
?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請聯(lián)系作者
【社區(qū)內(nèi)容提示】社區(qū)部分內(nèi)容疑似由AI輔助生成,瀏覽時請結(jié)合常識與多方信息審慎甄別。
平臺聲明:文章內(nèi)容(如有圖片或視頻亦包括在內(nèi))由作者上傳并發(fā)布,文章內(nèi)容僅代表作者本人觀點,簡書系信息發(fā)布平臺,僅提供信息存儲服務(wù)。

相關(guān)閱讀更多精彩內(nèi)容

友情鏈接更多精彩內(nèi)容