🔖

dbtのパイプライン上でBQMLを使いたいんじゃ!!

2022/12/03に公開

この記事はdbt Advent Calendar 2022の12月3日の記事です!
タイトルはぜひテレビ千鳥の大悟を想像しながら読んでください

概要

  • 社内で用いるための学習モデルを簡単にバッチ実行するにはどうしたらいいかな?というのを調査したものになります

    • プロダクトで使うための学習モデルは想定していません
  • dbtのパイプラインと共にBQMLを使っていくにはどのように作っていくといいか実験しました

解決手法と選定理由

  • 所属するチーム、組織のケイパビリティ的にBQMLがフィットと判断しました
    • 特徴量の生成をdbtで管理できる
    • 「Vertex AIでモデルをホストして〜」みたいなのは現状のチームにはtoo much
    • 使うモデルが複雑なものではない(今の所せいぜいGBDT)
    • DSとしてリソースを確保できるのか現状微妙なので、BQML経由でAuto ML tablesを使う余地がある

以上の理由よりBQMLを選定しました

技術検証のゴール

  • データ基盤のテーブルと、特徴量やモデル用のテーブルを別のデータセットで管理できるようにする
    • これをdbtのワークフローの中で解決できるのか
  • dbtのワークフローの中でBQML実行できるようにする
    • 通常viewやtableを作る前提なのでCREATE TABLE などのDDLがついてコンパイルされてしまう
    • suffixでモデルの学習日とか入れられると後々の検証に役立ちそう

ゴールイメージ

(実験場であるkawaii-dbtで実験してます)


  • modelsの下にDWH用のディレクトリとML用のディレクトリを分ける
  • suffixでディレクトリ名がつく
    • 指定すればsuffixがつくし、指定しなければつかない(kawaii-dbtのままがいいということもできる)
  • モデルのsuffixに実行日時をつける

ゴールとしないこと

  • 学習モデルやハイパーパラメータの検討

結果

データセットも分けられるし、dbtでBQMLも実行できる


喜ぶ大悟

データ基盤のテーブルと、特徴量やモデル用のテーブルを別のデータセットで管理できるか

  • これはもう少し詳細に考えると「dbtのワークフローの中でdatasetを分けて出力できるか」になります

方法

公式のドキュメントにもあるCustom Schemaを使えば実現できます
https://docs.getdbt.com/docs/build/custom-schemas

dbt_project.ymlの修正

  • dbt_project.ymlmodels プロパティを追加する
name: kawaii_dbt
version: '1.0.0'
config-version: 2

profile: 'kawaii-dbt'

model-paths: ["models"]
analysis-paths: ["analyses"]
test-paths: ["tests"]
seed-paths: ["seeds"]
macro-paths: ["macros"]
snapshot-paths: ["snapshots"]

target-path: "target"
clean-targets:
  - "target"
  - "dbt_packages"

models:
  kawaii_dbt:
    dwh:
      +schema: dwh
      + tags:
        - "dwh"
    ml:
      +schema: ml
      +tags:
        - "ml"
  • kawaii_dbt の部分はname プロパティになります(必須です)、その下のdwhml がディレクトリ構成で言うmodels/の子ディレクトリに当たる
    • なんかうまくパースできない時のトラブルシューティングはこちら

https://discourse.getdbt.com/t/faq-i-got-an-unused-model-configurations-error-message-what-does-this-mean/112

  • tags プロパティはDWHの部分とMLの部分を分けて実行したい時ように追加してるだけで、ここの文脈ではなくても大丈夫です
    • でもdbt run -s tag:ml でML部分だけ実行とかしたくなりそうじゃない?

custom schemaをオーバーライドするmacroの用意

  • schema ってのはBigQueryではdatasetのことです
  • generate_schema_name.sql というファイル名でmacrosディレクトリ配下にmacroを用意する必要があります(おそらくファイル名は重要ではなく、macro名が重要)
  • どこのschemaに吐き出すのかというのを設定するところをオーバーライドするmacroという理解です
{% macro generate_schema_name(custom_schema_name, node) -%}

    {%- set default_schema = target.schema -%}
    {%- if custom_schema_name is none -%}

        {{ default_schema }}

    {%- else -%}

        {{ default_schema }}_{{ custom_schema_name | trim }}

    {%- endif -%}

{%- endmacro %}

以上!

なんかうまくいかんって時に見るところ

[WARNING]: Configuration paths exist in your dbt_project.yml file which do not apply to any resources.
There are 1 unused configuration paths:
- models.kawaii-dbt.ml

ってwarningが出てきてたらdbt_project.yml をうまくパースできていません

dbt_project.yml をいじって、dbt parse を実行してwarningが出なくなるまで頑張りましょう

これ見ながら頑張ってください(再掲)

https://discourse.getdbt.com/t/faq-i-got-an-unused-model-configurations-error-message-what-does-this-mean/112

dbtのワークフローの中でBQML実行できるのか?

  • 結論、できる
  • 日付をsuffixにする部分もできる(ただし癖あり)

方法

dbt_ml packageを使う

dbt - Package hub

  • 開発が盛んというわけでもないのと、メジャーバージョンがリリースされてるわけではないので、使う際の影響は考えた方がいいのかなと思っています

configでalias を渡す

BQMLの部分になります
あとでaliasに応じてモデル名を動的に与えたいのでaliasを指定しています

{{
    config(
        alias='train',
        materialized='model',
        ml_config={
            'model_type': 'LINEAR_REG',
            'max_iterations': 10,
            'input_label_cols': ['duration_minutes']
        }
    )
}}
select *
from {{ ref("feature_label") }}

custom aliasをオーバーライドするmacroの用意

https://docs.getdbt.com/docs/build/custom-aliases

  • alias ってのがBigQueryではtableのことです
  • generate_alias_name.sql というファイル名でmacrosディレクトリ配下にmacroを用意する必要があります
  • schemaと同じでオーバーライド用のmacroです
  • この中で動的にaliasをいじっています
    • aliasがtrainって名前だったらsuffixに今の日時を付与しています
{% macro generate_alias_name(custom_alias_name=none, node=none) -%}

    {%- if custom_alias_name is none -%}

        {{ node.name }}

    {%- elif custom_alias_name == 'train' -%}
        {% set now = modules.pytz.timezone('Asia/Tokyo').localize(modules.datetime.datetime.now()) %}
        {% set now_formatted = now.strftime('%Y%m%d%H%M') %}
        {{ custom_alias_name | trim }}_{{ now_formatted }}
    {%- else -%}

        {{ custom_alias_name | trim }}

    {%- endif -%}

{%- endmacro %}

以上!

困った時に見るとこ

  • 一回実行すると、モデルの依存関係をキャッシュとして保持しているのか(推測)、二回目の実行がこけることがありました
  • そういう時はtargetディレクトリ消して実行するとうまくいくはず
    • それでもダメな時はdbt clean してみてね
  • それでもダメならどこか間違ってるのでtypoとかないか確かめてね

そのほかに考慮しないとなということ

  • 前提、この取り組みはまだ始まる前なので、実際に使ってみたら使いにくい!変える!とかあるかもしれません
  • 特徴量に作成日時のカラムを追加して、何かあった時に調査できるようにしたいと思っています
  • 弊社ではdbt-coreをCloud Buildでキックしていて、上限が24時間なのですが学習をdbtのパイプラインで回す場合は学習時間を把握してタイムアウトを設定する必要があると思います

まとめ

  • BQMLまわりを調査してたけれど、一番大きい収穫はCustom schemaCustom aliasの習得でした
  • 現状dbtのパイプラインにBQMLを載せるにはpackage使うしか方法が見当たらなかったのですが、dbtのワークフローの中でDDLを手軽に実行できるといろんなことができていいなと思いました
  • もし他により良い方法があったらコメントください

Discussion