☃️

dbtで引き出すSnowflake MLの真の力

に公開

やること

この記事では、dbtを使ってSnowflake MLを最大限活用する方法をご紹介します。
単なるデモに留まらず、運用を見据えたEnd-to-EndのMLパイプライン構築を目指します。

使用したコードは全てGitHubのリポジトリにて公開しています。
https://github.com/XraySierra0211/tasty_bytes_ML/tree/main

使用するデータ

Snowflakeのデモデータとして提供されている、tasty bytesを使用します。
こちらのチュートリアルからコードを一部拝借し、Snowflakeにdbt projectの環境を作ります。
https://docs.snowflake.com/ja/ja/user-guide/tutorials/dbt-projects-on-snowflake-getting-started-tutorial

tasty bytesは世界各地でキッチンカーを運営するフランチャイズの本部です。
各キッチンカーごとのPOSデータが利用できます。
今回はキッチンカーが属する地域別の商品需要予測をすることにしました。

実行環境のセットアップ

今回は全てのコードを dbt projects on Snowflake (Workspace) で実行します。
まずはsetupに入っているコードを実行していきます。
初めてdbt projectを触る方は、公式のチュートリアルを見ながらやることをお勧めします。
チュートリアルを終えるとtasty bytesのデータがSnowflakeに取り込めているはずです。

運用を見据えたMLパイプラインの構築を目指す、と言っておきながら大変恐縮ですが、今回はACCOUNTADMINを使用して全てのコードを実行しています。

dbtの構成

全体のデータフローは以下のようになっています。
dbt lineage

プロジェクトのディレクトリ構成はこちらです。

tasty_bytes_dbt_ml_demo/
├─ macros
│ ├─ generate_schema_name.sql
│ └─ materialization_snowflake__feature_view.sql
├─ models
│ ├─ features
│ ├─ ml_models
│ └─ staging
├─ setup
│ └─ setup.sql
├─ dbt_project.yml
├─ packages.yml
└─ profiles.yml

簡単にmodels配下を説明しておきます。
stagingは特徴量作成のために、初期の加工を行うスキーマです。
内部でraw_モデルとcurated_モデルに分かれており、curated_モデルでデータが実体化されます。
featuresはSnowflakeの特徴量ストアとなるスキーマです。
Snowflakeの特徴量ストアはDynamic TableかViewかどちらかを選択できますが、本記事ではViewを選択しています。
また詳細は後述しますが、特徴量を作成するためのエンコーダーやスケーラーはfeaturesのモデルレジストリ内に保存します。
そして最後はml_modelsです。ここはMLモデルや予測結果を保存するスキーマです。

MLモデル学習時はtrainタグを使います

dbt build --select tag:train --traget dev

推論を実行したい時はpredictです。

dbt build --select tag:predict --traget dev

それでは詳細の解説に入ります。

RAW

このスキーマはtasty bytesの生のデータが入っています。
いわゆるBronzeレイヤーです。
特筆することは無いのですが、Snowflake公式のtasty bytesデモでは、このレイヤーのテストでこけるようになっているようです。
私のリポジトリでは、こけないように微修正しています。

STAGING

ここは特徴量開発に向けてデータを整形するスキーマです。
いわゆるSilverレイヤーです。
多くの場合でスタースキーマやDataVault2.0など、何かしらの体系立ったデータ加工が実施されます。
今回はMLの機能にスポットを当てたいので、大したことはしていません。

このスキーマ内はraw_とcurated_という接頭語がついたモデルがありますが、raw_はビューで実装しており、curated_はテーブルで実体化しています。実運用ではincrementalとなる想定です。

raw_は列の名寄せや日付のフォーマット統一、データ型の変換などを実施しておきます。
curated_では特徴量作成以外のビジネスロジックを実装します。例えば欠損値埋め、外れ値除去などです。その他、学習データから外さなければならないデータの除去、集計、他データとの結合までを済ませておきます。

curated_モデルから特徴量を作ることになります。
ここを大福帳のような巨大なテーブルにしてしまうと、特徴量の定義が変わる度に再計算が必要になる可能性があるため、効率が悪いです。一方で正規化に拘りすぎても、特徴量ストア内での結合が複雑となり可読性が下がります。
扱うデータに合わせた程良いモデリングが求められるレイヤーです。

今回はキッチンカー別のデータを日付ごと、地域ごとに集計する作業があります。
https://github.com/XraySierra0211/tasty_bytes_ML/blob/main/tasty_bytes_dbt_demo/models/staging/curated_day_order.sql

FEATURES

さて、ここからが本題です。
Snowflakeには特徴量ストアという機能があり、学習データ・推論データで特徴量の定義を一元管理することができます。
特徴量ストアはエンティティという仕組みで特徴量をグルーピングしており、その中にFeature Viewという形でデータを保存します。これらはSnowflakeの一般的なオブジェクトと対応しています。

特徴量ストア スキーマ
エンティティ タグ
Feature View ビュー・動的テーブル

特徴量ストアについては、こちらの記事で詳しく解説しています。
https://zenn.dev/takikomi/articles/cd6c0d26b76f89

dbtを使ってFeature Viewを作成するには、事前にスキーマを特徴量ストアとして初期化することと、エンティティを作成すること、が必要です。
今回はこれらをまとめて作成できるcustom fearurizationを作りました。
特徴量ストアの初期化からFeature Viewの作成までをストアドプロシージャで実行します。使ったプロシージャは同じスキーマに保存されるようにしています。

マクロは長いのでアコーディオンにしています

dbtモデルはSnowpark dataframe又はsqlテキストで実装できます。
https://github.com/XraySierra0211/tasty_bytes_ML/blob/main/tasty_bytes_dbt_demo/models/features/fv_orders_lag.py

このモデルは販売数量のLAG特徴量を作成しています。
一つのモデルで必要なエンティティの作成まで済みます。

ここでさらにひと工夫します。
特徴量の中にはStandardScalerやLabelEncoderを使って変換をしないといけないものがあります。
これらのツールを使う時は、MLモデル学習時にfitして、推論時には同じpythonオブジェクトでtransform
します。推論時に別のオブジェクトでfit→transformしてしまうと、学習時と異なる特徴量が生まれてしまう為、やってはいけません。
したがってFeature Viewは学習時のみ更新し、推論時には動かないようにする必要があります。
dbtは各モデルにタグを付加することができ、実行時に対象のタグがついたモデルのみ実行することが可能です。特徴量ストアのモデルは基本的にこの機能を使って、学習時のみ動くように制御します。

また、スケーラーなどのオブジェクトの保管にはSnowflakeのモデルレジストリを活用することができます。
https://github.com/XraySierra0211/tasty_bytes_ML/blob/main/tasty_bytes_dbt_demo/models/features/fv_location_le.py#L01-L52

モデルレジストリに保管したモデルはSQLで推論(transform)が実行できます。
そのお陰で、簡単にビューの定義に組み込むことができます。
https://github.com/XraySierra0211/tasty_bytes_ML/blob/main/tasty_bytes_dbt_demo/models/features/fv_location_le.py#L55-L83

Feature ViewはSnowsightで確認できます。
fv
スケーラーの作成、保存そして特徴量定義の統一、これらが1つのdbtモデルで完結するのはとてもありがたいことです。
Snowflake MLの強みの1つと言えるでしょう。

ML_MODELS

特徴量の定義が終わったので、MLモデルを作ります。
ここで朗報ですが、株式会社estieさんのリポジトリでMLモデルをモデルレジストリに登録するcustom materializationが公開されています。
こちらを拝借し、dbt projects on Snowflakeで動くように微修正しました。
https://github.com/estie-inc/dbt_snowflake_ml

このmaterializationを使うと、MLモデルをレジストリに保存できます。
学習時のみ動けば良いので、trainタグを付けておきます。
特徴量ストアから学習データを作りますが、ここではSnowflake MLのAPIを使うためdbt上のリネージュが繋がらなくなります。少々不格好ではありますが、dbt.ref()を実行して参照するFeature Viewとのリネージュを繋いでおきます。
これはあくまでdbtのリネージュの話であり、Snowflake内部のリネージュはこんなことをしなくても繋がっているのでご安心ください。

https://github.com/XraySierra0211/tasty_bytes_ML/blob/main/tasty_bytes_dbt_demo/models/ml_model/xgb_model.py#L1-L22

学習データはFEATURESスキーマ内のFeature Viewから取得しますが、generate_training_set()を使うとエンティティに指定したjoin_keysに基いて適切にJoinしてくれます。時系列データの場合はASOF JOINが実行されます。

https://github.com/XraySierra0211/tasty_bytes_ML/blob/main/tasty_bytes_dbt_demo/models/ml_model/xgb_model.py#L25-L53

あとはオーソドックスにxgboostのモデルを書いていきます。
https://github.com/XraySierra0211/tasty_bytes_ML/blob/main/tasty_bytes_dbt_demo/models/ml_model/xgb_model.py#L55-L92

ml
作成したモデルはSnowsightで確認できます。

続いては推論時に稼働するモデルです。
推論時も特徴量ストアからデータを取る為、Pythonモデルを使います。
https://github.com/XraySierra0211/tasty_bytes_ML/blob/main/tasty_bytes_dbt_demo/models/ml_model/order_prediction.py

せっかく予測値を特徴量とセットで保存したのでSHAP値も計算し、説明可能性の向上を図ります。
モデルレジストリに保存したモデルのうち、対象のモデルはexplainメソッドが付与されます。
これを使ってSHAP値を計算し、テーブルに保管しておきましょう。

Snowflake MLのAPIで描画することもできます。
https://qiita.com/takimiko_gohan/items/4eb764695d5604ea3031

感想

dbtを使ってSnowflake MLをフル活用したパイプラインを構築してみました。
Snowflake MLはAPIがとてもシンプルに作られており、SQLとも親和性が良いためdbtを使ったパイプライン構築が可能です。
すでにSnowflake x dbtでデータパイプラインを作っている場合には、他のDWHにデータを持ち出すことなく、同じアーキテクチャの延長線上でML環境を作ることができます。
Snowflake上で全てが完結するというシンプルさを持ちつつ、申し分ない機能を発揮するSnowflake ML、皆さんもぜひ使ってみてください✨

Discussion