tkm2261's blog

研究員(OR屋) → データ分析官 → MLエンジニア → ニート → 米国CS PhDが諸々書いてます

PythonでCSVを高速&省メモリに読みたい

今日はPython (Pandas)で高速にCSVを読むことに挑戦したいと思います。

Kaggleに参加するたびに、イライラしていたので各実装の白黒はっきりさせようと思います。

R使いが羨ましいなぁと思う第一位がCSV読込が簡単に並列出来て速いことなので、

なんとかGILのあるPythonでも高速に読み込みたいと思います。

ただ、この検証ではコーディング量が多いものは検証しません。

CSV読込は頻出するので、フットワークの軽さが重要です。(オレオレライブラリ嫌い)

Pickleは早いけど。。。

Kaggleでは、大規模なCSVを高速に読みたいということが頻発します。

シリアライズだけなら、Pickleでのread/writeが爆速なのですが、

  • バイナリなので中身が確認できない
  • 一度メモリに全展開が必要

と欠点もあるため、なるべくCSVで管理したいところです。

見てみるとこんな感じ。

Line #    Mem usage    Increment   Line Contents
================================================
     8   88.887 MiB    0.000 MiB   @profile
     9                             def main():
    10 4116.051 MiB 4027.164 MiB       df = pd.read_csv('train_data.csv')  # .values
    11 4116.051 MiB    0.000 MiB       with open('aaa', 'wb') as f:
    12 4116.051 MiB    0.000 MiB           pickle.dump(df, f, -1)
    13 4116.051 MiB    0.000 MiB       with open('aaa', 'rb') as f:
    14 4116.051 MiB    0.000 MiB           df = pickle.load(f)

pickleはもっとメモリ喰うと思っていたので、GNU版のtimeで調べると

$ /usr/bin/time -f "%M KB" python test.py
27827088 KB

約28GB… いやいやメモリ使いすぎでしょう。。。

個人的な感覚はオブジェクトサイズの2倍程度だと思ってたので驚きです。こいつはどうにかしないといけません。

皆様、memory_profilerは関数内部で消費されたメモリは表示されませんので気をつけて!

(実は最後の最後で気づいて全実験をやり直した。マジでmemory_profilerさん勘弁して下さい。)

結論はDask使おう!

今回の計測結果がこちらです。
読み込み後のオブジェクトが約2GBぐらいなのでそれを参考に見て下さい。

カッコが付いているところは計測したけど子プロセス分入ってるか自信がない箇所です。

実装 実行時間 memory_profile最大使用メモリ GNU版timeの最大使用メモリ
pandas.read_csv() 39.2s 4.0GB 6.3GB
pandas.read_csv() (dtype指定) 37.2s 2.0GB 3.2GB
pandas.read_csv() (gzip圧縮) 48.5s 4.0GB 6.3GB
numpy.genfromtxt() 4min 41s timeout 22.8 GB
pandas.read_csv() (chunksize指定 + multiprocessing) 43.6s 計測不可 (4.6GB)
pandas.read_csv() (chunksize指定) 40.6s 2.5GB 4.4GB
pandas.read_csv() (chunksize指定+GC) 40.8s 2.5GB 4.4GB
dask.dataframe.read_csv() (dask.multiprocessing.get) 16.3s 計測不可 (4.2GB)
ファイル分割(10ファイル・並列なし) 49.1s 2.3GB 4.4GB
ファイル分割(10ファイル・並列) 8.89s 計測不可 (4.2GB)
pickle 2.18s 2.1GB 28GB

計測した結果から言うと、daskを使うのが速くて実装が楽です! 、デフォルトread_csvはかなりメモリを使用します!

ファイル分割が一番効くのはそうなんですが、↑の結果は行での分割なのでKaggleとかの特徴量で管理したいときには微妙なんですよね。
daskは複数ファイル読込も対応しているので、2行で書けるdaskの記法に統一してしまっても良さげです。

16コアで2倍程度の高速化というのはちょっと物足りない気もしますが、1ファイルなのでしょうがないんですかね

列で分割した場合の結果も後述しますが、pd.mergeではcopy=Falseを指定した方が良いです。

検証環境

  • Ubuntu 16.04
  • c4.4xlarge (16 vCPU)
  • SSDストレージ
  • pandas==0.20.3
  • dask==0.15.0

当初はBash on Windows環境でやってたのですが、こちらはマルチスレッドでの挙動が怪しくてボツにしました。

データ

  • 1,000,000行, 263列
  • 2.6GB (GZIP圧縮後0.54GB)

速度検証

pandas.read_csv()

まずは王道のチェック

In [2]: %time tmp = pd.read_csv('train_data.csv')
CPU times: user 37.1 s, sys: 2.06 s, total: 39.2 s
Wall time: 39.2 s

この40秒ベンチマーク

pandas.read_csv() (dtype指定)

dtypeを指定してみた。

In [4]: %time tmp = pd.read_csv('train_data.csv', dtype=np.float32)
CPU times: user 36.7 s, sys: 564 ms, total: 37.2 s
Wall time: 37.2 s

若干速くなった。

pandas.read_csv() (gzip圧縮)

SSDのIO性能がボトルネックの可能性があるのでgzip圧縮もテスト

In [2]: %time tmp = pd.read_csv('train_data.csv.gz')
CPU times: user 47.5 s, sys: 984 ms, total: 48.5 s
Wall time: 48.5 s

gzipは速くはならないみたい

numpy.genfromtxt()

numpy.loadtxtは欠損値があると読み込めないので、こちらを検証

In [3]: %time tmp = np.genfromtxt('train_data.csv', delimiter=',', skip_header=1)
CPU times: user 4min 33s, sys: 7.98 s, total: 4min 41s
Wall time: 4min 41s

4分ぐらいかかっているので、pandasの実装の方が良いみたいですね。

pandas.read_csv() (chunksize指定 + multiprocessing)

実装

import numpy as np
import pandas as pd
from multiprocessing import Pool

def get_df(df):
    return df

p = Pool()
tmp = pd.concat(list(p.map(get_df, pd.read_csv('train_data.csv', chunksize=100000))), ignore_index=True)
p.close()
p.join()

結果

CPU times: user 39.4 s, sys: 3.9 s, total: 43.3 s
Wall time: 43.6 s

CPUもちゃんと全部動いておらず、この実装は駄目みたいですね。

やるならファイルを分割して並列読込が良さそう。

pandas.read_csv() (chunksize指定)

メモリが少ないときは使う実装なので一応確認

実装

import numpy as np
import pandas as pd
from multiprocessing import Pool

df = None

for tmp in  pd.read_csv('train_data.csv', chunksize=100000):
    if df is None:
        df = tmp
    else:
        df = df.append(tmp, ignore_index=True)

結果

In [1]: %time %run hoge.py
CPU times: user 31.7 s, sys: 8.94 s, total: 40.6 s
Wall time: 40.6 s

さほど時間が変わらず読み込めています。

pandas.read_csv() (chunksize指定 + GC)

さらにメモリに気を使ってGC入れると

import gc
import numpy as np
import pandas as pd
from multiprocessing import Pool

df = None

for tmp in  pd.read_csv('train_data.csv', chunksize=100000):
    if df is None:
        df = tmp
    else:
        df = df.append(tmp, ignore_index=True)
    del tmp
    gc.collect()
In [1]: %time %run test.py
CPU times: user 37.5 s, sys: 3.32 s, total: 40.8 s
Wall time: 40.8 s

ほぼ変わらない時間で実行出来ています。誤差レベルなのでメモリ無い時はGC入れときましょう

dask.dataframe.read_csv()

並列といえばdask! ということで試します。

import dask.dataframe as ddf
import dask.multiprocessing

df = ddf.read_csv('train_data.csv')
df = df.compute(get=dask.multiprocessing.get)
In [1]: %time %run test.py
CPU times: user 7.16 s, sys: 7.49 s, total: 14.6 s
Wall time: 16.3 s

CPUが全部動いて、倍ぐらいは速くなりました!

ただ16コアで倍なので、もうすこし贅沢を言いたい気もする

あとCHUNKSIZEを指定すると、上手く並列しなかったのでデフォルトで良さげです。

ファイル分割

100,000行ごとに10ファイルに分割して読込してみる

import glob
import numpy as np
import pandas as pd
from multiprocessing import Pool

def get_df(path):
    return pd.read_csv(path)

p = Pool()
tmp = pd.concat(p.map(get_df, glob.glob('tmp/*csv')), ignore_index=True)
p.close()
p.join()
In [1]: %time %run test.py
CPU times: user 1.73 s, sys: 1.92 s, total: 3.66 s
Wall time: 8.89 s

流石に速い。10倍ぐらい速くなってますね。

あと、本来ならコア数の倍数で分割するのが良さげです

メモリ使用量

ただ速ければいいというのは不公平なのでメモリも測ります。

特にpd.concatの瞬間に凄いメモリを使ってる可能性があります。

memory_profilerで見てみます

pandas.read_csv()

Line #    Mem usage    Increment   Line Contents
================================================
     8   88.660 MiB    0.000 MiB   @profile
     9                             def main():
    10 4115.828 MiB 4027.168 MiB       tmp = pd.read_csv('train_data.csv')

普通にread_csvすると4GBほど使用

GNU版time計測だと、6.3GB

pandas.read_csv() (dtype指定)

Line #    Mem usage    Increment   Line Contents
================================================
     8   88.680 MiB    0.000 MiB   @profile
     9                             def main():
    10 2109.586 MiB 2020.906 MiB       tmp = pd.read_csv('train_data.csv', dtype=np.float32)

64bit -> 32bitで2GBになり半分に減少

GNU版time計測だと3.2GB 同様ですね。

pandas.read_csv() (chunksize指定)

Line #    Mem usage    Increment   Line Contents
================================================
    11   88.633 MiB    0.000 MiB   @profile
    12                             def main():
    13   88.633 MiB    0.000 MiB       df = None
    14 2497.973 MiB 2409.340 MiB       for tmp in pd.read_csv('train_data.csv', chunksize=100000):
    15 2297.340 MiB -200.633 MiB           if df is None:
    16  492.543 MiB -1804.797 MiB               df = tmp
    17                                     else:
    18 2497.992 MiB 2005.449 MiB               df = df.append(tmp, ignore_index=True)

確かにchunkで読み込むとメモリ使用が減少してますね。

GNU版time計測だと4.4GB 一応素のread_csvよりは減っています。

pandas.read_csv() (chunksize指定 + GC)

Line #    Mem usage    Increment   Line Contents
================================================
    11   89.121 MiB    0.000 MiB   @profile
    12                             def main():
    13   89.121 MiB    0.000 MiB       df = None
    14 2300.328 MiB 2211.207 MiB       for tmp in pd.read_csv('train_data.csv', chunksize=100000):
    15 2300.328 MiB    0.000 MiB           if df is None:
    16  493.230 MiB -1807.098 MiB               df = tmp
    17                                     else:
    18 2500.980 MiB 2007.750 MiB               df = df.append(tmp, ignore_index=True)
    19 2300.328 MiB -200.652 MiB           del tmp
    20 2300.328 MiB    0.000 MiB           gc.collect()

GNU版time計測も4.4GBと同じ。

今回の例ではGCは余り効いてないでが、処理が長くて複雑な参照があると効くことも

ファイル分割

Line #    Mem usage    Increment   Line Contents
================================================
     8   88.434 MiB    0.000 MiB   @profile
     9                             def main():
    10 2326.051 MiB 2237.617 MiB       tmp = pd.concat(map(get_df, glob.glob('tmp/*csv')), ignore_index=True)

concatなのにメモリ使用が少ない。。。

GNU版time計測だと4.4GB

mapがイテレータになってる効果があるかもなのでlistにキャストしてみます。

Line #    Mem usage    Increment   Line Contents
================================================
    11   88.668 MiB    0.000 MiB   @profile
    12                             def main():
    13 2325.824 MiB 2237.156 MiB       tmp = list(map(get_df, glob.glob('tmp/*csv')))
    14 2325.797 MiB   -0.027 MiB       tmp = pd.concat(tmp, ignore_index=True)

GNU版time計測だと4.4GB

listにキャストしても変わらないのでイテレータで渡す意味はなさそうです。

read_csvよりも複数ファイル読込の方がメモリ使用が少ないのは意外でした。read_csvはかなりバッファを持ってメモリ確保しているっぽいですね。

複数ファイル読込は、速度に加えてメモリ側にも恩恵がありそうです。

気になったので、copy=Falseも見てみます。

Line #    Mem usage    Increment   Line Contents
================================================
     8   88.543 MiB    0.000 MiB   @profile
     9                             def main():
    10 2327.086 MiB 2238.543 MiB       tmp = pd.concat(map(get_df, glob.glob('tmp/*csv')), ignore_index=True, copy=False)

GNU版time計測も4.4GB

copy=Falseってあんま意味ないんかい。。。

列分割でのメモリ使用量

上の例では行でファイル分割しましたが、Kaggleでは特徴の出し入れを頻繁に行うので、

列分割での管理の方が理想的です。

以前こんなツイートをしたので合わせて検証します。

ファイルを前100列と残り163列に分割して検証します。

まずはpandas.merge()

Line #    Mem usage    Increment   Line Contents
================================================
     5   88.871 MiB    0.000 MiB   @profile
     6                             def main():
     7 1613.852 MiB 1524.980 MiB       df1 = pd.read_csv('train_data_first.csv')
     8 3381.113 MiB 1767.262 MiB       df2 = pd.read_csv('train_data_last.csv')
     9
    10 5387.531 MiB 2006.418 MiB       df = pd.merge(df1, df2, left_index=True, right_index=True) # パターン1
    11 5387.777 MiB    0.246 MiB       df = df1.merge(df2, left_index=True, right_index=True)) # パターン2
    12 3381.242 MiB -2006.535 MiB      df = pd.merge(df1, df2, left_index=True, right_index=True, copy=False) # パターン3

GNU版time計測は

  • パターン1: 5.5GB
  • パターン2: 5.5GB
  • パターン3: 4.7GB

pd.merge()とpd.DataFrame.merge()ではメモリ使用量に差はないようです。

mergeの場合はcopy=Falseが有効です。参照で問題ない場合は指定しましょう。

次に、numpy.concatenate

Line #    Mem usage    Increment   Line Contents
================================================
     6   88.859 MiB    0.000 MiB   @profile
     7                             def main():
     8 1613.848 MiB 1524.988 MiB       df1 = pd.read_csv('train_data_first.csv').values
     9 3381.113 MiB 1767.266 MiB       df2 = pd.read_csv('train_data_last.csv').values
    10
    11 5387.574 MiB 2006.461 MiB       df = np.concatenate([df1, df2], axis=1) # パターン1
    12 5387.645 MiB    0.070 MiB       df = np.hstack([df1, df2]) # パターン2
    13 5387.645 MiB    0.000 MiB       df = np.c_[df1, df2] # パターン3

GNU版time計測は全て同じでした。

  • パターン1: 5.5GB
  • パターン2: 5.5GB
  • パターン3: 5.5GB

copy指定しない場合は、numpy, pandasともに同じメモリ使用量でした。

私のツイートの数倍というのの再現は出来ませんでしたが、copy=Falseが良く効く場合だったのかもしれません。

KagglerのためのGit入門

お久しぶりです。絶賛ニートを楽しんでるtkm2261です。

今日はTwitterで意見が諸々散見されたので、私のKaggleでのgit活用法を共有しようと思います。

Gitの独学はハードルが高いですよね。。。

gitの運用は開発プロセスと密接なのであまり外に出てこなかったり、
実際に自分でやらないと覚えない系なので独学は結構難しいと思ってます。
(同じ理由で、テストコード系も広まらなかったり)

私も学生時代はSVNに馴染めなくて、業務でプロダクト開発して初めてバージョン管理が身についたタイプです。
最初はSourceTreeで慣れ親しんで、gitコマンドという流れを辿りました。いきなりGitコマンドのハードルの高さは理解してるつもりです。

www.sourcetreeapp.com

とはいえ、慣れるとこんなに便利なものはないので、出来る限り平易に語りたいと思います。

Kaggle利用特化(一人開発用)の最低限な要素を詰めましたので参考になれば幸いです。

変なとこあればTwitterまで

KaggleでのGitの有用性

個人的には複数環境で並行でタスクをこなすので、無いとやってられないです。

AWSのスポットインスタンスGCPマシン($300クーポンで確保)、自宅マシンの3マシンは大体いつも管理してます。

ある環境で上手く行ったものを、うまく行った箇所だけ全体に共有するのはGitが最適です。

勿論ですが作業ログが残るので結果の再現性に絶大な威力を発揮します。

フォルダ管理との長所短所

もう一つの有力な選択肢として、作業フォルダ丸ごとS3とかに定期的に上げてしまう方法が有力かと思います。

結論からいうと、たまにフォルダをs3に保存、普段はGitが最強かと思います。

長所

  • 作業が楽
  • ある地点に戻るのが楽
  • データも一緒に保存するので、再現性が高い

短所

  • サイズが大きいと時間がかかる
  • 結局しっちゃかめっちゃかになる
  • 別環境との同期がしづらい。
  • 差分管理がしづらい

Git入門したてだと『Gitで全部やるぞー』と気負いがちですが、組み合わせて一番楽な方法を取りましょう。

とりあえずこれだけ覚えておけばOK

GitやGithubは複数人開発するために様々な機能がありますが、Kaggle用に作業ログと再現性のためなら至ってシンプルに使えます。

  • 1: git add (gitにファイルを追加)
    • git add *.py *.R
    • git add -u
  • 2: git status (現在の状態確認)
  • 3: git commit -m “なにかコメント” (addしたファイルの変更確定)
  • 4: git push origin master (commitをアップロード)
  • 5: git pull (リモートの変更を手元に反映)
  • 6: git checkout (ブランチ切替、ファイル変更取消)
    • git checkout ファイル名
    • git checkout ブランチ名orハッシュ値
    • git checkout --ours ファイル名
  • 7: git reset --hard (変更全取消)

これだけで問題ないでしょう。応用編でstashとbranchは触れますが使わなくてもいけます。

各コマンドの詳細は後述

Git初心者最大の問題: どうやってある時点に戻るの?

ここでgit revert使いたい方もいるかもしれませんが、revertは色々面倒なことになるので止めましょう。

筆者推奨の戻し方は超原始的に、戻したいファイルに上書くだけです。

  • 別のフォルダに、新たにクローン
  • git checkout ハッシュ値 で戻したいところのコミットに戻る
  • このフォルダから戻したいファイルを、元のフォルダにコピー

あとはコミットコメントに戻した旨を書いておけば十分でしょう

Git初心者の気になりポイント

  • Q: プルリクとか言うもの使った方が良いんじゃないの?
    • A: 必要ありません。あれは複数人開発用&公開済みサービス用なのでKaggleでは使いません。業務でも公開前の最初期はプルリクでなくmaster pushでやる事も多いです。
  • Q: コンフリクト怖い
    • A: git checkout --ours ファイル名 を覚えるだけで大丈夫です。(後述)
  • Q: ブランチってやつ切るんでしょ?
    • A: 必要ありません。実験的なコードを残す方法として応用編で触れますが、実験的なコードはファイル名別にしてmaster pushで良いかと
  • Q: master push怖い
    • A: 自分しか使わないので存分に壊しましょう。最悪リポジトリ作り直せばいいですし
  • Q: いまどういう状態かわからなくなった
    • A: git statusでみれます。ただ初心者にはわかりづらいので慣れるまではSorceTreeを使いましょう

.gitignoreの運用

Git初心者ありがちなミスとして、巨大ファイルをgitに入れてしまうミスがあります。

これやると巨大ファイルの差分管理してしまい、元のファイル以上にgitを圧迫して、フォルダ管理より取り回しが悪いものになってしまいます

間違って入れたら、git rm --cached ファイル名 で消すしか無いですが、その前に.gitignoreを書いて未然に防ぎましょう。

data/
input/
*.csv
*.gz
*.pkl
*~
*.swp

Kagglerなら公式カーネル準拠でinput/以下にデータを入れることが多いと思いますので指定しておくと安全です。
あと私は加工データをdata/以下によく入れてるので追加してあります。

各コマンド詳細

あとは補足内容なので、分からない箇所だけ参考にしてください。

1. git add (gitにファイルを追加)

ファイルをgit管理以下に追加するコマンドです。

  • git add *.py *.R (PythonとかRのコードを追加)
  • git add -u (変更のあった管理下に入ってるファイルを追加)

これで十分かと思います。git add -Aとかは.gitignoreがしっかり指定してあればOKですが、巨大ファイルを入れる危険性があるので微妙です。

2. git status (現在の状態確認)

いまのGitの状態を確認するコマンド。全部addが終わった後や、コミット前には必ず見る習慣を付けましょう。

3. git commit -m “なにかコメント” (addしたファイルの変更確定)

コメントをつけてaddしたファイルの変更を確定します。

コメントは適当でよいですが、作業が思い出せるレベルではあったほうが良いです。

Kagglerの場合は作業でわけられない時があると思うので、日付だけとかでも良いです

理想的には毎submit後にはcommitしてLBスコアとかをコメントに入れておくと良いです

4. git push origin master (commitをアップロード)

リモートにcommit内容をアップロードします。他環境から先にpushがあるとrejectされますが、その時は先にpullします。

Kaggleの場合はアップロードためらう要素はないので、commitしたらpushする癖を付けましょう。

5. git pull (リモートの変更を手元に反映)

リモート内容を手元に反映します。ブランチを指定した方が丁寧ですが、Kaggle用途なら省略可です。

他環境で先にpushがあると失敗します。手元の変更と被らなければpullするだけで解決しますが、失敗する場合は下記で対処。

  • リモートのファイルを正としたい場合 ・・・ git checkout ファイル名 で戻してpullしましょう。手元の変更が全て要らないならgit reset --hard
  • 手元のファイルを正としたい場合 ・・・ 手元ファイルを何処かにコピーして、↑でリモートのファイルを正とした後、コピーで戻しましょう
  • 両方の変更を保持したい場合 ・・・ 対象ファイルをcommitしてpullしましょう。コンフリクトが起きる可能性がありますがgit checkout --ours ファイル名 を使いましょう。(後述)

6. git checkout (ブランチ切替、ファイル変更取消)

checkoutの用途としては、ブランチの切り替えとファイルの変更を取り消すの2つがありますが、

ここではブランチは使わないので、ファイルの変更を前のcommitに戻すコマンドとしてgit checkout ファイル名 を覚えておきましょう。

これは消してしまったファイルにも有効で、ミスってrmしてしまったらcheckoutでザオリクしましょう。

git checkout --ours ファイル名

度々登場しました、コンフリクトを解消するコマンドです。

勿論分かる人はファイルを直接編集してコンフリクト解消しても良いですが、Kagglerは手元のファイルを正としたい場合がほとんどと思います。

『--ours』の通り、手元環境を正としてコンフリクトを解決してくれます。一応、リモートを正とするgit checkout --theirsもあります。

7. git reset --hard (変更全取消)

通称バルスコマンド。手元の変更を全て破棄して直前commitの状態に戻します。

git管理下のファイルだけなので、作ったデータファイルとかは消えません。

応用編

git diff (ファイル差分確認)

前のcommitとの変更差分が見れます。ファイル名を指定するとそのファイルのdiffだけ見れます。

学習実行前のチェックに流すと良いです。

git rm --cached ファイル名 (git管理から除外)

ファイルをGit管理下から外します。--cachedが無いとファイル自体も消えるので注意

git log (コミット履歴確認)

コミット履歴を見れます。checkoutであるcommitに戻るときのハッシュ値を調べるのに重宝します。

git branch ブランチ名 (ブランチ作成)

ブランチを作ります。Kaggleではブランチで残したいケースは少ないと思いますが、Gitのメイン機能なので覚えて起きましょう。

作ったあとcheckoutで切り替えるのを忘れずに。

用途としては、

  • 実験的なコード書いて、ファイル別にするのも面倒だからブランチとして残す
  • configファイルを別にして、CPU用、並列実行用、GPU用に分ける

とか考えられます。

特に後者はdevelopブランチの変更をmaster, parallel, gpuブランチに適用みたいな、
hotfixをmasterとreleaseに適用する的な業務レベルのgit使いが要求されるのでKaggleではそこまでやらないかと

そこまで出来るgit使いはご自由どうぞ

git stash (手元変更の一時保存)

手元の変更を一時保存して、git stash popで元に戻せるコマンドです。

変更がかぶると、git stash popで戻せなくなることがあるので、これを使うならbranchを切りましょう

変更前のコードを見たいならgthubかBitbucketをブラウザから見ましょう

トラブルシューティング

何かあれば随時増やします

間違ってaddしてしまった

git reset --sorf HEAD ファイル名で外せます。

ただ新ファイルとかの場合は少し違うので、git statusすると外し方書いてあるので読みましょう。

間違ってcommitしてしまった。

git reset –soft HEAD^ で戻ります。

ファイルのadd忘れや、コメント直したいだけの場合はgit commit –amendで変更できます。

ただし、これはPush前の話なのでPushしちゃったら諦めて、直したコミットを更に上げましょう

commitログがちょっと汚くなるレベルなのでKaggleでは気にする事ないです。

gitってどのサービス使えば良いの?

サービス使わずに自分でホストしても良いですが。スポットインスタンスから見れるように

githubやBitbucketを使う方が良いでしょう。

特にBitbucketはプライベートリポジトリが無限に持てるのと、SourceTree連携があるので初心者にはBitbucketをオススメします

bitbucket.org

結局お前はどう使ってるの?

大した事はしてません。よくやるのは特徴量作成と学習は別のマシンでやって、上手くいったら各自pushで気が向いたらpullみたいな感じです。

ファイルがそもそも違うので、ほとんどコンフリクトも起こらないので単純です。

パラメタサーチの時は同じファイルいじりますが、変更が大きければcommitしてmergeしますが、コピペで片方に持ってって片方はgit resetで破棄とか普通にやります。

私は業務で慣れることが出来たので大体対処出来ますが、『何か起きたら。。。』といった不安がgit導入の最大障壁ですよね。。。

ニートになりました。

表題のとおり2017/07/01ニートとなりました。

詳細はTwitterに書いたとおりです

退職エントリが長いやつに碌なのがいないと思ってるので、特にこれ以上ありません。

勉強会とか誘って頂けると超助かります。

LightGBMをGPUで速度検証

LightGBMとは

Microsoftが公開しているGradient Boosting Decision Tree(GBDT)の実装です。

github.com

GBDTの実装で一番有名なのはxgboostですが、LightGBMは2016年末に登場してPython対応から一気に普及し始め、 最近のKaggleコンペではxgboostよりも、Winning Solutionで多く見る気がしています。

私もQuoraコンペではお世話になりました

https://www.slideshare.net/tkm2261/quora-76995457

ニートなので金がない

在職中は会社のマシンで回してたりしたので、気軽に32コアぐらい使ってましたが、

ニートで自費で借りると破産しかねないのでGPUで高速にならないかなーと思って検証しました。

環境構築

AWSのp2.xlargeでAnaconda3系使って検証しました。

基本はここに従います。

LightGBM/GPU-Tutorial.md at master · Microsoft/LightGBM · GitHub

マニュアルに書いてないですが

python setup.py install時にgpuオプションが必須です。

これでしばらく詰んだので皆様忘れずに。

github.com

# 諸々入れて
sudo apt-get update
sudo apt-get install --no-install-recommends nvidia-375
sudo apt-get install --no-install-recommends nvidia-opencl-icd-375 nvidia-opencl-dev opencl-headers

# 再起動して
sudo init 6

# また諸々いれて
sudo apt-get install --no-install-recommends git cmake build-essential libboost-dev libboost-system-dev libboost-filesystem-dev

# 本体を入れる
git clone --recursive https://github.com/Microsoft/LightGBM
cd LightGBM
mkdir build ; cd build
cmake -DUSE_GPU=1 .. 
make -j$(nproc)
cd ..

cd python-package/
python setup.py install --gpu # ここのオプション忘れずに!

成功すると、起動時にこんなログが流れます

[LightGBM] [Info] This is the GPU trainer!!
[LightGBM] [Info] Total Bins 50745
[LightGBM] [Info] Number of data: 6767336, number of used features: 283
[LightGBM] [Info] Using requested OpenCL platform 0 device 0
[LightGBM] [Info] Using GPU Device: Tesla K80, Vendor: NVIDIA Corporation
[LightGBM] [Info] Compiling OpenCL Kernel with 256 bins...
[LightGBM] [Info] GPU programs have been built
[LightGBM] [Info] Size of histogram bin entry: 12
[LightGBM] [Info] 248 dense feature groups (1600.55 MB) transfered to GPU in 1.454054 secs. 16 sparse feature groups.

速度検証

同一タスクをCPUと検証

ここを見る限り2~3倍高速化する模様

LightGBM/GPU-Performance.md at master · Microsoft/LightGBM · GitHub

問題サイズ

項目
データサイズ 行数: 8,474,661, 列数: 269
パラメータ {‘max_depth’: 5, ‘learning_rate’: 0.01, ‘min_data_in_leaf’: 10, ‘feature_fraction’: 0.7, ‘metric’: ‘binary_logloss’, ‘bagging_fraction’: 0.9, ‘lambda_l1’: 1, ‘max_bin’: 500, ‘min_split_gain’: 0, ‘device’: ‘gpu’, ‘gpu_platform_id’: 0, ‘gpu_device_id’: 0}

100ラウンド固定の場合

環境 時間 logloss
p2.xlarge (GPU K80) 3m41s 0.256575
p2.xlarge (4 CPU) 7m29s 0.256574
c4.4xlarge (16 CPU) 1m58s 0.256574

4CPUには大体2倍ぐらいの高速化。16 CPUよりは倍ぐらい遅い

max_bins=63に変更した場合

max_binsが小さいとき(推奨63)により速いと書いてるので検証

環境 時間 logloss
p2.xlarge (GPU) 3m28s  0.256575
p2.xlarge (4 CPU) 6m27s 0.256574

今回のデータだと、binサイズを上げてもあんまり関係ないみたいです。

early stoppingの場合

early_stopping_rounds=30にして80%で学習、20%で精度検証

環境 時間 logloss Best Round数
p2.xlarge (GPU K80) 11m27s 0.243065 808
p2.xlarge (4 CPU) 26m53s 0.243078 750
c4.4xlarge (16 CPU) 8m16s 0.243078 750

100ラウンドの時と異なり、c4.4xlargeとくらべてRound数考慮するとほぼ同程度の速度が出ています。

GPUなので最初のオーバーヘッドがあると思われます。

10,000ラウンドぐらいの結果が気になりますが、気が向いたらアップデートします。

結論

GPU(K80)だとc4.4xlarge(16 CPU)程度の性能が出ることがわかりました。

スポットインスタンス価格だとp2.xlargeもc4.4xlargeも$0.2ぐらいで変わりませんが、

家にGTX 1080とかある人は、CPUに投資するよりDNNとかも出来るGPUに資金を集中出来て良さそうです。

さらにGPU拡張はマージされたばかりなので、速度向上が見込めるので将来有望です

Kaggle Quoraコンペ 17位でした

お久しぶりです。最近、色々ありましてブログを再開しようと思います。

基本的に、スライド作って話す事が多いので、まずその辺りのこと書こうと思います。時系列狂うかもですがご容赦を

今月6月に終了した、Quora Question Pairs | Kaggleに参加してソロ17位でした。

あと1個上の順位だったら、ゴールドメダルだったので悔しくて仕方ないですがMasterになる前にもっと精進せいというお告げと信じて頑張ります

手法とかはこちらにまとまっています。

コンペ後に適当な感じで社内勉強会に外部の方も招待したら、かなりの人数が集まり議論の活発な良い勉強会となりました。

個人的にはログ周りの話で盛り上がれたのが良かったです。最良パラメータや手元スコアを残しておくのは長期のコンペを戦う上で必須かと思います。

Kaggle Master達もログの残し方はそれぞれ違っていたので、最良解もそれぞれと言った感じでしょうか

あとこのコンペでKaggle Expertになり順位がつくようになりました。

f:id:tkm2261:20170621162016p:plain

446位なのは直近のコンペが高く評価されるシステムらしく、BoschとQuoraで高くなってるものと思われます。

でも順位よりも、Masterに早くなりたい。。。

Data Science Bowl 2017(肺がん検知)の上位手法を調べた

お久しぶりです。最近、色々ありましてブログを再開しようと思います。

基本的に、スライド作って話す事が多いので、まずその辺りのこと書こうと思います。時系列狂うかもですがご容赦を

今年2017年の4月に1億円コンペで有名になった、Data Science Bowl 2017 | Kaggleにちょっと参加しました。

画像DNNの知見を貯めようと参加しましたが全く歯が立たたず、2ステージ制の1ステージ目で諦めてしまいました。

このままでは流石に悔しかったので上位手法を調べました。

内容は見ていただければと思いますが、印象的だったのは上位手法ほどシンプルだった点です。

やはりシンプルな手法が実務でもコンペでも最も強い気がします。

NIPS・ICDM 2016論文輪読会を主催 & RSVRGの論文を読んだ

お久しぶりです。最近、色々ありましてブログを再開しようと思います。

基本的に、スライド作って話す事が多いので、まずその辺りのこと書こうと思います。時系列狂うかもですがご容赦を

今年2017年2月にNIPS・ICDM 2016論文輪読会を主催しました。会社で参加した人が結構いたので、外部も招いちゃえということでやりました。

内容については、会社ブログを書いたのでそちらを参照してください

www.rco.recruit.co.jp

嬉しいことに多くの発表者に立候補頂き、私の枠埋める用に作ったスライドはお蔵入りしたのですが、

闇に葬り去るにはもったいない気がしたので、公開しました。

内容としては、収束性のオーダーを改善したSVRGをリーマン多様体上に拡張した論文です。

連続最適化の収束性の証明はいつも泣きそうになるんですが、あれがぱっとわかるぐらい頭が良くなりたい。。。