広告プラットフォームへのオーディエンス同期 ― カスタムと類似
予測スコアやセグメントを広告プラットフォームのカスタムオーディエンス・類似拡張として活用する方法を扱います。同意とハッシュ化の前提、更新頻度、マッチ率といった実務の勘所を、計測連載で作った同意基盤の上で整理します。
by Shin
自社のセグメントを広告配信にどう効かせるのか。答えは「カスタム/類似オーディエンスとして媒体へ同期する」ですが、成否を分けるのは同期という行為そのものではありません。同意・ハッシュ化・更新頻度・マッチ率、そして何より種となるセグメントの質が、配信に効くかどうかを決めます。この記事では、その実務と前提を、過去の受託案件をもとに設計した架空のD2C企業SOLNA(スキンケア)のChurn/高LTVスコアを種にして整理します。
前提は計測設計の連載(なぜ計測は静かに壊れるのか ― 計測設計という土台)で作った同意・ハッシュ化・識別の基盤、入力は第2回のReverse ETLとモデル駆動オーディエンスです。本記事は「マートで定義したオーディエンスを、広告という特定のデスティネーションへ届けるときに固有の論点」だけを足します。新しい手法の話ではなく、warehouseの成果物を媒体が受け取れる形へ変換し、腐らせずに回し続ける——その手順と監視の話です。
カスタムオーディエンスと類似拡張
二つは別物です。カスタムオーディエンスは、自社の1stパーティリスト(顧客の連絡先や識別子)を媒体側のアカウントに突合して作る配信対象です。「いま手元にいる人」に当てる、あるいはその逆に除外するために使います。類似拡張(lookalike/similar)は、種(シード)を渡すと媒体が「その種に似た」ユーザーを広げてくれる仕組みで、「まだ手元にいない、似た人」を新規獲得で狙うものです。
ここで外してはいけない一点があります。類似拡張の精度の天井は、ほぼ種の質で決まります。広く薄い種を渡せば、媒体は「薄く広い特徴」を学習して薄い拡張を返します。逆に、較正した(予測確率を実際の発生率に合わせた)スコアで純度を上げた種は、小さくても拡張の方向が定まります。「とりあえず全顧客を種にする」のが最ももったいない使い方で、これは後段の「効くセグメントの作り方」で具体化します。
同意とハッシュ化の前提(計測設計の連載の上で)
活性化は同意基盤の上にしか乗りません。広告目的の利用に同意していない顧客は、同期の母集合から機械的に外します。これは運用ルールではなく、SQLの WHERE 句として実装に埋め込むべきものです。「あとで気をつける」は必ず漏れます。
媒体へ渡すのは、正規化したうえでSHA-256でハッシュ化した識別子だけです。ハッシュ化前のメールアドレスや電話番号をwarehouseの外へ出してはいけません。ハッシュ化はwarehouse側で行い、同期パイプラインには平文を一切流さない設計にします。
正規化を揃えることが、地味ですが効きます。ハッシュは1文字でも違えば完全に別物になるため、媒体側の正規化規則と自社の前処理がずれると、本来マッチするはずの人が落ちます。最低限、メールは小文字化+前後空白の除去、電話はE.164形式(国番号付与の設計が必要)に揃えます。
個人データ(PII)の置き場所も設計事項です。メールや電話は分析用テーブルから分離し、アクセスを絞ったPII専用テーブルに置きます。同期のたびに最小限のジョインで取り出し、出力はハッシュ済みの列だけにする——分析基盤とPIIを混載しないことが、横漏れの確率を一段下げます。
-- 1) モデル駆動オーディエンス:同意済み・高スコア顧客を、ハッシュ化IDで吐き出す
-- 入力: predictions(Kaggle連載の較正済みスコア)/ customer_consent(計測設計の連載の同意基盤)/ customer_pii(PII分離)
WITH eligible AS (
SELECT
p.customer_id,
p.score_calibrated -- 較正済み確率(0〜1)
FROM `mart.predictions_churn` AS p
JOIN `mart.customer_consent` AS c USING (customer_id)
WHERE c.ad_personalization = TRUE -- 広告利用に同意のある顧客だけ
AND p.score_calibrated >= 0.65 -- 閾値(後述:較正が前提)
),
hashed AS (
SELECT
e.customer_id,
-- email: 小文字化・トリム後に SHA-256(16進)
TO_HEX(SHA256(LOWER(TRIM(pii.email)))) AS email_sha256,
-- phone: 数字以外を除去し国番号付きにしてから SHA-256(下は日本固定の簡略例)
TO_HEX(SHA256(
CONCAT('+81', REGEXP_REPLACE(pii.phone, r'[^0-9]', ''))
)) AS phone_sha256
FROM eligible AS e
JOIN `pii.customer_pii` AS pii USING (customer_id)
)
SELECT customer_id, email_sha256, phone_sha256
FROM hashed;更新頻度とマッチ率
マッチ率は、渡したハッシュのうち媒体がアカウントに突合できた割合です。自社・受託の運用では、メール単体でおおむね5〜7割、メールと電話の複合キーで6〜8割のレンジに収まることが多く、リストの鮮度や顧客属性で上下します(数値は匿名化・レンジ表記)。
ただし、マッチ率を上げること自体を目的化しないことが大事です。マッチ率が多少低くても、純度の高い種であれば配信価値はあります。逆に、マッチ率が極端に低い(数%)ときは、配信の良し悪しを論じる前に正規化のミスを疑うべきです。たいていは小文字化漏れ・空白・電話の国番号まわりが原因で、ここはハッシュ化前の前処理を直すだけで戻ります。
鮮度の管理は、活性化が「やりっぱなし」になる最大の分岐点です。古い種は腐ります。解約・同意撤回・スコア閾値割れを反映し続けないと、配信対象に「もう対象でない人」が残り、無駄打ちと機会損失が静かに積み上がります。実務では、変動の速いセグメント(Churnリスクなど)は日次の差分同期、安定したセグメント(高LTV層)は週次の全置換、というようにセグメントの変動速度に頻度を合わせます。媒体側のメンバーシップ保持期間(一定日数で自動失効する設定)も、鮮度の保険として併用します。
同期は冪等に、つまり差分(add/remove)で回します。毎回フルで投げ直すより、前回スナップショットとの差分だけを送るほうが、上限・レート制限・コストのいずれにも優しく、そして remove 側に同意撤回・解約・閾値割れが自動的に集まるという利点があります。
-- 2) 差分同期(冪等):前回同期スナップショットと比較して add / remove を出す
WITH current_set AS (
SELECT customer_id FROM `mart.audience_highvalue_today` -- 今日の対象(block 1 の母集合)
),
last_synced AS (
SELECT customer_id FROM `ops.audience_synced_snapshot` -- 前回同期済み
)
SELECT customer_id, 'add' AS op FROM current_set
WHERE customer_id NOT IN (SELECT customer_id FROM last_synced)
UNION ALL
SELECT customer_id, 'remove' AS op FROM last_synced
WHERE customer_id NOT IN (SELECT customer_id FROM current_set);
-- remove には「同意撤回・解約・スコア閾値割れ」が自動で入る(いずれも current_set の WHERE で落ちるため)# 3) 差分をプラットフォームへ反映する最小構成(add / remove をバッチ送信)
# 前提: ハッシュ化と同意ゲートは warehouse 側で適用済み。生PIIはここに来ない。
import time
def sync_audience(deltas, push_add, push_remove, batch=10_000):
adds = [d for d in deltas if d["op"] == "add"]
removes = [d for d in deltas if d["op"] == "remove"]
for fn, rows in ((push_add, adds), (push_remove, removes)):
for i in range(0, len(rows), batch):
chunk = rows[i:i + batch]
for attempt in range(3): # 失敗時は指数バックオフでリトライ
if fn(chunk): # 各媒体APIの upsert / remove(最小構成)
break
time.sleep(2 ** attempt)
# push_add / push_remove は媒体ごとに実装する(Meta カスタムオーディエンス / Google カスタマーマッチ 等)。
# 必須キー・フィールド名・件数上限はdocs依存で変わるため、最新の公式ドキュメントで確認する。効くセグメントの作り方(予測スコアの閾値)
種は、Kaggle連載・dbt連載が出力した予測スコアやマートから作ります。ここで効いてくるのが較正です。閾値(block 1 の score_calibrated >= 0.65)が件数と純度に素直に効くのは、確率が較正されている——スコア0.65の人が実際におよそ65%の確率で当該事象を起こす——ときだけです。較正されていないスコアに閾値を引いても、「0.65」という数字が何件・どんな純度を意味するのかが定まらず、種サイズが読めません。評価指標と較正の扱いはKaggle連載の予測と評価指標に譲りますが、活性化側の要点は「閾値で種を切るなら、その前にスコアを較正しておく」です。
閾値はボリュームとのトレードオフです。上げれば純度は上がりますが種は小さくなります。類似拡張は種が数百だと拡張が不安定になりがちで、数千〜数万の規模が扱いやすいレンジです。つまり閾値は「純度を取りにいく」一方で「類似が成立する最小サイズを割らない」という二つの制約の間で決めます。純度と種サイズのどちらを優先すべきかは目的次第で、ここは機械的には決まりません。
スコアの向きで使い分けるのも実務的です。Churnリスクが高い層は、新規獲得の類似の種にするとむしろ「解約しやすい人に似た人」を集めてしまうので、獲得キャンペーンからは除外に回し、別途win-backのカスタムオーディエンスとして扱う——といった切り分けをします。高LTVのpropensityが高い層は、類似の種として素直に効きます。同じスコアでも、種にするか除外にするかで効果は逆になります。
最後にもう一度だけ。ここまでは「誰を配信対象にするか」を予測で決める話です。その対象に当てたことが成果を増やしたかどうかは、配信前に切ったholdoutでしか測れません。propensityで始め、因果はholdoutで確かめる——この分離を崩さないことが、活性化を「やりっぱなし」にしない唯一の作法です。具体的なholdout設計は第5回、増分の検証は第6回で扱います。
章末チェックリスト
- カスタムオーディエンスと類似拡張の違い(手元の人 vs 似た人/当てる vs 除外)を言える
- 同意のない顧客を、運用ルールでなく
WHERE句で母集合から外している - 媒体へ渡すのは正規化+SHA-256のハッシュのみで、生PIIはwarehouse外へ出していない
- PIIを分析テーブルから分離し、出力はハッシュ済み列だけにしている
- マッチ率を経路別に把握し、極端に低いときは正規化ミスを先に疑える
- 同期は差分(add/remove)で冪等に回し、同意撤回・解約・閾値割れが自動で
removeに入る - セグメントの変動速度に同期頻度を合わせ、媒体側の保持期間も併用している
- 種を切る前にスコアを較正し、純度と種サイズの両睨みで閾値を決めている
- スコアの向きで「種にする/除外にする」を切り分けている
- 「対象にした≠効いた」を理解し、検証はholdout(第5・6回)に回している
ここまでは、warehouseのセグメントを広告の配信面へ「届け、腐らせずに回す」までの実装でした。次回は逆向き——媒体に実成果で最適化させるために、成約やLTVをConversions API・オフラインCVで送り返す運用を扱います。判断レイヤーの「何を測り、何を動かすか」は本記事のbridge先(記述・予測編)に整理しています。