天色グラフィティ

技術ちっくなことを書きます

Dockerでデータ分析環境を手軽に作る方法

f:id:ejinote:20190512163833p:plain

何かデータ分析を行わなければいけないとき、手軽に分析環境を用意したいというニーズがあります。

Jupyter Notebook上でnumpy、pandas、matplotlib、scikit-learnあたりが使えれば十分でしょうか。XGBoostやLightGBMといったライブラリも使えるようにしておきたいという人もいるかと思います。

一方、ローカルにいろいろなライブラリをインストールしていくと、次第に環境が汚れていってライブラリの衝突などが起こりやすくなってしまいます。

KaggleにはKernelという計算環境があり、そこには主要な機械学習ライブラリが予めインストールされています。データ分析をやっていく上で不自由はありません。今回はDockerとdocker-composeを使ってKaggle Kernelを手元に再現し、ポータブルな分析環境として使う方法を紹介します。

データ分析界隈には、なんとなくDockerを敬遠しているような人もいるかと思いますので、この機会に入門してしまいましょう。

解説はMacOSで行いますが、Dockerとdocker-composeがインストールできれば同じ方法が使えるかと思います。

登場する用語の説明

Dockerは、まっさらな環境を簡単に作るツールです。

Dockerfileというファイルに環境の設定を書けば、そのとおりの環境を作ることができます。他人が作ったDockerfileに自分なりのカスタマイズを加えることも可能です。

今回は、Kaggle公式が公開しているpython環境をもとにカスタマイズを行っていきます。

加えてコンテナ(container)イメージ(image)という用語も覚えましょう。

f:id:ejinote:20190512163300p:plain

イメージはDockerfileをビルドして作ります。イメージはDockerfileという設計図を元に作られた「型」だと考えて良いと思います。

イメージを元にコンテナを起動し、そのコンテナ上でJupyterを立ち上げます。利用する際はコンテナにアクセスします。イメージができていればコンテナは数秒で起動できますし、どれだけ環境を汚してもイメージから起動しなおせばまた使うことができます。同じイメージから何個もコンテナを起動することも可能です。

Google Container Registryは便利なイメージが多く登録されているサイトです。今回はKaggleが提供しているPythonイメージを利用します。

docker-composeはDockerfileのビルドと起動を管理するツールです。

f:id:ejinote:20190512163312p:plain

Dockerコンテナを起動する際、いろいろなオプションをつけることができます。しかし、それをいちいちコマンドラインで打つのは煩雑です。docker-composeを使えば、様々なオプションをdocker-compose.ymlというYAMLファイルで設定することができます。

docker-composeは本来は複数のコンテナを連携して使うために作られたツールですが、今回はコンテナを簡単に立ち上げるためだけに使いたいと思います。

Dockerとdocker-composeのインストール

それでは、Dockerとdocker-composeをインストールしていきましょう。

既にインストール済みの方はこの節は読み飛ばしてください。

https://docs.docker.com/docker-for-mac/install/ に従ってDocker Desktop for Macをインストールします。

インストールが終わったら、アプリケーションからDocker.appを起動します。(起動にはしばらく時間がかかるかもしれません)

Dockerが起動されたら以下のコマンドを打ち、dockerとdocker-composeのバージョンが表示されるか確認してください。

$ docker --version
$ docker-compose --version

Kaggle Kernelを元に分析環境を作る

それでは、Kaggleが提供しているDockerfileを元に、データ分析の環境を作っていきます。

今回は以下のようなフォルダ構成で解析を進めていくとします。

.
├── .dockerignore
├── Dockerfile
├── docker-compose.yml
├── input/     # データ
├── notebook/  # Jupyter Notebook
└── script/    # スクリプト

このうち、以下の3つのファイルは新しく作成します。

  • Dockerfile
  • docker-compose.yml
  • .dockerignore

Dockerfile

Dockerfileは、イメージの定義です。どういうOSを使って、どういうライブラリをインストールするのか、という内容を書きます。

# kaggleのpython環境をベースにする
FROM gcr.io/kaggle-images/python:v56

# ライブラリの追加インストール
RUN pip install -U pip && \
    pip install fastprogress japanize-matplotlib

内容はこれだけです。

FROM gcr.io/kaggle-images/python:v56は、Google Container Registry(gcr.io)上にあるKaggle公式Dockerイメージを出発点にしてイメージを定義する、という意味です。 gcr.io/kaggle-images/python には、既にnumpyやpandasを始めとして、データ分析に必要な多くのライブラリが含まれています。

:v56はタグの指定です。KaggleのDockerイメージは日々改善されているので、今の時点での最新版を書いておくと良いでしょう。タグの一覧はgcr.io/kaggle-images/pythonにアクセスすると見ることができます。

:latestと指定して最新版を入れることもできますが、イメージが更新されるたびにダウンロードが走ってしまいますし、環境がころころ変わるのは望ましいことではないので、バージョンを特定しておくことをおすすめします。

もし足りないライブラリがあるならば、 RUN コマンドを使うことで pip install をすることができます。

docker-compose.yml

docker-compose.ymlにはコンテナを起動する際の設定を書きます。

version: "3"
services:
  jupyter:
    build: .
    volumes:
      - $PWD:/tmp/working
    working_dir: /tmp/working
    ports:
      - 8888:8888
    command: jupyter notebook --ip=0.0.0.0 --allow-root --no-browser

versionはdocker-compose.ymlの書き方のバージョンです。3を指定しておけばよいかと思います。

services以下に起動するコンテナの設定を書いていきます。jupyterはコンテナを管理するための名前です。

build: .は同じフォルダにあるDockerfileをビルドする、という意味です。先程作成したDockerfileがビルドされます。

volumesはマウントするフォルダの設定です。$PWD:/tmp/workingとすることにより、現在のディレクトリを、Dockerコンテナ上の/tmp/workingに紐付けることができます。これにより、Dockerコンテナの中からでも現在のディレクトリ(とその中のデータとか)にアクセスすることができます。

working_dirはコンテナ内の初期ディレクトリの設定です。今回は/tmp/workingに設定しています。

portsはポートフォワードの設定です。手元のポート:コンテナのポートの形式で書きます。

ふだんJupyter Notebookを起動するとき、localhost:8888からアクセスすることになるかと思います。今回はDockerコンテナ上でJupyterを起動するため、Dockerコンテナ内のlocalhost:8888にアクセスしないとJupyterを見ることができません。しかし、portsの設定をしておくことで、Dockerコンテナ内の localhost:8888を手元のlocalhost:8888につなぐことができます。

commandはDockerコンテナを起動したときに走るコマンドの内容です。今回はJupyter Notebookを起動しています。

いくつか見慣れないオプションがついているかと思います。

--ip=0.0.0.0は起動したJupyter Notebookにコンテナ外からアクセスできるようにするオプションです。

--allow-rootはルートでの実行を許可するオプションです。Dockerコンテナはデフォルトでルート権限で起動するので、このオプションが必要です。

--no-browserはJupyter Notebook起動時にブラウザを立ち上げないオプションです。

.dockerignore

.dockerignoreは、Dockerfileをビルドする際に無視するファイルを列挙するものです。

今回の場合、データやノートブックはDockerfileのビルドの瞬間は必要ありませんから、.dockerignoreに書いておきましょう。(コンテナとのデータの共有は、ビルドを行った後にマウントで対応します)

input
notebook
script

input/などに大容量のデータが入っていて、かつ.dockerignoreに記述もしなかった場合、Dockerのビルドの際に時間がかかるようになってしまいます。ビルドに必要の無いファイルやフォルダは、.dockerignoreに書くように心がけましょう。

起動してみる

それでは、実際に分析環境を起動してみましょう。

ここで注意ですが、この操作は初回時に限りめちゃめちゃ重いデータをダウンロードする必要があります。実は、Kaggleの公式イメージは便利なライブラリがたくさん入っている反面、とても重いDockerイメージになっています。その容量、なんと18GB!

学校や職場など、WiFi環境が整っている場所で行ってください。間違ってもテザリング中なんかにやらないようにしてくださいね。

$ docker-compose up --build

まず、最初にDockerfileのビルドが走り、その後Dockerコンテナの中でJupyter Notebookが起動されます。最後のJupyter Notebookが起動するところはだいたいこんな感じのログになるかと思います。

jupyter_1  | [I 07:11:32.796 NotebookApp] Writing notebook server cookie secret to /root/.local/share/jupyter/runtime/notebook_cookie_secret
jupyter_1  | [I 07:11:33.065 NotebookApp] JupyterLab extension loaded from /opt/conda/lib/python3.6/site-packages/jupyterlab
jupyter_1  | [I 07:11:33.065 NotebookApp] JupyterLab application directory is /opt/conda/share/jupyter/lab
jupyter_1  | [I 07:11:33.068 NotebookApp] Serving notebooks from local directory: /tmp/working
jupyter_1  | [I 07:11:33.069 NotebookApp] 0 active kernels
jupyter_1  | [I 07:11:33.069 NotebookApp] The Jupyter Notebook is running at:
jupyter_1  | [I 07:11:33.069 NotebookApp] http://33c518cf1679:8888/?token=hogefugatoken
jupyter_1  | [I 07:11:33.069 NotebookApp] Use Control-C to stop this server and shut down all kernels (twice to skip confirmation).
jupyter_1  | [C 07:11:33.069 NotebookApp]
jupyter_1  |
jupyter_1  |     Copy/paste this URL into your browser when you connect for the first time,
jupyter_1  |     to login with a token:
jupyter_1  |         http://33c518cf1679:8888/?token=hogefugatoken

最後のlogin with a tokenの下のURLをコピーし、33c518cf1679の部分をlocalhostに変えてブラウザのURLバーに入れればJupyter Notebookにアクセスできます。

ローカルにあるファイルにもアクセスできることを確認してください。

まとめ

今回はDockerとdocker-composeを使い、Kaggleの環境をベースにした分析環境の立ち上げを行いました。

自分なりにカスタマイズしたDockerfileを持っておけば、新しいデータが来たときも迅速に解析に取り掛かることができます。

みなさんもぜひ試してみてください。

Docker/Kubernetes 実践コンテナ開発入門

Docker/Kubernetes 実践コンテナ開発入門

追記 (2019/5/13)

Twitterでmhiro2さんにいただいた指摘を元に記事を修正しました。ありがとうございました!

Kaggle Eloコンペの振り返り・上位解法まとめ

f:id:ejinote:20190302203900p:plain

KaggleのElo Merchant Category Recommendationコンペに参加しました。

僕は@kasuminkoさん、@hirokasさんとチームを組んで、ラスト2週間だけ参加しました。結果から書くと、Public 221位からのPrivate 2220位という乱高下で儚く散りました。

手元に銀メダル相当のスコアを持っていたにもかかわらず、間違ったサブミットを選んでしまっており、とても悔しいです。

  • コンペの概要
  • 上位解法
    • 1st place solution (30CrMnSiA)
      • 特徴量
      • モデル
    • 5th place solution (Evgeny Patekha)
      • 特徴量
      • モデル
    • 7th place solution (senkin)
      • 特徴量
      • モデル
      • その他
    • 11th place solution (Zakaria EL Mesaoudi)
      • 特徴量
      • モデル
      • その他
    • 16th place solution (nlgn)
      • 特徴量
      • モデル
    • 18th place solution (pocket)
      • 特徴量
      • モデル
    • 上位解法のうち、今後も使えそうなアイディアのメモ
  • 僕たちの解法

コンペの概要

Eloというブラジルのカード会社が主催となり、各ユーザーの購買行動に対応したRoyalty Scoreという値を予測するコンペです。

データとしては各カードの属性を書いたtrain.csv/test.csvと、それぞれのカードに紐付いた購買履歴(transaction)を表すhistorical_transactions.csv/new_merchant_transactions.csvの4ファイルが与えられていました。

タスクの種類は値を当てる回帰で、targetの分布は0を中心とした分布に加え、-33付近に外れ値が存在するというものでした。

f:id:ejinote:20190302203644p:plain

評価指標はRMSEで、外れ値の影響を受けやすい指標です。特に今回は-33.22付近とかなり外れた位置にサンプルが存在するため、外れ値をどう捌くかが大きな論点のひとつでした。

続きを読む

Quoraコンペの振り返りと上位解法まとめ

f:id:ejinote:20190215163908p:plain

KaggleのQuora Insincere Questions Classificationコンペに参加しました。

f:id:ejinote:20190215162126p:plain

結果は121位で、銀メダルでした。これで銀メダルが3枚目です。わーい。

Public Leaderboardで692位と振るわず、コンペのdeadlineが修論発表の当日だったので直前ほとんど何も出来ず、「Quora何もわからなかった」という絶望感でいっぱいでした。

蓋を開けてみたら多くのTeamが消え、あれよあれよという間に銀メダルになりました。

  • タスクの概要
  • 上位陣の手法
    • 1st place solution (Psi)
      • モデル
      • Embedding
      • 閾値
      • バリデーション
    • 2nd place solution (takapt)
    • 3rd place solution (Guanshuo Xu)
    • 7th place solution (yufuin)
    • 10th place solution (tks)
    • 13th place solution (Canming Wang)
    • 15th place solution (Bai)
  • 覚えておきたいテクニック
  • 【おまけ】151th place solution (僕)
  • 終わりに

タスクの概要

QuoraというQ&Aサイト(日本で言うYahoo!知恵袋)に投稿された質問の中で、有害なもの(攻撃的、差別的、卑猥など)を分類するコンペです。

タスクの種類は0 or 1の2値分類で、正例がかなり少ない不均衡なデータセットになっています。

f:id:ejinote:20190215172356p:plain

評価指標はF1 scoreです。確率として出力した予測値をどこから1として、どこから0とするかの閾値決めが大きく最終スコアに影響を与えました

コンペの形式としてはKernel経由の提出のみを受け入れるKernel Onlyコンペかつ、締切後にテストデータを差し替えて順位を決定する2-stage制コンペでした。 1st stageではテストデータが56,000件程度であるのに対し、2nd stageでは376,000件と6倍程度に増えることが予告されていました。

制限時間内に実行出来なかった場合は順位がつかないという厳しいペナルティがあるため、素早く前処理をしたり、上手くモデルを訓練する必要のあるコンペだったと言えるでしょう。

続きを読む

決定木は本当に変換に依存しないのか?

f:id:ejinote:20190110011802j:plain

決定木をベースとしたモデル(RandomForestやXGBoost、LightGBMなど)は正規化などの前処理が必要ないと言われています。

理由として挙げられるのは「決定木は特徴量の大小関係のみに着目しており、値自体には意味がないから」というものです。

先日もkaggler-jaというSlackチャンネルでこの話が出ました(細かく読まなくて大丈夫です)。

 

自分で質問に答えておいて、「本当か?」という疑問が湧いてきたので実験してみることにしました。

結論から言うと、StandardScalerやMinMaxScalerなどの各特徴量について線形な変換に対しては結果は変化しないけど、logをかけたりするとちょっと変わるということが分かりました。

決定木のアルゴリズム(ざっくり)

決定木を作成する際のおおまかな流れは以下のとおりです。

  1. 目的変数(target)をうまく分離できる特徴量と、分割境界を決める
  2. サンプルを基準よりも小さい群と大きい群に分ける
  3. それぞれの群を繰り返し分割していく
続きを読む

2018年、たいへんお世話になりました。

f:id:ejinote:20181231235259j:plain

晦日なので実家に帰っています。あまり酒を嗜んだりはしない両親とシャンパンを飲み、話しているうちにこんな時間になってしまいました。

帰省する前は「親と話すことなんてそんなに多くはないよなぁ」と思っていましたが、24歳になって昔(中高時代など)を振り返っているうちに「あの頃自分はこう思っていたんだ。親のことをこう見ていたんだ」と伝えたくなりました。

父方、母方の祖母がともに今年亡くなったこともあり、残った人生を謳歌しようとしている両親を見ていると考えさせられるものがあります。

さて。本年を振り返ってみますと、良くも悪くも技術漬けの毎日であったように思います。

プログラミング言語としてはPythonC++Golang、Rustあたりをちょいちょいと触っていましたし、要素技術としては機械学習、特にKaggleに多く触れた一年でした(研究をしろ)。

今年のKaggle戦績は銀2枚銅1枚で、それなりにコミットしたコンペでそれなりの結果を残したということに終止するかと思います。来年は金ピカのメダルとお金を狙っていきたいですね。DeNAは強い人ばかりなので、入社前にMasterになりたいという欲求が強いです。

f:id:ejinote:20181231234914p:plain

競技プログラミングは結局水色でフィニッシュしてしまいました。特に終盤でRatedのコンテストに全然出られなかったのが足を引っ張ったかと思います。来年は予定の調整を頑張っていきたいです。

f:id:ejinote:20181231235009p:plain

プログラミングまわりで少し頑張ったのは、9/21から毎日草を生やしたということでしょうか。もちろん途中にはすごくしょうもないコミットが挟まっていて、「手段の目的化」という言葉が頭をよぎらないわけではないですが、どんな日でも毎日PCを開いてコードを書くという習慣付けが行えたことは価値があることだと思っています。

f:id:ejinote:20181231234929p:plain

ブログは自分としてはがんばって書きました。このブログは今年の4月に開設しましたが、現在読者数が47人になり、ちらほらと読んでいただけているようです。切りの良い数字を目指したいので、年末やることがなくてこの記事を読んでいる方は読者登録ボタンをぽちりと押して頂けると幸いです。来年は月1回以上書くという超低い目標を設定してやっていきたいと思います。金メダル取りました!って記事が書けるといいですね。

年末っぽいコンテンツとしては、Amazonの購入履歴などを見ながら今年買って(契約して)よかったものを挙げておきましょう。順番は思いついた順です。

  • Google Home
    • 声で指示するのが予想以上にストレスフリーだった。主に使用している機能はNature Remoと組み合わせた家電の制御と、目覚まし。あとは天気予報。今はAmazonで売ってないっぽいです。
  • Netflix
    • 月額1000円以下で無限にコンテンツがあってしまうので学生にはおすすめできない。
  • アイリスオーヤマのふとん乾燥機
    • 睡眠時のQOLが爆上がりする。知らない間に汗を吸って重たくなっていた布団が軽くなる。最高。
  • ラムダッシュの5枚刃のやつ
    • 今まで3枚刃を使っていたけど完全に時間の無駄だった。仕上がりのクオリティとかかる時間が格段に違う。クリスマスプレゼントでもらった。
  • Dashlane
    • パスワードを覚えたり、手入力したりという手間から開放された。1年契約した。
  • Happy Hacking KeyBoard BT
    • タイプ感がすごく良い。MBPと組み合わせた際の優雅さを犠牲に満足感を得る感じ。彼女にもプレゼントした。
  • ダンボールをお手軽にゴミに出すやつ
    • Amazonを多用することもあって家にダンボールがあふれかえる。紐で縛るのはめちゃめちゃめんどくさいけど、これを使えばちょっとめんどくさいくらいで済む。300円くらいなのでめっちゃおすすめ。
  • Kyash
    • VISAカードとして使えるウォレットアプリ。手軽に送金が行えてユーザー体験が良いです。ユーザーが増えてくれると嬉しいです。

来年の目標の話をしましょう。

すでに上のほうでだいぶ書いてしまっていますが、まずは金メダルがほしいですね。修論をサクッと片付けてコミットしていきたいです。長期的な視野で行くと、キャリアについての考え方を固めていきたいです。自分の好みはユーザーの顔が想像できる所で施策を考えたり分析をしたりするところにあるのですが、それがどういう仕事になり、どういう場所で生きていくのかについて考えていきたいです。

4月から生活が大きく変わるので、早いうちに順応したいものです。いろいろな兼ね合いもあって修論時期と引っ越し先を探す時期と諸々がかぶってなかなか大変になりそうです。

いつもの通り、Amazon欲しいものリストがこちらです。新生活を控えた3月ごろになると生々しいリストになっているかと思いますが、今はかわいいもんです。

ついでにKyashのバーコードがこちらです。

手軽に投銭できるので、ぜひ僕で試してみてください。

それではみなさま、2018年は大変お世話になりました。2019年もどうぞよろしくお願いいたします。

間に合った!

コピペで使える。Kaggleでの実験を効率化する小技まとめ

f:id:ejinote:20181220184224j:plain

この記事はKaggle Advent Calendar 201820日目の記事です。当初の予定ではPLAsTiCCコンペの振り返りをするはずだったのですが、時空の狭間に吸い込まれた結果0サブミットでフィニッシュしてしまいました。何ででしょうね。 そこで、代わりにKaggleで使える便利なスニペットまとめを書くことにします。

ちなみにもうひとネタあったのでいつか書きたいですが、修論があるのでいったん見送り……

  • LINEに通知を送る
  • 処理にかかる時間を計測する
  • LightGBMの学習結果をログに出す
  • Google Spreadsheetに結果を記録する
    • Google Spreadsheet側の設定
    • Pythonからアクセスする
  • Notebook上でライブラリを毎回再読込する
  • DataFrameのメモリを節約する
  • まとめ

LINEに通知を送る

f:id:ejinote:20181220181419p:plain
Home Creditコンペで実際に送っていたログ

「思考は止めてもサーバーは止めるな」とは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)
  1. https://notify-bot.line.me/my/ からパーソナルアクセストークンを発行する
  2. 発行したアクセストークンをYOUR_ACCESS_TOKENに入れる

これでLINE通知を送ることができます。アクセストークンはコンペが終わったら無効化すれば、GitHubにコードをそのままpushしても大丈夫です。

副作用として、計算が終了したことがわかってしまうというものがあります。

続きを読む

2018年11月を振り返ってみる

先月に引き続き11月の振り返りをやっていきます。

やったこと

毎日GitHubに草を生やした

先月に引き続き毎日草を生やせました。

授業のTAの一環で簡単なコンペを開催するツールを作った(公開準備中)

KaggleのInClassコンペのように、限られた人だけが参加するデータ分析コンペを簡単に作れるソフトウェアを書きました。

取り回しに若干バギーなところ(サブミットがAPIを直に叩かないとできないとか)が残っているので、それを直して公開に持っていきたいです。

ロジックはFlaskで書いていて、フロントエンドはBulmaを使っています。

HHKBデビューした

HHKB BTのUS配列無刻印バージョンを買いました。超かっこいいです。

研究室で使っているキーボードはRealforceで、あのぬるぬるした打鍵感も嫌いじゃないですが、HHKBのシュコシュコ感はなかなか癖になります。けっこうおすすめです。

JIS→USの移行は1週間ぐらい大変な苦痛を伴いましたが、今では慣れました。無刻印はそんなに違和感なく使うことができています。 そもそもキーボード見ながらタイピングしているわけじゃないし、他の人にキーボード貸すわけでもないので、違和感なく使えるのは当たり前といえば当たり前です。

キーボード買ったことによる副次的な効果として、他人のキーボードを観察するようになりました。HHKBユーザーを見つけると勝手に親近感を感じています。

(ちなみに彼女へのクリスマスプレゼントもこれになりました)

統計検定

終わった

AppBrewでバイト

コスメの口コミアプリLIPSを開発している株式会社AppBrewで週2〜3くらいのペースで働いています。 データ分析周りのこまごました処理を主に行っています。

AppBrewはデータの民主化をがんばっていて、エンジニアじゃない人もSQLが叩けるのが好印象です。

面白かった記事

買った本

今月はあまり本を買っていないことにいま気がついた。

イノベーション・オブ・ライフ ハーバード・ビジネススクールを巣立つ君たちへ

イノベーション・オブ・ライフ ハーバード・ビジネススクールを巣立つ君たちへ

ちはやふる(40) (BE LOVE KC)

ちはやふる(40) (BE LOVE KC)

12月に向けて

修論をそろそろ書いていかないとまずいような気がしています。