On this page

Multiplicative Weights

Five forecasts, one ferry

A small ferry company has to decide every morning whether to cancel its afternoon crossing, and it has to decide before anyone knows what the sea will do. Five marine forecast services, A to E, each send a one-word call: rough or calm. In the evening the company sees what the sea did. Here are twelve days.

1 2 3 4 5 6 7 8 9 10 11 12 A B C D E sea – R – – – R R – – – – – – R R – – R – R – – – – – – – – – – – – R – – R R R – R – – – – R R – R – R R R R – – – R – R R – R R R – R R – R – – R
Twelve days (rows) of calls from the five services (R = rough, – = calm) and what the sea did. Pink: a wrong call. Over the twelve days A is wrong 8 times, B, D and E 7 times each, and C twice.

Looking back, C was the one to follow: wrong twice, while every other service was wrong at least seven times. But on day 1 nobody knew that, and next month a different service may be the good one, or none may be. The company wants a rule it can follow day by day that is wrong not many more times than the best service in hindsight, whatever the sea and the services do.

This is the experts problem. There are n experts and T rounds. In each round every expert predicts, the algorithm predicts, and then the outcome is revealed. Nothing is assumed about where the outcomes or the predictions come from: an adversary may choose them. As with the online problems of Online Algorithms, the algorithm commits before it sees the future and is judged against a benchmark that saw everything. Here the benchmark is the best single expert, with m mistakes, and the algorithm's extra cost over it is its regret. A stronger benchmark, a forecaster who is right every day, is out of reach: the adversary below makes any deterministic rule wrong every day.

from fractions import Fraction as F
import math

sea = [int(c) for c in "101001010111"]                 # 1 = rough, 0 = calm
calls = {k: [int(c) for c in s] for k, s in zip("ABCDE", [
    "010000100001", "101100101010", "001001011111", "000000111010", "000000001100"])}
T, n = len(sea), len(calls)
wrong = {k: [calls[k][t] != sea[t] for t in range(T)] for k in calls}
assert {k: sum(w) for k, w in wrong.items()} == {"A": 8, "B": 7, "C": 2, "D": 7, "E": 7}

Two obvious rules

Majority vote. Call whatever most services call. It ignores their records, and on these days three or four mediocre services often agree: the vote is wrong 8 times out of 12.

Follow the leader. Call whatever the service with the fewest mistakes so far calls. It uses the records, but it puts all its trust in one service, and a later section shows that an adversary can make any deterministic rule like it wrong every day, while the better of two services is wrong only half the time.

majority = [int(2 * sum(calls[k][t] for k in calls) > n) for t in range(T)]
assert sum(majority[t] != sea[t] for t in range(T)) == 8

One case has a clean answer. If some service is known to be perfect, vote only among the services that have made no mistake yet. Each time the vote is wrong, at least half of those services were wrong and are dropped, and the perfect one never is, so the vote is wrong at most log2n times. When nobody is perfect this drops everyone. The fix is to shrink a service that errs instead of dropping it.

Weighted majority

Give each service a weight, 1 at the start. Each morning, add up the weights of the services that say rough and of those that say calm, and call the heavier side (a tie goes to rough). Each evening, multiply the weight of every service that was wrong by β=1/2. After mi mistakes, service i weighs 2−mi.

1 2 3 4 5 6 7 8 9 10 11 12 A B C D E WM sea 1 1 1 1 1 – R 2⁻¹ 1 2⁻¹ 2⁻¹ 2⁻¹ – – 2⁻² 1 2⁻¹ 2⁻¹ 2⁻¹ R R 2⁻³ 1 2⁻¹ 2⁻² 2⁻² – – 2⁻³ 2⁻¹ 2⁻¹ 2⁻² 2⁻² – – 2⁻³ 2⁻¹ 2⁻¹ 2⁻² 2⁻² – R 2⁻⁴ 2⁻² 2⁻¹ 2⁻³ 2⁻³ – – 2⁻⁵ 2⁻³ 2⁻¹ 2⁻⁴ 2⁻³ R R 2⁻⁶ 2⁻⁴ 2⁻¹ 2⁻⁴ 2⁻⁴ R – 2⁻⁶ 2⁻⁵ 2⁻² 2⁻⁵ 2⁻⁵ R R 2⁻⁷ 2⁻⁶ 2⁻² 2⁻⁶ 2⁻⁵ R R 2⁻⁸ 2⁻⁶ 2⁻² 2⁻⁶ 2⁻⁶ R R
Weighted majority with β = 1/2 on the twelve days: each service's weight at the start of the day, the algorithm's call (WM) and the sea. Pink: a wrong call, after which that weight halves. WM is wrong on days 1, 6 and 9; from day 8 on, C outweighs the other four together.

On day 1 four services say calm, so the algorithm says calm and is wrong. A, C, D and E halve. On day 3, B (weight 1) and C (weight 1/2) say rough, against 5/4 for calm: the algorithm says rough and is right, while the plain majority is wrong. The weights of the frequent offenders fall quickly. From day 8 on, C alone outweighs the other four, so the algorithm follows C, including on day 9, when C is wrong. In all it is wrong 3 times: once more than C, and 5 times fewer than the majority.

def weights(t, beta=F(1, 2)):
    """Each service's weight at the start of day t (0-based): beta to the power of its mistakes."""
    return {k: beta ** sum(wrong[k][:t]) for k in calls}

def wm_call(t):
    w = weights(t)
    rough = sum(w[k] for k in calls if calls[k][t] == 1)
    calm = sum(w[k] for k in calls if calls[k][t] == 0)
    assert rough != calm                               # no ties on these days
    return int(rough >= calm)

wm_wrong = [t + 1 for t in range(T) if wm_call(t) != sea[t]]
assert wm_wrong == [1, 6, 9]
assert all(weights(t)["C"] > sum(weights(t).values()) - weights(t)["C"] for t in range(7, 12))
assert weights(6)["C"] < sum(weights(6).values()) - weights(6)["C"]      # not yet on day 7

Why it works: watch the total weight

Let W be the total weight of all services. It starts at n and never grows. The whole analysis follows it.

Invariant

Every mistake of the algorithm multiplies the total weight W by at most 3/4, and a service with mi mistakes so far weighs exactly 2−mi, so 2−mi≤W.

Proof. When the algorithm is wrong, the side it followed was wrong and carried at least half of W. That half is halved, so the new total is at most W/2+W/4=3W/4. After M mistakes, W≤n(3/4)M, and every single weight is at most W. ◻

Theorem (weighted majority, β=1/2)

For every sequence of predictions and outcomes, even one an adversary picks by watching the algorithm, and for every expert i, the number of mistakes M of weighted majority satisfies M≤mi+log2nlog2(4/3)≈2.41(mi+log2n).

Proof. From the invariant, 2−mi≤n(3/4)M. Take log2 of both sides and rearrange. ◻

On the ferry days, with m=2 and n=5, the bound allows 10 mistakes, and the algorithm made 3. The bound is a guarantee over every sequence, not a prediction for this one.

The same argument works for any penalty β=1−ε. A mistake now multiplies W by at most 1−ε/2, and a service with mi mistakes weighs (1−ε)mi, so

M≤miln11−ε+lnnln11−ε/2≤2(1+ε)mi+2lnnε

for 0<ε≤1/2, using ln11−ε≤ε+ε2 and ln11−ε/2≥ε/2. A small ε brings the factor close to 2, at the price of a larger additive term.

assert F(71, 256) == sum(weights(T).values())        # the total after day 12
assert F(1, 4) <= F(71, 256) <= 5 * F(3, 4) ** 3       # 2^(-m_C) <= W <= n (3/4)^M
assert math.floor((2 + math.log2(5)) / math.log2(4 / 3)) == 10
for k in range(1, 501):                                # the two logarithm facts on a grid
    e = k / 1000
    assert math.log(1 / (1 - e)) <= e + e * e and math.log(1 / (1 - e / 2)) >= e / 2

No deterministic rule beats a factor of 2

Could a cleverer deterministic rule get the factor below 2? No.

Claim. For every deterministic algorithm and every T, there is a sequence with two experts on which the algorithm is wrong all T times and the better expert at most ⌊T/2⌋ times.

Proof. One expert always says rough and the other always says calm. The algorithm is deterministic, so its call on each day is a function of what it has seen, and the adversary can compute it. Make the sea do the opposite. The algorithm is wrong every day. On each day exactly one of the two experts is right, so their mistakes add up to T, and the better one has at most ⌊T/2⌋. ◻

1 2 3 4 5 6 7 8 rough calm WM sea R – R – R – – R R – R – R – – R R – R – R – – R R – R – R – – R
The adversary against weighted majority, with one service that always says rough and one that always says calm: the sea does the opposite of WM's call. WM is wrong all 8 days (pink); the two services are wrong 4 days each.

So a deterministic algorithm can be forced to M≥2m, and weighted majority's factor 2(1+ε) is as good as a deterministic rule gets, up to the ε. Follow the leader is deterministic, so the same adversary beats it.

def adversary_days(days):
    """The adversary against weighted majority (beta = 1/2) with two fixed experts."""
    m_rough = m_calm = mistakes = 0
    for _ in range(days):
        call = int(F(1, 2) ** m_rough >= F(1, 2) ** m_calm)    # weighted vote, tie -> rough
        mistakes += 1                                          # the sea does the opposite
        m_rough, m_calm = m_rough + (call == 1), m_calm + (call == 0)
    return mistakes, min(m_rough, m_calm)

assert adversary_days(8) == (8, 4) and adversary_days(21) == (21, 10)

Randomize: follow an expert chosen by weight

The adversary had to know the call before the sea turned. So don't let it: keep the same weights, and each day follow one expert, chosen at random with probability proportional to its weight. Now the expected number of mistakes on a day is the fraction of the weight on wrong experts.

The same idea works for any costs, not just mistakes. Let expert i have a loss ℓit∈[0,1] in round t, and let Li be its total. The multiplicative weights algorithm (also called Hedge, or randomized weighted majority) keeps weights wi, starting at 1, plays the distribution pt=wt/Wt, and after the round updates

wi←wi(1−εℓit).

Theorem (multiplicative weights)

Let 0<ε≤1/2. For every sequence of loss vectors in [0,1]n, even one in which ℓt is chosen after seeing pt, and for every expert i, ∑t=1Tpt·ℓt≤(1+ε)Li+lnnε.

The left side is the algorithm's expected loss over its own coins, against an oblivious adversary: one that fixes the whole sequence in advance, knowing the algorithm but not its coins.

Proof. After round t the total weight is

Wt+1=∑iwit(1−εℓit)=Wt(1−εpt·ℓt),

and 1−x≤e−x, so after T rounds WT+1≤ne−εL, where L is the left side of the theorem. For a single expert, 1−εx≥(1−ε)x when 0≤x≤1 (the right side is convex in x and the two agree at 0 and 1), so wiT+1≥(1−ε)Li. A single weight is at most the total, so (1−ε)Li≤ne−εL. Take logarithms:

εL≤lnn+Liln11−ε≤lnn+(ε+ε2)Li.

Divide by ε. ◻

With ε=1/2 and losses of 0 or 1, the weights are exactly weighted majority's. On the ferry days the randomized rule expects 4.80 mistakes, within its bound 3+2ln5≈6.22. That is more than the deterministic vote's 3 on this particular sequence. The theorems compare worst cases: the deterministic rule's factor can be forced up to 2, while the randomized rule's is at most 1+ε, plus the additive term.

def expected_mistakes(eps):
    total = F(0)
    for t in range(T):
        w = weights(t, 1 - eps)
        total += sum(w[k] for k in calls if wrong[k][t]) / sum(w.values())
    return total

assert round(float(expected_mistakes(F(1, 2))), 2) == 4.80
assert float(expected_mistakes(F(1, 2))) <= 1.5 * 2 + math.log(5) / 0.5

Regret. Every Li≤T, so the theorem gives L−Li≤εT+(lnn)/ε. The right side is smallest at ε=lnn/T, which is at most 1/2 once T≥4lnn, and then

L−miniLi≤2Tlnn.

The regret per round, 2lnn/T, goes to zero: the algorithm does, on average, as well as the best expert. This choice of ε needs T in advance. Without it, one can restart with doubled guesses of T at a constant-factor cost (the standard doubling trick, not proved here).

If the decision itself can be divided (a portfolio split across funds, say), putting the fraction pit on expert i costs exactly pt·ℓt. Then no coins are needed at all, and the theorem holds against an adversary that sees the split before it picks the losses.

No algorithm avoids √T regret

Take two experts, one always saying rough and one always calm, and let the sea be a fair coin each day, independent of everything before. Whatever an algorithm does, deterministic or not, it is right on each day with probability 1/2, so it expects T/2 mistakes. If X is the number of rough days, the better expert makes min(X,T−X) mistakes, which is T/2−|X−T/2|. So the regret averaged over all sequences is 𝔼|X−T/2|, and some fixed sequence has at least that much expected regret (the averaging step of Yao's principle, from Zero-Sum Games). For even T,

𝔼|X−T/2|=T2(TT/2)2−T≈T/(2π),

which is 3.98 for T=100 and 12.61 for T=1000. So regret Ω(T) is unavoidable for every algorithm. With n experts the known lower bound, as T and n grow, is Ω(Tlnn) (cited, not proved here), so multiplicative weights is optimal up to the constant.

def spread(T):
    """E|X - T/2| for X ~ Binomial(T, 1/2), exactly, by summing over X."""
    return sum(F(math.comb(T, x), 2 ** T) * abs(F(x) - F(T, 2)) for x in range(T + 1))

for days in (100, 1000):
    assert spread(days) == F(days // 2 * math.comb(days, days // 2), 2 ** days)
assert round(float(spread(100)), 2) == 3.98 and round(float(spread(1000)), 2) == 12.61

Measure the claim

Here is multiplicative weights with ε=lnn/T on n=4 experts whose losses are independent fair coins (0 or 1), ten sequences per length (seeds 451 to 460). The ratio divides the regret by Tlnn, so the theorem says it never exceeds 2.

T mean regret mean ratio largest ratio of the ten
100 5.5 0.46 0.81
1,000 13.6 0.37 0.70
10,000 56.0 0.48 0.62

The ratio stays flat, well below 2. On these sequences every algorithm expects to lose T/2, as in the previous section, so the regret measures how lucky the best of four coins was: this is the lower-bound side, and it grows like T for everyone. The constant 2 is the price of the worst sequence. These are sample means of ten runs on these instances, not expectations; the course's verification script recomputes them.

Solving a zero-sum game with multiplicative weights

The game H from Zero-Sum Games has rows (3,−1,1) and (−2,4,0): Rowan picks a row, Cole a column, and Cole pays Rowan the entry. Its value is 2/3. Solving it there took a lower envelope; a general m×n game took a linear program. Multiplicative weights gives an approximate answer with nothing but best responses.

Rowan runs multiplicative weights over her rows. In round t she plays the mix pt, and Cole answers with a best response jt, a pure column minimizing (ptTM)j (Zero-Sum Games: against a known mix, a pure reply is enough). Rowan's loss on row i is (b−Mijt)/R, where the entries lie in [a,b] and R=b−a, so that losses lie in [0,1]. After T rounds, let p¯ be the average of Rowan's mixes and q¯ the frequencies of Cole's answers.

Theorem (games by multiplicative weights)

With ε=lnm/T and T≥4lnm, for every m×n game with entries in an interval of length R, maxi(Mq¯)i−minj(p¯TM)j≤2Rlnm/T.

Proof. Let gt=minj(ptTM)j, what Cole's answer leaves Rowan in round t. Rewrite the regret bound L−Li≤2Tlnm in payoffs: Rowan's losses are (b−gt)/R and row i's are (b−Mijt)/R, so for every row i

1T∑tgt≥(Mq¯)i−2Rlnm/T.

And minj(p¯TM)j≥1T∑tgt, because the minimum of an average is at least the average of the minima. Combine the two. ◻

Weak duality (Zero-Sum Games) says every guarantee is at most every hold-down, minj(p¯TM)j≤v≤maxi(Mq¯)i. So the two numbers bracket the value, both strategies are within the gap of optimal, and anyone can check the bracket without trusting the run. Here is H (m=2, R=6, Cole taking the lowest column on ties), with lower =minj(p¯TH)j and upper =maxi(Hq¯)i:

rounds T lower upper the theorem's gap
10 0.603 1.200 3.16
100 0.630 0.820 1.00
1,000 0.653 0.718 0.32
10,000 0.662 0.683 0.10

After 10,000 rounds, p¯≈(0.662,0.338) and q¯≈(0,0.159,0.841), close to the exact (2/3,1/3) and (0,1/6,5/6). As T grows the gap goes to zero, which proves the hard direction of the minimax theorem, maxpminq=minqmaxp (sketch: a compactness step turns "within every δ" into exact optimal strategies).

H = [[3, -1, 1], [-2, 4, 0]]

def bracket(M, p, q):
    lower = min(sum(p[i] * M[i][j] for i in range(len(M))) for j in range(len(M[0])))
    upper = max(sum(M[i][j] * q[j] for j in range(len(M[0]))) for i in range(len(M)))
    return lower, upper

lower, upper = bracket(H, [F(2, 3), F(1, 3)], [0, F(1, 6), F(5, 6)])
assert lower == upper == F(2, 3)                        # exact strategies (Zero-Sum Games): no gap
lower, upper = bracket(H, [F(662, 1000), F(338, 1000)], [0, F(159, 1000), F(841, 1000)])
assert lower <= F(2, 3) <= upper and upper - lower < F(3, 100)
assert [round(2 * 6 * math.sqrt(math.log(2) / t), 2) for t in (10, 100, 1000, 10000)] == [3.16, 1.0, 0.32, 0.1]

Cost

Experts. One round costs O(n) arithmetic operations (the sum W, the update, and a random choice or a split), and the algorithm stores n weights. It never looks at old rounds again.

Games. One round computes pTM for Cole's best response, mn multiplications and additions, plus O(m) for the update. To reach a gap of δ the theorem needs T=⌈4R2lnm/δ2⌉ rounds, so the total is

O(mnR2logmδ2)

arithmetic operations on real numbers. Linear programming (Linear Programming and Polytopes and Integrality) finds the exact value in time polynomial in the bits of M. Multiplicative weights is only approximate and pays 1/δ2, but its count does not depend on the bits, and Cole never has to list his columns: it is enough that someone can compute a best response.

Floating point. The weights shrink geometrically. In IEEE double precision the smallest positive number is 2−1074, so a weight halved 1,075 times becomes exactly 0 and can never recover, and if every weight reaches 0 the total W is 0. Dividing every weight by the largest after each round leaves pt unchanged and keeps the numbers near 1; storing lnwi instead works too.

assert 0.5 ** 1074 > 0 and 0.5 ** 1075 == 0.0

A problem that looks different

A school's timetabling office has 300 soft rules: no teacher teaches three periods in a row, the chemistry lab is not double-booked, and so on. It owns a solver that, given any weighting of the rules, returns a timetable that satisfies their weighted average. No single call satisfies every rule. Can a sequence of calls, with the weights changing between them, produce a mix of timetables that nearly satisfies all 300, and who plays the part of the experts? The lab's last problem is a different one.

Practise

In the lab you draw weighted majority day by day and watch the total weight obey its bound; predict and then trace the multiplicative weights distribution on a new instance; solve zero-sum games approximately, with a bracket anyone can check; measure regret against an adversary and on random losses at three lengths; and solve a problem that doesn't say what it is.

Recap

You can now: state the experts problem and its benchmark; run weighted majority and prove its mistake bound from the total weight; prove that no deterministic rule beats a factor of 2; prove the (1+ε)Li+(lnn)/ε bound for multiplicative weights and tune ε for regret 2Tlnn; and solve a zero-sum game to within a gap δ with a checkable bracket.

Invariant: the total weight W falls by a factor tied to the algorithm's own loss, and never falls below the best expert's weight, which depends only on that expert's loss.

Complexity achieved: weighted majority, M≤2.41(m+log2n) mistakes on every sequence; multiplicative weights, expected regret at most 2Tlnn against an oblivious adversary (or exactly, for a split decision against an adaptive one), which is optimal up to the constant; O(n) time per round. A zero-sum game to gap δ in O(mnR2logm/δ2) arithmetic operations.

Failure mode: trusting one expert completely, as follow the leader does: any deterministic rule can be forced to twice the best expert's mistakes. A fixed ε that ignores T fails more quietly: its bound, εT+(lnn)/ε, grows in proportion to T.

In real software: scikit-learn's AdaBoostClassifier (version 1.8) reweights its training examples multiplicatively: after each round it multiplies the weight of every misclassified example by eα, computing it as the exponential of the log-weight plus α, rescales the weights to sum to 1, and before each round raises any weight that has shrunk below machine epsilon back to that value, so that no weight vanishes.

Retrieval (Zero-Sum Games): why is it enough for Cole to answer a known mix p with a single column, and never with a mix of his own?

Check yourself

  1. Why does each mistake of weighted majority multiply the total weight by at most 3/4, and how does that become M≤2.41(m+log2n)?
  2. Run weighted majority on the ferry days with β=0, so that a service is dropped at its first mistake. What happens, and what does the bound say?
  3. The randomized rule expects 4.80 mistakes on the ferry days, the deterministic vote makes 3. Which is better, and why does this not contradict the theorems?

Topic list modeled on the public schedule of CMU 15-451/651 Algorithm Design and Analysis (Fall 2025). All lecture text, proofs, examples, code, and exercises are original to SciMigo; no CMU course materials are reproduced. This course is independent and not affiliated with or endorsed by Carnegie Mellon University.

Your turn

Now try it yourself

Write the code, press Run, and scrub through the frames your program draws. The checks tell you when it works.

Loading the lab…

In this lab

  1. Weighted majority, one night at a time

    An orchard in a frost valley gets a call from six weather stations every evening: frost or no frost tonight. If frost comes and the heaters are off, the blossom is lost; if the heaters run for nothing, a night's fuel is wasted. The orchard decides by weighted majority, as in the lesson, but with its own stations and its own fourteen nights.

    Write weighted_majority(calls, outcomes, beta). calls[i][t] is station i's call on night t (1 = frost, 0 = no frost) and outcomes[t] is what happened. Every station starts with weight 1. Each night, add up the weights of the stations calling frost and of those calling no frost, and call the heavier side, frost on a tie. Then multiply the weight of every station that was wrong by beta. Return the list of your calls, one per night.

    Draw as you go: call show(...) once each night, before you update the weights, and once more after the last night. show draws one bar per station (its height is the weight), the total weight W and the bound n · ((1 + beta)/2)^M from the lesson's invariant, where M is the number of mistakes so far. The checks read the bars in every frame and test that invariant.

    Before you press Run: the stations' records are in CALLS. Which station ends with the largest weight, and on which nights is the orchard wrong? Count on paper for the first four nights.

  2. Predict the shares, then trace multiplicative weights

    A courier firm has three routes out of its depot: North, Coast and Tunnel. Each day it splits its vans between them, and in the evening it learns each route's delay on a scale from 0 to 1 (1 = the worst day that route can have). Over eight days the delays were the table DELAYS.

    Write hedge(losses, eps), the lesson's multiplicative weights algorithm. losses[t][i] is route i's loss on day t. Every route starts with weight 1. On day t the distribution is each weight divided by the total; after the day, multiply each weight by 1 − eps · loss. Return the list of the distributions p used on days 1 to T, one list of shares per day. The day's own losses must not change that day's distribution: they are not known in the morning.

    Call show(day, p, losses[t], paid) once per day, where paid is the expected loss so far including that day, the sum of p · loss over the days. It draws the shares as bars. With eps = Fraction(1, 2) and fractions as losses, every share is an exact fraction.

    Answer the three questions before you press Run. Work the first one out on paper from the first two rows of DELAYS.

  3. Solve a zero-sum game with multiplicative weights and best responses

    Now the lesson's application, on a new game. A harbour authority has three patrol plans (rows) and a smuggler has four coves to land in (columns). HARBOUR[i][j] is the expected number of seizures when the patrol uses plan i and the smuggler lands in cove j: the patrol wants it large, the smuggler small. It is a zero-sum game, as in Zero-Sum Games, and it has no saddle point.

    Write solve_game(M, T), returning (p, q): a mix p over the rows and a mix q over the columns, each a list of probabilities. Run T rounds. In each round the row player plays the multiplicative weights distribution over the rows, and the column player answers with a best response, a column that makes the row player's expected payoff smallest. Turn payoffs into losses in [0, 1] as the lesson does: with entries between a and b, row i's loss is (b − M[i][j]) / (b − a). Use eps = sqrt(ln m / T). Return the average of the row player's distributions and the frequencies of the column player's answers.

    The given bracket(M, p, q) returns (lower, upper): what p guarantees against every column, and what q concedes against every row. The value of the game lies between them, and the checks require upper − lower ≤ 2R·sqrt(ln m / T), the lesson's bound, where R = b − a. Watch for a game whose entries are all equal (then R = 0) and for a game with one row.

    The starter returns the naive answer from Zero-Sum Games: the row with the best worst case and the column with the best worst case, played every time. The demo draws your mixes and the bracket after 10, 50, 250, 1,000 and 4,000 rounds.

  4. Measure regret: the adversary, the wrong eps, and the coins

    The lesson proved that multiplicative weights with eps = sqrt(ln n / T) has regret at most 2·sqrt(T ln n) on every sequence, and that no algorithm avoids regret of order sqrt(T). Measure both sides, and what goes wrong with an eps that ignores T.

    Hedge(n, eps) is the lesson's algorithm, given: dist() returns today's shares and update(losses) applies a day's losses. Write three functions. coins(n, T, seed) returns T rows of n losses: with rng = random.Random(seed), each row is [rng.randrange(2) for _ in range(n)]. favourite_regret(n, T, eps) runs one Hedge(n, eps) for T rounds against an adversary that watches it: each round the expert with the largest share (the lowest index on a tie) gets loss 1 and every other expert 0. It returns the regret: the sum over rounds of p · loss, minus the smallest expert total. coin_regret(n, T, eps, seeds) returns the mean regret of a fresh Hedge(n, eps) on coins(n, T, seed) over the seeds.

    The program measures three things at T = 250, 1,000 and 4,000, each divided by sqrt(T ln n) and drawn as bars: the watching adversary against the tuned eps (n = 2), the same adversary against eps = 1/2, and fair coins with the tuned eps (n = 8, six seeds). The dashed line is the proven 2. The checks then run your functions on sizes and seeds of their own and recount. Answer the three questions first.

  5. Mastery challenge: splitting traffic across eight carriers

    Riverline streams video to a city through eight transit links run by different carriers. At the start of every hour its router must decide what fraction of the hour's traffic goes over each link. At the end of the hour each carrier bills a congestion charge per unit of traffic, anywhere from 0 to 250, and the bill is the traffic on each link times its charge. The charges depend on everybody else's traffic, on outages, and on the carriers' pricing games, and Riverline has no model of any of it. Worse, the carriers see how Riverline split the hour before they set the charges.

    The contract with Riverline's customers says: over a season of 2,000 hours, the total bill will be at most 32,300 more than the bill of the single link that, looking back, would have been cheapest for the whole season. The allowance comes from a general rule, 2·C·sqrt(T·ln n) for n links, T hours and charges up to C, rounded up; the checks run other seasons with the allowance that rule gives.

    Write the class Router(links, hours, top). split() returns a list of links fractions, none negative, adding up to 1. report(charges) is then called with that hour's charges (numbers from 0 to top). Nothing else is known in advance, and the checks include carriers that read your split before they set the charges.

    The starter sends everything over the link that has been cheapest so far. Answer the four questions, then write it. The demo draws a season against carriers that always overcharge whichever link carries the most traffic: your split in the last hour, and your extra bill against the allowance.