Tae Hyun Kim (Lowell)

Contextual Bandits

Definition

Contextual Bandits는 **맥락(context)**에 따라 최적의 행동(arm)이 달라지는 다중 슬롯 머신 문제입니다.

각 라운드 tt에서:

  1. 맥락(context) xtx_t를 관측 (예: 고객 특성)
  2. 행동 ata_t를 선택 (예: 가격 수준)
  3. 보상 rtr_t를 관측 (예: 구매 여부 × 이익)

목표: 누적 보상 최대화 또는 후회(regret) 최소화

RegretT=t=1T[r(xt)r(xt,at)]\text{Regret}_T = \sum_{t=1}^{T} \left[ r^*(x_t) - r(x_t, a_t) \right]

Intuitive Understanding

프라이싱에서 Contextual Bandits는 “이 고객에게 어떤 가격을 제시해야 하는가?”를 실시간으로 학습합니다.

고소득 고객에게는 높은 가격이, 가격 민감 고객에게는 낮은 가격이 최적일 수 있습니다. Contextual Bandits는 **탐색(exploration)**과 **활용(exploitation)**의 균형을 맞추면서 이 관계를 학습합니다.

Key Properties

기본 Bandits와의 차이

유형설명프라이싱 예시
K-armed Bandits맥락 없이 최적 행동 학습단일 최적 가격 찾기
Contextual Bandits맥락에 따른 최적 행동 학습고객별 최적 가격 찾기

탐색-활용 트레이드오프

  • 활용(Exploitation): 현재 최선으로 알려진 행동 선택
  • 탐색(Exploration): 불확실한 행동을 시도하여 정보 수집

너무 많은 활용 → 차선의 정책에 고착 너무 많은 탐색 → 단기 보상 희생

주요 알고리즘

알고리즘접근법특징
ε-greedy확률 ε로 무작위 탐색단순, 하지만 비효율적 탐색
UCB불확실성 보너스 추가효율적 탐색, 결정론적
Thompson Sampling사후 분포에서 샘플링베이지안, 좋은 실증 성과
LinUCB선형 보상 가정 + UCB대표적 contextual 알고리즘

Example

LinUCB 구현

class LinUCBPricing:
    def __init__(self, n_prices, context_dim, alpha=1.0):
        self.n_prices = n_prices
        self.d = context_dim
        self.alpha = alpha  # 탐색 파라미터

        # 각 행동(가격)에 대한 파라미터
        self.A = [np.eye(context_dim) for _ in range(n_prices)]
        self.b = [np.zeros(context_dim) for _ in range(n_prices)]

    def select_price(self, context, price_levels, marginal_cost):
        """UCB로 가격 선택"""
        ucb_values = []

        for i in range(self.n_prices):
            A_inv = np.linalg.inv(self.A[i])
            theta = A_inv @ self.b[i]

            # 예측 전환율 + 불확실성 보너스
            pred = context @ theta
            uncertainty = self.alpha * np.sqrt(context @ A_inv @ context)
            ucb = pred + uncertainty

            # 기대 이익 = (가격 - MC) × 예측 전환율
            expected_profit = (price_levels[i] - marginal_cost) * ucb
            ucb_values.append(expected_profit)

        best_action = np.argmax(ucb_values)
        return best_action, price_levels[best_action]

    def update(self, price_idx, context, reward):
        """관측으로 파라미터 업데이트"""
        self.A[price_idx] += np.outer(context, context)
        self.b[price_idx] += reward * context

프라이싱 시뮬레이션

# 가격 수준
price_levels = np.array([10, 15, 20, 25, 30])
marginal_cost = 8

# LinUCB 에이전트
agent = LinUCBPricing(n_prices=5, context_dim=4, alpha=0.5)

# 시뮬레이션
total_reward = 0
for t in range(10000):
    # 고객 도착
    context = generate_customer()  # [income, age, loyalty, recency]

    # 가격 선택
    action, price = agent.select_price(context, price_levels, marginal_cost)

    # 구매 결정 (실제 환경에서는 관측)
    conversion = simulate_purchase(context, price)
    reward = (price - marginal_cost) * conversion

    # 학습
    agent.update(action, context, conversion)
    total_reward += reward

print(f"총 보상: {total_reward:.2f}")

References

  • Li, L., et al. (2010). “A Contextual-Bandit Approach to Personalized News Article Recommendation.”
  • Agrawal, S., & Goyal, N. (2013). “Thompson Sampling for Contextual Bandits with Linear Payoffs.”
  • Comprehensive Personalized Pricing Guide, Part IX, §26.3

연결 그래프