Docker使って手軽にGCEで機械学習できる環境を整えた
最近、強化学習的なとこに手を出してみたけど手元のMacbook AirじゃメモリやCPU的に厳しいのでクラウドで実行することを検討した。 最初はAmazon EC2借りようかなーと考えていたけれど、Google Compute Engine(GCE) だとプリエンプティブという格安実行モードがあることを知り、GCEで実行することにした。プリエンプティブモードで実行すると、1日しかインスタンスを実行できない、突然インスタンスが停止するなどのデメリットがある代わりに、通常価格より50%以上安くインスタンスを利用できる。
しかし毎回ファイルを転送してsshで繋いで実行するのは正直面倒くさい。そこでDockerを使って効率的にプログラムをGCEで実行する環境を整えた。 具体的には機械学習用イメージのビルド→イメージのアップロード→コンテナの起動・実行をコマンドラインからささっと実行できるようにした。
環境構築にあたって以下の2つのサービスを使用した。
Google Container Registryは使用したネットワークとストレージ分だけ課金されるので、少しでも課金額を抑えたい場合には1レジストリだけ無料で使えるDocker Hubなどを使った方がいいかもしれない。
注意: 今回使用するスクリプトはGoogleのクラウドサービスを使用するので、当然のことながら課金が必要になります。特にコンテナの実行までシェルスクリプトで実行してしまうので、変な設定で実行すると青天井の請求が来る可能性があります。ここに載っている内容を実行する場合には自己責任で実行してください。
Step.0 前準備
gcloudのインストール
Google Compute Engineをコマンドラインから使うためにglcoud
をインストールする・
ここでは導入方法の説明は省くがGCPのgcloudコマンドをインストールする - Qiitaを参考にすればインストールできると思う。
プロジェクトの作成・Google Container RegistryのAPI有効化
Google Cloud Platformにアクセスして新しいプロジェクトを作成する。 左のサイドーからContainer Registryを選択してAPIを有効化する。(Google Container Registry以外のサービスを使う人、既に有効化している人はこの作業をする必要はありません)
スクリプトで使用する用の環境変数の設定
今回の説明ではプロジェクト名や、イメージの名前を環境変数に設定して使用する(スクリプトにベタ打ちしてGithubにアップしちゃうのが怖いので)。
CLOUDML_HOST_REGIONにはasia.gcr.io
、eu.gcr.io
、us.gcr.io
などから選べる(参考:Pushing and Pulling Images | Container Registry | Google Cloud)。CLOUDML_PROJECTIDには作成した自分のプロジェクトIDを、CLOUDML_IMAGE_NAMEには適当にイメージの名前を設定する。
export CLOUDML_HOST_REGION="REGION TO UPLOAD" export CLOUDML_PROJECT_ID="YOUR PROJECT ID" export CLOUDML_IMAGE_NAME="YOUR IMAGE NAME"
Step1. Dockerfileの準備
普通にDockerfileを作成する。今回はtensorflow、keras、lightgbmのCPU版をインストールしている。また実行するファイルをビルドするときに変えられるように実行ファイル名を変数として持たせている。注意点としてpythonの引数に-u
を指定しないと出力がフラッシュされなくて、Stack Driverにログが正しく表示されないのでちゃんと指定する。
Step2. アップロード用のスクリプトの準備
DockerfileからイメージをビルドしてGoogle Container Repositoryにアップロードするスクリプトを作る。
実行したいファイルを引数に渡すとそのファイルを実行するイメージをビルドしてくれる。main.pyというスクリプトを実行するイメージを作成したい場合には以下のように実行する。
./deploy.sh main.py
実行に成功すると最後にレポジトリの名前を出力してくれる。
Step3. 実行用のスクリプトの準備
使用するイメージの名前、使うインスタンスの名前を指定するとGCE上で実行してくれるスクリプトを作る。
細かいインスタンスの設定はgcloud beta compute instances create-with-container
の引数で行う。
今回は4コアCPU、メモリ20GB、ディスクサイズ50GB、プリエンプティブ有効の設定で実行した。
他の設定で実行したい場合には一度手動でインスタンスを作成して課金額を確認したあとに
https://cloud.google.com/sdk/gcloud/reference/beta/compute/instances/create-with-containerを参考にして引数を決定するのが良いと思う。
2018年11月4日現在gcloud beta compute instances create-with-container
は実行することができるが、ベータ版のコマンドなので今後のアップデートで実行できなくなる可能性がある。
実行してみる
今回作成したスクリプトを使用してkerasのmnistを実行してみる。今回作成したスクリプト類はGitHub - mikebird28/cloudml-templateに置いてある。環境変数などを設定したあとにdeploy.shを実行すると長々とビルドメッセージが流れたあと最後にレポジトリの名前を教えてくれる。
./deploy.sh main.py
deploy.shの実行が終わったら次のようにしてコンテナを実行する。今回はインスタンス名としてtest-instance1を指定したが、自分で実行するときは好きな名前を使用して問題ない。
./exec_container.sh test-instance1 レポジトリ名
コンテナの実行に成功するとインスタンス、IPアドレス、CPU数などの詳細が出力される。ブラウザ上でGCEのインスタンス一覧を確認するとコンテナが実行されていることが確認できる。またインスタンスを選んでログを表示すればプログラムが実行されていることが確認できる。
今回はCPUしか使用してないがDockerfileとexec_container.shを修正すればGPUも使用できるようになるはず。現状exec_container.pyにバグがあるみたいで、処理が一通り終わるともう一度コンテナがはじめから実行されてしまう。時間があったら今後修正する予定。
参考
機械学習の前処理を効率的にするPreprepっていうライブラリを作った
最近kaggleは放置して機械学習を使った競馬の予想をやってるんですけど、少しパラメータを変えただけのコードを実行するだけなのに、毎回一から前処理をしていると時間がかかりすぎて死ぬという問題が発生していました。
そんな悩みを解決するために、実行するときに前処理をキャッシュしておいて、次に実行するときには以前と変化した部分だけを計算するPreprepというライブラリを作りました。
よく何言ってるかわからないと思うので実際に例を見てもらったほうが早い気がします。
import preprep import pandas as pd def func1(df): return df*2 def func2(df): return df*3 def func3(df): return df*4 def func4(df): return df*5 def func5(df,n = 1): return df*n df = pd.DataFrame([[1,2,3],[4,5,6]]) #キャッシュするディレクトリを指定してインスタンスを作成 p = preprep.Preprep("./cache_files") #前処理の関数を登録 func1からfunc3を順番に実行していく p = p.add(func1,name = "func1") p = p.add(func2,name = "func2") p = p.add(func3,name = "func3") #fit_geneで実際に前処理を実行 df = p.fit_gene(df,verbose = True) print(df.head())
1回目の出力
[*] start running graph [*] no cache exists for func1, calculate [*] no cache exists for func2, calculate [*] no cache exists for func3, calculate 0 1 2 0 24 48 72 1 96 120 144
2回目の出力
[*] start running graph [*] available cache for func1 exists, skip calculation [*] available cache for func2 exists, skip calculation [*] available cache for func3 exists, skip calculation 0 1 2 0 24 48 72 1 96 120 144
1回目に実行したときはキャッシュが存在していないので全ての前処理を計算していますが、2回目に実行するときにはキャッシュが存在しているので計算をスキップしています。キャッシュが存在している状態で、fit_geneに渡すDataFrameが変化すると次のようになります。
#DataFrameの値を変更 #df = pd.DataFrame([[1,2,3],[4,5,6]]) df = pd.DataFrame([[1,2,3],[4,5,7]]) #キャッシュするディレクトリを指定してインスタンスを作成 p = preprep.Preprep("./cache_files") #前処理の関数を登録 p = p.add(func1,name = "func1") p = p.add(func2,name = "func2") p = p.add(func3,name = "func3") #fit_geneで実際に前処理を実行 df = p.fit_gene(df,verbose = True) print(df.head())
[*] start running graph [*] saved cache for func1 exists, but dataset hash value has changed, calculate [*] saved cache for func2 exists, but dataset hash value has changed, calculate [*] saved cache for func3 exists, but dataset hash value has changed, calculate 0 1 2 0 24 48 72 1 96 120 168
ちゃんと入力されたDataFrameが変化していることを検出して計算をやり直しています。またfunc3中身を書き換えたり、他の関数に変えたりすると、func3以前はキャッシュから読み込んで、func3以降だけ計算をやり直してくれます。ちゃんと内部で依存グラフを作ってそれを解決しているので、今回のような関数を順番に実行していくだけでなく、枝分かれしたり、結合したりみたいな複雑なものでもちゃんと必要なとこだけ計算をやり直してくれます。
df = pd.DataFrame([[1,2,3],[4,5,7]]) p = preprep.Preprep("./cache_files") #func3をfunc4に変更 p = p.add(func1,name = "func1") p = p.add(func2,name = "func2") #p = p.add(func3,name = "func3") p = p.add(func4,name = "func3") #fit_geneで実際に前処理を実行 df = p.fit_gene(df,verbose = True) print(df.head())
出力
[*] start running graph [*] available cache for func1 exists, skip calculation [*] available cache for func2 exists, skip calculation [*] saved cache for func3 exists, but op hash value has changed, calculate 0 1 2 0 30 60 90 1 120 150 210
実際に自分で使っていますが、以前に比べて飛躍的に実験を回すスピードが上がったと思います。これまで前処理に毎回30分くらいかかっていましたが、preprepのおかげで変化したとこだけ実行するので今ではほとんど前処理に時間を取られません(入力するDataFrameが変わったりすると、1から計算しなおさないといけないのでやっぱり時間がかかりますが)。
インストールの方法ですがpipで配布しているので下記のコマンドでインストールできる(はず)です。
pip install preprep
まだ生まれたばっかのライブラリなのでバグが多かったりドキュメントが存在してなかったりするので今後その辺はこつこつ直していこうかなと思います。バグとか見つけたらここかgithubでぜひ教えていください。
TargetEncodingのスムーシング
はじめてのFeature Engineering で紹介したTargetEncoding, LikelihoodEncodingはカテゴリ変数の前処理としては非常に強力な手法だが、あるカテゴリに所属するデータの数が少ないと過学習の原因になってしまう可能性がある。データ数が少ないカテゴリを持つデータセットに対して効果的にTragetEncodingを使うために、この論文ではTargetEncodingのスムージングという手法を提案している。
まず通常のTargetEncoding、LikelihoodEncodingは以下のような式で表現できる。
ここで はクラスタi に所属しているデータの数、 はクラスタiに所属していて目的変数が1の数を表している。、の数が十分に大きければ過学習を気にする必要は無いかもしれないが、これらの値が小さいと間接的に目的変数の値を予想できるようになってしまうので過学習の要因になってしまう。この問題を解決するために以下のように改良された式が提案されている。
上で説明した式に加えて、、 という変数が追加されている。、 はデータの総数、はデータセット全体の中で目的変数が1の数を表している。また は 対して単調増加で、0〜1の範囲を取る関数を表している。
この式は の数が十分に大きければ第一項の影響が大きくなり普通のTargetEncodingと同じような値を示す。一方で の数が小さいと第二項の影響が大きくなりデータセット全体の目的変数が1の割合に近づく。このようにデータ数が少ないクラスタに対してはデータセット全体の値を用いることで、機械学習モデルが過学習することを防いでいる。
上の式で出てきたには次のような関数を用いることが多いようだ。
この関数はシグモイド関数的な特徴(k = 0、f = 1とすれば標準シグモイド関数)を示し、k、fの値を調整することでチューニングすることができる。
参考
- A Preprocessing Scheme for High-Cardinality Categorical Attributes in Classification and Prediction Problems https://kaggle2.blob.core.windows.net/forum-message-attachments/225952/7441/high%20cardinality%20categoricals.pdf
- Python target encoding for categorical features https://www.kaggle.com/ogrellier/python-target-encoding-for-categorical-features
はじめてのFeature Engineering
先日KaggleのAdTracking Competition に参加したが惨敗したため、特訓することにした。 Ad Tracking Competition で4位の人がFeature Engineeringについて非常にまとまった資料を公開していたので、その要約をまとめた。 勉強がてらPythonでサンプルコードを書いたので一緒においておく。
h2o-meetups/Feature Engineering.pdf at master · h2oai/h2o-meetups · GitHub
何故Feature Engineeringが必要なのか
価値のある特徴量を生成し、ノイズの原因となる特徴量を除去することでシンプルで精度のいいモデルを実現する。
たとえば下の図で、左の状態だと円形にプロットが分布していて、ニューラルネットワークやSVMなどの非線形モデルでないと分類することができない。 しかし各点を極座標で表して右のように変換すれば、非線形の手法を使わなくても決定木や線形分類器などのシンプルな手法で識別できるようになる。
具体的な手法(カテゴリ変数の場合)
多くの機械学習手法(ニューラルネットワークsvm)はカテゴリ変数を直接学習させることが出来ない。これらのモデルにカテゴリ変数を学習させたい場合には、カテゴリ変数を数値に変換する必要がある。よりモデルがカテゴリ変数の意味を理解しやすいように様々な手法が考案されている。カテゴリ変数の特徴(値に大小関係があるか、疎な入力でも問題がないか、特定の値だけが重要なのか)などを考えてどのような前処理を施すか決定することになる。
Label Encoding
単純にラベルを数字に置き換えるだけの処理。 多くの人が使ったことがあるのではないかと思う。下の例だとA→0、B→1、C→2という様に置き換えてる。pythonだとsklearnのLabelEncoderを使うのが楽。
idx | category | encoded_feature |
---|---|---|
0 | A | 0 |
1 | A | 0 |
2 | A | 0 |
3 | B | 1 |
4 | B | 1 |
5 | C | 2 |
import pandas as pd from sklearn.preprocessing import LabelEncoder df = pd.DataFrame({ "category" : ["A","A","A","B","B","C"], }) le = LabelEncoder() df["encoded_feature"] = le.fit_transform(df["category"]) df.head(10)
One Hot Encoding
カテゴリ変数の各値ごとにカラムを分けて0、1で表現する方法。下の例だとcategory_A, category_B, category_Cという列を新しく作って、Aの場合には(1, 0, 0)、Bの場合には(0, 1 ,0)、Cの場合には(0, 0, 1)と表現する。ニューラルネットワークでカテゴリ変数を表すときはOne Hot Encodingが第一選択肢になる気がする。
LabelEncodingのときと同様、One Hot Encoderがsklearnに収録されているので、それを使うのが楽。pandasにはget_dummiesというメソッドが収録されているので、それを使っても同じことが出来る。
category | category_A | category_B | category_C | |
---|---|---|---|---|
0 | A | 1 | 0 | 0 |
1 | A | 1 | 0 | 0 |
2 | A | 1 | 0 | 0 |
3 | B | 0 | 1 | 0 |
4 | B | 0 | 1 | 0 |
5 | C | 0 | 0 | 1 |
#one hot encoding sample import pandas as pd from sklearn.preprocessing import OneHotEncoder df = pd.DataFrame({ "category" : ["A","A","A","B","B","C"], }) #文字をLabel Encodingして数値に変換 le = LabelEncoder() df["encoded_feature"] = le.fit_transform(df["category"]) #One Hot Encoding ohe = OneHotEncoder() ohe_array = ohe.fit_transform(df[["encoded_feature"]]) df.drop("encoded_feature", axis = 1, inplace = True) #DataFrameに変換 ohe_df = pd.DataFrame(ohe_array.todense()).astype("uint8") ohe_df.columns = ["category_A","category_B","category_C"] #元のDataFrameと結合 df = pd.concat([df,ohe_df], axis = 1) df.head(10)
Frequency Encoding
各値の出現頻度で表現する方法。例えば以下の表におけるAの値は、3(Aの出現回数)/ 6(全体の行数)で0.5となる。pandasを使えばgroupbyとmergeを駆使して比較的に簡単に書くことが出来る。
category | category_counts | frequency_encoding | |
---|---|---|---|
0 | A | 3 | 0.500000 |
1 | A | 3 | 0.500000 |
2 | A | 3 | 0.500000 |
3 | B | 2 | 0.333333 |
4 | B | 2 | 0.333333 |
5 | C | 1 | 0.166667 |
#Frequency Encoding Sample import pandas as pd from sklearn.preprocessing import OneHotEncoder df = pd.DataFrame({ "category" : ["A","A","A","B","B","C"], }) #各カテゴリーの出現回数を計算 grouped = df.groupby("category").size().reset_index(name='category_counts') #元のデータセットにカテゴリーをキーとして結合 df = df.merge(grouped, how = "left", on = "category") df["frequency"] = df["category_counts"]/df["category_counts"].count() df.head(10)
Target Mean Encoding
Likelihood Encodingとも呼ばれる。Google検索でのヒット数的にはTarget Mean Encodingの方がメジャー。あるカテゴリ変数の値を持ち目的変数が1の個数を、その値の出現回数で割ったもの。 下の例だと、Aの出現回数は3回、カテゴリーがAで目的変数が1を持つものの出現回数は2回なのでTarget Mean Encodingの値は2/3 = 0.666 となる。
category | outcome | category_counts | outcome_counts | target_mean_encoding | |
---|---|---|---|---|---|
0 | A | 1 | 3 | 2 | 0.666667 |
1 | A | 1 | 3 | 2 | 0.666667 |
2 | A | 0 | 3 | 2 | 0.666667 |
3 | B | 1 | 2 | 1 | 0.500000 |
4 | B | 0 | 2 | 1 | 0.500000 |
5 | C | 1 | 1 | 1 | 1.000000 |
#Target Mean Sample import pandas as pd from sklearn.preprocessing import OneHotEncoder df = pd.DataFrame({ "category" : ["A","A","A","B","B","C"], "outcome" : [1,1,0,1,0,1], }) 単純に計算すれば次のようなコードになる。 #各カテゴリーともう一つの特徴量の出現回数を計算 grouped_category = df.groupby("category")["category"].count().reset_index(name='category_counts') grouped_outcome = df.groupby("category")["outcome"].sum().reset_index(name='outcome_counts') #元のデータセットにカテゴリーをキーとして結合 df = df.merge(grouped_category, how = "left", on = "category") df = df.merge(grouped_outcome, how = "left", on = "category") df["target_mean_encoding"] = df["outcome_counts"]/df["category_counts"] df.head(10)
Target Mean Encodingには過学習を防ぐための leave one out schemaという手法も提案されている。これは上の表で1列目のAに対してTarget Mean Encodingを計算するときに、自分自身の値を除いて計算する方法だ。この方法を用いて1列目のAに対してTarget Mean Encoding を計算すると(自分を除いたカテゴリーがAで目的変数が1の総数) / (自分を除いたAの出現回数)= 0.5 となる。
category | outcome | category_counts | outcome_counts | target_mean_encoding | |
---|---|---|---|---|---|
0 | A | 1 | 3 | 2 | 0.5 |
1 | A | 1 | 3 | 2 | 0.5 |
2 | A | 0 | 3 | 2 | 1.0 |
3 | B | 1 | 2 | 1 | 0.0 |
4 | B | 0 | 2 | 1 | 1.0 |
5 | C | 1 | 2 | 2 | 1.0 |
6 | C | 1 | 2 | 2 | 1.0 |
#Target Mean Sample with leave one out scheme import pandas as pd from sklearn.preprocessing import OneHotEncoder df = pd.DataFrame({ "category" : ["A","A","A","B","B","C","C"], "outcome" : [1,1,0,1,0,1,1], }) #各カテゴリーの出現回数を計算 grouped_category = df.groupby("category")["category"].count().reset_index(name='category_counts') grouped_outcome = df.groupby("category")["outcome"].sum().reset_index(name='outcome_counts') #元のデータセットにカテゴリーをキーとして結合 df = df.merge(grouped_category, how = "left", on = "category") df = df.merge(grouped_outcome, how = "left", on = "category") #計算する際に自分自身の値は除く df["target_mean_encoding"] = (df["outcome_counts"] - df["outcome"])/(df["category_counts"] - 1) df.head(10)
おわりに
4種類のカテゴリ変数の数値化に関する方法を紹介した。「カテゴリー変数に対する手法」という目次をつけておいて、時間が無くて数値変数に対する手法の紹介まで出来なかったのが申し訳ない。 もとのスライドには、Weight of Evidecneと呼ばれる手法や、具体的な特徴量抽出の手順などが記載されているので時間があったら追記するかもしれない。
feather - pandasの読み書きを高速化
kaggleに参加して巨大なCSVファイルを読み書きしていると、それだけで数分近くかかってしまうことがある。 そこで紹介されていたfeatherというDataFrame保存用ライブラリを使ってみた。 featherはpandasのDataFrameを高速に保存、ロードするために開発されたApache Arrowのpythonラッパーだ。
Apache Arrow特有のカラム型メモリ管理をすることで、データの読み書きを高速化しているそうだ。 ベースになっているApache Arrowが他言語や他オープンソース(SparkやHadoop)との互換性を重視しているために、feather形式で保存すればRなどでも読み込める。
featherのインストールはpipから簡単にできる。
pip install feather-format
使い方も簡単でwrite_dataframe、read_dataframeを呼ぶだけ。
import feather import pandas as pd #データフレームの読み込み df = feather.read_dataframe(path_feather) #データフレームの書き込み feather.write_dataframe(df, path_feather)
またpandasのバージョン0.20.0からpandas.DataFrame.to_featherやpandas.read_featherというメソッドが追加されているので、以下のような方法でも使える。
import pandas as pd #データフレームの読み込み df = pd.read_feather(path_feather) #データフレームの書き込み df.to_feather(path_feather)
約18列、150000行のデータのデータセットで、csvとfeatherそれぞれの読み書きの速度を計測すると、
読み込み速度
# csvからの読み込み %time df = pd.read_csv(path_csv) # feather formatからの読み込み %time df = pd.read_feather(path_feather) %time df = feather.read_dataframe(path_feather)
CPU times: user 29.1 s, sys: 27.7 s, total: 56.8 s Wall time: 1min 38s CPU times: user 5.53 s, sys: 10.1 s, total: 15.6 s Wall time: 37.7 s CPU times: user 5.46 s, sys: 11.1 s, total: 16.5 s Wall time: 35.7 s
書き込み速度
# csvへの書き込み %time df.to_csv(path_csv) # featherへの書き込み %time df.to_feather(path_feather) %time feather.write_dataframe(df, path_feather)
CPU times: user 53 s, sys: 11.4 s, total: 1min 4s Wall time: 1min 30s CPU times: user 18.3 s, sys: 10.1 s, total: 28.4 s Wall time: 39 s CPU times: user 18.8 s, sys: 8.76 s, total: 27.5 s Wall time: 35.5 s
実時間で読み書きともに約2.5倍程度と非常に高速に動作することが確認できた。データセットの保存や読み込みに使えば大幅な時間の節約になる。 長期のデータには向いていない(破損とかに弱い?)そうなので、あくまで前処理したデータのキャッシュなどに使うのがいいと思う。
Talking Data AdTracking Fraud Detection Challenge
先日開催されたTalking Data AdTracking Fraud Detection Challengeに参加した。 Talking Data AdTracking Fraud Detection Challengeは中国の広告企業TalkingData主催のコンペで、広告クリックのログから不正なクリックを検出するというコンペティションだ。
TalkingData AdTracking Fraud Detection Challenge | Kaggle
初めてのkaggleで最終的な結果は1012位/3967位、上位26%だった。最初のコンペティションとしては悪くない結果だとは思うが、個人的にはもう少し上を狙いたかった。
コンペティション概要
以下の表に示す特徴を持つ、約180,000,000のレコードを持つ巨大なデータセットが与えられた。これらの特徴は全てLabel Encodeされていて全て整数で与えられている。
特徴量 | 説明 |
---|---|
ip | クリック元のipアドレス |
os | クリック元のOSやバージョンに関する情報 |
app | クリック元のアプリ |
device | クリック元の携帯電話の種類 |
channel | クリックされた広告のid的なもの |
click_time | クリックされた時刻 |
is_attributed | 目的変数 0は通常のクリック、1は不正なクリック |
ノートパソコンで今回のコンペに参加したが到底データセット全てはメモリに乗らず、一部を利用して学習させる方針で挑んだ。以下実際に参加したログと感想。
4/17
click_timeをパースして、day、hour、minuteなどの特徴量に分解した。 また一時間ごとのipの出現回数や、一日ごとのip-osの組み合わせの出現回数を特徴に加えxgboostでデータの一部を学習した。 与えられた学習データを学習用とテスト用に分けて評価したところ、テストデータでは約96%を達成。 しかしテストデータを送信したところ94%程度しか達成できなかった。2900人中2700位とか。
4/18
データセットの前半と後半でデータの傾向が少し違うらしい。 データが膨大でメモリに乗らないので(mac book airでは厳しいものがある)後半の一部を使用。 また単位時間あたりのipアドレス数を初めとした特徴量を導入した。 これらの結果リーダーボードで96.3%を達成。3000人中1800位とか。まだまだ。
4/21
当初はxgboostを使っていたが、Kernelを見ていると多くの人がlightgbmを使っている人が多かったのでlightgbmを試してみた。 lightgbmはカテゴリ変数をいい感じに取り扱ってくれるらしくて、そのおかげか96.5%を達成できた。あとパラメータによるのかもしれないが、少し計算速度が速くなったように感じた。
4/25
Kernelで公開されていた、次のクリックまでの経過時間を表すnext_clickという特徴を導入した。 この特徴量を導入しただけで飛躍的に精度が向上し、精度97.5を達成し3100人中上位700位まで一気に順位があがった。
4/23
Test Dataには4, 5, 9, 10, 13, 14時のデータしか含まれていないという情報がKernelで流れていた。 そこで学習に使うデータも4, 5, 9, 10, 13, 14時に絞ってみたところ、精度97.82を達成して3300人中500位近くまで順位を上げることが出来た。
4/25日以降
他に何個か特徴量を試したが精度の向上に繋がることはなかった。途中で最高精度を達成したファイルを直接書き換えてしまい、ロールバックすることも出来ず精度97.5あたりをうろついていた。怒涛の勢いで追い上げをくらい、順位を3900人中1000位近くまで落としてしまった。
最終日に誰かがディスカッションボードで精度98.11(上位5%程度の精度)のKernelを公開して物議を醸していて笑った。
感想
初めてKaggleのコンペティションに参加してみての感想としては、Kernelで初心者向けのコードが公開されていたりするので、英語さえ読めれば参加のハードルは低いと思った。正直、機械学習用のsklearn、xgboost、lightgbm、kerasなどのライブラリはpythonが使えれば中身を理解していなくても動かせてしまう(それで勝てるかは別問題として)。参加してみるとDiscussionとかで色々な知識を身につけることが出来るので、機械学習とかに興味がある人はとりあえず参加してみれば力をつけることが出来るのではないかと思う。ただ常時kaggleのことが気になってしまって他の作業ができなくなるののにだけは注意方がいい。
やはり精度を上げるための肝は、いかに優れた特徴量を設計するかにかかっているように感じた。下手な特徴量では逆に精度をさげてしまうため、効率的に有効な特徴量を見つけていくノウハウを学ぶ必要がある。
今回の反省点としては、
- 試してみた特徴量やプログラムは、リーダーボードのスコアと一緒に管理しておく。
- 積極的にKernel、Discussionを見て人の意見を取り入れていくこと。
- 一度作成したモデルもしっかり保管しておく。きちんと管理しておけば、最後の最後でBaggingやStackingして少し精度を挙げられる可能性がある。
あたりであろうか。
またCERNのコンペティションやロシアの広告会社Avitoのコンペティションが開催されるようなので参加してみようかと思う。Kaggleに参加してみたいけど難しそうと思っている人は、とりあえず適当なこんぺ