- **베이지안 최적화(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단계
- surrogate model 적합: 지금까지의
(설정, 성과)점들로 Gaussian Process를 적합한다 → 모든 후보 지점에서 평균μ(x)와 불확실성σ(x)를 얻는다. - acquisition 함수 계산:
μ와σ를 “여기 찍으면 이득 볼 기대치” 라는 하나의 점수로 변환한다. - 다음 지점 선택: acquisition을 최대화하는
x를 고른다 (= 다음에 돌릴 A/B 설정). - 평가 후 업데이트: 실제로 실험해 성과를 관측 → 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단계를 이 데이터에 그대로 대입한다:
- surrogate 적합: 이 3점으로 Gaussian Process를 적합한다. 그러면
0~30%사이 아무 할인율이나 물어봐도 μ(예측 전환율)와 σ(불확실성)를 즉시·공짜로 답해준다.10%→ μ≈2.8%, σ≈0.4% (관측점들 사이라 비교적 확신)18%→ μ≈3.2%, σ≈0.5% (최고점 15% 바로 옆이라 넘을 여지가 있음)30%→ μ≈2.5%, σ≈1.3% (관측 범위 끝이라 불확실 큼)
- acquisition 계산: 각 후보를 EI로 점수화한다 →
18%가 “현재 최고 3.4%를 넘을 기대값”이 가장 크다. (10%는 μ가 낮아 넘기 어렵고,30%는 σ는 크지만 μ가 너무 낮다.) - 다음 지점 선택: surrogate에 수천 개 할인율을 공짜로 물어 acquisition이 최대인
18%를 찾는다. - 평가 후 업데이트:
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 선택에 따라 성능이 좌우된다.
참고 문서
- https://en.wikipedia.org/wiki/Bayesian_optimization
- 구현 라이브러리: https://botorch.org/ (BoTorch — PyTorch 기반, 위 코드 예시 참고)
- surrogate model: Gaussian Process
- 디딤돌 개념: B 테스트
- 세션 원본 메모: 데이터 분석