この記事は**「Gitでコード管理しているのに、KaggleのNotebook提出用に別のコードを用意するのが面倒だ」**と感じてる人向けの記事となっております。また、Notebookで提出するというコンペであるCodeCompetition
を対象として話を進めてゆきます。CodeCompetition
については後述します。
はじめに
再掲しますが**「Gitでコード管理しているのに、KaggleのNotebook提出用に別のコードを用意するのが面倒だ」**。この不満、心当たりがある方が多いのではないでしょうか。
今回書く記事はその不満を解消できる方法を発見しましたので共有し、その方法について解説するというのが趣旨になります。
また記事内容ですが主にこちらのKaggle Kernel - lopuhin/imet-2019-submissionの内容を参考に書いております。
前置きのために記事ではCodeCompetition
とはから説明していますが本題から読みたい方は#git等で管理しているファイル構造をそのまま使うから読んで下さい。
CodeCompetitionとは
冒頭で触れたCodeCompetition
について説明させてください。
Kaggleのドキュメントで説明されている内容を引用します。
Some competitions are code competitions. In these competitions all submissions are made from inside of a Kaggle Notebook, and it is not possible to upload submissions to the Competition directly.
下記はそれを私が意訳したものです。
CSVなどのファイルを直接アップロードするのではなく、Notebookを利用してそこにファイル提出するまでのソースコードを書きそれを提出するという形式のコンペを指します。
開催中(2019/12/25現在)のコンペ8種の内(Featured/Researchのみカウント)6種類がCodeCompetition
であるのでおそらく参加されている方も多いと思います。
[参考]CodeCompetition例
赤枠で囲ったCode Competitionというタグが付けられたコンペがCodeCompetitionになります。
CodeCompetitionでの提出方法
Kaggle内でコードを書こうとするとScripts
とNotebooks
という2種類の記述する手段があり、CodeCompetition
では2つの形式のどちらかに提出ファイルを生成するまでのコードを書きそれを提出する形になります。
Scripts
このScriptのみにコードを書いてゆきます。
Notebooks
いわゆるJupter Notebooks、Scripts同様これのみにコードを書いてゆきます。
CodeCompetitionの不満点
デバッグやテストのしやすさを考えると、こんな感じで処理毎にモジュール(ファイル)で分けたいという思いがあると思います。(タイタニックコンペに取り組んだ際の一例です)
titanic_sample
│ dataset.py
│ model.text
│ predict.py
│ train.py
│ utils.py
│ __init__.py
│
├─resources
gender_submission.csv
test.csv
train.csv
提出時にも同様のファイル構造で提出できればよいのですが上記で触れたようにScriptsかもしくはNotebooksにほぼ提出専用のコードを書くことになっているというのが現状の不満点です。
Git等で管理しているファイル構造をそのまま使う!
パッケージとしてインストールしてしまおう
遅くなりましたが、本題に入ります。
Gitで管理しているコード郡をNotebook
環境下でそのまま使えれば、KaggleのNotebook提出用に別のコードを用意する必要なんてないわけです。というわけでNotebook
環境下にそれらのコードを用意してしまうというのが今回説明する解決方法になります。
簡単な概要
これ以降手順の説明に入りますが説明が長くなるので簡単にやることを書くと、
KaggleのNotebookにGitで管理しているコードをアップロードして、それらをパッケージとしてインストールしSubmitまでの書かれたコードをコマンドライン上から実行するという流れです。
Submitするまでの手順説明
具体的にどうやるのか手順を説明します。
説明をわかりやすくするためにタイタニックのデータを使って説明します。
また、前処理、モデルの学習等のコードはこちらを参考にさせていただきました。
Kaggle Kernel - sishihara/upura-kaggle-tutorial-04-lightgbm
手順①
Github - lopuhin/kaggle-script-templateをForkします。
手順②
ForkしたリポジトリをTemplateリポジトリにする。この作業は色々なコンペでこのTemplateリポジトリを使い回すためにやっています。
参考に私が作成したTemplateリポジトリのURLを貼っておきます。
Github - wakamezake/kaggle-script-template
手順③
手順②で作成したTemplateリポジトリを使って、コンペ用のリポジトリを作成する。
Github - wakamezake/Titanic_submit_script
手順④
easy_goldディレクトリ(Github - wakamezake/Titanic_submit_scriptだとtitanic_sampleにrenameしてます)にいつもどおりコードを書く。注意点として以下のように最終的にsubmission.csv
が出力されるようにコードを書いてください。
def run(command):
os.system('export PYTHONPATH=${PYTHONPATH}:/kaggle/working && ' + command)
run('python setup.py develop --install-dir /kaggle/working')
run('python titanic_sample/train.py model.text --test_size 0.3')
run('python titanic_sample/predict.py model.text submission.csv')
そしてタイタニックでの例ですがおそらく以下のようなフォルダ構成になると思います。
.
│ .gitignore
│ build.py
│ README.rst
│ script_template.py
│ setup.py
│
├─build
│ .keep
│ script.py
│
├─titanic_sample
│ dataset.py
│ model.text
│ predict.py
│ train.py
│ utils.py
│ __init__.py
│
├─resources
gender_submission.csv
test.csv
train.csv
手順⑤
build.py
を実行します。実行後build/script.py
が生成されているのでそれをNotebooks
に貼り付けます。その後はCommitボタンを押してsubmitするといういつもの流れです。
貼り付けた例としてこちらのカーネルを用意しました。Kaggle Kernel - wakamezake/titanic-submission-sample
Submitするまでの手順説明は以上になります。
少し解説します
手順をざっと説明してきましたがやったことを解説しますと・・・
手順の中で実行したbuild.py
について。まずすべてのコードをbase64でエンコードして文字列に変換します、その際エンコードしたコードのファイルパスをkeyにエンコードして文字列をvalueにした辞書を作ります。そしてscript_template.py
というscript.py
を作るためのテンプレートのfile_data
部分に辞書の内容を置換します。
script.py
をNotebooks
に貼り付けたあとエンコードされたコードをデコードして元のファイル構造を保った状態に戻します。そしてpython setup.py develop --install-dir /kaggle/working
を実行してNotebooks
の環境にデコードしたコードをパッケージとしてインストールします。
解説は以上です。
build.py
#!/usr/bin/env python3
import base64
import gzip
import os
from pathlib import Path
is_Windows = True if os.name == "nt" else False
def encode_file(path: Path) -> str:
compressed = gzip.compress(path.read_bytes(), compresslevel=9)
return base64.b64encode(compressed).decode('utf-8')
def build_script():
to_encode = list(Path('titanic_sample').glob('*.py')) + [Path('setup.py')]
if is_Windows:
# https://stackoverflow.com/questions/54671385/convert-windowspath-to-posixpath
file_data = {str(path.as_posix()): encode_file(path) for path in
to_encode}
else:
file_data = {str(path): encode_file(path) for path in to_encode}
template = Path('script_template.py').read_text('utf8')
Path('build/script.py').write_text(
template.replace('{file_data}', str(file_data)),
encoding='utf8')
if __name__ == '__main__':
build_script()
script_template.py
import gzip
import base64
import os
from pathlib import Path
from typing import Dict
# this is base64 encoded source code
file_data: Dict = {file_data} # ←ここに置換しています
for path, encoded in file_data.items():
print(path)
path = Path(path)
path.parent.mkdir(exist_ok=True)
path.write_bytes(gzip.decompress(base64.b64decode(encoded)))
def run(command):
os.system('export PYTHONPATH=${PYTHONPATH}:/kaggle/working && ' + command)
run('python setup.py develop --install-dir /kaggle/working')
run('python titanic_sample/main.py')
script.py
import base64
import gzip
import os
from pathlib import Path
from typing import Dict
# this is base64 encoded source code
file_data: Dict = {
'titanic_sample/dataset.py': 'H4sIAHQXAl4C/6VVUW/aMBB+R+I/WLzE0VCA7Q0pD6grVbVp7QZvCEUmOahVx7FsB8Gm/fednYQktKs2Na3ks+/7Pp/Pd2avi5woZp8E3xGeq0Jb8ojT4WA4qKeyzNWZMEOkuqwpJjNcwX+VDQd7J2KeBTAto7zIQCQGBKSWF7IRtZpxmVgwNjFKcOs28DzLLZM8TQzLlYCotFyYhvTwLfmyuLv7euvQnxfrRfLj4WFNYh8iDaJowqUq7aTWCAjftxwCwgAJookGU5Q6BROEw8ENs3AoNE+ZSJbAbIleFNwEt/mO6WfIgjEJHlPBjHHWCk7Bdjioond5QmwbyISMvCdKzXGEKHe6V0HoqDGm3OX8ddQBZAY68QhjMHc1xf1lsCdKg9IFnsPQjFkWzocDgp+zN1WgESIES4FugpwJcAfYg7e2Y7KZjskMR0yZw8RrXULYlbhkYBvtuRCS0WAV/BMeT/JCImeK/kKBOcF9gxscZzh+x/Hj7zBixp4V0ErOfVzanviSaWgDkSrKgUna9YVvhLY4dMjdpRwyjjpvcZcs5+K84j+hc7BHptMnnH9o0s13K+Xnsy733ixEISvitHVEokg3r6nHPildVi2nAStTenJbAKJgWVUelja3XwNVhnePXqwY2imx8Irs9AxYWvUhBhFPo0+NlC9lDKGr1RZ+nSPHvMY0Ze938yCmD2CTtBAIHa1KfeRHyEZtQiqFtJAps3TjNxl7aaxPg50fLxk2b9gnXJV/7cSHxoLbqsxl1cnfWO5L/xF7GOQB9L1v6jVPn8E664btuHRtfbmfTBeK9qXGhJ24iWcvKqWfLH+rcwGySlW47aXJe1vnfNvQz0kj4MdNm7Fa4NQHVBG2qCa68IKuNvQvzd+xPe0xGkcmeDZuwnGGX2m27TzY9NRiKqNt3v//2gK8WO/S0/iDVOSJsfi8x9N3SRk8neX7c1yfM+w1Gn2ZvCr5+KTQ6zQi9Q+pvtnNYQcAAA==',
'titanic_sample/predict.py': 'H4sIAHQXAl4C/31QwWrEIBC9B/IPspcYCNJLL4UU2kPPhR5LGcw6SYUYg06W3b+vRpPdbqEi6Pjec948bWbriEk3zNJ5LIuy0Olp1MM3DZ1h0rNx6CLSO2sYaZKTPoKXZh5RKEnSI7FNZaWC/Nakyi+d0RT1ZaGwZwMShH6LwYk8r5/KgoW1tnes3a2Il8x5XxFe3/KEVGr/hB+MVTjCLOn78B/NLjQvlA39ogeOD72zaj2izm9dHdLippV2HcRIPe3+8w93wyWMQ8PCPgOhp7pJdR3Yt2lt5OBuQ5LRDViHjNDQiVdrPYVQ0uC9HrGNBsQ1iCy6wOxQBdWKiFjoI/Fs5Y7D8+2ZPYjHWkhPlxm5nuhq7bP6WNxJn1BVX0GRBDsqyMLRn/jq5W/aDdOTwnP7JkePdQpS9wxgkgYBWNuyCiDGClDlXFPIZfEDh06KTakCAAA=',
'titanic_sample/train.py': 'H4sIAHQXAl4C/5VUTY+bMBC9R8p/sHKBSCxN1dtKPqy0Pe+q3dtqZQ0wsG4xINuJklb97x1/AEmWRiocsPzezLzxPCPV0GvLQDcDaIPr1Xolw1Yrm3fbFIqBYW1TOKTWvWJWWuhkKQyoocW8AgsGLYtRjw8vD+Lb09NLxtoeKhFhF201yE4MYN8Zn3nsE9t4JC/NYUMsNHaZREDkuLfCmjVoBSnfK+ysSbf36xWjxzeiKXxsKn+InGePpNtzXg5VNSVJN6qvsPX1N7dod3dep5G/cJMxexqQ19SvzRjpgn1r+S7/EhNQlCE1MY//uExm1KHR7nXnaXNr/kjSo/DfjJ3GxVEcoJWV24mLEiw2vZYltKJGoFxoxqOguYVAqk/r/DFM42PeQP/Xs1CCL5adqyKp+1D0WrrGGjV2JfJJ6G0l/yFkGh4od/q/57xJX/zA0soDJvcsKWQH+pQE+M8Y520Q9YdRzPEhZ8aWNPvGBHVr+OuMTyfydk5FXfRkBbfPP+/OkG6vRNH35C/d77uKwN05jKDbkzC2HwbZNYFjiBQYl6byfcyuUq6T0RzRl1eXKGCTu8M1Mvm0EfDZQdNUaYc424ylp9ldHtu6gzz7GaRTslhtaYQU85p8VQXon1glGUueyxaMcavveEzeLud0cV34tb35LZvHBvi1Pflo06WgJcW3roTXmRs4oPDL1B/q/LPZhhnJmgnRgUIhGOcsEcJNTIgkjizMb736C/g+V6a2BQAA',
'titanic_sample/utils.py': 'H4sIAHQXAl4C/yXNsQqDMBCA4T2Qdwg4OOnVLqVCB6FFRFFw6SimBD2a5II5C337tnT8v+VHF2hjRVEKKRK1ModYAizI667zBzmwFPYVPTznZbEmQ2c4Ox6KM2hLGtwc2WzwU9gZbczDO+mKkxRDP7VVXXe3Umkiqy4q/fd0H8a26evp2oypQv+d58a/cCMvxQet+8gdkAAAAA==',
'titanic_sample/__init__.py': 'H4sIAHQXAl4C/wMAAAAAAAAAAAA=',
'setup.py': 'H4sIAHQXAl4C/0srys9VKE4tKS0oyc/PKVbIzC3ILyqBiPBy8XKBGRq8XApAkJeYm2qrXpJZkpiXmRxfnJhbkJOqrgORK0hMzk5MTy22jUZXEAtUocnLBQBmZqg3aQAAAA=='}
for path, encoded in file_data.items():
print(path)
path = Path(path)
path.parent.mkdir(exist_ok=True)
path.write_bytes(gzip.decompress(base64.b64decode(encoded)))
def run(command):
os.system('export PYTHONPATH=${PYTHONPATH}:/kaggle/working && ' + command)
run('python setup.py develop --install-dir /kaggle/working')
run('python titanic_sample/main.py')