こんにちは。ML チームの takuji です。だんだん寒くなってきて、お鍋が食べたくなる季節になってきましたね。
こちらの記事は Gunosy Advent Calendar 2024 の 3 日目の記事です。
本記事では、広告キャンペーン単位で A/B テストを行った話を書きたいと思います。
背景
- 弊社のニュースアプリに広告主から出稿される広告は、広告キャンペーンという単位で管理されています。
- ここでいう広告は、グノシー、ニュースパス、auサービスTodayに配信される運用型広告などが該当します。
- 広告キャンペーンとは、広告を管理するための単位で、予算やターゲットなどを設定します。
- 運用型広告には自動入札機能があり、ユーザごとに入札価格が自動で最適化されます。
- その裏では、機械学習モデルが動いていて、広告主の設定に合わせて効果的な配信が可能となっています。
- 広告の A/B テストは、ユーザ単位で行われることが一般的かなと思いますが、いくつか課題があります。
- 例えば、機械学習モデルの新旧での比較を行う場合、広告の予算が共通であるために他方が不利になる可能性があります。
- 他には、その広告に新旧の機械学習モデルのログが混在してしまうため、正確な比較ができない可能性があります。
- これらの課題を対処したく、弊社では「広告キャンペーン単位 A/B テスト」を導入しています。
広告キャンペーン単位 A/B テストとは
- 名前の通り、広告キャンペーン単位で A/B テストを行うことです。
- 特に考慮することなく、A/B テストが可能かというとそうではありません。
- 広告キャンペーン数がユーザ数と比べると極端に少ないため、サンプル数不足になりやすいです。
- サンプル数が少ないと、広告数値(例: eCPM)がブレてしまい、評価が難しくなります。
A/B テストでブレをなくすために
- A/B テストにおいて、ノイズ(ランダムなばらつき)が発生することは避けられません。
- ノイズによってシグナル(本来検出したい効果)を誤って解釈してしまう可能性があるため、ノイズを抑える工夫が必要です。
- 具体的には、「シグナル」を正確に捉えるために、共変量バランスを保つようにして、ノイズを小さくします。
- 詳しくは、https://developers.cyberagent.co.jp/blog/archives/47637/ を参考にしてください。
- 実際に社内でも、広告データを使って以下の手法で実験を行いました。
- 純粋なランダム探索
- 参考記事に登場する「共変量バランスを揃える再ランダム化」という手法(以下、共変量を揃える探索)
- 広告キャンペーンのメタデータを使って共変量バランスを閾値以下になるようにします。
- 実績値を使った探索
- 実績値を使うため、既存の広告キャンペーンしか対象にできない点に注意が必要です。
- 新規の広告キャンペーンには適用できないため、どの施策でも使えるわけではありません。
- 良し悪しでいうと、実績値を使った探索 > 共変量を揃える探索 > 純粋なランダム探索 という結果になりました。
- 共変量バランスを揃えるだけでは、なかなか実績値のブレを抑えることができませんでした。
- そのため、実績値を使った探索を採用して、広告キャンペーン単位で A/B テストを行うことにしました。
- 既存キャンペーンしか A/B 対象にできないが、群間の水準が合うのではあれば、実績値を使う価値はあるという判断です。
より等しい条件での比較をするために
- 広告キャンペーン単位で A/B テストを成立させるためには、工夫が必要です。
- 以下に、広告キャンペーン単位で A/B テストを行うための工夫を紹介します。
- 対象となる広告キャンペーンを決めるために、基準日を設けて、その日に稼働している広告キャンペーンを対象とします。
- 各群の広告キャンペーン数の差分が限りなく小さくなるようにします。
- 例えば、基準日から翌日にかけて、広告キャンペーンが停止してしまう可能性があるため、それを考慮して調整します。
- 各群で実績値(例: eCPM)を出して、その差分が N %以内となるようにします。
- 疑似コードで書くと以下のようになります。
- 上記の工夫を踏まえて A/B 割り当て調整自体は make コマンドを叩けば、自動で行われるようにしています。
- 数百万回の試行を重ねて、条件をクリアした 100個程度の ab key と実績値を作成します。
- ab key は、広告キャンペーンを A/B に割り当てるためのキーです。
- 数百万回の試行を重ねて、条件をクリアした 100個程度の ab key と実績値を作成します。
- 作成されたデータをスプレッドシートで確認し、最も良さそうな ab key を選択します。
- 念のために、Redash でも本番のログで集計してみて、問題なければ ab key を採用します。
- make コマンドを叩いて ab key を探索する時は、サマリーテーブルを使っているため、厳密に出せない部分があったりするためです。
実績値を使った探索の疑似コード:
# 制約値の定義 THRESHOLD_SIZE = N # サイズの許容差 THRESHOLD_A = X # 指標Aの許容差分 THRESHOLD_B = Y # 指標Bの許容差分 # 群分割 ab_key = generate_random_string() group_a, group_b = split_group(campaigns, ab_key) # 各群の指標を計算 a_metrics = calc_metrics(group_a) b_metrics = calc_metrics(group_b) # 制約チェック if (abs(len(group_a) - len(group_b)) <= THRESHOLD_SIZE and calc_diff(a_metrics.value_a, b_metrics.value_a) <= THRESHOLD_A and calc_diff(a_metrics.value_b, b_metrics.value_b) <= THRESHOLD_B): # 条件を満たす群分割が見つかった
どのぐらいのブレがあるのか
- 上記の工夫を踏まえても、広告数値のブレはありますが、A/A 比較で差分が把握できているため、施策の優劣は比較的正確に判断できます。
- 広告キャンペーン数は最低でも 90個程度は必要かなと思います。
- 純粋なランダム探索と比べ、実績値を使うことで eCPM のブレ幅を 11.8 %から 4.2 %まで抑制できます。
まとめ
- 広告の ユーザ単位 A/B テストに代わる方法として、広告キャンペーン単位の A/B テストを紹介しました。
- 明日の Gunosy Advent Calendar 2024 では nagayama さんが「Android Push 通知の Tips」についてお話します。お楽しみに!