Skip to content

Commit 75be91b

Browse files
committed
updates
1 parent 1f4dc0c commit 75be91b

1 file changed

Lines changed: 63 additions & 34 deletions

File tree

lectures/wald_friedman.md

Lines changed: 63 additions & 34 deletions
Original file line numberDiff line numberDiff line change
@@ -73,6 +73,7 @@ from scipy.integrate import quad
7373
from scipy.stats import beta
7474
from collections import namedtuple
7575
import pandas as pd
76+
import scipy as sp
7677
```
7778

7879
This lecture uses ideas studied in {doc}`the lecture on likelihood ratio processes<likelihood_ratio_process>` and {doc}`the lecture on Bayesian learning<likelihood_bayes>`.
@@ -327,8 +328,7 @@ f0 = create_beta_density(1, 1)
327328
f1 = create_beta_density(9, 9)
328329
grid = np.linspace(0, 1, 50)
329330
330-
fig, ax = plt.subplots(figsize=(10, 8))
331-
ax.set_title("Original Distributions")
331+
fig, ax = plt.subplots()
332332
ax.plot(grid, f0(grid), lw=2, label="$f_0$")
333333
ax.plot(grid, f1(grid), lw=2, label="$f_1$")
334334
ax.legend()
@@ -538,7 +538,10 @@ def run_sprt_simulation(a0, b0, a1, b1, α, β, N, seed):
538538
true_f0 = (i % 2 == 0)
539539
truth_h0[i] = true_f0
540540
541-
n, accept_f0 = sprt_single_run(a0, b0, a1, b1, logA, logB, true_f0, seed + i)
541+
n, accept_f0 = sprt_single_run(
542+
a0, b0, a1, b1,
543+
logA, logB,
544+
true_f0, seed + i)
542545
stopping_times[i] = n
543546
decisions_h0[i] = accept_f0
544547
@@ -555,8 +558,10 @@ def run_sprt(params):
555558
truth_h0_bool = truth_h0.astype(bool)
556559
decisions_h0_bool = decisions_h0.astype(bool)
557560
558-
type_I = np.sum(truth_h0_bool & ~decisions_h0_bool) / np.sum(truth_h0_bool)
559-
type_II = np.sum(~truth_h0_bool & decisions_h0_bool) / np.sum(~truth_h0_bool)
561+
type_I = np.sum(truth_h0_bool & ~decisions_h0_bool) \
562+
/ np.sum(truth_h0_bool)
563+
type_II = np.sum(~truth_h0_bool & decisions_h0_bool) \
564+
/ np.sum(~truth_h0_bool)
560565
561566
return {
562567
'stopping_times': stopping_times,
@@ -571,8 +576,8 @@ params = SPRTParams(α=0.05, β=0.10, a0=2, b0=5, a1=5, b1=2, N=20000, seed=1)
571576
results = run_sprt(params)
572577
573578
print(f"Average stopping time: {results['stopping_times'].mean():.2f}")
574-
print(f"Empirical type I error: {results['type_I']:.3f} (target = {params.α})")
575-
print(f"Empirical type II error: {results['type_II']:.3f} (target = {params.β})")
579+
print(f"Empirical type I error: {results['type_I']:.3f} (target = {params.α})")
580+
print(f"Empirical type II error: {results['type_II']:.3f} (target = {params.β})")
576581
```
577582
578583
As anticipated in the passage above in which Wald discussed the quality of
@@ -597,7 +602,7 @@ def compute_wald_thresholds(α, β):
597602
return A, B, np.log(A), np.log(B)
598603
599604
def plot_sprt_results(results, params, title=""):
600-
"""Reusable function to plot SPRT results."""
605+
"""Plot SPRT results."""
601606
fig, axes = plt.subplots(1, 3, figsize=(20, 6))
602607
603608
# Distribution plots
@@ -656,8 +661,10 @@ def plot_confusion_matrix(results, ax):
656661
657662
for i in range(2):
658663
for j in range(2):
659-
percent = confusion_data[i, j] / row_totals[i, 0] if row_totals[i, 0] > 0 else 0
660-
color = 'white' if confusion_data[i, j] > confusion_data.max() * 0.5 else 'black'
664+
percent = confusion_data[i, j] / row_totals[i, 0] \
665+
if row_totals[i, 0] > 0 else 0
666+
color = 'white' if confusion_data[i, j] > confusion_data.max() * 0.5 \
667+
else 'black'
661668
ax.text(j, i, f'{confusion_data[i, j]}\n({percent:.1%})',
662669
ha="center", va="center", color=color, fontweight='bold',
663670
fontsize=14)
@@ -765,7 +772,7 @@ for a0, b0, a1, b1 in param_comb:
765772
param_list.append((a0, b0, a1, b1))
766773
767774
# Create the plot
768-
fig, ax = plt.subplots(figsize=(6, 6))
775+
fig, ax = plt.subplots()
769776
770777
scatter = ax.scatter(js_dists, mean_stopping_times,
771778
s=80, alpha=0.7, linewidth=0.5)
@@ -1009,6 +1016,8 @@ In the two exercises below, please try to rewrite the entire SPRT suite in this
10091016
10101017
In the first exercise, we apply the sequential probability ratio test to distinguish two models generated by 3-state Markov chains
10111018
1019+
(For a review on likelihood ratio processes for Markov chains, see [this section](lrp_markov).)
1020+
10121021
Consider distinguishing between two 3-state Markov chain models using Wald's sequential probability ratio test.
10131022
10141023
You have competing hypotheses about the transition probabilities:
@@ -1049,6 +1058,12 @@ The test stops when:
10491058
:class: dropdown
10501059
```
10511060

1061+
Below is one solution to the exercise.
1062+
1063+
In the lecture, we write the code more verbosely to illustrate the concepts clearly.
1064+
1065+
In the code below, we simplified some of the code structure for a shorter presentation.
1066+
10521067
```{code-cell} ipython3
10531068
MarkovSPRTParams = namedtuple('MarkovSPRTParams',
10541069
['α', 'β', 'P_0', 'P_1', 'N', 'seed'])
@@ -1076,7 +1091,8 @@ def simulate_markov_chain(P, pi_0, T, seed):
10761091
return path
10771092
10781093
@njit
1079-
def markov_sprt_single_run(P_0, P_1, pi_0, pi_1, logA, logB, true_P, true_pi, seed):
1094+
def markov_sprt_single_run(P_0, P_1, pi_0, pi_1,
1095+
logA, logB, true_P, true_pi, seed):
10801096
"""Run single SPRT for Markov chains."""
10811097
max_n = 10000
10821098
path = simulate_markov_chain(true_P, true_pi, max_n, seed)
@@ -1137,14 +1153,18 @@ P_1 = np.array([[0.5, 0.3, 0.2],
11371153
[0.2, 0.6, 0.2],
11381154
[0.2, 0.2, 0.6]])
11391155
1140-
params_markov = MarkovSPRTParams(α=0.05, β=0.10, P_0=P_0, P_1=P_1, N=1000, seed=42)
1156+
params_markov = MarkovSPRTParams(α=0.05, β=0.10,
1157+
P_0=P_0, P_1=P_1, N=1000, seed=42)
11411158
results_markov = run_markov_sprt(params_markov)
11421159
11431160
plot_confusion_matrix = lambda results, ax: None
11441161
fig, (ax1, ax2) = plt.subplots(1, 2, figsize=(10, 5))
11451162
1146-
ax1.hist(results_markov['stopping_times'], bins=50, color="steelblue", alpha=0.8)
1147-
ax1.set_title("Stopping Times"), ax1.set_xlabel("n"), ax1.set_ylabel("Frequency")
1163+
ax1.hist(results_markov['stopping_times'],
1164+
bins=50, color="steelblue", alpha=0.8)
1165+
ax1.set_title("Stopping Times")
1166+
ax1.set_xlabel("n")
1167+
ax1.set_ylabel("Frequency")
11481168
11491169
# Confusion matrix (reusing pattern from lecture)
11501170
f0_c = np.sum(results_markov['truth_h0'] & results_markov['decisions_h0'])
@@ -1154,20 +1174,23 @@ f1_i = np.sum(~results_markov['truth_h0'] & results_markov['decisions_h0'])
11541174
11551175
confusion_data = np.array([[f0_c, f0_i], [f1_i, f1_c]])
11561176
ax2.imshow(confusion_data, cmap='Blues')
1157-
ax.set_title('Confusion Matrix')
1158-
ax.set_xticks([0, 1])
1159-
ax.set_xticklabels(['Accept $H_0$', 'Reject $H_0$'])
1160-
ax.set_yticks([0, 1])
1161-
ax.set_yticklabels(['True $P^{(0)}$', 'True $P^{(1)}$'])
1177+
ax2.set_title('Confusion Matrix')
1178+
ax2.set_xticks([0, 1])
1179+
ax2.set_xticklabels(['Accept $H_0$', 'Reject $H_0$'])
1180+
ax2.set_yticks([0, 1])
1181+
ax2.set_yticklabels(['True $P^{(0)}$', 'True $P^{(1)}$'])
11621182
11631183
row_totals = confusion_data.sum(axis=1, keepdims=True)
11641184
11651185
for i in range(2):
11661186
for j in range(2):
1167-
percent = confusion_data[i, j] / row_totals[i, 0] if row_totals[i, 0] > 0 else 0
1168-
color = 'white' if confusion_data[i, j] > confusion_data.max() * 0.5 else 'black'
1169-
ax.text(j, i, f'{confusion_data[i, j]}\n({percent:.1%})',
1170-
ha="center", va="center", color=color, fontweight='bold')
1187+
percent = confusion_data[i, j] / row_totals[i, 0] \
1188+
if row_totals[i, 0] > 0 else 0
1189+
color = 'white' if confusion_data[i, j] > confusion_data.max() * 0.5 \
1190+
else 'black'
1191+
ax2.text(j, i, f'{confusion_data[i, j]}\n({percent:.1%})',
1192+
ha="center", va="center", color=color, fontweight='bold',
1193+
fontsize=14)
11711194
11721195
plt.tight_layout()
11731196
plt.show()
@@ -1182,6 +1205,8 @@ plt.show()
11821205
11831206
In this exercise, apply Wald's sequential test to distinguish between two VAR(1) models with different dynamics and noise structures.
11841207
1208+
For a review of the likelihood ratio process with VAR models, see {doc}`likelihood_var`.
1209+
11851210
Given VAR models under each hypothesis:
11861211
- $H_0$: $x_{t+1} = A^{(0)} x_t + C^{(0)} w_{t+1}$
11871212
- $H_1$: $x_{t+1} = A^{(1)} x_t + C^{(1)} w_{t+1}$
@@ -1210,17 +1235,17 @@ Tasks:
12101235
:class: dropdown
12111236
```
12121237

1213-
```{code-cell} ipython3
1214-
import scipy as sc
1238+
Below is one solution to the exercise
12151239

1240+
```{code-cell} ipython3
12161241
VARSPRTParams = namedtuple('VARSPRTParams',
12171242
['α', 'β', 'A_0', 'C_0', 'A_1', 'C_1', 'N', 'seed'])
12181243
12191244
def create_var_model(A, C):
12201245
"""Create VAR model."""
12211246
μ_0 = np.zeros(A.shape[0])
12221247
CC = C @ C.T
1223-
Σ_0 = sc.linalg.solve_discrete_lyapunov(A, CC)
1248+
Σ_0 = sp.linalg.solve_discrete_lyapunov(A, CC)
12241249
12251250
CC_inv = np.linalg.inv(CC + 1e-10 * np.eye(CC.shape[0]))
12261251
Σ_0_inv = np.linalg.inv(Σ_0 + 1e-10 * np.eye(Σ_0.shape[0]))
@@ -1246,14 +1271,16 @@ def var_log_likelihood(x_curr, x_prev, model, initial=False):
12461271
return -0.5 * (n * np.log(2 * np.pi) + model['log_det_CC'] +
12471272
diff @ model['CC_inv'] @ diff)
12481273
1249-
def var_sprt_single_run(model_0, model_1, model_true, logA, logB, seed):
1250-
"""Single VAR SPRT run"""
1274+
def var_sprt_single_run(model_0, model_1, model_true,
1275+
logA, logB, seed):
1276+
"""Single VAR SPRT run."""
12511277
np.random.seed(seed)
12521278
max_T = 500
12531279
12541280
# Generate VAR path
12551281
Σ_chol = np.linalg.cholesky(model_true['Σ_0'])
1256-
x = model_true['μ_0'] + Σ_chol @ np.random.randn(len(model_true['μ_0']))
1282+
x = model_true['μ_0'] + Σ_chol @ np.random.randn(
1283+
len(model_true['μ_0']))
12571284
12581285
# Initial likelihood ratio
12591286
log_L = (var_log_likelihood(x, None, model_1, True) -
@@ -1299,8 +1326,10 @@ def run_var_sprt(params):
12991326
type_I = np.sum(truth_h0 & ~decisions_h0) / np.sum(truth_h0)
13001327
type_II = np.sum(~truth_h0 & decisions_h0) / np.sum(~truth_h0)
13011328
1302-
return {'stopping_times': stopping_times, 'decisions_h0': decisions_h0,
1303-
'truth_h0': truth_h0, 'type_I': type_I, 'type_II': type_II}
1329+
return {'stopping_times': stopping_times,
1330+
'decisions_h0': decisions_h0,
1331+
'truth_h0': truth_h0,
1332+
'type_I': type_I, 'type_II': type_II}
13041333
13051334
# Run VAR SPRT
13061335
A_0 = np.array([[0.8, 0.1],
@@ -1329,8 +1358,8 @@ ax2.bar(x - 0.2, [results_markov['type_I'], results_var['type_I']],
13291358
0.4, label='Type I', alpha=0.7)
13301359
ax2.bar(x + 0.2, [results_markov['type_II'], results_var['type_II']],
13311360
0.4, label='Type II', alpha=0.7)
1332-
ax2.axhline(y=0.05, linestyle='--', alpha=0.5)
1333-
ax2.axhline(y=0.10, linestyle='--', alpha=0.5)
1361+
ax2.axhline(y=0.05, linestyle='--', alpha=0.5, color='C0')
1362+
ax2.axhline(y=0.10, linestyle='--', alpha=0.5, color='C1')
13341363
ax2.set_xticks(x), ax2.set_xticklabels(['Markov', 'VAR'])
13351364
ax2.legend(), plt.tight_layout(), plt.show()
13361365
```

0 commit comments

Comments
 (0)