今日はKaggleのForest Cover Type (Kernels Only) というコンペティションの記録第二回。
何をやったかというととりあえずものは試しということで自分がわかるものをすごく簡単に1つ試してみた。
前回の記事通り、データを見てみるとone-hotエンコードされているデータが多いので一旦ランダムフォレストタイプのもので学習させてみようと思い、Kaggleでは有名なLight-GBMを使うことに。
いずれはこのアルゴリズムの解説記事を作りたいところ。
LightGBMを使えるようにする
macOS High Sierra ver10.13.4を使っている。
以下のサイトを参照に必要なモジュールをインストールして使ってみた。
インストール
$ brew install cmake gcc@7
pipを使う。
$ export CXX=g++-7 CC=gcc-7
$ pip install --no-binary lightgbm lightgbm
$ pip list --format=columns | grep -i lightgbm
lightgbm 2.1.1
早速試す
前処理
とりあえず簡単な前処理を。
欠損値(いわゆるNaNとか)は無いのがわかっていたのでそこを気にせずどんどん進めていく。
import pandas as pd
import numpy as np
import matplotlib.pyplot as plt
import lightgbm as lgb
from sklearn.model_selection import train_test_split
import gc
次にデータを読み込む。
train = pd.read_csv("../input/train.csv")
train.head()
出力結果は以下の通り。
Id | Elevation | Aspect | Slope | Horizontal_Distance_To_Hydrology | Vertical_Distance_To_Hydrology | Horizontal_Distance_To_Roadways | Hillshade_9am | Hillshade_Noon | Hillshade_3pm | Horizontal_Distance_To_Fire_Points | Wilderness_Area1 | Wilderness_Area2 | Wilderness_Area3 | Wilderness_Area4 | Soil_Type1 | Soil_Type2 | Soil_Type3 | Soil_Type4 | Soil_Type5 | Soil_Type6 | Soil_Type7 | Soil_Type8 | Soil_Type9 | Soil_Type10 | Soil_Type11 | Soil_Type12 | Soil_Type13 | Soil_Type14 | Soil_Type15 | Soil_Type16 | Soil_Type17 | Soil_Type18 | Soil_Type19 | Soil_Type20 | Soil_Type21 | Soil_Type22 | Soil_Type23 | Soil_Type24 | Soil_Type25 | Soil_Type26 | Soil_Type27 | Soil_Type28 | Soil_Type29 | Soil_Type30 | Soil_Type31 | Soil_Type32 | Soil_Type33 | Soil_Type34 | Soil_Type35 | Soil_Type36 | Soil_Type37 | Soil_Type38 | Soil_Type39 | Soil_Type40 | Cover_Type | |
---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|
0 | 1 | 2596 | 51 | 3 | 258 | 0 | 510 | 221 | 232 | 148 | 6279 | 1 | 0 | 0 | 0 | 0 | 0 | 0 | 0 | 0 | 0 | 0 | 0 | 0 | 0 | 0 | 0 | 0 | 0 | 0 | 0 | 0 | 0 | 0 | 0 | 0 | 0 | 0 | 0 | 0 | 0 | 0 | 0 | 1 | 0 | 0 | 0 | 0 | 0 | 0 | 0 | 0 | 0 | 0 | 0 | 5 |
1 | 2 | 2590 | 56 | 2 | 212 | -6 | 390 | 220 | 235 | 151 | 6225 | 1 | 0 | 0 | 0 | 0 | 0 | 0 | 0 | 0 | 0 | 0 | 0 | 0 | 0 | 0 | 0 | 0 | 0 | 0 | 0 | 0 | 0 | 0 | 0 | 0 | 0 | 0 | 0 | 0 | 0 | 0 | 0 | 1 | 0 | 0 | 0 | 0 | 0 | 0 | 0 | 0 | 0 | 0 | 0 | 5 |
2 | 3 | 2804 | 139 | 9 | 268 | 65 | 3180 | 234 | 238 | 135 | 6121 | 1 | 0 | 0 | 0 | 0 | 0 | 0 | 0 | 0 | 0 | 0 | 0 | 0 | 0 | 0 | 1 | 0 | 0 | 0 | 0 | 0 | 0 | 0 | 0 | 0 | 0 | 0 | 0 | 0 | 0 | 0 | 0 | 0 | 0 | 0 | 0 | 0 | 0 | 0 | 0 | 0 | 0 | 0 | 0 | 2 |
3 | 4 | 2785 | 155 | 18 | 242 | 118 | 3090 | 238 | 238 | 122 | 6211 | 1 | 0 | 0 | 0 | 0 | 0 | 0 | 0 | 0 | 0 | 0 | 0 | 0 | 0 | 0 | 0 | 0 | 0 | 0 | 0 | 0 | 0 | 0 | 0 | 0 | 0 | 0 | 0 | 0 | 0 | 0 | 0 | 0 | 1 | 0 | 0 | 0 | 0 | 0 | 0 | 0 | 0 | 0 | 0 | 2 |
4 | 5 | 2595 | 45 | 2 | 153 | -1 | 391 | 220 | 234 | 150 | 6172 | 1 | 0 | 0 | 0 | 0 | 0 | 0 | 0 | 0 | 0 | 0 | 0 | 0 | 0 | 0 | 0 | 0 | 0 | 0 | 0 | 0 | 0 | 0 | 0 | 0 | 0 | 0 | 0 | 0 | 0 | 0 | 0 | 1 | 0 | 0 | 0 | 0 | 0 | 0 | 0 | 0 | 0 | 0 | 0 | 5 |
前処理する関数を作る。
テストデータも同様に処理できるよう、Cover_Typeだけ個別に操作できるように引数を指定して切り替えができるようにしておく。
一応距離関係(特定の地域までの距離を示す`Horizontal…`と標高を表す`Elevation`)については対数の方が効果的だと思い、対数をとる。
今思うと`log(1+x)`をとる関数`np.log1p`があったのでそれを今度から使おう。
以下で定義した関数を実行しようとするとRunTimeWarningが出てしまった。
def pre_process(train, tr=True):
train["log_Elevation"] = np.log(train["Elevation"] + 1e-1 )
train["log_Horizontal_Distance_To_Hydrology"] = np.log(train["Horizontal_Distance_To_Hydrology"])
train["log_Horizontal_Distance_To_Roadways"] = np.log(train["Horizontal_Distance_To_Roadways"])
train["log_Horizontal_Distance_To_Fire_Points"] = np.log(train["Horizontal_Distance_To_Fire_Points"])
del train["Horizontal_Distance_To_Fire_Points"]
del train["Horizontal_Distance_To_Hydrology"]
del train["Horizontal_Distance_To_Roadways"]
del train["Elevation"]
if tr: # ラベルが1~7に対してlgbmでは0~6になるので1つずつ下げる。
y = train["Cover_Type"].copy() - 1
del train["Cover_Type"]
return train, y
return train
ここで1つずつラベル(Cover_Type)を1つずつ下げていることに注意。
(どうやらlgbmだと0~6でラベルづけをしているらしい)
提出するファイルを作成するとき、1つずつ足すことを忘れないようにしないと。
モデルに入れて学習
とりあえずモデルに入れて学習させる。
X, y = pre_process(train)
X = X.values
y = y.values
lgbm_params = {
'objective' : 'multiclass',
'num_class' : 7,
}
model_list = []
acc_list = []
for i in range(5):
X_train, X_test, y_train, y_test = train_test_split(
X, y, test_size=0.25, random_state=42
)
lgb_train = lgb.Dataset(X_train, y_train) # lgbに入れ込むようのデータセットに変形する。
lgb_eval = lgb.Dataset(X_test, y_test, reference=lgb_train)
# ここから学習開始
model = lgb.train(lgbm_params, lgb_train, valid_sets=lgb_eval)
print("training finished!!")
y_pred = model.predict(X_test, num_iteration=model.best_iteration)
y_pred_max = np.argmax(y_pred, axis=1)
accuracy = sum(y_test == y_pred_max) / len(y_test)
print(accuracy)
model_list.append(model)
acc_list.append(accuracy)
学習モデルでもっとも性能が良かったものを使うことにする。
arr_acc = np.array(acc_list)
model = model_list(np.argmax(arr_acc))
予測値をだす
提出ファイルを作るために予測をしていく。
test = pd.read_csv("data/test.csv")
test_X = pre_process(test, tr=False)
y_pred_max = np.argmax(y_pred + 1, axis=1) # 予測した値の中で一番大きいものをそのセットでの予測ラベルとする。
y_pred_max = y_pred_max.reshape(-1, 1) # 連結しやすいよう縦ベクトルに変形
test_id = test_id.reshape(-1,1) # 同じく縦ベクトルに変形。
sub = np.hstack((test_id, y_pred_max)) # 横方向(horizontal)にくっつける
submission = pd.DataFrame(sub, columns=['Id', 'Cover_Type']) # DataFrameを使ってラベルづけ
submission.to_csv("output/submission_test.csv", index=False) # 提出ファイルの出力
結果
とりあえずこれをkernel(Kaggleで与えられたサーバー上の実行環境)で実行して、生成されたファイルを提出するとスコアが記録される。
きになるスコアは
0.6903!! だった。
順位は147チーム中120位だった。
まとめ
かなり簡単なものだったが一応スコア的なものが出たのはちょっと安心。
とはいえまだまだ改善の余地があるので他の人のコードを読み込んで、どんな学習モデルを使っているのかを見ていきたい。