Module 3. Bandits, exploration, and one-step decisions
Try each exercise first. Expand worked answers or project guidance when needed; their open state is shared between RU/EN.
Download course files (.zip) 382.1 KBThe archive preserves the source folder structure, both languages, and supporting files.
Bandit or sequential problem
For each case, choose the most accurate model: ordinary bandit, contextual bandit, or Markov decision process (MDP).
-
For every independent request, a service chooses one of three models. Request features are known, one scalar score arrives after the response, and the choice does not affect later requests.
-
An agent opens web pages, extracts information, and decides which link to follow next.
-
Two fixed banners are rotated for an otherwise identical audience; only the click on the displayed banner is observed.
-
A dialogue system chooses an utterance, after which the user replies and changes the course of the conversation.
For every answer, name one assumption whose violation would change the model.
Show answer and solution
-
Contextual bandit. Request features affect the model choice, but the response does not change the next request. The abstraction fails if serving a user affects retention or later requests.
-
Markov decision process (MDP). Opening a page changes the available links and information, so the action creates a successor state.
-
Ordinary bandit. Context is treated as constant and only the displayed banner's reward is observed. Audience drift or changing banner appeal would introduce non-stationarity or context.
-
MDP, possibly partially observed. The utterance changes the future dialogue. A bandit would be adequate only for one isolated utterance with no continuation.
The decisive test is whether action value depends on a controlled future state.
What regret measures
Select three correct statements.
-
A trajectory's pseudo-regret can be computed without knowing the true reward means.
-
The decomposition shows that an action with a small gap may be selected more often at a small immediate cost.
-
Sublinear regret means .
-
Logarithmic regret guarantees that an algorithm outperforms every other algorithm at every finite horizon.
-
Realized cumulative reward remains random under a fixed action sequence when rewards are noisy.
Show answer and solution
Statements 2, 3, and 5 are correct.
Pseudo-regret requires the true means, which are normally known only to a simulator. The gap decomposition assigns cost to every suboptimal selection. Sublinear regret is exactly . An asymptotic order does not rank all algorithms at every finite horizon. Finally, noisy rewards make realized cumulative reward random even under a fixed action sequence.
Constant ε and optimistic initialization
Select the two most accurate statements.
-
With fixed , ε-greedy continues to select suboptimal actions with positive frequency.
-
Optimistic initialization guarantees logarithmic regret in every non-stationary environment.
-
A constant step size can help under reward drift because it downweights old observations.
-
Pure greediness has linear regret on every individual run.
-
Any decaying schedule automatically gives optimal regret.
Show answer and solution
Statements 1 and 3 are correct.
A fixed ε leaves a permanent random branch. A constant step size exponentially downweights old observations and can track drift. Optimistic initialization is not a universal guarantee, pure greediness can succeed on a particular run, and a decaying ε schedule can stop exploration too early if it decreases too fast.
Scope of a classical confidence-bound guarantee
An upper confidence bound (UCB) method is used in a stationary bandit; UCB1 is its classical variant.
Select three conditions or qualifications needed to read the UCB1 guarantee correctly.
-
Rewards are bounded; the standard theorem uses .
-
Reward means remain unchanged over time.
-
The logarithmic bound is independent of action gaps .
-
Finite-horizon regret may remain substantial when two actions are nearly equal.
-
Any neural uncertainty score is automatically a UCB confidence bound.
-
The action set may change arbitrarily during learning while the classical UCB1 proof remains unchanged.
Show answer and solution
An upper confidence bound (UCB) method and its classical UCB1 variant must be read together with their assumptions. Statements 1, 2, and 4 are correct.
Classical UCB1 uses bounded rewards in , stationary means, and an instance-dependent bound involving . Nearly tied actions can therefore be expensive to distinguish. An arbitrary neural uncertainty score is not automatically a confidence bound. An arbitrarily changing action set is a different problem, so the classical proof cannot simply be reused unchanged.
What Thompson sampling does
A Bernoulli action starts with a prior. After 8 successes and 2 failures, select three correct statements.
-
The posterior is .
-
The algorithm always selects the largest posterior mean with no randomness.
-
On every round, it samples one plausible mean for every action.
-
A poorly observed action can be selected because its posterior is wide.
-
The 1933 publication date itself proves a modern logarithmic regret guarantee.
Show answer and solution
Statements 1, 3, and 4 are correct.
The posterior is
Thompson sampling draws one plausible mean per action and selects the largest draw. A broad posterior can therefore trigger exploration. The method is not greedy in the posterior mean, and the historical publication date does not establish later regret theory.
An LLM as a contextual bandit
Select three correct statements.
-
If the prompt is context, a full response is one action, a scalar reward follows, and the episode ends, the external problem is a contextual bandit.
-
The whole-response abstraction implies that tokens cease to be sequential actions.
-
A multi-turn tool-using agent usually needs state that contains consequences of previous actions.
-
Choosing among several models for independent requests can be a contextual bandit.
-
Every Best-of-N procedure is automatically a multi-armed bandit.
Show answer and solution
Statements 1, 3, and 4 are correct.
A whole-response terminal abstraction is a contextual bandit. Token generation remains sequential internally. Tool-using and multi-turn systems need state because actions change what happens next. Model routing over independent requests is a natural contextual bandit. Fixed-policy Best-of-N is repeated sampling and selection, not automatically a multi-armed bandit.
Overlap in logged-policy evaluation
Inverse propensity scoring (IPS) evaluates a target policy from logs generated by behavior policy . Its self-normalized form is self-normalized inverse propensity scoring (SNIPS), while a doubly robust (DR) estimator combines reward predictions with propensity weights.
Select three correct statements.
-
Every pair with requires .
-
Small behavior probabilities can produce large weights and high variance.
-
If behavior probabilities were not logged, they can always be reconstructed exactly from selected actions.
-
Self-normalized IPS is generally biased.
-
A doubly robust estimator creates an observed reward for an action that was never selected or modeled.
Show answer and solution
Inverse propensity scoring (IPS), self-normalized inverse propensity scoring (SNIPS), and a doubly robust (DR) estimator all depend on how the log was collected. Statements 1, 2, and 4 are correct.
IPS needs positive behavior probability wherever the target policy places mass. Small propensities cause large importance weights and high variance. SNIPS usually reduces sensitivity to scale but introduces bias. Chosen actions alone do not identify the randomized selection probability, and DR does not manufacture observed counterfactual outcomes without support.
Temperature, Best-of-N, and entropy
Item 6 refers to group relative policy optimization (GRPO). Select four correct statements.
-
Raising the temperature of a pure softmax with fixed logits flattens the distribution.
-
Temperature sampling from a frozen LLM updates the policy from reward by itself.
-
For independent candidates with the same success probability , is the probability that the pool contains at least one success.
-
The finite-sample pass@ estimator from candidates and successes is .
-
Lower entropy always means a worse policy.
-
If all rewards in a GRPO group are equal, subtracting the group mean gives zero relative signal.
Show answer and solution
Statements 1, 3, 4, and 6 are correct.
Temperature changes a fixed softmax distribution but does not update parameters from reward. The coverage formula assumes independence and the same success probability for a fixed prompt. Perfect selection is an additional condition for equating coverage with returned-answer accuracy; a successful candidate can exist without being selected. The combinatorial expression estimates pass@ from a finite sample. Entropy can decrease for legitimate reasons; it becomes a failure mode only in context. Equal group rewards yield zero mean-centered advantages.
Incremental mean
An action has been selected 4 times and its current estimate is . The fifth reward is .
-
Compute incrementally.
-
Find the sum of the first four rewards consistent with .
-
Verify the result with the direct mean of all five observations.
Show answer and solution
The first four rewards sum to
Directly,
Regret decomposition
Three actions have means
Over rounds, expected selection counts are
-
Find and the gaps .
-
Compute expected regret.
-
How does regret change if 10 selections of action 2 are replaced by 10 selections of action 3?
Show answer and solution
The optimum is , so
Expected regret is
Replacing ten selections of action 2 by action 3 removes regret, leaving .
One UCB1 index calculation
At round , two actions have estimates and counts
For
compute and to three decimals and state the selected action. Explain why the action with the lower sample mean may win.
Show answer and solution
Action 2 is selected. Its sample mean is lower, but it has only one quarter as many observations, so its uncertainty bonus is much larger. The index is an optimistic selection rule, not a claim that action 2 is truly better.
Beta posterior
Two Bernoulli actions use priors.
- action 1: 12 successes and 8 failures;
- action 2: 3 successes and 1 failure.
-
Write both posteriors.
-
Compute their means.
-
Which action has the larger posterior mean?
-
Why can Thompson sampling still select the other action?
Show answer and solution
Action 1:
Action 2:
Action 2 has the larger posterior mean, but its posterior is based on only four observations and is wider. A sample from can exceed a sample from on a given round, so Thompson sampling can choose action 1.
IPS, SNIPS, and DR on three rows
The log contains:
| 1 | 1 | 0.50 | 1.00 | 0.60 | 0.70 |
| 2 | 0 | 0.25 | 0.50 | 0.30 | 0.40 |
| 3 | 1 | 0.80 | 0.40 | 0.70 | 0.65 |
-
Compute .
-
Compute .
-
Compute .
-
Compute the doubly robust (DR) estimate.
-
Which row contributes most to IPS variance, and why?
Show answer and solution
The weights are
IPS:
SNIPS:
The row-wise DR terms are
Therefore
Rows 1 and 2 have the largest weights and are most capable of inflating variance. In general, the most dangerous row combines small behavior probability with non-zero target probability.
Entropy-regularized policy, Best-of-N, and pass@k
Two actions have scores and temperature .
-
Compute .
-
If one independent candidate succeeds with probability , find the chance of at least one success for .
-
Find the smallest giving probability at least .
-
For , , and , compute the finite-sample pass@ estimator.
-
Explain why parts 2 and 4 describe different statistical objects.
Show answer and solution
For five independent samples,
For at least 90%,
so .
The finite-sample estimate is
The first number uses a known population success probability for future independent draws. The second is computed from a finite set of ten candidates with two observed successes.
Pseudo-regret from actions
Implement:
import numpy as np
def pseudo_regret_curve(means: np.ndarray, actions: np.ndarray) -> np.ndarray:
"""Return cumulative pseudo-regret after each action."""
...Requirements:
meansis a one-dimensional vector of means;actionsis a one-dimensional integer vector;- an invalid action index raises
ValueError; - the result has floating type and the same length as
actions.
Check:
means = np.array([0.2, 0.5, 0.7])
actions = np.array([0, 2, 1])
# expected: [0.5, 0.5, 0.7]Open checks:
np.testing.assert_allclose(
pseudo_regret_curve([0.2, 0.5, 0.7], [0, 2, 1]),
[0.5, 0.5, 0.7],
)Show answer and solution
import numpy as np
def pseudo_regret_curve(means: np.ndarray, actions: np.ndarray) -> np.ndarray:
means = np.asarray(means, dtype=float)
actions = np.asarray(actions, dtype=int)
if means.ndim != 1 or means.size < 2:
raise ValueError("means must be one-dimensional")
if actions.ndim != 1:
raise ValueError("actions must be one-dimensional")
if np.any((actions < 0) | (actions >= means.size)):
raise ValueError("invalid action index")
return np.cumsum(means.max() - means[actions], dtype=float)Every increment is non-negative, so cumulative pseudo-regret cannot decrease.
ε-greedy Bernoulli bandit
Implement:
import numpy as np
def run_epsilon_greedy(
probabilities: np.ndarray,
horizon: int,
epsilon: float,
seed: int,
) -> tuple[np.ndarray, np.ndarray]:
"""Return action and reward arrays."""
...Rules:
- reward is
rng.random() < probabilities[action]; - break greedy ties uniformly;
- update estimates as sample means;
- use
np.random.default_rng(seed); - decide whether to explore before sampling the reward.
Compare , , and on one instance over at least 100 independent runs. Report mean pseudo-regret, not a conclusion from one seed.
Open checks:
actions, rewards = run_epsilon_greedy([0.1, 0.4, 0.6], 40, 0.1, 17)
assert actions.shape == (40,)
assert rewards.shape == (40,)
assert np.all((rewards == 0.0) | (rewards == 1.0))Show answer and solution
import numpy as np
def run_epsilon_greedy(probabilities, horizon, epsilon, seed):
p = np.asarray(probabilities, dtype=float)
if p.ndim != 1 or p.size < 2 or np.any((p < 0) | (p > 1)):
raise ValueError("invalid probabilities")
if horizon <= 0 or not 0 <= epsilon <= 1:
raise ValueError("invalid horizon or epsilon")
rng = np.random.default_rng(seed)
q = np.zeros(p.size)
n = np.zeros(p.size, dtype=int)
actions = np.empty(horizon, dtype=int)
rewards = np.empty(horizon, dtype=float)
for t in range(horizon):
if rng.random() < epsilon:
action = int(rng.integers(p.size))
else:
best = np.flatnonzero(np.isclose(q, q.max()))
action = int(rng.choice(best))
reward = float(rng.random() < p[action])
q[action] += (reward - q[action]) / (n[action] + 1)
n[action] += 1
actions[t] = action
rewards[t] = reward
return actions, rewardsAverage over many independent seeds. One greedy run may succeed or fail by chance and does not estimate expected regret.
UCB1
Implement UCB1 for rewards in :
import numpy as np
def run_ucb1(
probabilities: np.ndarray,
horizon: int,
seed: int,
) -> tuple[np.ndarray, np.ndarray]:
...Requirements:
-
select actions
0, 1, ..., K-1on the first rounds; -
then use ;
-
update sample means incrementally;
-
return action and reward arrays.
Verify that every action is selected at least once and cumulative pseudo-regret never decreases.
Open checks:
actions, rewards = run_ucb1([0.1, 0.4, 0.6], 3, 5)
assert np.array_equal(actions, [0, 1, 2])
assert rewards.shape == (3,)Show answer and solution
import numpy as np
def run_ucb1(probabilities, horizon, seed):
p = np.asarray(probabilities, dtype=float)
if p.ndim != 1 or p.size < 2 or np.any((p < 0) | (p > 1)):
raise ValueError("invalid probabilities")
if horizon <= 0:
raise ValueError("horizon must be positive")
rng = np.random.default_rng(seed)
k = p.size
q = np.zeros(k)
n = np.zeros(k, dtype=int)
actions = np.empty(horizon, dtype=int)
rewards = np.empty(horizon, dtype=float)
for t in range(horizon):
if t < k:
action = t
else:
index = q + np.sqrt(2 * np.log(t + 1) / n)
best = np.flatnonzero(np.isclose(index, index.max()))
action = int(rng.choice(best))
reward = float(rng.random() < p[action])
q[action] += (reward - q[action]) / (n[action] + 1)
n[action] += 1
actions[t] = action
rewards[t] = reward
return actions, rewardsThe forced first pass prevents division by zero and ensures every action has at least one observation.
Thompson sampling
Implement Beta–Bernoulli Thompson sampling:
import numpy as np
def run_thompson(
probabilities: np.ndarray,
horizon: int,
seed: int,
prior_alpha: float = 1.0,
prior_beta: float = 1.0,
) -> tuple[np.ndarray, np.ndarray, np.ndarray, np.ndarray]:
"""Return actions, rewards, and final alpha/beta arrays."""
...Increment alpha[action] after success and beta[action] after failure. Compare with UCB1 on several instances, including one with close means. Distinguish your empirical observation from a general guarantee.
Open checks:
actions, rewards, alpha, beta = run_thompson(
[0.1, 0.4, 0.6], 10, 23
)
assert actions.shape == rewards.shape == (10,)
assert alpha.shape == beta.shape == (3,)
assert np.isclose((alpha + beta).sum(), 16.0)Show answer and solution
import numpy as np
def run_thompson(
probabilities,
horizon,
seed,
prior_alpha=1.0,
prior_beta=1.0,
):
p = np.asarray(probabilities, dtype=float)
if p.ndim != 1 or p.size < 2 or np.any((p < 0) | (p > 1)):
raise ValueError("invalid probabilities")
if horizon <= 0 or prior_alpha <= 0 or prior_beta <= 0:
raise ValueError("invalid parameters")
rng = np.random.default_rng(seed)
alpha = np.full(p.size, float(prior_alpha))
beta = np.full(p.size, float(prior_beta))
actions = np.empty(horizon, dtype=int)
rewards = np.empty(horizon, dtype=float)
for t in range(horizon):
samples = rng.beta(alpha, beta)
action = int(np.argmax(samples))
reward = float(rng.random() < p[action])
alpha[action] += reward
beta[action] += 1.0 - reward
actions[t] = action
rewards[t] = reward
return actions, rewards, alpha, betaA defensible report states the number of independent runs, horizon, reward instance, and summary statistic. It does not promote an instance-specific result into a universal ranking.
IPS, SNIPS, and doubly robust evaluation
Implement:
def ips_value(rewards, behavior_probs, target_probs) -> float:
...
def snips_value(rewards, behavior_probs, target_probs) -> float:
...
def doubly_robust_value(
rewards,
behavior_probs,
target_probs,
predictions_logged,
predictions_target,
) -> float:
...predictions_target[i] already contains . Raise ValueError for non-positive behavior probabilities. Add an experiment where one propensity decreases from to and show how its IPS weight changes.
Open checks:
actual = ips_value(
[1.0, 0.0, 1.0],
[0.5, 0.25, 0.5],
[1.0, 0.0, 0.5],
)
assert np.isclose(actual, 1.0)Show answer and solution
import numpy as np
def _arrays(rewards, behavior_probs, target_probs):
r = np.asarray(rewards, dtype=float)
p = np.asarray(behavior_probs, dtype=float)
q = np.asarray(target_probs, dtype=float)
if r.ndim != 1 or p.shape != r.shape or q.shape != r.shape or r.size == 0:
raise ValueError("equal non-empty vectors required")
if np.any(p <= 0) or np.any(p > 1) or np.any((q < 0) | (q > 1)):
raise ValueError("invalid probabilities")
return r, p, q
def ips_value(rewards, behavior_probs, target_probs):
r, p, q = _arrays(rewards, behavior_probs, target_probs)
return float(np.mean(q * r / p))
def snips_value(rewards, behavior_probs, target_probs):
r, p, q = _arrays(rewards, behavior_probs, target_probs)
w = q / p
if w.sum() <= 0:
raise ValueError("zero total target weight")
return float(np.dot(w, r) / w.sum())
def doubly_robust_value(
rewards,
behavior_probs,
target_probs,
predictions_logged,
predictions_target,
):
r, p, q = _arrays(rewards, behavior_probs, target_probs)
logged = np.asarray(predictions_logged, dtype=float)
target = np.asarray(predictions_target, dtype=float)
if logged.shape != r.shape or target.shape != r.shape:
raise ValueError("prediction shapes must match")
return float(np.mean(target + q / p * (r - logged)))With target probability 0.5, lowering behavior probability from 0.5 to 0.01 increases the weight from 1 to 50. One such row can dominate the estimate, which is the practical cost of weak overlap.
Entropy, Best-of-N, and pass@k
Implement:
import numpy as np
def stable_softmax(logits, temperature=1.0) -> np.ndarray:
...
def categorical_entropy(probabilities) -> float:
...
def best_of_n_success(p: float, n: int) -> float:
...
def pass_at_k(n: int, c: int, k: int) -> float:
...Requirements:
- softmax remains stable for large logits;
- entropy is in nats and uses ;
- Best-of-N uses stable
log1p/expm1for small and large ; - pass@ uses or an equivalent stable product;
- invalid raise
ValueError.
Verify that entropy rises for temperatures , , and with fixed logits, but do not infer anything about correctness or answer quality from that fact.
Open checks:
p = stable_softmax([1000.0, 1001.0])
assert np.isclose(p.sum(), 1.0)
assert np.all(p > 0.0)Show answer and solution
from math import comb
import numpy as np
def stable_softmax(logits, temperature=1.0):
z = np.asarray(logits, dtype=float)
if z.ndim != 1 or z.size == 0 or not np.all(np.isfinite(z)):
raise ValueError("invalid logits")
if temperature <= 0 or not np.isfinite(temperature):
raise ValueError("temperature must be positive")
scaled = z / temperature
weights = np.exp(scaled - scaled.max())
return weights / weights.sum()
def categorical_entropy(probabilities):
p = np.asarray(probabilities, dtype=float)
if p.ndim != 1 or np.any(p < 0) or not np.isclose(p.sum(), 1):
raise ValueError("invalid probability vector")
positive = p[p > 0]
return float(-np.dot(positive, np.log(positive)))
def best_of_n_success(p, n):
if not 0 <= p <= 1 or n < 1:
raise ValueError("invalid p or n")
if p == 1:
return 1.0
return float(-np.expm1(n * np.log1p(-p)))
def pass_at_k(n, c, k):
if n <= 0 or c < 0 or c > n or k <= 0 or k > n:
raise ValueError("invalid n, c, k")
if n - c < k:
return 1.0
return float(1 - comb(n - c, k) / comb(n, k))For fixed logits, pure-softmax entropy increases from temperature 0.5 to 1 to 2. That fact alone says nothing about factual correctness, calibration, or task reward.