TFMAで始めるBigQuery MLのモデル分析
はじめに
BigQuery Advent Calendar 2020 16日目の記事になります。
BigQuery便利ですよね。
ビッグデータの分析を素早く楽にしてくれて、雑なクエリを書いても高速に結果を返してくれる。
機械学習モデルもBigQuery MLを使えばSQLから簡単に作ることもできます。
BigQuery MLの素晴らしいところはBigQueryの中で閉じていることだと思いますが、作成された機械学習モデルを評価する、特に可視化して分析をするといった時にはBigQueryの中に閉じた状態でなんとかするのは難しいのかなと思います。
そこで今回はTensorFlow Model Analysis(以下TFMA)を用いて可視化とモデルの分析を簡単にできる方法をご紹介しようと思います。
今回の検証ではtensorflow_model_analysis==0.25.0を利用しました。
また、モデル自体はBigQuery MLで出生児体重を予測するをほとんどそのまま利用しています。
colabノートブックを作成したので、こちらを参照ください.
TFMAで分析するための手順
大雑把ですが以下の5つの手順でできます。
- BigQuery MLでモデルを訓練をする
 - BigQuery MLの予測結果をpandas.DataFrameにする
 - 
tfma.EvalConfigを作る - 
tfma.analyze_raw_dataで分析結果を作成する - 
tfma.view.render_slicing_metricsで分析結果を可視化する 
実際にTFMAを利用するのは3~5なので、実際にやることはそんなに多くはないです。
簡単、簡単😄
実際にやってみる
1. BigQuery MLでモデルを訓練をする
CREATE MODELを使って、線形回帰モデルを作成します。
チュートリアルだとRAND()関数を使って訓練データをサンプリングしているのですが、再現性があるのかを私が理解できておらずFARM_FINGERPRINTを利用したものに変更しています。
利用したクエリは以下の通りです。project_idを指定していないので、実行するプロジェクトにbqml_tutorialというデータセットがあることを暗黙で仮定しているので注意してください
CREATE MODEL IF NOT EXISTS
  `bqml_tutorial.natality_model` OPTIONS (model_type='linear_reg',
    input_label_cols=['weight_pounds']) AS
SELECT
  weight_pounds,
  is_male,
  gestation_weeks,
  mother_age,
  CAST(mother_race AS string) AS mother_race
FROM
  `bigquery-public-data.samples.natality`
WHERE
  weight_pounds IS NOT NULL
  AND MOD(ABS(farm_fingerprint(FORMAT("%d:%d:%d", year, month, day))),100) <= 1
2. BigQuery MLの予測結果をpandas.DataFrameにする
ここでは、予測値とラベルに加えて分析したい特徴量列を追加したデータフレームを作成します。基本的にはチュートリアルのままですが、カテゴリーとして扱われる列が欲しかったのでstate列を追加しています。(いくつかあるカテゴリー列の中でstateを選択した理由は特にないです。)
Colab(Jupyter Notebook)で実行しているので、%%bigqueryを使ってpandas.DataFrameを作成しました
SELECT
  *
FROM
  ML.PREDICT(MODEL `bqml_tutorial.natality_model`,
    (
    SELECT
      is_male,
      gestation_weeks,
      mother_age,
      state,
      weight_pounds,
      CAST(mother_race AS STRING) AS mother_race
    FROM
      `bigquery-public-data.samples.natality`
    WHERE
      weight_pounds IS NOT NULL
      AND MOD(ABS(farm_fingerprint(FORMAT("%d:%d:%d", year, month, day))),100) = 2 ))
 3. tfma.EvalConfigを作る
ここまででデータとモデルの準備はできたので分析に必要なTFMAの設定値を作成していきましょう。
tfma.EvalConfigを作成するのに必要なものは3つあります。
- モデルの設定値の
tfma.ModelSpec - モデルを評価する評価の設定値の
tfma.MetricsSpec - 分析したい特徴量の設定値の
tfma.SlicingSpec 
少し詳細に見ていきましょう
1. tfma.ModelSpecを作成する
tfma.ModelSpecは端的に言えばモデルの設定値です。
TensorFlowのsignatureやTFXのmodel_validateのためのベースラインかどうかなどのいろんな設定ができます。
今回のケースでは特定のモデルに依存しないModel Agnosticとして扱いたいので、prediction_keyとlabel_keyを設定してあげればtfma.ModelSpecの作成は完了です。
コードは以下の通りです
LABEL = LABEL = 'weight_pounds'
model_specs = [tfma.ModelSpec(prediction_key=f'predicted_{LABEL}', label_key=LABEL)
2. tfma.MetricsSpecを作成する
tfma.MetricsSpecはtfmaで分析をする際に評価する評価値の設定になります。評価値はtf.keras.metrics.*またはtfma.metrics.*にあるものが利用可能です。
また, tf.keras.metrics.Metricsかtfma.metrics.Metricsを継承することで、自作したものも利用することができます。
TensorFlow Model Analysis Metrics and Plotsに記載されてるので、興味がある方はご覧ください。
今回の問題は回帰にあたるため、MeanSquaredErrorを設定してみましょう。
metrics = [
    tf.keras.metrics.MeanSquaredError(name='mse')
]
metrics_specs = tfma.metrics.specs_from_metrics(metrics)
これでtfma.MetricsSpecの作成は完了です
3. tfma.SlicingSpecを作成する
tfma.SlicingSpecはどの特徴量を分析したいかというものを記載した設定値になります。
そして、このSlicingというのがTFMAの大きな特徴だと思っています。
Slicingの厳密な定義についてはまだ理解不足な点があるのですが、ある特徴量を考えたときの部分集合という理解を私はしています。
少し例を用いて説明します。
以下の3つの要素を持つカテゴリー変数を考えてみましょう。
| category_variable | 
|---|
| a | 
| b | 
| c | 
ここでSlicingSpecとしてcategory_variable設定したとすると、TFMAは各要素のa, b, cごとにMetricsSpecで設定された評価指標で評価をしてくれます。 | 
| MeanSquaredErrorを設定したとすると、以下のような表が出来上がります。 | 
| category_variable | 
| -: | 
| a | 
| b | 
| c | 
このように全体を評価するのではなく、ある部分集合で評価するための設定値がSlicingSpecになります。
slicing_specs = [
  tfma.SlicingSpec(),  # 全体を評価
  tfma.SlicingSpec(feature_keys=['is_male']),
  tfma.SlicingSpec(feature_keys=['gestation_weeks']),
  tfma.SlicingSpec(feature_keys=['mother_age']),
  tfma.SlicingSpec(feature_keys=['state']),
  tfma.SlicingSpec(feature_keys=['gestation_weeks', 'mother_age'])  # 組み合わせを評価
]
- Slicingについては Automated Data Slicing for Model Validationで定義されていると思いますが、この記事を書いている時点ではまだ読めていません。
 
4. tfma.EvalConfigを作成する
必要なものは全て作成できたので、tfma.EvalConfigを作成していきましょう。
1~3で準備は整ったので、あとは引数として渡してあげるだけで良いです。
eval_config = tfma.EvalConfig(
    model_specs=model_specs,
    metrics_specs=metrics_specs,
    slicing_specs=slicing_specs)
 4. tfma.analyze_raw_dataで分析結果を作成する
さて、ここまででBigQuery MLで作ったモデルを分析する準備は整いました。
分析の実行の仕方は簡単で, tfma.analyze_raw_dataにpandas.DataFrameとtfma.EvalConfigを渡してあげるだけで完了です。
実行時間が少し長めなので、気長に待ちましょう(数十分以内には終わってたと思います)。
result_ = tfma.analyze_raw_data(
  data=df,
  eval_config=eval_config)
 5. tfma.view.render_slicing_metricsで分析結果を可視化する
tfma.view.render_slicing_metricsにtfma.analyze_raw_dataで作成したオブジェクト(tensorflow_model_analysis.view.view_types.EvalResult)とSlicingとして指定した値を渡してあげれば完了です。
ね、簡単でしょ?
全体評価の場合にはSlicingを渡す必要は特にありません。
tfma.view.render_slicing_metrics(result_)
(スクリーンショットの表にmse以外の評価指標がいくつかありますが、記事中のコードとは別の設定でやっているためです)
特定の列に注目する場合にはslicing_columnに列名を渡すか、slicing_specに作成したslicing_specを渡してあげる必要があります。
ここでは、stateと組み合わせ評価したgestation_weeks, mother_ageを可視化してみましょう。
tfma.view.render_slicing_metrics(result_, slicing_column='state')
tfma.view.render_slicing_metrics(result_, slicing_spec=tfma.SlicingSpec(feature_keys=['gestation_weeks', 'mother_age']))
スクリーンショット中の表に、各特徴量の値ごとに評価値が表示されいます。
これによって、特定のカテゴリーだけ外れているとかなどの分析が簡単にできます。また、特徴量の値の組み合わせにも対応しているおかげで、楽に分析ができるのかなと思います
注意しなければいけないのは、tfma.EvalConfigで指定したSlicingSpecしか可視化の際に指定できないという点です。
また、このやり方だとJupyter Notebookで処理できるレベルのデータ規模に依存してしまうのが、難点です。
Apache Beam+Cloud DataFlowを使ったやり方もあるのですが、上記のやり方よりも若干複雑になるため今回は試しませんでしたが、どこか別の機会ではやりたいなとは思ってます。
後書き
なんでこんなことをしようと思ったかについてちょっとだけ。
最初にやろうとしたの「Keras Tuner + BigQuery MLでハイパーパラメータチューニングを自動化する」ということをしようとしていましたが、「チューニングするためのトライアル回数だけスキャンがかかって料金がかかること」「そこまでするくらいならAutoML Tablesを使って方がいい」という点からボツになりました。
なぜBigQuery ML + TFMAで記事を書こうと思ったかという話ですが、これは現職都合になります。
現職で開発したML PipelineがTensorflow Data Validationを利用していて、本番環境での推論時に特徴量に差異がないかを監視して通知を出したりしています。
システム化するという観点で見た時にモデルに関してはまだまだ課題が多くあり、その一歩としてTFMAに目をつけたというのがこの記事を書こうとしたモチベーションです。
そのため、記事の本題がBigQueryとは少し逸れてしまったことは申し訳なく思っています。
BigQuery MLでもAutoml Tablesのバッチ推論したデータであっても上記のやり方は有効ですので、うまいこと使い道を見つけられるきっかけになれば幸いです。
Discussion