はじめに
こんにちは、ZOZO NEXTのApplied MLチームでMLエンジニアをしている柳です。機械学習を使ってビジネス上の課題解決をする仕事に取り組んでいます。今回は、BizDevメンバーのAutoML Tables活用をサポートする中で出会った課題やその解決方法について紹介します。
概要
ZOZOTOWNでは様々なプロモーション施策が日々打たれています。ZOZOTOWNをご利用の方は、メールやアプリ上でキャンペーンやクーポンの配信を受け取ることも多いのではないでしょうか。このような配信施策では、ターゲットを絞ることが重要です。無闇矢鱈に多数のユーザーに配信をしてしまうと配信コストがかかります。さらに、興味のないキャンペーンが大量に通知されるとユーザー体験も損なわれます。そのため、個々のユーザーの興味を抽出し、それに合わせた配信をするのが理想です。
弊社では、MLのビジネス活用を進めるBizDevメンバーを中心に、このような課題に取り組んでいます。そこでよく使われているのが、GCPのAutoML Tablesです。以前から存在するサービスですが、Vertex AIの登場に伴ってその一機能としても提供されるようになりました。専門的なMLライブラリの扱い方を覚える必要がなく、ビジネス課題をMLを使って解決するのに集中できる便利なツールです。私たちのようなMLエンジニアは特徴量の作り方のディスカッションを時々するくらいで、基本的にはBizDevメンバーがモデリングから配信まで行っていました。
しかし、このようなAutoMLのビジネス活用が拡大していく中で、徐々に技術的負債が溜まっていることもわかってきました。それらは大別すると以下のように分類できます。
- コードの管理やレビュー環境に関する課題
- モデルの学習や評価方法に関する課題
特に後者はある程度MLを使った経験がないとなかなか気づきづらいようなものでした。本記事ではこれら課題の具体的な内容と、それを解決するための取り組みについて紹介します。
AutoML Tablesによるモデリング
本章では、AutoML Tablesの説明と、配信施策での使用例を紹介します。
基本的な使い方
AutoML Tablesの使い方は概ね以下の流れです。詳しくは公式ドキュメントを参照してください。
- BigQueryテーブルなどに、学習用の表形式データを用意する
- のデータをVertex AIのデータセットとしてインポートし、特徴量として利用するカラム、ラベルとして利用するカラムの選択、及び回帰や分類など課題の種類と最適化指標を指定し学習を開始する
- コンソールで精度や特徴量の重要度を確認し、モデルがうまくできていそうかをチェックする
- 用途に合わせて推論用データを作成し、推論する
配信施策における使い方
配信施策では「ユーザーがある対象に興味を持っているか」を予測するようなモデルを作ります。以下では、例として「ユーザーがカテゴリXに興味を持っているか」を予測するモデルの作り方を考えてみます。様々な方法が考えられますが、ここでは次のようにアプローチしてみましょう。
- 学習
- 二値分類を解く
- ある期間にカテゴリXの商品を購入したユーザーを正例とし、負例はカテゴリXの商品を購入していないユーザーから正例と同じ数だけサンプリングする
- 推論
- AutoML Tablesの二値分類モデルでは、バッチ推論をすると各ユーザーに0から1の予測値が付与される
- この値の上位Kユーザーを最終的な推論結果とする
特徴量は各ユーザーの年齢などの属性情報や、ZOZOTOWNでの実際の行動履歴を用います。BigQueryを使って正例・負例ユーザーを抽出し、特徴量をjoinすればデータセットは完成です。
あとは、前述のようにAutoML Tablesを利用することで作業は完了します。弊社のBizDevメンバーは普段からBigQueryを使って分析しているので、データ抽出用のSQLを難なく書くことができます。そのため、BizDevメンバーだけでモデル作成から配信用のユーザー抽出まで行えます。
発生した課題
弊社では前述の通り、 BizDevメンバーがAutoML Tablesを活用してきました。しかし、利用の拡大に伴い、以下のような問題が見られるようになりました。
SQLの管理不足からバグが生じやすくなった課題
いくらAutoML Tablesがノーコードで学習・推論してくれると言っても、それに投入するデータを作るにはSQLを書く必要があります。施策が変われば抽出したいユーザーも変わり、予測に有効な特徴量も変わってきます。そのような場合、往々にして以前の施策で使っていたSQLを流用して新規施策用のデータを抽出することになります。上記の例で言えば、「カテゴリXに興味あるユーザーを当てるためのSQLを、カテゴリYを当てるためのものに変えよう」ということです。また、配信期間が変われば特徴量を計算する期間も変わってきます。このような際に、しばしばSQLをローカルで直接書き換え利用することが行われていました。修正や継ぎ足しが行われたSQLは可読性が低下し、バグが入りやすくなります。
実際に、学習したモデルの特徴量の重要度に違和感があり調べてみたところ、SQLにバグが混入していたということがありました。これは仕組みを作って防ぐべき問題です。
オフライン評価が未整備である課題
MLモデルの改善を正しい方向に進める上で、適切なオフライン評価を設定することは非常に重要です。上記の例で作成したいのは「予測上位K件に正例ユーザー(カテゴリX購入者)をできるだけ多く含めることができる」モデルです。そのため、本来であれば以下のようなPrecision@KやRecall@Kなどのメトリクスで評価をすべきです。
しかし、AutoML Tablesではこれらのメトリクスは自動で計算されません。その代わりに、二値分類のAUCなどが計算されコンソールに表示されます。通常の二値分類タスクであればこれで問題ありませんが、今回は負例をサンプリングしているため、サンプリング方法に敏感な指標となってしまいます。正例との識別が難しい負例をより多くサンプルするようにすれば、二値分類の精度は低くなります。逆に識別が簡単な負例を多くサンプルすれば二値分類の精度は上がります。私たちが本当に欲しいモデルは正例を精度良く抽出できるようなモデルであり、負例のサンプリング方法によってメトリクスが上下するのは好ましくありません。
モデルを正しく改善していくために、本来評価したいPrecision@KやRecall@Kなどのメトリクスが確認できるように環境を整理する必要がありました。
繰り返し作業が発生する課題
前述の通り、AutoMLで自動化できるのはあくまでも学習・推論作業のみであり、データの抽出は当然自分でやらなければなりません。そのため、BizDevメンバーが毎回決められた手順でSQLを逐次実行しており、繰り返し作業や計算の各ステップが終わるまでのソワソワして待つ時間が生じていました。
解決方法
本章では、上記課題を解決するために取り組んだ解決方法を紹介します。
SQL管理の厳格化
まずは、シンプルにデータ抽出用SQLのGit管理を厳格化することにしました。SQLごとに「正例抽出用」「特徴量の抽出用」など役割を明確にし、集計期間や集計対象カテゴリなど、パラメトライズできる部分をクエリパラメータにしました。そして、新たに特徴量や学習ターゲットを追加する際には、GitHub上でプルリクエストを作る運用方針にしています。
適切なオフライン評価の実装
適切なオフライン評価をするために、AutoMLの外部に評価機能を実装しました。例えば、上記の例では、カテゴリXの購入ユーザーを時系列に沿って学習用と評価用に分割します。この評価用に分けられたユーザーをground truthとして、モデルのprecision@Kやrecall@Kを計算します。こうすることで、負例サンプリングの方法に鈍感な評価ができるようになります。そして、これらのメトリクスの評価は後述のパイプラインに組み込み、コンソール上で確認できるようにしています。
Vertex Pipelinesによる自動化
繰り返し作業の自動化をするため、以下のワークフローをVertex Pipelines上に実装しました。なお、Vertex Pipelinesは先日GA版になった機能です。
- データ抽出
- AutoML Tablesによる学習
- AutoML外での評価・バッチ推論
構築したワークフローは以下の通りです。
このワークフローにより、前述のメトリクスは以下のように可視化されます。
Vertex Pipelinesはパイプライン定義を記入したJSONファイルをアップロードすることで、GCPコンソールから実行できます。なお、このJSONファイルの管理・更新はMLエンジニアが担当します。BizDevメンバーにはJSONファイルを渡し、適宜パラメータを変更して施策に合ったモデリングをしてもらいます。これにより、ノーコードの環境を維持しつつ、BizDevメンバーの作業負荷の軽減を実現しました。
また、MLエンジニアがコード類を管理し、個々の現場でSQLを修正して利用することがなくなったため、バグが混入するリスクも減少しました。
AutoML Tablesのパイプラインコンポーネントに関するTips
最後にVertex PipelinesでAutoML Tablesを使う際のTipsを紹介します。なお、Vertex Pipelinesについては過去の記事でも紹介しているのでご参照ください。
また、AutoML Tablesを使ったパイプラインについては、以下の公式ブログが参考になります。
特徴量の指定方法
学習コンポーネントに渡したテーブルの中から特定の特徴量のみ学習に使う方法を紹介します。上記の公式ブログから学習コンポーネントの部分を抜粋します。
from google_cloud_pipeline_components import aiplatform as gcc_aip @kfp.dsl.pipeline(name="automl-tab-beans-training-v2", pipeline_root=PIPELINE_ROOT) def pipeline( bq_source: str = "bq://aju-dev-demos.beans.beans1", display_name: str = DISPLAY_NAME, project: str = PROJECT_ID, gcp_region: str = "us-central1", api_endpoint: str = "us-central1-aiplatform.googleapis.com", thresholds_dict_str: str = '{"auRoc": 0.95}', ): dataset_create_op = gcc_aip.TabularDatasetCreateOp( project=project, display_name=display_name, bq_source=bq_source ) training_op = gcc_aip.AutoMLTabularTrainingJobRunOp( project=project, display_name=display_name, optimization_prediction_type="classification", optimization_objective="minimize-log-loss", budget_milli_node_hours=1000, column_transformations=[ {"numeric": {"column_name": "Area"}}, {"numeric": {"column_name": "Perimeter"}}, {"numeric": {"column_name": "MajorAxisLength"}}, ... other columns ... {"categorical": {"column_name": "Class"}}, ], dataset=dataset_create_op.outputs["dataset"], target_column="Class", ) ...
学習コンポーネントは AutoMLTabularTrainingJobRunOp
です。ここで使用する特徴量を指定する際に column_transformations
という変数を設定しています。
しかし、Pythonコンポーネントのドキュメントには、次のように書かれています。
Consider using column_specs as column_transformations will be deprecated eventually.
つまり、現在は column_specs
という変数の利用が推奨されています。column_transformations
は辞書のリストが入る仕様ですが、column_specs
では仕様が以下のように変更されています。
{"Area": "numeric", "Perimeter": "numeric",...}
バッチ推論と結果の取得
上記の AutoMLTabularTrainingJobRunOp
で作成されたモデルを ModelBatchPredictOp
に渡すことでバッチ推論が可能です。
batch_prediction_op = gcc_aip.ModelBatchPredictOp( project=<プロジェクト名>, location=<リージョン名>, job_display_name=<好きなディスプレイ名>, model=training_op.outputs["model"], # AutoMLTabularTrainingJobRunOpの出力 bigquery_source_input_uri=<推論用BigQueryテーブル名>, bigquery_destination_output_uri=<推論結果の書き込み先>, instances_format="bigquery", predictions_format="bigquery", )
bigquery_source_input_uri
は推論対象のBigQueryのテーブルです。詳しくはドキュメントを参照してください。
bigquery_destination_output_uri
には、 bq://<project>
もしくは bq://<project>.<dataset>
形式で推論結果の出力先を指定します。テーブル名などはコンポーネントによって一意のものが自動で付与されます。
また、バッチ推論コンポーネントで作られた推論結果のテーブルは、コンポーネントによって一意の名前がつけられており、ユーザー側で指定することができません。このテーブル名を取得するには、バッチ推論リソースにアクセスする必要があります。例えば、以下のようなコンポーネントで直接 curl
を使って取得します。
@component( base_image=base_image, output_component_file=None, ) def get_batch_predict_info_op_wrapper( batch_predict_job: Input[Artifact], # バッチ推論の出力 data_path: OutputPath(str), # テーブル名の書き込み先 ): import os import subprocess uri = batch_predict_job.uri url = uri.replace( "aiplatform://v1/", "https://us-central1-aiplatform.googleapis.com/v1/", ) os.makedirs(os.path.dirname(data_path), exist_ok=True) cmd = 'curl -X GET -H "Content-Type: application/json" -H "Authorization: Bearer "$(gcloud auth application-default print-access-token) "{url}" > "{data_path}"'.format( url=url, data_path=data_path ) subprocess.call(cmd, shell=True)
最後に
本記事ではZOZOTOWNにおけるMLのビジネス応用の一例と、それを改善するための取り組みを紹介しました。
ZOZO NEXTでは、機械学習を適切に使用して課題を解決できるMLエンジニアを募集しています。今回は配信施策について紹介しましたが、検索や推薦の領域でもML活用が進んでいます。
ご興味のある方は、以下のリンクからぜひご応募ください。