Apitore blog

Apitoreを運営していた元起業家のブログ

Kaggleのタイタニックチュートリアルで色々もがいてみた

はじめに

先日投稿したこちらの記事の続報。色々と小手先のテクニックで試行錯誤した。結論を言うと、先日の結果が一番良かった・・・。

工夫したこと

いっぱいやったので簡単に書く

関連投稿(追記)

ランダムフォレスト(ranger)を使う

識別器を変更するも精度は上がらず。適当パラメータチューニング済み。

SVM(e1071)を使う

識別器を変更するも精度は上がらず。適当パラメータチューニング済み。RBFカーネル+ソフトマージン。

年齢の素性を範囲で区切った

上がらず。詳細は下記。

範囲
不明 UNK
0 1
1-5 2
6-9 3
10-19 4
20-29 5
30-39 6
40-49 7
50-59 8
60- 9

Cabin素性を追加

上がらず。Cabinの頭のアルファベットだけ使う。

Ticket素性を追加

上がらず。Ticketの頭の英字部分と数字部分を分割し、それぞれTicketHeadとTicketNoとする。

SibSp素性を追加

上がらず。SibSpに範囲を指定(2以下か3以上)した。

Parch素性を追加

上がらず。同上。

predictionの0/1判定の閾値を変更

上がらず。閾値=[Survived=0の数]/[全数]とした。

さいごに

色々やってみたが、80%の壁は超えられなかった。識別器も変えて素性の組み合わせも色々変えたがダメだった。小手先のテクニックは出し尽くしたつもり。 次にやるとしたらデータクレンジングか外部知識の導入、欠損値の推定だ。欠損値の推定は生データを見る限りではたとえ推定できても生存可否判定にはたいして影響がないように思う。データクレンジングについても、やってもたいして影響がないように思う。学習データにおいて、特徴空間上で類似した2つのベクトルのラベルが正負で逆を向いている場合、正負のどちらに倒すべきかの判定が難しいことは想像できると思う。今回の生データを見る限りではそういうデータが多く、類似ベクトルの正負の割合が2:8~3:7だった。よって、精度80%の壁は妥当に思う。外部知識の導入はタスクがタスクだけにチートにしかならない。 ここから先の領域に行くためには、もう少し凝った処理をする必要がありそう。クラスタリングしてクラスタ毎に識別器つくるとか。とりあえずタイタニックチュートリアルはこれで終わり。次はKaggleの別のタスクをやるか、もしくはApitoreのインフラ技術の紹介、もしくはAPIの開発をやって記事にする。

ソースコード

library(xgboost)
library(Matrix)
set.seed(7485)
# 家族の生死のリスト準備
familystatus = function(d) {
  rows = nrow(d)
  rtn = NULL
  for (i in 1:rows) {
    if (d[i,"SibSp"]==0 && d[i,"Parch"]==0) {
      # do nothing
    } else {
      name = as.character(d[i,"FamilyName"])
      survive = d[i,"Survived"]
      if (survive==0) survive=-1
      if (is.null(rtn[name]) || is.na(rtn[name])) {
        rtn[name]=survive
      } else {
        rtn[name]=rtn[name]+survive
      }
    }
  }
  rtn
}
# 家族の生死素性化
addfamilystatus = function(d,l) {
  rows = nrow(d)
  rtn = NULL
  for (i in 1:rows) {
    if (d[i,"SibSp"]==0 && d[i,"Parch"]==0) {
      rtn = c(rtn,"U")
    } else {
      name = as.character(d[i,"FamilyName"])
      if (is.na(l[name])) {
        rtn = c(rtn,"U")
      } else if (l[name]>0) {
        rtn = c(rtn,"A")
      } else {
        rtn = c(rtn,"D")
      }
    }
  }
  rtn
}
# 家族の生死のリストアップデート
updatefamilystatus = function(d,l) {
  rows = nrow(d)
  rtn = l
  for (i in 1:rows) {
    if (d[i,"SibSp"]==0 && d[i,"Parch"]==0) {
      # do nothing
    } else {
      name = as.character(d[i,"FamilyName"])
      survive = d[i,"Survived"]
      if (survive==0) survive=-1
      if (is.null(rtn[name]) || is.na(rtn[name])) {
        rtn[name]=survive
      } else {
        rtn[name]=rtn[name]+survive
      }
    }
  }
  rtn
}
## Model
data = read.csv("train.csv")
data$Pclass = ifelse(is.na(data$Pclass), as.character(-1), as.character(data$Pclass))
data$FamilyName = gsub(",.+$","",data$Name)
data$Cabin = gsub("[0-9 ].*$","",data$Cabin)
data$Age = ifelse(is.na(data$Age), -1, round(data$Age))
data$RangeSibSp = ifelse(data$SibSp>2, "2", "1")
data$RangeParch = ifelse(data$Parch>2, "2", "1")
data$TicketHead = NULL
data$TicketNo = NULL
tickets = strsplit(as.character(data$Ticket)," ")
data$RangeAge = NULL
rows = nrow(data)
for (i in 1:rows) {
  if (length(tickets[[i]])==1) {
    data[i, "TicketHead"] = "None"
    data[i, "TicketNo"] = gsub("...$","",tickets[[i]][1])
  } else if (length(tickets[[i]])==3) {
    data[i, "TicketHead"] = paste(tickets[[i]][1],tickets[[i]][2],sep="")
    data[i, "TicketNo"] = gsub("...$","",tickets[[i]][3])
  } else {
    data[i, "TicketHead"] = tickets[[i]][1]
    data[i, "TicketNo"] = gsub("...$","",tickets[[i]][2])
  }
  chk = data[i,"Age"]
  if (chk == -1) {
    data[i,"RangeAge"] = as.character("UNK")
  } else if (chk==0) {
    data[i,"RangeAge"] = as.character("1")
  } else if (chk>=1 && chk<6) {
    data[i,"RangeAge"] = as.character("2")
  } else if (chk>=6 && chk<10) {
    data[i,"RangeAge"] = as.character("3")
  } else if (chk>=10 && chk<20) {
    data[i,"RangeAge"] = as.character("4")
  } else if (chk>=20 && chk<30) {
    data[i,"RangeAge"] = as.character("5")
  } else if (chk>=30 && chk<40) {
    data[i,"RangeAge"] = as.character("6")
  } else if (chk>=40 && chk<50) {
    data[i,"RangeAge"] = as.character("7")
  } else if (chk>=50 && chk<60) {
    data[i,"RangeAge"] = as.character("8")
  } else {
    data[i,"RangeAge"] = as.character("9")
  }
}
data$Age = as.character(data$Age)
fsurvivelist = familystatus(data)
data$FamilyStatus = addfamilystatus(data,fsurvivelist)
cols = ncol(data)
for (i in 3:cols) {
  data[,i] = factor(data[,i])
}
train = sparse.model.matrix(Survived~Pclass+FamilyStatus+Sex+Cabin+RangeAge+RangeSibSp+RangeParch+TicketHead+TicketNo+Embarked,
                            data)
train.label = data$Survived
bst = xgboost(data=train,
              label=train.label,
              nrounds=10,
              eta=0.1,
              gamma=0.1,
              max_depth=6,
              min_child_weight=10,
              subsumple=1,
              colsumple_bytree=1,
              objective="binary:logistic",
              verbose=0)
## Predict
data.test = NULL
data.test = read.csv("test.csv")
data.test$Survived = 0
data.test$Pclass = ifelse(is.na(data.test$Pclass), as.character(-1), as.character(round(data.test$Pclass)))
data.test$FamilyName = gsub(",.+$","",data.test$Name)
data.test$Cabin = gsub("[0-9 ].*$","",data.test$Cabin)
data.test$Age = ifelse(is.na(data.test$Age), -1, round(data.test$Age))
data.test$RangeSibSp = ifelse(data.test$SibSp>2, "2", "1")
data.test$RangeParch = ifelse(data.test$Parch>2, "2", "1")
data.test$TicketHead = NULL
data.test$TicketNo = NULL
tickets = strsplit(as.character(data$Ticket)," ")
data.test$RangeAge = NULL
rows = nrow(data.test)
for (i in 1:rows) {
  if (length(tickets[[i]])==1) {
    data.test[i, "TicketHead"] = "None"
    data.test[i, "TicketNo"] = gsub("...$","",tickets[[i]][1])
  } else if (length(tickets[[i]])==3) {
    data.test[i, "TicketHead"] = paste(tickets[[i]][1],tickets[[i]][2],sep="")
    data.test[i, "TicketNo"] = gsub("...$","",tickets[[i]][3])
  } else {
    data.test[i, "TicketHead"] = tickets[[i]][1]
    data.test[i, "TicketNo"] = gsub("...$","",tickets[[i]][2])
  }
  chk = data.test[i,"Age"]
  if (chk == -1) {
    data.test[i,"RangeAge"] = as.character("UNK")
  } else if (chk==0) {
    data.test[i,"RangeAge"] = as.character("1")
  } else if (chk>=1 && chk<6) {
    data.test[i,"RangeAge"] = as.character("2")
  } else if (chk>=6 && chk<10) {
    data.test[i,"RangeAge"] = as.character("3")
  } else if (chk>=10 && chk<20) {
    data.test[i,"RangeAge"] = as.character("4")
  } else if (chk>=20 && chk<30) {
    data.test[i,"RangeAge"] = as.character("5")
  } else if (chk>=30 && chk<40) {
    data.test[i,"RangeAge"] = as.character("6")
  } else if (chk>=40 && chk<50) {
    data.test[i,"RangeAge"] = as.character("7")
  } else if (chk>=50 && chk<60) {
    data.test[i,"RangeAge"] = as.character("8")
  } else {
    data.test[i,"RangeAge"] = as.character("9")
  }
}
data.test$Age = as.character(data.test$Age)
#data.test$FamilyStatus = addfamilystatus(data.test,fsurvivelist)
#pred = predict(bst, data.test)
#prediction = pred$predictions
#rtn=NULL
#rtn$PassengerId=data.test$PassengerId
#rtn$Survived = pred$predictions
#write.csv(rtn,'mypredict.csv')
rtn.pred = NULL
rows = nrow(data.test)
for (j in 1:rows) {
  dtest = data.test
  dtest$FamilyStatus = addfamilystatus(dtest,fsurvivelist)
  cols = ncol(dtest)
  for (i in 2:cols) {
    dtest[,i] = factor(dtest[,i])
  }
  test = sparse.model.matrix(Survived~Pclass+FamilyStatus+Sex+Cabin+RangeAge+RangeSibSp+TicketHead+TicketNo+RangeParch+Embarked,
                             dtest)
  test.label = dtest$Survived
  pred = predict(bst, test)
  prediction = as.numeric(pred > 0.5)
  rtn.pred = c(rtn.pred,prediction[j])
  # update fsurvivelist
  dtest$Survived = prediction
  fsurvivelist = updatefamilystatus(dtest[j,],fsurvivelist)
}
rtn=NULL
rtn$PassengerId=data.test$PassengerId
rtn$Survived = rtn.pred
write.csv(rtn,'mypredict.csv')