• **베이지안 최적화(Bayesian Optimization, BO)**는 “평가 한 번이 비싼 블랙박스 함수”를 가장 적은 횟수로 최적화하는 순차적 전략
  • 미분도 안 되고 수식도 모르는 함수 f(x)를, 확률모형(surrogate)으로 “대신 상상”하면서 다음에 시도할 지점을 똑똑하게 고르는 방법
  • A/B 테스트 맥락에선 “A와 비교할 다음 후보 B를 어디로 둘지”를 정하는 의사결정 엔진

해당 개념이 필요한 이유

  • f(x)를 한 번 평가하는 데 일주일 트래픽 / 큰 비용이 든다 (예: 한 설정으로 B 테스트 1회). 그러니 시도 횟수 자체를 아껴야 한다
  • 가능한 설정(후보 B)이 연속적이거나 조합이 폭발하면, 모든 걸 A/B로 돌리는 건 불가능하다 (B 테스트의 한계)
  • 그리드/랜덤 탐색은 이전 결과에서 배우지 않는다. BO는 지금까지의 결과로 함수 모양을 추정해, 다음에 가장 이득 볼 곳만 찍는다

AS-IS — 모든 조합을 A/B로 (그리드/완전탐색)

flowchart TB
  G["후보 설정 100개<br/>(할인율 × 버튼색 × 문구 ...)"]
  G --> T1["설정1 A/B 1주"]
  G --> T2["설정2 A/B 1주"]
  G --> T3["..."]
  G --> T100["설정100 A/B 1주"]
  T1 --> R["100주 소요 / 트래픽 폭발<br/>이전 결과에서 못 배움"]
  T2 --> R
  T3 --> R
  T100 --> R

TO-BE — BO 루프로 똑똑하게 (몇 번 만에 수렴)

flowchart LR
  S["관측된 결과 몇 개"] --> M["surrogate 모형<br/>(GP: 평균 μ + 불확실성 σ)"]
  M --> A["acquisition 함수<br/>다음에 이득 볼 곳을 점수화"]
  A --> N["다음 설정 1개 선택<br/>= 다음 A/B 테스트"]
  N --> E["평가(실험 1회)"]
  E --> S
  E -. 수렴 .-> Best["최적 설정"]

BO 루프 4단계

  1. surrogate model 적합: 지금까지의 (설정, 성과) 점들로 Gaussian Process를 적합한다 → 모든 후보 지점에서 평균 μ(x)와 불확실성 σ(x)를 얻는다.
  2. acquisition 함수 계산: μσ“여기 찍으면 이득 볼 기대치” 라는 하나의 점수로 변환한다.
  3. 다음 지점 선택: acquisition을 최대화하는 x를 고른다 (= 다음에 돌릴 A/B 설정).
  4. 평가 후 업데이트: 실제로 실험해 성과를 관측 → surrogate 갱신 → 1번으로 반복.

핵심은 “비싼 진짜 함수 f 대신, 싼 surrogate를 최적화한다” 는 것. acquisition을 푸는 건 빠르니, 진짜 비싼 평가는 꼭 필요한 곳에서만 한다.

예시로 따라가기 — 할인율 하나를 튜닝한다고 하자

상황: 할인율을 0~30% 중 하나로 정해 전환율을 최대화하고 싶다. 즉 f(할인율) = 전환율이고, f 한 번 평가 = 실제 B 테스트 1주 + 트래픽 소모다.

지금까지 비싼 평가 3번으로 모은 점:

할인율 x전환율 (관측 f)
5%2.1%
15%3.4% ← 현재 최고 f⁺
25%2.8%

이제 루프 4단계를 이 데이터에 그대로 대입한다:

  1. surrogate 적합: 이 3점으로 Gaussian Process를 적합한다. 그러면 0~30% 사이 아무 할인율이나 물어봐도 μ(예측 전환율)와 σ(불확실성)를 즉시·공짜로 답해준다.
    • 10% → μ≈2.8%, σ≈0.4% (관측점들 사이라 비교적 확신)
    • 18% → μ≈3.2%, σ≈0.5% (최고점 15% 바로 옆이라 넘을 여지가 있음)
    • 30% → μ≈2.5%, σ≈1.3% (관측 범위 끝이라 불확실 큼)
  2. acquisition 계산: 각 후보를 EI로 점수화한다 → 18%가 “현재 최고 3.4%를 넘을 기대값”이 가장 크다. (10%는 μ가 낮아 넘기 어렵고, 30%는 σ는 크지만 μ가 너무 낮다.)
  3. 다음 지점 선택: surrogate에 수천 개 할인율을 공짜로 물어 acquisition이 최대인 18%를 찾는다.
  4. 평가 후 업데이트: 18% 실제 A/B 1주를 돌려 전환율을 관측 → 4번째 점으로 추가 → 다시 1번으로.

왜 진짜 f가 아니라 surrogate를 최적화하나 (이게 핵심 논리)

진짜 f를 직접 최적화surrogate를 최적화 (BO)
0~30%를 0.1% 간격으로 다 보려면A/B 300번 = 300주GP에 300개 질의 = 밀리초·공짜
비싼 평가(A/B) 횟수후보 수만큼 (폭발)한 라운드에 딱 1번 — surrogate가 고른 곳에서만
  • 탐색(어디가 좋을지 뒤지는 일)은 공짜인 surrogate 위에서 수천 번 한다.
  • 진짜 비싼 f(= A/B 1주)surrogate가 “여기”라고 찍은 한 점에서만 쓴다.
  • 그래서 f를 몇 번만 평가하고도 최적 근처에 도달한다 — “비싼 f 대신 싼 surrogate를 최적화한다”는 말의 정확한 의미가 바로 이것이다.

acquisition 함수 = 탐험과 활용의 저울

acquisition 함수는 surrogate가 준 μ(예측)와 σ(불확실성)를 저울질해 다음 지점을 정한다.

  • 활용(exploitation): 지금까지 제일 좋았던 곳 근처 — μ가 높은 곳
  • 탐험(exploration): 아직 안 가봐서 모르는 곳 — σ가 큰 곳
  • 좋은 acquisition은 이 둘을 한 수식으로 저울질해, 너무 한쪽에 치우치지 않게 한다

대표 3가지 (모두 “최대화” 기준)

f⁺ = 지금까지 관측된 최고 성과, Φ/φ = 표준정규 CDF/PDF, Z = (μ(x) − f⁺) / σ(x)

함수수식성격
UCB (Upper Confidence Bound)μ(x) + κ·σ(x)κ로 탐험 강도를 직접 조절. 가장 직관적
PI (Probability of Improvement)Φ(Z)현재 최고를 넘을 확률만 봄. 탐험이 약한 편
EI (Expected Improvement)(μ−f⁺)·Φ(Z) + σ·φ(Z)넘는 양의 기대값. 실무 기본값으로 가장 많이 씀
  • UCB: κ를 키우면 탐험↑, 줄이면 활용↑ — 가장 손에 잡히는 노브.
  • EI: “얼마나 더 좋아질지의 기대값”이라, 조금 불확실해도 크게 개선될 여지가 있는 곳을 노린다.

헷갈리는 3형제 구분

이름이 다 “베이지안/실험” 계열이라 섞이지만, 던지는 질문이 다르다.

베이지안 최적화 (BO)베이지안 A/B 테스트멀티암드밴딧 (MAB)
한 줄 목적다음에 뭘 테스트할지 정함테스트 결과를 어떻게 해석할지트래픽을 어떻게 배분할지
다루는 문제크거나 연속적인 설정 공간에서 최적 설정 탐색두 변형 결과의 사후확률 P(B > A) 계산정해진 몇 개 변형 사이 실시간 트래픽 배분
핵심 도구surrogate(Gaussian Process) + acquisition베이즈 정리, 사후분포보상 추정 + 탐험/활용 정책
시간축실험과 실험 사이에서 다음 점 선택실험 분석실험 매 순간
  • 셋 다 “불확실성을 확률로 다룬다”는 베이지안 사고를 공유해서 헷갈리지만, BO=어디를 실험할까 / 베이지안 A/B=결과 해석 / MAB=지금 트래픽 배분으로 질문이 갈린다.
  • 실무에선 겹쳐 쓰기도 한다: BO가 고른 설정을 MAB로 굴리고, 그 결과를 베이지안 A/B로 해석.

A/B 테스트와의 연결 (핵심)

  • “A와 비교할 B를 누구로?” 라는 질문 = BO에서 다음 평가 지점 x를 고르는 문제와 정확히 같다.
  • A/B 테스트 1회 = f(설정)을 한 번 평가하는 비싼 호출이다.
  • BO는 “이전 A/B 결과들”로 surrogate를 만들어, 다음에 가장 유망한 설정만 골라 A/B에 태운다 → 적은 실험으로 최적 근처에 도달한다.
  • 그래서 B 테스트 “만으로는 부족”하다 — A/B는 비교하는 도구일 뿐이고, 무엇을 비교할지 고르는 두뇌가 BO다.

실제 구현: BoTorch

직접 GP를 짜고 acquisition을 미분할 필요 없이, BoTorch가 위의 BO 루프 4단계를 거의 그대로 함수로 제공한다.

  • PyTorch 기반 베이지안 최적화 라이브러리(Meta 관리). GPU 가속·자동미분을 그대로 쓰고, GP는 GPyTorch로 처리한다.
  • acquisition을 몬테카를로로 계산해, EI 같은 함수도 미분 가능하게 최적화한다.
  • 더 고수준으로 쓰고 싶으면 BoTorch 위에 얹힌 Ax 플랫폼이 실험 관리까지 해준다.
BO 루프 단계BoTorch 구성요소
① surrogate 적합SingleTaskGP + fit_gpytorch_mll
② acquisition 계산LogExpectedImprovement (EI의 수치 안정 버전)
③ 다음 지점 선택optimize_acqf
④ 평가 후 반복내 실험 결과로 train_X/Y 갱신 후 다시 ①로
import torch
from botorch.models import SingleTaskGP
from botorch.fit import fit_gpytorch_mll
from botorch.acquisition import LogExpectedImprovement
from botorch.optim import optimize_acqf
from gpytorch.mlls import ExactMarginalLogLikelihood
 
# 관측 데이터: train_X=설정들, train_Y=성과(전환율 등). 보통 정규화해서 넣는다.
train_X = torch.rand(10, 2)            # 예: 2차원 설정 10개
train_Y = evaluate(train_X)            # 비싼 평가(= A/B 1회씩)의 결과
 
# ① surrogate = GP 적합
gp  = SingleTaskGP(train_X, train_Y)
mll = ExactMarginalLogLikelihood(gp.likelihood, gp)
fit_gpytorch_mll(mll)                  # 커널 하이퍼파라미터 학습
 
# ② acquisition (EI)
logEI = LogExpectedImprovement(model=gp, best_f=train_Y.max())
 
# ③ 다음에 평가할 설정 1개 선택
candidate, _ = optimize_acqf(
    logEI,
    bounds=torch.tensor([[0.0, 0.0], [1.0, 1.0]]),
    q=1, num_restarts=5, raw_samples=20,
)
# candidate = 다음 A/B 테스트로 돌릴 설정 → 평가해 train_X/Y에 추가하고 ①로 반복

라이브러리를 써도 머릿속 모델은 그대로다: GP로 “모양+불확실성”을 추정(①)하고, acquisition으로 “다음 한 점”을 고르는(②③) 일을 함수가 대신 해줄 뿐이다.

한계

  • 보통 ≤20차원에서 잘 된다 (고차원은 어려움).
  • surrogate가 Gaussian Process면 데이터 N에 대해 O(N³) → 평가가 수백~수천 번이면 부담 (BO는 원래 “적은 평가”가 전제라 보통은 괜찮다).
  • surrogate / 커널 / acquisition 선택에 따라 성능이 좌우된다.

참고 문서