機械学習では多様なデータを組み合わせて使うことがおおく、様々な前処理が必要になってきます。学習・推論プロセスの再現性を高めるためには、手動でのコマンド実行や単純なシェルスクリプトのみで管理するのは相当に厳しくなってきます、、、というのは、この記事をお読みの皆さまの多くが実感されていることと思います。
そこで世の中にはタスクランナーと呼ばれるツールが開発されており、元祖である GNU make から Luigi, Airflow など様々なツールがあります。A curated list of awesome pipeline toolkits には、そのようなツールのリストが作られていて、2019年12月現在、168 もの(!!)ツールが並んでいます。
そんなよくいえば百花繚乱、悪く言えば決定打に欠けるタスクランナーですが、この記事では、私が最近気に入って使っている Snakemake の魅力を伝えたいと思います。
Snakemake の魅力?
一言にまとめるなら、 Python のパワフルさと make のシンプルさを兼ね備えている 点が大きな特徴です。
(一番右が Snakemake のアイコンです)make のシンプルさ
Snakemake は比較的シンプルな記述が可能 です。タスク記述は YAML風(?) で、わりと Makefile に近い、シンプルな書き味に仕上がっています。例えば、こんな感じです。
rule A:
input: "httpd_access.log"
output: "analyzed_log.txt"
shell: "analysis_command {input} > {output}"
けっこう Makefile に似ています、よね!?
同じく Python ベースのタスクランナーである Luigi, AirFlow といったタスクランナーでは、各タスクをクラスとして定義します。入力などをメソッドとして記述するため、少々記述が煩雑になります。
Snakemake のセットアップは Python 環境があれば conda install snakemake
, pip install snakemake
といったコマンド1つで終わりで、こちらもシンプルです。AirFlow のようにサーバを立てたりといった面倒はありません。make のように「最初からシステムに入っている」にはさすがに及びませんが。
ちなみに、上の Snakefile があるフォルダで snakemake を実行すると、 analysis_command httpd_access.log > analyzed_log.txt
が実行されます。analysis_command
を cat
にした場合の実行例は次のようになります。(-p
は実行するコマンドを表示するオプション)
$ snakemake -p
Building DAG of jobs...
Using shell: /bin/bash
Provided cores: 1 (use --cores to define parallelism)
Rules claiming more threads will be scaled down.
Job counts:
count jobs
1 A
1
[Tue Nov 17 21:55:22 2020]
rule A:
input: httpd_access.log
output: analyzed_log.txt
jobid: 0
cat httpd_access.log > analyzed_log.txt
[Tue Nov 17 21:55:22 2020]
Finished job 0.
1 of 1 steps (100%) done
Complete log: /Users/smatsumoto/tmp/snakemake/.snakemake/log/2020-11-17T215522.025457.snakemake.log
Python のパワフルさ
Snakemake は Python ベースのツールで、タスク記述も Python スクリプトに変換して実行しています。つまり、
いざとなったら Python コードでやりたい放題
です。このあたりは最近よくある Python ベースのタスクランナーに共通する利点です。
さきほどの記述にあった shell:
のところを script:
とすると、Python コードを書くこともできます。またルール記述以外の部分は通常の Python コードとして実行されるので、適当な関数を定義して、ルールのなかでそれらの関数を使うこともできます。当然、Python と同じく #
でコメントを書くこともできます。
Python ベースで動作するおかげでいろいろな処理を自然に書くことができます。make では $@
とか $^
とか、いろいろなナゾの記号を使うことで複雑な処理を実現していましたが、Snakemake では
謎の記号を使う必要がありません!!
ちゃんと名前がついた変数になっていることは、個人的にとてもありがたいです。ネットでの検索のしやすさが段違いなので。
特徴的な機能
色々と特徴的な機能がありますが、ここでは下記 2 点を取り上げます。
- ワイルドカード
- リモートファイルのサポート
ワイルドカード
冒頭に挙げた例は httpd_access.log
を入力して analyzed_log.txt
を出力するという単純極まる例でしたが、多くのタスクランナーと同じく Snakemake にも一つのタスク記述で複数ファイルの変換を指定できる書き方があります。それが「ワイルドカード」です。
rule A:
input: "img_{img_num}.jpg"
output: "annot_{img_num}.txt"
shell: "analysis_command {input} > {output}"
この記述の {img_num}
の部分がワイルドカードです。中の文字列は任意のものを指定可能です。このように書いておくと、annot_001.txt
, annot_002.txt
,... の生成には、それぞれ img_001.jpg
, img_002.jpg
,... が必要、ということが指定できます。
また、ワイルドカードは複数書いても構いません。例えば {date}/img_{img_num}.jpg
といったような書き方も OK です。
make のパターンルールに相当する機能ですが、謎の記号みがなくていい感じかなと思うところです。
リモートファイルのサポート
機械学習でもクラウドのストレージを使うことも多くなってきてると思います。Snakemake はそのようなリモートファイルのサポートもあります。
書き方は非常に単純で input: S3.remote('input.txt')
などと書くと、処理の開始前にダウンロードしてくれます。(S3 は、AWS のクラウドストレージサービス)
また、output: S3.remote('output.txt')
とすると、処理成功時にアップロードしてくれます。前述のワイルドカードもサポートされているので、ローカルにファイルがある場合とあまり変わらずにタスクを書くことができます。
そのため、AWS の各種コンピューティングサービス(EC2, ECS, Batch など)で稼働する解析処理も簡単に書くことができます。
どんな用途に向いている?
小規模チームや初期実験・探索フェーズでの利用 にフィットするかと思います。
セットアップは非常に容易ですし、学習コストも高くありません。記述がシンプルなので、色々と試行錯誤もやりやすいと思います。開発者の方はバイオインフォマティクスの研究者の方なこともあってか、研究寄りの状況で使いやすいツールになっているように感じます。
また make を使っているけど、変数が覚えられない という方や、 もう少し柔軟にタスクを書きたい という方にはピッタリだと思います。これは私自身がそのパターンですね Python と親和性の高い、今ドキの使い方に合わせやすい make という感覚で使えると思います。
一方、タスク実行状況の可視化などは強くないので、大規模な ML チームの開発基盤としては若干、力不足かもしれません。可視化ツールも出てきているようですが、Airflow や Luigi と比較できるほど成熟してないかな、という印象です。
実際のつかいこなし
私自身は Snakemake と make の両方を使っています。
make で済むところは make で済ませ、解析フローの本体のように $@
とか変数を使いたくなってくるところには Snakemake を使うことが多いです。だいたいの場合、
- make は 「1 ファイルで複数のスクリプトがおいておける置き場」として利用
- Snakemake で主要な分析処理を実行
という役割分担ですね。
# Makefile - さまざまなコマンドの寄せ集め場所として。
analyze:
snakemake analyze # 分析の中身は Snakemake で管理
clean:
rm -rf work/*
Snakemake の改善希望な点
Snakemake のタスク記述はけっこうシンプルですが、make と比べるとちょっと煩雑です。基本的に Snakemake ではファイル間の依存関係を記述する前提になっているため、タスク同士をつなぐ依存関係を書きたいときは output: touch(some-file)
等と、タスク完了を示す空ファイルを touch する形になります。make とくらべるとちょっと冗長ですね。
あと同じ記述の繰り返しが増えがちです。このあたりは、タスクがちゃんとした Python クラスになってる Luigi や AirFlow だと、似て非なるタスクをループでバンバン生成したりと、よりやりたい放題できるので、YAML 風記述を採用したことのデメリットですね1。ワイルドカードで色々と対応できるっちゃできるのですが、ファイル名に制約があったり、ワイルドカードでどう書くといいか知恵を絞るという少々不毛な作業になることもあったりなかったり。
・・・このあたりを改善できるようなコードをこの年末年始に書きたい、なぁ。
まとめ
以上、Snakemake の紹介でした。凝ったツールに手を出すのは気が重いけど、make を使い続けるのもしんどいなぁ、と思ってるような方はぜひ一度、 Snakemake を試してもらえればと思います。使ってみての疑問点など、コメントに書いてもらえば分かる範囲でお答えできればと思います。
なお、Snakefile の書き方やコマンドラインオプションなどをまとめた Snakemake チートシートという記事も書いているので、興味のある方は参照してもらえればうれしいです。
-
Snakemake は、タスク定義を Python オブジェクトに内部で置き換えてから、実行します。逆に言えばタスク定義の時点では Python オブジェクトではないため、自由に生成したりはできない、ということですね。 ↩