From 074bdc309f0c422ab566e1df9a37bfa38fd3124f Mon Sep 17 00:00:00 2001 From: Xu Senbo <1170676717@qq.com> Date: Sun, 19 Nov 2023 21:28:00 +0800 Subject: [PATCH 1/9] I have run all the code in the local --- .../deep-learning/autoencoder.ipynb | 582 ++++++++ .../deep-learning/autoencoder.md | 316 ----- .../deep-learning/cnn.ipynb | 1216 +++++++++++++++++ .../deep-learning/cnn.md | 1205 ---------------- 4 files changed, 1798 insertions(+), 1521 deletions(-) create mode 100644 open-machine-learning-jupyter-book/deep-learning/autoencoder.ipynb delete mode 100644 open-machine-learning-jupyter-book/deep-learning/autoencoder.md create mode 100644 open-machine-learning-jupyter-book/deep-learning/cnn.ipynb delete mode 100644 open-machine-learning-jupyter-book/deep-learning/cnn.md diff --git a/open-machine-learning-jupyter-book/deep-learning/autoencoder.ipynb b/open-machine-learning-jupyter-book/deep-learning/autoencoder.ipynb new file mode 100644 index 0000000000..d8facd45fa --- /dev/null +++ b/open-machine-learning-jupyter-book/deep-learning/autoencoder.ipynb @@ -0,0 +1,582 @@ +{ + "cells": [ + { + "cell_type": "code", + "execution_count": 1, + "metadata": { + "tags": [ + "hide-cell" + ] + }, + "outputs": [], + "source": [ + "# Install the necessary dependencies\n", + "\n", + "import os\n", + "import sys \n", + "!{sys.executable} -m pip install --quiet pandas scikit-learn numpy matplotlib jupyterlab_myst ipython" + ] + }, + { + "cell_type": "markdown", + "metadata": { + "tags": [ + "remove-cell" + ] + }, + "source": [ + "---\n", + "license:\n", + " code: MIT\n", + " content: CC-BY-4.0\n", + "github: https://github.com/ocademy-ai/machine-learning\n", + "venue: By Ocademy\n", + "open_access: true\n", + "bibliography:\n", + " - https://raw.githubusercontent.com/ocademy-ai/machine-learning/main/open-machine-learning-jupyter-book/references.bib\n", + "---" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "# Autoencoder\n", + "\n", + "## Overview\n", + "\n", + "An autoencoder is a type of artificial neural network used to learn efficient codings of unlabeled data (unsupervised learning). An autoencoder learns two functions: an encoding function that transforms the input data, and a decoding function that recreates the input data from the encoded representation. The autoencoder learns an efficient representation (encoding) for a set of data, typically for dimensionality reduction.\n", + "\n", + "## Unsupervised Learning\n", + "\n", + "Autoencoder is a kind of unsupervised learning, which means working with datasets without considering a target variable. There are some Applications and Goals for it:\n", + "\n", + "- Finding hidden structures in data.\n", + "- Data compression.\n", + "- Clustering.\n", + "- Retrieving similar objects.\n", + "- Exploratory data analysis.\n", + "- Generating new examples.\n", + "\n", + "And for unsupervised learning, its main Principal Component Analysis (PCA) is:\n", + "\n", + "- Find directions of maximum variance\n", + "\n", + ":::{figure} https://static-1300131294.cos.ap-shanghai.myqcloud.com/images/deep-learning/autoencoder/01_PCA1.png\n", + "---\n", + "name: Illustration of PCA\n", + ":::\n", + "\n", + "- Transform features onto directions of maximum variance\n", + "\n", + ":::{figure} https://static-1300131294.cos.ap-shanghai.myqcloud.com/images/deep-learning/autoencoder/02_PCA2.png\n", + "---\n", + "name: Illustration of PCA\n", + ":::\n", + "\n", + "- Usually consider a subset of vectors of most variance (dimensionality reduction)\n", + "\n", + ":::{figure} https://static-1300131294.cos.ap-shanghai.myqcloud.com/images/deep-learning/autoencoder/03_PCA3.png\n", + "---\n", + "name: Illustration of PCA\n", + ":::" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "## Fully-connected Autoencoder\n", + "\n", + "Here is an example of a basic fully-connected autoencoder\n", + "\n", + ":::{figure} https://static-1300131294.cos.ap-shanghai.myqcloud.com/images/deep-learning/autoencoder/04_simple.png\n", + "---\n", + "name: Illustration of Fully Connected autoencoder\n", + ":::\n", + "\n", + ":::{note}\n", + "If we don't use non-linear activation functions and minimize the MSE, this is very similar to PCA. However, the latent dimensions will not necessarily be orthogonal and will have same variance.\n", + ":::\n", + "\n", + "The loss function of this simple model is \n", + "$$L(x, x') = \\left\\lVert x - x' \\right\\rVert^2_2 = \\sum_i (x_i - x_i')^2$$\n", + "\n", + "\n", + "### Potential Autoencoder Applications\n", + "\n", + "And there are some potential autoencoder applications, for example:\n", + "- After training, disregard the output part, we can use embedding as input to classic machine learning methods (SVM, KNN, Random Forest, ...).\n", + "- Similar to transfer learning, we can train autoencoder on large image dataset, then fine tune encoder part on your own, smaller dataset and/or provide your own output (classification) layer.\n", + "- Latent space can also be used for visualization (EDA, clustering), but there are better methods for that.\n", + "\n", + "## Convolutional Autoencoder\n", + "\n", + "For convolutional autoencoder, we mainly use transposed convolution construct the output, and transposed convolution (sometimes called \"deconvolution\") allows us to increase the size of the output feature map compared to the input feature map.\n", + "\n", + "The difference between regular convolution and transposed convolution can be seen from the following image.\n", + "\n", + ":::{figure} https://static-1300131294.cos.ap-shanghai.myqcloud.com/images/deep-learning/autoencoder/05_diff_conv.png\n", + "---\n", + "name: Difference between regular and transposed convolution\n", + ":::\n", + "\n", + "In transposed convolutions, we stride over the output; hence, larger strides will result in larger outputs (opposite to regular convolutions); and we pad the output; hence, larger padding will result in smaller output maps.\n", + "\n", + "So, the whole model consists of two parts, encoder and decoder, and they are composed with regular convolution and transposed convolution respectively.\n", + "\n", + ":::{figure} https://static-1300131294.cos.ap-shanghai.myqcloud.com/images/deep-learning/autoencoder/06_convmodel.png\n", + "---\n", + "name: Structure of convoluted autoencoder\n", + ":::\n", + "\n", + ":::{note}\n", + "Here is some other tricks to help our training:\n", + "1. Add dropout layers to force networks to learn redundant features.\n", + "2. Add dropout after the input, or add noise to the input to learn to denoise images.\n", + "3. Add L1 penalty to the loss to learn sparse feature representations.\n", + ":::\n", + "\n", + "## Code\n", + "\n", + "Let's build a 2-layers auto-encoder with TensorFlow to compress images to a lower latent space and then reconstruct them. And this project will be done on MNIST dataste." + ] + }, + { + "cell_type": "code", + "execution_count": 2, + "metadata": {}, + "outputs": [], + "source": [ + "import tensorflow as tf\n", + "import numpy as np" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "MNIST Dataset parameters." + ] + }, + { + "cell_type": "code", + "execution_count": 3, + "metadata": {}, + "outputs": [], + "source": [ + "num_features = 784 # data features (img shape: 28*28)." + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "Training parameters." + ] + }, + { + "cell_type": "code", + "execution_count": 4, + "metadata": {}, + "outputs": [], + "source": [ + "learning_rate = 0.01\n", + "training_steps = 20000\n", + "batch_size = 256\n", + "display_step = 1000" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "Network Parameters" + ] + }, + { + "cell_type": "code", + "execution_count": 5, + "metadata": {}, + "outputs": [], + "source": [ + "num_hidden_1 = 128 # 1st layer num features.\n", + "num_hidden_2 = 64 # 2nd layer num features (the latent dim)." + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "Prepare MNIST data." + ] + }, + { + "cell_type": "code", + "execution_count": 6, + "metadata": {}, + "outputs": [], + "source": [ + "from tensorflow.keras.datasets import mnist\n", + "(x_train, y_train), (x_test, y_test) = mnist.load_data()\n", + "# Convert to float32.\n", + "x_train, x_test = x_train.astype(np.float32), x_test.astype(np.float32)\n", + "# Flatten images to 1-D vector of 784 features (28*28).\n", + "x_train, x_test = x_train.reshape([-1, num_features]), x_test.reshape([-1, num_features])\n", + "# Normalize images value from [0, 255] to [0, 1].\n", + "x_train, x_test = x_train / 255., x_test / 255." + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "Use tf.data API to shuffle and batch data." + ] + }, + { + "cell_type": "code", + "execution_count": 7, + "metadata": {}, + "outputs": [], + "source": [ + "train_data = tf.data.Dataset.from_tensor_slices((x_train, y_train))\n", + "train_data = train_data.repeat().shuffle(10000).batch(batch_size).prefetch(1)\n", + "\n", + "test_data = tf.data.Dataset.from_tensor_slices((x_test, y_test))\n", + "test_data = test_data.repeat().batch(batch_size).prefetch(1)" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "Store layers weight & bias.\n", + "A random value generator to initialize weights." + ] + }, + { + "cell_type": "code", + "execution_count": 8, + "metadata": {}, + "outputs": [ + { + "name": "stderr", + "output_type": "stream", + "text": [ + "C:\\Users\\Victor\\anaconda3\\lib\\site-packages\\keras\\initializers\\initializers.py:120: UserWarning: The initializer RandomNormal is unseeded and being called multiple times, which will return identical values each time (even if the initializer is unseeded). Please update your code to provide a seed to the initializer, or avoid using the same initalizer instance more than once.\n", + " warnings.warn(\n" + ] + } + ], + "source": [ + "random_normal = tf.initializers.RandomNormal()\n", + "\n", + "weights = {\n", + " 'encoder_h1': tf.Variable(random_normal([num_features, num_hidden_1])),\n", + " 'encoder_h2': tf.Variable(random_normal([num_hidden_1, num_hidden_2])),\n", + " 'decoder_h1': tf.Variable(random_normal([num_hidden_2, num_hidden_1])),\n", + " 'decoder_h2': tf.Variable(random_normal([num_hidden_1, num_features])),\n", + "}\n", + "biases = {\n", + " 'encoder_b1': tf.Variable(random_normal([num_hidden_1])),\n", + " 'encoder_b2': tf.Variable(random_normal([num_hidden_2])),\n", + " 'decoder_b1': tf.Variable(random_normal([num_hidden_1])),\n", + " 'decoder_b2': tf.Variable(random_normal([num_features])),\n", + "}" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "Building the encoder." + ] + }, + { + "cell_type": "code", + "execution_count": 9, + "metadata": {}, + "outputs": [], + "source": [ + "def encoder(x):\n", + " # Encoder Hidden layer with sigmoid activation.\n", + " layer_1 = tf.nn.sigmoid(tf.add(tf.matmul(x, weights['encoder_h1']),\n", + " biases['encoder_b1']))\n", + " # Encoder Hidden layer with sigmoid activation.\n", + " layer_2 = tf.nn.sigmoid(tf.add(tf.matmul(layer_1, weights['encoder_h2']),\n", + " biases['encoder_b2']))\n", + " return layer_2" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "Building the decoder." + ] + }, + { + "cell_type": "code", + "execution_count": 10, + "metadata": {}, + "outputs": [], + "source": [ + "def decoder(x):\n", + " # Decoder Hidden layer with sigmoid activation.\n", + " layer_1 = tf.nn.sigmoid(tf.add(tf.matmul(x, weights['decoder_h1']),\n", + " biases['decoder_b1']))\n", + " # Decoder Hidden layer with sigmoid activation.\n", + " layer_2 = tf.nn.sigmoid(tf.add(tf.matmul(layer_1, weights['decoder_h2']),\n", + " biases['decoder_b2']))\n", + " return layer_2" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "Mean square loss between original images and reconstructed ones." + ] + }, + { + "cell_type": "code", + "execution_count": 11, + "metadata": {}, + "outputs": [], + "source": [ + "def mean_square(reconstructed, original):\n", + " return tf.reduce_mean(tf.pow(original - reconstructed, 2))" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "Adam optimizer." + ] + }, + { + "cell_type": "code", + "execution_count": 12, + "metadata": {}, + "outputs": [], + "source": [ + "optimizer = tf.optimizers.Adam(learning_rate=learning_rate)" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "Optimization process. " + ] + }, + { + "cell_type": "code", + "execution_count": 13, + "metadata": {}, + "outputs": [], + "source": [ + "def run_optimization(x):\n", + " # Wrap computation inside a GradientTape for automatic differentiation.\n", + " with tf.GradientTape() as g:\n", + " reconstructed_image = decoder(encoder(x))\n", + " loss = mean_square(reconstructed_image, x)\n", + "\n", + " # Variables to update, i.e. trainable variables.\n", + " trainable_variables = list(weights.values()) + list(biases.values())\n", + " \n", + " # Compute gradients.\n", + " gradients = g.gradient(loss, trainable_variables)\n", + " \n", + " # Update W and b following gradients.\n", + " optimizer.apply_gradients(zip(gradients, trainable_variables))\n", + " \n", + " return loss" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "Run training for the given number of steps." + ] + }, + { + "cell_type": "code", + "execution_count": 14, + "metadata": {}, + "outputs": [ + { + "name": "stdout", + "output_type": "stream", + "text": [ + "step: 0, loss: 0.234978\n", + "step: 1000, loss: 0.016520\n", + "step: 2000, loss: 0.010679\n", + "step: 3000, loss: 0.008460\n", + "step: 4000, loss: 0.007236\n", + "step: 5000, loss: 0.006323\n", + "step: 6000, loss: 0.006220\n", + "step: 7000, loss: 0.005524\n", + "step: 8000, loss: 0.005355\n", + "step: 9000, loss: 0.005005\n", + "step: 10000, loss: 0.004884\n", + "step: 11000, loss: 0.004767\n", + "step: 12000, loss: 0.004663\n", + "step: 13000, loss: 0.004198\n", + "step: 14000, loss: 0.004016\n", + "step: 15000, loss: 0.003990\n", + "step: 16000, loss: 0.004066\n", + "step: 17000, loss: 0.004013\n", + "step: 18000, loss: 0.003900\n", + "step: 19000, loss: 0.003652\n", + "step: 20000, loss: 0.003604\n" + ] + } + ], + "source": [ + "for step, (batch_x, _) in enumerate(train_data.take(training_steps + 1)):\n", + " \n", + " # Run the optimization.\n", + " loss = run_optimization(batch_x)\n", + " \n", + " if step % display_step == 0:\n", + " print(\"step: %i, loss: %f\" % (step, loss))" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "Testing and Visualization." + ] + }, + { + "cell_type": "code", + "execution_count": 15, + "metadata": {}, + "outputs": [], + "source": [ + "import matplotlib.pyplot as plt" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "Encode and decode images from test set and visualize their reconstruction." + ] + }, + { + "cell_type": "code", + "execution_count": 16, + "metadata": {}, + "outputs": [ + { + "name": "stdout", + "output_type": "stream", + "text": [ + "Original Images\n" + ] + }, + { + "data": { + "image/png": "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", + "text/plain": [ + "
" + ] + }, + "metadata": {}, + "output_type": "display_data" + }, + { + "name": "stdout", + "output_type": "stream", + "text": [ + "Reconstructed Images\n" + ] + }, + { + "data": { + "image/png": "iVBORw0KGgoAAAANSUhEUgAAAWwAAAFjCAYAAAAZyTFsAAAAOXRFWHRTb2Z0d2FyZQBNYXRwbG90bGliIHZlcnNpb24zLjUuMiwgaHR0cHM6Ly9tYXRwbG90bGliLm9yZy8qNh9FAAAACXBIWXMAAA9hAAAPYQGoP6dpAADNPElEQVR4nOy9aYyc2XUe/NS+vrWvXV29d3PnDIczw5nRMrKWUSwLSKzATmwjXn4EMuQ4UgREtqwA0RjKzCf9EPQjspIJAktAothAYsWGV40iz8jS7JyFZJNsdrP37tqr3tr3qu8HfQ5vvawmm5xudlXzfYBCb9VV73vr3ueee85zztF0u90uVKhQoULFwEN70BegQoUKFSp2B5WwVahQoWJIoBK2ChUqVAwJVMJWoUKFiiGBStgqVKhQMSRQCVuFChUqhgQqYatQoULFkEAlbBUqVKgYEqiErUKFChVDApWwVahQoWJIcKCE/Ud/9EeYnJyE2WzG2bNn8Q//8A8HeTkqVKhQMdDQH9Qb/+mf/im+8IUv4I/+6I/wgQ98AP/1v/5X/PzP/zwuX76MsbGx2/5vp9PB9vY2JEmCRqO5T1esQoUKFXuPbreLYrGIkZERaLV3sKG7B4THH3+8+9u//ds9vzt69Gj393//9+/4vxsbG10A6kN9qA/1cWgeGxsbd+S+A3GJNBoNnD9/Hs8880zP75955hm88sortzy/Xq+jUCjwo6sWGFShQsUhgyRJd3zOgRB2Op1Gu91GMBjs+X0wGEQ8Hr/l+c8//zycTic/7uQyUaFChYphw27cuwcadFReYLfb7XvRX/7yl5HP5/mxsbFxvy5RhQoVKgYGBxJ09Pl80Ol0t1jTyWTyFqsbAEwmE0wm0/26PBUqVKgYSByIhW00GnH27Fm8+OKLPb9/8cUX8dRTTx3EJalQoULFwOPAZH1f/OIX8a/+1b/Co48+iieffBIvvPAC1tfX8du//dsHdUkqBgCiS0wNLqtQ0YsDI+x/8S/+BTKZDP7wD/8QsVgMJ0+exF//9V9jfHz8oC5JxR5Bo9FAo9FAq9VCq9XCYrHAaDTy78S/0/cajQbdbhedTgfdbhfNZhPFYhHNZhOdTgedTuegb2toQeML3NgE1Y1weKHpDuGnVygU4HQ6D/oyVCigJGmLxQK73Y7Tp09jfHwcer0eZrMZer0eJpMJVqsVWq0Wer0eBoMB7XYblUoF9XodW1tbePnllxGLxVCr1VAul1XSvguIG6FOp4Nef8M2a7VavCm22+0DvkoVIvL5PBwOx22fc2AWtorDB5GwzWYzJEmCx+PByZMn8fDDD8NoNEKSJBiNRlitVjidTuj1ehiNRphMJrRaLciyjHK5jCtXrmBxcZF199VqVSXsXYAsaZGwaYzJNiPSplPNg4R+yrRhgkrYKvYMBoMBVqsVBoMB0WgUo6OjcLlciEQi8Hg80Ov1sNls0Ov1sFgsMJvN0Ol0MBgM0Ol00Gg0rAZyu92YnJyERqPB9vY2KpUKW4Z7uchEV8Gww2AwwGKxwGAwwG63w+12s2Wt1WrRaDQQj8eRy+UOfUkHuj+bzcaGAc25breLer2OVquFRqOBXC6Her2OTqeDdrs90HNBJWwVewKNRgO73Y5wOAy73Y4Pf/jD+NCHPgSbzYZQKASXy8XuD61Wy0Qt+rO73S70ej3a7TYMBgP+2T/7Z8jlcnjllVeQTCbRarXQbrfv6Sjfj6DoNKDRaO75dQcBdG9WqxUTExOQJAlHjhzBE088AZvNhlKphHK5jFwuhx/+8IcoFot8v4NMTvcK8aQ3OjqKM2fOwOVyYWRkBKOjo+h0OkilUigWi4jH43j11VeRSCRQr9f5JDeo46IStor3DSJdsuxcLhei0SiOHTsGi8UCm83GljMVtxGP7CLo791uF5OTkwgEAlhZWYHJZIJWq31fbhGlAoUWtvJvwwhyfZAbanR0FCdOnIAkSZBlGbIsI5lMwuFw9B33wwYibLvdjtHRUXg8HkxPT2N6ehrtdhvb29vIZrM8ZrlcDu12e+DHRSVsFfcMcmG4XC6YTCZMTU3hoYcegsvlwsTEBKtDyHom9QcdSSuVCrrdLsxmM0wmE3Q6HYxGI1vh5D4JhUKYm5uD0+lEMplEIpG4J+LuR9Li69Dvh9VXbjQa4Xa74ff74Xa7YbVaYbFYIMsyqtUqKpXKoVfdaDQa2Gw2jIyMwGq14tixYzh69CjcbjdCoRDcbjffOwX4RkZG0G63kc1mUavV+O+DaGWrhK3inkBWGh3D3W43Hn74YTzzzDPweDxwu91c/pYIu9VqoVqtotlsQpZlxGIxtFoteL1eeDweGI3GnkCky+VCu93G9PQ0Hn/8caRSKbzzzjtIp9N3TTi0+Eg1QdZ6q9UCAHbX0POGkdBMJhNCoRCi0ShCoRAcDgfMZjM6nQ6KxSJKpRLq9Tra7fZAH/vvFeTecjqdeOihhxAKhXDixAk89dRTcDqdPT5sn8+HZrMJm82GxcVFmM1mrK2tIZ1Os1ExiFAJW8U9QXSDOBwOuFwueL1e+Hw+uFwuWCwWPl6SVddoNFAqlZiwc7kcWq1Wj1LEarUyWRKxms1mOBwONBoNmM3mHt32XoCs/zvWIh5QiHEAo9EIs9nM8QHghiqkUqmgVqux33pQCeleIW7EZrOZ56Pb7YbD4YAkSTAYDDAYDPxZU5Dcbrez207MDRjEMXpgCVv04ym/B24uYvGh4gbEZJhwOIzHHnsM4+PjiEajbNW1220Ui0XUajWsrKwgmUyiXC5ja2sL5XIZ5XIZsiyj0+nA7/fD5/PB6XTiiSeewNzcHC8oWoB0lCXt9r0uKNIfi4oTMWGHnjNM6EfY3W4XhUIBWq0Wi4uLePPNNyHLMruTDouFTfduNBo5uD0+Po6HH34Y0WgU4XAYVquV3WxKkMHh8/mQTqfZJUfSx0HDA0XYYkBBDHops+8AsMQHwKGNpt8rNBoNzGYzrFYrwuEwHn30URw7dgxWqxUOhwNarZaP4LIs4+2338bly5eRy+Vw9epVDvA0Gg1oNBr4fD54vV4Eg0EEAgGMjIyw71ur1bKfvNvtwmKxvC8rSJkwMqwkTRDnLSUmEWHn83l0Oh0sLS3hjTfeQKVS4bE/LKB7N5lMiEQiiEajmJycxMMPP4yxsTFO4CLZKIE2ab1eD5fLhXq9DofDAaPRyFb4IK77B4KwReuZPjij0cikQLuq+HeywugoT8d6kpYpP1BxN+73IfeLPg/aZNgtaJHodLqeBwXxut0uk0Mul0M2m0Uul4MsyyiVSqhUKmi322i1WtBoNBwQq1ar7FMWQeRO/tf3i53K+A4riKzFR6fT4fEsl8uo1Wqo1Wo8vsM690SISUEWiwUOhwNutxtOp5MD2bSu+/0vzWGbzQZJkuB0OuHxeKDValEqlXoyQgdlvA49YYsWNH2wBoMBo6OjiEQiMJvNCAaDcDqd0Ol07MciQmm1WkilUkgmk6jX60ilUsjn87wQms0mms0mC+9p5xbfH+iVq9FXpatlp+8HDeS7pjRzSh0nMuh0Orhy5QouXrwIWZbx7rvvYmVlBfV6HbIso16v870TYRcKBdjtdrTb7R5rqNPpoFAoYHV1Fel0Gtlsdk/8sIM8vncDIhyLxQKXy8VNPmq1GtbW1lCpVBCPx1EoFFCv1wc6oLZbiDprt9sNt9sNj8eDhx9+GMePH+egNyVkAbdu0mScSZKE6elphEIhWK1WGI1G5PN5XLt2DYuLi6jX6yiXy6jX6wd1uz049IQN9AbIrFYrH5+OHTsGm82G6elpBINBzoYiHxaR8erqKlZXV1Eul2GxWJBMJlGr1aDValGv11kKpEy+EC170e+q9J0Ct5L1IGfgiZaNRqNBs9lErVYDAB67ra0tXLx4Efl8HouLi4jFYrdkktF9NptNVKtVHkfR3UH1RVKpFBKJBIrF4qHxv75f0DhRsNZms8Fms8FqtaJcLiORSKBcLiObzfZI+g4DiHDtdjt8Ph/8fj8mJiZw5MgR1v5TEJIgzhlSLlksFoTDYdTrdeh0OnYl1Wo1xONx6HQ6Pt0NAg41YSv91AB6LDvS/VKRIrE4kWhhe71eJiW9Xg+fz4dGo4FisYhGo4FqtYpSqcRH90aj0ZPVB9y0sNvt9i0Lh6wFnU7H79vpdFAqlZDNZvk6+rkLDgLkJqpUKpwtRhF4o9GIVquF9fV1ZDIZHiNlkA+4uZFZrVZ4PB54vV5ObRcXWr1eRz6fhyzLqNVqKlkLEEnL7XbDZrP1BB3z+Tyq1eqhCpybTCbYbDYOek9PT/P82clnvRNonQLgcbRYLJienuaT48rKClKpFJrNJiqVyoGuw0NL2Eq/tTIFmVwkZrMZHo8H4XAYOp2O61uIygGfz4fZ2Vl0Oh00m032Y9PRnhQPrVaL04Apy8pisfT4warVKqcGU5U6WnQWi4XJqV6v4+rVq3j11VdRKBRQKpXYr3bQIF0vWR0//elP4fP5ek4la2trWF9fR6PRQLlc3tH3rNVqEQqFcPToUYRCIYRCIdhsNh6zdrsNWZaxvLyMra0tVpYAOFDp1UGfgER1RDQaxezsLMLhMEKhELxeL1ZWVrC8vIx0Oo1kMnlo5Hyksx4fH4ckSXj88cfx6KOPQpIkRKNR7mZFmz6t+36vQ7+nDY4CkK1WC3Nzc/jYxz6GbDaLH/3oR7hw4QLy+TxWVlZQKBTu920zDi1hA70WtlKuB9y0bE0mE7tCjEYj+73o+SaTicu50v+I0qhisQhZlrmGM8mpXC4XbDYbBys7nQ7XdGi32xz41Ov1bB3VajWk02ne3S9evNjjchgEfWi320Wj0WDpXiKRQLVa5ezFVquFZDKJbDbb1w0igpJvKHmGLGyx1gVtYoVCgcdB/P+DHo+DhE6ng8PhYM0x6YnJ90+nkp3cSMM0fqILiPz14XAYk5OTsFqtnN1Jz+33VQkxYUqv18NqtaLb7cLj8aDZbCKdTuPq1avY2toCALbGDwqHmrCBXt0tod1uI5VKYW1tDbIsw+VyodlssktEVD6QHliv17MLxWAw8GsD6Kk3TDphCmhYLJYedQmRdrvdZgkWVbGz2+2cot1sNpHNZnHq1Clks1ksLi6iWCwOhFtE9L3XajUkk0mUSiW0Wi1WcpASZCcdOyXKWCwWTExM4OGHH4bH44HH42ESIfdSvV7n71ut1qGwFN8vRN11IBDgbFOaQxTMpcD4ThiWcRRPyh6PB0eOHIHX60U0GoXT6exRfSnJuV8wX5yXYrar6MKk2jhzc3PQaDRYX1/nypHkurzf43eoCZsGkz4QKqeo0+mwubmJVqsFu92OVquFeDzeE3Skms10THK5XDAYDEzCSv02uTbIGtfpdBx1JlcKuQWI3CjLymAwcCo3APj9ft7xAXBG4MrKCr/GQS802nxKpRIHYJXB1H7XSovJZDJxFtqpU6fw8Y9/HHa7HZIk8WvVajWW/NFjUCRWB30NNP/MZjPGxsbw0EMPwWKxwOPxMGGXSiV2XR309b5fUKBbr9cjEongySefRCgUwsjICPx+PxtNd1Jj0fdi8hR9LwbSxbK/586dw8mTJ3HhwgXMz8/zqeUg5uKhJmzg1oVFg1yr1VAqldDtdpHL5ZioKWBhNps5yAjc3HFJa0wTSKPRoF6vs6yPrGvgxgZBgUQibFFPbDKZ2JdNrwfcJDUKhjYaDRiNxvs4aruD6GfeDUT3lNFohN1uh91u59R2cocAN/zkVO7yMNe/uFcQqdAxnoKNYko6bZqDEPd4v6D7pbXpdDrZ5SjWU9/JuiYjguYQ1cMWXXYiWdNr0UmZ3DAkoSSd+/3GoSdsgrjQyS1BEp56vY61tbUeZYcYELRarUwmkiT1tLki9wVZMaSWIH2syWRCvV5HJpNhv3Qul0On00EoFEI4HIbD4cCZM2dgt9sBgAmf5GzpdBrFYnHos/J0Oh3r4I8cOYKnnnoKXq8XJ06c6Knq1+l0IMsy3nnnHSQSCVy9evXQKR3eDzQaDUvXyJfrcrkA3CRpciPRZjfs42YwGLiAk8/ng8/n45gHGTzKTEb6SqqsQqGAYrGISqWCpaUlxOPxHkFAKBTC9PQ0rFYr/H4/vF5vj5qMtN6SJGF1dRUXL17kipP3Cw8EYSuJjpQdlUoFGo0G8Xi8b50BmgCiT5t8hHq9nqWA4vHfZDJxIJHIvVQqYX19nSVu5EKYmprC9PQ0/H4/RkdHMTY21jPpyuUykskk0ul0T+bVsIJ89WazGUeOHMHP//zPIxAIwOfzccKSSNjvvvsulpaWsLKy0lP28kEHlQZwOp1wOBz8td1ucwITzTMKDg879Ho9HA4HHA4HS/i8Xm9PcgyBZLs0l0gSm8vlEIvFkM1m8fd///eYn5/v2cyOHz+ORqPB2Y5ut7tn7Xs8Hjz00EMIBoMwGo1YXFxEpVK5v+NwX99tgEBuDSVEjTCBghFE6hSg7HQ6rGggMqnX60zYFFgsFovI5/OsoKjX69BqtajValxFTQxg0AIjrTepI4aVrMXovs/nY72rJEmw2+3s7ul0OqjVaj1qG1HloOIGlC4lkrCRe4qCs8Dh6ZJOBpLT6WRDSUyMUapdRIURGUqZTAaJRIJLJuTzebawAUCWZWQyGXQ6HQSDQTQaDR5bMVHH5XKxMWY2m1nqe1/G4b68ywFgN5P0ds9RulBot6au3lRvQKnHFetq0KRqNpscrSc/Gv2/LMvsL6dJRoqSra0tXLhwAYlEokdLO0yggKxer8fo6Cg+/elPY2JiAhMTE4hEInykpWP88vIykskkrl+/jsuXL2N5eZlLsgLD6w7aKxBxBINBHD9+HMFgkC3BWq0GWZZ5s6M09GEfM41GA0mScPLkSYyOjmJ2dpZ910pVCK0fqqOyvb3NCqsrV67gypUrKJfL2NzcRDqd5v8hVKtVOBwOdLtdBAIBdj2ZzWbYbDZMTEzA7/cjl8thcnISdrsd2WyWiX6/cWgJey8hTnrl8VJppe9ktSsXjU6nY90yFfUn1wpZ3NlsFmtra4jFYpwtuFsMkr6WIu5utxtnz57FqVOnIEkSN4kV/a7xeBzXr1/H6uoqNjY2uMnBYTjWv1+IWbtOpxOjo6Pw+/2w2+2cG1Aul1EoFHqaFg8zxAD82NgYZmZmEA6HWX7bz29NmbgUO1paWkIul8M777yDd999l8tJKOWOzWYTpVKJpXwUNzKZTByfCgQCaLVaCIfDCAaDAG6cqrPZ7H1Zcyphv08oXSjkPxP/poRy0QUCAV50tVoNqVQK5XIZmUyGfZD32mHloGEwGBAMBuH1ejExMcGBI1ENQkkxhUIB6+vrWF9fRzweP9QF9+8VosqGWoCREokIKpVKQZbloTyR9QOd0qgDOuU6ALcGF4l0V1dXUSwWeePP5/PI5/M9Wn7x/ykLul6vQ6/XQ5ZlxONxrvxHCTnkL6c2eDabDdVqFYlEgpVg+znmKmHvEfoR9e0IW6/XY2ZmBh/72MfgdrsRiUSg1+tRKpVw/vx5bG1tYX5+nlUsw+rDtdlseOKJJ/Dwww9jZGQEU1NTHNQBbvjpl5eXcfnyZWSzWbz22mtYWFhAtVpFOp3uu7geVIhSM4fDgXA4zFXpKNHq4sWLWF1dxdLSEsc9hnncyOVhsVgwMjKCyclJuFyunoxDcoOQAmR1dRV/9Vd/xS2/tra2UKvV+O/kllSWRKZ11mg0cP36dbz55pvwer0s6QPA8ZaZmRnodDrIsgydToeNjQ0uYHa7RKX3C5Ww9xC7ta5p0TmdTkQiETidTthsNmi1WjQaDaRSKWxtbSGdTg91lJ8so2AwiOnpafh8vp4+gxQkKxQK2N7eRiaTwcbGBjY2Ntg1NMgNUQ8CYoYjaYLJHVKv15FOp5FIJJDP54d23ihBBg5p9umegZvuRiLaarWKXC6H5eVlXLt2DYVCgfs0KjsNKUGvAQD5fJ6789DGJ2q0nU4nxsbG4Ha74fV6YTQa0Ww2973NnErYe4CdissoJ4VGo4Hf7+dWWkeOHEE4HOZ2W9R7b2trCysrK2xhDiMoK81qtcLn8yESiUCSJE5OIAVMtVrF5uYmlpaWIMsyF9EapuP8/YoXUPIWKUQ8Hk+PK400/slkEoVCYajGsB/E7EZSXlGgkUiarNpSqYQLFy5gdXUVW1tb2Nzc5G7xlCBzp9OG6AOXZRmbm5toNpsoFArce5Sui1w03e6NuiOhUAiFQgGJRGJfS7GqhP0+IBJ1v+CH8rkajQbRaBTPPPMM/H4/Hn74YT5aUa0MWZaxsLCAd999l383bCAfq8VigdPpRDQaxdzcHDc9AG5E44lYFhYW8Oabb3LVQ7HBwaBDWVhsP0FZflSOdmRkhKtB0thtb2/z8fwgal3sJWgemUwmmM1mNmyoWBMVH6OKhH/1V3+FV155hd1pYp16cRzuNCbtdhvJZBKtVgv5fB7ZbJbdHJTgRUlzRqMRkUgEU1NTyOVyqFQqyOfz+zYmKmHfB9AxlprXUsF1CmhQoR6qm0FNaoc5yi/qhMXgmFgXnKL15XKZre1Go3FL/YdhwP2wsokgRBIzGo3cSYYybg9TZxlSGCnLpdL8IOs6n88jlUohHo/ftm71buW+9BrU+IE6IdF1iW3ZqEwzKVf2Eyph7zHECUETy2g0wul0wmQyYXx8HHNzcwiFQvD7/dBqtSiXy3j77bdx7do1doUMK1mTVfTQQw/h3Llz8Pv9mJ6e5hoXZI1WKhXEYjHu+Uip1AcZXL1XS/l+fE7kN52amoLL5eJu4FqtlpNAcrkc928c1K7fdwOdTscZjYFAgDX7wA0JXqPRwLVr1/Dmm28inU7zyeJ2qqo7nYQJYjMSMqa63W7f8q20qYgp8vs1J1TC3gPsVGuX/mYwGNjfODY2hmPHjiEQCHDdkkqlgvPnz+Pll1+GLMtca2RYYTAYcPr0afzyL/8yHA4H3ytwMwmpXC4jHo+zBI3SqJX9MO/XpkVW090qUu7npupwODA9PQ2v18s9CKmULfW7pMzZYTuh9APViY9EIggGg1ygjci0Uqng2rVr+PGPf4xCocCEDfTmPuy0PnfaoMndQun9lJFM0j/x/5WErVrYhwDke6SoPn24JCWiRp/UWWZYA43koyZXiCRJ3FtPPMaSooGK8YgF9g+SZAaV4MQSvpIkweFwcAlVCrwVCgWuI3LQ47hX0Gq1XODKZrP1dECnexT7ge6mI3y/an79kt9El0e/zjU0j6l0Mp0Q91uZoxL2HqDfh07QaG50VBkfH0cwGEQ4HGayLhaLKBaLiMVi2NzcxNra2kA1/Lxb+P1+zM7OwuVyYXZ2Fm63GxaLBSaTCcBN2VS73UY8Hsd7772HRCKBjY2NvsqQgyCdQSM7Mf7hcrkwMzODQCAAr9cL4EYDiZWVFbz33nvY3t7m7LxBuod7AdWemZycxCOPPIJwOMy10pWFnYgwla3jdspCFr+K8RL6PcWaXC4XPB4PJEmC1WrtaR9IXaCoQNvq6ipvmmrizBBASTRi7WeTyQS/349IJNIj+qfaD9lsFul0GqlUamgXm0ajgcPhwOTkJHw+H0KhUE/3E+DmUbPZbEKWZayuriIWiyGfzw9EgsygjjvNI0mSuO+l3W5n4kgkElheXkY2mz1UhbJEDb/L5eLGIQRxPikVMXdyg/SrnS3+3mQywW6386lYrDUuukyofV0qleJmHvsJlbDfJ24XxCBVBDX69fv9kCSJF1oymeS6vIVCYSjJmixAjUYDm82GQCDA9S2Ux1fSmJdKJcTj8Z4AmfI176fvepDqrihBrhDqZkSKG61Wy5YludNu1+x4mCD6hi0Wyy2uNfE5Yn0V+v2dtNb95Lj0ftQUwuv1YmxsjMtGkH9anC+U+CU2n97vzVIl7PeBfkcumhCk1aSSosePH8exY8e4SUGxWMQ777yDv/7rv4Ysy1hZWRlY0rgdSBWi1+sxMjKCs2fPIhQKYXR0tKf7fKfTQTKZxN///d9jY2MDV69eRSwW4z6VymOpciPcD62zWDJ3UKWElHxELdX8fj8CgQC71HK5HLa3t7GyssLxkH64X1rxvQCtH4PBAK/Xi/Hxce7/SYTabDZ7CFb8LJWkvVNeBH0lIqYN0Ww249SpU3jiiSe4Zgi5Q+g9xI5IJEu9U//MvYBK2PsIysyijs4+n4/919RZfGlpiX3ZwwjRMrHZbEwoNpuNFwURdqVSwebmJpaXlxGPx7lULXDnQNF+EQ1ZTIMqoyQLW2xabDabmZzJwqaNr5+FfTsV06CCSJva5Cllc0pLWxkUvN2cUbpOiOwpaG61WuH1ejE6OgqHwwFJknhTEOe0aGE3Gg3Vwh50kOXXLwXdbDZjenoakUgE09PT8Hg8MBqNaDQayOfzKJfLyGazKJVKO4r8hwF0gqBJLgZnaEKnUikUCgUum7q+vn5LYSeCckz3mkTFxd6vw/aggTqtUB0N8qUSSZPKhrqq9JuLhEHckPqB+qKSG4IsW/GUQBssBf+oRyP9nbCTdU0kbTQa4XA4oNfrEQgEMDY2BkmSuGyEzWaD1WrtkeuRLJXyCO5nOQWVsN8HlEdocfe32Ww4e/YsHnvsMfh8PoyMjMButyMej2NtbQ2yLPMH3s+POwzQaG5UURsdHYXP58Po6CgXsiLCLpfLuH79OtbW1rC8vIyLFy9ibW2NEx/6EUw/sn6/C0G0yMRu94MMOqZT/0LaEE0mE3dQyWazKJfLrGFX6tiB4SFqgiipo0c//zF1byKjp5+FezvCJncTBXHn5uZw7tw5OJ1OTE5OYnx8nK1uOhnTZiHLMpaWlriLDZVtVS3sIQO5CCi70e/3w+VycdEj6j5Dqdj9ItzDBGqbRNInsYM1HRupzgW1SaPGpUq/dT+8n3HpF2NQHqkHHRQLIaKmzUbMxDtsHeV3cnuIEMlT7Ibe77WUr0uWNaWUS5LEDTV8Ph9cLhdXBSQLX/m+Yhs7MY9gv6ES9h6BJgM1RA2FQhgbG8PExATX0K3Vatjc3MSrr76KdDqNtbW1oU10oIlvt9sxOzuL8fFxTExMMGlTQkG5XMba2hrm5+eRSCRYp7rTPd+rVa20oAHcEoii1+y3uAbtM6DrtdlsmJ6eRjQaxejoKMxmM7RaLer1OmRZRqFQ6Km/chgh3ptSnaE8USjJntwrpNaivIBoNAqv1wuHw8Hp/lRv22q1wul09vitaZOkphrxeBzvvvsukskkYrHYfWu0oRL2HkKr1cLhcCASiSAcDmNsbAzj4+MccKtWq9ja2sLrr7+OeDzONYuHcaGJ2uDZ2VlOt6d6D5T5ValUsLGxgfn5eS4gfztL6F7GQrlQlT01RSuLovvKjeGgNeD9QC6nyclJzM3NYXR0lHXtpOGnLiqEg0482gsoPwvqgSqexoi0lYQtbtY0B4xGI3Q6HVwuFzfQffjhhzExMQG3243Z2Vlu7ktt68iHLoISvxqNBmKxGC5evHiL0mm/oRL2HoBIgoqskxaZ0mnF3o1EWtSZYlgXlUiOFFkXj+xKjawYPFIuPtEiUlopd5JkATfrRIuRfvE9gZs1TNrtNoxGI6sp6P1oIQ6avE+v1/eML/lw6fRCUrJBuua9gNLVobw3+nypFAJ99iT3ozlArg+9Xg+n08kxFr/fD4/Hw0RNwXJlN3bxehqNBpdQpVrbYrDzfkAl7D2AwWCAxWKBxWLB2bNn8cwzz7B+U6fToVQqcRr2xYsXkclkerpaDxvEoI3ZbOZiRNTJmorhdLtdWCwWBINBTExMIJ1Oc59Kck0AN5sdiMdd+r5fUFLcLCihxOPxcIBuZGSElSoU/CyVSlzJTdkns91uY3t7m2sgUwDpTmMA7J8VKx7jA4EAB3QBoNVqIZFI4MqVK8hmszvWXx7WoCNtppQYVKvVmHRp3lksFgDAxMQEHnvsMSbOdrsNh8OB8fFx2O12ruFD84RI2efz8d8pxkQyXKXPnFwviUQCP/7xj7G5uYnLly8jnU73bJj3A3tO2M8//zz+7M/+DFevXoXFYsFTTz2Fr3/96zhy5Ag/p9vt4tlnn8ULL7yAXC6Hc+fO4dvf/jZOnDix15dzX0DEZbPZMDU1hSeffJJTWnU6HWq1GtbW1rC6uorNzU0OOA7bQhIhyqIkSYLT6exZVJ1Oh4+jFHxtt9ssS6OAJACudwzc6PEo6qKVUXdR5UHaXCrob7PZEI1GcezYMS49qtPp0Gq1kE6nUSwWewhbVBuQ3FKj0eyKrPeTDMVTCRGKx+Nhq5FS+zc2Nu5L/Yr7DfrsxQxCmlPdbpet6W63i0AggOnp6R7S9Pl8OH36NLxeLywWCxwOxy3yQJq/tAGIrhTltdBclWUZFy9e5KQvCjjez7Hfc8J++eWX8Tu/8zt47LHH0Gq18JWvfAXPPPMMLl++DJvNBgD4xje+gW9+85v47ne/i7m5OXzta1/DJz7xCSwsLECSpL2+pH2BSBxut5ubg4ZCIZhMJrYYqZ4uyX8OS6+9O0XpxWw1j8eDSCQCs9mMUqmEYDDIzwFuErbSNUFWExEqbQK0+MhNYLVaEQwG+WsoFILZbObrIQK2Wq0910x1KKjTdq1WQ7VaRTwe33Wnn/1I6iE5n6gOoU4ndN1inebDMJ+UIMLOZrPY3Ny8JXmGXJAulwuRSKRnk3W5XOz6EMdOJOt+CTf9NOt0DdR3NJ1Os1vkIGq27Dlh/+3f/m3Pz3/8x3+MQCCA8+fP48Mf/jC63S6+9a1v4Stf+Qo+85nPAAC+973vIRgM4vvf/z4++9nP7vUl7QvIutTpdDh27Bh+8Rd/EYFAALOzs3A4HNxnr9FoIJlMYn5+HhcvXkS5XL4l6DVsINIjMqWjK6WiAze1tHa7HSdOnEAkEkGlUsEjjzyCarXKbiTRyul2u9x1hmRTzWYT+XyeE20o489gMMDv93OpUUoqsdlscLvd0Ol0TGgUaKS+fOQuoU7ajUaDVT25XA6vvfbavrZ5uhOoDrTL5UIgEIDD4eDNhjavQqHABYd2Oq0N6xyj665UKrh06RK0Wi1GR0f51AqAreLp6Wn4/f6emAid+pTBw35STjFQqbyGTqeDcrmMS5cuYWlpidVO6+vrPEfv9xjvuw+bJr7H4wEArKysIB6P45lnnuHnmEwmPP3003jllVf6ErayRkKhUNjnq74zxF3e4/FgZmYG4XAYPp8PJpOpxxKqVCrIZrNIpVI7pg8PG0QLm/zB/SL2ZGFbrVY0Gg24XC40Go0e3yId/0k2RQHZfD6Per2ObDYLm82GZrPJfkiTyYRwOAy32w2DwcAWGHUT12g0KBaLMBgMaLfbsNvt6HQ6Pc8tl8uwWq1cE6Jer/NGcifsh2Utvja1WLNarTAajRxQIzfOYbewgRvWbSaTwebmJrdCI8UIKUAoaAjcGt/YqYO5qELZyboW9daZTAZbW1uIxWI9MYOD2BD3lbC73S6++MUv4oMf/CBOnjwJAIjH4wDAx2JCMBjE2tpa39d5/vnn8eyzz+7npe4aopxtenoaTqcTs7OzrOmk2s+NRgOrq6vY3t7GwsIClxAdVhmfEkTWxWIRV69eRbfbRSQSwdGjR3sIjxYWNS0lHzFZyqIvsdvtsgXcarVgMpnQarVgs9lgt9vZQqaN0ul0ctqwmKxDJS6pih31jxQL83S7XeTzeSSTSdTrdWxtbWF7exuFQgG5XG5X979fygyqAz0zM4Pp6WlWQQDgEw2dbm7XDmvY0Ww2mS86nQ5WV1d7mhqIlnO/B0EZb6DNdieS7nQ6nEWayWRw5coVzM/PI51Oo1Kp9LzW/ca+Eva/+Tf/BhcuXMBPf/rTW/7WL3Npp+yzL3/5y/jiF7/IPxcKBUSj0b292F1A9Hl5PB48+eSTGBsbw5EjRzA6OsoF1rvdG6VEL1y4gPPnzyMejyOdTg9lB/SdQCSRyWTw6quvYmVlBY8++ihGR0dvaZhKwUgAHKNQdvGgr1TnmSxJcr2QD7tfsoSYDEOE1ul0kMvlkMlk0Gq1UCgU2IqmutHJZJLbSqVSKT4B3amm8X5K6Eh7ffLkSXzoQx+C1+uF2+3meUWnTXK33Y+CQweFer2O69evY319HbIsY3Z2FtVqlbvF9/NJ3y6bVSRu8YQk6r0prrG+vo4rV64gmUziZz/7Gd555x3ubnOQBte+Efbv/u7v4i/+4i/wk5/8BKOjo/z7UCgE4IalHQ6H+ffJZPIWq5tAAZhBAE0Qk8kEl8sFn8/HljWpH0izWSgUkE6nIcvy0Er47gRSLFAz2GKxyL59ZUqvmHV2u4APQZT66fV6zp4kkiIrk34vBhNJylcqldBsNpmwKQBMX9PpNKrVKrLZLGRZPtDPSNSVUxKHJEm3FM6nez5s2mslut0uF3aik08ul4MkSWg0Gj0BaGDnpgQ7vbbyK9UmobVLVjZllNL4HyT2nLC73S5+93d/Fz/4wQ/w0ksvYXJysufvk5OTCIVCePHFF3HmzBkAN9wHL7/8Mr7+9a/v9eXsKajcI+l+R0dHMTk5Cb/fD51Oh06ng3w+j0KhgHg8juvXr2NhYQHlcpmbgx42VKtVrK2t8QmC/M1iWUpJkjgt2O/3w2w2cx0HZYKL2P+RFgf1LCRLmQKFVO2wVqshl8txYJHImxJLyDdOZE+9DyuVCruq9rtTyG4g+v0lSeJiTxTMLZVK2N7e5i7pInEfVtC9ZbNZvPbaa1haWsKxY8fQbrfhcrng9/u5bDGAOzbBVZ7eKGDearUQj8exvLyMUqmE+fl5XL58GcViEfF4fGDGec8J+3d+53fw/e9/H3/+538OSZLYB+V0OrnFzxe+8AU899xzmJ2dxezsLJ577jlYrVb86q/+6l5fzp5Cq9XCarXCbrdzvdyJiQnu5kyETQGK69ev49q1azwxDiMo9Vyj0WBtbQ1vv/02DAYDAoEAy+soOOh2uzEzMwOXywWr1cplLSmoJlrgZPF0Oh0UCgX2NSeTSSSTSVSrVaysrLC+en19nRsYU3BKjBnslH5OFtkgFOCi+6fAqNvtZt9/t3ujpOf29jYymQxkWT50RZ92QrfbRS6Xw+uvvw69Xo90Og1JkuD3+9HpdDihaKcgo/g69FWs9pfP51Gr1XDt2jX8wz/8A7LZLBYWFrCwsHDfOsnsFntO2N/5zncAAB/5yEd6fv/Hf/zH+M3f/E0AwJe+9CVUq1V87nOf48SZH/7whwOtwSbrh1oWUZYUZfaRH7VarfLRrVqtDm1xp7sBWcKNRgOlUokz0UjRYTAY2PK12+2oVCqwWCwoFApMUFTNkAibXBvdbpePp/V6vceNQbWIi8UiSqUSW85iqVExBV0J0fc5KJ+PWGiISKXVanH9kHw+zwQzKNd8PyBKM4vFIvc/9Xq9yOVyXNfabDbf4m4DblU1UfJarVZDKpVCrVZDIpFgF2apVEK9Xh+4oO6+uETuBI1Gg69+9av46le/utdvvy8QyZoi9+Pj45xJRQTTaDSwtLSEl19+GZlMBrFY7IGwgAi0OWm1Wi5updPpsLa2xj5+MSOSrGqxYJOyKzYl01AJS5L9tVotdoeQm0OUvQF3VnJ0Op2BSt8mCxsASqUSEokEzGYzms0mDAYDn2BSqRQ2NzcPjeJoNxDdGOvr6/jxj38Mm82G9fV1bG5uwuFw4OjRoxgZGekp+ET/S+uTOvRcunQJGxsbyOfzWFtbQ7FYRDqdxsbGBmq1GrvgBs3YUmuJ7AJk9RiNRoRCIczMzCAUCnFACLgZtEgkErh8+TJkWUYulxu4D3w/IWqx6aipRL8go/J7+lm0bMQx3On7u8WgfTa0YZHKiCrxEQGl02ksLy8jlUohl8sNlOV3P0D3m06nUSgUuIhXp9OBx+PhZCPKhlRK9agWuyzLuHbtGubn55HNZnH16lXkcjnOmRhkI0sl7F2AjuzUmzEQCHDLL0r4oKMWVeajurmD+sEfFPolLeykj92JUA/rmNIcorrply9fhslkgtPphMFgwPXr11mJs1Oz3QcBog+aYkalUglOpxPFYpFdcmKXGMqipezW1dVVJBKJvo1EBnl+qYR9B4hC/VAohGPHjuGRRx6BzWbjIuek3ST5EQXCBkF5MMhQLgwlcT9ooCBrJpPBj3/8Y5w/f54r9mm1WsiyzHVOyE//IIIMoVarheXlZSQSCej1erzxxhssbKBStCLEpCNZljnmQdmiymzdQYRK2HcAVYWzWCxcpyIQCLDVTZFpChKRuJ4CFiruHg8iWQO9flpSGlGmKGWIHvbuMrsFESs1IgZ6W4CJHdYJYuneQXZ73A4qYe8A0ddKTQmohq5YRpRAOzcA1hWLEjXCME4SFfcXIqGIhPMgJMu8HygzFpUW9jBY0HeCStgKKANgOp0ObrcbExMTCIVCcLlcfESl54o1lbvdLoxGI5rNJmq1GhO2Stoq7gYisYgnNXXu3B5ikLFf+Ythh0rYAvopF+hIShXiRCkacFMdQrWUxbq8yiPZYZgwKu4/1HlzbziM46YStgA6gooPqntN6dTkSwTA/saVlRW8+eabXNmLovidToeTalTFiAoVKt4vVMJWQLSsxdoOZGEDN4maMq9WVlbw0ksvIZlMcguwdrvNwQ/ynQ1SkoYKFSqGDyph7wCytim7jtKoE4kEHA5Hj0QokUhwRS9Sh4h+NFELqpK1ChUq7hUqYSvQj2SXl5eRz+dhtVoxPz/PLYnINbK1tYWFhQVUKhVucQX01t+lIJJK2CpUqLhXaLpDyCCFQoErdN0PULF9nU4Hm80Gs9nc8/dqtcpuEEAlZRUqVNw98vk8HA7HbZ+jWti7gGghN5vNW+RCYnMClaxVqFCxX1AJexcQ3SNUJ0QEBRVVslahQsV+QiXsuwDJ89SUcxUqVBwEbt+iQYUKFSpUDAxUwlahQoWKIYHqElGhYgghlj1Q9f39odYSUaFCxb7idtmwYgs1m80Gq9XKnVSoFIJaAuGGDNdkMrEclwqw1et1LtA2rFAJW4WKAcGdioVR9UidTgdJkuDz+dBqtZDJZJisVbXSjfLG1PyZykMAN2pnixLcYYRK2CoOBEpyErNLCcO8sO4GNBZ6vZ7rrIs116muuk6n4+dQD8N2uw2TyQSbzYZ6vQ5Zlh9Ia1ur1cJqtXKhNo/Hw11nqJ5PMplEo9HgcsjDWBtbJex9Rr+ms4QHzfcoHunFriDisbXVavX0yDzMYySOg06ng8vlgsvlgtVqxezsLEZGRph8qFok1WKnn1utFjeljcVi+OlPf4p4PI5yuQxZlrl8wmEdQwDsIjpz5gwmJyfhdrtx7NgxuFyuno7pP/3pT/HKK6+gUqkgk8lwp5phgkrY+whl5b9+/snDvJD6gQibOoGT5ajT6bgCokaj6SmgBRy+caK5QISt1+shSRICgQBcLhfOnDmDI0eOwG63IxqNckkEm812S6cjIuzFxUWsr69zYheVSzhsYyeCxtFkMmFychJnz55FKBTCE088gWAwyG37KpUKj1E+n0epVFIJ+0GFstmBTqeDwWCA3++HJEn8s1ar5QnUbrdRLBZRKpXQarU4cHQYFxcd6a1WK/x+P5OP1WplKxsAV0ZsNptoNpsolUpoNBqoVCqQZfnQ+GjJF63Varm5s9lsRjgcRjgchsPhQDgchsfjgdVqhcVi6WmeIRI2+Wvb7Ta8Xi/m5uZgsViQTqfhdDpRq9WQy+WQy+UOXUauRqOB2WyG2WyGy+WC3+9HMBiE1+vloCPNmVarhXq9jnK5fEujkWGCStjvA6KVRA+73Q673Q63242PfexjOHnyJEwmEyRJgslk4qNqrVbD1atXcfXqVZRKJaysrCCZTA51g9B+EI/v0WgUH/vYxxCNRuF2uxEOh2EwGNBsNtm3SBtXqVTCxsYGSqUSlpeX8fbbb6NcLjOZDyPolEUkq9frcezYMfzcz/0c3G43IpEIRkZGYDKZ4PF4YLfbodPpYDKZevzY4klNr9fD6XTCarVCkiR4PB5Uq1Vsb2/j2rVrKBQKeOONN/DGG29wt/VhJSsRNBZ+vx/hcBjBYBBnzpzB448/zuuNTmq1Wg21Wg3ZbBaxWAzlchmVSuWgb+GeoBL2PULZlYaO9RQAcrlcmJiYwOnTp3uCIIVCAel0mmuSZDIZGI1G7pB92EAEZTAYIEkSJicnMT09Db/fj2g0CqPRiHq9jlqthna7jXK5jHq9jnw+D71ej3w+j0KhALPZzLXGKUA5rCA3iMFggMvlwtTUFPx+PyKRCCKRCLuLlJ2/xa9idySj0QiDwQCLxQJJktDpdLiapSzLuH79OoxGIzfVGHaI42CxWOByueDxeODz+RAIBHqUIRQXoR6rRNbDummphL1LEPFQJNpqtSIQCMBqtcJkMvHxngJHTqcTc3NzcLvd3LFGr9fDarXC7XbDZrNhZmYGOp0O2WwWjUaDXQK5XA71ev2gb3lPQIoGj8eDcDgMt9sNh8MBg8HAHXvIAgLAY6zVajExMYFqtQqNRoNarYZ8Po/V1VWsra3xUXcYIZbpdblccLvd8Hg8sNlsrAxR9g2lZhr9grBE3PQ8OulFIhE4HA4cP34cuVwOhUIBy8vLSCQS/LrDCOqxajQaEQqFMDU1xWuRav2Q3joej2N5eRmyLCMejw+9T18l7F2AFpDRaITH42G/42OPPYZAIACHw8GRfCJsg8EAh8MBi8XSswCNRiPsdjva7TY8Hg9OnDiBdDqNZrOJTqeDXC6HarV6KAib7jcSiWBiYgJjY2MYGRlBIBBAp9NBuVzmQGO9XmelhM1mAwCEQiF0u112FciyjB/96EeIxWLs7x/GxUcbu91uRzAYxMjICPv2xYQPZQMM8sWKG5Uo+6OfAfAGQMQlSRJSqRTq9Tq73oYNYoCR3EBTU1M4e/YsnE4nXC4Xq4zoxLa4uIiXX34ZmUwGi4uLvM6G8f4BlbB3hOjyMBqN/HA6nXC73XC73QgEAggGg3A6nfD5fDAajWxd0/+JC08MNlEAyWQyodPp8P81m00+zg0zRHcRHdXpdKLT6dBut1GtVnt8jAaDgclap9PxcymgZDQaIUkS9Ho9Wq0WV08cNogqGYPBwHNLDMCKJX3pXlutVk+mHo2xwWDg/xW12xaLBTqdDk6nE36/H51OBxaLBQaDgcl/2IiL7tdqtbLf3uFwwG63s96aAoyNRgPFYhGZTAaZTAaVSmVoT2WE4WeGPQYtGKvVCqfTCbPZjCNHjnD03efz8QSJRqM8UUgfCwCVSgUajYaP86LmmPy5wA1SAgC73Y6jR4/CZDJheXkZ6+vryOVyQ2tBAjc3J6PRCL/fj7GxMXg8HlbHxGIxLC4ustyqUCjAYrHg1KlTGBsbg91ux9jYGEwmE8cDisUixsbGEA6HUS6XkcvlUCqVDvpW7wliZqJoPVN7OQoOUvC1WCyiWq2iUCig3W5zEo3BYOAAJblBSPpHcy0QCAC4YXWfPHmSOyRtbW2hXC4f5DDsGmTk6HQ6RCIRnD17Fi6XCydPnkQkEuH1V6lUkM/nsbS0BFmWcfXqVSwuLkKWZVbKiBvesK0vlbAVIMK22WwIhUKQJAlPP/00nnnmGdhsNjgcDlitVgA3j5+00GjRVavVHrWHGOkXI/70sNlsOH78OCKRCCRJwksvvQStVjvURzciDJPJBJ/Ph2g0CovFgna7jVKphNXVVfzsZz+DLMtIp9NIp9NwOBysZAgEAqyYIPdSpVLB2NgYIpEI8vk8arXaUBI2zQ2RsMn3Si4PIuhEIoE333wTyWQSuVwOiUQCzWYTZrMZJpMJZrMZY2Nj8Pv9CAQC7AcnciMlhdvths/nw+LiIsrlMpLJJLLZ7FARNp1IotEonnzySfj9foyMjCAcDkOj0aDZbKJarSKdTuPixYuIxWJYWVnB9evXUS6XeY2KGDbSVglbgOgGIT8ZuSroSG+1WmE2m9HpdLguAVk+JE+r1WpMtp1Oh4NMer0eDoeDF5SoMKEsNpPJxMfaYZpISoip1CaTCRaLBUajkRcIbWwUta/VajAajeh2uz0+XDE7kjTu9BhGVQ2dmkieWKvVUKlUUKlU0Gg0YDAYuD5IsVhEKpVCKpXi5JhcLodWq9WT+UgGhEajQSqVQrfbZWmp6Cqh00ogEECz2WR/+TCc5CgYTZI9h8MBh8MBs9nMJ1Vae8ViEfl8HrIss5affNfA8AZbgQeQsO9UXIdIIRKJ4KmnnoLP58Pc3BycTif7nSlQls1mUavVsLa2hkuXLqFUKiGXy0GWZbaagBtBErfbDbPZjOPHj+Ppp5+Gw+HoyfSjTYA2BJPJNLQBEvLfW61W2O12eDweBINB/jtVTsvlcuxbbLVa0Gq1cDqdrCYRCYW62dMxXwy0DQtIxdFoNNi6XVtbw/z8PNxuNz+vWq1iYWEBW1tbKJVKWF9f70kiIiOANsT19XVYrVZ4PB6sr6/D4/Fgbm4Ojz/+OCRJgsFggMFggN1ux5kzZxCNRnHp0iVcu3YN2WyWk7kGdZ5R6vnMzAw8Hg+OHz+OmZkZuN1utrprtRq2trYQi8WwubmJ9957D5ubmyiVSqhUKnx/4j2KyhpgOIj8gSLsndLDxb/TIvB6vThy5AhH8S0WS4+2s9lsolAooFgsYnV1FW+99RYfWZPJZE9WnsViQTAY5CDQI488wt+T1UnSP3IBUGBo2I5swM2Nj7LQJEmC0+nkAGOr1eLsTrE0KHAjdkAKCjqF0GuKdUdEC3yYxodOF6VSCbVaDalUChsbG+ybJh//W2+9hcXFRTQaDeTzeQ420kMcF9rAXC4XSqUSXC4XAOD48eM9Ljiz2YypqSmMjY2xVttoNLIFOoig+6RM0FAohNHRUQSDQbhcLh6zTqeDTCaDjY0NbGxsYHV1FVtbW+xm2mmODNv8eaAI+04fjOhbLJVKiMViaLVaPUV3KBhUKBSwsrLCX9PpNPL5PCd+iJpZOuaS5EiZXgyAj8fkZxumSdQPYrCVNiA6nrfbbbhcLgSDQRiNRnYPeL1eDqBZrVY+6opJDxSgLJVKA0syd4I4z8rlMuLxOEqlEvuzS6USCoUC+/PFuUTzQpwftNlRtb5Op4N4PM7zMxQKIRgM9hTeMhqN8Pl8CIVCKBQKSKVSA5tMQoaUxWKB3W6HyWQCAFYaVatV5PN5pFIpxONxZDKZvutIqfwSDTBSHCk3xkHDA0XYIvp9GGQ5t9ttbG5u4mc/+1lP4gEAZDIZlEolJJNJXLhwgYk6m82yD1skElogtVqNkx/EMpn0O0pZLxQKnNE3zBIksrAtFgu7RsidBAAzMzN48sknewrwOBwOnDhxAtFolGVvAJjU8vl8TxCpUqkM5KK6Eyj+0Wq1sLW1hU6nw/77breLRqOBdDrNxZtE/6sSZLFrNBoUi0Wsra1Br9cz2Xs8HnzgAx9ga5osco/Hg1OnTsFms2FpaQmFQmFgCRu44VYMBoOIRqNsWdMJhQKo7777Lt577z2USiXk8/meJBnx9KzX6+F2u2G323vGtVQqcYVDstxvN78OYu49cIR9pw+AduVSqYR4PI5isQiPxwOv18uZU7IsI5FIYGFhAclkknWfyl2ZLBrxwxez0UT3DFmRSut8WEGnCPI5k7+eThZkYVO9DK1WC0mS4PV6YbPZeIxobIrFIgqFAqeqk4Z7WEHurnK5jEQiwacJAD0p+rerLSMWzaKvRN7xeByrq6soFAo4fvw4z0EyEkwmE7xeL5caFd9/kCDWX7FYLLDZbLy5UeC6UChAlmVkMhlODhIVITRO5PcnHbfdbgfQO36kmhHjJv3Q76RzP/DAEfZu0O12uc5HqVSCyWRiqV4ul2OtJ3Ww6Fe3mXSjer0efr8fZ86cQSAQwNGjR1kxQv6zer2O9fV1LC8vY3l5ma2dYXaN0AZUrVbZ1UNWN/lbJycnUa/XexI9JElioqZx3d7exmuvvYZ0Oo3V1dWhDcYqQfdIY0Dk1K8WuJI47kQkZH1S+VVZlpnwSGESDAaZ3GlDvV+Gwt34jsV1QvOJDBvKkiWCFvX/ZAR4PB7OB/D5fCwCIMKmexarQpKrRYwXADelvNlsFslkkuve5PP5+zJuKmErQINOQSGtVotYLIb33nsPALiqnJj+Sv9HH66Y4Uc62U9+8pOYnZ3lescGg4EXbLlcxuXLl/Haa68hlUohl8v1+NKGDbS4isUiJElCpVJBtVrles50vHU4HD2KBxozcp3QZri4uIgf/OAH2Nra6umochhA90hEo9PperIb+ymZ6KsyK1L8vlAoYGNjA9lsFhsbG4jH41zql2SWk5OT8Pl82N7eZkXO/dgIlUWs7vRc0W1YKBRgt9tRLpdhNBpRLpdZCULxIarZ0+l0MDY2hlOnTsHhcODUqVM4ceIEF2gzm809YydWjZRlGbIs91yLWC1xYWEBr7/+OmRZxtLSEpdJ3m+ohL0DRB8yCfLpmNQvCKQEBTYoUOLxeLjuCH3oFMFuNBooFArIZrOs5x5WsiaQlUiV0sSUewp6KY+qRFpAb5W1crnM6cW1Wu3QRPwB3NY33Y+slZX7+v0fADYodDodp/6bTCY2MMQ62kTW4usPCpTXRPcnZojS8yjATV8BwOl0wuv1sguOuviQIkscZzFeYLPZYLPZetwi9B4ajQayLMPr9UKj0fTUC9rvdasS9m0gTg7aPW8nvhdlZ1arFUePHsX4+DgmJiYwOjrKWmwia8rwSyQSWF5exsrKCh/5dvvB305XflAgsiVlRyKRwNraGtxuNyRJ4jGgRdVPolcul1nuRnpaqu53O3/uMJK2OM/E70X0I+rbSVTphEJH9ng8jlqtBrfbzSdBqmND0j+y7vcb9P67sa4pUGg2m+FwOHgOmUwmrt3T6XRgt9tx7NgxzmPwer2wWCwIBAKYmJiAzWZDOBzmU4Zer+c1JvqwaQ2bzWY4nc4el5Q4Z6emptDtdjm4mUqlOBFsPwu3qYS9C4g7+U6gxUPHJqvVirm5OZw9exbBYJA7iNBz2+02MpkMlpaWuAQklQ2916I8g5QAQJF2SoNeX19HvV7H6OgoHA4HL0alRSf6E1dXV5FKpbC5uYlqtdo3tfgwQLTwlBuw0qrerQVMhK3Varm0aKPRwNjYGLrdLhce63a7LHGjNm33A7s1RkgtJBI2SfsMBgMntDmdTrRaLfh8PrhcLhw5cgQulwuSJHHGJ21K9P6inE98T7KaSaVEEIu2UXllMkguX76MYrHIAoT9gkrYewBxQYlp5pQe7HA4enyEdNTP5/NIJpPc0GAvAo2DQNZALwlRkNZms/E93i5LkRZRtVrlWMJhJOp+2KvPTyQkMQdAGWchIiO526Apb8QSB1TVkNwe5PogXbYkSWg2m1xKwm63cx16nU7HkltyF5H7QzkuAHosa0pMopIRdF3kYqLApl6vR7FY3FfDSSXsu8ROFg592Ha7netlHz16FKdPn+bovEaj4fZX5XIZ7777Lv7u7/4OsixjY2PjnrqEDwpB9wMFH1dWVtDpdFAoFHDy5Eme3EopmRgjqFQq2NzcxNraGpLJ5ECnTu8nlPes9Kne7n/EwlKiskKUyVHzX5fLxQkog9JblEjRZrPxNXo8HiZkqo9CJGyz2dhXL7ofKZC4urqK1dVVlMtlLC8vI5lM9tQYp5iTRqNhJQ2paaim0OTkJBwOR08c5ujRo6hUKkilUnjppZcgy3KPymkvse+E/fzzz+MP/uAP8PnPfx7f+ta3ANyYUM8++yxeeOEF5HI5nDt3Dt/+9rdx4sSJ/b6cfYHoDqGWRV6vFyMjIxgfH+fdmVwhlMm2urrKvQqr1eqhtCKbzSZr1S0WC0fTKUCzk0uEarUkEgmWWhGG0U/9frCTz175e+VmTz9T0JdcbUTYdLwng8JisXBp4EEYX7JuKQGLSsiSwoP0/USMkiSxu4esYTrN1ut1bG9v49KlS8jlcjh//jxWVlZ6XJD0Hnq9nt/HarVienoaoVAIgUCAZYFE5jqdDiMjI2g2m4jH47h48SK7lvbjtLKvhP3mm2/ihRdewOnTp3t+/41vfAPf/OY38d3vfhdzc3P42te+hk984hNYWFiAJEn7eUn3jH7BHvFn8m95vV7MzMywbI0CarQAisUiNjY2WOhPqe6HkayB3qQgCqhShiL5CMUEELKGxIQI2szupMx5UKDUZvcbE3EzpHouSv2/+BqDOv+U7htyh1D8Q0ywEl1AVCSL0u7L5TIWFhawtrbGBhOdOIjwxd6s4ry1Wq3cd9Tr9aJarcLpdCIYDLILlMr/UlkFcrfstdRv3wi7VCrh137t1/Df/tt/w9e+9jX+fbfbxbe+9S185StfwWc+8xkAwPe+9z0Eg0F8//vfx2c/+9n9uqR7hjLYo/RR0YdmsVjw8MMP41Of+hQnhoj1CtrtNtbX1/Hiiy8ilUrh8uXLXBNjUBfM+wXVxqAgazKZ5DZrFDii8SSipqSltbU11rjeSZf+IJB4v3u80yam1WrhcrkwNjbG8RTRDysqJQbRcCCiFqs/kiuHtPsi6YrutHK5jCtXruCVV16BLMtIpVJcQoJUR+LYUX11UROv1WqxsbEBo9GIQCCAdDoNv9+Pubk5nDt3Dna7ncu8ms1mboFXrVb5ZLmX2DfC/p3f+R38wi/8Aj7+8Y/3EPbKygri8TieeeYZ/p3JZMLTTz+NV155pS9hUzYToVAo7Ndl3xY7ZZuJk8rn87GfS8zaE1Pet7a2EI/Hkc1mORh0WEGWCtVTqVarqFQqnNigPMJTlJ0s7EKh0FNfXMVN7DbpxGQy8RGfNkilEaL0tw6SWwS4ucboIUpBiWCJHKkaoizL2NzcxPz8PJezpT6iu31fjUaDUqnEQUu/349arQaPx8OqJQp8lkol1m+Txb7X2BfC/pM/+ROcP38eb7311i1/i8fjANBTH5l+Xltb6/t6zz//PJ599tm9v9C7hHJC08PtdiMajcLpdGJ0dBROp7OnHCsd0ZrNJnK5HBes2e3kGXaQNl2UaNHRXFwYYpssCiSJZWpV7B6iVUo9R0mtBNzcSCm7j3y9YubuQUMMOpLPmvzuBDKGKDZUqVSQTCaxtLTEktA7afj7QXwezb9KpYJYLIZKpQKfz8fdf8i3bjKZEA6HMTc3h1QqhUwms+cdffacsDc2NvD5z38eP/zhD2E2m3d8Xr9g005R7y9/+cv44he/yD8XCgVEo9G9ueBd4E5JMuFwGE888QS8Xi+OHj3KDWMp/ZxqGtMHvra2hng8ztmThxmif99gMHD1PpPJ1FdX3Gg02C1Cp48HYVPbDXZKkuo3hwwGAyRJgiRJ8Pl83OvRarXy5kgnV6qbQWM+KHNSTGChhCuS2IluSYp95HI5TsU/f/48Njc3kUwmIcsyl5G423ujU59Go0GhUMDS0hKMRiNsNhuOHj2KUqnEY2symTA9PQ2tVouVlRUsLS0hk8ns6XjuOWGfP38eyWQSZ8+e5d+122385Cc/wX/+z/8ZCwsLAG5Y2uFwmJ+TTCZvsboJJpOJLYNBAhG22WyG2+2Gx+PhwI4Y3Ol0Ohxso4UxaItjv0CRfqPRyD5rGpudjow0JmSVH1aXUT8DZbcJJTs9j16TNkciaVI20JiLEkqxv+QgzUcxcUbpBiGIFna9XkelUkG5XObqjnuZ3yDqt+k9LBYL+8Ip4cbhcMBms+1LBcQ9J+yPfexjuHjxYs/vfuu3fgtHjx7F7/3e72FqagqhUAgvvvgizpw5A+CGVfXyyy/j61//+l5fzp5BGVXX6XSw2+0wGo0YHR3FiRMnOKNRdIV0u11ks1m89957SCaTuHbtGsrl8kAGePYSYsbYkSNHMDk5icnJSYyMjHDAkQJHNLZ6vZ716sFgEDMzMwCAdDrNmZKDRip3A6U/tp8fWVlMTPw/5fdKiO3TZmdn8eSTT8Lr9eLkyZOwWCz8N3p9cof0c4kMAnQ6HdxuN8LhMPx+P7sZxWAp5TVUKhXuNkMlkMkNKSpJ3o9vXixIlkwmMT8/z5UOR0ZG0O12uVUbtWbb6ySaPSdsSZJw8uTJnt/ZbDaeOADwhS98Ac899xxmZ2cxOzuL5557DlarFb/6q7+615ez5xDJhSQ8kUgEJ06cQCgUYm2mmGGWzWZx8eJFXL9+HWtra0zYw0o8u4FI2EePHsWjjz7KG5rT6eypfKYcU6rmNz09DYPBgKWlJfYXAhgoUrkbiAEymif0ewDsUxatX/HvIsGLG5doRJDbYHZ2Fj//8z/P+mEqUCQSCBU7EnXa+5HscS8g69rlct1C2DRnut0u16vJ5/PY3NzE4uIi91WlcgYiYfercX03fm1yj6RSKVy6dAlutxsjIyNoNBqcsk71Tuha7+Y97oQDyXT80pe+hGq1is997nOcOPPDH/5wYDXYStAxn7KfnE5nj38NAPsIKb2a+j+SPnQQFsV+gqw90gCTpIyIqt/RVnSFUIfvcrmMbDbLc4PcScM2fuQ6I/eQw+HgABqNg5iNSAQK9GYuEqGKvn16DZPJxHPR5XLB4XBwerYy7ZpcCERqgxgr0Gg0HKTeKdjYaDRQKpVQKpVQLBZRLBZRLpfZEt7NWrsbq5ueR8XNqtXqLVm4t3P3vV/cF8J+6aWXen7WaDT46le/iq9+9av34+33FLQ4/H4/PvKRj2BiYgJzc3NMRgB4MWxsbCCVSuH69etYWFjgHntkKQ4b6ewWGo2GU4lDoRCOHDmC06dP97QKA26StGhNkvbV6/Xisccew9GjRxEKhQDcKBq/traG9fX1gbEE7wRavCaTCRMTE/D7/dz5m4oSUcNlWZa54S41zyArmJrzptNpztyr1+tM1JS0dfr0aXi9Xpw6dQqRSIQVS+KYA+Au45lMBolEoqe2xqBAp9Nx0NTlcrGrUfS7x2IxLCwsIJfL4fLly1haWkKtVkMul+PGBiJxK5OG7nUOUUlkUo8oT0b7NY5qLZG7hFgz5MSJEzh58iS8Xi83jSUioUW3sbGBzc1NbG1tYXt7+9DrrgGwK8TtdvdN0Vdq0+l7+l9aqDMzM0wi29vbcDgcKBaL2NzcHJoxpPlCHU8mJiYwMjKCD3zgAxzvsFgsAIBEIoFkMolqtYqtrS1O8qhWq2i1Wkgmkxz4qlQqAG4YECR7CwaDOHbsGCKRCCYmJjgIrsy2Jd9vNptlf+8g+a/FDEYK4olNmWnetFot5HI5rK+vszpka2uLpaGUdi7WsKfXF9/rXpQjFHjUarV8KlK+z35AJey7APmoqLiMeOQkC4Y6L5MedH19nS2Y/dx5BwnkfzSZTKy53sl/SiVYafKTPIqsIQruhsNhmEwmzjoDMBSBW/LVU1JVNBpFIBCA0+lkRRHVWJYkqac/qM1m6+ls5HQ6Ybfbe9QQWq2W52AgEEAoFOISpEpFCBENJZbEYjFsbm4ik8nc4n45SIgbi/hVrEVPpEynDlmWUS6Xb0vUt8PdEDdtwJIkwel0wmq19iT1iFmYtwsS3wtUwr4LGAwGhMNh+Hw+TE9PY2xsDKOjoz2LrlarcdnUt99+G6+++ipPKjG9ehAWxn6BLGxKQacNTSRsSgNuNBpYW1vD2toajEYjJicnEQgEuKa40WhEJBLBU089hWKxiGw2iytXrqBSqaBSqexr7eG9ALXkcjgcOH36ND784Q/DbrcjEonAbrf3+DvtdjuCwSDHP8R6FJ1OB9VqlWsui5ucJEncjs7r9XIrNpqTRHTtdpsbGK+treGVV17B4uIi0un0wOYEKNcLkTUVTFtfX8e7776LYrGIWCyGYrG44+ltJxBZ74a0aQ47HA5MTk7C4/EgGAzyCYA2W9FQ2UujQiXsuwCRiNPphMvl4gwnOsLRZKIjK2VaUaBMnESHHdQlhLqZAL1HUQoY1Wo17kJvNBrh9Xp74gFkdYdCIS5dSyU19yuws1cQk6uMRiM8Hg8ikQgHYikYSKBu4AB65oroZqvX65zVR4RNtSxocxDrXBNEq5SIP5lMcnOIQXGHEESiFteM6L+mmiDk7xd9yf0Si/qVlrgXfzaJDogHrFYrB0XFRgnKTN69WPcqYe8CtOiocenJkycxNjYGSZJ6rEbyMy4tLSGZTCKZTPIk6hcku9fEiWEAnTqUSggCFXii2sQXL16EVqtFNpuF1+uF1+vFiRMn4Ha7eYFYLBZEIhGcPHkSsixjcXER1Wr1gO7w7kEkQ5Yv0Jvh209vLc4v0vWSNI3ImWo3kwtJlAlqNBqWl9brdcTjcWQyGcRiMT7hDIPEVNSwi1X1xOYLSlK8m3vazXPFjjNer5ebGPt8Ps5qJoONNta9hkrYuwBNCkmS8Mgjj+CjH/0otysSsxmbzSbW19fx2muvccAxn8/zB9lvUtxu1x/0RbQTyIdNWXYiQQHgyDp1QX/33Xfx93//9+h0OnA4HLBYLJziOzU1BYfDwYtibm4O1WoVqVQKhUIB8Xh8KMZJrJNCqhCRrIFbm+wqLUPqQwigRwJLG6IY+KL5SFm2pFdeXl7GxsYG1tbWkMvlOKA5yGOoHCOxEBRl0NZqNQ5mAzuvndsZSXcaA1GaOTIygoceegjBYJBPTZSIVCqVekoC7yVUwr4DxEQH6ivn8Xg40AD0psdS1w6qMtevChp9JUuBflYuut343wYVSstHCbI26/U6Jz6QFUg+XzrmUhcQnU4Hm80Gj8eDZrPZ41IYVMIRr4vuT6/X76ha6Afx7+QG6qf1FX2xQK/7gB7kVlHK3QYVtNEprVW6f9L607juRNp7EfxTFjCjWi1UF0e0sPer/6hK2DtA3MkjkQhGR0c5U49S0oGbmmvyW+dyOWQyGZZKiWm0wM3a2TqdDl6vl9tlkZaWrEcqyENBpmEib1LT+P1+7l7dzy1CJEJH9maziUqlgkajgWw2i+vXr6PVamFqagrBYBAGgwEejweTk5OcHEJ99QaxhZiYoFIsFrG0tARJkhAIBLizCW36yrERFUU0f2g+0vN32qyURoEYfKSyoLQRkptl0HzYALiLy/Xr11Gv1zE7OwvgZiAXAGZnZ/H0009zFxmaM5VK5ZaWezv5tncTaKRAejQahcPhwNjYGLxeb88crNfr2NzcxLVr17CyssI6+r2MW6mEvQPIoiNlyEMPPQSfz4dQKNRT2IWsFsqwyuVySKfTKBaLnK4qfmBU8tJoNCIajWJqaqonCCXLMicCZLNZ1Go1fp/7EbB8v8ERUafu9Xp7euspMxuV1h9JzjQaDTKZDJaXl1GtVmGxWHDq1Cno9Xp4PB7e3FwuF4xGI1tgg0g6pPAolUpYWlpCu93G1NQUZmZm4HK5ANx0aYjjLmYeKlPUlZvfTp+Z6Eoha9zj8QC4UfGS5uFuFRL3G81mkxPPNBoNZ7hSEFen02FmZgYWiwXpdJpT0inAL+qj+0kF7ybIqNFoYLVaMTo6Cr/fj9HRUe4vSaUoarUaNjY2cOXKFcTjcd409hIqYfcB6SytVitX4nO73TtK1MQPnyw/vV6PWq12i8vDbDazljscDnP9EUp+0Ol08Hg83I8uk8nc17ThvVq0/SL8IsgKIu2sciGJZC7euxhw2muN636BLG06MVGwj4KC/Z5P2OlzF10fyqM/kZo49hRXoPlHihuPx8PGxaApmLrdLo9ZuVxmqaNYh8ZkMkGSJG4uEAwGORBdrVY5/f5eciDEpCeKyTidTng8Hj4h0emk1WqhWq2iXC73VAnca6iErYCYej4zMwOn04mzZ8/i7NmzsNvtrBEm0MIhX9rRo0c5CJLJZFAsFqHX69nnTQvFaDRyvQdRCpTP5xGJRJDP53Hp0iXk83nk8/ke6dUgLSoliERIj240GtFoNG55HqUSJxIJxOPxW4JwoiRO2V1ExKCRjBJ0fY1GA7FYDNVqFVqtFslkEm63GzabjRUeSogEozza9zMYRHKm+UTPAW6c7sLhMNxuN4xGIz7ykY9gbm4Oi4uLeOONN/gIPyiut0ajga2tLeTzeQBALBZjGZ3T6YRer4fT6WRXmUajwenTp1mrn8vlEIvFsLS0xDU/dmvxinLMQCAAl8uFaDSKc+fOcfKT2WxGt9vlNZpMJrGwsID5+Xl2ae41VMIWIKbEOhwOTExMwOv1clVBarUkukPE/6MkD4fDgWazyTstFTKioj+keBB9mGQBUJuhQqGAcrmMt99+m+s8DOKxVYRIOuSPFo+FIuGUSiVsbm4iFotBluW+JLETWfc74Qw6KI26Wq3C4/GgUCigVCpxvW/xJLaTr5W+7ydfU1rZNI+VOm+j0YhOpwOtVouTJ08iGAyi2WziwoUL3Bx5UAib+oDKsgyPxwNZllEsFjlZiKxei8WCdrsNm82GI0eOIJFIQK/XI5lMQqvVcrp6u93edXIMWfHkeguFQhgdHcXs7CwmJiZYLQIAlUoF6XQayWSSs0fJp73XUAlbAO2oJOELhULwer1wOp1cAJ5STrvdbk9tAwo8UF2IVqsFnU7HgR06Qok1fcUFRRYmBdyy2SwXsNmLAuz3AyKZULRcbNQgkgctNkqrprGk8bXZbPD5fAiHw3C5XJwxVq1Wkc1mkc/nhy4Zifz0VLbA4XCg0+nA5XLdksosqkCUPmy6V1FJpKzcp4ToKgFudqShFlfUJITm2yBAdI+VSiUsLi6i0+kgFArxmrNYLFzKgLT/NpsNgUAAer2euz1Rf9BCocBxJ4qX0JjRKZl4gBrrRqNReL1ehMNh9vmTBd1ut7G6uorV1VVOkd/PU4pK2AL0ej1njU1OTuLRRx+F3+9HKBTiRSVmkCmVH91uF3a7nReQuKuTv4teQ7QYxefncjnMz89jc3MTS0tL3FpsEH2M/UDXWK1WkcvlYDQaOfhD99zt3ij0PjIyAoPBgFwuh62tLXQ6HU4CiUQiOHPmDObm5uD1emE0GtFut5FOp7G8vIxYLIZcLsfyqUEfFwqCtVotJBIJnD9/Htvb2zh58iR8Ph/PETHDUwwaAr2ELSpjiHzIIBCz7MQNQLS87XY7otEoPB4PNjY24PP5OHVdjCkcNCiOsbm5if/9v/83F137yEc+Ao/Hg2g0itHRUV6D9KDN59ixYzhx4gQqlQpWV1extraGer2OXC7HzXXJYCBLmuJQLpeL3S0UYyJ/OblbyuUyzp8/j3feeQflchnxeJz176qsb58h7qwOhwN+v58L9VB7JXqIFqOo/SQo/Yo7HXGJbGhiku87kUhAlmVejMNASgSysEW9r/g3AHzqqFarXHeBrCYKwvp8PgSDQa44J24Ew2hh0yKuVqtIJpPodrtc/J7cIko/fj+/vfhaYhOCbrfbUwqA0C85Rzz1kYUtNlUYFNDnWiqVsLy8zKfWo0ePot1uw+VycblicjOSUdTpdFg4QP5kSs2nDVKMLwUCAYyNjcFisXC2LZ1ELBZLzzql0hOFQgFra2tYXFzsO9/3Giph4+YkttlsmJychNfrxfj4OHeOoEWwUyaaSBiitUwfINU8oHZMFLWmzKhOp4NCoYB6vY5EIsHBOHKJDFIgaDcQfYBiVxlx/NxuN44cOYJwOAybzdbTYonartHftFotJyOsr6/jvffeQyaTQTabHRqyBm66jGq1GuLxOMrlMhwOB0ZHR+F2u+Hz+ZgkqIWa6DoTNeupVAqpVIprsrRaLS4qRRUPRWtbWQ+byoPSQ2zCO4ggsux2u0gkEnjnnXfgcDgQi8WwuroKq9XK9cbFzjtWqxVer5fXGVnIxWKRA8C0UUmSxKc5i8XC7s1ut8vjtL29jUqlgkQigZWVFZTLZcRisR7XJf3PfkAlbNw8frpcLjz88MMYGxvDkSNHEAwGIUlST8R9J4hqAFpUpBIplUrY2NjgxqDJZLJnwTQaDaRSKbYac7kcZ0kOiytEiX7Fn4Cb1h4Vc2o2m3j00UdRLBYBgBcaBWpNJhMqlQpLuy5duoQf/ehHKJVKyGazQ7WRATcX//Xr16HT6VhbTBvYkSNHYDabWTomutMoK7TRaODKlSu4dOlSTxlRv9+PdrvNBbTITaAM0gI3YiykbiD3ANW9GUR0u10OvK+uriKVSkGv18Pv9/NG94lPfAIPPfQQGwBGo5EtZACIRqM9SWiim050FwFgY6rZbCKZTCKfzyMWi+HVV19FIpFAOp3GxsYGJ8zR57jf81El7H8ELQxqZ0XZjGLSgQj6cER/Ivkp6YPO5/NM0kTeuVwOqVSKm4cWi0U0m00kEgnOaiQN57CRtBK3cwvRQiI3CNVvpkUmam2pZjaNJdU+HvTSqjuBNNlarRb5fJ7rUfv9fsiy3NO7kNRHZJmXSiXe1DOZDM87CoKTlJKyTTudzi1xl273Zi9EqntB8ZZBd711u90ePTa535rNJgejyXVG903jpzwhKyGWhaD5RfpqWqvZbBbpdJrVK+IJ+H6M2wNP2EpflqhM6JecQTtovV7nAE0sFsPi4iKXCpVlGa1Wi+uJkPKDyomS3lXsWE0ThHxkg7xo7gRaMPl8HmazmWWJohKCCJmqIBJBkQuASLrVamFxcRFvvvkmMpkMLl26xO6lQcxs3C1ow8/lcrhy5QrMZjNisRguXrzIda0lSerp/5jP55FOp1Gr1bC+vo6NjY0eF4YkSdja2oIkSfD7/YhGoxwos9vtAMDEnE6n8d5773EQN5/P71uFub2GaCyVy2WOC7z00ku4du0aV3qkWtVUyoCqR4ouOno94KZ/u9FoYHFxEUtLSyiXy1hZWWF/9fLyMvdmvd9kDaiEzcoN8ndR+rnYQ44gyozq9TrS6TTK5TIuXLiAH//4xxw5jsVi7BYRm6aKr0Hfi78bZpImiBacLMswm809WV+iX5vGl9wn4hhQ1li1WsXCwgL+7u/+jkvWlkqlgbcE7wS6V7IKKRBI0jQ65ZFEzWq1Ip1OY21tjY/hpVKpZ8z0ej3ee+89LqcwOzvLyV6BQAAAmJDE4z1ZkMNA1gS6b9L653I5xONx6HQ6hEIhbG9vIxgM4sSJE9wsW6mgEV+HLHci48uXL+MnP/kJCoUCFhcXEYvFOF5wkIXZHnjCFkEfgpgSLX4otJNT0ILcGBQglGWZE17ENkX02g8KyN9IpJtOp7G1tcW+WarjrIwLiPUfSqUSkzP590ulEnf0OCzjSUd6AByIbjabPW3QjEYjb4B0EqN5qFQcaTQaDnJTXQ3RpUebZzab7fFbD+s8peul9arRaLimj16vRzwex+bmJgqFAuuqRe21SNiVSoU78iSTSU7UKZfL7KM+6NPvA0/Yog+afIOpVAoajYYTZojAq9Uqrl+/jng8jlQqhXfffRfpdLonACEupGG3Au8VnU6HXUCkZHj99dcRiUTw4Q9/GOFwGBaLhQvniElDJGeMxWL42c9+hng8ju3tbayvr/MxdNgCjbuFaLnl83mUy2Xo9XrkcjkOUFLmaD+pJxGPVqvl4kPkyzabzQBuFqOqVqvcGmyYpJG3A91DoVDA/Pw8TCYTrly5gtdff53dIeQS6ReIJZdlq9VCPB5HIpFAs9lEsVi8b8XX7oQHnrABsFVNR6JCoQCbzYZarcYWC31wq6uruH79Ora3t/HTn/4UsViM6zofViK5W5BvsVKpQK/XI5/Pw2q14siRIxgbG4PRaITT6WT9NZEUlZaNxWJYWVnBq6++itXV1aE8st8LREKgruh3+/9ijEWW5Z6/i/GYgyae/US1WsXGxgb/LCpAxBiKMj4lxo8OyuVxJzzwhC1+OPRBdzodpFIpztQjf3S5XMbi4iI2NzeRTqf5ePmgWtJ3Ao2reHq5du0aqtUqbDYbZ5LR+NXrdWxtbSGTyWB7e5uDuodBMTMIeFDHsJ9lLAYdCcpSrIMITXeQr24HFAoFOJ3OPXs92oHNZjP8fj9HlClllYin1WqxrKrRaKBQKHCG2SDuxoMACuqSGsTn83HwR9k6rNPp8NhS+jCdXA67da3i/kCZ9SnioF0e+XweDofjts9RCVsBMUCzk1ZzCIdMhQoVA47dEPYD7xJRQiVjFSpUDCpUwlZAGXVXoUKFikHBrTnXKlSoUKFiIKEStgoVKlQMCVTCVqFChYohgUrYKlSoUPGP2KlhxKBADTqqUKFCBXBLs4hBzK1QCVuFChUqcDPJCxjcCpoqYatQoUIFemuxDCJZAyphq1ChQgWA3izmQSRrQCVsFSpUqGAMKlETVJWIChUqVAwJVMJWoUKFiiGBStgqVKhQMSRQfdj3ABLWk8herYe9ezwoXU9UqNgPqIR9lyCSFtsMHZaeePcDyj56KlSo2D1Uwr4DNBoNjEYjjEYjdDodzGYzjEZjTwpro9Hg5qiNRoO70Bx2UqJNy2AwcBd0ZTdqjUbDfxczyaiDDzXfpW7o1FfvQYFoAOj1euj1N5akmHFHXXcehDml4vZQCfs2IBKKRqOIRqOQJAnHjx9HJBIBcLMH3Pb2Nq5du4ZyuYyNjQ1sbm5yH8jD2KFGbGhqs9kwMjICq9UKu90Op9MJrVbLhGwwGOD3+7lDuslkgk6n48a6jUYDa2trWFtbQ61WgyzLKJfLQ9Ff7/2ANnuDwQCTyQS9Xg+v1wufzwe9Xg+TyQSDwQBZlrG6uopCoYBWq8XGQL/XAtSTy2HHvhD21tYWfu/3fg9/8zd/g2q1irm5Ofz3//7fcfbsWQA3JtWzzz6LF154AblcDufOncO3v/1tnDhxYj8u555AVo9Op4PX68XMzAx8Ph8+8pGP4Pjx42wZttttLCwsQK/XI5fLoVarIZlM9vi26fthhbIYDhG2TqeDxWJBMBiEy+WCy+VCKBSC0Wjkk4bJZMLk5CSCwSD0ej2sViv0ej1KpRIymQyq1SpMJhOq1SrK5TLq9Tqq1SqAwc022yvQ/DKZTDAajfD5fBgfH4fRaITVaoXZbEYymUQ6neYxERsSK/sTHuaxUnEDe07YuVwOH/jAB/BzP/dz+Ju/+RsEAgFcv34dLpeLn/ONb3wD3/zmN/Hd734Xc3Nz+NrXvoZPfOITWFhYgCRJe31J9wS9Xg+j0Qiz2QyPx4NIJAKPxwNJkmAymfi43+124Xa7MTY2BpfLhWq1ilqthlqthng8jnw+f+isRWqoazQa4XQ6EQgE4PF44PV6EQ6HezrNm0wmBINBeDwedinpdDro9XpotVrUajWMj4+jVqv1WJHNZhOVSuVQkrbYmNjr9WJiYgJWqxWRSAQjIyPsGtHpdOh0OnA4HCiXy6jVami1Wmi324duTO43RFeUGFcRMx0HcYz3nLC//vWvIxqN4o//+I/5dxMTE/x9t9vFt771LXzlK1/BZz7zGQDA9773PQSDQXz/+9/HZz/72b2+pHsCEbXdbsfJkyfx4Q9/GJIkYWRkBHa7HcDN46fZbEY4HObTxIkTJ5DJZPDSSy9hfn6e/ZCDOAF2gqiAUf5er9fD4/HA4/FgZGQEjzzyCEZGRuDz+TA6OspuD3KbUBd6MVBLhN5utzExMYFHH30UmUwGf/M3f8MW+Pb2NsrlMoC7P+oPqsVJZG02m6HX63Hq1Cn84i/+Inw+H9xuN1wuF7rdLp82VldXsb6+jk6ng0KhgHa7jWaz2VNNbi/JRUlg4tfDAprDNEfJeBDdmJ1Oh2Mpg3T/e07Yf/EXf4FPfvKT+KVf+iW8/PLLiEQi+NznPod//a//NQBgZWUF8XgczzzzDP+PyWTC008/jVdeeaUvYdfrddTrdf65UCjs9WX3gD5Qi8UCm80Gr9eLkZER2Gw22O126PX6noltMBggSVIPCZE/l4JIg1xjtx9E6aJywhIJ0z36/X4Eg0H4/X6MjIxwkJbGaafXIaKxWq3weDxwuVzw+/2QJAntdpsrp+3ltQ8CaH4ZDAZ4vV7Mzc0hFApBkiS+91wuh1KphGKxCEmSYLVaUavVoNPp2MLea0Lt5/oST5LDDro/cucRURuNxp5gOZG3uCEOCvacsJeXl/Gd73wHX/ziF/EHf/AHeOONN/Bv/+2/hclkwq//+q8jHo8DAILBYM//BYNBrK2t9X3N559/Hs8+++xeX+otEBUPgUAAx44dg8vlYv+rVqtlC0cEBdja7TZMJhP8fj/0ej1Onz4No9GIXC6HhYUFyLLMu/ego98kFY+QOp2OJ73ZbGYXCVnV/aBc+LSAaHN0Op04fvw4dDodtra2UCqVeLzv5oQySAtMBN2v1WrF+Pg4nE4notEonE4nbDYbTCZTz8mGyNJkMsFqtaJcLt9yfN/LMqD0fuLPwwjxJCcGdT0eD9xuNwwGA1wuF6xWK3Q6Hc9bmmetVguJRAKJRAKNRgO5XO6eT3p7jT0n7E6ng0cffRTPPfccAODMmTOYn5/Hd77zHfz6r/86P0+5mysni4gvf/nL+OIXv8g/FwoFRKPRPb1uIiGLxQKDwYDx8XF84AMfgNfrxfT0NEwmExN2rVbjawZuBIIajQa63S4sFgui0Sj8fj9MJhNOnz6NpaUl5PN5VCqVHSP9g4Z+gS36mcaK1AykEDGbzbxQdvoslYRAhGQ0GmEymfDBD34Qp0+fxvz8PDY2NlCr1VCpVNgNMOzQaDRwOBw4ffo0RkZGcPz4cQQCATgcDuh0up5Nrd1uQ6vV8kmmVCrxZkgB7b22svu9zqDPVSVEKandbofH44HZbMaJEydw9OhR2O12RKNR+Hy+nrhKo9HgGNRbb72Ft99+G8ViEQsLC6hWqwPh195zwg6Hwzh+/HjP744dO4b/83/+DwAgFAoBAOLxOMLhMD8nmUzeYnUTTCYTTCbTXl8qQzwq0Y5stVrhcDjgdDphMBjYihaj9LRgiIQBMPEAgMvlgkajgSzLsNvtfKwVX2PQsdNGSvdOJwby+RHJKAM6yq8iiIQoiGk0GuH1etkVQK95GAgbAHQ6Hex2O1wuFyRJgtFohMFgAICeuSWexshq7Ie9nku3M54GGXS6oxObXq+HJElwOBywWCzweDwIBoOw2WwIBoMsoSTCbjabTNiBQICFEpR3MQhrds8J+wMf+AAWFhZ6fnft2jWMj48DACYnJxEKhfDiiy/izJkzAG4knrz88sv4+te/vteXsyvQYqBIvd1uRygUgs1mg8FgQDabRT6fR7PZRCKRQD6fR6vVQrVaRavV4v/X6/U4evQo5ubmoNfrmfD1ej0++clP4tSpU1haWsKbb76JUql0IPd6LxAnKm1QFEfQ6XSYn59HLpdjNY3JZILNZoPNZus5nup0OhgMBg74kEVJIN+4TqdDKBTCuXPnEAqFcO3aNbzxxhtDtdH1A21gZrMZgUAAo6OjrJ4BblrNzWYT+XweyWSSH6lUilU0+z0Gw0bWZFHTScXtdmNubg4ul4tPJ0ajEX6/H4FAgEmajCwxwYv+Nj4+jmq1ilQqha2tLWxubva4qg4Ke07Y/+7f/Ts89dRTeO655/DLv/zLeOONN/DCCy/ghRdeAHBjcL/whS/gueeew+zsLGZnZ/Hcc8/BarXiV3/1V/f6cvg9gZ0tEdqZLRYLwuEwB9FIM5xOp5HJZFAsFnHx4kWsr69zkkej0WDdrMViwTPPPAO/3w+73c76ZKfTCZPJhGKxiB//+MeYn58fKsIWQScNWZZRrVbR6XRw+fJlpFIpBINBVKtVWK1WuN1ueDyengxIg8EAu90Og8HAR1ag1zdO7pFwOIzHH38c09PTMBgMuHjx4sD4Ed8P6B59Ph8ikQjcbjcHpsmiFgk7lUohnU4jlUqxT79fHGC/Ao/0u0EdczHwHwqFMDo6img0ik984hMYHR2F2WyGzWbjZC56VCoVNBqNnnszGAwwGo3odrush4/FYjh//jxvqgc9FntO2I899hh+8IMf4Mtf/jL+8A//EJOTk/jWt76FX/u1X+PnfOlLX0K1WsXnPvc5Tpz54Q9/uG8a7N0e8Sg1utlssi640WhAlmVkMhmUSiXk83n+fblc5gQRcgkUi0Xk83l0u132kRmNRr43CnRotdoD36374XYEICoG2u02Wq0WarUaisUiW83ky67X66jVaj2ELbo8bDYbXC4XB3PFBUEqCpvNhmazCYvF0mOND9qY7RbikZ3cfCR3pHujeVSv11GpVFCtVjlBS3SP7AVEN5WokiAMQ+6AeHKz2WxwOBxwOByceESuJiJpiiOVSiXUajUYjUbUajU+FVKmLlncBoPhntVK+4F9yXT89Kc/jU9/+tM7/l2j0eCrX/0qvvrVr+7H2/fF7SYeLYRKpYLNzU1ks1nIsoxcLgedTscWDmUxyrLMdUPoa61WQ7lcxuLiIpxOJ3w+HxwOB0KhEMxmM0KhEDweD/x+P2w2Gx/JlIqTg4RIijslZ9CxXSSRS5cusczv0qVLnHREvj9aADabjV1N09PTePTRR2G321k+KRKX2WzGyMgI3G43n3ZMJhNvjsMGrVbLpwdJkuDxeODz+XpkokTU5XIZ29vbuH79OtLpNIrFIsc+xLIAtOnvBv2kmfR50wmRroEkbbVabaDmpxLkXjKbzXC5XJiamsKJEyfg8XhgtVoB3BAoFItF1Ot1XL9+HcvLy6jX68jn8yiXy7BYLPD7/bBarZidncXZs2dht9vRbDZ7agiRhX7QUGuJ/CMo1TydTsNoNKJSqbDlSMfSVquFSqXCEjPl0dRgMCAWi2FxcRHFYhGPPPIILwgKXJJ7hH4elAUhqj/ulE5PfmxSx1QqlR4JlVKWRoQtSRLGxsbgcDjQbDYxPT3NrhLlKchoNLIP0uVysTRrWAOPND4kgbTb7ZAkiZU1wE13U71eRy6XQzwehyzLPSqZnTL0dvP+yp+J9CnIrtfr0Wg0eDMeNINCCRpTq9UKSZIQCoUwPj4Om83Gro1KpYJEIoFyuYz5+XmcP38e1WoV2WwW5XIZVquVcyxarRbPScoDEAuaDYJvXyVs9EbmyYohORmlT++mmBNZJYVCAVarlY+z4nGTdvRms4lsNjtQGZCiVOxuromeT4tc6e6h+xfTz6lglsfjwfj4OBMyLRDghsXf7XbhcDjYp5hIJJBKpYYuPVtUhjidTs50FE81ohKJ5h/pguk0cy/aayIb2hxJyub1etnH63K5oNVqkc/nkc/nUa/X2eIHBtcNJQZqs9kstra2YDKZkMvluHhWIpFApVLB9vY2W9v1eh3NZhONRgOlUgmdToezS+neRZ/3oLiHVMJGr566Wq2iXq/3WI7kjxXTVfu9RqfTQTabBQDUajVkMhlUKhX2Y2s0Gni9Xpw5cwajo6O4dOkSZFlGq9W6b/e6E0SJHv18p+cT6H9EK7BfSjsVdtLpdJBlGdvb23A6nfj4xz8OSZJgsVhgtVp5rMi/PTU1hX/yT/4J0uk0Xn31Vbz66qu3+HUHFUSWRqMRo6OjmJiYwMzMDDweD0vPaLxIVlYqlVAoFNgKJOJWkjZw+89JqZnX6/Vwu92w2WwIBAJ49NFHuZKi3++HRqPB0tIS5w288847KBaLA1nATDxdkH96fn4e8XicCZxImBRexWKR74c2QTpFGAwGRKNRZDIZHjOtVssxqkGpvKkS9j+CFgFZ2CI5Ky0cen6/16jX6ygUCpAkiS1sCl5oNBpYLBYEAgFotVqsrq7uqK09CNzLZFRK/pQ/AzcXF7lQaPG3Wi04nU6cPn2a065FvT1ZheRKkSSJKyMOA1mL0Ol0LDkjF49YV12cf2TZUhExcf7t9gQkuk3oaG8wGGCxWCBJErxeL8bHxxGJRDibV6PRMLGRFT4oroDbgTa7bDbLfvdiscjigWKxeMv4EehnnU6HWq3GxhptcP02yYOEStgCaLdWJnkoF8rtPjjatUnDub6+zsE28l1OTEzA4XBgeXkZZrOZ/29Y/bOi/1lUkoh/V6LRaLC1s7y8jHfeeQdOpxNzc3MIh8M9hGO32zE6OgqHw4EjR44gHo+jVCpha2sLuVyON9dBWFBK0KZDOuCpqSmEw2EmQ+DmRkep0dVqFcViEbIsM4HQKWy3/mqLxQKHw8ESSXpPv98Ph8MBj8eD2dlZuN1u9gF3uzcqT5JBQYomYHAVI+Q+ouukDa5Wq/UIA3YiXZ1Ox6c6SZLgdDpht9tRLpf5lEOn60EgbZWwBZAfFgAHHpSBnTt9YBQ0KpVKWFtbw/z8PKsBKJB28uRJlEolXLt2jdURpGkWr2WYIFrTymvvR94USCsUCnjnnXfQarXg8/mYVMQqah6PBzabja1OnU6HbDaLV155hTv9iF1ZBgli3fCJiQmcPn2ag6mi5UqEXS6XUSqVkM1mkUwm+VQiBhzp+f1AzyG/v8PhwBNPPIFz587BarXC6/WyFp4CjRR8bLfbCIfDqNfrTPhEhIPgthNB5CluZqVSqee0Qo+dTmIUtKRMSK/XC7/fD7fbDVmWsbW1hVQqxQ01BmFuqYS9A5QEvdtjoahRLpfLkGUZZrOZJxUljwBgH6YYaCMMms9wr0F+xm63i1KphHQ6Db1ez5aRCFGr7XA44PV60el0OCtytzr7gwJZ2SR9JMtVSS50hBfjKEpX3O3uU/S9WiwWDnD6/X6Ew2FOaKLNop+2nSR+pGQa9PFVEumdTnYEuh/6XEhqS9mO5BenbOZBgUrYO2AnK/F2k4B2cyLrxcVFVCoVzM7O4vjx41z1z2KxoNvt9gj16Th8p/c4TKDxyufz2NjYQLPZRDweRzqdZm0t+bSJYKjgv81m48CdKEUbNFCSjNlshiRJcLvdnAgE9Er5rl27htdeew3pdBqbm5u8oYmkJAYAlUd0UVN88uRJfOhDH4LH4+FyECQrVJa9FZUQJpMJTqcTlUqFk1BIUTGI4wvsvFZ3guhuczgcXIBramoKVquV1TIrKyvIZrMoFosD4Q4BVMK+Le7lAyKLqFwuY2lpCdvb20zgALgOMgCuNQJgaFPVRdytNhgAEzbJ2ZLJJDKZDOx2O7uR6LV1Oh08Hg+63Zs1tInQByl4S6Brps5FkiTB5XJxrXBRklar1XD9+nW89NJLyOfziMViO2qg+xkPlERCnZHOnj2LX/iFX4DP5+spLqVUVyhfkwi7Wq3C6XRyt5tisbgvY/R+sVufvhIkNXU4HDh69CimpqYwOjoKi8UCjeZGwbaVlRXk83km7EGAStj7AKUumSwTZcSdnrNbmdYwQXTpKL9XgsaJNLGyLLOLQPl/oupBeawfRIjXKyZiEChAXS6X+bGbYzjNGdFaNJlM8Hq93LKN0rPF9xSrLIpKG9GKp/gBJfm8n2YSgwiSjNKJgyr6GY1GHhPSwVer1YE6WaiEvU8gvyX5qGmCkH+behZS6izV0z5MEGs3A/3JWiTmXC6H+fl5dDodRKNRdnnslIYt9j4UCX2QxlF0iVD9EBoX6ixDltzq6iqSySQqlQpqtdqurUey2EdGRvDxj38cR44cQTgchiRJtyTmUJCOApytVotLAtPziKxHRkZw5MgRpFIpzikYFNfA3UA5/6hDvSRJrIufnZ1Fu91GNptFtVrlCn1Uf2RQoBL2PkJM/yXrSrRwqIAUFaQ5TNhJv9vPZUKSxnK5zGUs2+02SqUSWq3WLQE68fXF1PdBA1nXpMOnIJ6YCUq9KzOZDJLJZE+WoRL9iJKsRaohfvr0aZw5c4atR+WJjlQn9XqdDQXy25IVTTJEt9uNkZER9m0P2mZ4r6DGBj6fD36/H6FQCOFwGNlsFrFYjOV8mUymbxD8IKES9j6Byj2GQiFEo1H2jdFxiwqli1lUhwGiFUPqDoq8ky6WjuPKzjsioZArSRlcE5UQVAOj0+n0rTY3CDCZTHA4HJAkif3IAJgEKpUK16qhFOk7SfbEjcpkMvFpZHJykotJiTW2iaAbjQZisRhyuRx3Tmq32yyn7KefV9bfHsQx3i1E11EwGMTExAQHY6mk7erqKnK5HLLZ7C3uykGAStj7AI3mRmW6J554Ao8//jg3qNVqtajX68hkMsjn85BlGaVSaeD8ZPcK0dq1Wq1chMfn80GSJNRqNa56SMW1xIQX2szo1EGbGQAmIArikWwtEAjAYDAglUod2H3vBK1WC7fbjfHxca5SCNwsodputxGPx/H2228jkUiwUmYn0hZ99mSxe71efPzjH8cjjzzCTRFE90an00Eul0MqlUIul8NPfvITXL58mRNFDAYDjh8/zooQ4GbVRgqGUr2bQalYdy8QU/QdDgfOnj2Lp556qqcT1OLiIv7yL/8SqVQKm5ubrDxSCfsQgxaKwWBAMBjE9PQ0XC5XXwub5FLD3kmFIFp+dCQneZ7b7eaAGrk8KCEB6C3ARWOi1CADvTWlSX0h+oUHCWTNKS1scomR1pe6ypRKpdtadOL40v1bLBaMjo7i2LFjkCQJNpuNtdP0XrVaDfl8HtlsFqurq7hy5Qp3vbFYLAiFQrxRiJXplIHJYQeRtmhh00mQLOz19XUkEgkUCoWBI2tAJey7xp0SF2w2G6xWK4LBIAKBAAKBAGeUAUA+n+c6x+l0emAyqN4PaIGL6dCzs7OYnp7mqnBOp5MLYlFdcercQw+LxYJTp05hbGwMo6OjcLlc7E4Rg4rkb6Uqc/V6vec5gzKedNJwu91cVhe4WXOGAs/VapVPFDtZ1hRcpLZ1Y2NjiEaj3CTa6/VyIhZwM+O20WhgYWEBb775JmRZxvLyMmRZhtPp5LRss9nM0j9xDEnBQj71QRnXu4EYQxodHUUkEmG/NdWkj8fjqNVqiMfjKBaLt/0sDhoqYe8Syvoi/f5GOuFQKISRkRFMTExgfHycA0/AjWbDb731Fh+BD4srRKPprXf90Y9+FB/5yEe4o7rFYunpg3n9+nXuSk0+Q0mS8IEPfABHjx6Fw+FAMBjsKUBEx3IaT8rio2LzgxR4pI1FkiSEw2EmVK1Wy+NAhYlkWUY+n++rDBHdTJQ+Te62D33oQ3A6nZiZmUEoFGLLGwAXISsWi3j11VfxJ3/yJz2V/8j/7XA4+PMxmUw9p5parYZSqcRB8UHz5+4G4mnv2LFjeOqpp+ByuTA9PQ2Hw4F0Oo2VlRWkUilcv34d2WwWhUJhYE8VKmG/T4gprpTmSkdgSvGlRUcBoEKhsOMCHTaIShgiAJfLBa/XywWvaBw6nQ5ba6VSCYFAoKeAP1WRo9ohRqNxR1cHkZOY2j+IATGSyIluG3I1ULKQWGv9dhA3K4fDgUAgAEmSYLfbe6ocUrBQLNOaTCZRrVZ7XE/kUlJ2VRF12mLwd1ggbnImkwkWi4VjHlT8ik693W6X5bVio4hBvV+VsHeJnY6q3W6Xm3eazWbMzs7i8ccfh8/nQzAYZKKmBVkoFLC5uYlYLMa9H4cZRB4mkwnHjh3Dxz72Mfh8Phw5coQ7xhMh0LG+0+kgFArh4YcfZn9+pVKByWTCxMQE/H5/T0laoFdLS7+jwN2glcCka6SmBdSM2Ww2cyp6qVRCqVTq6Qu6k5RPTMSi55KMTyRrek6n08G1a9fwwx/+EJlMBu+88w4rQki543K5cOzYMczMzCASifAGSRsJGRZkcZKLYBDGdyeIJxGfzwer1drj36em35TNqdFouKE0daUfVMuaoBL2XUKZvUcLkyzJmZkZfPCDH+QjPVVBo4VQKBSwtbWFra2tHotnWGEwGOByuWCz2XD06FE888wzCAQC7MsXCZYscQAIhULw+Xw9Fp+YaNQvEUbMxKOgmFL+NygQXSKBQIA3NTpllUolFItF7nJyO0UC1Q8RrXKz2cwnETrFiYX5r127hh/84AfY2tri96DPQq/Xw+l04tixYzh9+jRsNht3Fq9Wq1zXplgsIpvNsuU5yGQmBmStVivGxsbgdrvxyCOP4JOf/CSPld1uZ/88JRCRiobK/dLrDdJ8IqiE/T5AxGKxWODxeGC32+F0OmGz2dhfSaU/qbZxPp/nbLNBXgC7AW1WVE+ZfKEWi+WWbuBAb4MDIg76nr72S7hRkrFSmz1oZA30WtniaYGuVZTM7XbDoecQgdP8o9dtNBoc1M1ms+x/JutYp9Px5ySmrhsMBt5IqZY0Xdsgq5jI5UHp91TX2uVyYWRkBC6Xi5thixsbgJ5mw6KhoExwGzSohH2PIKtar9djdnYW586dg9vtxkMPPcQFdwCgWq0iHo/jtddeQzwex4ULFyDLck91uUHdzW8Hsmbsdjump6cRCoUwMTEBq9XaQwBEMPRVXCRkKYvfiy4Q0fqm1xB9vtTeSQyIDcI4ippfimlQDepOp8NzggpdUd/G223gIlmLJw2S8JGm+8UXX8Tm5iYuXbrEPUNpvO12Ox577DEcPXqUVSZi+jqlyq+urvL15XI5Hu9BApEspZVLkoQjR45gZGSETx8mkwlutxter5fLQlA6frlc5o2J4gGVSgWSJHG+RK1WAzA4qiNAJex7BqXvmkwmhEIhPPTQQ/D7/dwZhQI49Xod2WwWFy5cwPLyMjY2NjjqPswWtijlGxkZwdjYGPx+P0wmU08qOXCTtCmI1el0mNCI2PspPJT/L76O8jFIi0qZiWixWDi4SpawLMvIZDLclHg399Av65PGmRJk3n77bczPzyOVSqFUKqHZbPK1mM1mzMzM4Ny5c/D5fKxcEV+/VCohmUwilUpBlmXWyg/SXBVdO8FgECdOnIDP52OFESlDRC05WdR0aiDFTKPR4IAkNTKg5w+iIaUS9l2AgmbkA5yamoLL5cKRI0fg9/u5dCZZfel0GoVCgcX45A8cNIK5F4h6aAq4UuF3Co7R0Z9qCostmyRJgs/nY8kVJRaJJLRbq3mQ5HzArRUFxd8DN5NZSqUSW8C7AbWaI103bXbk8qAsUXpdkkGSj9rv97MKx+Fw9CTy0Okln89ja2sL6XQa5XJ5oE4uBKqFTr7qaDQKt9sNSZLYzUYbfD6fRyKRYJ88pf+TwVSr1Thw7vF4EI1GUS6XEY/H+bMZpPWqEvZdQKfTcceO2dlZ/PIv/zKmpqZYD0wdU0hb/NJLL3Fn9GvXrvX06Bu0RXA3EI/85DP0eDyciFCpVLhUaKFQwGuvvYbFxUXWH7fbbczNzeHxxx+Hw+GA3++H3+/vW35UfE8APR3AxfZWSnfKQYI2MZPJxARCoE7cqVSK63rsRuOs0WgQDofxwQ9+kHtD0mvT0T6bzSIej2N7e5vJhqr4TU5OIhgM4tSpUzh27Bh3lhGDlY1GA8vLy/jpT3+KXC6HeDw+cPprjUaDSCSCc+fOwePx4Ny5c3j88cfZ9USuD6rTMz8/jx/96EfI5XLIZDLIZrPQ6XQIBAJc73t8fByBQAButxvRaBSVSgWvv/46CoUCb3yDki+hEvZdQGzz5Pf7cfToURw/fpzdI2Sl1Ot1lMtlbGxs4OrVq3zMpPKUgxrQuBsoU6SVFjaNgSzLWFtbw+XLlznQRiU9p6amAAB2u31HH2m/gKT4EMl6UI6wtKHtVEVQrLdMxbDuRNbkfgqFQggGg7f0W6QCT7RZktVOvmvaFL1eL9xuN392yiBcPp/H9vY2Z/wNElkDN7OJR0ZG2AUZiUR6CmvRyY5Od9evX2c3TyqVgtFoRLlcRiAQQLvdxvT0NDfLsFqtqFaruHbt2m0/w4OCSti7ANUxdjgcePTRRzE7O8tpwbSjkxxqYWEB8/PzPFGo67K4MPupHoYJ5D8korbb7ZAkiTvAU+o1SddkWebu5qSKoN8BgNPpZMIWLWVRBSH+jmpohEIhVCoV6PV6XL16lQlQ2dD4INFPKUPjJxLCTpuNTqdjJYff70c0GkUwGGQdMQUx8/k8qtUq9Ho994w0Go1cJuDMmTPweDzwer09SUbdbheyLGN7exuFQgHb29vI5/MDmZ4tbi6k3acNSqPRIJ1Oo1gsolgsYn19HcViEdeuXcPa2hry+TxKpRIbDJlMhiWhsViM8yloDtOap9PHoARdVcK+A8gH6Ha7EQ6H8alPfQof/ehHYTKZ2GdNlefK5TJef/11/N//+39RKpV6xPiiNOpeFsFOR34xANUP+7HgyJdvsVhgt9s5s9Fms3H2WLVa5ZrC6XQayWSyxx+YSqVYi+7xeDAyMsI+19upR0QFyuTkJCswLl26hGq1ilKp1NMM4qAI506fB8n97tQxh1LwxSJPlEFKvUALhQLriA0GAyRJgiRJLDV94okn8NGPfpTlfPQZ0SORSOD8+fNIp9O4evUq0ul0TzfyQQHNA7png8GAfD7Prov33nsPKysrPaosihWQz5qCifV6HQaDAYVCAX6/H7VaDYFAgBOcSKJKxsegQCXs20BMN5ckCU6nEz6fj+s2iEEfIu1sNotEIoFKpdJDHvdK0uL3SktN+Zz7CWXQkYKxt1N70P/R/yr/1u89lN+T0oJklaQrpke73ea6IrRA7/Q++wGlRlzZ+VzU/fZLEiKQu40qH1qt1p4iT6JyBrjZ9Zw2UrvdDrfbzd17SG4K3Ow/SpurmCQzyComCl5TkLVQKHBhsVQqhUQige3tbcTj8R5JKHDzhENSStFSbzabbCAoyx4MClTC3gEmkwk2mw1ms5kDG16vF5OTk0walKl49epVvPzyy5wGXCgUOIV4t0TRj5xFH624QYiuldslNewnSRFRkI+UOpxbrVYA4PrfkiTh+PHjaLVaPfcxNzeHs2fPwul0YmRkpKfwPhF/P/8h/Z4sT0p1/+hHP4oTJ05gc3OT4wb5fJ5dMaLufb/JmwilXq9zv0bgpgFApXfb7TYqlQqMRiNqtdotpK7RaFgJ4fF4EA6Hub4KzUHybTudTkQiETzxxBOYnZ2F2+1GKBSC1WrFkSNHuMqfOHdTqRTK5TIuX76MN954A+l0mguSDZIrhEDXlM1mMT8/D6vVikQigXfffRetVgubm5vIZDIoFotcHlX8P+XJlGIt2WwWALiCodFo5GC4xWLhjWEQxkQl7B1ALg+Hw4HHHnsMv/RLv8SprVRxjTSdCwsL+PM//3NuL1QoFO5ZkiYSlaiaoO+Bm4kkZCGJ6bTArYS0HxONCJuy59xud09HeJ/Px5mfx44dAwDWJet0OszOzuKRRx5h3zdloSkf4riQy4TGiNwhlAZfLpdx9epVGAwGDnaKG2e/LuH7Adog9Ho9u2k0mptNB4xGI0KhEIxGI1KpFI+JeF1ioDEajSIcDnN3FNHiI1KnQK7RaES9Xmc3k8lk4iL94vygvoWyLPcQttg0YtBA64lkohqNBhcuXODkIbFMwe02HZoLZGlnMhm0221EIhGul02EbTKZuDnGIAS1VcIWQCSp0dwoFRoKheB0Otk/a7FYeiYHFeQnfScdq8TX2+l9APRYkCIRkVyN/JwiiVPiBVnW99N3Lb62uGkoXR7ATR8tFdrx+Xx8vNfpdFwfmlwpYgLN7dwq4iZI/2MwGLgvIXWhMZlMLKEkyRtws0GC+Jr7OT6k4KCgM40NuTfIxWGxWFhCRvdG40Wp/2JlQxGiSomKQRFJK11VohuEXAi5XI4D44MiX7sd6DMkf7YYQL0bN44YT6AHuUJoYxXn5iBAJWwBJKA3Go145JFH8KlPfQo+nw8zMzM9ATUigAsXLiCbzWJxcbGHrJXaWwJZiGLlOrKYaHLQUd9kMvU0UiWtbbPZ5GMf+dyUXVv6kdtegjYNstTo2oiA6dhPpHPq1ClEo9GeDZHKsO7kJ9zp2pW+SLKIvF4v2u02d1Kp1+tIJBJIJBLI5/N49dVXce3aNdRqNeRyOSbH/fDTkoVHMrlUKoVGowGHwwG9Xg+bzYZIJAKXy4VMJoPZ2VluwkvWHH3+Xq8XY2NjmJqaQjAYvEW+RsWOSFLpcDjQ6XQ4ICyezNrtNpcRXVpawt/93d9heXkZsVjslnZtgwzxc+t3QhW/3umkS+uNFDTk63e5XHA6nVyGll7roKEStgAqIGO1WjE1NYUPf/jDfHSlEpZiPeeNjQ1sb28jFotx+UoKiN3uPWjXpkASBdDIUiTfGfmENRpNTw1tSqtVNp+9X8oI0XdO1mOz2eQNje6TTgvUQQa4tX64csEp/Yw7kba4MEVXjMPhwOjoKDqdDgfSUqkUkyGVNW00Gvz/ez1eYiCQ2nORooPcQqTgoI7dBoMBtVoNsiyj2+2yrEySJASDQYRCId7glOMgWtE0LuKYEKglWT6fRywWw3vvvYf5+Xk+hQwCIe0W/ea6eP/9AvT9QGve6XSye81isbCbrVar3XY932+ohP2PEAM8Xq8XIyMjTKLK2hjkj3S5XGg0GohEIpibm0OlUuG/i19F0GuRVU2vTxYV+c/ob3TUbTQaKBaL/Bo6na4nqHK/ZWxESuVyGSsrKzCbzdwnj04FoptDWTNkJ9WL6JcX61zTV1FZ0a8UK3DTVSK6EyKRCGZnZ9n/eT+UEN3ujeL46XSaP0MxaKrRaOB2uzE5OQm3280p1wBY0UE1WqjovmgxK+ekMkhNoLGr1+uIxWLY3t7G5uYmF0IaVDXI3WInd9rtnmsymeD3+zEyMgK3282fCylIBq1BtkrYuGntBYNBfPKTn8Ts7CxH5sUWVQDYh03p6dQK7MSJE2g2m0y8tGCUC0jUZItWJi1EkZzoe1pUmUymJ7tQdIXsNsi5VyBSjcVi+Mu//Eu8+uqrOHfuHD796U9zpTQ6ldyuwBNdP72eWBua2olRfWYAfAKino4UrBRJjMZVkiS2Uj/ykY/g6NGjWFpaQj6f5zKioiROvJ73CzqFbG1t4d1330UwGMTMzAxv9mQpHz9+HH6/v6fWBRGJwWCA3W5HOBxmlQd1rhE/a5o3ostJHGsqupXL5fDKK69wgHF7e5st62GyrvtBqSpSntSUz6WTrtfrxcMPP4xjx47B7XbDarWyIRKPxyHL8kDVrVcJG7dG5EkWJUbvlc8n35fZbIbNZmN/F7k5lIuHJlOz2ewJQCmDjiJR0aPRaPS0gOqnZb7fi47eq1wuY3V1Fel0GpFIBJVKhSV6RCK7DSTS5kRpxZQ9StmLBLJQlZsj0CuJpNOLXq9n0qvVatzBfL/VEOTjT6fT0Ov1PQkYdN0ul4tJgmpQi/ENvV7PwW6l5UzjK45zPzcTjSs1ml1aWuIg+SBZj/cKpZqIcLv1IOZYUG4F6dvF+UdrcVCgEjZ662JQQXQlEYjPBW4sOGqrRCTd7XZvaW0lHltF3ytpdcmapLRqcn2QEoQmz8bGBtLpNKrVKgqFAreWOuji8tRFp9FoYHFxES+99BLr1ScmJtgvT4Ebun/R5VGv19mypuxQscO66GMly91sNiMSiXDVNtIcK61tJZGJKfWtVqtHKrkfvuxisYhEIoFut4vr169z66pwOMzKFoPBwCcumjvKgvpKIlZu0PQ9xTXEUwv58BOJBBecohTtYYe4zkR9unIzE9ee0WhEOByGy+XC2NgYXC4XZ45SED2fz3O1TTXTcYBAHzTJ0ChLTNmtm0DELvoSd4pa9yvc32g0oNXe7ERTqVRQq9Wwvb3NdUfS6TQHMYmU4/E44vE4EzoFHQ86st9oNJBOp7kKXTwe567plNbv9/u59gU9RNcHpRdXKhUsLi6ynn19fZ1rOpOqgxQYkiTh9OnTmJiYQDAYZJ+vktyUbidqKkDaZbHE6F77cinwSfVUzp8/j2azyZsLneBortF1ALcPoPVTAymJmmo6NxoNrK2tcY3spaUlxGKxHnnjsEN0O5J7CLjZWg3o3bwtFgtmZmYwMzPDlfokSWLNfLFYRDKZxNbWFq/PQcEDT9giiAzEwJeyYBP9XQkidvH54usQORH5UFCDOl+QCqRarUKW5VsImwrXkIuALOuDDhiRrxYASqUSEokEyuUy0uk0ZFlGu91mC1t0E9Fm1Wq1UCwWkc/nUS6Xkc1muYgPfSW3kLjxUTCPdMfVahWNRqNHTii6WsSx7Ddmuw1W3S2oOUGlUoEsy0in0zAajSgWi1whjq73TsoZMUlKnIfi/CTiptMZ9Y/MZDLI5XI8ToMwd/YCSkImmav4vVjOgE4xlNRF0kcA3ImmVCqhWq32GBWDggeesMUFLcsyLl68iHK5jJGREUxNTXGNZyJIWoBiTzgqMEMtiMg3SGQs+qXJsiYFAf1elmXWctNXkfDF1x3UhqjUs7JWq+Gtt95CoVCAzWbD+Pg4NysgzTDVW67X6z2SRep00mg0elpciX0PaTwuXbqE7e1tjI6OwmAwYHR0FB6Ph8tt0v+VSiW89957WF1dxebmJjY2Nrg11/1o4Nvt3tDuLywsQJZleDwebG9vc/3lEydOcBat3W7vceGIGz+luovNeEWJJblSNBoNSqUSE/Rbb72Ft99+G8ViEalU6lAEGQmiZU2nFhoz+kplEUi253K5MDk5ienpaUiSxEHo1dVVvPfee8jn85ifn2fDapDG6oEnbOCm64IIO5VKYXZ2FlarlQNV5EMVuy3TB5rP55FMJjnNlcggk8nwkb5cLjMJi5NJ/J5IWKn+uN33gwSySLRaLYrFIi5dusSd5EOhEMxmM7xeL0wmE7a2tnDt2jUumkVVDcmiEX3cBKVrSpZlJmq73Y5kMsl9JcnlQTWRL1y4gLfffhuyLHNK9k7lbvcDtVoNS0tLWF5ehsViwYULF2CxWPDYY4/BaDTC5/MhEAj0+K1JuknzplQqIZvN8omDNm76u5iwlEqlsLGxgVKphDfffBNvvfUWE/4gbvb3CtHVRfppJWFTNiTVBHe73ZiamsLU1BQ6nQ5kWUY2m8W7776Lv/7rv0Y2m+VmI4M2Viph4yYRUACNNNZbW1tM2PThkZVMUXfKHkun06jX68jlcsjlcmg0GigUClzakaxm0c0iEtJOWup+RDKIZE2g+xKrFBYKBQ4UdrtdGI1GZLNZFAoF9hsSkdCpRXQv7fQ+NJ5UbY7eg3oVkhVKrgiqlUwER69zv8aTLD2ygFutFneJocBrtVrtqR9Dc41ObLIs96iHANxC2BqNhtPOS6USB3EH9WT2fkDjSbEAaqJLD9HC9vl83F6NVFfNZpPJOZfLoVQqcUB/ENeZpjuIV3UHFAoFOJ3OPX1NjUbDEh+LxQKHw8HHePH4SQtdPKZTh2VR8UCuDyIgkYh2spqVGMKPhkELhopDkTSNdNPktxeVMECvWuNO90/+cJvNhnA4zI0UqOYzoV6vs8qGXDAU+b/TZ7AfoIpwOp0OPp8Po6OjrDSieiFi0JHGRPTlE8SNX3SJkJqo0WhwTXLRWDgMIL260WjsCUJTBqlY8IqkkhRLEU8i77zzDpLJJGKxGK5du9aj/7+fyOfzcDgct33OnlvYrVYLX/3qV/E//+f/RDweRzgcxm/+5m/iP/yH/9CjG3722WfxwgsvIJfL4dy5c/j2t7+NEydO7PXl7Bqkmd3a2gLQm513WKzf+wmx+NF+yaKoGD2pZkTfrzIZSYxD3E9XyE7XTbryUqmEtbU1ALdmyN7uK22G9HpE2GSdkytAlPcdRohy3FAoxDkUR44cgcvlYncJcDNQW6/Xsb29ze7L+fl5bG5u8mlkkOWOe07YX//61/Ff/st/wfe+9z2cOHECb731Fn7rt34LTqcTn//85wEA3/jGN/DNb34T3/3udzE3N4evfe1r+MQnPoGFhQVIkrTXl3RXEBdwP3eF8jkqDhZKCRcF30TVDoCe4OKgxQBEY0CpHQb6J4aI90AbkGiRiyQ9SPe61xAljGJAnvT24lwgZVK1WuUaQNvb2z0KrEEfqz0n7FdffRX/9J/+U/zCL/wCAGBiYgL/63/9L7z11lsAbkyeb33rW/jKV76Cz3zmMwCA733vewgGg/j+97+Pz372s3t9SXcF5eIRf6diMKGUqN2J4Ab589zpGvtlLyr/R/m8QduY9hqipJSaGheLRUiSBKPRCLvdzierdruNbDaL7e1tyLKM1157DQsLCyiVStje3ka5XN5RsjtI2PNCrx/84Afx//7f/8O1a9cAAO+99x5++tOf4lOf+hQAcM+1Z555hv/HZDLh6aefxiuvvNL3NcnvKD7uBw77hD9MUGqRxQJSYhGpYf08laeDnfIElL8/7KB7JuuaCFpMcCM/PzUrzuVy2N7exvr6OmKxGEqlEsebBh17bmH/3u/9HvL5PI4ePcpR7v/0n/4TfuVXfgUAEI/HAQDBYLDn/4LBIPvylHj++efx7LPP7vWlqlChYshBZEyBZeqCXq1W4Xa7OYbSbrc5Rb9cLiMWi3Hi1jD59/ecsP/0T/8U/+N//A98//vfx4kTJ/Duu+/iC1/4AkZGRvAbv/Eb/Lx+2Vw7ZZt9+ctfxhe/+EX+uVAoIBqN7vWlq1ChYshAZFsul3HlyhUsLS3BYDDgJz/5CTd1oBMWKT9ICjqMGZ97Ttj//t//e/z+7/8+/uW//JcAgFOnTmFtbQ3PP/88fuM3fgOhUAgAWEFCSCaTt1jdBLFUpwoVKlQoQSRcrVah1WpRKpU4JV2ZzTqIgefdYs992JVK5ZYqdzqdjnexyclJhEIhvPjii/z3RqOBl19+GU899dReX44KFSoeMIgp6RRwVCZjDSNZAwC6e4zf+I3f6EYike5f/uVfdldWVrp/9md/1vX5fN0vfelL/Jz/7//7/7pOp7P7Z3/2Z92LFy92f+VXfqUbDoe7hUJhV++Rz+e7ANSH+lAf6uPQPPL5/B25b88Ju1AodD//+c93x8bGumazuTs1NdX9yle+0q3X6/ycTqfT/Y//8T92Q6FQ12QydT/84Q93L168uOv3UAlbfagP9XHYHrshbDU1XYUKFSoGALtJTd9zH7YKFSpUqNgfqIStQoUKFUMClbBVqFChYkigErYKFSpUDAlUwlahQoWKIYFK2CpUqFAxJFBbhKkYCijLhqpQ8SBCJWwVAwsiabEYfVdIL6a0Y0AlcRUPBlTCVjGwoG4hBoMBNpsNWq22b20IgkraKg47VMK+T1C719wdDAYDrFYr9Ho9JEmCy+WCXq/vaWqcy+VQLpd7ah6rUHGYoRL2PoIawtL3AA5HxbB9BFnVbrcbDz30ELxeL6LRKI4dOwaLxcKV2EqlEl5//XUsLi4in89jfX0dxWJRHVMVhxoqYe8TiHiIqIm8la2bhplg9uPUQONmt9sxMzODaDSKo0eP4qmnnoIkSewCyWQyqNfrqNfrSKVSSCQSKBaLPc1rVag4bFAJe49hMBg4SOb3++FyuaDVapm4ZVlGPB5Ho9HgHnTDir0mar1eD5/PB0mSEIlEEI1GEYlE4PV6YTAYeBy73S4MBgMcDgcCgQBarRaMRiPXXVcJe3fo1+GJNkwAfTuxqGN7sFAJew+h1Wpht9vhcDjgdrvxzDPP4KGHHoJGo4FOpwMAvP322/jbv/1bZLNZbij8oC8COn1IkoTHHnsMR48eRTgcxpNPPolgMAibzQabzQa9Xs+uJIvFgsnJSf6/+fl5ZLNZtFot1d3UB0TC4leRnAk6nY4b11KAl8ay+49t/NSxPTiohL1HEBUNFosFLpcLU1NTOH36NLRaLRN2LpeDJEkol8vQ6/XqAgB4QzMajQgEApicnEQwGEQkEkEgEOjZ8IAbxKHT6SBJErxeL9LpNIxGY89JRsXOULrrROIWO41T93VAJetBgUrYewQ60o+MjODkyZPw+XyIRCJct5smu9lshsFg4CP8gwraxPR6PWZnZzE9PQ2Xy4VHH30UMzMzcDgcMJvNAIB2u83NU4vFIorFIkqlEi5duoT19XVsbGygUCjcYhGquAHRqqbTDLmUjEYjHA4HgsEgDAYDt9aq1+tYX19HOp1ma3vQxlV5SqDvlTEicdMZdqiEvQcQretjx47hk5/8JDweD44cOQK/38/Khna7DbvdDqvVCrPZDL3+wR1+nU4Hk8kEq9WKD33oQ/jn//yfQ5IkBAIBOJ1OaLVaGAwGAECr1UKtVkOz2cTa2hrW19chyzJee+01LC4uolAoIJPJoNlsHopFuR8QyU2r1cJqtWJkZASSJGFmZgaPPfYYrFYrSqUSSqUScrkcXn75ZRSLRZZSDpJsku6FXDjAjTlFhE0PkoCKbp1hxoPLGHsIWgS0EHw+H5xOJ6xWKwwGQ88Orx7Zb0Cv18NqtcJut8Pr9WJkZAQ2mw1OpxMWi4UXGpF1qVRCo9GALMvIZrPI5XLIZrPIZrOoVCoqWe8SItHR+LvdbgQCAdjtdhQKBZjNZmg0Gpj+//auNDau67yeIWffV86Qw02UZEmW5GixUzdx7Th2HDhOgiBA06ZN46L9Ebd1atVNk7QuEKdobDcF0qDZigRBUtRJHBSxiyQtDMtpasdVvJGSSlkbTZHDbfZ9X19/CN+nO49DilpnHvUOQIiaeSTfvfPuud8932Yw9KTMRCRNm7pOp+MxkVOavuh0Rt+TU1r8XklQCfsqoL+/H0ajEWazGXa7HV6vt+1I32q12EIsFArIZrPIZrOoVCqKe2CuFEQYExMTePe73w2Px4ODBw/C5XJx+nm9Xkcul8P8/DxyuRyi0Sjm5+dRqVQQj8cRj8dRqVSwsLDAlnWj0QCgfAtKDlFrJoiEtJGf7/T7bDYbduzYgaGhIYyPj2NkZARGoxE2mw0OhwM6nQ42m40jb3oBJOeQ3GgymTA0NITBwUH09/dDp9OxtU33XKvVUC6X0Ww2kU6nkU6nUS6XsbS0hEQigVarxXKbEp4dlbCvArRaLRO20+mE1+uF1WqFwWDg2OtqtcqWYiaTYcK+kSCeRCYmJvChD30IwWCQwx+JrOv1OtLpNKanp7GysoLZ2VlMTU0hn8+jUqmgUqnwQiPNuldI5WpClDDkluNGCGatsD2KZrrpppuwfft2BAIBjIyMwGAwsCTS19fH5QCIKLsZfSNa0F6vF3v27IHT6cTevXuxd+9e6PV69g2J91kul/l0FgqFEAqFkMlk0Gq1+GQmzmWvk7ZK2FcBpMfq9Xro9Xr2tJNl1Gw2USqVUCqVUC6Xb1jnmFarhcVigV6vh9PphMPhgM1m4yN4s9lENptFoVBALBZDLBZDIpFAOp1GPp9HsVhEtVpFrVZblTG6meaS5AitVsvSkVarRa1WQ6lUQqvV4jh+YOMkQ2RNETlmsxkWi4Ud4SQ1iJIDodvzq9PpYLVaodfr4XK5eJN3u92w2+1M1uQXog2cxlWv15HP51EqlaDT6RAMBnnzT6VSfAKuVqs9vfmrhH2F0Gg0MJvNCAQCcDqdcLlcvADIKioWi5iZmUE8Hse5c+fYUuxFz/u1hMvlwoEDBzAwMIBbb70VwWAQLpeLdf5cLodf/epXOHHiBBKJBE6ePIlkMolCoYBUKsXW0HoV+jZD6JnBYMDY2Bh8Ph8cDge2b98Ou92OhYUFnDhxAqVSCclkEqlUqi30bi2IURRGoxEmkwkulwtDQ0MYHR1lIiRDgyx7+t3dJDCxVMGBAwfg9Xpx00034fbbb4fD4YDL5YLb7eaNSH6qaDabcDgcaLVacLvd2LZtG6rVKg4ePIhMJoNoNIpXX30V0WgUkUgEoVCop6VKlbCvAPRwGAwGuFwuuFwuWCyWNkcIAFQqFYTDYSwtLSEWi6FSqbCVeCPBbDbjpptuwvj4OLZt2wa32w2bzcYOoFKphJMnT+Lll19GNpvF3Nwc1wehueqkycrnkV7rJAkA3bcWLwatVgufz4fx8XH4/X68853vhM/nw/T0NDtdK5UKMpnMqvjo9eqGazQa6PV6lu9cLhe8Xi+fDEX5RSw7IP6e670hihLOtm3bMDY2hh07dmDfvn2ssW/UKer1egGgLSno3LlzKBQKsFqtkCQJy8vLPV2oTSXsKwQ9UFqtltPS5fGhjUYD2WwWyWQS+Xy+p8KjrgeIBIxGIzweD/x+PxwOBydolEolFAoFJBIJ1vcLhcJFTyCi5dipPst6pN1roIxNypSdmJjA1q1b4Xa74fF4YLfb+f1qtcoEu1bGojh2+len08Hj8cDn8yEQCLDUIhJ0rVZDsVhs03e7pfH29/fDarXCaDTyPQ8ODsLpdPIJdiOfb6driOQtFguGh4cBnB/78vIySyfFYrHn5DaVsK8CqF6zxWLpGApVLBYxOzuLU6dOIRKJtOlknR6mXnpArhRk1el0OnYW3XLLLXwMbzabCIfDmJ2dRTQaxdmzZxEKhTh5o9NciJUPL4ZOlmcvdq/p7+/H+Pg4br75Zng8Htx9993Ys2cPP1v9/f1Ip9MIBoMwGAxIJpO84XUaj+iIpefRarVi79692L17N4aHhzEwMACTycTvN5tN5PN5rKysIBwOI5/Ptzl2rxdoAzIYDBgZGcHg4CC2bduG22+/HRMTE7BYLDCbzRwRIm7YnU4V8u/FBBufz4c777wTpVIJR48ehSRJSCQSmJ+fx/z8POdP9IqurRL2VYBoYcvJmo5fuVwO6XQahUJhzQ9/M+ivcpB3X6vVwmQywe12Y2BggPXSRqOBYrGIRCLR5mBcK/JDvgAvZ7568cjb19cHu92OwcFBTs/ftm1bW1QIJV2ZTCa2sIHOm778dcrEdblcGBkZYbImJ51oYRcKBZRKJdRqta6Gu/X398Nut8Pj8cDr9WJgYACBQGBNvXota3i9IlcmkwmDg4NoNpuIx+Pw+/3QaDRIJBJcu6ZXyBpQCXvDkC9ycbFQ9ANZ2OIRVR7Ef7Hf34tkciXQ6XQYHByE1+vF2NgYrFYry0aU1BCJRHD69Gkkk0kuhrXW4tvoa0qBVquFwWCAyWTCyMgIbrnlFrjdbrhcLt6QSG/N5XJYXl5GNBpFJpNhy1e0GDvp2TqdDgaDAVarFX6/H8PDw3A6nRydA4Cfz2QyibfffhvxeBy5XK4tweR6zbMooQWDQezatQvDw8OwWCyr5B8xEaZUKqFarfJ4aH6pzgxF3Ygp+mRQaDQa+P1+7Nu3D5lMhqNnSqUSlpaWkEwmeyIiSSXsi6CTFSNfEFSPwel08hGzk67aKX52rd+/Waxtg8GA7du3Y+fOndiyZQvcbjdLIfV6HZVKBfPz8/j1r3+NXC6HWCx20UVxOaQtfmadLLNuwWAwwOl0wmazYe/evbjnnnv4yE/PAIXwxWIxnD59GpFIZBVhA2s7W4msnU4nxsbGcPPNN3NYnxhf3Ww2sbS0hDfffBPpdBqxWIxDB6+nlUkZjBaLBTt27MBv/uZvchioaFnTPTebTdRqNcTjcWSz2TZiNZlMsNlsHB5pNBr5xEeSCp0yxsfH4fP5UK1WMT4+jmAwiGQyyU5wyr7tJlTCXgcbdVhRHLbBYGgLi+rkCLvRQI4jMV6W9FJaAOVyGblcjjXTjWIzzCnFF9vtdjgcDi5pQLIaJV1Vq1WUy2UUi0UUi0WOMtpI8gxZ2BQdQvqvXLprNpttn0U3IpnEZCE6uTocDi6vKzdsGo0GarUaJ6URYdMGQ8+TWCudMpPFvwWAw3Hr9TpLMPS6WNlQtbAVgk4ODXLmBINB+P1+blgg/zlaEGt92JvJqgYuLDyj0YgtW7bgwIEDHPYoSRLK5TJLIMlkEplMhiMT1sOVzhMt5m5n7tHGvnXrVtx7773w+XycsSfeWzKZxBtvvIFYLIbJyUmOS5fry2vptDqdDkNDQ9ixYwf8fj8GBgZYIhB1a9oMUqkUd+8pl8sArv/GqNfrYbFYYLVa+V86uQLgyJV6vY75+XnMzc2hUCjg7NmzWFlZ4bED508XRPYOhwNOp5Mt96GhIf5bYj0SAAgEAmi1WgiHw5iamoLVamUHbDc1bZWwN4i1nBlE2FTTwG63r7ICRClkow+/0ombHn6DwYDx8XHs37+fFwdwPmU4Ho8jk8kwcVMWqEhC10Kz7rYOKVqQW7duxYc//GEMDQ3B4XAwYRMxJJNJvPrqqzhz5gyWl5eRSCRQLpfbrMhOcg/pwFqtFsPDwzhw4AA8Hg8GBgaYnMSw02KxiFwuh1QqhVgshmKx2JUEEooqMplMXJyKfENEppIkoV6vo1arYW5uDv/7v/+LdDqNqakpzM/P8/jF00V/fz98Ph+8Xi/cbjcA8InDYDBwZUj6uUAgAJfLBafTiUAgAIvFgkqlws9ot6AS9mVCdFyQY4PSiTs5HOVfnaB0khZBUTNGo5HT9mlRkC6by+WQzWZRLpfbrEXKEN0IlDhnlJpvMBjgdruZlGh+Wq0WyuUyarUa8vk8MpkMMpkMisXiuhaeSNykA9MmSRqwGL9NP0N/R8zA7aYlKZZ6IK1ZLJ1K/o9qtYp8Po9UKoVMJsPRLeLapNo0tEZJkkun00gmk2g0GnC5XG1/n4wNSoAjZ2WnyJTrDZWw10EnB464KOiDpA4z5HkXr6VUatLa5JEimy3qAbiQBOJyueD3+2G329nKId0xGo1icnIS8Xgci4uLPC9iWKS4udH3nU4v3baYNwoiEb/fjzvvvBPBYBC33HILfD4fH9sBoFqtYm5uDuFwGDMzMzh16hRmZ2fXLCMrdzwC561Hl8sFq9WKiYkJ7N27lz8TuUGRSCRw/PhxpFIpLCws8GbRDcLWaM6XevB4PJwwZLVa28iyVCohHo8jn8/jzJkzOHr0KAqFAuLxOGq1Gv8eIm76WTISbDYb7HY7kskkhoeHWSOXn1DIOUn6P0lp3YRK2BfBWkQgxhdTIX4qbCRfEETY6xV9UgLhbBQU30q1VcxmM/R6PYAL+mMmk+FkmWQy2UbY651QRIeuSChKmD+6d7vdjn379mHHjh0IBoNcAItQr9cRjUZx7tw5zM/PY2lpibXZjZAoyQoUGULV+OTRJ/SVy+UQCoUQj8eRSCRQq9W6VudGozmfMEOZnSRZEPlKkoRqtcqnjpWVFczNzfEmI5crRBKuVCrsUJ2ZmeGfuf3221cZA7S+ydI2GAyo1Wpdrw2uEvZlgh4g8Utu/ZF1Td8rqYzjlUCj0cBiscDn83ExLI1Gw1EhtIFRSJbJZILX62XL3Gw2A7hATrTZid+3Wi2WU8QWYr2Kvr4+PuZbrVZ2gIkZe/S8UN3vUCjEmbHAxZ8Z8Zm02+0YGxvjinad5DqqI041x2OxGAqFQtdOLKKj2m63cwszeSgmRbOUy+WO6fNyiGRMc1wqlZDL5diB22g02iK8xHsieU+sD9QtqIR9mRB1LlFnE+sWNxoNdo5QLO2NUKGvv78fg4OD2LdvH3w+Hzt5SJut1+uc5NBsNuHz+eDz+WC1WrF7926MjIzw7yKLivTbXC6HYrGIfD6PkydPYnFxkY+6dBzuRfT398PlcsHhcGB0dBQTExPYvn07h5IB4PC0ZDKJqakp/OIXv+B2XeuRqHiMp3Ki4+PjeN/73gefz4ddu3ZxwhJtDo1GgzXrUCiEN954A5FIBOl0uqu1bvr6+uB2u7F161ZuBCI3hqgkaiqVQi6XWzd9XowqIkmDYrYrlQocDgdyuRwqlQr3WRX/FnXmcTqdaLVaXW/rpxL2FUBuXXfSu8USld0OCbpeIAubPPJ0pBUdRuJcUKcTu92OrVu3Ytu2bW0RIuVymWO06SiczWY566+Tr6HXQEd9ks4o5lrU7MVkokQigeXlZdRqtTVrqnQCRYbY7XYMDw/D7/fD7Xa3dWMBwDW1yXGXSCQQj8cv6W9dC4gWts1m62jVihY2GUAbPRXQM1ipVKDRaDimnSxs+b2IQQWqha1giBo2OSjkyQhyx4do4WxmaDQaWK1WDAwMwO12w2Qy8etarRatVgs+nw979uxBsViEzWZjKWR0dJQtclqEVD+j0WhwD8JsNotwOIxKpYJsNsvNDXoVOp0OgUAAY2NjGB0dhclkWqXH5/N5xONxRKPRVREh6xGFmPgxMDDA9dm9Xi88Hg//LeCCrEIlfzOZDJf8JampWxAJ0mg0tjWqFnV3kpdIYqK1J/dryCFa23T6Fb+odoj8nsigKJfLXV+/KmFfJkRtS95lRlxcRNakgdVqta7v0tcStJG5XC6Mj4/D4XDAarXyxkVJG2NjYzAajWg0GqxXih1pgPY6EUQmtLiy2SxyuRz6+/sRDocRiUSQy+W6PPq1YTAYMDExgQMHDnC3cjERpNFoIJFIsCM2m82yzg+snRgDXHDUWq1WjI2NsaQwOjoKj8fDc06QJAmFQgEzMzNYWVnB/Pw8R6B0qxMSkTWdRMhBKs9uBM7LFBSnTZYvbW5rbXDyaCNK0KIs0mq1Cp1Ot8pSpzwLl8uFSqWiSiJKhWg9r9VZupO1vZnJmkAJCyaTiWs30Os0Z1RDo9VqsYUtD+kTFyBthqTT1ut1mEwmmEwmLmnbq6BNjJo0U7lUEWT1iT0rxZMZ6a/iMyQnbKPRyGnulGwits2iv0N/i5JlSqVSz7SsE8coPxXIyVSMt77c9SUPHe10P6KvqtvrVyXsqwSyuDtZ16SBGY1GVKvVrn/o1wpimVkiJ1GHpPcp7E/U97PZLOr1OhKJBDuSSLd2OBycUk3yCDlyKWqkF8imE+jkZTQa4XQ6uXyBWLpTPDlQNIzdbkcgEODfAYD1b3nxfpoDj8eDm2++GW63G2NjY20hpsAF3brRaCCTySAUCuHcuXOIRqNtzWjlRHk9IGYEk5OZQunElnBkHVNT60ajsab/aC2QFS/WVpGXrCWQcUHV+1RJRKGQPxCdwvtEwibS7oVd+lpBzK4zmUxtlp5I2PTQUxEoio3N5/M4deoUFhcXOTmiXC5jbGwMu3fvhs1mg8/nw8DAQBtZd7Nm88VADW+pjZyYJEMERePoRNgkE+l0Oo6pFrPvqLVatVqFy+XCzp074XQ6ufUXgciOHI3ZbBYLCwuYmZlBOp3uWExKHqZ6rSHeY7FYZANHjK0mwhadjnSvGyFtuo4yQImsTSbTqpR9US8nwu5UifN6QiXsy8RaD0gnbzX9/3rXFb7eoJRisQt3pzZOtCjJqk6n00ilUigWi4hGo1wvg3oXWq1WJJNJ1Go1Tsghy0us19xrEImBWl2RTNEp/Z42umazCa/Xi1qtxoRNxYt8Pl9bh/NWq8UnDtog5daimMBFhZ7K5TL3Fu2UPdktSNL5wmCZTAZ9fX3IZrPIZDL8fqvVQjqd5kgh6iIvz3FYi6wBsC/FaDS2hfJ1MqR6LW9CJewrAFmNANhCIs1LfGgoUL9QKPR0R+YrhdVq5Z57VPeaju9Ae7JGKBTC5OQkcrkcFhYWsLCwgFqtxn0vSc+lTiCxWAwWiwUHDhyATqfj6BCa014Ml+zv74ff78eWLVswMDCAYDAIp9PJJEGgZ4Zkn0ajge3bt3NUAqX1E6GL2m2z2US1WuUoB9osKaMRuJAsUq1WeUNcXFxEPB5HKpVapWF36/kkbX1+fh61Wo035mg0ynJOs9lEKBTC9PQ0CoUCFhYW+P5pDOvdP50CfT4fBgcHMTQ01LG3pXhPFB8vlpvtlpV9yYT98ssv4x//8R8xOTmJcDiM5557Dh/5yEf4fUmS8MUvfhHf/va3kU6n8Ru/8Rv4xje+gd27d/M11WoVn/nMZ/CjH/0I5XIZ99xzD775zW9yM0wlQu6lJtBr9KH3kjVztWEwGODxeLigkVgwR4zDbjQaiMfjmJ6eRjwex8zMDGZmZljHFclDo9Egl8txFxCfz4eJiQk+FpOW3WuETaTqdDoxOjrKCUTkcBQXPBG2GFEjymdEJnQt0H5qIyuZSLnZbPLxXTz1URw7kTYlIXW7FZiIZrPJHXUoG5Q2HJJAFhYWcPLkSU6gEuWci2nXNJ92ux0+n4+LcK11GqR7EpPeuimJXLJrvVgs4h3veAe+/vWvd3z/y1/+Mr7yla/g61//Ot544w0EAgG8733vQz6f52sOHTqE5557Ds888wxeeeUVFAoFfPCDH1RUN3ExQoQgP5LRrk8aZa/rrVcCjUbD8b+BQAA2m62NXFqtFiqVCiKRCBYWFrC8vIx4PM71ncUKceL8kI5I2jgdZakk5lqWUTdBVjH1sBwaGkIgEOBNTB4CKlZ9FDNnxcJFnY7sNFdiKBv9DvHnRCmEEnJ6Jfa6E8QTQTwex8LCAhYXF7G8vIyVlRUkk0nW7TeaOCPOIenSpF13WsN0D/V6nbNN6eTXTVyyhX3//ffj/vvv7/ieJEn46le/isceewwf/ehHAQD/+q//Cr/fjx/+8If41Kc+hWw2i+9+97v4t3/7N9x7770AgKeffhojIyN48cUX8f73v/8KhnP9ICbOdPIc01GqXC6jVCrxV6+ET11NiDWEb7vtNvj9foyOjrbVL5YkiWs7r6ys4PTp05iammIrjzLsOhXl1+l0HHHicrngcrlQq9W4sH0veO9FUPEii8WCXbt24Y477oDdbkcwGGSpgkiCdGg5RGOgk/NPPNGJCSVUt4U0buBCY12qyvfmm28ik8kgkUigWq32nBEhxtwfP34cZ8+ebXs2qPvORjqaiyGQ5PTW6/VwOBzw+/1wuVyrokNImqlWqygUClheXsbMzAwymUzXJc2rqmHPzc0hEongvvvu49cMBgPuuusuHDlyBJ/61KcwOTmJer3eds3Q0BD27NmDI0eOdCRsCmwndDtBQnwI1rJ+xOO/3MLejKBQPepsLSaGyC3sUCiElZUVJBIJFAoFXnh0rRxipAU5NQGwbNDtZAYRRJZ0EnA6nRgcHITVauXegnQdcOEEQa/JY6xFiJakWOpALq2IEUuis7FUKiGZTGJlZYUbFGxE973eEE+nyWSyowRxKfcrX6+ihS0mFclPhGRhF4tFrretOAt7PUQiEQCA3+9ve93v9yMUCvE1er1+VdFwv9/PPy/Hk08+iS9+8YtX81YvG/SharVaLl9ptVrboiKA8xYQdVMhS2YjRzelgWKMSRckDZtIlYi6VqtxOczFxUUkk8k2PZ8Wpbi46Fjv9Xqxe/duuFwujIyMwGq18qKTyyK9MLdmsxmDg4NwOBzweDwcubFWAXy5s48sPJIF0uk0KpUKEweRBkU7DA0NweVydZSIWq0WisUil05Np9Nt3X16HXJ57EpAhE2SldVqbeufKa5PigWnjY0cu93WsK+JadLJ2ryYxrjeNX/913+NRx99lP+fy+XaKrpdb5BTyOPxwOv1cglRyrjTaDRcpD8UCmFpaWnTyiG0cRmNRni9Xm6VRkf/ZrOJQqGAQqGASCSCM2fO4NSpUyiVSqtif+VWJxHQyMgIfuu3fgsDAwMYHByEy+XicDeql9xLmY4OhwMTExPwer0IBoNwOBx8Oui0NujUJWrSpNGmUimcPHmSLeOzZ89yZTmK7b7nnnuwd+9eTtcWpSiqPb6wsIBoNIpIJIJ4PM4NNZRw4rvcNdOJT4iwqQsP+RXo79BnQKGFZFkTaXd7vq4qYVNmViQSweDgIL8ei8XY6g4EAqjVakin021WdiwWw7ve9a6Ov5ce9l6CPJazUz1s0rB7USe8WhDnQYwzFheBWGa2UqkwWa81J6LeqNPpeHFRE1/Rb9BL8yoeuY1GIx+5xRZXcsgrOZJlTe2ustkskskkkskkYrEYF7yi308la+Ubn9xaLBQKbC12s2ZINyFKTtSUQF5nBVgtZ4q1bLqNq0rYW7ZsQSAQwOHDh7F//34A5x0eL730Ev7hH/4BAHDw4EHodDocPnwYH/vYxwAA4XAYJ06cwJe//OWreTvXBPShG41G7uTh9XpXZUlRim2pVOrZOOGrAZPJhLGxMXg8HgwPD7c1TCXiIAcOHSvXcxTRHJpMJgSDQVitVoyMjHD1OfLqUwLFysoKYrFY18uCiujU5qzZbLbNCd0rFdKv1WpcxIq0fkomWlxcRKFQQDabRSwWQ6vVgt1uB3B+fdGmJpIPkX+tVkMkEsFbb73FNaSJfHpps7sWEOULMgBICvH5fPx8dTr5iOUC5Fm13cQlE3ahUMDbb7/N/5+bm8OxY8fgdrsxOjqKQ4cO4YknnsD27duxfft2PPHEEzCbzfi93/s9AOePi3/8x3+Mv/zLv2S98zOf+Qz27t3LUSO9DpGwx8bGmLDl4UHk6KHY2F7RWK8mzGYzxsfHEQwGMTw8zBXURHmDajxTGNZahC1aqGazGcFgEB6PB6Ojo1zXmX4nEfby8jIymUzPNC9YS/Kgf+kZoNdIXy6VSpienuaEkJmZGcRiMY7wIP1UdFJqtVpOmOl08qCNMhwO49SpU8hkMkilUlypbrM9ixcD+VvMZjO8Xi+Gh4dZWuqUFUpkLZJ2t31Ql0zYb775Ju6++27+P2nLDz74IL7//e/js5/9LMrlMv70T/+UE2deeOEF2Gw2/pl/+qd/glarxcc+9jFOnPn+97/fU6FZa0F0ion1eNfSy9bz+m8GiEfvi8XBkpVDNTDE9whkner1etjtdrhcLo6u6OvrY0uHLPZuNoxdD6LU0Wg0VhVrok08lUohFosxcZNVTbq/eK34PFGrMbPZvKoYlBgZQkWS6KRHjsYbjawBcBgulToWK/CJTm/xlEz/9krAwCUT9nve856LLszHH38cjz/++JrXGI1GfO1rX8PXvva1S/3zXYcYFmSz2eB0OmGxWAC0LwIidFpUIql3+0O/mqjX60ilUtBqtchms6vC8zQaDeuFJpMJNpsNDocDxWKxzWoUtV9KHd63bx8mJiYwMjLC3nzSdaPRKJaXl7G8vIxqtdozFjYATqsn4s1kMuyH0el0yOfzCIVCyOfzmJ+fx/Hjx5HP5xGJRBCNRllzJm2aiIM2Mr1ej5GREezevZvbq4lF/JvNJtdlyefzWFlZwcrKCkqlEv/OGwlEwtQFibqxm0ymtmxc+fVi5JG8gfamihLZrBA/SDpekWbbKVtKdGyI+uVmkkaoN6Ber+dCPPKIH3mJWZPJxHVX5AuF5szpdGJiYoKrz1HlOao0R0WBkslkx5IA3YSYLl4ul3lzIoubKuUlk0mcOnUKr776KnfNKRaLqyw58bmjefR4PBgfH4fH4+EStsCFE0+lUuEoh3Q6zYWSerkrz9XAWkYRGVBUe12soy4/ndD18rjsXnjGVMK+TMh34U47NBXjER8MAIpKwb8YiLABIJFIIBaLQZIkTvulesV6vR42mw3j4+Po6+tDJpOB3W5vi1iginRmsxljY2Ow2+1cE4N011gshpmZGe7K0itHVQJFZWQyGbRaLT4JiBFFiUQCCwsLPF+U6Xmx0gVidq3ZbOamvmIElRiWlkqlkE6nWVrZ7E5GEXIJktaj2WzmvIG1kt4AtMlucietojRsFRcgZpXJ0dfXB5vNBrfbDYfDwQ+J6HDbDIunVCphbm4Oer0eTqcTW7duxcDAAEZGRjA8PNy2UEZGRnD//fdz09fFxUWOHKnX69z30Ol0wuPxYGJiAm63m5M/qtUq3njjDbzwwgvIZrOYm5vrqc2PFnM6ncaZM2dgNBo5hJWcWzqdDvF4HFNTU5zpmU6n2yxw8ffJs2mJ+AOBAG666SbY7XY4HA6+no7usVgMJ06cQCKRwNLSEs9zL1iJ1xPiiZiiQyjaaC2ypg2PyriWSqVVkki3oBL2ZWK93Rm40LxAjEumcLTNBLKw+/r62GIEzkcD1Wo1loJ0Oh2sVitGR0dRr9e56wpZMbVaDXq9HsFgkKv92e126PV6dp6Vy2XE43HMzs6iUCggn893fQF1AlnYer0e0WiUM2GpgFUsFsPS0hISicRFE1jk0ohoYTudTthsNo7KETv4VCoVpFIpLpR0sZobmxFypz+VpyXteq01LA9FFUsAdPt5Uwn7MiBq1OQkk+tgYr0Cg8HAzo3NumgkSeLiQg6HA4lEAtFoFCaTCcPDwxySR8WaALDUQaUr+/r6WBLRarUol8uo1+sIh8OYnZ1FNpvFmTNnej61mkLqJElihyz1Bezv72e9WmzLdTFIksSZjQ6HAw6Hg7ugEPlQCCVVmEsmk4jH4ygWi5vqVLce5JEeooVts9kwMDAAj8fDRbLW+h3VapU7IVHiWy+sXZWwLxH0wIsxnfJqX2LEA0WJ0KIlTXczLRyyPJaWlpBOp6HT6TA+Ps5du9/73vdiz549MBgMcDgc0Ov1cLvdXP9ctGBIL6SONNVqFVNTU3j++eeRTCYRDocRj8fbCkb1Gmg8Go2GS5nSM0HvU+El4OIkSiRENUqoBZgotZGFTfHuqVQKi4uLrPXfSPo10N5dhkL5qNclneDWsq4pPj4WiyGZTHJlQNXCVijEUDTR8SjHerHX3f7grwXEzidk/VGDXYp+sNvtPF/kLBPLZJZKJU5UoNA2KrqfSqW4MW8vWDvrgZ4RihYRLT4xceZSQFII1W6RN4gALlj3lKwkNs3YjM/cehAlD6pNs5FyvGQ4UHOMXjIMVMK+BIgRCWLbL4PBwKnHdB05myKRCBKJRFvB9V4nm8sFEVGj0UA6nUaz2UQikYBWq8Xp06fh9/tx2223wefzcUhkX18f9xcsl8uYnZ1FJBLhOsT5fB5LS0uIRCJcC0NJxCOP25Wnpm8ERDoulwu7du3iErZiowIA3NCYqvpls1k+0itpzq4U4vySNEnPm8PhaNP9O/0sGRkrKyucyNQrG55K2JcIkZQoxtZisbRphEToRNiUetxLrZiuFcSxZ7NZaLVa1rK3bt0Ko9GIrVu3cgd00nSpDdiRI0dw+vRpjgLJ5XKrHHNKmz/xfq/k3omwA4EABgcHmbDFZ04k61wux/N3o4KSjSiJzeFwsDNbDprHVquFXC6HlZUVpNNplMvlLtx5Z6iEfZmg/njxeBwGgwHVapVTp0kDo0pr1B1caURzJSACoSQOWgTxeBxmsxn5fB6VSgX9/f3I5/Ps4Ekmk8hkMsjn8ygWi5yZ10vH0m5AjOs3mUxcbEwE9T0UmxP0SnRDNyCPsCHno3gqWUvDFvs49tKJWCXsSwQ5kxKJBF5++WWcOnUK+/btg8Vigd1uZ90wlUrhyJEjOH78ONLpNNLp9Ka3rjuBCLter2NhYQH/+Z//yZl55KknnZd6+NEGR+FoNzpIEjEajXC73fB6vW1d0ckqLJVKWFhY4FR0qh1yoz1zwGopCjjvA1hPwyaiptIC+Xy+J7rMiFAJ+zIgSRLy+TxOnjzJmVP79+/n16l/3pkzZzA9Pc3dK27UhUOhZpVKBfF4fF3rRvxXxXmISTOUWi1mN5JVWK1WkUgkEA6H25JxbsT57CRDUes2eRiueB05wKmWvRjN0wtQCfsyQe2bACAajeLEiROw2+3cdJfqXMgzzG7ExSNHpzlQ56UzRFKhUEcqNqbT6Vh+q1Qq3FEmHA4jk8n0FNF0C2LUDCVg6fX6Vb0wybAgSYmqQKqSyCZBo9FAJpNBX18fXnvtNczOznLqOR2rUqkUCoUCpxyrpNQOdT42BrKwc7kcZmdnkc/nOe2/Xq9jfn4eiUQC586dw69//WvMzc1xOdUbfY4bjQZKpRI0Gg3y+Tyy2SwAwG63t53oaOOLRqNsbOVyOQ4z7ZV5VAn7MkHJHQBQqVQ4JVuFimsFqlRIdVvE5saxWIwt7Gg02jOZed1GJwubYqvlEhx1SCcLm6S8XppHlbBVqOhhkEMRABeNstlsePvtt+H1etFoNBAOh5FKpRCPx/lE1ysWYbdB80dO79deew1OpxOlUgmjo6Nc76evrw+hUAhTU1OcJUrErhK2ChUqNgyylufn5xGLxbguCWnYpLVSZE0vEUy3QYRdq9Xw1ltvIZlMwu12c/Ewo9EIl8sFvV6PEydO4Pnnn0c8Hkc4HEa5XO65k4pK2CpUKARiZx2KKxaTPVR0hhj2mEgk0Gq1uCExlVk1GAysXadSKd74em1eVcJWoUJBEKUONfJo46Aa1xQN8vrrr2NxcZFjs/v6+rCwsIDl5WXWsHsx6UglbBUqFIheI5JeByVwVatV5PN5ZDIZlpToi2SlXiRqgkrYKlSouCEgkrMYKSISdK+fWlTCVqFCxQ0FymiUk3OvWtUiVMJWoULFDYdecyZuFKu7xyoAvb4LqlChQsWlYiO8pkjCzufz3b4FFSpUqLiq2AivaSQFmqutVgsrKyuQJAmjo6NYXFyE3W7v9m1dVeRyOYyMjKhjUxjUsSkT3RwbVfkcGhrivp9rQZEadl9fH4aHh5HL5QCcL+Sy2R4ggjo2ZUIdmzLRrbE5HI4NXadISUSFChUqbkSohK1ChQoVCoGiCdtgMOALX/hCW/eNzQJ1bMqEOjZlQiljU6TTUYUKFSpuRCjawlahQoWKGwkqYatQoUKFQqAStgoVKlQoBCphq1ChQoVCoBK2ChUqVCgEiiXsb37zm9iyZQuMRiMOHjyIX/3qV92+pUvGk08+idtuuw02mw0DAwP4yEc+gjNnzrRdI0kSHn/8cQwNDcFkMuE973kP3nrrrS7d8eXjySefhEajwaFDh/g1JY9teXkZn/jEJ+DxeGA2m7Fv3z5MTk7y+0odW6PRwN/+7d9iy5YtMJlMmJiYwN/93d+1VbdTythefvllfOhDH8LQ0BA0Gg3+4z/+o+39jYyjWq3i05/+NLxeLywWCz784Q9jaWnpOo5CBkmBeOaZZySdTid95zvfkU6ePCk98sgjksVikUKhULdv7ZLw/ve/X/re974nnThxQjp27Jj0wAMPSKOjo1KhUOBrnnrqKclms0k/+clPpOnpael3fud3pMHBQSmXy3Xxzi8Nr7/+ujQ+Pi7dcsst0iOPPMKvK3VsqVRKGhsbk/7wD/9Qeu2116S5uTnpxRdflN5++22+Rqlj+/u//3vJ4/FIP//5z6W5uTnp3//93yWr1Sp99atf5WuUMrb/+q//kh577DHpJz/5iQRAeu6559re38g4HnroISkYDEqHDx+WpqampLvvvlt6xzveITUajes8mvNQJGG/853vlB566KG213bu3Cl9/vOf79IdXR3EYjEJgPTSSy9JkiRJrVZLCgQC0lNPPcXXVCoVyeFwSP/yL//Srdu8JOTzeWn79u3S4cOHpbvuuosJW8lj+9znPifdcccda76v5LE98MAD0h/90R+1vfbRj35U+sQnPiFJknLHJifsjYwjk8lIOp1OeuaZZ/ia5eVlqa+vT3r++eev272LUJwkUqvVMDk5ifvuu6/t9fvuuw9Hjhzp0l1dHWSzWQCA2+0GAMzNzSESibSN1WAw4K677lLMWP/sz/4MDzzwAO69996215U8tp/+9Ke49dZb8du//dsYGBjA/v378Z3vfIffV/LY7rjjDvziF7/A2bNnAQDHjx/HK6+8gg984AMAlD02ERsZx+TkJOr1ets1Q0ND2LNnT9fGqrhqfYlEAs1mE36/v+11v9+PSCTSpbu6ckiShEcffRR33HEH9uzZAwA8nk5jDYVC1/0eLxXPPPMMJicn8eabb656T8ljO3fuHL71rW/h0Ucfxd/8zd/g9ddfx5//+Z/DYDDgk5/8pKLH9rnPfQ7ZbBY7d+5Ef38/ms0mvvSlL+HjH/84AGV/biI2Mo5IJAK9Xg+Xy7Xqmm5xjeIIm6DRaNr+L0nSqteUhIcffhj/93//h1deeWXVe0oc6+LiIh555BG88MILMBqNa16nxLG1Wi3ceuuteOKJJwAA+/fvx1tvvYVvfetb+OQnP8nXKXFsP/7xj/H000/jhz/8IXbv3o1jx47h0KFDGBoawoMPPsjXKXFsnXA54+jmWBUniXi9XvT396/a4WKx2KrdUin49Kc/jZ/+9Kf45S9/ieHhYX49EAgAgCLHOjk5iVgshoMHD0Kr1UKr1eKll17CP//zP0Or1fL9K3Fsg4ODuPnmm9te27VrFxYWFgAo+3P7q7/6K3z+85/H7/7u72Lv3r34gz/4A/zFX/wFnnzySQDKHpuIjYwjEAigVqshnU6vec31huIIW6/X4+DBgzh8+HDb64cPH8a73vWuLt3V5UGSJDz88MN49tln8d///d/YsmVL2/tbtmxBIBBoG2utVsNLL73U82O95557MD09jWPHjvHXrbfeit///d/HsWPHMDExodixvfvd714Vfnn27FmMjY0BUPbnViqVVnU96e/v57A+JY9NxEbGcfDgQeh0urZrwuEwTpw40b2xdsXVeYWgsL7vfve70smTJ6VDhw5JFotFmp+f7/atXRL+5E/+RHI4HNL//M//SOFwmL9KpRJf89RTT0kOh0N69tlnpenpaenjH/94T4ZQbQRilIgkKXdsr7/+uqTVaqUvfelL0szMjPSDH/xAMpvN0tNPP83XKHVsDz74oBQMBjms79lnn5W8Xq/02c9+lq9Rytjy+bx09OhR6ejRoxIA6Stf+Yp09OhRDv/dyDgeeughaXh4WHrxxRelqakp6b3vfa8a1nc5+MY3viGNjY1Jer1eOnDgAIfCKQkAOn5973vf42tarZb0hS98QQoEApLBYJDuvPNOaXp6uns3fQWQE7aSx/azn/1M2rNnj2QwGKSdO3dK3/72t9veV+rYcrmc9Mgjj0ijo6OS0WiUJiYmpMcee0yqVqt8jVLG9stf/rLj+nrwwQclSdrYOMrlsvTwww9LbrdbMplM0gc/+EFpYWGhC6M5D7UetgoVKlQoBIrTsFWoUKHiRoVK2CpUqFChEKiErUKFChUKgUrYKlSoUKEQqIStQoUKFQqBStgqVKhQoRCohK1ChQoVCoFK2CpUqFChEKiErUKFChUKgUrYKlSoUKEQqIStQoUKFQrB/wPS3DkUJ9C0rwAAAABJRU5ErkJggg==", + "text/plain": [ + "
" + ] + }, + "metadata": {}, + "output_type": "display_data" + } + ], + "source": [ + "n = 4\n", + "canvas_orig = np.empty((28 * n, 28 * n))\n", + "canvas_recon = np.empty((28 * n, 28 * n))\n", + "for i, (batch_x, _) in enumerate(test_data.take(n)):\n", + " # Encode and decode the digit image.\n", + " reconstructed_images = decoder(encoder(batch_x))\n", + " # Display original images.\n", + " for j in range(n):\n", + " # Draw the generated digits.\n", + " img = batch_x[j].numpy().reshape([28, 28])\n", + " canvas_orig[i * 28:(i + 1) * 28, j * 28:(j + 1) * 28] = img\n", + " # Display reconstructed images.\n", + " for j in range(n):\n", + " # Draw the generated digits.\n", + " reconstr_img = reconstructed_images[j].numpy().reshape([28, 28])\n", + " canvas_recon[i * 28:(i + 1) * 28, j * 28:(j + 1) * 28] = reconstr_img\n", + "\n", + "print(\"Original Images\") \n", + "plt.figure(figsize=(n, n))\n", + "plt.imshow(canvas_orig, origin=\"upper\", cmap=\"gray\")\n", + "plt.show()\n", + "\n", + "print(\"Reconstructed Images\")\n", + "plt.figure(figsize=(n, n))\n", + "plt.imshow(canvas_recon, origin=\"upper\", cmap=\"gray\")\n", + "plt.show()" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "## Your turn! 🚀\n", + "\n", + "TBD.\n", + "\n", + "## Self study\n", + "\n", + "You can refer to this book chapter for further study:\n", + "\n", + "- [deeplearningbook](https://www.deeplearningbook.org/contents/autoencoders.html)\n", + "\n", + "## Acknowledgments\n", + "\n", + "Thanks to [Sebastian Raschka](https://github.com/rasbt) for creating the open-source project [stat453-deep-learning-ss20](https://github.com/rasbt/stat453-deep-learning-ss20) and [Aymeric Damien](https://github.com/aymericdamien) for creating the open-source project [TensorFlow-Examples](https://github.com/aymericdamien/TensorFlow-Examples/). They inspire the majority of the content in this chapter.\n" + ] + } + ], + "metadata": { + "kernelspec": { + "display_name": "Python 3 (ipykernel)", + "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.9.16" + } + }, + "nbformat": 4, + "nbformat_minor": 4 +} diff --git a/open-machine-learning-jupyter-book/deep-learning/autoencoder.md b/open-machine-learning-jupyter-book/deep-learning/autoencoder.md deleted file mode 100644 index 5266b142d8..0000000000 --- a/open-machine-learning-jupyter-book/deep-learning/autoencoder.md +++ /dev/null @@ -1,316 +0,0 @@ ---- -jupytext: - cell_metadata_filter: -all - formats: md:myst - text_representation: - extension: .md - format_name: myst - format_version: 0.13 - jupytext_version: 1.11.5 -kernelspec: - display_name: Python 3 - language: python - name: python3 ---- - -# Autoencoder - -## Overview - -An autoencoder is a type of artificial neural network used to learn efficient codings of unlabeled data (unsupervised learning). An autoencoder learns two functions: an encoding function that transforms the input data, and a decoding function that recreates the input data from the encoded representation. The autoencoder learns an efficient representation (encoding) for a set of data, typically for dimensionality reduction. - -## Unsupervised Learning - -Autoencoder is a kind of unsupervised learning, which means working with datasets without considering a target variable. There are some Applications and Goals for it: - -- Finding hidden structures in data. -- Data compression. -- Clustering. -- Retrieving similar objects. -- Exploratory data analysis. -- Generating new examples. - -And for unsupervised learning, its main Principal Component Analysis (PCA) is: - -- Find directions of maximum variance - -:::{figure-md} 01_PCA1 - - -Illustration of PCA -::: - -- Transform features onto directions of maximum variance - -:::{figure-md} 02_PCA2 - - -Illustration of PCA -::: - -- Usually consider a subset of vectors of most variance (dimensionality reduction) - -:::{figure-md} 03_PCA3 - - -Illustration of PCA -::: - -## Fully-connected Autoencoder - -Here is an example of a basic fully-connected autoencoder - -:::{figure-md} 04_simple - - -Illustration of Fully Connected autoencoder -::: - -```{note} -If we don't use non-linear activation functions and minimize the MSE, this is very similar to PCA. However, the latent dimensions will not necessarily be orthogonal and will have same variance. -``` - -The loss function of this simple model is $L(x, x^') = ||x - x^'||^2_2 = \sum_i (x_i - x_i^')^2$. - -### Potential Autoencoder Applications - -And there are some potential autoencoder applications, for example: -- After training, disregard the output part, we can use embedding as input to classic machine learning methods (SVM, KNN, Random Forest, ...). -- Similar to transfer learning, we can train autoencoder on large image dataset, then fine tune encoder part on your own, smaller dataset and/or provide your own output (classification) layer. -- Latent space can also be used for visualization (EDA, clustering), but there are better methods for that. - -## Convolutional Autoencoder - -For convolutional autoencoder, we mainly use transposed convolution construct the output, and transposed convolution (sometimes called "deconvolution") allows us to increase the size of the output feature map compared to the input feature map. - -The difference between regular convolution and transposed convolution can be seen from the following image. - -:::{figure-md} 05_diff_conv - - -Difference between regular and transposed convolution -::: - -In transposed convolutions, we stride over the output; hence, larger strides will result in larger outputs (opposite to regular convolutions); and we pad the output; hence, larger padding will result in smaller output maps. - -So, the whole model consists of two parts, encoder and decoder, and they are composed with regular convolution and transposed convolution respectively. - -:::{figure-md} 06_convmodel - - -Structure of convoluted autoencoder -::: - -```{note} -Here is some other tricks to help our training: -1. Add dropout layers to force networks to learn redundant features. -2. Add dropout after the input, or add noise to the input to learn to denoise images. -3. Add L1 penalty to the loss to learn sparse feature representations. -``` - -## Code - -Let's build a 2-layers auto-encoder with TensorFlow to compress images to a lower latent space and then reconstruct them. And this project will be done on MNIST dataste. - -```{code-cell} -import tensorflow as tf -import numpy as np -``` - -MNIST Dataset parameters. - -```{code-cell} -num_features = 784 # data features (img shape: 28*28). -``` - -Training parameters. - -```{code-cell} -learning_rate = 0.01 -training_steps = 20000 -batch_size = 256 -display_step = 1000 -``` - -Network Parameters - -```{code-cell} -num_hidden_1 = 128 # 1st layer num features. -num_hidden_2 = 64 # 2nd layer num features (the latent dim). -``` - -Prepare MNIST data. - -```{code-cell} -from tensorflow.keras.datasets import mnist -(x_train, y_train), (x_test, y_test) = mnist.load_data() -# Convert to float32. -x_train, x_test = x_train.astype(np.float32), x_test.astype(np.float32) -# Flatten images to 1-D vector of 784 features (28*28). -x_train, x_test = x_train.reshape([-1, num_features]), x_test.reshape([-1, num_features]) -# Normalize images value from [0, 255] to [0, 1]. -x_train, x_test = x_train / 255., x_test / 255. -``` - -Store layers weight & bias. -A random value generator to initialize weights. - -```{code-cell} -random_normal = tf.initializers.RandomNormal() - -weights = { - 'encoder_h1': tf.Variable(random_normal([num_features, num_hidden_1])), - 'encoder_h2': tf.Variable(random_normal([num_hidden_1, num_hidden_2])), - 'decoder_h1': tf.Variable(random_normal([num_hidden_2, num_hidden_1])), - 'decoder_h2': tf.Variable(random_normal([num_hidden_1, num_features])), -} -biases = { - 'encoder_b1': tf.Variable(random_normal([num_hidden_1])), - 'encoder_b2': tf.Variable(random_normal([num_hidden_2])), - 'decoder_b1': tf.Variable(random_normal([num_hidden_1])), - 'decoder_b2': tf.Variable(random_normal([num_features])), -} -``` - -Building the encoder. - -```{code-cell} -def encoder(x): - # Encoder Hidden layer with sigmoid activation. - layer_1 = tf.nn.sigmoid(tf.add(tf.matmul(x, weights['encoder_h1']), - biases['encoder_b1'])) - # Encoder Hidden layer with sigmoid activation. - layer_2 = tf.nn.sigmoid(tf.add(tf.matmul(layer_1, weights['encoder_h2']), - biases['encoder_b2'])) - return layer_2 -``` - -Building the decoder. - -```{code-cell} -def decoder(x): - # Decoder Hidden layer with sigmoid activation. - layer_1 = tf.nn.sigmoid(tf.add(tf.matmul(x, weights['decoder_h1']), - biases['decoder_b1'])) - # Decoder Hidden layer with sigmoid activation. - layer_2 = tf.nn.sigmoid(tf.add(tf.matmul(layer_1, weights['decoder_h2']), - biases['decoder_b2'])) - return layer_2 -``` - -Mean square loss between original images and reconstructed ones. - -```{code-cell} -def mean_square(reconstructed, original): - return tf.reduce_mean(tf.pow(original - reconstructed, 2)) -``` - -Adam optimizer. - -```{code-cell} -optimizer = tf.optimizers.Adam(learning_rate=learning_rate) -``` - - -Optimization process. - -```{code-cell} -def run_optimization(x): - # Wrap computation inside a GradientTape for automatic differentiation. - with tf.GradientTape() as g: - reconstructed_image = decoder(encoder(x)) - loss = mean_square(reconstructed_image, x) - - # Variables to update, i.e. trainable variables. - trainable_variables = weights.values() + biases.values() - - # Compute gradients. - gradients = g.gradient(loss, trainable_variables) - - # Update W and b following gradients. - optimizer.apply_gradients(zip(gradients, trainable_variables)) - - return loss -``` - -Run training for the given number of steps. - -```{code-cell} -for step, (batch_x, _) in enumerate(train_data.take(training_steps + 1)): - - # Run the optimization. - loss = run_optimization(batch_x) - - if step % display_step == 0: - print("step: %i, loss: %f" % (step, loss)) -``` - -Testing and Visualization. - -```{code-cell} -import matplotlib.pyplot as plt -``` - -Encode and decode images from test set and visualize their reconstruction. - -```{code-cell} -n = 4 -canvas_orig = np.empty((28 * n, 28 * n)) -canvas_recon = np.empty((28 * n, 28 * n)) -for i, (batch_x, _) in enumerate(test_data.take(n)): - # Encode and decode the digit image. - reconstructed_images = decoder(encoder(batch_x)) - # Display original images. - for j in range(n): - # Draw the generated digits. - img = batch_x[j].numpy().reshape([28, 28]) - canvas_orig[i * 28:(i + 1) * 28, j * 28:(j + 1) * 28] = img - # Display reconstructed images. - for j in range(n): - # Draw the generated digits. - reconstr_img = reconstructed_images[j].numpy().reshape([28, 28]) - canvas_recon[i * 28:(i + 1) * 28, j * 28:(j + 1) * 28] = reconstr_img - -print("Original Images") -plt.figure(figsize=(n, n)) -plt.imshow(canvas_orig, origin="upper", cmap="gray") -plt.show() - -print("Reconstructed Images") -plt.figure(figsize=(n, n)) -plt.imshow(canvas_recon, origin="upper", cmap="gray") -plt.show() -``` - - - - - - -## Your turn! 🚀 - -TBD. - -## Self study - -You can refer to this book chapter for further study: - -- [deeplearningbook](https://www.deeplearningbook.org/contents/autoencoders.html) - -## Acknowledgments - -Thanks to [Sebastian Raschka](https://github.com/rasbt) for creating the open-source project [stat453-deep-learning-ss20](https://github.com/rasbt/stat453-deep-learning-ss20) and [Aymeric Damien](https://github.com/aymericdamien) for creating the open-source project [TensorFlow-Examples](https://github.com/aymericdamien/TensorFlow-Examples/). They inspire the majority of the content in this chapter. - ---- - -```{bibliography} -:filter: docname in docnames -``` \ No newline at end of file diff --git a/open-machine-learning-jupyter-book/deep-learning/cnn.ipynb b/open-machine-learning-jupyter-book/deep-learning/cnn.ipynb new file mode 100644 index 0000000000..26a91bb947 --- /dev/null +++ b/open-machine-learning-jupyter-book/deep-learning/cnn.ipynb @@ -0,0 +1,1216 @@ +{ + "cells": [ + { + "cell_type": "code", + "execution_count": null, + "metadata": { + "tags": [ + "hide-cell" + ] + }, + "outputs": [], + "source": [ + "# Install the necessary dependencies\n", + "\n", + "import os\n", + "import sys \n", + "!{sys.executable} -m pip install --quiet pandas scikit-learn numpy matplotlib jupyterlab_myst ipython imageio scikit-image requests\n", + "# Convolutional Neural Networks" + ] + }, + { + "cell_type": "markdown", + "metadata": { + "tags": [ + "remove-cell" + ] + }, + "source": [ + "---\n", + "license:\n", + " code: MIT\n", + " content: CC-BY-4.0\n", + "github: https://github.com/ocademy-ai/machine-learning\n", + "venue: By Ocademy\n", + "open_access: true\n", + "bibliography:\n", + " - https://raw.githubusercontent.com/ocademy-ai/machine-learning/main/open-machine-learning-jupyter-book/references.bib\n", + "---" + ] + }, + { + "attachments": {}, + "cell_type": "markdown", + "metadata": {}, + "source": [ + "# Convolutional Neural Networks" + ] + }, + { + "attachments": {}, + "cell_type": "markdown", + "metadata": {}, + "source": [ + "Convolutional Neural Networks (CNNs) are responsible for the latest major breakthroughs in image recognition in the past few years.\n", + "\n", + "In mathematics, a convolution is a function that is applied over the output of another function. In our case, we will consider applying a matrix multiplication (filter) across an image. See the below diagram for an example of how this may work.\n", + "\n", + "" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "metadata": {}, + "outputs": [], + "source": [ + "from IPython.display import HTML\n", + "display(HTML(\"\"\"\n", + "

\n", + "\n", + "A demo of convolution function. [source]\n", + "

\n", + "\"\"\"))" + ] + }, + { + "attachments": {}, + "cell_type": "markdown", + "metadata": {}, + "source": [ + "CNNs generally follow a structure. The main convolutional setup is (input array) -> (convolutional filter layer) -> (Pooling) -> (Activation layer). The above diagram depicts how a convolutional layer may create one feature. Generally, filters are multidimensional and end up creating many features. It is also common to have a completely separate filter-feature creator of different sizes acting on the same layer. After this convolutional filter, it is common to apply a pooling layer. This pooling may be a max-pooling or an average pooling or another aggregation. One of the key concepts here is that the pooling layer has no parameters while decreasing the layer size. See the below diagram for an example of max-pooling.\n", + "\n", + "\n", + "\n", + "After the max pooling, there is generally an activation layer. One of the more common activation layers is the ReLU (Rectified Linear Unit) {cite}`reluwiki`." + ] + }, + { + "attachments": {}, + "cell_type": "markdown", + "metadata": {}, + "source": [ + "## MNIST handwritten digits\n", + "\n", + "Here we illustrate how to use a simple CNN with three convolutional units to predict the MNIST handwritten digits. \n", + "\n", + "```{note}\n", + "There is good reason why this dataset is used like the 'hello world' of image recognition, it is fairly compact while having a decent amount of training, test, and validation data. It only has one channel (black and white) and only ten possible outputs (0-9).\n", + "```\n", + "\n", + "When the script is done training the model, you should see similar output to the following graphs.\n", + "\n", + "\n", + "\n", + "Training and test loss (left) and test batch accuracy (right).\n", + "\n", + "\n", + "\n", + "A random set of 6 digits with actual and predicted labels. You can see a prediction failure in the lower right box.\n" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "metadata": {}, + "outputs": [], + "source": [ + "from IPython.display import HTML\n", + "display(HTML(\"\"\"\n", + "

\n", + "\n", + "A demo of CNN. [source]\n", + "

\n", + "\"\"\"))" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "metadata": {}, + "outputs": [], + "source": [ + "from IPython.display import HTML\n", + "display(HTML(\"\"\"\n", + "

\n", + "\n", + "A demo of CNN. [source]\n", + "

\n", + "\"\"\"))" + ] + }, + { + "attachments": {}, + "cell_type": "markdown", + "metadata": {}, + "source": [ + "### Code" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "metadata": {}, + "outputs": [], + "source": [ + "import tensorflow as tf\n", + "from tensorflow.keras.datasets import mnist\n", + "import matplotlib.pyplot as plt\n", + "\n", + "# Load dataset\n", + "(x_train, y_train), (x_test, y_test) = mnist.load_data()\n", + "\n", + "# Data preprocessing\n", + "x_train = x_train.reshape(-1, 28, 28, 1).astype('float32') / 255.0\n", + "x_test = x_test.reshape(-1, 28, 28, 1).astype('float32') / 255.0\n", + "\n", + "# Build model\n", + "model = tf.keras.Sequential([\n", + " tf.keras.layers.Conv2D(32, (3, 3), activation='relu', input_shape=(28, 28, 1)),\n", + " tf.keras.layers.MaxPooling2D((2, 2)),\n", + " tf.keras.layers.Conv2D(64, (3, 3), activation='relu'),\n", + " tf.keras.layers.MaxPooling2D((2, 2)),\n", + " tf.keras.layers.Flatten(),\n", + " tf.keras.layers.Dense(64, activation='relu'),\n", + " tf.keras.layers.Dense(10, activation='softmax')\n", + "])\n", + "\n", + "# Compiler model\n", + "model.compile(optimizer='adam', loss='sparse_categorical_crossentropy', metrics=['accuracy'])\n", + "\n", + "# Train model\n", + "history = model.fit(x_train, y_train, epochs=5, validation_data=(x_test, y_test))\n", + "\n", + "# Test model\n", + "test_loss, test_acc = model.evaluate(x_test, y_test, verbose=2)\n", + "print('Test accuracy:', test_acc)\n", + "\n", + "# Visualizing the training process\n", + "plt.plot(history.history['accuracy'], label='Training Accuracy')\n", + "plt.plot(history.history['val_accuracy'], label='Validation Accuracy')\n", + "plt.xlabel('Epoch')\n", + "plt.ylabel('Accuracy')\n", + "plt.legend()\n", + "plt.show()" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "metadata": {}, + "outputs": [], + "source": [ + "import matplotlib.pyplot as plt\n", + "import numpy as np\n", + "import tensorflow as tf\n", + "from tensorflow.keras.datasets import mnist\n", + "\n", + "# Load data\n", + "(train_images, train_labels), (test_images, test_labels) = mnist.load_data()\n", + "\n", + "# Normalize pixel values to the range [0, 1]\n", + "train_images = train_images / 255.0\n", + "test_images = test_images / 255.0\n", + "\n", + "# Convert images to 4D tensors (batch_size, height, width, channels)\n", + "train_images = np.expand_dims(train_images, axis=-1)\n", + "test_images = np.expand_dims(test_images, axis=-1)\n", + "\n", + "# Set model parameters\n", + "batch_size = 100\n", + "learning_rate = 0.005\n", + "evaluation_size = 500\n", + "image_width = train_images.shape[1]\n", + "image_height = train_images.shape[2]\n", + "target_size = np.max(train_labels) + 1\n", + "num_channels = 1 # greyscale = 1 channel\n", + "generations = 500\n", + "eval_every = 5\n", + "conv1_features = 25\n", + "conv2_features = 50\n", + "max_pool_size1 = 2 # NxN window for 1st max pool layer\n", + "max_pool_size2 = 2 # NxN window for 2nd max pool layer\n", + "fully_connected_size1 = 100\n", + "\n", + "# Define the model\n", + "model = tf.keras.Sequential([\n", + " tf.keras.layers.Conv2D(conv1_features, (4, 4), activation='relu', input_shape=(image_width, image_height, num_channels)),\n", + " tf.keras.layers.MaxPooling2D((max_pool_size1, max_pool_size1)),\n", + " tf.keras.layers.Conv2D(conv2_features, (4, 4), activation='relu'),\n", + " tf.keras.layers.MaxPooling2D((max_pool_size2, max_pool_size2)),\n", + " tf.keras.layers.Flatten(),\n", + " tf.keras.layers.Dense(fully_connected_size1, activation='relu'),\n", + " tf.keras.layers.Dense(target_size)\n", + "])\n", + "\n", + "# Compile the model\n", + "model.compile(optimizer=tf.keras.optimizers.SGD(learning_rate=learning_rate, momentum=0.9),\n", + " loss=tf.keras.losses.SparseCategoricalCrossentropy(from_logits=True),\n", + " metrics=['accuracy'])\n", + "\n", + "# Train the model\n", + "train_loss = []\n", + "train_acc = []\n", + "test_acc = []\n", + "for i in range(generations):\n", + " rand_index = np.random.choice(len(train_images), size=batch_size)\n", + " rand_x = train_images[rand_index]\n", + " rand_y = train_labels[rand_index]\n", + " \n", + " history = model.train_on_batch(rand_x, rand_y)\n", + " temp_train_loss, temp_train_acc = history[0], history[1]\n", + " \n", + " if (i+1) % eval_every == 0:\n", + " eval_index = np.random.choice(len(test_images), size=evaluation_size)\n", + " eval_x = test_images[eval_index]\n", + " eval_y = test_labels[eval_index]\n", + " \n", + " test_loss, temp_test_acc = model.evaluate(eval_x, eval_y, verbose=0)\n", + " \n", + " # Record and print results\n", + " train_loss.append(temp_train_loss)\n", + " train_acc.append(temp_train_acc)\n", + " test_acc.append(temp_test_acc)\n", + " acc_and_loss = [(i+1), temp_train_loss, temp_train_acc * 100, temp_test_acc * 100]\n", + " acc_and_loss = [np.round(x, 2) for x in acc_and_loss]\n", + " print('Generation # {}. Train Loss: {:.2f}. Train Acc (Test Acc): {:.2f}% ({:.2f}%)'.format(*acc_and_loss))\n", + "\n", + "# Plot loss over time\n", + "plt.plot(range(0, generations, eval_every), train_loss, 'k-')\n", + "plt.title('Softmax Loss per Generation')\n", + "plt.xlabel('Generation')\n", + "plt.ylabel('Softmax Loss')\n", + "plt.show()\n", + "\n", + "# Plot train and test accuracy\n", + "plt.plot(range(0, generations, eval_every), train_acc, 'k-', label='Train Set Accuracy')\n", + "plt.plot(range(0, generations, eval_every), test_acc, 'r--', label='Test Set Accuracy')\n", + "plt.title('Train and Test Accuracy')\n", + "plt.xlabel('Generation')\n", + "plt.ylabel('Accuracy')\n", + "plt.legend(loc='lower right')\n", + "plt.show()\n", + "\n", + "# Plot some samples\n", + "# Plot the 6 of the last batch results:\n", + "predictions = model.predict(train_images[:6])\n", + "predictions = np.argmax(predictions, axis=1)\n", + "images = np.squeeze(train_images[:6])\n", + "\n", + "Nrows = 2\n", + "Ncols = 3\n", + "for i in range(6):\n", + " plt.subplot(Nrows, Ncols, i+1)\n", + " plt.imshow(np.reshape(images[i], [28, 28]), cmap='Greys_r')\n", + " plt.title('Pred: ' + str(predictions[i]), fontsize=10)\n", + " frame = plt.gca()\n", + " frame.axes.get_xaxis().set_visible(False)\n", + " frame.axes.get_yaxis().set_visible(False)\n", + "plt.show()" + ] + }, + { + "attachments": {}, + "cell_type": "markdown", + "metadata": {}, + "source": [ + "## CIFAR-10\n", + "\n", + "Here we will build a convolutional neural network to predict the `CIFAR-10` data.\n", + "\n", + "The script provided will download and unzip the `CIFAR-10` data. Then it will start training a CNN from scratch. You should see similar output at the end of the following two graphs.\n", + "\n", + "\n", + "\n", + "Here we see the training loss (left) and the test batch accuracy (right)." + ] + }, + { + "attachments": {}, + "cell_type": "markdown", + "metadata": {}, + "source": [ + "### Code" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "metadata": {}, + "outputs": [], + "source": [ + "import os\n", + "import tensorflow as tf\n", + "import matplotlib.pyplot as plt\n", + "import urllib.request\n", + "\n", + "# Set model parameters\n", + "batch_size = 128\n", + "data_dir = 'temp'\n", + "output_every = 50\n", + "generations = 200\n", + "eval_every = 500\n", + "image_height = 32\n", + "image_width = 32\n", + "crop_height = 24\n", + "crop_width = 24\n", + "num_channels = 3\n", + "num_targets = 10\n", + "extract_folder = 'cifar-10-batches-bin'\n", + "\n", + "# Load data\n", + "data_dir = 'temp'\n", + "if not os.path.exists(data_dir):\n", + " os.makedirs(data_dir)\n", + "cifar10_url = 'http://www.cs.toronto.edu/~kriz/cifar-10-binary.tar.gz'\n", + "\n", + "# Check if file exists, otherwise download it\n", + "data_file = os.path.join(data_dir, 'cifar-10-binary.tar.gz')\n", + "if os.path.isfile(data_file):\n", + " pass\n", + "else:\n", + " # Download file\n", + " def progress(block_num, block_size, total_size):\n", + " progress_info = [cifar10_url, float(block_num * block_size) / float(total_size) * 100.0]\n", + " print('\\r Downloading {} - {:.2f}%'.format(*progress_info), end=\"\")\n", + " filepath, _ = urllib.request.urlretrieve(cifar10_url, data_file, progress)\n", + " # Extract file\n", + " tarfile.open(filepath, 'r:gz').extractall(data_dir)\n", + "\n", + "# Load CIFAR-10 dataset\n", + "(train_images, train_labels), (test_images, test_labels) = tf.keras.datasets.cifar10.load_data()\n", + "\n", + "# Preprocess the data\n", + "train_images = train_images / 255.0\n", + "test_images = test_images / 255.0\n", + "\n", + "# Crop images\n", + "train_images = tf.image.crop_to_bounding_box(train_images, 4, 4, 24, 24)\n", + "test_images = tf.image.crop_to_bounding_box(test_images, 4, 4, 24, 24)\n", + "\n" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "metadata": {}, + "outputs": [], + "source": [ + "# Convert labels to integers\n", + "train_labels = train_labels.flatten()\n", + "test_labels = test_labels.flatten()\n", + "\n", + "# Define the model architecture\n", + "model = tf.keras.Sequential([\n", + " tf.keras.layers.Conv2D(64, (5, 5), activation='relu', input_shape=(crop_height, crop_width, num_channels)),\n", + " tf.keras.layers.MaxPooling2D(pool_size=(3, 3), strides=(2, 2)),\n", + " tf.keras.layers.Conv2D(64, (5, 5), activation='relu'),\n", + " tf.keras.layers.MaxPooling2D(pool_size=(3, 3), strides=(2, 2)),\n", + " tf.keras.layers.Flatten(),\n", + " tf.keras.layers.Dense(384, activation='relu'),\n", + " tf.keras.layers.Dense(192, activation='relu'),\n", + " tf.keras.layers.Dense(num_targets)\n", + "])\n", + "\n", + "# Define loss function\n", + "loss_fn = tf.keras.losses.SparseCategoricalCrossentropy(from_logits=True)\n", + "\n", + "# Create accuracy metric\n", + "accuracy_metric = tf.keras.metrics.SparseCategoricalAccuracy()\n", + "\n", + "# Create optimizer\n", + "optimizer = tf.keras.optimizers.SGD(learning_rate=0.1, momentum=0.9)\n", + "\n", + "# Compile the model\n", + "model.compile(optimizer=optimizer, loss=loss_fn, metrics=[accuracy_metric])\n", + "\n", + "# Train the model\n", + "history = model.fit(train_images, train_labels, batch_size=batch_size, epochs=generations, \n", + " validation_data=(test_images, test_labels), verbose=1)\n", + "\n", + "# Evaluate the model\n", + "test_loss, test_accuracy = model.evaluate(test_images, test_labels, verbose=0)\n", + "\n", + "# Print loss and accuracy\n", + "print('Test Loss:', test_loss)\n", + "print('Test Accuracy:', test_accuracy)\n", + "\n", + "# Plot loss over time\n", + "plt.plot(history.history['loss'], 'k-')\n", + "plt.title('Softmax Loss per Generation')\n", + "plt.xlabel('Generation')\n", + "plt.ylabel('Softmax Loss')\n", + "plt.show()\n", + "\n", + "# Plot accuracy over time\n", + "plt.plot(history.history['sparse_categorical_accuracy'], 'k-')\n", + "plt.title('Test Accuracy')\n", + "plt.xlabel('Generation')\n", + "plt.ylabel('Accuracy')\n", + "plt.show()" + ] + }, + { + "attachments": {}, + "cell_type": "markdown", + "metadata": {}, + "source": [ + "## How to fine-tune current CNN architectures?" + ] + }, + { + "attachments": {}, + "cell_type": "markdown", + "metadata": {}, + "source": [ + "The purpose of the script provided in this section is to download the CIFAR-10 data and sort it out in the proper folder structure for running it through the TensorFlow fine-tuning tutorial. The script should create the following folder structure.\n", + "\n", + "-train_dir\n", + " |--airplane\n", + " |--automobile\n", + " |--bird\n", + " |--cat\n", + " |--deer\n", + " |--dog\n", + " |--frog\n", + " |--horse\n", + " |--ship\n", + " |--truck\n", + "-validation_dir\n", + " |--airplane\n", + " |--automobile\n", + " |--bird\n", + " |--cat\n", + " |--deer\n", + " |--dog\n", + " |--frog\n", + " |--horse\n", + " |--ship\n", + " |--truck\n", + "\n", + " ### Code" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "metadata": {}, + "outputs": [], + "source": [ + "# In this script, we download the CIFAR-10 images and\n", + "# transform/save them in the Inception Retraining Format\n", + "#\n", + "# The end purpose of the files is for re-training the\n", + "# Google Inception tensorflow model to work on the CIFAR-10.\n", + "\n", + "import os\n", + "import tarfile\n", + "import pickle as cPickle\n", + "import numpy as np\n", + "import urllib.request\n", + "import imageio\n", + "from tensorflow.python.framework import ops\n", + "ops.reset_default_graph()\n", + "\n", + "cifar_link = 'https://www.cs.toronto.edu/~kriz/cifar-10-python.tar.gz'\n", + "data_dir = 'temp'\n", + "if not os.path.isdir(data_dir):\n", + " os.makedirs(data_dir)\n", + "\n", + "# Download tar file\n", + "target_file = os.path.join(data_dir, 'cifar-10-python.tar.gz')\n", + "if not os.path.isfile(target_file):\n", + " print('CIFAR-10 file not found. Downloading CIFAR data (Size = 163MB)')\n", + " print('This may take a few minutes, please wait.')\n", + " filename, headers = urllib.request.urlretrieve(cifar_link, target_file)\n", + "\n", + "# Extract into memory\n", + "tar = tarfile.open(target_file)\n", + "tar.extractall(path=data_dir)\n", + "tar.close()\n", + "objects = ['airplane', 'automobile', 'bird', 'cat', 'deer', 'dog', 'frog', 'horse', 'ship', 'truck']\n", + "\n", + "# Create train image folders\n", + "train_folder = 'train_dir'\n", + "if not os.path.isdir(os.path.join(data_dir, train_folder)):\n", + " for i in range(10):\n", + " folder = os.path.join(data_dir, train_folder, objects[i])\n", + " os.makedirs(folder)\n", + "# Create test image folders\n", + "test_folder = 'validation_dir'\n", + "if not os.path.isdir(os.path.join(data_dir, test_folder)):\n", + " for i in range(10):\n", + " folder = os.path.join(data_dir, test_folder, objects[i])\n", + " os.makedirs(folder)\n", + "\n", + "# Extract images accordingly\n", + "data_location = os.path.join(data_dir, 'cifar-10-batches-py')\n", + "train_names = ['data_batch_' + str(x) for x in range(1,6)]\n", + "test_names = ['test_batch']\n", + "\n", + "\n", + "def load_batch_from_file(file):\n", + " file_conn = open(file, 'rb')\n", + " image_dictionary = cPickle.load(file_conn, encoding='latin1')\n", + " file_conn.close()\n", + " return image_dictionary\n", + "\n", + "\n", + "def save_images_from_dict(image_dict, folder='data_dir'):\n", + " # image_dict.keys() = 'labels', 'filenames', 'data', 'batch_label'\n", + " for ix, label in enumerate(image_dict['labels']):\n", + " folder_path = os.path.join(data_dir, folder, objects[label])\n", + " filename = image_dict['filenames'][ix]\n", + " # Transform image data\n", + " image_array = image_dict['data'][ix]\n", + " image_array.resize([3, 32, 32])\n", + " # Save image using imageio\n", + " output_location = os.path.join(folder_path, filename)\n", + " # Ensure the pixel values are in the range [0, 255]\n", + " image_array = np.clip(image_array, 0, 255).astype(np.uint8)\n", + " imageio.imwrite(output_location, image_array.transpose(1, 2, 0))\n", + "\n", + "# Sort train images\n", + "for file in train_names:\n", + " print('Saving images from file: {}'.format(file))\n", + " file_location = os.path.join(data_dir, 'cifar-10-batches-py', file)\n", + " image_dict = load_batch_from_file(file_location)\n", + " save_images_from_dict(image_dict, folder=train_folder)\n", + "\n", + "# Sort test images\n", + "for file in test_names:\n", + " print('Saving images from file: {}'.format(file))\n", + " file_location = os.path.join(data_dir, 'cifar-10-batches-py', file)\n", + " image_dict = load_batch_from_file(file_location)\n", + " save_images_from_dict(image_dict, folder=test_folder)\n", + " \n", + "# Create labels file\n", + "cifar_labels_file = os.path.join(data_dir,'cifar10_labels.txt')\n", + "print('Writing labels file, {}'.format(cifar_labels_file))\n", + "with open(cifar_labels_file, 'w') as labels_file:\n", + " for item in objects:\n", + " labels_file.write(\"{}\\n\".format(item))" + ] + }, + { + "attachments": {}, + "cell_type": "markdown", + "metadata": {}, + "source": [ + "## Stylenet / Neural-Style\n", + "\n", + "The purpose of this script is to illustrate how to do stylenet in TensorFlow. We reference the following [paper](https://arxiv.org/abs/1508.06576) for this algorithm.\n", + "\n", + "But there is some prerequisites,\n", + "\n", + "- Download the `VGG-verydeep-19.mat` file.\n", + "- You must download two images, a style image and a content image for the algorithm to blend.\n", + "\n", + "The style image is\n", + "\n", + ":::{figure} https://static-1300131294.cos.ap-shanghai.myqcloud.com/images/deep-learning/CNN/starry_night.jpg\n", + "---\n", + "name: Style image: starry night\n", + ":::\n", + "\n", + "The context image is below.\n", + "\n", + ":::{figure} https://static-1300131294.cos.ap-shanghai.myqcloud.com/images/deep-learning/CNN/book_cover.jpg\n", + "---\n", + "name: Content image: book cover\n", + ":::\n", + "\n", + "The final result looks like\n", + "\n", + ":::{figure} https://static-1300131294.cos.ap-shanghai.myqcloud.com/images/deep-learning/CNN/05_stylenet_ex.png\n", + "---\n", + "name: stylenet final result\n", + ":::\n" + ] + }, + { + "attachments": {}, + "cell_type": "markdown", + "metadata": {}, + "source": [ + "### Code" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "metadata": {}, + "outputs": [], + "source": [ + "# We use two images, an original image and a style image\n", + "# and try to make the original image in the style of the style image.\n", + "#\n", + "# Reference paper:\n", + "# https://arxiv.org/abs/1508.06576\n", + "#\n", + "# Need to download the model 'imagenet-vgg-verydee-19.mat' from:\n", + "# http://www.vlfeat.org/matconvnet/models/beta16/imagenet-vgg-verydeep-19.mat\n", + "\n", + "import os\n", + "import scipy.io\n", + "import scipy.misc\n", + "import imageio\n", + "from skimage.transform import resize\n", + "from operator import mul\n", + "from functools import reduce\n", + "from PIL import Image\n", + "import numpy as np\n", + "import requests\n", + "import tensorflow.compat.v1 as tf\n", + "tf.disable_eager_execution()\n", + "from tensorflow.python.framework import ops\n", + "ops.reset_default_graph()\n", + "\n", + "# URLs\n", + "original_image_url = 'https://static-1300131294.cos.ap-shanghai.myqcloud.com/images/deep-learning/CNN/book_cover.jpg'\n", + "style_image_url = 'https://static-1300131294.cos.ap-shanghai.myqcloud.com/images/deep-learning/CNN/starry_night.jpg'\n", + "vgg_url = 'https://static-1300131294.cos.ap-shanghai.myqcloud.com/data/deep-learning/cnn/imagenet-vgg-verydeep-19.mat'\n", + "\n", + "# Local directories\n", + "data_dir = 'temp'\n", + "vgg_dir = os.path.join(data_dir, 'VGG')\n", + "if not os.path.exists(vgg_dir):\n", + " os.makedirs(vgg_dir)\n", + "\n", + "# Function to download and save a file\n", + "def download_file(url, directory):\n", + " response = requests.get(url)\n", + " filename = url.split('/')[-1]\n", + " filepath = os.path.join(directory, filename)\n", + " with open(filepath, 'wb') as f:\n", + " f.write(response.content)\n", + " return filepath\n", + "\n", + "# Download images and VGG Network\n", + "original_image_path = download_file(original_image_url, data_dir)\n", + "style_image_path = download_file(style_image_url, data_dir)\n", + "vgg_path = download_file(vgg_url, vgg_dir)\n", + "\n", + "# Load images using PIL and convert to NumPy arrays\n", + "original_image = Image.open(original_image_path)\n", + "style_image = Image.open(style_image_path)\n", + "original_image = np.array(original_image)\n", + "style_image = np.array(style_image)\n", + "\n", + "# Default Arguments\n", + "original_image_weight = 5.0\n", + "style_image_weight = 500.0\n", + "regularization_weight = 100\n", + "learning_rate = 10\n", + "generations = 100\n", + "output_generations = 25\n", + "beta1 = 0.9\n", + "beta2 = 0.999\n", + "\n", + "# Get shape of target and make the style image the same\n", + "target_shape = original_image.shape\n", + "style_image = resize(style_image, target_shape)\n", + "\n", + "# VGG-19 Layer Setup\n", + "# From paper\n", + "vgg_layers = ['conv1_1', 'relu1_1',\n", + " 'conv1_2', 'relu1_2', 'pool1',\n", + " 'conv2_1', 'relu2_1',\n", + " 'conv2_2', 'relu2_2', 'pool2',\n", + " 'conv3_1', 'relu3_1',\n", + " 'conv3_2', 'relu3_2',\n", + " 'conv3_3', 'relu3_3',\n", + " 'conv3_4', 'relu3_4', 'pool3',\n", + " 'conv4_1', 'relu4_1',\n", + " 'conv4_2', 'relu4_2',\n", + " 'conv4_3', 'relu4_3',\n", + " 'conv4_4', 'relu4_4', 'pool4',\n", + " 'conv5_1', 'relu5_1',\n", + " 'conv5_2', 'relu5_2',\n", + " 'conv5_3', 'relu5_3',\n", + " 'conv5_4', 'relu5_4']\n", + "\n", + "\n", + "# Extract weights and matrix means\n", + "def extract_net_info(path_to_params):\n", + " vgg_data = scipy.io.loadmat(path_to_params)\n", + " normalization_matrix = vgg_data['normalization'][0][0][0]\n", + " mat_mean = np.mean(normalization_matrix, axis=(0, 1))\n", + " network_weights = vgg_data['layers'][0]\n", + " return mat_mean, network_weights\n", + " \n", + "\n", + "# Create the VGG-19 Network\n", + "def vgg_network(network_weights, init_image):\n", + " network = {}\n", + " image = init_image\n", + "\n", + " for i, layer in enumerate(vgg_layers):\n", + " if layer[0] == 'c':\n", + " weights, bias = network_weights[i][0][0][0][0]\n", + " weights = np.transpose(weights, (1, 0, 2, 3))\n", + " bias = bias.reshape(-1)\n", + " conv_layer = tf.nn.conv2d(image, tf.constant(weights), (1, 1, 1, 1), 'SAME')\n", + " image = tf.nn.bias_add(conv_layer, bias)\n", + " elif layer[0] == 'r':\n", + " image = tf.nn.relu(image)\n", + " else: # pooling\n", + " image = tf.nn.max_pool(image, (1, 2, 2, 1), (1, 2, 2, 1), 'SAME')\n", + " network[layer] = image\n", + " return network\n", + "\n", + "# Here we define which layers apply to the original or style image\n", + "original_layers = ['relu4_2', 'relu5_2']\n", + "style_layers = ['relu1_1', 'relu2_1', 'relu3_1', 'relu4_1', 'relu5_1']\n", + "\n", + "# Get network parameters\n", + "normalization_mean, network_weights = extract_net_info(vgg_path)\n", + "\n", + "shape = (1,) + original_image.shape\n", + "style_shape = (1,) + style_image.shape\n", + "original_features = {}\n", + "style_features = {}\n", + "\n", + "# Set style weights\n", + "style_weights = {l: 1./(len(style_layers)) for l in style_layers}\n", + "\n", + "# Computer feature layers with original image\n", + "g_original = tf.Graph()\n", + "with g_original.as_default(), tf.Session() as sess1:\n", + " image = tf.placeholder('float', shape=shape)\n", + " vgg_net = vgg_network(network_weights, image)\n", + " original_minus_mean = original_image - normalization_mean\n", + " original_norm = np.array([original_minus_mean])\n", + " for layer in original_layers:\n", + " original_features[layer] = vgg_net[layer].eval(feed_dict={image: original_norm})\n", + "\n", + "# Get style image network\n", + "g_style = tf.Graph()\n", + "with g_style.as_default(), tf.Session() as sess2:\n", + " image = tf.placeholder('float', shape=style_shape)\n", + " vgg_net = vgg_network(network_weights, image)\n", + " style_minus_mean = style_image - normalization_mean\n", + " style_norm = np.array([style_minus_mean])\n", + " for layer in style_layers:\n", + " features = vgg_net[layer].eval(feed_dict={image: style_norm})\n", + " features = np.reshape(features, (-1, features.shape[3]))\n", + " gram = np.matmul(features.T, features) / features.size\n", + " style_features[layer] = gram\n", + "\n", + "# Make Combined Image via loss function\n", + "with tf.Graph().as_default():\n", + " # Get network parameters\n", + " initial = tf.random_normal(shape) * 0.256\n", + " init_image = tf.Variable(initial)\n", + " vgg_net = vgg_network(network_weights, init_image)\n", + "\n", + " # Loss from Original Image\n", + " original_layers_w = {'relu4_2': 0.5, 'relu5_2': 0.5}\n", + " original_loss = 0\n", + " for o_layer in original_layers:\n", + " temp_original_loss = original_layers_w[o_layer] * original_image_weight *\\\n", + " (2 * tf.nn.l2_loss(vgg_net[o_layer] - original_features[o_layer]))\n", + " original_loss += (temp_original_loss / original_features[o_layer].size)\n", + "\n", + " # Loss from Style Image\n", + " style_loss = 0\n", + " style_losses = []\n", + " for style_layer in style_layers:\n", + " layer = vgg_net[style_layer]\n", + " feats, height, width, channels = [x.value for x in layer.get_shape()]\n", + " size = height * width * channels\n", + " features = tf.reshape(layer, (-1, channels))\n", + " style_gram_matrix = tf.matmul(tf.transpose(features), features) / size\n", + " style_expected = style_features[style_layer]\n", + " style_losses.append(style_weights[style_layer] * 2 *\n", + " tf.nn.l2_loss(style_gram_matrix - style_expected) /\n", + " style_expected.size)\n", + " style_loss += style_image_weight * tf.reduce_sum(style_losses)\n", + "\n", + " # To Smooth the results, we add in total variation loss\n", + " total_var_x = reduce(mul, init_image[:, 1:, :, :].get_shape().as_list(), 1)\n", + " total_var_y = reduce(mul, init_image[:, :, 1:, :].get_shape().as_list(), 1)\n", + " first_term = regularization_weight * 2\n", + " second_term_numerator = tf.nn.l2_loss(init_image[:, 1:, :, :] - init_image[:, :shape[1]-1, :, :])\n", + " second_term = second_term_numerator / total_var_y\n", + " third_term = (tf.nn.l2_loss(init_image[:, :, 1:, :] - init_image[:, :, :shape[2]-1, :]) / total_var_x)\n", + " total_variation_loss = first_term * (second_term + third_term)\n", + "\n", + " # Combined Loss\n", + " loss = original_loss + style_loss + total_variation_loss\n", + "\n", + " # Declare Optimization Algorithm\n", + " optimizer = tf.train.AdamOptimizer(learning_rate, beta1, beta2)\n", + " train_step = optimizer.minimize(loss)\n", + "\n", + " # Initialize variables and start training\n", + " with tf.Session() as sess:\n", + " tf.global_variables_initializer().run()\n", + " for i in range(generations):\n", + "\n", + " train_step.run()\n", + "\n", + " # Print update and save temporary output\n", + " if (i+1) % output_generations == 0:\n", + " print('Generation {} out of {}, loss: {}'.format(i + 1, generations,sess.run(loss)))\n", + "\n", + " image_eval = init_image.eval(session=sess)\n", + " image_eval = image_eval.reshape(shape[1:]) # 确保形状正确\n", + " image_eval += normalization_mean # 加上均值\n", + " image_eval = np.clip(image_eval, 0, 255) # 确保值在0到255之间\n", + " image_eval = image_eval.astype(np.uint8) # 转换为uint8\n", + "\n", + "# 保存图像\n", + "output_file = 'temp_output_{}.jpg'.format(i)\n", + "imageio.imwrite(output_file, image_eval)" + ] + }, + { + "attachments": {}, + "cell_type": "markdown", + "metadata": {}, + "source": [ + "## Deepdream in TensorFlow\n", + "Note: There is no new code in this script. It originates from the TensorFlow tutorial located here. However, this code is modified slightly to run on Python 3. The code is also commented very heavily to explain, line-by-line, what occurs in the deepdream demo.\n", + "\n", + "Here are some potential outputs.\n", + "\n", + ":::{figure} https://static-1300131294.cos.ap-shanghai.myqcloud.com/images/deep-learning/CNN/06_deepdream_ex.png\n", + "---\n", + "name: Deepdream outputs\n", + ":::" + ] + }, + { + "attachments": {}, + "cell_type": "markdown", + "metadata": {}, + "source": [ + "### Code" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "metadata": {}, + "outputs": [], + "source": [ + "# Using TensorFlow for Deep Dream\n", + "#---------------------------------------\n", + "# From: Alexander Mordvintsev\n", + "# --https://www.tensorflow.org/tutorials/generative/deepdream\n", + "#\n", + "# And as this code use Tensorflow 2.x, you may need to restart the kernel to run successfully." + ] + }, + { + "cell_type": "code", + "execution_count": null, + "metadata": {}, + "outputs": [], + "source": [ + "import tensorflow as tf" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "metadata": {}, + "outputs": [], + "source": [ + "import numpy as np\n", + "\n", + "import matplotlib as mpl\n", + "\n", + "import IPython.display as display\n", + "import PIL.Image" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "metadata": {}, + "outputs": [], + "source": [ + "url = 'https://storage.googleapis.com/download.tensorflow.org/example_images/YellowLabradorLooking_new.jpg'" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "metadata": {}, + "outputs": [], + "source": [ + "# Download an image and read it into a NumPy array.\n", + "def download(url, max_dim=None):\n", + " name = url.split('/')[-1]\n", + " image_path = tf.keras.utils.get_file(name, origin=url)\n", + " img = PIL.Image.open(image_path)\n", + " if max_dim:\n", + " img.thumbnail((max_dim, max_dim))\n", + " return np.array(img)\n", + "\n", + "# Normalize an image\n", + "def deprocess(img):\n", + " img = 255*(img + 1.0)/2.0\n", + " return tf.cast(img, tf.uint8)\n", + "\n", + "# Display an image\n", + "def show(img):\n", + " display.display(PIL.Image.fromarray(np.array(img)))\n", + "\n", + "\n", + "# Downsizing the image makes it easier to work with.\n", + "original_img = download(url, max_dim=500)\n", + "show(original_img)\n", + "display.display(display.HTML('Image cc-by: Von.grzanka'))" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "metadata": {}, + "outputs": [], + "source": [ + "base_model = tf.keras.applications.InceptionV3(include_top=False, weights='imagenet')" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "metadata": {}, + "outputs": [], + "source": [ + "# Maximize the activations of these layers\n", + "names = ['mixed3', 'mixed5']\n", + "layers = [base_model.get_layer(name).output for name in names]\n", + "\n", + "# Create the feature extraction model\n", + "dream_model = tf.keras.Model(inputs=base_model.input, outputs=layers)" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "metadata": {}, + "outputs": [], + "source": [ + "def calc_loss(img, model):\n", + " # Pass forward the image through the model to retrieve the activations.\n", + " # Converts the image into a batch of size 1.\n", + " img_batch = tf.expand_dims(img, axis=0)\n", + " layer_activations = model(img_batch)\n", + " if len(layer_activations) == 1:\n", + " layer_activations = [layer_activations]\n", + "\n", + " losses = []\n", + " for act in layer_activations:\n", + " loss = tf.math.reduce_mean(act)\n", + " losses.append(loss)\n", + "\n", + " return tf.reduce_sum(losses)" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "metadata": {}, + "outputs": [], + "source": [ + "class DeepDream(tf.Module):\n", + " def __init__(self, model):\n", + " self.model = model\n", + "\n", + " @tf.function(\n", + " input_signature=(\n", + " tf.TensorSpec(shape=[None,None,3], dtype=tf.float32),\n", + " tf.TensorSpec(shape=[], dtype=tf.int32),\n", + " tf.TensorSpec(shape=[], dtype=tf.float32),)\n", + " )\n", + " def __call__(self, img, steps, step_size):\n", + " print(\"Tracing\")\n", + " loss = tf.constant(0.0)\n", + " for n in tf.range(steps):\n", + " with tf.GradientTape() as tape:\n", + " # This needs gradients relative to `img`\n", + " # `GradientTape` only watches `tf.Variable`s by default\n", + " tape.watch(img)\n", + " loss = calc_loss(img, self.model)\n", + "\n", + " # Calculate the gradient of the loss with respect to the pixels of the input image.\n", + " gradients = tape.gradient(loss, img)\n", + "\n", + " # Normalize the gradients.\n", + " gradients /= tf.math.reduce_std(gradients) + 1e-8 \n", + " \n", + " # In gradient ascent, the \"loss\" is maximized so that the input image increasingly \"excites\" the layers.\n", + " # You can update the image by directly adding the gradients (because they're the same shape!)\n", + " img = img + gradients*step_size\n", + " img = tf.clip_by_value(img, -1, 1)\n", + "\n", + " return loss, img" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "metadata": {}, + "outputs": [], + "source": [ + "deepdream = DeepDream(dream_model)" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "metadata": {}, + "outputs": [], + "source": [ + "def run_deep_dream_simple(img, steps=100, step_size=0.01):\n", + " # Convert from uint8 to the range expected by the model.\n", + " img = tf.keras.applications.inception_v3.preprocess_input(img)\n", + " img = tf.convert_to_tensor(img)\n", + " step_size = tf.convert_to_tensor(step_size)\n", + " steps_remaining = steps\n", + " step = 0\n", + " while steps_remaining:\n", + " if steps_remaining>100:\n", + " run_steps = tf.constant(100)\n", + " else:\n", + " run_steps = tf.constant(steps_remaining)\n", + " steps_remaining -= run_steps\n", + " step += run_steps\n", + "\n", + " loss, img = deepdream(img, run_steps, tf.constant(step_size))\n", + " \n", + " display.clear_output(wait=True)\n", + " show(deprocess(img))\n", + " print (\"Step {}, loss {}\".format(step, loss))\n", + "\n", + "\n", + " result = deprocess(img)\n", + " display.clear_output(wait=True)\n", + " show(result)\n", + "\n", + " return result" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "metadata": {}, + "outputs": [], + "source": [ + "dream_img = run_deep_dream_simple(img=original_img, \n", + " steps=100, step_size=0.01)" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "metadata": {}, + "outputs": [], + "source": [ + "import time\n", + "start = time.time()\n", + "\n", + "OCTAVE_SCALE = 1.30\n", + "\n", + "img = tf.constant(np.array(original_img))\n", + "base_shape = tf.shape(img)[:-1]\n", + "float_base_shape = tf.cast(base_shape, tf.float32)\n", + "\n", + "for n in range(-2, 3):\n", + " new_shape = tf.cast(float_base_shape*(OCTAVE_SCALE**n), tf.int32)\n", + "\n", + " img = tf.image.resize(img, new_shape).numpy()\n", + "\n", + " img = run_deep_dream_simple(img=img, steps=50, step_size=0.01)\n", + "\n", + "display.clear_output(wait=True)\n", + "img = tf.image.resize(img, base_shape)\n", + "img = tf.image.convert_image_dtype(img/255.0, dtype=tf.uint8)\n", + "show(img)\n", + "\n", + "end = time.time()\n", + "end-start" + ] + }, + { + "attachments": {}, + "cell_type": "markdown", + "metadata": {}, + "source": [ + "## Your turn! 🚀\n", + "\n", + "TBD." + ] + }, + { + "attachments": {}, + "cell_type": "markdown", + "metadata": {}, + "source": [ + "## Self study\n", + "\n", + "You can refer to those YouTube videos for further study:\n", + "\n", + "- [Convolutional Neural Networks (CNNs) explained, by deeplizard](https://www.youtube.com/watch?v=YRhxdVk_sIs)\n", + "- [Convolutional Neural Networks Explained (CNN Visualized), by Futurology](https://www.youtube.com/watch?v=pj9-rr1wDhM)" + ] + }, + { + "attachments": {}, + "cell_type": "markdown", + "metadata": {}, + "source": [ + "### Research trend\n", + "\n", + "State of the Art Convolutional Neural Networks (CNNs) Explained | Deep Learning in 2020:\n", + "\n", + "
\n", + " \n", + "
" + ] + }, + { + "attachments": {}, + "cell_type": "markdown", + "metadata": {}, + "source": [ + "## Acknowledgments\n", + "\n", + "Thanks to [Nick](https://github.com/nfmcclure) for creating the open-source course [tensorflow_cookbook](https://github.com/nfmcclure/tensorflow_cookbook). It inspires the majority of the content in this chapter.\n", + "\n", + "---\n" + ] + } + ], + "metadata": { + "kernelspec": { + "display_name": "Python 3 (ipykernel)", + "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.9.16" + } + }, + "nbformat": 4, + "nbformat_minor": 4 +} diff --git a/open-machine-learning-jupyter-book/deep-learning/cnn.md b/open-machine-learning-jupyter-book/deep-learning/cnn.md deleted file mode 100644 index 8f996dc028..0000000000 --- a/open-machine-learning-jupyter-book/deep-learning/cnn.md +++ /dev/null @@ -1,1205 +0,0 @@ ---- -jupytext: - cell_metadata_filter: -all - formats: md:myst - text_representation: - extension: .md - format_name: myst - format_version: 0.13 - jupytext_version: 1.11.5 -kernelspec: - display_name: Python 3 - language: python - name: python3 ---- - - -# Convolutional Neural Networks - -```{epigraph} -Thanks to Convolutional Neural Network, computer vision is working far better than just two years ago, and this is enabling numerous exciting applications ranging from safe autonomous driving, to accurate face recognition, to automatic reading of radiology images. - --- Andrew Ng -``` - -Convolutional Neural Networks (CNNs) are responsible for the latest major breakthroughs in image recognition in the past few years. - -In mathematics, a convolution is a function that is applied over the output of another function. In our case, we will consider applying a matrix multiplication (filter) across an image. See the below diagram for an example of how this may work. - -:::{figure-md} 01_intro_cnn-dl - - -Illustration of matrix mutliplication (filter) in CNN {cite}`reluwiki` -::: - -

- -A demo of convolution function. [source] -

- -CNNs generally follow a structure. The main convolutional setup is (input array) -> (convolutional filter layer) -> (Pooling) -> (Activation layer). The above diagram depicts how a convolutional layer may create one feature. Generally, filters are multidimensional and end up creating many features. It is also common to have a completely separate filter-feature creator of different sizes acting on the same layer. After this convolutional filter, it is common to apply a pooling layer. This pooling may be a max-pooling or an average pooling or another aggregation. One of the key concepts here is that the pooling layer has no parameters while decreasing the layer size. See the below diagram for an example of max-pooling. - -:::{figure-md} 01_intro_cnn2-dl - - -Illustration of max pooling -::: - -After the max pooling, there is generally an activation layer. One of the more common activation layers is the ReLU (Rectified Linear Unit) {cite}`reluwiki`. - -## MNIST handwritten digits - -Here we illustrate how to use a simple CNN with three convolutional units to predict the MNIST handwritten digits. - -```{note} -There is good reason why this dataset is used like the 'hello world' of image recognition, it is fairly compact while having a decent amount of training, test, and validation data. It only has one channel (black and white) and only ten possible outputs (0-9). -``` - -When the script is done training the model, you should see similar output to the following graphs. - -:::{figure-md} 02_cnn1_loss_acc-dl - - -Train MINIST dataset with CNN: accuracy and loss -::: - -Training and test loss (left) and test batch accuracy (right). - -:::{figure-md} 02_cnn1_mnist_output-dl - - -Train MINIST dataset with CNN: prediction output -::: - -A random set of 6 digits with actual and predicted labels. You can see a prediction failure in the lower right box. - -

- -A demo of CNN. [source] -

- -

- -A demo of CNN. [source] -

- -### Code - -```{code-cell} -# In this example, we will download the MNIST handwritten -# digits and create a simple CNN network to predict the -# digit category (0-9) - -import matplotlib.pyplot as plt -import numpy as np -import tensorflow as tf -from tensorflow.examples.tutorials.mnist import input_data -from tensorflow.python.framework import ops -ops.reset_default_graph() - -# Start a graph session -sess = tf.Session() - -# Load data -data_dir = 'temp' -mnist = input_data.read_data_sets(data_dir, one_hot=False) - -# Convert images into 28x28 (they are downloaded as 1x784) -train_xdata = np.array([np.reshape(x, (28, 28)) for x in mnist.train.images]) -test_xdata = np.array([np.reshape(x, (28, 28)) for x in mnist.test.images]) - -# Convert labels into one-hot encoded vectors -train_labels = mnist.train.labels -test_labels = mnist.test.labels - -# Set model parameters -batch_size = 100 -learning_rate = 0.005 -evaluation_size = 500 -image_width = train_xdata[0].shape[0] -image_height = train_xdata[0].shape[1] -target_size = np.max(train_labels) + 1 -num_channels = 1 # greyscale = 1 channel -generations = 500 -eval_every = 5 -conv1_features = 25 -conv2_features = 50 -max_pool_size1 = 2 # NxN window for 1st max pool layer -max_pool_size2 = 2 # NxN window for 2nd max pool layer -fully_connected_size1 = 100 - -# Declare model placeholders -x_input_shape = (batch_size, image_width, image_height, num_channels) -x_input = tf.placeholder(tf.float32, shape=x_input_shape) -y_target = tf.placeholder(tf.int32, shape=(batch_size)) -eval_input_shape = (evaluation_size, image_width, image_height, num_channels) -eval_input = tf.placeholder(tf.float32, shape=eval_input_shape) -eval_target = tf.placeholder(tf.int32, shape=(evaluation_size)) - -# Declare model parameters -conv1_weight = tf.Variable(tf.truncated_normal([4, 4, num_channels, conv1_features], - stddev=0.1, dtype=tf.float32)) -conv1_bias = tf.Variable(tf.zeros([conv1_features], dtype=tf.float32)) - -conv2_weight = tf.Variable(tf.truncated_normal([4, 4, conv1_features, conv2_features], - stddev=0.1, dtype=tf.float32)) -conv2_bias = tf.Variable(tf.zeros([conv2_features], dtype=tf.float32)) - -# fully connected variables -resulting_width = image_width // (max_pool_size1 * max_pool_size2) -resulting_height = image_height // (max_pool_size1 * max_pool_size2) -full1_input_size = resulting_width * resulting_height * conv2_features -full1_weight = tf.Variable(tf.truncated_normal([full1_input_size, fully_connected_size1], - stddev=0.1, dtype=tf.float32)) -full1_bias = tf.Variable(tf.truncated_normal([fully_connected_size1], stddev=0.1, dtype=tf.float32)) -full2_weight = tf.Variable(tf.truncated_normal([fully_connected_size1, target_size], - stddev=0.1, dtype=tf.float32)) -full2_bias = tf.Variable(tf.truncated_normal([target_size], stddev=0.1, dtype=tf.float32)) - - -# Initialize Model Operations -def my_conv_net(conv_input_data): - # First Conv-ReLU-MaxPool Layer - conv1 = tf.nn.conv2d(conv_input_data, conv1_weight, strides=[1, 1, 1, 1], padding='SAME') - relu1 = tf.nn.relu(tf.nn.bias_add(conv1, conv1_bias)) - max_pool1 = tf.nn.max_pool(relu1, ksize=[1, max_pool_size1, max_pool_size1, 1], - strides=[1, max_pool_size1, max_pool_size1, 1], padding='SAME') - - # Second Conv-ReLU-MaxPool Layer - conv2 = tf.nn.conv2d(max_pool1, conv2_weight, strides=[1, 1, 1, 1], padding='SAME') - relu2 = tf.nn.relu(tf.nn.bias_add(conv2, conv2_bias)) - max_pool2 = tf.nn.max_pool(relu2, ksize=[1, max_pool_size2, max_pool_size2, 1], - strides=[1, max_pool_size2, max_pool_size2, 1], padding='SAME') - - # Transform Output into a 1xN layer for next fully connected layer - final_conv_shape = max_pool2.get_shape().as_list() - final_shape = final_conv_shape[1] * final_conv_shape[2] * final_conv_shape[3] - flat_output = tf.reshape(max_pool2, [final_conv_shape[0], final_shape]) - - # First Fully Connected Layer - fully_connected1 = tf.nn.relu(tf.add(tf.matmul(flat_output, full1_weight), full1_bias)) - - # Second Fully Connected Layer - final_model_output = tf.add(tf.matmul(fully_connected1, full2_weight), full2_bias) - - return final_model_output - -model_output = my_conv_net(x_input) -test_model_output = my_conv_net(eval_input) - -# Declare Loss Function (softmax cross entropy) -loss = tf.reduce_mean(tf.nn.sparse_softmax_cross_entropy_with_logits(logits=model_output, labels=y_target)) - -# Create a prediction function -prediction = tf.nn.softmax(model_output) -test_prediction = tf.nn.softmax(test_model_output) - - -# Create accuracy function -def get_accuracy(logits, targets): - batch_predictions = np.argmax(logits, axis=1) - num_correct = np.sum(np.equal(batch_predictions, targets)) - return 100. * num_correct/batch_predictions.shape[0] - -# Create an optimizer -my_optimizer = tf.train.MomentumOptimizer(learning_rate, 0.9) -train_step = my_optimizer.minimize(loss) - -# Initialize Variables -init = tf.global_variables_initializer() -sess.run(init) - -# Start training loop -train_loss = [] -train_acc = [] -test_acc = [] -for i in range(generations): - rand_index = np.random.choice(len(train_xdata), size=batch_size) - rand_x = train_xdata[rand_index] - rand_x = np.expand_dims(rand_x, 3) - rand_y = train_labels[rand_index] - train_dict = {x_input: rand_x, y_target: rand_y} - - sess.run(train_step, feed_dict=train_dict) - temp_train_loss, temp_train_preds = sess.run([loss, prediction], feed_dict=train_dict) - temp_train_acc = get_accuracy(temp_train_preds, rand_y) - - if (i+1) % eval_every == 0: - eval_index = np.random.choice(len(test_xdata), size=evaluation_size) - eval_x = test_xdata[eval_index] - eval_x = np.expand_dims(eval_x, 3) - eval_y = test_labels[eval_index] - test_dict = {eval_input: eval_x, eval_target: eval_y} - test_preds = sess.run(test_prediction, feed_dict=test_dict) - temp_test_acc = get_accuracy(test_preds, eval_y) - - # Record and print results - train_loss.append(temp_train_loss) - train_acc.append(temp_train_acc) - test_acc.append(temp_test_acc) - acc_and_loss = [(i+1), temp_train_loss, temp_train_acc, temp_test_acc] - acc_and_loss = [np.round(x, 2) for x in acc_and_loss] - print('Generation # {}. Train Loss: {:.2f}. Train Acc (Test Acc): {:.2f} ({:.2f})'.format(*acc_and_loss)) - - -# Matlotlib code to plot the loss and accuracies -eval_indices = range(0, generations, eval_every) -# Plot loss over time -plt.plot(eval_indices, train_loss, 'k-') -plt.title('Softmax Loss per Generation') -plt.xlabel('Generation') -plt.ylabel('Softmax Loss') -plt.show() - -# Plot train and test accuracy -plt.plot(eval_indices, train_acc, 'k-', label='Train Set Accuracy') -plt.plot(eval_indices, test_acc, 'r--', label='Test Set Accuracy') -plt.title('Train and Test Accuracy') -plt.xlabel('Generation') -plt.ylabel('Accuracy') -plt.legend(loc='lower right') -plt.show() - -# Plot some samples -# Plot the 6 of the last batch results: -actuals = rand_y[0:6] -predictions = np.argmax(temp_train_preds, axis=1)[0:6] -images = np.squeeze(rand_x[0:6]) - -Nrows = 2 -Ncols = 3 -for i in range(6): - plt.subplot(Nrows, Ncols, i+1) - plt.imshow(np.reshape(images[i], [28, 28]), cmap='Greys_r') - plt.title('Actual: ' + str(actuals[i]) + ' Pred: ' + str(predictions[i]), - fontsize=10) - frame = plt.gca() - frame.axes.get_xaxis().set_visible(False) - frame.axes.get_yaxis().set_visible(False) -``` - -## CIFAR-10 - -```{seealso} -Alex Krizhevsky, Vinod Nair, and Geoffrey Hinton. [CIFAR-10 and CIFAR-100 datasets](https://www.cs.toronto.edu/~kriz/cifar.html). -``` - -Here we will build a convolutional neural network to predict the `CIFAR-10` data. - -The script provided will download and unzip the `CIFAR-10` data. Then it will start training a CNN from scratch. You should see similar output at the end of the following two graphs. - -:::{figure-md} 03_cnn2_loss_acc-dl - - -Train `CIFAR-10` dataset with CNN: accuracy and loss -::: - -Here we see the training loss (left) and the test batch accuracy (right). - -### Code - -```{code-cell} -# In this example, we will download the CIFAR-10 images -# and build a CNN model with dropout and regularization -# -# CIFAR is composed ot 50k train and 10k test -# images that are 32x32. - -import os -import sys -import tarfile -import matplotlib.pyplot as plt -import numpy as np -import tensorflow as tf -from six.moves import urllib -from tensorflow.python.framework import ops -ops.reset_default_graph() - -# Change Directory -try: - abspath = os.path.abspath(__file__) -except NameError: - abspath = os.getcwd() -dname = os.path.dirname(abspath) -os.chdir(dname) - -# Start a graph session -sess = tf.Session() - -# Set model parameters -batch_size = 128 -data_dir = 'temp' -output_every = 50 -generations = 20000 -eval_every = 500 -image_height = 32 -image_width = 32 -crop_height = 24 -crop_width = 24 -num_channels = 3 -num_targets = 10 -extract_folder = 'cifar-10-batches-bin' - -# Exponential Learning Rate Decay Params -learning_rate = 0.1 -lr_decay = 0.1 -num_gens_to_wait = 250. - -# Extract model parameters -image_vec_length = image_height * image_width * num_channels -record_length = 1 + image_vec_length # ( + 1 for the 0-9 label) - -# Load data -data_dir = 'temp' -if not os.path.exists(data_dir): - os.makedirs(data_dir) -cifar10_url = 'http://www.cs.toronto.edu/~kriz/cifar-10-binary.tar.gz' - -# Check if file exists, otherwise download it -data_file = os.path.join(data_dir, 'cifar-10-binary.tar.gz') -if os.path.isfile(data_file): - pass -else: - # Download file - def progress(block_num, block_size, total_size): - progress_info = [cifar10_url, float(block_num * block_size) / float(total_size) * 100.0] - print('\r Downloading {} - {:.2f}%'.format(*progress_info), end="") - filepath, _ = urllib.request.urlretrieve(cifar10_url, data_file, progress) - # Extract file - tarfile.open(filepath, 'r:gz').extractall(data_dir) - - -# Define CIFAR reader -def read_cifar_files(filename_queue, distort_images = True): - reader = tf.FixedLengthRecordReader(record_bytes=record_length) - key, record_string = reader.read(filename_queue) - record_bytes = tf.decode_raw(record_string, tf.uint8) - image_label = tf.cast(tf.slice(record_bytes, [0], [1]), tf.int32) - - # Extract image - image_extracted = tf.reshape(tf.slice(record_bytes, [1], [image_vec_length]), - [num_channels, image_height, image_width]) - - # Reshape image - image_uint8image = tf.transpose(image_extracted, [1, 2, 0]) - reshaped_image = tf.cast(image_uint8image, tf.float32) - # Randomly Crop image - final_image = tf.image.resize_image_with_crop_or_pad(reshaped_image, crop_width, crop_height) - - if distort_images: - # Randomly flip the image horizontally, change the brightness and contrast - final_image = tf.image.random_flip_left_right(final_image) - final_image = tf.image.random_brightness(final_image,max_delta=63) - final_image = tf.image.random_contrast(final_image,lower=0.2, upper=1.8) - - # Normalize whitening - final_image = tf.image.per_image_standardization(final_image) - return final_image, image_label - - -# Create a CIFAR image pipeline from reader -def input_pipeline(batch_size, train_logical=True): - if train_logical: - files = [os.path.join(data_dir, extract_folder, 'data_batch_{}.bin'.format(i)) for i in range(1,6)] - else: - files = [os.path.join(data_dir, extract_folder, 'test_batch.bin')] - filename_queue = tf.train.string_input_producer(files) - image, label = read_cifar_files(filename_queue) - - # min_after_dequeue defines how big a buffer we will randomly sample - # from -- bigger means better shuffling but slower start up and more - # memory used. - # capacity must be larger than min_after_dequeue and the amount larger - # determines the maximum we will prefetch. Recommendation: - # min_after_dequeue + (num_threads + a small safety margin) * batch_size - min_after_dequeue = 5000 - capacity = min_after_dequeue + 3 * batch_size - example_batch, label_batch = tf.train.shuffle_batch([image, label], - batch_size=batch_size, - capacity=capacity, - min_after_dequeue=min_after_dequeue) - - return example_batch, label_batch - - -# Define the model architecture, this will return logits from images -def cifar_cnn_model(input_images, batch_size, train_logical=True): - def truncated_normal_var(name, shape, dtype): - return(tf.get_variable(name=name, shape=shape, dtype=dtype, initializer=tf.truncated_normal_initializer(stddev=0.05))) - def zero_var(name, shape, dtype): - return(tf.get_variable(name=name, shape=shape, dtype=dtype, initializer=tf.constant_initializer(0.0))) - - # First Convolutional Layer - with tf.variable_scope('conv1') as scope: - # Conv_kernel is 5x5 for all 3 colors and we will create 64 features - conv1_kernel = truncated_normal_var(name='conv_kernel1', shape=[5, 5, 3, 64], dtype=tf.float32) - # We convolve across the image with a stride size of 1 - conv1 = tf.nn.conv2d(input_images, conv1_kernel, [1, 1, 1, 1], padding='SAME') - # Initialize and add the bias term - conv1_bias = zero_var(name='conv_bias1', shape=[64], dtype=tf.float32) - conv1_add_bias = tf.nn.bias_add(conv1, conv1_bias) - # ReLU element wise - relu_conv1 = tf.nn.relu(conv1_add_bias) - - # Max Pooling - pool1 = tf.nn.max_pool(relu_conv1, ksize=[1, 3, 3, 1], strides=[1, 2, 2, 1],padding='SAME', name='pool_layer1') - - # Local Response Normalization (parameters from paper) - # paper: http://papers.nips.cc/paper/4824-imagenet-classification-with-deep-convolutional-neural-networks - norm1 = tf.nn.lrn(pool1, depth_radius=5, bias=2.0, alpha=1e-3, beta=0.75, name='norm1') - - # Second Convolutional Layer - with tf.variable_scope('conv2') as scope: - # Conv kernel is 5x5, across all prior 64 features and we create 64 more features - conv2_kernel = truncated_normal_var(name='conv_kernel2', shape=[5, 5, 64, 64], dtype=tf.float32) - # Convolve filter across prior output with stride size of 1 - conv2 = tf.nn.conv2d(norm1, conv2_kernel, [1, 1, 1, 1], padding='SAME') - # Initialize and add the bias - conv2_bias = zero_var(name='conv_bias2', shape=[64], dtype=tf.float32) - conv2_add_bias = tf.nn.bias_add(conv2, conv2_bias) - # ReLU element wise - relu_conv2 = tf.nn.relu(conv2_add_bias) - - # Max Pooling - pool2 = tf.nn.max_pool(relu_conv2, ksize=[1, 3, 3, 1], strides=[1, 2, 2, 1], padding='SAME', name='pool_layer2') - - # Local Response Normalization (parameters from paper) - norm2 = tf.nn.lrn(pool2, depth_radius=5, bias=2.0, alpha=1e-3, beta=0.75, name='norm2') - - # Reshape output into a single matrix for multiplication for the fully connected layers - reshaped_output = tf.reshape(norm2, [batch_size, -1]) - reshaped_dim = reshaped_output.get_shape()[1].value - - # First Fully Connected Layer - with tf.variable_scope('full1') as scope: - # Fully connected layer will have 384 outputs. - full_weight1 = truncated_normal_var(name='full_mult1', shape=[reshaped_dim, 384], dtype=tf.float32) - full_bias1 = zero_var(name='full_bias1', shape=[384], dtype=tf.float32) - full_layer1 = tf.nn.relu(tf.add(tf.matmul(reshaped_output, full_weight1), full_bias1)) - - # Second Fully Connected Layer - with tf.variable_scope('full2') as scope: - # Second fully connected layer has 192 outputs. - full_weight2 = truncated_normal_var(name='full_mult2', shape=[384, 192], dtype=tf.float32) - full_bias2 = zero_var(name='full_bias2', shape=[192], dtype=tf.float32) - full_layer2 = tf.nn.relu(tf.add(tf.matmul(full_layer1, full_weight2), full_bias2)) - - # Final Fully Connected Layer -> 10 categories for output (num_targets) - with tf.variable_scope('full3') as scope: - # Final fully connected layer has 10 (num_targets) outputs. - full_weight3 = truncated_normal_var(name='full_mult3', shape=[192, num_targets], dtype=tf.float32) - full_bias3 = zero_var(name='full_bias3', shape=[num_targets], dtype=tf.float32) - final_output = tf.add(tf.matmul(full_layer2, full_weight3), full_bias3) - - return final_output - - -# Loss function -def cifar_loss(logits, targets): - # Get rid of extra dimensions and cast targets into integers - targets = tf.squeeze(tf.cast(targets, tf.int32)) - # Calculate cross entropy from logits and targets - cross_entropy = tf.nn.sparse_softmax_cross_entropy_with_logits(logits=logits, labels=targets) - # Take the average loss across batch size - cross_entropy_mean = tf.reduce_mean(cross_entropy, name='cross_entropy') - return cross_entropy_mean - - -# Train step -def train_step(loss_value, generation_num): - # Our learning rate is an exponential decay after we wait a fair number of generations - model_learning_rate = tf.train.exponential_decay(learning_rate, generation_num, - num_gens_to_wait, lr_decay, staircase=True) - # Create optimizer - my_optimizer = tf.train.GradientDescentOptimizer(model_learning_rate) - # Initialize train step - train_step = my_optimizer.minimize(loss_value) - return train_step - - -# Accuracy function -def accuracy_of_batch(logits, targets): - # Make sure targets are integers and drop extra dimensions - targets = tf.squeeze(tf.cast(targets, tf.int32)) - # Get predicted values by finding which logit is the greatest - batch_predictions = tf.cast(tf.argmax(logits, 1), tf.int32) - # Check if they are equal across the batch - predicted_correctly = tf.equal(batch_predictions, targets) - # Average the 1's and 0's (True's and False's) across the batch size - accuracy = tf.reduce_mean(tf.cast(predicted_correctly, tf.float32)) - return accuracy - -# Get data -print('Getting/Transforming Data.') -# Initialize the data pipeline -images, targets = input_pipeline(batch_size, train_logical=True) -# Get batch test images and targets from pipline -test_images, test_targets = input_pipeline(batch_size, train_logical=False) - -# Declare Model -print('Creating the CIFAR10 Model.') -with tf.variable_scope('model_definition') as scope: - # Declare the training network model - model_output = cifar_cnn_model(images, batch_size) - # This is very important!!! We must set the scope to REUSE the variables, - # otherwise, when we set the test network model, it will create new random - # variables. Otherwise we get random evaluations on the test batches. - scope.reuse_variables() - test_output = cifar_cnn_model(test_images, batch_size) - -# Declare loss function -print('Declare Loss Function.') -loss = cifar_loss(model_output, targets) - -# Create accuracy function -accuracy = accuracy_of_batch(test_output, test_targets) - -# Create training operations -print('Creating the Training Operation.') -generation_num = tf.Variable(0, trainable=False) -train_op = train_step(loss, generation_num) - -# Initialize Variables -print('Initializing the Variables.') -init = tf.global_variables_initializer() -sess.run(init) - -# Initialize queue (This queue will feed into the model, so no placeholders necessary) -tf.train.start_queue_runners(sess=sess) - -# Train CIFAR Model -print('Starting Training') -train_loss = [] -test_accuracy = [] -for i in range(generations): - _, loss_value = sess.run([train_op, loss]) - - if (i+1) % output_every == 0: - train_loss.append(loss_value) - output = 'Generation {}: Loss = {:.5f}'.format((i+1), loss_value) - print(output) - - if (i+1) % eval_every == 0: - [temp_accuracy] = sess.run([accuracy]) - test_accuracy.append(temp_accuracy) - acc_output = ' --- Test Accuracy = {:.2f}%.'.format(100.*temp_accuracy) - print(acc_output) - -# Print loss and accuracy -# Matlotlib code to plot the loss and accuracies -eval_indices = range(0, generations, eval_every) -output_indices = range(0, generations, output_every) - -# Plot loss over time -plt.plot(output_indices, train_loss, 'k-') -plt.title('Softmax Loss per Generation') -plt.xlabel('Generation') -plt.ylabel('Softmax Loss') -plt.show() - -# Plot accuracy over time -plt.plot(eval_indices, test_accuracy, 'k-') -plt.title('Test Accuracy') -plt.xlabel('Generation') -plt.ylabel('Accuracy') -plt.show() -``` - -## How to fine-tune current CNN architectures? - -The purpose of the script provided in this section is to download the CIFAR-10 data and sort it out in the proper folder structure for running it through the TensorFlow fine-tuning tutorial. The script should create the following folder structure. - -```{code-cell} --train_dir - |--airplane - |--automobile - |--bird - |--cat - |--deer - |--dog - |--frog - |--horse - |--ship - |--truck --validation_dir - |--airplane - |--automobile - |--bird - |--cat - |--deer - |--dog - |--frog - |--horse - |--ship - |--truck -``` - -### Code - -```{code-cell} -# In this script, we download the CIFAR-10 images and -# transform/save them in the Inception Retraining Format -# -# The end purpose of the files is for re-training the -# Google Inception tensorflow model to work on the CIFAR-10. - -import os -import tarfile -import _pickle as cPickle -import numpy as np -import urllib.request -import scipy.misc -from tensorflow.python.framework import ops -ops.reset_default_graph() - -cifar_link = 'https://www.cs.toronto.edu/~kriz/cifar-10-python.tar.gz' -data_dir = 'temp' -if not os.path.isdir(data_dir): - os.makedirs(data_dir) - -# Download tar file -target_file = os.path.join(data_dir, 'cifar-10-python.tar.gz') -if not os.path.isfile(target_file): - print('CIFAR-10 file not found. Downloading CIFAR data (Size = 163MB)') - print('This may take a few minutes, please wait.') - filename, headers = urllib.request.urlretrieve(cifar_link, target_file) - -# Extract into memory -tar = tarfile.open(target_file) -tar.extractall(path=data_dir) -tar.close() -objects = ['airplane', 'automobile', 'bird', 'cat', 'deer', 'dog', 'frog', 'horse', 'ship', 'truck'] - -# Create train image folders -train_folder = 'train_dir' -if not os.path.isdir(os.path.join(data_dir, train_folder)): - for i in range(10): - folder = os.path.join(data_dir, train_folder, objects[i]) - os.makedirs(folder) -# Create test image folders -test_folder = 'validation_dir' -if not os.path.isdir(os.path.join(data_dir, test_folder)): - for i in range(10): - folder = os.path.join(data_dir, test_folder, objects[i]) - os.makedirs(folder) - -# Extract images accordingly -data_location = os.path.join(data_dir, 'cifar-10-batches-py') -train_names = ['data_batch_' + str(x) for x in range(1,6)] -test_names = ['test_batch'] - - -def load_batch_from_file(file): - file_conn = open(file, 'rb') - image_dictionary = cPickle.load(file_conn, encoding='latin1') - file_conn.close() - return image_dictionary - - -def save_images_from_dict(image_dict, folder='data_dir'): - # image_dict.keys() = 'labels', 'filenames', 'data', 'batch_label' - for ix, label in enumerate(image_dict['labels']): - folder_path = os.path.join(data_dir, folder, objects[label]) - filename = image_dict['filenames'][ix] - #Transform image data - image_array = image_dict['data'][ix] - image_array.resize([3, 32, 32]) - # Save image - output_location = os.path.join(folder_path, filename) - scipy.misc.imsave(output_location,image_array.transpose()) - -# Sort train images -for file in train_names: - print('Saving images from file: {}'.format(file)) - file_location = os.path.join(data_dir, 'cifar-10-batches-py', file) - image_dict = load_batch_from_file(file_location) - save_images_from_dict(image_dict, folder=train_folder) - -# Sort test images -for file in test_names: - print('Saving images from file: {}'.format(file)) - file_location = os.path.join(data_dir, 'cifar-10-batches-py', file) - image_dict = load_batch_from_file(file_location) - save_images_from_dict(image_dict, folder=test_folder) - -# Create labels file -cifar_labels_file = os.path.join(data_dir,'cifar10_labels.txt') -print('Writing labels file, {}'.format(cifar_labels_file)) -with open(cifar_labels_file, 'w') as labels_file: - for item in objects: - labels_file.write("{}\n".format(item)) -``` - -## Stylenet / Neural-Style - -The purpose of this script is to illustrate how to do stylenet in TensorFlow. We reference the following [paper](https://arxiv.org/abs/1508.06576) for this algorithm. - -But there is some prerequisites, - -- Download the `VGG-verydeep-19.mat` file. -- You must download two images, a style image and a content image for the algorithm to blend. - -The style image is - -:::{figure-md} starry_night-dl - - -Style image: starry night -::: - -The context image is below. - -:::{figure-md} book_cover-dl - - -Content image: book cover -::: - -The final result looks like - -:::{figure-md} 05_stylenet_ex-dl - - -stylenet final result -::: - -### Code - -```{code-cell} -# We use two images, an original image and a style image -# and try to make the original image in the style of the style image. -# -# Reference paper: -# https://arxiv.org/abs/1508.06576 -# -# Need to download the model 'imagenet-vgg-verydee-19.mat' from: -# http://www.vlfeat.org/matconvnet/models/beta16/imagenet-vgg-verydeep-19.mat - -import os -import scipy.io -import scipy.misc -import imageio -from skimage.transform import resize -from operator import mul -from functools import reduce -import numpy as np -import tensorflow as tf -from tensorflow.python.framework import ops -ops.reset_default_graph() - -# Image Files -original_image_file = 'images/book_cover.jpg' -style_image_file = 'images/starry_night.jpg' - -# Saved VGG Network path under the current project dir. -vgg_path = 'imagenet-vgg-verydeep-19.mat' - -# Default Arguments -original_image_weight = 5.0 -style_image_weight = 500.0 -regularization_weight = 100 -learning_rate = 10 -generations = 100 -output_generations = 25 -beta1 = 0.9 -beta2 = 0.999 - -# Read in images -original_image = imageio.imread(original_image_file) -style_image = imageio.imread(style_image_file) - -# Get shape of target and make the style image the same -target_shape = original_image.shape -style_image = resize(style_image, target_shape) - -# VGG-19 Layer Setup -# From paper -vgg_layers = ['conv1_1', 'relu1_1', - 'conv1_2', 'relu1_2', 'pool1', - 'conv2_1', 'relu2_1', - 'conv2_2', 'relu2_2', 'pool2', - 'conv3_1', 'relu3_1', - 'conv3_2', 'relu3_2', - 'conv3_3', 'relu3_3', - 'conv3_4', 'relu3_4', 'pool3', - 'conv4_1', 'relu4_1', - 'conv4_2', 'relu4_2', - 'conv4_3', 'relu4_3', - 'conv4_4', 'relu4_4', 'pool4', - 'conv5_1', 'relu5_1', - 'conv5_2', 'relu5_2', - 'conv5_3', 'relu5_3', - 'conv5_4', 'relu5_4'] - - -# Extract weights and matrix means -def extract_net_info(path_to_params): - vgg_data = scipy.io.loadmat(path_to_params) - normalization_matrix = vgg_data['normalization'][0][0][0] - mat_mean = np.mean(normalization_matrix, axis=(0,1)) - network_weights = vgg_data['layers'][0] - return mat_mean, network_weights - - -# Create the VGG-19 Network -def vgg_network(network_weights, init_image): - network = {} - image = init_image - - for i, layer in enumerate(vgg_layers): - if layer[0] == 'c': - weights, bias = network_weights[i][0][0][0][0] - weights = np.transpose(weights, (1, 0, 2, 3)) - bias = bias.reshape(-1) - conv_layer = tf.nn.conv2d(image, tf.constant(weights), (1, 1, 1, 1), 'SAME') - image = tf.nn.bias_add(conv_layer, bias) - elif layer[0] == 'r': - image = tf.nn.relu(image) - else: # pooling - image = tf.nn.max_pool(image, (1, 2, 2, 1), (1, 2, 2, 1), 'SAME') - network[layer] = image - return network - -# Here we define which layers apply to the original or style image -original_layers = ['relu4_2', 'relu5_2'] -style_layers = ['relu1_1', 'relu2_1', 'relu3_1', 'relu4_1', 'relu5_1'] - -# Get network parameters -normalization_mean, network_weights = extract_net_info(vgg_path) - -shape = (1,) + original_image.shape -style_shape = (1,) + style_image.shape -original_features = {} -style_features = {} - -# Set style weights -style_weights = {l: 1./(len(style_layers)) for l in style_layers} - -# Computer feature layers with original image -g_original = tf.Graph() -with g_original.as_default(), tf.Session() as sess1: - image = tf.placeholder('float', shape=shape) - vgg_net = vgg_network(network_weights, image) - original_minus_mean = original_image - normalization_mean - original_norm = np.array([original_minus_mean]) - for layer in original_layers: - original_features[layer] = vgg_net[layer].eval(feed_dict={image: original_norm}) - -# Get style image network -g_style = tf.Graph() -with g_style.as_default(), tf.Session() as sess2: - image = tf.placeholder('float', shape=style_shape) - vgg_net = vgg_network(network_weights, image) - style_minus_mean = style_image - normalization_mean - style_norm = np.array([style_minus_mean]) - for layer in style_layers: - features = vgg_net[layer].eval(feed_dict={image: style_norm}) - features = np.reshape(features, (-1, features.shape[3])) - gram = np.matmul(features.T, features) / features.size - style_features[layer] = gram - -# Make Combined Image via loss function -with tf.Graph().as_default(): - # Get network parameters - initial = tf.random_normal(shape) * 0.256 - init_image = tf.Variable(initial) - vgg_net = vgg_network(network_weights, init_image) - - # Loss from Original Image - original_layers_w = {'relu4_2': 0.5, 'relu5_2': 0.5} - original_loss = 0 - for o_layer in original_layers: - temp_original_loss = original_layers_w[o_layer] * original_image_weight *\ - (2 * tf.nn.l2_loss(vgg_net[o_layer] - original_features[o_layer])) - original_loss += (temp_original_loss / original_features[o_layer].size) - - # Loss from Style Image - style_loss = 0 - style_losses = [] - for style_layer in style_layers: - layer = vgg_net[style_layer] - feats, height, width, channels = [x.value for x in layer.get_shape()] - size = height * width * channels - features = tf.reshape(layer, (-1, channels)) - style_gram_matrix = tf.matmul(tf.transpose(features), features) / size - style_expected = style_features[style_layer] - style_losses.append(style_weights[style_layer] * 2 * - tf.nn.l2_loss(style_gram_matrix - style_expected) / - style_expected.size) - style_loss += style_image_weight * tf.reduce_sum(style_losses) - - # To Smooth the results, we add in total variation loss - total_var_x = reduce(mul, init_image[:, 1:, :, :].get_shape().as_list(), 1) - total_var_y = reduce(mul, init_image[:, :, 1:, :].get_shape().as_list(), 1) - first_term = regularization_weight * 2 - second_term_numerator = tf.nn.l2_loss(init_image[:, 1:, :, :] - init_image[:, :shape[1]-1, :, :]) - second_term = second_term_numerator / total_var_y - third_term = (tf.nn.l2_loss(init_image[:, :, 1:, :] - init_image[:, :, :shape[2]-1, :]) / total_var_x) - total_variation_loss = first_term * (second_term + third_term) - - # Combined Loss - loss = original_loss + style_loss + total_variation_loss - - # Declare Optimization Algorithm - optimizer = tf.train.AdamOptimizer(learning_rate, beta1, beta2) - train_step = optimizer.minimize(loss) - - # Initialize variables and start training - with tf.Session() as sess: - tf.global_variables_initializer().run() - for i in range(generations): - - train_step.run() - - # Print update and save temporary output - if (i+1) % output_generations == 0: - print('Generation {} out of {}, loss: {}'.format(i + 1, generations,sess.run(loss))) - image_eval = init_image.eval() - best_image_add_mean = image_eval.reshape(shape[1:]) + normalization_mean - output_file = 'temp_output_{}.jpg'.format(i) - imageio.imwrite(output_file, best_image_add_mean) - - - # Save final image - image_eval = init_image.eval() - best_image_add_mean = image_eval.reshape(shape[1:]) + normalization_mean - output_file = 'final_output.jpg' - scipy.misc.imsave(output_file, best_image_add_mean) -``` - -## Deepdream in TensorFlow -Note: There is no new code in this script. It originates from the TensorFlow tutorial located here. However, this code is modified slightly to run on Python 3. The code is also commented very heavily to explain, line-by-line, what occurs in the deepdream demo. - -Here are some potential outputs. - -:::{figure-md} 06_deepdream_ex-dl - - -Deepdream outputs -::: - -### Code - -```{code-cell} -# Using TensorFlow for Deep Dream -#--------------------------------------- -# From: Alexander Mordvintsev -# --https://github.com/tensorflow/tensorflow/tree/master/tensorflow/examples/tutorials/deepdream -# -# Make sure to download the deep dream model here: -# https://storage.googleapis.com/download.tensorflow.org/models/inception5h.zip -# -# Run: -# me@computer:~$ wget https://storage.googleapis.com/download.tensorflow.org/models/inception5h.zip -# me@computer:~$ unzip inception5h.zip -# -# More comments added inline. - - -from __future__ import absolute_import -from __future__ import division -from __future__ import print_function - -import os -import matplotlib.pyplot as plt -import numpy as np -import PIL.Image -import tensorflow as tf -from io import BytesIO -from tensorflow.python.framework import ops -ops.reset_default_graph() - -# Start a graph session -graph = tf.Graph() -sess = tf.InteractiveSession(graph=graph) - -os.chdir('~/Documents/tensorflow/inception-v1-model/') - -# Model filename -model_fn = 'tensorflow_inception_graph.pb' - -# Load graph parameters -with tf.gfile.FastGFile(model_fn, 'rb') as f: - graph_def = tf.GraphDef() - graph_def.ParseFromString(f.read()) - -# Create placeholder for input -t_input = tf.placeholder(np.float32, name='input') - -# Imagenet average bias to subtract off images -imagenet_mean = 117.0 -t_preprocessed = tf.expand_dims(t_input-imagenet_mean, 0) -tf.import_graph_def(graph_def, {'input':t_preprocessed}) - -# Create a list of layers that we can refer to later -layers = [op.name for op in graph.get_operations() if op.type=='Conv2D' and 'import/' in op.name] - -# Count how many outputs for each layer -feature_nums = [int(graph.get_tensor_by_name(name+':0').get_shape()[-1]) for name in layers] - -# Print count of layers and outputs (features nodes) -print('Number of layers', len(layers)) -print('Total number of feature channels:', sum(feature_nums)) - -# Picking some internal layer. Note that we use outputs before applying the ReLU nonlinearity -# to have non-zero gradients for features with negative initial activations. -layer = 'mixed4d_3x3_bottleneck_pre_relu' -channel = 30 # picking some feature channel to visualize - -# start with a gray image with a little noise -img_noise = np.random.uniform(size=(224,224,3)) + 100.0 - -def showarray(a, fmt='jpeg'): - # First make sure everything is between 0 and 255 - a = np.uint8(np.clip(a, 0, 1)*255) - # Pick an in-memory format for image display - f = BytesIO() - # Create the in memory image - PIL.Image.fromarray(a).save(f, fmt) - # Show image - plt.imshow(a) - - -def T(layer): - '''Helper for getting layer output tensor''' - return graph.get_tensor_by_name("import/%s:0"%layer) - - -# The following function returns a function wrapper that will create the placeholder -# inputs of a specified dtype -def tffunc(*argtypes): - '''Helper that transforms TF-graph generating function into a regular one. - See "resize" function below. - ''' - placeholders = list(map(tf.placeholder, argtypes)) - def wrap(f): - out = f(*placeholders) - def wrapper(*args, **kw): - return out.eval(dict(zip(placeholders, args)), session=kw.get('session')) - return wrapper - return wrap - - -# Helper function that uses TF to resize an image -def resize(img, size): - img = tf.expand_dims(img, 0) - # Change 'img' size by linear interpolation - return tf.image.resize_bilinear(img, size)[0, :, :, :] - - -def calc_grad_tiled(img, t_grad, tile_size=512): - '''Compute the value of tensor t_grad over the image in a tiled way. - Random shifts are applied to the image to blur tile boundaries over - multiple iterations.''' - # Pick a subregion square size - sz = tile_size - # Get the image height and width - h, w = img.shape[:2] - # Get a random shift amount in the x and y direction - sx, sy = np.random.randint(sz, size=2) - # Randomly shift the image (roll image) in the x and y directions - img_shift = np.roll(np.roll(img, sx, 1), sy, 0) - # Initialize the while image gradient as zeros - grad = np.zeros_like(img) - # Now we loop through all the sub-tiles in the image - for y in range(0, max(h-sz//2, sz),sz): - for x in range(0, max(w-sz//2, sz),sz): - # Select the sub image tile - sub = img_shift[y:y+sz,x:x+sz] - # Calculate the gradient for the tile - g = sess.run(t_grad, {t_input:sub}) - # Apply the gradient of the tile to the whole image gradient - grad[y:y+sz,x:x+sz] = g - # Return the gradient, undoing the roll operation - return np.roll(np.roll(grad, -sx, 1), -sy, 0) - -def render_deepdream(t_obj, img0=img_noise, - iter_n=10, step=1.5, octave_n=4, octave_scale=1.4): - # defining the optimization objective, the objective is the mean of the feature - t_score = tf.reduce_mean(t_obj) - # Our gradients will be defined as changing the t_input to get closer to - # the values of t_score. Here, t_score is the mean of the feature we select, - # and t_input will be the image octave (starting with the last) - t_grad = tf.gradients(t_score, t_input)[0] # behold the power of automatic differentiation! - - # Store the image - img = img0 - # Initialize the octave list - octaves = [] - # Since we stored the image, we need to only calculate n-1 octaves - for i in range(octave_n-1): - # Extract the image shape - hw = img.shape[:2] - # Resize the image, scale by the octave_scale (resize by linear interpolation) - lo = resize(img, np.int32(np.float32(hw)/octave_scale)) - # Residual is hi. Where residual = image - (Resize lo to be hw-shape) - hi = img-resize(lo, hw) - # Save the lo image for re-iterating - img = lo - # Save the extracted hi-image - octaves.append(hi) - - # generate details octave by octave - for octave in range(octave_n): - if octave>0: - # Start with the last octave - hi = octaves[-octave] - # - img = resize(img, hi.shape[:2])+hi - for i in range(iter_n): - # Calculate gradient of the image. - g = calc_grad_tiled(img, t_grad) - # Ideally, we would just add the gradient, g, but - # we want do a forward step size of it ('step'), - # and divide it by the avg. norm of the gradient, so - # we are adding a gradient of a certain size each step. - # Also, to make sure we aren't dividing by zero, we add 1e-7. - img += g*(step / (np.abs(g).mean()+1e-7)) - print('.',end = ' ') - showarray(img/255.0) - -# Run Deep Dream -if __name__=="__main__": - # Create resize function that has a wrapper that creates specified placeholder types - resize = tffunc(np.float32, np.int32)(resize) - - # Open image - img0 = PIL.Image.open('book_cover.jpg') - img0 = np.float32(img0) - # Show Original Image - showarray(img0/255.0) - - # Create deep dream - render_deepdream(T(layer)[:, :, :, channel], img0, iter_n=15) - - sess.close() -``` - -## Your turn! 🚀 - -TBD. - -## Self study - -You can refer to those YouTube videos for further study: - -- [Convolutional Neural Networks (CNNs) explained, by deeplizard](https://www.youtube.com/watch?v=YRhxdVk_sIs) -- [Convolutional Neural Networks Explained (CNN Visualized), by Futurology](https://www.youtube.com/watch?v=pj9-rr1wDhM) - -### Research trend - -State of the Art Convolutional Neural Networks (CNNs) Explained | Deep Learning in 2020: - -
- -
- -## Acknowledgments - -Thanks to [Nick](https://github.com/nfmcclure) for creating the open-source course [tensorflow_cookbook](https://github.com/nfmcclure/tensorflow_cookbook). It inspires the majority of the content in this chapter. - ---- - -```{bibliography} -:filter: docname in docnames -``` From 7bfc187a6427a8b112f5ce647bf52d1c445a6990 Mon Sep 17 00:00:00 2001 From: Xu Senbo <1170676717@qq.com> Date: Sun, 19 Nov 2023 21:31:03 +0800 Subject: [PATCH 2/9] Add new acknowledgment --- open-machine-learning-jupyter-book/deep-learning/cnn.ipynb | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/open-machine-learning-jupyter-book/deep-learning/cnn.ipynb b/open-machine-learning-jupyter-book/deep-learning/cnn.ipynb index 26a91bb947..0d19684a5a 100644 --- a/open-machine-learning-jupyter-book/deep-learning/cnn.ipynb +++ b/open-machine-learning-jupyter-book/deep-learning/cnn.ipynb @@ -1186,7 +1186,7 @@ "source": [ "## Acknowledgments\n", "\n", - "Thanks to [Nick](https://github.com/nfmcclure) for creating the open-source course [tensorflow_cookbook](https://github.com/nfmcclure/tensorflow_cookbook). It inspires the majority of the content in this chapter.\n", + "Thanks to [Nick](https://github.com/nfmcclure) for creating the open-source course [tensorflow_cookbook](https://github.com/nfmcclure/tensorflow_cookbook). And thanks to [TensorFlow](https://www.tensorflow.org/) for creating the open source project [DeepDream](https://www.tensorflow.org/tutorials/generative/deepdream) It inspires the majority of the content in this chapter.\n", "\n", "---\n" ] From c00138fdd7e76d017dbfd1d6810f8611a3c8bb97 Mon Sep 17 00:00:00 2001 From: Xu Senbo <1170676717@qq.com> Date: Sun, 19 Nov 2023 21:55:08 +0800 Subject: [PATCH 3/9] Fix figure problem --- .../deep-learning/autoencoder.ipynb | 12 ++++++++++++ .../deep-learning/cnn.ipynb | 8 ++++++++ 2 files changed, 20 insertions(+) diff --git a/open-machine-learning-jupyter-book/deep-learning/autoencoder.ipynb b/open-machine-learning-jupyter-book/deep-learning/autoencoder.ipynb index d8facd45fa..00d8a8725a 100644 --- a/open-machine-learning-jupyter-book/deep-learning/autoencoder.ipynb +++ b/open-machine-learning-jupyter-book/deep-learning/autoencoder.ipynb @@ -65,6 +65,8 @@ ":::{figure} https://static-1300131294.cos.ap-shanghai.myqcloud.com/images/deep-learning/autoencoder/01_PCA1.png\n", "---\n", "name: Illustration of PCA\n", + "---\n", + "PCA 1\n", ":::\n", "\n", "- Transform features onto directions of maximum variance\n", @@ -72,6 +74,8 @@ ":::{figure} https://static-1300131294.cos.ap-shanghai.myqcloud.com/images/deep-learning/autoencoder/02_PCA2.png\n", "---\n", "name: Illustration of PCA\n", + "---\n", + "PCA 2\n", ":::\n", "\n", "- Usually consider a subset of vectors of most variance (dimensionality reduction)\n", @@ -79,6 +83,8 @@ ":::{figure} https://static-1300131294.cos.ap-shanghai.myqcloud.com/images/deep-learning/autoencoder/03_PCA3.png\n", "---\n", "name: Illustration of PCA\n", + "---\n", + "PCA 3\n", ":::" ] }, @@ -93,6 +99,8 @@ ":::{figure} https://static-1300131294.cos.ap-shanghai.myqcloud.com/images/deep-learning/autoencoder/04_simple.png\n", "---\n", "name: Illustration of Fully Connected autoencoder\n", + "---\n", + "Simple\n", ":::\n", "\n", ":::{note}\n", @@ -119,6 +127,8 @@ ":::{figure} https://static-1300131294.cos.ap-shanghai.myqcloud.com/images/deep-learning/autoencoder/05_diff_conv.png\n", "---\n", "name: Difference between regular and transposed convolution\n", + "---\n", + "diff\n", ":::\n", "\n", "In transposed convolutions, we stride over the output; hence, larger strides will result in larger outputs (opposite to regular convolutions); and we pad the output; hence, larger padding will result in smaller output maps.\n", @@ -128,6 +138,8 @@ ":::{figure} https://static-1300131294.cos.ap-shanghai.myqcloud.com/images/deep-learning/autoencoder/06_convmodel.png\n", "---\n", "name: Structure of convoluted autoencoder\n", + "---\n", + "Structure\n", ":::\n", "\n", ":::{note}\n", diff --git a/open-machine-learning-jupyter-book/deep-learning/cnn.ipynb b/open-machine-learning-jupyter-book/deep-learning/cnn.ipynb index 0d19684a5a..2cba0d26b6 100644 --- a/open-machine-learning-jupyter-book/deep-learning/cnn.ipynb +++ b/open-machine-learning-jupyter-book/deep-learning/cnn.ipynb @@ -617,6 +617,8 @@ ":::{figure} https://static-1300131294.cos.ap-shanghai.myqcloud.com/images/deep-learning/CNN/starry_night.jpg\n", "---\n", "name: Style image: starry night\n", + "---\n", + "starry night\n", ":::\n", "\n", "The context image is below.\n", @@ -624,6 +626,8 @@ ":::{figure} https://static-1300131294.cos.ap-shanghai.myqcloud.com/images/deep-learning/CNN/book_cover.jpg\n", "---\n", "name: Content image: book cover\n", + "---\n", + "book cover\n", ":::\n", "\n", "The final result looks like\n", @@ -631,6 +635,8 @@ ":::{figure} https://static-1300131294.cos.ap-shanghai.myqcloud.com/images/deep-learning/CNN/05_stylenet_ex.png\n", "---\n", "name: stylenet final result\n", + "---\n", + "stylenet\n", ":::\n" ] }, @@ -884,6 +890,8 @@ ":::{figure} https://static-1300131294.cos.ap-shanghai.myqcloud.com/images/deep-learning/CNN/06_deepdream_ex.png\n", "---\n", "name: Deepdream outputs\n", + "---\n", + "deepdream\n", ":::" ] }, From 08f671ab623f456772904602f2b0ea923829051f Mon Sep 17 00:00:00 2001 From: Xu Senbo <1170676717@qq.com> Date: Sun, 19 Nov 2023 22:10:22 +0800 Subject: [PATCH 4/9] delete : --- open-machine-learning-jupyter-book/deep-learning/cnn.ipynb | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/open-machine-learning-jupyter-book/deep-learning/cnn.ipynb b/open-machine-learning-jupyter-book/deep-learning/cnn.ipynb index 2cba0d26b6..0a981fb9e0 100644 --- a/open-machine-learning-jupyter-book/deep-learning/cnn.ipynb +++ b/open-machine-learning-jupyter-book/deep-learning/cnn.ipynb @@ -616,7 +616,7 @@ "\n", ":::{figure} https://static-1300131294.cos.ap-shanghai.myqcloud.com/images/deep-learning/CNN/starry_night.jpg\n", "---\n", - "name: Style image: starry night\n", + "name: Style image starry night\n", "---\n", "starry night\n", ":::\n", @@ -625,7 +625,7 @@ "\n", ":::{figure} https://static-1300131294.cos.ap-shanghai.myqcloud.com/images/deep-learning/CNN/book_cover.jpg\n", "---\n", - "name: Content image: book cover\n", + "name: Content image book cover\n", "---\n", "book cover\n", ":::\n", From d0b95b481eac2f0851bdfb155414beb4bee1910e Mon Sep 17 00:00:00 2001 From: Xu Senbo <1170676717@qq.com> Date: Sun, 19 Nov 2023 22:23:19 +0800 Subject: [PATCH 5/9] delete --- --- open-machine-learning-jupyter-book/deep-learning/cnn.ipynb | 4 +--- 1 file changed, 1 insertion(+), 3 deletions(-) diff --git a/open-machine-learning-jupyter-book/deep-learning/cnn.ipynb b/open-machine-learning-jupyter-book/deep-learning/cnn.ipynb index 0a981fb9e0..c7f686ace0 100644 --- a/open-machine-learning-jupyter-book/deep-learning/cnn.ipynb +++ b/open-machine-learning-jupyter-book/deep-learning/cnn.ipynb @@ -1194,9 +1194,7 @@ "source": [ "## Acknowledgments\n", "\n", - "Thanks to [Nick](https://github.com/nfmcclure) for creating the open-source course [tensorflow_cookbook](https://github.com/nfmcclure/tensorflow_cookbook). And thanks to [TensorFlow](https://www.tensorflow.org/) for creating the open source project [DeepDream](https://www.tensorflow.org/tutorials/generative/deepdream) It inspires the majority of the content in this chapter.\n", - "\n", - "---\n" + "Thanks to [Nick](https://github.com/nfmcclure) for creating the open-source course [tensorflow_cookbook](https://github.com/nfmcclure/tensorflow_cookbook). And thanks to [TensorFlow](https://www.tensorflow.org/) for creating the open source project [DeepDream](https://www.tensorflow.org/tutorials/generative/deepdream) It inspires the majority of the content in this chapter.\n" ] } ], From 71c774162b5ca02123219ab089ae749089dde7db Mon Sep 17 00:00:00 2001 From: Xu Senbo <1170676717@qq.com> Date: Sun, 19 Nov 2023 22:39:51 +0800 Subject: [PATCH 6/9] Modify toc.yml --- open-machine-learning-jupyter-book/_toc.yml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/open-machine-learning-jupyter-book/_toc.yml b/open-machine-learning-jupyter-book/_toc.yml index f5539a076a..cac0eebdbd 100644 --- a/open-machine-learning-jupyter-book/_toc.yml +++ b/open-machine-learning-jupyter-book/_toc.yml @@ -92,7 +92,7 @@ parts: - file: deep-learning/cnn - file: deep-learning/gan.md - file: deep-learning/rnn.md - - file: deep-learning/autoencoder.md + - file: deep-learning/autoencoder.ipynb - file: deep-learning/lstm.ipynb - file: deep-learning/time-series.ipynb - file: deep-learning/dqn.ipynb From ec45ba9fe2e3028aedfed61732bbef99207915f7 Mon Sep 17 00:00:00 2001 From: Xu Senbo <86239038+bestfw@users.noreply.github.com> Date: Sun, 19 Nov 2023 22:44:32 +0800 Subject: [PATCH 7/9] Update _toc.yml --- open-machine-learning-jupyter-book/_toc.yml | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/open-machine-learning-jupyter-book/_toc.yml b/open-machine-learning-jupyter-book/_toc.yml index 622058792f..3bc0b95cda 100644 --- a/open-machine-learning-jupyter-book/_toc.yml +++ b/open-machine-learning-jupyter-book/_toc.yml @@ -91,7 +91,7 @@ parts: - file: deep-learning/dl-overview - file: deep-learning/cnn - file: deep-learning/gan.md - - file: deep-learning/rnn.md + - file: deep-learning/rnn.ipynb - file: deep-learning/autoencoder.ipynb - file: deep-learning/lstm.ipynb - file: deep-learning/time-series.ipynb @@ -237,4 +237,4 @@ parts: - file: slides/ml-advanced/kernel-method - file: slides/ml-advanced/model-selection - file: slides/deep-learning/cnn - - file: slides/deep-learning/gan \ No newline at end of file + - file: slides/deep-learning/gan From 19d991b9c63f890c6d6ed899c706e706eada5780 Mon Sep 17 00:00:00 2001 From: Xu Senbo <1170676717@qq.com> Date: Sun, 19 Nov 2023 23:14:42 +0800 Subject: [PATCH 8/9] Fix error --- .../deep-learning/autoencoder.ipynb | 6 +++--- open-machine-learning-jupyter-book/deep-learning/cnn.ipynb | 2 +- 2 files changed, 4 insertions(+), 4 deletions(-) diff --git a/open-machine-learning-jupyter-book/deep-learning/autoencoder.ipynb b/open-machine-learning-jupyter-book/deep-learning/autoencoder.ipynb index 00d8a8725a..5158c8fed7 100644 --- a/open-machine-learning-jupyter-book/deep-learning/autoencoder.ipynb +++ b/open-machine-learning-jupyter-book/deep-learning/autoencoder.ipynb @@ -64,7 +64,7 @@ "\n", ":::{figure} https://static-1300131294.cos.ap-shanghai.myqcloud.com/images/deep-learning/autoencoder/01_PCA1.png\n", "---\n", - "name: Illustration of PCA\n", + "name: Illustration of PCA 1\n", "---\n", "PCA 1\n", ":::\n", @@ -73,7 +73,7 @@ "\n", ":::{figure} https://static-1300131294.cos.ap-shanghai.myqcloud.com/images/deep-learning/autoencoder/02_PCA2.png\n", "---\n", - "name: Illustration of PCA\n", + "name: Illustration of PCA 2\n", "---\n", "PCA 2\n", ":::\n", @@ -82,7 +82,7 @@ "\n", ":::{figure} https://static-1300131294.cos.ap-shanghai.myqcloud.com/images/deep-learning/autoencoder/03_PCA3.png\n", "---\n", - "name: Illustration of PCA\n", + "name: Illustration of PCA 3\n", "---\n", "PCA 3\n", ":::" diff --git a/open-machine-learning-jupyter-book/deep-learning/cnn.ipynb b/open-machine-learning-jupyter-book/deep-learning/cnn.ipynb index c7f686ace0..52677dc7f1 100644 --- a/open-machine-learning-jupyter-book/deep-learning/cnn.ipynb +++ b/open-machine-learning-jupyter-book/deep-learning/cnn.ipynb @@ -84,7 +84,7 @@ "\n", "\n", "\n", - "After the max pooling, there is generally an activation layer. One of the more common activation layers is the ReLU (Rectified Linear Unit) {cite}`reluwiki`." + "After the max pooling, there is generally an activation layer. One of the more common activation layers is the ReLU (Rectified Linear Unit)." ] }, { From 5b404f1952be3b9b6e54b45ce3b9ae6541bfea76 Mon Sep 17 00:00:00 2001 From: Xu Senbo <1170676717@qq.com> Date: Sun, 19 Nov 2023 23:26:35 +0800 Subject: [PATCH 9/9] Fix error --- .../deep-learning/autoencoder.ipynb | 30 ++++--------------- .../deep-learning/cnn.ipynb | 12 -------- 2 files changed, 6 insertions(+), 36 deletions(-) diff --git a/open-machine-learning-jupyter-book/deep-learning/autoencoder.ipynb b/open-machine-learning-jupyter-book/deep-learning/autoencoder.ipynb index 5158c8fed7..77d09293b6 100644 --- a/open-machine-learning-jupyter-book/deep-learning/autoencoder.ipynb +++ b/open-machine-learning-jupyter-book/deep-learning/autoencoder.ipynb @@ -63,28 +63,19 @@ "- Find directions of maximum variance\n", "\n", ":::{figure} https://static-1300131294.cos.ap-shanghai.myqcloud.com/images/deep-learning/autoencoder/01_PCA1.png\n", - "---\n", - "name: Illustration of PCA 1\n", - "---\n", - "PCA 1\n", + "Illustration of PCA\n", ":::\n", "\n", "- Transform features onto directions of maximum variance\n", "\n", ":::{figure} https://static-1300131294.cos.ap-shanghai.myqcloud.com/images/deep-learning/autoencoder/02_PCA2.png\n", - "---\n", - "name: Illustration of PCA 2\n", - "---\n", - "PCA 2\n", + "Illustration of PCA\n", ":::\n", "\n", "- Usually consider a subset of vectors of most variance (dimensionality reduction)\n", "\n", ":::{figure} https://static-1300131294.cos.ap-shanghai.myqcloud.com/images/deep-learning/autoencoder/03_PCA3.png\n", - "---\n", - "name: Illustration of PCA 3\n", - "---\n", - "PCA 3\n", + "Illustration of PCA\n", ":::" ] }, @@ -97,10 +88,7 @@ "Here is an example of a basic fully-connected autoencoder\n", "\n", ":::{figure} https://static-1300131294.cos.ap-shanghai.myqcloud.com/images/deep-learning/autoencoder/04_simple.png\n", - "---\n", - "name: Illustration of Fully Connected autoencoder\n", - "---\n", - "Simple\n", + "Illustration of Fully Connected autoencoder\n", ":::\n", "\n", ":::{note}\n", @@ -125,10 +113,7 @@ "The difference between regular convolution and transposed convolution can be seen from the following image.\n", "\n", ":::{figure} https://static-1300131294.cos.ap-shanghai.myqcloud.com/images/deep-learning/autoencoder/05_diff_conv.png\n", - "---\n", - "name: Difference between regular and transposed convolution\n", - "---\n", - "diff\n", + "Difference between regular and transposed convolution\n", ":::\n", "\n", "In transposed convolutions, we stride over the output; hence, larger strides will result in larger outputs (opposite to regular convolutions); and we pad the output; hence, larger padding will result in smaller output maps.\n", @@ -136,10 +121,7 @@ "So, the whole model consists of two parts, encoder and decoder, and they are composed with regular convolution and transposed convolution respectively.\n", "\n", ":::{figure} https://static-1300131294.cos.ap-shanghai.myqcloud.com/images/deep-learning/autoencoder/06_convmodel.png\n", - "---\n", - "name: Structure of convoluted autoencoder\n", - "---\n", - "Structure\n", + "Structure of convoluted autoencoder\n", ":::\n", "\n", ":::{note}\n", diff --git a/open-machine-learning-jupyter-book/deep-learning/cnn.ipynb b/open-machine-learning-jupyter-book/deep-learning/cnn.ipynb index 52677dc7f1..b56a91363c 100644 --- a/open-machine-learning-jupyter-book/deep-learning/cnn.ipynb +++ b/open-machine-learning-jupyter-book/deep-learning/cnn.ipynb @@ -615,28 +615,19 @@ "The style image is\n", "\n", ":::{figure} https://static-1300131294.cos.ap-shanghai.myqcloud.com/images/deep-learning/CNN/starry_night.jpg\n", - "---\n", "name: Style image starry night\n", - "---\n", - "starry night\n", ":::\n", "\n", "The context image is below.\n", "\n", ":::{figure} https://static-1300131294.cos.ap-shanghai.myqcloud.com/images/deep-learning/CNN/book_cover.jpg\n", - "---\n", "name: Content image book cover\n", - "---\n", - "book cover\n", ":::\n", "\n", "The final result looks like\n", "\n", ":::{figure} https://static-1300131294.cos.ap-shanghai.myqcloud.com/images/deep-learning/CNN/05_stylenet_ex.png\n", - "---\n", "name: stylenet final result\n", - "---\n", - "stylenet\n", ":::\n" ] }, @@ -888,10 +879,7 @@ "Here are some potential outputs.\n", "\n", ":::{figure} https://static-1300131294.cos.ap-shanghai.myqcloud.com/images/deep-learning/CNN/06_deepdream_ex.png\n", - "---\n", "name: Deepdream outputs\n", - "---\n", - "deepdream\n", ":::" ] },