GitHub

@@ -63,6 +63,8 @@ from scipy.stats import beta as beta_dist

6363

import pandas as pd

6464

from IPython.display import display, Math

6565

import quantecon as qe

66+67+

rng = np.random.default_rng()

6668

```

67696870

## Likelihood Ratio Process

@@ -154,18 +156,18 @@ def likelihood_ratio(w, f_func, g_func):

154156

return f_func(w) / g_func(w)

155157156158

@jit

157-

def simulate_likelihood_ratios(a, b, f_func, g_func, T=50, N=500):

159+

def simulate_likelihood_ratios(a, b, f_func, g_func, rng, T=50, N=500):

158160

"""

159161

Generate N sets of T observations of the likelihood ratio.

160162

"""

161163

l_arr = np.empty((N, T))

162164

for i in range(N):

163165

for j in range(T):

164-

w = np.random.beta(a, b)

166+

w = rng.beta(a, b)

165167

l_arr[i, j] = f_func(w) / g_func(w)

166168

return l_arr

167169168-

def simulate_sequences(distribution, f_func, g_func,

170+

def simulate_sequences(distribution, f_func, g_func, rng,

169171

F_params=(1, 1), G_params=(3, 1.2), T=50, N=500):

170172

"""

171173

Generate N sequences of T observations from specified distribution.

@@ -177,7 +179,7 @@ def simulate_sequences(distribution, f_func, g_func,

177179

else:

178180

raise ValueError("distribution must be 'f' or 'g'")

179181180-

l_arr = simulate_likelihood_ratios(a, b, f_func, g_func, T, N)

182+

l_arr = simulate_likelihood_ratios(a, b, f_func, g_func, rng, T, N)

181183

l_seq = np.cumprod(l_arr, axis=1)

182184

return l_arr, l_seq

183185

@@ -207,7 +209,7 @@ draws from $g$.

207209208210

```{code-cell} ipython3

209211

# Simulate when nature draws from g

210-

l_arr_g, l_seq_g = simulate_sequences('g', f, g, (F_a, F_b), (G_a, G_b))

212+

l_arr_g, l_seq_g = simulate_sequences('g', f, g, rng, (F_a, F_b), (G_a, G_b))

211213

plot_likelihood_paths(l_seq_g,

212214

title="$L(w^{t})$ paths when nature draws from g",

213215

ylim=[0, 3])

@@ -288,7 +290,7 @@ averaging across these many paths at each $t$.

288290289291

```{code-cell} ipython3

290292

l_arr_g, l_seq_g = simulate_sequences('g',

291-

f, g, (F_a, F_b), (G_a, G_b), N=50000)

293+

f, g, rng, (F_a, F_b), (G_a, G_b), N=50000)

292294

```

293295294296

It would be useful to use simulations to verify that unconditional means

@@ -335,7 +337,7 @@ Please note the scale of the $y$ axis.

335337336338

```{code-cell} ipython3

337339

# Simulate when nature draws from f

338-

l_arr_f, l_seq_f = simulate_sequences('f', f, g,

340+

l_arr_f, l_seq_f = simulate_sequences('f', f, g, rng,

339341

(F_a, F_b), (G_a, G_b), N=50000)

340342

```

341343

@@ -787,7 +789,7 @@ for i, scenario in enumerate(scenarios):

787789

T = 150

788790789791

# Generate data from h

790-

h_data = np.random.beta(scenario["h_params"][0],

792+

h_data = rng.beta(scenario["h_params"][0],

791793

scenario["h_params"][1], (N_paths, T))

792794

l_ratios, l_cumulative = compute_likelihood_ratios(h_data, f, g)

793795

log_l_cumulative = np.log(l_cumulative)

@@ -876,45 +878,45 @@ of IID draws from $g$.

876878

Here is Python code that we'll use to implement timing protocol 1 and 2

877879878880

```{code-cell} ipython3

879-

def protocol_1(π_minus_1, T, N=1000, F_params=(1, 1), G_params=(3, 1.2)):

881+

def protocol_1(π_minus_1, T, rng, N=1000, F_params=(1, 1), G_params=(3, 1.2)):

880882

"""

881883

Simulate Protocol 1: Nature decides once at t=-1 which model to use.

882884

"""

883885

F_a, F_b = F_params

884886

G_a, G_b = G_params

885887886888

# Single coin flip for the true model

887-

true_models_F = np.random.rand(N) < π_minus_1

889+

true_models_F = rng.random(N) < π_minus_1

888890

sequences = np.empty((N, T))

889891890892

n_f = np.sum(true_models_F)

891893

n_g = N - n_f

892894893895

if n_f > 0:

894-

sequences[true_models_F, :] = np.random.beta(F_a, F_b, (n_f, T))

896+

sequences[true_models_F, :] = rng.beta(F_a, F_b, (n_f, T))

895897

if n_g > 0:

896-

sequences[~true_models_F, :] = np.random.beta(G_a, G_b, (n_g, T))

898+

sequences[~true_models_F, :] = rng.beta(G_a, G_b, (n_g, T))

897899898900

return sequences, true_models_F

899901900-

def protocol_2(π_minus_1, T, N=1000, F_params=(1, 1), G_params=(3, 1.2)):

902+

def protocol_2(π_minus_1, T, rng, N=1000, F_params=(1, 1), G_params=(3, 1.2)):

901903

"""

902904

Simulate Protocol 2: Nature decides at each time step which model to use.

903905

"""

904906

F_a, F_b = F_params

905907

G_a, G_b = G_params

906908907909

# Coin flips for each time step

908-

true_models_F = np.random.rand(N, T) < π_minus_1

910+

true_models_F = rng.random((N, T)) < π_minus_1

909911

sequences = np.empty((N, T))

910912911913

n_f = np.sum(true_models_F)

912914

n_g = N * T - n_f

913915914916

if n_f > 0:

915-

sequences[true_models_F] = np.random.beta(F_a, F_b, n_f)

917+

sequences[true_models_F] = rng.beta(F_a, F_b, n_f)

916918

if n_g > 0:

917-

sequences[~true_models_F] = np.random.beta(G_a, G_b, n_g)

919+

sequences[~true_models_F] = rng.beta(G_a, G_b, n_g)

918920919921

return sequences, true_models_F

920922

```

@@ -970,12 +972,12 @@ Now let's simulate timing protocol 1 and compute the error probabilities

970972

```{code-cell} ipython3

971973972974

def compute_protocol_1_errors(π_minus_1, T_max, N_simulations, f_func, g_func,

973-

F_params=(1, 1), G_params=(3, 1.2)):

975+

rng, F_params=(1, 1), G_params=(3, 1.2)):

974976

"""

975977

Compute error probabilities for Protocol 1.

976978

"""

977979

sequences, true_models = protocol_1(

978-

π_minus_1, T_max, N_simulations, F_params, G_params)

980+

π_minus_1, T_max, rng, N_simulations, F_params, G_params)

979981

l_ratios, L_cumulative = compute_likelihood_ratios(sequences,

980982

f_func, g_func)

981983

@@ -1007,10 +1009,10 @@ The following code visualizes the error probabilities for timing protocol 1

10071009

:tags: [hide-input]

1008101010091011

def analyze_protocol_1(π_minus_1, T_max, N_simulations, f_func, g_func,

1010-

F_params=(1, 1), G_params=(3, 1.2)):

1012+

rng, F_params=(1, 1), G_params=(3, 1.2)):

10111013

"""Analyze Protocol 1"""

10121014

result = compute_protocol_1_errors(π_minus_1, T_max, N_simulations,

1013-

f_func, g_func, F_params, G_params)

1015+

f_func, g_func, rng, F_params, G_params)

1014101610151017

# Plot results

10161018

fig, (ax1, ax2) = plt.subplots(1, 2, figsize=(12, 5))

@@ -1046,7 +1048,7 @@ T_max = 30

10461048

N_simulations = 10_000

1047104910481050

result_p1 = analyze_protocol_1(π_minus_1, T_max, N_simulations,

1049-

f, g, (F_a, F_b), (G_a, G_b))

1051+

f, g, rng, (F_a, F_b), (G_a, G_b))

10501052

```

1051105310521054

Notice how the model selection error probability approaches zero as $T$ grows.

@@ -1078,12 +1080,12 @@ Now let's write some code to simulate it

1078108010791081

```{code-cell} ipython3

10801082

def compute_protocol_2_errors(π_minus_1, T_max, N_simulations, f_func, g_func,

1081-

F_params=(1, 1), G_params=(3, 1.2)):

1083+

rng, F_params=(1, 1), G_params=(3, 1.2)):

10821084

"""

10831085

Compute error probabilities for Protocol 2.

10841086

"""

10851087

sequences, true_models = protocol_2(π_minus_1,

1086-

T_max, N_simulations, F_params, G_params)

1088+

T_max, rng, N_simulations, F_params, G_params)

10871089

l_ratios, _ = compute_likelihood_ratios(sequences, f_func, g_func)

1088109010891091

T_range = np.arange(1, T_max + 1)

@@ -1186,11 +1188,11 @@ Now we simulate timing protocol 2 and compute the classification error probabili

11861188

In the next cell, we also compare the theoretical classification accuracy to the empirical classification accuracy

1187118911881190

```{code-cell} ipython3

1189-

def analyze_protocol_2(π_minus_1, T_max, N_simulations, f_func, g_func,

1191+

def analyze_protocol_2(π_minus_1, T_max, N_simulations, f_func, g_func, rng,

11901192

theory_error=None, F_params=(1, 1), G_params=(3, 1.2)):

11911193

"""Analyze Protocol 2."""

11921194

result = compute_protocol_2_errors(π_minus_1, T_max, N_simulations,

1193-

f_func, g_func, F_params, G_params)

1195+

f_func, g_func, rng, F_params, G_params)

1194119611951197

# Plot results

11961198

plt.figure(figsize=(10, 6))

@@ -1210,7 +1212,7 @@ def analyze_protocol_2(π_minus_1, T_max, N_simulations, f_func, g_func,

12101212

return result

1211121312121214

# Analyze Protocol 2

1213-

result_p2 = analyze_protocol_2(π_minus_1, T_max, N_simulations, f, g,

1215+

result_p2 = analyze_protocol_2(π_minus_1, T_max, N_simulations, f, g, rng,

12141216

theory_error, (F_a, F_b), (G_a, G_b))

12151217

```

12161218

@@ -1403,8 +1405,8 @@ for i, ((f_a, f_b), (g_a, g_b)) in enumerate(distribution_pairs):

14031405

chernoff_vals[i], _ = compute_chernoff_entropy(f, g)

1404140614051407

# Generate samples

1406-

sequences_f = np.random.beta(f_a, f_b, (N_half, T_large))

1407-

sequences_g = np.random.beta(g_a, g_b, (N_half, T_large))

1408+

sequences_f = rng.beta(f_a, f_b, (N_half, T_large))

1409+

sequences_g = rng.beta(g_a, g_b, (N_half, T_large))

1408141014091411

# Compute likelihood ratios and cumulative products

14101412

_, L_cumulative_f = compute_likelihood_ratios(sequences_f, f, g)

@@ -1585,12 +1587,12 @@ def markov_kl_divergence(P_f, P_g, pi_f):

15851587

kl_rate = np.sum(pi_f[:, np.newaxis] * P_f * log_ratios)

15861588

return kl_rate

158715891588-

def simulate_markov_chain(P, pi_0, T, N_paths=1000):

1590+

def simulate_markov_chain(P, pi_0, T, rng, N_paths=1000):

15891591

"""

15901592

Simulate N_paths sample paths from a Markov chain

15911593

"""

15921594

mc = qe.MarkovChain(P, state_values=None)

1593-

initial_states = np.random.choice(len(P), size=N_paths, p=pi_0)

1595+

initial_states = rng.choice(len(P), size=N_paths, p=pi_0)

15941596

paths = np.zeros((N_paths, T+1), dtype=int)

1595159715961598

for i in range(N_paths):

@@ -1621,7 +1623,7 @@ def compute_likelihood_ratio_markov(paths, P_f, P_g, π_0_f, π_0_g):

1621162316221624

return L_ratios

162316251624-

def analyze_markov_chains(P_f, P_g,

1626+

def analyze_markov_chains(P_f, P_g, rng,

16251627

T=500, N_paths=1000, plot_paths=True, n_show=50):

16261628

"""

16271629

Complete analysis of two Markov chains

@@ -1642,7 +1644,7 @@ def analyze_markov_chains(P_f, P_g,

1642164416431645

if plot_paths:

16441646

# Simulate and plot paths

1645-

paths_from_f = simulate_markov_chain(P_f, π_f, T, N_paths)

1647+

paths_from_f = simulate_markov_chain(P_f, π_f, T, rng, N_paths)

16461648

L_ratios_f = compute_likelihood_ratio_markov(

16471649

paths_from_f, P_f, P_g, π_f, π_g)

16481650

@@ -1676,16 +1678,16 @@ def analyze_markov_chains(P_f, P_g,

16761678

'kl_rate_gf': kl_rate_gf

16771679

}

167816801679-

def compute_markov_selection_error(T_values, P_f, P_g, π_0_f, π_0_g, N_sim=1000):

1681+

def compute_markov_selection_error(T_values, P_f, P_g, π_0_f, π_0_g, rng, N_sim=1000):

16801682

"""

16811683

Compute model selection error probability for Markov chains

16821684

"""

16831685

errors = []

1684168616851687

for T in T_values:

16861688

# Simulate from both models

1687-

paths_f = simulate_markov_chain(P_f, π_0_f, T, N_sim//2)

1688-

paths_g = simulate_markov_chain(P_g, π_0_g, T, N_sim//2)

1689+

paths_f = simulate_markov_chain(P_f, π_0_f, T, rng, N_sim//2)

1690+

paths_g = simulate_markov_chain(P_g, π_0_g, T, rng, N_sim//2)

1689169116901692

# Compute likelihood ratios

16911693

L_f = compute_likelihood_ratio_markov(paths_f, P_f, P_g, π_0_f, π_0_g)

@@ -1717,7 +1719,7 @@ P_g = np.array([[0.5, 0.3, 0.2],

17171719

[0.2, 0.6, 0.2],

17181720

[0.2, 0.2, 0.6]])

171917211720-

markov_results = analyze_markov_chains(P_f, P_g)

1722+

markov_results = analyze_markov_chains(P_f, P_g, rng)

17211723

```

1722172417231725

## Related lectures

Read the original on github.com ↗