實戰1-Kaggle-Tatanic

泰坦尼克生存預測問題是機器學習入門的經典案例,通過分析已知訓練集的乘客信息和生存結果,對預測集中的乘客做出預測。簡單、教程多,是個初學者練手的好項目。

1. 數據可視化

#coding:utf-8
import matplotlib.pyplot as plt
import numpy as np
import pandas as pd
import statsmodels.api as sm
from statsmodels.nonparametric.kde import KDEUnivariate
from statsmodels.nonparametric import smoothers_lowess
from pandas import DataFrame

df = pd.read_csv("data/train.csv")
df = df.drop(['Ticket','Cabin'], axis=1)

# 指定圖的參數
fig = plt.figure(figsize=(18,6), dpi=100)
a = 0.65
# Step 1
ax1 =fig.add_subplot(341)
df.Survived.value_counts().plot(kind='bar', color="blue", alpha=a)
ax1.set_xlim(-1, len(df.Survived.value_counts()))
plt.title("Step. 1")

ax2 = fig.add_subplot(345)
df.Survived[df.Sex == 'male'].value_counts(sort=False).plot(kind='bar',label='Male')
df.Survived[df.Sex == 'female'].value_counts(sort=False).plot(kind='bar', color='#FA2379',label='Female')
ax2.set_xlim(-1, 2)
plt.title("Step. 2 \nWho Survied? with respect to Gender."); plt.legend(loc='best')

ax3 = fig.add_subplot(346)
(df.Survived[df.Sex == 'male'].value_counts(sort=False)/float(df.Sex[df.Sex == 'male'].size)).plot(kind='bar',label='Male')
(df.Survived[df.Sex == 'female'].value_counts(sort=False)/float(df.Sex[df.Sex == 'female'].size)).plot(kind='bar', color='#FA2379',label='Female')
ax3.set_xlim(-1,2)
plt.title("Who Survied proportionally?"); plt.legend(loc='best')


# Step 3
ax4 = fig.add_subplot(349)
female_highclass = df.Survived[df.Sex == 'female'][df.Pclass != 3].value_counts()
female_highclass.plot(kind='bar', label='female highclass', color='#FA2479', alpha=a)
ax4.set_xticklabels(["Survived", "Died"], rotation=0)
ax4.set_xlim(-1, len(female_highclass))
plt.title("Who Survived? with respect to Gender and Class"); plt.legend(loc='best')

ax5 = fig.add_subplot(3,4,10, sharey=ax1)
female_lowclass = df.Survived[df.Sex == 'female'][df.Pclass == 3].value_counts()
female_lowclass.plot(kind='bar', label='female, low class', color='pink', alpha=a)
ax5.set_xticklabels(["Died","Survived"], rotation=0)
ax5.set_xlim(-1, len(female_lowclass))
plt.legend(loc='best')

ax6 = fig.add_subplot(3,4,11, sharey=ax1)
male_lowclass = df.Survived[df.Sex == 'male'][df.Pclass == 3].value_counts()
male_lowclass.plot(kind='bar', label='male, low class',color='lightblue', alpha=a)
ax6.set_xticklabels(["Died","Survived"], rotation=0)
ax6.set_xlim(-1, len(male_lowclass))
plt.legend(loc='best')

ax7 = fig.add_subplot(3,4,12, sharey=ax1)
male_highclass = df.Survived[df.Sex == 'male'][df.Pclass != 3].value_counts()
male_highclass.plot(kind='bar', label='male highclass', alpha=a, color='steelblue')
ax7.set_xticklabels(["Died","Survived"], rotation=0)
ax7.set_xlim(-1, len(male_highclass))
plt.legend(loc='best')

plt.show()

關鍵語句解析:

  • 圖片格式:bar, kde, barh
  • set_xticklables, setxlim

</br></br></br>

2. 清理數據

原始信息如下:

可以用data.info()獲取整體信息:

2.1 補全缺少的信息(年齡、Cabin)
f = pd.read_csv("data/train.csv")

### 使用 RandomForestClassifier 填補缺失的年齡屬性
def set_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



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(df)
data_train = set_Cabin_type(data_train)

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

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

Age我們嘗試擬合補全,用scikit-learn中的RandomForest來擬合一下缺失的年齡數據(注:RandomForest是一個用在原始數據中做不同采樣,建立多顆DecisionTree,再進行average等等來降低過擬合現象,提高結果的機器學習算法)
按Cabin有無數據,將這個屬性處理成Yes和No兩種類型。

2.2 轉換為0、1
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)

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

  • 原本Cabin取值為yes的,在此處的”Cabin_yes”下取值為1,在”Cabin_no”下取值為0
  • 原本Cabin取值為no的,在此處的”Cabin_yes”下取值為0,在”Cabin_no”下取值為1
2.3 縮小某些參數的波動尺度
#scalling age and fare
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)

2.4 相同方法處理測試集

data_test = pd.read_csv("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)

</br>

3. 邏輯回歸建模

#logistec regression model build
# 用正則取出我們要的屬性值
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)

</br>

4. 預測結果并輸出

# 預測結果
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("data/logistic_regression_predictions.csv", index=False)

得到的結果:

</br>

5. 算法優化

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

得到生存結果和各個變量之間的相關關系:


交叉驗證:

# 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)  #cv參數代表不同的交叉驗證分類方法


#數據分割,看看bad case
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("data/train.csv")
bad_cases = origin_data_train.loc[origin_data_train['PassengerId'].isin(split_cv[predictions != cv_df.as_matrix()[:,0]]['PassengerId'].values)]

print bad_cases

現在有了”train_df” 和 “vc_df” 兩個數據部分,前者用于訓練model,后者用于評定和選擇模型。

我們隨便列一些可能可以做的優化操作:

  • Age屬性不使用現在的擬合方式,而是根據名稱中的『Mr』『Mrs』『Miss』等的平均值進行填充。
  • Age不做成一個連續值屬性,而是使用一個步長進行離散化,變成離散的類目feature。
  • Cabin再細化一些,對于有記錄的Cabin屬性,我們將其分為前面的字母部分(我猜是位置和船層之類的信息) 和 后面的數字部分(應該是房間號,有意思的事情是,如果你仔細看看原始數據,你會發現,這個值大的情況下,似乎獲救的可能性高一些)。
  • Pclass和Sex倆太重要了,我們試著用它們去組出一個組合屬性來試試,這也是另外一種程度的細化。
  • 單加一個Child字段,Age<=12的,設為1,其余為0(你去看看數據,確實小盆友優先程度很高啊)
  • 如果名字里面有『Mrs』,而Parch>1的,我們猜測她可能是一個母親,應該獲救的概率也會提高,因此可以多加一個Mother字段,此種情況下設為1,其余情況下設為0
  • 登船港口可以考慮先去掉試試(Q和C本來就沒權重,S有點詭異)
  • 把堂兄弟/兄妹 和 Parch 還有自己 個數加在一起組一個Family_size字段(考慮到大家族可能對最后的結果有影響)
  • Name是一個我們一直沒有觸碰的屬性,我們可以做一些簡單的處理,比如說男性中帶某些字眼的(‘Capt’, ‘Don’, ‘Major’, ‘Sir’)可以統一到一個Title,女性也一樣。

** learning curves**
有一個很可能發生的問題是,我們不斷地做feature engineering,產生的特征越來越多,用這些特征去訓練模型,會對我們的訓練集擬合得越來越好,同時也可能在逐步喪失泛化能力,從而在待預測的數據上,表現不佳,也就是發生過擬合問題。

對過擬合而言,通常以下策略對結果優化是有用的:

  • 做一下feature selection,挑出較好的feature的subset來做training
  • 提供更多的數據,從而彌補原始數據的bias問題,學習到的model也會更準確

從另一個角度上說,如果模型在待預測的數據上表現不佳,除掉上面說的過擬合問題,也有可能是欠擬合問題,也就是說在訓練集上,其實擬合的也不是那么好。

  • 而對于欠擬合而言,我們通常需要更多的feature,更復雜的模型來提高準確度。

我們也可以把錯誤率替換成準確率(得分),得到另一種形式的learning curve(sklearn 里面是這么做的)。

#coding:utf-8
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.
    參數解釋
    ----------
    estimator : 你用的分類器。
    title : 表格的標題。
    X : 輸入的feature,numpy類型
    y : 輸入的target vector
    ylim : tuple格式的(ymin, ymax), 設定圖像中縱坐標的最低點和最高點
    cv : 做cross-validation的時候,數據分成的份數,其中一份作為cv集,其余n-1份作為training(默認為3份)
    n_jobs : 并行的的任務數(默認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"訓練樣本數")
        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"訓練集上得分")
        plt.plot(train_sizes, test_scores_mean, 'o-', color="r", label=u"交叉驗證集上得分")

        plt.legend(loc="best")

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

    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

這個模塊用于畫出基于測試集和訓練集中的評分得到的曲線,用來判斷是否還需要更多特征。在trainning.py中調用:
learningCurve.plot_learning_curve(clf, u"學習曲線", X, y)

目前的曲線看來,我們的model并不處于overfitting的狀態(overfitting的表現一般是訓練集上得分高,而交叉驗證集上要低很多,中間的gap比較大)。因此我們可以再做些feature engineering的工作,添加一些新產出的特征或者組合特征到模型中。

</br></br>

6. 模型融合(model ensemble)

當我們手頭上有一堆在同一份數據集上訓練得到的分類器(比如logistic regression,SVM,KNN,random forest,神經網絡),那我們讓他們都分別去做判定,然后對結果做投票統計,取票數最多的結果為最后結果。
模型融合可以比較好地緩解,訓練過程中產生的過擬合問題,從而對于結果的準確度提升有一定的幫助。
解決過擬合問題還可以用bagging,sklearn里也有這個方法,具體就是每次訓練去整個訓練集的一個子集,這樣每次的過擬合都是針對子集的過擬合,整合起來就會減輕過擬合問題。

最后編輯于
?著作權歸作者所有,轉載或內容合作請聯系作者
平臺聲明:文章內容(如有圖片或視頻亦包括在內)由作者上傳并發布,文章內容僅代表作者本人觀點,簡書系信息發布平臺,僅提供信息存儲服務。
  • 序言:七十年代末,一起剝皮案震驚了整個濱河市,隨后出現的幾起案子,更是在濱河造成了極大的恐慌,老刑警劉巖,帶你破解...
    沈念sama閱讀 228,702評論 6 534
  • 序言:濱河連續發生了三起死亡事件,死亡現場離奇詭異,居然都是意外死亡,警方通過查閱死者的電腦和手機,發現死者居然都...
    沈念sama閱讀 98,615評論 3 419
  • 文/潘曉璐 我一進店門,熙熙樓的掌柜王于貴愁眉苦臉地迎上來,“玉大人,你說我怎么就攤上這事。” “怎么了?”我有些...
    開封第一講書人閱讀 176,606評論 0 376
  • 文/不壞的土叔 我叫張陵,是天一觀的道長。 經常有香客問我,道長,這世上最難降的妖魔是什么? 我笑而不...
    開封第一講書人閱讀 63,044評論 1 314
  • 正文 為了忘掉前任,我火速辦了婚禮,結果婚禮上,老公的妹妹穿的比我還像新娘。我一直安慰自己,他們只是感情好,可當我...
    茶點故事閱讀 71,826評論 6 410
  • 文/花漫 我一把揭開白布。 她就那樣靜靜地躺著,像睡著了一般。 火紅的嫁衣襯著肌膚如雪。 梳的紋絲不亂的頭發上,一...
    開封第一講書人閱讀 55,227評論 1 324
  • 那天,我揣著相機與錄音,去河邊找鬼。 笑死,一個胖子當著我的面吹牛,可吹牛的內容都是我干的。 我是一名探鬼主播,決...
    沈念sama閱讀 43,307評論 3 442
  • 文/蒼蘭香墨 我猛地睜開眼,長吁一口氣:“原來是場噩夢啊……” “哼!你這毒婦竟也來了?” 一聲冷哼從身側響起,我...
    開封第一講書人閱讀 42,447評論 0 289
  • 序言:老撾萬榮一對情侶失蹤,失蹤者是張志新(化名)和其女友劉穎,沒想到半個月后,有當地人在樹林里發現了一具尸體,經...
    沈念sama閱讀 48,992評論 1 335
  • 正文 獨居荒郊野嶺守林人離奇死亡,尸身上長有42處帶血的膿包…… 初始之章·張勛 以下內容為張勛視角 年9月15日...
    茶點故事閱讀 40,807評論 3 355
  • 正文 我和宋清朗相戀三年,在試婚紗的時候發現自己被綠了。 大學時的朋友給我發了我未婚夫和他白月光在一起吃飯的照片。...
    茶點故事閱讀 43,001評論 1 370
  • 序言:一個原本活蹦亂跳的男人離奇死亡,死狀恐怖,靈堂內的尸體忽然破棺而出,到底是詐尸還是另有隱情,我是刑警寧澤,帶...
    沈念sama閱讀 38,550評論 5 361
  • 正文 年R本政府宣布,位于F島的核電站,受9級特大地震影響,放射性物質發生泄漏。R本人自食惡果不足惜,卻給世界環境...
    茶點故事閱讀 44,243評論 3 347
  • 文/蒙蒙 一、第九天 我趴在偏房一處隱蔽的房頂上張望。 院中可真熱鬧,春花似錦、人聲如沸。這莊子的主人今日做“春日...
    開封第一講書人閱讀 34,667評論 0 26
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽。三九已至,卻和暖如春,著一層夾襖步出監牢的瞬間,已是汗流浹背。 一陣腳步聲響...
    開封第一講書人閱讀 35,930評論 1 287
  • 我被黑心中介騙來泰國打工, 沒想到剛下飛機就差點兒被人妖公主榨干…… 1. 我叫王不留,地道東北人。 一個月前我還...
    沈念sama閱讀 51,709評論 3 393
  • 正文 我出身青樓,卻偏偏與公主長得像,于是被迫代替她去往敵國和親。 傳聞我的和親對象是個殘疾皇子,可洞房花燭夜當晚...
    茶點故事閱讀 47,996評論 2 374

推薦閱讀更多精彩內容