{
"cells": [
{
"cell_type": "markdown",
"metadata": {
"colab_type": "text",
"execution": {},
"id": "view-in-github"
},
"source": [
"
"
]
},
{
"cell_type": "markdown",
"metadata": {
"execution": {}
},
"source": [
"# Tutorial 2: Hidden Markov Model\n",
"\n",
"**Week 3, Day 2: Hidden Dynamics**\n",
"\n",
"**By Neuromatch Academy**\n",
"\n",
"**Content creators:** Yicheng Fei with help from Jesse Livezey and Xaq Pitkow\n",
"\n",
"**Content reviewers:** John Butler, Matt Krause, Meenakshi Khosla, Spiros Chavlis, Michael Waskom\n",
"\n",
"**Production editors:** Ella Batty, Gagana B, Spiros Chavlis"
]
},
{
"cell_type": "markdown",
"metadata": {
"execution": {}
},
"source": [
"---\n",
"# Tutorial objectives\n",
"\n",
"*Estimated timing of tutorial: 1 hour, 5 minutes*\n",
"\n",
"The world around us is often changing, but we only have noisy sensory measurements. Similarly, neural systems switch between discrete states (e.g. sleep/wake) which are observable only indirectly, through their impact on neural activity. **Hidden Markov Models** (HMM) let us reason about these unobserved (also called hidden or latent) states using a time series of measurements.\n",
"\n",
"Here we'll learn how changing the HMM's transition probability and measurement noise impacts the data. We'll look at how uncertainty increases as we predict the future, and how to gain information from the measurements.\n",
"\n",
"We will use a binary latent variable $s_t \\in \\{0,1\\}$ that switches randomly between the two states, and a 1D Gaussian emission model $m_t|s_t \\sim \\mathcal{N}(\\mu_{s_t},\\sigma^2_{s_t})$ that provides evidence about the current state.\n",
"\n",
"By the end of this tutorial, you should be able to:\n",
"- Describe how the hidden states in a Hidden Markov model evolve over time, both in words, mathematically, and in code\n",
"- Estimate hidden states from data using forward inference in a Hidden Markov model\n",
"- Describe how measurement noise and state transition probabilities affect uncertainty in predictions in the future and the ability to estimate hidden states.\n",
"\n",
"
\n",
"\n",
"**Summary of Exercises**\n",
"1. Generate data from an HMM.\n",
"2. Calculate how predictions propagate in a Markov Chain without evidence.\n",
"3. Combine new evidence and prediction from past evidence to estimate hidden states."
]
},
{
"cell_type": "markdown",
"metadata": {},
"source": [
"\n"
]
},
{
"cell_type": "code",
"execution_count": null,
"metadata": {
"cellView": "form",
"execution": {},
"tags": [
"remove-input"
]
},
"outputs": [],
"source": [
"# @markdown\n",
"from IPython.display import IFrame\n",
"from ipywidgets import widgets\n",
"out = widgets.Output()\n",
"with out:\n",
" print(f\"If you want to download the slides: https://osf.io/download/zsfbn/\")\n",
" display(IFrame(src=f\"https://mfr.ca-1.osf.io/render?url=https://osf.io/zsfbn/?direct%26mode=render%26action=download%26mode=render\", width=730, height=410))\n",
"display(out)"
]
},
{
"cell_type": "markdown",
"metadata": {
"execution": {}
},
"source": [
"---\n",
"# Setup"
]
},
{
"cell_type": "markdown",
"metadata": {},
"source": [
"## Install and import feedback gadget\n"
]
},
{
"cell_type": "code",
"execution_count": null,
"metadata": {
"cellView": "form",
"execution": {},
"tags": [
"hide-input"
]
},
"outputs": [],
"source": [
"# @title Install and import feedback gadget\n",
"\n",
"!pip3 install vibecheck datatops --quiet\n",
"\n",
"from vibecheck import DatatopsContentReviewContainer\n",
"def content_review(notebook_section: str):\n",
" return DatatopsContentReviewContainer(\n",
" \"\", # No text prompt\n",
" notebook_section,\n",
" {\n",
" \"url\": \"https://pmyvdlilci.execute-api.us-east-1.amazonaws.com/klab\",\n",
" \"name\": \"neuromatch_cn\",\n",
" \"user_key\": \"y1x3mpx5\",\n",
" },\n",
" ).render()\n",
"\n",
"\n",
"feedback_prefix = \"W3D2_T2\""
]
},
{
"cell_type": "code",
"execution_count": null,
"metadata": {
"execution": {}
},
"outputs": [],
"source": [
"# Imports\n",
"import numpy as np\n",
"import time\n",
"from scipy import stats\n",
"from scipy.optimize import linear_sum_assignment\n",
"from collections import namedtuple\n",
"import matplotlib.pyplot as plt\n",
"from matplotlib import patches"
]
},
{
"cell_type": "markdown",
"metadata": {},
"source": [
"## Figure Settings\n"
]
},
{
"cell_type": "code",
"execution_count": null,
"metadata": {
"cellView": "form",
"execution": {},
"tags": [
"hide-input"
]
},
"outputs": [],
"source": [
"# @title Figure Settings\n",
"import logging\n",
"logging.getLogger('matplotlib.font_manager').disabled = True\n",
"\n",
"import ipywidgets as widgets # interactive display\n",
"from ipywidgets import interactive, interact, HBox, Layout,VBox\n",
"from IPython.display import HTML\n",
"%config InlineBackend.figure_format = 'retina'\n",
"plt.style.use(\"https://raw.githubusercontent.com/NeuromatchAcademy/course-content/NMA2020/nma.mplstyle\")"
]
},
{
"cell_type": "markdown",
"metadata": {},
"source": [
"## Plotting Functions\n"
]
},
{
"cell_type": "code",
"execution_count": null,
"metadata": {
"cellView": "form",
"execution": {},
"tags": [
"hide-input"
]
},
"outputs": [],
"source": [
"# @title Plotting Functions\n",
"\n",
"def plot_hmm1(model, states, measurements, flag_m=True):\n",
" \"\"\"Plots HMM states and measurements for 1d states and measurements.\n",
"\n",
" Args:\n",
" model (hmmlearn model): hmmlearn model used to get state means.\n",
" states (numpy array of floats): Samples of the states.\n",
" measurements (numpy array of floats): Samples of the states.\n",
" \"\"\"\n",
" T = states.shape[0]\n",
" nsteps = states.size\n",
" aspect_ratio = 2\n",
" fig, ax1 = plt.subplots(figsize=(8,4))\n",
" states_forplot = list(map(lambda s: model.means[s], states))\n",
" ax1.step(np.arange(nstep), states_forplot, \"-\", where=\"mid\", alpha=1.0, c=\"green\")\n",
" ax1.set_xlabel(\"Time\")\n",
" ax1.set_ylabel(\"Latent State\", c=\"green\")\n",
" ax1.set_yticks([-1, 1])\n",
" ax1.set_yticklabels([\"-1\", \"+1\"])\n",
" ax1.set_xticks(np.arange(0,T,10))\n",
" ymin = min(measurements)\n",
" ymax = max(measurements)\n",
"\n",
" ax2 = ax1.twinx()\n",
" ax2.set_ylabel(\"Measurements\", c=\"crimson\")\n",
"\n",
" # show measurement gaussian\n",
" if flag_m:\n",
" ax2.plot([T,T],ax2.get_ylim(), color=\"maroon\", alpha=0.6)\n",
" for i in range(model.n_components):\n",
" mu = model.means[i]\n",
" scale = np.sqrt(model.vars[i])\n",
" rv = stats.norm(mu, scale)\n",
" num_points = 50\n",
" domain = np.linspace(mu-3*scale, mu+3*scale, num_points)\n",
"\n",
" left = np.repeat(float(T), num_points)\n",
" # left = np.repeat(0.0, num_points)\n",
" offset = rv.pdf(domain)\n",
" offset *= T / 15\n",
" lbl = \"measurement\" if i == 0 else \"\"\n",
" # ax2.fill_betweenx(domain, left, left-offset, alpha=0.3, lw=2, color=\"maroon\", label=lbl)\n",
" ax2.fill_betweenx(domain, left+offset, left, alpha=0.3, lw=2, color=\"maroon\", label=lbl)\n",
" ax2.scatter(np.arange(nstep), measurements, c=\"crimson\", s=4)\n",
" ax2.legend(loc=\"upper left\")\n",
" ax1.set_ylim(ax2.get_ylim())\n",
" plt.show(fig)\n",
"\n",
"\n",
"def plot_marginal_seq(predictive_probs, switch_prob):\n",
" \"\"\"Plots the sequence of marginal predictive distributions.\n",
"\n",
" Args:\n",
" predictive_probs (list of numpy vectors): sequence of predictive probability vectors\n",
" switch_prob (float): Probability of switching states.\n",
" \"\"\"\n",
" T = len(predictive_probs)\n",
" prob_neg = [p_vec[0] for p_vec in predictive_probs]\n",
" prob_pos = [p_vec[1] for p_vec in predictive_probs]\n",
" fig, ax = plt.subplots()\n",
" ax.plot(np.arange(T), prob_neg, color=\"blue\")\n",
" ax.plot(np.arange(T), prob_pos, color=\"orange\")\n",
" ax.legend([\n",
" \"prob in state -1\", \"prob in state 1\"\n",
" ])\n",
" ax.text(T/2, 0.05, \"switching probability={}\".format(switch_prob), fontsize=12,\n",
" bbox=dict(boxstyle=\"round\", facecolor=\"wheat\", alpha=0.6))\n",
" ax.set_xlabel(\"Time\")\n",
" ax.set_ylabel(\"Probability\")\n",
" ax.set_title(\"Forgetting curve in a changing world\")\n",
" plt.show(fig)\n",
"\n",
"\n",
"def plot_evidence_vs_noevidence(posterior_matrix, predictive_probs):\n",
" \"\"\"Plots the average posterior probabilities with evidence v.s. no evidence\n",
"\n",
" Args:\n",
" posterior_matrix: (2d numpy array of floats): The posterior probabilities in state 1 from evidence (samples, time)\n",
" predictive_probs (numpy array of floats): Predictive probabilities in state 1 without evidence\n",
" \"\"\"\n",
" nsample, T = posterior_matrix.shape\n",
" posterior_mean = posterior_matrix.mean(axis=0)\n",
" fig, ax = plt.subplots(1)\n",
" ax.plot([0.0, T], [0., 0.], color=\"red\", linestyle=\"dashed\")\n",
" ax.plot(np.arange(T), predictive_probs, c=\"orange\", linewidth=2, label=\"No evidence\")\n",
" ax.scatter(np.tile(np.arange(T), (nsample, 1)), posterior_matrix, s=0.8,\n",
" c=\"green\", alpha=0.3, label=\"With evidence(Sample)\")\n",
" ax.plot(np.arange(T), posterior_mean, c='green',\n",
" linewidth=2, label=\"With evidence(Average)\")\n",
" ax.legend()\n",
" ax.set_yticks([0.0, 0.25, 0.5, 0.75, 1.0])\n",
" ax.set_xlabel(\"Time\")\n",
" ax.set_ylabel(\"Probability in State +1\")\n",
" ax.set_title(\"Gain confidence with evidence\")\n",
" plt.show(fig)\n",
"\n",
"\n",
"def plot_forward_inference(model, states, measurements, states_inferred,\n",
" predictive_probs, likelihoods, posterior_probs,\n",
" t=None, flag_m=True, flag_d=True, flag_pre=True,\n",
" flag_like=True, flag_post=True):\n",
" \"\"\"Plot ground truth state sequence with noisy measurements, and ground truth states v.s. inferred ones\n",
"\n",
" Args:\n",
" model (instance of hmmlearn.GaussianHMM): an instance of HMM\n",
" states (numpy vector): vector of 0 or 1(int or Bool), the sequences of true latent states\n",
" measurements (numpy vector of numpy vector): the un-flattened Gaussian measurements at each time point, element has size (1,)\n",
" states_inferred (numpy vector): vector of 0 or 1(int or Bool), the sequences of inferred latent states\n",
" \"\"\"\n",
" T = states.shape[0]\n",
" if t is None:\n",
" t = T-1\n",
" nsteps = states.size\n",
" fig, ax1 = plt.subplots(figsize=(11,6))\n",
" # true states\n",
" states_forplot = list(map(lambda s: model.means[s], states))\n",
" ax1.step(np.arange(nstep)[:t+1], states_forplot[:t+1], \"-\", where=\"mid\", alpha=1.0, c=\"green\", label=\"true\")\n",
" ax1.step(np.arange(nstep)[t+1:], states_forplot[t+1:], \"-\", where=\"mid\", alpha=0.3, c=\"green\", label=\"\")\n",
" # Posterior curve\n",
" delta = model.means[1] - model.means[0]\n",
" states_interpolation = model.means[0] + delta * posterior_probs[:,1]\n",
" if flag_post:\n",
" ax1.step(np.arange(nstep)[:t+1], states_interpolation[:t+1], \"-\", where=\"mid\", c=\"grey\", label=\"posterior\")\n",
"\n",
" ax1.set_xlabel(\"Time\")\n",
" ax1.set_ylabel(\"Latent State\", c=\"green\")\n",
" ax1.set_yticks([-1, 1])\n",
" ax1.set_yticklabels([\"-1\", \"+1\"])\n",
" ax1.legend(bbox_to_anchor=(0,1.02,0.2,0.1), borderaxespad=0, ncol=2)\n",
"\n",
" ax2 = ax1.twinx()\n",
" ax2.set_ylim(\n",
" min(-1.2, np.min(measurements)),\n",
" max(1.2, np.max(measurements))\n",
" )\n",
" if flag_d:\n",
" ax2.scatter(np.arange(nstep)[:t+1], measurements[:t+1], c=\"crimson\", s=4, label=\"measurement\")\n",
" ax2.set_ylabel(\"Measurements\", c=\"crimson\")\n",
"\n",
" # show measurement distributions\n",
" if flag_m:\n",
" for i in range(model.n_components):\n",
" mu = model.means[i]\n",
" scale = np.sqrt(model.vars[i])\n",
" rv = stats.norm(mu, scale)\n",
" num_points = 50\n",
" domain = np.linspace(mu-3*scale, mu+3*scale, num_points)\n",
"\n",
" left = np.repeat(float(T), num_points)\n",
" offset = rv.pdf(domain)\n",
" offset *= T /15\n",
" lbl = \"\"\n",
" ax2.fill_betweenx(domain, left+offset, left, alpha=0.3, lw=2, color=\"maroon\", label=lbl)\n",
" ymin, ymax = ax2.get_ylim()\n",
" width = 0.1 * (ymax-ymin) / 2.0\n",
" centers = [-1.0, 1.0]\n",
" bar_scale = 15\n",
"\n",
" # Predictions\n",
" data = predictive_probs\n",
" if flag_pre:\n",
" for i in range(model.n_components):\n",
" domain = np.array([centers[i]-1.5*width, centers[i]-0.5*width])\n",
" left = np.array([t,t])\n",
" offset = np.array([data[t,i]]*2)\n",
" offset *= bar_scale\n",
" lbl = \"todays prior\" if i == 0 else \"\"\n",
" ax2.fill_betweenx(domain, left+offset, left, alpha=0.3, lw=2, color=\"dodgerblue\", label=lbl)\n",
"\n",
" # Likelihoods\n",
" data = likelihoods\n",
" data /= np.sum(data,axis=-1, keepdims=True)\n",
" if flag_like:\n",
" for i in range(model.n_components):\n",
" domain = np.array([centers[i]+0.5*width, centers[i]+1.5*width])\n",
" left = np.array([t,t])\n",
" offset = np.array([data[t,i]]*2)\n",
" offset *= bar_scale\n",
" lbl = \"likelihood\" if i == 0 else \"\"\n",
" ax2.fill_betweenx(domain, left+offset, left, alpha=0.3, lw=2, color=\"crimson\", label=lbl)\n",
" # Posteriors\n",
" data = posterior_probs\n",
" if flag_post:\n",
" for i in range(model.n_components):\n",
" domain = np.array([centers[i]-0.5*width, centers[i]+0.5*width])\n",
" left = np.array([t,t])\n",
" offset = np.array([data[t,i]]*2)\n",
" offset *= bar_scale\n",
" lbl = \"posterior\" if i == 0 else \"\"\n",
" ax2.fill_betweenx(domain, left+offset, left, alpha=0.3, lw=2, color=\"grey\", label=lbl)\n",
" if t0.5$? Why?\n",
"\n",
"\n",
"\n"
]
},
{
"cell_type": "markdown",
"metadata": {},
"source": [
" Execute this cell to enable helper function `simulate_prediction_only`\n"
]
},
{
"cell_type": "code",
"execution_count": null,
"metadata": {
"cellView": "form",
"execution": {},
"tags": [
"hide-input"
]
},
"outputs": [],
"source": [
"# @markdown Execute this cell to enable helper function `simulate_prediction_only`\n",
"\n",
"def simulate_prediction_only(model, nstep):\n",
" \"\"\"\n",
" Simulate the diffusion of HMM with no observations\n",
"\n",
" Args:\n",
" model (GaussianHMM1D instance): the HMM instance\n",
" nstep (int): total number of time steps to simulate(include initial time)\n",
"\n",
" Returns:\n",
" predictive_probs (list of numpy vector): the list of marginal probabilities\n",
" \"\"\"\n",
" entropy_list = []\n",
" predictive_probs = []\n",
" prob = model.startprob\n",
" for i in range(nstep):\n",
"\n",
" # Log probabilities\n",
" predictive_probs.append(prob)\n",
"\n",
" # One step forward\n",
" prob = prob @ model.transmat\n",
"\n",
" return predictive_probs"
]
},
{
"cell_type": "markdown",
"metadata": {},
"source": [
" Execute this cell to enable the widget!\n"
]
},
{
"cell_type": "code",
"execution_count": null,
"metadata": {
"cellView": "form",
"execution": {},
"tags": [
"hide-input"
]
},
"outputs": [],
"source": [
"# @markdown Execute this cell to enable the widget!\n",
"\n",
"np.random.seed(101)\n",
"T = 100\n",
"noise_level = 0.5\n",
"\n",
"@widgets.interact(switch_prob=widgets.FloatSlider(min=0.0, max=1.0, step=0.01,\n",
" value=0.1))\n",
"def plot(switch_prob=switch_prob):\n",
" model = create_HMM(switch_prob=switch_prob, noise_level=noise_level)\n",
" predictive_probs = simulate_prediction_only(model, T)\n",
" plot_marginal_seq(predictive_probs, switch_prob)"
]
},
{
"cell_type": "code",
"execution_count": null,
"metadata": {
"execution": {}
},
"outputs": [],
"source": [
"# to_remove explanation\n",
"\n",
"\"\"\"\n",
"1) You forget more quickly with high switching probability because you become less\n",
"certain that the state is the one you know.\n",
"\n",
"2) With switch_prob > 0.5, the predictive probabilities cross over 0 and eventually oscillate.\n",
"\"\"\";"
]
},
{
"cell_type": "markdown",
"metadata": {},
"source": [
"#### Submit your feedback\n"
]
},
{
"cell_type": "code",
"execution_count": null,
"metadata": {
"cellView": "form",
"execution": {},
"tags": [
"hide-input"
]
},
"outputs": [],
"source": [
"# @title Submit your feedback\n",
"content_review(f\"{feedback_prefix}_Forgetting_in_a_changing_world_Interactive_Demo_and_Discussion\")"
]
},
{
"cell_type": "markdown",
"metadata": {},
"source": [
"#### Video 5: Section 2 Exercise Discussion\n"
]
},
{
"cell_type": "code",
"execution_count": null,
"metadata": {
"cellView": "form",
"execution": {},
"tags": [
"remove-input"
]
},
"outputs": [],
"source": [
"# @title Video 5: Section 2 Exercise Discussion\n",
"from ipywidgets import widgets\n",
"from IPython.display import YouTubeVideo\n",
"from IPython.display import IFrame\n",
"from IPython.display import display\n",
"\n",
"\n",
"class PlayVideo(IFrame):\n",
" def __init__(self, id, source, page=1, width=400, height=300, **kwargs):\n",
" self.id = id\n",
" if source == 'Bilibili':\n",
" src = f'https://player.bilibili.com/player.html?bvid={id}&page={page}'\n",
" elif source == 'Osf':\n",
" src = f'https://mfr.ca-1.osf.io/render?url=https://osf.io/download/{id}/?direct%26mode=render'\n",
" super(PlayVideo, self).__init__(src, width, height, **kwargs)\n",
"\n",
"\n",
"def display_videos(video_ids, W=400, H=300, fs=1):\n",
" tab_contents = []\n",
" for i, video_id in enumerate(video_ids):\n",
" out = widgets.Output()\n",
" with out:\n",
" if video_ids[i][0] == 'Youtube':\n",
" video = YouTubeVideo(id=video_ids[i][1], width=W,\n",
" height=H, fs=fs, rel=0)\n",
" print(f'Video available at https://youtube.com/watch?v={video.id}')\n",
" else:\n",
" video = PlayVideo(id=video_ids[i][1], source=video_ids[i][0], width=W,\n",
" height=H, fs=fs, autoplay=False)\n",
" if video_ids[i][0] == 'Bilibili':\n",
" print(f'Video available at https://www.bilibili.com/video/{video.id}')\n",
" elif video_ids[i][0] == 'Osf':\n",
" print(f'Video available at https://osf.io/{video.id}')\n",
" display(video)\n",
" tab_contents.append(out)\n",
" return tab_contents\n",
"\n",
"\n",
"video_ids = [('Youtube', 'GRnlvxZ_ozk'), ('Bilibili', 'BV1DM4y1K7tK')]\n",
"tab_contents = display_videos(video_ids, W=730, H=410)\n",
"tabs = widgets.Tab()\n",
"tabs.children = tab_contents\n",
"for i in range(len(tab_contents)):\n",
" tabs.set_title(i, video_ids[i][0])\n",
"display(tabs)"
]
},
{
"cell_type": "markdown",
"metadata": {},
"source": [
"#### Submit your feedback\n"
]
},
{
"cell_type": "code",
"execution_count": null,
"metadata": {
"cellView": "form",
"execution": {},
"tags": [
"hide-input"
]
},
"outputs": [],
"source": [
"# @title Submit your feedback\n",
"content_review(f\"{feedback_prefix}_Section_2_Exercise_Discussion_Video\")"
]
},
{
"cell_type": "markdown",
"metadata": {
"execution": {}
},
"source": [
"# Section 3: Forward inference in an HMM\n",
"\n",
"*Estimated timing to here from start of tutorial: 35 min*"
]
},
{
"cell_type": "markdown",
"metadata": {},
"source": [
"## Video 6: Inference in an HMM\n"
]
},
{
"cell_type": "code",
"execution_count": null,
"metadata": {
"cellView": "form",
"execution": {},
"tags": [
"remove-input"
]
},
"outputs": [],
"source": [
"# @title Video 6: Inference in an HMM\n",
"from ipywidgets import widgets\n",
"from IPython.display import YouTubeVideo\n",
"from IPython.display import IFrame\n",
"from IPython.display import display\n",
"\n",
"\n",
"class PlayVideo(IFrame):\n",
" def __init__(self, id, source, page=1, width=400, height=300, **kwargs):\n",
" self.id = id\n",
" if source == 'Bilibili':\n",
" src = f'https://player.bilibili.com/player.html?bvid={id}&page={page}'\n",
" elif source == 'Osf':\n",
" src = f'https://mfr.ca-1.osf.io/render?url=https://osf.io/download/{id}/?direct%26mode=render'\n",
" super(PlayVideo, self).__init__(src, width, height, **kwargs)\n",
"\n",
"\n",
"def display_videos(video_ids, W=400, H=300, fs=1):\n",
" tab_contents = []\n",
" for i, video_id in enumerate(video_ids):\n",
" out = widgets.Output()\n",
" with out:\n",
" if video_ids[i][0] == 'Youtube':\n",
" video = YouTubeVideo(id=video_ids[i][1], width=W,\n",
" height=H, fs=fs, rel=0)\n",
" print(f'Video available at https://youtube.com/watch?v={video.id}')\n",
" else:\n",
" video = PlayVideo(id=video_ids[i][1], source=video_ids[i][0], width=W,\n",
" height=H, fs=fs, autoplay=False)\n",
" if video_ids[i][0] == 'Bilibili':\n",
" print(f'Video available at https://www.bilibili.com/video/{video.id}')\n",
" elif video_ids[i][0] == 'Osf':\n",
" print(f'Video available at https://osf.io/{video.id}')\n",
" display(video)\n",
" tab_contents.append(out)\n",
" return tab_contents\n",
"\n",
"\n",
"video_ids = [('Youtube', 'fErhvxE9SHs'), ('Bilibili', 'BV17f4y1571y')]\n",
"tab_contents = display_videos(video_ids, W=730, H=410)\n",
"tabs = widgets.Tab()\n",
"tabs.children = tab_contents\n",
"for i in range(len(tab_contents)):\n",
" tabs.set_title(i, video_ids[i][0])\n",
"display(tabs)"
]
},
{
"cell_type": "markdown",
"metadata": {},
"source": [
"## Submit your feedback\n"
]
},
{
"cell_type": "code",
"execution_count": null,
"metadata": {
"cellView": "form",
"execution": {},
"tags": [
"hide-input"
]
},
"outputs": [],
"source": [
"# @title Submit your feedback\n",
"content_review(f\"{feedback_prefix}_Forward_inference_in_an_HMM_Video\")"
]
},
{
"cell_type": "markdown",
"metadata": {
"execution": {}
},
"source": [
"### Coding Exercise 3.1: Forward inference of HMM\n",
"\n",
"As a recursive algorithm, let's assume we already have yesterday's posterior from time $t-1$: $p(s_{t-1}|m_{1:t-1})$. When the new data $m_{t}$ comes in, the algorithm performs the following steps:\n",
"\n",
"* **Predict**: transform yesterday's posterior over $s_{t-1}$ into today's prior over $s_t$ using the transition matrix $D$:\n",
"\n",
"\\begin{equation}\n",
"\\text{today's prior}=p(s_t|m_{1:t-1})= p(s_{t-1}|m_{1:t-1}) D\n",
"\\end{equation}\n",
"\n",
"* **Update**: Incorporate measurement $m_t$ to calculate the posterior $p(s_t|m_{0:t})$\n",
"\n",
"\\begin{equation}\n",
"\\text{posterior} \\propto \\text{prior}\\cdot \\text{likelihood}=p(m_t|s_t)p(s_t|m_{0:t-1})\n",
"\\end{equation}\n",
"\n",
"In this exercise, you will:\n",
"\n",
"* STEP 1: Complete the code in function `markov_forward` to calculate the predictive marginal distribution at next time step\n",
"\n",
"* STEP 2: Complete the code in function `one_step_update` to combine predictive probabilities and data likelihood into a new posterior\n",
" * Hint: We have provided a function to calculate the likelihood of $m_t$ under the two possible states: `compute_likelihood(model,M_t)`.\n",
"\n",
"* STEP 3: Using code we provide, plot the posterior and compare with the true values\n",
"\n",
"The complete forward inference is implemented in `simulate_forward_inference` which just calls `one_step_update` recursively."
]
},
{
"cell_type": "markdown",
"metadata": {},
"source": [
" Execute to enable helper functions `compute_likelihood` and `simulate_forward_inference`\n"
]
},
{
"cell_type": "code",
"execution_count": null,
"metadata": {
"cellView": "form",
"execution": {},
"tags": [
"hide-input"
]
},
"outputs": [],
"source": [
"# @markdown Execute to enable helper functions `compute_likelihood` and `simulate_forward_inference`\n",
"\n",
"def compute_likelihood(model, M):\n",
" \"\"\"\n",
" Calculate likelihood of seeing data `M` for all measurement models\n",
"\n",
" Args:\n",
" model (GaussianHMM1D): HMM\n",
" M (float or numpy vector)\n",
"\n",
" Returns:\n",
" L (numpy vector or matrix): the likelihood\n",
" \"\"\"\n",
" rv0 = stats.norm(model.means[0], np.sqrt(model.vars[0]))\n",
" rv1 = stats.norm(model.means[1], np.sqrt(model.vars[1]))\n",
" L = np.stack([rv0.pdf(M), rv1.pdf(M)],axis=0)\n",
" if L.size==2:\n",
" L = L.flatten()\n",
" return L\n",
"\n",
"\n",
"def simulate_forward_inference(model, T, data=None):\n",
" \"\"\"\n",
" Given HMM `model`, calculate posterior marginal predictions of x_t for T-1 time steps ahead based on\n",
" evidence `data`. If `data` is not give, generate a sequence of measurements from first component.\n",
"\n",
" Args:\n",
" model (GaussianHMM instance): the HMM\n",
" T (int): length of returned array\n",
"\n",
" Returns:\n",
" predictive_state1: predictive probabilities in first state w.r.t no evidence\n",
" posterior_state1: posterior probabilities in first state w.r.t evidence\n",
" \"\"\"\n",
"\n",
" # First re-calculate hte predictive probabilities without evidence\n",
" # predictive_probs = simulate_prediction_only(model, T)\n",
" predictive_probs = np.zeros((T,2))\n",
" likelihoods = np.zeros((T,2))\n",
" posterior_probs = np.zeros((T, 2))\n",
" # Generate an measurement trajectory condtioned on that latent state x is always 1\n",
" if data is not None:\n",
" M = data\n",
" else:\n",
" M = np.random.normal(model.means[0], np.sqrt(model.vars[0]), (T,))\n",
"\n",
" # Calculate marginal for each latent state x_t\n",
" predictive_probs[0,:] = model.startprob\n",
" likelihoods[0,:] = compute_likelihood(model, M[[0]])\n",
" posterior = predictive_probs[0,:] * likelihoods[0,:]\n",
" posterior /= np.sum(posterior)\n",
" posterior_probs[0,:] = posterior\n",
"\n",
" for t in range(1, T):\n",
" prediction, likelihood, posterior = one_step_update(model, posterior_probs[t-1], M[[t]])\n",
" # normalize and add to the list\n",
" posterior /= np.sum(posterior)\n",
" predictive_probs[t,:] = prediction\n",
" likelihoods[t,:] = likelihood\n",
" posterior_probs[t,:] = posterior\n",
" return predictive_probs, likelihoods, posterior_probs\n",
"\n",
"help(compute_likelihood)\n",
"help(simulate_forward_inference)"
]
},
{
"cell_type": "markdown",
"metadata": {
"colab_type": "text",
"execution": {}
},
"source": [
"```python\n",
"def markov_forward(p0, D):\n",
" \"\"\"Calculate the forward predictive distribution in a discrete Markov chain\n",
"\n",
" Args:\n",
" p0 (numpy vector): a discrete probability vector\n",
" D (numpy matrix): the transition matrix, D[i,j] means the prob. to\n",
" switch FROM i TO j\n",
"\n",
" Returns:\n",
" p1 (numpy vector): the predictive probabilities in next time step\n",
" \"\"\"\n",
" ##############################################################################\n",
" # Insert your code here to:\n",
" # 1. Calculate the predicted probabilities at next time step using the\n",
" # probabilities at current time and the transition matrix\n",
" raise NotImplementedError(\"`markov_forward` is incomplete\")\n",
" ##############################################################################\n",
"\n",
" # Calculate predictive probabilities (prior)\n",
" p1 = ...\n",
"\n",
" return p1\n",
"\n",
"def one_step_update(model, posterior_tm1, M_t):\n",
" \"\"\"Given a HMM model, calculate the one-time-step updates to the posterior.\n",
" Args:\n",
" model (GaussianHMM1D instance): the HMM\n",
" posterior_tm1 (numpy vector): Posterior at `t-1`\n",
" M_t (numpy array): measurement at `t`\n",
"\n",
" Returns:\n",
" posterior_t (numpy array): Posterior at `t`\n",
" \"\"\"\n",
" ##############################################################################\n",
" # Insert your code here to:\n",
" # 1. Call function `markov_forward` to calculate the prior for next time\n",
" # step\n",
" # 2. Calculate likelihood of seeing current data `M_t` under both states\n",
" # as a vector.\n",
" # 3. Calculate the posterior which is proportional to\n",
" # likelihood x prediction elementwise,\n",
" # 4. Don't forget to normalize\n",
" raise NotImplementedError(\"`one_step_update` is incomplete\")\n",
" ##############################################################################\n",
"\n",
" # Calculate predictive probabilities (prior)\n",
" prediction = markov_forward(...)\n",
"\n",
" # Get the likelihood\n",
" likelihood = compute_likelihood(...)\n",
"\n",
" # Calculate posterior\n",
" posterior_t = ...\n",
"\n",
" # Normalize\n",
" posterior_t /= ...\n",
"\n",
" return prediction, likelihood, posterior_t\n",
"\n",
"\n",
"# Set random seed\n",
"np.random.seed(12)\n",
"\n",
"# Set parameters\n",
"switch_prob = 0.4\n",
"noise_level = .4\n",
"t = 75\n",
"\n",
"# Create and sample from model\n",
"model = create_HMM(switch_prob = switch_prob,\n",
" noise_level = noise_level,\n",
" startprob=[0.5, 0.5])\n",
"\n",
"measurements, states = sample(model, nstep)\n",
"\n",
"# Infer state sequence\n",
"predictive_probs, likelihoods, posterior_probs = simulate_forward_inference(model, nstep,\n",
" measurements)\n",
"states_inferred = np.asarray(posterior_probs[:,0] <= 0.5, dtype=int)\n",
"\n",
"# Visualize\n",
"plot_forward_inference(\n",
" model, states, measurements, states_inferred,\n",
" predictive_probs, likelihoods, posterior_probs,t=t, flag_m = 0\n",
" )\n",
"\n",
"```"
]
},
{
"cell_type": "code",
"execution_count": null,
"metadata": {
"execution": {}
},
"outputs": [],
"source": [
"# to_remove solution\n",
"\n",
"def markov_forward(p0, D):\n",
" \"\"\"Calculate the forward predictive distribution in a discrete Markov chain\n",
"\n",
" Args:\n",
" p0 (numpy vector): a discrete probability vector\n",
" D (numpy matrix): the transition matrix, D[i,j] means the prob. to\n",
" switch FROM i TO j\n",
"\n",
" Returns:\n",
" p1 (numpy vector): the predictive probabilities in next time step\n",
" \"\"\"\n",
"\n",
" # Calculate predictive probabilities (prior)\n",
" p1 = p0 @ D\n",
"\n",
" return p1\n",
"\n",
"def one_step_update(model, posterior_tm1, M_t):\n",
" \"\"\"Given a HMM model, calculate the one-time-step updates to the posterior.\n",
" Args:\n",
" model (GaussianHMM1D instance): the HMM\n",
" posterior_tm1 (numpy vector): Posterior at `t-1`\n",
" M_t (numpy array): measurements at `t`\n",
" Returns:\n",
" posterior_t (numpy array): Posterior at `t`\n",
" \"\"\"\n",
" # Calculate predictive probabilities (prior)\n",
" prediction = markov_forward(posterior_tm1, model.transmat)\n",
"\n",
" # Get the likelihood\n",
" likelihood = compute_likelihood(model, M_t)\n",
"\n",
" # Calculate posterior\n",
" posterior_t = prediction * likelihood\n",
"\n",
" # Normalize\n",
" posterior_t /= np.sum(posterior_t)\n",
"\n",
" return prediction, likelihood, posterior_t\n",
"\n",
"\n",
"# Set random seed\n",
"np.random.seed(12)\n",
"\n",
"# Set parameters\n",
"switch_prob = 0.4\n",
"noise_level = .4\n",
"t = 75\n",
"\n",
"# Create and sample from model\n",
"model = create_HMM(switch_prob = switch_prob,\n",
" noise_level = noise_level,\n",
" startprob=[0.5, 0.5])\n",
"\n",
"measurements, states = sample(model, nstep)\n",
"\n",
"# Infer state sequence\n",
"predictive_probs, likelihoods, posterior_probs = simulate_forward_inference(model, nstep,\n",
" measurements)\n",
"states_inferred = np.asarray(posterior_probs[:,0] <= 0.5, dtype=int)\n",
"\n",
"# Visualize\n",
"with plt.xkcd():\n",
" plot_forward_inference(\n",
" model, states, measurements, states_inferred,\n",
" predictive_probs, likelihoods, posterior_probs,t=t, flag_m = 0\n",
" )"
]
},
{
"cell_type": "markdown",
"metadata": {},
"source": [
"#### Submit your feedback\n"
]
},
{
"cell_type": "code",
"execution_count": null,
"metadata": {
"cellView": "form",
"execution": {},
"tags": [
"hide-input"
]
},
"outputs": [],
"source": [
"# @title Submit your feedback\n",
"content_review(f\"{feedback_prefix}_Forward_inference_of_HMM_Exercise\")"
]
},
{
"cell_type": "markdown",
"metadata": {
"execution": {}
},
"source": [
"## Interactive Demo 3.2: Forward inference in binary HMM\n",
"\n",
"Now visualize your inference algorithm. Play with the sliders and checkboxes to help you gain intuition.\n",
"\n",
"* Use the sliders `switch_prob` and `log10_noise_level` to change the switching probability and measurement noise level.\n",
"\n",
"* Use the slider `t` to view prediction (prior) probabilities, likelihood, and posteriors at different times.\n",
"\n",
"When does the inference make a mistake? For example, set `switch_prob=0.1`, `log_10_noise_level=-0.2`, and take a look at the probabilities at time `t=2`."
]
},
{
"cell_type": "markdown",
"metadata": {},
"source": [
" Execute this cell to enable the widget\n"
]
},
{
"cell_type": "code",
"execution_count": null,
"metadata": {
"cellView": "form",
"execution": {},
"tags": [
"hide-input"
]
},
"outputs": [],
"source": [
"# @markdown Execute this cell to enable the widget\n",
"\n",
"nstep = 100\n",
"\n",
"@widgets.interact\n",
"def plot_forward_inference_widget(\n",
" switch_prob=widgets.FloatSlider(min=0.0, max=1.0, step=0.01, value=0.05),\n",
" log10_noise_level=widgets.FloatSlider(min=-1., max=1., step=.01, value=0.1),\n",
" t=widgets.IntSlider(min=0, max=nstep-1, step=1, value=nstep//2),\n",
" flag_d=widgets.Checkbox(value=True, description='measurements',\n",
" disabled=False, indent=False),\n",
" flag_pre=widgets.Checkbox(value=True, description='todays prior',\n",
" disabled=False, indent=False),\n",
" flag_like=widgets.Checkbox(value=True, description='likelihood',\n",
" disabled=False, indent=False),\n",
" flag_post=widgets.Checkbox(value=True, description='posterior',\n",
" disabled=False, indent=False),\n",
" ):\n",
"\n",
" np.random.seed(102)\n",
"\n",
" # global model, measurements, states, states_inferred, predictive_probs, likelihoods, posterior_probs\n",
" model = create_HMM(switch_prob=switch_prob,\n",
" noise_level=10.**log10_noise_level,\n",
" startprob=[0.5, 0.5])\n",
" measurements, states = sample(model, nstep)\n",
"\n",
" # Infer state sequence\n",
" predictive_probs, likelihoods, posterior_probs = simulate_forward_inference(model, nstep,\n",
" measurements)\n",
" states_inferred = np.asarray(posterior_probs[:,0] <= 0.5, dtype=int)\n",
"\n",
" fig = plot_forward_inference(\n",
" model, states, measurements, states_inferred,\n",
" predictive_probs, likelihoods, posterior_probs,t=t,\n",
" flag_m=0,\n",
" flag_d=flag_d,flag_pre=flag_pre,flag_like=flag_like,flag_post=flag_post\n",
" )\n",
" plt.show(fig)"
]
},
{
"cell_type": "markdown",
"metadata": {},
"source": [
"### Submit your feedback\n"
]
},
{
"cell_type": "code",
"execution_count": null,
"metadata": {
"cellView": "form",
"execution": {},
"tags": [
"hide-input"
]
},
"outputs": [],
"source": [
"# @title Submit your feedback\n",
"content_review(f\"{feedback_prefix}_Forward_inference_of_HMM_Interactive_Demo\")"
]
},
{
"cell_type": "markdown",
"metadata": {},
"source": [
"### Video 7: Section 3 Exercise Discussion\n"
]
},
{
"cell_type": "code",
"execution_count": null,
"metadata": {
"cellView": "form",
"execution": {},
"tags": [
"remove-input"
]
},
"outputs": [],
"source": [
"# @title Video 7: Section 3 Exercise Discussion\n",
"from ipywidgets import widgets\n",
"from IPython.display import YouTubeVideo\n",
"from IPython.display import IFrame\n",
"from IPython.display import display\n",
"\n",
"\n",
"class PlayVideo(IFrame):\n",
" def __init__(self, id, source, page=1, width=400, height=300, **kwargs):\n",
" self.id = id\n",
" if source == 'Bilibili':\n",
" src = f'https://player.bilibili.com/player.html?bvid={id}&page={page}'\n",
" elif source == 'Osf':\n",
" src = f'https://mfr.ca-1.osf.io/render?url=https://osf.io/download/{id}/?direct%26mode=render'\n",
" super(PlayVideo, self).__init__(src, width, height, **kwargs)\n",
"\n",
"\n",
"def display_videos(video_ids, W=400, H=300, fs=1):\n",
" tab_contents = []\n",
" for i, video_id in enumerate(video_ids):\n",
" out = widgets.Output()\n",
" with out:\n",
" if video_ids[i][0] == 'Youtube':\n",
" video = YouTubeVideo(id=video_ids[i][1], width=W,\n",
" height=H, fs=fs, rel=0)\n",
" print(f'Video available at https://youtube.com/watch?v={video.id}')\n",
" else:\n",
" video = PlayVideo(id=video_ids[i][1], source=video_ids[i][0], width=W,\n",
" height=H, fs=fs, autoplay=False)\n",
" if video_ids[i][0] == 'Bilibili':\n",
" print(f'Video available at https://www.bilibili.com/video/{video.id}')\n",
" elif video_ids[i][0] == 'Osf':\n",
" print(f'Video available at https://osf.io/{video.id}')\n",
" display(video)\n",
" tab_contents.append(out)\n",
" return tab_contents\n",
"\n",
"\n",
"video_ids = [('Youtube', 'CNrjxNedqV0'), ('Bilibili', 'BV1EM4y1T7cB')]\n",
"tab_contents = display_videos(video_ids, W=730, H=410)\n",
"tabs = widgets.Tab()\n",
"tabs.children = tab_contents\n",
"for i in range(len(tab_contents)):\n",
" tabs.set_title(i, video_ids[i][0])\n",
"display(tabs)"
]
},
{
"cell_type": "markdown",
"metadata": {},
"source": [
"### Submit your feedback\n"
]
},
{
"cell_type": "code",
"execution_count": null,
"metadata": {
"cellView": "form",
"execution": {},
"tags": [
"hide-input"
]
},
"outputs": [],
"source": [
"# @title Submit your feedback\n",
"content_review(f\"{feedback_prefix}_Section_3_Exercise_Discussion_Video\")"
]
},
{
"cell_type": "markdown",
"metadata": {
"execution": {}
},
"source": [
"---\n",
"# Summary\n",
"\n",
"*Estimated timing of tutorial: 1 hour, 5 minutes*\n",
"\n",
"In this tutorial, you\n",
"\n",
"* Simulated the dynamics of the hidden state in a Hidden Markov model and visualized the measured data (Section 1)\n",
"* Explored how uncertainty in a future hidden state changes based on the probabilities of switching between states (Section 2)\n",
"* Estimated hidden states from the measurements using forward inference, connected this to Bayesian ideas, and explored the effects of noise and transition matrix probabilities on this process (Section 3)"
]
}
],
"metadata": {
"@webio": {
"lastCommId": null,
"lastKernelId": null
},
"colab": {
"collapsed_sections": [],
"include_colab_link": true,
"name": "W3D2_Tutorial2",
"provenance": [],
"toc_visible": true
},
"kernel": {
"display_name": "Python 3",
"language": "python",
"name": "python3"
},
"kernelspec": {
"display_name": "Python 3",
"name": "python3"
},
"language_info": {
"codemirror_mode": {
"name": "ipython",
"version": 3
},
"file_extension": ".py",
"mimetype": "text/x-python",
"name": "python",
"nbconvert_exporter": "python",
"pygments_lexer": "ipython3",
"version": "3.9.17"
},
"toc": {
"base_numbering": 1,
"nav_menu": {},
"number_sections": false,
"sideBar": true,
"skip_h1_title": true,
"title_cell": "Table of Contents",
"title_sidebar": "Contents",
"toc_cell": false,
"toc_position": {},
"toc_section_display": true,
"toc_window_display": true
}
},
"nbformat": 4,
"nbformat_minor": 0
}