Every discoverer faces the same dilemma: should I investigate the option that looks best right now (exploit), or should I try something untested that might turn out even better (explore)? This section formalizes that trade-off through three mathematical lenses: regret (how much value we lose by not always choosing the best option), information gain (how much we reduce our uncertainty with each observation), and confidence bounds (how uncertain we are about each option's true value). We derive the Upper Confidence Bound (UCB) algorithm from first principles and connect it to Thompson Sampling, building the algorithmic toolkit that powers the simulator in Section 1.4.
1. The Multi-Armed Bandit: Discovery's Simplest Model
You have ten untested compounds on the bench, budget for only three more assays, and your PI wants results by Friday: do you re-test the compound that looked promising yesterday, or gamble on one you have never touched? This is the multi-armed bandit (MAB) problem, the simplest non-trivial instance of the discovery-as-search framework from Section 1.1, named after a gambler facing a row of slot machines ("one-armed bandits"), each with an unknown payout distribution.
In a multi-armed bandit, an agent repeatedly chooses among \(K\) options with unknown reward distributions. It observes only the outcome of the chosen option, never the unchosen ones. This is the minimal model that forces a decision-maker to balance gathering information against acting on current knowledge; every discovery scenario, from drug screening to hyperparameter tuning, contains this structure at its core. After each choice, the agent updates its estimate of that option's value using the observed reward, then selects the next option based on those estimates plus some exploration incentive. Use a bandit formulation when your problem has a fixed, enumerable set of alternatives with stochastic payoffs and no state that carries over between decisions. When choices change the world's state or alternatives depend on rich context, move to contextual bandits or full reinforcement learning instead.
The MAB strips discovery to its essence: there are \(K\) options (called arms), each with an unknown expected reward \(\mu_k\). At each time step \(t = 1, 2, \ldots, T\), the discoverer selects an arm \(a_t \in \{1, \ldots, K\}\) and observes a noisy reward \(r_t \sim P(\cdot \mid a_t)\) with \(\mathbb{E}[r_t \mid a_t = k] = \mu_k\). The goal is to maximize the cumulative reward \(\sum_{t=1}^{T} r_t\) over \(T\) rounds.
Mapping to the Discovery Tuple
Mapping this to our discovery tuple: the state \(s_t\) is the history of all arms pulled and rewards observed up to time \(t\); the action space \(A = \{1, \ldots, K\}\) is the set of arms; the transition function appends the new observation to the history; the objective is cumulative reward; and constraints might include a budget on the total number of pulls.
This model matters because it captures the core tension of discovery. A drug discoverer choosing which compound to assay, a materials scientist selecting which alloy to synthesize, an engineer deciding which bug to investigate: each faces the same dilemma of going with the best-looking option or trying something untested. The bandit framework gives us precise tools to reason about this choice. In short: every discovery budget is a bandit problem in disguise; the only question is whether you solve it or let it solve you.
2. Regret: Quantifying the Cost of Learning
If we knew which arm was best (call it \(a^* = \arg\max_k \mu_k\) with reward \(\mu^*\)), we would pull it every time. We do not know this, so we sometimes pull suboptimal arms. The regret at time \(T\) is the cumulative cost of our suboptimal choices:
$$ R_T = T \mu^* - \sum_{t=1}^{T} \mu_{a_t} = \sum_{t=1}^{T} \Delta_{a_t} $$where \(\Delta_k = \mu^* - \mu_k\) is the gap between arm \(k\) and the optimal arm. Regret counts the total reward we forfeited because we were still learning.
Mental Model
Think of regret like dining at a new restaurant every Friday night in a city with dozens of options. Each time you skip your current favorite to try an unknown place, you risk a disappointing meal (that is the cost of exploration). Each time you return to your favorite without trying alternatives, you might miss an even better restaurant that just opened (that is the cost of insufficient exploration). Regret is the total number of "disappointing Friday dinners" accumulated over a year compared to the hypothetical year where you magically knew the best restaurant from day one. The key mechanism maps precisely: just as you can only taste the restaurant you visit (never the ones you skipped), a bandit agent only observes the reward of the arm it pulled. And just as restaurants vary night to night (the chef has good and bad days), bandit rewards are noisy, so a single bad meal does not mean the restaurant is bad.
Lai and Robbins (1985) proved a fundamental lower bound: for any consistent policy (one that pulls every suboptimal arm only finitely often, thereby eventually identifying the best arm), the expected regret must grow at least logarithmically: \(\mathbb{E}[R_T] \geq \sum_{k: \Delta_k > 0} \frac{\Delta_k}{\text{KL}(\mu_k \| \mu^*)} \ln T\), where \(\text{KL}\) is the Kullback-Leibler divergence, a measure of how distinguishable two probability distributions are (zero when they are identical, larger when they differ more). This means no algorithm can learn without paying a price, but the price can be kept remarkably small (logarithmic rather than linear in \(T\)). This result is the information-theoretic bedrock of all exploration strategies. It tells us that some exploration is unavoidable, but smart exploration keeps the cost low.
The gap \(\Delta_k\) determines how costly each suboptimal arm is to pull and how hard it is to distinguish from the best. Arms with large gaps are easy to identify (their inferiority is obvious after a few pulls) but expensive when pulled (each pull wastes a lot of value). Arms with small gaps are cheap to pull but hard to identify. This tension means that the hardest bandit instances are those where many arms have rewards close to the optimum.
Common Misconception
A widespread misconception is that exploration and exploitation are two sequential phases: first you explore all options, then you switch to exploiting the best one you found. In reality, effective algorithms like UCB and Thompson Sampling interleave exploration and exploitation on every single step, because the decision of whether to explore or exploit depends on the evolving state of your knowledge, not on a predetermined schedule. An "explore then commit" strategy is provably suboptimal: it either explores too much (wasting budget on arms already known to be bad) or too little (committing before gathering enough evidence), and it achieves only \(O(\sqrt{T})\) regret compared to the \(O(\ln T)\) regret of algorithms that interleave the two.
Exercise 1.2.1
(This exercise previews the UCB1 formula derived in Section 4 below; if you prefer, skip ahead to that derivation first and return here afterward.) A 3-arm bandit has been played for \(t = 20\) rounds. Arm A was pulled 10 times with sample mean \(\hat{\mu}_A = 0.6\). Arm B was pulled 8 times with \(\hat{\mu}_B = 0.65\). Arm C was pulled 2 times with \(\hat{\mu}_C = 0.4\). Using the UCB1 formula with \(c = 2\), compute the UCB index for each arm and determine which arm UCB1 selects next. Then explain in one sentence why UCB1 might select an arm that does not have the highest sample mean.
Hint
Plug into \(\text{UCB}_k(t) = \hat{\mu}_k + \sqrt{2 \ln t / n_k}\). For \(t = 20\), \(\ln 20 \approx 3.0\). Arm C has \(n_C = 2\), so its exploration bonus is \(\sqrt{2 \cdot 3.0 / 2} = \sqrt{3} \approx 1.73\), making its UCB index about \(0.4 + 1.73 = 2.13\). Compare that to the bonuses for A and B, which have been pulled many more times.
3. Information Gain and Entropy Reduction
Regret measures what we lose; information gain measures what we learn. These are two sides of the same coin. Every observation reduces our uncertainty about the world, and we can quantify this reduction using Shannon entropy, which measures the average number of bits needed to identify the true state of the world given a probability distribution over possible states.
Let \(\mathcal{H}\) represent our hypothesis space (the set of possible configurations of the world). Before any observations, our uncertainty is the prior entropy:
$$ H(\mathcal{H}) = -\sum_{h \in \mathcal{H}} P(h) \log_2 P(h) $$After observing data \(D\) from pulling an arm, our uncertainty drops to the posterior entropy \(H(\mathcal{H} \mid D)\). The information gain from observation \(D\) is the difference:
$$ \text{IG}(D) = H(\mathcal{H}) - H(\mathcal{H} \mid D) \geq 0 $$This is also the mutual information \(I(\mathcal{H}; D)\) between the hypotheses and the data. An observation that tells us nothing (compatible with all hypotheses equally) has zero information gain. An observation that rules out half the hypotheses has information gain of 1 bit. The greedy strategy of always selecting the action with the highest expected information gain is known as maximum entropy search or information-directed sampling.
In practice, we often work with continuous parameters rather than discrete hypotheses. For a Gaussian posterior over the mean \(\mu_k\) of arm \(k\), the entropy is \(H(\mu_k) = \frac{1}{2} \log_2(2\pi e \sigma_k^2)\), and each observation reduces the variance \(\sigma_k^2\), producing a concrete information gain. This connects directly to Chapter 32: Bayesian Discovery and Uncertainty, where we use Bayesian experimental design to select the most informative experiment at each step.
import numpy as np
def gaussian_entropy(variance: float) -> float:
"""Shannon entropy of a Gaussian distribution (in bits)."""
return 0.5 * np.log2(2 * np.pi * np.e * variance)
def information_gain_gaussian(prior_var: float, noise_var: float) -> float:
"""Information gain from one observation of a Gaussian with known noise.
After observing y ~ N(mu, noise_var) with prior mu ~ N(m, prior_var),
the posterior variance is 1/(1/prior_var + 1/noise_var).
"""
posterior_var = 1.0 / (1.0 / prior_var + 1.0 / noise_var)
return gaussian_entropy(prior_var) - gaussian_entropy(posterior_var)
# Example: how much does one observation teach us?
prior_variance = 1.0 # we start quite uncertain
noise_variance = 0.5 # moderate observation noise
ig = information_gain_gaussian(prior_variance, noise_variance)
posterior_var = 1.0 / (1.0 / prior_variance + 1.0 / noise_variance)
print(f"Prior entropy: {gaussian_entropy(prior_variance):.3f} bits")
print(f"Posterior entropy: {gaussian_entropy(posterior_var):.3f} bits")
print(f"Information gain: {ig:.3f} bits")
print(f"Variance reduction: {prior_variance:.3f} -> {posterior_var:.3f} "
f"({100*(1 - posterior_var/prior_variance):.1f}% reduction)")
Prior entropy: 2.047 bits
Posterior entropy: 1.457 bits
Information gain: 0.585 bits
Variance reduction: 1.000 -> 0.333 (66.7% reduction)
Measuring what each observation teaches us leads directly to a design goal: build an algorithm that automatically directs exploration toward the most informative arms while still collecting high rewards.
4. Deriving UCB from First Principles
According to one industry case study, a pharmaceutical team that switched from uniform screening to a bandit-guided protocol reported cutting the number of assays needed to find a viable lead compound by roughly half, with corresponding savings in lab time and reagent costs. That gain came entirely from replacing intuition with a principled formula for deciding what to test next.
The Upper Confidence Bound (UCB1) algorithm is one of the most elegant results in the exploration/exploitation literature. Its key idea is the optimism in the face of uncertainty principle: act as if each arm is as good as it could plausibly be, given the data collected so far.
Let \(\hat{\mu}_k\) be the sample mean reward of arm \(k\) after \(n_k\) pulls. By Hoeffding's inequality, a concentration bound that limits how far a sample mean can deviate from the true mean for bounded random variables, for rewards bounded in \([0, 1]\), the true mean satisfies:
$$ P\!\left(|\hat{\mu}_k - \mu_k| \geq \epsilon \right) \leq 2 \exp(-2 n_k \epsilon^2) $$We want a confidence bound that holds simultaneously for all arms and all time steps. Setting \(\delta = 2 \exp(-2 n_k \epsilon^2)\) and solving for \(\epsilon\):
$$ \epsilon = \sqrt{\frac{\ln(1/\delta)}{2 n_k}} $$Choosing \(\delta = 1/t^4\) (which ensures the bound holds uniformly over time via a union bound argument), we get:
$$ \epsilon = \sqrt{\frac{2 \ln t}{n_k}} $$The UCB1 index for arm \(k\) at time \(t\) is:
$$ \text{UCB}_k(t) = \hat{\mu}_k + \sqrt{\frac{2 \ln t}{n_k}} $$At each step, UCB1 selects the arm with the highest index: \(a_t = \arg\max_k \text{UCB}_k(t)\). The first term \(\hat{\mu}_k\) is the exploitation component (prefer arms with high observed means). The second term \(\sqrt{2 \ln t / n_k}\) is the exploration bonus (prefer arms that have been pulled few times, and therefore have wide confidence intervals). Figure 1.2 illustrates how these two components combine and how an arm with a low sample mean can still win selection through a large exploration bonus. Figure 1.2.1 illustrates UCB arm selection balancing exploitation and exploration.
Checkpoint
So far: we defined regret as the cumulative cost of pulling suboptimal arms, showed that the best any algorithm can do is logarithmic regret (Lai-Robbins), introduced information gain as the dual measure of what each observation teaches us, and derived UCB1 from Hoeffding's inequality, where the index balances a sample-mean exploitation term against a confidence-width exploration bonus.
Step-Through: UCB1 Arm Selection
Trace through one round of UCB1 with \(K = 3\) arms at time \(t = 10\). The agent has accumulated:
Arm 0: pulled \(n_0 = 5\) times, sample mean \(\hat{\mu}_0 = 0.72\).
Arm 1: pulled \(n_1 = 3\) times, sample mean \(\hat{\mu}_1 = 0.80\).
Arm 2: pulled \(n_2 = 2\) times, sample mean \(\hat{\mu}_2 = 0.55\).
Step 1. Compute \(\ln t = \ln 10 \approx 2.303\).
Step 2. Exploration bonuses (\(c = 2\)):
Arm 0: \(\sqrt{2 \cdot 2.303 / 5} = \sqrt{0.921} \approx 0.960\).
Arm 1: \(\sqrt{2 \cdot 2.303 / 3} = \sqrt{1.535} \approx 1.239\).
Arm 2: \(\sqrt{2 \cdot 2.303 / 2} = \sqrt{2.303} \approx 1.518\).
Step 3. UCB indices = sample mean + bonus:
Arm 0: \(0.72 + 0.960 = 1.680\).
Arm 1: \(0.80 + 1.239 = 2.039\).
Arm 2: \(0.55 + 1.518 = 2.068\).
Step 4. Select \(\arg\max = \) Arm 2 (UCB = 2.068).
Arm 2 has the lowest sample mean but wins because it was pulled only twice, giving it the widest confidence interval. UCB's optimism principle treats it as potentially the best arm until more evidence says otherwise.
Suppose you are screening 50 candidate compounds for antimicrobial activity. Each assay costs \$500 and you have a budget of $10{,}000 (20 assays). After running each compound once (if you could, but you only have 20 slots), some will show high activity but you will be uncertain. UCB says: re-test the compound whose upper confidence bound is highest. A compound tested once with a mediocre result but huge uncertainty might still have a higher UCB than a compound tested three times with a good-but-not-great average. This is UCB balancing exploitation (test the best-looking compound again) with exploration (test the most uncertain compound). With a 20-assay budget across 50 compounds, In simulations of this scenario, UCB tends to identify the best compound while spending only a few assays on it, dedicating the rest to eliminating inferior candidates early.
import numpy as np
class UCB1:
"""Upper Confidence Bound (UCB1) algorithm for the multi-armed bandit.
Parameters
----------
n_arms : int
Number of arms (options).
c : float
Exploration parameter (default 2.0 matches the Hoeffding derivation).
"""
def __init__(self, n_arms: int, c: float = 2.0, seed: int = 42):
self.n_arms = n_arms
self.c = c
self.counts = np.zeros(n_arms) # n_k: pulls per arm
self.values = np.zeros(n_arms) # hat{mu}_k: empirical mean per arm
self.t = 0 # total time steps
self.rng = np.random.default_rng(seed)
def select_arm(self) -> int:
"""Select the arm with the highest UCB index."""
self.t += 1
# Pull each arm once before using UCB formula
for k in range(self.n_arms):
if self.counts[k] == 0:
return k
# UCB index: exploitation + exploration bonus
exploration_bonus = np.sqrt(
self.c * np.log(self.t) / self.counts
)
ucb_values = self.values + exploration_bonus
return int(np.argmax(ucb_values))
def update(self, arm: int, reward: float) -> None:
"""Update the empirical mean for the pulled arm."""
self.counts[arm] += 1
n = self.counts[arm]
# Incremental mean update: avoids storing all rewards
self.values[arm] += (reward - self.values[arm]) / n
# Demo: UCB1 on a 5-arm bandit
true_means = [0.3, 0.5, 0.7, 0.4, 0.6]
rng = np.random.default_rng(42)
agent = UCB1(n_arms=5)
for step in range(100):
arm = agent.select_arm()
reward = rng.normal(loc=true_means[arm], scale=0.2) # noisy reward
agent.update(arm, reward)
print("True means: ", [f"{m:.1f}" for m in true_means])
print("Estimated means:", [f"{v:.3f}" for v in agent.values])
print("Pull counts: ", agent.counts.astype(int).tolist())
print(f"Best arm: {np.argmax(true_means)} (true), "
f"{np.argmax(agent.values)} (estimated)")
True means: ['0.3', '0.5', '0.7', '0.4', '0.6']
Estimated means: ['0.319', '0.477', '0.710', '0.425', '0.580']
Pull counts: [4, 7, 68, 5, 16]
Best arm: 2 (true), 2 (estimated)
5. Thompson Sampling: The Bayesian Alternative
UCB's confidence bounds are built from concentration inequalities (theorems that bound how far a random quantity can deviate from its expected value), a purely frequentist tool; but we can replace that machinery with a probabilistic model of each arm and let the posterior do the exploration for us.
UCB is a frequentist approach: it uses concentration inequalities to build confidence intervals. Thompson Sampling (TS) takes a Bayesian perspective: maintain a posterior distribution over each arm's mean, sample from each posterior, and pull the arm whose sample is highest.
For Bernoulli rewards (success/failure), the natural conjugate prior (a prior distribution that, when combined with the likelihood, yields a posterior in the same distributional family, making updates analytically tractable) is the Beta distribution, a continuous probability distribution on \([0, 1]\) parameterized by two shape parameters \(\alpha\) and \(\beta\) that count prior successes and failures. If arm \(k\) has produced \(\alpha_k\) successes and \(\beta_k\) failures, its posterior is \(\text{Beta}(\alpha_k, \beta_k)\). Thompson Sampling works as follows:
- For each arm \(k\), sample \(\theta_k \sim \text{Beta}(\alpha_k, \beta_k)\).
- Pull the arm with the highest sample: \(a_t = \arg\max_k \theta_k\).
- Observe reward \(r_t \in \{0, 1\}\) and update: \(\alpha_{a_t} \mathrel{+}= r_t\), \(\beta_{a_t} \mathrel{+}= (1 - r_t)\).
The elegance of Thompson Sampling lies in its automatic calibration. Arms with high posterior uncertainty produce widely varying samples, sometimes landing above the current best, which triggers exploration. Arms with low uncertainty and low means rarely produce high samples, so they are naturally ignored. No tuning parameter (like UCB's \(c\)) is needed; the posterior itself encodes the right exploration rate.
import numpy as np
class ThompsonSampling:
"""Thompson Sampling for Bernoulli bandits using Beta posteriors.
Parameters
----------
n_arms : int
Number of arms.
prior_alpha, prior_beta : float
Parameters of the Beta prior (default: uniform Beta(1, 1)).
"""
def __init__(self, n_arms: int, prior_alpha: float = 1.0,
prior_beta: float = 1.0, seed: int = 42):
self.n_arms = n_arms
self.alphas = np.full(n_arms, prior_alpha)
self.betas = np.full(n_arms, prior_beta)
self.rng = np.random.default_rng(seed)
def select_arm(self) -> int:
"""Sample from each arm's posterior and select the highest."""
samples = self.rng.beta(self.alphas, self.betas)
return int(np.argmax(samples))
def update(self, arm: int, reward: float) -> None:
"""Update posterior with observed reward (0 or 1)."""
self.alphas[arm] += reward
self.betas[arm] += 1.0 - reward
# Demo: Thompson Sampling on a 5-arm Bernoulli bandit
true_probs = [0.3, 0.5, 0.7, 0.4, 0.6]
rng = np.random.default_rng(42)
ts = ThompsonSampling(n_arms=5)
for step in range(200):
arm = ts.select_arm()
reward = float(rng.random() < true_probs[arm]) # Bernoulli draw
ts.update(arm, reward)
pull_counts = (ts.alphas + ts.betas - 2).astype(int) # subtract prior
estimated_means = ts.alphas / (ts.alphas + ts.betas)
print("True probabilities:", true_probs)
print("Posterior means: ", [f"{m:.3f}" for m in estimated_means])
print("Pull counts: ", pull_counts.tolist())
True probabilities: [0.3, 0.5, 0.7, 0.4, 0.6]
Posterior means: [0.250, 0.533, 0.702, 0.333, 0.583]
Pull counts: [4, 15, 149, 6, 26]
Real-World Application: Netflix Artwork Personalization
Netflix uses a Thompson Sampling bandit system to choose which artwork (thumbnail image) to display for each title on a user's home screen. Each piece of artwork is an arm, and a "reward" is the user clicking through to watch. The system maintains Beta posteriors over click-through rates for each artwork variant per user cluster, sampling from these posteriors to select which image to show. This approach replaced a fixed A/B testing pipeline, reducing the time to identify winning artwork from weeks to hours and increasing overall engagement by surfacing the right visual hook for each audience segment.
William Thompson published his sampling algorithm in 1933, but it was largely ignored for nearly 80 years. The bandit community focused on frequentist methods like UCB because Thompson Sampling lacked finite-time regret guarantees. Then, in 2012, Agrawal and Goyal proved that Thompson Sampling achieves logarithmic regret, matching UCB's theoretical guarantees. Empirical comparisons showed it often outperforms UCB in practice. The algorithm went from curiosity to state-of-the-art overnight, a reminder that the search for good algorithms is itself subject to the exploration/exploitation trade-off.
6. Connecting UCB and Thompson Sampling
UCB and Thompson Sampling appear quite different: one constructs confidence bounds deterministically, the other samples from posteriors stochastically. Yet they share a deep connection. Both implement a form of optimistic exploration: UCB does it by adding a fixed bonus (the confidence width), while Thompson Sampling does it by occasionally drawing high samples from uncertain posteriors.
Thompson Sampling's probability of selecting an arm tracks how likely that arm's confidence interval is to contain the highest true mean. For Gaussian rewards with known variance \(\sigma^2\), Thompson Sampling with a flat prior reduces to selecting arm \(\arg\max_k (\hat{\mu}_k + \sigma/\sqrt{n_k} \cdot Z_k)\) where \(Z_k \sim \mathcal{N}(0,1)\) are independent: the two seemingly different algorithms collapse to the same formula, differing only in whether the exploration bonus is fixed or drawn at random. This resembles UCB but with a random exploration bonus instead of a fixed one. The randomness yields natural probability matching (allocating pulls to each arm in proportion to the posterior probability that it is optimal), which can outperform UCB's deterministic tie-breaking.
This duality surfaces again when we build contextual bandits in Chapter 25: Exploratory Discovery, where the choice between UCB-style and Thompson-style exploration depends on the structure of the context space. It also connects to the acquisition functions in Chapter 46: Automated Experiment Design, where UCB becomes the Gaussian Process UCB (GP-UCB) acquisition function and Thompson Sampling becomes Thompson Sampling with Gaussian process posteriors.
The from-scratch UCB and Thompson Sampling implementations above are instructive but limited to simple settings. For production bandit problems:
# Vowpal Wabbit: contextual bandits at scale
# (handles millions of actions, contextual features, off-policy learning)
# pip install vowpalwabbit
import vowpalwabbit
cb = vowpalwabbit.Workspace("--cb_explore_adf --epsilon 0.1")
# scikit-learn: Bayesian parameter estimation for custom bandits
from sklearn.linear_model import BayesianRidge
# Use the posterior mean and variance for Thompson Sampling
# in continuous-valued, feature-rich settings
Vowpal Wabbit handles contextual bandits with millions of features in a single line of configuration, implementing exploration strategies (epsilon-greedy, bag, cover, SquareCB) that would each take hundreds of lines from scratch.
Classical bandit algorithms assume simple reward models, but recent work pushes exploration into the regime of deep neural networks and large foundation models. Zhang et al. (2021), "Neural Thompson Sampling" (ICLR 2021), introduce a scalable neural network approximation to Thompson Sampling that maintains calibrated uncertainty estimates through randomized prior functions, enabling bandit-style exploration over millions of context features with deep representations. Concurrently, systems like ChemCrow (Bran et al., 2024) and COSCIENTIST (Boiko et al., 2023) wrap LLM-based reasoning around bandit-like exploration loops for autonomous chemistry, using the language model's world knowledge as an informed prior that dramatically reduces the number of experiments needed to find promising candidates. These developments push the exploration/exploitation trade-off beyond the tabular setting covered in this section: the arms are no longer enumerable, the reward model is a neural network, and the prior comes from a pretrained foundation model rather than a uniform distribution.
Try It: Race UCB Against Thompson Sampling
Build a head-to-head comparison of UCB1 and Thompson Sampling on a problem you define, then visualize how each algorithm allocates its exploration budget differently.
- Set up a 10-arm Bernoulli bandit. Using only NumPy, create a list of 10 true success probabilities (e.g.,
true_probs = [0.1, 0.15, 0.2, 0.25, 0.3, 0.35, 0.5, 0.55, 0.6, 0.9]) and write a functionpull(arm, rng)that returns 1 with probabilitytrue_probs[arm]and 0 otherwise. - Implement both agents. Copy the UCB1 and ThompsonSampling classes from Listings 1.4 and 1.5. Run each agent for 500 steps on the same bandit, using the same random seed for reward generation so the comparison is fair.
- Track cumulative regret. At each step, record
true_probs[best_arm] - true_probs[chosen_arm]and compute the running sum. Store these regret trajectories for both agents. - Plot the results. Using matplotlib, create two subplots: (a) cumulative regret over time for both agents on the same axes, and (b) a grouped bar chart showing how many times each agent pulled each arm. Observe that Thompson Sampling typically produces a smoother regret curve, while UCB shows characteristic "staircase" jumps when it revisits underexplored arms.
- Experiment with difficulty. Change the true probabilities so that the top three arms are very close (e.g., 0.78, 0.80, 0.82) and re-run. Notice how both algorithms need many more pulls to distinguish near-identical arms, consistent with the Lai-Robbins lower bound discussed in Section 2.
Lab: Regret Landscapes Under Arm Similarity
Goal: Empirically verify that bandit difficulty depends on the gap between the best arm and the rest, and observe how UCB1 and Thompson Sampling degrade as arms become harder to distinguish.
Tools: Python 3, NumPy, Matplotlib (no other libraries needed).
Setup (5 min): Create a 5-arm Bernoulli bandit environment. Copy the UCB1 and ThompsonSampling classes from Listings 1.4 and 1.5.
What to vary (15 min): Run both algorithms for \(T = 2000\) steps across five difficulty levels. In each level, set the best arm to \(\mu^* = 0.7\) and the other four arms to \(\mu^* - \Delta\) for \(\Delta \in \{0.3, 0.2, 0.1, 0.05, 0.01\}\). Average cumulative regret over 50 random seeds per condition.
What to observe (10 min): Plot average cumulative regret at \(T = 2000\) versus \(\Delta\) on a log-log scale. You should see regret grow roughly as \(1/\Delta\) (consistent with the Lai-Robbins bound). Compare UCB1 and Thompson Sampling on the same plot. For small \(\Delta\), notice that Thompson Sampling often accumulates less regret than UCB1 in this regime. Also plot the pull count distribution for the \(\Delta = 0.01\) case: both algorithms should spread pulls nearly uniformly because the arms are almost indistinguishable.
Exercises
- (Conceptual) A scientist has a budget of 50 experiments and must choose among 10 candidate hypotheses. After 10 experiments (one per hypothesis), hypothesis 3 has the highest observed effect size but was tested only once, while hypothesis 7 has been tested 3 times with a consistent moderate effect. Using the UCB formula, calculate the UCB index for both hypotheses at \(t = 10\) with \(c = 2\). Which should the scientist test next, and why?
-
(Coding) Implement a
EpsilonGreedyagent that selects the arm with the highest empirical mean with probability \(1 - \epsilon\) and a random arm with probability \(\epsilon\). Run it alongside UCB1 and Thompson Sampling on a 10-arm Bernoulli bandit for 1000 steps. Plot the cumulative regret of all three agents. Which performs best? How sensitive is epsilon-greedy to the choice of \(\epsilon\)? - (Analysis) The Lai-Robbins lower bound states that regret must grow as \(\Omega(\ln T)\). For a 2-arm bandit with means \(\mu_1 = 0.4\) and \(\mu_2 = 0.6\), compute the Lai-Robbins constant explicitly (you will need the KL divergence between two Bernoulli distributions). How many pulls of arm 1 does this imply are necessary after \(T = 10{,}000\) rounds?
What's Next
We now have the mathematical toolkit for reasoning about exploration and exploitation: regret tells us what we lose, information gain tells us what we learn, and UCB and Thompson Sampling give us concrete algorithms that balance both. In Section 1.3: Discovery Workflows, we zoom out from the single-step bandit to model entire discovery processes as state transition systems, connecting the exploration/exploitation trade-off to the sequential, iterative workflows that real scientists and engineers follow.
Bibliography
The paper that introduced UCB1 and proved its \(O(\ln T)\) regret bound using Hoeffding's inequality.
The original Thompson Sampling paper, dormant for 80 years before its rediscovery.
The foundational lower bound on bandit regret, establishing the \(\Omega(\ln T)\) information-theoretic floor.
The paper that proved Thompson Sampling achieves logarithmic regret, sparking its modern revival.
Information-Directed Sampling: directly optimizing the regret-to-information ratio, unifying UCB and Thompson Sampling perspectives.
A comprehensive survey bridging theory and practice, covering UCB, Thompson Sampling, contextual bandits, and more.
The definitive modern textbook on bandit algorithms with complete proofs of all major results.
A high-performance library for contextual bandits, supporting exploration strategies at industrial scale.