멀티암드 밴딧, 지는 변형에 트래픽을 덜 태우는 실험

들어가며

배너 문구 세 개를 놓고 A/B 테스트를 돌린다고 하자. A안, B안, C안. 트래픽을 3분의 1씩 균등하게 나눠서 2주를 돌린다. 그리고 3일쯤 지나면 대략 감이 온다. C안 전환율이 눈에 띄게 높고, A안은 확실히 처진다.

그런데도 실험은 규칙대로 2주를 채운다. 그 2주 동안 A안은 계속 전체 트래픽의 3분의 1을 받는다. 이미 지고 있는 게 보이는데도 말이다. 실험이 끝날 때쯤 세어 보면, 확실히 처지는 변형 하나에 수천 명의 사용자를 태운 셈이다. 그 사용자들은 더 나은 화면을 볼 수도 있었는데 지는 화면을 봤다.

여기서 자연스러운 질문이 나온다. 지는 게 보이면 그쪽 트래픽을 줄이고 이기는 쪽으로 옮기면 안 되나? 그걸 실험 도중에, 자동으로 하는 방식이 멀티암드 밴딧(multi-armed bandit)이다. 이 글은 밴딧이 정확히 무슨 일을 하는지, 언제 쓰면 좋고 언제 오히려 고정 A/B가 나은지, 그리고 밴딧이 절대 대신 못 해주는 일이 무엇인지를 정리한다.

탐색과 활용 다시 보기

밴딧을 이해하려면 먼저 두 행동을 구분해야 한다. 활용(exploitation)과 탐색(exploration)이다. 전에 A/B 테스트가 국소 최적에 갇히는 이야기를 하면서 이 두 개념을 꺼낸 적이 있는데, 여기서 다시 짚고 간다.

활용은 지금까지 아는 최선을 더 쓰는 것이다. C안이 제일 좋아 보이면 C안에 트래픽을 몰아주는 일. 탐색은 아직 잘 모르는 후보를 일부러 더 찔러보는 것이다. A안이 지금 처져 보여도 표본이 적어서 우연히 낮게 나온 걸 수도 있으니, 확인차 트래픽을 조금 흘려보는 일.

이 둘은 근본적으로 상충한다. 지금 아는 최선에 전부 걸면(순수 활용) 사실 더 좋았을지 모를 후보를 영영 못 본다. 반대로 계속 골고루 찔러보기만 하면(순수 탐색) 이미 지는 게 뻔한 변형에도 트래픽을 낭비한다. 고정 A/B 테스트는 이 스펙트럼에서 순수 탐색 쪽 끝에 가깝다. 실험이 끝날 때까지 트래픽을 정해진 비율로 고정해 두고, 승패 판정은 마지막에 한 번 몰아서 한다. 실험 중간에 알게 된 사실을 트래픽 배분에 반영하지 않는다.

밴딧은 이 스펙트럼의 중간에서 움직인다. 실험을 돌리면서 얻은 데이터로, 매 순간 활용과 탐색의 비율을 조금씩 다시 정한다. 지는 게 뚜렷해질수록 그쪽 탐색을 줄이고 이기는 쪽 활용을 늘린다. 슬롯머신에 빗대면 이렇다. 팔(arm)이 여러 개 달린 슬롯머신 앞에 앉았는데, 어느 팔이 돈을 잘 주는지 모른다. 골고루 당겨보며 알아내야(탐색) 하지만, 잘 주는 팔을 찾았으면 거기 집중해야(활용) 딴 돈이 커진다. 멀티암드 밴딧이라는 이름이 여기서 왔다. 팔이 여럿 달린 도박 기계라는 뜻이다.

밴딧이 하는 일

밴딧이 매 순간 저울질하는 대상은 후회(regret)라는 개념으로 정리된다. 후회는 이런 뜻이다. 만약 처음부터 제일 좋은 변형을 알고 거기에만 트래픽을 다 태웠다면 얻었을 성과에서, 실제로 얻은 성과를 뺀 값. 지는 변형에 트래픽을 태울수록 후회가 쌓인다. 들어가며의 예시에서 A안에 태운 트래픽이 바로 후회다. 밴딧의 목표는 이 후회를 최대한 줄이는 것이다. 다시 말해, 지는 팔에 트래픽을 되도록 덜 낭비하면서 이기는 팔을 빨리 찾아 몰아주는 것.

그럼 활용과 탐색을 구체적으로 어떻게 섞을까. 대표적인 방법 세 가지를 쉬운 것부터 본다.

첫째, 엡실론-그리디(epsilon-greedy)다. 규칙이 아주 단순하다. 매번 사용자가 올 때, 정해둔 작은 확률(엡실론, 예를 들어 10%)만큼은 후보 중 하나를 무작위로 골라 보여준다. 이게 탐색이다. 나머지 90%는 지금까지 전환율이 제일 높은 변형을 보여준다. 이게 활용이다. 90%는 최선에 쓰고 10%는 딴 데를 찔러보는 셈이다. 단순해서 이해하기 쉽지만, 탐색에 쓰는 10%를 변형마다 똑같이 나눠 쓰는 게 약점이다. 확실히 지는 변형이든 아슬아슬한 변형이든 탐색 트래픽을 똑같이 받는다.

둘째, 톰슨 샘플링(Thompson sampling)이다. 이건 각 변형의 전환율을 하나의 확정된 숫자로 보지 않고, 그럴듯한 값들의 분포로 들고 있다. 데이터가 적을 땐 이 분포가 넓게 퍼져 있어서 불확실하다. 데이터가 쌓일수록 좁아진다. 사용자가 올 때마다 각 변형의 분포에서 값을 하나씩 뽑아 보고, 그중 제일 높은 값이 나온 변형을 보여준다. 쉽게 말해, 이 변형이 진짜 최고일 확률에 비례해서 트래픽을 배분하는 셈이다. 아직 불확실한 변형은 가끔 운 좋게 높은 값이 뽑혀 탐색 기회를 얻고, 확실히 좋은 변형은 자주 뽑혀 트래픽을 많이 받는다. 탐색과 활용이 분포의 폭 안에 자연스럽게 녹아 있다.

셋째, UCB(upper confidence bound)다. 우리말로 신뢰 상한이다. 이건 각 변형을 평가할 때 지금까지의 평균 전환율에 불확실성만큼의 가산점을 얹는다. 데이터가 적어서 불확실한 변형일수록 이 가산점이 크다. 그래서 아직 덜 시험해 본 변형이 실제 평균이 좀 낮아도 가산점 덕에 위로 올라와 탐색 기회를 얻는다. 표어로 요약하면, 불확실하면 일단 낙관적으로 봐준다는 원칙이다. 시험을 거듭할수록 불확실성이 줄어 가산점이 작아지고, 그러면 진짜 실력(평균 전환율)으로 순위가 정해진다.

세 방법의 결은 다르지만 공통점은 하나다. 매 순간 최선에 전부 걸지도 않고, 매 순간 골고루 나누지도 않는다. 지금 아는 정보에 따라 활용과 탐색의 비율을 스스로 조절한다.

엡실론-그리디를 직접 돌려보기

말로만 들으면 손에 안 잡히니, 제일 단순한 엡실론-그리디를 R로 짧게 돌려보자. 변형 세 개의 진짜 전환율이 각각 10%, 12%, 16%라고 하자(현실에선 우리가 이 값을 모른다). 사용자 2만 명이 차례로 들어오고, 엡실론은 10%로 둔다. 이걸 고정 균등 분할(세 변형에 트래픽을 3분의 1씩)과 비교한다.

rates <- c(0.10, 0.12, 0.16)   # 세 변형의 진짜 전환율 (우리는 모른다고 가정)
N <- 20000                     # 들어오는 사용자 수
eps <- 0.10                    # 탐색 확률
K <- length(rates)

counts  <- integer(K)          # 변형별 노출 수
rewards <- integer(K)          # 변형별 전환 수

for (t in seq_len(N)) {
  if (runif(1) < eps) {
    arm <- sample(K, 1)                         # 탐색: 무작위로 하나
  } else {
    means <- ifelse(counts > 0, rewards / counts, 0)
    arm <- which.max(means)                     # 활용: 지금까지 최선
  }
  reward <- rbinom(1, 1, rates[arm])            # 이 변형이 전환됐나 (1/0)
  counts[arm]  <- counts[arm]  + 1
  rewards[arm] <- rewards[arm] + reward
}

cat("변형별 노출 수:", counts, "\n")
cat("변형별 관측 전환율:", round(rewards / counts, 3), "\n")
cat("밴딧 총 전환 수:", sum(rewards), "\n")

# 비교: 고정 균등 분할 (3분의 1씩)
per <- N %/% K
fixed <- sum(rbinom(K, per, rates))
cat("고정 균등분할 총 전환 수:", fixed, "\n")

한 번 돌린 결과는 이렇게 나온다(무작위라 실행마다 숫자는 조금씩 달라진다).

변형별 노출 수: 908 709 18383
변형별 관측 전환율: 0.093 0.116 0.16
밴딧 총 전환 수: 3103
고정 균등분할 총 전환 수: 2530

노출 수를 보자. 2만 명 중 1만 8천여 명이 제일 좋은 세 번째 변형(전환율 16%)으로 갔다. 처지는 첫 번째 변형은 900명 남짓만 받았다. 밴딧이 초반 탐색으로 세 변형을 조금씩 시험해 본 뒤, 이기는 쪽으로 트래픽을 몰아준 것이다. 결과적으로 밴딧은 3103번 전환을 얻었고, 트래픽을 끝까지 3분의 1씩 고정한 쪽은 2530번에 그쳤다. 같은 사용자 수를 놓고 570여 번의 전환을 더 건진 셈이다. 이 차이가 바로 앞 절에서 말한 후회의 감소다. 지는 변형에 태울 뻔한 트래픽을 이기는 변형으로 돌린 만큼이 성과로 돌아왔다.

언제 밴딧이 낫고 언제 고정 A/B가 나은가

밴딧이 이렇게 이득이면 항상 밴딧을 쓰면 되지 않나 싶지만, 그렇지 않다. 둘은 목적이 다른 도구다.

밴딧이 빛나는 상황은 이렇다. 첫째, 변형이 많을 때다. 후보가 다섯 개, 열 개로 늘면 고정 A/B로 다 균등하게 돌리는 건 지는 변형에 그만큼 트래픽을 많이 버리는 일이다. 밴딧은 지는 후보들을 빠르게 쳐내고 남은 트래픽을 유망한 쪽에 집중시킨다. 둘째, 캠페인이 짧을 때다. 하루짜리 이벤트 배너나 주말 프로모션처럼 실험 자체가 곧 실전이라, 실험 기간의 손실을 그대로 떠안는 경우. 이럴 땐 배우면서 동시에 이겨야 하므로 밴딧이 맞다. 셋째, 목표가 후회 최소화 그 자체일 때다. 정밀한 결론보다 실험 기간 전체의 총 전환을 한 건이라도 더 건지는 게 중요한 경우.

반대로 고정 A/B가 나은 상황도 분명하다. 핵심은 밴딧이 트래픽을 이기는 쪽으로 몰아준다는 바로 그 성질이 통계적 판단에는 방해가 된다는 점이다.

첫째, 효과 크기를 정확히 재고 싶을 때다. 이 변형이 저 변형보다 정확히 몇 %p 나은지, 그 차이의 신뢰구간이 어디까지 걸쳐 있는지 깔끔하게 추정하려면 각 변형에 충분한 표본이 고르게 쌓여야 한다. 그런데 밴딧은 지는 변형의 표본을 일부러 줄인다. 위 시뮬레이션에서 첫 번째 변형은 900명밖에 안 받았다. 이 표본으로는 그 변형의 전환율을 정밀하게 추정하기 어렵다. 정확한 효과 크기(effect size)와 신뢰구간이 필요하면, 트래픽을 균등하게 고정하는 A/B가 낫다. 효과 크기와 유의성을 왜 함께 봐야 하는지는 지난 p-value 글에서 따로 다뤘다.

둘째, 비정상성(non-stationarity)이 의심될 때다. 비정상성은 시간에 따라 최적의 답이 바뀌는 상황을 말한다. 예를 들어 평일엔 A안이 낫다가 주말엔 B안이 나은 경우, 아침 사용자와 저녁 사용자의 취향이 다른 경우, 신제품 출시로 갑자기 선호가 뒤집히는 경우다. 밴딧은 초반 데이터로 이기는 팔을 정하고 그쪽에 트래픽을 몰아주기 때문에, 나중에 판이 바뀌어도 한동안 옛 우승자에 갇힐 수 있다. 이미 다른 팔에 트래픽을 거의 안 주고 있어서, 판이 바뀐 걸 알아챌 데이터가 안 들어오기 때문이다. 이런 변화를 정면으로 다루려면 최근 데이터에 가중치를 더 주는 식으로 밴딧을 손봐야 하는데, 그럴 바엔 애초에 각 변형을 꾸준히 관측하는 고정 설계가 판단하기 쉽다.

정리하면 이렇다. 밴딧은 실험 기간의 손실을 줄이는 도구고, 고정 A/B는 정확한 결론을 얻는 도구다. 실전 성과가 급하면 밴딧, 믿을 만한 숫자가 급하면 A/B다.

밴딧도 못 하는 일

여기서 가장 중요한 한계를 짚어야 한다. 밴딧은 이미 손에 쥔 후보들 사이에서 트래픽을 나누는 도구일 뿐이다. 근본적으로 다른 새 후보를 만들어내지는 못한다.

앞의 A/B 테스트가 국소 최적에 갇히는 이야기에서 이걸 지형에 빗댔다. 지금 서 있는 봉우리를 아무리 정밀하게 오르는 도구라도, 골짜기 건너에 있는 더 높은 산으로 데려다주지는 못한다는 이야기였다. 밴딧도 똑같다. 밴딧이 하는 일은 이미 만들어 놓은 A안, B안, C안 사이에서 트래픽 배분을 최적화하는 것이다. 셋 다 같은 봉우리의 서로 다른 지점이라면, 밴딧은 그중 제일 높은 지점을 빨리 찾아줄 뿐이다. 골짜기 건너 두 배 높은 산에 해당하는 완전히 다른 구조의 화면은 후보 목록에 없으면 밴딧의 시야에 아예 들어오지 않는다.

쉽게 말해, 밴딧은 잴 대상을 만들지 못한다. 배너 문구 세 개를 넣어주면 그 셋 중 최선을 골라주지만, 문구가 아니라 배너를 없애고 화면 흐름을 통째로 바꾸는 게 정답일 가능성은 밴딧이 제안하지 않는다. 그런 먼 산 후보는 사용자 이해에서 나온다. 인터뷰, 관찰, 정성 조사가 지금 후보들이 다 같은 낮은 봉우리에 몰려 있을지 모른다는 다른 가설을 만든다. 밴딧과 A/B는 그렇게 만들어진 후보들 중 어느 게 실제로 높은지를 재는 자에 가깝다. 잴 대상 자체를 만드는 건 사람이다.

이 구분을 흐리면 위험하다. 밴딧이 세 변형 중 하나로 트래픽을 잘 몰아주는 걸 보고 우리 최적화가 자동으로 돌아간다고 안심하는 순간, 세 변형이 전부 낮은 봉우리에 있다는 가능성은 시야에서 사라진다. 밴딧은 주어진 후보 안에서의 손실만 줄인다. 후보 밖에 더 높은 산이 있는지는 밴딧의 관심사가 아니고, 알려주지도 않는다.

마무리

들어가며의 장면으로 돌아가자. 지는 게 뻔한 A안에 2주 내내 트래픽 3분의 1을 태우던 그 낭비. 멀티암드 밴딧은 바로 그 낭비를 겨냥한 도구다. 실험을 돌리면서 얻은 데이터로 활용과 탐색의 비율을 매 순간 다시 정하고, 지는 쪽 트래픽을 이기는 쪽으로 옮겨 후회를 줄인다. 변형이 많거나, 캠페인이 짧거나, 실험 기간의 성과 자체가 급할 때 특히 잘 맞는다.

다만 두 가지를 잊으면 안 된다. 하나, 정확한 효과 크기나 비정상성 판단이 필요하면 트래픽을 균등하게 고정하는 A/B가 여전히 낫다. 밴딧이 지는 표본을 줄이는 성질이 여기선 오히려 걸림돌이다. 둘, 밴딧은 알려진 후보들 사이의 배분일 뿐, 판을 뒤집을 새 후보를 만들지는 못한다. 그건 도구가 아니라 사람이 하는 일이다.

그래서 밴딧을 쓴다는 건 손 놓고 자동화에 맡기는 게 아니다. 어떤 후보들을 저울 위에 올릴지 정하고, 지금이 후보 안에서 배분을 최적화할 때인지 아니면 후보 목록 자체를 다시 짤 때인지 판단하는 일이 그대로 남는다. 밴딧은 저울 위 후보들의 손실을 줄이는 자일 뿐, 무엇을 저울에 올릴지는 정해주지 않는다. 그 결정은 디자이너와 팀의 몫이다.