Multi-Armed Bandit
Compare ε-greedy, UCB and Thompson sampling on slot machines with hidden payouts
The slot machines
Each pull pays 1 with the arm's hidden probability p, otherwise 0. All three strategies play their own copy of the same machines, one pull each per step.
Controls
Step 0 of 5,000. ε and c can be changed mid-run; changing arms or seed starts over.
ε-greedy
| Arm | Pulls | Est. |
|---|---|---|
| 1 | 0 | – |
| 2 | 0 | – |
| 3 | 0 | – |
| 4 | 0 | – |
| 5 | 0 | – |
UCB1
| Arm | Pulls | Est. | Upper bound |
|---|---|---|---|
| 1 | 0 | – | ∞ |
| 2 | 0 | – | ∞ |
| 3 | 0 | – | ∞ |
| 4 | 0 | – | ∞ |
| 5 | 0 | – | ∞ |
Thompson sampling
| Arm | Pulls | Est. | Post. mean |
|---|---|---|---|
| 1 | 0 | – | 0.500 |
| 2 | 0 | – | 0.500 |
| 3 | 0 | – | 0.500 |
| 4 | 0 | – | 0.500 |
| 5 | 0 | – | 0.500 |
Cumulative regret
Regret adds up pbest − pchosen on every pull. A curve that flattens means the strategy has settled on the best arm.
Thompson sampling posteriors
Each curve is Beta(1 + wins, 1 + losses): what Thompson sampling believes about that arm's payout rate. Wide curves still get explored; narrow ones low down are abandoned.
How to use this visualizer
- 1Press Run. Every step, each of the three strategies pulls one arm of its own copy of the machines.
- 2Watch the pull counts: a good strategy ends up pulling the best arm most of the time. The highlighted row is the arm just pulled.
- 3Compare the cumulative regret curves. ε-greedy grows in a straight line; UCB1 and Thompson sampling bend over and flatten.
- 4Watch the Thompson posteriors sharpen. Click Reveal payout rates to see where the true rates lie.
- 5Change ε and c mid-run. UCB1's theory uses c = √2, but that explores cautiously; on these machines c around 0.5 to 1 usually earns less regret.
- 6Press New machines for a fresh set of payout rates; sets where two arms are close are the hardest.
The explore-exploit dilemma
Picture a row of slot machines, each paying out with a different unknown probability. Every pull you must choose: play the machine that has paid best so far, or try another one that might be better. Too little exploring and you can get stuck on a mediocre arm. Too much and you waste pulls on arms you already know are poor.
The multi-armed bandit is reinforcement learning with a single state: there is no future to plan for, only the trade-off between learning and earning. The same trade-off appears in A/B testing, ad selection, clinical trials and recommendation systems.
Exploration
Exploitation
Regret
Uncertainty
| Strategy | Idea | Regret growth |
|---|---|---|
| ε-greedy | Exploit the best estimate, but explore a random arm with probability ε | Linear (keeps exploring forever at rate ε) |
| UCB1 | Pick the arm with the highest optimistic upper bound on its value | Logarithmic, O(log T) |
| Thompson sampling | Sample a plausible rate from each posterior and pick the highest sample | Logarithmic, often lowest in practice |
Key formulas
Sample-average estimate
Q(a) = (total reward from a) / N(a)
- Q(a)
- estimated payout rate of arm a
- N(a)
- number of times arm a has been pulled
UCB1
at = argmaxa [ Q(a) + c √( ln t / N(a) ) ]
- t
- total pulls so far
- c
- exploration strength; c = √2 is the classic UCB1 choice
Thompson sampling with a Beta posterior
θa ~ Beta(1 + S(a), 1 + F(a)), then play argmaxa θa
- S(a), F(a)
- wins and losses of arm a so far
- Beta(1, 1)
- the uniform prior we start from
Cumulative regret
Regret(T) = Σt=1..T ( p* − pat )
p* is the best arm's true rate. This is the expected regret, so luck on individual pulls does not add noise.
Code
import math, random
def epsilon_greedy(counts, wins, eps):
if random.random() < eps:
return random.randrange(len(counts))
return max(range(len(counts)), key=lambda k: wins[k] / counts[k] if counts[k] else 0)
def ucb1(counts, wins, c=math.sqrt(2)):
for k, n in enumerate(counts):
if n == 0:
return k # try every arm once
t = sum(counts)
return max(range(len(counts)),
key=lambda k: wins[k] / counts[k] + c * math.sqrt(math.log(t) / counts[k]))
def thompson(counts, wins):
samples = [random.betavariate(1 + wins[k], 1 + counts[k] - wins[k]) for k in range(len(counts))]
return max(range(len(counts)), key=lambda k: samples[k])
# After pulling arm k and seeing reward r (0 or 1):
# counts[k] += 1; wins[k] += rPractice problems
Problem 1: Which arm does UCB1 pull?
Show solutionHide solution
ln 100 ≈ 4.605
A: 0.60 + 1.414 × √(4.605 / 40) = 0.60 + 1.414 × 0.339 ≈ 1.080
B: 0.50 + 1.414 × √(4.605 / 10) = 0.50 + 1.414 × 0.679 ≈ 1.460
B has the lower estimate but far more uncertainty, so its upper bound is higher.
UCB1 pulls arm B
Problem 2: Same mean, different certainty
Show solutionHide solution
A: Beta(1 + 7, 1 + 3) = Beta(8, 4), mean 8 / 12 ≈ 0.667
B: Beta(1 + 1, 1 + 0) = Beta(2, 1), mean 2 / 3 ≈ 0.667
Beta(2, 1) has density 2x and CDF x², so P(θ > 0.9) = 1 − 0.9² = 0.19
For Beta(8, 4) the same probability is only about 0.02, because its posterior is much narrower.
Equal means, but B is sampled above 0.9 about 19% of the time, so it still gets explored
Discussion
Questions, ideas or a different approach? Join the conversation.