Posted at

因果推論でデジタル広告のCPA・ROASを改善する方法


記事の概要

現在、デジタル広告の成長率は著しい。インターネットに接続すると、

多くのページに広告が埋め込まれている。広告には必ず配信している

広告主がおり、多額の広告費用が使われている。

しかし、多くの広告主は費用を下げたいと考えているはずだ。

よって、今回はコンバージョン数を下げずに広告費を減らせる

可能性がある方法を記載する。


広告主が取り扱う広告のKPI

広告主は主に以下の指標でデジタル広告を評価する。

  CVs = 訪問ユーザ数(全部) x コンバージョン率

  Cost = 訪問ユーザ数(広告経由) x クリック単価

  Revenue = 訪問ユーザ数(全部) x コンバージョン率 x 単価 x 購入回数

  CPA = Cost / Cvs

  ROAS = Revenue / Cost

訪問ユーザ数(広告経由) x クリック単価が広告費用となる。

これを下げれば、CPA・ROASも改善する仕組みだ。


広告の貢献度の評価

広告の分析にはアトリビューションというものがあり、広告の貢献度を図るために定義された指標だ。

購入の直前に触れた広告のみを評価するラストクリックや、購入までの最初に触れた広告のみを評価する

ファーストクリックが一般的だ。

 https://anagrams.jp/blog/basic-of-attribution/

アトリビューション分析によって広告媒体のCPAやROASを計測し、どの時点で貢献しているかが可視化できる。

ただ、これだけでは広告の貢献度を図るのは難しい。なぜなら、広告に触れた

ユーザがコンバージョンしても、広告がきっかけなのかはわからない。

つまり、「広告に触れなくても」コンバージョンしていた可能性がある。


因果推論によるターゲット分類

ここで使用するのが因果推論だ。因果推論は簡潔に言うと「原因」と

「結果」を明らかにする手法だ。例えば医療業界で言うと、「この薬を

飲んだから(原因)、病気が治った(結果)」、ということを検証するために

利用される。以下の事例がわかりやすい。

https://healthpolicyhealthecon.com/2014/09/30/study-design-overview/

広告で言うと「広告に触れて(原因)、商品を購入した(結果)」と

置き換えられる。因果推論を使用する場合、「ある広告に触れたユーザ」と

「ある広告に触れなかったユーザ」が、それぞれ「コンバージョンしたか否か」

のデータを収集する。

このデータを因果推論のモデルに学習させ、テストデータで予測させると、

ターゲットを以下のように分類できる。

 ① あまのじゃくユーザ:広告を配信すると、コンバージョンしなくなる。

 ② 無関心ユーザ:広告を配信有無に関わらず、コンバージョンしない。

 ③ 確実購入ユーザ:広告を配信有無に関わらず、コンバージョンする。

 ④ 説得可能ユーザ:広告を配信すると、コンバージョンする。

例えば、モデルの予測で④の数が多ければ、広告がユーザを説得して

いることになるので、それは配信するべき広告となる。

しかし、①②③の数が多い場合は広告を配信しなくてもユーザは

コンバージョンするし、コンバージョンを阻害している可能性もある。


CPA・ROASを改善するために

ここまででわかる通り、因果推論でCPA・ROASを改善する方法は以下だ。

・因果推論モデルを用いてターゲットを分類する。

・説得可能ユーザが多い広告の出稿を増やすか、配信不要ユーザが多い広告の

 出稿を減らす。

この流れをPythonで実装してみる。


1. モジュールインポート

# Main imports

from econml.metalearners import TLearner, SLearner, XLearner, DomainAdaptationLearner, DoublyRobustLearner
from sklearn.linear_model import LinearRegression
from sklearn.ensemble import RandomForestRegressor, RandomForestClassifier, GradientBoostingRegressor
from sklearn.model_selection import train_test_split

# Helper imports
import os, sys
import numpy as np
import pandas as pd
from numpy.random import binomial, multivariate_normal, normal, uniform
import matplotlib.pyplot as plt


2.データセットの作成

データは以下から取得。BigQueryに存在するGoogleAnalyticsの

公開データセットで、サイトへのアクセス情報だけでなく広告経由の流入も

格納されている。既にダウンロードして、results_bqga.csvにまとめている。


  https://bigquery.cloud.google.com/table/bigquery-public-data:google_analytics_sample.ga_sessions_20170801

os.listdir("../input")

df_bqga = pd.read_csv("../input/results_bqga.csv")
# 欠損値の確認
df_bqga.isnull().sum()

total_visits 0
total_transaction 890508
campaign 0
source 0
medium 0
fullVisitorId 0
visitNumber 0

# 欠損値を0に変換
df_bqga["total_transaction"] = df_bqga["total_transaction"].apply(lambda x : 0 if x != x else x)

# ユーザ、参照元、流入経路、キャンペーンでグルーピング
cols = ["fullVisitorId", "source", "medium", "campaign", "visitNumber", "total_visits"]
df_bqga_grp = df_bqga.groupby(by=cols).sum().reset_index()


3. CATE計算のためのデータ作成

CATEは「ある特徴量で条件付けた際の介入の因果効果の期待値」と言える。

つまりCATEが高いユーザは広告を付与した場合の効果が高いので、広告を配信するべきという結論が得られる。主に3つのデータが必要である。

・Outcome(Y) = 目的変数でコンバージョンを達成したか、売上高など

・Treatment(T) = 広告やクーポンなど、アクションに触れたか否か

・Feature(X)= サイト訪問数や属性など、ユーザの特徴

以下のコードで上記のデータを作成している。

# Treatmentの作成(広告に触れたか否か)

df_bqga_grp["Treatment"] = df_bqga_grp.medium.apply(lambda x : 1 if x in ["cpc", "cpm", "affiliate"] else 0)

# Outcomeの作成(購入したか否か)
df_bqga_grp["Outcome"] = df_bqga_grp.total_transaction.apply(lambda x : 0 if x == 0 else 1)

# Featureの作成(自然検索で訪問したか、再訪問したか、何回訪問したか)
df_bqga_grp["is_organic"] = df_bqga_grp.medium.apply(lambda x : 1 if x == "organic" else 0)
df_bqga_grp["is_return"] = df_bqga_grp.visitNumber.apply(lambda x : 0 if x == 1 else 1)
df_bqga_grp = pd.get_dummies(data=df_bqga_grp, columns=["total_visits"])

# 不要な列の作成
df_bqga_grp.drop(columns=["source", "medium", "campaign", "visitNumber" ,"total_transaction"], axis=1, inplace=True)

df_data = df_bqga_grp.groupby(by=["fullVisitorId"]).sum().reset_index()

# 全データを0 or 1に変換
df_data.iloc[:, 1] = df_data.iloc[:, 1].apply(lambda x : 0 if x == 0 else 1)
df_data.iloc[:, 2] = df_data.iloc[:, 2].apply(lambda x : 0 if x == 0 else 1)
df_data.iloc[:, 3] = df_data.iloc[:, 3].apply(lambda x : 0 if x == 0 else 1)
df_data.iloc[:, 4] = df_data.iloc[:, 4].apply(lambda x : 0 if x == 0 else 1)
df_data.iloc[:, 5] = df_data.iloc[:, 5].apply(lambda x : 0 if x == 0 else 1)
df_data.iloc[:, 6] = df_data.iloc[:, 6].apply(lambda x : 0 if x == 0 else 1)
df_data.iloc[:, 7] = df_data.iloc[:, 7].apply(lambda x : 0 if x == 0 else 1)

# 不均衡データの修正
df_case1 = df_data[(df_data["Treatment"] == 1) & (df_data["Outcome"] == 1)]
df_case2 = df_data[(df_data["Treatment"] == 1) & (df_data["Outcome"] == 0)].iloc[0:1000, :]
df_case3 = df_data[(df_data["Treatment"] == 0) & (df_data["Outcome"] == 1)].iloc[0:1000, :]
df_case4 = df_data[(df_data["Treatment"] == 0) & (df_data["Outcome"] == 0)].iloc[0:1000, :]


4. データ分割

 トレーニング&テストデータに分割

train_df, test_df = train_test_split(df_data, test_size=0.2, random_state=0, stratify=df_data['Treatment'])

# ユーザIDを抽出しておき、データセットからはユーザIDを削除
user_ids = test_df.fullVisitorId
train_df.drop(["fullVisitorId"], inplace=True, axis=1)
test_df.drop(["fullVisitorId"], inplace=True, axis=1)


5. CATEの計算と予測(ここからが本題)

先ほど作成したデータから広告を配信するべきユーザ=CATE値が高い

ユーザを算出する。今回はeconmlを用いて実行する。

モジュールの詳細を知りたい方は以下を参照してほしい。

  https://github.com/microsoft/EconML


EconMLとは?

EconMLはMicrosoftが作成したパッケージで、計量経済学と機械学習を

融合させたもの。このツールを用いて、意思決定を自動化することが

最終目的らしい。今回はEconMLを用いてユーザ毎のCATE値を計算してみる。


データの投入

# 学習に必要なデータを投入

T = train_df.Treatment
Y = train_df.Outcome
X = train_df.drop(["Outcome", "Treatment"], axis=1)

# テスト用データにはユーザの特徴のみを投入しておく
X_test = test_df.drop(["Outcome", "Treatment"], axis=1)


モデル学習

EconMLには様々な計算用アルゴリズムが用意されている。その中の一つで

Meta-Learnersパッケージの中からDR-Learnerのアルゴリズムを使用して

学習する。

# Instantiate Doubly Robust Learner

outcome_model = GradientBoostingRegressor(n_estimators=100, max_depth=6)
pseudo_treatment_model = GradientBoostingRegressor(n_estimators=100, max_depth=6)
propensity_model = RandomForestClassifier(n_estimators=100, max_depth=6,
class_weight='balanced_subsample')

DR_learner = DoublyRobustLearner(outcome_model=outcome_model, pseudo_treatment_model=pseudo_treatment_model,
propensity_model=propensity_model)
# Train DR_learner
DR_learner.fit(Y, T, X)


CATE値の予測

最後にEconMLでCATE値を予測して、ユーザIDと並べてみる。

# Estimate treatment effects on test data

DR_te = DR_learner.effect(X_test)
df_cate = pd.DataFrame({"user-id" : user_ids, "cate" : DR_te})
df_cate.head()
user-id cate
2.121750e+15 -0.188887
2.766490e+17 -0.086540
2.252240e+15 -0.137244
1.563470e+15 -0.188887
1.677000e+15 0.597261


CATEを計算した後

これでユーザ毎のCATE値が計算できた。あとはこの値に従って広告を配信する

ユーザと停止するユーザを振り分ける。ただ、いきなり広告配信を取りやめる

のは勇気がいるので、徐々に予算を減らしていき総コンバージョン数が

変わらないことを検証しながら運用していくのだ。

機械学習はマーケティングの分野にも確実に応用されていき、クリエイティブ

出ない分野はどんどん効率化されていくだろう。