{ "cells": [ { "cell_type": "markdown", "id": "75be0b95", "metadata": {}, "source": [ "# Posterior Distributions for AR(1) Parameters\n", "\n", "We’ll begin with some Python imports." ] }, { "cell_type": "code", "execution_count": null, "id": "f4d1b636", "metadata": { "hide-output": false }, "outputs": [], "source": [ "!pip install arviz pymc numpyro jax" ] }, { "cell_type": "code", "execution_count": null, "id": "2a8fa3ff", "metadata": { "hide-output": false }, "outputs": [], "source": [ "import arviz as az\n", "import pymc as pmc\n", "import numpyro\n", "from numpyro import distributions as dist\n", "\n", "import numpy as np\n", "import jax.numpy as jnp\n", "from jax import random\n", "import matplotlib.pyplot as plt\n", "\n", "%matplotlib inline\n", "\n", "import logging\n", "logging.basicConfig()\n", "logger = logging.getLogger('pymc')\n", "logger.setLevel(logging.CRITICAL)" ] }, { "cell_type": "markdown", "id": "5efbde47", "metadata": {}, "source": [ "This lecture uses Bayesian methods offered by [pymc](https://www.pymc.io/projects/docs/en/stable/) and [numpyro](https://num.pyro.ai/en/stable/) to make statistical inferences about two parameters of a univariate first-order autoregression.\n", "\n", "The model is a good laboratory for illustrating\n", "consequences of alternative ways of modeling the distribution of the initial $y_0$:\n", "\n", "- As a fixed number \n", "- As a random variable drawn from the stationary distribution of the $\\{y_t\\}$ stochastic process \n", "\n", "\n", "The first component of the statistical model is\n", "\n", "\n", "\n", "$$\n", "y_{t+1} = \\rho y_t + \\sigma_x \\epsilon_{t+1}, \\quad t \\geq 0 \\tag{52.1}\n", "$$\n", "\n", "where the scalars $\\rho$ and $\\sigma_x$ satisfy $|\\rho| < 1$ and $\\sigma_x > 0$;\n", "$\\{\\epsilon_{t+1}\\}$ is a sequence of i.i.d. normal random variables with mean $0$ and variance $1$.\n", "\n", "The second component of the statistical model is\n", "\n", "\n", "\n", "$$\n", "y_0 \\sim {\\cal N}(\\mu_0, \\sigma_0^2) \\tag{52.2}\n", "$$\n", "\n", "Consider a sample $\\{y_t\\}_{t=0}^T$ governed by this statistical model.\n", "\n", "The model\n", "implies that the likelihood function of $\\{y_t\\}_{t=0}^T$ can be **factored**:\n", "\n", "$$\n", "f(y_T, y_{T-1}, \\ldots, y_0) = f(y_T| y_{T-1}) f(y_{T-1}| y_{T-2}) \\cdots f(y_1 | y_0 ) f(y_0)\n", "$$\n", "\n", "where we use $f$ to denote a generic probability density.\n", "\n", "The statistical model [(52.1)](#equation-eq-themodel)-[(52.2)](#equation-eq-themodel-2) implies\n", "\n", "\n", "\\begin{aligned}\n", "f(y_t | y_{t-1}) & \\sim {\\mathcal N}(\\rho y_{t-1}, \\sigma_x^2) \\\\\n", " f(y_0) & \\sim {\\mathcal N}(\\mu_0, \\sigma_0^2)\n", "\\end{aligned}\n", "\n", "\n", "We want to study how inferences about the unknown parameters $(\\rho, \\sigma_x)$ depend on what is assumed about the parameters $\\mu_0, \\sigma_0$ of the distribution of $y_0$.\n", "\n", "Below, we study two widely used alternative assumptions:\n", "\n", "- $(\\mu_0,\\sigma_0) = (y_0, 0)$ which means that $y_0$ is drawn from the distribution ${\\mathcal N}(y_0, 0)$; in effect, we are **conditioning on an observed initial value**. \n", "- $\\mu_0,\\sigma_0$ are functions of $\\rho, \\sigma_x$ because $y_0$ is drawn from the stationary distribution implied by $\\rho, \\sigma_x$. \n", "\n", "\n", "**Note:** We do **not** treat a third possible case in which $\\mu_0,\\sigma_0$ are free parameters to be estimated.\n", "\n", "Unknown parameters are $\\rho, \\sigma_x$.\n", "\n", "We have independent **prior probability distributions** for $\\rho, \\sigma_x$ and want to compute a posterior probability distribution after observing a sample $\\{y_{t}\\}_{t=0}^T$.\n", "\n", "The notebook uses pymc4 and numpyro to compute a posterior distribution of $\\rho, \\sigma_x$. We will use NUTS samplers to generate samples from the posterior in a chain. Both of these libraries support NUTS samplers.\n", "\n", "NUTS is a form of Monte Carlo Markov Chain (MCMC) algorithm that bypasses random walk behaviour and allows for convergence to a target distribution more quickly. This not only has the advantage of speed, but allows for complex models to be fitted without having to employ specialised knowledge regarding the theory underlying those fitting methods.\n", "\n", "Thus, we explore consequences of making these alternative assumptions about the distribution of $y_0$:\n", "\n", "- A first procedure is to condition on whatever value of $y_0$ is observed. This amounts to assuming that the probability distribution of the random variable $y_0$ is a Dirac delta function that puts probability one on the observed value of $y_0$. \n", "- A second procedure assumes that $y_0$ is drawn from the stationary distribution of a process described by [(52.1)](#equation-eq-themodel)\n", " so that $y_0 \\sim {\\cal N} \\left(0, {\\sigma_x^2\\over (1-\\rho)^2} \\right)$ \n", "\n", "\n", "When the initial value $y_0$ is far out in a tail of the stationary distribution, conditioning on an initial value gives a posterior that is **more accurate** in a sense that we’ll explain.\n", "\n", "Basically, when $y_0$ happens to be in a tail of the stationary distribution and we **don’t condition on $y_0$**, the likelihood function for $\\{y_t\\}_{t=0}^T$ adjusts the posterior distribution of the parameter pair $\\rho, \\sigma_x$ to make the observed value of $y_0$ more likely than it really is under the stationary distribution, thereby adversely twisting the posterior in short samples.\n", "\n", "An example below shows how not conditioning on $y_0$ adversely shifts the posterior probability distribution of $\\rho$ toward larger values.\n", "\n", "We begin by solving a **direct problem** that simulates an AR(1) process.\n", "\n", "How we select the initial value $y_0$ matters.\n", "\n", "- If we think $y_0$ is drawn from the stationary distribution ${\\mathcal N}(0, \\frac{\\sigma_x^{2}}{1-\\rho^2})$, then it is a good idea to use this distribution as $f(y_0)$. Why? Because $y_0$ contains information about $\\rho, \\sigma_x$. \n", "- If we suspect that $y_0$ is far in the tails of the stationary distribution – so that variation in early observations in the sample have a significant **transient component** – it is better to condition on $y_0$ by setting $f(y_0) = 1$. \n", "\n", "\n", "To illustrate the issue, we’ll begin by choosing an initial $y_0$ that is far out in a tail of the stationary distribution." ] }, { "cell_type": "code", "execution_count": null, "id": "94762c26", "metadata": { "hide-output": false }, "outputs": [], "source": [ "def ar1_simulate(rho, sigma, y0, T):\n", "\n", " # Allocate space and draw epsilons\n", " y = np.empty(T)\n", " eps = np.random.normal(0.,sigma,T)\n", "\n", " # Initial condition and step forward\n", " y = y0\n", " for t in range(1, T):\n", " y[t] = rho*y[t-1] + eps[t]\n", "\n", " return y\n", "\n", "sigma = 1.\n", "rho = 0.5\n", "T = 50\n", "\n", "np.random.seed(145353452)\n", "y = ar1_simulate(rho, sigma, 10, T)" ] }, { "cell_type": "code", "execution_count": null, "id": "eea25db3", "metadata": { "hide-output": false }, "outputs": [], "source": [ "plt.plot(y)\n", "plt.tight_layout()" ] }, { "cell_type": "markdown", "id": "f6ce0e91", "metadata": {}, "source": [ "Now we shall use Bayes’ law to construct a posterior distribution, conditioning on the initial value of $y_0$.\n", "\n", "(Later we’ll assume that $y_0$ is drawn from the stationary distribution, but not now.)\n", "\n", "First we’ll use **pymc4**." ] }, { "cell_type": "markdown", "id": "82a05073", "metadata": {}, "source": [ "## PyMC Implementation\n", "\n", "For a normal distribution in pymc,\n", "$var = 1/\\tau = \\sigma^{2}$." ] }, { "cell_type": "code", "execution_count": null, "id": "87ed3e07", "metadata": { "hide-output": false }, "outputs": [], "source": [ "AR1_model = pmc.Model()\n", "\n", "with AR1_model:\n", "\n", " # Start with priors\n", " rho = pmc.Uniform('rho', lower=-1., upper=1.) # Assume stable rho\n", " sigma = pmc.HalfNormal('sigma', sigma = np.sqrt(10))\n", "\n", " # Expected value of y at the next period (rho * y)\n", " yhat = rho * y[:-1]\n", "\n", " # Likelihood of the actual realization\n", " y_like = pmc.Normal('y_obs', mu=yhat, sigma=sigma, observed=y[1:])" ] }, { "cell_type": "markdown", "id": "cb75e998", "metadata": {}, "source": [ "[pmc.sample](https://www.pymc.io/projects/docs/en/latest/api/generated/pymc.sample.html?highlight=sample#pymc.sample) by default uses the NUTS samplers to generate samples as shown in the below cell:" ] }, { "cell_type": "code", "execution_count": null, "id": "3c5000e2", "metadata": { "hide-output": false }, "outputs": [], "source": [ "with AR1_model:\n", " trace = pmc.sample(50000, tune=10000, return_inferencedata=True)" ] }, { "cell_type": "code", "execution_count": null, "id": "85422d0d", "metadata": { "hide-output": false }, "outputs": [], "source": [ "with AR1_model:\n", " az.plot_trace(trace, figsize=(17,6))" ] }, { "cell_type": "markdown", "id": "379806f1", "metadata": {}, "source": [ "Evidently, the posteriors aren’t centered on the true values of $.5, 1$ that we used to generate the data.\n", "\n", "This is a symptom of the classic **Hurwicz bias** for first order autoregressive processes (see Leonid Hurwicz [[Hur50](https://python.quantecon.org/zreferences.html#id4)].)\n", "\n", "The Hurwicz bias is worse the smaller is the sample (see [[OW69](https://python.quantecon.org/zreferences.html#id3)]).\n", "\n", "Be that as it may, here is more information about the posterior." ] }, { "cell_type": "code", "execution_count": null, "id": "c2322706", "metadata": { "hide-output": false }, "outputs": [], "source": [ "with AR1_model:\n", " summary = az.summary(trace, round_to=4)\n", "\n", "summary" ] }, { "cell_type": "markdown", "id": "8795231b", "metadata": {}, "source": [ "Now we shall compute a posterior distribution after seeing the same data but instead assuming that $y_0$ is drawn from the stationary distribution.\n", "\n", "This means that\n", "\n", "$$\n", "y_0 \\sim N \\left(0, \\frac{\\sigma_x^{2}}{1 - \\rho^{2}} \\right)\n", "$$\n", "\n", "We alter the code as follows:" ] }, { "cell_type": "code", "execution_count": null, "id": "65af69d6", "metadata": { "hide-output": false }, "outputs": [], "source": [ "AR1_model_y0 = pmc.Model()\n", "\n", "with AR1_model_y0:\n", "\n", " # Start with priors\n", " rho = pmc.Uniform('rho', lower=-1., upper=1.) # Assume stable rho\n", " sigma = pmc.HalfNormal('sigma', sigma=np.sqrt(10))\n", "\n", " # Standard deviation of ergodic y\n", " y_sd = sigma / np.sqrt(1 - rho**2)\n", "\n", " # yhat\n", " yhat = rho * y[:-1]\n", " y_data = pmc.Normal('y_obs', mu=yhat, sigma=sigma, observed=y[1:])\n", " y0_data = pmc.Normal('y0_obs', mu=0., sigma=y_sd, observed=y)" ] }, { "cell_type": "code", "execution_count": null, "id": "2bcefe42", "metadata": { "hide-output": false }, "outputs": [], "source": [ "with AR1_model_y0:\n", " trace_y0 = pmc.sample(50000, tune=10000, return_inferencedata=True)\n", "\n", "# Grey vertical lines are the cases of divergence" ] }, { "cell_type": "code", "execution_count": null, "id": "ea067fc6", "metadata": { "hide-output": false }, "outputs": [], "source": [ "with AR1_model_y0:\n", " az.plot_trace(trace_y0, figsize=(17,6))" ] }, { "cell_type": "code", "execution_count": null, "id": "5d1bbb34", "metadata": { "hide-output": false }, "outputs": [], "source": [ "with AR1_model:\n", " summary_y0 = az.summary(trace_y0, round_to=4)\n", "\n", "summary_y0" ] }, { "cell_type": "markdown", "id": "a95eca8d", "metadata": {}, "source": [ "Please note how the posterior for $\\rho$ has shifted to the right relative to when we conditioned on $y_0$ instead of assuming that $y_0$ is drawn from the stationary distribution.\n", "\n", "Think about why this happens.\n", "\n", "It is connected to how Bayes Law (conditional probability) solves an **inverse problem** by putting high probability on parameter values\n", "that make observations more likely.\n", "\n", "We’ll return to this issue after we use numpyro to compute posteriors under our two alternative assumptions about the distribution of $y_0$.\n", "\n", "We’ll now repeat the calculations using numpyro." ] }, { "cell_type": "markdown", "id": "e7dec8a4", "metadata": {}, "source": [ "## Numpyro Implementation" ] }, { "cell_type": "code", "execution_count": null, "id": "973c5117", "metadata": { "hide-output": false }, "outputs": [], "source": [ "\n", "def plot_posterior(sample):\n", " \"\"\"\n", " Plot trace and histogram\n", " \"\"\"\n", " # To np array\n", " rhos = sample['rho']\n", " sigmas = sample['sigma']\n", " rhos, sigmas, = np.array(rhos), np.array(sigmas)\n", "\n", " fig, axs = plt.subplots(2, 2, figsize=(17, 6))\n", " # Plot trace\n", " axs[0, 0].plot(rhos) # rho\n", " axs[1, 0].plot(sigmas) # sigma\n", "\n", " # Plot posterior\n", " axs[0, 1].hist(rhos, bins=50, density=True, alpha=0.7)\n", " axs[0, 1].set_xlim([0, 1])\n", " axs[1, 1].hist(sigmas, bins=50, density=True, alpha=0.7)\n", "\n", " axs[0, 0].set_title(\"rho\")\n", " axs[0, 1].set_title(\"rho\")\n", " axs[1, 0].set_title(\"sigma\")\n", " axs[1, 1].set_title(\"sigma\")\n", " plt.show()" ] }, { "cell_type": "code", "execution_count": null, "id": "f87fd3af", "metadata": { "hide-output": false }, "outputs": [], "source": [ "def AR1_model(data):\n", " # set prior\n", " rho = numpyro.sample('rho', dist.Uniform(low=-1., high=1.))\n", " sigma = numpyro.sample('sigma', dist.HalfNormal(scale=np.sqrt(10)))\n", "\n", " # Expected value of y at the next period (rho * y)\n", " yhat = rho * data[:-1]\n", "\n", " # Likelihood of the actual realization.\n", " y_data = numpyro.sample('y_obs', dist.Normal(loc=yhat, scale=sigma), obs=data[1:])" ] }, { "cell_type": "code", "execution_count": null, "id": "9e9cf81d", "metadata": { "hide-output": false }, "outputs": [], "source": [ "# Make jnp array\n", "y = jnp.array(y)\n", "\n", "# Set NUTS kernal\n", "NUTS_kernel = numpyro.infer.NUTS(AR1_model)\n", "\n", "# Run MCMC\n", "mcmc = numpyro.infer.MCMC(NUTS_kernel, num_samples=50000, num_warmup=10000, progress_bar=False)\n", "mcmc.run(rng_key=random.PRNGKey(1), data=y)" ] }, { "cell_type": "code", "execution_count": null, "id": "53c39b20", "metadata": { "hide-output": false }, "outputs": [], "source": [ "plot_posterior(mcmc.get_samples())" ] }, { "cell_type": "code", "execution_count": null, "id": "0290b9d2", "metadata": { "hide-output": false }, "outputs": [], "source": [ "mcmc.print_summary()" ] }, { "cell_type": "markdown", "id": "a6b7a594", "metadata": {}, "source": [ "Next, we again compute the posterior under the assumption that $y_0$ is drawn from the stationary distribution, so that\n", "\n", "$$\n", "y_0 \\sim N \\left(0, \\frac{\\sigma_x^{2}}{1 - \\rho^{2}} \\right)\n", "$$\n", "\n", "Here’s the new code to achieve this." ] }, { "cell_type": "code", "execution_count": null, "id": "e7942f01", "metadata": { "hide-output": false }, "outputs": [], "source": [ "def AR1_model_y0(data):\n", " # Set prior\n", " rho = numpyro.sample('rho', dist.Uniform(low=-1., high=1.))\n", " sigma = numpyro.sample('sigma', dist.HalfNormal(scale=np.sqrt(10)))\n", "\n", " # Standard deviation of ergodic y\n", " y_sd = sigma / jnp.sqrt(1 - rho**2)\n", "\n", " # Expected value of y at the next period (rho * y)\n", " yhat = rho * data[:-1]\n", "\n", " # Likelihood of the actual realization.\n", " y_data = numpyro.sample('y_obs', dist.Normal(loc=yhat, scale=sigma), obs=data[1:])\n", " y0_data = numpyro.sample('y0_obs', dist.Normal(loc=0., scale=y_sd), obs=data)" ] }, { "cell_type": "code", "execution_count": null, "id": "8f38b19f", "metadata": { "hide-output": false }, "outputs": [], "source": [ "# Make jnp array\n", "y = jnp.array(y)\n", "\n", "# Set NUTS kernal\n", "NUTS_kernel = numpyro.infer.NUTS(AR1_model_y0)\n", "\n", "# Run MCMC\n", "mcmc2 = numpyro.infer.MCMC(NUTS_kernel, num_samples=50000, num_warmup=10000, progress_bar=False)\n", "mcmc2.run(rng_key=random.PRNGKey(1), data=y)" ] }, { "cell_type": "code", "execution_count": null, "id": "216928ff", "metadata": { "hide-output": false }, "outputs": [], "source": [ "plot_posterior(mcmc2.get_samples())" ] }, { "cell_type": "code", "execution_count": null, "id": "54b480f6", "metadata": { "hide-output": false }, "outputs": [], "source": [ "mcmc2.print_summary()" ] }, { "cell_type": "markdown", "id": "ba3ce93e", "metadata": {}, "source": [ "Look what happened to the posterior!\n", "\n", "It has moved far from the true values of the parameters used to generate the data because of how Bayes’ Law (i.e., conditional probability)\n", "is telling numpyro to explain what it interprets as “explosive” observations early in the sample.\n", "\n", "Bayes’ Law is able to generate a plausible likelihood for the first observation by driving $\\rho \\rightarrow 1$ and $\\sigma \\uparrow$ in order to raise the variance of the stationary distribution.\n", "\n", "Our example illustrates the importance of what you assume about the distribution of initial conditions." ] } ], "metadata": { "date": 1680677898.3750327, "filename": "ar1_bayes.md", "kernelspec": { "display_name": "Python", "language": "python3", "name": "python3" }, "title": "Posterior Distributions for AR(1) Parameters" }, "nbformat": 4, "nbformat_minor": 5 }