背景
關於 Kaggle
- www.kaggle.com/
- 這是一個為你提供完美資料,為你提供實際應用場景,可以與小夥伴在資料探勘領域 high 的不要不要的的地方啊!!!
Kaggle 是一個用來學習、分享和競賽的線上資料實驗平臺,有點類似 KDD—CUP(國際知識發現和資料探勘競賽),企業或者研究者可以將背景、資料、期望指標等釋出在 kaggle 上,用競賽的方式向全世界的資料科學家和愛好者尋求解決方案。熱愛數(dong)據(shou)挖(zhe)掘(teng)的小夥伴們可以下載/分析資料,使用統計/機器學習/資料探勘等方面的知識,建立演算法模型,得出結果並提交,排名靠前可能還會有獎勵哦!
關於泰坦尼克號之災
- 問題背景頁
- 下載 Data 的頁面
- 泰坦尼克號問題背景
- 就是大家從小到大被洗腦的“u jump I jump”的「jack 和 rose」的故事了。遊艇在撞擊了一個冰山後沉沒了。乘客們都驚慌失措,副船長「lady and kid first」,所以模型不會向拋硬幣那樣看臉決定你是否獲救。而是有著一定背景的,至於出了女士和孩子優先,還有哪些值得我們考慮,這就是稍後我們在特徵工程中解決的問題了。
- 訓練和測試資料是一些乘客的個人資訊以及存活情況,嘗試應用這些資料來建立一個合適的模型進行預測。
- 這是一個二分類問題(survived 或者 not),本文嘗試用 logistic regression 來處理問題
- 說明
- 「沒有所謂的演算法優劣,也沒有絕對高效能的機器學習演算法,只有在特定的場景、資料和特徵下更合適的機器學習的演算法。」由於還只是在學習階段,對於 XGBC、隨機森林、SVC 還不瞭解,本文所用的演算法只是 logistic regression。
初識資料
在 Data 下我們會看到官方給的 train.csv 和 test.csv 兩個檔案,分別是訓練和測試資料。我們可以使用 virtualenv 來建立一個“隔離”的 python 應用環境(虛擬環境)。在這裡,你不需要考慮系統原有庫的版本,只需要 pip 來管理你需要用到的一切。
import pandas as pd
import numpy as np
from pandas import Series,DataFrame
data_train = pd.read_csv("./train.csv")
pd.DataFrame(data_train)
複製程式碼
pandas 是常用的 python 資料處理包,把 csv 檔案讀入成 dataframe 格式,在 jupyter notebook 中,可以看到我們的資料長什麼樣:
我們就可以把它看作一張 excel 表格,共有 12 列,891 行(代表在 train_csv 中共有 891 個乘客)。Survived 欄位代表該乘客是否獲救(1 代表獲救,0 代表沒有獲救),其餘是一些個人資訊
- passengerId => 乘客的 ID
- Pclass => 乘客所在艙位的等級(1、2、3 等艙)
- Name => 姓名
- Sex => 性別
- Age => 年齡
- SibSp => 兄弟姐妹個數
- Parch => 父母與小孩個數
- Ticket => 船票資訊
- Fare => 票價
- Cabin => 客艙資訊
- Embarked => 登船所在港口
data_train.info()
複製程式碼
這裡告訴了我麼一些關於 data_train 的基本資訊,比如共有 891 個乘客,但是有些欄位的資料不全。比如 Age(年齡)、Cabin(客艙資訊)。
data_train.describe()
複製程式碼
在這裡我們看到了什麼資訊呢?從 mean 行中約有 0.38 的人最後獲救了,乘客的平均年齡為 29.7 歲,平均票價為 32.20
資料初步分析
每個乘客大概有 12 種屬性提供給我們,僅僅對於上面兩行操作對於資料的瞭解還無法為我們提供想法和思路,我們現在不知道哪些有用哪些對於模型沒有用,所以到了最關鍵的特徵工程環節。我們知道最終的 output 是 Suvived,現在需要尋找最後的 Survived 與各個屬性之間有著什麼樣的內在關係。從大副的口中已經得知「要首先考慮婦女和兒童」。
for x in data1_x:
if data1[x].dtype != 'float64' :
print('Survival Correlation by:', x)
print(data1[[x, Target[0]]].groupby(x, as_index=False).mean())
print('-'*10, '\n')
print(pd.crosstab(data1['Title'],data1[Target[0]]))
複製程式碼
在本圖中,我們發現女性有 0.74 活下來,而男性只有 0.18。歪果盆友果然很尊重 lady,lady first 踐行得不錯。性別無疑要作為重要特徵加入最後的模型之中。在 Pclass 中,客艙為等級 1 的乘客獲救的概率高很多,嗯這個一定也影響最後獲救的結果(等級高的乘客一定也更有錢)。
fig = plt.figure()
fig.set(alpha=0.2)
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({'Survived':Survived_1, 'unSurvived':Survived_0})
df.plot(kind='bar', stacked=True)
plt.title("Survived status of all passenger classes")
plt.xlabel("passanger's level")
plt.ylabel("number")
plt.show()
複製程式碼
從本圖中我們可以看到,明顯如果你是等級為 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("age")# plots an axis lable
plt.ylabel("dendity")
plt.title("Age distribution of passengers levels")
plt.legend(('first level', 'second level','third level'),loc='best')
複製程式碼
從各等級乘客的年齡分佈中,我們可以看到「不同艙位/乘客等級可能和財富/地位有關係,最後獲救概率可能會不一樣」,所以年齡也會是影響我們最終結果的原因之一。
簡單資料預處理
我們對大體資料已經看過一遍了,對於感興趣的屬性也有了大概的瞭解。現在我們需要 簡單處理一下這些資料,為機器學習建模做點準備了。先從最突出的資料屬性開始吧,Cabin 和 Age,因為這兩項有些乘客的資訊不包含它們,有丟失的資料對於下一步的工作影響太大。
先說 Cabin,暫時我們就按照剛才說的,按 Cabin 有無資料,將這個屬性處理成 Yes 和 No 兩種型別吧。
再說 Age:
通常遇到缺值的情況,我們會有幾種常見的處理方式
- 如果缺值的樣本佔總數比例極高,我們可能就直接捨棄了,作為特徵加入的話,可能反倒帶入 noise,影響最後的結果了
- 如果缺值的樣本適中,而該屬性非連續值特徵屬性(比如說類目屬性),那就把 NaN 作為一個新類別,加到類別特徵中
- 如果缺值的樣本適中,而該屬性為連續值特徵屬性,有時候我們會考慮給定一個 step(比如這裡的 age,我們可以考慮每隔 2/3 歲為一個步長),然後把它離散化,之後把 NaN 作為一個 type 加到屬性類目中。
- 有些情況下,缺失的值個數並不是特別多,那我們也可以試著根據已有的值,擬合一下資料,補充上。
本例中,因為 Cabin 不是影響最終結果的特徵之一。所以直接考慮別的需要用到的特徵(性別,等級,等級),並將其中的類目型轉化為數值型特徵,我們可以使用 pandas 的“get_dummies”來完成這個工作,並接在原來的“data_train”上
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_Embarked, dummies_Sex, dummies_Pclass], axis=1)
df.drop(['Pclass', 'Name', 'Sex', 'Ticket', 'Embarked'], axis=1, inplace=True)
df
複製程式碼
na!我們將這些類目屬性成功轉化為 0,1 的數值屬性了。這樣看來,好像差不多都完成了,可是如果再看看 Age 和 Fare 兩個屬性,乘客關於這兩個屬性的數值變化幅度也太大了!!如果大家瞭解邏輯迴歸與梯度下降的話,會知道各屬性之間的 scale 差距太大,將對收斂速度造成很大的傷害(甚至不收斂)... 所以我們先用 scikit-learn 裡面的 preprocessing 模組對這兩個屬性做一個處理(就是將變化幅度較大的特徵化到 [-1,1] 內)
import sklearn.preprocessing as preprocessing
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)
df
複製程式碼
嗯,這樣初級的資料處理就完成的差不多了
建模
我麼把需要的 feature 欄位提取出來,轉成 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 結果
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)
clf
複製程式碼
OK!!!通過這樣建模後,我們得到一個 model,然後再把 test.csv 通過同樣的資料簡單處理後,就可以得到預測結果了。
系統優化
等等,你以為這樣就完了麼。這其實只完成了剛剛開始的一步,我們只是做了一個 baseline model,一切都還是基礎的,我們還需要優化。
不過在現在的場景下,先不著急做這個事情,我們這個 baseline 系統還有些粗糙,先再挖掘挖掘。
- 首先,Name 和 Ticket 兩個屬性被我們完整捨棄了 (好吧,其實是因為這倆屬性,幾乎每一條記錄都是一個完全不同的值,我們並沒有找到很直接的處理方式)。
- 然後,我們想想,年齡的擬合本身也未必是一件非常靠譜的事情,我們依據其餘屬性,其實並不能很好地擬合預測出未知的年齡。再一個,以我們的日常經驗,小盆友和老人可能得到的照顧會多一些,這樣看的話,年齡作為一個連續值,給一個固定的係數,應該和年齡是一個正相關或者負相關,似乎體現不出兩頭受照顧的實際情況,所以,說不定我們把年齡離散化,按區段分作類別屬性會更合適一些。(大家去 kaggle 上可以看看大神的 kernels)
文 / joeCDC
數學愛好者
編 / 熒聲
本文已由作者授權釋出,版權屬於創宇前端。歡迎註明出處轉載本文。本文連結:knownsec-fed.com/2018-12-04-…
想要訂閱更多來自知道創宇開發一線的分享,請搜尋關注我們的微信公眾號:創宇前端(KnownsecFED)。歡迎留言討論,我們會盡可能回覆。
感謝您的閱讀。