コピペで使える。Kaggleでの実験を効率化する小技まとめ
この記事はKaggle Advent Calendar 2018の20日目の記事です。当初の予定ではPLAsTiCCコンペの振り返りをするはずだったのですが、時空の狭間に吸い込まれた結果0サブミットでフィニッシュしてしまいました。何ででしょうね。 そこで、代わりにKaggleで使える便利なスニペットまとめを書くことにします。
ちなみにもうひとネタあったのでいつか書きたいですが、修論があるのでいったん見送り……
- LINEに通知を送る
- 処理にかかる時間を計測する
- LightGBMの学習結果をログに出す
- Google Spreadsheetに結果を記録する
- Notebook上でライブラリを毎回再読込する
- DataFrameのメモリを節約する
- まとめ
LINEに通知を送る
「思考は止めてもサーバーは止めるな」とはkaggler-jaの管理者であるtkmさんの名言です。サーバーを止めないためには正常・異常問わず計算終了時に通知を送ることが重要です。
import requests def send_line_notification(message): line_token = 'YOUR_LINE_TOKEN' # 終わったら無効化する endpoint = 'https://notify-api.line.me/api/notify' message = "\n{}".format(message) payload = {'message': message} headers = {'Authorization': 'Bearer {}'.format(line_token)} requests.post(endpoint, data=payload, headers=headers)
- https://notify-bot.line.me/my/ からパーソナルアクセストークンを発行する
- 発行したアクセストークンを
YOUR_ACCESS_TOKEN
に入れる
これでLINE通知を送ることができます。アクセストークンはコンペが終わったら無効化すれば、GitHubにコードをそのままpushしても大丈夫です。
副作用として、計算が終了したことがわかってしまうというものがあります。
処理にかかる時間を計測する
どこまで処理が進んでいるか分からないのは気持ちの良いものではありません。 また、Kernelコンペの場合はそれぞれの部分でどの程度時間がかかっているかを把握し、制限時間内に収める必要があります。
Jupyter Notebookの場合
Jupyter Notebookの場合、計測したいセルの先頭に
%%time
というマジックコマンドを書くだけで時間を計測してくれます。
例として、0から9999までの和を10000回計算するのにかかる時間を測定してみます。
Pythonスクリプトの場合
import logging import time from contextlib import contextmanager @contextmanager def timer(name, logger=None, level=logging.DEBUG): print_ = print if logger is None else lambda msg: logger.log(level, msg) t0 = time.time() print_(f'[{name}] start') yield print_(f'[{name}] done in {time.time() - t0:.0f} s')
使い方と出力は以下のとおりです。with文を利用し、その中の処理にかかった時間を出力します。
with timer('preprocessing'): # preprocessing
[preprocessing] start (途中での出力) [preprocessing] done in 17 s
小技としては、ロガーを受け取れるようになっています。 条件によって関数を差し替える方法を知っていると、コーディングの幅が広がると思います。
LightGBMの学習結果をログに出す
import logging from lightgbm.callback import _format_eval_result def log_evaluation(logger, period=1, show_stdv=True, level=logging.DEBUG): def _callback(env): if period > 0 and env.evaluation_result_list and (env.iteration + 1) % period == 0: result = '\t'.join([_format_eval_result(x, show_stdv) for x in env.evaluation_result_list]) logger.log(level, '[{}]\t{}'.format(env.iteration+1, result)) _callback.order = 10 return _callback
log_evaluation
関数にロガーを渡してあげることで、そのロガーを利用してログを吐くことができます。
clf = lgb.LGBMClassifier()
callbacks = [log_evaluation(logger, period=10)]
clf.fit(X_train, y_train, eval_set=[(X_val, y_val)], callbacks=callbacks)
これはこちらの記事でも紹介していますので、詳しくはそちらを確認してください。
Google Spreadsheetに結果を記録する
Google App Scriptを利用して、実験の結果をGoogle Spreadsheetに自動で記入します。
複数人で同時に実験をする際、パラメータや結果を簡単に共有することができます。
Google Spreadsheet側の設定
まず、結果を記録したいスプレッドシートを用意します。
ツール > スクリプトエディタ
を開きます。
以下のコードを貼り付けます。これはjavascriptで書かれており、POSTメソッドで送られてきたデータをパースし、タイムスタンプを先頭につけてスプレッドシートに書き込みます。
function doPost(e) { if (e==null || e.postData == null || e.postData.contents == null) { return; } var ss = SpreadsheetApp.getActive(); var sheet = ss.getActiveSheet(); var data = JSON.parse(e.postData.contents); var timestamp = new Date(); data.unshift(timestamp); sheet.appendRow(data); }
次に、公開 > ウェブアプリケーションとして導入
を選びます。
設定画面が開くので、以下のように設定してください。
導入
を選択すると、アプリケーションを承認する画面が出るので、承認してください。
するとウェブアプリケーションとして公開され、URLが表示されます。 これをコピーしておきましょう。
これでGoogle Spreadsheet側の設定は終了です。
Pythonからアクセスする
Google Spreadsheetに登録したスクリプトは、json形式でリストを送れば、その各要素をセルに書き込むというものになっています。
Pythonから扱うには、以下のような関数を用意すると便利です。
YOUR_ENDPOINT_URL
には、先程コピーしたURLを入れてください。
import json import requests def write_spreadsheet(*args): endpoint = 'YOUR_ENDPOINT_URL' requests.post(endpoint, json.dumps(args))
これを使えば、書き込みたいものを関数に渡してあげるだけで良いです。 引数として渡した各要素をそれぞれ別のセルに入れてくれます。
クロスバリデーションの結果などはリストやnumpyのarrayに入っていることが多いですが、その場合は先頭に*
をつけることでばらばらにして引数に入れてくれます。
# scores = np.array([0.8, 0.7, 0.75, 0.8]) とする # write_spreadsheet('baseline', 0.8, 0.7, 0.75, 0.8) は以下のように書ける write_spreadsheet('baseline', *scores)
実行結果は以下のとおりです。先頭にタイムスタンプが付き、CVの結果がそれぞれ別のセルに入っていることが分かるかと思います。
Notebook上でライブラリを毎回再読込する
Kaggle用に自作ライブラリを使っている場合、コンペをやりながら自作ライブラリも書き換えていくことが多いと思います。
Jupyter Notebookでライブラリを読み込む際、普通は1回import
した時点で中身は固定されてしまいます。
そこで、ライブラリを書き換えた時点でリロードする必要が出てきます。
% reload_ext autoreload
% autoreload 2
これをノートブックの先頭に書いておくと、関数を実行するたびにリロードが走るようになります。
より詳しくはこちらのQiitaをご覧ください。
DataFrameのメモリを節約する
pandasのDataFrameはint
ならnp.int64
に、float
ならnp.float64
がデフォルトで使われます。
しかし、ある程度データセットが大きくなってくると、DataFrameがメモリを圧迫して学習を思うように進めることができなかったりします。
解決方法のひとつはpd.read_csv()
などをする際にdtypes
を指定して読み込むことです。
この方法は最初のデータセットは適切な精度で読み込むことができる反面、途中で作成した特徴量などには一切タッチできません。
そこで、各列の値の範囲を参照し、適切な型に変換します。
def reduce_mem_usage(df, logger=None, level=logging.DEBUG): print_ = print if logger is None else lambda msg: logger.log(level, msg) start_mem = df.memory_usage().sum() / 1024**2 print_('Memory usage of dataframe is {:.2f} MB'.format(start_mem)) for col in df.columns: col_type = df[col].dtype if col_type != 'object' and col_type != 'datetime64[ns]': c_min = df[col].min() c_max = df[col].max() if str(col_type)[:3] == 'int': if c_min > np.iinfo(np.int8).min and c_max < np.iinfo(np.int8).max: df[col] = df[col].astype(np.int8) elif c_min > np.iinfo(np.int16).min and c_max < np.iinfo(np.int16).max: df[col] = df[col].astype(np.int16) elif c_min > np.iinfo(np.int32).min and c_max < np.iinfo(np.int32).max: df[col] = df[col].astype(np.int32) elif c_min > np.iinfo(np.int64).min and c_max < np.iinfo(np.int64).max: df[col] = df[col].astype(np.int64) else: if c_min > np.finfo(np.float16).min and c_max < np.finfo(np.float16).max: df[col] = df[col].astype(np.float32) # feather-format cannot accept float16 elif c_min > np.finfo(np.float32).min and c_max < np.finfo(np.float32).max: df[col] = df[col].astype(np.float32) else: df[col] = df[col].astype(np.float64) end_mem = df.memory_usage().sum() / 1024**2 print_('Memory usage after optimization is: {:.2f} MB'.format(end_mem)) print_('Decreased by {:.1f}%'.format(100 * (start_mem - end_mem) / start_mem)) return df
オリジナルはKaggleで見たのですが、3つカスタマイズを加えてあります。
- ロガーを受け入れるようにした
object
とdatetime
は変換させない- np.float16はfeather形式で書き出せないため、np.float16を使わない
追記:オリジナルはこちらのKernelだそうです。教えてくださったupuraさんありがとうございます!
まとめ
以上、Kaggle Advent Calendar 2018 20日目の記事でした。
昨日の記事は、カレーちゃんさんのkaggleコンペのディスカッションの情報をメールで通知する方法【2018年12月版】 - kaggle全力でやりますでした。
明日はnamakemonoさんのBERTでKaggleの過去問Quora Question Pairsを解いてみるです。(公開されました!)