탐험과 활용의 균형을 맞추기 위한 알고리즘, 톰슨샘플링

탐험과 활용의 균형을 맞추기 위한 알고리즘, 톰슨샘플링
Photo by Ays Be / Unsplash

톰슨 샘플링이란?

  • 톰슨 샘플링(Thompson Sampling)은 멀티 암드 밴딧 문제(Multi-Armed Bandit Problem)에서 사용되는 확률론적 알고리즘으로, 다양한 선택지(예: 광고, 제품 추천) 중에서 가장 효율적인 선택지를 반복적으로 탐색하는 방법입니다
  • 이는 탐험(Exploration)과 활용(Exploitation)의 균형을 잘 맞추기 위한 기법으로, 각 선택지의 성공 확률을 베이지안 방법론을 통해 갱신하면서 최적의 선택을 수행합니다.

Motivation

  • 기존의 Greedy 알고리즘이나 Epsilon-Greedy 같은 기법은 선택지 중 성공 가능성이 높은 팔을 우선적으로 탐색하거나 무작위로 탐색하는 방식입니다. 하지만 이 방식은 탐험과 활용 간의 균형을 제대로 맞추지 못하는 단점이 있습니다. 이 문제를 해결하기 위해 톰슨 샘플링이 등장했습니다.
  • 톰슨 샘플링은 각 선택지의 성공 확률에 대한 불확실성을 고려하고, 이를 기반으로 확률적으로 선택지를 탐색하면서 점차적으로 성공 확률이 높은 선택지를 집중적으로 활용하게끔 설계되었습니다.

Pros & Cons

Pros

  • 탐험과 활용의 균형: 톰슨 샘플링은 성공 확률이 낮은 선택지도 탐험할 기회를 제공하므로, 최적의 선택지를 찾는 과정에서 놓치는 선택지를 줄일 수 있습니다.
  • 베이지안 업데이트: 톰슨 샘플링은 베이지안 확률론을 사용하여 각 선택지의 성공 확률을 반복적으로 업데이트하며, 데이터가 추가될수록 더 정확한 예측이 가능합니다.
  • 다양한 상황에 유연하게 대응: 선택지의 성공률이 동적으로 변하거나 불확실성이 클 때도 톰슨 샘플링은 유연하게 작동합니다.

Cons

  • 계산 복잡도: 각 선택지에 대해 베이지안 업데이트를 수행하기 때문에 연산량이 다소 많아질 수 있습니다. 특히 선택지가 많을수록 계산 복잡도가 증가합니다.
  • 초기 데이터 부족 시 성능 저하: 탐험과 활용 간 균형을 잡기 위해서는 어느 정도의 초기 데이터가 필요합니다. 초기 데이터가 부족할 경우, 최적의 선택을 찾는 데 더 많은 시간이 소요될 수 있습니다.

Alternative

  • Epsilon-Greedy: 일정 확률(ε)로 무작위 탐험을 하고, 나머지 확률로 현재 가장 성능이 좋은 선택지를 활용하는 방법입니다. 단순하면서도 빠르게 동작하지만, 탐험과 활용의 균형이 명확하게 설정되지 않을 수 있습니다.
  • Upper Confidence Bound (UCB): 탐험을 제어하기 위해 각 선택지의 상한선을 계산하고, 상한선이 높은 선택지를 선택하는 방법입니다. 톰슨 샘플링보다 계산 복잡도가 낮으나, 동적인 상황에서 성능이 떨어질 수 있습니다.

Code

  • 아래코드에서 톰슨샘플링은 성공확률(베타분포)에 기반해서 가장 높은 확률을 가진 Arm을 선택합니다.
  • 자연스럽게 시도횟수만큼 누적보장승은 증가하게 되고 이는 시간이 지남에 따라 알고리즘이 성공 확률이 높은 슬롯머신을 더 자주 선택하게 됨을 의미합니다.
import numpy as np
import matplotlib.pyplot as plt

# 각 슬롯머신(팔)의 실제 성공 확률
true_probs = [0.1, 0.3, 0.5]  # 3개의 슬롯머신
n_arms = len(true_probs)
n_trials = 1000  # 시도 횟수

# 베타 분포의 성공 및 실패 횟수 추적
successes = np.zeros(n_arms)
failures = np.zeros(n_arms)

# 결과 추적
rewards = np.zeros(n_trials)

# 톰슨 샘플링 실행
for t in range(n_trials):
    sampled_theta = [np.random.beta(1 + successes[i], 1 + failures[i]) for i in range(n_arms)]
    chosen_arm = np.argmax(sampled_theta)
    
    # 선택한 팔을 당겼을 때의 보상
    reward = np.random.rand() < true_probs[chosen_arm]
    
    # 성공 또는 실패 기록
    if reward:
        successes[chosen_arm] += 1
    else:
        failures[chosen_arm] += 1
    
    rewards[t] = reward

# 누적 보상 그래프
cumulative_rewards = np.cumsum(rewards)
plt.plot(cumulative_rewards)
plt.xlabel('Trials')
plt.ylabel('Cumulative Reward')
plt.title('Thompson Sampling - Cumulative Reward')
plt.show()

500

A/B 테스트 관련

  • A/B 테스트에서는 일반적으로 두 그룹에 대해 동일한 시간 동안 실험을 진행한 후, 결과를 수집하고 분석하여 승자를 결정합니다. 이 과정에서 주로 T-TestChi-Square Test를 사용하여 두 대안의 성능 차이를 평가합니다
  • 이에 반해서 톰슨 샘플링을 이용한 A/B 테스트는 실험 도중에도 성공 가능성이 높은 대안을 더 많이 탐색하도록 설계됩니다. 톰슨 샘플링은 베이지안 방식으로 각 대안(A, B)의 성공 확률을 추정한 후, 확률적으로 더 나은 대안을 선택하는 방식입니다. 즉 A/B 테스트에서 효율적이고 실시간적인 의사 결정을 가능하게 합니다.
항목 T-Test 기반 A/B 테스트 톰슨 샘플링 기반 A/B 테스트
실험 종료 후 분석 실험이 종료된 후에 데이터를 분석해 승자를 결정. 실험 도중에도 데이터에 기반해 실시간으로 더 나은 대안을 선택.
탐험과 활용 고정된 비율로 두 대안을 실험. 탐험과 활용 간 균형을 잡으면서 실시간으로 선택.
리소스 사용 실험이 끝날 때까지 비효율적인 대안도 트래픽을 할당해야 함. 성능이 낮은 대안에는 점차적으로 적은 트래픽 할당.
결과 분석 실험 종료 후 결과를 T-Test로 분석. 실험 진행 중에도 실시간 분석 가능.

예시 코드

  • 시간이 지남에 따라 B 그룹이 A 그룹보다 성공률이 훨씬 높다는 것을 톰슨 샘플링 알고리즘이 학습하게 됩니다. 그 이후로는 B 그룹을 훨씬 더 자주 선택하게 되며, B 그룹의 누적 성공률이 빠르게 증가하는 것을 볼 수 있습니다.
import numpy as np  
import matplotlib.pyplot as plt  
  
# A/B 테스트에서 두 그룹의 전환율  
true_conversion_A = 0.3  
true_conversion_B = 0.4  
n_trials = 1000  # 실험 횟수  
  
# 베타 분포를 통한 성공/실패 추적  
successes_A, failures_A = 0, 0  
successes_B, failures_B = 0, 0  
cumulative_rewards_A, cumulative_rewards_B = [], []  
  
# 톰슨 샘플링 기반 A/B 테스트 실행  
for _ in range(n_trials):  
    # A와 B에 대해 베타 분포에서 샘플링  
    theta_A = np.random.beta(1 + successes_A, 1 + failures_A)  
    theta_B = np.random.beta(1 + successes_B, 1 + failures_B)  
      
    # 더 큰 값을 가진 대안을 선택  
    if theta_A > theta_B:  
        # A 선택  
        reward = np.random.rand() < true_conversion_A  
        successes_A += reward  
        failures_A += 1 - reward  
    else:  
        # B 선택  
        reward = np.random.rand() < true_conversion_B  
        successes_B += reward  
        failures_B += 1 - reward  
      
    cumulative_rewards_A.append(successes_A)  
    cumulative_rewards_B.append(successes_B)  
  
# 결과 시각화  
plt.plot(cumulative_rewards_A, label="A Cumulative Rewards")  
plt.plot(cumulative_rewards_B, label="B Cumulative Rewards")  
plt.xlabel('Trials')  
plt.ylabel('Cumulative Successes')  
plt.legend()  
plt.show()

500