見出し画像

BigQueryでUplift Modeling分析

電通デジタルで機械学習エンジニアをしている今井です。
本記事では、BigQueryでUplift Modeling分析を行うための方法について紹介します。

広告効果を上げるためには?

広告効果とは、広告に接触した場合と接触していない場合とのその後のコンバージョン(CV)の差である、と言えます。
介入が無作為に割り当てられるランダム化比較試験(randomized controlled trial, RCT)において、広告効果は平均処置効果(average treatment effect, ATE)として推定できます。
詳しくは過去記事[1]にまとめています。

図1

Uplift Modelingは「広告施策において、その効果を上げるためには誰を広告配信対象とするべきか」を推定するための方法です。

ユーザーの特徴量を 𝐱𝑖 とすると、Uplift Scoreは下記のように算出されます。

画像2

Uplift Scoreを正しく推定できると、スコアが高いユーザー群には「介入があればCVし、介入がなければCVしない」ユーザーが多く含まれます。
反対に、スコアが低いユーザー群には「介入があってもCVしない、介入がなくてもCVする」ユーザーが多く含まれます。

従来はCVしやすいユーザー、すなわち 𝑃(𝑦𝑖=1|𝐱𝑖) が高いユーザーに広告配信を行うことで、獲得CV数を最大化していましたが、「仮に広告配信しなくてもCVしていた」ユーザーが多く含まれるという問題もありました。

そこで、CVしやすいユーザーではなくUplift Scoreが高いユーザーに広告配信をすることで、先述のUplift Scoreの特徴により、広告効果による獲得CV数の最大化を実現できるようになります。

Uplift Scoreの推定方法

本記事では、BigQuery MLを使ってUplift Scoreを推定するための方法を紹介します。
BigQuery MLの使い方は過去記事[1]にまとめています。

分析用のサンプルデータには、MineThatData E-Mail Analytics And Data Mining Challenge dataset を使用します。
このデータには、64,000名の顧客の購買実績とともに、1/3ずつ無作為に分割したユーザー群に対して実施された、男性向けメールを送る/女性向けメールを送る/メールを送らない、というそれぞれの施策とCV結果が含まれています。

ここでは、男性向けメールを送る/女性向けメールを送るをそれぞれTreatment群/Control群とし、サイト来訪の有無(visit)をCVとします。
また、モデル学習用と予測用とで無作為にデータを2分割しておきます。

#standardSQL

SELECT
 *,
 CASE
   WHEN unique_id >= 0.50 THEN "TRAIN"
   ELSE "TEST"
 END AS assign
FROM (
 SELECT
   *,
   RAND() AS unique_id,
   CASE segment
     WHEN "Mens E-Mail" THEN 1
     WHEN "Womens E-Mail" THEN 0
   END AS w,
   visit AS y
 FROM
   `project_id.dataset_id.MineThatData_orig`
 WHERE
   segment IN ("Mens E-Mail", "Womens E-Mail")
)

スクリーンショット 2020-06-02 19.21.03

Uplift Modelingでは、
・ Treatment群/Control群とで2つの予測モデルを作成する方法(以下、Two-Model Approach)
・ class variable transformationという手法で1つの予測モデルだけを作成する方法(以下、One-Model Approach)
が広く使われています。

Two-Model Approachでは、

画像4

として推定されます。

#standardSQL

CREATE OR REPLACE MODEL
 `dataset_id.model_name`
OPTIONS(
 MODEL_TYPE="logistic_reg",
 input_label_cols=["y"],
 max_iterations=50,
 early_stop=false,
 l1_reg=0.01
) AS
 SELECT
   recency,
   history,
   mens,
   womens,
   zip_code,
   newbie,
   channel,
   y,
 FROM
   `project_id.dataset_id.MineThatData`
 WHERE
   w = 1 -- Treatment群での予測モデル学習
   -- Control群での予測モデル学習の場合は、w=0とする
   AND assign = "TRAIN"
#standardSQL

WITH predicts_treatment AS (
 SELECT
   unique_id,
   y,
   w,
   predicted_y_probs[OFFSET(0)].prob AS p_treatment
 FROM
   ML.PREDICT(MODEL `dataset_id.model_name_treatment`, (
     SELECT
       *
     FROM
       `project_id.dataset_id.MineThatData`
     WHERE
       assign = "TEST"
     )
   )
), predicts_control AS (
 SELECT
   unique_id,
   predicted_y_probs[OFFSET(0)].prob AS p_control
 FROM
   ML.PREDICT(MODEL `dataset_id.model_name_control`, (
     SELECT
       *
     FROM
       `project_id.dataset_id.MineThatData`
     WHERE
       assign = "TEST"
     )
   )
)

SELECT
 y,
 w,
 p_treatment - p_control AS uplift_score
FROM
 predicts_treatment
 LEFT JOIN
 predicts_control
 USING (unique_id)

一方、One-Model Approachでは、

画像5

を目的変数とする予測モデルを作成することで、

画像6

として推定されます。
詳しくは参考文献[2]を一読ください。

#standardSQL

CREATE OR REPLACE MODEL
 `dataset_id.model_name`
OPTIONS(
 MODEL_TYPE="logistic_reg",
 input_label_cols=["z"],
 max_iterations=50,
 early_stop=false,
 l1_reg=0.01
) AS
 SELECT
   recency,
   history,
   mens,
   womens,
   zip_code,
   newbie,
   channel,
   CASE
     WHEN w = 1 AND y = 1 THEN 1
     WHEN w = 0 AND y = 0 THEN 1
     ELSE 0
   END AS z
 FROM
   `project_id.dataset_id.MineThatData`
 WHERE
   assign = "TRAIN"
#standardSQL

WITH predicts AS (
 SELECT
   unique_id,
   y,
   w,
   predicted_z_probs[OFFSET(0)].prob AS p
 FROM
   ML.PREDICT(MODEL `dataset_id.model_name`, (
     SELECT
       *,
       CASE
         WHEN w = 1 AND y = 1 THEN 1
         WHEN w = 0 AND y = 0 THEN 1
         ELSE 0
       END AS z
     FROM
       `project_id.dataset_id.MineThatData`
     WHERE
       assign = "TEST"
     )
   )
)

SELECT
 y,
 w,
 2 * p - 1 AS uplift_score
FROM
 predicts

実行すると下記のような結果になります。

スクリーンショット 2020-06-03 13.32.21

Uplift Modelingの評価

Two-Model ApproachとOne-Model ApproachとでそれぞれUplift Scoreを推定しましたが、どちらを使うべきでしょうか?

Uplift Modelingの評価にはAUUC(area under the uplift curve)やQini係数を使用するのが一般的です。
AUUCとQini係数の算出には、Uplift Scoreが閾値 𝜙 以上/未満のユーザーに対して介入/非介入行為をした場合にどれぐらいCV件数が増えるかを表すUplift曲線とQini曲線という指標を使います。

画像13

画像8

𝑦(𝜙),𝑛(𝜙) はそれぞれ Uplift Score ≥𝜙 でのCV数, ユーザー数を表します。

これらは Uplift Score ≥𝜙 での層別化マッチングと同義であり、Uplift曲線はATE, Qini曲線はATT(average treatment effect on the treated)を表します。

#standardSQL

WITH stats AS (
 SELECT DISTINCT
   uplift_score,
   COUNT(1) OVER desc_uplift_score AS index,
   SUM(w) OVER desc_uplift_score AS n_t,
   SUM((1 - w)) OVER desc_uplift_score AS n_c,
   SUM(y * w) OVER desc_uplift_score AS y_t,
   SUM(y * (1 - w)) OVER desc_uplift_score AS y_c
 FROM
   `project_id.dataset_id.uplift_score`
 WINDOW desc_uplift_score AS (ORDER BY uplift_score DESC)
)

SELECT
 uplift_score,
 index / MAX(index) OVER() AS proportion,
 CASE
   WHEN n_t > 0 AND n_c > 0 THEN (y_t / n_t - y_c / n_c) * (n_t + n_c)
   WHEN n_t > 0 AND n_c = 0 THEN y_t
   WHEN n_t = 0 AND n_c > 0 THEN - y_c
 END AS uplift_curve,  
 CASE
   WHEN n_t > 0 AND n_c > 0 THEN y_t - y_c * n_t / n_c
   WHEN n_t > 0 AND n_c = 0 THEN y_t
   WHEN n_t = 0 AND n_c > 0 THEN 0
 END AS qini_curve,
 (MAX(y_t) OVER() / MAX(n_t) OVER() - MAX(y_c) OVER() / MAX(n_c) OVER()) * index AS baseline_uplift,
 (MAX(y_t) OVER() - MAX(y_c) OVER() * MAX(n_t) OVER() / MAX(n_c) OVER()) * index / MAX(index) OVER() AS baseline_qini
FROM
 stats

スクリーンショット 2020-06-28 14.45.12

ここで、proportion はUplift Scoreを降順に並べて閾値をずらしていったときの百分率です。
また、baseline_uplift は(0,0)と

画像14

とを、baseline_qini

画像12

とを結ぶ直線で、無作為な介入行為を表します。

AUUC/Qini係数は、Uplift曲線/Qini曲線下の面積からこの直線下の面積を差し引いた値であり、これらが高いほどUplift Modelingの性能が高いことを意味します。

#standardSQL

WITH Riemann_stats AS (
 SELECT
   proportion - LAG(proportion, 1, 0) OVER asc_proportion AS Riemann_sum_width,
   uplift_curve + LAG(uplift_curve, 1, 0) OVER asc_proportion AS Riemann_sum_height_uplift_curve,
   baseline_uplift + LAG(baseline_uplift, 1, 0) OVER asc_proportion AS Riemann_sum_height_baseline_uplift,
   qini_curve + LAG(qini_curve, 1, 0) OVER asc_proportion AS Riemann_sum_height_qini_curve,
   baseline_qini + LAG(baseline_qini, 1, 0) OVER asc_proportion AS Riemann_sum_height_baseline_qini
 FROM
   `project_id.dataset_id.curve`
 WINDOW asc_proportion AS (ORDER BY proportion ASC)
), auc AS (
 SELECT
   0.5 * SUM(Riemann_sum_width * Riemann_sum_height_uplift_curve) AS auc_uplift_curve,
   0.5 * SUM(Riemann_sum_width * Riemann_sum_height_baseline_uplift) AS auc_baseline_uplift,
   0.5 * SUM(Riemann_sum_width * Riemann_sum_height_qini_curve) AS auc_qini_curve,
   0.5 * SUM(Riemann_sum_width * Riemann_sum_height_baseline_qini) AS auc_baseline_qini
 FROM
   Riemann_stats
)

SELECT
 auc_uplift_curve - auc_baseline_uplift AS auuc,
 auc_qini_curve - auc_baseline_qini AS qini_coefficient
FROM
 auc

AUUCは
・ Two-Model Approach: 151.32
・ One-Model Approach: 148.06
Qini係数は
・ Two-Model Approach: 75.74
・ One-Model Approach: 74.87
となり、Two-Model Approachのほうがモデル性能が高いことになります。

下記を実行すると、Uplift Modelingの結果をMatplotlibで可視化できます。

import matplotlib.pyplot as plt
from google.cloud import bigquery
client = bigquery.Client()

sql = """
SELECT
 proportion,
 uplift_curve,
 qini_curve,
 baseline_uplift,
 baseline_qini
FROM
 `project_id.dataset_id.curve`
"""
df_curve = client.query(sql).to_dataframe()

sql = """
SELECT
 auuc,
 qini_coefficient
FROM
 `project_id.dataset_id.evaluate`
"""
df_evaluate = client.query(sql).to_dataframe()

fig, (ax_uplift, ax_qini) = plt.subplots(ncols=2, figsize=(10,4))

ax_uplift.plot(df_curve.proportion, df_curve.uplift_curve, label='Uplift curve')
ax_uplift.plot(df_curve.proportion, df_curve.baseline_uplift, label='baseline')
ax_uplift.set_xlabel('percentage of total number')
ax_uplift.set_ylabel('cumulative uplift')
ax_uplift.set_title('AUUC: {}'.format(df_evaluate.auuc[0]))
ax_uplift.legend(loc='lower right', frameon=False)

ax_qini.plot(df_curve.proportion, df_curve.qini_curve, label='Qini curve')
ax_qini.plot(df_curve.proportion, df_curve.baseline_qini, label='baseline')
ax_qini.set_xlabel('percentage of total number')
ax_qini.set_ylabel('cumulative uplift')
ax_qini.set_title('Qini coefficient: {}'.format(df_evaluate.qini_coefficient[0]))
ax_qini.legend(loc='lower right', frameon=False)

fig.show()

画像15

最後に、Qini曲線と無作為介入との差(conversion_lift)が大きくなる場合の閾値で介入/非介入のユーザー群を決定します。

#standardSQL

SELECT
 uplift_score,
 proportion,
 qini_curve - baseline_qini AS conversion_lift
FROM
 `project_id.dataset_id.curve`
ORDER BY conversion_lift DESC

スクリーンショット 2020-06-03 15.25.24

上記の結果から、Uplift Score ≥0.02 である上位59.4%のユーザー群には男性向けメールを、それ以外には女性向けメールを送ることでメール配信の効果を上げることが期待できます(なお、簡易化のためUplift Scoreを小数第2位に四捨五入しています)。

おわりに

本記事では、広告効果を最適化するためのUplift Modeling分析について紹介しました。

最後に余談ですが、
BigQuery MLの新機能として、2020年6月にXGBoostDeep Neural Network (DNN)がリリースされました。
これまでは線形モデルしか使用できませんでしたが、このアップデートによりBigQuery MLがデータサイエンティストにとっての”銀の弾丸”となりうるのではないでしょうか。

参考文献

[1] BigQueryで傾向スコア分析
[2] Jaskowski+, Uplift modeling for clinical trial data, 2012
[3] Radcliffe+, Using control groups to target on predicted lift: Building and assessing uplift model, 2007
[4] 有賀康顕, 中山心太, 西林孝「仕事で始める機械学習」