Created
September 12, 2018 17:48
-
-
Save sadatnfs/15c596272e480676842a4d8d7964a4d0 to your computer and use it in GitHub Desktop.
Fitting model in TFP with fixed effect coefficient
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
{ | |
"cells": [ | |
{ | |
"cell_type": "code", | |
"execution_count": 1, | |
"metadata": {}, | |
"outputs": [], | |
"source": [ | |
"%%capture \n", | |
"\n", | |
"%matplotlib inline\n", | |
"\n", | |
"import IPython\n", | |
"import matplotlib.pyplot as plt\n", | |
"import numpy as np\n", | |
"import pandas as pd\n", | |
"import requests\n", | |
"import tensorflow as tf\n", | |
"import tensorflow_probability as tfp\n", | |
"import warnings\n", | |
"\n", | |
"from tensorflow_probability import edward2 as ed\n", | |
"tfd = tfp.distributions\n", | |
"\n", | |
"from keras.constraints import non_neg\n", | |
"\n", | |
"import statsmodels.api as sm\n", | |
"import statsmodels.formula.api as smf\n", | |
" \n", | |
"plt.style.use('ggplot')" | |
] | |
}, | |
{ | |
"cell_type": "code", | |
"execution_count": 2, | |
"metadata": {}, | |
"outputs": [ | |
{ | |
"data": { | |
"text/html": [ | |
"<div>\n", | |
"<style scoped>\n", | |
" .dataframe tbody tr th:only-of-type {\n", | |
" vertical-align: middle;\n", | |
" }\n", | |
"\n", | |
" .dataframe tbody tr th {\n", | |
" vertical-align: top;\n", | |
" }\n", | |
"\n", | |
" .dataframe thead th {\n", | |
" text-align: right;\n", | |
" }\n", | |
"</style>\n", | |
"<table border=\"1\" class=\"dataframe\">\n", | |
" <thead>\n", | |
" <tr style=\"text-align: right;\">\n", | |
" <th></th>\n", | |
" <th>iso3</th>\n", | |
" <th>year</th>\n", | |
" <th>ln_gdppc</th>\n", | |
" <th>ln_TFR</th>\n", | |
" <th>ln_pop</th>\n", | |
" <th>intercept</th>\n", | |
" </tr>\n", | |
" </thead>\n", | |
" <tbody>\n", | |
" <tr>\n", | |
" <th>0</th>\n", | |
" <td>0</td>\n", | |
" <td>1960</td>\n", | |
" <td>7.626012</td>\n", | |
" <td>2.008214</td>\n", | |
" <td>16.012330</td>\n", | |
" <td>1.0</td>\n", | |
" </tr>\n", | |
" <tr>\n", | |
" <th>1</th>\n", | |
" <td>0</td>\n", | |
" <td>1961</td>\n", | |
" <td>7.613911</td>\n", | |
" <td>2.008214</td>\n", | |
" <td>16.031095</td>\n", | |
" <td>1.0</td>\n", | |
" </tr>\n", | |
" <tr>\n", | |
" <th>2</th>\n", | |
" <td>0</td>\n", | |
" <td>1962</td>\n", | |
" <td>7.610066</td>\n", | |
" <td>2.008214</td>\n", | |
" <td>16.050445</td>\n", | |
" <td>1.0</td>\n", | |
" </tr>\n", | |
" <tr>\n", | |
" <th>3</th>\n", | |
" <td>0</td>\n", | |
" <td>1963</td>\n", | |
" <td>7.607401</td>\n", | |
" <td>2.008214</td>\n", | |
" <td>16.070370</td>\n", | |
" <td>1.0</td>\n", | |
" </tr>\n", | |
" <tr>\n", | |
" <th>4</th>\n", | |
" <td>0</td>\n", | |
" <td>1964</td>\n", | |
" <td>7.603515</td>\n", | |
" <td>2.008214</td>\n", | |
" <td>16.090864</td>\n", | |
" <td>1.0</td>\n", | |
" </tr>\n", | |
" </tbody>\n", | |
"</table>\n", | |
"</div>" | |
], | |
"text/plain": [ | |
" iso3 year ln_gdppc ln_TFR ln_pop intercept\n", | |
"0 0 1960 7.626012 2.008214 16.012330 1.0\n", | |
"1 0 1961 7.613911 2.008214 16.031095 1.0\n", | |
"2 0 1962 7.610066 2.008214 16.050445 1.0\n", | |
"3 0 1963 7.607401 2.008214 16.070370 1.0\n", | |
"4 0 1964 7.603515 2.008214 16.090864 1.0" | |
] | |
}, | |
"execution_count": 2, | |
"metadata": {}, | |
"output_type": "execute_result" | |
} | |
], | |
"source": [ | |
"## Get GDP data for testing\n", | |
"data = pd.read_csv('/home/j/Project/IRH/Forecasting/gdp/data/RT_2018_GDP_use.csv')\n", | |
"\n", | |
"## Prep data\n", | |
"data = data[['iso3', 'year', 'ln_gdppc', 'ln_TFR', 'ln_pop']]\n", | |
"data['intercept'] = 1.\n", | |
"data = data.dropna()\n", | |
"\n", | |
"# Remap categories to start from 0 and end at max(category).\n", | |
"data['iso3'] = data['iso3'].astype('category').cat.codes\n", | |
"\n", | |
"## Number of REs\n", | |
"n_res = max(data.iso3) + 1\n", | |
"\n", | |
"data.head()" | |
] | |
}, | |
{ | |
"cell_type": "markdown", | |
"metadata": {}, | |
"source": [ | |
"### Model:\n", | |
"$$ ln(GDPpc) = \\alpha + \\alpha_i + \\beta \\ln(TFR) + \\epsilon_{i,t} $$ \n", | |
"$$ \\epsilon_{i,t} \\sim \\mathcal{N}(0, \\sigma^2) $$ \n", | |
"$$ \\alpha_i \\sim \\mathcal{N}(0, \\sigma_a^2) $$\n" | |
] | |
}, | |
{ | |
"cell_type": "code", | |
"execution_count": 3, | |
"metadata": { | |
"scrolled": true | |
}, | |
"outputs": [ | |
{ | |
"data": { | |
"text/html": [ | |
"<table class=\"simpletable\">\n", | |
"<tr>\n", | |
" <td>Model:</td> <td>MixedLM</td> <td>Dependent Variable:</td> <td>ln_gdppc</td> \n", | |
"</tr>\n", | |
"<tr>\n", | |
" <td>No. Observations:</td> <td>11700</td> <td>Method:</td> <td>REML</td> \n", | |
"</tr>\n", | |
"<tr>\n", | |
" <td>No. Groups:</td> <td>195</td> <td>Scale:</td> <td>0.1200</td> \n", | |
"</tr>\n", | |
"<tr>\n", | |
" <td>Min. group size:</td> <td>60</td> <td>Likelihood:</td> <td>-4798.2007</td>\n", | |
"</tr>\n", | |
"<tr>\n", | |
" <td>Max. group size:</td> <td>60</td> <td>Converged:</td> <td>Yes</td> \n", | |
"</tr>\n", | |
"<tr>\n", | |
" <td>Mean group size:</td> <td>60.0</td> <td></td> <td></td> \n", | |
"</tr>\n", | |
"</table>\n", | |
"<table class=\"simpletable\">\n", | |
"<tr>\n", | |
" <td></td> <th>Coef.</th> <th>Std.Err.</th> <th>z</th> <th>P>|z|</th> <th>[0.025</th> <th>0.975]</th>\n", | |
"</tr>\n", | |
"<tr>\n", | |
" <th>Intercept</th> <td>9.929</td> <td>0.070</td> <td>142.555</td> <td>0.000</td> <td>9.793</td> <td>10.066</td>\n", | |
"</tr>\n", | |
"<tr>\n", | |
" <th>ln_TFR</th> <td>-0.876</td> <td>0.011</td> <td>-82.073</td> <td>0.000</td> <td>-0.897</td> <td>-0.855</td>\n", | |
"</tr>\n", | |
"<tr>\n", | |
" <th>Group Var</th> <td>0.909</td> <td>0.270</td> <td></td> <td></td> <td></td> <td></td> \n", | |
"</tr>\n", | |
"</table>" | |
], | |
"text/plain": [ | |
"<class 'statsmodels.iolib.summary2.Summary'>\n", | |
"\"\"\"\n", | |
" Mixed Linear Model Regression Results\n", | |
"========================================================\n", | |
"Model: MixedLM Dependent Variable: ln_gdppc \n", | |
"No. Observations: 11700 Method: REML \n", | |
"No. Groups: 195 Scale: 0.1200 \n", | |
"Min. group size: 60 Likelihood: -4798.2007\n", | |
"Max. group size: 60 Converged: Yes \n", | |
"Mean group size: 60.0 \n", | |
"--------------------------------------------------------\n", | |
" Coef. Std.Err. z P>|z| [0.025 0.975]\n", | |
"--------------------------------------------------------\n", | |
"Intercept 9.929 0.070 142.555 0.000 9.793 10.066\n", | |
"ln_TFR -0.876 0.011 -82.073 0.000 -0.897 -0.855\n", | |
"Group Var 0.909 0.270 \n", | |
"========================================================\n", | |
"\n", | |
"\"\"\"" | |
] | |
}, | |
"execution_count": 3, | |
"metadata": {}, | |
"output_type": "execute_result" | |
} | |
], | |
"source": [ | |
"## Fit in StatsModels first\n", | |
"md = smf.mixedlm(\"ln_gdppc ~ 1 + ln_TFR\", data, groups=data[\"iso3\"])\n", | |
"sm_fit = md.fit() \n", | |
"sm_fit.summary()" | |
] | |
}, | |
{ | |
"cell_type": "code", | |
"execution_count": 4, | |
"metadata": {}, | |
"outputs": [], | |
"source": [ | |
"## Get the data for 'features' and 'labels' (xvars and yvar)\n", | |
"get_value = lambda dataframe, key, dtype: dataframe[key].values.astype(dtype)\n", | |
"\n", | |
"## We get the country codes as integers and then attach on the fixed effects\n", | |
"features_train = {\n", | |
" k: get_value(data, key=k, dtype=np.int32)\n", | |
" for k in ['iso3']}\n", | |
"features_train.update({\n", | |
" k: get_value(data, key=k, dtype=np.float64)\n", | |
" for k in ['intercept', 'ln_TFR']})\n", | |
"\n", | |
"## Get yvar\n", | |
"labels_train = get_value(data, key='ln_gdppc', dtype=np.float64)" | |
] | |
}, | |
{ | |
"cell_type": "markdown", | |
"metadata": {}, | |
"source": [ | |
"### Define our mixed effects model, returning the prediction for fixed TFRs" | |
] | |
}, | |
{ | |
"cell_type": "code", | |
"execution_count": 5, | |
"metadata": {}, | |
"outputs": [], | |
"source": [ | |
"## Keeping TFR fixed\n", | |
"def linear_mixed_effects_model(features):\n", | |
" \n", | |
" # Set up fixed effects and other parameters.\n", | |
" intercept = tf.get_variable(\"intercept\", []) # alpha in eq\n", | |
" \n", | |
" ## Constraint is not needed, but can be done as such\n", | |
" # beta in eq \n", | |
" effect_ln_TFR = tf.get_variable(\"effect_ln_TFR\", [])\n", | |
" \n", | |
" ## The two variances (exp to force positive estimate)\n", | |
" stddev_iso3 = tf.exp(tf.get_variable(\"stddev_iso3\", [])) \n", | |
" model_stddev = tf.exp(tf.get_variable(\"model_stddev\", []))\n", | |
" \n", | |
" ## Set up random effects.\n", | |
" effect_iso3 = ed.MultivariateNormalDiag( \n", | |
" loc=tf.zeros(n_res),\n", | |
" scale_identity_multiplier=stddev_iso3,\n", | |
" name=\"effect_iso3\")\n", | |
"\n", | |
" ## Likelihood \n", | |
" ln_gdppc = ed.Normal(\n", | |
" loc=((intercept * features['intercept']) +\n", | |
" (effect_ln_TFR * features[\"ln_TFR\"]) + \n", | |
" tf.gather(effect_iso3, features[\"iso3\"])),\n", | |
" scale=model_stddev, name=\"ln_gdppc\")\n", | |
" return ln_gdppc" | |
] | |
}, | |
{ | |
"cell_type": "code", | |
"execution_count": 6, | |
"metadata": {}, | |
"outputs": [], | |
"source": [ | |
"# Unnormalized target density as a function of states of REs.\n", | |
"def target_log_prob_fn(effect_iso3): \n", | |
" return log_joint( \n", | |
" features=features_train,\n", | |
" effect_iso3=effect_iso3, \n", | |
" ln_gdppc=labels_train)" | |
] | |
}, | |
{ | |
"cell_type": "markdown", | |
"metadata": {}, | |
"source": [ | |
"### Set up the graph, and the backend of the E-M algorithm (fixed TFR)" | |
] | |
}, | |
{ | |
"cell_type": "code", | |
"execution_count": 7, | |
"metadata": {}, | |
"outputs": [], | |
"source": [ | |
"tf.reset_default_graph()\n", | |
"model_template = tf.make_template(\"model\", linear_mixed_effects_model)\n", | |
"\n", | |
"## Make joint LL\n", | |
"log_joint = ed.make_log_joint_fn(model_template)\n", | |
"\n", | |
"## Set up E-step (MCMC) for RE vars\n", | |
"effect_iso3 = tf.get_variable(\"effect_iso3\", [n_res], trainable=False)\n", | |
"\n", | |
"## Global step variable holder\n", | |
"global_step = tf.Variable(0, trainable=False, name=\"global_step\")\n", | |
"\n", | |
"## Set up MCMC object\n", | |
"hmc = tfp.mcmc.HamiltonianMonteCarlo(\n", | |
" target_log_prob_fn=target_log_prob_fn,\n", | |
" step_size=0.02,\n", | |
" num_leapfrog_steps=2)\n", | |
"\n", | |
"## RE state to update\n", | |
"current_state = [effect_iso3]\n", | |
"\n", | |
"## Update state\n", | |
"with warnings.catch_warnings():\n", | |
" warnings.simplefilter(\"ignore\")\n", | |
" next_state, kernel_results = hmc.one_step(current_state=current_state,\n", | |
" previous_kernel_results=hmc.bootstrap_results(current_state))\n", | |
"\n", | |
"## Update Expectation\n", | |
"expectation_update = tf.group(effect_iso3.assign(next_state[0]))\n", | |
"\n", | |
"# Set up M-step (gradient descent), using Adam Optimizer\n", | |
"with tf.control_dependencies([expectation_update]):\n", | |
" loss = -target_log_prob_fn(effect_iso3) \n", | |
" learning_rate = tf.train.exponential_decay(learning_rate=0.01, \n", | |
" global_step=global_step,\n", | |
" decay_steps=20, \n", | |
" decay_rate=0.90, staircase=True)\n", | |
" optimizer = tf.train.AdamOptimizer(learning_rate=learning_rate)\n", | |
" minimization_update = optimizer.minimize(loss)" | |
] | |
}, | |
{ | |
"cell_type": "markdown", | |
"metadata": {}, | |
"source": [ | |
"### Fit the first run (fixed TFR)" | |
] | |
}, | |
{ | |
"cell_type": "code", | |
"execution_count": 8, | |
"metadata": { | |
"scrolled": false | |
}, | |
"outputs": [ | |
{ | |
"name": "stdout", | |
"output_type": "stream", | |
"text": [ | |
"Warm-Up Iteration: 0 Acceptance Rate: 1.000\n", | |
"Warm-Up Iteration: 249 Acceptance Rate: 0.880\n", | |
"Iteration: 0 Loss: 114910.953,ln_TFR:-0.557, intercept:-1.116\n", | |
"Iteration: 20 Loss: 28770.760,ln_TFR:-0.044, intercept:-0.431\n", | |
"Iteration: 40 Loss: 16941.445,ln_TFR:-0.141, intercept:-0.050\n", | |
"Iteration: 60 Loss: 11933.211,ln_TFR:-0.419, intercept:0.242\n", | |
"Iteration: 80 Loss: 9291.289,ln_TFR:-0.648, intercept:0.470\n", | |
"CPU times: user 7.85 s, sys: 333 ms, total: 8.18 s\n", | |
"Wall time: 5.87 s\n" | |
] | |
} | |
], | |
"source": [ | |
"%%time\n", | |
"\n", | |
"num_warmup_iters = 250\n", | |
"num_iters = 100\n", | |
"num_accepted = 0\n", | |
"\n", | |
"## Record samples of the fitted params and the loss\n", | |
"effect_iso3_samples = np.zeros([num_iters, n_res])\n", | |
"effect_ln_TFR_samples = np.zeros([num_iters])\n", | |
"effect_int_samples = np.zeros([num_iters]) \n", | |
"loss_history = np.zeros([num_iters])\n", | |
"\n", | |
"## Push fixed effects into scope so that we can record them \n", | |
"## (by default, only the REs get recorded since they're in the state)\n", | |
"with tf.variable_scope('model', reuse=True):\n", | |
" ln_tfr_wghts = tf.get_variable(name='effect_ln_TFR', dtype=np.float32)\n", | |
" int_wghts = tf.get_variable(name='intercept', dtype=np.float32) \n", | |
"\n", | |
"## Start our sesh\n", | |
"with tf.Session() as sess:\n", | |
" sess.run(tf.global_variables_initializer())\n", | |
"\n", | |
" # Run warm-up stage.\n", | |
" for t in range(num_warmup_iters):\n", | |
" _, is_accepted_val = sess.run(\n", | |
" [expectation_update, kernel_results.is_accepted])\n", | |
" num_accepted += is_accepted_val\n", | |
" if t % 250 == 0 or t == num_warmup_iters - 1:\n", | |
" print(\"Warm-Up Iteration: {:>3} Acceptance Rate: {:.3f}\".format(\n", | |
" t, num_accepted / (t + 1)))\n", | |
"\n", | |
" num_accepted = 0 # reset acceptance rate counter\n", | |
"\n", | |
" # Run iterations (no convergence criteria)\n", | |
" for t in range(num_iters):\n", | |
" for _ in range(5): # run 5 MCMC iterations before every joint EM update\n", | |
" _ = sess.run(expectation_update)\n", | |
" [\n", | |
" _,\n", | |
" _,\n", | |
" effect_iso3_val,\n", | |
" ln_tfr_val, \n", | |
" int_val,\n", | |
" is_accepted_val,\n", | |
" loss_val,\n", | |
" ] = sess.run([\n", | |
" expectation_update,\n", | |
" minimization_update,\n", | |
" effect_iso3,\n", | |
" ln_tfr_wghts, \n", | |
" int_wghts,\n", | |
" kernel_results.is_accepted,\n", | |
" loss,\n", | |
" ])\n", | |
" effect_iso3_samples[t, :] = effect_iso3_val \n", | |
" effect_ln_TFR_samples[t] = ln_tfr_val \n", | |
" effect_int_samples[t] = int_val\n", | |
" num_accepted+= is_accepted_val\n", | |
" loss_history[t] = loss_val\n", | |
" if t % 20 == 0:\n", | |
" print(\"Iteration: {} Loss: {:.3f},ln_TFR:{:.3f}, intercept:{:.3f}\".\n", | |
" format(t, loss_val, ln_tfr_val, int_val))" | |
] | |
} | |
], | |
"metadata": { | |
"kernelspec": { | |
"display_name": "Python 3", | |
"language": "python", | |
"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.6.5" | |
} | |
}, | |
"nbformat": 4, | |
"nbformat_minor": 2 | |
} |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment