Contextual Bandits
Instead of running a 2-week A/B test and throwing away half your traffic on a losing idea, a Contextual Bandit learns *during* the test, smoothly shifting traffic away from the loser and toward the winner.
Why Does This Exist?
In classical E-Commerce, if you want to test two different recommendation algorithms, you run an A/B Test. You send 50% of your users to Algorithm A, and 50% to Algorithm B. You wait 2 weeks, look at the dashboard, realize Algorithm B is terrible, and turn it off.
This is incredibly wasteful. For 2 weeks, you intentionally gave 50% of your users a terrible experience just to prove a point. This lost you money.
A Multi-Armed Bandit (MAB) solves this. It doesn't wait 2 weeks. On Day 1, it starts at 50/50. By Day 3, it notices Algorithm A is winning, so it shifts traffic to 70/30. By Day 7, it's 95/5. It learns while it tests, minimizing your losses.
A Contextual Bandit goes one step further. It realizes that there is no single "best" algorithm. Algorithm A might be the best for Mobile users, but Algorithm B is the best for Desktop users. It uses the user's context (features) to decide which arm to pull.
Think of It Like This
Think of It Like This
You walk into a casino with 3 slot machines (Bandits).
A/B Testing: You pull Machine 1 exactly 100 times. Then you pull Machine 2 exactly 100 times. Then Machine 3 exactly 100 times. Finally, you calculate the average payout and only play the best one forever.
Multi-Armed Bandit: You pull them all a few times. Machine 2 seems to be paying out the most. So, you start pulling Machine 2 most of the time (Exploitation), but every once in a while you pull Machine 1 or 3 just in case you got unlucky earlier (Exploration).
Contextual Bandit: You realize that Machine 1 pays out the most when it's raining outside, and Machine 2 pays out the most when it's sunny. You look at the weather (the Context), and pull the correct machine.
The Explore-Exploit Dilemma
All Bandits are built on the fundamental tension of Reinforcement Learning:
- Exploitation: Doing what you currently believe is the best thing, to maximize short-term profit.
- Exploration: Trying something you think is sub-optimal, just to gather more data and prove yourself right or wrong.
If you only Exploit, you might get stuck in a local minimum forever (e.g., "I know showing the user Batman gets clicks, so I will only ever show them Batman"). If you only Explore, you are just showing random items and ruining the user experience.
How to balance it:
- Epsilon-Greedy (-greedy): The simplest approach. 90% of the time, show the best item. 10% of the time (), pick a completely random item.
- Upper Confidence Bound (UCB): Pick items that have high scores, or items that you are highly uncertain about. Uncertainty is treated as a bonus.
- Thompson Sampling: A Bayesian approach. You maintain a probability distribution for every item. You sample from the distribution, which naturally balances exploration (wide distributions) and exploitation (tall, narrow distributions).
Contextual Bandits in Recommender Systems
In modern recommenders (like Netflix or Spotify), the homepage is almost entirely driven by Contextual Bandits.
They don't use Bandits to pick individual movies. The action space (10,000 movies) is too large. Instead, they use Contextual Bandits as the Routing Layer.
- Arm 1: The "Because you watched Matrix" carousel.
- Arm 2: The "Trending Now" carousel.
- Arm 3: The "New Releases" carousel.
- Context: The User is a 25-year-old male who logged in at 2 AM on a Tuesday.
The Contextual Bandit looks at the Context, and predicts which of the 3 carousels has the highest probability of getting a click right now. It puts that carousel at the very top of the homepage.
Show Me the Code
Implementing a simple -greedy Contextual Bandit.
import numpy as np
class ContextualBandit: def __init__(self, n_arms, n_features, epsilon=0.1): self.epsilon = epsilon # We maintain a separate linear regression model for EVERY arm # weights shape: [n_arms, n_features] self.weights = np.zeros((n_arms, n_features)) def select_arm(self, context_vector): """Decide which carousel to show the user.""" # 1. EXPLORE: 10% of the time, pick a random carousel if np.random.random() < self.epsilon: return np.random.randint(len(self.weights)) # 2. EXPLOIT: 90% of the time, predict the best carousel # We calculate the expected payout (dot product) for all arms based on this specific context expected_payouts = np.dot(self.weights, context_vector) # Pick the arm with the highest expected payout return np.argmax(expected_payouts) def update(self, arm_pulled, context_vector, reward): """Learn from the result (0 for ignore, 1 for click).""" # A highly simplified gradient descent step learning_rate = 0.01 prediction = np.dot(self.weights[arm_pulled], context_vector) error = reward - prediction # Update the weights for the arm we actually pulled self.weights[arm_pulled] += learning_rate * error * context_vector
# Usagebandit = ContextualBandit(n_arms=3, n_features=5)user_context = np.array([1, 0, 0, 24, 1]) # e.g. [IsMobile, IsTablet, IsDesktop, Age, IsWeekend]
# The bandit decides which carousel to showchosen_carousel = bandit.select_arm(user_context)
# The user either clicks it (1) or ignores it (0)user_clicked = 1
# The bandit learns from the interaction instantlybandit.update(chosen_carousel, user_context, reward=user_clicked)Watch Out For
Watch Out For
Delayed Feedback. Bandits assume that when you pull an arm, you immediately get the reward. In the real world, a user might click a movie (immediate reward), but they might turn it off after 10 minutes (delayed negative reward). If your Bandit learns too fast based only on immediate clicks, it becomes a clickbait engine. You must carefully define the "Reward" to include long-term satisfaction.
The Quick Version
- A/B Tests are static, wasteful, and assume there is a single global winner.
- Multi-Armed Bandits learn dynamically, shifting traffic to the winner during the test to minimize wasted traffic.
- Contextual Bandits use user features to realize that different arms are optimal for different users.
- They constantly balance Exploitation (showing what works) with Exploration (trying new things to gather data).
- They are heavily used to rank UI components (like carousels) rather than individual items.
What to Read Next
- Off-Policy Evaluation
- A/B Testing for ML (Coming soon)