|
| 1 | +--- |
| 2 | +jupytext: |
| 3 | + text_representation: |
| 4 | + extension: .md |
| 5 | + format_name: myst |
| 6 | + format_version: 0.13 |
| 7 | + jupytext_version: 1.16.1 |
| 8 | +kernelspec: |
| 9 | + display_name: Python 3 (ipykernel) |
| 10 | + language: python |
| 11 | + name: python3 |
| 12 | +--- |
| 13 | + |
| 14 | +# Another Look at Machine Learning a Ramsey Plan |
| 15 | + |
| 16 | +I'd like you to use your gradient code to compute a Ramsey plan for a versionof Calvo's original model. |
| 17 | + |
| 18 | +That model was not LQ. |
| 19 | + |
| 20 | +But your code can be tweaked to compute the Ramsey plan I suspect. |
| 21 | + |
| 22 | +I sketch the main idea here. |
| 23 | + |
| 24 | +## Calvo's setup |
| 25 | + |
| 26 | +The Ramsey planner's one-period social utility function |
| 27 | +is |
| 28 | + |
| 29 | +$$ |
| 30 | +u(c) + v(m) \equiv u(f(x)) + v(m) |
| 31 | +$$ |
| 32 | + |
| 33 | +where in our notation Calvo or Chang's objects become |
| 34 | + |
| 35 | +$$ |
| 36 | +\begin{align*} |
| 37 | +x & = \mu \cr |
| 38 | +m & = -\alpha \theta \cr |
| 39 | +u(c) & = u(f(x)) = u(f(\mu)) \cr |
| 40 | +v(m) & = v (-\alpha \theta) |
| 41 | +\end{align*} |
| 42 | +$$ |
| 43 | + |
| 44 | + |
| 45 | +In the quantecon lecture about the Chang-Calvo model, we deployed the following functional forms: |
| 46 | + |
| 47 | +$$ |
| 48 | +u(c) = \log(c) |
| 49 | +$$ |
| 50 | + |
| 51 | +$$ |
| 52 | +v(m) = \frac{1}{500}(m \bar m - 0.5m^2)^{0.5} |
| 53 | +$$ |
| 54 | + |
| 55 | +$$ |
| 56 | +f(x) = 180 - (0.4x)^2 |
| 57 | +$$ |
| 58 | + |
| 59 | +where $\bar m$ is a parameter set somewhere in the quantecon code. |
| 60 | + |
| 61 | +So with this parameterization of Calvo and Chang's functions, components of our one-period criterion become |
| 62 | + |
| 63 | +$$ |
| 64 | +u(c_t) = \log (180 - (0.4 \mu_t)^2) |
| 65 | +$$ |
| 66 | + |
| 67 | +and |
| 68 | + |
| 69 | +$$ |
| 70 | +v(m_t - p_t) = \frac{1}{500}((-\alpha \theta_t) \bar m - 0.5(-\alpha \theta_t)^2)^{0.5} |
| 71 | +$$ |
| 72 | + |
| 73 | +As in our ``calvo_machine_learning`` lecture, the Ramsey planner maximizes the criterion |
| 74 | + |
| 75 | +$$ |
| 76 | +\sum_{t=0}^\infty \beta^t [ u(c_t) + v(m_t)] \tag{1} |
| 77 | +$$ |
| 78 | + |
| 79 | +subject to the constraint |
| 80 | + |
| 81 | + |
| 82 | +$$ |
| 83 | +\theta_t = \frac{1}{1+\alpha} \sum_{j=0}^\infty \left(\frac{\alpha}{1+\alpha}\right)^j \mu_{t+j}, \quad t \geq 0 \tag{2} |
| 84 | +$$ |
| 85 | + |
| 86 | + |
| 87 | +## Proposal to Humphrey |
| 88 | + |
| 89 | +I'd like to take big parts of the code that you used for the ``gradient`` method only -- the first method you created -- and adapt it to compute a Ramsey plan for this non LQ version of the Calvo's model. |
| 90 | + |
| 91 | + * it is quite close to the version in the main part of Calvo's 1978 classic paper |
| 92 | + |
| 93 | +I'd like you to use exactly the same assumptions about the $\{\mu_t\}_{t=0}^\infty$ process that is in the code, which means that you'll only have to compute a truncated sequence $\{\mu_t\}_{t=0}^T$ parameterized by $T$ and $\bar \mu$ as in the code. |
| 94 | + |
| 95 | +And it would be great if you could also compute the Ramsey plan restricted to a constant $\vec \mu$ sequence so that we could get our hands on that plan to plot and compare with the ordinary Ramsey plan. |
| 96 | + |
| 97 | + |
| 98 | +After you've computed the Ramsey plan, I'd like to ask you to plot the pretty graphs of $\vec \theta, \vec \mu$ and also $\vec v$ where (recycling notation) here $\vec v$ is a sequence of continuation values. |
| 99 | + |
| 100 | +Qualitatively, these graphs should look a lot like the ones plotted in the ``calvo_machine_learning`` lecture. |
| 101 | + |
| 102 | +Later, after those plots are done, I'd like to describe some **nonlinear** regressions to run that we'll use to try to discover a recursive represenation of a Ramsey plan. |
| 103 | + |
| 104 | + * this will be fun -- we might want to use some neural nets to approximate these functions -- then we'll **really** be doing machine learning. |
| 105 | + |
| 106 | + |
| 107 | +## Excuses and Apologies |
| 108 | + |
| 109 | +I have recycled some notation -- e.g., $v$ and $v_t$. And Calvo uses $m$ for what we call $m_t - p_t$ and so on. |
| 110 | + |
| 111 | +Later we can do some notation policing and cleaning up. |
| 112 | + |
| 113 | +But right now, let's just proceed as best we can with notation that makes it easiest to take |
| 114 | +the ``calvo_machine_learning`` code and apply it with minimal changes. |
| 115 | + |
| 116 | +Thanks! |
| 117 | + |
| 118 | +```{code-cell} ipython3 |
| 119 | +from quantecon import LQ |
| 120 | +import numpy as np |
| 121 | +import jax.numpy as jnp |
| 122 | +from jax import jit, grad |
| 123 | +import optax |
| 124 | +import statsmodels.api as sm |
| 125 | +import matplotlib.pyplot as plt |
| 126 | +``` |
| 127 | + |
| 128 | +```{code-cell} ipython3 |
| 129 | +from collections import namedtuple |
| 130 | +
|
| 131 | +def create_ChangML(T=40, β=0.3, c=2, α=1, mbar=30.0): |
| 132 | + ChangML = namedtuple('ChangML', ['T', 'β', 'c', 'α', |
| 133 | + 'mbar']) |
| 134 | +
|
| 135 | + return ChangML(T=T, β=β, c=c, α=α, mbar=mbar) |
| 136 | +
|
| 137 | +
|
| 138 | +model = create_ChangML() |
| 139 | +``` |
| 140 | + |
| 141 | +```{code-cell} ipython3 |
| 142 | +@jit |
| 143 | +def compute_θ(μ, α): |
| 144 | + λ = α / (1 + α) |
| 145 | + T = len(μ) - 1 |
| 146 | + μbar = μ[-1] |
| 147 | + |
| 148 | + # Create an array of powers for λ |
| 149 | + λ_powers = λ ** jnp.arange(T + 1) |
| 150 | + |
| 151 | + # Compute the weighted sums for all t |
| 152 | + weighted_sums = jnp.array( |
| 153 | + [jnp.sum(λ_powers[:T-t] * μ[t:T]) for t in range(T)]) |
| 154 | + |
| 155 | + # Compute θ values except for the last element |
| 156 | + θ = (1 - λ) * weighted_sums + λ**(T - jnp.arange(T)) * μbar |
| 157 | + |
| 158 | + # Set the last element |
| 159 | + θ = jnp.append(θ, μbar) |
| 160 | + |
| 161 | + return θ |
| 162 | +
|
| 163 | +def compute_V(μ, model): |
| 164 | + β, c, α, mbar,= model.β, model.c, model.α, model.mbar |
| 165 | + θ = compute_θ(μ, α) |
| 166 | + |
| 167 | + T = len(μ) - 1 |
| 168 | + t = jnp.arange(T) |
| 169 | + |
| 170 | + def u(μ, m, mbar): |
| 171 | + # print('μ', μ) |
| 172 | + f_μ = 180 - (0.4 * μ)**2 |
| 173 | + # print('f_μ', f_μ) |
| 174 | + # print('m, mbar:', m, mbar, (mbar * m - 0.5 * m**2)) |
| 175 | + v_m = 1 / 500 * jnp.sqrt(jnp.maximum(1e-16, mbar * m - 0.5 * m**2)) |
| 176 | + # print('v_μ', v_m) |
| 177 | + return jnp.log(f_μ) + v_m |
| 178 | + |
| 179 | + # Compute sum except for the last element |
| 180 | + |
| 181 | + # TO TOM: -α*θ is causing trouble in utility calculation (u) above |
| 182 | + # As it makes mbar * m - 0.5 * m**2 < 0 and sqrt of negative |
| 183 | + # values returns NA |
| 184 | + V_sum = jnp.sum(β**t * u(μ[:T], -α*θ[:T], mbar)) |
| 185 | + # print('V_sum', V_sum) |
| 186 | + |
| 187 | + # Compute the final term |
| 188 | + V_final = (β**T / (1 - β)) * u(μ[-1], -α*θ[-1], mbar) |
| 189 | + # print('V_final', V_final) |
| 190 | + V = V_sum + V_final |
| 191 | + |
| 192 | + return V |
| 193 | +
|
| 194 | +# compute_V(jnp.array([1.0, 1.0, 1.0]), model) |
| 195 | +``` |
| 196 | + |
| 197 | +```{code-cell} ipython3 |
| 198 | +def adam_optimizer(grad_func, init_params, |
| 199 | + lr=0.1, |
| 200 | + max_iter=10_000, |
| 201 | + error_tol=1e-7): |
| 202 | +
|
| 203 | + # Set initial parameters and optimizer |
| 204 | + params = init_params |
| 205 | + optimizer = optax.adam(learning_rate=lr) |
| 206 | + opt_state = optimizer.init(params) |
| 207 | +
|
| 208 | + # Update parameters and gradients |
| 209 | + @jit |
| 210 | + def update(params, opt_state): |
| 211 | + grads = grad_func(params) |
| 212 | + updates, opt_state = optimizer.update(grads, opt_state) |
| 213 | + params = optax.apply_updates(params, updates) |
| 214 | + return params, opt_state, grads |
| 215 | +
|
| 216 | + # Gradient descent loop |
| 217 | + for i in range(max_iter): |
| 218 | + params, opt_state, grads = update(params, opt_state) |
| 219 | + |
| 220 | + if jnp.linalg.norm(grads) < error_tol: |
| 221 | + print(f"Converged after {i} iterations.") |
| 222 | + break |
| 223 | +
|
| 224 | + if i % 100 == 0: |
| 225 | + print(f"Iteration {i}, grad norm: {jnp.linalg.norm(grads)}") |
| 226 | + |
| 227 | + return params |
| 228 | +``` |
| 229 | + |
| 230 | +```{code-cell} ipython3 |
| 231 | +T = 40 |
| 232 | +
|
| 233 | +# Initial guess for μ |
| 234 | +μ_init = jnp.ones(T) |
| 235 | +
|
| 236 | +# Maximization instead of minimization |
| 237 | +grad_V = jit(grad( |
| 238 | + lambda μ: -compute_V(μ, model))) |
| 239 | +``` |
| 240 | + |
| 241 | +```{code-cell} ipython3 |
| 242 | +%%time |
| 243 | +
|
| 244 | +# Optimize μ |
| 245 | +optimized_μ = adam_optimizer(grad_V, μ_init) |
| 246 | +
|
| 247 | +print(f"optimized μ = \n{optimized_μ}") |
| 248 | +``` |
| 249 | + |
| 250 | +```{code-cell} ipython3 |
| 251 | +compute_V(optimized_μ, model) |
| 252 | +``` |
| 253 | + |
| 254 | +```{code-cell} ipython3 |
| 255 | +model = create_ChangML(β=0.8) |
| 256 | +
|
| 257 | +grad_V = jit(grad( |
| 258 | + lambda μ: -compute_V(μ, model))) |
| 259 | +``` |
| 260 | + |
| 261 | +```{code-cell} ipython3 |
| 262 | +%%time |
| 263 | +
|
| 264 | +# Optimize μ |
| 265 | +optimized_μ = adam_optimizer(grad_V, μ_init) |
| 266 | +
|
| 267 | +print(f"optimized μ = \n{optimized_μ}") |
| 268 | +``` |
| 269 | + |
| 270 | +```{code-cell} ipython3 |
| 271 | +compute_V(optimized_μ, model) |
| 272 | +``` |
0 commit comments