기술 포스트 · perf-marketing
광고 어트리뷰션, 왜 베이지안이 더 안정적인가 — 마케터를 위한 친절한 안내
데이터가 적을 때 last-touch가 흔들리는 이유, 그리고 베이지안 사전(prior)이 어떻게 신규 채널의 ROAS 추정을 안정시키는지 토이 코드 한 묶음으로 풀어봅니다.
“신규 채널 첫 주 ROAS가 8이래. 진짜로?” — 한 번쯤은 이런 메시지를 받아본 적 있죠. 이 글은 그 8이 흔들리는 숫자인 이유와, 베이지안이라는 단어를 빌리지 않고도 더 차분하게 보고할 방법에 대한 이야기예요.
1. 왜 어트리뷰션이 흔들리는가
광고를 운영하다 보면 항상 같은 장면이 반복됩니다. 새 채널을 켜고 첫 주 ROAS가 튀어 보이거나, 캠페인을 끄자마자 전환이 사라지는 듯 보이거나. 우리는 그걸 보고 의사결정을 합니다. 예산을 옮기고, 보고서에 “이 채널 좋아요”라고 쓰고요.
그런데 한 발 떨어져서 보면, 첫 100건 안팎의 데이터로 만든 추정치는 본질적으로 분산이 큽니다. 동전을 100번만 던져서 “이 동전은 53% 확률로 앞면이 나와요”라고 말할 수는 없는 것과 같아요.
2. Last-touch는 무엇이 문제인가
가장 단순한 어트리뷰션 모델은 last-touch입니다. 전환 직전에 클릭된 채널에 100% 공로를 줍니다. 구현이 쉽고 GA4 기본값이라 익숙하죠. 하지만 두 가지 약점이 있어요.
채널 간 공정성
오랫동안 인지도를 쌓아준 디스플레이 광고는 last-touch로 절대 인정받지 못합니다. 마지막에 검색 광고가 가져가니까요.
적은 데이터에서의 흔들림
신규 채널은 첫 주에 전환이 5건만 들어와도 last-touch가 그대로 ROAS를 계산합니다. 5건 기반의 ROAS는 통계적 의미가 거의 없는데도, 보고 자료에서는 마치 진짜 숫자처럼 행동합니다.
이 두 번째 문제를 베이지안이 깔끔하게 다룹니다.
3. “사전(prior)“이라는 한 단어의 마법
베이지안의 핵심을 한 줄로 줄이면 이렇습니다.
“이 채널이 처음이라도, 우리는 광고 채널이 보통 어떻게 움직이는지에 대한 사전 지식이 있다.”
이걸 prior(사전 분포)라고 부릅니다. 새 채널의 첫 주 ROAS를 추정할 때, 0건 데이터로 시작하는 게 아니라 “광고 채널의 ROAS는 보통 1.5~5 사이 어딘가, 평균 2.8 정도”라는 가정을 깔고 시작해요.
데이터가 1건, 10건, 100건 들어올수록 prior의 영향력은 줄어들고, 데이터가 진실에 가까워집니다. 이게 자동으로 분산을 흡수해주는 거예요.
수식 한 줄로 그림 그리기
베이즈 정리를 마케터 친화적 표기로 바꾸면 이렇게 보여요.
각 항의 의미를 풀어 쓰면:
- — prior. “이 채널의 ROAS는 대략 이 정도일 것이다”라는 처음의 믿음
- — likelihood. ROAS가 어떤 값일 때, 우리가 본 매출 데이터가 얼마나 그럴듯한가
- — posterior. 데이터를 본 뒤 갱신된 ROAS 분포 (← 보고서에 쓸 숫자)
데이터가 적을수록 prior가 posterior를 크게 끌고 가고, 데이터가 많을수록 likelihood가 prior를 압도합니다. 데이터 양에 따라 자동으로 균형이 잡힌다는 게 베이지안의 진짜 매력이에요.
분포 모양 — 익숙해지면 일이 빨라진다
ROAS처럼 0보다 크고 한쪽으로 꼬리가 긴 값에는 로그정규(Log-normal) 분포를 prior로 자주 씁니다. 평균과 표준편차로 모양을 정해요.
이고 로 두면, 분포 중앙값이 약 2.8이고 90% 구간이 대략 1.5 ~ 5.5 사이가 되어요. 사내 평균과 자연스럽게 일치합니다.
신뢰 구간을 보고서에 쓰는 방법
posterior가 분포라는 게 핵심입니다. 한 점이 아니에요. 거기서 분위수(quantile)를 뽑으면 그게 신뢰 구간이 됩니다.
코드로는 한 줄(trace.posterior['roas'].quantile([0.05, 0.95]))이면 끝이고, 이 두 숫자만 보고서에 같이 박아두면 의사결정 회의의 톤이 달라집니다.
4. 토이 코드 — 50줄로 직관 잡기
import numpy as npimport pymc as pm
# 시나리오: 신규 채널 첫 주 — 노출 10,000회, 클릭 200건, 전환 5건, 매출 150,000원# 광고비 50,000원, 단순 ROAS = 3.0 (그러나 5건만으로 추정)spend = 50_000revenue_observed = 150_000conversions = 5clicks = 200
# 베이지안 모델: ROAS의 사전 분포를 "광고 채널 평균 2.8, 표준편차 1.0"으로 둠with pm.Model() as model: # prior: 우리 회사 평균 ROAS 분포 roas_prior = pm.Lognormal('roas', mu=np.log(2.8), sigma=0.4)
# likelihood: 관측된 매출이 광고비 × ROAS 부근에서 발생했다고 가정 pm.Normal('observed', mu=spend * roas_prior, sigma=spend * 0.3, observed=revenue_observed)
trace = pm.sample(2000, return_inferencedata=True)
# 결과print(trace.posterior['roas'].mean().item()) # 평균 ROAS 추정치print(trace.posterior['roas'].quantile([0.05, 0.95])) # 90% 신뢰 구간평균 ROAS: 2.9390% 신뢰 구간: 2.21 ~ 3.78last-touch로는 단순히 3.0 (확정)으로 보고됐을 숫자가, 베이지안에서는 “평균 2.93, 90% 확률로 2.21~3.78 사이”로 바뀝니다. 의사결정 회의에서 이 차이는 큽니다.
5. 실무에서 어떻게 쓰나
단계별 신뢰 구간 보고
신규 채널 보고서에 “ROAS 3.0”만 적지 말고, “3.0 (90% CI: 2.2~3.8)” 식으로 구간을 같이 적습니다. 의사결정자가 “오, 아직 흔들리는 숫자구나”를 직관적으로 알 수 있어요.
90% CI를 동료에게 한 번 풀어주는 약속을 정해두면 보고서 톤이 통일됩니다.
“이 채널의 ROAS가 2.2 미만일 가능성 5%, 3.8 초과일 가능성 5%, 그 사이 90%.”
이 한 줄을 위키에 박아두면 다음부터는 모두가 자연스럽게 구간을 머릿속에 그리게 돼요.
채널 간 비교의 안정화
채널 A는 전환 1,000건이 쌓여 ROAS 3.0, 채널 B는 전환 5건으로 ROAS 8.0이라고 합시다. last-touch만 보면 B가 압승이지만, 베이지안 신뢰 구간을 그려보면 A는 [2.85, 3.15], B는 [1.5, 18]일 수 있어요. B는 아직 진짜 모릅니다.
베이지안에서는 “두 채널의 ROAS 차이”도 그냥 분포 빼기로 계산할 수 있어서, 이런 직관적 보고가 가능해집니다.
roas_a = trace_a.posterior['roas'].values.flatten()roas_b = trace_b.posterior['roas'].values.flatten()
diff = roas_b - roas_aprob_b_better = (diff > 0).mean()print(f"B가 A보다 ROAS 높을 확률: {prob_b_better:.0%}")print(f"차이의 90% CI: {np.percentile(diff, [5, 95])}")B가 A보다 ROAS 높을 확률: 71%차이의 90% CI: [-1.2, +6.8]“B가 A보다 좋을 확률 71%“는 회의에서 즉시 와닿는 표현입니다. 동시에 차이의 신뢰 구간이 음수까지 걸쳐 있다는 점도 같이 보고합니다 — 확률 71%여도 실제로는 A가 더 좋을 가능성이 29% 남아 있다는 뜻이에요. 두 숫자를 함께 노출하는 게 정직한 보고입니다.
MMM(Marketing Mix Modeling)과의 연결
규모가 커지면 베이지안은 자연스럽게 MMM으로 진화합니다. Robyn(Meta), LightweightMMM(Google) 같은 오픈소스가 이미 모두 베이지안 기반이에요. 처음부터 이 사고방식에 익숙해두면 MMM 도입 시 학습 곡선이 훨씬 완만합니다.
MMM의 핵심 부품 두 가지도 모두 베이지안 prior로 들어갑니다.
- Adstock (광고 잔향 효과) — 오늘 노출이 며칠에 걸쳐 효과를 남기는가. 보통 사이의 감쇠 계수로 prior를 둡니다.
- Saturation (포화 효과) — 광고비를 두 배 쓴다고 매출이 두 배 되지 않음. Hill 또는 Michaelis–Menten 곡선의 모양 파라미터에 prior를 둡니다.
이 둘이 prior로 깔리면, “이 채널은 이미 포화 상태라 추가 예산은 효율이 떨어진다” 같은 해석이 자동으로 나옵니다. 신규 채널 ROAS 추정 1편짜리 모델이 채널 포트폴리오 전체 최적화로 자연스럽게 확장되는 거예요.
6. prior를 데이터에서 뽑아내기
prior를 임의로 정하면 결과가 흔들립니다. 자사 데이터에서 prior를 뽑아내는 게 정석이에요.
1단계: 과거 채널 ROAS 수집
지난 12개월 동안 어느 정도 데이터가 쌓였던 채널 30~50개의 ROAS 평균을 모읍니다.
import numpy as npimport pandas as pd
# 과거 채널별 ROAS 12개월 평균history = pd.DataFrame({ 'channel': ['Meta_search', 'Meta_video', 'Google_search', 'Naver', 'TikTok', 'Kakao', '...'], 'roas': [3.2, 2.8, 4.5, 2.1, 1.9, 2.5, ...],})
mu_log = np.log(history['roas']).mean()sigma_log = np.log(history['roas']).std()print(f"prior: LogNormal(μ={mu_log:.2f}, σ={sigma_log:.2f})")# prior: LogNormal(μ=1.03, σ=0.42)2단계: prior의 95% 구간이 합리적인지 검사
from scipy.stats import lognormlow, high = lognorm.ppf([0.025, 0.975], sigma_log, scale=np.exp(mu_log))print(f"prior 95% 범위: {low:.1f} ~ {high:.1f}")# prior 95% 범위: 1.2 ~ 6.5대부분의 채널 ROAS가 이 안에 들어오면 prior로 OK. 너무 좁으면(예: 2.53.2) 새 채널의 진짜 분산을 못 잡고, 너무 넓으면(예: 0.150) prior 효과가 사라져 last-touch와 다를 게 없어집니다.
3단계: 채널 군집별 prior
가능하면 prior를 채널 군집(검색 / 디스플레이 / 영상 / SNS)별로 따로 둡니다. 검색은 평균 4.0, 디스플레이는 1.8 식으로 base가 다르니까요. 새 채널을 어느 군집에 편입할지의 의사결정 단계에서 prior가 먼저 결정되는 셈이에요.
수식으로 보면, 각 군집 마다 별도의 prior를 두는 계층적(hierarchical) 모델이 됩니다.
여기서 는 채널 가 속한 군집입니다. 군집 안의 채널들이 prior를 공유하면서, 각 채널의 데이터로 개별 posterior가 갱신돼요. 이런 계층 구조가 “신규 검색 채널은 신규 디스플레이 채널보다 안정적으로 추정된다”는 사실을 자동으로 반영합니다.
7. 자주 빠지는 함정 (확장판)
- prior를 너무 강하게 깔지 말 것. 표준편차 0.1처럼 좁게 두면 데이터가 prior를 못 이깁니다. “내가 정한 답”으로 끝납니다.
- prior를 데이터 기반으로 정할 것. 자사 과거 30~50개 채널의 ROAS 분포를 보고 평균·표준편차를 잡습니다. 임의로 정하면 신뢰성이 떨어져요.
- 신뢰 구간을 보고서에서 빠뜨리지 말 것. 평균값만 적으면 last-touch와 똑같아 보입니다.
- 모델보다 데이터 정합성이 우선. 어트리뷰션 윈도우, 디바이스 매칭, 쿠키 정책이 더 큰 흔들림 요인일 때가 많아요.
- MCMC 수렴 점검을 빠뜨리지 말 것. PyMC가 출력하는 R-hat이 1.01보다 크거나 ess(effective sample size)가 너무 작으면 샘플링이 안정되지 않은 신호. 결과를 믿지 말고 샘플 수를 늘리거나 prior를 다시 봅니다.
- 신규 채널 첫 주에 결정하지 말 것. 90% CI 폭이 평균값의 50% 이상이면 아직 데이터가 부족합니다. 폭이 30% 이내로 줄어들 때까지 기다립니다.
- 자동 입찰 시스템 입력으로 raw posterior를 흘리지 말 것. 분산이 큰 신규 채널이 우연히 큰 예산을 받을 수 있어요. 시스템 입력값으로는 보수적인 분위수(예: 25%)를 씁니다.
8. 자주 받는 질문
Q. 베이지안이 그렇게 좋으면 왜 다 안 쓰나요?
세 가지 이유. (1) 학습 비용 — 통계 배경이 없으면 prior·posterior 개념이 어렵게 느껴집니다. (2) 도구 의존성 — PyMC·Stan 같은 라이브러리가 새로 들어옵니다. (3) 의사결정 문화 — “확률 71%로 좋다”가 “유의했다/안 했다”보다 의사소통 비용이 약간 큽니다. 그래서 GA4 기본값은 last-touch로 남아 있어요. 이 글은 그 비용을 줄이는 첫 걸음입니다.
Q. 프리퀀티스트 신뢰 구간과 어떻게 다른가요?
같은 90% CI 라도 의미가 미묘하게 달라요. 프리퀀티스트의 90% CI는 “이 실험을 100번 반복하면 90번은 진짜 ROAS를 포함하는 구간”, 베이지안의 90% credible interval은 “진짜 ROAS가 이 구간에 있을 확률 90%”. 후자가 마케터 직관에 훨씬 가깝고, 보고서에서 “확률 90%로…”라고 말할 수 있는 건 베이지안뿐이에요.
Q. 신뢰 구간이 너무 넓으면 의사결정을 어떻게 하나요?
폭이 평균값의 50% 이상이면 “아직 결정할 단계가 아니다”가 맞는 답입니다. 그 자체가 의사결정 — 데이터 더 모으기. 마케터가 가장 자주 빠지는 함정이 “결론은 내야 하니까 점추정이라도 쓴다”인데, 폭이 넓다는 사실 자체를 보고하는 게 더 정직하고 장기적으로 성과도 좋습니다.
Q. PyMC 대신 더 가벼운 도구는 없나요?
R 사용자라면 brms나 rstanarm, Python에서는 NumPyro(JAX 기반, GPU 가속)나 PyMC가 표준입니다. 분포가 단순한 경우라면 베이지안 conjugate prior 공식만으로 손계산도 가능해요 (Beta–Binomial 같은 케이스). 첫 도입은 conjugate 공식 → 단일 채널 PyMC → 채널 군집 hierarchical 모델 순서가 학습 부담이 가장 적습니다.
9. 마치며
베이지안 어트리뷰션은 새로운 마법이 아닙니다. “적은 데이터로는 적게 자신하자”라는 상식을 수학으로 옮긴 것뿐이에요. 마케터 입장에서 가장 큰 가치는 코드 그 자체보다, 신뢰 구간을 함께 보고하는 습관입니다.
다음 주 보고서에서 신규 채널 ROAS 옆에 ±를 한 번만 붙여보세요. 회의 분위기가 달라집니다. 그 다음 한 달 안에는, 채널 비교에서 “B가 A보다 좋을 확률 71%“라는 표현이 자연스럽게 나오는 팀이 됩니다. 그게 베이지안이 주는 진짜 변화예요.