오늘은 추천 알고리즘을 공부하면 많이 듣게 되는 기초, Multi Armed Bandit (MAB)에 대해서 알아보자. 그 중 Epsilon Greedy와 Tompson Sampling에 대해서 설명해보겠다.
0. 기본 지식
exploration
이름 그대로 탐험을 의미한다. 추천할 때 새로운 아이템을 추천하는 등, 학습에 fitting되지 않는 미지의 영역을 새롭게 보여주는 개념으로 이해할 수 있다.
아직 클릭이 적은 아이템이나, 기존 로직에 반영하지 않은 fresh 아이템을 노출하는 것이 여기에 해당한다. 그렇기에 매일 유저에게 비슷한 결의 아이템이 추천되는 것을 방지할 수 있다. 단, 당장의 reward는 낮을 수밖에 없다.
exploitation
데이터를 기반으로 해서 추천을 해주는 영역이다. 이미 쌓인 데이터 기반으로 확실한 reward를 보장할 수 있다.
추천에서는 이 exploration과 exploitation의 균형을 잘 잡는 것이 중요하다.
MAB
여러개의 arm bandit 중 하나를 선택한다는 의미의 Multi Arm Bandit이다. 카지노 슬롯 머신을 생각하면 쉽다. 추천에서는 여러개의 액션(ex. 추천할 수 있는 아이템) 중 하나에 해당하는 슬롯 레버 Arm을 선택한다고 상상해보자.
prior, posterior, likelihood
이전 포스트에서 다룬 적 있으나, 구글에서 글 안에 글 링크를 달면 싫어하는 것 같다. 자세한 건 내 블로그의 다른 글을 참고해주시면 된다.
오늘 설명할 버전으로 기술하면 아래와 같다.
- θ
- 이 arm을 선택할 확률
- prior: P(θ)
- beta distribution
- posterior : P(θ|D)
- 업데이트된 성공 확률 분포
- likelihood : P(D|θ)
- 이 arm의 reward가 1또는 0
beta distribution
beta distribution은 다음과 같은 확률분포를 띈다.

여기서 MAB에 중요한 개념은 어떻게 beta distribution이 업데이트 되는지다. conjucate prior라고 가정했을 때,
posterior는
P(θ∣D) = k∼Beta(α+k,β+n−k)
이 된다. 즉, k가 "성공"일 때 알파값은 α +=k , 베타값은 β -=k 와 같이 업데이트 된다.
1. epsilon greedy
강화학습에서 나오는 개념이다. 지금까지의 가장 reward가 높은 아이템을 exploitation을 하지만, 일정한 비율 입실론(ε)에 대해서는 탐험 exploration을 한다.
파이썬으로 간단하게 예시를 작성해볼 수 있다. 만약 0.3의 확률은 새로운 아이템을 '탐험' 시키고, 그렇지 않으면 reward가 가장 높은 아이템을 추천으로 선택하는 epsilon greedy 함수를 작성해보자.
import numpy as np
# 먼저 유저에게 해당 아이템이 추천됐을 때 기대되는 reward를 얻는다.
user_action_reward = [0, 0, 0.3 ,0.7, 1.0] #1명의 유저, 5개의 아이템에 대한 reward
# epsilon greedy function
def epsilon_greedy(rewards, epsilon = 0.3):
if np.random.rand() < epsilon:
# 랜덤하게 eps 확률에는 랜덤하게 데이터가 나간다.
choosen_idx = np.random.randint(len(rewards))
else:
# Exploit: choose the action with the highest Q-value
choosen_idx = np.argmax(rewards)
expected_reward = rewards[choosen_idx]
return choosen_idx, expected_reward
epsilon_greedy(user_action_reward)
이론상 10번 중 3번은 가장 높은 1.0에 해당하는 index 4가 아닌 다른 아이템이 return된다.
2. Tompson Sampling
epsilon-greedy가 일정한 확률대로 새로운 아이템을 return한다면, tompson sampling은 그 확률을 베이지안 (baysian) 에 기반한다. 그리고이 arm이 성공적인가 아닌가를 1 또는 0 으로 본다.
posterior가 baysian이면 보통 conjucate (prior와 posterior가 유사한 확률분포를 따른다고 가정) prior는 beta distribution을 따른다.
P(θ)가 beta-distribution일 때, baysian을 이용해서 학습한 arm bandit 알고리즘을 tompson sampling이라 할 수 있겠다. (실은 다른 conjucate prior를 사용해도 무방하다. 여기선 "보통" 자주 쓰이는 beta - baysian conjucate를 예시로 설명한다.)
베이시안에 따라 posterior는 다음과 같이
- Beta(α + 성공횟수, β + 실패횟수)
로 확률이 업데이트가 된다. 데이터 학습이 진행될수록 distribution의 파라미터인 α 값이 커지면서 탐험(exploration)보다 exploitation의 확률이 더 올라간다.

이제 파이썬으로 구현해보자. 이번에도 각 아이템이 추천됐을 때의 reward를 데이터로 삼는다.
먼저 arm을 선택하는 확률이다. 이는 계속 언급했듯이 prior 로 beta distribution 기반이다.
def select_arm(self):
# 각 arm의 성공확률 θ
# P(θ) beta distribution 구하는 방법: np.random.beta(self.alphas[i], self.betas[i])
samples = [np.random.beta(self.alphas[i], self.betas[i]) for i in range(self.num_arms)]
return np.argmax(samples)
다음은 알파와 베타를 업데이트 해야한다. posterior는 해당 값에 성공 여부를 더해주면 된다.
def update(self, chosen_arm, reward):
#여기가 baysian.
# P(θ|a)는 likelihood. reward가 success threshold보다 큰지 아닌지 binary.
# P(θ)는 beta-distribution.
# P(a|θ)는 업데이트된 성공 확률 분포. (posterior)
if reward[chosen_arm] >= self.success_ths:
self.alphas[chosen_arm] += 1
else:
self.betas[chosen_arm] += 1
이제 코드를 합쳐보자.
import numpy as np
class ThompsonSampler:
def __init__(self, num_arms, success_ths): #num_arms: 현재 아이템 갯수
self.num_arms = num_arms
self.alphas = np.ones(num_arms) # successes
self.betas = np.ones(num_arms) # failures
self.success_ths = success_ths
def select_arm(self):
# 각 arm의 확률 θ
# θ beta distribution 구하는 방법: np.random.beta(self.alphas[i], self.betas[i])
samples = [np.random.beta(self.alphas[i], self.betas[i]) for i in range(self.num_arms)]
return np.argmax(samples)
def update(self, chosen_arm, reward):
#여기가 baysian.
# P(θ|a)는 reward가 success threshold보다 큰지 아닌지 binary.
# P(θ)는 beta-distribution.
# P(a|θ)는 이 arm을 선택할 확률임.
if reward[chosen_arm] >= self.success_ths:
self.alphas[chosen_arm] += 1
else:
self.betas[chosen_arm] += 1
n_arms = 5 # 5개의 아이템으로 간주
success_ths = 0.7 # 추천 성공으로 간주하는 reward의 threshold
smp = ThompsonSampler(n_arms, success_ths)
trial = smp.select_arm()
print(f'current selected item idx: {trial}')
smp.update(trial, user_action_reward)
print(f'current alpha, beta distribution: {smp.alphas, smp.betas}')
## current selected item idx: 4
## current alpha, beta distribution: (array([1., 1., 1., 1., 2.]), array([1., 1., 1., 1., 1.]))
이를 계속해서 시도하면 높은 reward를 가진 arm에 따라 distribution도 수렴하는 것이다.
'머신러닝 > 파이썬 구현 머신러닝' 카테고리의 다른 글
| 파이썬 구현으로 알아보는 TF-IDF와 BM25 (0) | 2025.09.13 |
|---|---|
| pytorch 공식 구현체로 보는 transformer MultiheadAttention과 numpy로 구현하기 (0) | 2023.07.01 |
| 딥러닝 이론 optimizer 정리 - GD , SGD, Momentum, Adam (0) | 2022.05.14 |
| 파이썬으로 기초 CNN 구현하기 1 - conv, pooling layer (4) | 2022.04.17 |
| 파이썬과 기초 딥러닝 개념- 이론편 1 (0) | 2022.04.03 |