Kaggle Titanic項目代碼精簡版(排名1307)
一、分析思路
第一次接觸Kaggle,有很多東西需要學習,Titanic這個比賽項目,主要是供參賽者熟悉Kaggle這個網站的,所以有很多的人都做了這個分析並分享了Kernel。我也是初學者,選擇這個項目的好處是Kernel資源比較多,討論也很多,方便學習。
首先介紹一下我的分析思路。很多人的分析都用了很長的代碼,其中特徵工程、處理缺失值、畫圖等佔了大多數篇幅。我的思路不太一樣,想用儘可能少的代碼,得出儘可能高的分數,所以在瀏覽kernel的時候,就專門找代碼短的,結果找到了一個準確率是0.78469的,代碼就二十幾行,正是我想要的。把代碼叉過來,自己跑了一遍,排名好像是3087,然後我的主要工作就是改進這個代碼。別看我的代碼短,其實後面我花的時間真是不少,也學到了很多東西。我對參考代碼的幾十次修改都對結果沒有改進,最後還是根據Kyle在楊穎同學這個項目下面的評論,稍加修改,得出了0.79904的準確率,排名為1307。首先看一下我模仿的代碼:
library(randomForest)
set.seed(1)
train <- read.csv("../input/train.csv",stringsAsFactors=FALSE)
test <- read.csv("../input/test.csv", stringsAsFactors=FALSE)
extractFeatures <- function(data) {
features <- c("Pclass",
"Age",
"Sex",
"Parch",
"SibSp",
"Fare",
"Embarked")
fea <- data[,features]
fea$Age[is.na(fea$Age)] <- -1
fea$Fare[is.na(fea$Fare)] <- median(fea$Fare, na.rm=TRUE)
fea$Embarked[fea$Embarked==""] = "S"
fea$Sex <- as.factor(fea$Sex)
fea$Embarked <- as.factor(fea$Embarked)
return(fea)
}
rf <- randomForest(extractFeatures(train), as.factor(train$Survived), ntree=100, importance=TRUE)
submission <- data.frame(PassengerId = test$PassengerId)
submission$Survived <- predict(rf, extractFeatures(test))
write.csv(submission, file = "1_random_forest_r_submission.csv", row.names=FALSE)
說明:在Kaggle網站上,讀取數據用"../input/train.csv"就可以,但是在自己的R-studio上面,還是要輸入本地的存儲地址。
二、缺失值的處理
1.直接替換缺失值
從微小的修改開始:對於age和fare為缺失值的情況,上面參考代碼是在自定義的函數中進行了處理,開始我很不理解,為什麼把年齡的缺失值設置為「-1」呢,直觀地想,用個平均值或中位數更好,但是經過我的嘗試後,準確率不僅沒有提高,反而降低了!那麼按照這個思路,我把票價缺失值也改為0,結果對預測準確率沒有影響!最後我給自己的解釋是:不論將缺失值定義為多少,他們的值都是一致的,也就是這些值不能給預測結果提供更多的信息,所以對預測結果的影響都一樣。那麼有什麼其他辦法填補缺失值呢?
2.用mice包的函數填補缺失值
我看很多人用了Mice函數。參考Jason的文章「Learn R | 數據預處理中的缺失值(下)」
「在面對複雜的缺失值問題時,多重插補(MI)最為常用,該方法是一種基於重複模擬的處理缺失值的方法,它將從一個包含缺失值的數據集中生成一組完整的數據集(通常是3到10個)。在每個模擬數據集中,缺失數據將用蒙特卡洛方法來填補。這樣的話,標準的統計方法便可應用到每個模擬的數據集上,通過組合輸出結果給出估計的結果,以及引入缺失值時的置信區間。
在R中,Amelia、mice和mi包都可執行多重插補,在本文中,我們重點學習其中的mice包,使用包中的mice()函數與pool()函數。
mice()函數首先從一個包含缺失數據的數據框開始,然後返回一個包含多個(默認為5個) 完整數據集的對象(每一次的缺失值的插補都通過Gibbs抽樣完成,每個包含缺失值的變數都默認可通過數據集中的其他變數預測得來)。每個完整數據集都是通過對原始數據框中的缺失數據進行插補而生成的。由於插補有隨機的成分,因此每個完整數據集都略有不同;
然後,with()函數可依次對每個完整數據集應用統計模型(如線性模型等);
最後,pool()函數將這些單獨的分析結果整合為一組結果。最終模型的標準誤和p值都將準確地反映出由於缺失值和多重插補而產生的不確定性。」
實現Mice填補的代碼比較簡單。
library(mice)
set.seed(1234)
train.complete<-mice(train,m=5)
train.complete<-complete(train.complete,5)
結果出乎意料,用這個方法補全缺失值以後,預測準確率反而降低了,到現在我也沒有想明白,也許是函數的使用上還可以改進,希望哪個同學能幫我解釋一下,感激不盡。
3.用rfimpute函數填補缺失值
後來我發現隨機森林包中自帶補齊缺失值的函數rfImpute(),我試了一下,結果還是拉低了預測準確率。代碼如下:
#填補缺失值,首先要去除character變數
train<- train[,c(-4,-9,-11,-12)]
train_impute <- rfImpute(Survived~., data = train)
需要說明的是,由於採用了不同的填補缺失值的方法,原來代碼中的自定義函數就要做相應修改,而我當時對randomforest的句法還沒搞明白,所以調代碼走了很多彎路,不過也練習了調試代碼。
三、隨機森林函數
1.句法
我模仿的代碼的隨機森林函數是:rf <- randomForest(extractFeatures(train),as.factor(train$Survived), ntree=100, importance=TRUE)。
這和大多數同學使用的句法不一樣,幫助文檔我也沒有看太懂,最後我找資料得知,直接輸入rf 模型名稱可以得到這個模型的解讀。這個模型里,extractFeatures(train)是因變數x,as.factor(train$Survived)是自變數y。
在提前將Survived定義為因子後,以上函數等效於:rf <-randomForest(Survived~Pclass+Age+Sex+Parch+SibSp+Fare+Embarked, data=train, ntree=100, importance=TRUE)。
另外通過查資料發現,隨機森林函數用來做kaggle項目的準確率一般很高。所以選對了建模的方式對於預測非常重要。
2.ntree值
很可惜,我在這個項目測試ntree和mtry的代碼沒有保存,因為開始認為kaggle網站上保存了代碼,但是後來因為run的次數太多了,找原來的代碼太耗時,在此也提醒同學們,每次run之前,要修改標題,提現本次代碼的特點,便於以後查找。
為了說明ntree值的含義,我還是參考Jason的文章「Learn R| Random Forest of Data Mining(下)」,
# 使用iris數據集
# 與決策樹構建一樣,分別選取訓練樣本(70%)和測試樣本(30%)
> index <- sample(2,nrow(iris),replace = TRUE,prob=c(0.7,0.3))
> traindata <- iris[index==1,]
> testdata <- iris[index==2,]
在使用模型前,我們需要確定,要生成多少棵樹構建森林,即模型中ntree參數的具體值,可通過圖形大致判斷模型內誤差穩定時的值。
> set.seed(1234)
> rf_ntree <- randomForest(Species~.,data=traindata,ntree=300)
> plot(rf_ntree)
從圖中可以看到,當ntree=50時,模型內的誤差就基本穩定了,出於更保險的考慮,我們確定ntree值為100。
就titanic的數據來說,我也嘗試用90棵樹run了一下,結果是一樣的。簡單說,就是樹多了增加計算時間且沒有必要,樹太少了,則會影響結果的準確性,所以用圖大概估算一下,選個稍有餘度的ntree值最好。
3.mtry值
接著上面的iris例子。
在構建隨機森林模型時,模型的準確性如何,關鍵在於選擇最優的變數個數(參數"mtry"),在上面的例子中,我們使用的iris數據集使用了函數中該參數的默認情況(數據集變數個數的二次方根(分類模型)或三分之一(預測模型))。但一般情況下需要進行人為的逐次挑選,以確定最佳的值。
這裡借鑒博客基於R語言的隨機森林演算法運用的做法:
> n <- length(names(traindata))
> set.seed(1234)
> for (i in 1:(n-1)){
+ model <- randomForest(Species~., data = traindata, mtry = i)
+ err <- mean(model$err.rate)
+ print(err)
+ }
[1] 0.05342314
[1] 0.0513293
[1] 0.05110437
[1] 0.05122018
觀察輸出結果,當mtry=3時,模型內平均誤差最小。
# 指定mtry=3,重新構建模型
> iris_rf_1 <- randomForest(Species ~ ., data=traindata,mtry=3, ntree=100, proximity=TRUE)
> iris_rf_1
Call:
randomForest(formula = Species ~ ., data = traindata, mtry = 3, ntree = 100, proximity = TRUE)
Type of random forest: classification
Number of trees: 100
No. of variables tried at each split: 3
OOB estimate of error rate: 6.19%
Confusion matrix:
setosa versicolor virginica class.error
setosa 31 0 0 0.00000000
versicolor 0 32 3 0.08571429
virginica 0 3 28 0.09677419
我們發現,當mtry=3時,隨機森林的袋外錯誤率由7.22%下降至6.19%,錯判數減少1個。這說明我們通過指定合適的mtry值實現了模型準確度的提升。
就titanic數據來說,我做過類似的嘗試,結果是mtry=2時模型內平均誤差最小,而這也是默認的值,這個語句可以忽略。
最後貼上我的代碼,這是R-studio上用的版本。
library(randomForest)
library(stringr)
path<-getwd()
train <- read.csv(str_c(path, "train.csv", sep="/"),stringsAsFactors=FALSE)
test <- read.csv(str_c(path, "test.csv", sep="/"), stringsAsFactors=FALSE)
#定義一個函數,選擇要分析的變數,對缺失值、字元等數據進行處理。
extractFeatures1 <- function(data) {
features1 <- c("Pclass",
"Age",
"Sex",
"Parch",
"SibSp",
"Fare",
"Embarked",
"Cabin")
fea1 <- data[,features1]
fea1$Cabin[fea1$Cabin==""] <- "X1"
fea1$Cabin[fea1$Cabin) !=""] <- "X2"
fea1$Age[is.na(fea1$Age)] <- 0
fea1$Fare[is.na(fea1$Fare)] <- 0
fea1$Embarked[fea1$Embarked==""] = "S"
fea1$Sex <- as.factor(fea1$Sex)
fea1$Embarked <- as.factor(fea1$Embarked)
fea1$Cabin <- as.factor(fea1$Cabin)
return(fea1)
}
#訓練模型
set.seed(1234)
rf <- randomForest(extractFeatures1(train), as.factor(train$Survived), ntree=100, importance=TRUE)
#用模型預測
#plot(rf)
submission <- data.frame(PassengerId = test$PassengerId)
submission$Survived <- predict(rf, extractFeatures1(test))
write.csv(submission, file = "1_random_forest_r_submission.csv", row.names=FALSE)
4.其他嘗試
考慮到Cabin 的第一個字母可能代表船上的某個區域,而這與逃生成功與否應該有聯繫,那麼用這個字母作為因子能否提高準確率呢?我就在自定義函數裡面添加了如下代碼:
fea1$Cabin[fea1$Cabin==""] <- "X"
fea1$Cabin <- substring(fea1$Cabin,1,1)
結果出現了報錯,說「Type of predictors in new data do not match that of the training data.」
應該是train和test裡面的艙位不是完全一樣的,使用下列語句進行了對比:
> train$Cabin[train$Cabin==""]<-"X"
> test$Cabin[test$Cabin==""]<-"X"
> str.train<-substring(train$Cabin,1,1)
> str.test<-substring(test$Cabin,1,1)
> nodup.train<- str.train[!duplicated(str.train)]
> nodup.train
[1] "X" "C" "E" "G" "D" "A" "B" "F" "T"
> nodup.test<- str.test[!duplicated(str.test)]
> nodup.test
[1] "X" "B" "E" "A" "C" "D" "F" "G"
對比發現,T這個艙位在test裡面沒有,所以也在train裡面加工一下就行了,在自定義函數裡面加上如下代碼:
fea1$Cabin[fea1$Cabin==""| fea1$Cabin=="T"] <- "X"
fea1$Cabin <- substring(fea1$Cabin,1,1)
結果跑出的數據準確率還是降低了,也就是說,添加的更加準確的艙位信息對結果有誤導,按照這個思路,那麼排除其他變數以後能否提高準確率呢?除了年齡、性別等主要變數,我在自定義函數裡面又挨個刪除了各變數嘗試,結果發現刪除任何變數都會導致準確率下降,所以最好都保留。
另外,我嘗試根據年齡將人群分組,在自定義函數里添加如下代碼:
fea1$Age[fea1$Age>=80]<-8
fea1$Age[fea1$Age<80&fea1$Age>=60]<-6
fea1$Age[fea1$Age<60&fea1$Age>=40]<-4
fea1$Age[fea1$Age<40&fea1$Age>=20]<-2
fea1$Age[fea1$Age<20&fea1$Age>=0]<-1
除了上面的代碼,我還試過按照10年為單位,把人劃分成9組,或者劃分成老人、中年人、兒童3組,或者將分組後的年齡變數轉換為因子,最後跑出的結果都差不多,基本相同,但準確率都有所下降,這也是失敗嘗試。
references:
1. Learn R | 數據預處理中的缺失值(下)
2. Learn R | Random Forest of Data Mining(下)
3. kaggle排名3027—預測泰坦尼克號乘客生存率
4. 基於R語言的隨機森林演算法運用
拖放至此處上傳
推薦閱讀:
※運用小數據逆襲,一家地區超市讓沃爾瑪甘拜下風
※如何用深度學習進行CT影像肺結節探測(附有基於Intel Extended Caffe的3D Faster RCNN代碼開源)
※開啟正確的數據分析師成長之路
※當我們在讀唐詩時,我們在讀什麼?
※猴子 的 Live - 如何零基礎從事數據分析