この記事はKaggle Advent Calender 12/04の記事として書かれています。
Kaggleはデータ分析者の登竜門としても使われことが多いかと思います。実際自分もその一人でした。
しかし、実際にデータ分析実務をして気づきます「あれ実務、Kaggleと全然ちがくね」。
何が違うかというと、分析ゴリゴリというよりもデータ生成周りが賢くなること、それが機動的に行えることが大事なのです。
今回はこのことを意識した分析環境づくりを僕おすすめのDVCで作って見たいと思います。データ分析用のツールは他にKedroやMLFlowなどのツールがありますが、Kedroは複雑で覚えること多いですし、MLFlowはPythonのコード修正が必要だったり、そもそも実験の管理自体はハイパラチューニングとしてPython内部でやれる(どっちかというと学習器のつなげ方などのコードレベルでの変化をトラッキングしたい)。
何も題材もないと不便なので、KaggleのTitanicを題材にしてやってみようと思います。
DVCとは?
DVC(Data Version Control)はGit管理下でデータ分析をするためのライブラリです。
主な機能はgitでラージファイルを扱えるようにさせる点にあります。この点はGit LFSにそっくりで、やり方もハッシュをGitに管理させるので同じです。
ただし、DVCはデータ分析用にデータパイプライン形成、実験管理などの機能も持っています。しかも基本はdvc.yamlをいじるだけで、pythonのコード自体はいつもどおりで構わないです(というかPythonである必要もない)。
また、dvcはgitの機能に似せて作っているのでgitに慣れていれば学習コストは低くすみます。
DVC導入
導入方法は簡単です。以下のようにコードを実行するだけ(事前にgitレポジトリは作成してください)
$ pip install dvc
$ dvc init
すると以下のファイル・フォルダが作成されます。
- .dvc: データの実体を保存するためのキャッシュフォルダ
- .dvcignore: dvcで管理したくないファイルがあるときに明示するファイル。.gitignoreに相当します。
増えたファイルはgit管理下に置きます。
$ git add .dvc/* .gitignore
$ git commit -m "dvc init"
データ追加
KaggleからTitanic号のデータ以下の用に追加します。
- data
- raw
- train.csv
- test.csv
- gender_submission.csv
このデータをDVCに追加します。
dvc add data/raw/train.csv data/raw/test.csv data/raw/gender_submission.csv
# フォルダごと追加することも可能
# dvc add data/raw
実行すると[データ名].dvcというファイルと.gitignoreが生成されます。.dvcの方はデータのハッシュのファイルで、gitignoreでデータの実体のファイルをgit管理下から除外してくれています。
$ git add data/raw/.gitignore data/raw/*
$ git commit -m "dvc add raw data"
今回は省きますが、dvcではS3やGCSなどのクラウドのストレージ、共有ドライブなどよく使うファイル共有先をリモートレポジトリとして登録でき、リモートレポジトリにデータをバックアップすることができます。
詳しくは公式サイトを参考にしてください。
データパイプライン構築
DVCを使うとデータパイプラインを簡単に作ることができます。
加工するためのコードにはsrc/proc.py
を使います。
パイプラインの構築方法には
- dvc runを使う
- dvc.yamlの編集
の2つの方法があります。
ここではより汎用的なdvc.yamlを編集する方法をとります。
ここでは学習するための加工済みデータを用意して、学習・推論するパイプラインを作ります。コードにはsrc/proc.py
とsrc/train.py
を使います。
import sys
from pathlib import Path
import pandas as pd
ROOT = Path(__file__).resolve().parent.parent
RAW_DIR = ROOT/'data'/'raw'
OUT_DIR = ROOT/'data'/'out'
if __name__ == '__main__':
train_csv = Path(RAW_DIR/'train.csv')
test_csv = Path(RAW_DIR/'test.csv')
train_df = pd.read_csv(train_csv)
test_df = pd.read_csv(test_csv)
test_df['Survived'] = -1
train_df['is_Test'] = 0
test_df['is_Test'] = 1
concat_df = pd.concat([train_df, test_df])
concat_df = pd.get_dummies(concat_df , dummy_na = False, drop_first = False,columns=['Sex', 'Ticket', 'Cabin', 'Embarked'])
concat_df['Age'] = concat_df['Age'].fillna(train_df['Age'].mean())
concat_df['Fare'] = concat_df['Fare'].fillna(train_df['Fare'].mean())
del concat_df['Name']
concat_df.to_pickle(OUT_DIR/'preprocess.pkl')
import sys
from pathlib import Path
import yaml
import pandas as pd
from sklearn import tree
from sklearn.model_selection import train_test_split
ROOT = Path(__file__).resolve().parent.parent
OUT_DIR = ROOT/'data'/'out'
if __name__ == '__main__':
df = pd.read_pickle(OUT_DIR/'preprocess.pkl')
train_valid = df.query('is_Test == 0').copy()
test = df.query('is_Test == 1').copy()
train, valid = train_test_split(train_valid, test_size=0.2, shuffle=True, random_state=0)
train_X = train.drop(columns=['Survived', 'is_Test'])
train_y = train['Survived']
valid_X = valid.drop(columns=['Survived', 'is_Test'])
valid_y = valid['Survived']
test_X = test.drop(columns=['Survived', 'is_Test'])
clf = tree.DecisionTreeClassifier().fit(train_X, train_y)
accuracy = (clf.predict(valid_X) == valid_y).mean().tolist()
obj = {'accuracy': accuracy}
with open('data/out/summary.yaml', 'w') as f:
yaml.dump(obj, f, encoding='utf-8')
test['Survived'] = clf.predict(test_X)
test[['PassengerId', 'Survived']].to_csv('data/out/gender_submission.csv', index=None)
次に、プロジェクト直下にdvc.yaml
を作ります。
stages:
data_proc:
cmd: python src/proc.py
deps:
- data/raw/test.csv
- data/raw/train.csv
- src/proc.py
outs:
- data/out/preprocess.pkl
train:
cmd: python src/train.py
deps:
- data/out/preprocess.pkl
- src/train.py
outs:
- data/out/gender_submission.csv
metrics:
- data/out/summary.yaml:
cache: false
このyamlの意味は
- data_proc/train: プロセスの名前です。任意の名称をつけることができます
- cmd: このプロセス自体を示すコマンドです。
- deps: プロセスが依存するデータ・ソースコード
- outs: プロセスが生成するデータ
- metics: outs同様プロセスが生成するデータですが、dvc metricsで集計する対象となります。
- cache: dvcで追っかける必要がないくらい容量が小さいファイルであることを明示する
dvc.yamlが書けたら
$ dvc repro
$ git add -A
$ git add commit -m "dvc repro 1"
を実行します。するとcmd
で指定したコードが順に実行されます。
dvcがすごいのはここからで、train.pyを書き換えます。
例えば、シード値を書き換えます。
# 修正前L18:
train, valid = train_test_split(train_valid, test_size=0.2, shuffle=True, random_state=0)
# 修正後:
train, valid = train_test_split(train_valid, test_size=0.2, shuffle=True, random_state=1000)
コードの書き換えが起こったので、データの再出力が必要です。
しかし、書き換えたのはtrain.pyだけなのでプロセスdata_proc
は再度処理される必要はありません。なので、dvc repro
を実行すると
$ dvc repro
'data\raw\test.csv.dvc' didn't change, skipping
'data\raw\train.csv.dvc' didn't change, skipping
Stage 'data_proc' didn't change, skipping
Running stage 'train':
> python src/train.py
Updating lock file 'dvc.lock'
このように、data_proc
は実行されずtrain
だけが実行されます。
また、metricsを指定したので
$ dvc metrics diff
Path Metric HEAD workspace Change
data\out\summary.yaml accuracy 0.86592 0.8324 -0.03352
このように、train.pyを書き換えたことでaccuracyがどのように変わったかが確認できます。
まとめ
- dvcの導入は
pip install dvc
で簡単 -
dvc.yaml
を書くだけでデータプロセスが作れる -
dvc repro
を唱えるだけで、よしなにデータ再生成ができる