diff --git a/Practical Reinforcement Learning/Week5_policy_based/A policy-based quiz.pdf b/Practical Reinforcement Learning/Week5_policy_based/A policy-based quiz.pdf new file mode 100644 index 0000000..777de77 Binary files /dev/null and b/Practical Reinforcement Learning/Week5_policy_based/A policy-based quiz.pdf differ diff --git a/Practical Reinforcement Learning/Week5_policy_based/atari_util.py b/Practical Reinforcement Learning/Week5_policy_based/atari_util.py new file mode 100644 index 0000000..0f6dd2a --- /dev/null +++ b/Practical Reinforcement Learning/Week5_policy_based/atari_util.py @@ -0,0 +1,59 @@ +"""Auxilary files for those who wanted to solve breakout with CEM or policy gradient""" +import numpy as np +import gym +from scipy.misc import imresize +from gym.core import Wrapper +from gym.spaces.box import Box + +class PreprocessAtari(Wrapper): + def __init__(self, env, height=42, width=42, color=False, crop=lambda img: img, + n_frames=4, dim_order='theano', reward_scale=1,): + """A gym wrapper that reshapes, crops and scales image into the desired shapes""" + super(PreprocessAtari, self).__init__(env) + assert dim_order in ('theano', 'tensorflow') + self.img_size = (height, width) + self.crop=crop + self.color=color + self.dim_order = dim_order + self.reward_scale = reward_scale + + n_channels = (3 * n_frames) if color else n_frames + obs_shape = [n_channels,height,width] if dim_order == 'theano' else [height,width,n_channels] + self.observation_space = Box(0.0, 1.0, obs_shape) + self.framebuffer = np.zeros(obs_shape, 'float32') + + def reset(self): + """resets breakout, returns initial frames""" + self.framebuffer = np.zeros_like(self.framebuffer) + self.update_buffer(self.env.reset()) + return self.framebuffer + + def step(self,action): + """plays breakout for 1 step, returns frame buffer""" + new_img, reward, done, info = self.env.step(action) + self.update_buffer(new_img) + return self.framebuffer, reward * self.reward_scale, done, info + + ### image processing ### + + def update_buffer(self,img): + img = self.preproc_image(img) + offset = 3 if self.color else 1 + if self.dim_order == 'theano': + axis = 0 + cropped_framebuffer = self.framebuffer[:-offset] + else: + axis = -1 + cropped_framebuffer = self.framebuffer[:,:,:-offset] + self.framebuffer = np.concatenate([img, cropped_framebuffer], axis = axis) + + def preproc_image(self, img): + """what happens to the observation""" + img = self.crop(img) + img = imresize(img, self.img_size) + if not self.color: + img = img.mean(-1, keepdims=True) + if self.dim_order == 'theano': + img = img.transpose([2,0,1]) # [h, w, c] to [c, h, w] + img = img.astype('float32') / 255. + return img diff --git a/Practical Reinforcement Learning/Week5_policy_based/env_pool.png b/Practical Reinforcement Learning/Week5_policy_based/env_pool.png new file mode 100644 index 0000000..a03e579 Binary files /dev/null and b/Practical Reinforcement Learning/Week5_policy_based/env_pool.png differ diff --git a/Practical Reinforcement Learning/Week5_policy_based/nnet_arch.png b/Practical Reinforcement Learning/Week5_policy_based/nnet_arch.png new file mode 100644 index 0000000..ee27fba Binary files /dev/null and b/Practical Reinforcement Learning/Week5_policy_based/nnet_arch.png differ diff --git a/Practical Reinforcement Learning/Week5_policy_based/practice_a3c.ipynb b/Practical Reinforcement Learning/Week5_policy_based/practice_a3c.ipynb new file mode 100644 index 0000000..f5379ab --- /dev/null +++ b/Practical Reinforcement Learning/Week5_policy_based/practice_a3c.ipynb @@ -0,0 +1,873 @@ +{ + "cells": [ + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "### Deep Kung-Fu with advantage actor-critic\n", + "\n", + "In this notebook you'll build a deep reinforcement learning agent for atari [KungFuMaster](https://gym.openai.com/envs/KungFuMaster-v0/) and train it with advantage actor-critic.\n", + "\n", + "![http://www.retroland.com/wp-content/uploads/2011/07/King-Fu-Master.jpg](http://www.retroland.com/wp-content/uploads/2011/07/King-Fu-Master.jpg)" + ] + }, + { + "cell_type": "code", + "execution_count": 1, + "metadata": {}, + "outputs": [], + "source": [ + "from __future__ import print_function, division\n", + "from IPython.core import display\n", + "import matplotlib.pyplot as plt\n", + "%matplotlib inline\n", + "import numpy as np\n", + "\n", + "#If you are running on a server, launch xvfb to record game videos\n", + "#Please make sure you have xvfb installed\n", + "import os\n", + "if os.environ.get(\"DISPLAY\") is str and len(os.environ.get(\"DISPLAY\"))!=0:\n", + " !bash ../xvfb start\n", + " %env DISPLAY=:1" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "For starters, let's take a look at the game itself:\n", + "* Image resized to 42x42 and grayscale to run faster\n", + "* Rewards divided by 100 'cuz they are all divisible by 100\n", + "* Agent sees last 4 frames of game to account for object velocity" + ] + }, + { + "cell_type": "code", + "execution_count": 2, + "metadata": {}, + "outputs": [ + { + "name": "stdout", + "output_type": "stream", + "text": [ + "\u001b[33mWARN: gym.spaces.Box autodetected dtype as . Please provide explicit dtype.\u001b[0m\n", + "Observation shape: (42, 42, 4)\n", + "Num actions: 14\n", + "Action names: ['NOOP', 'UP', 'RIGHT', 'LEFT', 'DOWN', 'DOWNRIGHT', 'DOWNLEFT', 'RIGHTFIRE', 'LEFTFIRE', 'DOWNFIRE', 'UPRIGHTFIRE', 'UPLEFTFIRE', 'DOWNRIGHTFIRE', 'DOWNLEFTFIRE']\n" + ] + } + ], + "source": [ + "import gym\n", + "from atari_util import PreprocessAtari\n", + "\n", + "def make_env():\n", + " env = gym.make(\"KungFuMasterDeterministic-v0\")\n", + " env = PreprocessAtari(env, height=42, width=42,\n", + " crop = lambda img: img[60:-30, 5:],\n", + " dim_order = 'tensorflow',\n", + " color=False, n_frames=4,\n", + " reward_scale = 0.01)\n", + " return env\n", + "\n", + "env = make_env()\n", + "\n", + "obs_shape = env.observation_space.shape\n", + "n_actions = env.action_space.n\n", + "\n", + "print(\"Observation shape:\", obs_shape)\n", + "print(\"Num actions:\", n_actions)\n", + "print(\"Action names:\", env.env.env.get_action_meanings())" + ] + }, + { + "cell_type": "code", + "execution_count": 3, + "metadata": {}, + "outputs": [ + { + "data": { + "image/png": "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\n", + "text/plain": [ + "
" + ] + }, + "metadata": {}, + "output_type": "display_data" + }, + { + "data": { + "image/png": "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\n", + "text/plain": [ + "
" + ] + }, + "metadata": {}, + "output_type": "display_data" + } + ], + "source": [ + "s = env.reset()\n", + "for _ in range(100):\n", + " s, _, _, _ = env.step(env.action_space.sample())\n", + "\n", + "plt.title('Game image')\n", + "plt.imshow(env.render('rgb_array'))\n", + "plt.show()\n", + "\n", + "plt.title('Agent observation (4-frame buffer)')\n", + "plt.imshow(s.transpose([0,2,1]).reshape([42,-1]))\n", + "plt.show()" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "### Build an agent\n", + "\n", + "We now have to build an agent for actor-critic training - a convolutional neural network that converts states into action probabilities $\\pi$ and state values $V$.\n", + "\n", + "Your assignment here is to build and apply a neural network - with any framework you want. \n", + "\n", + "For starters, we want you to implement this architecture:\n", + "![nnet_arch.png](nnet_arch.png)\n", + "\n", + "After your agent gets mean reward above 50, we encourage you to experiment with model architecture to score even better." + ] + }, + { + "cell_type": "code", + "execution_count": 4, + "metadata": {}, + "outputs": [], + "source": [ + "import tensorflow as tf\n", + "tf.reset_default_graph()\n", + "sess = tf.InteractiveSession()" + ] + }, + { + "cell_type": "code", + "execution_count": 5, + "metadata": {}, + "outputs": [ + { + "name": "stderr", + "output_type": "stream", + "text": [ + "Using TensorFlow backend.\n" + ] + } + ], + "source": [ + "import keras\n", + "from keras.layers import Conv2D, Dense, Flatten, Input\n", + "from keras.models import Model\n", + "\n", + "class Agent:\n", + " def __init__(self, name, state_shape, n_actions, reuse=False):\n", + " \"\"\"A simple actor-critic agent\"\"\"\n", + " \n", + " with tf.variable_scope(name, reuse=reuse):\n", + " \n", + " # Prepare neural network architecture\n", + " ### Your code here: prepare any necessary layers, variables, etc.\n", + " inputs = Input(shape=state_shape)\n", + " x = Conv2D(32, (3, 3), strides=2, activation='relu')(inputs)\n", + " x = Conv2D(32, (3, 3), strides=2, activation='relu')(x)\n", + " x = Conv2D(32, (3, 3), strides=2, activation='relu')(x)\n", + " x = Flatten()(x)\n", + " x = Dense(128, activation='relu')(x)\n", + " logits = Dense(n_actions, activation='linear')(x)\n", + " state_value = Dense(1, activation='linear')(x)\n", + " \n", + " self.network = Model(inputs=inputs, outputs=[logits, state_value])\n", + " \n", + " # prepare a graph for agent step\n", + " self.state_t = tf.placeholder('float32', [None,] + list(state_shape))\n", + " self.agent_outputs = self.symbolic_step(self.state_t)\n", + " \n", + " def symbolic_step(self, state_t):\n", + " \"\"\"Takes agent's previous step and observation, returns next state and whatever it needs to learn (tf tensors)\"\"\"\n", + " \n", + " # Apply neural network\n", + " ### Your code here: apply agent's neural network to get policy logits and state values.\n", + " \n", + " logits, state_value = self.network(state_t)\n", + " state_value = state_value[:, 0]\n", + " \n", + " assert tf.is_numeric_tensor(state_value) and state_value.shape.ndims == 1, \\\n", + " \"please return 1D tf tensor of state values [you got %s]\" % repr(state_value)\n", + " assert tf.is_numeric_tensor(logits) and logits.shape.ndims == 2, \\\n", + " \"please return 2d tf tensor of logits [you got %s]\" % repr(logits)\n", + " # hint: if you triggered state_values assert with your shape being [None, 1], \n", + " # just select [:, 0]-th element of state values as new state values\n", + " \n", + " return (logits, state_value)\n", + " \n", + " def step(self, state_t):\n", + " \"\"\"Same as symbolic step except it operates on numpy arrays\"\"\"\n", + " sess = tf.get_default_session()\n", + " return sess.run(self.agent_outputs, {self.state_t: state_t})\n", + " \n", + " def sample_actions(self, agent_outputs):\n", + " \"\"\"pick actions given numeric agent outputs (np arrays)\"\"\"\n", + " logits, state_values = agent_outputs\n", + " policy = np.exp(logits) / np.sum(np.exp(logits), axis=-1, keepdims=True)\n", + " return np.array([np.random.choice(len(p), p=p) for p in policy])" + ] + }, + { + "cell_type": "code", + "execution_count": 6, + "metadata": {}, + "outputs": [], + "source": [ + "agent = Agent(\"agent\", obs_shape, n_actions)\n", + "sess.run(tf.global_variables_initializer())" + ] + }, + { + "cell_type": "code", + "execution_count": 7, + "metadata": {}, + "outputs": [ + { + "name": "stdout", + "output_type": "stream", + "text": [ + "action logits:\n", + " [[ 0.07089273 0.02756768 -0.05570034 0.06700554 0.06069527 -0.01415996\n", + " -0.00033508 0.00273614 -0.04520407 0.00565409 -0.08711416 0.03533225\n", + " -0.00779367 0.06893449]]\n", + "state values:\n", + " [0.05507889]\n" + ] + } + ], + "source": [ + "state = [env.reset()]\n", + "logits, value = agent.step(state)\n", + "print(\"action logits:\\n\", logits)\n", + "print(\"state values:\\n\", value)" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "### Let's play!\n", + "Let's build a function that measures agent's average reward." + ] + }, + { + "cell_type": "code", + "execution_count": 8, + "metadata": {}, + "outputs": [], + "source": [ + "def evaluate(agent, env, n_games=1):\n", + " \"\"\"Plays an a game from start till done, returns per-game rewards \"\"\"\n", + "\n", + " game_rewards = []\n", + " for _ in range(n_games):\n", + " state = env.reset()\n", + " \n", + " total_reward = 0\n", + " while True:\n", + " action = agent.sample_actions(agent.step([state]))[0]\n", + " state, reward, done, info = env.step(action)\n", + " total_reward += reward\n", + " if done: break\n", + " \n", + " game_rewards.append(total_reward)\n", + " return game_rewards\n" + ] + }, + { + "cell_type": "code", + "execution_count": 9, + "metadata": {}, + "outputs": [ + { + "name": "stdout", + "output_type": "stream", + "text": [ + "[2.0, 7.0, 11.0]\n" + ] + } + ], + "source": [ + "env_monitor = gym.wrappers.Monitor(env, directory=\"kungfu_videos\", force=True)\n", + "rw = evaluate(agent, env_monitor, n_games=3,)\n", + "env_monitor.close()\n", + "print (rw)" + ] + }, + { + "cell_type": "code", + "execution_count": 10, + "metadata": {}, + "outputs": [ + { + "data": { + "text/html": [ + "\n", + "\n" + ], + "text/plain": [ + "" + ] + }, + "execution_count": 10, + "metadata": {}, + "output_type": "execute_result" + } + ], + "source": [ + "#show video\n", + "from IPython.display import HTML\n", + "import os\n", + "\n", + "video_names = list(filter(lambda s:s.endswith(\".mp4\"),os.listdir(\"./kungfu_videos/\")))\n", + "\n", + "HTML(\"\"\"\n", + "\n", + "\"\"\".format(\"./kungfu_videos/\"+video_names[-1])) #this may or may not be _last_ video. Try other indices\n", + "\n" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "### Training on parallel games\n", + "![img](env_pool.png)\n", + "\n", + "To make actor-critic training more stable, we shall play several games in parallel. This means ya'll have to initialize several parallel gym envs, send agent's actions there and .reset() each env if it becomes terminated. To minimize learner brain damage, we've taken care of them for ya - just make sure you read it before you use it.\n" + ] + }, + { + "cell_type": "code", + "execution_count": 11, + "metadata": {}, + "outputs": [], + "source": [ + "class EnvBatch:\n", + " def __init__(self, n_envs = 10):\n", + " \"\"\" Creates n_envs environments and babysits them for ya' \"\"\"\n", + " self.envs = [make_env() for _ in range(n_envs)]\n", + " \n", + " def reset(self):\n", + " \"\"\" Reset all games and return [n_envs, *obs_shape] observations \"\"\"\n", + " return np.array([env.reset() for env in self.envs])\n", + " \n", + " def step(self, actions):\n", + " \"\"\"\n", + " Send a vector[batch_size] of actions into respective environments\n", + " :returns: observations[n_envs, *obs_shape], rewards[n_envs], done[n_envs,], info[n_envs]\n", + " \"\"\"\n", + " results = [env.step(a) for env, a in zip(self.envs, actions)]\n", + " new_obs, rewards, done, infos = map(np.array, zip(*results))\n", + " \n", + " # reset environments automatically\n", + " for i in range(len(self.envs)):\n", + " if done[i]:\n", + " new_obs[i] = self.envs[i].reset()\n", + " \n", + " return new_obs, rewards, done, infos" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "__Let's try it out:__" + ] + }, + { + "cell_type": "code", + "execution_count": 12, + "metadata": {}, + "outputs": [ + { + "name": "stdout", + "output_type": "stream", + "text": [ + "\u001b[33mWARN: gym.spaces.Box autodetected dtype as . Please provide explicit dtype.\u001b[0m\n", + "\u001b[33mWARN: gym.spaces.Box autodetected dtype as . Please provide explicit dtype.\u001b[0m\n", + "\u001b[33mWARN: gym.spaces.Box autodetected dtype as . Please provide explicit dtype.\u001b[0m\n", + "\u001b[33mWARN: gym.spaces.Box autodetected dtype as . Please provide explicit dtype.\u001b[0m\n", + "\u001b[33mWARN: gym.spaces.Box autodetected dtype as . Please provide explicit dtype.\u001b[0m\n", + "\u001b[33mWARN: gym.spaces.Box autodetected dtype as . Please provide explicit dtype.\u001b[0m\n", + "\u001b[33mWARN: gym.spaces.Box autodetected dtype as . Please provide explicit dtype.\u001b[0m\n", + "\u001b[33mWARN: gym.spaces.Box autodetected dtype as . Please provide explicit dtype.\u001b[0m\n", + "\u001b[33mWARN: gym.spaces.Box autodetected dtype as . Please provide explicit dtype.\u001b[0m\n", + "\u001b[33mWARN: gym.spaces.Box autodetected dtype as . Please provide explicit dtype.\u001b[0m\n", + "State shape: (10, 42, 42, 4)\n", + "Actions: [13 3 0]\n", + "Rewards: [0. 0. 0.]\n", + "Done: [False False False]\n" + ] + } + ], + "source": [ + "env_batch = EnvBatch(10)\n", + "\n", + "batch_states = env_batch.reset()\n", + "\n", + "batch_actions = agent.sample_actions(agent.step(batch_states))\n", + "\n", + "batch_next_states, batch_rewards, batch_done, _ = env_batch.step(batch_actions)\n", + "\n", + "print(\"State shape:\", batch_states.shape)\n", + "print(\"Actions:\", batch_actions[:3])\n", + "print(\"Rewards:\", batch_rewards[:3])\n", + "print(\"Done:\", batch_done[:3])\n" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "# Actor-critic\n", + "\n", + "Here we define a loss functions and learning algorithms as usual." + ] + }, + { + "cell_type": "code", + "execution_count": 13, + "metadata": {}, + "outputs": [], + "source": [ + "# These placeholders mean exactly the same as in \"Let's try it out\" section above\n", + "states_ph = tf.placeholder('float32', [None,] + list(obs_shape)) \n", + "next_states_ph = tf.placeholder('float32', [None,] + list(obs_shape))\n", + "actions_ph = tf.placeholder('int32', (None,))\n", + "rewards_ph = tf.placeholder('float32', (None,))\n", + "is_done_ph = tf.placeholder('float32', (None,))" + ] + }, + { + "cell_type": "code", + "execution_count": 14, + "metadata": {}, + "outputs": [], + "source": [ + "# logits[n_envs, n_actions] and state_values[n_envs, n_actions]\n", + "logits, state_values = agent.symbolic_step(states_ph)\n", + "next_logits, next_state_values = agent.symbolic_step(next_states_ph)\n", + "next_state_values = next_state_values * (1 - is_done_ph)\n", + "\n", + "# probabilities and log-probabilities for all actions\n", + "probs = tf.nn.softmax(logits) # [n_envs, n_actions]\n", + "logprobs = tf.nn.log_softmax(logits) # [n_envs, n_actions]\n", + "\n", + "# log-probabilities only for agent's chosen actions\n", + "logp_actions = tf.reduce_sum(logprobs * tf.one_hot(actions_ph, n_actions), axis=-1) # [n_envs,]" + ] + }, + { + "cell_type": "code", + "execution_count": 15, + "metadata": {}, + "outputs": [], + "source": [ + "\n", + "\n", + "# compute advantage using rewards_ph, state_values and next_state_values\n", + "gamma = 0.99\n", + "advantage = rewards_ph + gamma*next_state_values - state_values\n", + "\n", + "assert advantage.shape.ndims == 1, \"please compute advantage for each sample, vector of shape [n_envs,]\"\n", + "\n", + "# compute policy entropy given logits_seq. Mind the \"-\" sign!\n", + "entropy = -tf.reduce_sum(probs*logprobs, axis=1)\n", + "\n", + "assert entropy.shape.ndims == 1, \"please compute pointwise entropy vector of shape [n_envs,] \"\n", + "\n", + "\n", + "\n", + "actor_loss = - tf.reduce_mean(logp_actions * tf.stop_gradient(advantage)) - 0.001 * tf.reduce_mean(entropy)\n", + "\n", + "# compute target state values using temporal difference formula. Use rewards_ph and next_step_values\n", + "target_state_values = rewards_ph + gamma*next_state_values\n", + "\n", + "critic_loss = tf.reduce_mean((state_values - tf.stop_gradient(target_state_values))**2 )\n", + "\n", + "train_step = tf.train.AdamOptimizer(1e-4).minimize(actor_loss + critic_loss)\n", + "sess.run(tf.global_variables_initializer())" + ] + }, + { + "cell_type": "code", + "execution_count": 16, + "metadata": {}, + "outputs": [ + { + "name": "stdout", + "output_type": "stream", + "text": [ + "You just might be fine!\n" + ] + } + ], + "source": [ + "# Sanity checks to catch some errors. Specific to KungFuMaster in assignment's default setup.\n", + "l_act, l_crit, adv, ent = sess.run([actor_loss, critic_loss, advantage, entropy], feed_dict = {\n", + " states_ph: batch_states,\n", + " actions_ph: batch_actions,\n", + " next_states_ph: batch_states,\n", + " rewards_ph: batch_rewards,\n", + " is_done_ph: batch_done,\n", + " })\n", + "\n", + "assert abs(l_act) < 100 and abs(l_crit) < 100, \"losses seem abnormally large\"\n", + "assert 0 <= ent.mean() <= np.log(n_actions), \"impossible entropy value, double-check the formula pls\"\n", + "if ent.mean() < np.log(n_actions) / 2: print(\"Entropy is too low for untrained agent\")\n", + "print(\"You just might be fine!\")" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "# Train \n", + "\n", + "Just the usual - play a bit, compute loss, follow the graidents, repeat a few million times.\n", + "![img](http://images6.fanpop.com/image/photos/38900000/Daniel-san-training-the-karate-kid-38947361-499-288.gif)" + ] + }, + { + "cell_type": "code", + "execution_count": 17, + "metadata": {}, + "outputs": [ + { + "name": "stdout", + "output_type": "stream", + "text": [ + "WARN: gym.spaces.Box autodetected dtype as . Please provide explicit dtype.\n", + "WARN: gym.spaces.Box autodetected dtype as . Please provide explicit dtype.\n", + "WARN: gym.spaces.Box autodetected dtype as . Please provide explicit dtype.\n", + "WARN: gym.spaces.Box autodetected dtype as . Please provide explicit dtype.\n", + "WARN: gym.spaces.Box autodetected dtype as . Please provide explicit dtype.\n", + "WARN: gym.spaces.Box autodetected dtype as . Please provide explicit dtype.\n", + "WARN: gym.spaces.Box autodetected dtype as . Please provide explicit dtype.\n", + "WARN: gym.spaces.Box autodetected dtype as . Please provide explicit dtype.\n", + "WARN: gym.spaces.Box autodetected dtype as . Please provide explicit dtype.\n", + "WARN: gym.spaces.Box autodetected dtype as . Please provide explicit dtype.\n" + ] + } + ], + "source": [ + "from IPython.display import clear_output\n", + "from tqdm import trange\n", + "from pandas import DataFrame\n", + "ewma = lambda x, span=100: DataFrame({'x':np.asarray(x)}).x.ewm(span=span).mean().values\n", + "\n", + "env_batch = EnvBatch(10)\n", + "batch_states = env_batch.reset()\n", + "\n", + "rewards_history = []\n", + "entropy_history = []" + ] + }, + { + "cell_type": "code", + "execution_count": 18, + "metadata": {}, + "outputs": [ + { + "data": { + "image/png": "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g0ZjEfpeqmiq43tDyxQblkwV/Q6rtu50p9NJ06ZNK+VB0VRfRISmTZtWmuWj0URDv5c0paWi32HVVokD+kHRAPp7oKle6O+jprRU5HemWitxjaau8N9FO/l2/aGqFqNS2LBwJpvX/YzSKZ81mnKjlXg1pkOHDhw/fryqxdBUAu8u3sXcTUerWoxKoceP47g98+88POkf/G/l/qoWp1Yzc+ZM0tPTq1oMTQWilXiMKKXw+Squ2I7H46mwvjXVnxyXh9TkahtnGlf+95vl7PU15++u51Az7wBPYVWLVGuJpsT1O6d2oJV4FHbv3s2ZZ57JhAkT6NOnD++//z79+/enT58+XHPNNeTm5rJ8+XKuuuoqAGbNmkVKSgqFhYU4nU5OO+00ACZPnszQoUM566yz+O1vf0t+fj4A48eP595772X48OE89NBDZGRkMHLkSM4++2xuu+02v7sxLy+Piy++mLPOOosePXrwySefVM0fRFMhKKXIc3lokFQ3lPgV53XhfvftAFxtX0jhrp+rWKKaxQcffMB5551H7969ue222/B6vTRo0IC//vWvnHXWWfTr148jR47w888/M3v2bB544AF69+7Njh07GDZsGI888ghDhw7llVdeYc+ePYwYMYJevXoxYsQI9u7dCxjvpttvv53BgwfTpUsXvvrqKwAGDx7MunXr/LIMHDgw6LOm8qkRb40nv9zI+n0nsNvtceuzW+s0Hr+0e4nttmzZwpQpU3jqqae46qqrmDt3LvXr1+fvf/87L730Eo888girV68GYNGiRfTo0YMVK1bg8Xg4//zzAbjqqqsYM2YMqampPProo7zzzjvcfffdAGzdupW5c+dit9u55557GDRoEI899hhff/01b7/9NgDfffcdrVu35uuvvwYgKysrbn8HTdVT4PbiU1C/jihxu01Yrs5kpOvv/JD0EHknj5BY1UKVgSe/3Ej6wey49lnSe2nTpk188sknLFmyBIfDwYQJE5g+fTp5eXn069ePZ599lgcffJDJkyfz6KOPctlll3HJJZdw9dVX+/s4efIkCxYsAODSSy9l7NixjBs3jnfffZd77rmHmTNnAoYRs2DBAnbs2MHw4cPZvn07N998M9OnT2fgwIFs3boVl8tFr1694vo30JSOuvHWKAft27enX79+fPXVV6SnpzNw4EAACgsL6d+/PwkJCXTq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tGnTuPzyywG47LLLeO+99/x9Be6fNm0aSimWLVtGw4YN/W73yqD2KHEwFPfg++KiwEOpzvXE09LS6NixI59++ilgDDjWrjXKO55//vn8/PPP2Gw2kpOT6d27N2+99VapHhpLYTdr1ozc3NxiJQ1zcnLo2rUrP/zwAz6fjzlz5uB0OnnxxReLzd2Hctlll/Hxxx/jcrnYtWsX27Zt47zz4v//q2zmbznGGaekckrDZNKSHRHd6XmFZsrVOmiJN6znACpnTtwinCV+PNdQ4oUVG7saFwJLkfbq1YsLL7ywxCCqMWPG8M9//pOzzz6bHTtNoP9IAAAgAElEQVR2FDv+6quvMmXKFHr16sX7778fFJBqlSMdPXp0UDnSc845J2o50ptvvplu3brRp08fevTowW233ea3ogcPHozH46FTp0706dOHzMzMUivxBx98kIkTJzJw4MCgQNhrr72WF198kU6dOtG9e3f69u3LkiVLUEqxbds2/va3v/nbdujQgXvvvZepU6fStm1bfwT/G2+8wc0330ynTp04/fTTGT16NAAPP/wwc+bMoXPnzsyZM4eHH34YgIsuuojTTjuNTp06ccstt/D666+X6l7KS917c5SR6l5PfPr06dxxxx0888wzuN1uxowZw1lnnUVSUhLt2rXzTwsMHjyYjz76iJ49e8Z8740aNeKWW26hZ8+edOjQIcgVf/311/PYY48xceJExo0bx6RJkxg8eDAff/wxEydO5IwzjCCiL774grvvvptjx45x8cUX07t3b77//nu6d+/OtddeS7du3UhISOC1117zW/c1lVyXh1/3ZPLHQR0BaJjiiBjYVkUVzKoFA05vxjXntMW9tqLc6fFtV10IV4oUKrcc6cGDB6OWI7XZbDz33HM899xzxY7ddNNN3HTTTYAxBWBZ6JYsgeWfA63swGP9+/f3G1FgKHUwyjk//vjjXHbZZbz22ms88cQTuN1uvvvuO9q3b09iYqL/nEgxA3379g2SwaJp06bMmzev2H4RiVrTvcKx1iZW5c8555yjQklPTw/6nJ2dXaxNTaImyx9Ndq/Xq6644gr1xBNP+NsdPXpUvfnmm8rtdsdNhtDvQ6z89NNPcZMhVn7YeFi1f+grtWT7MaWUUnd88Ksa8eL8sG23HclW7R/6Ss1acyDs8VD5gV9VNXhmo/2Ee54j3dfRbKd68ZE/KvV4mlJeT4nntX/oK9X+oa9ianM8x+nf9/aCHar9Q1+pp77cWKz9899s8p/j8/mU1+uL2HdZv4eFHq/Kd0V/HtbuO6HW7jsRtC87O1v5fD51PNepvL7IclUE48aNU59++mmx/W+++aZq27atmjFjRqXKE43Qd9TSpUvV5Zdfrnr37q0GDRqknn/+eeV0OiOcXTkyhfvuxON5rnvDf01csdlsfPbZZ7z++uuMGjUKp9NJ69atuffee2OKBq2NLNh6lHqJdvq2N+YK05IjW+I5zrrrTgdonpqE23oNed1gi58XRgVtm4FtYdsVtbxl2krmbjoSU6nT0rD5cA5KKXq1bVTqc7MK3Bw4UUChx0erhilxlSsakcqR/u53v+O2226rNDnKQr9+/fzL5Go71frNoZSqFZHKtR273c7dd9/N3XffXSH9qxqUeUspxfwtxxhwejMSzapkaSmOiNHpddmdblGIqbi9heBIjt64FMQ6Jx6o7eduOhKmQWi/pX8vlec77DXP9daAJXCa8FTkO6zaBrYlJyeTkZFRo17gmvijlCIjI6NYZqXqys7jeew/UcCwrkULhdKSE3B5fDjdxdco55lKvE6vEw+0xONI8BIz4/eGA9kczXGGtIvOq/O2cfN7K4Aqfi/pV2GNpKLfYdX2zWGt1zt2zFiq43Q6a8yLPBw1Wf6qlj05OTkoi1J1xlpaNrRLgBJPMaKwc5wekh3B7mK/O72OLzEDDEs8noRRekt3ZjDixQWsf6Jo/bKvBAv3pTlFAVSh76VYOXKiAIBNOZHd4eHaOJ1OvJLAiXw3uUl2co4kRjq90qjq90E4qrtMFfkOq7ZvDofDQceOHf2f58+fXyEF1SuLmix/TZa9slmw9RinNa9PuyZFaVTTkg0lnu100zw1Kah9nnanV5wSDyBQTVsDp7IQ+l6KldEPG5kPo821h2szf/58DtU7jYmz1zPm3HZM+m349MWVSXV8H9RlmaqtO12jqWm4PF6W7cxgSEjJ0YamJR4uuK1oTrxmL6srD4WqotzpAdtRjO3a7KWet+kIHR7+utgUgqb2oJW4RhMn9mUW4PL4OKtdw6D9aSmGksoOYwHmuDwk2m0kJdRdJV5Rlng0xb39aE5M7eJNSa77aJRFzveWGtncNh2KnLFNU7PRSlyjiRN7M42kFac2Ca5IZrnTw0Wo57kqv5Z4daPClHhgYFuIvZ2Z5454rCJZsK10c+nlxRo02PUqn1qLVuIaTZzYm2HUgj61SXA+/LRo7nRn3a5gBtCltZl7uwIt8aju9BpiiZcFa1maTb/pay36X6vRxIk9mfnUS7TTrEFwBHFgYFsoua66WcEsELeYwX7ugrj2Wx51uS8zP25yBFLZBrFXW+K1Hq3ENZo4sTcjn1Ob1CuWCCTZYcNhl7CVzHJdnjqbrc3CVVFKPIqJHfgvat2o+NKkQ1nVJxDMEnX9gSwOZzn56xfrcXtjq9ayfHcmACv3nqgg6TRVjVbiGk2c2JuZX2w+HIwCCQ1Twlcyy9Vz4hTaLCUeX+s3Vjf56c0bFNtXFsP1eK6LY2ZZ00gUesruH0g/lM1fv1jP9F/2sqiUc+tbD+vAttqKVuIaTRzw+VREJQ6R86fnOrU7vdCyxD0VZ/1WRoa1vs/M5dxn51aoHD7z/NJ2k5JYt79jtRmtxDWaOHAs14XL46N90/BKPDXFEXaJWa7LW+cD29w2053tzkcpxZNfbmTDgay4XqO0gW3VaQY50CtgTdWUVolvO6It8dqKVuIaTRzYY0WmN60f9nhackLYJWa5LnedTrkK4MQMBHQ7yXV5mLJkN9e9tbTc/ZbH6K2oOLCy9CsBQwprK9KtHctx8f6yPcX2/7rnBCv36Hnx2kjdfntoNHFib6a1vCyCOz3FwYETwYFbHq8Pp9tH/Tru6vxpRw4kA+4Cv3KKR/XCaOu/3Z6SAsOqky1eRLQ/yxWvLWHNvpMADOrUjI7NggeU6/ef5Jz2jStSPE0VoC1xjSYO7M3IwybQplH4AhdpycUD2/JcRlWzuh7Y5iEBt7Kb7nRjXzxUaNA68ZBj3208HPFYTSDc3LqlwAEOnSwe6a8rmdZOtBLXaOLAnsx8WjVM8dcQD6VhioPsAk/QyzfHZSj1ur7EDEyXusdZpFHDaPHSzpN/ufagfztU5w0OyW8fSkW508vk4peIHyKyNcwcuE+Xda6VaCWu0cSBvZn5EYPawMifXuj14Qpw41qWeF2PTgdTibvz/S5wW4gWnZt+hEv+vbhUfb4YUEI01LWe7Ch69YWzagVjydiMFftKdc2SKIsaPRjGqs4v9DJrzYGI50z9eXfxa2sdXivRbw+NJg7szchnZPeWEY/7s7YVuP01xXNNS7yuu9MBClSiMSduudNDDM4dx3Ljej1vgG85km6744OVrNhd+mAwpVRc5vQtApMEWd0+NmsD2U4P/5qzlaM5LtKf+k3QOSfyiwdRaku8dqItcY2mnOS6PGTkFQbVEA/Fyp8eGKFu1bWuqiVmItJORH4SkU0islFE/hSmjYjIqyKyXUTWiUifipDFsMQLIirUWOdzY12HHahkwy4xEykxcYvFruN5QaU+460r7WHe0tZyxd0Z+eQXeosd94b5g2kVXjvRJoBGU06swiftm4RfXgbGEjMIzp/uD2yrOne6B7hPKbVKRFKBlSIyRymVHtBmNNDZ/DkfeMP8HVcKSPKvE4fiM7/ltSKjn16+voe/MD/m3spyGzZb6a16j6949L02xGsnJVrikUbrItJEROaIyDbzd2Nzf6WM3DWa6kKkEqSBFFUyK3KNVrU7XSl1SCm1ytzOATYBbUKaXQ5MUwbLgEYi0ireshiWuDPiErNYLexYFVVg7/FO9hJN1rIMRgKLl8Qql8cbzhLXWrw2EsvbI+xoHRgPzFNKTRKRh4GHgYeopJG7RlNd8K8RjxLY1jCleCUzvzu9GqwTF5EOwNnALyGH2gCB0V37zX2HQs6/FbgVoGXLlsyfPz/q9XJzc4PaOFUi2ZlH+HKeEbyWmVcYdHzHzuAypZH6D1WSVrvdu4PPX7duLb6Dxt99/eHimfRWrVpJQUGwO72ke/K3W7CAhAjW855tm5h/YmvYY+Guk5uby76De/2fjx8/HvacDg9/HfTZ41PF5N25Yyfz2R/12rEQ+r+rDtRlmUp8eyilDmE+sEqpHBGxRuuXA8PMZu8B8zGUuH/kDiwTkUYi0srsR6OpdezJyKdRPYdfUYcjMLDNoig63V6xApaAiDQA/gf8WSmVHXo4zCnFTDql1NvA2wB9+/ZVw4YNi3rN+fPn42/z3dcUkERaioekUzoD6wEI7GONZyts3+b/HKl/r0/B998Ua7fKvRV2FJ1/1lln+ZeZ5a47CGtWB/XT95y+pGxdBfn5xfoqxnfBCnTIkKHFlxqabYb170uPNg2j9hN4nfnz59OxY2vYacjerFkzOHok/PkhDBs2LEi2Fm1OpU+/0/3fxbIS9L+rJtRlmUoV2BYyWm9pKWbzdwuzWaSRu0ZTK4lW+MQi1T8nHuxOT3HYSQgXuVRJiIgDQ4FPV0p9HqbJfqBdwOe2wMEw7crMtX3b4sIBHmdEl2+8E5UEpjKN91xxed3WoUFpgUZ9rEHv4ZIOvT5/B72e+KE8ommqITH78UJH61GWUMQ0ci+v+62mUZPl17JHZ8uBfDqk2Uq8jsMG67fsZL4YLs2tu1w4xBf1vIqUX4yH+B1gk1LqpQjNZgN3icjHGNNiWfH2qp3SMAWXcoDHFVGhxj4nHlu7x2Zt4Mf7hxnnhDkuEp/Ur6HEIt7pj3zD7kkX+z8Hzol/vzE2K/zCbpGXO2pqFzEp8Qij9SOWm9wMdDlq7o9p5F4u91sNpCbLr2WPjMfrI/OH77j6vA4MG3ZG1LaNlsylUfMWDBvWC4D/HVpNU2dWVPkqWP6BwO+B9SKyxtz3CHAqgFLqTeAb4CJgO5AP/CHeQtgEXDhQHleUJWYxKvEYr7nzeF7ROWH63nI4p8xlQ6NWTCuDlV6W6HS9JrzuUKISjzJanw2MAyaZv2cF7K/QkbtGU104lOXE41NRs7VZpCUnBAW25TrdVVqGVCm1mBICns3YljsrUg5BKDTd6YEa0OP1+acaQt3pGw5kRZ5bjgPr9p8suVEZKNMSszJ4BKYt3cNVfdqGPVbRfztN5RLLZJw1Wr9ARNaYPxdhKO8LRWQbcKH5GYyR+06MkftkYEL8xdZoqgf+EqRR1ohbWPnTLfJc3ioPaqsuuHDgdhXw05Zj/n3/+Wm7fzvUsoyUgjWikoyiPcMdstmE3Rn5xQ8E4PH6yHMVj2yPN2UNmfhlZ0bY/eFK4mpqLrFEp0cbrY8I077CR+4aTXUhluVlFmkpDjLzipY65bg8Eaue1SV+3HKU4SSQKF4WbDmMZVt8smIff/6/LkDFJioJ53qOxUMyYfoqfkiPbY7aoiy3URZLHCIHweU4tRKvTei0qxpNOdiTmUei3cYpackltk1LdgQtMct1uf1R63WZHKcbl0oEIJEiy/ZQVlEqU1+M4ellmXMON0AY2iV6lTMgogKPOidehtFIWZX4uv3hq76t2Ve6anCa6o1W4hpNOdiXmU/bxinYYwg+SktJCHJlane6iTLc6QCJhLcSPeVcYxY1FWqYfRVWirQM58Ty3QrHV+vChyK9uWBHmfrTVE+0EtdoysGejPyYXOlgWuLOopriuU4PDZLKl3ijtlBozuwlBSjxRvWK/jbhCnqEoyxu97JGoUfsL4qqLsulKirSXCkVc5EXTfVFK3GNpowopdibkU/7EhK9WKSlOPD6FPmFXlweL4Ven3anm1iWeLIUudPP7dDEvx2uoEe8CK8iK8YU//eP22IekFg8+WV6yY1KoDknaEFwWdUZv+7j3GfnsuGAdq/XZLQS12jKyMl8NzkuT9QSpIH4U6863UUpVxO1O12BkewF6Nio6O8RqEbdYQp6lAU7Xq6yLcRGwKAg3hnbovQ3f8sx5qQfLrGP9RHms6PRVo5xkW0ZDyd8SH0K/PuTKGRF8p0sTy6KN37yy438vMOIXt9+NL612jWVizYDNJoysseMTG/ftOTlZRBQBKXAQ4rDeNM3KGce69qAUoqWYliJF7OQBVwOwPoACzFe7vQb7XN50vEeSW43cClQ8YlRQt31Lk/JXoUlO47Ts23p1nK/5Hid82xbALDj41nPjQC0k6PF2k5ZsrtUfWuqL9oS12jKiH95Wczu9KKa4jlWGdIqTPZSXfD4FD/6zgZga1bR3yMwOt3tjY873RosNCHHvy+8Ci+7Yg89syxjhNK63AG/AgdoJ0Xr7T9PfMK//Y+Et0gj2PLWJUprNlqJazQxcDzXxdfrDpGRWxQItDej5DrigQRWMrPc6VqJG7Wvj6jGJbaJhUgKyVKkYh5XYY7FgsfrI7+wdAleyqLUy6LEAxlg2wBAU7JIk6KkNdcmLOBPCV8Etf3LJ2vLdS1N1aLfIBpNDExZsovXftqBTaBvhyaM7NaSNfuyaJGaREqM89pppjs9q8DtX8LUQAe2cTjbiZ0kAOoRPlraW0pzdpBtPSt8XYvtl4CtgycLaN0oJazij3S5295fybzNR4MKlBQ/V0X9HAuxKvGespO+ti2s8nUO2p8mxpx4Iyk+3z3Mtoan+X3Qvmynm7RkBx8s20Ovtg3p1bZRqWXWVA36DaLRxMChLCdN6ydyw/mn8kP6EZ75ehMAfdtHtyADSbPKkRa4/Wt/G+h14gB4seNUDupLUUBWPXNwVOjxMSfGzGhKwZmyhw8Sn+c9z4XAlezLzGffCcMaFTOgTSEczXEZSjyMvoykQudtLj6/HMr3G49w9TlFecuLWeIxuK+dbi9PfrmRc5Ojt/0y6dGIx86QvX7Pg0slkGRG/v/s616sba8nfmDK+HN5dKZhwUcbpGiqF1qJazQxcDy3kDaNU7h3ZFfuHdmVPRl5zNt0tFTBR5Ylnu30kJhgKCi9TryIXFKoT9E8eKuGRha8W9//NeY+FJCGobDPtO3F6fYy+B8/+Y/bTKWWLIVB5xjHfNTHSQ71ypXmdcHWY8FKPKSvWFbLvbVwJwD72xd/RT+XMJl9qiU7VKuofXyX9DBXup4EDMU93G64zQPd64EsjZBrXVO90Upco4mB4zkuv1IBIyL9j4M6lqoPh91GvUQ72QVukh1GOIrfnb76AzixBzpfCO3Oi5vcNYk8lUx9KVLiVrrRo9mlS0jiMUN97PiKZV6zBgn1cPpd65a7+/GE9xiXMIdOzmklXiNaGtgv1x7k39ef7f8canmXZnwQ7jK/S/ip+M4IWIOVE6T6950pe8K2PZlfGHa/pnqjA9s0mhg4nuuiWYOkcvdjZG1zk2sGttVz2GHvLzDrLlj4D3jvMti3vNzXqYnkkUIDiivx0qCUwhegxEOxBgn1cfqVqWUpX2efDxiWfEnz2OHSwPa3beSTxKew4w2RqbiMZaUhsa3p3udrzipfJ5IxFPNJ1cB/rIvtQNhzZvy6v8xyaaoOrcQ1mhLw+RQZeYU0S00sd19pKQlkF3jMlKsJ2GwC6z/Fb595C2H3onJfpyaRaNbazCMpKElJWfOXW4lcEvByJCvYir/c/jMAraXIdWwp1QRT+SZTWKK1HG5e+0XHG5xv28wpZJZwbtnpJOEVcCjtbMfoY9tOqjm14C32qtfLymoLWolrNCVwssCN16fiZolnFbjJdbmLlpe5zTlKsYM9EToMLvd1ahKWQsxTKdQL407ffDgbMDKPdZbo1qICHGIo4x623SzZcTzkqMEw+9piexPEUP5J4g47Jz577cGic8Icd6sE//klCllGovX9X8/oYvtaiTGgyDej/z3KeOWnBgyWwhG4lFJTvdFKXKMpAatIRFyUeIrDn3bVX8Hs0Fo4pSdc8FcYN7vOzYlbCjGX5GB3uvl2sjzXHyc+w5ykB0kjL2p/ge5suypa0z3Eti6o3ZLthoJfH5I73HJBh3LPR6ujXjcfI2aicUAimXCEX9IWWwb3wMC/UOZ4+/q3H3bfDEALOQnAp95h/NtzBU94xgHQ3NwfiXcW74p6XFN90EpcoymB47lxVOLJCWbGNo+RcjVzFxzZAGddD4Pvq3MKPJA8lRIU2BbK2bbtQHRFphQkBMyFJ3mKFGpKyBr0f35vZDj7fNUB/9IzsNzp0c3lcDo3CyP9bosQBenxKdrKMeYl3sdpcrBUke/WlMLpcoAhtrXUi3Lv2RhJh3b5WnJYNTFlMTLUZat6vOi5lp1mRPuPSfdHve7r83ewck/0aQFN9UArcY2mBCwl3jy1/Eq8YYrDnBN3k5qUAFu+MQ50vajcfddULJ2WR3LQnHgkHBI9Y1qgJZ7ozvZvF5qV0rb52hj9UNTPMwlT/NvJUliiyzucks9WhhK9LeHLoP2z1xxklG05p9sOcaN9bpm86fOSHmBa4t+pJ8Z3cafvlGJtslR9+jn/zaWFz/plsQYUTox4jmMq9iQuy3edKLmRpsrRSlyjKQHLnd48Tu70HKebHKfHcKdv/hpa9oAmpVuuVpuwCpDkkWxmGouu5jpIlCpgqihADYItcSsbnJXJrQFF66VvSJjn3x5p+5V7P1lTgszF9+WZ7vTQrHNz0g9TYB5LpjB8cpkYNbvlhfiD+0E+9Qxhr685ucro+yQNOExTcqnn9wr0lh14lM1fr32vauHvK4HogyFbxVRj1cQZrcQ1mhI4nltIot3mL2BSHtKSHfgUHMl20tKeB3uX1mkrHIoU2KW2pQBcbV8IQKOU8KsBpiX+nf+zreRkfmGxACyFClLiBdlFUehW0JyVpz1Vwlv9f0j4nhH5X9MyLfKgLdwcdhJG0NkB1Yx5m47gdHvZcjiHn7Yco0AZ9/K7hB/LVXCkj20bAPtVcx7w3M6Qwle4w/1n3vRc6p+ThyKvQJK4KSAJa3bdRSLzvWcBcLocJBplWeKnqXy0EtdoSuB4roumDRKROLzUiiqZeejj+gWUD87QKS4BPvJe4N9+0fE6VzTaGbFtX9tWej81h3OemVvsWOCc+He/bvZvWxbyYYz54mgR2u3kaNT84eHUsKXEU6WAm977lae+Sue1n4x5fF9AiFpp0rw6Q4zlQbb1uJQDL0Xpehf5ejHJc31Qu2yKyuM6Cc4K+LnXWP0QLbYAMJY/aqo9WolrNCVwLCc+iV6gqJIZQPfsRZDWFlqdFZe+azrf+4zo6mQK+a19MZdseqDUfSgVPCfeUIoi2S0lfsScF06NkH4UjGpn0Vzc4Y5ZUe1W9PyHv+z1K2dr2RuUboXZogOGFj+u0gDDks6j5O+iK0Bxu0MSc1qeiMDUs+HQOrxmoJW4RlMCRra28id6gaL86cm46JC13LDCtdsSgHxzbteq+Z2gIiuZHJXi33a6vWTmFbVNCFCYgcvRUkx3eoYy8t03iGKJl/gfCaOJrTXcgbnJQxPJWG7uYt2VlCHOtLybS1aQ2zwyRXfQWoKjzHPN80u0xPX3skaglbhGUwLHc11xiUwHIzodjDXLDp8Tzqjb8+GBWBHUVsYzX5TXU6AVPX7Kcvo8PQcwdGvgnHhKgLWZiBeXcpCDMQBIJZ9pS3eH7b8sGdssSzyV4ha+JVMi7jKlXT0RkDa1QMX2Xbyn8M6w+62laGlh5AwkI0/nUq8JaCWu0UTB51Nk5BbG3Z0+0r6SQkcatB8Yl35rAwWmm/gU03L0SUJEhRc4n71sZ7ClaQ9Z822RiBsXCeSY1nADKeCxWRsBcKngeWOFEE2VR3On1xeXP/Lb7043PydHyAZXEoEDmvwY3OnGtQx51vhOC9qfrYz58jSJnjTn1XnbSiOiporQSlyjiUJWgRtPeVKu7v0F5j4Be5YBRmCbHS8X2FZxvPUwsOtSpD3aGPO9bux4lI3BdqOmtaHErVbBmu8yMwd6ID6fQqmi6HSvkmJKvBAHuQGWuEW6ao9XBbqPo7uSowW2hfYNwWvSX/x2fUz9BZIYcH5BjEp8u7ke/t2QdKw51MOnhEYSWzEVTfVGK3GNJgr+bG1lcafvWw7vXQKL/wVTRsG7o0ld/Tb3JcygieRS2LhznKWt6UiQlemTogjs0FSo4ZaHfbZyP4qiwLY8UoLO62vbSjPJxmW67fvZNvmPJeLmR18f/+cS3elhzOlkKcRpWvR++cxmgS7+CeqTEnovTmLAAKG3bI/pnFWqC+c5X2O2L9jb48OGTRR/SviCK2yLSy2LpnqhlbhGE4WivOllCGzbvcioSgaAwNFN2Of8lQkJX6IUnLr+tSotOyoi74rIURHZEOH4MBHJEpE15s9jFS2TM0iJ2/zKNDSBSjgyzXrY1hKzXJKDIrDPsO0Laj/EXmQRJ+KhMGDZli1MGdNAwin5ejj96U6tgDorkY0jQImfKkeK91fCqCEwS11ySQVWAjhK46jHX058nWcT3om5P031o0QlHu5BF5EnRORAwMN9UcCxiSKyXUS2iMioihJco6kMjuWWI1tbu37mhkBCMtwwAwb+GS+CCIjPXdVlR6cCvymhzSKlVG/z56mKFsgZNDdd5NK20o1GQ7BypxsKM1el+F3c7UOyvO1XzYI+O/D407KC4RqPplit4imB59cXl7/fUE9BoBLeptqUeC+hBLrTn3dfH6VlbBSqogGLka3OuNkkCukuu0vM5qapPsRiiU8l/IP+r4CH+xsAEekGjAG6m+e8LhLgE9NoahjHcw1LrkzR6V5T8fS6tqg62RkX48ZhlIS0O6q07KhSaiGUUPy6kgkM4HJ4C/xu69DiJS5VPHuetSIqIYw7PdCdDTDLOyBIkSWKm8KAAUQi7qgu9WlL9wR9bivHgKL87FahEhXgTncqB5mqAU3JJhIpOHnPMYnTQ+qGB7rT82JaYhYdy2NgMchm2GhbksfzddIj/MPxdrmvoakcSswjqZRaKCIdYuzvcuBjpZQL2CUi24HzgKVlllCjqUKO57pw2MW/NKxUbP4GHPXg0lfAYa5rbnceTzedRMPDv/CncX8gqfpXLesvImuBg8D9SqmN4RqJyK3ArQAtW7Zk/vz5UTvNzc31t8nJKbJaJUB1Orx5LDDbhFbvSjinzBIAACAASURBVBIPhvVYZK3v2LGTJQV7sIsXrxIKVKLfnW6ts97hM6p45alkEsXrD3ZLxIM7wJ2eJG4yMopStoaSlRVcvnSgqQQLzPn2+uag49hxQ7k78FJIAkdUY1rIyWJ/H7eZjL2/LZ2h9nU8ygf8wf2Q/3igJZ4bsEa+rOxSrTiVY/7PHyQ+z0JvT//nq+yLudc9Iez/MfB/V12oyzKVJxn0XSIyFvgVuE8pdQJoAywLaLPf3FeM8jz0NZGaLH9dln3DNhcNEmDBggWlO1Ep+q37nJyGPdm45JegQ+nuNqz1Xc65e5zI3uiyVfHffhXQXimVa06ZzQTCRuMppd4G3gbo27evGjZsWNSO58+fj9Umdf0iyDasU0uJ7/Sdwmm2wwwZ1B/m/BjWnW7HF5R+tNPppzPg7NYcXDQFD3ZO0oBOGBatZYm/7PktYFjpYAwOLCVeiIMH3bfwD8dkGpFH06ZN4djRsPKnpqXByaKSo1YSlw+9I7jYvpwUU95slQLkkoDHkEml0lDyOGfwEBLsRV4Hl8cLP3znl9MT9GpWQUq8tJZ4alICOa5g9/h97jv41X5H0L7AGAGLcP/HwP9ddaEuy1RWJf4G8DTGUPhp4EXgj4RflxHWK1Weh74mUpPlr8uyv7drOW1wMWxYKd3eB1fDggySRz/DsN7B1599dA07so8wfPjwErupyr+9Uio7YPsbEXldRJoppY5HO688WO70DNI4jcMol7EMKtSdDoZiDlTiYi7ttvZnqDRG2FYByh9YZqUgtaLg6+PkJKmmRZ7ADO9w/uGYzAj7al7KjpzRLLSK2dgEI9nMftXc3y/AtqOG/A48eEgglxTacoxCry9IiauQKPZAr0ACXmxSdMHSKvGbB5/Gv+ZuDdp3nIal6kNTfSlTdLpS6ohSyquU8gGTMVzmYFje7QKatsVww2k0NZJjuWXMm775axAbdC4e23l1n7bcdUGnOEhXsYjIKWJWfRGR8zDeF5F9zGXknguKjHtLVZ1QqcaGqcSt6PRZ3gH86usCBK+9BuN/ZWVs82BjgG0jSeLhZcdr/kAty62eZ6Z4NSx8ZVriwTbNxoOR565Dl5jlm1nUDpqBbaGDDgde3NjJJoUzbXsj9mt5HLyBsQEh92nJHonU5OD7iJQ9tbvzHUa4/hmxn9vsX0Y8pqk+lEmJi0irgI9XAlbk+mxgjIgkiUhHDNdb1a2h0WjKyfGcMmZr2/wNnDoA6jctdmhAp2bcOuT0OEhXPkTkI4x4la4isl9EbhKR20XkdrPJ1cAGc078VWCMKkvO0BIY2f2UYvsyLSXuNi1xU7n903MdX3mNqP/QYLW3Fuzk/OfmYceLBzsPu28BoJVk+t3RRZa4oQgbUIAdHzZR/sC2r7z92O5rHVVmX8CfoYMcopHkssvXEjcJuJWd+hJsxSeIF7dK8A9GlCt8tjQrFWpgpHxiiBJ/4Xf9o8oWSqS0NXmksCMkUv4R90387O0GwETHR6W6jqZqKNGdbj7ow4BmIrIfeBwYJiK9MQbOu4HbAJRSG0VkBpAOeIA7lVLecP1qNNUdpRQZeWXIm565C45uhFHPVYxgcUIpFXWtklLqP8B/Kkkc45qmysnEzOKWb1jDlvLLV0l+a9pB+FdLgjlXvkKdwUlVnzZyvJibusgSd5Ko3OYx43XowlFiha/Aocz8pPsAWGumN80nKYwlbsyJL/H1YLR9BVJwAlKLu7StoiyewMh5MzL9sGrMKXKCpi1aQ0BQWkmUpo7JMdWw5OIvmmpFLNHp4R70iNkBlFLPAs+WRyiNpjqQVeDG7S1DytUt3xi/u+riJqXFUuIZpiW+dNNuoJFfieeR7Fe2oZa4RYJpiQM0kjwakUc90zK2lGNeQCUvy9K13OlOlRiUQjUcoXPiAB3Mtej5JPOHhO85phryuvcKoMidfsysoIbzZPEOgCRz8JAYkNDFku9fnquZ5+3DDw1aRJUtFCllNbIVqgv9SQdgx76DnNa2Van70FQeOmObRhMBf8rV0mZr2/wNtOgOTTpWgFR1g5OmEt+2z8hu9pDjY8Cwki1FnCDhE5IkiNc/p7zGZ0xbNCEHKLK2LSVeD1eAEnf4r5FEeEu8m+zmiYSp4Cs+gLBysluDiwcdM4pkMgcW2RjFRzbvCl5nbln21uAhJTDnu3mfTuXgOA3xKcW6J0aGla8sWO5zgOW+M3jNcwVTPEYsx22vf8nnqw5EOlVTDdBKXKOJwLEcM9FLaSzxvAzY+7NRJ1xTaqwKZMfM6OnjxdZqi98lHsmdbseL21T073suBKCxWezDsrat2uUNpMDvrvZb4iQG5VzvITu5x/45AO8m/pPxCT+Q5gteJw6w22fM7YeuaTdk9eAmwR+wZ3eeCCt7kRIvcscXyWcMMvJcHn81PIBfHhkRti+Lkozou9z3cFPhfXRwfkgWDXCRyDfe8wEjnmDLkZzoHWiqFK3ENZoIHCtL8ZNt34Py6TrhZeQnX28ADphR3m5ncQViucoju9OL1o9bS8laiKE0LWu5yBJ3+i3dQmUp+CQSxeuPaH/V8R/udXxGU7L8CtWuitzdR1UjACZ6bgaMUqShGO70BH9d8NnLjJw5a/ed5NGZ6/31yS0lHjgn7wgJyrPbgrVyy7TgaPVQnS0lzHJnksY83zlB+w5hBGSeIpm8vXBn1PM1VUt5kr1oNLWa4/7iJ6VQ4qs/gKQ08EQPjNKEZ5LnemZ4h/nXWxtz4YaCs6LSSwpsswfMiTvNDGrNxLCcrVriBSTiU0J9cRVTklapz3q4yCbBP1hoKSewSrI4lBNIobvspoWcZIZnKHtVyyA5DgWkNk0QD4XKwUkMJZ5UaMyJ/27yMvIKvdwzwlhmlyRFlvg5soWGksdJU/FbnoJWDUuXsa0s09lHVGN8SmgjFZYSQBMntCWu0UTgeK6LBJvQKNaUqzsXwp4l4MqBaZdXaYWymoqLRDap9rhw4FNCPXH6rdONvg4AAYFtEebEKZoTd5ku6OaYShxDiSts5JNkWOKh7uoAK32s/Xv//kAlnqSMQdrXSY8ARoGT688zUmRc5/obLpUQVMzFYQ4sXCSSq5JpIoaHwWZq2HBz4v9LepJ3E18o8hSYcthKqZTLEpLmJoGDNKWH7EJKqOimqVq0EtdoInA810XTBonYYn1rrn7f3FBGCdKqrVBWwxFTybr888P/3955x0dVZQ/8e2cmk94gEEINvffeFFAQxLZ2WRSw61p23XXV1Z9rXbFtcXXXsgrq2kUXBEURCSAqvfcAAVKAhBCSkEySmbm/P96blkxLSDIZcr+fTz5577773jvz5r0575x77jmOvOSB3ek257h5ha5IW4nTWGSE8wUAtCjyWMprRKc7ErdMNm7kqYh36WbQ8lVNMmwmQWjzuKdWfe9xziRKnQp5rezN57bziXOrZOYu0ykZT7KuxB1Wss3uqiIGrnnxWpv+kqG7+71Fiq979AK+f+A8r9ejroHl7UUBFxo3M8f4bd0OoGgUlBJXKHxQUFrLRC/lerCSMILRHNIKZecC5boSdwSZOdzcTne68OVOd42Ju7vTHVa4gzMyklhR4bR0qyeCaSk8M7bdaHIp7o62bI9tscLiMVZdQjQJeCpxRz70U8Q5o+UdL4jH9BSvDoWdiivwrZUo8vgs3mgdH+W8VyVwfo9Wzm2dU+J87uePF6uuBWCUYVed9lc0DkqJKxQ+KKhNylVbFWSvh26TYdKjrtKjiqCIiqj5U1QmI4kWFUTrQV4O97Qj8jyYeeJOd7o4TSvhGVFeRpSHO91htTuC4arPFV9t68d23aW/ydgXcNXljqfcaYmDNvYeKaqcx3Yke3GeV7e0HfucKNbWHWPi7i8oL0W8oX1+P0ocXAFsAk/re0Rn19j8P64fFLQ7/jXbFRy0t6mRjlbRtFBKXNHsydh7guNeil0UlNRCiR9eoyXwGDobxv9eKfB6oLo73eK0xDWlUj2nuAOTsGGTnmPi3iglmlgszuM4lFW5dFi0ntqujChnMJ1jTPy0Hqi2zt7TQ4k7ouAdGdjc3elaMhltf8cejmy2/pLMVPr5LO5IINLk+mkXAoZ01CLo2yVF88RlfYM6DmiFUlqippg1ZZQSVzRrqmx2bn13A3O/2ePRLqXU3OnxQSZ62f0VmKKh66QGkLJ5Uq6nL3W5080e/6N8JGRxt8Qt0vX9PVp1s0e/MhmppV31GZ3u+WJnpspZnSxS6uP00sxmezeett4IwMAOmrIs0Wt+O8bFTcLmTFKjzUPXlLVjfNumK3FfnwkgW5925xO3d47qGe6dAXTULtDtpEygpag5J17RdFBKXNGsOXbagtUu+X7Xca2ms05xuZVKmz24RC92u1a1rPuFYI4J3F8RFGUyklhhcbqeHRayoxZ49SIjDozYa7jTAT6wXejR7wxRxHmJTne8JDiC2ByYsTrTt5p1SzxKVLHb3hErJgwCOrbQvn+HJR6vFzSJcLfEiSCKSm59d4NHYFsklc6AN+8Er36rZ4Wde1V/pvRJZWD7pFpFumlKvJgFG7MDd1aEBKXEFc2a3CLNUiqpsLJ6n2tOrCPRS1DFT3I3QUke9Lq0QWRsrpQTRQwVxOouaWeiFundUnbgXme8ws84cpmM0izx6oFt+vET8aw0FiGsLktcd/FHU+EcQzcYhLO6mSOIro/hsC6T1TkMYJFmokQl3+8+7hyflhK+Nj9CS79KPEgkNaZFdmsdz5s3DcNsMtTOEieRZErJ2JN39nIpGgSlxBXNmrzT2o+yQcDXO1w/VK686UEo8d1fgcEEPeovn7UCzuju9EShKdMSXYmXOYuX1MyMBq564lBzTLyNW3azM0R5FECpkJ7udMd5HURTQZQeeBYltSQ00VQ4+wtgyTbtHnKUMv218XvejHiZFqLUIwGNw23ucHPb7JKuhpqK8o7K3/q4OvDgRT2ZMbKjc93dwP6zn3Hv2kw5OyXjMAhJtL00+J0UjYoKO1Q0a3J0S3xa/zSW6S71SJMxeCUupabE08dDdHJDi3vO4q1KeZmMJMZQwQsRbwGubGtaERSD07VdHXdL3JsLekR6C9ZlFXJGt/Sru9MdLwkJ1SzxFm5WcqReOMUk7E43v/up8knmjIwkAhtTjBsBvCpxxy42t7Jo39iGE0MFL1mvZbvs4vUzAvxmYjef2+IiTfzuwh787ft9RJmMHtsCpWF1x5EmNtFaPYe9oqmgLHFFsya3qJzkmAiuHtKeEouVNZmaS92VcjVAYFv+Hig8AL2VK72+KdengDlwuNO1RDBRTtd2dYzYnePPDtbYNMtUIumVphUhOSOjiRA2/s/0X8DlTrdjoEJG1LDE2wmXIouigqhqSWgSq7mwD8o0uopc57rVzcWvWfTSOWxjc3uLyZEpzKp62KnAH6q6jSsqnvL6Wb3hONL9F3Yna+50zCbPn3l/lnjWXM/CPY74g/OKFgZ9fkXjopS4olmTW1RO26RoxnZLIT7KxJJtWk3o/NIKjAZBckwAJb57MSBU1bKzxJtiKdUt5WyZwj57OzfrGt2K9mGJCxsmk0uhDrS8yeyqh1zn0v8f1wuXGISm9io9srlF1hgTd8doq3DWOHe50wWT+7jyp5+UiU73O+AxxQy06WRVNu3cVjdL3Fxt6twntolskb6t7uqfS3pza3jpFwwZ9oEAlBlia7GXojFRSlxxznK0sIxvs6r8/qjlFllomxSN2WRgcp9Ulu06RqXVTkFJJS1ig0i5uucraD8c4tvUs/SKMhmFQUgSKGOTvXu1bZHE6e70OMr42Pw0XYVW99o9Yxtoc7nd0606pnXtka7xZLsUTksZoFRGeyhgd3629SEKVxIaRyAc4FEitLhahjjXFDOtj/t0spOlNUuP1hZHxrjW1aqa1ZVlvzsPKyYKZALRNjUm3lRRSlxxTlJls3PXBxv5aE+lc9zbG7lF5bRL0lyG0/unUWyxsuZAAQWlFYGnl506DHlbofcl9Sm6QsdRiCRBlHGa2BrbHJb4JMMWRhl284DpM0CLBLcLT3e6N3bJTs5lTcm7XtgKife533GSiKbSaYm7Z1JzH35xjOE7+NWwdI/+7ko8KsIlb6SPl4dAxJhNvHzNQD64daTffo657IHonqpdg1MynhirmiveVFFKXHFO8s8fMtmRo+W+zioo89qn2FJFSYWVtkmashjXPYX4SBNfb8vTUq4Gml72y7+0/4kd6k3u5oq38pruFu5p6Zn/+4yMds4TdwS4ndETrJiqWeLuSOnuuhf8xzoNgIpqMb5F0ne+8XI9HWxUdXe6wFlSFKiRq91oMnPpwLZOd7p7zfDW8ZHOtLJf2Ub7PHcgrhranrZJ/kuV9k5LYN8z04I+ZiHxVJWokqRNFaXEFeccW48W8dqKTMZ31zJcHSrw7gp0zBF3/OhFmoxc2CeV73YdJ++0xX9Q29F1sO5Nbfl/d6uyo2fJh7eN5O/XDfJoc1jiQA1LvNQtsM0xj9zR34gNu/A98cY9OrtAJgI1052W4KkIC3WlflrGOKPLHUlo3F82ot0sakO1Ep7SYKRlrNmp9KPdLPHHF+4kU7bjJ1sffrAP9il7fVE92M0fp2Q8LaoVg1E0HZQSV5xTWKps/O7TLbSOj+TVGUMwG+GQD0u8uhIHuLh/GqfLqzhREsCdvvVjkPqPtCo7etakJUZzxeB2Hm1n3BTpaempxB1lRLuLbP4v4gPANSfchB2b8P7TJoRnEN0xqRUHiRRWHh3pemlw5Gl37gdcVDGXKyufxIJZm7+uB74V6y8Y1QuPtKiWuEXqLxanpOambuMW7V5aYcWITT9WHWuHNhAnZYKzkpqi6aGUuOKc4vmleziYf4YXrx5IYnQEqTEGsk56jzLOKdIsuXZuSnx89xTiIrUfW79zxAv2a/9V2dEGo1S6vpcOIt9j2xkZRayo4K2Il51tjkIpJqzYfaTAkNJTRTqCz2xS0D3ZZUW7W9egjV/vlR05INtRKqMxC5tTsRW5vWC41/o+oCd8OWBP0xoMmkx5aC8O880v4p4g1YTdmaSmMUhNCK64T45sSQtRiqxQwW1NEaXEFecMPx0oYN6aLGaN7sQ43ZWeGiPIKvCuxHOLyokwCg+LOyrCyIW9WwP4Ln5SeAgO/wgDrlNlRxuQUjd3+ke2iR7bzuiWeGs3C9ERrW7Cjt2HJQ6e1rIj+Mzhrv9QDwobbthb43wuubSXi94xmqXtGPuuPk3uddslXFnxBGvs/bTtdm2mxHHpSgoU51Zz3OiRpKbhWfunC9nz9NSA/Y7r3opNu/YG6KkIBUqJK84JLFU2HvxsG11SYnl4Wm9ne5tYA0cKy7Da7DX2yS0qp01iVI1pZJcM0Cyodkk+ipn88m/NAr/wSVV2tAFxt8SLiOeSAWnO9TNolrhjXBqgNac4z7AVg5DY/I2JeykZelRqL25pulemt+GIs8+zVTO4rvL/PM4NOOuT+6rzLTGwSfZwTm8z2CuRaMleHPnfU9wqhGmZ5hr3J9kQRA7WfLS4gTVbdjW0OIo6oJS44pxgZ24xOUXlPHhRT6LNLmsmNUZgtUuyT9WcZpZbVE5bL1HRF/RuzZd3j2F4upc0qmWFsPl9GHAtJKTV3K6oN0qrBZe5B425K3gH4407eM/8PIBfi9ZdbWXJVFbZ+vNk1U0AzgIm7jnL37JdwgHpGq93lhmVJVgxctt436lRwZVExmCvdKaXvatKO34KLiVuFHZnHfTGIlAaBHAF/8VWqdSrTRGlxBXnBI4gta6tPacGpcZqt/ghL+PiuUUWj/FwB0IIBndM9rDYnGx4B6rKYPQ99SC1wh8Oi/ewXbOS3b+OMjf39m57zSl+fueJux2nnChuqnqETbKHR5ef7b4LiDheLuLsJUHNR3fkZBe2SqQ+Bu5wqf/D/Jqzn3sJ1cbCmyX+wtUDGNbJ9QKbr2e2i1P505skqgCK4pwgx0ukOUCbGE2JZxWcgZ6udptdcqzYEnBOrQfWClj7BnS7EFL7nLXMCv9IDNxQ+Sh77R149OLeZJ5wBVaVSpcS765navPY14873e85dUu5erY1dxzz0c1VxdiMJq/FWzz76zEXBpMzju2Q1Lw47vnYjSFwp3t7T712WAeuHeZ6MTpJAjYpiKs61YiSKYJFWeKKc4KcU+UkRkc4I8sdxJshPtLEoWrBbSdKLNjssnZKfNuncOYEjLm3PkQOOUKId4QQJ4QQO3xsF0KIV4QQmUKIbUKIIY0t48/2vhSSQFJMhIfCKXebAnbKS3a1iwd5T8Aj8V/Fy5WiV1Auzayy9a/Rx6HgWwjNEr9Qz5c+pmuK12P+1zaZ+dYpnOg1iwij9pNbSQTr7Zr1P1RoAWOmEFjiXr1N1bBjoJAE4q2FjSCRorYEVOLeHnQhRAshxDIhxH79f7LeHvKHXtE8cU+f6o4QgvSU2BpK3DVHPMg804d/ge+fgBZdoPP5ZytuU2E+4C88eRrQXf+7Hfh3I8jkE3d94x4tfkbW/A4TYnx/r8HW0x5c8QZzqv5Yo71AJgCQLEqxCyOjurQka+50+rVL9HqcMqJ4wjobe1QCD0x2ue3/Y9WK5ozQI+EbOzq9NuTLJOKrVNa2pkgwlvh8aj7oDwPLpZTdgeX6OjSxh17RfMjRq5F5Iz0ltsZccW9zxH1ydB28dymUFcDpo5C9/qzlbQpIKVcB/syry4H3pMYvQJIQIiTRfNU91mVuivt3VXfzT+sVHtuFMQJf+NPh7uexEOlVqRYTS4V0lC2tndKNdyuQ4sjM9lDEx/za+H2jzxOvDfkykZgqZYk3RQIOHEkpVwkh0qs1Xw5M0JffBTKAh3B76IFfhBBJQog0KWVefQmsUHgj51Q5o7q09Lqtc8sYlmzLpcJqI9Kk/eg6LPG0YJR41motKxuA3a6tN49pZe2Ao27r2XpbjedZCHE72os7qampZGRk+D1waWlpwD7u7N2zh7wi1zTBMt2dnmVPZbPszmZrN/5hvZK1kb+hpSjhwMEsoOb7RlVlJUeOHKnR7pBp3bpgXtAEBSTSjpNU2Qn6c2zetJmSQy6l715Z7dmId6iQJuxBKPHaXLfaUv3YfVsa2HnSTj5JdLfmkJGRUevvrjFozjLVNbAt1aGYpZR5QojWenuTeeibGuEsf1OX/UyVpKTCSvnJXDIyPDN7lZaWYik+il3CgqUraRun/Uiu31VBbARs+PnHgMdPPgkD9GW7MLK1MJbiRroeIb723oxWr2FcUso3gTcBhg0bJidMmOD3wBkZGXjts3SJ1/49e/ai7GgRZGsK2BEA5goEE1gxOVOvdu/Vl0m21vyw54THccxmM+mdOsDBzBrniIuLo1uvIbAmcArdApmoBaWZzDU/h4/PMHjIEIZ2Sva6PUe2pA2FHmPiA9snsjW7ZvWwQNe21rjJU/3Y7xxcByfzyZeJtBanaXv++WSsXFn/MpwlPu+nENJYMtV3dHrjP/RhQjjL39Rl351XDMtXc/6wfkzo72l9ZWRkMG3AQN7c9hOtuvRlgh6E9N/D6+mYYmHChCDSpa7PhO3AiNsx9r+GIY1ohYf42mcD7hFi7YHcEMniMZZ9Qp+i9abNswysw5IV5ljemT2cLo8swV7tF8jbmLijLVCkuYN8fe50MFPMgqFAJtLOcNIjOv2PU3vx2w/WkV8epFANgGMeeb5MwoSVLfuzQiaLwjt1HYA57hgb0/87Xneb1EOvaB7knPI+vcxB5xQtpaZ7+tWcIgttE4MMatv6CbTuA9NeaC5udAeLgJv0gNVRwOnGGhp748ahHusS6WEhlBBDuuVDPqmWjtWZLCVS+86NXrKZeLM0ovRhFnuQWtzxElGbMXF/AXUOC7xKuuyqfm0TefF831Pd6pvF946r0Zagj+Gf0OeKPzjvu0aTRxEcdVXii4BZ+vIsYKFbe0geekXzxTFH3FeQWlKMmaSYCI+EL7l+AuE8KDwI2eu0DG3BhjWHCUKIj4CfgZ5CiGwhxC1CiDuFEHfqXb4GDgKZwFvA3Y0l20V923isB2shG3THn4jSLOXqU6gGd0zy+j1GRmg/hW0SgnuxO4Gm1Lwda8l9NZWhL36yafkGHHPdLbgF5DXy7eYtuv7pK7S877lSizfpIE7U6KMILQHd6fqDPgFIEUJkA38G5gKfCiFuAY4A1+jdvwYuRnvoy4A5DSCzQuFBblE5ZpPBb/3v9JaxHMrXlHhphZXT5VXBKfFtnwIC+l8TsGu4IaW8IcB2CfymkcTxi8THuFw1/mW7jOcMb0NyOgBGNyX71T3j6NY6jtdXHqixX6ReX7ulv8p1buTpSi3BSxazvm29TzXzppPvqHqA7cZbSRBauVz3EqjV3w8uGZDGwPZJQclXXyRGay8VmXra2RGGPYCq2NeUCCY63deDfoGXvk3moVc0H7L1OeL+Eld0SYnl54PaD25esHPEpYRtn0D6OEhsX2/yKmpPfFRw4Tsf2SaxwHYe22I0d7e7N71/e025Xju8A59vzHZ6cAB+Nbg9cCxoefbbNaUWYysJ0NM/JdUywznKqUJNpf/qjNCl3SgmlnJp5k7TYjK4LWRyKGrSNCclKhS1IOeU90Qv7qSnxJJ32kJ5pS2g+91J9gbNnT7w+voSVVFHpvcPdnq6oBJXdjdvucHbJUWz5uFJHm2DOtTOwj0g29aqPwSXHc1DiTex4Zs1ej75iMqiAD0VjYlS4oqwx1e2NnfS9eC2w4VnyNUTvQR0p2/7GExR0PuyepFTUXdqq9AcyjvY3WqrL0+jFdrZae/kdfv+Z6cFfawXq651LruXNXWIdMf5XXj35oYLqLx2WHvenjUsYL/5Ni3nV+wZ7/PsFaFBKXFFWFNhtXGipCKgQu7c0hWhnltUjtEgaB3vZ/zTWgk7voCeF0NUQn2KrGgEHEq8eq14X7j3mjmqY1D7jLC8xjWVf/a6LcJo4PFLPIvkVBflsztHA/Ca7Qq6Wt7n8apZH0CfXQAAIABJREFUTkUJrheLR6b15vwerYKSqS68cPVALuidGrDfXrs2pHQs9xDpDy9h+e7jDSaTIniUEleENXmO9KnJgSxxbezxoK7E2yREYTL6uf0zv4fyQuVKD1McCtMYpIntbulP7evbdf/ObJfFeoJkj5Ko1bl5XGey5k53k8lTFrvbBHYbRt6zXeSRxc1foZZQkE8SRTKWk3mHAbjl3Q0hlkgBSokrwpxgx7fjoyJIiYvULPHT5YGD2n75N0TEQGTNClmKxiWo/PbVEE53enCK0N1Kln7i4Cf1Cmyx+qL6nPVA0fZNbEgcEGTKdnQ1qNQfTQmlxBVhjUOJtw9giQN0Tokhq6CM3KIAdcT3fw9Zq6CqHN6/UiuAoggZJmPdtVmQ3nQPhRnsnPTay1LNEm+oE9UzVw5p51w+LFNpL1ypje3V0+EpGh2lxBVhTc6pcoSA1CCSdKS3jOVgQSl5p/0keinNh0WOWZJSK3ySFTiXtqLhGZHeotb7BB3Y5ua6bii1VH30Jkx0uEfkfq6e492IDQCL1RYqsRQ6SokrwpqconJS46MwmwLfyukpsRSUVlJlk96VeNERmDcVygrBaAZh1P6nq+QWocSh7K4Y3I61f6qRnsIv3qaYecXDEm8Y7VrdtR/oNE3FnX7jKFcEfq5siUnY6ScOAVBisYZKLIWOUuKKsCa3qDxgUJuDLvo0M4B21cfE8/fCO1M1S3zWVzB7CUx6FGYtam750psc7mPUwXhc3OnZRotpSAuQJ78x9GX1cwRypzeVwDb3l49cmQLAwsjH6SmOMPIvy0MllkJHKXFFWJMTbA50XHPFodoc8U3vwRvnQ2UZzPkaOo7SFPf43ysFHkJM+oB2qyBToXrD4Qq+Zqj/jHtBW+z1SPgFtsE2e2fncj+RBcBz3+xm//Gzy1ynqDtKiSvCFrtdkldkCTp6Ob2lFyW+7ztYdC9Yy8FaBlVlDSGqog48MKUHAJN6ta7zMRzKOVD8lUdgW53P5p/qxw1siTc9TpHAc1VaJu5LjD9jwsobKw8y+W+ryD6lnp1QoJS4ImwpKK2g0mYP2p0ebTbSJiGK+EiTs8Qiq15wdbBZVRBbiOnQwvVd3jy2M/dN6sat47v47N8jNY7Vf5zoc7tjWldDua7fCyKTms+XzIAvFk1RjcMbtksBmGjcynXGDGf7i9/uDZFEzRulxBVhS7Zzjnjw46RdW8e6lH7BfsjZpAWwqSC2JsGK309wpiyNijDywJSeREX4rtkd7WcbuCzsQJZ4rzRXPoBBbpXCvr5vPAt/M9bnfoM7Bs657ivocmh6Mp1a+q4X3jRVuCfPRrxDEpor/WRpJXa7bLDAQIV3lBJXhC05pxxK3PcPYXWevKwvL10zUFv5/gktocsNH6kgtiaCyWggwl8mvWqkBBgvT47RcpE7Smp6I2vudI/jJMe68pf3aZvAQB/FUdb96QLio3wfNxAJURGsfNDTi/DhbSNJ96PYmwpzKh90Lj9g+pxIKvkxs4Auf/qaez/aHELJmh9KiSvCltxgS4q60a11PP3aJULWGtizGMb9FnpcpILYwpTObsGK3rh2WAeeu7I/t47v7LdfXWhdy0j5YIgwGvj0jtG8ddOwoPO+NwYvXzOQG0a4csqvsA9mesVfALjJtIy9UbPpLbR0rIu35YVExuaKUuKKsCWnqJyEKFPtrSG7Hb57DBLawai7G0Y4RYMRY3a50AMNGxsNghtGdKyVde+NrY9PYdsTU5zHrA2PTe9NSpw5qADMxOgIWidEMblP3dO7NgRXDW3Pc1f292jbKdMply6vxQWGTc7lo4Vl7Mg53WjyNWdMgbsoFE2TnFPltEuug+tx5xeQuwmueB3MTd91qfDEXYU21tSwxBjXi+K2P0+pVcrUC3qnsuGxyQH7vT5zCD1SwytX/+WVT9Nd5PB0xDu0EYXO9vEvrADwKACjaBiUJa4IW3KCqCNeg0OrYfED0KILDLiuYQRTNCge6lO4Mp8Fkz+/PoiNrIP3Jwim9vNdPa2psk92YIl9FLkyhY7ihNc+drvkk/VHqLTaG1m65oGyxBVhS05ROaO6tAyus5Sw7VP4310gbdq88JwNahw8DHE3ggXCmdFNCJjWrw1dWvkfJ3cwf85w2vjI5LbgrjHszis+a1mbC21FAf0MWXwinuJD6yQW2scB8JsPNjGlbyoPLdhOzqlyjhVb2J1Xwlf3jguxxOcOSokrwpJiSxUlFqv/oLYja+mx559wYr7mPi867Npmt2lzwpUSDzvc07C6D08LBP+eOTTo40zo6TuJzNBOyQztlFwn+c5lxnVL4cfMghrtr1p/xeMR7zPSsIeR5j0stYygAjNLtucxsotWuOZUWRWfbshubJHPeZQ7XRGWBJxednQdzL+Ytse+h11fQnwbGHs/mKLUnPBzCCHCpxrYuUCEj7Kw820XeawPN7gSv2w8fArwfOHalVtMxl7v7ndF7VCWuCIscUwv85mtbf93YNcrLAmjaxpZr0s0Czx9vLLCzwHc53c30QRn5xTuc+jdsWNggOUtWopiVkT+nhuMy/nRrkWzL9ySq/Vxe9m6+BUtM2LW3Oms2HOCNolR9E5LaFjhz1GUEleEJTmB5oif0kolSgwId6u7wwilvMMch+X99OV9mTGyE0cLVc7uxuLJy/oSWZbPR3sqa2wrJpZiqXnGOnkJcnv/l8M12gDmzF8PqEj2uqLc6YqwJOdUOWaTgZRYLxm7inNh92LoNplDnX+tMrGdYzgMuiuHtMdoEM71UFQia27ER0VwUbq/yHzBQtsY0sRJUin0009RXyglrghLHNPLvGa1WvmCFrg2/SWOdLpaKfBzFIfObpcUzQW9WvOP6weFViAFAAftabQUJayNugeBmlbW0CglrghLtDriXlzpJw/A5vdh6GxITm9ssRQhwGwy8Pbs4QxoH7gYiaLhyaOFc3mSIfg86m+uOuCxXlZpZdY76zhyUg2X+EMpcUWD8OP+Ap78ameDVDQqq7SSeaKUji28zAfOeA4MEXDegzW3Kc4p6lo+NNQsumcsX9w9JtRinBVLf+t7ZsentgkUyjgA3ja/7Pc4J0srnMt/+XoP+46XUKC3Ld99gpX78nn+2z31IPG5i1Liigbh9ZUHmLcmi5X78uv92J9tyKbEYuXqoe08NxzbAds/h1F3QnzTyj2tqEf098JwHQIf0D6JIR3Dew66/+pxgiEVbwR1nMcX7fRYn/K3VUx8MQMIWG5doXNWSlwIkSWE2C6E2CKE2KC3tRBCLBNC7Nf/h/fdqqg1xZYqfjl4EoDXVmTW67GtNjv/+fGgnoyjhefGr/+gz/9W2aCCQQgxVQixVwiRKYR42Mv22UKIfP353iKEuDUUciqaHi1jzdw6zl9lOMESmxaLEknNSHYHZRXWGm0l1drKK23sPVZC+sNL+HxjNvuPl9RJ5nOV+rDEJ0opB0kph+nrDwPLpZTdgeX6uqIZsXJvPla75JIBaazPOsW6Q/UXpbp05zGOFpZz2/gunhs2vgtHfgZbJXw8U0v2ovCJEMIIvAZMA/oANwgh+njp+on+fA+SUv6nUYUMQLha4ucCQggeu6QPn9w+iuW/P99rn2zZCsBnTnWAFXt9e+psdi0o7oc9J3jpOy15zB8+28rkv62qq9jnJA3hTr8ceFdffhe4ogHOoWjCfL/7OC1izTx/1QBS4sy8Wk/WuJSSN1cdpHNKbM1SjWtecfTSFHnW6no55znMCCBTSnlQSlkJfIz27CoUQTOyS0u6torzuu07m2bXDTJk0l7UfljtTIXNubxs1/Ea24stVbU+5sItOZw649szEI6cbbIXCXwnhJDAG1LKN4FUKWUegJQyTwjhNUGxEOJ24HaA1NRUMjIy/J6otLQ0YJ+mTDjLXxvZrXbJsh1lDEk1sf7nH5nQVvL5vnzmLVxO50Rj4AP4YfdJG9uyLczqY2b1qpXO9vjivQwtzMQujCAlUhjZWhhLcUZGWF93aND7ph1w1G09Gxjppd9VQojzgH3A76SUR730adTn2WGhrVq1ioha1vZuKJnqA2/nDrVM3ghWpuNoI6kvRrwJwLiKv5MtfeerdycjI4P9R3wr6fcWLefxnyzcPiCSMW1NPmX6+lAl/Voa6Zhg5GS5nd+vLKdnsoFHRjZ8xbvG+u7OVomPlVLm6op6mRAi6DBCXeG/CTBs2DA5YcIEv/0zMjII1KcpE87y10b2nw4UUGZdy8yJA5nQrw1DR1Xx3dwf+KU4kTmXDwt8AD+8O28dLWPtPHLDJKIi3F4I/vsqRLfAcPU8yN0I6eMZos8ND+frDg0qvzftVz2W6CvgIyllhRDiTjTP2iRvB2vM59mw7GtsNsn5552P2VR/zsSQ3StLlwB4PXdTvH+9yqR/ho9uG8UNb/0CuNzpDn6M/C1dLe/TWxymlGh6iaMstXvP4bDN1o53d+3zKUNsu57AVgqMKUyYMMjndZq9dAmfUsU/rh9EQmsTsIFyEdUo17SxvruzUuJSylz9/wkhxJdoLrrjQog03QpPA1SW+2bE97tOYDYZGN89BdAyPM0ek84rP2Sy/3gJ3VPj/e5fabWz7lAhI7u0IMLo+oHed7yEFXvzeWByD08Fnr0RMpfBhU9A1wnanyIYsoEObuvtgVz3DlLKk26rbwHPN4JcAVEFT5ouo7u6lwYWvGq9nHtMC50tc01vcY3JNabdw/IuldTMAPfXZb4VOIBdvwm+2JzDF5tz6J9ixJ++vP/jLc5leY7Fvdf5NVYIESuEiHcsA1OAHcAiYJbebRaw0PsRFOcaUkq+332csV1bEhvpej+cM7YzMWYj/8o44GdvjZe+28vMt9dy4V9X8r/NOdj0qglvrjpIdISRG0d18txh5VyIbgHDb6vXz9IMWA90F0J0FkKYgevRnl0n+ku4g8uA3Y0oX0BUYFvT4cWrB/DO7Jqetpes15Fu+cC57q7AAVJF3YJeHZXRHGwvsHHGS6S7N861l8Cz8UWlAj8KIbYC64AlUsqlwFxgshBiPzBZX1c0AzJPlHKksIwLqwWdJceamTGiI4u25vrNvrT/eAnv/HiI83q0IsZs4refbGHaP1bx6fqjLNySw7XD2ntWUcreqFUrG3MvRHoPrlF4R0ppBe4BvkVTzp9KKXcKIZ4SQlymd7tPCLFTf8bvA2aHRlpP/nJlf1rGmjGeI1p83pzhLL43vKdFXjOsA5N6+crNIPjMep7XLRMNW7y2B+Lj9TVDMy599UfGPLecE8UWv/vWhxIvsVSRd7r87A9UD9RZietRrQP1v75Symf19pNSyguklN31/yoLfjNh2W4tgvQCLw/zbed1wSgEL3y7x2sWNykljy/cSYzZyN+uHciSe8fx6ozBWG2SPy7Yhs0uubX6tDKHFT5CWeF1QUr5tZSyh5Syq9vz+7iUcpG+/Ij+bA+UUk6UUjaJ1FnXDuvAxv+b7D1vfhgysWdr+rVLDLUYDcpnNtc0tD32DlRKbUhsvGF7vZ3jYP4Zck9b+Hp7nt9+OUXltcokWWG1Ybd79p/+yo+Mfu6HOslZ36hSpIp64/tdxxnQPpE2iTVzmqcmRHHvpG68vGwfPVLjue+C7h7bF2/L4+eDJ3n6in601LNBXTKgLVP7tuF/W3KxS0mHFjGuHTa+p1nhw26GSP/j7AqFIrSsk70ZbvkXHcQJ9sn2lBLDz5H3UIyX1MlnyScbspnYqzXn65nfvLHlaBGDg8ya1/OxpVw/vANzrxoAgKXKxhG9/G2l1U72qTK6+Jhm1xiotKuKeiG/pILNR4u4sLfvdKf3TOrGlUPa8ddl+1iwMdvZXlph5Zklu+jXLoEZIzp67GMyGrh6aHuuHeYWg7XvO1h8v7a85UOV2EWhCAPySWKT7EEp2st4mijkKuNq/mT6IMCetWN3XrFfBQ5wurwKu116WNiVVjuVVu9V19zd9ze+vda5/KcvtzPp5ZXkFpXz1dZcb7s2OMoSV9QLK/acQEr8KnEhBHOvHMCx0xYeWrCNNolRjO2WwivL93O8uIJ/zxyK0Z+L1GaFDe/AssdA6g+brUpL7KLKjSoUTQKDAHsQ3uoqaSRC2LjdtISDMo3Zxm+ZWjkX77Mf65fHF2o5248UlrH7qalEm40Me2YZlio77948ghve+oVPbh9F26Sa88nXZ7mC6n4+oE3geODTLfxysJAOLWIY1KFxq+kpS1xRLyzbfZx2SdH0TvPv2jabDPx75lC6tIrlzvc3smRbHu/8eIhrh7X3XRTi6DpYdB+8Ogy+eRBa9wFTJAijnivdd0UlhULRuMysPoPEB/0q3ma9vQelMoq5Ef+hl+EoWVG/Zrn59w0soaa8HS7x3o8v5XixhWKLlUqbnUe+2AbAdW/+wvgXVvg9Tk6RFtyWeeIMQNAR8vWJssQVZ42lysbq/flcN6wDIoiI4cToCObNGcGvXlvDbz7cREKUiYem9tI2Hl2nWdZtBoK9CnZ8ATs+1y1vAZMeh/EPQPZ6rV/6eGWFKxRNiCcu7cuj03vT87GlfvtVYOYne1+GmzznhHc15NFRHOeITCWVQqJEJYdlm4YUmZF/We5czvIxg2Zn7mn6tvUegOgonxqKUEulxBVnzZebc7BU2ZncJ/gHrV1SNO/MHs7N89fz4EU9tWC2vd/AJzPB7vY2Kwwu17kwaE+JEJriVspboWhyGAyCSENwKZYP2dO8tl9lXMXfrNewNuoeAPpa3uYMDZ8q1R/TX/mRrLnT/fbZmn2aMd1SGkkiDeVOV5wVJ0sreH7pHkakt2Bst5b+Ox9dB6tfdgai9WuXyNqZsVyTPRfmXwIfz3BT4AKGzIKbFoEpWrnOFYpzkEO6hb3f3o5pFc/xZNWNANxgXOFRwnSR+THOM2wlgTMhkTNYnl/a+LMwlSWu8MvGw6fYfdLGBB/b//L1HkotVp79VT//rvSj6+DdS8BaCQYjdJsMpccQuVtwpuzuMkEvJ2rVFPbgmZq1PWuRcp0rFOcge2RHfrL14WXrNeyWndht68RU43pGGvbQShQ5+3U15PGeWcv6m275MFTiUlQWuALaqTOVnkmpGhilxBU+OXWmkpvnr6e4vIr0HnlM6+/p+vr5wEkWbMrm7glda+ZEd4xtdxgJxXmw/CmwauNG2K1waBVEJ+FU4MIInc+DiY/WVNjKda5QnJNUYGZG1WMebV/ZRjPSsIfe4ggAq2z9Oc/oSgrTmlOcILg53vXN9pzTAfuMff4Hdj01tRGk0VBKvJkipWRnbjF90hJ8Zr56edleSiusdIg3cN/Hm3kjwuBMrVhhtfHo/7bToUU0907yTNyiWd2X6kpbV9LxaWAwaTkPjRFw0/+09ncv0+p/O1zlSmErFM2abKmNKb9l/isAX9rGeSjxy41reMt2CQCxlJMmTlImozhBEtYGVmk3vh04J0VZpY2fMgsaVA53lBJvhuQWlfPQgm2s3l/AreM689glfWr02Z1XzIdrj3DjqE6MjMnn33siuPO/m5g3ezhju6Xw1qqDHMw/w7w5w4k2Vwti2b0YrG75iwfNhMv+CTkbalrZylWuUCjcOFqt5vhy+2AAjslk2ohTPBrxIR/aLuAM0eyMusXZb4VtIHOqHmpUWX2xM7eY7oG71QsqsK0ZIaXksw1Huehvq9h4+BTjuqXwnx8PsXBLTo1+T361k8ToCH43uQcxEYL3bh5B55ax3PruBhZszOafP2QyvX8aE3t6PnCcPABbP9KWhUELShs6CwwGTUmP/72nsvbWplAowp4rB7cD4KVrBtZqv0PSNWz3dNVMioljjOUVplS4KuHujLqFyww/eew33LD3LKQNX5QSbyacKLZw67sbePDzbfRum8DS+89j3pzhjOjcgj9+vo0dbmM93+w4xi8HC3lgSk+SYrQAjeRYM/+9dSRpiVH8/rOtRBgNPH5pNQs+fx/Mu1gb8778XzDpMc3SVgpaoWh2OJK2uQ/WzRzV0VtXD+wYSLd8SLrlQ962XQxALikUE8cJ6cqG9or5VQB+tPUFalrwoeTZr3dzyuI9hWt9o5R4M+B0WRWXv7aGHzML+L9L+vDxbaPo2DKGCKOBf/16CC1izdzx/kYKz1RiqbLx7JLd9GoTXyOPeav4SD64bSRDOyXz5GV9SU3QC50cXQffPARvT9bmdM9eAoN/rSxshULhUfd93FnOoR5f8XfurrzPo+2Wqgd5xzqVjuI4rleH0OMjDXu9o5R4M+DJxTs5UVLBJ3eM5pZxnT0C2VLiInl95lDySyu458NN/GtFJjlF5TxxWV+veczTEqNZcNcYrhraXms4uFKzvte+DpYimDoXUmuOsSsUiuaFI/3q6K4tidXjZsZ2S2Hulf3rfMwKzGyTXZ3rsysfpAIzh2UqsaKCG43L8FTkkmdMb5Mu8njI9BFZUTPoJw7W+fy1wdZI7xMqsO0c54c9x/liUw73TurmMzH/wA5JPHtFPx78fBs/HTjJ9P5pjOriJ3HL0XVadrWSPNj5pZYeFbRpYkVZ9f8hFApF2DG0U7Izw5nJaABs2O1w/YiOPLNkN6V1zDOeLVs5lzOlZkycklop0Kcj5vN0xHx6WuYzzbCOv5v/BcBMkyut6uLIx9hi78IVlc/U6fzBUlbVOFpcKfFzmNPlVfzpix30SI3jnknd/Pa9ZlgHdueV8PnGozxycS/XhqPr6Hj4c8i0gjES9iyGdW+BtGnb2/TXxsLtVpVRTaFQeOWTO0bxv825JERrKieIEgt+0RK+SBwj7t/ah3ts3xs12+/+gwwH6SWOsEdqQ4atKMKKgVMknJ1gbihLPADv/pTFmsyCwOUrmzHPLtlFfmkFb940lEhT4FzGj1/ahz9O7UlUhN53+wL44jY6Sxscer/mDsIIfX+lKW41TUyhUPigV5sEHp7mUpDpLWODSpziH9fvfgVmxlhe4W3zS/Q2HPG5x0jLq8587EsjHwbgsL01nQwnNLnqMRtcMOVY64OwVOKlFVZe/m4vxRYrCzZlc+2wDqEWqcmxcl8+n27I5q4JXRnQPsj6tkfXEZW1GmJaQeZ3sPsrwPGoCBg0A3pfBp/NVglaFApFnZk/Zzivrshk3posAJ64tA/D0luwcl8+L35bt6liuaQwrXIuAPGUUUKMvkXykOljvrSN4zgtSLd8SFbUDOd+DgUOkBU1w0ORt+Q0ieIMB2XbWsujLHE/fLT2CMUWKx1aRPPSt3uZ3j+N2EjvH6XwTCXJMRFBlchsLKSUFJRW0io+skGOX2Kp4pEF2+jWOo77L/CTcsC97GfhQfj2T67xbXOsprR3fIHdWonBFAlDZ6tc5gqF4qxpGRfJny/t61TiN41Ox2AQ9GuXWGcl7o5LgQMInrfe4LF9duWDzDe/6HXfrKgZTK94lp2yMwsj/4/2Qsu+5q7c+4mD9BDZfGE/j1QKiRPlHJDtPI5jl2pM3CsVVhv/+fEgs9of5/ZOudz7UyxvrOzAA1N61uj7zfY87v5wE7eM9Z6VLBScLqvioQXbWLrzGK/PHMrUfvVfJ3fuN3s4VmxhwV1jXK7x6qx9E5Y+5Crz6YGA0ffCxEdg6ByyfniPLpNuUrnMFQpFvfLWTcP4cnO2z9TPPVPj2Xu8pN7Pm2EfzAjLaxSQyATDFo7JFhiwszhSy+O+JPLRGvusNt/P+Mp/0F7kO/v9QX5KW1Ho7HNv5T18ZR8DgKmRhnnDTon/b3MO7Uq287jtOYwFVXwcFcFNqw3kjuhI2yRXvdkdOad54NOtjI08iPnnhSwRFzN9+hUhlBzWZxVy/0ebOVFSQbukaB79cjvD0pNJias/i3zj4VN8sPYIt4zrzOCObkUCHFZ3dDLs+EJbdiKg+2StKImtSnOTd7tA29RhBEc6ldFFKW2FQlHPTO6TyuQ+qT63T+zVmjHdWjot9vrEUUTlB/sQZ9vVFY/zeeRTXvt3MOR7uOEBDwUO8E/zq/zB/innV/6d9ITGmcEdVkrcZpe8seogtyXuxmDRKmJFyAoeE+/w9ecmbr1gABz5hWJTC374YSsfmtYxSO6BCIlt3WccKPmJrj0HQnGOVvaykRSTzS5ZmFnJwm9/pn1yDAvuGkO02cgl//yRR77Yzps3Dq0Xd3+Vzc6jX24nLTGK303u4dpwdJ1Wr9umVxGLSoaRd8HGeS6lfd6D2p9ykysUiiZAt9ZxPDC5Bxl7TzSIEvfGBtmLmyv/gAUzH5r/AsBoyz+5y7SIm0zLfO630jaA843bAG2MPStqBssr5zeGyOGlxJftOsbR/NNcnLoNYQEQCGGgu+kE/bL/hHxX65cA3AdYI5MQFdq4hFHY6brnDXDUbM+YC9P/quX1DkKBHi+28NTiXfRtm8CUPm3o1jouKJnLKq3c9t4G1mRWcfmgtjxzRT/i8zfDvtW8OLIL9685zoJNOVztSJ5yFsxfk8WeYyW8PnMocZF6xbCs1bDoPpcCR8CoO2HCw9DvSu9lPxUKhSLE3Dy2M2aTgUm9WnPHeV3I2JvfIK716jgsc/cx8Metc/jUdr7TjZ5u+QDPhLJAFTxmep9bTd8AYK++vYEIGyUupeTfKzJ5JW4eiad3w/mPgCkC0sdT1bIf3754A5fIDAyATQqyet9G17HXuJW6jGCZHMYk2xqMSG2e8+L7tUxjXSZogVw9LvKqxGx2yX0fbcZ+ZC2ddu7ij9/2pihlMLM75DMldh9tBkz2s98WLAd/5u22+5g0/DrElv/Ad4+B3cplwkhc0gWsWdSJ04VtSKw6AX0ug/Rx2gEcLnB3JeutDcgpKudv3+/jwt6tuSj+EHzxZ8jbCvm7NRe6swyoGbpO0nZSY9sKhaKJsfXxKby+6gDXDtMMG5PRwCMX92Zqvzb86l8/Bdi74dghuwScgvaM9Uaesc4kFgvPmxIbRa6wUeK7C+2MPfY+0yJWwPkPw8SHndvigKhRt1Dx009EYEUaIzQF7hZJLdLH07sBysTlAAANDklEQVSojMoF64jAitFkRoy8C/Z+DWv/rR1o9cvQ+1LNQjVFQ/Y6SOzIt/uKuezoCm4wr0RIGxIDR8s70n7nYQxI7BteRoy8E9H9Qqg4A7mbkEnpfL7jNGMzVzMrcjmi0IZ43/MGENLGJMt3XCCANXrjujcgOR2Su0DWKrDbtPrbE/6kKeIfnnIlVpnxGUREwaFVLN1WyQyO84fK/Yh5joMJGHMfTHwUjm1TrnKFQtHkSYyJ4KGpvWq0x1WbgfT5naM5Vmwh51Q5z32zp0b/0CE4Q7SaJ+7B0XX03vUSEyN+wtb3KowTHq7R5YLJl/J05guMNe3mwmlXeY2kbt8BdpV/xNLFn5EdPZQ/j51DYlQ8/PCMHqUttXSiuxd5HPti0K6U/qUI7HQyFCCF1Bwmdhvil9fgl9ec+wjgumr7gYBukzXlrI9Fixu/YOfKz+h1YB5GoWcgMkZotbftelpCWyUsf8LzA1st8N6loB/+FtAy4Re4vf0JA0QnaYpeWd0KhSKM6dQyluHpyfRIjeeDtUfo1y6RYektAJqYEtewN1IxlqZfAOXoOuzzpjPB+hN2BMZhc7yOYRsNgid+M4fJd7yA6DjS5+H6jLiQoTOfYXFhB26at44zbUdr6USFUbO+Zy2CYbfgGO+wIfgs4lLKZyzUtjv6TXkWYYpGCiNWYyQ3V/ye76OmIh37ScGKxF9hn7EATNHYMYApCs5/EGZ9BZMe1c7VaQx9JlyP1WDGKg1UCjNZY1+EmQu0/sKINEayoOOjPFR1KxWYsElBpTSyjW7OxIM2BLbR98HMz11yqjSoCoXiHMFsMvDZnWN49lf9yZo73ef02TdvHOq1/ZPbRzWkeDVopGniDWeJCyGmAv8AjMB/pJRz63SgrNUIu1XT28Kgubg7n51iOr9HK16dMZi7PtjEnGXJvD/jSyJzfnK5mg0m5JYPsVsrqJQmhl18C9E9JtRMcpLaB5G1moj08fyqsD1vffYZ40w/YJJWbMLEmCvuwNB5NMxa5H2uteNadRwJNy5k/eqveO1QGj9+UsalA5N4+PJPKN2TwQt7WvHD/nRmjZ6Ote9VcGQN62Uf1madovvh3xGBFWEyY+xzqUrGoqgVgZ5TIUQk8B4wFDgJXCelzGpsORUKf8yfGsvA4WP4alsuU/q2Ycl949idV8LVQ9uzPfs0Nil9FoBqKKJMYRzYJoQwAq8Bk4FsYL0QYpGUcletD5Y+HmGKxG6twFCPluWUvm34+3WDuP/jzdy6IoW3bvotZqOBghILx0VPtvT8J3lbltF/3HSmDZ6o7VTdJe22fmkH6NhiFr+Zb2KkYRfXXzODhM6jnf0CzbWO7DKa0V1G06esijdXH2Demiy+2moDhtMnLYH/ze7PQMdN2HUM44BxQGVWV87sW0Vi74kqGYuiVgT5nN4CnJJSdhNCXA88jz5SpFA0JZJjzdw0Oh2Avm0T6dtWG1rs3941xLj0t+PZe6yErUdPM31AGkaD4O7/bqRnm3hW7M2vV3miw1mJAyOATCnlQQAhxMfA5UDtlbhuWdawZOuBSwe2xVJl48HPtzHyL8s5U2HF6oxGiOHi/nfw4NQhfo/hzsAOSbzyxzsQAmLMdbu0iTERPHhRL2aP6cz7P2eREh/JjBEd9VJ+NTGnj8acPrpO51I0e4J5Ti8HntCXPwdeFUIIKRvLWahQ1B+92iTQq00Clw9ypUj96REtsdXpsioGPvUdAKO6tOCXg1oil1bxkeSXVNQ8WBNBNMSzKIS4GpgqpbxVX78RGCmlvMetz+3A7QCpqalDP/74Y7/HLC0tJS4uuLnZtWVtnpVt+TaSowRJkYLkKO0vPcGAoZ5yrjek/A2Nkj10VJd/4sSJG6WUw+rj2EE+pzv0Ptn6+gG9T0G1YzWZ57muKJmCo7nIZLNLMovs9GxhpLRS8mVmJTf0Mju37z9lJ8EsMBrgdIUkJVrw/RErJ8rs/GZQJGVnzgSUqV6eZyllvf8B16CNrznWbwT+6av/0KFDZSBWrFgRsE9TJpzlV7KHjuryAxtkIz6nwE6gvdv6AaClv+OG6/OsZAoOJVNwBCNTfTzPDRWdng241wdtD+Q20LkUCkXdCOY5dfYRQpiARKAQhULRJGgoJb4e6C6E6CyEMAPXA4sC7KNQKBqXYJ7TRcAsfflq4AfdglAoFE2ABglsk1JahRD3AN+iTV15R0q5syHOpVAo6oav51QI8RSam28R8DbwvhAiE80Cvz50EisUiuo02DxxKeXXwNcNdXyFQnH2eHtOpZSPuy1b0MbOFQpFE6TpZ2xTKBQKhULhFaXEFQqFQqEIU5QSVygUCoUiTFFKXKFQKBSKMKVBMrbVWggh8oHDAbqlAAUB+jRlwll+JXvoqC5/Jyllq1AJEwxh/DwrmYJDyRQcwch01s9zk1DiwSCE2CDrKd1kKAhn+ZXsoSPc5fdFU/xcSqbgUDIFR2PJpNzpCoVCoVCEKUqJKxQKhUIRpoSTEn8z1AKcJeEsv5I9dIS7/L5oip9LyRQcSqbgaBSZwmZMXKFQKBQKhSfhZIkrFAqFQqFwQylxhUKhUCjClLBQ4kKIqUKIvUKITCHEw6GWJxBCiHeEECeEEDvc2loIIZYJIfbr/5NDKaM3hBAdhBArhBC7hRA7hRD36+1NXnYAIUSUEGKdEGKrLv+TentnIcRaXf5P9LKbTRIhhFEIsVkIsVhfDxvZg6Ghn+Xa3sNC4xVdnm1CiCFux5ql998vhJjl1j5UCLFd3+cVIYQIUragvlshRKS+nqlvT3c7xiN6+14hxEVu7bW+rkKIJCHE50KIPfr1Gh3q6ySE+J3+ve0QQnykP9ONfp1ELX7D6/Pa+DqHX6SUTfoPrUTiAaALYAa2An1CLVcAmc8DhgA73NpeAB7Wlx8Gng+1nF7kTgOG6MvxwD6gTzjIrssmgDh9OQJYC4wCPgWu19tfB+4Ktax+PsMDwIfAYn09bGQP4rM1+LNc23sYuBj4Rr93RgFr9fYWwEH9f7K+nKxvWweM1vf5BphWn98tcDfwur58PfCJvtxHv2aRQGf9Whrrel2Bd4Fb9WUzkBTK6wS0Aw4B0W7XZ3YorhO1+A2vz2vj6xx+r1uoH+wgbrTRwLdu648Aj4RariDkTq92A+wF0vTlNGBvqGUM4jMsBCaHqewxwCZgJFrWJJO3+6kp/QHtgeXAJGCx/oCHhexBfr5Gf5YD3cPAG8ANbv336ttvAN5wa39Db0sD9ri1e/Srj+8Wrb77aH3ZpPcT1a+Xo19driuQgKYwRbX2kF0nNCV+FE3pmfTrdFGorhNB/obX57XxdQ5/f+HgTnd8sQ6y9bZwI1VKmQeg/28dYnn8orumBqNZs2Eju+6y3AKcAJahvXkXSSmtepemfP/8HfgjYNfXWxI+sgdDoz7LQd7DvmTy157tpT0QtflunefWt5/W+9dWVn90AfKBebqL/z9CiFhCeJ2klDnAS8ARIA/tc28ktNfJnca4NrX+rQ0HJe5tHEXNi2tAhBBxwALgt1LK4lDLUxuklDYp5SA0y2cE0Ntbt8aVKjBCiEuAE1LKje7NXro2OdlrQaN9nlrcw75kqm27P1lq+902uExolusQ4N9SysHAGTT3rS8a4zolA5ejucDbArHAND/HaYzrFAwhlSMclHg20MFtvT2QGyJZzobjQog0AP3/iRDL4xUhRATaj98HUsov9OawkN0dKWURkIE2RpUkhDDpm5rq/TMWuEwIkQV8jOZ2/TvhIXuwNMqzXMt72JdM/trb1/Iz1Pa7dZ5b354IFNZBVn9kA9lSyrX6+udoSj2U1+lC4JCUMl9KWQV8AYwhtNfJnca4NrX+rQ0HJb4e6K5HKJrRAhgWhVimurAIcEQnzkIbq2tS6BGSbwO7pZR/ddvU5GUHEEK0EkIk6cvRaD8Ku4EVwNV6tyYpv5TyESlleyllOto9/oOU8teEgey1oMGf5Trcw4uAm/QI41HAad2N+S0wRQiRrFuIU9DGU/OAEiHEKP1cNxHgO6nDd+su69V6f6m3X69HZXcGuqMFSNX6ukopjwFHhRA99aYLgF2hvE5obvRRQogYfR+HTCG7TtVojGtT+9/aQIPmTeEPLfpvH9r45qOhlicIeT9CG9OpQnvrugVtrGY5sF//3yLUcnqRexyaW2cbsEX/uzgcZNflHwBs1uXfATyut3dBe4gzgc+AyFDLGuBzTMAVwRxWsgfx2Rr0Wa7tPYzm2nxNl2c7MMztWDfr1z0TmOPWPky/vw4Ar1ItOOxsv1sgSl/P1Ld3cdv/Uf28e3GL9q7LdQUGARv0a/U/tAjqkF4n4Elgj77f+2gR5o1+najFb3h9Xhtf5/D3p9KuKhQKhUIRpoSDO12hUCgUCoUXlBJXKBQKhSJMUUpcoVAoFIowRSlxhUKhUCjCFKXEFQqFQqEIU5QSVygUCoUiTFFKXKFQKBSKMOX/ARR778X1xDbUAAAAAElFTkSuQmCC\n", + "text/plain": [ + "
" + ] + }, + "metadata": {}, + "output_type": "display_data" + }, + { + "name": "stderr", + "output_type": "stream", + "text": [ + "100%|██████████████████████████████████████████████████████████████████████████| 100000/100000 [35:42<00:00, 46.67it/s]\n" + ] + } + ], + "source": [ + "for i in trange(100000): \n", + " \n", + " batch_actions = agent.sample_actions(agent.step(batch_states))\n", + " batch_next_states, batch_rewards, batch_done, _ = env_batch.step(batch_actions)\n", + " \n", + " feed_dict = {\n", + " states_ph: batch_states,\n", + " actions_ph: batch_actions,\n", + " next_states_ph: batch_next_states,\n", + " rewards_ph: batch_rewards,\n", + " is_done_ph: batch_done,\n", + " }\n", + " batch_states = batch_next_states\n", + " \n", + " _, ent_t = sess.run([train_step, entropy], feed_dict)\n", + " entropy_history.append(np.mean(ent_t))\n", + "\n", + " if i % 500 == 0: \n", + " if i % 2500 == 0:\n", + " rewards_history.append(np.mean(evaluate(agent, env, n_games=3)))\n", + " if rewards_history[-1] >= 50:\n", + " print(\"Your agent has earned the yellow belt\")\n", + "\n", + " clear_output(True)\n", + " plt.figure(figsize=[8,4])\n", + " plt.subplot(1,2,1)\n", + " plt.plot(rewards_history, label='rewards')\n", + " plt.plot(ewma(np.array(rewards_history),span=10), marker='.', label='rewards ewma@10')\n", + " plt.title(\"Session rewards\"); plt.grid(); plt.legend()\n", + " \n", + " plt.subplot(1,2,2)\n", + " plt.plot(entropy_history, label='entropy')\n", + " plt.plot(ewma(np.array(entropy_history),span=1000), label='entropy ewma@1000')\n", + " plt.title(\"Policy entropy\"); plt.grid(); plt.legend() \n", + " plt.show()\n", + " \n", + " \n", + "\n" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "Relax and grab some refreshments while your agent is locked in an infinite loop of violence and death.\n", + "\n", + "__How to interpret plots:__\n", + "\n", + "The session reward is the easy thing: it should in general go up over time, but it's okay if it fluctuates ~~like crazy~~. It's also OK if it reward doesn't increase substantially before some 10k initial steps. However, if reward reaches zero and doesn't seem to get up over 2-3 evaluations, there's something wrong happening.\n", + "\n", + "\n", + "Since we use a policy-based method, we also keep track of __policy entropy__ - the same one you used as a regularizer. The only important thing about it is that your entropy shouldn't drop too low (`< 0.1`) before your agent gets the yellow belt. Or at least it can drop there, but _it shouldn't stay there for long_.\n", + "\n", + "If it does, the culprit is likely:\n", + "* Some bug in entropy computation. Remember that it is $ - \\sum p(a_i) \\cdot log p(a_i) $\n", + "* Your agent architecture converges too fast. Increase entropy coefficient in actor loss. \n", + "* Gradient explosion - just [clip gradients](https://stackoverflow.com/a/43486487) and maybe use a smaller network\n", + "* Us. Or TF developers. Or aliens. Or lizardfolk. Contact us on forums before it's too late!\n", + "\n", + "If you're debugging, just run `logits, values = agent.step(batch_states)` and manually look into logits and values. This will reveal the problem 9 times out of 10: you'll likely see some NaNs or insanely large numbers or zeros. Try to catch the moment when this happens for the first time and investigate from there." + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "### \"Final\" evaluation" + ] + }, + { + "cell_type": "code", + "execution_count": 19, + "metadata": {}, + "outputs": [ + { + "name": "stdout", + "output_type": "stream", + "text": [ + "Final mean reward: 227.0\n" + ] + } + ], + "source": [ + "env_monitor = gym.wrappers.Monitor(env, directory=\"kungfu_videos\", force=True)\n", + "final_rewards = evaluate(agent, env_monitor, n_games=20,)\n", + "env_monitor.close()\n", + "print(\"Final mean reward:\", np.mean(final_rewards))\n", + "\n", + "video_names = list(filter(lambda s:s.endswith(\".mp4\"),os.listdir(\"./kungfu_videos/\")))" + ] + }, + { + "cell_type": "code", + "execution_count": 20, + "metadata": {}, + "outputs": [ + { + "data": { + "text/html": [ + "\n", + "\n" + ], + "text/plain": [ + "" + ] + }, + "execution_count": 20, + "metadata": {}, + "output_type": "execute_result" + } + ], + "source": [ + "HTML(\"\"\"\n", + "\n", + "\"\"\".format(\"./kungfu_videos/\"+video_names[-1])) " + ] + }, + { + "cell_type": "code", + "execution_count": 21, + "metadata": {}, + "outputs": [ + { + "data": { + "text/html": [ + "\n", + "\n" + ], + "text/plain": [ + "" + ] + }, + "execution_count": 21, + "metadata": {}, + "output_type": "execute_result" + } + ], + "source": [ + "HTML(\"\"\"\n", + "\n", + "\"\"\".format(\"./kungfu_videos/\"+video_names[-2])) #try other indices " + ] + }, + { + "cell_type": "code", + "execution_count": 22, + "metadata": {}, + "outputs": [], + "source": [ + "# if you don't see videos, just navigate to ./kungfu_videos and download .mp4 files from there." + ] + }, + { + "cell_type": "code", + "execution_count": 23, + "metadata": {}, + "outputs": [ + { + "name": "stdout", + "output_type": "stream", + "text": [ + "WARN: gym.spaces.Box autodetected dtype as . Please provide explicit dtype.\n", + "Submitted to Coursera platform. See results on assignment page!\n" + ] + } + ], + "source": [ + "from submit import submit_kungfu\n", + "env = make_env()\n", + "submit_kungfu(agent, env, evaluate, '', '')" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "```\n", + "\n", + "```\n", + "```\n", + "\n", + "```\n", + "```\n", + "\n", + "```\n", + "```\n", + "\n", + "```\n", + "```\n", + "\n", + "```\n", + "```\n", + "\n", + "```\n", + "```\n", + "\n", + "```\n", + "```\n", + "\n", + "```\n" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "### Now what?\n", + "Well, 5k reward is [just the beginning](https://www.buzzfeed.com/mattjayyoung/what-the-color-of-your-karate-belt-actually-means-lg3g). Can you get past 200? With recurrent neural network memory, chances are you can even beat 400!\n", + "\n", + "* Try n-step advantage and \"lambda\"-advantage (aka GAE) - see [this article](https://arxiv.org/abs/1506.02438)\n", + " * This change should improve early convergence a lot\n", + "* Try recurrent neural network \n", + " * RNN memory will slow things down initially, but in will reach better final reward at this game\n", + "* Implement asynchronuous version\n", + " * Remember [A3C](https://arxiv.org/abs/1602.01783)? The first \"A\" stands for asynchronuous. It means there are several parallel actor-learners out there.\n", + " * You can write custom code for synchronization, but we recommend using [redis](https://redis.io/)\n", + " * You can store full parameter set in redis, along with any other metadate\n", + " * Here's a _quick_ way to (de)serialize parameters for redis\n", + " ```\n", + " import joblib\n", + " from six import BytesIO\n", + "```\n", + "```\n", + " def dumps(data):\n", + " \"converts whatever to string\"\n", + " s = BytesIO()\n", + " joblib.dump(data,s)\n", + " return s.getvalue()\n", + "``` \n", + "```\n", + " def loads(string):\n", + " \"converts string to whatever was dumps'ed in it\"\n", + " return joblib.load(BytesIO(string))\n", + "```" + ] + } + ], + "metadata": { + "anaconda-cloud": {}, + "kernelspec": { + "display_name": "Python 3", + "language": "python", + "name": "python3" + }, + "language_info": { + "codemirror_mode": { + "name": "ipython", + "version": 3 + }, + "file_extension": ".py", + "mimetype": "text/x-python", + "name": "python", + "nbconvert_exporter": "python", + "pygments_lexer": "ipython3", + "version": "3.6.5" + } + }, + "nbformat": 4, + "nbformat_minor": 1 +} diff --git a/Practical Reinforcement Learning/Week5_policy_based/practice_reinforce.ipynb b/Practical Reinforcement Learning/Week5_policy_based/practice_reinforce.ipynb new file mode 100644 index 0000000..d72036c --- /dev/null +++ b/Practical Reinforcement Learning/Week5_policy_based/practice_reinforce.ipynb @@ -0,0 +1,450 @@ +{ + "cells": [ + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "# REINFORCE in TensorFlow\n", + "\n", + "This notebook implements a basic reinforce algorithm a.k.a. policy gradient for CartPole env.\n", + "\n", + "It has been deliberately written to be as simple and human-readable.\n" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "The notebook assumes that you have [openai gym](https://github.com/openai/gym) installed.\n", + "\n", + "In case you're running on a server, [use xvfb](https://github.com/openai/gym#rendering-on-a-server)" + ] + }, + { + "cell_type": "code", + "execution_count": 1, + "metadata": {}, + "outputs": [ + { + "data": { + "image/png": "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\n", + "text/plain": [ + "
" + ] + }, + "metadata": {}, + "output_type": "display_data" + } + ], + "source": [ + "import gym\n", + "import numpy as np, pandas as pd\n", + "import matplotlib.pyplot as plt\n", + "%matplotlib inline\n", + "\n", + "env = gym.make(\"CartPole-v0\")\n", + "\n", + "#gym compatibility: unwrap TimeLimit\n", + "if hasattr(env,'env'):\n", + " env=env.env\n", + "\n", + "env.reset()\n", + "n_actions = env.action_space.n\n", + "state_dim = env.observation_space.shape\n", + "\n", + "plt.imshow(env.render(\"rgb_array\"))\n", + "env.close()" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "# Building the policy network" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "For REINFORCE algorithm, we'll need a model that predicts action probabilities given states.\n", + "\n", + "For numerical stability, please __do not include the softmax layer into your network architecture__. \n", + "\n", + "We'll use softmax or log-softmax where appropriate." + ] + }, + { + "cell_type": "code", + "execution_count": 2, + "metadata": {}, + "outputs": [], + "source": [ + "import tensorflow as tf\n", + "\n", + "#create input variables. We only need for REINFORCE\n", + "states = tf.placeholder('float32',(None,)+state_dim,name=\"states\")\n", + "actions = tf.placeholder('int32',name=\"action_ids\")\n", + "cumulative_rewards = tf.placeholder('float32', name=\"cumulative_returns\")" + ] + }, + { + "cell_type": "code", + "execution_count": 3, + "metadata": {}, + "outputs": [ + { + "name": "stderr", + "output_type": "stream", + "text": [ + "Using TensorFlow backend.\n" + ] + } + ], + "source": [ + "import keras\n", + "from keras.layers import Dense\n", + "\n", + "model = keras.models.Sequential()\n", + "model.add(Dense(128, activation='relu', input_shape=state_dim))\n", + "model.add(Dense(64, activation='relu'))\n", + "model.add(Dense(n_actions, activation='linear'))\n", + "\n", + "logits = model(states)\n", + "\n", + "policy = tf.nn.softmax(logits)\n", + "log_policy = tf.nn.log_softmax(logits)" + ] + }, + { + "cell_type": "code", + "execution_count": 4, + "metadata": {}, + "outputs": [], + "source": [ + "#utility function to pick action in one given state\n", + "get_action_proba = lambda s: policy.eval({states:[s]})[0] " + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "#### Loss function and updates\n", + "\n", + "We now need to define objective and update over policy gradient.\n", + "\n", + "Our objective function is\n", + "\n", + "$$ J \\approx { 1 \\over N } \\sum _{s_i,a_i} \\pi_\\theta (a_i | s_i) \\cdot G(s_i,a_i) $$\n", + "\n", + "\n", + "Following the REINFORCE algorithm, we can define our objective as follows: \n", + "\n", + "$$ \\hat J \\approx { 1 \\over N } \\sum _{s_i,a_i} log \\pi_\\theta (a_i | s_i) \\cdot G(s_i,a_i) $$\n", + "\n", + "When you compute gradient of that function over network weights $ \\theta $, it will become exactly the policy gradient.\n" + ] + }, + { + "cell_type": "code", + "execution_count": 5, + "metadata": {}, + "outputs": [], + "source": [ + "#get probabilities for parti\n", + "indices = tf.stack([tf.range(tf.shape(log_policy)[0]),actions],axis=-1)\n", + "log_policy_for_actions = tf.gather_nd(log_policy,indices)" + ] + }, + { + "cell_type": "code", + "execution_count": 6, + "metadata": {}, + "outputs": [], + "source": [ + "# policy objective as in the last formula. please use mean, not sum.\n", + "# note: you need to use log_policy_for_actions to get log probabilities for actions taken\n", + "\n", + "J = tf.reduce_mean(log_policy_for_actions*cumulative_rewards)\n" + ] + }, + { + "cell_type": "code", + "execution_count": 7, + "metadata": {}, + "outputs": [], + "source": [ + "#regularize with entropy\n", + "entropy = -tf.reduce_sum(policy*log_policy, axis=1)" + ] + }, + { + "cell_type": "code", + "execution_count": 8, + "metadata": {}, + "outputs": [], + "source": [ + "#all network weights\n", + "all_weights = tf.get_collection(tf.GraphKeys.TRAINABLE_VARIABLES)\n", + "\n", + "#weight updates. maximizing J is same as minimizing -J. Adding negative entropy.\n", + "loss = -J -0.1 * entropy\n", + "\n", + "update = tf.train.AdamOptimizer().minimize(loss,var_list=all_weights)" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "### Computing cumulative rewards" + ] + }, + { + "cell_type": "code", + "execution_count": 9, + "metadata": {}, + "outputs": [], + "source": [ + "def get_cumulative_rewards(rewards, #rewards at each step\n", + " gamma = 0.99 #discount for reward\n", + " ):\n", + " \"\"\"\n", + " take a list of immediate rewards r(s,a) for the whole session \n", + " compute cumulative rewards R(s,a) (a.k.a. G(s,a) in Sutton '16)\n", + " R_t = r_t + gamma*r_{t+1} + gamma^2*r_{t+2} + ...\n", + " \n", + " The simple way to compute cumulative rewards is to iterate from last to first time tick\n", + " and compute R_t = r_t + gamma*R_{t+1} recurrently\n", + " \n", + " You must return an array/list of cumulative rewards with as many elements as in the initial rewards.\n", + " \"\"\"\n", + " \n", + " cumulative_rewards = np.array(rewards).astype(np.float32)\n", + " for i in range(len(rewards)-2, -1, -1):\n", + " cumulative_rewards[i] += gamma*cumulative_rewards[i+1]\n", + " \n", + " return cumulative_rewards\n", + " \n", + " " + ] + }, + { + "cell_type": "code", + "execution_count": 10, + "metadata": {}, + "outputs": [ + { + "name": "stdout", + "output_type": "stream", + "text": [ + "looks good!\n" + ] + } + ], + "source": [ + "assert len(get_cumulative_rewards(range(100))) == 100\n", + "assert np.allclose(get_cumulative_rewards([0,0,1,0,0,1,0],gamma=0.9),[1.40049, 1.5561, 1.729, 0.81, 0.9, 1.0, 0.0])\n", + "assert np.allclose(get_cumulative_rewards([0,0,1,-2,3,-4,0],gamma=0.5), [0.0625, 0.125, 0.25, -1.5, 1.0, -4.0, 0.0])\n", + "assert np.allclose(get_cumulative_rewards([0,0,1,2,3,4,0],gamma=0), [0, 0, 1, 2, 3, 4, 0])\n", + "print(\"looks good!\")" + ] + }, + { + "cell_type": "code", + "execution_count": 11, + "metadata": {}, + "outputs": [], + "source": [ + "def train_step(_states,_actions,_rewards):\n", + " \"\"\"given full session, trains agent with policy gradient\"\"\"\n", + " _cumulative_rewards = get_cumulative_rewards(_rewards)\n", + " update.run({states:_states,actions:_actions,cumulative_rewards:_cumulative_rewards})" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "### Playing the game" + ] + }, + { + "cell_type": "code", + "execution_count": 12, + "metadata": {}, + "outputs": [], + "source": [ + "def generate_session(t_max=1000):\n", + " \"\"\"play env with REINFORCE agent and train at the session end\"\"\"\n", + " \n", + " #arrays to record session\n", + " states,actions,rewards = [],[],[]\n", + " \n", + " s = env.reset()\n", + " \n", + " for t in range(t_max):\n", + " \n", + " #action probabilities array aka pi(a|s)\n", + " action_probas = get_action_proba(s)\n", + " \n", + " a = np.random.choice(n_actions, p=action_probas)\n", + " \n", + " new_s,r,done,info = env.step(a)\n", + " \n", + " #record session history to train later\n", + " states.append(s)\n", + " actions.append(a)\n", + " rewards.append(r)\n", + " \n", + " s = new_s\n", + " if done: break\n", + " \n", + " train_step(states,actions,rewards)\n", + " \n", + " return sum(rewards)\n", + " " + ] + }, + { + "cell_type": "code", + "execution_count": 13, + "metadata": {}, + "outputs": [ + { + "name": "stdout", + "output_type": "stream", + "text": [ + "mean reward:60.590\n", + "mean reward:160.860\n", + "mean reward:165.670\n", + "mean reward:451.360\n", + "You Win!\n" + ] + } + ], + "source": [ + "s = tf.InteractiveSession()\n", + "s.run(tf.global_variables_initializer())\n", + "\n", + "for i in range(100):\n", + " \n", + " rewards = [generate_session() for _ in range(100)] #generate new sessions\n", + " \n", + " print (\"mean reward:%.3f\"%(np.mean(rewards)))\n", + "\n", + " if np.mean(rewards) > 300:\n", + " print (\"You Win!\")\n", + " break\n", + " \n" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "### Results & video" + ] + }, + { + "cell_type": "code", + "execution_count": 14, + "metadata": {}, + "outputs": [], + "source": [ + "#record sessions\n", + "import gym.wrappers\n", + "env = gym.wrappers.Monitor(gym.make(\"CartPole-v0\"),directory=\"videos\",force=True)\n", + "sessions = [generate_session() for _ in range(100)]\n", + "env.env.close()\n" + ] + }, + { + "cell_type": "code", + "execution_count": 15, + "metadata": {}, + "outputs": [ + { + "data": { + "text/html": [ + "\n", + "\n" + ], + "text/plain": [ + "" + ] + }, + "execution_count": 15, + "metadata": {}, + "output_type": "execute_result" + } + ], + "source": [ + "#show video\n", + "from IPython.display import HTML\n", + "import os\n", + "\n", + "video_names = list(filter(lambda s:s.endswith(\".mp4\"),os.listdir(\"./videos/\")))\n", + "\n", + "HTML(\"\"\"\n", + "\n", + "\"\"\".format(\"./videos/\"+video_names[-1])) #this may or may not be _last_ video. Try other indices" + ] + }, + { + "cell_type": "code", + "execution_count": 16, + "metadata": {}, + "outputs": [ + { + "name": "stdout", + "output_type": "stream", + "text": [ + "Submitted to Coursera platform. See results on assignment page!\n" + ] + } + ], + "source": [ + "from submit import submit_cartpole\n", + "submit_cartpole(generate_session, '', '')" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "metadata": {}, + "outputs": [], + "source": [ + "# That's all, thank you for your attention!\n", + "# Not having enough? There's an actor-critic waiting for you in the honor section.\n", + "# But make sure you've seen the videos first." + ] + } + ], + "metadata": { + "kernelspec": { + "display_name": "Python 3", + "language": "python", + "name": "python3" + }, + "language_info": { + "codemirror_mode": { + "name": "ipython", + "version": 3 + }, + "file_extension": ".py", + "mimetype": "text/x-python", + "name": "python", + "nbconvert_exporter": "python", + "pygments_lexer": "ipython3", + "version": "3.6.5" + } + }, + "nbformat": 4, + "nbformat_minor": 1 +} diff --git a/Practical Reinforcement Learning/Week5_policy_based/submit.py b/Practical Reinforcement Learning/Week5_policy_based/submit.py new file mode 100644 index 0000000..553e552 --- /dev/null +++ b/Practical Reinforcement Learning/Week5_policy_based/submit.py @@ -0,0 +1,20 @@ +import sys +import numpy as np +sys.path.append("..") +import grading + + +def submit_cartpole(generate_session, email, token): + sessions = [generate_session() for _ in range(100)] + session_rewards = np.array(sessions) + grader = grading.Grader("oyT3Bt7yEeeQvhJmhysb5g") + grader.set_answer("7QKmA", int(np.mean(session_rewards))) + grader.submit(email, token) + + +def submit_kungfu(agent, env, evaluate, email, token): + sessions = [evaluate(agent=agent, env=env, n_games=1) for _ in range(100)] + session_rewards = np.array(sessions) + grader = grading.Grader("6sPnVCn6EeieSRL7rCBNJA") + grader.set_answer("HhNVX", 100*int(np.mean(session_rewards))) + grader.submit(email, token)