blob: fa92b05314a2454cca7414f633da90f3fc88daad [file] [log] [blame]
{
"cells": [
{
"cell_type": "markdown",
"metadata": {},
"source": [
"# Hyperband diagonal using MNIST\n",
"\n",
"Implemention of Hyperband https://arxiv.org/pdf/1603.06560.pdf for MPP with a synchronous barrier. Uses the Hyperband schedule but runs it on a diagonal across brackets, instead of one bracket at a time, to be more efficient with cluster resources. \n",
"\n",
"Model architecture based on https://keras.io/examples/mnist_transfer_cnn/ \n",
"\n",
"To load images into tables we use the script called <em>madlib_image_loader.py</em> located at https://github.com/apache/madlib-site/tree/asf-site/community-artifacts/Deep-learning which uses the Python Imaging Library so supports multiple formats http://www.pythonware.com/products/pil/\n",
"\n",
"## Table of contents\n",
"<a href=\"#import_libraries\">1. Import libraries</a>\n",
"\n",
"<a href=\"#load_and_prepare_data\">2. Load and prepare data</a>\n",
"\n",
"<a href=\"#image_preproc\">3. Call image preprocessor</a>\n",
"\n",
"<a href=\"#define_and_load_model\">4. Define and load model architecture</a>\n",
"\n",
"<a href=\"#hyperband\">5. Hyperband diagonal</a>\n",
"\n",
"<a href=\"#plot\">6. Plot results</a>\n",
"\n",
"<a href=\"#print\">7. Print run schedules (display only)</a>"
]
},
{
"cell_type": "code",
"execution_count": 2,
"metadata": {
"scrolled": true
},
"outputs": [
{
"name": "stderr",
"output_type": "stream",
"text": [
"/Users/fmcquillan/anaconda/lib/python2.7/site-packages/IPython/config.py:13: ShimWarning: The `IPython.config` package has been deprecated since IPython 4.0. You should import from traitlets.config instead.\n",
" \"You should import from traitlets.config instead.\", ShimWarning)\n",
"/Users/fmcquillan/anaconda/lib/python2.7/site-packages/IPython/utils/traitlets.py:5: UserWarning: IPython.utils.traitlets has moved to a top-level traitlets package.\n",
" warn(\"IPython.utils.traitlets has moved to a top-level traitlets package.\")\n"
]
}
],
"source": [
"%load_ext sql"
]
},
{
"cell_type": "code",
"execution_count": 4,
"metadata": {},
"outputs": [],
"source": [
"# Greenplum Database 5.x on GCP (PM demo machine) - direct external IP access\n",
"#%sql postgresql://gpadmin@34.67.65.96:5432/madlib\n",
"\n",
"# Greenplum Database 5.x on GCP - via tunnel\n",
"%sql postgresql://gpadmin@localhost:8000/madlib\n",
" \n",
"# PostgreSQL local\n",
"#%sql postgresql://fmcquillan@localhost:5432/madlib\n",
"\n",
"# psycopg2 connection\n",
"import psycopg2 as p2\n",
"#conn = p2.connect('postgresql://fmcquillan@localhost:5432/madlib')\n",
"conn = p2.connect('postgresql://gpadmin@localhost:8000/madlib')\n",
"cur = conn.cursor()"
]
},
{
"cell_type": "code",
"execution_count": 3,
"metadata": {},
"outputs": [
{
"name": "stdout",
"output_type": "stream",
"text": [
"1 rows affected.\n"
]
},
{
"data": {
"text/html": [
"<table>\n",
" <tr>\n",
" <th>version</th>\n",
" </tr>\n",
" <tr>\n",
" <td>MADlib version: 1.17-dev, git revision: rel/v1.16-50-g5abfb79, cmake configuration time: Tue Nov 26 01:00:01 UTC 2019, build type: release, build system: Linux-3.10.0-1062.4.3.el7.x86_64, C compiler: gcc 4.8.5, C++ compiler: g++ 4.8.5</td>\n",
" </tr>\n",
"</table>"
],
"text/plain": [
"[(u'MADlib version: 1.17-dev, git revision: rel/v1.16-50-g5abfb79, cmake configuration time: Tue Nov 26 01:00:01 UTC 2019, build type: release, build system: Linux-3.10.0-1062.4.3.el7.x86_64, C compiler: gcc 4.8.5, C++ compiler: g++ 4.8.5',)]"
]
},
"execution_count": 3,
"metadata": {},
"output_type": "execute_result"
}
],
"source": [
"%sql select madlib.version();\n",
"#%sql select version();"
]
},
{
"cell_type": "markdown",
"metadata": {},
"source": [
"<a id=\"import_libraries\"></a>\n",
"# 1. Import libraries\n",
"From https://keras.io/examples/mnist_transfer_cnn/ import libraries and define some params"
]
},
{
"cell_type": "code",
"execution_count": 5,
"metadata": {},
"outputs": [
{
"name": "stderr",
"output_type": "stream",
"text": [
"Using TensorFlow backend.\n"
]
},
{
"name": "stdout",
"output_type": "stream",
"text": [
"Couldn't import dot_parser, loading of dot files will not be possible.\n"
]
}
],
"source": [
"from __future__ import print_function\n",
"\n",
"import datetime\n",
"import keras\n",
"from keras.datasets import mnist\n",
"from keras.models import Sequential\n",
"from keras.layers import Dense, Dropout, Activation, Flatten\n",
"from keras.layers import Conv2D, MaxPooling2D\n",
"from keras import backend as K\n",
"\n",
"now = datetime.datetime.now\n",
"\n",
"#batch_size = 128\n",
"num_classes = 10\n",
"#epochs = 5\n",
"\n",
"# input image dimensions\n",
"img_rows, img_cols = 28, 28\n",
"# number of convolutional filters to use\n",
"filters = 32\n",
"# size of pooling area for max pooling\n",
"pool_size = 2\n",
"# convolution kernel size\n",
"kernel_size = 3\n",
"\n",
"if K.image_data_format() == 'channels_first':\n",
" input_shape = (1, img_rows, img_cols)\n",
"else:\n",
" input_shape = (img_rows, img_cols, 1)"
]
},
{
"cell_type": "markdown",
"metadata": {},
"source": [
"Others needed in this workbook"
]
},
{
"cell_type": "code",
"execution_count": 6,
"metadata": {},
"outputs": [],
"source": [
"import pandas as pd\n",
"import numpy as np"
]
},
{
"cell_type": "markdown",
"metadata": {},
"source": [
"<a id=\"load_and_prepare_data\"></a>\n",
"# 2. Load and prepare data\n",
"\n",
"First load MNIST data from Keras, consisting of 60,000 28x28 grayscale images of the 10 digits, along with a test set of 10,000 images."
]
},
{
"cell_type": "code",
"execution_count": 7,
"metadata": {},
"outputs": [
{
"name": "stdout",
"output_type": "stream",
"text": [
"(10000, 28, 28)\n",
"(10000, 28, 28, 1)\n"
]
}
],
"source": [
"# the data, split between train and test sets\n",
"(x_train, y_train), (x_test, y_test) = mnist.load_data()\n",
"\n",
"# reshape to match model architecture\n",
"print(x_test.shape)\n",
"x_train = x_train.reshape(len(x_train), *input_shape)\n",
"x_test = x_test.reshape(len(x_test), *input_shape)\n",
"print(x_test.shape)"
]
},
{
"cell_type": "markdown",
"metadata": {},
"source": [
"Load datasets into tables using image loader scripts called <em>madlib_image_loader.py</em> located at https://github.com/apache/madlib-site/tree/asf-site/community-artifacts/Deep-learning"
]
},
{
"cell_type": "code",
"execution_count": 8,
"metadata": {},
"outputs": [],
"source": [
"# MADlib tools directory\n",
"import sys\n",
"import os\n",
"madlib_site_dir = '/Users/fmcquillan/Documents/Product/MADlib/Demos/data'\n",
"sys.path.append(madlib_site_dir)\n",
"\n",
"# Import image loader module\n",
"from madlib_image_loader import ImageLoader, DbCredentials"
]
},
{
"cell_type": "code",
"execution_count": 34,
"metadata": {},
"outputs": [],
"source": [
"# Specify database credentials, for connecting to db\n",
"db_creds = DbCredentials(user='gpadmin',\n",
" host='localhost',\n",
" port='8000',\n",
" password='')"
]
},
{
"cell_type": "code",
"execution_count": 35,
"metadata": {},
"outputs": [],
"source": [
"# Initialize ImageLoader (increase num_workers to run faster)\n",
"iloader = ImageLoader(num_workers=5, db_creds=db_creds)"
]
},
{
"cell_type": "code",
"execution_count": 36,
"metadata": {},
"outputs": [
{
"name": "stdout",
"output_type": "stream",
"text": [
"Done.\n",
"MainProcess: Connected to madlib db.\n",
"Executing: CREATE TABLE train_mnist (id SERIAL, x REAL[], y TEXT)\n",
"CREATE TABLE\n",
"Created table train_mnist in madlib db\n",
"Spawning 5 workers...\n",
"Initializing PoolWorker-11 [pid 34068]\n",
"PoolWorker-11: Created temporary directory /tmp/madlib_RbuQlbqxI5\n",
"Initializing PoolWorker-12 [pid 34069]\n",
"PoolWorker-12: Created temporary directory /tmp/madlib_tEyH9GMFGV\n",
"Initializing PoolWorker-13 [pid 34070]\n",
"PoolWorker-13: Created temporary directory /tmp/madlib_TyYs4viAVD\n",
"Initializing PoolWorker-14 [pid 34071]\n",
"Initializing PoolWorker-15 [pid 34072]\n",
"PoolWorker-14: Created temporary directory /tmp/madlib_KTwnncRsaq\n",
"PoolWorker-15: Created temporary directory /tmp/madlib_jtG9zAC8HU\n",
"PoolWorker-11: Connected to madlib db.\n",
"PoolWorker-13: Connected to madlib db.\n",
"PoolWorker-14: Connected to madlib db.\n",
"PoolWorker-12: Connected to madlib db.\n",
"PoolWorker-15: Connected to madlib db.\n",
"PoolWorker-11: Wrote 1000 images to /tmp/madlib_RbuQlbqxI5/train_mnist0000.tmp\n",
"PoolWorker-14: Wrote 1000 images to /tmp/madlib_KTwnncRsaq/train_mnist0000.tmp\n",
"PoolWorker-13: Wrote 1000 images to /tmp/madlib_TyYs4viAVD/train_mnist0000.tmp\n",
"PoolWorker-12: Wrote 1000 images to /tmp/madlib_tEyH9GMFGV/train_mnist0000.tmp\n",
"PoolWorker-15: Wrote 1000 images to /tmp/madlib_jtG9zAC8HU/train_mnist0000.tmp\n",
"PoolWorker-11: Loaded 1000 images into train_mnist\n",
"PoolWorker-14: Loaded 1000 images into train_mnist\n",
"PoolWorker-13: Loaded 1000 images into train_mnist\n",
"PoolWorker-12: Loaded 1000 images into train_mnist\n",
"PoolWorker-15: Loaded 1000 images into train_mnist\n",
"PoolWorker-11: Wrote 1000 images to /tmp/madlib_RbuQlbqxI5/train_mnist0001.tmp\n",
"PoolWorker-14: Wrote 1000 images to /tmp/madlib_KTwnncRsaq/train_mnist0001.tmp\n",
"PoolWorker-11: Loaded 1000 images into train_mnist\n",
"PoolWorker-13: Wrote 1000 images to /tmp/madlib_TyYs4viAVD/train_mnist0001.tmp\n",
"PoolWorker-12: Wrote 1000 images to /tmp/madlib_tEyH9GMFGV/train_mnist0001.tmp\n",
"PoolWorker-15: Wrote 1000 images to /tmp/madlib_jtG9zAC8HU/train_mnist0001.tmp\n",
"PoolWorker-11: Wrote 1000 images to /tmp/madlib_RbuQlbqxI5/train_mnist0002.tmp\n",
"PoolWorker-14: Loaded 1000 images into train_mnist\n",
"PoolWorker-14: Wrote 1000 images to /tmp/madlib_KTwnncRsaq/train_mnist0002.tmp\n",
"PoolWorker-13: Loaded 1000 images into train_mnist\n",
"PoolWorker-15: Loaded 1000 images into train_mnist\n",
"PoolWorker-12: Loaded 1000 images into train_mnist\n",
"PoolWorker-13: Wrote 1000 images to /tmp/madlib_TyYs4viAVD/train_mnist0002.tmp\n",
"PoolWorker-11: Loaded 1000 images into train_mnist\n",
"PoolWorker-14: Loaded 1000 images into train_mnist\n",
"PoolWorker-15: Wrote 1000 images to /tmp/madlib_jtG9zAC8HU/train_mnist0002.tmp\n",
"PoolWorker-12: Wrote 1000 images to /tmp/madlib_tEyH9GMFGV/train_mnist0002.tmp\n",
"PoolWorker-13: Loaded 1000 images into train_mnist\n",
"PoolWorker-11: Wrote 1000 images to /tmp/madlib_RbuQlbqxI5/train_mnist0003.tmp\n",
"PoolWorker-14: Wrote 1000 images to /tmp/madlib_KTwnncRsaq/train_mnist0003.tmp\n",
"PoolWorker-13: Wrote 1000 images to /tmp/madlib_TyYs4viAVD/train_mnist0003.tmp\n",
"PoolWorker-12: Loaded 1000 images into train_mnist\n",
"PoolWorker-15: Loaded 1000 images into train_mnist\n",
"PoolWorker-15: Wrote 1000 images to /tmp/madlib_jtG9zAC8HU/train_mnist0003.tmp\n",
"PoolWorker-12: Wrote 1000 images to /tmp/madlib_tEyH9GMFGV/train_mnist0003.tmp\n",
"PoolWorker-11: Loaded 1000 images into train_mnist\n",
"PoolWorker-14: Loaded 1000 images into train_mnist\n",
"PoolWorker-11: Wrote 1000 images to /tmp/madlib_RbuQlbqxI5/train_mnist0004.tmp\n",
"PoolWorker-13: Loaded 1000 images into train_mnist\n",
"PoolWorker-14: Wrote 1000 images to /tmp/madlib_KTwnncRsaq/train_mnist0004.tmp\n",
"PoolWorker-13: Wrote 1000 images to /tmp/madlib_TyYs4viAVD/train_mnist0004.tmp\n",
"PoolWorker-12: Loaded 1000 images into train_mnist\n",
"PoolWorker-15: Loaded 1000 images into train_mnist\n",
"PoolWorker-11: Loaded 1000 images into train_mnist\n",
"PoolWorker-12: Wrote 1000 images to /tmp/madlib_tEyH9GMFGV/train_mnist0004.tmp\n",
"PoolWorker-15: Wrote 1000 images to /tmp/madlib_jtG9zAC8HU/train_mnist0004.tmp\n",
"PoolWorker-14: Loaded 1000 images into train_mnist\n",
"PoolWorker-13: Loaded 1000 images into train_mnist\n",
"PoolWorker-11: Wrote 1000 images to /tmp/madlib_RbuQlbqxI5/train_mnist0005.tmp\n",
"PoolWorker-14: Wrote 1000 images to /tmp/madlib_KTwnncRsaq/train_mnist0005.tmp\n",
"PoolWorker-13: Wrote 1000 images to /tmp/madlib_TyYs4viAVD/train_mnist0005.tmp\n",
"PoolWorker-12: Loaded 1000 images into train_mnist\n",
"PoolWorker-15: Loaded 1000 images into train_mnist\n",
"PoolWorker-12: Wrote 1000 images to /tmp/madlib_tEyH9GMFGV/train_mnist0005.tmp\n",
"PoolWorker-15: Wrote 1000 images to /tmp/madlib_jtG9zAC8HU/train_mnist0005.tmp\n",
"PoolWorker-11: Loaded 1000 images into train_mnist\n",
"PoolWorker-14: Loaded 1000 images into train_mnist\n",
"PoolWorker-13: Loaded 1000 images into train_mnist\n",
"PoolWorker-11: Wrote 1000 images to /tmp/madlib_RbuQlbqxI5/train_mnist0006.tmp\n",
"PoolWorker-13: Wrote 1000 images to /tmp/madlib_TyYs4viAVD/train_mnist0006.tmp\n",
"PoolWorker-14: Wrote 1000 images to /tmp/madlib_KTwnncRsaq/train_mnist0006.tmp\n",
"PoolWorker-12: Loaded 1000 images into train_mnist\n",
"PoolWorker-15: Loaded 1000 images into train_mnist\n",
"PoolWorker-12: Wrote 1000 images to /tmp/madlib_tEyH9GMFGV/train_mnist0006.tmp\n",
"PoolWorker-15: Wrote 1000 images to /tmp/madlib_jtG9zAC8HU/train_mnist0006.tmp\n",
"PoolWorker-11: Loaded 1000 images into train_mnist\n",
"PoolWorker-13: Loaded 1000 images into train_mnist\n",
"PoolWorker-14: Loaded 1000 images into train_mnist\n",
"PoolWorker-11: Wrote 1000 images to /tmp/madlib_RbuQlbqxI5/train_mnist0007.tmp\n",
"PoolWorker-15: Loaded 1000 images into train_mnist\n",
"PoolWorker-12: Loaded 1000 images into train_mnist\n",
"PoolWorker-13: Wrote 1000 images to /tmp/madlib_TyYs4viAVD/train_mnist0007.tmp\n",
"PoolWorker-14: Wrote 1000 images to /tmp/madlib_KTwnncRsaq/train_mnist0007.tmp\n",
"PoolWorker-11: Loaded 1000 images into train_mnist\n",
"PoolWorker-15: Wrote 1000 images to /tmp/madlib_jtG9zAC8HU/train_mnist0007.tmp\n",
"PoolWorker-12: Wrote 1000 images to /tmp/madlib_tEyH9GMFGV/train_mnist0007.tmp\n",
"PoolWorker-11: Wrote 1000 images to /tmp/madlib_RbuQlbqxI5/train_mnist0008.tmp\n",
"PoolWorker-13: Loaded 1000 images into train_mnist\n",
"PoolWorker-13: Wrote 1000 images to /tmp/madlib_TyYs4viAVD/train_mnist0008.tmp\n",
"PoolWorker-14: Loaded 1000 images into train_mnist\n",
"PoolWorker-14: Wrote 1000 images to /tmp/madlib_KTwnncRsaq/train_mnist0008.tmp\n",
"PoolWorker-12: Loaded 1000 images into train_mnist\n",
"PoolWorker-15: Loaded 1000 images into train_mnist\n",
"PoolWorker-11: Loaded 1000 images into train_mnist\n",
"PoolWorker-13: Loaded 1000 images into train_mnist\n",
"PoolWorker-14: Loaded 1000 images into train_mnist\n",
"PoolWorker-12: Wrote 1000 images to /tmp/madlib_tEyH9GMFGV/train_mnist0008.tmp\n",
"PoolWorker-15: Wrote 1000 images to /tmp/madlib_jtG9zAC8HU/train_mnist0008.tmp\n",
"PoolWorker-13: Wrote 1000 images to /tmp/madlib_TyYs4viAVD/train_mnist0009.tmp\n",
"PoolWorker-11: Wrote 1000 images to /tmp/madlib_RbuQlbqxI5/train_mnist0009.tmp\n",
"PoolWorker-14: Wrote 1000 images to /tmp/madlib_KTwnncRsaq/train_mnist0009.tmp\n",
"PoolWorker-12: Loaded 1000 images into train_mnist\n",
"PoolWorker-12: Wrote 1000 images to /tmp/madlib_tEyH9GMFGV/train_mnist0009.tmp\n",
"PoolWorker-15: Loaded 1000 images into train_mnist\n",
"PoolWorker-11: Loaded 1000 images into train_mnist\n",
"PoolWorker-13: Loaded 1000 images into train_mnist\n",
"PoolWorker-15: Wrote 1000 images to /tmp/madlib_jtG9zAC8HU/train_mnist0009.tmp\n",
"PoolWorker-13: Wrote 1000 images to /tmp/madlib_TyYs4viAVD/train_mnist0010.tmp\n",
"PoolWorker-11: Wrote 1000 images to /tmp/madlib_RbuQlbqxI5/train_mnist0010.tmp\n",
"PoolWorker-14: Loaded 1000 images into train_mnist\n",
"PoolWorker-14: Wrote 1000 images to /tmp/madlib_KTwnncRsaq/train_mnist0010.tmp\n",
"PoolWorker-12: Loaded 1000 images into train_mnist\n",
"PoolWorker-12: Wrote 1000 images to /tmp/madlib_tEyH9GMFGV/train_mnist0010.tmp\n",
"PoolWorker-11: Loaded 1000 images into train_mnist\n",
"PoolWorker-13: Loaded 1000 images into train_mnist\n",
"PoolWorker-15: Loaded 1000 images into train_mnist\n",
"PoolWorker-14: Loaded 1000 images into train_mnist\n",
"PoolWorker-12: Loaded 1000 images into train_mnist\n",
"PoolWorker-15: Wrote 1000 images to /tmp/madlib_jtG9zAC8HU/train_mnist0010.tmp\n",
"PoolWorker-13: Wrote 1000 images to /tmp/madlib_TyYs4viAVD/train_mnist0011.tmp\n",
"PoolWorker-11: Wrote 1000 images to /tmp/madlib_RbuQlbqxI5/train_mnist0011.tmp\n",
"PoolWorker-14: Wrote 1000 images to /tmp/madlib_KTwnncRsaq/train_mnist0011.tmp\n"
]
},
{
"name": "stdout",
"output_type": "stream",
"text": [
"PoolWorker-12: Wrote 1000 images to /tmp/madlib_tEyH9GMFGV/train_mnist0011.tmp\n",
"PoolWorker-15: Loaded 1000 images into train_mnist\n",
"PoolWorker-13: Loaded 1000 images into train_mnist\n",
"PoolWorker-11: Loaded 1000 images into train_mnist\n",
"PoolWorker-14: Loaded 1000 images into train_mnist\n",
"PoolWorker-15: Wrote 1000 images to /tmp/madlib_jtG9zAC8HU/train_mnist0011.tmp\n",
"PoolWorker-12: Loaded 1000 images into train_mnist\n",
"PoolWorker-15: Loaded 1000 images into train_mnist\n",
"PoolWorker-11: Removed temporary directory /tmp/madlib_RbuQlbqxI5\n",
"PoolWorker-15: Removed temporary directory /tmp/madlib_jtG9zAC8HU\n",
"PoolWorker-12: Removed temporary directory /tmp/madlib_tEyH9GMFGV\n",
"PoolWorker-13: Removed temporary directory /tmp/madlib_TyYs4viAVD\n",
"PoolWorker-14: Removed temporary directory /tmp/madlib_KTwnncRsaq\n",
"Done! Loaded 60000 images in 45.7068669796s\n",
"5 workers terminated.\n",
"MainProcess: Connected to madlib db.\n",
"Executing: CREATE TABLE test_mnist (id SERIAL, x REAL[], y TEXT)\n",
"CREATE TABLE\n",
"Created table test_mnist in madlib db\n",
"Spawning 5 workers...\n",
"Initializing PoolWorker-16 [pid 34074]\n",
"PoolWorker-16: Created temporary directory /tmp/madlib_MjwU1yRoMW\n",
"Initializing PoolWorker-17 [pid 34075]\n",
"PoolWorker-17: Created temporary directory /tmp/madlib_kTezv88uWu\n",
"Initializing PoolWorker-18 [pid 34076]\n",
"PoolWorker-18: Created temporary directory /tmp/madlib_TFIofbewK1\n",
"Initializing PoolWorker-19 [pid 34077]\n",
"PoolWorker-19: Created temporary directory /tmp/madlib_QUIRxlckvj\n",
"PoolWorker-20: Created temporary directory /tmp/madlib_Eii5YFUzCZ\n",
"Initializing PoolWorker-20 [pid 34078]\n",
"PoolWorker-17: Connected to madlib db.\n",
"PoolWorker-18: Connected to madlib db.\n",
"PoolWorker-19: Connected to madlib db.\n",
"PoolWorker-16: Connected to madlib db.\n",
"PoolWorker-20: Connected to madlib db.\n",
"PoolWorker-18: Wrote 1000 images to /tmp/madlib_TFIofbewK1/test_mnist0000.tmp\n",
"PoolWorker-19: Wrote 1000 images to /tmp/madlib_QUIRxlckvj/test_mnist0000.tmp\n",
"PoolWorker-17: Wrote 1000 images to /tmp/madlib_kTezv88uWu/test_mnist0000.tmp\n",
"PoolWorker-16: Wrote 1000 images to /tmp/madlib_MjwU1yRoMW/test_mnist0000.tmp\n",
"PoolWorker-20: Wrote 1000 images to /tmp/madlib_Eii5YFUzCZ/test_mnist0000.tmp\n",
"PoolWorker-18: Loaded 1000 images into test_mnist\n",
"PoolWorker-17: Loaded 1000 images into test_mnist\n",
"PoolWorker-19: Loaded 1000 images into test_mnist\n",
"PoolWorker-18: Wrote 1000 images to /tmp/madlib_TFIofbewK1/test_mnist0001.tmp\n",
"PoolWorker-16: Loaded 1000 images into test_mnist\n",
"PoolWorker-20: Loaded 1000 images into test_mnist\n",
"PoolWorker-18: Loaded 1000 images into test_mnist\n",
"PoolWorker-19: Wrote 1000 images to /tmp/madlib_QUIRxlckvj/test_mnist0001.tmp\n",
"PoolWorker-17: Wrote 1000 images to /tmp/madlib_kTezv88uWu/test_mnist0001.tmp\n",
"PoolWorker-16: Wrote 1000 images to /tmp/madlib_MjwU1yRoMW/test_mnist0001.tmp\n",
"PoolWorker-20: Wrote 1000 images to /tmp/madlib_Eii5YFUzCZ/test_mnist0001.tmp\n",
"PoolWorker-19: Loaded 1000 images into test_mnist\n",
"PoolWorker-17: Loaded 1000 images into test_mnist\n",
"PoolWorker-16: Loaded 1000 images into test_mnist\n",
"PoolWorker-20: Loaded 1000 images into test_mnist\n",
"PoolWorker-16: Removed temporary directory /tmp/madlib_MjwU1yRoMW\n",
"PoolWorker-19: Removed temporary directory /tmp/madlib_QUIRxlckvj\n",
"PoolWorker-17: Removed temporary directory /tmp/madlib_kTezv88uWu\n",
"PoolWorker-20: Removed temporary directory /tmp/madlib_Eii5YFUzCZ\n",
"PoolWorker-18: Removed temporary directory /tmp/madlib_TFIofbewK1\n",
"Done! Loaded 10000 images in 6.80017995834s\n",
"5 workers terminated.\n"
]
}
],
"source": [
"# Drop tables\n",
"%sql DROP TABLE IF EXISTS train_mnist, test_mnist\n",
"\n",
"# Save images to temporary directories and load into database\n",
"iloader.load_dataset_from_np(x_train, y_train, 'train_mnist', append=False)\n",
"iloader.load_dataset_from_np(x_test, y_test, 'test_mnist', append=False)"
]
},
{
"cell_type": "markdown",
"metadata": {},
"source": [
"<a id=\"image_preproc\"></a>\n",
"# 3. Call image preprocessor\n",
"\n",
"Transforms from one image per row to multiple images per row for batch optimization. Also normalizes and one-hot encodes.\n",
"\n",
"Training dataset"
]
},
{
"cell_type": "code",
"execution_count": 37,
"metadata": {
"scrolled": true
},
"outputs": [
{
"name": "stdout",
"output_type": "stream",
"text": [
"Done.\n",
"1 rows affected.\n",
"1 rows affected.\n"
]
},
{
"data": {
"text/html": [
"<table>\n",
" <tr>\n",
" <th>source_table</th>\n",
" <th>output_table</th>\n",
" <th>dependent_varname</th>\n",
" <th>independent_varname</th>\n",
" <th>dependent_vartype</th>\n",
" <th>class_values</th>\n",
" <th>buffer_size</th>\n",
" <th>normalizing_const</th>\n",
" <th>num_classes</th>\n",
" </tr>\n",
" <tr>\n",
" <td>train_mnist</td>\n",
" <td>train_mnist_packed</td>\n",
" <td>y</td>\n",
" <td>x</td>\n",
" <td>text</td>\n",
" <td>[u'0', u'1', u'2', u'3', u'4', u'5', u'6', u'7', u'8', u'9']</td>\n",
" <td>1000</td>\n",
" <td>255.0</td>\n",
" <td>10</td>\n",
" </tr>\n",
"</table>"
],
"text/plain": [
"[(u'train_mnist', u'train_mnist_packed', u'y', u'x', u'text', [u'0', u'1', u'2', u'3', u'4', u'5', u'6', u'7', u'8', u'9'], 1000, 255.0, 10)]"
]
},
"execution_count": 37,
"metadata": {},
"output_type": "execute_result"
}
],
"source": [
"%%sql\n",
"DROP TABLE IF EXISTS train_mnist_packed, train_mnist_packed_summary;\n",
"\n",
"SELECT madlib.training_preprocessor_dl('train_mnist', -- Source table\n",
" 'train_mnist_packed', -- Output table\n",
" 'y', -- Dependent variable\n",
" 'x', -- Independent variable\n",
" 1000, -- Buffer size\n",
" 255 -- Normalizing constant\n",
" );\n",
"\n",
"SELECT * FROM train_mnist_packed_summary;"
]
},
{
"cell_type": "markdown",
"metadata": {},
"source": [
"Test dataset"
]
},
{
"cell_type": "code",
"execution_count": 39,
"metadata": {},
"outputs": [
{
"name": "stdout",
"output_type": "stream",
"text": [
"Done.\n",
"1 rows affected.\n",
"1 rows affected.\n"
]
},
{
"data": {
"text/html": [
"<table>\n",
" <tr>\n",
" <th>source_table</th>\n",
" <th>output_table</th>\n",
" <th>dependent_varname</th>\n",
" <th>independent_varname</th>\n",
" <th>dependent_vartype</th>\n",
" <th>class_values</th>\n",
" <th>buffer_size</th>\n",
" <th>normalizing_const</th>\n",
" <th>num_classes</th>\n",
" </tr>\n",
" <tr>\n",
" <td>test_mnist</td>\n",
" <td>test_mnist_packed</td>\n",
" <td>y</td>\n",
" <td>x</td>\n",
" <td>text</td>\n",
" <td>[u'0', u'1', u'2', u'3', u'4', u'5', u'6', u'7', u'8', u'9']</td>\n",
" <td>5000</td>\n",
" <td>255.0</td>\n",
" <td>10</td>\n",
" </tr>\n",
"</table>"
],
"text/plain": [
"[(u'test_mnist', u'test_mnist_packed', u'y', u'x', u'text', [u'0', u'1', u'2', u'3', u'4', u'5', u'6', u'7', u'8', u'9'], 5000, 255.0, 10)]"
]
},
"execution_count": 39,
"metadata": {},
"output_type": "execute_result"
}
],
"source": [
"%%sql\n",
"DROP TABLE IF EXISTS test_mnist_packed, test_mnist_packed_summary;\n",
"\n",
"SELECT madlib.validation_preprocessor_dl('test_mnist', -- Source table\n",
" 'test_mnist_packed', -- Output table\n",
" 'y', -- Dependent variable\n",
" 'x', -- Independent variable\n",
" 'train_mnist_packed' -- Training preproc table\n",
" );\n",
"\n",
"SELECT * FROM test_mnist_packed_summary;"
]
},
{
"cell_type": "markdown",
"metadata": {},
"source": [
"<a id=\"define_and_load_model\"></a>\n",
"# 4. Define and load model architecture\n",
"\n",
"Model with feature and classification layers trainable"
]
},
{
"cell_type": "code",
"execution_count": 8,
"metadata": {},
"outputs": [
{
"name": "stdout",
"output_type": "stream",
"text": [
"_________________________________________________________________\n",
"Layer (type) Output Shape Param # \n",
"=================================================================\n",
"conv2d_1 (Conv2D) (None, 26, 26, 32) 320 \n",
"_________________________________________________________________\n",
"activation_1 (Activation) (None, 26, 26, 32) 0 \n",
"_________________________________________________________________\n",
"conv2d_2 (Conv2D) (None, 24, 24, 32) 9248 \n",
"_________________________________________________________________\n",
"activation_2 (Activation) (None, 24, 24, 32) 0 \n",
"_________________________________________________________________\n",
"max_pooling2d_1 (MaxPooling2 (None, 12, 12, 32) 0 \n",
"_________________________________________________________________\n",
"dropout_1 (Dropout) (None, 12, 12, 32) 0 \n",
"_________________________________________________________________\n",
"flatten_1 (Flatten) (None, 4608) 0 \n",
"_________________________________________________________________\n",
"dense_1 (Dense) (None, 128) 589952 \n",
"_________________________________________________________________\n",
"activation_3 (Activation) (None, 128) 0 \n",
"_________________________________________________________________\n",
"dropout_2 (Dropout) (None, 128) 0 \n",
"_________________________________________________________________\n",
"dense_2 (Dense) (None, 10) 1290 \n",
"_________________________________________________________________\n",
"activation_4 (Activation) (None, 10) 0 \n",
"=================================================================\n",
"Total params: 600,810\n",
"Trainable params: 600,810\n",
"Non-trainable params: 0\n",
"_________________________________________________________________\n"
]
}
],
"source": [
"# define two groups of layers: feature (convolutions) and classification (dense)\n",
"feature_layers = [\n",
" Conv2D(filters, kernel_size,\n",
" padding='valid',\n",
" input_shape=input_shape),\n",
" Activation('relu'),\n",
" Conv2D(filters, kernel_size),\n",
" Activation('relu'),\n",
" MaxPooling2D(pool_size=pool_size),\n",
" Dropout(0.25),\n",
" Flatten(),\n",
"]\n",
"\n",
"classification_layers = [\n",
" Dense(128),\n",
" Activation('relu'),\n",
" Dropout(0.5),\n",
" Dense(num_classes),\n",
" Activation('softmax')\n",
"]\n",
"\n",
"# create complete model\n",
"model = Sequential(feature_layers + classification_layers)\n",
"\n",
"model.summary()"
]
},
{
"cell_type": "markdown",
"metadata": {},
"source": [
"Load into model architecture table using psycopg2"
]
},
{
"cell_type": "code",
"execution_count": 9,
"metadata": {},
"outputs": [
{
"name": "stdout",
"output_type": "stream",
"text": [
"Done.\n",
"1 rows affected.\n"
]
},
{
"data": {
"text/html": [
"<table>\n",
" <tr>\n",
" <th>model_id</th>\n",
" <th>name</th>\n",
" </tr>\n",
" <tr>\n",
" <td>1</td>\n",
" <td>feature + classification layers trainable</td>\n",
" </tr>\n",
"</table>"
],
"text/plain": [
"[(1, u'feature + classification layers trainable')]"
]
},
"execution_count": 9,
"metadata": {},
"output_type": "execute_result"
}
],
"source": [
"%sql DROP TABLE IF EXISTS model_arch_table_mnist;\n",
"query = \"SELECT madlib.load_keras_model('model_arch_table_mnist', %s, NULL, %s)\"\n",
"cur.execute(query,[model.to_json(), \"feature + classification layers trainable\"])\n",
"conn.commit()\n",
"\n",
"# check model loaded OK\n",
"%sql SELECT model_id, name FROM model_arch_table_mnist;"
]
},
{
"cell_type": "markdown",
"metadata": {},
"source": [
"<a id=\"hyperband\"></a>\n",
"# 5. Hyperband diagonal"
]
},
{
"cell_type": "markdown",
"metadata": {},
"source": [
"Create tables"
]
},
{
"cell_type": "code",
"execution_count": 34,
"metadata": {},
"outputs": [
{
"name": "stdout",
"output_type": "stream",
"text": [
"Done.\n",
"Done.\n",
"Done.\n",
"Done.\n",
"Done.\n",
"Done.\n",
"1 rows affected.\n",
"Done.\n",
"Done.\n",
"Done.\n",
"Done.\n",
"1 rows affected.\n"
]
},
{
"data": {
"text/plain": [
"[]"
]
},
"execution_count": 34,
"metadata": {},
"output_type": "execute_result"
}
],
"source": [
"%%sql\n",
"-- overall results table\n",
"DROP TABLE IF EXISTS results_mnist;\n",
"CREATE TABLE results_mnist ( \n",
" mst_key INTEGER, -- note not SERIAL\n",
" model_id INTEGER, \n",
" compile_params TEXT,\n",
" fit_params TEXT, \n",
" model_type TEXT, \n",
" model_size DOUBLE PRECISION, \n",
" metrics_elapsed_time DOUBLE PRECISION[], \n",
" metrics_type TEXT[], \n",
" training_metrics_final DOUBLE PRECISION, \n",
" training_loss_final DOUBLE PRECISION, \n",
" training_metrics DOUBLE PRECISION[], \n",
" training_loss DOUBLE PRECISION[], \n",
" validation_metrics_final DOUBLE PRECISION, \n",
" validation_loss_final DOUBLE PRECISION, \n",
" validation_metrics DOUBLE PRECISION[], \n",
" validation_loss DOUBLE PRECISION[], \n",
" model_arch_table TEXT, \n",
" num_iterations INTEGER, \n",
" start_training_time TIMESTAMP, \n",
" end_training_time TIMESTAMP,\n",
" s INTEGER, \n",
" i INTEGER,\n",
" run_id SERIAL\n",
" );\n",
"\n",
"-- model selection table\n",
"DROP TABLE IF EXISTS mst_table_hb_mnist;\n",
"CREATE TABLE mst_table_hb_mnist (\n",
" mst_key SERIAL, \n",
" s INTEGER, -- bracket\n",
" model_id INTEGER, \n",
" compile_params VARCHAR, \n",
" fit_params VARCHAR\n",
" );\n",
"\n",
"-- model selection summary table\n",
"DROP TABLE IF EXISTS mst_table_hb_mnist_summary;\n",
"CREATE TABLE mst_table_hb_mnist_summary (model_arch_table VARCHAR);\n",
"INSERT INTO mst_table_hb_mnist_summary VALUES ('model_arch_table_mnist');\n",
"\n",
"-- model selection table for diagonal\n",
"DROP TABLE IF EXISTS mst_diag_table_hb_mnist;\n",
"CREATE TABLE mst_diag_table_hb_mnist (\n",
" mst_key INTEGER, -- note not SERIAL since this table derived from main model selection table\n",
" s INTEGER, -- bracket\n",
" model_id INTEGER, \n",
" compile_params VARCHAR, \n",
" fit_params VARCHAR\n",
" );\n",
"\n",
"-- model selection summary table for diagonal\n",
"DROP TABLE IF EXISTS mst_diag_table_hb_mnist_summary;\n",
"CREATE TABLE mst_diag_table_hb_mnist_summary (model_arch_table VARCHAR);\n",
"INSERT INTO mst_diag_table_hb_mnist_summary VALUES ('model_arch_table_mnist');"
]
},
{
"cell_type": "markdown",
"metadata": {},
"source": [
"Generalize table names"
]
},
{
"cell_type": "code",
"execution_count": 35,
"metadata": {},
"outputs": [],
"source": [
"results_table = 'results_mnist'\n",
"\n",
"output_table = 'mnist_multi_model'\n",
"output_table_info = '_'.join([output_table, 'info'])\n",
"output_table_summary = '_'.join([output_table, 'summary'])\n",
"\n",
"mst_table = 'mst_table_hb_mnist'\n",
"mst_table_summary = '_'.join([mst_table, 'summary'])\n",
"\n",
"mst_diag_table = 'mst_diag_table_hb_mnist'\n",
"mst_diag_table_summary = '_'.join([mst_diag_table, 'summary'])\n",
"\n",
"model_arch_table = 'model_arch_library_mnist'"
]
},
{
"cell_type": "markdown",
"metadata": {},
"source": [
"Hyperband diagonal logic"
]
},
{
"cell_type": "code",
"execution_count": 36,
"metadata": {},
"outputs": [],
"source": [
"import numpy as np\n",
"from random import random\n",
"from math import log, ceil\n",
"from time import time, ctime\n",
"\n",
"class Hyperband_diagonal:\n",
" \n",
" def __init__( self, get_params_function, try_params_function ):\n",
" self.get_params = get_params_function\n",
" self.try_params = try_params_function\n",
"\n",
" self.max_iter = 9 # maximum iterations per configuration\n",
" self.eta = 3 # defines downsampling rate (default = 3)\n",
"\n",
" self.logeta = lambda x: log( x ) / log( self.eta )\n",
" self.s_max = int( self.logeta( self.max_iter ))\n",
" self.B = ( self.s_max + 1 ) * self.max_iter\n",
" self.setup_full_schedule()\n",
" self.create_mst_superset()\n",
" \n",
" self.best_loss = np.inf\n",
" self.best_accuracy = 0.0\n",
"\n",
" \n",
" # create full Hyperband schedule for all brackets ahead of time\n",
" def setup_full_schedule(self):\n",
" self.n_vals = np.zeros((self.s_max+1, self.s_max+1), dtype=int)\n",
" self.r_vals = np.zeros((self.s_max+1, self.s_max+1), dtype=int)\n",
" sum_leaf_n_i = 0 # count configurations at leaf nodes across all s\n",
" \n",
" print (\" \")\n",
" print (\"Hyperband brackets\")\n",
"\n",
" # loop through each bracket in reverse order\n",
" for s in reversed(range(self.s_max+1)):\n",
" \n",
" print (\" \")\n",
" print (\"s=\" + str(s))\n",
" print (\"n_i r_i\")\n",
" print (\"------------\")\n",
" counter = 0\n",
"\n",
" n = int(ceil(int(self.B/self.max_iter/(s+1))*self.eta**s)) # initial number of configurations\n",
" r = self.max_iter*self.eta**(-s) # initial number of iterations to run configurations for\n",
"\n",
" #### Begin Finite Horizon Successive Halving with (n,r)\n",
" for i in range(s+1):\n",
" # n_i configs for r_i iterations\n",
" n_i = n*self.eta**(-i)\n",
" r_i = r*self.eta**(i)\n",
"\n",
" self.n_vals[s][i] = n_i\n",
" self.r_vals[s][i] = r_i\n",
"\n",
" print (str(n_i) + \" \" + str (r_i))\n",
"\n",
" # check if leaf node for this s\n",
" if counter == s:\n",
" sum_leaf_n_i += n_i\n",
" counter += 1\n",
" \n",
" #### End Finite Horizon Successive Halving with (n,r)\n",
"\n",
" #print (\" \")\n",
" #print (\"sum of configurations at leaf nodes across all s = \" + str(sum_leaf_n_i))\n",
" #print (\"(if have more workers than this, they may not be 100% busy)\")\n",
" \n",
" \n",
" # generate model selection tuples for all brackets\n",
" def create_mst_superset(self):\n",
" # get hyper parameter configs for each bracket s\n",
" for s in reversed(range(self.s_max+1)):\n",
" n = int(ceil(int(self.B/self.max_iter/(s+1))*self.eta**s)) # initial number of configurations\n",
" r = self.max_iter*self.eta**(-s) # initial number of iterations to run configurations for\n",
"\n",
" print (\" \")\n",
" print (\"Create superset of MSTs, i.e., i=0 for for each bracket s\")\n",
" print (\" \")\n",
" print (\"s=\" + str(s))\n",
" print (\"n=\" + str(n))\n",
" print (\"r=\" + str(r))\n",
" print (\" \")\n",
" \n",
" # n random configurations for each bracket s\n",
" self.get_params(n, s)\n",
" \n",
" \n",
" # Hyperband diagonal logic\n",
" def run( self, skip_last = 0, dry_run = False ): \n",
" \n",
" print (\" \")\n",
" print (\"Hyperband diagonal\")\n",
" print (\"outer loop on diagonal:\")\n",
" \n",
" # outer loop on diagonal\n",
" for i in range(self.s_max+1):\n",
" print (\" \")\n",
" print (\"i=\" + str(i))\n",
" \n",
" # zero out diagonal table\n",
" %sql TRUNCATE TABLE $mst_diag_table\n",
" \n",
" # loop on brackets s desc to create diagonal table\n",
" print (\"loop on s desc to create diagonal table:\")\n",
" for s in range(self.s_max, self.s_max-i-1, -1):\n",
"\n",
" # build up mst table for diagonal\n",
" %sql INSERT INTO $mst_diag_table (SELECT * FROM $mst_table WHERE s=$s);\n",
" \n",
" # first pass\n",
" if i == 0:\n",
" first_pass = True\n",
" else:\n",
" first_pass = False\n",
" \n",
" # multi-model training\n",
" print (\" \")\n",
" print (\"try params for i = \" + str(i))\n",
" U = self.try_params(i, self.r_vals[self.s_max][i], first_pass) # r_i is the same for all diagonal elements\n",
" \n",
" # loop on brackets s desc to prune model selection table\n",
" # don't need to prune if finished last diagonal\n",
" if i < self.s_max:\n",
" print (\"loop on s desc to prune mst table:\")\n",
" for s in range(self.s_max, self.s_max-i-1, -1):\n",
" \n",
" # compute number of configs to keep\n",
" # remember i value is different for each bracket s on the diagonal\n",
" k = int( self.n_vals[s][s-self.s_max+i] / self.eta)\n",
" print (\"pruning s = {} with k = {}\".format(s, k))\n",
"\n",
" # temporarily re-define table names due to weird Python scope issues\n",
" results_table = 'results_mnist'\n",
"\n",
" output_table = 'mnist_multi_model'\n",
" output_table_info = '_'.join([output_table, 'info'])\n",
" output_table_summary = '_'.join([output_table, 'summary'])\n",
"\n",
" mst_table = 'mst_table_hb_mnist'\n",
" mst_table_summary = '_'.join([mst_table, 'summary'])\n",
"\n",
" mst_diag_table = 'mst_diag_table_hb_mnist'\n",
" mst_diag_table_summary = '_'.join([mst_diag_table, 'summary'])\n",
"\n",
" model_arch_table = 'model_arch_library_mnist'\n",
" \n",
" query = \"\"\"\n",
" DELETE FROM {mst_table} WHERE s={s} AND mst_key NOT IN (SELECT {output_table_info}.mst_key FROM {output_table_info} JOIN {mst_table} ON {output_table_info}.mst_key={mst_table}.mst_key WHERE s={s} ORDER BY validation_loss_final ASC LIMIT {k}::INT);\n",
" \"\"\".format(**locals())\n",
" cur.execute(query)\n",
" conn.commit()\n",
" \n",
" # these were not working so used cursor instead\n",
" #%sql DELETE FROM $mst_table WHERE s=$s AND mst_key NOT IN (SELECT $output_table_info.mst_key FROM $output_table_info JOIN $mst_table ON $output_table_info.mst_key=$mst_table.mst_key WHERE s=$s ORDER BY validation_loss_final ASC LIMIT $k::INT);\n",
" #%sql DELETE FROM mst_table_hb_mnist WHERE s=1 AND mst_key NOT IN (SELECT mnist_multi_model_info.mst_key FROM mnist_multi_model_info JOIN mst_table_hb_mnist ON mnist_multi_model_info.mst_key=mst_table_hb_mnist.mst_key WHERE s=1 ORDER BY validation_loss_final ASC LIMIT 1);\n",
" \n",
" # keep track of best loss so far (for display purposes only)\n",
" loss = %sql SELECT validation_loss_final FROM $output_table_info ORDER BY validation_loss_final ASC LIMIT 1;\n",
" accuracy = %sql SELECT validation_metrics_final FROM $output_table_info ORDER BY validation_loss_final ASC LIMIT 1;\n",
" \n",
" if loss < self.best_loss:\n",
" self.best_loss = loss\n",
" self.best_accuracy = accuracy\n",
" \n",
" print (\" \")\n",
" print (\"best validation loss so far = \" + str(loss))\n",
" print (\"best validation accuracy so far = \" + str(accuracy))\n",
" \n",
" return"
]
},
{
"cell_type": "markdown",
"metadata": {},
"source": [
"Generate params and insert into MST table"
]
},
{
"cell_type": "code",
"execution_count": 37,
"metadata": {},
"outputs": [],
"source": [
"def get_params(n, s):\n",
" \n",
" from sklearn.model_selection import ParameterSampler\n",
" from scipy.stats.distributions import uniform\n",
" import numpy as np\n",
" \n",
" # model architecture\n",
" model_id = [1]\n",
"\n",
" # compile params\n",
" # loss function\n",
" loss = ['categorical_crossentropy']\n",
" # optimizer\n",
" optimizer = ['Adam', 'SGD']\n",
" # learning rate (sample on log scale here not in ParameterSampler)\n",
" lr_range = [0.001, 0.1]\n",
" lr = 10**np.random.uniform(np.log10(lr_range[0]), np.log10(lr_range[1]), n)\n",
" # metrics\n",
" metrics = ['accuracy']\n",
"\n",
" # fit params\n",
" # batch size\n",
" batch_size = [32, 64, 128]\n",
" # epochs\n",
" epochs = [1]\n",
"\n",
" # create random param list\n",
" param_grid = {\n",
" 'model_id': model_id,\n",
" 'loss': loss,\n",
" 'optimizer': optimizer,\n",
" 'lr': lr,\n",
" 'metrics': metrics,\n",
" 'batch_size': batch_size,\n",
" 'epochs': epochs\n",
" }\n",
" param_list = list(ParameterSampler(param_grid, n_iter=n))\n",
" \n",
" for params in param_list:\n",
"\n",
" model_id = str(params.get(\"model_id\"))\n",
" compile_params = \"$$loss='\" + str(params.get(\"loss\")) + \"',optimizer='\" + str(params.get(\"optimizer\")) + \"(lr=\" + str(params.get(\"lr\")) + \")',metrics=['\" + str(params.get(\"metrics\")) + \"']$$\" \n",
" fit_params = \"$$batch_size=\" + str(params.get(\"batch_size\")) + \",epochs=\" + str(params.get(\"epochs\")) + \"$$\" \n",
" row_content = \"(\" + str(s) + \", \" + model_id + \", \" + compile_params + \", \" + fit_params + \");\"\n",
" \n",
" %sql INSERT INTO $mst_table (s, model_id, compile_params, fit_params) VALUES $row_content\n",
" \n",
" return"
]
},
{
"cell_type": "markdown",
"metadata": {},
"source": [
"Run model hopper for candidates in MST table"
]
},
{
"cell_type": "code",
"execution_count": 38,
"metadata": {},
"outputs": [],
"source": [
"def try_params(i, r, first_pass):\n",
" \n",
" # multi-model fit\n",
" if first_pass:\n",
" # cold start\n",
" %sql DROP TABLE IF EXISTS $output_table, $output_table_summary, $output_table_info;\n",
" # passing vars as madlib args does not seem to work\n",
" #%sql SELECT madlib.madlib_keras_fit_multiple_model('train_mnist_packed', $output_table, $mst_diag_table, $r_i::INT, 0);\n",
" %sql SELECT madlib.madlib_keras_fit_multiple_model('train_mnist_packed', 'mnist_multi_model', 'mst_diag_table_hb_mnist', $r::INT, FALSE, 'test_mnist_packed');\n",
"\n",
" else:\n",
" # warm start to continue from previous run\n",
" %sql SELECT madlib.madlib_keras_fit_multiple_model('train_mnist_packed', 'mnist_multi_model', 'mst_diag_table_hb_mnist', $r::INT, FALSE, 'test_mnist_packed', NULL, True);\n",
"\n",
" # save results via temp table\n",
" # add everything from info table\n",
" %sql DROP TABLE IF EXISTS temp_results;\n",
" %sql CREATE TABLE temp_results AS (SELECT * FROM $output_table_info);\n",
" \n",
" # add summary table info and i value (same for each row)\n",
" %sql ALTER TABLE temp_results ADD COLUMN model_arch_table TEXT, ADD COLUMN num_iterations INTEGER, ADD COLUMN start_training_time TIMESTAMP, ADD COLUMN end_training_time TIMESTAMP, ADD COLUMN s INTEGER, ADD COLUMN i INTEGER;\n",
" %sql UPDATE temp_results SET model_arch_table = (SELECT model_arch_table FROM $output_table_summary), num_iterations = (SELECT num_iterations FROM $output_table_summary), start_training_time = (SELECT start_training_time FROM $output_table_summary), end_training_time = (SELECT end_training_time FROM $output_table_summary), i = $i;\n",
" \n",
" # get the s value for each run (not the same for each row since diagonal table crosses multiple brackets)\n",
" %sql UPDATE temp_results SET s = m.s FROM mst_diag_table_hb_mnist AS m WHERE m.mst_key = temp_results.mst_key;\n",
" \n",
" # copy temp table into results table\n",
" %sql INSERT INTO $results_table (SELECT * FROM temp_results);\n",
"\n",
" return"
]
},
{
"cell_type": "markdown",
"metadata": {},
"source": [
"Call Hyperband diagonal"
]
},
{
"cell_type": "code",
"execution_count": null,
"metadata": {
"scrolled": false
},
"outputs": [
{
"name": "stdout",
"output_type": "stream",
"text": [
" \n",
"Hyperband brackets\n",
" \n",
"s=2\n",
"n_i r_i\n",
"------------\n",
"9 1.0\n",
"3.0 3.0\n",
"1.0 9.0\n",
" \n",
"s=1\n",
"n_i r_i\n",
"------------\n",
"3 3.0\n",
"1.0 9.0\n",
" \n",
"s=0\n",
"n_i r_i\n",
"------------\n",
"3 9\n",
" \n",
"Create superset of MSTs, i.e., i=0 for for each bracket s\n",
" \n",
"s=2\n",
"n=9\n",
"r=1.0\n",
" \n",
"1 rows affected.\n",
"1 rows affected.\n",
"1 rows affected.\n",
"1 rows affected.\n",
"1 rows affected.\n",
"1 rows affected.\n",
"1 rows affected.\n",
"1 rows affected.\n",
"1 rows affected.\n",
" \n",
"Create superset of MSTs, i.e., i=0 for for each bracket s\n",
" \n",
"s=1\n",
"n=3\n",
"r=3.0\n",
" \n",
"1 rows affected.\n",
"1 rows affected.\n",
"1 rows affected.\n",
" \n",
"Create superset of MSTs, i.e., i=0 for for each bracket s\n",
" \n",
"s=0\n",
"n=3\n",
"r=9\n",
" \n",
"1 rows affected.\n",
"1 rows affected.\n",
"1 rows affected.\n",
" \n",
"Hyperband diagonal\n",
"outer loop on diagonal:\n",
" \n",
"i=0\n",
"Done.\n",
"loop on s desc to create diagonal table:\n",
"9 rows affected.\n",
" \n",
"try params for i = 0\n",
"Done.\n"
]
}
],
"source": [
"hp = Hyperband_diagonal(get_params, try_params )\n",
"results = hp.run()\n",
"#hp.n_vals[1]"
]
},
{
"cell_type": "markdown",
"metadata": {},
"source": [
"<a id=\"plot\"></a>\n",
"# 6. Plot results"
]
},
{
"cell_type": "code",
"execution_count": 22,
"metadata": {},
"outputs": [],
"source": [
"%matplotlib notebook\n",
"import matplotlib.pyplot as plt\n",
"from matplotlib.ticker import MaxNLocator\n",
"from collections import defaultdict\n",
"import pandas as pd\n",
"import seaborn as sns\n",
"sns.set_palette(sns.color_palette(\"hls\", 20))\n",
"plt.rcParams.update({'font.size': 12})\n",
"pd.set_option('display.max_colwidth', -1)"
]
},
{
"cell_type": "markdown",
"metadata": {},
"source": [
"Training dataset"
]
},
{
"cell_type": "code",
"execution_count": 30,
"metadata": {},
"outputs": [
{
"name": "stdout",
"output_type": "stream",
"text": [
"8 rows affected.\n"
]
},
{
"data": {
"application/javascript": [
"/* Put everything inside the global mpl namespace */\n",
"window.mpl = {};\n",
"\n",
"\n",
"mpl.get_websocket_type = function() {\n",
" if (typeof(WebSocket) !== 'undefined') {\n",
" return WebSocket;\n",
" } else if (typeof(MozWebSocket) !== 'undefined') {\n",
" return MozWebSocket;\n",
" } else {\n",
" alert('Your browser does not have WebSocket support.' +\n",
" 'Please try Chrome, Safari or Firefox ≥ 6. ' +\n",
" 'Firefox 4 and 5 are also supported but you ' +\n",
" 'have to enable WebSockets in about:config.');\n",
" };\n",
"}\n",
"\n",
"mpl.figure = function(figure_id, websocket, ondownload, parent_element) {\n",
" this.id = figure_id;\n",
"\n",
" this.ws = websocket;\n",
"\n",
" this.supports_binary = (this.ws.binaryType != undefined);\n",
"\n",
" if (!this.supports_binary) {\n",
" var warnings = document.getElementById(\"mpl-warnings\");\n",
" if (warnings) {\n",
" warnings.style.display = 'block';\n",
" warnings.textContent = (\n",
" \"This browser does not support binary websocket messages. \" +\n",
" \"Performance may be slow.\");\n",
" }\n",
" }\n",
"\n",
" this.imageObj = new Image();\n",
"\n",
" this.context = undefined;\n",
" this.message = undefined;\n",
" this.canvas = undefined;\n",
" this.rubberband_canvas = undefined;\n",
" this.rubberband_context = undefined;\n",
" this.format_dropdown = undefined;\n",
"\n",
" this.image_mode = 'full';\n",
"\n",
" this.root = $('<div/>');\n",
" this._root_extra_style(this.root)\n",
" this.root.attr('style', 'display: inline-block');\n",
"\n",
" $(parent_element).append(this.root);\n",
"\n",
" this._init_header(this);\n",
" this._init_canvas(this);\n",
" this._init_toolbar(this);\n",
"\n",
" var fig = this;\n",
"\n",
" this.waiting = false;\n",
"\n",
" this.ws.onopen = function () {\n",
" fig.send_message(\"supports_binary\", {value: fig.supports_binary});\n",
" fig.send_message(\"send_image_mode\", {});\n",
" if (mpl.ratio != 1) {\n",
" fig.send_message(\"set_dpi_ratio\", {'dpi_ratio': mpl.ratio});\n",
" }\n",
" fig.send_message(\"refresh\", {});\n",
" }\n",
"\n",
" this.imageObj.onload = function() {\n",
" if (fig.image_mode == 'full') {\n",
" // Full images could contain transparency (where diff images\n",
" // almost always do), so we need to clear the canvas so that\n",
" // there is no ghosting.\n",
" fig.context.clearRect(0, 0, fig.canvas.width, fig.canvas.height);\n",
" }\n",
" fig.context.drawImage(fig.imageObj, 0, 0);\n",
" };\n",
"\n",
" this.imageObj.onunload = function() {\n",
" fig.ws.close();\n",
" }\n",
"\n",
" this.ws.onmessage = this._make_on_message_function(this);\n",
"\n",
" this.ondownload = ondownload;\n",
"}\n",
"\n",
"mpl.figure.prototype._init_header = function() {\n",
" var titlebar = $(\n",
" '<div class=\"ui-dialog-titlebar ui-widget-header ui-corner-all ' +\n",
" 'ui-helper-clearfix\"/>');\n",
" var titletext = $(\n",
" '<div class=\"ui-dialog-title\" style=\"width: 100%; ' +\n",
" 'text-align: center; padding: 3px;\"/>');\n",
" titlebar.append(titletext)\n",
" this.root.append(titlebar);\n",
" this.header = titletext[0];\n",
"}\n",
"\n",
"\n",
"\n",
"mpl.figure.prototype._canvas_extra_style = function(canvas_div) {\n",
"\n",
"}\n",
"\n",
"\n",
"mpl.figure.prototype._root_extra_style = function(canvas_div) {\n",
"\n",
"}\n",
"\n",
"mpl.figure.prototype._init_canvas = function() {\n",
" var fig = this;\n",
"\n",
" var canvas_div = $('<div/>');\n",
"\n",
" canvas_div.attr('style', 'position: relative; clear: both; outline: 0');\n",
"\n",
" function canvas_keyboard_event(event) {\n",
" return fig.key_event(event, event['data']);\n",
" }\n",
"\n",
" canvas_div.keydown('key_press', canvas_keyboard_event);\n",
" canvas_div.keyup('key_release', canvas_keyboard_event);\n",
" this.canvas_div = canvas_div\n",
" this._canvas_extra_style(canvas_div)\n",
" this.root.append(canvas_div);\n",
"\n",
" var canvas = $('<canvas/>');\n",
" canvas.addClass('mpl-canvas');\n",
" canvas.attr('style', \"left: 0; top: 0; z-index: 0; outline: 0\")\n",
"\n",
" this.canvas = canvas[0];\n",
" this.context = canvas[0].getContext(\"2d\");\n",
"\n",
" var backingStore = this.context.backingStorePixelRatio ||\n",
"\tthis.context.webkitBackingStorePixelRatio ||\n",
"\tthis.context.mozBackingStorePixelRatio ||\n",
"\tthis.context.msBackingStorePixelRatio ||\n",
"\tthis.context.oBackingStorePixelRatio ||\n",
"\tthis.context.backingStorePixelRatio || 1;\n",
"\n",
" mpl.ratio = (window.devicePixelRatio || 1) / backingStore;\n",
"\n",
" var rubberband = $('<canvas/>');\n",
" rubberband.attr('style', \"position: absolute; left: 0; top: 0; z-index: 1;\")\n",
"\n",
" var pass_mouse_events = true;\n",
"\n",
" canvas_div.resizable({\n",
" start: function(event, ui) {\n",
" pass_mouse_events = false;\n",
" },\n",
" resize: function(event, ui) {\n",
" fig.request_resize(ui.size.width, ui.size.height);\n",
" },\n",
" stop: function(event, ui) {\n",
" pass_mouse_events = true;\n",
" fig.request_resize(ui.size.width, ui.size.height);\n",
" },\n",
" });\n",
"\n",
" function mouse_event_fn(event) {\n",
" if (pass_mouse_events)\n",
" return fig.mouse_event(event, event['data']);\n",
" }\n",
"\n",
" rubberband.mousedown('button_press', mouse_event_fn);\n",
" rubberband.mouseup('button_release', mouse_event_fn);\n",
" // Throttle sequential mouse events to 1 every 20ms.\n",
" rubberband.mousemove('motion_notify', mouse_event_fn);\n",
"\n",
" rubberband.mouseenter('figure_enter', mouse_event_fn);\n",
" rubberband.mouseleave('figure_leave', mouse_event_fn);\n",
"\n",
" canvas_div.on(\"wheel\", function (event) {\n",
" event = event.originalEvent;\n",
" event['data'] = 'scroll'\n",
" if (event.deltaY < 0) {\n",
" event.step = 1;\n",
" } else {\n",
" event.step = -1;\n",
" }\n",
" mouse_event_fn(event);\n",
" });\n",
"\n",
" canvas_div.append(canvas);\n",
" canvas_div.append(rubberband);\n",
"\n",
" this.rubberband = rubberband;\n",
" this.rubberband_canvas = rubberband[0];\n",
" this.rubberband_context = rubberband[0].getContext(\"2d\");\n",
" this.rubberband_context.strokeStyle = \"#000000\";\n",
"\n",
" this._resize_canvas = function(width, height) {\n",
" // Keep the size of the canvas, canvas container, and rubber band\n",
" // canvas in synch.\n",
" canvas_div.css('width', width)\n",
" canvas_div.css('height', height)\n",
"\n",
" canvas.attr('width', width * mpl.ratio);\n",
" canvas.attr('height', height * mpl.ratio);\n",
" canvas.attr('style', 'width: ' + width + 'px; height: ' + height + 'px;');\n",
"\n",
" rubberband.attr('width', width);\n",
" rubberband.attr('height', height);\n",
" }\n",
"\n",
" // Set the figure to an initial 600x600px, this will subsequently be updated\n",
" // upon first draw.\n",
" this._resize_canvas(600, 600);\n",
"\n",
" // Disable right mouse context menu.\n",
" $(this.rubberband_canvas).bind(\"contextmenu\",function(e){\n",
" return false;\n",
" });\n",
"\n",
" function set_focus () {\n",
" canvas.focus();\n",
" canvas_div.focus();\n",
" }\n",
"\n",
" window.setTimeout(set_focus, 100);\n",
"}\n",
"\n",
"mpl.figure.prototype._init_toolbar = function() {\n",
" var fig = this;\n",
"\n",
" var nav_element = $('<div/>')\n",
" nav_element.attr('style', 'width: 100%');\n",
" this.root.append(nav_element);\n",
"\n",
" // Define a callback function for later on.\n",
" function toolbar_event(event) {\n",
" return fig.toolbar_button_onclick(event['data']);\n",
" }\n",
" function toolbar_mouse_event(event) {\n",
" return fig.toolbar_button_onmouseover(event['data']);\n",
" }\n",
"\n",
" for(var toolbar_ind in mpl.toolbar_items) {\n",
" var name = mpl.toolbar_items[toolbar_ind][0];\n",
" var tooltip = mpl.toolbar_items[toolbar_ind][1];\n",
" var image = mpl.toolbar_items[toolbar_ind][2];\n",
" var method_name = mpl.toolbar_items[toolbar_ind][3];\n",
"\n",
" if (!name) {\n",
" // put a spacer in here.\n",
" continue;\n",
" }\n",
" var button = $('<button/>');\n",
" button.addClass('ui-button ui-widget ui-state-default ui-corner-all ' +\n",
" 'ui-button-icon-only');\n",
" button.attr('role', 'button');\n",
" button.attr('aria-disabled', 'false');\n",
" button.click(method_name, toolbar_event);\n",
" button.mouseover(tooltip, toolbar_mouse_event);\n",
"\n",
" var icon_img = $('<span/>');\n",
" icon_img.addClass('ui-button-icon-primary ui-icon');\n",
" icon_img.addClass(image);\n",
" icon_img.addClass('ui-corner-all');\n",
"\n",
" var tooltip_span = $('<span/>');\n",
" tooltip_span.addClass('ui-button-text');\n",
" tooltip_span.html(tooltip);\n",
"\n",
" button.append(icon_img);\n",
" button.append(tooltip_span);\n",
"\n",
" nav_element.append(button);\n",
" }\n",
"\n",
" var fmt_picker_span = $('<span/>');\n",
"\n",
" var fmt_picker = $('<select/>');\n",
" fmt_picker.addClass('mpl-toolbar-option ui-widget ui-widget-content');\n",
" fmt_picker_span.append(fmt_picker);\n",
" nav_element.append(fmt_picker_span);\n",
" this.format_dropdown = fmt_picker[0];\n",
"\n",
" for (var ind in mpl.extensions) {\n",
" var fmt = mpl.extensions[ind];\n",
" var option = $(\n",
" '<option/>', {selected: fmt === mpl.default_extension}).html(fmt);\n",
" fmt_picker.append(option)\n",
" }\n",
"\n",
" // Add hover states to the ui-buttons\n",
" $( \".ui-button\" ).hover(\n",
" function() { $(this).addClass(\"ui-state-hover\");},\n",
" function() { $(this).removeClass(\"ui-state-hover\");}\n",
" );\n",
"\n",
" var status_bar = $('<span class=\"mpl-message\"/>');\n",
" nav_element.append(status_bar);\n",
" this.message = status_bar[0];\n",
"}\n",
"\n",
"mpl.figure.prototype.request_resize = function(x_pixels, y_pixels) {\n",
" // Request matplotlib to resize the figure. Matplotlib will then trigger a resize in the client,\n",
" // which will in turn request a refresh of the image.\n",
" this.send_message('resize', {'width': x_pixels, 'height': y_pixels});\n",
"}\n",
"\n",
"mpl.figure.prototype.send_message = function(type, properties) {\n",
" properties['type'] = type;\n",
" properties['figure_id'] = this.id;\n",
" this.ws.send(JSON.stringify(properties));\n",
"}\n",
"\n",
"mpl.figure.prototype.send_draw_message = function() {\n",
" if (!this.waiting) {\n",
" this.waiting = true;\n",
" this.ws.send(JSON.stringify({type: \"draw\", figure_id: this.id}));\n",
" }\n",
"}\n",
"\n",
"\n",
"mpl.figure.prototype.handle_save = function(fig, msg) {\n",
" var format_dropdown = fig.format_dropdown;\n",
" var format = format_dropdown.options[format_dropdown.selectedIndex].value;\n",
" fig.ondownload(fig, format);\n",
"}\n",
"\n",
"\n",
"mpl.figure.prototype.handle_resize = function(fig, msg) {\n",
" var size = msg['size'];\n",
" if (size[0] != fig.canvas.width || size[1] != fig.canvas.height) {\n",
" fig._resize_canvas(size[0], size[1]);\n",
" fig.send_message(\"refresh\", {});\n",
" };\n",
"}\n",
"\n",
"mpl.figure.prototype.handle_rubberband = function(fig, msg) {\n",
" var x0 = msg['x0'] / mpl.ratio;\n",
" var y0 = (fig.canvas.height - msg['y0']) / mpl.ratio;\n",
" var x1 = msg['x1'] / mpl.ratio;\n",
" var y1 = (fig.canvas.height - msg['y1']) / mpl.ratio;\n",
" x0 = Math.floor(x0) + 0.5;\n",
" y0 = Math.floor(y0) + 0.5;\n",
" x1 = Math.floor(x1) + 0.5;\n",
" y1 = Math.floor(y1) + 0.5;\n",
" var min_x = Math.min(x0, x1);\n",
" var min_y = Math.min(y0, y1);\n",
" var width = Math.abs(x1 - x0);\n",
" var height = Math.abs(y1 - y0);\n",
"\n",
" fig.rubberband_context.clearRect(\n",
" 0, 0, fig.canvas.width, fig.canvas.height);\n",
"\n",
" fig.rubberband_context.strokeRect(min_x, min_y, width, height);\n",
"}\n",
"\n",
"mpl.figure.prototype.handle_figure_label = function(fig, msg) {\n",
" // Updates the figure title.\n",
" fig.header.textContent = msg['label'];\n",
"}\n",
"\n",
"mpl.figure.prototype.handle_cursor = function(fig, msg) {\n",
" var cursor = msg['cursor'];\n",
" switch(cursor)\n",
" {\n",
" case 0:\n",
" cursor = 'pointer';\n",
" break;\n",
" case 1:\n",
" cursor = 'default';\n",
" break;\n",
" case 2:\n",
" cursor = 'crosshair';\n",
" break;\n",
" case 3:\n",
" cursor = 'move';\n",
" break;\n",
" }\n",
" fig.rubberband_canvas.style.cursor = cursor;\n",
"}\n",
"\n",
"mpl.figure.prototype.handle_message = function(fig, msg) {\n",
" fig.message.textContent = msg['message'];\n",
"}\n",
"\n",
"mpl.figure.prototype.handle_draw = function(fig, msg) {\n",
" // Request the server to send over a new figure.\n",
" fig.send_draw_message();\n",
"}\n",
"\n",
"mpl.figure.prototype.handle_image_mode = function(fig, msg) {\n",
" fig.image_mode = msg['mode'];\n",
"}\n",
"\n",
"mpl.figure.prototype.updated_canvas_event = function() {\n",
" // Called whenever the canvas gets updated.\n",
" this.send_message(\"ack\", {});\n",
"}\n",
"\n",
"// A function to construct a web socket function for onmessage handling.\n",
"// Called in the figure constructor.\n",
"mpl.figure.prototype._make_on_message_function = function(fig) {\n",
" return function socket_on_message(evt) {\n",
" if (evt.data instanceof Blob) {\n",
" /* FIXME: We get \"Resource interpreted as Image but\n",
" * transferred with MIME type text/plain:\" errors on\n",
" * Chrome. But how to set the MIME type? It doesn't seem\n",
" * to be part of the websocket stream */\n",
" evt.data.type = \"image/png\";\n",
"\n",
" /* Free the memory for the previous frames */\n",
" if (fig.imageObj.src) {\n",
" (window.URL || window.webkitURL).revokeObjectURL(\n",
" fig.imageObj.src);\n",
" }\n",
"\n",
" fig.imageObj.src = (window.URL || window.webkitURL).createObjectURL(\n",
" evt.data);\n",
" fig.updated_canvas_event();\n",
" fig.waiting = false;\n",
" return;\n",
" }\n",
" else if (typeof evt.data === 'string' && evt.data.slice(0, 21) == \"data:image/png;base64\") {\n",
" fig.imageObj.src = evt.data;\n",
" fig.updated_canvas_event();\n",
" fig.waiting = false;\n",
" return;\n",
" }\n",
"\n",
" var msg = JSON.parse(evt.data);\n",
" var msg_type = msg['type'];\n",
"\n",
" // Call the \"handle_{type}\" callback, which takes\n",
" // the figure and JSON message as its only arguments.\n",
" try {\n",
" var callback = fig[\"handle_\" + msg_type];\n",
" } catch (e) {\n",
" console.log(\"No handler for the '\" + msg_type + \"' message type: \", msg);\n",
" return;\n",
" }\n",
"\n",
" if (callback) {\n",
" try {\n",
" // console.log(\"Handling '\" + msg_type + \"' message: \", msg);\n",
" callback(fig, msg);\n",
" } catch (e) {\n",
" console.log(\"Exception inside the 'handler_\" + msg_type + \"' callback:\", e, e.stack, msg);\n",
" }\n",
" }\n",
" };\n",
"}\n",
"\n",
"// from http://stackoverflow.com/questions/1114465/getting-mouse-location-in-canvas\n",
"mpl.findpos = function(e) {\n",
" //this section is from http://www.quirksmode.org/js/events_properties.html\n",
" var targ;\n",
" if (!e)\n",
" e = window.event;\n",
" if (e.target)\n",
" targ = e.target;\n",
" else if (e.srcElement)\n",
" targ = e.srcElement;\n",
" if (targ.nodeType == 3) // defeat Safari bug\n",
" targ = targ.parentNode;\n",
"\n",
" // jQuery normalizes the pageX and pageY\n",
" // pageX,Y are the mouse positions relative to the document\n",
" // offset() returns the position of the element relative to the document\n",
" var x = e.pageX - $(targ).offset().left;\n",
" var y = e.pageY - $(targ).offset().top;\n",
"\n",
" return {\"x\": x, \"y\": y};\n",
"};\n",
"\n",
"/*\n",
" * return a copy of an object with only non-object keys\n",
" * we need this to avoid circular references\n",
" * http://stackoverflow.com/a/24161582/3208463\n",
" */\n",
"function simpleKeys (original) {\n",
" return Object.keys(original).reduce(function (obj, key) {\n",
" if (typeof original[key] !== 'object')\n",
" obj[key] = original[key]\n",
" return obj;\n",
" }, {});\n",
"}\n",
"\n",
"mpl.figure.prototype.mouse_event = function(event, name) {\n",
" var canvas_pos = mpl.findpos(event)\n",
"\n",
" if (name === 'button_press')\n",
" {\n",
" this.canvas.focus();\n",
" this.canvas_div.focus();\n",
" }\n",
"\n",
" var x = canvas_pos.x * mpl.ratio;\n",
" var y = canvas_pos.y * mpl.ratio;\n",
"\n",
" this.send_message(name, {x: x, y: y, button: event.button,\n",
" step: event.step,\n",
" guiEvent: simpleKeys(event)});\n",
"\n",
" /* This prevents the web browser from automatically changing to\n",
" * the text insertion cursor when the button is pressed. We want\n",
" * to control all of the cursor setting manually through the\n",
" * 'cursor' event from matplotlib */\n",
" event.preventDefault();\n",
" return false;\n",
"}\n",
"\n",
"mpl.figure.prototype._key_event_extra = function(event, name) {\n",
" // Handle any extra behaviour associated with a key event\n",
"}\n",
"\n",
"mpl.figure.prototype.key_event = function(event, name) {\n",
"\n",
" // Prevent repeat events\n",
" if (name == 'key_press')\n",
" {\n",
" if (event.which === this._key)\n",
" return;\n",
" else\n",
" this._key = event.which;\n",
" }\n",
" if (name == 'key_release')\n",
" this._key = null;\n",
"\n",
" var value = '';\n",
" if (event.ctrlKey && event.which != 17)\n",
" value += \"ctrl+\";\n",
" if (event.altKey && event.which != 18)\n",
" value += \"alt+\";\n",
" if (event.shiftKey && event.which != 16)\n",
" value += \"shift+\";\n",
"\n",
" value += 'k';\n",
" value += event.which.toString();\n",
"\n",
" this._key_event_extra(event, name);\n",
"\n",
" this.send_message(name, {key: value,\n",
" guiEvent: simpleKeys(event)});\n",
" return false;\n",
"}\n",
"\n",
"mpl.figure.prototype.toolbar_button_onclick = function(name) {\n",
" if (name == 'download') {\n",
" this.handle_save(this, null);\n",
" } else {\n",
" this.send_message(\"toolbar_button\", {name: name});\n",
" }\n",
"};\n",
"\n",
"mpl.figure.prototype.toolbar_button_onmouseover = function(tooltip) {\n",
" this.message.textContent = tooltip;\n",
"};\n",
"mpl.toolbar_items = [[\"Home\", \"Reset original view\", \"fa fa-home icon-home\", \"home\"], [\"Back\", \"Back to previous view\", \"fa fa-arrow-left icon-arrow-left\", \"back\"], [\"Forward\", \"Forward to next view\", \"fa fa-arrow-right icon-arrow-right\", \"forward\"], [\"\", \"\", \"\", \"\"], [\"Pan\", \"Pan axes with left mouse, zoom with right\", \"fa fa-arrows icon-move\", \"pan\"], [\"Zoom\", \"Zoom to rectangle\", \"fa fa-square-o icon-check-empty\", \"zoom\"], [\"\", \"\", \"\", \"\"], [\"Download\", \"Download plot\", \"fa fa-floppy-o icon-save\", \"download\"]];\n",
"\n",
"mpl.extensions = [\"eps\", \"jpeg\", \"pdf\", \"png\", \"ps\", \"raw\", \"svg\", \"tif\"];\n",
"\n",
"mpl.default_extension = \"png\";var comm_websocket_adapter = function(comm) {\n",
" // Create a \"websocket\"-like object which calls the given IPython comm\n",
" // object with the appropriate methods. Currently this is a non binary\n",
" // socket, so there is still some room for performance tuning.\n",
" var ws = {};\n",
"\n",
" ws.close = function() {\n",
" comm.close()\n",
" };\n",
" ws.send = function(m) {\n",
" //console.log('sending', m);\n",
" comm.send(m);\n",
" };\n",
" // Register the callback with on_msg.\n",
" comm.on_msg(function(msg) {\n",
" //console.log('receiving', msg['content']['data'], msg);\n",
" // Pass the mpl event to the overridden (by mpl) onmessage function.\n",
" ws.onmessage(msg['content']['data'])\n",
" });\n",
" return ws;\n",
"}\n",
"\n",
"mpl.mpl_figure_comm = function(comm, msg) {\n",
" // This is the function which gets called when the mpl process\n",
" // starts-up an IPython Comm through the \"matplotlib\" channel.\n",
"\n",
" var id = msg.content.data.id;\n",
" // Get hold of the div created by the display call when the Comm\n",
" // socket was opened in Python.\n",
" var element = $(\"#\" + id);\n",
" var ws_proxy = comm_websocket_adapter(comm)\n",
"\n",
" function ondownload(figure, format) {\n",
" window.open(figure.imageObj.src);\n",
" }\n",
"\n",
" var fig = new mpl.figure(id, ws_proxy,\n",
" ondownload,\n",
" element.get(0));\n",
"\n",
" // Call onopen now - mpl needs it, as it is assuming we've passed it a real\n",
" // web socket which is closed, not our websocket->open comm proxy.\n",
" ws_proxy.onopen();\n",
"\n",
" fig.parent_element = element.get(0);\n",
" fig.cell_info = mpl.find_output_cell(\"<div id='\" + id + \"'></div>\");\n",
" if (!fig.cell_info) {\n",
" console.error(\"Failed to find cell for figure\", id, fig);\n",
" return;\n",
" }\n",
"\n",
" var output_index = fig.cell_info[2]\n",
" var cell = fig.cell_info[0];\n",
"\n",
"};\n",
"\n",
"mpl.figure.prototype.handle_close = function(fig, msg) {\n",
" var width = fig.canvas.width/mpl.ratio\n",
" fig.root.unbind('remove')\n",
"\n",
" // Update the output cell to use the data from the current canvas.\n",
" fig.push_to_output();\n",
" var dataURL = fig.canvas.toDataURL();\n",
" // Re-enable the keyboard manager in IPython - without this line, in FF,\n",
" // the notebook keyboard shortcuts fail.\n",
" IPython.keyboard_manager.enable()\n",
" $(fig.parent_element).html('<img src=\"' + dataURL + '\" width=\"' + width + '\">');\n",
" fig.close_ws(fig, msg);\n",
"}\n",
"\n",
"mpl.figure.prototype.close_ws = function(fig, msg){\n",
" fig.send_message('closing', msg);\n",
" // fig.ws.close()\n",
"}\n",
"\n",
"mpl.figure.prototype.push_to_output = function(remove_interactive) {\n",
" // Turn the data on the canvas into data in the output cell.\n",
" var width = this.canvas.width/mpl.ratio\n",
" var dataURL = this.canvas.toDataURL();\n",
" this.cell_info[1]['text/html'] = '<img src=\"' + dataURL + '\" width=\"' + width + '\">';\n",
"}\n",
"\n",
"mpl.figure.prototype.updated_canvas_event = function() {\n",
" // Tell IPython that the notebook contents must change.\n",
" IPython.notebook.set_dirty(true);\n",
" this.send_message(\"ack\", {});\n",
" var fig = this;\n",
" // Wait a second, then push the new image to the DOM so\n",
" // that it is saved nicely (might be nice to debounce this).\n",
" setTimeout(function () { fig.push_to_output() }, 1000);\n",
"}\n",
"\n",
"mpl.figure.prototype._init_toolbar = function() {\n",
" var fig = this;\n",
"\n",
" var nav_element = $('<div/>')\n",
" nav_element.attr('style', 'width: 100%');\n",
" this.root.append(nav_element);\n",
"\n",
" // Define a callback function for later on.\n",
" function toolbar_event(event) {\n",
" return fig.toolbar_button_onclick(event['data']);\n",
" }\n",
" function toolbar_mouse_event(event) {\n",
" return fig.toolbar_button_onmouseover(event['data']);\n",
" }\n",
"\n",
" for(var toolbar_ind in mpl.toolbar_items){\n",
" var name = mpl.toolbar_items[toolbar_ind][0];\n",
" var tooltip = mpl.toolbar_items[toolbar_ind][1];\n",
" var image = mpl.toolbar_items[toolbar_ind][2];\n",
" var method_name = mpl.toolbar_items[toolbar_ind][3];\n",
"\n",
" if (!name) { continue; };\n",
"\n",
" var button = $('<button class=\"btn btn-default\" href=\"#\" title=\"' + name + '\"><i class=\"fa ' + image + ' fa-lg\"></i></button>');\n",
" button.click(method_name, toolbar_event);\n",
" button.mouseover(tooltip, toolbar_mouse_event);\n",
" nav_element.append(button);\n",
" }\n",
"\n",
" // Add the status bar.\n",
" var status_bar = $('<span class=\"mpl-message\" style=\"text-align:right; float: right;\"/>');\n",
" nav_element.append(status_bar);\n",
" this.message = status_bar[0];\n",
"\n",
" // Add the close button to the window.\n",
" var buttongrp = $('<div class=\"btn-group inline pull-right\"></div>');\n",
" var button = $('<button class=\"btn btn-mini btn-primary\" href=\"#\" title=\"Stop Interaction\"><i class=\"fa fa-power-off icon-remove icon-large\"></i></button>');\n",
" button.click(function (evt) { fig.handle_close(fig, {}); } );\n",
" button.mouseover('Stop Interaction', toolbar_mouse_event);\n",
" buttongrp.append(button);\n",
" var titlebar = this.root.find($('.ui-dialog-titlebar'));\n",
" titlebar.prepend(buttongrp);\n",
"}\n",
"\n",
"mpl.figure.prototype._root_extra_style = function(el){\n",
" var fig = this\n",
" el.on(\"remove\", function(){\n",
"\tfig.close_ws(fig, {});\n",
" });\n",
"}\n",
"\n",
"mpl.figure.prototype._canvas_extra_style = function(el){\n",
" // this is important to make the div 'focusable\n",
" el.attr('tabindex', 0)\n",
" // reach out to IPython and tell the keyboard manager to turn it's self\n",
" // off when our div gets focus\n",
"\n",
" // location in version 3\n",
" if (IPython.notebook.keyboard_manager) {\n",
" IPython.notebook.keyboard_manager.register_events(el);\n",
" }\n",
" else {\n",
" // location in version 2\n",
" IPython.keyboard_manager.register_events(el);\n",
" }\n",
"\n",
"}\n",
"\n",
"mpl.figure.prototype._key_event_extra = function(event, name) {\n",
" var manager = IPython.notebook.keyboard_manager;\n",
" if (!manager)\n",
" manager = IPython.keyboard_manager;\n",
"\n",
" // Check for shift+enter\n",
" if (event.shiftKey && event.which == 13) {\n",
" this.canvas_div.blur();\n",
" event.shiftKey = false;\n",
" // Send a \"J\" for go to next cell\n",
" event.which = 74;\n",
" event.keyCode = 74;\n",
" manager.command_mode();\n",
" manager.handle_keydown(event);\n",
" }\n",
"}\n",
"\n",
"mpl.figure.prototype.handle_save = function(fig, msg) {\n",
" fig.ondownload(fig, null);\n",
"}\n",
"\n",
"\n",
"mpl.find_output_cell = function(html_output) {\n",
" // Return the cell and output element which can be found *uniquely* in the notebook.\n",
" // Note - this is a bit hacky, but it is done because the \"notebook_saving.Notebook\"\n",
" // IPython event is triggered only after the cells have been serialised, which for\n",
" // our purposes (turning an active figure into a static one), is too late.\n",
" var cells = IPython.notebook.get_cells();\n",
" var ncells = cells.length;\n",
" for (var i=0; i<ncells; i++) {\n",
" var cell = cells[i];\n",
" if (cell.cell_type === 'code'){\n",
" for (var j=0; j<cell.output_area.outputs.length; j++) {\n",
" var data = cell.output_area.outputs[j];\n",
" if (data.data) {\n",
" // IPython >= 3 moved mimebundle to data attribute of output\n",
" data = data.data;\n",
" }\n",
" if (data['text/html'] == html_output) {\n",
" return [cell, data, j];\n",
" }\n",
" }\n",
" }\n",
" }\n",
"}\n",
"\n",
"// Register the function which deals with the matplotlib target/channel.\n",
"// The kernel may be null if the page has been refreshed.\n",
"if (IPython.notebook.kernel != null) {\n",
" IPython.notebook.kernel.comm_manager.register_target('matplotlib', mpl.mpl_figure_comm);\n",
"}\n"
],
"text/plain": [
"<IPython.core.display.Javascript object>"
]
},
"metadata": {},
"output_type": "display_data"
},
{
"data": {
"text/html": [
"<img src=\"data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAABEwAAAImCAYAAABJvh+8AAAgAElEQVR4XuzdC5xVVfn/8WcGHEEDVEQFQchLKgKapqWCifzwbirmL/9mCt7wlkl44ScJeAEKvBGpQCKYJlpA5j3xggUqppkiWCZeQMUyQC4iyGX+r++yM80MM3PWOWcO59nnfPbrxQuZWfvstd5rjeuZZ6+9dlllZWWlcSCAAAIIIIAAAggggAACCCCAAAIIVAmUkTBhNCCAAAIIIIAAAggggAACCCCAAAI1BUiYMCISKVBWVpZxvc866yybPHlyxudlcsJ3v/tdmzZtmv32t781/Xeuxy9+8Qv74Q9/aBdffLHpvzk2j8Dll19uN910k40ePdr03xwIIIAAAghkIkCckolWfsu+8cYb1rVrV9tnn31M/82BAAIIZCJAwiQTLcq6Eejbt+8mdfn444/tD3/4g2299dZ1Jiu6d+9u5557bl7bQMIkr7xpP/yRRx6xE044wY477jjTf2d7kDDJVo7zEEAAAQQkQJziZxyQMPHTF9QEgSQKkDBJYq9R5zoFZs6caT179rSOHTvae++9VxCljz76yFasWGE777yztWjRIuc6fPrpp6ZE0Lbbbms77rhjzp9X7B/QWAmTTz75xJYsWWI77LCDbbfddsXORvsQQAABBDaDAHHKZkCu4xIkTArjzlURKBYBEibF0pO0wzwEInRDYQUaK2FS2FZwdQQQQACBYhQgTilMr5IwKYw7V0WgWARImBRLT9KO6IRJ9X1BrrnmGhs2bJg99thjtnjxYvvf//1fu/fee00vj/rNb34Tvv7SSy+ZVo588cUXtssuu9jRRx9tgwYNsrZt226iXt8jOdUf8fh//+//ma77+OOP29KlS61Dhw52+umn209+8hOrqKio8Zn17WFSPTEwdepUGzlypN133322cOHCsBpFddTX6qqjLvDrX//afv7zn4dnebfccks7+OCDbciQIaaVFZk+0lK9Lg888IBdd911YQ8XmbVv39769+8f9gHR89zvvPNO8J4xY4YtW7bM9tprr9Du+vZ7Wbt2rU2YMMGmTJli8+fPtzVr1oQVRCeddFLoA7U1dXzjG9+wV155pc6fhOqP6KTK/fnPfw51uPHGG+3ll18OfaF6/c///E+ob0N7mLz++ut26623hjGndm611VZVY+PSSy+1du3a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sLZt29q6devspZdesl//+te2++6728svv2xbb711RmoenTJqAIURQAABBBAooICneZSESQEHApdGAIEvBTZuXGMrl88KfzZsWGFNmrS0Fq26hz/l5c1gQgABJwKeAhiRKCnSqVMna9mypc2bNy/8rWPhwoXWuXNnO+igg+yZZ57JSk8rVq666iq7++677cwzz8zoM7w5ZVR5CiOAAAIIIFBgAU/zKAmTAg8GLo9AqQt8sfYj+3DhcNuwfomZlZmZFr19+XeTpq1t510GW8WW7UqdifYj4ELAUwAjkMmTJ1u/fv1s2LBhNnTo0BpGffv2DckOJU86dOiQsd9vfvMb+973vmdjxoyxSy+9NKPzvTllVHkKI4AAAgggUGABT/MoCZMCDwYuj0ApC2hlyfsLBtqG9Uv/kyiprVFmTZpuZx13u4mVJqU8UGi7GwFPAYxQLrzwwrD/yJNPPmm9e/eu4TRhwoTwiM60adOsT58+aQ1XrVoVHufR33/5y19s4MCB9uGHH4a9Tfbaa6+051cv4M0po8pTGAEEEEAAgQILeJpHSZgUeDBweQRKWWD5sqfsk4/vTEvQZqfzrNW2vdKWowACCORXwFMAo5aecMIJ9sgjj9j8+fNt7733rtH4xx57zI477rjoFSKpFSmpD9lnn33sxhtvtKOPPjot6ooVK0x/UsfixYvD40CLFi2y9u3bpz2fAggggAACCCDwXwFP8QYJE0YmAggUTOCD94bZms//Xs/qklS1yqxZ872sfaeay+0LVmkujEAJC3gKYNQNvXr1CnuULFiwwHbdddcaPaOv6/sjR460QYMGpe01JV0++ugjW7JkSdgs9k9/+pNdfvnl9v3vfz/tuXok6Nprr92kHAmTtHQUQAABBBBAYBMBT/EGCRMGKAIIFEzg/QU/tnVffJT2+ltUtLOOu92cthwFEEAgvwKeAhi1tDFXmNSW06M8erXwlClT7LTTTmsQlhUm+R13fDoCCCCAQGkJeIo3SJiU1tijtQi4EmCFiavuoDIIpBXwFMCoso25h0ntxldWVlqrVq1s//33t5kzZ6a1qV7Am1NGlacwAggggAACBRbwNI+SMCnwYODyCJSyAHuYlHLv0/YkCngKYOQ3adIkO/vss8PjMEOGDKlBqrfn6C062b4lZ/369faVr3zF9txzT3vttdcy6i5vThlVnsIIIIAAAggUWMDTPErCpMCDgcsjUMoCvCWnlHuftidRwFMAI79ly5ZZx44dw0qQefPmWcuWLQOrkiSdO3e2Aw880J599tnwtdWrV4evq2zbtm2r+D/++GPbaaedNumOsWPHhtcJ6007ehNPJoc3p0zqTlkEEEAAAQQKLeBpHiVhUujRwPURKHGBL9Z+ZB8uHG4b1i8xs7L/bAD75d9Nmra2nXcZbBVbtitxJZqPgA8BTwFMSmT8+PF2wQUXWJcuXUJyY+3ataZkhzZvnTVrlu27776hqB6r6dmzp5111llh5Unq2H777e2QQw6xAw44ILzRZunSpaGs3rLTqVMne/7552skWGJ6wqNTTL0pgwACCCCAgAcBT/MoCRMPI4I6IFDiAlppsnL5bFu5fJZt2LDcmjRpZS1adbcWrQ618vJmJa5D8xHwI+ApgKmuMnXqVBs9erTNnTvXKioqrEePHjZ8+HDr1q1bVbH6EibXX3+9zZgxw956662QZNlyyy1tjz32CBvKDhgwwLbddtuMO8CrU8YN4QQEEEAAAQQKIOBpHiVhUoABwCURQAABBBBIooCnAMazH06ee4e6IYAAAgh4F/A0j5Iw8T5aqB8CCCCAAAJOBDwFME5I6qwGTp57h7ohgAACCHgX8DSPkjDxPlqoHwIIIIAAAk4EPAUwTkhImHjuCOqGAAIIIJBIAU/xBgmTRA4hKo0AAggggMDmF/AUwGz+1sdfEad4K0oigAACCCBQW8DTPErChPGJAAIIIIAAAlECngKYqAoXqBBOBYLnsggggAACRSHgaR4lYVIUQ4pGIIAAAgggkH8BTwFM/lub/RVwyt6OMxFAAAEEEPA0j5IwYTwigAACCCCAQJSApwAmqsIFKoRTgeC5LAIIIIBAUQh4mkczSphMnz7dRo0aZXPnzrWKigrr0aOHjRgxwrp06RLVMc8991woP2fOHFu7dq3tsccedv7559tFF11k5eXlDX7GFVdcYTfeeKM1adLE1q9fH3W92oU8wWfVAE5CAAEEEECggALMo3H4OMU5UQoBBBBAAIG6BDzNo9EJk4kTJ9q5554bkiP9+/e3NWvW2NixY23ZsmU2e/Zs69q1a4O9/cADD9jpp59u22+/fUiQtGnTxmbMmGEPPvhg+Pdtt91W7/l//vOf7eCDD7bmzZvb559/TsKEnysEEEAAAQQKIOApgClA86MviVM0FQURQAABBBDYRMDTPBqVMFFSpFOnTtayZUubN29e+FvHwoULrXPnznbQQQfZM888U29Xa0VIu3btbNWqVfbGG2/YrrvuWlVWyZcJEyaEpMshhxyyyWesW7fODjjgANttt91CcmbWrFkkTPihQgABBBBAoAACngKYAjQ/+pI4RVNREAEEEEAAgeQnTCZPnmz9+vWzYcOG2dChQ2s0qG/fvnb33XeH5EmHDh3q7O5XX33V9t9/fzvqqKPsiSeeqFHmhRdeCImSc845x+68885Nzr/uuuvspptusvnz59v3v/99Eib8QCGAAAIIIFAgARIBcfA4xTlRCgEEEEAAgboEPM2jUStMLrzwQhs3bpw9+eST1rt37xpt0uoQrRKZNm2a9enTp84ef/HFF8MjNSeffLJpH5Tqx2uvvWb77bef7bPPPmH1SfVDq1mUaLn55pvt4osvtsMPP5yECT9TCCCAAAIIFEjAUwBTIIKoy+IUxUQhBBBAAAEE6hTwNI9GJUxOOOEEe+SRR8Iqj7333rtGox577DE77rjjbMyYMXbppZfW2eBPP/007F2yww472IIFC8JeJKnj1ltvtQEDBliLFi1sxYoVVV/fuHFjWHmizWD1GI7+zjRhos+r/pmLFy8Ojw8tWrTI2rdvz/BEAAEEEEAAgQwEPAUwGVR7sxfFabOTc0EEEEAAgSIS8DSPRiVMevXqFfYoUbKj+v4j6hN9Xd8fOXKkDRo0qN5uuuCCC2z8+PF29NFHmx6zUQLlqaeesssvv9xWr15tlZWVNfYmueWWW+yqq64yPc6j1Sc6Mk2Y6BGia6+9dpM6kTApop8mmoIAAgggsNkEPAUwm63RWVwIpyzQOAUBBBBAAIH/CHiaR6MSJrmuMFG79RrhgQMHhg1etZGrDm0eq8SIEi3aGHbp0qXh6++88054645Wntxwww1VAyfThAkrTPiZQwABBBBAoPEEPAUwjdeqxv8knBrflE9EAAEEECgdAU/zaFTCJNc9TKp3rZIY2pukrKzM9t13X9uwYUNInGiPE70pR8eJJ55oc+bMCStQmjVrVnW6Nn195ZVX7G9/+5s1bdo0vLknk8MTfCb1piwCCCCAAAIeBJhH43oBpzgnSiGAAAIIIFCXgKd5NCphMmnSJDv77LPD4y1Dhgyp0Sa9PUdv0WnoLTkNDYOpU6faqaeeGlaSDB48OBTVJrDaDLahY8cdd7SPP/44oxHmCT6jilMYAQQQQAABBwLMo3GdgFOcE6UQQAABBBAoioTJsmXLrGPHjtaqVauwOkQrQnQoSdK5c2c78MAD7dlnnw1f034k+rrKtm3btsERsGTJkrCyRJvCvvnmm9a6detQXitL9LXah15prNUlDzzwQFh5cvzxx2c0wghgMuKiMAIIIIAAAjUEmEfjBgROcU6UQgABBBBAoCgSJmqENmzVxq1dunQJrxHWniRjx441JT30Fhs9XqNj5syZ1rNnTzvrrLPCypPUMWXKlPBv7UOi1SHvvvuuTZw4MbzF5uGHHw7npDsy3cOk9ucRwKQT5vsIIIAAAgjUL8A8Gjc6cIpzohQCCCCAAAJFkzBRQ/T4zOjRo23u3LlWUVFhPXr0sOHDh1u3bt2q2llfwkR7j2hz19dff920YqVNmzZ25JFHhsdwdt9996iRQsIkiolCCCCAAAII5EWAREAcK05xTpRCAAEEEECgqBImSe9OApik9yD1RwABBBAopADzaJw+TnFOlEIAAQQQQICEiaMxQADjqDOoCgIIIIBA4gSYR+O6DKc4J0ohgAACCCBAwsTRGCCAcdQZVAUBBBBAIHECzKNxXYZTnBOlEEAAAQQQIGHiaAwQwDjqDKqCAAIIIJA4AebRuC7DKc6JUggggAACCJAwcTQGCGAcdQZVQQABBBBInEBjz6NffPGF3X///eGNe3369LGOHTsmzsR7oFcUoDQCAQQQQKCkBBo73sgFr6yysrIylw9I0rme4JPkRl0RQAABBBCQQC7z6IABA+yZZ56x1157LWBu3LjRDj30UHvppZdMoUirVq1szpw59rWvfS3x2Lk4Jb7xNAABBBBAAIEcBTzNoyRMcuxMTkcAAQQQQKBUBHIJYLp162a9e/e2m266KXD97ne/s1NOOcUGDRpkX//61+2iiy6yE0880e68887Ec+bilPjG0wAEEEAAAQRyFPA0j5IwybEzOR0BBBBAAIFSEcglgNluu+3shhtuCIkRHeedd549/fTT9s4774R//+QnP7H77ruv6t9JNs3FKcntpu4IIIAAAgg0hoCneZSESWP0KJ+BAAIIIIBACQjkEsBstdVW9vOf/9zOPffcILXXXntZ9+7dq1aUTJo0yS6++GJbvXp14iVzcUp842kAAggggAACOQp4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FAKe5lESJkUxpGgEAggggAAC+RfwFMDkv7XZXwGn7O04EwEEEEAAAU/zaEYJk+nTp9uoUaNs7ty5VlFRYT169LARI0ZYly5donr1ueeeC+XnzJlja9eutT322MPOP/98u+iii6y8vLzqM5YtW2b33nuvPf744zZv3jz75z//aW3btrUDDjjABg8ebF//+tejrle7kCf4rBrASQgggAACCBRQgHk0Dh+nOCdKIYAAAgggUJeAp3k0OmEyceJEO/fcc0NypH///rZmzRobO3asKbkxe/Zs69q1a4O9/cADD9jpp59u22+/fUiQtGnTxmbMmGEPPvhg+Pdtt91Wdf4TTzxhxx13nB1xxBHhzw477GD/+Mc/bNy4cbZixQq777777LTTTst4dHmCz7jynIAAAggggECBBZhH4zoApzgnSiGAAAIIIFAUCRMlRTp16mQtW7YMKz70t46FCxda586d7aCDDrJnnnmm3t5ev369tWvXzlatWmVvvPGG7brrrlVllXyZMGFCSLoccsgh4evvvfee6Zzdd9+9xmfq3P3339+22247++ijj2qsSokZagQwMUqUQQABBBBAoG4B5tG4kYFTnBOlEEAAAQQQKIqEyeTJk61fv342bNgwGzp0aI029e3b1+6+++6QPOnQoUOdPf7qq6+GRMdRRx1lWj1S/XjhhRdCouScc86xO++8M+2I0efo8xYvXmw77bRT2vLVCxDAZMRFYQQQQAABBGoIMI/GDQic4pwohQACCCCAQFEkTC688MLwOMyTTz5pvXv3rtEmrQ7RKpFp06ZZnz596uzxF1980Q4++GA7+eSTTfugVD9ee+0122+//WyfffYJq08aOjZu3Gjt27e3JUuW2PLly61Zs2YZjTACmIy4KIwAAggggEAiEia57LH28MMP2+9//3vTDRzd/FFsoRWu5513np155pnWtGnTjEcB8UbGZJyAAAIIIIBAlYCneTRqD5MTTjjBHnnkEZs/f77tvffeNbryscceC/uNjBkzxi699NI6u/nTTz8Ne5doL5IFCxZY8+bNq8rdeuutNmDAAGvRokXYn6ShQ3um6BpnnXWWadVLukOfV/0ztSpFjw8tWrQoJF44EEAAAQQQQCBewFMAk6p1rnusabXq1ltvbSeddFKIcXRD5v7777eXX37Zjj322BD/lJWVxSOZmUenjBpAYQQQQAABBAoo4GkejUqY9OrVK+xRomRH9f1HZKiv6/sjR460QYMG1ct6wQUX2Pjx4+3oo4+26667LiRQnnrqKbv88stt9erVVllZGfYtqe+YOXNmeKRn5513DkGM9jFJd+gRomuvvXaTYiRM0snxfQQQQAABBDYV8BTAqHa57rGmz3j66afDBvPVkyIbNmywww8/3GbNmmWPPvpoSJxkcnhzyqTulEUAAQQQQKDQAp7m0aiESa4rTASu1wgPHDgwbPC6bt260AfaPPaWW24JiRYlS5YuXVpn32hDWCVaWrVqZUqc1N4Mtr4OZYVJoYc610cAAQQQKCYBTwGMXHPdY62hvvn5z39uP/rRj9LeEKrrM7w5FdMYpC0IIIAAAsUv4GkejUqY5LqHSfUuVRJDb9rRnZx9993XdBdHiRPtcaLESO3jj3/8Y3jkZ9tttw2rWWKTJQQwxf92icGPAAAgAElEQVSDRAsRQAABBDavgKcARi1vzPiktuRVV11lo0aNsrvuuitsfJ/J4c0pk7pTFgEEEEAAgUILeJpHoxImkyZNsrPPPjs83jJkyJAafgoidIenobfkNAQ+depUO/XUU+2GG26wwYMH1yj67LPP2vHHHx/2PlGy5Ktf/WpOfecJPqeGcDICCCCAAAIFEPA2jzbGCtj6brB06dLFysvL7e233077GDArWgswGLkkAggggEDRCniKN6ISJnpGuGPHjuGRGK0O0YoQHUqSdO7c2Q488EBTckOH9iPR11W2bdu2DXai3najlSXaFPbNN9+01q1bV5XXM8UKhNq1axeSJbvsskvOA8ITfM6N4QMQQAABBBDYzALe5tHG2GOtNuGqVavs29/+tr366qvhDYB6w1+6gz3T0gnxfQQQQAABBOIFPMUbUQkTNU0btmrjVt1x0WuEtSeJ3lqjpIc2RdPjNTq0x0jPnj03eZPNlClTwkoUbaK244472rvvvmva2V53ZfRKP52TOrSp62GHHRb2OtHKE230WvtQAKNd7TM5PMFnUm/KIoAAAggg4EHA2zza2CtMlCzRBq+Ka37xi1/YRRddFMXOCpMoJgohgAACCCAQJeAp3ohOmKhlenxm9OjRNnfuXKuoqLAePXrY8OHDrVu3blUNry9h8sorr4TNXV9//fWwq32bNm3syCOPDI/h1N6XJLWJW0OaSrh06tQpCjxVyBN8RhWnMAIIIIAAAg4EvM2jjbmHycqVK+2YY46x559/3u64445wcyjbw5tTtu3gPAQQQAABBAoh4GkezShhUgisxrymJ/jGbBefhQACCCCAwOYQ8DaPNtYea8uXLw9v43vppZfsl7/8Zdi3LZfDm1MubeFcBBBAAAEENreAp3mUhMnm7n2uhwACCCCAQEIFPAUwImyMPdaULNGKV62EVQLmBz/4Qc69480p5wbxAQgggAACCGxGAU/zKAmTzdjxXAoBBBBAAIEkC3gKYFKOue6xpo3rtXfaiSeeaN/97nc36R49dlz90eOY/vPoFFNvyiCAAAIIIOBBwNM8SsLEw4igDggggAACCCRAwFMAU50rlz3WysrKGpQfOnSo6S04mRxenTJpA2URQAABBBAolICneZSESaFGAddFAAEEEEAgYQKeAhjPdDh57h3qhgACCCDgXcDTPErCxPtooX4IIIAAAgg4EfAUwDghqbMaOHnuHepWygIbN66xlctnhT8bNqywJk1aWotW3cOf8vJmpUxD2xFwJeBpHiVh4mpoUBkEEEAAAQT8CngKYPwqmeHkuXeoW6kKfLH2I/tw4XDbsH6JmelRvMqqv5s0bW077zLYKrZsV6o8tBsBVwKe5lESJq6GBpVBAAEEEEDAr4CnAMavEgkTz31D3UpTQCtL3l8w0DasX/qfRElthzJr0nQ767jbTaw0Kc0hQqudCXiKN0iYOBscVAcBBBBAAAGvAp4CGK9GqhdOnnuHupWiwPJlT9knH9+ZtultdjrPWm3bK205CiCAQH4FPM2jJEzy29d8OgIIIIAAAkUj4CmA8YyKk+feoW6lKPDBe8Nszed/r2d1SUqkzJo138vadxpaikS0GQFXAp7mURImroYGlUEAAQQQQMCvgKcAxq8SK0w89w11K02B9xf82NZ98VHaxm9R0c467nZz2nIUQACB/Ap4ijdImOS3r/l0BBBAAAEEikbAUwDjGRUnz71D3UpRgBUmpdjrtDnJAp7mURImSR5J1B0BBBBAAIHNKOApgNmMzc74UjhlTMYJCORVgD1M8srLhyPQ6AKe5lESJo3evXwgAggggAACxSngKYDxLIyT596hbqUowFtySrHXaXOSBTzNoyRMkjySqDsCCCCAAAKbUcBTALMZm53xpXDKmIwTEMi7wBdrP7IPFw63DeuXmFnZfzaA/fLvJk1b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Lg1grDIcUiKcQoLntggggAACkRBwqR8lYBKJJkUhEEAAAQQQSL+ASwOY9Jc2+B1wCm7HmQgggAACCLjUjxIwoT0igAACCCCAgJWASwMYqwyHlAinkOC5LQIIIIBAJARc6kcJmESiSVEIBBBAAAEE0i/g0gAm/aUNfgecgttxJgIIIIAAAi71o74CJrNmzZI777xTli1bJvn5+dK5c2cZPXq0tGrVyqpWly5datK/8847sn79ejnwwAOlXbt2cv3118uJJ54Yd419+/bJ448/LhMmTJBPPvlEduzYIU2aNJFevXrJoEGDpEGDBlb3jE3kErzvzHMCAggggAACIQvQj9pVAE52TqRCAAEEEEAgkYBL/ah1wGTy5Mly6aWXmuDIgAEDTABj/PjxsnHjRlmwYIG0bt26zNpeuHChdOnSRerVqyeXXXaZCX6sXr1aHn74Yfn666/lxRdflFNPPbX4Gtddd53cc8890rVrVxMkqVatmrz11lvy2GOPyaGHHipLliyRGjVq+GphLsH7yjiJEUAAAQQQcECAftSuEnCycyIVAggggAACkQiYaFCkWbNmUlBQIMuXLzd/6/H555CTC8AAACAASURBVJ9Ly5YtpUOHDjJnzpwya7tPnz7yxBNPmNkpsTNSFi1aZGaZnHPOOfL000+ba2zfvl32339/OfbYY+Xdd9+VvLy84mtfc801JlAze/Zs6dGjh68WxgDGFxeJEUAAAQQQiBOgH7VrEDjZOZEKAQQQQACBSARMpk6dKv3795cRI0bI8OHD48rUr18/mTZtmgme6KyRZEfPnj1NkKOwsFDq1q1bnOyrr76Sgw8+WC666CJzHT00Tf369UXPefbZZ+Mueccdd8jQoUNNgEZnn/g5GMD40SItAggggAAC8QL0o3YtAic7J1IhgAACCCAQiYDJwIEDzV4ir7zyinTv3j2uTLqkRpfozJw5U3r37p20xu+//365+uqr5bTTTpORI0cWL8n505/+JDrL5PXXX49b1nPcccfJ4sWLzZ4nel3dM0WX5Fx11VWi/6ZLeCpVquSrhTGA8cVFYgQQQAABBOIE6EftGgROdk6kQgABBBBAIBIBE292yIoVK+Soo46KK9MLL7xglsbce++9ostlkh179+6VW265RTRwsnXr1uJkujxHN5M97LDD4k5dtWqVmdUyb968uJ9feeWVZm+T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\" width=\"999.9999783255842\">"
],
"text/plain": [
"<IPython.core.display.HTML object>"
]
},
"metadata": {},
"output_type": "display_data"
},
{
"name": "stdout",
"output_type": "stream",
"text": [
"1 rows affected.\n",
"1 rows affected.\n",
"1 rows affected.\n",
"1 rows affected.\n",
"1 rows affected.\n",
"1 rows affected.\n",
"1 rows affected.\n",
"1 rows affected.\n"
]
}
],
"source": [
"#df_results = %sql SELECT * FROM $results_table ORDER BY run_id;\n",
"df_results = %sql SELECT * FROM $results_table ORDER BY training_loss ASC LIMIT 12;\n",
"df_results = df_results.DataFrame()\n",
"\n",
"#set up plots\n",
"fig, axs = plt.subplots(nrows=1, ncols=2, figsize=(10,5))\n",
"fig.legend(ncol=4)\n",
"fig.tight_layout()\n",
"\n",
"ax_metric = axs[0]\n",
"ax_loss = axs[1]\n",
"\n",
"ax_metric.xaxis.set_major_locator(MaxNLocator(integer=True))\n",
"ax_metric.set_xlabel('Iteration')\n",
"ax_metric.set_ylabel('Metric')\n",
"ax_metric.set_title('Training metric curve')\n",
"\n",
"ax_loss.xaxis.set_major_locator(MaxNLocator(integer=True))\n",
"ax_loss.set_xlabel('Iteration')\n",
"ax_loss.set_ylabel('Loss')\n",
"ax_loss.set_title('Training loss curve')\n",
"\n",
"for run_id in df_results['run_id']:\n",
" df_output_info = %sql SELECT training_metrics,training_loss FROM $results_table WHERE run_id = $run_id\n",
" df_output_info = df_output_info.DataFrame()\n",
" training_metrics = df_output_info['training_metrics'][0]\n",
" training_loss = df_output_info['training_loss'][0]\n",
" X = range(len(training_metrics))\n",
" \n",
" ax_metric.plot(X, training_metrics, label=run_id, marker='o')\n",
" ax_loss.plot(X, training_loss, label=run_id, marker='o')\n",
"\n",
"# fig.savefig('./lc_keras_fit.png', dpi = 300)"
]
},
{
"cell_type": "markdown",
"metadata": {},
"source": [
"Validation dataset"
]
},
{
"cell_type": "code",
"execution_count": 31,
"metadata": {},
"outputs": [
{
"name": "stdout",
"output_type": "stream",
"text": [
"8 rows affected.\n"
]
},
{
"data": {
"application/javascript": [
"/* Put everything inside the global mpl namespace */\n",
"window.mpl = {};\n",
"\n",
"\n",
"mpl.get_websocket_type = function() {\n",
" if (typeof(WebSocket) !== 'undefined') {\n",
" return WebSocket;\n",
" } else if (typeof(MozWebSocket) !== 'undefined') {\n",
" return MozWebSocket;\n",
" } else {\n",
" alert('Your browser does not have WebSocket support.' +\n",
" 'Please try Chrome, Safari or Firefox ≥ 6. ' +\n",
" 'Firefox 4 and 5 are also supported but you ' +\n",
" 'have to enable WebSockets in about:config.');\n",
" };\n",
"}\n",
"\n",
"mpl.figure = function(figure_id, websocket, ondownload, parent_element) {\n",
" this.id = figure_id;\n",
"\n",
" this.ws = websocket;\n",
"\n",
" this.supports_binary = (this.ws.binaryType != undefined);\n",
"\n",
" if (!this.supports_binary) {\n",
" var warnings = document.getElementById(\"mpl-warnings\");\n",
" if (warnings) {\n",
" warnings.style.display = 'block';\n",
" warnings.textContent = (\n",
" \"This browser does not support binary websocket messages. \" +\n",
" \"Performance may be slow.\");\n",
" }\n",
" }\n",
"\n",
" this.imageObj = new Image();\n",
"\n",
" this.context = undefined;\n",
" this.message = undefined;\n",
" this.canvas = undefined;\n",
" this.rubberband_canvas = undefined;\n",
" this.rubberband_context = undefined;\n",
" this.format_dropdown = undefined;\n",
"\n",
" this.image_mode = 'full';\n",
"\n",
" this.root = $('<div/>');\n",
" this._root_extra_style(this.root)\n",
" this.root.attr('style', 'display: inline-block');\n",
"\n",
" $(parent_element).append(this.root);\n",
"\n",
" this._init_header(this);\n",
" this._init_canvas(this);\n",
" this._init_toolbar(this);\n",
"\n",
" var fig = this;\n",
"\n",
" this.waiting = false;\n",
"\n",
" this.ws.onopen = function () {\n",
" fig.send_message(\"supports_binary\", {value: fig.supports_binary});\n",
" fig.send_message(\"send_image_mode\", {});\n",
" if (mpl.ratio != 1) {\n",
" fig.send_message(\"set_dpi_ratio\", {'dpi_ratio': mpl.ratio});\n",
" }\n",
" fig.send_message(\"refresh\", {});\n",
" }\n",
"\n",
" this.imageObj.onload = function() {\n",
" if (fig.image_mode == 'full') {\n",
" // Full images could contain transparency (where diff images\n",
" // almost always do), so we need to clear the canvas so that\n",
" // there is no ghosting.\n",
" fig.context.clearRect(0, 0, fig.canvas.width, fig.canvas.height);\n",
" }\n",
" fig.context.drawImage(fig.imageObj, 0, 0);\n",
" };\n",
"\n",
" this.imageObj.onunload = function() {\n",
" fig.ws.close();\n",
" }\n",
"\n",
" this.ws.onmessage = this._make_on_message_function(this);\n",
"\n",
" this.ondownload = ondownload;\n",
"}\n",
"\n",
"mpl.figure.prototype._init_header = function() {\n",
" var titlebar = $(\n",
" '<div class=\"ui-dialog-titlebar ui-widget-header ui-corner-all ' +\n",
" 'ui-helper-clearfix\"/>');\n",
" var titletext = $(\n",
" '<div class=\"ui-dialog-title\" style=\"width: 100%; ' +\n",
" 'text-align: center; padding: 3px;\"/>');\n",
" titlebar.append(titletext)\n",
" this.root.append(titlebar);\n",
" this.header = titletext[0];\n",
"}\n",
"\n",
"\n",
"\n",
"mpl.figure.prototype._canvas_extra_style = function(canvas_div) {\n",
"\n",
"}\n",
"\n",
"\n",
"mpl.figure.prototype._root_extra_style = function(canvas_div) {\n",
"\n",
"}\n",
"\n",
"mpl.figure.prototype._init_canvas = function() {\n",
" var fig = this;\n",
"\n",
" var canvas_div = $('<div/>');\n",
"\n",
" canvas_div.attr('style', 'position: relative; clear: both; outline: 0');\n",
"\n",
" function canvas_keyboard_event(event) {\n",
" return fig.key_event(event, event['data']);\n",
" }\n",
"\n",
" canvas_div.keydown('key_press', canvas_keyboard_event);\n",
" canvas_div.keyup('key_release', canvas_keyboard_event);\n",
" this.canvas_div = canvas_div\n",
" this._canvas_extra_style(canvas_div)\n",
" this.root.append(canvas_div);\n",
"\n",
" var canvas = $('<canvas/>');\n",
" canvas.addClass('mpl-canvas');\n",
" canvas.attr('style', \"left: 0; top: 0; z-index: 0; outline: 0\")\n",
"\n",
" this.canvas = canvas[0];\n",
" this.context = canvas[0].getContext(\"2d\");\n",
"\n",
" var backingStore = this.context.backingStorePixelRatio ||\n",
"\tthis.context.webkitBackingStorePixelRatio ||\n",
"\tthis.context.mozBackingStorePixelRatio ||\n",
"\tthis.context.msBackingStorePixelRatio ||\n",
"\tthis.context.oBackingStorePixelRatio ||\n",
"\tthis.context.backingStorePixelRatio || 1;\n",
"\n",
" mpl.ratio = (window.devicePixelRatio || 1) / backingStore;\n",
"\n",
" var rubberband = $('<canvas/>');\n",
" rubberband.attr('style', \"position: absolute; left: 0; top: 0; z-index: 1;\")\n",
"\n",
" var pass_mouse_events = true;\n",
"\n",
" canvas_div.resizable({\n",
" start: function(event, ui) {\n",
" pass_mouse_events = false;\n",
" },\n",
" resize: function(event, ui) {\n",
" fig.request_resize(ui.size.width, ui.size.height);\n",
" },\n",
" stop: function(event, ui) {\n",
" pass_mouse_events = true;\n",
" fig.request_resize(ui.size.width, ui.size.height);\n",
" },\n",
" });\n",
"\n",
" function mouse_event_fn(event) {\n",
" if (pass_mouse_events)\n",
" return fig.mouse_event(event, event['data']);\n",
" }\n",
"\n",
" rubberband.mousedown('button_press', mouse_event_fn);\n",
" rubberband.mouseup('button_release', mouse_event_fn);\n",
" // Throttle sequential mouse events to 1 every 20ms.\n",
" rubberband.mousemove('motion_notify', mouse_event_fn);\n",
"\n",
" rubberband.mouseenter('figure_enter', mouse_event_fn);\n",
" rubberband.mouseleave('figure_leave', mouse_event_fn);\n",
"\n",
" canvas_div.on(\"wheel\", function (event) {\n",
" event = event.originalEvent;\n",
" event['data'] = 'scroll'\n",
" if (event.deltaY < 0) {\n",
" event.step = 1;\n",
" } else {\n",
" event.step = -1;\n",
" }\n",
" mouse_event_fn(event);\n",
" });\n",
"\n",
" canvas_div.append(canvas);\n",
" canvas_div.append(rubberband);\n",
"\n",
" this.rubberband = rubberband;\n",
" this.rubberband_canvas = rubberband[0];\n",
" this.rubberband_context = rubberband[0].getContext(\"2d\");\n",
" this.rubberband_context.strokeStyle = \"#000000\";\n",
"\n",
" this._resize_canvas = function(width, height) {\n",
" // Keep the size of the canvas, canvas container, and rubber band\n",
" // canvas in synch.\n",
" canvas_div.css('width', width)\n",
" canvas_div.css('height', height)\n",
"\n",
" canvas.attr('width', width * mpl.ratio);\n",
" canvas.attr('height', height * mpl.ratio);\n",
" canvas.attr('style', 'width: ' + width + 'px; height: ' + height + 'px;');\n",
"\n",
" rubberband.attr('width', width);\n",
" rubberband.attr('height', height);\n",
" }\n",
"\n",
" // Set the figure to an initial 600x600px, this will subsequently be updated\n",
" // upon first draw.\n",
" this._resize_canvas(600, 600);\n",
"\n",
" // Disable right mouse context menu.\n",
" $(this.rubberband_canvas).bind(\"contextmenu\",function(e){\n",
" return false;\n",
" });\n",
"\n",
" function set_focus () {\n",
" canvas.focus();\n",
" canvas_div.focus();\n",
" }\n",
"\n",
" window.setTimeout(set_focus, 100);\n",
"}\n",
"\n",
"mpl.figure.prototype._init_toolbar = function() {\n",
" var fig = this;\n",
"\n",
" var nav_element = $('<div/>')\n",
" nav_element.attr('style', 'width: 100%');\n",
" this.root.append(nav_element);\n",
"\n",
" // Define a callback function for later on.\n",
" function toolbar_event(event) {\n",
" return fig.toolbar_button_onclick(event['data']);\n",
" }\n",
" function toolbar_mouse_event(event) {\n",
" return fig.toolbar_button_onmouseover(event['data']);\n",
" }\n",
"\n",
" for(var toolbar_ind in mpl.toolbar_items) {\n",
" var name = mpl.toolbar_items[toolbar_ind][0];\n",
" var tooltip = mpl.toolbar_items[toolbar_ind][1];\n",
" var image = mpl.toolbar_items[toolbar_ind][2];\n",
" var method_name = mpl.toolbar_items[toolbar_ind][3];\n",
"\n",
" if (!name) {\n",
" // put a spacer in here.\n",
" continue;\n",
" }\n",
" var button = $('<button/>');\n",
" button.addClass('ui-button ui-widget ui-state-default ui-corner-all ' +\n",
" 'ui-button-icon-only');\n",
" button.attr('role', 'button');\n",
" button.attr('aria-disabled', 'false');\n",
" button.click(method_name, toolbar_event);\n",
" button.mouseover(tooltip, toolbar_mouse_event);\n",
"\n",
" var icon_img = $('<span/>');\n",
" icon_img.addClass('ui-button-icon-primary ui-icon');\n",
" icon_img.addClass(image);\n",
" icon_img.addClass('ui-corner-all');\n",
"\n",
" var tooltip_span = $('<span/>');\n",
" tooltip_span.addClass('ui-button-text');\n",
" tooltip_span.html(tooltip);\n",
"\n",
" button.append(icon_img);\n",
" button.append(tooltip_span);\n",
"\n",
" nav_element.append(button);\n",
" }\n",
"\n",
" var fmt_picker_span = $('<span/>');\n",
"\n",
" var fmt_picker = $('<select/>');\n",
" fmt_picker.addClass('mpl-toolbar-option ui-widget ui-widget-content');\n",
" fmt_picker_span.append(fmt_picker);\n",
" nav_element.append(fmt_picker_span);\n",
" this.format_dropdown = fmt_picker[0];\n",
"\n",
" for (var ind in mpl.extensions) {\n",
" var fmt = mpl.extensions[ind];\n",
" var option = $(\n",
" '<option/>', {selected: fmt === mpl.default_extension}).html(fmt);\n",
" fmt_picker.append(option)\n",
" }\n",
"\n",
" // Add hover states to the ui-buttons\n",
" $( \".ui-button\" ).hover(\n",
" function() { $(this).addClass(\"ui-state-hover\");},\n",
" function() { $(this).removeClass(\"ui-state-hover\");}\n",
" );\n",
"\n",
" var status_bar = $('<span class=\"mpl-message\"/>');\n",
" nav_element.append(status_bar);\n",
" this.message = status_bar[0];\n",
"}\n",
"\n",
"mpl.figure.prototype.request_resize = function(x_pixels, y_pixels) {\n",
" // Request matplotlib to resize the figure. Matplotlib will then trigger a resize in the client,\n",
" // which will in turn request a refresh of the image.\n",
" this.send_message('resize', {'width': x_pixels, 'height': y_pixels});\n",
"}\n",
"\n",
"mpl.figure.prototype.send_message = function(type, properties) {\n",
" properties['type'] = type;\n",
" properties['figure_id'] = this.id;\n",
" this.ws.send(JSON.stringify(properties));\n",
"}\n",
"\n",
"mpl.figure.prototype.send_draw_message = function() {\n",
" if (!this.waiting) {\n",
" this.waiting = true;\n",
" this.ws.send(JSON.stringify({type: \"draw\", figure_id: this.id}));\n",
" }\n",
"}\n",
"\n",
"\n",
"mpl.figure.prototype.handle_save = function(fig, msg) {\n",
" var format_dropdown = fig.format_dropdown;\n",
" var format = format_dropdown.options[format_dropdown.selectedIndex].value;\n",
" fig.ondownload(fig, format);\n",
"}\n",
"\n",
"\n",
"mpl.figure.prototype.handle_resize = function(fig, msg) {\n",
" var size = msg['size'];\n",
" if (size[0] != fig.canvas.width || size[1] != fig.canvas.height) {\n",
" fig._resize_canvas(size[0], size[1]);\n",
" fig.send_message(\"refresh\", {});\n",
" };\n",
"}\n",
"\n",
"mpl.figure.prototype.handle_rubberband = function(fig, msg) {\n",
" var x0 = msg['x0'] / mpl.ratio;\n",
" var y0 = (fig.canvas.height - msg['y0']) / mpl.ratio;\n",
" var x1 = msg['x1'] / mpl.ratio;\n",
" var y1 = (fig.canvas.height - msg['y1']) / mpl.ratio;\n",
" x0 = Math.floor(x0) + 0.5;\n",
" y0 = Math.floor(y0) + 0.5;\n",
" x1 = Math.floor(x1) + 0.5;\n",
" y1 = Math.floor(y1) + 0.5;\n",
" var min_x = Math.min(x0, x1);\n",
" var min_y = Math.min(y0, y1);\n",
" var width = Math.abs(x1 - x0);\n",
" var height = Math.abs(y1 - y0);\n",
"\n",
" fig.rubberband_context.clearRect(\n",
" 0, 0, fig.canvas.width, fig.canvas.height);\n",
"\n",
" fig.rubberband_context.strokeRect(min_x, min_y, width, height);\n",
"}\n",
"\n",
"mpl.figure.prototype.handle_figure_label = function(fig, msg) {\n",
" // Updates the figure title.\n",
" fig.header.textContent = msg['label'];\n",
"}\n",
"\n",
"mpl.figure.prototype.handle_cursor = function(fig, msg) {\n",
" var cursor = msg['cursor'];\n",
" switch(cursor)\n",
" {\n",
" case 0:\n",
" cursor = 'pointer';\n",
" break;\n",
" case 1:\n",
" cursor = 'default';\n",
" break;\n",
" case 2:\n",
" cursor = 'crosshair';\n",
" break;\n",
" case 3:\n",
" cursor = 'move';\n",
" break;\n",
" }\n",
" fig.rubberband_canvas.style.cursor = cursor;\n",
"}\n",
"\n",
"mpl.figure.prototype.handle_message = function(fig, msg) {\n",
" fig.message.textContent = msg['message'];\n",
"}\n",
"\n",
"mpl.figure.prototype.handle_draw = function(fig, msg) {\n",
" // Request the server to send over a new figure.\n",
" fig.send_draw_message();\n",
"}\n",
"\n",
"mpl.figure.prototype.handle_image_mode = function(fig, msg) {\n",
" fig.image_mode = msg['mode'];\n",
"}\n",
"\n",
"mpl.figure.prototype.updated_canvas_event = function() {\n",
" // Called whenever the canvas gets updated.\n",
" this.send_message(\"ack\", {});\n",
"}\n",
"\n",
"// A function to construct a web socket function for onmessage handling.\n",
"// Called in the figure constructor.\n",
"mpl.figure.prototype._make_on_message_function = function(fig) {\n",
" return function socket_on_message(evt) {\n",
" if (evt.data instanceof Blob) {\n",
" /* FIXME: We get \"Resource interpreted as Image but\n",
" * transferred with MIME type text/plain:\" errors on\n",
" * Chrome. But how to set the MIME type? It doesn't seem\n",
" * to be part of the websocket stream */\n",
" evt.data.type = \"image/png\";\n",
"\n",
" /* Free the memory for the previous frames */\n",
" if (fig.imageObj.src) {\n",
" (window.URL || window.webkitURL).revokeObjectURL(\n",
" fig.imageObj.src);\n",
" }\n",
"\n",
" fig.imageObj.src = (window.URL || window.webkitURL).createObjectURL(\n",
" evt.data);\n",
" fig.updated_canvas_event();\n",
" fig.waiting = false;\n",
" return;\n",
" }\n",
" else if (typeof evt.data === 'string' && evt.data.slice(0, 21) == \"data:image/png;base64\") {\n",
" fig.imageObj.src = evt.data;\n",
" fig.updated_canvas_event();\n",
" fig.waiting = false;\n",
" return;\n",
" }\n",
"\n",
" var msg = JSON.parse(evt.data);\n",
" var msg_type = msg['type'];\n",
"\n",
" // Call the \"handle_{type}\" callback, which takes\n",
" // the figure and JSON message as its only arguments.\n",
" try {\n",
" var callback = fig[\"handle_\" + msg_type];\n",
" } catch (e) {\n",
" console.log(\"No handler for the '\" + msg_type + \"' message type: \", msg);\n",
" return;\n",
" }\n",
"\n",
" if (callback) {\n",
" try {\n",
" // console.log(\"Handling '\" + msg_type + \"' message: \", msg);\n",
" callback(fig, msg);\n",
" } catch (e) {\n",
" console.log(\"Exception inside the 'handler_\" + msg_type + \"' callback:\", e, e.stack, msg);\n",
" }\n",
" }\n",
" };\n",
"}\n",
"\n",
"// from http://stackoverflow.com/questions/1114465/getting-mouse-location-in-canvas\n",
"mpl.findpos = function(e) {\n",
" //this section is from http://www.quirksmode.org/js/events_properties.html\n",
" var targ;\n",
" if (!e)\n",
" e = window.event;\n",
" if (e.target)\n",
" targ = e.target;\n",
" else if (e.srcElement)\n",
" targ = e.srcElement;\n",
" if (targ.nodeType == 3) // defeat Safari bug\n",
" targ = targ.parentNode;\n",
"\n",
" // jQuery normalizes the pageX and pageY\n",
" // pageX,Y are the mouse positions relative to the document\n",
" // offset() returns the position of the element relative to the document\n",
" var x = e.pageX - $(targ).offset().left;\n",
" var y = e.pageY - $(targ).offset().top;\n",
"\n",
" return {\"x\": x, \"y\": y};\n",
"};\n",
"\n",
"/*\n",
" * return a copy of an object with only non-object keys\n",
" * we need this to avoid circular references\n",
" * http://stackoverflow.com/a/24161582/3208463\n",
" */\n",
"function simpleKeys (original) {\n",
" return Object.keys(original).reduce(function (obj, key) {\n",
" if (typeof original[key] !== 'object')\n",
" obj[key] = original[key]\n",
" return obj;\n",
" }, {});\n",
"}\n",
"\n",
"mpl.figure.prototype.mouse_event = function(event, name) {\n",
" var canvas_pos = mpl.findpos(event)\n",
"\n",
" if (name === 'button_press')\n",
" {\n",
" this.canvas.focus();\n",
" this.canvas_div.focus();\n",
" }\n",
"\n",
" var x = canvas_pos.x * mpl.ratio;\n",
" var y = canvas_pos.y * mpl.ratio;\n",
"\n",
" this.send_message(name, {x: x, y: y, button: event.button,\n",
" step: event.step,\n",
" guiEvent: simpleKeys(event)});\n",
"\n",
" /* This prevents the web browser from automatically changing to\n",
" * the text insertion cursor when the button is pressed. We want\n",
" * to control all of the cursor setting manually through the\n",
" * 'cursor' event from matplotlib */\n",
" event.preventDefault();\n",
" return false;\n",
"}\n",
"\n",
"mpl.figure.prototype._key_event_extra = function(event, name) {\n",
" // Handle any extra behaviour associated with a key event\n",
"}\n",
"\n",
"mpl.figure.prototype.key_event = function(event, name) {\n",
"\n",
" // Prevent repeat events\n",
" if (name == 'key_press')\n",
" {\n",
" if (event.which === this._key)\n",
" return;\n",
" else\n",
" this._key = event.which;\n",
" }\n",
" if (name == 'key_release')\n",
" this._key = null;\n",
"\n",
" var value = '';\n",
" if (event.ctrlKey && event.which != 17)\n",
" value += \"ctrl+\";\n",
" if (event.altKey && event.which != 18)\n",
" value += \"alt+\";\n",
" if (event.shiftKey && event.which != 16)\n",
" value += \"shift+\";\n",
"\n",
" value += 'k';\n",
" value += event.which.toString();\n",
"\n",
" this._key_event_extra(event, name);\n",
"\n",
" this.send_message(name, {key: value,\n",
" guiEvent: simpleKeys(event)});\n",
" return false;\n",
"}\n",
"\n",
"mpl.figure.prototype.toolbar_button_onclick = function(name) {\n",
" if (name == 'download') {\n",
" this.handle_save(this, null);\n",
" } else {\n",
" this.send_message(\"toolbar_button\", {name: name});\n",
" }\n",
"};\n",
"\n",
"mpl.figure.prototype.toolbar_button_onmouseover = function(tooltip) {\n",
" this.message.textContent = tooltip;\n",
"};\n",
"mpl.toolbar_items = [[\"Home\", \"Reset original view\", \"fa fa-home icon-home\", \"home\"], [\"Back\", \"Back to previous view\", \"fa fa-arrow-left icon-arrow-left\", \"back\"], [\"Forward\", \"Forward to next view\", \"fa fa-arrow-right icon-arrow-right\", \"forward\"], [\"\", \"\", \"\", \"\"], [\"Pan\", \"Pan axes with left mouse, zoom with right\", \"fa fa-arrows icon-move\", \"pan\"], [\"Zoom\", \"Zoom to rectangle\", \"fa fa-square-o icon-check-empty\", \"zoom\"], [\"\", \"\", \"\", \"\"], [\"Download\", \"Download plot\", \"fa fa-floppy-o icon-save\", \"download\"]];\n",
"\n",
"mpl.extensions = [\"eps\", \"jpeg\", \"pdf\", \"png\", \"ps\", \"raw\", \"svg\", \"tif\"];\n",
"\n",
"mpl.default_extension = \"png\";var comm_websocket_adapter = function(comm) {\n",
" // Create a \"websocket\"-like object which calls the given IPython comm\n",
" // object with the appropriate methods. Currently this is a non binary\n",
" // socket, so there is still some room for performance tuning.\n",
" var ws = {};\n",
"\n",
" ws.close = function() {\n",
" comm.close()\n",
" };\n",
" ws.send = function(m) {\n",
" //console.log('sending', m);\n",
" comm.send(m);\n",
" };\n",
" // Register the callback with on_msg.\n",
" comm.on_msg(function(msg) {\n",
" //console.log('receiving', msg['content']['data'], msg);\n",
" // Pass the mpl event to the overridden (by mpl) onmessage function.\n",
" ws.onmessage(msg['content']['data'])\n",
" });\n",
" return ws;\n",
"}\n",
"\n",
"mpl.mpl_figure_comm = function(comm, msg) {\n",
" // This is the function which gets called when the mpl process\n",
" // starts-up an IPython Comm through the \"matplotlib\" channel.\n",
"\n",
" var id = msg.content.data.id;\n",
" // Get hold of the div created by the display call when the Comm\n",
" // socket was opened in Python.\n",
" var element = $(\"#\" + id);\n",
" var ws_proxy = comm_websocket_adapter(comm)\n",
"\n",
" function ondownload(figure, format) {\n",
" window.open(figure.imageObj.src);\n",
" }\n",
"\n",
" var fig = new mpl.figure(id, ws_proxy,\n",
" ondownload,\n",
" element.get(0));\n",
"\n",
" // Call onopen now - mpl needs it, as it is assuming we've passed it a real\n",
" // web socket which is closed, not our websocket->open comm proxy.\n",
" ws_proxy.onopen();\n",
"\n",
" fig.parent_element = element.get(0);\n",
" fig.cell_info = mpl.find_output_cell(\"<div id='\" + id + \"'></div>\");\n",
" if (!fig.cell_info) {\n",
" console.error(\"Failed to find cell for figure\", id, fig);\n",
" return;\n",
" }\n",
"\n",
" var output_index = fig.cell_info[2]\n",
" var cell = fig.cell_info[0];\n",
"\n",
"};\n",
"\n",
"mpl.figure.prototype.handle_close = function(fig, msg) {\n",
" var width = fig.canvas.width/mpl.ratio\n",
" fig.root.unbind('remove')\n",
"\n",
" // Update the output cell to use the data from the current canvas.\n",
" fig.push_to_output();\n",
" var dataURL = fig.canvas.toDataURL();\n",
" // Re-enable the keyboard manager in IPython - without this line, in FF,\n",
" // the notebook keyboard shortcuts fail.\n",
" IPython.keyboard_manager.enable()\n",
" $(fig.parent_element).html('<img src=\"' + dataURL + '\" width=\"' + width + '\">');\n",
" fig.close_ws(fig, msg);\n",
"}\n",
"\n",
"mpl.figure.prototype.close_ws = function(fig, msg){\n",
" fig.send_message('closing', msg);\n",
" // fig.ws.close()\n",
"}\n",
"\n",
"mpl.figure.prototype.push_to_output = function(remove_interactive) {\n",
" // Turn the data on the canvas into data in the output cell.\n",
" var width = this.canvas.width/mpl.ratio\n",
" var dataURL = this.canvas.toDataURL();\n",
" this.cell_info[1]['text/html'] = '<img src=\"' + dataURL + '\" width=\"' + width + '\">';\n",
"}\n",
"\n",
"mpl.figure.prototype.updated_canvas_event = function() {\n",
" // Tell IPython that the notebook contents must change.\n",
" IPython.notebook.set_dirty(true);\n",
" this.send_message(\"ack\", {});\n",
" var fig = this;\n",
" // Wait a second, then push the new image to the DOM so\n",
" // that it is saved nicely (might be nice to debounce this).\n",
" setTimeout(function () { fig.push_to_output() }, 1000);\n",
"}\n",
"\n",
"mpl.figure.prototype._init_toolbar = function() {\n",
" var fig = this;\n",
"\n",
" var nav_element = $('<div/>')\n",
" nav_element.attr('style', 'width: 100%');\n",
" this.root.append(nav_element);\n",
"\n",
" // Define a callback function for later on.\n",
" function toolbar_event(event) {\n",
" return fig.toolbar_button_onclick(event['data']);\n",
" }\n",
" function toolbar_mouse_event(event) {\n",
" return fig.toolbar_button_onmouseover(event['data']);\n",
" }\n",
"\n",
" for(var toolbar_ind in mpl.toolbar_items){\n",
" var name = mpl.toolbar_items[toolbar_ind][0];\n",
" var tooltip = mpl.toolbar_items[toolbar_ind][1];\n",
" var image = mpl.toolbar_items[toolbar_ind][2];\n",
" var method_name = mpl.toolbar_items[toolbar_ind][3];\n",
"\n",
" if (!name) { continue; };\n",
"\n",
" var button = $('<button class=\"btn btn-default\" href=\"#\" title=\"' + name + '\"><i class=\"fa ' + image + ' fa-lg\"></i></button>');\n",
" button.click(method_name, toolbar_event);\n",
" button.mouseover(tooltip, toolbar_mouse_event);\n",
" nav_element.append(button);\n",
" }\n",
"\n",
" // Add the status bar.\n",
" var status_bar = $('<span class=\"mpl-message\" style=\"text-align:right; float: right;\"/>');\n",
" nav_element.append(status_bar);\n",
" this.message = status_bar[0];\n",
"\n",
" // Add the close button to the window.\n",
" var buttongrp = $('<div class=\"btn-group inline pull-right\"></div>');\n",
" var button = $('<button class=\"btn btn-mini btn-primary\" href=\"#\" title=\"Stop Interaction\"><i class=\"fa fa-power-off icon-remove icon-large\"></i></button>');\n",
" button.click(function (evt) { fig.handle_close(fig, {}); } );\n",
" button.mouseover('Stop Interaction', toolbar_mouse_event);\n",
" buttongrp.append(button);\n",
" var titlebar = this.root.find($('.ui-dialog-titlebar'));\n",
" titlebar.prepend(buttongrp);\n",
"}\n",
"\n",
"mpl.figure.prototype._root_extra_style = function(el){\n",
" var fig = this\n",
" el.on(\"remove\", function(){\n",
"\tfig.close_ws(fig, {});\n",
" });\n",
"}\n",
"\n",
"mpl.figure.prototype._canvas_extra_style = function(el){\n",
" // this is important to make the div 'focusable\n",
" el.attr('tabindex', 0)\n",
" // reach out to IPython and tell the keyboard manager to turn it's self\n",
" // off when our div gets focus\n",
"\n",
" // location in version 3\n",
" if (IPython.notebook.keyboard_manager) {\n",
" IPython.notebook.keyboard_manager.register_events(el);\n",
" }\n",
" else {\n",
" // location in version 2\n",
" IPython.keyboard_manager.register_events(el);\n",
" }\n",
"\n",
"}\n",
"\n",
"mpl.figure.prototype._key_event_extra = function(event, name) {\n",
" var manager = IPython.notebook.keyboard_manager;\n",
" if (!manager)\n",
" manager = IPython.keyboard_manager;\n",
"\n",
" // Check for shift+enter\n",
" if (event.shiftKey && event.which == 13) {\n",
" this.canvas_div.blur();\n",
" event.shiftKey = false;\n",
" // Send a \"J\" for go to next cell\n",
" event.which = 74;\n",
" event.keyCode = 74;\n",
" manager.command_mode();\n",
" manager.handle_keydown(event);\n",
" }\n",
"}\n",
"\n",
"mpl.figure.prototype.handle_save = function(fig, msg) {\n",
" fig.ondownload(fig, null);\n",
"}\n",
"\n",
"\n",
"mpl.find_output_cell = function(html_output) {\n",
" // Return the cell and output element which can be found *uniquely* in the notebook.\n",
" // Note - this is a bit hacky, but it is done because the \"notebook_saving.Notebook\"\n",
" // IPython event is triggered only after the cells have been serialised, which for\n",
" // our purposes (turning an active figure into a static one), is too late.\n",
" var cells = IPython.notebook.get_cells();\n",
" var ncells = cells.length;\n",
" for (var i=0; i<ncells; i++) {\n",
" var cell = cells[i];\n",
" if (cell.cell_type === 'code'){\n",
" for (var j=0; j<cell.output_area.outputs.length; j++) {\n",
" var data = cell.output_area.outputs[j];\n",
" if (data.data) {\n",
" // IPython >= 3 moved mimebundle to data attribute of output\n",
" data = data.data;\n",
" }\n",
" if (data['text/html'] == html_output) {\n",
" return [cell, data, j];\n",
" }\n",
" }\n",
" }\n",
" }\n",
"}\n",
"\n",
"// Register the function which deals with the matplotlib target/channel.\n",
"// The kernel may be null if the page has been refreshed.\n",
"if (IPython.notebook.kernel != null) {\n",
" IPython.notebook.kernel.comm_manager.register_target('matplotlib', mpl.mpl_figure_comm);\n",
"}\n"
],
"text/plain": [
"<IPython.core.display.Javascript object>"
]
},
"metadata": {},
"output_type": "display_data"
},
{
"data": {
"text/html": [
"<img src=\"data:image/png;base64,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zTfbW2+9ZatXr7Z27drZcccdZz/5yU+sVatWUddML+QJPuvKcwICCCCAAAJFFmAejesAnOKcKIUAAggggEBNAp7m0eiEyaRJk+yss84KyZGBAweGBMb48eNt6dKlNnv2bOvSpUudvX3PPffYySefbDvssENIkCjhMX36dLv//vvD72+88cYq5w8ePNhuuOEG69mzZ0iSbLnllvbss8/a7bffbh07drS5c+da06ZNsxphnuCzqjiFEUAAAQQQcCDAPBrXCTjFOVEKAQQQQACBkkiYKCnSoUMHa968uc2fPz/8qmPhwoXWqVMnO+CAA+ypp56qtbfXrVtnbdq0sZUrV9prr71mu+2228aySr5MnDgxJF0OOuig8OerVq2ybbfd1vbZZ5+wGqVBgwYby1944YUhUfPQQw9Znz59shphBDBZcVEYAQQQQACBKgLMo3EDAqc4J0ohgAACCCBQEgmTKVOm2IABA2zEiBE2fPjwKm3q37+/3XbbbSF5okdmajpefvll23fffe3II4+0xx57rEqR5557LiRKzjzzTLvlllvC3y1ZsiSsROnbt6898MADVcr/4he/sKFDh4YEjVafZHMQwGSjRVkEEEAAAQSqCjCPxo0InOKcKIUAAggggEBJJEzOO++8sJfI448/br17967SJq0O0SqRe++91/r161djjz///PN24IEH2vHHH2/aByX90KM1Wkmy9957h9UnqWP//fc3JVq054k+V3um6JGcCy64wPR3jz766Cb7nmQabgQwmYT4ewQQQAABBGoXYB6NGx04xTlRCgEEEEAAgZJImGilhx6Bef31122vvfaq0qZHHnkkPBozbtw40+MyNR2fffZZWDGy44472oIFC6rsPaJ9SrRfSbNmzWz58uUbT3/vvffCqpYZM2ZU+cjzzz8/7G3SuHHjjKNLn5f+mYsXLw6PDy1atMjatm2b8XwKIIAAAggggMB/BUgExI0GnOKcKIUAAggggEBJJEx69eoVHoFRsiN9/xE1Tn+uv7/mmmvCozK1Heeee65NmDDBjjrqKLvyyitDAuWJJ56wiy++OOxZUllZadrrJHX8+9//tssvv9z+9a9/2YknnmhbbbWV/fnPf7Zbb701JFJSj+/UNcT0CNHIkSM3KULChB9MBBBAAAEEshcgERBnhlOcE6UQQAABBBAoiYRJvitMhKDXCA8Z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TrnbcSYCCCCAAAKe5lESJoxHBBBAAAEEEIgS8BTARFW4SIVwKhI8l0UAAQQQKAkBT/MoCZOSGFI0AgEEEEAAgcILeApgCt/a3K+AU+52nIkAAggggICneZSECeMRAQQQQAABBKIEPAUwURUuUiGcigTPZRFAAAEESkLA0zxKwqQkhhSNQAABBBBAoPACngKYwrc29yvglLsdZyKAAAIIIOBpHiVhwnhEAAEEEEAAgSgBTwFMVIWLVAinIsFzWQQQQACBkhDwNI+SMCmJIUUjEEAAAQQQKLyApwCm8K3N/Qo45W7HmQgggAACCHiaR0mYMB4RQAABBBBAIErAUwATVeEiFcKpSPBcFgEEEECgJAQ8zaMkTEpiSNEIBBBAAAEECi/gKYApfGtzvwJOudtxJgIIIIAAAp7mURImjEcEEEAAAQQQiBLwFMBEVbhIhXAqEjyXRQABBBAoCQFP8ygJk5IYUjQCAQQQQACBwgt4CmAK39rcr4BT7naciQACCCCAgKd5NKuEybRp02zMmDE2b948q6iosB49etjo0aOtc+fOUb06c+bMUH7OnDm2Zs0a22OPPeycc86xQYMGWcOGDTf5jA0bNtgtt9xit956q82fP98qKytt1113te9973v285//POqa6YU8wWddeU5AAAEEEECgyALMo3EdgFOcE6UQQAABBBCoScDTPBqdMJk0aZKdddZZITkycOBAW716tY0fP96WLl1qs2fPti5dutTZ2/fcc4+dfPLJtsMOO4QESatWrWz69Ol2//33h9/feOONVc5ft26dnXDCCfbII4/YD37wAzvkkEOsQYMG9t5774X/7rrrrqxHlyf4rCvPCQgggAACCBRZgHk0rgNwinOiFAIIIIAAAiWRMFFSpEOHDta8efOw0kO/6li4cKF16tTJDjjgAHvqqadq7W0lP9q0aWMrV6601157zXbbbbeNZZV8mThxYki6HHTQQRv//KqrrrIRI0aEhMmRRx5ZLyOJAKZeGPkQBBBAAIEyFWAejet4nOKcKIUAAggggEBJJEymTJliAwYMCAmM4cOHV2lT//797bbbbgvJk3bt2tXY4y+//LLtu+++IfHx2GOPVSnz3HPPhUTJmWeeGR6/0bFq1aqQYDnssMPCChQ9irNixQpr1qxZWGWS60EAk6sc5yGAAAIIIGDGPBo3CnCKc6IUAggggAACJZEwOe+88+zmm2+2xx9/3Hr37l2lTVodolUi9957r/Xr16/GHn/++eftwAMPtOOPP960D0r6MXfuXNtnn31s7733DqtPdOg6Sq6MGjUqPPLz29/+1pYtWxYSJnpM59prr7WWLVtmPboIYLIm4wQEEEAAAQQ2CjCPxg0GnOKcKIUAAggggEBJJEz69u1rDz30kL3++uu21157VWmTHpnp06ePjRs3zi688MIae/yzzz4Le5fsuOOOtmDBAmvatOnGcjfccIMNHjw4JEOWL18e/lyfddFFF4V9Tho1amSXX355WHGi1SZ33HFH2C/lhRdesCZNmtQ5wvR5qc9UwcWLF4fHhxYtWmRt27ZldCKAAAIIIIBAFgIkAuKwcIpzohQCCCCAAAIlkTDp1atX2KNEyY70/UfUOP25/v6aa66xoUOH1trj5557rk2YMMGOOuoou/LKK0MC5YknnrCLL744PIKjx26014mOq6++2n72s5+FZMmrr74a9klJHaeeempImuiz9Iadug49QjRy5MhNipAw4QcTAQQQQACB7AVIBMSZ4RTnRCkEEEAAAQRKImGS7woTIeg1wkOGDAkbvK5duza4aPPY66+/PiRalCz59NNPw59fd911IZGivU20GWz68fTTT9vhhx8e3pxz99131znCWGHCDyACCCCAAAL1J0AiIM4SpzgnSiGAAAIIIFASCZN89zBJR1ASQ2/a0eat3bp1s/Xr14fEifY4SSVH/vCHP4SEiPYrmTp1ahXDN998MzwWpL1UtNdJNgcBTDZalEUAAQQQQKCqAPNo3IjAKc6JUggggAACCJREwmTy5Ml2xhlnhMdbhg0bVqVNenuO3qJT11ty6hoGSoiceOKJ4TGcK664IhR9//33w2uMtd/InDlzqpw+ffp0O+KII+yUU06x22+/PasRRgCTFReFEUAAAQQQqCLAPBo3IHCKc6IUAggggAACJZEw0Ztq2rdvby1atAirQ7QiRIeSJNpfpHv37qZHZXRoPxL9ucq2bt26zhGwZMmSsLJEm8K+8cYbVd5807NnT5s5c2ZImOjzU4fetKPNX/U4jlahZHMQwGSjRVkEEEAAAQSqCjCPxo0InOKcKIUAAggggEBJJEzUCG2yqo1bO3fuHF4jrD1Jxo8fb0p6zJo1Kzxeo2PGjBmmZMfpp58eVp6kjrvuuiv8/rDDDrOddtrJ3n33XZs0aVJ4i82DDz4Yzkk/9IrhQw45JGwG+6Mf/Si8JeeBBx6wP//5z2Hj2IcfftgaNmyY1QgjgMmKi8IIIIAAAghUEWAejRsQOMU5UQoBBBBAAIGSSZioIXp8ZuzYsTZv3jyrqKiwHj162KhRo6xr164b21lbwuSll14Km7vqrTdasaJXBuvRGj2G07FjxxpHivYr0dtytHpFiRU9pqO35Fx22WXh+tkeBDDZilEeAQQQQACB/wowj8aNBpzinCiFAAIIIIBASSVMkt6dBDBJ70HqjwACCCBQTAHm0Th9nOKcKIUAAggggAAJE0djgADGUWdQFQQQQACBxAkwj8Z1GU5xTpRCAAEEEECAhImjMUAA46gzqAoCCCCAQOIEmEfjugynOCdKIYAAAgggQMLE0RgggHHUGVQFAQQQQCBxAsyjcV2GU5wTpRBAAAEEECBh4mgMEMA46gyqggACCCCQOAHm0bguwynOiVIIIIAAAgiQMHE0BghgHHUGVUEAAQQQSJwA82hcl+EU50QpBBBAAAEESJg4GgMEMI46g6oggAACCCROgHk0rstwinOiFAIIIIAAAiRMHI0BAhhHnUFVEEAAAQQSJ8A8GtdlOMU5UQoBBBBAAAESJo7GAAGMo86gKggggAACiRNgHo3rMpzinCiFAAIIIIAACRNHY4AAxlFnUBUEEEAAgcQJMI/GdRlOcU6UQgABBBBAgISJozFAAOOoM6gKAggggEDiBJhH47oMpzgnSiGAAAIIIEDCxNEYIIBx1BlUBQEEEEAgcQLMo3FdhlOcE6UQQAABBBAgYeJoDBDAOOoMqoIAAgggkDgB5tG4LsMpzolSCCCAAAIIkDBxNAYIYBx1BlVBAAEEEEicAPNoXJfhFOdEKQQQQAABBEiYOBoDBDCOOoOqIIAAAggkToB5NK7LcIpzohQCCCCAAAIkTByNAQIYR51BVRBAAAEEEifAPBrXZTjFOVEKAQQQQAABEiaOxgABjKPOoCoIIIAAAokTYB6N6zKc4pwohQACCCCAAAkTR2OAAMZRZ1AVBBBAAIHECTCPxnUZTnFOlEIAAQQQQICEiaMxQADjqDOoCgIIIIBA4gSYR+O6DKc4J0ohgAACCCBAwsTRGCCAcdQZVAUBBBBAIHECzKNxXYZTnBOlEEAAAQQQIGHiaAwQwDjqDKqCAAIIIJA4AebRuC7DKc6JUggggAACCJAwcTQGCGAcdQZVQQABBBBInADzaFyX4RTnRCkEEEAAAQRImDgaAwQwjjqDqiCAAAIIJE6AeTSuy3CKc6IUAggggAACJEwcjQECGEedQVUQQAABBBInwDwa12U4xTlRCgEEEEAAARImjsYAAYyjzqAqCCCAAAKJE2AejesynOKcKIUAAggggAAJE0djgADGUWdQFQQQQACBxAkwj8Z1GU5xTpRCAAEEEECAhImjMUAA46gzqAoCCCCAQOIEmEfjugynOCdKIYAAAgggQMLE0RgggHHUGVQFAQQQ0EedfwAAIABJREFUQCBxAsyjcV2GU5wTpRBAAAEEECBh4mgMEMA46gyqggACCCCQOAHm0bguwynOiVIIIIAAAgiQMHE0BghgHHUGVUEAAQQQSJwA82hcl+EU50QpBBBAAAEESJg4GgMEMI46g6oggAACCCROgHk0rstwinOiFAIIIIAAAiRMHI0BAhhHnUFVEEgT2LBhta1YNiv8t379cmvUqLk1a3FI+K9hwyZYIYCAEwHm0biOwCnOiVIIIIAAAgiQMHE0BghgHHUGVUHgPwJfrvnQPlg4ytavW2JmDcyscuOvjRq3tF12vcIqtmyDFwIIOBBgHo3rBJzinCiFAAIIIIAACRNHY4AAxlFnUBUEzEwrS95fMMTWr/v0P4mS6iwNrFHj7a397tex0oQRg4ADAebRuE7AKc6JUggggAACCJAwcTQGCGAcdQZVQcDMli19wj7+6JaMFq12PttabNcrYzkKIIBAYQWYR+N8cYpzohQCCCCAAAIkTByNAQIYR51BVRAws3++N8JWf/H3WlaXpIgaWJOme1rbDsMxQwCBIgswj8Z1AE5xTpRCAAEEEECAhImjMUAA46gzqAoCZvb+gp/Y2i8/zGixRUUba7/7LzOWowACCBRWgHk0zhenOCdKIYAAAgggQMLE0RgggHHUGVQFAVaYMAYQSJwA82hcl+EU50QpBBBAAAEESJg4GgMEMI46g6ogwB4mjAEEEifAPBrXZTjFOVEKAQQQQAABEiaOxgABjKPOoCoI8JYcxgACiRNgHo3rMpzinCiFAAIIIIAACRNHY4AAxlFnUBUE/iPw5ZoP7YOFo2z9uiVm1uA/G8B+9Wujxi1tl12vsIot2+CFAAIOBJhH4zoBpzgnSiGAAAIIIEDCxNEYIIBx1BlUBYE0gQ0bVtuKZbNtxbJZtn79MmvUqIU1a3GINWtxsDVs2AQrBBBwIsA8GtcROMU5UQoBBBBAAAESJo7GAAGMo86gKggggAACiRNgHo3rMpzinCiFAAIIIIAACRNHY4AAxlFnUBUEEEAAgcQJMI/GdRlOcU6UQgABBBBAgISJozFAAOOoM6gKAggggEDiBLzOo9OmTbMxY8bYvHnzrKKiwnr06GGjR4+2zp07ZzR+9NFH7eabb7ZXX33VPv74Y2vYsKG1b9/evv/979uPf/xj23bbbTN+RvUCXp2ybggnIIAAAgggUAQBT/Nog8rKysoiGBTlkp7giwLARRFAAAEEEMhDwOM8OmnSJDvrrLNCcmTgwIG2evVqGz9+vC1dutRmz55tXbp0qbPFY8eOtVmzZtl+++1nrVu3trVr19oLL7xgv//9761jx4724osv2tZbb52VmkenrBpAYQQQQAABBIoo4GkeJWFSxIHApRFAAAEEEEiSgKcARm5KinTo0MGaN29u8+fPD7/qWLhwoXXq1MkOOOAAe+qpp3Ii1oqVyy67zG677TY77bTTsvoMb05ZVZ7CCCCAAAIIFFnA0zxKwqTIg4HLI4AAAgggkBQBTwGMzKZMmWIDBgywESNG2PDhw6sw9u/fPyQ7lDxp165d1sR/+MMf7Ac/+IGNGzfOLrzwwqzO9+aUVeUpjAACCCCAQJEFPM2jJEyKPBi4PAIIIIAAAkkR8BTAyOy8884L+488/vjj1rt37yqMEydODI/o3HvvvdavX7+MxCtXrgyP8+jXv/3tbzZkyBD74IMPwt4me+65Z8bz0wt4c8qq8hRGAAEEEECgyAKe5lESJkUeDFweAQQQQACBpAh4CmBk1rdvX3vooYfs9ddft7322qsK4yOPPGJ9+vSJXiGSWpGS+pC9997brr32WjvqqKMyds/y5ctN/6WOxYsXh8eBFi1aZG3bts14PgUQQAABBBBA4L8CnuINEiaMTAQQQAABBBCIEvAUwKjCvXr1CnuULFiwwHbbbbcqbdCf6++vueYaGzp0aMb2Keny4Ycf2pIlS8Jmsc8884xdfPHF9sMf/jDjuXokaOTIkZuUI2GSkY4CCCCAAAIIbCLgKd4gYcIARQABBBBAAIEoAU8BjCpcnytMqgPoUR69Wviuu+6yk046qU4fVphEDR8KIYAAAgggECXgKd4gYRLVZRRCAAEEEEAAAU8BjHqjPvcwqd67lZWV1qJFC9t3331txowZWXW+N6esKk9hBBBAAAEEiizgaR4lYVLkwcDlEUAAAQQQSIqApwBGZpMnT7YzzjgjPA4zbNiwKox6e47eopPrW3LWrVtn22yzjX3jG9+wuXPnZtVF3pyyqjyFEUAAAQQQKLKAp3mUhEmRBwOXRwABBBBAICkCngIYmS1dutTat28fVoLMnz/fmjdvHiiVJOnUqZN1797dnn766fBnq1atCn+usq1bt95I/tFHH9nOO++8SReMHz8+vE5Yb9rRm3iyObw5ZVN3yiKAAAIIIFBsAU/zKAmTYo8Gro8AAggggEBCBDwFMCmyCRMm2LnnnmudO3cOyY01a9aYkh3avHXWrFnWrVu3UFSP1fTs2dNOP/30sPIkdeywww520EEH2X777RfeaPPpp5+GsnrLTocOHezZZ5+tkmCJ6SqPTjH1pgwCCCCAAAIeBDzNoyRMPIwI6oAAAggggEACBDwFMOlcU6dOtbFjx9q8efOsoqLCevToYaNGjbKuXbtuLFZbwuSqq66y6dOn21tvvRWSLFtuuaXtscceYUPZwYMH23bbbZd1z3h1yrohnIAAAggggEARBDzNoyRMijAAuCQCCCCAAAJJFPAUwHj2w8lz71A3BBBAAAHvAp7mURIm3kcL9UMAAQQQQMCJgKcAxglJjdXAyXPvUDcEEEAAAe8CnuZREibeRwv1QwABBBBAwImApwDGCQkJE88dQd0QQAABBBIp4CneIGGSyCFEpRFAAAEEENj8Ap4CmM3f+vgr4hRvRUkEEEAAAQSqC3iaR0mYMD4RQAABBBBAIErAUwATVeEiFcKpSPBcFgEEEECgJAQ8zaMkTEpiSNEIBBBAAAEECi/gKYApfGtzvwJOudtxJgIIIIAAAp7mURImjEcEEEAAAQQQiBLwFMBEVbhIhXAqEjyXRQABBBAoCQFP8ygJk5IYUjQCAQQQQACBwgt4CmAK39rcr4BT7naciQACCCCAgKd5NKuEybRp02zMmDE2b948q6iosB49etjo0aOtc+fOUb06c+bMUH7OnDm2Zs0a22OPPeycc86xQYMGWcOGDev8jEsuucSuvfZaa9Soka1bty7qetULeYLPqQGchAACCCCAQBEFmEfj8HGKc6IUAggggAACNQl4mkejEyaTJk2ys846KyRHBg4caKtXr7bx48fb0qVLbfbs2dalS5c6e/uee+6xk08+2XbYYYeQIGnVqpVNnz7d7r///vD7G2+8sdbz//rXv9qBBx5oTZs2tS+++IKECT9XCCCAAAIIFEHAUwBThOZHXxKnaCoKIoAAAgggsImAp3k0KmGipEiHDh2sefPmNn/+/PCrjoULF1qnTp3sgAMOsKeeeqrWrtaKkDZt2tjKlSvttddes912221jWSVfJk6cGJIuBx100CafsXbtWttvv/1s9913D8mZWbNmkTDhhwoBBBBAAIEiCHgKYIrQ/OhL4hRNRUEEEEAAAQSSnzCZMmWKDRgwwEaMGGHDhw+v0qD+/fvbbbfdFpIn7dq1q7G7X375Zdt3333tyCOPtMcee6xKmeeeey4kSs4880y75ZZbNjn/yiuvtOuuu85ef/11++EPf0jChB8oBBBAAAEEiiRAIiAOHqc4J0ohgAACCCBQk4CneTRqhcl5551nN998sz3++OPWu3fvKm3S6hCtErn33nutX79+Nfb4888/Hx6pOf744037oKQfc+fOtX322cf23nvvsPok/dBqFiVafvnLX9r5559vhx12GAkTfqYQQAABBBAokoCnAKZIBFGXxSmKiUIIIIAAAgjUKOBpHo1KmPTt29ceeuihsMpjr732qtKoRx55xPr06WPjxo2zCy+8sMYGf/bZZ2Hvkh133NEWLFgQ9iJJHTfccIMNHjzYmjVrZsuXL9/45xs2bAgrT7QZrB7D0a/ZJkz0eemfuXjx4vD40KJFi6xt27YMTwQQQAABBBDIQsBTAJNFtTd7UZw2OzkXRAABBBAoIQFP82hUwqRXr15hjxIlO9L3H1Gf6M/199dcc40NHTq01m4699xzbcKECXbUUUeZHrNRAuWJJ56wiy++2FatWmWVlZVV9ia5/vrr7bLLLjM9zqPVJzqyTZjoEaKRI0duUicSJiX000RTEEAAAQQ2m4CnAGazNTqHC+GUAxqnIIAAAggg8B8BT/NoVMIk3xUmardeIzxkyJCwwas2ctWhzWOVGFGiRRvDfvrpp+HP33nnnfDWHa08ufrqqzcOnGwTJqww4WcOAQQQQACB+hPwFMDUX6vq/5Nwqn9TPhEBBBBAoHwEPM2jUQmTfPcwSe9aJTG0N0mDBg2sW7dutn79+pA40R4nelOOjmOPPdbmzJkTVqA0adJk4+na9PWll16yN9980xo3bhze3JPN4Qk+m3pTFgEEEEAAAQ8CzKNxvYBTnBOlEEAAAQQQqEnA0zwalTCZPHmynXHGGeHxlmHDhlVpk96eo7fo1PWWnLqGwdSpU+3EE08MK0muuOKKUFSbwGoz2LqOnXbayT766KOsRpgn+KwqTmEEEEAAAQQcCDCPxnUCTnFOlEIAAQQQQKAkEiZLly619u3bW4sWLcLqEK0I0aEkSadOnax79+729NNPhz/TfiT6c5Vt3bp1nSNgyZIlYWWJNoV94403rGXLlqG8Vpboz6ofeqWxVpfcc889YeXJd7/73axGGAFMVlwURgABBBBAoIoA82jcgMApzolSCCCAAAIIlETCRI3Qhq3auLVz587hNcLak2T8+PGmpIfeYqPHa3TMmDHDevbsaaeffnpYeZI67rrrrvB77UOi1SHvvvuuTZo0KbzF5sEHHwznZDqy3cOk+ucRwGQS5u8RQAABBBCoXYB5NG504BTnRCkEEEAAAQRKJmGihujxmbFjx9q8efOsoqLCevToYaNGjbKuXbtubGdtCRPtPaLNXV999VXTipVWrVrZEUccER7D6dixY9RIIWESxUQhBBBAAAEECiJAIiCOFac4J0ohgAACCCBQUgmTpHcnAUzSe5D6I4AAAggUU4B5NE4fpzgnSiGAAAIIIEDCxNEYIIBx1BlUBQEEEEAgcQLMo3FdhlOcE6UQQAABBBAgYeJoDBDAOOoMqoIAAgggkDgB5tG4LsMpzolSCCCAAAIIkDBxNAYIYBx1BlVBAAEEEEicAPNoXJfhFOdEKQQQQAABBEiYOBoDBDCOOoOqIIAAAggkToB5NK7LcIpzohQCCCCAAAIkTByNAQIYR51BVRBAAAEEEifAPBrXZTjFOVEKAQQQQAABEiaOxgABjKPOoCoIIIAAAokTqO959Msvv7S7777blixZYv369bP27dsnzsR7oFcSoDQCAQQQQKCsBOo73sgHr0FlZWVlPh+QpHM9wSfJjboigAACCCAggXzm0cGDB9tTTz1lc+fODZgbNmywgw8+2F544QVTKNKiRQubM2eOff3rX088dj5OiW88DUAAAQQQQCBPAU/zKAmTPDuT0xFAAAEEECgXgXwCmK5du1rv3r3tuuuuC1z33XefnXDCCTZ06FD75je/aYMGDbJjjz3WbrnllsRz5uOU+MbTAAQQQAABBPIU8DSPkjDJszM5HQEEEEAAgXIRyCeA2X777e3qq68OiREdZ599tj355JP2zjvvhN//9Kc/tTvvvHPj75Nsmo9TkttN3RFAAAEEEKgPAU/zKAmT+uhRPgMBBBBAAIEyEMgngNlqq63sV7/6lZ111llBas8997RDDjlk44qSyZMn2/nnn2+rVq1KvGQ+TolvPA1AAAEEEEAgTwFP8ygJkzw7k9MRQAABBBAoF4F8AhjtTXLkkUfa+PHj7d1337Xdd9/dpkyZYqeddlrgGzNmTPjvk08+STxnPk6JbzwNQAABBBBAIE8BT/MoCZM8O5PTEUAAAQQQKBeBfAKYSy65xMaNGxcexdHmrq+//rq9//771qpVq8B3+umn2/z58+3FF19MPGc+TolvPA1AAAEEEEAgTwFP8ygJkzw7k9MRQAABBBAoF4F8ApjPPvvMTjzxxLBvSZMmTao8nvPFF1/YzjvvHJIp1157beI583FKfONpAAIIIIAAAnkKeJpHSZjk2ZmcjgACCCCAQLkI1EcAs3z5cmvatKltscUWG9mUMHnrrbesXbt2ps1hk37Uh1PSDag/AggggAACuQp4mkdJmOTai5yHAAIIIIBAmQl4CmA80+PkuXeoGwIIIICAdwFP8ygJE++jhfohgAACCCDgRCCfAGb27Nn28ssv2wUXXLCxNX/84x/tsssusyVLltiAAQPshhtucNLS/KqRj1N+V+ZsBBBAAAEEki/gaR4lYZL88UQLEEAAAQQQ2CwC+QQwxxxzjDVu3NgeeOCBUNf33nvPvvGNb1iLFi1sp512CpvATpgwYeNrhzdLgwp0kXycClQlPhYBBBBAAIHECHiaR0mYJGbYUFEEEEAAAQSKK5BPAKP9Sc4//3wbOnRoaMTo0aPtyiuvtAULFtguu+xiRx99tGlj2Oeee664jayHq+fjVA+X5yMQQAABBBBItICneZSESaKHEpVHAAEEEEBg8wnkE8Boo9ebbrrJ+vfvHyp8xBFHWGVlpU2fPj38/je/+Y0NGzbMPvnkk83XoAJdKR+nAlWJj0UAAQQQQCAxAp7mURImiRk2VBQBBBBAAIHiCuQTwOixm0svvdSGDBli69ats+22284uueSSkCTRMXHiRLvooots1apVxW1kPVw9H6d6uDwfgQACCCCAQKIFPM2jJEwSPZSoPAIIIIAAAptPIJ8AplevXmFz1yeeeMLuueceu/DCC+2ZZ56xgw46KDTg8ssvtzvvvDPsbZL0Ix+npLed+iOAAAIIIJCvgKd5lIRJvr3J+QgggAACCJSJQD4BzGOPPWZ9+/a1DRs2BK3999/f5syZs1FOv991111t2rRpidfMxynxjacBCCCAAAII5CngaR4lYZJnZ3I6AggggAAC5SKQbwCjFSX333+/bbvttuH1wnosR4f2LTnnnHPstNNOs+OOOy7xnPk6JR6ABiCAAAIIIJCHgKd5lIRJHh3JqQgggAACCJSTgKcAxrM7Tp57h7ohgAACCHgX8DSPkjDxPlqoHwIIIIAAAk4E6iuAmTdvXnidsI7dd9/dunTp4qSF9VON+nKqn9rwKQgggAACCCRLwNM8SsIkWWOH2iKAAAIIIFA0gXwDmJkzZ9rAgQPtH//4R5U2fP3rX7cJEybYoYceWrS21eeF83Wqz7rwWQgggAACCCRNwNM8SsIkaaOH+iKAAAIIIFAkgXwCmJdeeskOOeSQUPOTTz5546oSrTa56667wp/PmjXL9t133yK1rv4um49T/dWCT0IAAQQQQCCZAp7mURImyRxD1BoBBBBAAIHNLpBPAPO9733PnnvuOXv22Wdtjz32qFL3t99+2w488EA7+OCDw6awST/ycUp626k/AggggAAC+Qp4mkdJmOTbm5yPAAIIIIBAmQjkE8C0bNnSzj//fLvyyitr1PrZz35mv/nNb2zJkiWJ18zHKfGNpwEIIIAAAgjkKeBpHiVhkmdncjoCCCCAAALlIpBPANO0aVO77rrrbNCgQTVyKVkyZMgQ++KLLxLPmY9T4htPAxBAAAEEEMhTwNM8SsIkz87kdAQQQAABBMpFIJ8AZq+99gqP4jzwwAM1ch177LH21ltv2RtvvJF4znycEt94GoAAAggggECeAp7mURImeXYmpyOAAAIIIFAuAvkEMCNHjjT9p7fkjBgxwnbaaafA9vHHH9tVV11lN954ow0fPtyGDRuWeM58nBLfeBqAAAIIIIBAngKe5lESJnl2JqcjgAACCCBQLgL5BDBr1qyxo48+2mbMmGENGjQw7WmiQ3uWVFZWWs+ePe3RRx+1ioqKxHPm45T4xtMABBBAAAEE8hTwNI+SMMmzMzkdAQQQQACBchHIN4DZsGGDTZkyxaZNm2bvvPNOYNt9992tX79+dtppp4VESsOGDRPPma9T4gFoAAIIIIAAAnkIeJpHSZjk0ZGcigACCCCAQDkJFDKAGTVqVHgkZ926dYknLaRT4nFoAAIIIIAAAhkEPM2jJEwYrggggAACCCAQJVDIAEYJE+1fsn79+qi6eC5USCfP7aZuCCCAAAII1IeAp3mUhEl99CifgQACCCCAQBkIFDKAIWFSBgOIJiKAAAIIIBAhUMh4I+LyVYqQMMlWjPIIIIAAAgiUqUAhAxgSJmU6qGg2AggggAAC1QQKGW9ki03CJFsxyiOAAAIIIFCmAoUMYEiYlOmgotkIIIAAAgiQMPExBgoZ6PloIbVAAAEEEECgcAKFnEdJmBSu3/hkBBBAAAEEkiRQyHgjWwdWmGQrRnkEEEAAAQTKVCDbAGb06NHRUjNmzLAnn3ySTV+jxSiIAAIIIIBAaQpkG28UUoGESSF1+WwEEEAAAQRKSCDbAKZhw4ZZtb5BgwYkTLISozACCCCAAAKlJ5BtvFFIARImhdTlsxFAAAEEECghgWwDmJkzZ2bd+u985ztZn+PthGydvNWf+iCAAAIIIFBMAU/zKAmTYo4Ero0AAggggECCBDwFMJ7ZcPLcO9QNAQQQQMC7gKd5lISJ99FC/RBAAAEEEHAi4CmAcUJSYzVw8tw71A0BBBBAwLuAp3mUhIn30UL9EEAAAQQQcCLgKYBxQkLCxHNHUDcEEEAAgUQKeIo3SJgkcghRaQQQQAABBDa/gKcAZvO3Pv6KOMVbURIBBBBAAIHqAp7mURImjE8EEEAAAQQQiBLwFMBEVbhIhXAqEjyXRQABBBAoCQFP8ygJk5IYUjQCAQQQQACBwgt4CmAK39rcr4BT7naciQACCCCAgKd5lIQJ4xEBBBBAAAEEogQ8BTBRFS5SIZyKBM9lEUAAAQRKQsDTPErCpCSGFI1AAAEEEECg8AKeApjCtzb3K+CUux1nIoAAAggg4GkeJWHCeEQAAQQQQACBKAFPAUxUhYtUCKciwXNZBBBAAIGSEPA0j5IwKYkhRSMQQAABBBAovICnAKbwrc39CjjlbseZCCCAAAIIeJpHSZgwHhFAAAEEEEAgSsBTABNV4SIVwqlI8FwWAQQQQKAkBDzNoyRMSmJI0QgEEEAAAQQKL+ApgCl8a3O/Ak6523EmAggggAACnuZREiaMRwQQQAABBBCIEvAUwERVuEiFcCoSPJdFAAEEECgJAU/zKAmTkhhSNAIBBBBAAIHCC3gKYArf2tyvgFPudpyJAAIIIICAp3mUhAnjEQEEEEAAAQSiBDwFMFEVLlIhnIoEz2URQAABBEpCwNM8SsKkJIYUjUAAAQQQQKDwAp4CmMK3Nvcr4JS7HWcigAACCCDgaR4lYcJ4RAABBBBAAIEoAU8BTFSFi1QIpyLBc1kEEEAAgZIQ8DSPkjApiSFFIxBAAAEEECi8gKcApvCtzf0KOOVux5kIIIAAAgh4mkdJmDAeEUAAAQQQQCBKwFMAE1XhIhXCqUjwXBYBBBBAoCQEPM2jJExKYkjRCAQQQAABBAov4CmAKXxrc78CTrnbcSYCCCCAAAKe5lESJoxHBBBAAAEEEIgS8BTARFW4SIVwKhI8l0UAAQQQKAkBT/MoCZOSGFI0AgEEEEAAgcILeApgCt/a3K+AU+52nIkAAggggICneTSrhMm0adNszJgxNm/ePKuoqLAePXrY6NGjrXPnzlG9OnPmzFB+zpw5tmbNGttjjz3snHPOsUGDBlnDhg03fsbSpUvtjjvusEcffdTmz59v//rXv6x169a233772RVXXGHf/OY3o65XvZAn+JwawEkIIIAAAggUUYB5NA4fpzgnSiGAAAIIIFCTgKd5NDphMmnSJDvrrLNCcmTgwIG2evVqGz9+vCm5MXv2bOvSpUudvX3PPffYySefbDvssENIkLRq1cqmT59u999/f/j9jTfeuPH8xx57zPr06WOHH354+G/HHXe0f/zjH3bzzTfb8uXL7c4777STTjop69HlCT7rynMCAggggAACRRZgHo3rAJzinCiFAAIIIIBASSRMlBTp0KGDNW/ePKz40K86Fi5caJ06dbIDDjjAnnrqqVp7e926ddamTRtbuXKlvfbaa7bbbrttLKvky8SJE0PS5aCDDgp//t5775nO6dixY5XP1Ln77ruvbb/99vbhhx9WWZUSM9QIYGKUKIMAAggggEDNAl7n0XxWwD744IP2pz/9yZ577rkQ1zRp0iTEH2effbaddtpp1rhx46yHg1enrBvCCQgggAACCBRBwNM8GrXCZMqUKTZgwAAbMWKEDR8+vApZ//797bbbbgtBRrt27WrkfPnll0Oi48gjjzStHkk/FKAoUXLmmWfaLbfckrE79Dn6vMWLF9vOO++csXx6AU/wWVWcwggggAACCDgQ8DiP5rsCVrHE1ltvbccdd5zttddetmzZMrv77rvtxRdftGOOOcYeeugha9CgQVb6Hp2yagCFEUAAAQQQKKKAp3k0KmFy3nnnhcdhHn/8cevdu3cVOq0O0SqRe++91/r161cj6/PPP28HHnigHX/88aa7QOnH3LlzbZ999rG99947rD6p69iwYYO1bdvWlixZEgIa3QXK5vAEn029KYsAAggggIAHAW/zaL4rYGX65JNPhsd/05Mi69evt8MOO8xmzZplDz/8cEicZHN4c8qm7pRFAAEEEECg2AKe5tGohEnfvn3DHZbXX3893H1JPx555JGw38i4cePswgsvrNH2s88+C3uXaC+SBQsWWNOmTTeWu+GGG2zw4MHWrFmzsD9JXYf2TNE1Tj/9dNOql0yHPi/9M7XJklv7AAAgAElEQVQqRY8PLVq0KCReOBBAAAEEEEAgXsBTAKNa57sCtq6W/+pXv7If//jHds0119jQoUPjkczMm1NWlacwAggggAACRRbwNI9GJUx69eoV9ihRsiN9/xE56s/195kCinPPPdcmTJhgRx11lF155ZUhgfLEE0/YxRdfbKtWrbLKysqwb0ltx4wZM8IjPbvssktYJqt9TDIdeoRo5MiRmxQjYZJJjr9HAAEEEEBgUwFPAYxql+8K2Lr6+LLLLgtvBrz11lvDY8nZHN6csqk7ZRFAAAEEECi2gKd5NCphku8KE4HrNcJDhgwJG7yuXbs29IE2j73++uvDnRslSz799NMa+0YbwirR0qJFC1PipPpmsLV1KCtMij3UuT4CCCCAQCkJeApg5Fof8UlN/aN26q2ADRs2tLfffjvjTRrijVIa5bQFAQQQQKDYAp7ijaiESX3ewVFQoTft6Fnhbt26mZ4TVuJEe5woMVL9+Mtf/hIe+dluu+3CapbYZEltAZA2pmWFSbF/BLg+AggggEASBTwFMPKrjxWw1ftBb/T7zne+EzaY1/5s2n8t08GK1kxC/D0CCCCAAALxAp7ijaiEyeTJk+2MM84Ij7cMGzasSku1TFXPENf1lpy6aKZOnWonnniiXX311XbFFVdUKfr000/bd7/73bD3iZIlX/va1+KVayjpCT6vhnAyAggggAACRRDwNo/W9woTJUu0was2e/31r39tgwYN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C61Lly42atQoa926dYXGb7/9tk2YMMEWLFjg/qxdu9bOP/9897OoR6hOUdvDeQgggAACCORSIKR5tGDnzp07c9n4OK8VEnycDlwbAQQQQACBKAIhzqMTJ060Cy64wCVHBg4caJs2bbJx48bZunXrbO7cudamTZtymzp58mQbMGCAtWjRwg499FB79tlnSZhEGRycgwACCCCAwG4SCCneIGGymzqVl0EAAQQQQCDpAiEFMLJWUqR58+ZWr149W7Jkiftbx/vvv2+tWrWyjh072syZM8vtljVr1li1atVsn332seXLl7vECStMkj6SaR8CCCCAQMgCIcUbJExCHinUDQEEEEAAgYAEQgpgxJJaHTJ8+HAbNmxYCan+/fvblClTXPKkadOmXookTLyYKIQAAggggEBWBUKKN0iYZLWreXEEEEAAAQSSIxBSACPVQYMG2fjx4+25556zHj16lIC+77773C0606ZNsz59+nh1AgkTLyYKIYAAAgggkFWBkOINEiZZ7WpeHAEEEEAAgeQIhBTASLVXr142Y8YMW7p0qR1xxBEloJ966inr2bOn3X777XbZZZd5dULUhMn69etNf1LHqlWr3O1AK1eutCZNmnhdm0IIIIAAAggg8LVASPEGCRNGJQIIIIAAAgh4CYQUwKjC3bt3d3uULFu2zFq2bFmiDfq5/n306NE2ZMgQr/ZFTZjolqARI0bscg0SJl7sFEIgZwI7tm2yDSvnuD/bN6+36jXrWd2mnd2fajVq5aweXAgBBMoXCCneIGHCaEUAAQQQQAABL4GQAhhVmBUmXt1GIQQQMLMtGz6yD+eOtO1frTGzAjPTg0K//rt67QZ20Hevs8K6B2KFAAIBCIQUb5AwCWBAUAUEEEAAAQSqgkBIAYy82MOkKowa6ohA/AJaWbLihcG2/au13yRKStepwKrX3teafX8sK03i7y5qgAC35MQ1BkIL9OJy4LoIIIAAAghEEQhtHp00aZKdd9557naYoUOHlmjSgAED3FN0eEpOlJ7mHASSJfD5ey/YJ29OqLBRDY++0Oq36F5hOQoggEB2BUKKN1hhkt2+5tURQAABBBBIjEBIAYxQ161bZ82aNbP69evbkiVLrF69es5aSZJWrVpZhw4dbNasWe5nGzdudD9X2caNG5fZJ1H3MCn9YqE5JWYA0hAEIgp88LfhtmnN22lWl6RetMBqNTjcmnQt+YjyiJfkNAQQqIRASPMoCZNKdCSnIoAAAgggkE8CIQUwKfd7773XLr74YmvdurV7jPDmzZtt3LhxtmbNGpszZ44dddRRrujs2bOtW7du1q9fP7fyJHV8/vnnrryOzz77zMaOHWvt27e33r17u5/pfO2VkskRolMm9acsAkkTWPH8lbb1i48qbNYeex1ozXrcUmE5CiCAQHYFQppHSZhkt695dQQQQAABBBIjEFIAUxx16tSpNmbMGFu8eLEVFhZaly5dbOTIkda2bduiYukSJqlVJek6qXSCxaczQ3XyqTtlEEiiACtMktirtCnJAiHNoyRMkjzSaBsCCCCAAAK7USCkAGY3Nmu3vxROu52UF0SgUgLsYVIpPk5GIOcCIc2jJExy3v1cEAEEEEAAgaopEFIAE7IgTiH3DnXLRwGekpOPvU6bq7JASPMoCZOqPJKoOwIIIIAAAjkUCCmAyWGzM74UThmTcQICWRfYsuEj+3DuSNv+1RozK/hmA9iv/65eu4Ed9N3rrLDugVmvBxdAAIGKBUKaR0mYVNxflEAAAQQQQAABMwspgAm5Q3AKuXeoWz4LaKXJhpVzbcPKObZ98+dWvWZ9q9u0s9Vt+l2rVqNWPtPQdgSCEghpHiVhEtTQoDIIIIAAAgiEKxBSABOuEomlkPuGuiGAAAIIhC8QUrxBwiT88UINEUAAAQQQCEIgpAAmCJA0lcAp5N6hbggggAACoQuENI+SMAl9tFA/BBBAAAEEAhEIKYAJhKTMauAUcu9QNwQQQACB0AVCmkdJmIQ+WqgfAggggAACgQiEFMAEQkLCJOSOoG4IIIAAAlVSIKR4g4RJlRxCVBoBBBBAAIHcC4QUwOS+9f5XxMnfipIIIIAAAgiUFghpHiVhwvhEAAEEEEAAAS+BkAIYrwrHVAinmOC5LAIIIIBAIgRCmkdJmCRiSNEIBBBAAAEEsi8QUgCT/dZGvwJO0e04EwEEEEAAgZDmURImjEcEEEAAAQQQ8BIIKYDxqnBMhXCKCZ7LIoAAAggkQiCkeZSESSKGFI1AAAEEEEAg+wIhBTDZb230K+AU3Y4zEUAAAQQQCGkeJWHCeEQAAQQQQAABL4GQAhivCsdUCKeY4LksAggggEAiBEKaR0mYJGJI0QgEEEAAAQSyLxBSAJP91ka/Ak7R7TgTAQQQQACBkOZREiaMRwQQQAABBBDwEggpgPGqcEyFcIoJnssigAACCCRCIKR5lIRJIoYUjUAAAQQQQCD7AiEFMNlvbfQr4BTdjjMRQAABBBAIaR4lYcJ4RAABBBBAAAEvgZACGK8Kx1QIp5jguSwCCCCAQCIEQppHSZgkYkjRCAQQQAABBLIvEFIAk/3WRr8CTtHtOBMBBBBAAIGQ5lESJoxHBBBAAAEEEPASCCmA8apwTIVwigmeyyKAAAIIJEIgpHmUhEkihhSNQAABBBBAIPsCIQUw2W9t9CvgFN2OMxFAAAEEEAhpHiVhwnhEAAEEEEAAAS+BkAIYrwrHVAinmOC5LAIIIIBAIgRCmkdJmCRiSNEIBBBAAAEEsi8QUgCT/dZGvwJO0e04EwEEEEAAgZDmURImjEcEEEAAAQQQ8BIIKYDxqnBMhXCKCZ7LIoAAAggkQiCkeZSESSKGFI1AAAEEEEAg+wIhBTDZb230K+AU3Y4zEUAAAQQQCGkeJWHCeEQAAQQQQAABL4GQAhivCsdUCKeY4LksAggggEAiBEKaR0mYJGJI0QgEEEAAAQSyLxBSAJP91ka/Ak7R7TgTAQQQQHx/JGEAACAASURBVACBkOZREiaMRwQQQAABBBDwEggpgPGqcEyFcIoJnssigAACCCRCIKR5lIRJIoYUjUAAAQQQQCD7AiEFMNlvbfQr4BTdjjMRQAABBBAIaR4lYcJ4RAABBBBAAAEvgZACGK8Kx1QIp5jguSwCCCCAQCIEQppHSZgkYkjRCAQQQAABBLIvEFIAk/3WRr8CTtHtOBMBBBBAAIGQ5lESJoxHBBBAAAEEEPASCCmA8apwTIVwigmeyyKAAAIIJEIgpHmUhEkihhSNQAABBBBAIPsCIQUw2W9t9CvgFN2OMxFAAAEEEAhpHiVhwnhEAAEEEEAAAS+BkAIYrwrHVAinmOC5LAIIIIBAIgRCmkdJmCRiSNEIBBBAAAEEsi8QUgCT/dZGvwJO0e04EwEEEEAAgZDmURImjEcEEEAAAQQQ8BIIKYDxqnBMhXCKCZ7LIoAAAggkQiCkeZSESSKGFI1AAAEEEEAg+wIhBTDZb230K+AU3Y4zEUAAAQQQCGkeJWHCeEQAAQQQQAABL4GQAhivCsdUCKeY4LksAggggEAiBEKaR0mYJGJI0QgEEEAAAQSyLxBSAJP91ka/Ak7R7TgTAQQQQACBkObRjBIm06dPt5tvvtkWL15shYWF1qVLFxs1apS1bt3aq1cXLVrkyr/yyiu2evVq23///a19+/b2y1/+0o4//vgSr7Fjxw578MEHbfz48fbOO+/Ypk2brGnTpta7d2+78sorrWHDhl7XLF4oJPiMK88JCCCAAAIIxCzAPOrXATj5OVEKAQQQQACBsgRCmke9EyYTJ060Cy64wCVHBg4c6BIY48aNs3Xr1tncuXOtTZs25fb2vHnzrGvXrtagQQO78MILXfJjxYoVdt9999knn3xiTz/9tJ100klFr3HFFVfYbbfdZt26dXNJkpo1a9pLL71kDzzwgB188MG2cOFCq127dkYjLCT4jCpOYQQSLvDVts/t9U8fsH99+YZt2bnVCgv2sIPrtLdj9vup1a5RP+Gtp3kIVB0B5lG/vsLJz4lSCCCAAAIIJCJhoqRI8+bNrV69erZkyRL3t47333/fWrVqZR07drSZM2eW29t9+/a1hx56yK1OKb4iZf78+W6VyRlnnGF//vOf3Wts3LjR9t57bzv66KPt1VdftYKCgqLXvuyyy1yiZsaMGdazZ8+MRhgBTEZcFEYgJwIffvmmPb7qZttqO3a53h5WzX7Y+Go7qM7ROakLF0EAgfIFmEf9RghOfk6UQgABBBBAIBEJk8mTJ9uAAQNs+PDhNmzYsBJt6t+/v02ZMsUlT7RqJN3Rq1cvl+RYs2aN7bvvvkXFPv74Y2vUqJGde+657nV0qMx+++1nOufxxx8v8ZI33XSTDRkyxCVotPokk4MAJhMtyiKQfQGtLJm8fFCZyZLU1ZU06d/8HlaaZL87uAICFQowj1ZI5Arg5OdEKQQQQAABBBKRMBk0aJDbS+S5556zHj16lGiTbqnRLTrTpk2zPn36pO3xO++80y699FI7+eSTbcSIEUW35Pz61782rTJ58cUXS9zWc8wxx9iCBQvcnid6Xe2ZoltyLrnkEtO/6RaeatWqZTTCCGAy4qIwAlkX+PvqO23BF3MqvM539upiXRr9vMJyFEAAgewKMI/6+eLk50QpBBBAAAEEEpEwSa0OWbp0qR1xxBEl2vTUU0+5W2Nuv/120+0y6Y7t27fb9ddfb0qcfPHFF0XFdHuONpM95JBDSpy6fPlyt6pl9uzZJX7+85//3O1tUqNGjQpH1/r1601/UseqVavc7UMrV660Jk2aVHg+BRBAILsCk5YNsA07v6rwInULatuAb0+qsBwFEEAguwIkAvx8cfJzohQCCCCAAAKJSJh0797d3QKzbNkya9myZYk26ef699GjR7tbZdIdO3futLvuusseeeQRt4nroYce6p5+M2bMGNtrr73c6zdr1qzo9P/85z927bXXmm7ZOfPMM23PPfe0Z5991n7/+9+7RMqECRMqHF26hUirWUofJEwqpKMAAjkRuPdffW2zbavwWjWthg08+MEKy1EAAQSyK0AiwM8XJz8nSiGQa4EdWzbZhiVz3J/tX6636nXqWd0jO7s/1Qpr5bo6XA8BBNIIhDSPej0lZ3esMFEyZezYse42m+KbvmoT2Hbt2tnpp59uf/rTnxzZl19+aW3btrUDDjjAPYGn+Kaveh3tY/Lkk0/aqaeeWu4gY4UJ70EEwhZghUnY/UPtECgtEFIAE3Lv4BRy71C3fBXYsuYj+/DhkbZ9wxozPVBi586iv6vXbWAHnX2dFTY4MF95aDcCQQmENI96JUwqu4fJ1q1b3SoSrSpRgqT0oUcSr1692j1eWMcf/vAH69evn/3ud7+zwYMHlyj+xhtvuD1MrrrqKrc6JZMjJPhM6k1ZBJIqwB4mSe1Z2pVUAeZRv57Fyc+JUgjkSkArS1bcN9i2f7H260RJ6aOgwKrvta81u2gsK01y1SlcB4FyBEKaR70SJpMmTbLzzjvP3d4ydOjQEk3T7TF6ik55T8nR3iEHHnig2/9E+6CUPvRzJUz0+GIdur1Ht+NoJcnVV19dorgeM3zsscfa5ZdfbrfeemtGAy0k+IwqTmEEEiqQekrORtthH1gtW2M1bbtVs+q2wxrYZmtim2xPnpKT0N6nWVVRgHnUr9dw8nOiFAK5Evh8wQv2yTMV387f8AcXWv2ju+eqWlwHAQTSCIQ0j3olTJTI0P4i9evXtyVLlli9evVc05QkadWqlXXo0MFmzZrlfrZx40b3c5Vt3Lix+9mOHTvc7TVr1651t9go4ZE6Xn75ZevcubN7+s4zzzzjfqxHCZ922mnutpzXX3/d9thjj6LyqdUuDz/8sJ111lkZDbKQ4DOqOIURSLDArDWP2f3rnrMdVt3M9FufgqK/q9l2u3Cfk6xbg94JFqBpCFQdAeZRv77Cyc+JUgjkSuCDB4fbppVvfxNfpLtqgdVqerg16TssV9XiOgggkJSEidpx77332sUXX+z2H9FjhDdv3mzjxo2zNWvW2Jw5c+yoo45yzdVTbbp16+ZuqdHKk9Rx9913m55wo1tz9Dp6Ks67775r99xzj+kJOnqssJ5go0P/ryTKK6+84pImffv2Ldr09YknnrBOnTq5a/o8Kad4HxDA8J5EICyB9dvW26Dl19p2Vy0lSkofO10a5Z7mo6xeja8TtRwIIBCfAPOonz1Ofk6UQiBXAivGX25b162u8HJ77NPIml18W4XlKIAAAtkVCGke9VphkuKYOnWq2zdE+5AUFhZaly5dbOTIkS6pkTrSJUz074899phLssyfP982bNhgDRo0sK5du7rHDacSLqnX0cavuiVn2rRp7uk8espOixYt7Ec/+pG7XadOnToZ91JI8BlXnhMQSKDA+NUTbPYXCytsWbe9jraBjc6vsBwFEEAguwLMo36+OPk5UQqBXAksv/Ny27Z+ddm/m0lVYqdZjXqNrPklJExy1S9cB4F0AiHNoxklTKp6l4YEX9UtqT8Cu0PgwmVX2IadW9OsLvlvBFO3YA+7/9uZ7Vm0O+rHayCAQEkB5lG/EYGTnxOlEMiVwPJRl9u2gopXmNSwRtb8GhImueoXroMACZPAxgABTGAdQnXyXuDcf11qWzwUCvX0rIPHeZSkCAIIZFOAedRPFyc/J0ohkCuB5UMut217rjarkfYOYLNtZjU2NrLmvyVhkqt+4ToIkDAJbAwQwATWIVQn7wVYYZL3QwCAKibAPOrXYTj5OVEKgVwJfDBquG1a/k+zJmamZ0mU3GPeTItdPzCr1eIIa3INm77mql+4DgIkTAIbAwQwgXUI1cl7AfYwyfshAEAVE2Ae9eswnPycKIVArgQ+n/2CfTJ5wtd7mGgPef3RrvLadX79N392mjXsf6HVP5HHCueqX7gOAiRMAhsDBDCBdQjVyXsBnpKT90MAgComwDzq12E4+TlRCoFcCezYvMlWXDPYtq9ba7ZTy0tKHQUFVn2ffa3Z6LFWrWatXFWL6yCAQBqBkOZRNn1lmCKAQKwCi75cbDetute2u1/7lFwjW9122q8aD7S2ddrEWkcujgACXwuEFMCE3Cc4hdw71C1fBbas+sg+HDPStq9dY1ZQ8HXi5Ju/q+/bwA765XVW2PjAfOWh3QgEJRDSPErCJKihQWUQyE8BrTR5+NM/2+tfvmWbd26zmgU17Jg6re3s/c60ejW0bpYDAQRCEAgpgAnBI10dcAq5d6hbPgtopcmGl+fahpfn2PbPP7fq9etb3eM6W93jvsvKknweGLQ9OIGQ5lESJsENDyqEAAIIIIBAmAIhBTBhCn1dK5xC7h3qhgACCCAQukBI8ygJk9BHC/VDAAEEEEAgEIGQAphASMqsBk4h9w51QwABBBAIXSCkeZSESeijhfohgAACCCAQiEBIAUwgJCRMQu4I6oYAAgggUCUFQoo3SJhUySFEpRFAAAEEEMi9QEgBTO5b739FnPytKIkAAggggEBpgZDmURImjE8EEEAAAQQQ8BIIKYDxqnBMhXCKCZ7LIoAAAggkQiCkeZSESSKGFI1AAAEEEEAg+wIhBTDZb230K+AU3Y4zEUAAAQQQCGkeJWHCeEQAAQQQQAABL4GQAhivCsdUCKeY4LksAggggEAiBEKaR0mYJGJI0QgEEEAAAQSyLxBSAJP91ka/Ak7R7TgTAQQQQACBkOZREiaMRwQQQAABBBDwEggpgPGqcEyFcIoJnssigAACCCRCIKR5lIRJIoYUjUAAAQQQQCD7AiEFMNlvbfQr4BTdjjMRQAABBBAIaR4lYcJ4RAABBBBAAAEvgZACGK8Kx1QIp5jguSwCCCCAQCIEQppHSZgkYkjRCAQQQAABBLIvEFIAk/3WRr8CTtHtOBMBBBBAAIGQ5lESJoxHBBBAAAEEEPASCCmA8apwTIVwigmeyyKAAAIIJEIgpHmUhEkihhSNQAABBBBAIPsCIQUw2W9t9CvgFN2OMxFAAAEEEAhpHiVhwnhEAAEEEEAAAS+BkAIYrwrHVAinmOC5LAIIIIBAIgRCmkfzKmGyfPlya9Gihc2bN88aN26ciMFEIxBAAAEEEMiVwKpVq6xjx4723nvvWfPmzXN12Sp3HeKNKtdlVBgBBBBAICCBkOKNvEqYvPbaay7Q40AAAQQQQACB6AL6xUOHDh2iv0DCzyTeSHgH0zwEEEAAgZwIhBBv5FXCZNOmTbZ48WJr2LCh1ahRIyedHOUiqYxaPq6Eyee2a6zQ/q9/e83Yz78VcPk89qtS27dt22affPKJtWnTxmrVqhVlisuLc4g3qkY3V6X33u4Wzee253u8Rd8Ta1aFODukeCOvEia7e7LJ1uuFdM9WttqY7nXzue0yof0fWNOmTW3lypXWpEmTXA+/WK9H39P3+TjuY33TcXHmnA/43MnXz518nnPzue35Hmvne99HnfZJmESVy+J5+TyY87nt+f4hnu/tZ+zzxSVfv7hkcTrlpSsQ4HOHz518/dzJ57Gfz20n1szfz7zKBAQkTCqjl6Vz8/mDLJ/bnu8f4vnefsZ+/k7i+d73WZpKeVkPgXwfe/nc/nxuO/FG/s639H1+973HtFhmERImUeWyeN769evtlltusSuvvNLq1auXxSuF99L53Hb1Bu1n7Ofj+z7fx36+v+/Dm4nyp0b5Pvbyuf353HbmnPyNtej7/O77qLM7CZOocpyHAAIIIIAAAggggAACCCCAAAKJFSBhktiupWEIIIAAAggggAACCCCAAAIIIBBVgIRJVDnOQwABBBBAAAEEEEAAAQQQQACBxAqQMEls19IwBBBAAAEEEEAAAQQQQAABBBCIKkDCJKoc5yGAAAIIIIAAAggggAACCCCAQGIFSJhkqWtXrFhh11xzjT3//PP2xRdf2GGHHWaXXHKJXXDBBRldcfr06XbzzTfb4sWLrbCw0Lp06WKjRo2y1q1bl/s6H330kbVq1co+//xzu/HGG+36668vUb5///42ZcqUMl9j8ODB9rvf/S6jehYvnOu2z5s3z8aMGWNvvvmmffzxx7Z9+3b71re+ZT/4wQ/sqquusgMPPHCXtqxZs8aZ/N///Z/pv5s3b27nn3++ezJRjRo1IrddJ+a6/atWrbK77rrL5s+fbwsWLLDVq1db9+7d7YUXXiizHcOHD7cRI0aU+W8/+tGPbOrUqeW2P+qYTL3oxo0b7YYbbrBHHnnEVPfGjRvb2Wefbb/+9a9tzz333OXamXr+7W9/M7Xxtddec6/VoUMH1169d3bHkcv26/ND11O/Llq0yL766it74IEHrG/fvmU2ReNYXmUdTzzxhP3P//xPpQhy2fbbbrvNHn/8cfvnP/9pa9eudU8MO/jgg+3CCy+0c88916pXr75LW5LU96Ubp/e45hAdK1eutCZNmpQoUlBQkLZvNX9UNGdUamBwcqwCmX5Gpqts1Pc38QbxBvEG8Ubxz5XdEW/o9aJ+JkWJN0OLOXLZduKNiqdwEiYVG2VcQs+215c0JSsuv/xya9Gihfti/uSTT7ovcsOGDfN6zYkTJ7oEiwLdgQMH2qZNm2zcuHG2bt06mzt3rrVp0ybt65x22mk2c+ZMl6wpL2GiL1+lDyVa2rVr51XH0oXiaPtDDz3kvkR27NjRffmuVq2a+3I5efJkq1u3rkskFE+abNiwwY499lh7++237Wc/+5m1bdvW9EVLr6FE0qRJkyK1XSfF0f7Zs2dbt27d7KCDDrL27du7L5k+CZNbb73V9ttvvxJtbdasWbmJhcqMSV1IySzV7cUXX7Sf/vSn1rVrV1u4cKHdc889dsIJJ7gEo/ovdWTq+eyzz7qkgCz05bJmzZp23333uS/dTz/9tH3/+9+P3Lc6Mdft13jU+NZ7slatWqbkYEUJk9q1a9t11123SztTYyQqQK7bfs4557jkpT7/NE71CMwZM2bYX//6V5cwKv3ZlbS+L95P77//vh155JHuR/pMT5cwUVLwoosu2qWLe/XqZfXr14/a9ZwXsECmn5HpmlKZ9zfxBvFGRQkT4o3MP0Qq856MEm+FFG/EEW+FFHPkuu+JNyp+f5Iwqdgo4xL67aeC+WnTplmfPn2Kzv/hD3/ovrTpi3rLli3LfV0lRfTbYv1WdcmSJe5vHQqc9eVJyQElRMo69Jt7faHQyhStFikvYbJz586M21feCXG3vXjd/vSnP9lPfvITt7pg6NChRf+k/5bJ2LFj3YqS1HHppZfanXfe6b7M64t8lCOO9isBpJUH+++/v6uyftPskzB577333BjzPSozJlPX+P3vf+9W8sj6jjvuKLq0+kKrgbTqSYapIxNPJWO0AuGTTz6xpUuXulVGOpS41JdNJRL03iuekPFtu8rF0f4PP/zQGjRo4JIlSgAOGDCgwoSJ+lRJtN15xNH2dPU/9dRT3eeofque6uMk9n3x9mu13KeffmqHH364Pfjgg2kTJv369XPjhCN/BDL5jEynUpn3N/HG16rEG+WvaCXeyOwzqTLvyajxVijxRlzxVigxRxx9T7xR8fuThEnFRhmV0O0G+m1oo0aN7N///neJc1MrAcpKYJS+SOrLUVkrUlK30yh50rRp0xKnKqhWQkW/vddvFfVb5fISJjt27DB94a5Tp06ZS9wzaXzcbS9dV/02vlOnTnbFFVfYLbfcUvTP+kKpL9Wy0pfo1LF8+XK3Gkhf6CdMmJBJ013ZUNqfScIk9WV8jz32qLC9Ucdk8Rc+8cQTXUJK1lrNkjqU8FFdjj/++KJbiTL1TL2/ylollLoN6e9//7t17ty5wraWVSDX7U/3mVDRChONb/22T35aYVXerRq+EHG3vXg9tSpMK5K0Mkmrw3Qkue//8Ic/2HnnneduMbv99ttdUjHdChMlTO6//363GlF9z5FsgUw/I9NpRH1/E2/8V5R4o+KECfGG/+dR1Pdk1HgrpHhDdYm7/XHGHHG2nXgj/XuUhIn/55dXyVdffdXd7qGlXVpKX/zYvHmz+4KuWwZ020R5x6BBg2z8+PH23HPPWY8ePUoU1S0GukWn9AoWFfrf//1fe+mll+ytt95yAXZFCROtXNFSd+0HcMwxx9ivfvUrO/30073aWrpQ3G1X8Kg/+vKtFQZDhgxx+5roy6NWXOjQHidKZumLuW5rKn3o1h0lvHRLT6ZH3O1P1dc3YZLqe5XX7V1a9VHeHjtRx2SqXlrNtNdee9nee+9t+k1G6UN9otVUWhGiI1PPm266yfW53h/a56L4offRySefvMuqokz6ONftjxrAaIwrEbplyxa3J4xuQ9KeMUcddVQmzS1RNs6267ctWkGifUx0280vf/lLd8uVbrNKJfqS2vfqSyXAlTDRPk2pZHm6hIkS30qWyEu34Giu+c1vfpPRSrLIg4QTcy6Q6WdkugpGfX8TbxBvEG8Qb+zueEOfU1E/k6LGm8U/G+OOOeJqO/FG+VM4CZPdHOIoiXHGGWfY1VdfbQriSx+6bUKb9WlfjfIOrQ7R/fr64n/EEUeUKPrUU09Zz5493W8bL7vssqJ/S/1cy9VPOeWUot+6lrXCRIkRJRaUJNEX2Hfeecftj6JVK7qVR19KMj3ibLvqWnozU60W0e04Wm2TOt544w3X5h//+Mf26KOP7tJE3er0r3/9y305y/SIu/2p+lYUwGhjK20CqZUWSg5ptce9997rkhX67b02lyzriDImi7+ONtfV9WSsQL/0oT7585//7BImSuZk6pm6pUrvA93CUPzQ+0i35ZRebZRJH+e6/aXr5nNLjm5X0aoqfWbo1qPXX3/d3WamL9BKGkXd+DbOthffyFZjWwmgu+++291+lTqS2vdnnnmm6TNLCXAlv8pLmGjfLG3afOihh5qS83PmzHGrTbTSRMlh3c7DkSyBTD8j07U+yvubeKPk5unEG2WvMCHe+O/q5kw+faK8JysTb4UUb6gucbY/7pgjrrYTb5T/DiVhksZHX759D91moD86tFxe9xTriR/6rW7pQ/fcK/DVb0fLO7QiQnuULFu2bJf9TvRz/fvo0aPdb9R1aJWIvhDqC9Ef//hH97NMbgFSeW0m+J3vfMfdSqREjM8mgSG0PeWoeuuPLPQlQ7t06zez2ng3deiWDO1PoiSKlp6VPvRvWqFT+qlC6foqpPan6lhRwqSstmzbts2NYX2xevnll90qqdJHpmOy9Pn6rbjGv8aoNtktfaTuxdeTc7QKKNP3km6l0h4p2hT0e9/7XomX17j49re/7VZmaeVWlCPX7S9dR5+ESVnt0q0rSlJp36R//OMfUZruPm8y+TyqbN8XP19jUivH9CQObZ6tZKb2vNEGx6kjiX3/l7/8xe2BpVU1J510kmtqeQmTsjpWyXMl0bS66plnnonU95yUfQHiDeINn1FGvPG1UlkxcGXnHOKNkoJxxhuqST7HHHG0nXij4k9gEiZpjDK5719PvUkFPHH9xufiiy92v53XF6LU5p+ZJkxEoS+TWg7me4TQ9nR11T3Fus1DS9JTiSWfFSapx9H6GITY/igJE7VVX6i0MkNPWJFZ6SPXWe9M30tJXWWQ6oeoAYzOP+uss9yKqnfffbfEygyfMa4yue778uqlflZiTKukUptnJ63vP/vsM3crjhJ/2uQ1dWSaMNF5Sn7qc097VWnzYI7wBIg3/PokxPk2VXPijbJXmKTrWeKN8sd83HNunPFGvsccue574g2/+YeEiZ+Td6k47inW7T26zUSrIoo/YUR10dNydBuCbrVQIiX1tJ10DdJvM3U7z8iRI+3aa6/1brcKxtH2iiqoR5Jq5YxuO9HBHibpxfQEGS3b1/4f2gek9JHr+yozHU9J3cdidyRMrrnmGvvtb3/rVhApiZjpkeu+L69+us1It58U//KUtL7/xS9+4W6n0Wqphg0bFnHoVsrp06ebVsppFZZWTVX0Zfvss882PclE+wYVf7x6pmOA8uEJZPoZma4Fmby/iTfSjwPiDf/3CPFG+VaZvCfLeqVM94wr/RqVSZhUNt5QXeJuf3GPXMccuW478Ybf5xYJEz8n71JaOq6dwBs3brzLU3L0dBAtqfR5Ss6kSZPc7SSlH4mriujRovowSz0l57HHHvPaqFVPltBKlPIO7WOi23H0DHBdP5MjjrZXVD99odBO/qmNRFVeT2fRz0o/JUePKdW9i5V5Sk6u+76s9kddYaJbmPTo63S3k2UyJtP1ywknnOBux/F9Sk4mnrNmzXK/kdf7QysQih96H2kVWGWekpPr9u/OAEZ7W+iLdlm3+FX0HtK/x9324nVM3Vanzynt46QjaX3fu3dvd/tRRYf2oapo1YhuXdJqHK0wqVmzZkUvyb9XIYE45lzijfQDhHjD/81DvFG+VdxzbmUSJpWNN/I95sh13xNv+H1ukTDxc8qolFZ16Ak5pZ9ioy+juqdc+5doYk0d+hKzdevWEpvyaZdmfbHXPiLajDO1MkRJEi3V1m9Y9SVBh+7t174bpQ+dpy+JWo6vD7B27dq5JexffvmleypO6UBbj9pVGf2tPR+i/DYy121Xm1evXu1+21r60JOITjvttF3u39dKHK2g0T4IV155ZdFp+gKmhJFuZdIX+yhHHO0vXc/yEibaq0T9X3p/GgXe2gR2wYIFbqPQ4vtDpF4/kzGp19NY1XWUPEwdelyzVrDoFoo77rij6Od67PPgwYNdIlCPRk0dmXhqY9NUwKpb01KP3E7t71NYWOhuSdFmqFGOONpfvJ4VBTDaVFcJptKHEgx6WpY+N6I8/Umvl+u2a4ymfkNWvD36mTYm02frww8/7D7bdCSt77WPUFlPktKGzPp80ibN++67r9vjROM5Xd/LSE9s0xLfip7MFuU9wTnxC2TyGanaEm9Ej7WIN3Yd78Qbn7pb4Yk3vh4buyPeyPeYI9fxFvGG3zxOwsTPKaNS+qKoTRb1Gz1tOKrd0/Xb4BheyAAAC8FJREFUQj31pqzf3qd2ZNaXgeKHgmKtCNEyT21WqScf6Au9gmM9AaGix4Sm28NEj9rVJoBKJhxyyCFFT8nRb+X1RtVTNXQLT5QjjrYrybPPPvu4Ww20qahuwdFS5alTp7ov7FrZI8PUoS/QqafhqJ1yVBlt+pVuM1hfizjar7oV33NEY0yJA+13oEOJt9STgnSvosabknd6koqW+2tljb6Mf/DBB+6x0rp1I93hOyZTY0/JD7126tAXW31516Sq28e0ya42JdVTT7QZrB4BrWRe6sjUU09uUNv0JColwJQkUZ2VpHzyySeLNs/07c/S5XLdfiU4Ul90lczSKhE9hSv13ldb27Zt66qppxHoVirdUqfPHH2R1t4VGtd6nLlu71CiNeqRy7brM0pJSyV6DzvsMPd0JSWGtU+TVkuojerP4smvpPV9Wf2Ubg8T3Xap2620wkqfgXqktP5fiSUlkzVfpPZ7idr/nBemQKafkcQbK1wyNmqsRbxBvJEaO0mbc0KKN2SczzFHLtuebmYj3igpQ8IkSzHQe++95/YAef75590XeD3q8ZJLLrGLLrpolyumC2BUUF/6x4wZ474k6MufvlRqdUTqS1J51U+XMNGKDD32WCsJ9CVEv83Vb6aPO+44t9+JvsRW5sh123Wrkb5IakWNkkn6wi1TJYWuuuoqO+igg3ZpjlbRaKWJvozqqRtKKugWJJWvUaNGZZpvuW6/KlvePgb64qmxoENJN41DbVCnp9YoeaTHSmtFiZJHSqJVdPiMyXQJE7223g96gpQ2IdUTcbQCRSsFhg4danXq1Nnl8pl46mStvNLrpzbvVZJAK62irhoqXaFctj+1qiRdn2jpZioxpi/I+qxQskHjW6uJNPZ79OjhNj1WEqWyR67artvldBuVvugroadxquRnmzZt3IoJ3TZXPLGWaleS+j6ThIk+x/Q5qMcPy05fCPUZqMfPKwma2gi8sv3P+WEKZPIZSbyxa8Ikk1iLeIN4o/inQJLmnNDiDd/35e6IN0OMOXIVb2WaMMnXeIOESZjxD7VCAAEEEEAAAQQQQAABBBBAAIEYBUiYxIjPpRFAAAEEEEAAAQQQQAABBBBAIEwBEiZh9gu1QgABBBBAAAEEEEAAAQQQQACBGAVImMSIz6URQAABBBBAAAEEEEAAAQQQQCBMARImYfYLtUIAAQQQQAABBBBAAAEEEEAAgRgFSJjEiM+lEUAAAQQQQAABBBBAAAEEEEAgTAESJmH2C7VCAAEEEEAAAQQQQAABBBBAAIEYBUiYxIjPpRFAAAEEEEAAAQQQQAABBBBAIEwBEiZh9gu1QgABBBBAAAEEEEAAAQQQQACBGAVImMSIz6URQAABBBBAAAEEEEAAAQQQQCBMARImYfYLtUIAAQQQQAABBBBAAAEEEEAAgRgFSJjEiM+lEUCgcgKTJ0+2AQMG2KxZs+zEE0+s3ItxNgIIIIAAAgggUIYA8QbDAoH8FSBhkr99T8vzXGD27NnWrVs3u/HGG+366693GsOHD7ejjz7aevfuHYzOY489Zm+++aarW+mDACaYbqIiCCCAAAIIlClAvMHAQACBqixAwqQq9x51R6ASAmUFMAUFBdavXz9TIiKUo3///jZlyhTbuXPnLlXavn27bd261QoLC61atWqhVJl6IIAAAggggMA3AsQbDAUEEKjKAiRMqnLvUXcEKiGQ6wBmy5YttmPHDqtVq1ZGtS4vYZLRC1EYAQQQQAABBHIuQLyRc3IuiAACu1GAhMluxOSlEKhKAsUDmM6dO7vbc8o6iq/sWLBggY0cOdL+9re/2WeffWZNmjSxH//4xzZ06FDbc889i05PJTk+/fRTu+aaa+yJJ56w//znP/bXv/7V7TXy6KOP2sMPP2x6vY8//tid27FjR3drkOqSOpo3b24rVqzYpVqTJk0yXSPdLTmq2w033GB/+ctf7KOPPrK9997bvve977mfHXLIIUWvt3z5cmvRooUNGzbMOnXqZCNGjLCFCxfaXnvtZX369LFbbrnF6tSpU5W6lboigAACCCAQlADxhhnxRlBDksogkJEACZOMuCiMQHIEigcwF154oT3//PP205/+1Lp06WIXXXRRUUP79u3r/vuZZ55xe5s0bdrUzj33XDvggANccuH+++93yQZtvFqjRg1XNpUw0X4oDRo0sF69ernVJT/4wQ/s8MMPd9fYZ599rEOHDta4cWNbuXKlTZw40SVPXnzxRTv++OPd62j/EiUt/v73v9sDDzxQVCf9e8uWLctMmGzYsMGOPfZYW7p0qZ199tkuAbNs2TK7++673eqWuXPnWqtWrdxrpQIYJWtUZuDAga59SuxMnTrV/f/48eOT0+m0BAEEEEAAgRwLEG8Qb+R4yHE5BHarAAmT3crJiyFQdQQyWSK7adMmtxLjW9/6lltdUrNmzaKGTps2zc444wyXvND+J8UTJmeddZb98Y9/NO2NUvz48ssvd1m5sXr1amvdurVLvjz55JNFxcu7JaesFSZa7aKNbLUS5tprry16HSVitLqle/fu9sILL5RImNSuXdveeustl4RJHaeccorNnDnT1q1bxyqTqjOsqSkCCCCAQGACxBv/TZgQbwQ2OKkOAh4CJEw8kCiCQBIFMglgZsyY4VaJaJXGmWeeWYJDt+zo1pnTTjvNJUeKJ0x0y41WmZR3aEWI9jfR6yjh8uqrr5pu5UkdmSZMjjzySPvggw/capXS+6Xothy1e82aNW6FS2qFyTnnnGMPPfRQiWqOHTvWrrrqKlu8eLFL5HAggAACCCCAQOYCxBvEG5mPGs5AIBwBEibh9AU1QSCnApkEMGPGjLGrr7663PopGaFbWYonTLSSpPjeJqkXWLRokdv3RCs4lDApfmg1im7fiZow0W9vlDR5/fXXd6nvL37xC7vjjjvsjTfesHbt2hUlTLR3ilalFD9Sq1fkdMIJJ+S0b7gYAggggAACSREg3iDeSMpYph35KUDCJD/7nVYj4FZaaKNXJQqUMNCR7rHCN910kw0ZMsTd5qL9Pso6tGKjffv2JRImZT0KWKs/tIeINla99NJL3Z4m2lhVjwUePXq0S6IUPy/TFSZREiba9HX48OFlJky0N4tu5eFAAAEEEEAAgcwFiDf+mzAh3sh8/HAGAnELkDCJuwe4PgIxCWQSwOhpM3pqjG5TufLKKyuscXlJDq3w0EoPrUbRqpTih/YvmTdvXomEyYABA9z+KGUlX8raw0S3z6RuySm+14quo/1LlAApfUsOAUyFXUoBBBBAAAEEIgkQb/z3lhzijUhDiJMQiFWAhEms/FwcgfgEygpg6tat61adPP744yUqtnHjRrchqlagaF+SRo0alfj3bdu22fr1623fffd1Py8vYXLXXXfZJZdc4jZeVQIjdTz99NN26qmnuv8tnhzRKpQ777zTJTlSr586p7xNX3UbkfYgSR160k7Xrl3L3PSVACa+cciVEUAAAQSSLUC8UfKxwqxoTfZ4p3XJEyBhkrw+pUUIeAmUFcD06NHDPXZXCQQ9EUcJEj3pRoceO6yNXbVqQ6s+dCuN9h/R43inT59uv/3tb12ipKKEyb///W9r27at1atXz37+85/bfvvtZ/Pnz3ebriopo01WiydM9HM92vgnP/mJ9ezZ0/bYYw/3JB09taeshEnxxwrrPD2COPVY4cLCQnvppZd2eawwCROvIUMhBBBAAAEEMhYg3iBhkvGg4QQEAhIgYRJQZ1AVBHIpUFYA8+6777okxiuvvFK0GWvx5MU///lPlxjR7TR6Ck39+vWtWbNmdtJJJ9mgQYOsadOmFSZMVEBJGT3y980333TJkQ4dOtgNN9xg999/v02ZMqVEwkQbwGrD2UceecRWrVrlNoSdNGmSS86UlTDR6+tRwHo93Ur00UcfuXp+//vftxEjRtihhx5axJx6Sg4Jk1yOPK6FAAIIIJBPAsQbJEzyabzT1uQJkDBJXp/SIgQQQAABBBBAAAEEEEAAAQQQqKTA/wPwU8SHe3ThcAAAAABJRU5ErkJggg==\" width=\"999.9999783255842\">"
],
"text/plain": [
"<IPython.core.display.HTML object>"
]
},
"metadata": {},
"output_type": "display_data"
},
{
"name": "stdout",
"output_type": "stream",
"text": [
"1 rows affected.\n",
"1 rows affected.\n",
"1 rows affected.\n",
"1 rows affected.\n",
"1 rows affected.\n",
"1 rows affected.\n",
"1 rows affected.\n",
"1 rows affected.\n"
]
}
],
"source": [
"#df_results = %sql SELECT * FROM $results_table ORDER BY run_id;\n",
"df_results = %sql SELECT * FROM $results_table ORDER BY validation_loss ASC LIMIT 12;\n",
"df_results = df_results.DataFrame()\n",
"\n",
"#set up plots\n",
"fig, axs = plt.subplots(nrows=1, ncols=2, figsize=(10,5))\n",
"fig.legend(ncol=4)\n",
"fig.tight_layout()\n",
"\n",
"ax_metric = axs[0]\n",
"ax_loss = axs[1]\n",
"\n",
"ax_metric.xaxis.set_major_locator(MaxNLocator(integer=True))\n",
"ax_metric.set_xlabel('Iteration')\n",
"ax_metric.set_ylabel('Metric')\n",
"ax_metric.set_title('Validation metric curve')\n",
"\n",
"ax_loss.xaxis.set_major_locator(MaxNLocator(integer=True))\n",
"ax_loss.set_xlabel('Iteration')\n",
"ax_loss.set_ylabel('Loss')\n",
"ax_loss.set_title('Validation loss curve')\n",
"\n",
"for run_id in df_results['run_id']:\n",
" df_output_info = %sql SELECT validation_metrics,validation_loss FROM $results_table WHERE run_id = $run_id\n",
" df_output_info = df_output_info.DataFrame()\n",
" validation_metrics = df_output_info['validation_metrics'][0]\n",
" validation_loss = df_output_info['validation_loss'][0]\n",
" X = range(len(validation_metrics))\n",
" \n",
" ax_metric.plot(X, validation_metrics, label=run_id, marker='o')\n",
" ax_loss.plot(X, validation_loss, label=run_id, marker='o')\n",
"\n",
"# fig.savefig('./lc_keras_fit.png', dpi = 300)"
]
},
{
"cell_type": "markdown",
"metadata": {},
"source": [
"<a id=\"print\"></a>\n",
"# 7. Print run schedules (display only)"
]
},
{
"cell_type": "markdown",
"metadata": {},
"source": [
"Pretty print reg Hyperband run schedule"
]
},
{
"cell_type": "code",
"execution_count": 32,
"metadata": {
"scrolled": false
},
"outputs": [
{
"name": "stdout",
"output_type": "stream",
"text": [
"max_iter = 9\n",
"eta = 3\n",
"B = 3*max_iter = 27\n",
" \n",
"s=2\n",
"n_i r_i\n",
"------------\n",
"9 1.0\n",
"3.0 3.0\n",
"1.0 9.0\n",
" \n",
"s=1\n",
"n_i r_i\n",
"------------\n",
"3 3.0\n",
"1.0 9.0\n",
" \n",
"s=0\n",
"n_i r_i\n",
"------------\n",
"3 9\n",
" \n",
"sum of configurations at leaf nodes across all s = 5.0\n",
"(if have more workers than this, they may not be 100% busy)\n"
]
}
],
"source": [
"import numpy as np\n",
"from math import log, ceil\n",
"\n",
"#input\n",
"max_iter = 9 # maximum iterations/epochs per configuration\n",
"eta = 3 # defines downsampling rate (default=3)\n",
"\n",
"logeta = lambda x: log(x)/log(eta)\n",
"s_max = int(logeta(max_iter)) # number of unique executions of Successive Halving (minus one)\n",
"B = (s_max+1)*max_iter # total number of iterations (without reuse) per execution of Succesive Halving (n,r)\n",
"\n",
"#echo output\n",
"print (\"max_iter = \" + str(max_iter))\n",
"print (\"eta = \" + str(eta))\n",
"print (\"B = \" + str(s_max+1) + \"*max_iter = \" + str(B))\n",
"\n",
"sum_leaf_n_i = 0 # count configurations at leaf nodes across all s\n",
"\n",
"#### Begin Finite Horizon Hyperband outlerloop. Repeat indefinitely.\n",
"for s in reversed(range(s_max+1)):\n",
" \n",
" print (\" \")\n",
" print (\"s=\" + str(s))\n",
" print (\"n_i r_i\")\n",
" print (\"------------\")\n",
" counter = 0\n",
" \n",
" n = int(ceil(int(B/max_iter/(s+1))*eta**s)) # initial number of configurations\n",
" r = max_iter*eta**(-s) # initial number of iterations to run configurations for\n",
"\n",
" #### Begin Finite Horizon Successive Halving with (n,r)\n",
" #T = [ get_random_hyperparameter_configuration() for i in range(n) ] \n",
" for i in range(s+1):\n",
" # Run each of the n_i configs for r_i iterations and keep best n_i/eta\n",
" n_i = n*eta**(-i)\n",
" r_i = r*eta**(i)\n",
" \n",
" print (str(n_i) + \" \" + str (r_i))\n",
" \n",
" # check if leaf node for this s\n",
" if counter == s:\n",
" sum_leaf_n_i += n_i\n",
" counter += 1\n",
" \n",
" #val_losses = [ run_then_return_val_loss(num_iters=r_i,hyperparameters=t) for t in T ]\n",
" #T = [ T[i] for i in argsort(val_losses)[0:int( n_i/eta )] ]\n",
" #### End Finite Horizon Successive Halving with (n,r)\n",
"\n",
"print (\" \")\n",
"print (\"sum of configurations at leaf nodes across all s = \" + str(sum_leaf_n_i))\n",
"print (\"(if have more workers than this, they may not be 100% busy)\")"
]
},
{
"cell_type": "markdown",
"metadata": {},
"source": [
"Pretty print Hyperband diagonal run schedule"
]
},
{
"cell_type": "code",
"execution_count": 33,
"metadata": {},
"outputs": [
{
"name": "stdout",
"output_type": "stream",
"text": [
"echo input:\n",
"max_iter = 9\n",
"eta = 3\n",
"s_max = 2\n",
"B = 3*max_iter = 27\n",
" \n",
"initial n, r values for each s:\n",
"s=2\n",
"n=9\n",
"r=1.0\n",
" \n",
"s=1\n",
"n=3\n",
"r=3.0\n",
" \n",
"s=0\n",
"n=3\n",
"r=9\n",
" \n",
"outer loop on diagonal:\n",
" \n",
"i=0\n",
"inner loop on s desc:\n",
"s=2\n",
"n_i=9\n",
"r_i=1.0\n",
" \n",
"i=1\n",
"inner loop on s desc:\n",
"s=2\n",
"n_i=3.0\n",
"r_i=3.0\n",
"s=1\n",
"n_i=3\n",
"r_i=3.0\n",
" \n",
"i=2\n",
"inner loop on s desc:\n",
"s=2\n",
"n_i=1.0\n",
"r_i=9.0\n",
"s=1\n",
"n_i=1.0\n",
"r_i=9.0\n",
"s=0\n",
"n_i=3\n",
"r_i=9\n"
]
}
],
"source": [
"import numpy as np\n",
"from math import log, ceil\n",
"\n",
"#input\n",
"max_iter = 9 # maximum iterations/epochs per configuration\n",
"eta = 3 # defines downsampling rate (default=3)\n",
"\n",
"logeta = lambda x: log(x)/log(eta)\n",
"s_max = int(logeta(max_iter)) # number of unique executions of Successive Halving (minus one)\n",
"B = (s_max+1)*max_iter # total number of iterations (without reuse) per execution of Succesive Halving (n,r)\n",
"\n",
"#echo output\n",
"print (\"echo input:\")\n",
"print (\"max_iter = \" + str(max_iter))\n",
"print (\"eta = \" + str(eta))\n",
"print (\"s_max = \" + str(s_max))\n",
"print (\"B = \" + str(s_max+1) + \"*max_iter = \" + str(B))\n",
"\n",
"print (\" \")\n",
"print (\"initial n, r values for each s:\")\n",
"initial_n_vals = {}\n",
"initial_r_vals = {}\n",
"# get hyper parameter configs for each s\n",
"for s in reversed(range(s_max+1)):\n",
" \n",
" n = int(ceil(int(B/max_iter/(s+1))*eta**s)) # initial number of configurations\n",
" r = max_iter*eta**(-s) # initial number of iterations to run configurations for\n",
" \n",
" initial_n_vals[s] = n \n",
" initial_r_vals[s] = r \n",
" \n",
" print (\"s=\" + str(s))\n",
" print (\"n=\" + str(n))\n",
" print (\"r=\" + str(r))\n",
" print (\" \")\n",
" \n",
"print (\"outer loop on diagonal:\")\n",
"# outer loop on diagonal\n",
"for i in range(s_max+1):\n",
" print (\" \")\n",
" print (\"i=\" + str(i))\n",
" \n",
" print (\"inner loop on s desc:\")\n",
" # inner loop on s desc\n",
" for s in range(s_max, s_max-i-1, -1):\n",
" n_i = initial_n_vals[s]*eta**(-i+s_max-s)\n",
" r_i = initial_r_vals[s]*eta**(i-s_max+s)\n",
" \n",
" print (\"s=\" + str(s))\n",
" print (\"n_i=\" + str(n_i))\n",
" print (\"r_i=\" + str(r_i))"
]
}
],
"metadata": {
"kernelspec": {
"display_name": "Python 2",
"language": "python",
"name": "python2"
},
"language_info": {
"codemirror_mode": {
"name": "ipython",
"version": 2
},
"file_extension": ".py",
"mimetype": "text/x-python",
"name": "python",
"nbconvert_exporter": "python",
"pygments_lexer": "ipython2",
"version": "2.7.10"
}
},
"nbformat": 4,
"nbformat_minor": 1
}