@@ -37,18 +37,23 @@ We approach the problem in two ways.
3737First, we solve it exactly using dynamic programming, assuming full knowledge of
3838the model — the demand distribution, cost parameters, and transition dynamics.
393940-Second, we show how a manager can learn the optimal policy from experience alone, using *[Q-learning](https://en.wikipedia.org/wiki/Q-learning)*.
40+Second, we show how a manager can learn the optimal policy from experience alone, using [Q-learning](https://en.wikipedia.org/wiki/Q-learning).
414142-The manager observes only the inventory level, the order placed, the resulting
43-profit, and the next inventory level — without knowing any of the underlying
44-parameters.
42+In this setting, we assume that the manager observes only
43+44+* the inventory level,
45+* the order placed,
46+* the resulting profit, and
47+* the next inventory level.
48+49+The manager knows the interest rate -- and hence the discount factor -- but not any of the other underlying parameters.
45504651A key idea is the *Q-factor* representation, which reformulates the Bellman
4752equation so that the optimal policy can be recovered without knowledge of the
48-transition function.
53+transition dynamics.
495450-We show that, given enough experience, the manager's learned policy converges to
51-the optimal one.
55+We show that, given enough experience, the
56+manager's learned policy converges to the optimal one.
52575358The lecture proceeds as follows:
5459@@ -67,16 +72,18 @@ import matplotlib.pyplot as plt
6772from typing import NamedTuple
6873```
697475+7076## The Model
717772-We study a firm where a manager tries to maximize shareholder value.
78+We study a firm where a manager tries to maximize shareholder value by
79+controlling inventories.
73807481To simplify the problem, we assume that the firm only sells one product.
75827683Letting $\pi_t$ be profits at time $t$ and $r > 0$ be the interest rate, the value of the firm is
77847885$$
79- V_0 = \sum_{t \geq 0} \beta^t \pi_t
86+ V_0 = \EE \sum_{t \geq 0} \beta^t \pi_t
8087 \qquad
8188 \text{ where }
8289 \quad \beta := \frac{1}{1+r}.
@@ -97,9 +104,9 @@ $$
97104$$
9810599106The term $A_t$ is units of stock ordered this period, which arrive at the start
100-of period $t+1$, after demand $D_{t+1}$ is realized and served.
107+of period $t+1$, after demand $D_{t+1}$ is realized and served:
101108102-**Timeline for period $t$:** observe $X_t$ → choose $A_t$ → demand $D_{t+1}$ arrives → profit realized → $X_{t+1}$ determined.
109+* observe $X_t$ → choose $A_t$ → demand $D_{t+1}$ arrives → profit realized → $X_{t+1}$ determined.
103110104111(We use a $t$ subscript in $A_t$ to indicate the information set: it is chosen
105112before $D_{t+1}$ is observed.)
@@ -115,7 +122,7 @@ $$
115122Here
116123117124* the sales price is set to unity (for convenience)
118-* revenue is the minimum of current stock and demand because orders in excess of inventory are lost rather than back-filled
125+* revenue is the minimum of current stock and demand because orders in excess of inventory are lost (not back-filled)
119126* $c$ is unit product cost and $\kappa$ is a fixed cost of ordering inventory
120127121128We can map our inventory problem into a dynamic program with state space $\mathsf X := \{0, \ldots, K\}$ and action space $\mathsf A := \mathsf X$.
@@ -463,9 +470,10 @@ The manager does not need to know the demand distribution $\phi$, the unit cost
463470All the manager needs to observe at each step is:
4644714654721. the current inventory level $x$,
466-2. the order quantity $a$ they chose,
467-3. the resulting profit $R_{t+1}$ (which appears on the books), and
468-4. the next inventory level $X_{t+1}$ (which they can read off the warehouse).
473+2. the order quantity $a$, which they choose,
474+3. the resulting profit $R_{t+1}$ (which appears on the books),
475+4. the discount factor $\beta$, which is determined by the interest rate, and
476+5. the next inventory level $X_{t+1}$ (which they can read off the warehouse).
469477470478These are all directly observable quantities — no model knowledge is required.
471479@@ -480,47 +488,29 @@ a)$ for every state-action pair $(x, a)$.
480488481489At each step, the manager is in some state $x$ and must choose a specific action
482490$a$ to take. Whichever $a$ is chosen, the manager observes profit $R_{t+1}$
483-and next state $X_{t+1}$, and updates **that one entry** $q_t(x, a)$ of the
491+and next state $X_{t+1}$, and updates *that one entry* $q_t(x, a)$ of the
484492table using the rule above.
485493486-**The max computes a value, not an action.**
487-488494It is tempting to read the $\max_{a'}$ in the update rule as prescribing the
489495manager's next action — that is, to interpret the update as saying "move to
490-state $X_{t+1}$ and take action $\argmax_{a'} q_t(X_{t+1}, a')$."
496+state $X_{t+1}$ and take an action in $\argmax_{a'} q_t(X_{t+1}, a')$."
491497492-But the $\max$ plays a different role. The quantity $\max_{a' \in
493-\Gamma(X_{t+1})} q_t(X_{t+1}, a')$ is a **scalar** — it estimates the value of
494-being in state $X_{t+1}$ under the best possible continuation. This scalar
495-enters the update as part of the target value for $q_t(x, a)$.
498+But the $\max$ plays a different role.
496499497-Which action the manager *actually takes* at state $X_{t+1}$ is a separate
498-decision entirely.
499-500-To see why this distinction matters, consider what happens if we modify the
501-update rule by replacing the $\max$ with evaluation under a fixed feasible
502-policy $\sigma$:
503-504-$$
505- q_{t+1}(x, a)
506- = (1 - \alpha_t) q_t(x, a) +
507- \alpha_t \left(R_{t+1} + \beta \, q_t(X_{t+1}, \sigma(X_{t+1}))\right).
508-$$
500+The quantity $\max_{a' \in \Gamma(X_{t+1})} q_t(X_{t+1}, a')$ is just an estimate of the value of
501+being in state $X_{t+1}$ under the best possible continuation.
509502510-This modified update is a stochastic sample of the Bellman *evaluation* operator
511-for $\sigma$. The Q-table then converges to $q^\sigma$ — the Q-function
512-associated with the lifetime value of $\sigma$, not the optimal one.
503+This scalar enters the update as part of the target value for $q_t(x, a)$.
513504514-By contrast, the original update with the $\max$ is a stochastic sample of the
515-Bellman *optimality* operator, whose fixed point is $q^*$. The $\max$ in the
516-update target is therefore what drives convergence to $q^*$.
505+Which action the manager *actually takes* at time $t+1$ is a separate decision.
517506518-In short, the $\max$ is doing the work of finding the optimum; without it, you only evaluate a fixed policy.
507+In short, the $\max$ is doing the work of finding the optimum; it does not dictate the action that the manager actually takes.
519508520509### The behavior policy
521510522-The rule governing how the manager chooses actions is called the **behavior
523-policy**. Because the $\max$ in the update target always points toward $q^*$
511+The rule governing how the manager chooses actions is called the **behavior policy**.
512+513+Because the $\max$ in the update target always points toward $q^*$
524514regardless of how the manager selects actions, the behavior policy affects only
525515which $(x, a)$ entries get visited — and hence updated — over time.
526516@@ -545,6 +535,7 @@ We use $\alpha_t = 1 / n_t(x, a)^{0.51}$, where $n_t(x, a)$ is the number of tim
545535546536This decays slowly enough to allow learning from later (better-informed) updates, while still satisfying the [Robbins–Monro conditions](https://en.wikipedia.org/wiki/Stochastic_approximation#Robbins%E2%80%93Monro_algorithm) for convergence.
547537538+548539### Exploration: epsilon-greedy
549540550541For our behavior policy, we use an $\varepsilon$-greedy strategy:
@@ -560,6 +551,16 @@ We decay $\varepsilon$ each step: $\varepsilon_{t+1} = \max(\varepsilon_{\min},\
560551561552The stochastic demand shocks naturally drive the manager across different inventory levels, providing exploration over the state space without any artificial resets.
562553554+### Optimistic initialization
555+556+A simple but powerful technique for accelerating learning is **optimistic initialization**: instead of starting the Q-table at zero, we initialize every entry to a value above the true optimum.
557+558+Because every untried action looks optimistically good, the agent is "disappointed" whenever it tries one — the update pulls that entry down toward reality. This drives the agent to try other actions (which still look optimistically high), producing broad exploration of the state-action space early in training.
559+560+This idea is sometimes called **optimism in the face of uncertainty** and is widely used in both bandit and reinforcement learning settings.
561+562+In our problem, the value function $v^*$ ranges from about 13 to 18. We initialize the Q-table at 20 — modestly above the true maximum — to ensure optimistic exploration without being so extreme as to distort learning.
563+563564### Implementation
564565565566We first define a helper to extract the greedy policy from a Q-table.
@@ -587,9 +588,9 @@ At specified step counts (given by `snapshot_steps`), we record the current gree
587588```{code-cell} ipython3
588589@numba.jit(nopython=True)
589590def q_learning_kernel(K, p, c, κ, β, n_steps, X_init,
590- ε_init, ε_min, ε_decay, snapshot_steps, seed):
591+ ε_init, ε_min, ε_decay, q_init, snapshot_steps, seed):
591592 np.random.seed(seed)
592- q = np.zeros((K + 1, K + 1))
593+ q = np.full((K + 1, K + 1), q_init)
593594 n = np.zeros((K + 1, K + 1)) # visit counts for learning rate
594595 ε = ε_init
595596@@ -642,22 +643,21 @@ The wrapper function unpacks the model and provides default hyperparameters.
642643```{code-cell} ipython3
643644def q_learning(model, n_steps=20_000_000, X_init=0,
644645 ε_init=1.0, ε_min=0.01, ε_decay=0.999999,
645- snapshot_steps=None, seed=1234):
646+ q_init=20.0, snapshot_steps=None, seed=1234):
646647 x_values, d_values, ϕ_values, p, c, κ, β = model
647648 K = len(x_values) - 1
648649 if snapshot_steps is None:
649650 snapshot_steps = np.array([], dtype=np.int64)
650651 return q_learning_kernel(K, p, c, κ, β, n_steps, X_init,
651- ε_init, ε_min, ε_decay, snapshot_steps, seed)
652+ ε_init, ε_min, ε_decay, q_init, snapshot_steps, seed)
652653```
653654654-### Running Q-learning
655-656-We run 20 million steps and take policy snapshots at steps 10,000, 1,000,000, and at the end.
655+Next we run $n$ = 5 million steps and take policy snapshots at steps 10,000, 1,000,000, and $n$.
657656658657```{code-cell} ipython3
659-snap_steps = np.array([10_000, 1_000_000, 19_999_999], dtype=np.int64)
660-q, snapshots = q_learning(model, snapshot_steps=snap_steps)
658+n = 5_000_000
659+snap_steps = np.array([10_000, 1_000_000, n], dtype=np.int64)
660+q, snapshots = q_learning(model, n_steps=n+1, snapshot_steps=snap_steps)
661661```
662662663663### Comparing with the exact solution
@@ -710,9 +710,11 @@ All panels use the **same demand sequence** (via a fixed random seed), so differ
710710711711The top panel shows the optimal policy from VFI for reference.
712712713-After only 10,000 steps the agent has barely explored and its policy is poor.
713+After 10,000 steps the agent has barely explored and its policy is poor.
714+715+By 1,000,000 steps the policy has improved but still differs noticeably from the optimum.
714716715-By step 20 million, the learned policy produces inventory dynamics that closely resemble the S-s pattern of the optimal solution.
717+By step 5 million, the learned policy produces inventory dynamics that closely resemble the S-s pattern of the optimal solution.
716718717719```{code-cell} ipython3
718720ts_length = 200