Multi-Armed Bandits
One state, several levers, unknown payouts: the smallest possible setup that still forces you to choose between cashing in and finding out.
Why Does This Exist?
Every agent faces one tension before all others: play the current best or test the rest. Full RL tangles that tension with states, transitions, and delayed rewards. Bandits isolate it. One state, actions called arms, each paying from a fixed unknown distribution with true mean . Pull for rounds, keep the total. That is the whole problem, named for the casino row of lever machines in Sutton and Barto (Chapter 2).
Solving it well matters beyond casinos: trial allocation, ad placement, and any A/B choice with a cost per sample. The rules invented here, UCB1 from Auer and colleagues (2002) and Thompson sampling from Thompson (1933), became the selection rules inside tree search and exploration bonuses everywhere. For the general tension see exploration vs exploitation; for the two smart rules see upper confidence bound and Thompson sampling.
The one idea you need first
An action value estimates the mean payout of one arm. The agent pulls, observes a reward, and nudges the estimate. Everything on this page is about which arm to pull next given those estimates, nothing about states or futures.
Think of It Like This
Checkout lanes at a huge grocery store
Your store has twelve lanes. Lane 3 has a veteran cashier and usually moves fast. Lane 7 just opened with someone new who might be faster. Every visit you pick one lane. Always picking lane 3 is exploitation: steady, but you never learn about lane 7. Trying lane 7 is exploration with a real price: a slow cart ahead of you.
A good rule mixes both: mostly lane 3, plus lane 7 exactly when it has been ignored long enough that its unknown speed outweighs its average. That bonus for the long ignored lane is the uncertainty term in UCB.
Where it stops: store lanes drift with crowds and staffing, while the basic bandit assumes fixed payout rates. Shoppers also see queue lengths before choosing, but a bandit agent sees only its own past rewards.
How It Actually Works
Each arm pays rewards with unknown mean . The agent tracks , usually the sample average, updated incrementally so no history is stored. After the th pull of arm pays :
The new reward moves the average by one th of the surprise. Estimates in hand, three standard rules pick the next arm:
- Epsilon-greedy. With probability pull the current best arm, else pull uniformly at random. One knob, no memory of which arm needs testing most.
- UCB1. Pull with total pulls and pulls of arm . The bonus is large for rarely pulled arms and decays for tested ones: optimism in the face of uncertainty.
- Thompson sampling. Keep a posterior belief over each , sample one plausible mean per arm, pull the highest sample. Winners get pulled more, losers still get sampled when beliefs overlap.
Success is measured by regret: the gap between always pulling the true best arm and what the rule actually banked. Good rules keep expected regret growing sublinearly, typically for UCB1, so the average shortfall per round shrinks toward zero.
Worked example
Arm estimates after total pulls: arm1 at over , arm2 at over . Since :
The next pull goes to arm2 despite the worse average: two pulls is too little evidence. Suppose arm2 then pays on its 3rd counted pull while its average sat at after earlier rounds. The incremental update gives when that reward is the 5th observation. One surprise moves a young average fast and an old average barely, which is exactly what the step does. The diagram draws the against decision.
Code
This snippet replays the worked example: one incremental average update and one UCB1 decision.
import math
def incremental_mean(q: float, n_new: int, reward: float) -> float: """Fold the newest reward into the average; n_new counts this pull.""" return q + (reward - q) / n_new
def ucb1(q: float, n: int, t: int) -> float: """UCB1 score for one arm after t total pulls.""" return q + math.sqrt(2 * math.log(t) / n)
print(f"Q new={incremental_mean(1.50, 5, 3.00):.4f}") # -> Q new=1.8000print(f"arm1={ucb1(0.70, 8, 12):.4f}") # -> arm1=1.4882print(f"arm2={ucb1(0.50, 2, 12):.4f}") # -> arm2=2.0764The printout matches the hand arithmetic: for the update and against for the decision, so arm2 gets the next pull.
Watch Out For
Averaging with 1/n after the payouts move
The incremental rule weights every reward equally, which is only right when never changes. If payouts drift, ancient rewards anchor the average and the agent keeps pulling a dead arm. Symptom: the estimated best lags reality by weeks while fresh rewards disagree with it. Fix: replace with a constant step , so recent rewards count more and the estimate tracks the drift.
Exploring uniformly forever with epsilon-greedy
Fixed keeps spending the same share of pulls on random arms even after the ranking is certain, so regret grows linearly instead of logarithmically. Symptom: total reward trails UCB1 by a widening gap as grows, with no late recovery. Fix: decay over time, or switch to UCB1 or Thompson sampling, which concentrate exploration exactly where uncertainty remains.
The Quick Version
- A bandit is one state with unknown payout distributions, isolating the choice between cashing in and finding out.
- Averages update incrementally with , so no reward history needs storing.
- Epsilon-greedy explores blindly, UCB1 adds an uncertainty bonus , Thompson sampling pulls the best of sampled beliefs.
- Regret measures the shortfall against always playing the true best arm; UCB1 keeps it growing only logarithmically.
- Fixed payout rates are assumed; drifting payouts need a constant step size so estimates track instead of anchor.