베이지안 최적화(Bayesian Optimization) 밑바닥부터 구현하기: GP 대리 모델과 획득 함수를 이용한 블랙박스 피크 탐색
요약
가우시안 프로세스(GP)를 대리 모델로 활용하여 비용이 많이 드는 블랙박스 함수를 최적화하는 베이지안 최적화의 구현 원리를 설명합니다. 기대 개선(EI)과 UCB 같은 획득 함수를 통해 탐색(exploration)과 활용(exploitation)의 균형을 맞추는 과정을 다룹니다.
핵심 포인트
- 가우시안 프로세스를 이용해 저렴한 대리 모델 구축
- 획득 함수를 통한 효율적인 샘플링 지점 결정
- Expected Improvement(EI)와 UCB 알고리즘의 원리
- 탐색과 활용의 균형을 통한 블랙박스 함수 최적화
어제 저는 _보정된 불확실성 밴드(calibrated uncertainty band)_를 제공하는 가우시안 프로세스(Gaussian Process, GP)를 구축했습니다. 오늘은 그 밴드를 **의사결정(decision)**으로 전환합니다. f(x)의 모든 평가가 학습 실행, 실험실 실험, A/B 테스트와 같이 비용이 많이 드는 경우, 그리드 탐색(grid search)을 수행할 여유가 없습니다. 베이지안 최적화(Bayesian Optimization)는 GP를 저렴한 **대리 모델(surrogate)**로 맞춘 다음, **획득 함수(acquisition function)**가 그 평균(mean)과 밴드(band)를 읽어 각 후보가 얼마나 유망한지 점수를 매깁니다. 그 후 argmax 지점에서 실제 평가를 한 번 수행하고, 다시 맞추고(refit), 이를 반복합니다. 무작위 탐색(random search)이 여전히 방황하고 있을 때, 베이지안 최적화는 불과 십여 번의 단계 만에 피크(peak)를 찾아냅니다. 데모 페이지의 모든 곡선은 어제 사용한 것과 동일한 Cholesky 기반 GP를 사용하여 실시간으로 계산됩니다. 전체 루프는 다음과 같습니다.
비용이 많이 드는 블랙박스와 몇 개의 시드 포인트
BO는 평가 비용이 비싸고 공식을 알 수 없는 함수를 위한 것입니다. 호출할 수 있는 횟수가 매우 제한적이므로, 대리 모델이 맞출 수 있도록 2~3개의 시드 평가(seed evaluations)로 시작합니다.
import numpy as np
def objective(x): # 비용이 많이 드는 블랙박스; 공식은 알 수 없음
return 1.7*np.exp(-(x-7)**2/1.6) + 1.15*np.exp(-(x-2.6)**2/2.5) - 0.15
...
대리 모델은 단순한 가우시안 프로세스입니다
대리 모델(surrogate)은 우리가 무료로 질의할 수 있는 저렴한 대역입니다. GP는 완벽합니다. 모든 입력값에 대해 사후 **평균(mean)**과 보정된 **±σ 밴드(±σ band)**를 반환합니다. 이것은 정확히 전날의 엔진—커널(kernel), 세 개의 행렬, 하나의 Cholesky solve—과 동일합니다.
def gp_posterior(X, y, Xs, noise=1e-3):
K = rbf(X, X) + noise*np.eye(len(X)) # train-train
Ks = rbf(Xs, X) # test-train
...
기대 개선(Expected Improvement) — 획득 함수
획득 함수 (Acquisition function)는 평균(mean)과 불확실성(uncertainty)을 하나의 수치로 결합하여, 각 후보 x가 샘플링할 가치가 얼마나 있는지 점수를 매깁니다. 기대 개선 (Expected Improvement, EI)은 다음과 같이 질문합니다: "지금까지의 최댓값 f⁺를 얼마나 초과할 것으로 기대하는가?" 이 함수는 높은 평균(활용, exploit)과 넓은 대역(탐색, explore) 모두에 보상을 주며, 이미 측정된 지점에서는 0으로 떨어집니다. 따라서 평가를 낭비하지 않습니다.
from scipy.stats import norm
def expected_improvement(mean, std, f_best, xi=0.01):
std = np.maximum(std, 1e-9)
...
UCB (Upper Confidence Bound)도 동일한 아이디어이지만, 하나의 명시적인 조절 장치인 μ + κ·σ를 가집니다. 이는 낙관주의자의 점수로, 작은 κ는 현재의 최댓값을 활용(exploit)하고, 큰 κ는 가장 넓은 대역을 추적(explore)합니다. 두 방식 모두 계산 비용이 저렴하므로, 조밀한 그리드(grid) 위에서 최댓값을 구할 수 있습니다.
루프: 적합(fit) → 획득 함수 최대화(maximise acquisition) → 평가(evaluate) → 반복
이것이 전체 방법론입니다. 지금까지 관찰된 모든 데이터에 대리 모델(surrogate)을 적합시키고, 조밀한 후보 그리드의 점수를 매긴 뒤, 획득 함수의 최댓값(argmax)을 선택합니다. 그 지점에서 실제 (비싼) 평가를 한 번 수행하고, 결과를 추가한 뒤 다시 반복합니다. 보통 12번 정도의 반복이면 충분합니다. 이는 데모가 클릭할 때마다 실행하는 루프와 정확히 일치합니다.
Xs = np.linspace(*bounds, 400) # 조밀한 후보 그리드
for t in range(12): # 소수의 비싼 평가
mean, std = gp_posterior(X, y, Xs) # 1. 지금까지의 데이터에 대리 모델 적합
...
그리드 탐색(grid search) 및 랜덤 탐색(random search)보다 우수한 이유
데모에서는 동일한 예산과 동일한 시드 포인트(seed points)를 사용하여 BO를 랜덤 탐색과 비교하며, 평가당 발견된 최댓값을 그래프로 나타냅니다. 두 방식 모두 진정한 최댓값을 향해 상승하지만, BO는 불확실성을 인지하는 모델이 보상이 가장 높다고 판단하는 곳에 모든 평가를 집중하기 때문에 훨씬 적은 평가 횟수로 목표에 도달합니다. 반면 랜덤 탐색은 단순히 추측할 뿐입니다. 이 격차야말로 대리 모델(surrogate)의 가치입니다. 새로운 포인트가 추가될 때마다 대역(band)은 그 주변으로 수렴하며, 획득 함수는 다시 목표를 재설정합니다.
from skopt import gp_minimize # scikit-optimize; torch/GPU의 경우 BoTorch/Ax 사용
res = gp_minimize(lambda x: -objective(x[0]), [bounds], n_calls=15, acq_func="EI")
print(-res.fun, res.x) # 최적값, 최적의 x — 약 15회의 평가(evals) 내에 완료
scikit-optimize의 gp_minimize (또는 GPU 환경에서의 BoTorch/Ax)는 이 모든 과정을 수행하며 커널(kernel)까지 튜닝합니다. 각 평가(evaluation) 비용이 실제로 매우 높고 예산이 적은 경우, 즉 하이퍼파라미터 튜닝(hyperparameter tuning), 재료 및 신약 스크리닝(materials and drug screening), 로보틱스(robotics), A/B 테스트와 같은 상황에서 베이지안 최적화(BO)를 활용하십시오. 비용은 GP의 O(N³) 촐레스키 분해(Cholesky decomposition)에서 발생하므로, 고차원(high-dimensional) 또는 배치(batched) 설정에서는 Thompson sampling, TuRBO, 또는 q-EI로 전환해야 합니다. 하지만 핵심은 간단합니다. 대역(band)을 위한 대리 모델(surrogate), 대역을 결정으로 전환하기 위한 획득 함수(acquisition), 그리고 탐색과 활용(explore-vs-exploit) 사이의 균형이 게임의 전부입니다.
"Sample next"를 누르고 숨겨진 목적 함수(objective)가 찾아지는 과정을 지켜보세요:
https://dev48v.infy.uk/ml/day38-bayesian-optimization.html
AI 자동 생성 콘텐츠
본 콘텐츠는 Dev.to AI tag의 원문을 AI가 자동으로 요약·번역·분석한 것입니다. 원 저작권은 원저작자에게 있으며, 정확한 내용은 반드시 원문을 확인해 주세요.
원문 바로가기