2831 lines
152 KiB
Plaintext
2831 lines
152 KiB
Plaintext
{
|
|
"cells": [
|
|
{
|
|
"cell_type": "markdown",
|
|
"id": "6d2db899",
|
|
"metadata": {
|
|
"editable": true
|
|
},
|
|
"source": [
|
|
"<!-- HTML file automatically generated from DocOnce source (https://github.com/doconce/doconce/)\n",
|
|
"doconce format html chapter13.do.txt -->"
|
|
]
|
|
},
|
|
{
|
|
"cell_type": "markdown",
|
|
"id": "f4908a97",
|
|
"metadata": {
|
|
"editable": true
|
|
},
|
|
"source": [
|
|
"# Recurrent neural networks: Overarching view\n",
|
|
"\n",
|
|
"Till now our focus has been, including convolutional neural networks\n",
|
|
"as well, on feedforward neural networks. The output or the activations\n",
|
|
"flow only in one direction, from the input layer to the output layer.\n",
|
|
"\n",
|
|
"A recurrent neural network (RNN) looks very much like a feedforward\n",
|
|
"neural network, except that it also has connections pointing\n",
|
|
"backward. \n",
|
|
"\n",
|
|
"RNNs are used to analyze time series data such as stock prices, and\n",
|
|
"tell you when to buy or sell. In autonomous driving systems, they can\n",
|
|
"anticipate car trajectories and help avoid accidents. More generally,\n",
|
|
"they can work on sequences of arbitrary lengths, rather than on\n",
|
|
"fixed-sized inputs like all the nets we have discussed so far. For\n",
|
|
"example, they can take sentences, documents, or audio samples as\n",
|
|
"input, making them extremely useful for natural language processing\n",
|
|
"systems such as automatic translation and speech-to-text.\n",
|
|
"\n",
|
|
"More to text to be added"
|
|
]
|
|
},
|
|
{
|
|
"cell_type": "markdown",
|
|
"id": "43cb4913",
|
|
"metadata": {
|
|
"editable": true
|
|
},
|
|
"source": [
|
|
"## A simple example"
|
|
]
|
|
},
|
|
{
|
|
"cell_type": "code",
|
|
"execution_count": 1,
|
|
"id": "cf6b9dab",
|
|
"metadata": {
|
|
"collapsed": false,
|
|
"editable": true
|
|
},
|
|
"outputs": [
|
|
{
|
|
"data": {
|
|
"image/png": "iVBORw0KGgoAAAANSUhEUgAAAi8AAAGdCAYAAADaPpOnAAAAOXRFWHRTb2Z0d2FyZQBNYXRwbG90bGliIHZlcnNpb24zLjUuMSwgaHR0cHM6Ly9tYXRwbG90bGliLm9yZy/YYfK9AAAACXBIWXMAAA9hAAAPYQGoP6dpAACc7ElEQVR4nO2deZwUxfn/Pz17A7vLuQf3cl9yCMohAoqCoMYrfjUqHlHzxVv5Gg2aGKNRTKJGjQrxRMWovwTwiMSIyiFyySkCAiqwCLss5+5y7N2/P2Zntrq7urv6mu6Zed6+eLnTU11d011d9dRzlSTLsgyCIAiCIIg4IeR3AwiCIAiCIKxAwgtBEARBEHEFCS8EQRAEQcQVJLwQBEEQBBFXkPBCEARBEERcQcILQRAEQRBxBQkvBEEQBEHEFSS8EARBEAQRV6T63QC3aWhowL59+5CdnQ1JkvxuDkEQBEEQAsiyjMrKSrRv3x6hkLFuJeGEl3379qFTp05+N4MgCIIgCBvs2bMHHTt2NCyTcMJLdnY2gPCPz8nJ8bk1BEEQBEGIUFFRgU6dOkXncSMSTniJmIpycnJIeCEIgiCIOEPE5YMcdgmCIAiCiCtIeCEIgiAIIq4g4YUgCIIgiLiChBeCIAiCIOIKEl4IgiAIgogrSHghCIIgCCKuIOGFIAiCIIi4goQXgiAIgiDiChJeCIIgCIKIKzwVXmbOnImBAwdGs92OHDkS//nPfwzPWbJkCYYOHYrMzEx069YNs2bN8rKJBEEQBEHEGZ4KLx07dsQTTzyBNWvWYM2aNTj77LNx0UUXYfPmzdzyO3fuxOTJk3HmmWdi/fr1eOCBB3DnnXdi7ty5XjaTIAiCIIg4QpJlWY7lBVu3bo2//OUvuPHGGzXf3X///fjwww+xdevW6LGpU6di48aNWLFihVD9FRUVyM3NRXl5Oe1tRBAEQRBxgpX5O2Y+L/X19Xj33Xdx/PhxjBw5kltmxYoVmDBhguLYxIkTsWbNGtTW1nLPqa6uRkVFheJfMrFwy378+5t9fjeDIAiCIGKG57tKb9q0CSNHjkRVVRVatGiB+fPno1+/ftyypaWlyM/PVxzLz89HXV0dDh48iMLCQs05M2bMwB/+8AdP2h506uobcPObawAAI7u1QZsWGT63iCAIgiC8x3PNS+/evbFhwwasXLkSt9xyC6677jps2bJFt7x6K+yIVUtvi+zp06ejvLw8+m/Pnj3uNT6A1NU34MbZX+O5z3egnrH4Hauu87FVBEEkEjV1DThRQ2MKEVw817ykp6ejR48eAIBhw4bh66+/xrPPPou///3vmrIFBQUoLS1VHCsrK0NqairatGnDrT8jIwMZGcmjcfj8u7Lov1+N6cYtU1VbD1kGstJTYtw6giASgTP+9AUOVFZjyyMT0Szd82mCICwT8zwvsiyjurqa+93IkSOxcOFCxbFPP/0Uw4YNQ1paWiyaF3iq6xq4x2UZOHisGidr6jH6T19g0rNL0dAQU19sgiAShAOV4TF6W2mlzy0hCD6eitQPPPAAJk2ahE6dOqGyshLvvvsuFi9ejE8++QRA2OSzd+9evPnmmwDCkUXPP/88pk2bhptvvhkrVqzAq6++infeecfLZiYEN77xNX44cBxTRnTBwWM1OHisBtV1DaR9IQiCIBIOTzUv+/fvx5QpU9C7d2+MHz8eq1atwieffIJzzz0XAFBSUoLi4uJo+aKiIixYsACLFy/G4MGD8eijj+K5557DZZdd5mUzE4IfDhwHALy1cnf0mAzSvBBhNv1UjjOe+AIfbNjrd1MIgiAc46nm5dVXXzX8fvbs2ZpjY8eOxbp16zxqUfzDd1vmQ1YjIsJt/1iHvUdP4q53N2BCvwLSyBFC0BBCBBXa2yjOYIOuzNILxjj/IBFgahhfqfvnfuNjS4h4or5BpnGECCQkvMQxZmYh0rwQEVih98ONxkkNZVnG4wu24p9rEjvtAGHO5bNW4MY31nC/+/HAMawrPhLjFhFEGIqBi2NMF0QkvBCNWDE3fr3rCF5a+iMA4PJhnbxpEBFY1JqWL74rQ32DjJSQshed/dQSAMBn08bgwfnfYnzfPPxqTPeYtZNIbkjzEmdIzDRkJps0kLqXaEQvySOP45ScLKnhDRunPPxfPP/FDm75P368Fat2HsbjC77zuGUE0QQJL3GG0ufFWDgh0YWwQ1ZakzNvPdkekw7eEz9RU48nP93OLX+ipt7bBhEEBxJeEhjSvBARLChekMkIL2P+vAhHjtd40CIiqFh10A1ZsUkShEuQ8BIgRDz72XHC1OWFZBeiESvCS3pK07Cw9+hJvPzljxRxkkRYfdKSJY8qgnAHEl4CQm19A8b+ZREun7XCsByFShN2cDLBfLK5FIMfWYhF35W52CIiqBgNG7zoIkqGSfgBCS8BYcu+Cvx05CTW7LYQemgyZpC7AhHBiuZFPRn9eOA4yk/W4obZX7vcKiKIGAkjl764PIYtIQh9SHgJCOrJZcUPh0xTuZuteGhFRESwonchhV1yI/L8SatL+A3leQkIihBoWcYvXl4JAOjfPhc98looSjaVM66TNC9EBCuh0gRhBkWhEX5DmpeAwM4t7Liw9+hJXPnSCry09AdNOXOHXRpgiDCkeSFEEXn+JLsQfkPCSwA5WduUN+H99Xux8kd+AijTPC80wBCNOPF5IZILkedPaRgIvyHhJSCwk8uxqqYMp3UOljg0vhARrJiNjPrN2U8t1s20SiQGYj4v3reDIIwg4SUgsD4vx6pro3+rVzjsFGQm19DqKHnYsOcopry6Ct+VVnC/t2Q2MvjuxwPHdTOtEomByKhBYwvhNyS8BID9FVUKVe0D87+N/l1frxJeJKVjrxE0vCQPF7/wFb7ccRDXvLIaB49V4//+30as3X3YVl0ivlLHqmn/o0RF5PmT8EL4DQkvPrNk+wEMf/xz/OrNtdFjq3c2TTp1DQ2655LmhVBz8Fg1HvrgW8xd9xMum9mU8NCaz4s5P5Qds944Ii4Q0rzoD0sEERNIePGZV778EUA4qoiH2ueFnYPqyWGX4LDr4AnNMSsZdkX6De08nbjIAoIJLYySj9r6BnywYS/2V1T53RQAJLwEHnU+BUVItYnqhUKlk5PUFK2gYi3Ni3m/qaunvhXv/HdzKf73rTU4ekK58abVaCMaZpKD15btxF3vbsCEvy71uykASHjxHbMokNp6/WWQ6d5GdhpExD2pnG1+3Yo2ikBJyuKPXQeP49/f7Isuav73rbX47+b9eErlgG01zwv1hOTgi8a9zcpP1pqUjA2UYTfgGE0SZqrbBllGbX0Dln1/EEO7tEJOZprbzSMCSGpIuyZxK9oogpFQTQSTcU8uBgCErpYw+ZTC6PEDldWKciLPX1ZoXqyJL9v3V6L8ZC1O69ra0nmEvwRNSCXNiw/U1TdgXfER1NY3mE4qtZpoo6a/zYQXWQb+9sX3uOH1rzHl1dU2W0vEGylczYv4+aR5SWzWcjZ/3bKvAnWNAqmZMHLxC1/h+wNNDttWzUYT/roUl89aoevnRwSToLkhkObFBx5bsBWvf7ULVwzrxP0+PTWEmrrwQMJGG73y5Y/ISG2SN0Wijeau/QkAsHHPUWeNJgJBRVUtsjNSDc1APJ8XK4gMUk6SJxL+ol70fLK5FJ9sLsXPh3bEk5cPMl1hb9hzFPe8tyH62SxwQI/iQyfQoWWWrXOJ2BMw2YU0L37w+le7AADvrdnD/Z5d1bKOkX/8eCt+98Hm6GfaHiC5WF98BAMf/lQxcfBwrHkRKGMUwk8EG71x4V+NCx2RcePoCTaRpr12cLopERBq6hqwT6UZC9p0QsJLAGGFF2OfF+N6SHhJLGYuDm/O+f6GfYbluA67LodKU7RR/GKe3NLas7ViTmAjJGmn8+By+azlGPXEF1hX3GRiDJrZiIQXnzF7f43U82Z+B5SLIbEQHet5mhf1IaOBSGTyIrNRsDlWXYd73tuAL77br/nO9MkJPFq2L1rxf6plNHakeQkuG38qBwD8c81P0WNBe+NJeAk4RlEdpg67bjeG8BVR7Qkv2kgt+Rh2HRHNCzNhPfLRFkz46xKcoMR1geFvn+/A/PV78cvZazTfuZFige2LVuRYVmNHmpfgU88Im0Fbr5DwEnCMVjVmgxBpXhILJ5oX9RGHsgveWL4Lb63YBQB47aud2L7/GD40MWcRsWPPEW2W5QgiUYpmsH3RijmhTmE2Ej6N8AnF9BOw+YSEl4BjpJ53OgjV1DVg6ltr8cbyXTZaRsQa0cGen6RO+dmo74iMUd+XHcPvPtgcmIRVhBIjnySzx2vV58XKIqmO0SST7BJ8WB+lYIkuFCrtO2YvsJMkdWYrovnrf4qGSV43qqtJSwi/ETUbCWleDLqGlcmLNWuGyInBd95ZXYza+gZHwqnVBbYlsxFTmDTDwYcNgw/a4yLhJUbsOXwCn2/djytO6xw9JrKSNvZ5MT7XrK8dPFZjUoKIR0TyvBgJKFYGqRDTiXkaHyJ27Dl8AtPnbQIAnF5klL3Wua8c+6QtaV4UkZTCpxE+UafQvARLevHUbDRjxgycdtppyM7ORl5eHi6++GJs27bN8JzFixdDkiTNv++++87LpnrOOU8vwcMfbcHzi3ZEj6XxHCtVGGleTtbUG55rtHHj797/Fn/5r/GzIIKFI58XCw67VoYotlbedYnYsWT7gejfVbVNY8NTnyrfc7MUPSI+LGx/0htnjlXX4Yn/fIdNjZErgNJsVN8g47vSCpSfINNjUJFlGVtLKvDDgWOB07x4KrwsWbIEt912G1auXImFCxeirq4OEyZMwPHjx03P3bZtG0pKSqL/evbs6WVTPae6MWPuut1Ho8dSUyRTj3sj2/U1r64yPFfvzKraery1crfhuUTwEI3OYKONIhOLNlRa/3wrDphsk7hRTkTMYPcoYrcV+dsX3yvKueGwq6yPf/zZz7Zj1pIfcOHzywCEk+D9fNaK6Pfrio/gvGe+xMgnPrd2QSJmHD1Ri0nPfonxTy0J3JYgnpqNPvnkE8Xn119/HXl5eVi7di3GjBljeG5eXh5atmzpYev8oWWzps0RRVaqtQ4ymeoNUmRrjk9E9Rpsv6ptaEBGKEXjL2NoNrLTOJDmxW9YQbKmTl8r68bbL7LH2nellYrP9/5zo+JzZJfiEyYaZMJ7duyvRIdWWWiWrhQJSsqrfGqROTFdKpWXh9WHrVub7yY6ZMgQFBYWYvz48Vi0aJFuuerqalRUVCj+BRlWeElLCZlOSI7kDJJRkhLW9ySqubOgebHSb9gVPgkv/sIKqOoNXVnccNhln7Re+bQU4+klaCv5ZGX59wdx7l+XYvKzX2q+E8327gcxE15kWca0adMwevRoDBgwQLdcYWEhXnrpJcydOxfz5s1D7969MX78eCxdupRbfsaMGcjNzY3+69SJv9mhn7Bq+Nys9OjfXjs4BqyvEQ6x4/MSEV6s5Xmx4oDZpBkkh11/YZ9bnYE3rNnzdRoqHRnv0kwcx0kDHAw+3BjOz7TrkDY3EPuMgva8YhZtdPvtt+Obb77BsmXLDMv17t0bvXv3jn4eOXIk9uzZgyeffJJrapo+fTqmTZsW/VxRURE4AeY4oxZlNS9eD/Z6g5CVfW6I4CD61FJVZiNAK/i4tU9JbR1pXoIC+0hrPNa8sLCT2uJtZbj3nxvx558PRCppXuICY/83sXJ+EBPNyx133IEPP/wQixYtQseOHS2fP2LECOzYsYP7XUZGBnJychT/ggabyCsztemWm73cTtEbGz7bqt3vhAg+og677GPXC7U3mjesDFI1bJ4XSpnqKyLPHQiHv6768ZBQPXoooo2YE65//WscPFaDX725FukkvMQ9CrNRwKQXTzUvsizjjjvuwPz587F48WIUFRXZqmf9+vUoLCx0uXWxg1Xhso8/HG3k3XUbZJlb/x3vrPfuooRnsI9y/vqfML5vPnIy0zTl2DEmYjbSCBaccaissgozFnyHwtxM4TbVKvp2sAa3pIN58DV1+sLLRxv34aON+ls5iIVKG5fPTEsx1SzT5p7BwGgOqk9Ws9Ftt92Gf/zjH/jggw+QnZ2N0tJSAEBubi6ysrIAhM0+e/fuxZtvvgkAeOaZZ9C1a1f0798fNTU1mDNnDubOnYu5c+d62VRPadBRvXnuIxCsvkY4heku97y3EeN6t8PsG07XFGMHmYhwoZVdtJ3jgXmb8NnWMktNYkP5Aza2JR2imhcr9YjAk0EKcjORlqoN2VecR8JL4GGfkYPAV0/wVHiZOXMmAGDcuHGK46+//jquv/56AEBJSQmKi4uj39XU1ODee+/F3r17kZWVhf79++Pjjz/G5MmTvWyqp7ArE/Z1TfE4L4ZaUq6pa8CM/2zllt118Di6tm3uaXsIMbaWVKB5eio6t2mGV778EXsOn8DDP+uv8VVavO0A93z2qUdWt5pQac688eNB8/xLamp0tIpE7FFo3BwIBlajjXgr8oKcTIXZqIoTuh00MwShJWk1LyLqx9mzZys+33fffbjvvvs8apE/KDUvTR/MvPGdor79b63cjde/2sUtO+7Jxfj0njHolZ/taZsIYw4eq8akxpDFXU+cjz9+HBY2LxrSQdjEqHDcrONrXrgDkY2xiV3hB21wSzbcMttZdebmaVBaZKQqHLh52cBZf4rquno0NABZ6SmWrk14i0LzErD3m1JixgT+Qw+bjbwTYNSdrfiQ8cp66Xb+Sp6IHbs54YoAcKK6XrinKEJmdVbgbg1DCvNEsMY2wiZWHXZ5c5okKYWTk7Va4YWdGIc//jn6PvSJYlsDIjaIRhsFbS8qEl5igJ4G1+t06urLkoNcPKAnbFh4dgqH3YjmxdxsZKd3kOYlOLh1+63WwzP/SJIyBxBPKGHPO9q4v9GPB6ybLgnvUO4qHaz3m4SXGKAXK58S8jbaSN3ZzCYX0VBcIvbIsngostJxUy9JnTsDUU0dO7i5UiVhgeJDJ/DCou9RUVXrmuJLpG+Y+bxIkBTO3Lx1Ey9Umoag2PF9WSW2lhhnpGefbdB8lGKWpC6ZYTsAOzB4ndRL3dcor0J8Izqws+p4vWgj3vxkZ2VVSw67vnLB375ERVUdduyvREFulit1CjnsKvY24n9faxKJRppg/2hokHHO0+Gs9ZMGFBiUU54TJEjzEgP0NC9erzLUfc1ssKBFT3CRId5flNFGjcKLqoxb4xBrGiCzUeypqKoDACz/4ZCLDrvWyvMmNUmSFH2D1zZaTPkHq0XR87NTlwva602alxig1Lw08eWOg55eVz1gBE1yJryBHWSiZiO1zwtnMrHl80Jmo0DQIMM11ZeYEMRm2OWZjZSLJV7f4As9Ii0knMI+j4qqWt1yQTYbkeYlxsRytdEgKwcDg+1ONHy54wBeXbYzcE5ayUr4OYj6vDDRRno+L1yzkfV2KfK8UF/xEffyG1s1G+kVV2QW5/m8UH/xDVYoqWzU3vFgH1HQNKukeYkBip05Yyi8aBx2Ba69eFsZ8rIzMeXV1QCAfoU5GNm9jSftI8JU19Wj+NAJ9DTIsWPJbMQ85qraesz4z1Ys3aEMg/ciVDpYQ1tyUd8gx1R4ZLsi77IhSZV9mdM7gpaxNVkx0rywBE1xT8JLDFDEysdwgFFfqs5ktPjhwDE88u8timP7jp50u1mEimteWYWvdx3Bi1efirzsjOhx9WQknOeFOe+9r/dg9a7DmjI8QdbO2r3exDRAxIYGObah0iKCdC3TN/7y322a70nz4h92dosOmmaVzEYuU1lVi/fX70UlI80q7Iax1LyoJiOzJEO8HAtkg/aer3cdAQC8vWq3fiHZnsPuLpPEhE4Jslo5mWiQXTQbuVBTg6w0G/G2suCGSlPYQExwulAJAiS8uMyd76zH3e9twD3vbYweYx95TH1eVMKKeZ4XsWOEN9SpnJLUj0t0YFc67PIlVrd8XvSc0YnY4qbcKFLXkePGpoYGWdb0ZyI42JmG9La58QsSXlxmUeMK47Ot+6PHZN80L0pMQ6V5wguthGKGum/Iir9lC5oXrcOuUZnosQQY0JKVBll2z2wkUKbGRI0ry0AtObUEFqfvahBedRJeYoByf4hYRhtZc9jlCSqkeYkdauFSsRu5bMXnpelvvQnErcGnQdVGwh/CZiN3HoAbQmiDLNsa62i8iQ1On3AQTMQkvMQA9h2OZVZJWbUaC5rNklCifj7qxyW6fYOiv+loXhpkGZv3lePCvy3Dlzvsb8jJCsTueV0QVnHVYdeNOmRlhl0iWMgOlWJBcLYm4SUGsCuZWEqsVrcH4Pu80FIoVqgFW/taDcZsZLCr9I2z12DT3vJoWLydFTdbPVkJ/MMtk516wWOXsOaFOkRQcbrQCIDsQsJLLGjwSfuhTVIXgB5H6FLf0KD0c9FoXsTqEXnMsgwcPlGjPCZWveocctgNAmHNi/MnUO9Sql43NUGE+zh9NmQ2ShLYAT6mmhdNqLQNG7RbjSFMqWtQrnrVkTx2oo0MSrkibSjMRgEY0JKV+gZ3jHb1LmleZJuh25EeXlpehQfnb8L2/ZXOG0NocO7z4kozHEHCSwzwz2FX/Zkc6IKM1udF+dlOtJEetfWyJmLEebSRcdn6BhlVtfXWL0II4YbQ4ZYQJMPZQu2+ud/g7VXFmPjMUhdaQ6hxuogmzUuSwD7nmG4Dr+pgZnkXeP4tFCodO9TPh+0qN7+5Bmt3HxGqR6SLvbZsp5WmGVxL3GH3vGeWot9Dn+BYtf5eKoR9dpQ511K4Ze5pkO1p9iJD0A9lxwCE2/Lc5zuoz7iM3jM+dKwa/1hVbH5+ANyZSHiJAX7tbWRV88ITU0jzEjvUz0dthtmw56hQPSKTzwcb92nPszHbKBx2TU7fUXYMDTKwTlAII/h8sGEvpr61FidqlBP6yh+120BYJZwvxiWfF1tnhgecVs3TokeeXrgdj3281XGbiCb03vVrX1uNB+ZvMj0/CP6TtLdRDGAfc6xDpVlsRRu52SDCEHVoqd2uIiKE1NS5s3RS56KRZdk0Qs3/YS++uevdDQCAfl/muF633OBWqLQzIahVs3TFZxJ43UXv0WzeVyF0PpmNkgSF5iWGD109+bnpb/Pt3nIcPFbtWn0ENKGltvuK3dNs+bw0nTRv3U849dGFWMPZCFJ5Hf8HvkTg8PEa80IWcStTr9O9llqqhBfCXSjaiBDDJ4dd9ZXMVH2iZqNv95bjgr8tw7A/fma7bYSWcLSRc0E3lsPKq4zvzJrdR3DkRC3ueGd9DFuQvHhh0nUrU6/s0HemVbM080KEbSjPCyGEclfp2F33pyMnsOfwSebaToIXm1j2/UEHrSL0UPtD2R0gYin08LpUSoiMjfGKmw67TibIFhlKjwbyvXMXp2to0rwkONe/vhr/WvuTKlQ6dtLL61/tUny24yzMGzSC0HETEaMMu1bw+/FkpqUYfk+9xx28iASU4ZbZyG4/DJ8UImnFU5yaboOw1QwJLx6yeNsB3PvPjUrNi4/P3NRsxA2V1uL35JioaDdmtFeP3cfj1nPNMhFeCPu8uPh7T+sPRzi7YTZyJgSR7OItTt/1IMwBJLzEAPY5+7nfh5mwzPd50R4NgtSdiJglqRPF/qrKneeamWYyrFD3sc2fP9nmaf1uOezaFrwbzyPZJdgEQftOwksMkBU+L/4/dCvwBpEgdNxkINaaF7cwNxv53cLEwBuHXXf6T4Ms2xonqGfEBqdj+PFq/zNlk/DiAou3leFXb67BgUp+6LBiv5oAZCbUg7+rtPZYLBPtJTPxECrNIyPVRHih7uMKXmgnGhrcSVK3rvgojpywHsoduTR1EW9x+ognP/cl3l+/153G2ISS1FnkZE09rnl1Fcb2aoc7x/cEAFz/+tcAgAydFadiV2kfR26zS/O+5wkvQciumKiwd9aujGhH6DlqY6LRg2c2otwu8YHNrP5cqmrtr9Sou3iLG7f37vc24OIhHVyoyR6kebHI3HU/Ye3uI3h64XbNdyVHT3LOUKrJY7q3kUV4kx4voiHAPyGhiGW00emPf46Kqlpb11PDc9hl20QTkztYMRuJPtsGWTbdA81LyKQYGxLB9E+aF4tUG6RV1+sQiv1fAjzzi7YsyL8hkbCrrbAzAbi1XQAAZKVzhBedv4nYcOmLy4XK7Sg7hpvfXONxa/RpMhtRL/GSBJBdvNW8zJgxA6eddhqys7ORl5eHiy++GNu2mXvLL1myBEOHDkVmZia6deuGWbNmedlMSxgtdvTmdHYSCrbmRXuMN4gkgtTuF8WHTqCsogpvLN+F78sqsefwCd2yTiM2/ILnsEtmI3/5vnGXZjOe+tTbaCZRqLt4wzuri3HO00sMx514wVPNy5IlS3DbbbfhtNNOQ11dHR588EFMmDABW7ZsQfPmzbnn7Ny5E5MnT8bNN9+MOXPm4KuvvsKtt96Kdu3a4bLLLvOyuUIYqWr1JnU5MJoX42vzJhjeTwqw/BVoDlRWY8xfFhmWUfQVwfusfm5+P570FO2aiP0tJMi4g9kGmHbwOxpSz2H3u9JKvLu6GFee3jnmbUokps8L7xj9yL+3OK7L70zangovn3zyieLz66+/jry8PKxduxZjxozhnjNr1ix07twZzzzzDACgb9++WLNmDZ588slgCC8G3+kKLwqfl+CGG/Gazzvm9wAXr+woqzQtw/YVUQ2XupjfwoF6Tq2uq8eMBd9FP1PvCS47BDU0atJSJM2u6HYwMhf9Zt4mEl5cwg0zcYrPmQRj6rBbXl4OAGjdurVumRUrVmDChAmKYxMnTsSaNWtQW6t1OquurkZFRYXin5cYrXb09i1i5ZUgz/u8gYPXXL8nx3jFLP8JAMUNFxVe1OWC9nhe/2oXZi/fFf0ctPYRznErnX9U80J9xDW2lVZi/FOL8cGGptDmkAszv99ZkGMmvMiyjGnTpmH06NEYMGCAbrnS0lLk5+crjuXn56Ourg4HD2o3BJwxYwZyc3Oj/zp16uR621mMHpjepM4e3XnwuLsNsoCdAYH3myhU2h4Zqeavm8KxVdRsZPI51qjbvfuQf30+kQlSFlq3TQjksOsef/x4C344cBx3vbsheswNYdPv/adiJrzcfvvt+Oabb/DOO++YllVrNyITKE/rMX36dJSXl0f/7dmzx50GC7aNRW+yiRcH16++P6Q5xmt5LHfGTiR4viBqZIualwfnb0LFSaVG0u/+pp14+O9MXX0DFm8rQ6VLIdqEf/g9kRH68ARLN55XQvu8RLjjjjvw4YcfYunSpejYsaNh2YKCApSWliqOlZWVITU1FW3atNGUz8jIQEZGhqvtNcLocelqJOJDduFy+z/W4arTO+MPFzVpy8hsZA+Ru6b0eTEv//aqYo2wEjSfJO04GW7fC4t+wF8/244hnVti/q1nxLxdcU+A5AW3mhLtysHqwnHJvHU/IS87E/nZmZrv3JA7/JZXPdW8yLKM22+/HfPmzcMXX3yBoqIi03NGjhyJhQsXKo59+umnGDZsGNLS0rxqqjB2oo38XglHsNOK2noZb6zYrTgWlN8Tb4jcN2UyN7H7/OMBpVnGb+FF3Wz1QBn5/p9rw1rS9cVHvW9UAsJLIOkbLjalpq4B/1r7k3sVJiHfl1Vi2v/biGteXYWCXJ7wQmYjQ2677TbMmTMH//jHP5CdnY3S0lKUlpbi5MmmTLTTp0/HtddeG/08depU7N69G9OmTcPWrVvx2muv4dVXX8W9997rZVOFMRowElDxEkW5uaSPDYljRGQRO9sDqOv1XXhRfdZ7Z/xeuRH+0DOvhe53MmT8fckPOHTcve0qkpGS8qro3y0ytAaWRDAbeSq8zJw5E+Xl5Rg3bhwKCwuj/957771omZKSEhQXF0c/FxUVYcGCBVi8eDEGDx6MRx99FM8991wgwqQB4wFXb9JIBE0F5elwjpjmReb+bXiOSlzwu7+pL69+Z6j3JCCCDzU9NYTrRnXVr0YGlmw/4E6bCAB85+eQC4KHz7KLtz4vIoPv7NmzNcfGjh2LdevWedAi59jK8xKQ0fqwg9VMgywjpfHXU7SRPbzSvKjL+a95UV5f/c5Q93GHIGmuRAVmCSYRm+40h2DgPRqB2AFTvEiSaAXamNEixqHSesfj/5VkJ8SA+YPGDULCi8zeZ7EbvXb3EcVnH/fVC6PRvOiYjYLks0E4wkqXM3vufnffRIN3P91495IqSV0iwD50tVCin2E3/gnOFgfxi1WHXbvmn3qfszibtTqimQmS5iAe8ds8yCLaFEkyz5WVCIu9IMG7nW6YfPw2G5HwYhXmganncN1oowSY7Nnfxmphrvj7Ciz6rsyPJsUdQqHSMv9vK/jtUK2efNTOgTQ3uUOQxhUrSeWM5rzg/KLEwSufFzIbxRns49Lm1+CfkwgvZL2OOWPVzsO4YfbXfjQp7hDSvLB/2+w4W0u83SLDDHLYjQ0Bkl0s9VWzOc+sqrW7D+O3729C+UlKbigC1+clAaKNYpKkLpFgpU3tnjJ60UaeNikmyHGyP1OQ8crnJehoHXZl7nHCGl47Zp/Zsy2+3KHdkoWHsNmo8T8n9Vw2cwWA8O+fcelAsQsTCtzJ8+JCQ5xc39/Lxx8KzYtK06IfbRT/k1BDAk6qsUakHyijjeLzPmvyvKgGucjv8lvtHO8EqX+Imo0kCSZSqywsCP1wgPbM0sMs2aUbGzMmdJK6RERS+LyoHXb55wRojLGNntmIEEdkoeyGz4sRsRhvtGYj5UV99idOGLx+D60Il1aaYjbp6VVFsq49+A67LmheEjlJXSLCPnN1vhP9aKP4n+z1HHYJccQ0cN4KibFYLbH9XZZlvLO6WPE9Cb/u4LVjtpWeIvpEzRQvRl2jVbN0exdNcrih0hRtlHwoQqVVg4f+rtIeNihGuBHCm+xY1bx40W9iMeCwv2H5D4dQWVWn+z0hhizLuGXOWsUxr6ONrExwVkzjpknqAr5HXLzx1srdmmO0t1ESYqR50dNIJMI7p/B5IbW/LUQ0cF77vMTaz+THg1q/hKjPS0xbEt/8dOQk/vNtqeKY52YjC2WtyFF2En0CwQoNjycOVFZrjrkxDpDwEsdofV4Sd8VApiLniEUbsX+7f89jkRVTVv4I7feetyDxOFZdpznm9TYdXgi6kmQcbQTo9w/NEETSr23cGFv8DpUm4cUiRplmE0FI0SOBf1rMENvbiPUXcb8NMTEbmXwffU9o8hHmRI1WeDHTRLTPzXR0Ta8ej1mGXT004yuNSbbRW4xakVfJYTfOkBUOlcrveP2heXpKQqg7E1kwixXWtwdwvw1+DzhAYviAxZrj1fWaY2b38aEL+zu6phdKOrMqZRj5DlLH4VF+wnqyPr17acUU5PdQQsKLRVh/D7XalrdqkJEYCwRWUqeQRXtYzbAbt9FGJs3mJalbsKnEuwYlAHbMRs4nF2+kFzNzlJ5vGPnaafn4mxIMeuRTPPGf7yydt/LHw9zjVp44+bzEGYrJRWM24pSXE2PFQKtl5wgFSnucTycWdmozx2SeJvLWt9d51ZyEoLJKu7o20+iKPOtR3dvofufV3GQ3VDoRxlG3+cNHmwEAs5b84Ep9VgQS2lU6zmAnF5F3qUGWsXmfv3vNuAH7u0nzYg+rTnLe+LzEVvPC+wmzl+9CBWcyJvSpOMnxeTHVvOg/61+c3hl/uuwU/GZSH90yvvi8GGTY1Wi6E0Kn7Yy0FHencCvDg9/zAAkvFmHfHxFv/5r6Bizcst/DFsUG9reaRQsQfKxGG3mx0myekeJ6nWrYVvN+wq5DJ/DAvE20PYAFKnlmIxMzipF/U35OBq44rTOy0vT7g1c+L4bjh0GXl+XE2GrFTVJT3H1I8fRKkvBiEaXDrjUHTBFaNUuz2qSYYGZv/uvC7bFpSBwjlKTOwCHcDVpkeL8Xq0if/+K7MhKBLVDHkVTMJnIjq1FEgDCarLxapJjuKm0iwETroR7kuubFCn6LkSS8WISdULyIIsrODKjwYmI2evbzHaiu00ZEEE1YFXa9WGXGQngRGdZS/Q5ViDN4Wl5zh139e9x0+/XL2F2FXzCw0KBOY5HDLMDB69w28Ybb75EVgdBvLRgJLxbxOpQ1qGo7cpZzjvUkde63ISs9BmYjgXb7uWKMN2RZRn09R3gxGYCMhBeRccbuWDSxf4FhnWYZdkVzvZDPC5Ce6t975HcQRyyWYQkF+8J4kXU2oLKL7x01ERBZqSRaqLTeb05LCQVWUA8S+46exGUzl6OkvErznbnDrv53EX8jL8xG5lFO9h88raGUuK55sVCd3wtaWv5YRGE2SoC9Z0Txwr8n2bAeKu1+G4LSu9JSzdPEE8Bzn+/gCi4AUFVr7Ihm5LAbGWYMn4DNx2M2n5pFGxnBWzCWlJ/Emyt2cbMQJzqpbkcbWSjr94KWNC9W8TgPR1CHc7/tm4lAEJLUxQIRdT6ZjcQw6gLHORFILGIOuwYCjmHtBnWb1Gk3zwvAfycuev4rlFVWY2tJJWZceopYIxOENJejjazg95xAI4hFlJoX9+sPqOJFEZZJ6bvtIXR7PHTYzUgN4fPvylytk4eQz0uIhh4RjLQnJ2vNHOTNHXaNhhu7CQ3NTJNGwo1Z1+FFPZY17pq8ZJv3fTtopLr8HlnR/Ps93tMIYhF2QvHE5yWg0ouIoxzJLsaIaV68MxvFwlkXEDOPpaUGs58HDSMFFW+/IxYj59io2cjgMdjNoGrUZrNoI4Cy7FpBT4Np9y5ZMhv5vF0DCS8WUSbgSh6zkUJ40fnZNKyEKauowoPzN2FriTKzsvVoI/c1L7HALMMuQA67ohitrE+a+HhI0NeCRM1GhqHS9h6Q2Xlmu0obmR2NhJdkHH/8NBv5LUiS8GIRdjXshebF782u9GgQMBvJsoz6Bhlrdx9GlalKO3GZ9v824u1VxZj07JeK4yK+IF6aJd1WMetBPi/uYTQenDB5x4y0HCKal5AEPHqR9Z2pzc1G+t/JMBbyjfK8JKNSxnWHXQvTj9/3m0YQi3geDRJM2UXMbARg5uLvcdnMFbhlztoYtSx4qDUuEUTUrFYzOFshFpsyAhBaAqeT8CKE0W0y6x6GmheBgSYlJGHKyK6m5cL1MeeZOuxa74eRvut1HqR4w23NS0WVeMQWaV7iDK8zoMaFz4uu5iW86R4ALNp2IAatCiZ6z1AsVJr/tx3U+9a4vQ+KHiLNVreFEu7ySXGgLTP0eWHK6J8v/lBapDcFrjoJldbrPJE6jc1G4e/KKqvw6rKdKD+R+Jt/so7vyk2DvRcsSHiJMxRJ6pLV50WvEK2EAOgP3rEMlf7lGUUaB91YpeQXGThTQyHF5BhUc6nfOFFQSZB0zXPRaCOhLQTMac5sO2EYKi2ZRRvx9bqR/mFkqo90u2tfXY1H/70Fv/7XRuNGxzEb9xzFYx9vUWzJUhfjxCt+a74oz4tFknZ7ANbkoeuwy/9iW2klDh2vxqjubd1vWADRfYYWVS9O+ldI0k4+sfJ5ESE9Va15CWjHjwGfb92PiqpaXDKko+Y7uxE/EfTMCtEMuwbnWjEzsruVG59msreRruZFazbS47vSSgDhzT8TlYte+EpzrLa+Iaa+ZAmteVm6dCkuvPBCtG/fHpIk4f333zcsv3jx4rCTmerfd99952UzLeH1xox6Y9WSX4/Df+460/XriSIeKq39AROfWYqrXl6F3YeOe9S6YKE3PMdS8xIKSZoVrh9mI72foB5kI039bMt+vLz0R28aFlBufGMN7nlvI/YePan5zijPixmSpO8YLeawG/7yzrN7ID8nw/Ba7IafZkKP+a7S2k4TqZLVvKjrKausxm3/WBf9HKv+HhRq6mIbu+y3ot1T4eX48eMYNGgQnn/+eUvnbdu2DSUlJdF/PXv29KiF1lE7VLptW9Sb+Nq0yEDfwhxXr2UFN0Kldx064V6DAoy+2cj8XLd8qkKSpGlHrBx2hUOlmc+RifKmN9fgsQVbsXb3Ec/aF1QOHavGi4u/x7ript/u1NSnt3Ffk+bFfAuBaRN6Y8mvzzK8TotMMbOR2TVlmd9nIkKc2Tj08Tcl0b+TLREiK9jFQrDwW/Piqdlo0qRJmDRpkuXz8vLy0LJlS/cb5ALs86pvkF23+5k52PmF2MRrXChZnDL1HXYFNC8CQqIIIUk7SaSFQvj1xN74y3+32a9YAJFmZ6Yp87yoBasDlfz9fBIN9nnPW7c36vC+64nzAehrXiRJINpI0o/qEnHYZU1WZtqS07q2xlffHwJgtiGkeah0xUllxMvjl5yCP30S1r5bUXYnm+ZF1v3gDZSkjsOQIUNQWFiI8ePHY9GiRYZlq6urUVFRofjnJexgU1svuy596k18frsEiDjsyhBTQyci0+d9g0c+2mJYRkgAtFheDz3Ny/8M66T47AUiGiN1X9B2jcTtKyzsrdp5UGtW1fN5EfGFMXLYFdmYkb2EWXjzLeO6Y9q5vfDvO0ab53kx+O7ef27EwWPV0c93nt0DVw3vLBRtpMbtHChBR6nx9F56ob2NGAoLC/HSSy9h7ty5mDdvHnr37o3x48dj6dKluufMmDEDubm50X+dOnXSLesG7PM6Vl3rehfRTSrl82DOTqR6nZZ3mC2bqMLLvqMn8c7qPXjtq52orquHrrba4s7cjnxeJKC0Qqm9SE1RCjSdWzezXb8RVkPCARFhJjFhbwMvA7KegCniCyNJ+tswhASkF8mC5iUjNQV3ju+JAR1yDYViyeSa5SeV4c0tm6UDaLoPVt6JtGRR9TbCCiyxkCtoV2mG3r17o3fv3tHPI0eOxJ49e/Dkk09izJgx3HOmT5+OadOmRT9XVFR4KsCwD6ziZJ0Hmhdrx1lyMlMtJRmyQoOAPVWGrBmX2PuVqGNJXb1y0NB32DWv64MNe5m67PctSZI010sNSQohwTMBQUTQhWycY8TlJgUVdvzISNPuPaWniU0NSagxqdvQYTf6fxNBg/O3Gca5Y6wtxJocixuFFzbTt8m5yaZ5Ub533l/Ob5+XwD/dESNGYMeOHbrfZ2RkICcnR/HPS1jptrKq1n2fFwfnpqd6t/GeiMMubzRhncicRE4Eibp6fWPv377Yoci9wCIijGz8qTz6t1OzkZqUUEjxDLyTXcQ0TH5rE4MA+17x/FP0+oyo2UjX50Ug2oh9PFa0pk6jjXjYMxslV/+Sdf72Cr81L4EXXtavX4/CwkK/mxGFfXcqquo8cNi17/Pi5cZ7CrORThmez4tCeEkAW8CS7QfQ76H/4p9r9kSPsT/rhUU/YH9FNedM6y/784u+t9NEAHwtV1jz0vTZz2zOZrciEfqKCOz4kZEm/v6Kmo3Mo40Mzme+tbLwaJ5urNC3pMVp/H9EWGOFF7N6YpWUMSh4nf1de70EjjY6duwYvv++aQDeuXMnNmzYgNatW6Nz586YPn069u7dizfffBMA8Mwzz6Br167o378/ampqMGfOHMydOxdz5871spmWYB9YRVWt66ozvfdNZJXqxj4X3ds1xw8HtI6DDUqnF+65vMN1jJ738PEaHKuuU+SEiDdueuNr1NbL+PW/vsHlw6yZJ2OpZuVNNmGfF+8HdLHds40LGTXzm5+OYtehE/jZoPYWWxY8FMJLqjLVe9j0p6N5ERFeIGA2MsmGa4fsTBPhxULFUSErKryItyNISRmdUlVbj0yOWZFF4fPidYOQ4GajNWvWYMiQIRgyZAgAYNq0aRgyZAgeeughAEBJSQmKi4uj5WtqanDvvfdi4MCBOPPMM7Fs2TJ8/PHHuPTSS71spiXYx1Vxss4Dh109Bzvzc92IHtF74UWijXidmbVR3/zmGgz6w6fRz3sOn8DzX+yIqz1I/E6J7QS1z4tZd7G7eaKY8GL8vdH89rPnv8Kd76xX5EOJV9gJJ4Mx+1Y3JhzTm6xFhVAnGXbtjibNDRYnEiRLQlGkbOR31DImW7Nu5vamhX7x9Kfb0Od3n2Dlj4eEz/FqnMrJTMVn08L+p36bjTxdAo8bN85whTV79mzF5/vuuw/33Xefl01yDDtBV3qgedEbMURWK26sNPTUwwKKl7DZSPUD6lTJAFgz0sUvfIVDx2uwpaQCL1491FZ7Yw3veYsOxrFcqfCyP6emhBSRUGYTYEZaCDUG/j16CPu8SOxn5TkimsZdB4/j1M6tLLcvSLCPidW8VNc2IDMtxUDzYl532GzEX6037W1kfL4djFLUhx12rRMR7KxkkQ2FJHzybQkGdWqJwtwsG1cNBs99EbZePPLRFiwwyLKu7CrejDWSJEU3C01ozUsiwj6vugYZssuJevRDpc1xwyFWz05cL8s4UFmNHw4cM9geQHvcaCO1Q8fD8RIrfhBfUfgN79eIqsFj+a7zbrta82KGmZpaDyHNi9ngKtDMRIgmYScA9t2LaJX07qXYQkUy0LxEShhFG3mjubCkeWn8f8QfSM8Znsf64qOYOmcdRv9pkYXWBRczzbrCYdejsUaSmgRfv7XQ8f/2xxhlB9HbA9U+TkKljfr2pUM6CF1f7wWRZRmnPfYZxj+1BCVH+dlPeZ1ZZOdtPx1HrcL7OaKtj6WalXffUywLLzbNRiJlZFUoriQphF+RViZCHg+91+OG2V8D0N8/TUR2Mc6wG5VeDM+3i3GmcOt2o4hWqrrW+mrRaAEVT5gtThXZuT1qg4QmjS1pXuIMte+H2++F3uTieIIXPF1PeGEH0YjGRAQ2B0qEtbsPK5JRxf8UJEYsX3aeFqxDqyyFgGsmyGTaDL0X+Znq90aWZcUxITNpnGleyiqrNBOp2YSjN76IhUobm3AAE7OR6RX0aWEQcWRnKIuYjapjvPlgkDBz4XErwaUZkefnt/ASv2EffqHy/XA/2sibqVy0Xj3hhSODaFD7MQD8Vc9lM1egQ8smG3QcKV64BHFdp+6XVwzrhF+eUaQQCsxW71ZCd5XIKD50AjlZ+sMLT2MpEgbL9qd4yuOxvvgILnlxOUZ1b4N/3DwietzMl0xvfBELlZZ0Q6WjZYwrMDw3PUXfJ6p5Rioqq/kJM+2ESkc1LxbMRvFKbX0DUjm7wluZG7yUK5o0L95dQwQSXiyi1rzEamNGx/UKltNb0YnE9PMmJD2z0d6jJ5lP8TMJ8SgtP2leCPomAC9Qzyl/+vlAy3Vk2NS8rN19BGP+sgjZGam44Yyu/EKydvBTCC+cLvHljgOY/dWu6Od42jX47VXhqMrlKv8u9r3iCSrOktSJaF7sv3tGJnM9wVKStJOyEZGiTT4via15KT9ZizOe+AIjurXBK9cNU3xnbjZi/vaicY315mal4fFLTvE9jw4JLxZRjCWy7HuiHlFExwu9QUdEw8T1eRGYsONd83LZzBVC5WLZU0T6pZlDpl2flyONoe+V1XXYq+cfBWWf6tauhaL/8No25dXVis/xpHnRew/Mkj/qmo1E9zYyuUdOQqWNupjp/kaCRPpB1Gxkw+clnvjvt6U4Vl2Hz7bu13xnJrAq9zbyZrSR5bBW7arhnT2p3wrxs3QJCLLqb7cX0145rzo1G4nuiKw+m+fzoiYB/C6FiKWN2KnQeE7ffNs+Lyz6m3jKiskvSxUWLNJd4yGPRySHUZ3O81D4vKju1Ucb9zlMUifpRiVFJjonodJGPcxoorXn86I1G8XLwtEKRmOEV7vAWyFI95yEF4uo9/hxPdrI1dqaEO34eoPOV98fND2X17FFJuxk2ePG6/e+e7vm0b9FhE0jQfnmM4tc6dl6z79BVptg1Q67LlzcZz7YsBeDHvkUT3+6TddkyB5V36s73lkf1WKpEdW8mGmnnIRKG01keiYOuxszRoQXK3le4hGj99ZUmDTxn3KD4IguJLxYRu3R7X60kf1zjTpsUdvm+l8y6A2KX+4QEF44x/RWnCyJMFHpMXPxD/i+rBKA96uWzq2bRf8WEapjcdv1Hr/aX0wtzIhMcAFaBHL57fxvAYSTjKmTNUZgfzPvXlXV8h1URRcjen4JkfsbdM1LU56X5Ig2cqJ5UVoFEl96IeHFIrJa8+LyCOqV2SgrXcwE4EQ1yfd5MR9sElh2wZ8++Q6Tn10GILbe+SLOweYrOecN1nPYDodGKx0+2ISPIq9BgMZRLuzv0zPjmYW3Ru7Duf3ycU7f/OhxEYddwCBvkwvC7SkdcgEA2ZztANzO3BvRvJyoSexoI0PhxeTGvbrsRyxrXGQGXbB3AxJeLKL2eYnR7gBi53JO7tgqC69eN0xYVevMriprhC+R7PLxlKROjcgEHwkn9Ww1xEHIbGT0nUvPRNfnBTyzEat5iX/YZ6CngVSbodVEhJ7u7Vrg1C4to8dFfNjCDrsO8ryYXGLmNUNx1fDOmH/bKM13euOIU7PRWyt3C58bjxgtOsyijeasLMY1r64CQGYjgoPSrii77oTp9jw+8+qhGM+s2MwwG+yM4N0KPXU5SxzLLpYGCb8z7KrxdYdpWe3voQ6V1rZNfSjoq8t6q5oXTpmI0BOSlM9LzOdF0hciorlujcw7xtfo0DILj19yCnrkZWu+M4w2smQ2UkYbJTp2TXFW6nECOezGMWphxf1n6a7ZxixJlRpHZiPOsUQPlbYivNrtK0O7tLJ8jlCotAOfBlH0HXZlzkJA+VnTHln9OTgDKQ/ZqvDC07zUR4QXSeEPJxZtpO/zIhJt5ATdTOEWd5WODIdWxzE1Qe8rEYzGSytpjTwLlfakVnuQ8GIRhdlItjZ5ieB2NFzkpTcaMC4a3D76t5PwU9t5XuLYSGDl6dsdUDIEB262djfuu7q5D1/YD+f2E9fiAQYOu7LWWdXq3ixBGkh5sL9dP8+LzP07gp7mRSzDrvkWCsamQ9NL6GKc58V6xU7DhONlfyOjIcJShl0X2sKtN0C3kYQXiygHWPejjdzeDE1k4mO3ixfbrZYPN8NuAmteln9/EH/4aLNwebtdxc79EeqXJv4Ol6g28zyzVzs8+fNB1tqhGyIsq3xezFPla+oI0ECqpqauQVcwufqVlVi7+7DmOF94CZtd1SYgkbk8nOfFLNrIXSEjgtvRRjzhxcrjrxXZ3yQAuJbnxTOfl+DcRxJeLKKNpXfZ5yXGZqN/3DRcIZA5yVrKc28RCZWOrCi+3nUYP3t+GdYXH7Hdhlhy1SurMGdlsVDZnQeP285RYadPiOXXMeaiwe3x9k3DrZ0k2A5ZVvUXWS3MmLc/SAMpS0n5SQx9dKHifWTfg6++PxTNyqz0+9EX/sNmo6abL/IYjPK8RM1GJufbRXdXacliht3GinjC0OHjNcJbbtQK+N4FAfbXHKisVozN1nxenL0bN59ZxK83QK8cCS8WUe8f4VTz8ovTlWmW3dZCRM1GOt+P6tFWMWg6ctjlbbYnZL4Ic/msFfjmp3Jc8dJK221wg2/3luMv//0Ox3U2lrPDWU8uxuzlu2ydK9onmjM7+Q7u1NK0vJkaWpIkDOncUnVMrC0R9J6+2kykdtiFDMxb9xOuenklDuvtYh6ggZTl0837NZsS6vu8KO+Bmrqoz4vSVCQUbQR9n5doGY+0nvqOwvauyTOT7T50Ane8s17ofJFM30GA7SenPfYZ7n5vQ/RzRJATGVOdChkXDGxvXshnSHixiDK00Xm00eBOuQo/E96LXZCTabv+DAGfFzac2WmeF/V1RDQvasnK7yyaF/xtGV5Y9AP+8t9tvraD5aze7UzLZGem4rNpY/DoxQNw1enme4+ITCKs1keWrbuT6z9+pagrQ9YMuNP+30Ys/+EQ/rpwu04NwYTdMT2C3uRp5qTcZDZSmoqEQtklfTOwiNnIiZuJ8ThiIVQ6Wh//+483lQjVUyeSsyEAqPvABxv2Rf+O3AORSEKn74ZetwjSO0fCi0Vk1d9OJVz1Lqs8E8E/p44UrEt7LF1Ak6LQvLjsMSzmOBpMtpZU+N0EAOE+8tK1w0zLZWemokdeNqaM6GLqqBmuV+Ta2rZYQX9vI5W/R4N2x/YIFVX8FPlBUmGz8CZuI/NZtIyB2VWSJIXZQPQ1TTExGxnhxIStG20kWYs2ipR1GtZfGwcOu/UNMr7dqz/mRPqV3U1yraD77AN0G0l4sYg6qZRTzUuKpOwmvHe0E5P23QheU4wmm/d+NaLxPNbnxd08L2IOu0EVX4KBBDFzXnZmmsV6rd9365oXPYddrQlW609mDG8C/nZvOXYdPG6tkS5jFDVkVJZ33rd7ywE0+rxYNhtJSHPggG/EaV2Nw/eNhCtrPi/h/zuNNooHzcuMBVvxyeZS3e+bzEbmdTn1edHXvARHeiHhxSLq3BROJdxQSNlRvEocpp6obhnXHcO7tQGgVEM6y/Miazq9iPASgM1SuQRFphJtRwtOmnan9Tq9B3oDrSyrzEYGDrt671h1bQMufuErPL5gKwBgf0UVLvjbMox7crGzRjvEihBvtrdRLevzogiVNm+HJBn5nggIPwZF3vuVsTbYOEmd9U5lxVmVR20cCC+vLNtp+H3kHoiYjbxSNAVJ20nCiwWqausxf/3e6Gc3zEYhSVIMJG5NmP93bi88e+XgpgNq9T/zN/teOxGe+Bl2vVVPe4lX7Tqvf4HFdoiRnWlNeBG7tvLqVruH3kCryesiWw+VXvBtCTbsOYqXlv4IIBzRFQSMoobUqBdDeqiT1AkJH9DP2+R0BW2WZ0Y/SZ1FzUtjaZG8NkbES6i0EVbMRk4nJvJ5STBW/nhI8dkNs5F2UHKHO8b3xEWDO5gXhHLQdLSrNeeYyCohKBoONV61q0+hNp26cTvEGjKgcaM8UVh/qNk3nIZfnqENj1ReWrYs0K3eeZh7XGs2ki0nqVM7dgelG3Ez5drcmDGCWosipjXT3x5ABCfmXNe2B4iYjRy+jKzD9JHjNXGTcZclJEnYc/gE7v/XN6ZlHTvsBuZt0sf9pVoCc+SEMmRTdsECqMnfEKOZnL2Mck8Z+3XKsnZyq48DdW2ssToQiwy0r10/DH0Lc7jf6efckLDhoXMhy0Cr5ukY07MdXvtKqbpWn+pW91SbjRo0mhfrERVB8Z3itV1XeGF+hZFyIKRy7BcNldbzlRLV3NhFV1Mi+ZRht/GZrPzxEK58aSUuGtwez145xFGdfnDD7K/xfdkx03JeyWZBEvpI82KBnw6fVHx2xWE3BMUo4dX4a1QtO646MhvBXqh0UCYdNV75H1lVgYv0sLP76KftN7pay2bpaNU8PVyOU9DLZ6NOO8Bu4inym9UDaVC6kRUNJPt6GI0lIQmWo42MfF5EUN/PX0/sDQB46IJ+pufqCegSp14RnJqNIn3lhUXfA1CGIMcLMmQhwQVwLmSQ2SjB2HtUK7yIdpL+7fVWxapoI5NVyYAO4XrOH1iIM3u2Fbp25Dp6iGapNIN3K0SEO78ddo9V1+Gud9fjsy37Fce9mgytCkVuhOPbLee25mXq2O4AIu9O0/Gyimqc/9yypgOq37y/okpTl9oZOCCyiyWfF7UAp0coJClynQhHGznImK0+87azemDNb8/BL0drzYtq9ISmGZcOtNYGgwy7Vojc2aAulESwMg44NhvpCS8Bkl5IeLHAIU6mT5GHedXwzvjNpD7c71LUeV5M3q05Nw7H81cNwVOXD8JbNw7HGT3CEUMT+1vbMI8VktgB1Jm2QTxEVNEWn8eTmYu/xwcb9uGmN9fE5HpWhTWn2j29y1mNNlI7l1vl0iEd0LfR36dBFV2kzkjLmlOq6+ox/PHPNfUFKWyThZ8pl28+ZR+tUWSeJElo1Sxd8dkUCUhxECrNu0bbFhmC5/KPn17U2tbeRk4jviOCYfyKLta0KZ7leQkQJLxY4NCxasXncHin+Xl52Rm6nSEUgiWH3ZbN0nHBwPbITEsBALx49VA8dfkgPHn5IPOG6MDa2h057Mra9tcLePn7/aIcOqaTft4jjATE04tau66JcrKKkiQJVw/vjPNPKURR2+YutEVqurbB9dm2HTnOT1KnlgfY3+mnbd6Sz4tJqHSEkBTuG/87thv+/POBwoKn2fYAZufbxXhjRhs+L041L3Lk2vzvdx08jlvmrMWmn8odXcdLrCjIvdK8OPU9chMSXiwQ0bzkNIakyhBbFXdtoz/ohyQJ95zbCwDw86EdFZ3mtK6t8G5jIjk9crPScNnQjsjOTMPExhBcXnpydZcryG3ackDpsOvM50WNSLSR3+g6NXqVc8eg2sy0FIzs3kZxLFZmIz0eu+QUvHD1qbrZUXvni0dPRU53w9n9s637VUdYbaLDym0yZ+VuzF23V3NcxOfFLFRakiRMn9QX/zOsk7CA62SjVUcOu0bCi5U2RKKNnPq8mHx/4xtf4z/fluLC55eZlPQGMQd1K5oXb7S1QRJeKNrIApEV+mOXnII73lmvsdurKWrbHJcO6YCfDWqPFaow6wghScKUEV1wZs926NK6GabP2xT97p9TR1lq3y3juqN7XnMML2pjWO76UV1xxWmdop9Znxenmhc18ZCkTm/nba+aZTawqzVRamfFfoU5uHN8D+RkpeGql1fp7gDL1ukWvLr+fedo9HzwP2LnN1Zg5uxuZ+zVal5i27HKKqrw2/e/5X6n93vYSWZ98VHdutXviGi0kZPJppcFoVSNkYOtNbNRo8+LY4fdSH18/M4RJNLfT9TUe9+QRnQ1L37b+BlIeBGkqrYexxrt8hG7b4MsG0q4I7q1wR3jewLQf2kiK6qISt6JbTc9NaS7GyibW+Thn/VXfKeYRBwlqbPn8+K304uu8OJRs8xyYKivq148j+zeBucNKAQA/Pj4ZNNIDCeTkLZ92mtZ2Yk8Mhmt0sn/EsHOupFtmR+aF7XfDotec9h28nzqIqjvu4g2TZLsbQ+QmRbCX34+KJqB2w5GXcKKmdg1zUvj2CQi9G3fX+nqOyOCiAZ/Hkejx8ONzO96M5YTM6TbeGo2Wrp0KS688EK0b98ekiTh/fffNz1nyZIlGDp0KDIzM9GtWzfMmjXLyyYKU98g45Zx3XHFsE7IyWoyGwn3EZ1nrh5brjq9CwBgRLfWttqpR//2uZh9w2lYeM8YzXfKUGn71wiHSisrEIlk8vt1MNu8sr5Bxv3/+gbvrC525XpW931RO12y2iwjweXfd4zGJUM64MWrT+V+37KZ/l5Iuk6+umeIISoQKgRhGxd16uRsB8Nm6jRn1U6+RtasbqFQadgzG/UpyMGFg/iLIFGMVuh2xhinaQuaoo3My07461JH17KDm71Vlr1zZncasu4mnmpejh8/jkGDBuGGG27AZZddZlp+586dmDx5Mm6++WbMmTMHX331FW699Va0a9dO6HwvaZ6RivvPC0cMRXYbNlN9sy+KrsOu6m06pWMu1vz2HEVkgVuM653HPc5OFGw7u7drjv7tc/HhRrGcCFY2pGPxWxOpp3mJPJuFW/bjvTV78N6aPa5cz2jVzPMrUctWovbsAR1y8dcrBmuO/+0XQ/Du18W6EXBGOH1WomOf1aG3rFIbSh1r7EwXz3y2Q6icepwQMhtJsKV5cWPaM5zkbEQbuWU2sit+H6uuQ2l5FXrktXDUDj3clLUbXNC86HWvIGlePBVeJk2ahEmTJgmXnzVrFjp37oxnnnkGANC3b1+sWbMGTz75pO/CC0vTgzWONmIfsxXvbdFwRLeo1/F5+cfNI7Bk+wFh4YU36gntKs059oePNuP3F/bnfOM+epqXSLsqTvKjXexi7vOiJLVxApKk8CA3qod4fh8eFw5qb3tl7dyJWex8xeArMBCf/tjn6Mzsvu6H5sVL1DKIWKS0JGTS+98x3bBtfyUWbzsAwJ1IrdO7tsbrX+3SbZco7jnsyor6rHLWk4txoLIa828dhSGdWzlqCw83NSWWLAI66Lo5BEh4CVS00YoVKzBhwgTFsYkTJ2LNmjWoreVPINXV1aioqFD885rIy2eWpI59Uarr+HkegtAXWPlCndfDioMWz0VSzGFXew29gc8qa3cfMV2Vs5oXrpnL5WdkuCjl+LxEBoyV08fjjV+ejgn9rOX0sUJkZdWrwBubv3h3sm42Kj58Ivo3+xi/+ekoHvt4Cyqq3BVC1Rg10/FGiDY1L3qTfl520wJp+uS+mH3D6dHPkTQMTjhvQAGev2oIFt87jtsuqzjOdm3isGvGgcpwmoxPVYksa+sbsOrHQ6iqte9M+/++3oPrXltt+3w1opqXW8Z1xy9O78T9Tm+RkjSaF6uUlpYiP185MOfn56Ourg4HDx5EYWGh5pwZM2bgD3/4Q6yaCICJmICxuo9dYZzQcebzKgW9FfRWqSkhyZIDse1dpT26Bat3Hsb//H0FMtNC+O5RfQ0gK7zUMMlDtpRUYMGmEtd9coxXLxLU66bIgJGfk4n8nEzOOe7xzcMTUF3bgJxMfX8Yu1iJ/3G6+GcXFT97/isAQLP01GhagljiRv/WOuzar+veCb004fgA8OyVg/Hioh/wp8usZcHlIUmSbvCAtaa7FG0UqU2wmkXbyjCuVztTTeOf/vMdXlm2ExcMLMTzV/F9y8y4b675RotWEPV5ibhBvLNaaw43CjAJCoHSvADalzSaGVHnpk2fPh3l5eXRf3v2uOOXYNhGpm2iPi96YW5B6Azsb2BljZSQZKl9vBdGzGHXm3sQyQNSVWu8OSSrWmc1ZCXlVbj17XX4dm+5q+0yNBtJPOfM2PWRZump0b2OvEDU7CTrfhAj0u1YIeagKslkrDBLqSCCrVBpnSK3n92T+xwuGtwB/71njCvJCI3bZcNs5FKSOlFueD2c98WsnleW7QQA/PubErtNcx2r/e2tG0/XHNP1eXGQN8htAiW8FBQUoLRU2WHKysqQmpqKNm34YXsZGRnIyclR/PMacc1LEyd01IpBEF5Y047sRHixqXnxKtyosko/dJWlntkkp7pO+5x2HjqhOeYEM19G9eAeJFWtKB1baRMlAhYcdpluY8t/pfGU/RVNAotRskg3MJqUnfogaM1GAu3xPY6Pj5VWRco63h4g4vNi4epf7jjo7KI+ceh4NZ77XMwRHADO7NlOc0zvPlGeFx1GjhyJjz76SHHs008/xbBhw5CW5r4a2z5NPi/GmpemB92uBX816/SldAO9nW1TJKuaF2hGpnr17nkcvJqbjxnk3Yjw14Xb8SzzotdwfJPcFh6sal5SArTaEUXPb8LO2GdHeImcU8n4uQRo3LWMn9q4IOBakrokuG13vLNe18dSlKTfHuDYsWPYsGEDNmzYACAcCr1hwwYUF4fzZUyfPh3XXntttPzUqVOxe/duTJs2DVu3bsVrr72GV199Fffee6+XzbRMU5ZQsb2NAGBCvwLc2ZiwjiUIgxCrWmd/jnrfJSv1RBDY2sizFeIxlYPm8eo6LNtxULFJ3rOqFQrvpXf7hTV65BI4odIB6CNW0RP4RJ81a4K0o7WICC/subUindEjnEbwqP2kRGoLarexIoy6v6u08XUSAaNMzU5JGuFlzZo1GDJkCIYMGQIAmDZtGoYMGYKHHnoIAFBSUhIVZACgqKgICxYswOLFizF48GA8+uijeO655wIVJg00CRza4VGJInInJGEax1kwCJ1BIYCpNS8W2se7EyKaF6/GjePVShPQDbO/xjWvrsLfvvhe95xYaF6MnnlYE6f8Pgh9xCq8Nsu8nTt1UJqNrF8/cgpbT63Ozs5uYRxt5Az15M3rp1ba4ydWnmeT2cip5sW62YhbT0B3MvcC3rgcpLHIU7PRuHHjDFccs2fP1hwbO3Ys1q1b52GrnNPksGv8Ioq8KEHoC6zPixOHXXDmpjqRXaVdugcNDTLmr9+LoV1aoWvb5pp07asbU9K/9/Ue3aiTWGhejPO8SJAk5T0L0oAhip7AJ9qflA679s1G7Cq/zmPhxaiVTh121X1AxCwQVG2CHS2UW5oXdoD6/QffIi8nE7ed1cPXXciDiCSF73mdHNyxKFA+L/ECazYydNgVeM6BMxupdphm+2paimSoeudGGwkMCnpCnizLlgbgf67dg/vnhje23PXE+ThWzc/rYfT+HT6ujUhx3+fF4EvOd/HosKu3Uhb9JXqmTFEip7Pd76SDXBx+o76fPMdyNUHtNZnp4nlkIq+/4+RonDwvb6zYDQC47awezupOQKSI1r0huMJLANxF449okjqYOOwK1BUE4aVeJ1QaULbPrOO6nefFqrng611HFJ/1QqSNBKJfzl6jOabeW8gphtsDgOOwG6ABQxRdn5cYOexGhRdG9Hn5y53Yd/Sk9QYI4uVTsmU2Cmi3yclMw4tXn4pZ1ww1LWs3w+7VwzsrPjdl2A3GTfnrwu2Y9v82BFbjI4Gv7QqS/x0JLzZo0ryI722kRxAmJtYtRf1r2BWP2T4pvFshtD2Azo2qU/nL7Dl8AlNeXYVlOiGM6lr0sl5aff9iG23E29vI/z5ilVSdviLssMv6vNiw9kQddlXdb/byXdYrE8TLaUh9O+2ajZ77xRC3muSIyacU4rwBBablIv3F6qSp3hZB5mhezPHuiT77+Q7MW7cXG/Yc9ewaVljz23Pw5583JSeUJP64R9sDJAhmzlti29a71Rr7NOiYjQClicMsZFeGMkMtYH9vI0A7ad3z3gZ8ueMgrnl1Fb8eVUV6wotVbZfbL6zRdjNhzYvyekHQzllFL5mV6E9xLdpIdXKGziacbuDlKlo9edsJhe3QMgs/c7hbtF9YVX6qJ17XHo3Lj9ivCLj3fjVC8bltiwwM7tQy+lkCP1iDNC9xjrDmRaCuIExM/zMsvL/FkM4tNS852z691XSETXvLseewUi3vxGyk1ryUlFvbOVhvYLB6y91+RMa7SmuPxaPPi562SFh4kdm/3TEbAd4IL/PW/YTrX1+tSYqYneGeS6HGYdeG/05cblYZMRtZfAlTVSuEyG+P1XC7cc9RbCut5H7H9meBfTNdp0PLLAzvpk36ynYxvX2xgpRhlxx2bRCZfGTYU2mzBMEkcP2orjilYy76t8/BnJW7Fd+xwkuaScflZXV0U/Pi1srWqsBo97qvXjcMN76h9aEx21VaPeHGY5I6vcnGjtnIVoJdjsMuAGSkOt90UM20/7cRANBM5Yjq5kSpXgWrNZwiCGW7DhqNTbY6Tmo0L43/59Wi937/dOQkHpi/CTef2U34ukeO1+DHg8dw2cwVAICdMyZrFivKxxD7d1t/PJMUf/Hu+eldW3vTKBuQ8GKD6COVTbSIAv0yAIoXhEISTmvslFqH3aa/7QhadneVBrSaF7Oa2IlRvaeSIorKtEWqdthU7ertEWS8q7SkeQYD2ufaur6fONa8KP52J1QaADLStEvdhgYZPx48hu7tWjhy6FRrXszeF5F3I1qX2mxksl+X0+sFjYgvmKggq9YQNGXY1T4TWW48rqo8sj3AF1vLhNs55NGFis819Q0agZl9Dn6sXfW6gbotbJ/74v/GYun2A7hqeBcPW2YNMhvZoGlvI5ONGQWmySDZEAHtYK9w2LWh4xRz2BU712zgUmyEqVKrs1VZveV2B309oczomaujjR6/5BSM6aXdeyTo6PUVO73dzu2PCL7qU3lmo99/uBnnPL3UMHmhCOq6zYQXtXBuhLouO5oXv8yP5/bLBwBcPrSjo3qstF/b/yJJ6rSYda/SCn1ztdlYwvNNYscTPzTvenOWQrCTgGYZTUJXt3YtcP0ZRUj30GfMKsFpSRwRDZWWjU0K8ZLnhcXI58XOiyamqubXW69qjLmDdNPfJ1QJ6pSrHWu/o9am8KJ3FcPVvaQceC8Z0sHWtf1GX/MiajZiw/et3//zn1vWWI/yOM9s9FajqfTphdstX4dF7RNm9lutOGuq+6xIqHSEF646Fe1zMzFrinloshc8e+VgvHztMDx68QDrJzM/28p7q+5/0X7AqcKKWfjvS3/E/8xaIZzwkBc0UK/QAvshvPCPK3xeIOFvvxiCjq2y8OyVg2PSLquQ2cgGkYfcYJakTqSuAPi8sBhFG9lZuanNNzw27yvnDiBqc42VOex4jXLQuPBvy6J/WxVeRLY44KF3HeNdpSXFwBuEjTvtoCe8iHYhhcBiQ3ZsWvEqT/Zy5Zim1ryY9DNLZiPVjRte1BrFh0+gZbM0HD3BT8YY4fyBhTh/YKHwtdymWXpqVPtiGeYWWXlv9X1eOGYjWBNgVu86jDW7j2AEx+lVDc+8V8+Ma36sXfV+K3t/JQno3z4Xy+4/O1bNskycDo0+EzUbmWwPECc+LyxGSerseJqLqMZLyqvw/oa9muMas5FpTU3tU5+7bX+T57/Ve27X50XvOkYCqzq/QtDMigDw4OS+pmW4extB/N43OJNduPUA3mo601OsmY2sCS/Kz7+7sB9+M6kPPrp9tHAd8Y6VtZNaSI3M17w67DiEb95XIVSOZzZix0Q/NO+3jOtuWiZ4o44WEl5soDAbGW3MKLS3UbC6ifpFZgdgs1Bpkfr0mLuWI7yozUYmlSlvpZE5z6rmxWXhRTL+jhUSgxCNpubmMebRF/paOrHfw95zJ1Fm2lPddVplNYvpqcrfZva6WPF5UY8TOZlpmDq2Ozq1biZcR7zjyGwUzbCrLWvHIbz40PFwfSblzMxGfnDj6CLTMkHJRGwECS82YJ+rU81L0OYmTZiuQnixYTZy8KKaCQ0na+rxt893cPMpGG+YaQ27Iab6ZiPJwKFVUmxHEA+DCA+n0UYNCp8X++1QCz5uzxvsylr9TM0mWydmI5YPbjtDuJ54gx2PrLwKVpLU2ekTogKImcOuH7l39MYUlb9u4CHhxQbsgzV02BWoK2iaF3WuCjaCwo7ZyMmraRZt9Mxn2/HUwu2Y+MxSAOrnol+vVQWSlRUyi752RdKYGNhzzPLpxAN6wplof1doXhz0IrV84PZUwa6s1b/ZzORnxRxpdN8GdWqJv14xSLiueMWKFlKtJTbyebGDaLAXbwNNu2boWBKwaYkLCS82YCVXw9WTQA8ImvByzYguOKNHGzx8YT8AQGZakzBj1Wy0dPsB4ZUF7zb8/oPN2PRTefSzui71viBsHUYTntV7bnewMda86K9+gmgqsorebxf9ZYrVqYNEkOp+4PZCt8pgd2ezbmZl1W3WJy4e3AEL7jwTr19/mnCd8QArbFhy2NXkedG/1w0Wd68HxAIRAL7DrlKrGBxBRuGwGwe6F4o2sgH7WI2EF6PH37JZGn41plug4uaBcGTA2zc17XvBCi9W5axrX1uNDi2zbLdl9a7DuPD5Zdj1xPkAtKtmo9feaMKLlc+L3nwT1q7oPXd9k1I8ofcTYrU9QNPJ6o/uThbszuWaHEkxMhsB4T7dr30Ouuc1R2FuJoraNheuO16w8t42T+dPbTxhwU73EhU6uJoXhdnI+rW9ImDraFNIeLGBYoVvU/Ey7dxeuHZkV9fa5BWZTEZSvzN0au61kR3b0JHaGnbzvOhdKSRJaN8yC2WV1dozEljzEs6LJHY+61PgpNdpuozbmhfGbKRejbsZbSSqdchITcGy+88OnC+dFUJS06TOvseiv+mcvnkY3bOt4ljkufNuuZ0u4cTnpcElZ3RPiYP+E/9LPB9gVWpGnTgeVG9mZDJJveyYT9xUi6pfdHXd7P029Hmx+Fjs53nhH08JSXjuyiEY0a21ZpdfCUBaPM88jeitkkX7g3Knc/vtUF/PS58X9VhgJnBYcQS3ItCmhKS4dfQGjM2tIrxy3WmashEhiCcsONn40wxetFFgNS/s3kZx0H1IeLED82ANzUZx0AHMYHOS2HFcdVV4MfksrhHz2+cF6NymGd791UiMVaX+D2tegv9aRhyOe+W34H6vd4vVr4teuT9/so05x71QabdXumymW7UTp9ljdLK3USKjlwfJikyvvl1NmheO8CJebRTRZ2cWbeS+OO0OQVUIsQR/lAwg7Etk5Lhl9K4N7dLKvQbFCDtmI1dfApOJSBFtZOiwa+2ybud5YYUndRkJUqC2ndfjwzvOwKVDOuCVa/kOovr3WHkvRcLvHYVK2z9VCDbFv8Zs5KLmJQ7kWdcI6SxCrCw61CUNzUaydaH2w437sOfwCdM2cTPsxljz0r99jlA5STmABh7yebGBItrI4t5Gqx4Yj9LyKvSPw52CrezFEsHo5cxKS8FJjlpVzfb9lcjOTLXmsGukebFoznN7Y0bjXaX920DPCn0KcvD0FYN1v9f77fyMt2b310motLfRRrWMukVjNnI1w27w+4Rb6Al9VgQ4tVARudNcLZ7NPnHfv74xLcNbRCnMRgGyG8VbDyPhxQbsQ7ba+fJzMpGfk+lug2KEPc2Lcw3IhL+G87g0V+WgUVctGsJuVQNf6yRWl4N6DxEWCYkxUfFWpDKsO7UCDh3FTaKNDh7TOk1bgd3dWT0xurm3UdBSKniJYuNC5mdbuQdazUvE50Vb1m4E2qHjyr7DG+t4j1jhz2Xryt7jdlSeFySRMtI9xDPsJtaAY2cSN3oFLOdW0ExE+hhNDJY3ZrTr86IzMbMTtloLJEkSurSJ/zBXPZlE4/MiUNcRk40HjTDL83LPexts1w0oNS9qwcysn+0o02aG1iMRBFpR9IYZS8KL2ucFwPvr9+LjTSWasna1cWrfNDNBJQLrQxekPC/syxikZulBmhcbiEYbJRp2VsBGL6ciHbXAwKQ2MWl8XpgqjJyLrWfYtWs24h83+6nn9M3DvRN6oX+H+DMtRuCZ5iTwnpl3k3JVbT3HYVf5eeWPhxxdQyG8CDojR3hw/rfC10kmh11FmLzC50W8Dk2/koG7dQRVOVLe4lieElJp4QVzyLiVgFEU0fvWtnkGurVtDkhAblaat41yARJebKDUvCR2tBGLrVBpmw7NIhiNNcbJA2Pj86J3HaMVZEgKh7nefnZPW9cMCnqCm1GEmNv0+d0nmHHpKYbXd0ptXVON6oWMm9oSM/+ZREJvTBXRvAzp3NJSnWbfGaHVvPCEF+0xtp8EafEbCklYOG1s9O+gQ2YjhxhPzsHvACKc0zcfAHDDGV0tn+um2Uhbd1Pt20orFcKMkbbE6mXd1rwY+rwkRpfRHfy0uXm85d/f7FN8dj1U2oHZiOCjp2Uyu5sPTu6Lhy/sz/3O6KnbNhvphGObHWPzRtnNIeUVKSEpbkyUpHmxAftuGW3QlShj10tThuLg8WocrKyxfK7dDMRW645szhjByE/Fep4XuxszmkdNnF7UWnmOrSsFixXTz8b89Xu532lNK97+YvUCwnXNCxttpBZe4mQS8JO/TxmKTzfvx8ofD2Hv0ZMAGu8bZ8Fg1lVuHtNN9zvDXaVt9gr1JM/TvPDWPawG2+bQQoA0L7ZgB0RDs1EsGhMDQiEJedmZtnJNGN0f5UZg1jEakIx8Xqxey67mRZLCe1hpr9/UgsLcLKyYfrbinHinMDeLG+4tQ6uNivn8bvIo6+obsHhbGSqqxJyEaw2jjSy3LumY2L8AT/3PIMXYonfbnGiyDQUU2w67kmnwBu+6bD+J1ZYrgzu1BAB0bxf/wQARSHixQbL6vNhxGvRSuDN67Y0EjlglqQtJEj5rtCGzqG9jYW4W811idBo9h78R3dooPnuuedH4bRo/y1lLfsD1r3+Na15ZJVR/bb3+RERmI3HYZG66O5Ib3M5uJpOysebFHqkCPi9czUtD7IWXl6YMxR1n98BbNw6PyfViAQkvNmDfITcdQ4OOnYmmipNh0kl9LEb+C26GStvWvABo2yLD8jmJQG5WOvd4WkoIf71iUPRzrDUvZv4N/1r7EwDgm5/KhepjtwfQJOAjs5EwrAbLjs/F3KmjDL8383mx4wulfr4yZ6jjOuyywksMHHYvGdIReTmZ+L8JvdG+ZZb5CXECCS82YCe/pNK8uDwYezm2G2UDjpV2w9bKO0H6TCuOuSyC8r54rXkx9nlRv767Dp2wVL+x2cjZb+tXKJbWPRFghUD2ronO7a2a84VlkYrs+ryoTaN2QqV/OmKtv1ll0oACXD+qq6fX8AsSXmygdNgNTqib17gtbCjzvFg/3zhU2kjjY/1adpB03i6jy/cpyPakLV7zv43OkteM6AwAaNlMfzIxirZyG3X1Xm4P8OWOg4rvnO5HlEz7GbEbGHrRJ8w0L3YWNCFJMvV/jByrrqvHwi37UVVbr9DkshuQesGoHm3jJnrIKjF5PV588UUUFRUhMzMTQ4cOxZdffqlbdvHixZAac12w/7777rtYNFUIZRp6/XKtDAbweMRtG75Ts5rRislNnxe76F1GbSsHgA9uOwO/u6AfLhrUwdtGecT95/XBv+8YjT/8bAAAvqNyBLYfef0srPq8WMVLDV+imZ2NYN9XLzSjXvi8pISU/YnvsBtmyiurcfOba/DWit0x3c8okZMbei68vPfee7j77rvx4IMPYv369TjzzDMxadIkFBcXG563bds2lJSURP/17BnMpF1GttK8HGv+DkEnSGaj373/Lcoq9felMUqoF6tJgSfs9cxrgVzOxD6oU0vcOLoobv0kQiEJAzrkRvsIz2E38q7E8ieqL3WwsgZ/X/KD4z2NItQYrF4SeeLwEk80L0ZmI5vqOPUihFfPzMU/4EBlNVbvOgwAWPBtiW0fOjvE6XAihOd5Xp5++mnceOONuOmmmwAAzzzzDP773/9i5syZmDFjhu55eXl5aNmypdfNs00km3TE4er6UV2Rn5OJ/u1zcO1rqwEgbjdg1MN1zYuD+t5audvwe0PNS4zU8bz79ZtJfWJzcZ/JTEvBQxf0w/HqOjy1cLviO/a5769wR4gQ5a+fhduycMt+/OuWUY71MLV1BsJLIs8cHuLFXfMkSZ3q+VZU1XHL/eGjzdG/B3Vs6XqiRCPidTEkgqfDeE1NDdauXYsJEyYojk+YMAHLly83PHfIkCEoLCzE+PHjsWjRIt1y1dXVqKioUPyLBZEuEZkjM9NScMu47ihq2xSyl5edWJoXtyd9hc+Lu1Wb+LzE5oXmXSaZFuO/HF2EO8ZrNaaxHE8XbTvAPb5m9xFX6q810Lw4fdaR3BzJwJQRXaJ/6y2SnMz5hmYjm/WmhpQ+L+c8vYRbrowR0FNCkuFmvm6TyOH6ngovBw8eRH19PfLz8xXH8/PzUVpayj2nsLAQL730EubOnYt58+ahd+/eGD9+PJYuXcotP2PGDOTm5kb/derUyfXfwSMyAUbsl5EBmbX1x8PmVlaIpxfB2OfFR+ElifwY9IinfmSGkc+LU7PRqV1aYs6Nw7H012c5qiceeOjCftG/9W6bkx2YDTUvNvVvoloNNmFmakiK6U7SKQns9B2T7QE04YqyrLv67d27N3r37h39PHLkSOzZswdPPvkkxowZoyk/ffp0TJs2Lfq5oqIiJgJMpPURm3dkQM7OTMPie8chPTWUMAnHIrhtw/dyEjPcHsCzq6qvo71SgnUJWySSOcXQ58Xh75QgYXTPto7qiBfSFLNs031j32JHwouhz4u9OkXHQ0VEqsR37PWKRFooqPFULmvbti1SUlI0WpaysjKNNsaIESNGYMeOHdzvMjIykJOTo/gXCyJ9YuGW/ZrvurZtnlDJgCK47/PianUKRKKNvLY9R67z8Z2jo8cSTaC1RZzdgj2HT2CtjpnJqA8ZaWVESNauIrojuVvYjjYS3P+BHYvsJsSzCwkvNklPT8fQoUOxcOFCxfGFCxdi1CjjjIgs69evR2FhodvNc4R6VX3khPVNC+MNKz4vY3q1M69Pke/D3ZfMcG+jiMnP4zEk8vvaM+n/YzlwBY3IL6/WybqcnZGK07q2il2DBDnzz4tw2czl+L6sUvOdUR8qq6zysFWJi95Q4J3Pi2xLnk6RJCFBnI18lGU5pmajRBZePDcbTZs2DVOmTMGwYcMwcuRIvPTSSyguLsbUqVMBhM0+e/fuxZtvvgkgHI3UtWtX9O/fHzU1NZgzZw7mzp2LuXPnet1Ua6j6xNETYhu5xTNWXoR0gVWJX5qXyHW9HkQi10ng8cMWJ2v5URmA+0nkzLAiTG7eV4EeecokgkZ9qMxhJFWyaun6t8/F/ooyAMph1pnPi/65Zz/Fd7Q1Q9QsyC6kwpoXW5ezBfm8OOCKK67AoUOH8Mgjj6CkpAQDBgzAggUL0KVL2Lu8pKREkfOlpqYG9957L/bu3YusrCz0798fH3/8MSZPnux1Uy2h7reHjsc25NMPrNjwRcp6OTQbDRChqObFa+ElfB1WS5esExLQ9LxP1NTrFog3vZSR5qVHfgts26/V1hB8Ftx5Jv6xejfuGt8Lpz32GQA3fV4cNo4Db+d0HqzPS4McW5+XRB5vYuKwe+utt+LWW2/lfjd79mzF5/vuuw/33XdfDFrlDLXZ6NCxxDcbZaSKi/G8LLJqvHyxjLJYNvm8eHZ5hXDLbhOQzGajCCf1hBfE9v58tHGfpYmE1zSj9l4/qis+/qbERsuSk37tc/DHi0/R/d6R2cj+qbqIRhuxvk8yYms2SuREiQmsVPIWdZ/QXU0mEJIk4dZx3YXKpgqYjRQTvN1G6WC0W2tlVR2+3Vvu6SDCCmaJO3zYo3u7FrrfxXJVesc76y2Vn79+L8b+ZRG27Avnktp39KRmPyOWzNQUR+0jlHjl82K/TjFfmb1HT0b/bmiQ8dHGfe43RodE3h8rgX+at6g77ZOXD/KlHbGGt2fNb8/vqzmmNhtdNLi9poyXzmRGg9V/vi3FBX9bhu/Ljnl2fYVg5qFjcjwyrjffmVtCsM1GS7YfwO5DJ/B0Y8bgK19aaVieHrVzWM2WVz4vtuu0UeXcdXvxXWnsTImJ7LBLwotN2EmoY6ssjOzexsfWxI6WWdrNJi89tSPSVSYltT34qcsHoV0MMw6LDHTFh73bjp40L1oiT0SSJHRu3UynUJDFlzBtmqfjxwPHPO0/APUbNUHzebFT5bFqfWd1LyDhhdDAdgkrviDxTg4na3BqioSV08erjinvSUiSNP4BXmoh6gXsD3ohu27A/jJ2ACGflzA8Z0dJkgKteYnQrV1z3PjGGtNyCTxvxAx2jHBiUvTivYul74pdEikhpJrkmXVd5mRtk49LWiLHo6no316bBDA1JGm2QlBPTpGNLBXHVN+7ichAV1XnnZ+SMoeNZ5eJC9Ia/Z/GMNli9Zwd42FCCEkSdh48blrO6VYQyd5v1ATP5yX4zyjo7XNCTKKNEhE2j4jaZJLIdGrdDO/+agTeWV2MDzaEHc9SQtphWi3x81bVXjqTiay0qrzUvOgMGsno8/LlfWdjw56jmNCvKas2T3bhCbhBRFTASsJH7Trse+xEe+JZdt6A91eKNiIMSSbNCwCM6NYGvfKbknWlhkKagZrVvOil47e6Mh3RrbVwWRGzUVUtaV5iQUFuJs4bUKDQtqToSK6xjDayi2gTk/25u03QfF7iQUtIZiPCkDTBPS4SCVY4CElajQLr86KXjt/K4N66eTr+8nPxiC6RSbDaQ+GF/W2J7DRnF568LyE+fIKENS/kcuuY5hlNxgFHPi8BiTaKNYms6SWzkQskm+YFUJrNeC+IkObF4otlpbjIJFhdFxuH3cQdPuwTzwJdPExa8c7vLuiH7aWVGMVEcQZN8yJDDrx2LZE1LyS8uEAyRRtFMBMO2HsSEVLUpyjfK5GkduIvolGSugiemo1CrNkocQcQu+g9y3hQxYti5bH3b5+DzY3J7wjgxtFFmmNBy7AbDybOBJZdyGzkBsmoeTHzKclMa8ouGnl/1GdYfa+sTAZC0UY2HHbPZCJm1LRt0ZQDh52c2QEkHswisYC3IpQkKS60GkZbT7CIdte87Ax8fOeZ9huUJDgSbD3oWPHQV+NZw2lG8s26HpCUwouZ5iVNmxr9pErTYfXFslJeZKA7YVHz8vK1wzD7htO53904ugj/N6F39LMyDDxxBxC76Kmz42A+cN1hN5EnGDfRe6eHdG5peq43/Sr4vTWR+xaZjVxAZB+fRMNMNshihJfa+rCGQ62tserDYknzIrA6NtogkEebFum6k27HVllCAgsJMmF4IZzx4rAr3kSxZ51MqRacoH6lt/9xEpZsP4DTi8yjED2JNmoIvlM2+bwQxgR/vHUdM7MR6/OiV1S554/5Na0MFCKaF6s+L0armJSQpPgNJKMYo5fjJx5eJbfzvCRjtKItVLc9PTWEc5ncQcanemA2ioPemsCyC5mN3CCRnAxFMfvNYsKINay8iCJuCSdqrO0zYpTwKSRJCuEqkQcNN+DlecnLyYwLPwJhs5FguXTafVoIJ8KCVxl2g45eJutEgIQXF5jYv8DvJsQcM7OMyIttyWwEiz4vAtLLCYtmI6OMwCkhSTFbJbKt2Q1YZcPbNw3HyG5t8MJVQwJnNqrhhNOLtlHURJiuo3kJukki1jjL8+I+8RFtlLh9iMxGDsnNSsN5A5JPeDFz2BV5r62+WNaijQR8Xtw0G0kS5XaxAHsvz+jRFmf0CEdxBWk+2Hf0JCY+s1RzXFS+Eu0DyejwbwfK82Id2h6A0OWaEZ2T0gnTbNUhsjpV+IgIXNPKfRYzG1kTXoyc39TqWb22Bk2z4Bd69zJIJti/L/kBlVVa06Lbvg56DrtJOKwY4izPiyfSS+BJ5D5EwotD0lOS014tYpZR7yz9n7vOxNSx3aOfrarFrfm8mLfveLU1nxej66u3SPBy08lE4OYx3QBAsVkjECw/goPHa7jHRc0F4g674c7yzBWDMbRLK7GTCGsk6d5GiezzQmYjh6SlJm7nMMIs2kiSJGSkhlDHaDf6Fuagb2EOZi35obGMtWta07x44PNiEm0k1zNbJugIZsmopeNxWtfWWPvbc9CqWbrieJDmg6Mn+MKLuNlI7FlHhJeLh3TARYPbo2j6ArELEMJ40a0C1FV1SeTRhtaHDmmRkZzyn9nqc0K/fG6iOhbrSerEyzZ4sG2RodlIUoZKJ/CCxzXatMjQrAyDZFbTE9BFTBCSJC6c87bSINxFluXoO3nhoPYu1Rk84eCec3qhQ8us6OdE7k4kvNjk7nN64tTOLXH50E5+N8UXjDQbN59ZhMy0FNMESVbyosiy+xl2rWIlz0sie/l7idlT++L/xsakHQA/0ggQ07xYef6U50WMu8b3tH0uO36M7dXOlfYE0Wx01zk9sYDZaiKRI9ZIeLHJ3ef0wrxbz0BWenL6vEzsH/ZVyM/J0C2TbhJF4eVr5YnwYqZ5YX+RTtHszOTU1Ili9ti6tWuBLm2aed6OPgXZuntfiWiHQhY0L5RhV4y7z+mJe87pZevcBrlJMHYruEtG8E1HibyGopGUsMXE/gX419SR6JmXrVvGbNsEb/c2slS1EMZJ6tSflQceu2QAig+dwJBOLd1vWAIhYpJRyw7ZGamotOh8LUJVHd8nSiyHkSRsAurQ0nthLBGQJAm98lvYOleGHF3QuKYVDajkwr5DCSy7kOaFsIckSRjWtTVym6XplrFiNjKDtVmL4I3ZSP87zfYAqu+vHt4F0yf3JZ8GE+wIndMn93W/IQCqdTQvIm1U5SwEANxxdg9NucGdWuJXjZFXhHfIcpPQ6dZ+Pw2yHHzhIPANtA8JL4RnpOnEC18xrBMKczNx8ZAO0WMitllL0UYuqV7O6t1kHzc0G6m+I58Xe4jInGrtjBfO0bIMVOtpXkQcdiFphHOeo+gzVwxOWtNzLGFNfW4lbgugywsApRky0yRoIp4hsxHhGXpmoz/9fCAaGmQs3XFAuC5Jklzf20gEVggxzbBrcaNJgod1s5EXgqIM2cDnxfz8sOZFLdDyyum3nbqQFruPmh0P3NJ++rUx468n9sZ3pZX4aOM+7vfN0lPx7JWDIctATqa+ZjzeIeGF8IxUA8+4UEiy9OrLsux6nhcR2EsardhSQqrtAUh6sYWI0Kl+tF7calnW33VcyGGXK2lrj1E3iQ3sdibumY1cqcYygzq2xG1n9dAVXgDgosEddL9LFMhsRLhOZJxI8zHZiVkSPSOUg5tY1lzK8+IOdoROLzQv9Q0y6nT60BsrdpueL0ErmPCamcgZUIMEa0Z2LdpI9meRQgJvGBJeCM8wizZicfuFdKJ4SdEx/xhNkmbRRoQYdp6bF1sx6GldRAmFtF5cvB5BsktsOHisOvq3e++mP6oX6jJhSHghPMPN3XIjw0RulpgN14nZiNW8SDrHeeew0xXJLvYQMcmoy3ghKFrdcVzN/wzrpJlleKt0EnKtMapHW0gSMKBDjqXzPttaFv07KGaj28/SRp+JQCbpMDERXl588UUUFRUhMzMTQ4cOxZdffmlYfsmSJRg6dCgyMzPRrVs3zJo1KxbNJFxGvTGjBhsv/52CWTbr3RJeRDUv6lBpGmBsYfTULhrMT+vuxb12Iry0bJaGeyf01jjscr1gqJtYIiczDVsfOQ8f3jbadh3uRRs5k14KcjNtnUd9Joznwst7772Hu+++Gw8++CDWr1+PM888E5MmTUJxcTG3/M6dOzF58mSceeaZWL9+PR544AHceeedmDt3rtdNJVzGyGEXsOit31hU9L11Mq4cYxKesROQkSyWqnbYtX/5pObMnm25x8/o0QZ//vlAAFoBxwvTi16kkQjj++QjPTUk5vNiFG1EnYhLZlqKI18ht/yMZDh7z+0OUdQtwnguvDz99NO48cYbcdNNN6Fv37545pln0KlTJ8ycOZNbftasWejcuTOeeeYZ9O3bFzfddBN++ctf4sknn/S6qYTLmGpebCBapRdJ6ozUzZlpKeSw6wIzLhmI+8/rozl+WtfWyEgN56y44Yyuiu+CYHqZc+Pw6N+R5mh9XiR88/AEXD28c/RYENqeqPx9ylDu8aCYjexCWt0wngovNTU1WLt2LSZMmKA4PmHCBCxfvpx7zooVKzTlJ06ciDVr1qC2tlZTvrq6GhUVFYp/RDAw07ywmL6PkQlB8MV1Em2kuKygKSgzNQWKyCQaYGyR2ywNt4zrrjnO6rVuGt0N0yc1CThBEBQz0pidoSP/V/UBSQqbPQoZc0EQ2p6oTOxfgHsnaPdCcuueO94B3eb51GfCeCq8HDx4EPX19cjPz1ccz8/PR2lpKfec0tJSbvm6ujocPHhQU37GjBnIzc2N/uvUKTl3eQ4iZqHSlt7diNlI8MV1S/HSvV14L5XMNONXRf09yS7eEQpJGNixZfRzEFai7Go+IrjqtSqk8Knyv+2JDO/+pngRnhZD9LqM2Ua4iUZMktSpO5BZwjFeed5xAJg+fTqmTZsW/VxRUUECTEAwC5W2I2CIDvVumY2aZaRg08MTTCOnMlRmI5qU3MXodgZBy8VuhaHXnMhxZdZmL1tF8LDjsJuRGkJIkhSO3A2y7JMDivKiQzq3hCwDD/+svx+N8Q1PRbW2bdsiJSVFo2UpKyvTaFciFBQUcMunpqaiTZs2mvIZGRnIyclR/CP84eYzi9C6eXp0o7m+hR48C8GBx7UMu5CQnZlmukdIRmpIMaTQpOSMbm2bKz5r/Ec88C9645en4/FLTrF1Li9CTeuwGz6QohBejBpPncgpvNtrR9Y9rWtrzXmOrUY2z1O3Y2jnVnj/tjMwOMl2rPdUeElPT8fQoUOxcOFCxfGFCxdi1KhR3HNGjhypKf/pp59i2LBhSEtL3H0aEoEHz++HNQ+eg7ycsE1/yoguuOecXph7C/9Zs5htzChHy4lRbz9YRIHoQJeRGlLubUQTjyP+OXUkXrjq1OhnjSDA/O2W5qVFRgr6tbcncCu1jBGzET9UOsQxMRHewHsP7Tjs8h6TXxszuhXqHe94biSbNm0aXnnlFbz22mvYunUr7rnnHhQXF2Pq1KkAwmafa6+9Nlp+6tSp2L17N6ZNm4atW7fitddew6uvvop7773X66YSLsAOzKkpIdx1Tk8M7dLKtfrFfV7c0rwIlpOUw2Scm9V9p02LDJw/sDD6Wev8ys/F4wz7Iic3N5BOqDQ7d9I85C28cHU791y9dxngXLtr93R21+hkxnOflyuuuAKHDh3CI488gpKSEgwYMAALFixAly5dAAAlJSWKnC9FRUVYsGAB7rnnHrzwwgto3749nnvuOVx22WVeN5WIMXbeXdHpxYuNGa2UpRW1t3hxryXJfl1sWoCmaCNV/Y3f8Jx7ebTLzrDVFqIJ9d0NSZItEZX3nHxSvJDw0khMHHZvvfVW3HrrrdzvZs+erTk2duxYrFu3zuNWEX7Dakc+3lQiVFZU4+taqLTNtTg57HqLF2YjCfY1IVyfF3X9gg67L00Zil2HjruqsUxWeOZGO8+Y28d8kl6SLapIj5gILwThFrEOlbareWkpuAcTIYZhtJFLY7kTgTOVjTaK+LyoTV2N/1cKL9prTuhfYLsdhBL14iMkSbYcvHnygnOzkb3zSfMShu4CERdEJoJYm42swLatdfP0mF8/kdE4v1o0G7XIMF+nuaV5iWx7oalKipRlDpGCzlP4WzTYMxtpUnjYb5YjSPMShu4C4RuWctRFhBFRs5FrPi9WVC9Nf7Yh4cVjtD4mevxmUh9ccZp57idJsm8m5G2FoefzwgpbZF70Fl6WY1tmI87zlWXZl5jCNNK8ACDhhfARb5PUWa/byfXUZVu3IOHFTYw2OTTbaK8wN1NIEydBsq0JYdugdykpqnkhgSVW8Bx27fhI8c5xOsbYPT2ieblxdBFaNkvDzY15tZIN8nkh4grRlapbodJmtTx5+SCc1jXsWMm2jTQv7qJxfmX+NpMFJEkSEpTtrsoBtdmosT69PC+kbYkZ6lsdkuzp1njJwr0wGxXkZKK0osqwTFpjY353QT88OLmva7tkxxukeSHiAutJ6lwSXkxmvZ8P7YgubZprjrfLzuSUJuxiNN+bCbQSxIVZu2Yjdv6IWjg12qJGs1GSTjZ+oBYU3Y02ck986ZnXAg9f2A/zbxNI6Mk6fCdxXyLNC+Ej1l9+0YHHj+3qG5iLdmyVFfsGJDCDOynDhhUDuEmnCEmSUE9zonkR0aZESlCG1Nihdpy369fEExIaZGc+S6zs0yIzFdefUWS7rmSEhBciroi1yt3K4qqssknd264FJRhzgyW/HoedB4/j9KLWiuNsLzATBkKS2HO0l76s8VzmxBYZKZpj7OckXizHnE6tmyk+S5LWr6lN83SM7dUO89bv1a2H18dkFw1HZEq0DpmNCN+w5bAb43fcSsg1q+1JZnWum3Rp0xzjeudpjit38DauQ5IkMYddJz4vzIktMsI5frQ+L2Q2ijWdVBrQEOcZS1LYd80IXi4hNzdm7JWf7ayyJISEFyJw/OL0zn43IQpvfJo6tjsA4Lfn91Ucv2RIB0w+pcB0ICScwwoGZgJHSBIzUNrNARK+RtN52Zmp/HZFoo1olR0z1GajlJA2XwsEosy8iDYCgPdvOwM3nNEV0yf3cV5ZkkFmIyJwGC1MY50Xg7e6uv+83rh2ZBe0b6lc1WWmpeDFq4fGqGXJjZUkdeFoI29DpdnzosKLTpnRPduia5tm6FNgbwdrQhxJkvDIRf3x0AebAQAZqSma8SWscTP3m1KXkGXZsL/k52Rgf0W1Yb2DO7XE4E4tDcsQfEh4IXxDbzrhboJmLUedKSkhyXZEkiRJGsGF8A9zh11BnxfJev+6YGAhurdroZj8sjPDZiN1PpfIp8y0FHzxf+Mou26MaJ/b9K5mpoVseTbZyc1jdh230jkkKyS8EIHDOCzWnWukCgovbjrlEd5gNq+ERH1eYF2z9/xVp2qO5UTNRirhhUJcfYH1V8lMS+Fu1qhmbK92WLL9QFMdXLORcZ8y60q0AHIG+bwQvqH37vO3n4/sF+POoJ8muD8ILY6CidJh18xs5J3mhUfvAr7zJYkr/sCOJ1zhhfNgHr1ogKoObRmzPqX3vIvaNsedZ/fAebQBpyNI80IEjlhoXihFe3zDCrFmzzKseRGs1UG3WHb/WThZU482TJh8akhCXePFyUzkD0rhRWs2Un8e1b0N2rTQOvqqMetSekL12X3yMG1Cb5OztUzsn4+LB3ewfF6iQsIL4Rt6JhkjHwa3ZA7eRno8yC4dTJQOu+ZlRcx/TjZmBICOrZppjqWwwgvpXnyBFTwyU801Lw2yrM3Ma8NspIedXpAakvD3KcNsXS9RIbMREThMjACuXENU80KyS/AxEwpCklisdNjnxZ02RWCFZNK8+AN73zPTUgz3yQLCIdDqvC5cK7OZ2Ujnedvxd6JhSAsJL0TgMHpR3ZoAhH1e3Lkc4TLWktSJrZK9CMMn86T/sHl1MtJC5loVWZuLh++HZ4xed7LTzUgDrIWEF8I39N5Ho/dU773vU5CNj24fjbP7aLOx8kjlbRNrsS2Ef7DaFrOVrOjeRoAHmhdGSCbNiz8ozEYch101MrRmI71oIzuP1I75kIYhLSS8EL6h3q/GiKadevkvviRJOKVjrvCeQkYr4jvO7tF0XRo2AoklnxeIZUOV4P4eMwqzEfm8+AI7ZmSkchx2NT4vWoE4JaQNRTOPNuI/bzvKOFpEaSHhhfCN/JxMTYp9wFhg0HvvI2pV0bnHyGF32rm9xCohfIN9eiICh1CGXYl8XhIRjeZFNevxHHbVcEOlTRY2uj4v1BFcgYQXwlfycjItlRfxbxAhlbfTWrSOpkpoxRNMFD4vAuXFd5V2d2JJSWE1L4QfsIJHZqrWYffp/xms+MzT0vFMk/X1JsKL3nEbHaEDJbTTQMIL4Su895g30UQO6TvBRb4QGxmEfV6EShG+YirQSuKh0q5rXlifFxJf/ECT54X5/OyVg3FaV5X5unEAmnvLKG4dEUoqqlB+slb3ukYmbqvMvuE0y+ckOiS8EL5i9T0WyaYqgmieF1K9BBVrviQNDVZrdYeUEGle/EaTYZf5ListRVM+onlhhwh19FG3ds0hy/Z2lrbq8/LMFYPRM5+ftTmZIeGF8BXexGPkn2D23ouOC0ZmI0VbBOsjYouVUGlAPEmd2xIG+bz4j9LnRRkqzUuZ0BD1n1M+O/bxiQQGuOXzQv2GDwkvhK/4pnmhUOmEwexJyrIsFm0kaX1enKZpYfsZmY38QeHzogqV5o0Dkb7CfpMSUobbiwgguj4vpmeqylO/4ULCC+ErXJ8Xi+WV34u96MIZdkn3EkhYodKt6A0JWuFXVEOnR4rD8wnnsM62GalKMxHv+fIiF9XjhZPkg1Yz7JLowofeLMJXePNOrYEXv67Drs73euOE+N5GQsUIHzGPQJNs7yrtNEOusG8V4Rlqh12l2Ujs+ai1HyLysr7DrtAlo1BoNR8SXgif0b6YdfUc78qoKtfEbKT6rPfipwpuD0AEHxFtm1CeF0iaCcep8ELbA/hPikJ4UZqNDH1e2J3LJUljRjJD32xkrU9QF+JDIzjhK3zNi35oiMgqm0VvkBHWvAiVInxF4FFmcqJKNNVwNC+OfV5o5vEddkgIZ9htwtDnhfkqJDVlBM/JTBXzedF12DU9VVUP9SEeJLwQvsJ7LY0S1+m9xnpChq7wIroxI0kvgUdkbH+Ak8lZUw+nLraf3H9eH4stIw1fENDubdT0Od1A88ISCkn4888H4Y6ze+DD20cLOuzyy1C0kTt4+mYdOXIEU6ZMQW5uLnJzczFlyhQcPXrU8Jzrr78+7PXP/BsxYoSXzSR8hB1IfjOpDzY8dC53QJHBCQHg1qf8rGs2IofdOKfpuYg8yQ4ts7DwnjHGhSTthMP2n3bZYvtmsZDmxX/UeV5CElDUtjlaN09HlzbNtSdwNS8SWjdPx/9N6I2ubZs70shZFUYyUkkA5pHqZeVXXXUVfvrpJ3zyyScAgF/96leYMmUKPvroI8PzzjvvPLz++uvRz+np6V42k/AR9j3OyUxDy2bp3JVPBLNVi2ioq7AvAskugcROtJGQ+l1VhJWjRfxm1DTP8HSIJQRgA4oiGXYX3jMGDTKQzhEMeOOPej0l5POiilaqb9DmjzHjrN7tcGbPdsLlkwnP3qytW7fik08+wcqVKzF8+HAAwMsvv4yRI0di27Zt6N27t+65GRkZKCgo8KppRIBQvuDh//OmiIhQYpY7QT0u6A0yolEGRoIUEQxE5wIRedXtUOlz+uZhwaYSXDO8s6N6CPuwr3BmY6i0kTmvgRMcoBaQ1Z+vHt4Zb68qVhxjhZQPbjsDF/xtWeO5xu2ddc1Q/HPNHvzl8kFo3ZwW7np4JrysWLECubm5UcEFAEaMGIHc3FwsX77cUHhZvHgx8vLy0LJlS4wdOxaPPfYY8vLyvGoq4SPKTKnhDzyBIWK+MU1Sp/qsbzYin5dEgZ1kxvZqhyXbD/DL2UgsxnYTtiv8emJvnDfAfIF10eAOOP+UQvJ98ZGauqYAAJ6mRU0DJ8+LRnhRSSBm9bZslhb9u6rWeK+K8wYUCPWtZMezN6q0tJQrcOTl5aG0tFT3vEmTJuHtt9/GF198gaeeegpff/01zj77bFRXV3PLV1dXo6KiQvGPiB/U4YgADE01VneVZiesX5zeqelaFG2UMLDP/CoDDYeY5kUVrabT4W47qwe6t2sh1D4SXPwlvzEAID0lxN3LSI3M8XlRjxfqvsRbJLFH8pkghNbN0zRlCetYfqsefvhhjUOt+t+aNWsA8Fc6siwbroCuuOIKnH/++RgwYAAuvPBC/Oc//8H27dvx8ccfc8vPmDEj6hCcm5uLTp06ccsRAYUzQHA1L2L+upxQ6aa/J/RrWs1QkrrkIScrrGC2k9JdMWlRX4hL0lND+PYPE7Hx9xMsZ7eNoD5NLdTyqlXnk/nv3WNw+1k9MOmUQlttIJRYNhvdfvvtuPLKKw3LdO3aFd988w3279+v+e7AgQPIz88Xvl5hYSG6dOmCHTt2cL+fPn06pk2bFv1cUVFBAkwcwb7zUcWLE82L6jM7YbFCUYqgz0tGGq2agwjbRYz6xK8n9kafghzTcnp1seZF8n+KX1pYcJzmJalTC77qRRJX86I61LsgG70L9N0lCGtYFl7atm2Ltm3bmpYbOXIkysvLsXr1apx++ukAgFWrVqG8vByjRo0Svt6hQ4ewZ88eFBbypdWMjAxkZFgPYSSCATsINGleDM/QqYf/NbtyZus107z89vy+eH/DXkwd092wHOE/evk0LhzUHred1aOpnI3cHHZX6kT8IuLzorEE8jQvtCuRp3i2rOzbty/OO+883HzzzVi5ciVWrlyJm2++GRdccIHCWbdPnz6YP38+AODYsWO49957sWLFCuzatQuLFy/GhRdeiLZt2+KSSy7xqqmEj7Cvd2SAYHOr/OL0sA/DPef2Cpc31byoBxkdzYtJRTed2Q3/vuNM5DYj+3QQUYZKN/2t0OSpzrETbZSi47BLJC56u0qzmEUfEd7jqU787bffximnnIIJEyZgwoQJGDhwIN566y1FmW3btqG8vBwAkJKSgk2bNuGiiy5Cr169cN1116FXr15YsWIFsrOzvWwq4RPs3iJR4YWZJR67eAA+mzYGt47rrigDALczq2qek526PFsvpdxOHESfpZ2U7uyu0GQ1Sg54Y4laA6f5zOlapLTzFk8zKLVu3Rpz5swxLMMmfsrKysJ///tfL5tEBIw2LZryGERedrZPhEISeuQ1Ca7seHDl6Z3w/KLvFfUZrbYV9ZLwkjCwT7KobVPGVE3kmVlFMkdzR90k6eAlI9RGF6k/m3jsEq5D6R8JX2nboslfKaKuNVrgGtmh1d8D+j4vNK7EN6xpUZKAf98xGmWVVeiZr6+hNc0RJEmGexvRVhHJQZN5mZPGQeczr2cVtWmGjXuOuts4IgoJL4SvtMxq8ik5eqIGgEm0ETNMcBc7BnvTsJMPqXQTB0mSMKBDLoBcw3JGz/yiwe3RLjsDdcyO5i1V/k5jGtO0t6GspwkNb1dpteBr9hkArhreBR1bNcPonuYBLoR1SHghfIW1HR+OCC8GK1w9zUvkTyOfF1bzkkaJw+Kawtws0zLq6cRI8/LslUM0Zdq1yFDU0al1M6ycPh65WeTEncjI0VDpJtQOu2YOvACQmiLh3okUGu0VJLwQgeGUDuGVs3GodBO8qUjj86KzsR5lPY1vcrPSsOjecULp3iMIRRsxf7dtkaHJ7VKQmwkisYkajRiBxNznRVsPKXe9hYQXwneW3X8Wduw/htE9wupV4SR1XOlF3zbNTkTp5IkZ97DOuSII5XlhirRpkY4DlfxtSYjEhTf+mEYbcaQXimj0FhJeCN/p2KoZOrZqFv2cn6OfdFDh88KRXtRHWA1LA7MfGpmNkg+xDLtNhXKz0nDwGAkvyUYDz2xkkteF739HeAkJL0Tg+NWYbvjpyEmc11+7syprBhKJTvzdBf3wqzfX4MbRReiR17SRHgkviY9ICncjrKSUJxIHoQy7mmgj88hHwl3o7SQCR7P0VDx5+SDud0rNi/H3AFDUpjlWPTA+OpHNvPpUdGrdDD8ePB4tc9tZ3fHCoh+cN5wINFYjzEh4SU6aNoFlfF5Uax11X+IFGdD2AN5Cy08ibuHZlDXRRiFluUmnFGJAh1ykMaPPZad2RJ8CyuCcaGiijSxOJi0yU2kCSkK4GXZNQqPr6jnCC3UdTyHhhYhbRKKN1CGNEXjbEhCJjdXHTJqX5ITXT9TjiFpUYfMDGdVDuAcJL0TcIuLzoiu8pCqFF3VILJF4WBVSszNJeEkmXrjqVLRpno7ZN5yu+U7Td1TjRQ1P80JaO0+ht5OIW/hOcspj6TqOuWlMqDStkBIU9d5GljUvlIwumTh/YCEmn1IQHUOUZiNlWXUuqpo60rzEGtK8EPGLxdBXFlaokSTg1M6t3GoVERB65in9mKxqXnrlt6AJKMlgxwv2b63ZSK15qefU5XLjCAWkeSHiFuXeI+H/13JszzxSVT4vD57fFwW5mbhgYHs3m0j4wPxbR2HxtgP45eiuiuOi0UaL7x2HY9V1yMuhbLrJDNtd1IKvWvNSXcvRvJDZyFNIeCHiFt7QICq8sGajkCQhOzMNd5/Ty6WWEX4ypHMrDOFo0kQznna1mLmXSHzUGXTVLnI15LAbc8hsRMQVRju9AvyQRR5qsxGRHHxw2xm4eLC4do36RvJi5PMiqx12eT4vXjSKiELCCxG38DUvYsJLGgkvScmgTi0xvFsbv5tBxAGs2UedUVcdnUgOu7GHhBcibuENDsJmo1TK80IQhD4KzUvI2OfloiEdeDW43ygiCgkvRNzCc4gTdtgNKX1eiOTBytMmp8vkhVWuqMcItc9LYW4mNv9hIn49sXf0GA0r3kLCCxG38AYHnuMcDzb0UW2/JgiCYE1DZmaj+gYZzVUZmUl28RYSXoiEgnXYPX9goW45VvNSr9YBEwSR9LCjgmQyU/J2ohaNbiPsQcILkVCwZqMnf87fmRoAMtNSon+np9JrQPCh+Sd5aWgQ17x0atUMgPmu94R70KhNxBXsgGDmsJuVnqIt0EhmWgqevXIwnrx8EFo2S3exhUTQmTSgEM0M+gZBAOI+L3NuHI5OrRuFF07iTMIbKEkdEVcoBgeuw664CeiiwbwIASLRyW2Whg0PTUB1XT1eXPwDLjAwLxLJC6tdCYX0vxvds230b8XiinQvnkLCCxG38AQZ0WgjIrlJTw0hPTWE+8/r43dTiICiEF7UmheB80nz4i1kNiLiFifbAxAEQRiRk9W0q7jWbMQXX0hgiR2keSHiFp43f40FsxFBEIQebVtk4PmrhiAzNUW7q7TOMKNw2CVBxlNI80LELezYEFHxpoluHUwQAjx0QT+0yEjFvRNo085k5IKB7XFOv3zN8dxmaZzSFCodS0jzQsQVeTmZ0b/ZseFYdR0AYMalp+CmN9fgzrN7xrppRALSMz8bG38/QbPyJpKb287qge2llYZO/9RjvIWEFyKuyMlMw2fTxiA9JUWxsjl8vAZAeLJZ8uuz/GoekYCQ4EKoyclMw+s3nK45zuaMIsWLt5DwQsQdPfKyNccqq+p8aAlBEEQTHVtlRf+mUGlvIZ8XgiAIgnCBLm2aR/8mzYu3eCq8PPbYYxg1ahSaNWuGli1bCp0jyzIefvhhtG/fHllZWRg3bhw2b97sZTMJgiAIwjGRbQIAEl68xlPhpaamBpdffjluueUW4XP+/Oc/4+mnn8bzzz+Pr7/+GgUFBTj33HNRWVnpYUuJeOVng9oDAPq3z/G5JQRBJDvpqSH87RdD8OhF/ZGXnWl+AmEbSdbLtuMis2fPxt13342jR48alpNlGe3bt8fdd9+N+++/HwBQXV2N/Px8/OlPf8L//u//ml6roqICubm5KC8vR04OTWiJzrHqOsxb9xPO61+giEQiCIIg4gsr83egfF527tyJ0tJSTJgwIXosIyMDY8eOxfLly7nnVFdXo6KiQvGPSB5aZKTi2pFdSXAhCIJIIgIlvJSWlgIA8vOVSYHy8/Oj36mZMWMGcnNzo/86derkeTsJgiAIgvAPy8LLww8/DEmSDP+tWbPGUaPUmQllWdbNVjh9+nSUl5dH/+3Zs8fRtQmCIAiCCDaW87zcfvvtuPLKKw3LdO3a1VZjCgoKAIQ1MIWFTdvUl5WVabQxETIyMpCRkWHregRBEARBxB+WhZe2bduibdu2XrQFRUVFKCgowMKFCzFkyBAA4YilJUuW4E9/+pMn1yQIgiAIIr7w1OeluLgYGzZsQHFxMerr67FhwwZs2LABx44di5bp06cP5s+fDyBsLrr77rvx+OOPY/78+fj2229x/fXXo1mzZrjqqqu8bCpBEARBEHGCp9sDPPTQQ3jjjTeinyPalEWLFmHcuHEAgG3btqG8vDxa5r777sPJkydx66234siRIxg+fDg+/fRTZGdrU8ITBEEQBJF8xCTPSyyhPC8EQRAEEX/EbZ4XgiAIgiAIM0h4IQiCIAgiriDhhSAIgiCIuIKEF4IgCIIg4goSXgiCIAiCiCtIeCEIgiAIIq7wNM+LH0Qiv2l3aYIgCIKIHyLztkgGl4QTXiorKwGAdpcmCIIgiDiksrISubm5hmUSLkldQ0MD9u3bh+zsbN2dqO1SUVGBTp06Yc+ePZQAz0PoPscOutexge5zbKD7HBu8us+yLKOyshLt27dHKGTs1ZJwmpdQKISOHTt6eo2cnBx6MWIA3efYQfc6NtB9jg10n2ODF/fZTOMSgRx2CYIgCIKIK0h4IQiCIAgiriDhxQIZGRn4/e9/j4yMDL+bktDQfY4ddK9jA93n2ED3OTYE4T4nnMMuQRAEQRCJDWleCIIgCIKIK0h4IQiCIAgiriDhhSAIgiCIuIKEF4IgCIIg4goSXgR58cUXUVRUhMzMTAwdOhRffvml302KK2bMmIHTTjsN2dnZyMvLw8UXX4xt27YpysiyjIcffhjt27dHVlYWxo0bh82bNyvKVFdX44477kDbtm3RvHlz/OxnP8NPP/0Uy58SV8yYMQOSJOHuu++OHqP77B579+7FNddcgzZt2qBZs2YYPHgw1q5dG/2e7rVz6urq8Nvf/hZFRUXIyspCt27d8Mgjj6ChoSFahu6zdZYuXYoLL7wQ7du3hyRJeP/99xXfu3VPjxw5gilTpiA3Nxe5ubmYMmUKjh496vwHyIQp7777rpyWlia//PLL8pYtW+S77rpLbt68ubx7926/mxY3TJw4UX799dflb7/9Vt6wYYN8/vnny507d5aPHTsWLfPEE0/I2dnZ8ty5c+VNmzbJV1xxhVxYWChXVFREy0ydOlXu0KGDvHDhQnndunXyWWedJQ8aNEiuq6vz42cFmtWrV8tdu3aVBw4cKN91113R43Sf3eHw4cNyly5d5Ouvv15etWqVvHPnTvmzzz6Tv//++2gZutfO+eMf/yi3adNG/ve//y3v3LlT/uc//ym3aNFCfuaZZ6Jl6D5bZ8GCBfKDDz4oz507VwYgz58/X/G9W/f0vPPOkwcMGCAvX75cXr58uTxgwAD5ggsucNx+El4EOP300+WpU6cqjvXp00f+zW9+41OL4p+ysjIZgLxkyRJZlmW5oaFBLigokJ944olomaqqKjk3N1eeNWuWLMuyfPToUTktLU1+9913o2X27t0rh0Ih+ZNPPontDwg4lZWVcs+ePeWFCxfKY8eOjQovdJ/d4/7775dHjx6t+z3da3c4//zz5V/+8peKY5deeql8zTXXyLJM99kN1MKLW/d0y5YtMgB55cqV0TIrVqyQAcjfffedozaT2ciEmpoarF27FhMmTFAcnzBhApYvX+5Tq+Kf8vJyAEDr1q0BADt37kRpaaniPmdkZGDs2LHR+7x27VrU1tYqyrRv3x4DBgygZ6Hitttuw/nnn49zzjlHcZzus3t8+OGHGDZsGC6//HLk5eVhyJAhePnll6Pf0712h9GjR+Pzzz/H9u3bAQAbN27EsmXLMHnyZAB0n73ArXu6YsUK5ObmYvjw4dEyI0aMQG5uruP7nnAbM7rNwYMHUV9fj/z8fMXx/Px8lJaW+tSq+EaWZUybNg2jR4/GgAEDACB6L3n3effu3dEy6enpaNWqlaYMPYsm3n33Xaxbtw5ff/215ju6z+7x448/YubMmZg2bRoeeOABrF69GnfeeScyMjJw7bXX0r12ifvvvx/l5eXo06cPUlJSUF9fj8ceewy/+MUvAFCf9gK37mlpaSny8vI09efl5Tm+7yS8CCJJkuKzLMuaY4QYt99+O7755hssW7ZM852d+0zPook9e/bgrrvuwqefforMzEzdcnSfndPQ0IBhw4bh8ccfBwAMGTIEmzdvxsyZM3HttddGy9G9dsZ7772HOXPm4B//+Af69++PDRs24O6770b79u1x3XXXRcvRfXYfN+4pr7wb953MRia0bdsWKSkpGimxrKxMI5US5txxxx348MMPsWjRInTs2DF6vKCgAAAM73NBQQFqampw5MgR3TLJztq1a1FWVoahQ4ciNTUVqampWLJkCZ577jmkpqZG7xPdZ+cUFhaiX79+imN9+/ZFcXExAOrTbvHrX/8av/nNb3DllVfilFNOwZQpU3DPPfdgxowZAOg+e4Fb97SgoAD79+/X1H/gwAHH952EFxPS09MxdOhQLFy4UHF84cKFGDVqlE+tij9kWcbtt9+OefPm4YsvvkBRUZHi+6KiIhQUFCjuc01NDZYsWRK9z0OHDkVaWpqiTElJCb799lt6Fo2MHz8emzZtwoYNG6L/hg0bhquvvhobNmxAt27d6D67xBlnnKEJ99++fTu6dOkCgPq0W5w4cQKhkHKqSklJiYZK0312H7fu6ciRI1FeXo7Vq1dHy6xatQrl5eXO77sjd98kIRIq/eqrr8pbtmyR7777brl58+byrl27/G5a3HDLLbfIubm58uLFi+WSkpLovxMnTkTLPPHEE3Jubq48b948edOmTfIvfvELbmhex44d5c8++0xet26dfPbZZyd1uKMIbLSRLNN9dovVq1fLqamp8mOPPSbv2LFDfvvtt+VmzZrJc+bMiZahe+2c6667Tu7QoUM0VHrevHly27Zt5fvuuy9ahu6zdSorK+X169fL69evlwHITz/9tLx+/fpoChC37ul5550nDxw4UF6xYoW8YsUK+ZRTTqFQ6VjywgsvyF26dJHT09PlU089NRriS4gBgPvv9ddfj5ZpaGiQf//738sFBQVyRkaGPGbMGHnTpk2Kek6ePCnffvvtcuvWreWsrCz5ggsukIuLi2P8a+ILtfBC99k9PvroI3nAgAFyRkaG3KdPH/mll15SfE/32jkVFRXyXXfdJXfu3FnOzMyUu3XrJj/44INydXV1tAzdZ+ssWrSIOyZfd911siy7d08PHTokX3311XJ2dracnZ0tX3311fKRI0cct1+SZVl2prshCIIgCIKIHeTzQhAEQRBEXEHCC0EQBEEQcQUJLwRBEARBxBUkvBAEQRAEEVeQ8EIQBEEQRFxBwgtBEARBEHEFCS8EQRAEQcQVJLwQBEEQBBFXkPBCEARBEERcQcILQRAEQRBxBQkvBEEQBEHEFSS8EARBEAQRV/x/KvTOSDf8O4MAAAAASUVORK5CYII=\n",
|
|
"text/plain": [
|
|
"<Figure size 640x480 with 1 Axes>"
|
|
]
|
|
},
|
|
"metadata": {
|
|
"filenames": {
|
|
"image/png": "/Users/mhjensen/Teaching/MachineLearning/doc/LectureNotes/_build/jupyter_execute/chapter13_3_0.png"
|
|
}
|
|
},
|
|
"output_type": "display_data"
|
|
},
|
|
{
|
|
"name": "stdout",
|
|
"output_type": "stream",
|
|
"text": [
|
|
"Metal device set to: Apple M1\n"
|
|
]
|
|
},
|
|
{
|
|
"name": "stdout",
|
|
"output_type": "stream",
|
|
"text": [
|
|
"Model: \"sequential\"\n"
|
|
]
|
|
},
|
|
{
|
|
"name": "stdout",
|
|
"output_type": "stream",
|
|
"text": [
|
|
"_________________________________________________________________\n"
|
|
]
|
|
},
|
|
{
|
|
"name": "stdout",
|
|
"output_type": "stream",
|
|
"text": [
|
|
" Layer (type) Output Shape Param # \n"
|
|
]
|
|
},
|
|
{
|
|
"name": "stdout",
|
|
"output_type": "stream",
|
|
"text": [
|
|
"=================================================================\n"
|
|
]
|
|
},
|
|
{
|
|
"name": "stdout",
|
|
"output_type": "stream",
|
|
"text": [
|
|
" simple_rnn (SimpleRNN) (None, 32) 1184 \n"
|
|
]
|
|
},
|
|
{
|
|
"name": "stdout",
|
|
"output_type": "stream",
|
|
"text": [
|
|
" \n"
|
|
]
|
|
},
|
|
{
|
|
"name": "stdout",
|
|
"output_type": "stream",
|
|
"text": [
|
|
" dense (Dense) (None, 8) 264 \n"
|
|
]
|
|
},
|
|
{
|
|
"name": "stdout",
|
|
"output_type": "stream",
|
|
"text": [
|
|
" \n"
|
|
]
|
|
},
|
|
{
|
|
"name": "stdout",
|
|
"output_type": "stream",
|
|
"text": [
|
|
" dense_1 (Dense) (None, 1) 9 \n"
|
|
]
|
|
},
|
|
{
|
|
"name": "stdout",
|
|
"output_type": "stream",
|
|
"text": [
|
|
" \n"
|
|
]
|
|
},
|
|
{
|
|
"name": "stdout",
|
|
"output_type": "stream",
|
|
"text": [
|
|
"=================================================================\n"
|
|
]
|
|
},
|
|
{
|
|
"name": "stdout",
|
|
"output_type": "stream",
|
|
"text": [
|
|
"Total params: 1,457\n"
|
|
]
|
|
},
|
|
{
|
|
"name": "stdout",
|
|
"output_type": "stream",
|
|
"text": [
|
|
"Trainable params: 1,457\n"
|
|
]
|
|
},
|
|
{
|
|
"name": "stdout",
|
|
"output_type": "stream",
|
|
"text": [
|
|
"Non-trainable params: 0\n"
|
|
]
|
|
},
|
|
{
|
|
"name": "stdout",
|
|
"output_type": "stream",
|
|
"text": [
|
|
"_________________________________________________________________\n"
|
|
]
|
|
},
|
|
{
|
|
"name": "stdout",
|
|
"output_type": "stream",
|
|
"text": [
|
|
"Epoch 1/100\n"
|
|
]
|
|
},
|
|
{
|
|
"name": "stderr",
|
|
"output_type": "stream",
|
|
"text": [
|
|
"2023-11-21 06:09:51.383315: W tensorflow/core/platform/profile_utils/cpu_utils.cc:128] Failed to get CPU frequency: 0 Hz\n"
|
|
]
|
|
},
|
|
{
|
|
"name": "stdout",
|
|
"output_type": "stream",
|
|
"text": [
|
|
"50/50 - 2s - loss: 0.6262 - 2s/epoch - 43ms/step\n"
|
|
]
|
|
},
|
|
{
|
|
"name": "stdout",
|
|
"output_type": "stream",
|
|
"text": [
|
|
"Epoch 2/100\n"
|
|
]
|
|
},
|
|
{
|
|
"name": "stdout",
|
|
"output_type": "stream",
|
|
"text": [
|
|
"50/50 - 0s - loss: 0.4148 - 440ms/epoch - 9ms/step\n"
|
|
]
|
|
},
|
|
{
|
|
"name": "stdout",
|
|
"output_type": "stream",
|
|
"text": [
|
|
"Epoch 3/100\n"
|
|
]
|
|
},
|
|
{
|
|
"name": "stdout",
|
|
"output_type": "stream",
|
|
"text": [
|
|
"50/50 - 0s - loss: 0.4134 - 485ms/epoch - 10ms/step\n"
|
|
]
|
|
},
|
|
{
|
|
"name": "stdout",
|
|
"output_type": "stream",
|
|
"text": [
|
|
"Epoch 4/100\n"
|
|
]
|
|
},
|
|
{
|
|
"name": "stdout",
|
|
"output_type": "stream",
|
|
"text": [
|
|
"50/50 - 1s - loss: 0.4111 - 507ms/epoch - 10ms/step\n"
|
|
]
|
|
},
|
|
{
|
|
"name": "stdout",
|
|
"output_type": "stream",
|
|
"text": [
|
|
"Epoch 5/100\n"
|
|
]
|
|
},
|
|
{
|
|
"name": "stdout",
|
|
"output_type": "stream",
|
|
"text": [
|
|
"50/50 - 0s - loss: 0.4099 - 446ms/epoch - 9ms/step\n"
|
|
]
|
|
},
|
|
{
|
|
"name": "stdout",
|
|
"output_type": "stream",
|
|
"text": [
|
|
"Epoch 6/100\n"
|
|
]
|
|
},
|
|
{
|
|
"name": "stdout",
|
|
"output_type": "stream",
|
|
"text": [
|
|
"50/50 - 0s - loss: 0.4051 - 449ms/epoch - 9ms/step\n"
|
|
]
|
|
},
|
|
{
|
|
"name": "stdout",
|
|
"output_type": "stream",
|
|
"text": [
|
|
"Epoch 7/100\n"
|
|
]
|
|
},
|
|
{
|
|
"name": "stdout",
|
|
"output_type": "stream",
|
|
"text": [
|
|
"50/50 - 0s - loss: 0.4082 - 448ms/epoch - 9ms/step\n"
|
|
]
|
|
},
|
|
{
|
|
"name": "stdout",
|
|
"output_type": "stream",
|
|
"text": [
|
|
"Epoch 8/100\n"
|
|
]
|
|
},
|
|
{
|
|
"name": "stdout",
|
|
"output_type": "stream",
|
|
"text": [
|
|
"50/50 - 0s - loss: 0.4052 - 447ms/epoch - 9ms/step\n"
|
|
]
|
|
},
|
|
{
|
|
"name": "stdout",
|
|
"output_type": "stream",
|
|
"text": [
|
|
"Epoch 9/100\n"
|
|
]
|
|
},
|
|
{
|
|
"name": "stdout",
|
|
"output_type": "stream",
|
|
"text": [
|
|
"50/50 - 0s - loss: 0.4032 - 446ms/epoch - 9ms/step\n"
|
|
]
|
|
},
|
|
{
|
|
"name": "stdout",
|
|
"output_type": "stream",
|
|
"text": [
|
|
"Epoch 10/100\n"
|
|
]
|
|
},
|
|
{
|
|
"name": "stdout",
|
|
"output_type": "stream",
|
|
"text": [
|
|
"50/50 - 0s - loss: 0.4053 - 439ms/epoch - 9ms/step\n"
|
|
]
|
|
},
|
|
{
|
|
"name": "stdout",
|
|
"output_type": "stream",
|
|
"text": [
|
|
"Epoch 11/100\n"
|
|
]
|
|
},
|
|
{
|
|
"name": "stdout",
|
|
"output_type": "stream",
|
|
"text": [
|
|
"50/50 - 0s - loss: 0.4007 - 446ms/epoch - 9ms/step\n"
|
|
]
|
|
},
|
|
{
|
|
"name": "stdout",
|
|
"output_type": "stream",
|
|
"text": [
|
|
"Epoch 12/100\n"
|
|
]
|
|
},
|
|
{
|
|
"name": "stdout",
|
|
"output_type": "stream",
|
|
"text": [
|
|
"50/50 - 0s - loss: 0.4036 - 446ms/epoch - 9ms/step\n"
|
|
]
|
|
},
|
|
{
|
|
"name": "stdout",
|
|
"output_type": "stream",
|
|
"text": [
|
|
"Epoch 13/100\n"
|
|
]
|
|
},
|
|
{
|
|
"name": "stdout",
|
|
"output_type": "stream",
|
|
"text": [
|
|
"50/50 - 0s - loss: 0.4008 - 449ms/epoch - 9ms/step\n"
|
|
]
|
|
},
|
|
{
|
|
"name": "stdout",
|
|
"output_type": "stream",
|
|
"text": [
|
|
"Epoch 14/100\n"
|
|
]
|
|
},
|
|
{
|
|
"name": "stdout",
|
|
"output_type": "stream",
|
|
"text": [
|
|
"50/50 - 0s - loss: 0.3978 - 447ms/epoch - 9ms/step\n"
|
|
]
|
|
},
|
|
{
|
|
"name": "stdout",
|
|
"output_type": "stream",
|
|
"text": [
|
|
"Epoch 15/100\n"
|
|
]
|
|
},
|
|
{
|
|
"name": "stdout",
|
|
"output_type": "stream",
|
|
"text": [
|
|
"50/50 - 0s - loss: 0.4018 - 449ms/epoch - 9ms/step\n"
|
|
]
|
|
},
|
|
{
|
|
"name": "stdout",
|
|
"output_type": "stream",
|
|
"text": [
|
|
"Epoch 16/100\n"
|
|
]
|
|
},
|
|
{
|
|
"name": "stdout",
|
|
"output_type": "stream",
|
|
"text": [
|
|
"50/50 - 0s - loss: 0.3984 - 448ms/epoch - 9ms/step\n"
|
|
]
|
|
},
|
|
{
|
|
"name": "stdout",
|
|
"output_type": "stream",
|
|
"text": [
|
|
"Epoch 17/100\n"
|
|
]
|
|
},
|
|
{
|
|
"name": "stdout",
|
|
"output_type": "stream",
|
|
"text": [
|
|
"50/50 - 0s - loss: 0.3975 - 446ms/epoch - 9ms/step\n"
|
|
]
|
|
},
|
|
{
|
|
"name": "stdout",
|
|
"output_type": "stream",
|
|
"text": [
|
|
"Epoch 18/100\n"
|
|
]
|
|
},
|
|
{
|
|
"name": "stdout",
|
|
"output_type": "stream",
|
|
"text": [
|
|
"50/50 - 0s - loss: 0.3954 - 447ms/epoch - 9ms/step\n"
|
|
]
|
|
},
|
|
{
|
|
"name": "stdout",
|
|
"output_type": "stream",
|
|
"text": [
|
|
"Epoch 19/100\n"
|
|
]
|
|
},
|
|
{
|
|
"name": "stdout",
|
|
"output_type": "stream",
|
|
"text": [
|
|
"50/50 - 0s - loss: 0.3995 - 446ms/epoch - 9ms/step\n"
|
|
]
|
|
},
|
|
{
|
|
"name": "stdout",
|
|
"output_type": "stream",
|
|
"text": [
|
|
"Epoch 20/100\n"
|
|
]
|
|
},
|
|
{
|
|
"name": "stdout",
|
|
"output_type": "stream",
|
|
"text": [
|
|
"50/50 - 0s - loss: 0.3987 - 449ms/epoch - 9ms/step\n"
|
|
]
|
|
},
|
|
{
|
|
"name": "stdout",
|
|
"output_type": "stream",
|
|
"text": [
|
|
"Epoch 21/100\n"
|
|
]
|
|
},
|
|
{
|
|
"name": "stdout",
|
|
"output_type": "stream",
|
|
"text": [
|
|
"50/50 - 0s - loss: 0.3961 - 446ms/epoch - 9ms/step\n"
|
|
]
|
|
},
|
|
{
|
|
"name": "stdout",
|
|
"output_type": "stream",
|
|
"text": [
|
|
"Epoch 22/100\n"
|
|
]
|
|
},
|
|
{
|
|
"name": "stdout",
|
|
"output_type": "stream",
|
|
"text": [
|
|
"50/50 - 0s - loss: 0.3948 - 444ms/epoch - 9ms/step\n"
|
|
]
|
|
},
|
|
{
|
|
"name": "stdout",
|
|
"output_type": "stream",
|
|
"text": [
|
|
"Epoch 23/100\n"
|
|
]
|
|
},
|
|
{
|
|
"name": "stdout",
|
|
"output_type": "stream",
|
|
"text": [
|
|
"50/50 - 0s - loss: 0.3968 - 447ms/epoch - 9ms/step\n"
|
|
]
|
|
},
|
|
{
|
|
"name": "stdout",
|
|
"output_type": "stream",
|
|
"text": [
|
|
"Epoch 24/100\n"
|
|
]
|
|
},
|
|
{
|
|
"name": "stdout",
|
|
"output_type": "stream",
|
|
"text": [
|
|
"50/50 - 0s - loss: 0.3970 - 447ms/epoch - 9ms/step\n"
|
|
]
|
|
},
|
|
{
|
|
"name": "stdout",
|
|
"output_type": "stream",
|
|
"text": [
|
|
"Epoch 25/100\n"
|
|
]
|
|
},
|
|
{
|
|
"name": "stdout",
|
|
"output_type": "stream",
|
|
"text": [
|
|
"50/50 - 0s - loss: 0.3929 - 444ms/epoch - 9ms/step\n"
|
|
]
|
|
},
|
|
{
|
|
"name": "stdout",
|
|
"output_type": "stream",
|
|
"text": [
|
|
"Epoch 26/100\n"
|
|
]
|
|
},
|
|
{
|
|
"name": "stdout",
|
|
"output_type": "stream",
|
|
"text": [
|
|
"50/50 - 0s - loss: 0.3971 - 445ms/epoch - 9ms/step\n"
|
|
]
|
|
},
|
|
{
|
|
"name": "stdout",
|
|
"output_type": "stream",
|
|
"text": [
|
|
"Epoch 27/100\n"
|
|
]
|
|
},
|
|
{
|
|
"name": "stdout",
|
|
"output_type": "stream",
|
|
"text": [
|
|
"50/50 - 0s - loss: 0.3960 - 446ms/epoch - 9ms/step\n"
|
|
]
|
|
},
|
|
{
|
|
"name": "stdout",
|
|
"output_type": "stream",
|
|
"text": [
|
|
"Epoch 28/100\n"
|
|
]
|
|
},
|
|
{
|
|
"name": "stdout",
|
|
"output_type": "stream",
|
|
"text": [
|
|
"50/50 - 0s - loss: 0.3915 - 447ms/epoch - 9ms/step\n"
|
|
]
|
|
},
|
|
{
|
|
"name": "stdout",
|
|
"output_type": "stream",
|
|
"text": [
|
|
"Epoch 29/100\n"
|
|
]
|
|
},
|
|
{
|
|
"name": "stdout",
|
|
"output_type": "stream",
|
|
"text": [
|
|
"50/50 - 0s - loss: 0.3942 - 449ms/epoch - 9ms/step\n"
|
|
]
|
|
},
|
|
{
|
|
"name": "stdout",
|
|
"output_type": "stream",
|
|
"text": [
|
|
"Epoch 30/100\n"
|
|
]
|
|
},
|
|
{
|
|
"name": "stdout",
|
|
"output_type": "stream",
|
|
"text": [
|
|
"50/50 - 0s - loss: 0.3937 - 445ms/epoch - 9ms/step\n"
|
|
]
|
|
},
|
|
{
|
|
"name": "stdout",
|
|
"output_type": "stream",
|
|
"text": [
|
|
"Epoch 31/100\n"
|
|
]
|
|
},
|
|
{
|
|
"name": "stdout",
|
|
"output_type": "stream",
|
|
"text": [
|
|
"50/50 - 0s - loss: 0.3944 - 445ms/epoch - 9ms/step\n"
|
|
]
|
|
},
|
|
{
|
|
"name": "stdout",
|
|
"output_type": "stream",
|
|
"text": [
|
|
"Epoch 32/100\n"
|
|
]
|
|
},
|
|
{
|
|
"name": "stdout",
|
|
"output_type": "stream",
|
|
"text": [
|
|
"50/50 - 0s - loss: 0.3912 - 446ms/epoch - 9ms/step\n"
|
|
]
|
|
},
|
|
{
|
|
"name": "stdout",
|
|
"output_type": "stream",
|
|
"text": [
|
|
"Epoch 33/100\n"
|
|
]
|
|
},
|
|
{
|
|
"name": "stdout",
|
|
"output_type": "stream",
|
|
"text": [
|
|
"50/50 - 0s - loss: 0.3939 - 445ms/epoch - 9ms/step\n"
|
|
]
|
|
},
|
|
{
|
|
"name": "stdout",
|
|
"output_type": "stream",
|
|
"text": [
|
|
"Epoch 34/100\n"
|
|
]
|
|
},
|
|
{
|
|
"name": "stdout",
|
|
"output_type": "stream",
|
|
"text": [
|
|
"50/50 - 0s - loss: 0.3932 - 446ms/epoch - 9ms/step\n"
|
|
]
|
|
},
|
|
{
|
|
"name": "stdout",
|
|
"output_type": "stream",
|
|
"text": [
|
|
"Epoch 35/100\n"
|
|
]
|
|
},
|
|
{
|
|
"name": "stdout",
|
|
"output_type": "stream",
|
|
"text": [
|
|
"50/50 - 0s - loss: 0.3863 - 445ms/epoch - 9ms/step\n"
|
|
]
|
|
},
|
|
{
|
|
"name": "stdout",
|
|
"output_type": "stream",
|
|
"text": [
|
|
"Epoch 36/100\n"
|
|
]
|
|
},
|
|
{
|
|
"name": "stdout",
|
|
"output_type": "stream",
|
|
"text": [
|
|
"50/50 - 0s - loss: 0.3880 - 446ms/epoch - 9ms/step\n"
|
|
]
|
|
},
|
|
{
|
|
"name": "stdout",
|
|
"output_type": "stream",
|
|
"text": [
|
|
"Epoch 37/100\n"
|
|
]
|
|
},
|
|
{
|
|
"name": "stdout",
|
|
"output_type": "stream",
|
|
"text": [
|
|
"50/50 - 0s - loss: 0.3888 - 447ms/epoch - 9ms/step\n"
|
|
]
|
|
},
|
|
{
|
|
"name": "stdout",
|
|
"output_type": "stream",
|
|
"text": [
|
|
"Epoch 38/100\n"
|
|
]
|
|
},
|
|
{
|
|
"name": "stdout",
|
|
"output_type": "stream",
|
|
"text": [
|
|
"50/50 - 0s - loss: 0.3893 - 446ms/epoch - 9ms/step\n"
|
|
]
|
|
},
|
|
{
|
|
"name": "stdout",
|
|
"output_type": "stream",
|
|
"text": [
|
|
"Epoch 39/100\n"
|
|
]
|
|
},
|
|
{
|
|
"name": "stdout",
|
|
"output_type": "stream",
|
|
"text": [
|
|
"50/50 - 0s - loss: 0.3895 - 446ms/epoch - 9ms/step\n"
|
|
]
|
|
},
|
|
{
|
|
"name": "stdout",
|
|
"output_type": "stream",
|
|
"text": [
|
|
"Epoch 40/100\n"
|
|
]
|
|
},
|
|
{
|
|
"name": "stdout",
|
|
"output_type": "stream",
|
|
"text": [
|
|
"50/50 - 0s - loss: 0.3904 - 448ms/epoch - 9ms/step\n"
|
|
]
|
|
},
|
|
{
|
|
"name": "stdout",
|
|
"output_type": "stream",
|
|
"text": [
|
|
"Epoch 41/100\n"
|
|
]
|
|
},
|
|
{
|
|
"name": "stdout",
|
|
"output_type": "stream",
|
|
"text": [
|
|
"50/50 - 0s - loss: 0.3877 - 448ms/epoch - 9ms/step\n"
|
|
]
|
|
},
|
|
{
|
|
"name": "stdout",
|
|
"output_type": "stream",
|
|
"text": [
|
|
"Epoch 42/100\n"
|
|
]
|
|
},
|
|
{
|
|
"name": "stdout",
|
|
"output_type": "stream",
|
|
"text": [
|
|
"50/50 - 0s - loss: 0.3852 - 446ms/epoch - 9ms/step\n"
|
|
]
|
|
},
|
|
{
|
|
"name": "stdout",
|
|
"output_type": "stream",
|
|
"text": [
|
|
"Epoch 43/100\n"
|
|
]
|
|
},
|
|
{
|
|
"name": "stdout",
|
|
"output_type": "stream",
|
|
"text": [
|
|
"50/50 - 0s - loss: 0.3902 - 446ms/epoch - 9ms/step\n"
|
|
]
|
|
},
|
|
{
|
|
"name": "stdout",
|
|
"output_type": "stream",
|
|
"text": [
|
|
"Epoch 44/100\n"
|
|
]
|
|
},
|
|
{
|
|
"name": "stdout",
|
|
"output_type": "stream",
|
|
"text": [
|
|
"50/50 - 0s - loss: 0.3856 - 446ms/epoch - 9ms/step\n"
|
|
]
|
|
},
|
|
{
|
|
"name": "stdout",
|
|
"output_type": "stream",
|
|
"text": [
|
|
"Epoch 45/100\n"
|
|
]
|
|
},
|
|
{
|
|
"name": "stdout",
|
|
"output_type": "stream",
|
|
"text": [
|
|
"50/50 - 0s - loss: 0.3876 - 447ms/epoch - 9ms/step\n"
|
|
]
|
|
},
|
|
{
|
|
"name": "stdout",
|
|
"output_type": "stream",
|
|
"text": [
|
|
"Epoch 46/100\n"
|
|
]
|
|
},
|
|
{
|
|
"name": "stdout",
|
|
"output_type": "stream",
|
|
"text": [
|
|
"50/50 - 0s - loss: 0.3880 - 444ms/epoch - 9ms/step\n"
|
|
]
|
|
},
|
|
{
|
|
"name": "stdout",
|
|
"output_type": "stream",
|
|
"text": [
|
|
"Epoch 47/100\n"
|
|
]
|
|
},
|
|
{
|
|
"name": "stdout",
|
|
"output_type": "stream",
|
|
"text": [
|
|
"50/50 - 0s - loss: 0.3873 - 446ms/epoch - 9ms/step\n"
|
|
]
|
|
},
|
|
{
|
|
"name": "stdout",
|
|
"output_type": "stream",
|
|
"text": [
|
|
"Epoch 48/100\n"
|
|
]
|
|
},
|
|
{
|
|
"name": "stdout",
|
|
"output_type": "stream",
|
|
"text": [
|
|
"50/50 - 0s - loss: 0.3848 - 448ms/epoch - 9ms/step\n"
|
|
]
|
|
},
|
|
{
|
|
"name": "stdout",
|
|
"output_type": "stream",
|
|
"text": [
|
|
"Epoch 49/100\n"
|
|
]
|
|
},
|
|
{
|
|
"name": "stdout",
|
|
"output_type": "stream",
|
|
"text": [
|
|
"50/50 - 0s - loss: 0.3852 - 447ms/epoch - 9ms/step\n"
|
|
]
|
|
},
|
|
{
|
|
"name": "stdout",
|
|
"output_type": "stream",
|
|
"text": [
|
|
"Epoch 50/100\n"
|
|
]
|
|
},
|
|
{
|
|
"name": "stdout",
|
|
"output_type": "stream",
|
|
"text": [
|
|
"50/50 - 0s - loss: 0.3877 - 447ms/epoch - 9ms/step\n"
|
|
]
|
|
},
|
|
{
|
|
"name": "stdout",
|
|
"output_type": "stream",
|
|
"text": [
|
|
"Epoch 51/100\n"
|
|
]
|
|
},
|
|
{
|
|
"name": "stdout",
|
|
"output_type": "stream",
|
|
"text": [
|
|
"50/50 - 0s - loss: 0.3852 - 447ms/epoch - 9ms/step\n"
|
|
]
|
|
},
|
|
{
|
|
"name": "stdout",
|
|
"output_type": "stream",
|
|
"text": [
|
|
"Epoch 52/100\n"
|
|
]
|
|
},
|
|
{
|
|
"name": "stdout",
|
|
"output_type": "stream",
|
|
"text": [
|
|
"50/50 - 0s - loss: 0.3840 - 446ms/epoch - 9ms/step\n"
|
|
]
|
|
},
|
|
{
|
|
"name": "stdout",
|
|
"output_type": "stream",
|
|
"text": [
|
|
"Epoch 53/100\n"
|
|
]
|
|
},
|
|
{
|
|
"name": "stdout",
|
|
"output_type": "stream",
|
|
"text": [
|
|
"50/50 - 0s - loss: 0.3834 - 444ms/epoch - 9ms/step\n"
|
|
]
|
|
},
|
|
{
|
|
"name": "stdout",
|
|
"output_type": "stream",
|
|
"text": [
|
|
"Epoch 54/100\n"
|
|
]
|
|
},
|
|
{
|
|
"name": "stdout",
|
|
"output_type": "stream",
|
|
"text": [
|
|
"50/50 - 0s - loss: 0.3851 - 446ms/epoch - 9ms/step\n"
|
|
]
|
|
},
|
|
{
|
|
"name": "stdout",
|
|
"output_type": "stream",
|
|
"text": [
|
|
"Epoch 55/100\n"
|
|
]
|
|
},
|
|
{
|
|
"name": "stdout",
|
|
"output_type": "stream",
|
|
"text": [
|
|
"50/50 - 0s - loss: 0.3864 - 446ms/epoch - 9ms/step\n"
|
|
]
|
|
},
|
|
{
|
|
"name": "stdout",
|
|
"output_type": "stream",
|
|
"text": [
|
|
"Epoch 56/100\n"
|
|
]
|
|
},
|
|
{
|
|
"name": "stdout",
|
|
"output_type": "stream",
|
|
"text": [
|
|
"50/50 - 0s - loss: 0.3812 - 447ms/epoch - 9ms/step\n"
|
|
]
|
|
},
|
|
{
|
|
"name": "stdout",
|
|
"output_type": "stream",
|
|
"text": [
|
|
"Epoch 57/100\n"
|
|
]
|
|
},
|
|
{
|
|
"ename": "KeyboardInterrupt",
|
|
"evalue": "",
|
|
"output_type": "error",
|
|
"traceback": [
|
|
"\u001b[0;31m---------------------------------------------------------------------------\u001b[0m",
|
|
"\u001b[0;31mKeyboardInterrupt\u001b[0m Traceback (most recent call last)",
|
|
"Input \u001b[0;32mIn [1]\u001b[0m, in \u001b[0;36m<cell line: 58>\u001b[0;34m()\u001b[0m\n\u001b[1;32m 55\u001b[0m model\u001b[38;5;241m.\u001b[39mcompile(loss\u001b[38;5;241m=\u001b[39m\u001b[38;5;124m'\u001b[39m\u001b[38;5;124mmean_squared_error\u001b[39m\u001b[38;5;124m'\u001b[39m, optimizer\u001b[38;5;241m=\u001b[39m\u001b[38;5;124m'\u001b[39m\u001b[38;5;124mrmsprop\u001b[39m\u001b[38;5;124m'\u001b[39m)\n\u001b[1;32m 56\u001b[0m model\u001b[38;5;241m.\u001b[39msummary()\n\u001b[0;32m---> 58\u001b[0m \u001b[43mmodel\u001b[49m\u001b[38;5;241;43m.\u001b[39;49m\u001b[43mfit\u001b[49m\u001b[43m(\u001b[49m\u001b[43mtrainX\u001b[49m\u001b[43m,\u001b[49m\u001b[43mtrainY\u001b[49m\u001b[43m,\u001b[49m\u001b[43m \u001b[49m\u001b[43mepochs\u001b[49m\u001b[38;5;241;43m=\u001b[39;49m\u001b[38;5;241;43m100\u001b[39;49m\u001b[43m,\u001b[49m\u001b[43m \u001b[49m\u001b[43mbatch_size\u001b[49m\u001b[38;5;241;43m=\u001b[39;49m\u001b[38;5;241;43m16\u001b[39;49m\u001b[43m,\u001b[49m\u001b[43m \u001b[49m\u001b[43mverbose\u001b[49m\u001b[38;5;241;43m=\u001b[39;49m\u001b[38;5;241;43m2\u001b[39;49m\u001b[43m)\u001b[49m\n\u001b[1;32m 59\u001b[0m trainPredict \u001b[38;5;241m=\u001b[39m model\u001b[38;5;241m.\u001b[39mpredict(trainX)\n\u001b[1;32m 60\u001b[0m testPredict\u001b[38;5;241m=\u001b[39m model\u001b[38;5;241m.\u001b[39mpredict(testX)\n",
|
|
"File \u001b[0;32m~/miniforge3/envs/myenv/lib/python3.9/site-packages/keras/utils/traceback_utils.py:64\u001b[0m, in \u001b[0;36mfilter_traceback.<locals>.error_handler\u001b[0;34m(*args, **kwargs)\u001b[0m\n\u001b[1;32m 62\u001b[0m filtered_tb \u001b[38;5;241m=\u001b[39m \u001b[38;5;28;01mNone\u001b[39;00m\n\u001b[1;32m 63\u001b[0m \u001b[38;5;28;01mtry\u001b[39;00m:\n\u001b[0;32m---> 64\u001b[0m \u001b[38;5;28;01mreturn\u001b[39;00m \u001b[43mfn\u001b[49m\u001b[43m(\u001b[49m\u001b[38;5;241;43m*\u001b[39;49m\u001b[43margs\u001b[49m\u001b[43m,\u001b[49m\u001b[43m \u001b[49m\u001b[38;5;241;43m*\u001b[39;49m\u001b[38;5;241;43m*\u001b[39;49m\u001b[43mkwargs\u001b[49m\u001b[43m)\u001b[49m\n\u001b[1;32m 65\u001b[0m \u001b[38;5;28;01mexcept\u001b[39;00m \u001b[38;5;167;01mException\u001b[39;00m \u001b[38;5;28;01mas\u001b[39;00m e: \u001b[38;5;66;03m# pylint: disable=broad-except\u001b[39;00m\n\u001b[1;32m 66\u001b[0m filtered_tb \u001b[38;5;241m=\u001b[39m _process_traceback_frames(e\u001b[38;5;241m.\u001b[39m__traceback__)\n",
|
|
"File \u001b[0;32m~/miniforge3/envs/myenv/lib/python3.9/site-packages/keras/engine/training.py:1384\u001b[0m, in \u001b[0;36mModel.fit\u001b[0;34m(self, x, y, batch_size, epochs, verbose, callbacks, validation_split, validation_data, shuffle, class_weight, sample_weight, initial_epoch, steps_per_epoch, validation_steps, validation_batch_size, validation_freq, max_queue_size, workers, use_multiprocessing)\u001b[0m\n\u001b[1;32m 1377\u001b[0m \u001b[38;5;28;01mwith\u001b[39;00m tf\u001b[38;5;241m.\u001b[39mprofiler\u001b[38;5;241m.\u001b[39mexperimental\u001b[38;5;241m.\u001b[39mTrace(\n\u001b[1;32m 1378\u001b[0m \u001b[38;5;124m'\u001b[39m\u001b[38;5;124mtrain\u001b[39m\u001b[38;5;124m'\u001b[39m,\n\u001b[1;32m 1379\u001b[0m epoch_num\u001b[38;5;241m=\u001b[39mepoch,\n\u001b[1;32m 1380\u001b[0m step_num\u001b[38;5;241m=\u001b[39mstep,\n\u001b[1;32m 1381\u001b[0m batch_size\u001b[38;5;241m=\u001b[39mbatch_size,\n\u001b[1;32m 1382\u001b[0m _r\u001b[38;5;241m=\u001b[39m\u001b[38;5;241m1\u001b[39m):\n\u001b[1;32m 1383\u001b[0m callbacks\u001b[38;5;241m.\u001b[39mon_train_batch_begin(step)\n\u001b[0;32m-> 1384\u001b[0m tmp_logs \u001b[38;5;241m=\u001b[39m \u001b[38;5;28;43mself\u001b[39;49m\u001b[38;5;241;43m.\u001b[39;49m\u001b[43mtrain_function\u001b[49m\u001b[43m(\u001b[49m\u001b[43miterator\u001b[49m\u001b[43m)\u001b[49m\n\u001b[1;32m 1385\u001b[0m \u001b[38;5;28;01mif\u001b[39;00m data_handler\u001b[38;5;241m.\u001b[39mshould_sync:\n\u001b[1;32m 1386\u001b[0m context\u001b[38;5;241m.\u001b[39masync_wait()\n",
|
|
"File \u001b[0;32m~/miniforge3/envs/myenv/lib/python3.9/site-packages/tensorflow/python/util/traceback_utils.py:150\u001b[0m, in \u001b[0;36mfilter_traceback.<locals>.error_handler\u001b[0;34m(*args, **kwargs)\u001b[0m\n\u001b[1;32m 148\u001b[0m filtered_tb \u001b[38;5;241m=\u001b[39m \u001b[38;5;28;01mNone\u001b[39;00m\n\u001b[1;32m 149\u001b[0m \u001b[38;5;28;01mtry\u001b[39;00m:\n\u001b[0;32m--> 150\u001b[0m \u001b[38;5;28;01mreturn\u001b[39;00m \u001b[43mfn\u001b[49m\u001b[43m(\u001b[49m\u001b[38;5;241;43m*\u001b[39;49m\u001b[43margs\u001b[49m\u001b[43m,\u001b[49m\u001b[43m \u001b[49m\u001b[38;5;241;43m*\u001b[39;49m\u001b[38;5;241;43m*\u001b[39;49m\u001b[43mkwargs\u001b[49m\u001b[43m)\u001b[49m\n\u001b[1;32m 151\u001b[0m \u001b[38;5;28;01mexcept\u001b[39;00m \u001b[38;5;167;01mException\u001b[39;00m \u001b[38;5;28;01mas\u001b[39;00m e:\n\u001b[1;32m 152\u001b[0m filtered_tb \u001b[38;5;241m=\u001b[39m _process_traceback_frames(e\u001b[38;5;241m.\u001b[39m__traceback__)\n",
|
|
"File \u001b[0;32m~/miniforge3/envs/myenv/lib/python3.9/site-packages/tensorflow/python/eager/def_function.py:915\u001b[0m, in \u001b[0;36mFunction.__call__\u001b[0;34m(self, *args, **kwds)\u001b[0m\n\u001b[1;32m 912\u001b[0m compiler \u001b[38;5;241m=\u001b[39m \u001b[38;5;124m\"\u001b[39m\u001b[38;5;124mxla\u001b[39m\u001b[38;5;124m\"\u001b[39m \u001b[38;5;28;01mif\u001b[39;00m \u001b[38;5;28mself\u001b[39m\u001b[38;5;241m.\u001b[39m_jit_compile \u001b[38;5;28;01melse\u001b[39;00m \u001b[38;5;124m\"\u001b[39m\u001b[38;5;124mnonXla\u001b[39m\u001b[38;5;124m\"\u001b[39m\n\u001b[1;32m 914\u001b[0m \u001b[38;5;28;01mwith\u001b[39;00m OptionalXlaContext(\u001b[38;5;28mself\u001b[39m\u001b[38;5;241m.\u001b[39m_jit_compile):\n\u001b[0;32m--> 915\u001b[0m result \u001b[38;5;241m=\u001b[39m \u001b[38;5;28;43mself\u001b[39;49m\u001b[38;5;241;43m.\u001b[39;49m\u001b[43m_call\u001b[49m\u001b[43m(\u001b[49m\u001b[38;5;241;43m*\u001b[39;49m\u001b[43margs\u001b[49m\u001b[43m,\u001b[49m\u001b[43m \u001b[49m\u001b[38;5;241;43m*\u001b[39;49m\u001b[38;5;241;43m*\u001b[39;49m\u001b[43mkwds\u001b[49m\u001b[43m)\u001b[49m\n\u001b[1;32m 917\u001b[0m new_tracing_count \u001b[38;5;241m=\u001b[39m \u001b[38;5;28mself\u001b[39m\u001b[38;5;241m.\u001b[39mexperimental_get_tracing_count()\n\u001b[1;32m 918\u001b[0m without_tracing \u001b[38;5;241m=\u001b[39m (tracing_count \u001b[38;5;241m==\u001b[39m new_tracing_count)\n",
|
|
"File \u001b[0;32m~/miniforge3/envs/myenv/lib/python3.9/site-packages/tensorflow/python/eager/def_function.py:947\u001b[0m, in \u001b[0;36mFunction._call\u001b[0;34m(self, *args, **kwds)\u001b[0m\n\u001b[1;32m 944\u001b[0m \u001b[38;5;28mself\u001b[39m\u001b[38;5;241m.\u001b[39m_lock\u001b[38;5;241m.\u001b[39mrelease()\n\u001b[1;32m 945\u001b[0m \u001b[38;5;66;03m# In this case we have created variables on the first call, so we run the\u001b[39;00m\n\u001b[1;32m 946\u001b[0m \u001b[38;5;66;03m# defunned version which is guaranteed to never create variables.\u001b[39;00m\n\u001b[0;32m--> 947\u001b[0m \u001b[38;5;28;01mreturn\u001b[39;00m \u001b[38;5;28;43mself\u001b[39;49m\u001b[38;5;241;43m.\u001b[39;49m\u001b[43m_stateless_fn\u001b[49m\u001b[43m(\u001b[49m\u001b[38;5;241;43m*\u001b[39;49m\u001b[43margs\u001b[49m\u001b[43m,\u001b[49m\u001b[43m \u001b[49m\u001b[38;5;241;43m*\u001b[39;49m\u001b[38;5;241;43m*\u001b[39;49m\u001b[43mkwds\u001b[49m\u001b[43m)\u001b[49m \u001b[38;5;66;03m# pylint: disable=not-callable\u001b[39;00m\n\u001b[1;32m 948\u001b[0m \u001b[38;5;28;01melif\u001b[39;00m \u001b[38;5;28mself\u001b[39m\u001b[38;5;241m.\u001b[39m_stateful_fn \u001b[38;5;129;01mis\u001b[39;00m \u001b[38;5;129;01mnot\u001b[39;00m \u001b[38;5;28;01mNone\u001b[39;00m:\n\u001b[1;32m 949\u001b[0m \u001b[38;5;66;03m# Release the lock early so that multiple threads can perform the call\u001b[39;00m\n\u001b[1;32m 950\u001b[0m \u001b[38;5;66;03m# in parallel.\u001b[39;00m\n\u001b[1;32m 951\u001b[0m \u001b[38;5;28mself\u001b[39m\u001b[38;5;241m.\u001b[39m_lock\u001b[38;5;241m.\u001b[39mrelease()\n",
|
|
"File \u001b[0;32m~/miniforge3/envs/myenv/lib/python3.9/site-packages/tensorflow/python/eager/function.py:2956\u001b[0m, in \u001b[0;36mFunction.__call__\u001b[0;34m(self, *args, **kwargs)\u001b[0m\n\u001b[1;32m 2953\u001b[0m \u001b[38;5;28;01mwith\u001b[39;00m \u001b[38;5;28mself\u001b[39m\u001b[38;5;241m.\u001b[39m_lock:\n\u001b[1;32m 2954\u001b[0m (graph_function,\n\u001b[1;32m 2955\u001b[0m filtered_flat_args) \u001b[38;5;241m=\u001b[39m \u001b[38;5;28mself\u001b[39m\u001b[38;5;241m.\u001b[39m_maybe_define_function(args, kwargs)\n\u001b[0;32m-> 2956\u001b[0m \u001b[38;5;28;01mreturn\u001b[39;00m \u001b[43mgraph_function\u001b[49m\u001b[38;5;241;43m.\u001b[39;49m\u001b[43m_call_flat\u001b[49m\u001b[43m(\u001b[49m\n\u001b[1;32m 2957\u001b[0m \u001b[43m \u001b[49m\u001b[43mfiltered_flat_args\u001b[49m\u001b[43m,\u001b[49m\u001b[43m \u001b[49m\u001b[43mcaptured_inputs\u001b[49m\u001b[38;5;241;43m=\u001b[39;49m\u001b[43mgraph_function\u001b[49m\u001b[38;5;241;43m.\u001b[39;49m\u001b[43mcaptured_inputs\u001b[49m\u001b[43m)\u001b[49m\n",
|
|
"File \u001b[0;32m~/miniforge3/envs/myenv/lib/python3.9/site-packages/tensorflow/python/eager/function.py:1853\u001b[0m, in \u001b[0;36mConcreteFunction._call_flat\u001b[0;34m(self, args, captured_inputs, cancellation_manager)\u001b[0m\n\u001b[1;32m 1849\u001b[0m possible_gradient_type \u001b[38;5;241m=\u001b[39m gradients_util\u001b[38;5;241m.\u001b[39mPossibleTapeGradientTypes(args)\n\u001b[1;32m 1850\u001b[0m \u001b[38;5;28;01mif\u001b[39;00m (possible_gradient_type \u001b[38;5;241m==\u001b[39m gradients_util\u001b[38;5;241m.\u001b[39mPOSSIBLE_GRADIENT_TYPES_NONE\n\u001b[1;32m 1851\u001b[0m \u001b[38;5;129;01mand\u001b[39;00m executing_eagerly):\n\u001b[1;32m 1852\u001b[0m \u001b[38;5;66;03m# No tape is watching; skip to running the function.\u001b[39;00m\n\u001b[0;32m-> 1853\u001b[0m \u001b[38;5;28;01mreturn\u001b[39;00m \u001b[38;5;28mself\u001b[39m\u001b[38;5;241m.\u001b[39m_build_call_outputs(\u001b[38;5;28;43mself\u001b[39;49m\u001b[38;5;241;43m.\u001b[39;49m\u001b[43m_inference_function\u001b[49m\u001b[38;5;241;43m.\u001b[39;49m\u001b[43mcall\u001b[49m\u001b[43m(\u001b[49m\n\u001b[1;32m 1854\u001b[0m \u001b[43m \u001b[49m\u001b[43mctx\u001b[49m\u001b[43m,\u001b[49m\u001b[43m \u001b[49m\u001b[43margs\u001b[49m\u001b[43m,\u001b[49m\u001b[43m \u001b[49m\u001b[43mcancellation_manager\u001b[49m\u001b[38;5;241;43m=\u001b[39;49m\u001b[43mcancellation_manager\u001b[49m\u001b[43m)\u001b[49m)\n\u001b[1;32m 1855\u001b[0m forward_backward \u001b[38;5;241m=\u001b[39m \u001b[38;5;28mself\u001b[39m\u001b[38;5;241m.\u001b[39m_select_forward_and_backward_functions(\n\u001b[1;32m 1856\u001b[0m args,\n\u001b[1;32m 1857\u001b[0m possible_gradient_type,\n\u001b[1;32m 1858\u001b[0m executing_eagerly)\n\u001b[1;32m 1859\u001b[0m forward_function, args_with_tangents \u001b[38;5;241m=\u001b[39m forward_backward\u001b[38;5;241m.\u001b[39mforward()\n",
|
|
"File \u001b[0;32m~/miniforge3/envs/myenv/lib/python3.9/site-packages/tensorflow/python/eager/function.py:499\u001b[0m, in \u001b[0;36m_EagerDefinedFunction.call\u001b[0;34m(self, ctx, args, cancellation_manager)\u001b[0m\n\u001b[1;32m 497\u001b[0m \u001b[38;5;28;01mwith\u001b[39;00m _InterpolateFunctionError(\u001b[38;5;28mself\u001b[39m):\n\u001b[1;32m 498\u001b[0m \u001b[38;5;28;01mif\u001b[39;00m cancellation_manager \u001b[38;5;129;01mis\u001b[39;00m \u001b[38;5;28;01mNone\u001b[39;00m:\n\u001b[0;32m--> 499\u001b[0m outputs \u001b[38;5;241m=\u001b[39m \u001b[43mexecute\u001b[49m\u001b[38;5;241;43m.\u001b[39;49m\u001b[43mexecute\u001b[49m\u001b[43m(\u001b[49m\n\u001b[1;32m 500\u001b[0m \u001b[43m \u001b[49m\u001b[38;5;28;43mstr\u001b[39;49m\u001b[43m(\u001b[49m\u001b[38;5;28;43mself\u001b[39;49m\u001b[38;5;241;43m.\u001b[39;49m\u001b[43msignature\u001b[49m\u001b[38;5;241;43m.\u001b[39;49m\u001b[43mname\u001b[49m\u001b[43m)\u001b[49m\u001b[43m,\u001b[49m\n\u001b[1;32m 501\u001b[0m \u001b[43m \u001b[49m\u001b[43mnum_outputs\u001b[49m\u001b[38;5;241;43m=\u001b[39;49m\u001b[38;5;28;43mself\u001b[39;49m\u001b[38;5;241;43m.\u001b[39;49m\u001b[43m_num_outputs\u001b[49m\u001b[43m,\u001b[49m\n\u001b[1;32m 502\u001b[0m \u001b[43m \u001b[49m\u001b[43minputs\u001b[49m\u001b[38;5;241;43m=\u001b[39;49m\u001b[43margs\u001b[49m\u001b[43m,\u001b[49m\n\u001b[1;32m 503\u001b[0m \u001b[43m \u001b[49m\u001b[43mattrs\u001b[49m\u001b[38;5;241;43m=\u001b[39;49m\u001b[43mattrs\u001b[49m\u001b[43m,\u001b[49m\n\u001b[1;32m 504\u001b[0m \u001b[43m \u001b[49m\u001b[43mctx\u001b[49m\u001b[38;5;241;43m=\u001b[39;49m\u001b[43mctx\u001b[49m\u001b[43m)\u001b[49m\n\u001b[1;32m 505\u001b[0m \u001b[38;5;28;01melse\u001b[39;00m:\n\u001b[1;32m 506\u001b[0m outputs \u001b[38;5;241m=\u001b[39m execute\u001b[38;5;241m.\u001b[39mexecute_with_cancellation(\n\u001b[1;32m 507\u001b[0m \u001b[38;5;28mstr\u001b[39m(\u001b[38;5;28mself\u001b[39m\u001b[38;5;241m.\u001b[39msignature\u001b[38;5;241m.\u001b[39mname),\n\u001b[1;32m 508\u001b[0m num_outputs\u001b[38;5;241m=\u001b[39m\u001b[38;5;28mself\u001b[39m\u001b[38;5;241m.\u001b[39m_num_outputs,\n\u001b[0;32m (...)\u001b[0m\n\u001b[1;32m 511\u001b[0m ctx\u001b[38;5;241m=\u001b[39mctx,\n\u001b[1;32m 512\u001b[0m cancellation_manager\u001b[38;5;241m=\u001b[39mcancellation_manager)\n",
|
|
"File \u001b[0;32m~/miniforge3/envs/myenv/lib/python3.9/site-packages/tensorflow/python/eager/execute.py:54\u001b[0m, in \u001b[0;36mquick_execute\u001b[0;34m(op_name, num_outputs, inputs, attrs, ctx, name)\u001b[0m\n\u001b[1;32m 52\u001b[0m \u001b[38;5;28;01mtry\u001b[39;00m:\n\u001b[1;32m 53\u001b[0m ctx\u001b[38;5;241m.\u001b[39mensure_initialized()\n\u001b[0;32m---> 54\u001b[0m tensors \u001b[38;5;241m=\u001b[39m \u001b[43mpywrap_tfe\u001b[49m\u001b[38;5;241;43m.\u001b[39;49m\u001b[43mTFE_Py_Execute\u001b[49m\u001b[43m(\u001b[49m\u001b[43mctx\u001b[49m\u001b[38;5;241;43m.\u001b[39;49m\u001b[43m_handle\u001b[49m\u001b[43m,\u001b[49m\u001b[43m \u001b[49m\u001b[43mdevice_name\u001b[49m\u001b[43m,\u001b[49m\u001b[43m \u001b[49m\u001b[43mop_name\u001b[49m\u001b[43m,\u001b[49m\n\u001b[1;32m 55\u001b[0m \u001b[43m \u001b[49m\u001b[43minputs\u001b[49m\u001b[43m,\u001b[49m\u001b[43m \u001b[49m\u001b[43mattrs\u001b[49m\u001b[43m,\u001b[49m\u001b[43m \u001b[49m\u001b[43mnum_outputs\u001b[49m\u001b[43m)\u001b[49m\n\u001b[1;32m 56\u001b[0m \u001b[38;5;28;01mexcept\u001b[39;00m core\u001b[38;5;241m.\u001b[39m_NotOkStatusException \u001b[38;5;28;01mas\u001b[39;00m e:\n\u001b[1;32m 57\u001b[0m \u001b[38;5;28;01mif\u001b[39;00m name \u001b[38;5;129;01mis\u001b[39;00m \u001b[38;5;129;01mnot\u001b[39;00m \u001b[38;5;28;01mNone\u001b[39;00m:\n",
|
|
"\u001b[0;31mKeyboardInterrupt\u001b[0m: "
|
|
]
|
|
}
|
|
],
|
|
"source": [
|
|
"%matplotlib inline\n",
|
|
"\n",
|
|
"# Start importing packages\n",
|
|
"import pandas as pd\n",
|
|
"import numpy as np\n",
|
|
"import matplotlib.pyplot as plt\n",
|
|
"import tensorflow as tf\n",
|
|
"from tensorflow.keras import datasets, layers, models\n",
|
|
"from tensorflow.keras.layers import Input\n",
|
|
"from tensorflow.keras.models import Model, Sequential \n",
|
|
"from tensorflow.keras.layers import Dense, SimpleRNN, LSTM, GRU\n",
|
|
"from tensorflow.keras import optimizers \n",
|
|
"from tensorflow.keras import regularizers \n",
|
|
"from tensorflow.keras.utils import to_categorical \n",
|
|
"\n",
|
|
"\n",
|
|
"\n",
|
|
"# convert into dataset matrix\n",
|
|
"def convertToMatrix(data, step):\n",
|
|
" X, Y =[], []\n",
|
|
" for i in range(len(data)-step):\n",
|
|
" d=i+step \n",
|
|
" X.append(data[i:d,])\n",
|
|
" Y.append(data[d,])\n",
|
|
" return np.array(X), np.array(Y)\n",
|
|
"\n",
|
|
"step = 4\n",
|
|
"N = 1000 \n",
|
|
"Tp = 800 \n",
|
|
"\n",
|
|
"t=np.arange(0,N)\n",
|
|
"x=np.sin(0.02*t)+2*np.random.rand(N)\n",
|
|
"df = pd.DataFrame(x)\n",
|
|
"df.head()\n",
|
|
"\n",
|
|
"plt.plot(df)\n",
|
|
"plt.show()\n",
|
|
"\n",
|
|
"values=df.values\n",
|
|
"train,test = values[0:Tp,:], values[Tp:N,:]\n",
|
|
"\n",
|
|
"# add step elements into train and test\n",
|
|
"test = np.append(test,np.repeat(test[-1,],step))\n",
|
|
"train = np.append(train,np.repeat(train[-1,],step))\n",
|
|
" \n",
|
|
"trainX,trainY =convertToMatrix(train,step)\n",
|
|
"testX,testY =convertToMatrix(test,step)\n",
|
|
"trainX = np.reshape(trainX, (trainX.shape[0], 1, trainX.shape[1]))\n",
|
|
"testX = np.reshape(testX, (testX.shape[0], 1, testX.shape[1]))\n",
|
|
"\n",
|
|
"model = Sequential()\n",
|
|
"model.add(SimpleRNN(units=32, input_shape=(1,step), activation=\"relu\"))\n",
|
|
"model.add(Dense(8, activation=\"relu\")) \n",
|
|
"model.add(Dense(1))\n",
|
|
"model.compile(loss='mean_squared_error', optimizer='rmsprop')\n",
|
|
"model.summary()\n",
|
|
"\n",
|
|
"model.fit(trainX,trainY, epochs=100, batch_size=16, verbose=2)\n",
|
|
"trainPredict = model.predict(trainX)\n",
|
|
"testPredict= model.predict(testX)\n",
|
|
"predicted=np.concatenate((trainPredict,testPredict),axis=0)\n",
|
|
"\n",
|
|
"trainScore = model.evaluate(trainX, trainY, verbose=0)\n",
|
|
"print(trainScore)\n",
|
|
"\n",
|
|
"index = df.index.values\n",
|
|
"plt.plot(index,df)\n",
|
|
"plt.plot(index,predicted)\n",
|
|
"plt.axvline(df.index[Tp], c=\"r\")\n",
|
|
"plt.show()"
|
|
]
|
|
},
|
|
{
|
|
"cell_type": "markdown",
|
|
"id": "897a47c7",
|
|
"metadata": {
|
|
"editable": true
|
|
},
|
|
"source": [
|
|
"## An extrapolation example\n",
|
|
"\n",
|
|
"The following code provides an example of how recurrent neural\n",
|
|
"networks can be used to extrapolate to unknown values of physics data\n",
|
|
"sets. Specifically, the data sets used in this program come from\n",
|
|
"a quantum mechanical many-body calculation of energies as functions of the number of particles."
|
|
]
|
|
},
|
|
{
|
|
"cell_type": "code",
|
|
"execution_count": 2,
|
|
"id": "6776ae2a",
|
|
"metadata": {
|
|
"collapsed": false,
|
|
"editable": true
|
|
},
|
|
"outputs": [],
|
|
"source": [
|
|
"\n",
|
|
"# For matrices and calculations\n",
|
|
"import numpy as np\n",
|
|
"# For machine learning (backend for keras)\n",
|
|
"import tensorflow as tf\n",
|
|
"# User-friendly machine learning library\n",
|
|
"# Front end for TensorFlow\n",
|
|
"import tensorflow.keras\n",
|
|
"# Different methods from Keras needed to create an RNN\n",
|
|
"# This is not necessary but it shortened function calls \n",
|
|
"# that need to be used in the code.\n",
|
|
"from tensorflow.keras import datasets, layers, models\n",
|
|
"from tensorflow.keras.layers import Input\n",
|
|
"from tensorflow.keras import regularizers\n",
|
|
"from tensorflow.keras.models import Model, Sequential\n",
|
|
"from tensorflow.keras.layers import Dense, SimpleRNN, LSTM, GRU\n",
|
|
"# For timing the code\n",
|
|
"from timeit import default_timer as timer\n",
|
|
"# For plotting\n",
|
|
"import matplotlib.pyplot as plt\n",
|
|
"\n",
|
|
"\n",
|
|
"# The data set\n",
|
|
"datatype='VaryDimension'\n",
|
|
"X_tot = np.arange(2, 42, 2)\n",
|
|
"y_tot = np.array([-0.03077640549, -0.08336233266, -0.1446729567, -0.2116753732, -0.2830637392, -0.3581341341, -0.436462435, -0.5177783846,\n",
|
|
"\t-0.6019067271, -0.6887363571, -0.7782028952, -0.8702784034, -0.9649652536, -1.062292565, -1.16231451, \n",
|
|
"\t-1.265109911, -1.370782966, -1.479465113, -1.591317992, -1.70653767])"
|
|
]
|
|
},
|
|
{
|
|
"cell_type": "markdown",
|
|
"id": "35227d36",
|
|
"metadata": {
|
|
"editable": true
|
|
},
|
|
"source": [
|
|
"The way the recurrent neural networks are trained in this program\n",
|
|
"differs from how machine learning algorithms are usually trained.\n",
|
|
"Typically a machine learning algorithm is trained by learning the\n",
|
|
"relationship between the x data and the y data. In this program, the\n",
|
|
"recurrent neural network will be trained to recognize the relationship\n",
|
|
"in a sequence of y values. This is type of data formatting is\n",
|
|
"typically used time series forcasting, but it can also be used in any\n",
|
|
"extrapolation (time series forecasting is just a specific type of\n",
|
|
"extrapolation along the time axis). This method of data formatting\n",
|
|
"does not use the x data and assumes that the y data are evenly spaced.\n",
|
|
"\n",
|
|
"For a standard machine learning algorithm, the training data has the\n",
|
|
"form of (x,y) so the machine learning algorithm learns to assiciate a\n",
|
|
"y value with a given x value. This is useful when the test data has x\n",
|
|
"values within the same range as the training data. However, for this\n",
|
|
"application, the x values of the test data are outside of the x values\n",
|
|
"of the training data and the traditional method of training a machine\n",
|
|
"learning algorithm does not work as well. For this reason, the\n",
|
|
"recurrent neural network is trained on sequences of y values of the\n",
|
|
"form ((y1, y2), y3), so that the network is concerned with learning\n",
|
|
"the pattern of the y data and not the relation between the x and y\n",
|
|
"data. As long as the pattern of y data outside of the training region\n",
|
|
"stays relatively stable compared to what was inside the training\n",
|
|
"region, this method of training can produce accurate extrapolations to\n",
|
|
"y values far removed from the training data set."
|
|
]
|
|
},
|
|
{
|
|
"cell_type": "code",
|
|
"execution_count": 3,
|
|
"id": "7dc577d1",
|
|
"metadata": {
|
|
"collapsed": false,
|
|
"editable": true
|
|
},
|
|
"outputs": [],
|
|
"source": [
|
|
"# FORMAT_DATA\n",
|
|
"def format_data(data, length_of_sequence = 2): \n",
|
|
" \"\"\"\n",
|
|
" Inputs:\n",
|
|
" data(a numpy array): the data that will be the inputs to the recurrent neural\n",
|
|
" network\n",
|
|
" length_of_sequence (an int): the number of elements in one iteration of the\n",
|
|
" sequence patter. For a function approximator use length_of_sequence = 2.\n",
|
|
" Returns:\n",
|
|
" rnn_input (a 3D numpy array): the input data for the recurrent neural network. Its\n",
|
|
" dimensions are length of data - length of sequence, length of sequence, \n",
|
|
" dimnsion of data\n",
|
|
" rnn_output (a numpy array): the training data for the neural network\n",
|
|
" Formats data to be used in a recurrent neural network.\n",
|
|
" \"\"\"\n",
|
|
"\n",
|
|
" X, Y = [], []\n",
|
|
" for i in range(len(data)-length_of_sequence):\n",
|
|
" # Get the next length_of_sequence elements\n",
|
|
" a = data[i:i+length_of_sequence]\n",
|
|
" # Get the element that immediately follows that\n",
|
|
" b = data[i+length_of_sequence]\n",
|
|
" # Reshape so that each data point is contained in its own array\n",
|
|
" a = np.reshape (a, (len(a), 1))\n",
|
|
" X.append(a)\n",
|
|
" Y.append(b)\n",
|
|
" rnn_input = np.array(X)\n",
|
|
" rnn_output = np.array(Y)\n",
|
|
"\n",
|
|
" return rnn_input, rnn_output\n",
|
|
"\n",
|
|
"\n",
|
|
"# ## Defining the Recurrent Neural Network Using Keras\n",
|
|
"# \n",
|
|
"# The following method defines a simple recurrent neural network in keras consisting of one input layer, one hidden layer, and one output layer.\n",
|
|
"\n",
|
|
"def rnn(length_of_sequences, batch_size = None, stateful = False):\n",
|
|
" \"\"\"\n",
|
|
" Inputs:\n",
|
|
" length_of_sequences (an int): the number of y values in \"x data\". This is determined\n",
|
|
" when the data is formatted\n",
|
|
" batch_size (an int): Default value is None. See Keras documentation of SimpleRNN.\n",
|
|
" stateful (a boolean): Default value is False. See Keras documentation of SimpleRNN.\n",
|
|
" Returns:\n",
|
|
" model (a Keras model): The recurrent neural network that is built and compiled by this\n",
|
|
" method\n",
|
|
" Builds and compiles a recurrent neural network with one hidden layer and returns the model.\n",
|
|
" \"\"\"\n",
|
|
" # Number of neurons in the input and output layers\n",
|
|
" in_out_neurons = 1\n",
|
|
" # Number of neurons in the hidden layer\n",
|
|
" hidden_neurons = 200\n",
|
|
" # Define the input layer\n",
|
|
" inp = Input(batch_shape=(batch_size, \n",
|
|
" length_of_sequences, \n",
|
|
" in_out_neurons)) \n",
|
|
" # Define the hidden layer as a simple RNN layer with a set number of neurons and add it to \n",
|
|
" # the network immediately after the input layer\n",
|
|
" rnn = SimpleRNN(hidden_neurons, \n",
|
|
" return_sequences=False,\n",
|
|
" stateful = stateful,\n",
|
|
" name=\"RNN\")(inp)\n",
|
|
" # Define the output layer as a dense neural network layer (standard neural network layer)\n",
|
|
" #and add it to the network immediately after the hidden layer.\n",
|
|
" dens = Dense(in_out_neurons,name=\"dense\")(rnn)\n",
|
|
" # Create the machine learning model starting with the input layer and ending with the \n",
|
|
" # output layer\n",
|
|
" model = Model(inputs=[inp],outputs=[dens])\n",
|
|
" # Compile the machine learning model using the mean squared error function as the loss \n",
|
|
" # function and an Adams optimizer.\n",
|
|
" model.compile(loss=\"mean_squared_error\", optimizer=\"adam\") \n",
|
|
" return model"
|
|
]
|
|
},
|
|
{
|
|
"cell_type": "markdown",
|
|
"id": "1978b6a1",
|
|
"metadata": {
|
|
"editable": true
|
|
},
|
|
"source": [
|
|
"## Predicting New Points With A Trained Recurrent Neural Network"
|
|
]
|
|
},
|
|
{
|
|
"cell_type": "code",
|
|
"execution_count": 4,
|
|
"id": "d4ad417a",
|
|
"metadata": {
|
|
"collapsed": false,
|
|
"editable": true
|
|
},
|
|
"outputs": [],
|
|
"source": [
|
|
"def test_rnn (x1, y_test, plot_min, plot_max):\n",
|
|
" \"\"\"\n",
|
|
" Inputs:\n",
|
|
" x1 (a list or numpy array): The complete x component of the data set\n",
|
|
" y_test (a list or numpy array): The complete y component of the data set\n",
|
|
" plot_min (an int or float): the smallest x value used in the training data\n",
|
|
" plot_max (an int or float): the largest x valye used in the training data\n",
|
|
" Returns:\n",
|
|
" None.\n",
|
|
" Uses a trained recurrent neural network model to predict future points in the \n",
|
|
" series. Computes the MSE of the predicted data set from the true data set, saves\n",
|
|
" the predicted data set to a csv file, and plots the predicted and true data sets w\n",
|
|
" while also displaying the data range used for training.\n",
|
|
" \"\"\"\n",
|
|
" # Add the training data as the first dim points in the predicted data array as these\n",
|
|
" # are known values.\n",
|
|
" y_pred = y_test[:dim].tolist()\n",
|
|
" # Generate the first input to the trained recurrent neural network using the last two \n",
|
|
" # points of the training data. Based on how the network was trained this means that it\n",
|
|
" # will predict the first point in the data set after the training data. All of the \n",
|
|
" # brackets are necessary for Tensorflow.\n",
|
|
" next_input = np.array([[[y_test[dim-2]], [y_test[dim-1]]]])\n",
|
|
" # Save the very last point in the training data set. This will be used later.\n",
|
|
" last = [y_test[dim-1]]\n",
|
|
"\n",
|
|
" # Iterate until the complete data set is created.\n",
|
|
" for i in range (dim, len(y_test)):\n",
|
|
" # Predict the next point in the data set using the previous two points.\n",
|
|
" next = model.predict(next_input)\n",
|
|
" # Append just the number of the predicted data set\n",
|
|
" y_pred.append(next[0][0])\n",
|
|
" # Create the input that will be used to predict the next data point in the data set.\n",
|
|
" next_input = np.array([[last, next[0]]], dtype=np.float64)\n",
|
|
" last = next\n",
|
|
"\n",
|
|
" # Print the mean squared error between the known data set and the predicted data set.\n",
|
|
" print('MSE: ', np.square(np.subtract(y_test, y_pred)).mean())\n",
|
|
" # Save the predicted data set as a csv file for later use\n",
|
|
" name = datatype + 'Predicted'+str(dim)+'.csv'\n",
|
|
" np.savetxt(name, y_pred, delimiter=',')\n",
|
|
" # Plot the known data set and the predicted data set. The red box represents the region that was used\n",
|
|
" # for the training data.\n",
|
|
" fig, ax = plt.subplots()\n",
|
|
" ax.plot(x1, y_test, label=\"true\", linewidth=3)\n",
|
|
" ax.plot(x1, y_pred, 'g-.',label=\"predicted\", linewidth=4)\n",
|
|
" ax.legend()\n",
|
|
" # Created a red region to represent the points used in the training data.\n",
|
|
" ax.axvspan(plot_min, plot_max, alpha=0.25, color='red')\n",
|
|
" plt.show()\n",
|
|
"\n",
|
|
"# Check to make sure the data set is complete\n",
|
|
"assert len(X_tot) == len(y_tot)\n",
|
|
"\n",
|
|
"# This is the number of points that will be used in as the training data\n",
|
|
"dim=12\n",
|
|
"\n",
|
|
"# Separate the training data from the whole data set\n",
|
|
"X_train = X_tot[:dim]\n",
|
|
"y_train = y_tot[:dim]\n",
|
|
"\n",
|
|
"\n",
|
|
"# Generate the training data for the RNN, using a sequence of 2\n",
|
|
"rnn_input, rnn_training = format_data(y_train, 2)\n",
|
|
"\n",
|
|
"\n",
|
|
"# Create a recurrent neural network in Keras and produce a summary of the \n",
|
|
"# machine learning model\n",
|
|
"model = rnn(length_of_sequences = rnn_input.shape[1])\n",
|
|
"model.summary()\n",
|
|
"\n",
|
|
"# Start the timer. Want to time training+testing\n",
|
|
"start = timer()\n",
|
|
"# Fit the model using the training data genenerated above using 150 training iterations and a 5%\n",
|
|
"# validation split. Setting verbose to True prints information about each training iteration.\n",
|
|
"hist = model.fit(rnn_input, rnn_training, batch_size=None, epochs=150, \n",
|
|
" verbose=True,validation_split=0.05)\n",
|
|
"\n",
|
|
"for label in [\"loss\",\"val_loss\"]:\n",
|
|
" plt.plot(hist.history[label],label=label)\n",
|
|
"\n",
|
|
"plt.ylabel(\"loss\")\n",
|
|
"plt.xlabel(\"epoch\")\n",
|
|
"plt.title(\"The final validation loss: {}\".format(hist.history[\"val_loss\"][-1]))\n",
|
|
"plt.legend()\n",
|
|
"plt.show()\n",
|
|
"\n",
|
|
"# Use the trained neural network to predict more points of the data set\n",
|
|
"test_rnn(X_tot, y_tot, X_tot[0], X_tot[dim-1])\n",
|
|
"# Stop the timer and calculate the total time needed.\n",
|
|
"end = timer()\n",
|
|
"print('Time: ', end-start)"
|
|
]
|
|
},
|
|
{
|
|
"cell_type": "markdown",
|
|
"id": "e2cad4fc",
|
|
"metadata": {
|
|
"editable": true
|
|
},
|
|
"source": [
|
|
"Changing the size of the recurrent neural network and its parameters\n",
|
|
"can drastically change the results you get from the model. The below\n",
|
|
"code takes the simple recurrent neural network from above and adds a\n",
|
|
"second hidden layer, changes the number of neurons in the hidden\n",
|
|
"layer, and explicitly declares the activation function of the hidden\n",
|
|
"layers to be a sigmoid function. The loss function and optimizer can\n",
|
|
"also be changed but are kept the same as the above network. These\n",
|
|
"parameters can be tuned to provide the optimal result from the\n",
|
|
"network. For some ideas on how to improve the performance of a\n",
|
|
"[recurrent neural network](https://danijar.com/tips-for-training-recurrent-neural-networks)."
|
|
]
|
|
},
|
|
{
|
|
"cell_type": "code",
|
|
"execution_count": 5,
|
|
"id": "c39f1516",
|
|
"metadata": {
|
|
"collapsed": false,
|
|
"editable": true
|
|
},
|
|
"outputs": [],
|
|
"source": [
|
|
"def rnn_2layers(length_of_sequences, batch_size = None, stateful = False):\n",
|
|
" \"\"\"\n",
|
|
" Inputs:\n",
|
|
" length_of_sequences (an int): the number of y values in \"x data\". This is determined\n",
|
|
" when the data is formatted\n",
|
|
" batch_size (an int): Default value is None. See Keras documentation of SimpleRNN.\n",
|
|
" stateful (a boolean): Default value is False. See Keras documentation of SimpleRNN.\n",
|
|
" Returns:\n",
|
|
" model (a Keras model): The recurrent neural network that is built and compiled by this\n",
|
|
" method\n",
|
|
" Builds and compiles a recurrent neural network with two hidden layers and returns the model.\n",
|
|
" \"\"\"\n",
|
|
" # Number of neurons in the input and output layers\n",
|
|
" in_out_neurons = 1\n",
|
|
" # Number of neurons in the hidden layer, increased from the first network\n",
|
|
" hidden_neurons = 500\n",
|
|
" # Define the input layer\n",
|
|
" inp = Input(batch_shape=(batch_size, \n",
|
|
" length_of_sequences, \n",
|
|
" in_out_neurons)) \n",
|
|
" # Create two hidden layers instead of one hidden layer. Explicitly set the activation\n",
|
|
" # function to be the sigmoid function (the default value is hyperbolic tangent)\n",
|
|
" rnn1 = SimpleRNN(hidden_neurons, \n",
|
|
" return_sequences=True, # This needs to be True if another hidden layer is to follow\n",
|
|
" stateful = stateful, activation = 'sigmoid',\n",
|
|
" name=\"RNN1\")(inp)\n",
|
|
" rnn2 = SimpleRNN(hidden_neurons, \n",
|
|
" return_sequences=False, activation = 'sigmoid',\n",
|
|
" stateful = stateful,\n",
|
|
" name=\"RNN2\")(rnn1)\n",
|
|
" # Define the output layer as a dense neural network layer (standard neural network layer)\n",
|
|
" #and add it to the network immediately after the hidden layer.\n",
|
|
" dens = Dense(in_out_neurons,name=\"dense\")(rnn2)\n",
|
|
" # Create the machine learning model starting with the input layer and ending with the \n",
|
|
" # output layer\n",
|
|
" model = Model(inputs=[inp],outputs=[dens])\n",
|
|
" # Compile the machine learning model using the mean squared error function as the loss \n",
|
|
" # function and an Adams optimizer.\n",
|
|
" model.compile(loss=\"mean_squared_error\", optimizer=\"adam\") \n",
|
|
" return model\n",
|
|
"\n",
|
|
"# Check to make sure the data set is complete\n",
|
|
"assert len(X_tot) == len(y_tot)\n",
|
|
"\n",
|
|
"# This is the number of points that will be used in as the training data\n",
|
|
"dim=12\n",
|
|
"\n",
|
|
"# Separate the training data from the whole data set\n",
|
|
"X_train = X_tot[:dim]\n",
|
|
"y_train = y_tot[:dim]\n",
|
|
"\n",
|
|
"\n",
|
|
"# Generate the training data for the RNN, using a sequence of 2\n",
|
|
"rnn_input, rnn_training = format_data(y_train, 2)\n",
|
|
"\n",
|
|
"\n",
|
|
"# Create a recurrent neural network in Keras and produce a summary of the \n",
|
|
"# machine learning model\n",
|
|
"model = rnn_2layers(length_of_sequences = 2)\n",
|
|
"model.summary()\n",
|
|
"\n",
|
|
"# Start the timer. Want to time training+testing\n",
|
|
"start = timer()\n",
|
|
"# Fit the model using the training data genenerated above using 150 training iterations and a 5%\n",
|
|
"# validation split. Setting verbose to True prints information about each training iteration.\n",
|
|
"hist = model.fit(rnn_input, rnn_training, batch_size=None, epochs=150, \n",
|
|
" verbose=True,validation_split=0.05)\n",
|
|
"\n",
|
|
"\n",
|
|
"# This section plots the training loss and the validation loss as a function of training iteration.\n",
|
|
"# This is not required for analyzing the couple cluster data but can help determine if the network is\n",
|
|
"# being overtrained.\n",
|
|
"for label in [\"loss\",\"val_loss\"]:\n",
|
|
" plt.plot(hist.history[label],label=label)\n",
|
|
"\n",
|
|
"plt.ylabel(\"loss\")\n",
|
|
"plt.xlabel(\"epoch\")\n",
|
|
"plt.title(\"The final validation loss: {}\".format(hist.history[\"val_loss\"][-1]))\n",
|
|
"plt.legend()\n",
|
|
"plt.show()\n",
|
|
"\n",
|
|
"# Use the trained neural network to predict more points of the data set\n",
|
|
"test_rnn(X_tot, y_tot, X_tot[0], X_tot[dim-1])\n",
|
|
"# Stop the timer and calculate the total time needed.\n",
|
|
"end = timer()\n",
|
|
"print('Time: ', end-start)"
|
|
]
|
|
},
|
|
{
|
|
"cell_type": "markdown",
|
|
"id": "842c7602",
|
|
"metadata": {
|
|
"editable": true
|
|
},
|
|
"source": [
|
|
"## Other Types of Recurrent Neural Networks\n",
|
|
"\n",
|
|
"Besides a simple recurrent neural network layer, there are two other\n",
|
|
"commonly used types of recurrent neural network layers: Long Short\n",
|
|
"Term Memory (LSTM) and Gated Recurrent Unit (GRU). For a short\n",
|
|
"introduction to these layers see <https://medium.com/mindboard/lstm-vs-gru-experimental-comparison-955820c21e8b>\n",
|
|
"and <https://medium.com/mindboard/lstm-vs-gru-experimental-comparison-955820c21e8b>.\n",
|
|
"\n",
|
|
"The first network created below is similar to the previous network,\n",
|
|
"but it replaces the SimpleRNN layers with LSTM layers. The second\n",
|
|
"network below has two hidden layers made up of GRUs, which are\n",
|
|
"preceeded by two dense (feeddorward) neural network layers. These\n",
|
|
"dense layers \"preprocess\" the data before it reaches the recurrent\n",
|
|
"layers. This architecture has been shown to improve the performance\n",
|
|
"of recurrent neural networks (see the link above and also\n",
|
|
"<https://arxiv.org/pdf/1807.02857.pdf>."
|
|
]
|
|
},
|
|
{
|
|
"cell_type": "code",
|
|
"execution_count": 6,
|
|
"id": "6f0e9b62",
|
|
"metadata": {
|
|
"collapsed": false,
|
|
"editable": true
|
|
},
|
|
"outputs": [],
|
|
"source": [
|
|
"def lstm_2layers(length_of_sequences, batch_size = None, stateful = False):\n",
|
|
" \"\"\"\n",
|
|
" Inputs:\n",
|
|
" length_of_sequences (an int): the number of y values in \"x data\". This is determined\n",
|
|
" when the data is formatted\n",
|
|
" batch_size (an int): Default value is None. See Keras documentation of SimpleRNN.\n",
|
|
" stateful (a boolean): Default value is False. See Keras documentation of SimpleRNN.\n",
|
|
" Returns:\n",
|
|
" model (a Keras model): The recurrent neural network that is built and compiled by this\n",
|
|
" method\n",
|
|
" Builds and compiles a recurrent neural network with two LSTM hidden layers and returns the model.\n",
|
|
" \"\"\"\n",
|
|
" # Number of neurons on the input/output layer and the number of neurons in the hidden layer\n",
|
|
" in_out_neurons = 1\n",
|
|
" hidden_neurons = 250\n",
|
|
" # Input Layer\n",
|
|
" inp = Input(batch_shape=(batch_size, \n",
|
|
" length_of_sequences, \n",
|
|
" in_out_neurons)) \n",
|
|
" # Hidden layers (in this case they are LSTM layers instead if SimpleRNN layers)\n",
|
|
" rnn= LSTM(hidden_neurons, \n",
|
|
" return_sequences=True,\n",
|
|
" stateful = stateful,\n",
|
|
" name=\"RNN\", use_bias=True, activation='tanh')(inp)\n",
|
|
" rnn1 = LSTM(hidden_neurons, \n",
|
|
" return_sequences=False,\n",
|
|
" stateful = stateful,\n",
|
|
" name=\"RNN1\", use_bias=True, activation='tanh')(rnn)\n",
|
|
" # Output layer\n",
|
|
" dens = Dense(in_out_neurons,name=\"dense\")(rnn1)\n",
|
|
" # Define the midel\n",
|
|
" model = Model(inputs=[inp],outputs=[dens])\n",
|
|
" # Compile the model\n",
|
|
" model.compile(loss='mean_squared_error', optimizer='adam') \n",
|
|
" # Return the model\n",
|
|
" return model\n",
|
|
"\n",
|
|
"def dnn2_gru2(length_of_sequences, batch_size = None, stateful = False):\n",
|
|
" \"\"\"\n",
|
|
" Inputs:\n",
|
|
" length_of_sequences (an int): the number of y values in \"x data\". This is determined\n",
|
|
" when the data is formatted\n",
|
|
" batch_size (an int): Default value is None. See Keras documentation of SimpleRNN.\n",
|
|
" stateful (a boolean): Default value is False. See Keras documentation of SimpleRNN.\n",
|
|
" Returns:\n",
|
|
" model (a Keras model): The recurrent neural network that is built and compiled by this\n",
|
|
" method\n",
|
|
" Builds and compiles a recurrent neural network with four hidden layers (two dense followed by\n",
|
|
" two GRU layers) and returns the model.\n",
|
|
" \"\"\" \n",
|
|
" # Number of neurons on the input/output layers and hidden layers\n",
|
|
" in_out_neurons = 1\n",
|
|
" hidden_neurons = 250\n",
|
|
" # Input layer\n",
|
|
" inp = Input(batch_shape=(batch_size, \n",
|
|
" length_of_sequences, \n",
|
|
" in_out_neurons)) \n",
|
|
" # Hidden Dense (feedforward) layers\n",
|
|
" dnn = Dense(hidden_neurons/2, activation='relu', name='dnn')(inp)\n",
|
|
" dnn1 = Dense(hidden_neurons/2, activation='relu', name='dnn1')(dnn)\n",
|
|
" # Hidden GRU layers\n",
|
|
" rnn1 = GRU(hidden_neurons, \n",
|
|
" return_sequences=True,\n",
|
|
" stateful = stateful,\n",
|
|
" name=\"RNN1\", use_bias=True)(dnn1)\n",
|
|
" rnn = GRU(hidden_neurons, \n",
|
|
" return_sequences=False,\n",
|
|
" stateful = stateful,\n",
|
|
" name=\"RNN\", use_bias=True)(rnn1)\n",
|
|
" # Output layer\n",
|
|
" dens = Dense(in_out_neurons,name=\"dense\")(rnn)\n",
|
|
" # Define the model\n",
|
|
" model = Model(inputs=[inp],outputs=[dens])\n",
|
|
" # Compile the mdoel\n",
|
|
" model.compile(loss='mean_squared_error', optimizer='adam') \n",
|
|
" # Return the model\n",
|
|
" return model\n",
|
|
"\n",
|
|
"# Check to make sure the data set is complete\n",
|
|
"assert len(X_tot) == len(y_tot)\n",
|
|
"\n",
|
|
"# This is the number of points that will be used in as the training data\n",
|
|
"dim=12\n",
|
|
"\n",
|
|
"# Separate the training data from the whole data set\n",
|
|
"X_train = X_tot[:dim]\n",
|
|
"y_train = y_tot[:dim]\n",
|
|
"\n",
|
|
"\n",
|
|
"# Generate the training data for the RNN, using a sequence of 2\n",
|
|
"rnn_input, rnn_training = format_data(y_train, 2)\n",
|
|
"\n",
|
|
"\n",
|
|
"# Create a recurrent neural network in Keras and produce a summary of the \n",
|
|
"# machine learning model\n",
|
|
"# Change the method name to reflect which network you want to use\n",
|
|
"model = dnn2_gru2(length_of_sequences = 2)\n",
|
|
"model.summary()\n",
|
|
"\n",
|
|
"# Start the timer. Want to time training+testing\n",
|
|
"start = timer()\n",
|
|
"# Fit the model using the training data genenerated above using 150 training iterations and a 5%\n",
|
|
"# validation split. Setting verbose to True prints information about each training iteration.\n",
|
|
"hist = model.fit(rnn_input, rnn_training, batch_size=None, epochs=150, \n",
|
|
" verbose=True,validation_split=0.05)\n",
|
|
"\n",
|
|
"\n",
|
|
"# This section plots the training loss and the validation loss as a function of training iteration.\n",
|
|
"# This is not required for analyzing the couple cluster data but can help determine if the network is\n",
|
|
"# being overtrained.\n",
|
|
"for label in [\"loss\",\"val_loss\"]:\n",
|
|
" plt.plot(hist.history[label],label=label)\n",
|
|
"\n",
|
|
"plt.ylabel(\"loss\")\n",
|
|
"plt.xlabel(\"epoch\")\n",
|
|
"plt.title(\"The final validation loss: {}\".format(hist.history[\"val_loss\"][-1]))\n",
|
|
"plt.legend()\n",
|
|
"plt.show()\n",
|
|
"\n",
|
|
"# Use the trained neural network to predict more points of the data set\n",
|
|
"test_rnn(X_tot, y_tot, X_tot[0], X_tot[dim-1])\n",
|
|
"# Stop the timer and calculate the total time needed.\n",
|
|
"end = timer()\n",
|
|
"print('Time: ', end-start)\n",
|
|
"\n",
|
|
"\n",
|
|
"# ### Training Recurrent Neural Networks in the Standard Way (i.e. learning the relationship between the X and Y data)\n",
|
|
"# \n",
|
|
"# Finally, comparing the performace of a recurrent neural network using the standard data formatting to the performance of the network with time sequence data formatting shows the benefit of this type of data formatting with extrapolation.\n",
|
|
"\n",
|
|
"# Check to make sure the data set is complete\n",
|
|
"assert len(X_tot) == len(y_tot)\n",
|
|
"\n",
|
|
"# This is the number of points that will be used in as the training data\n",
|
|
"dim=12\n",
|
|
"\n",
|
|
"# Separate the training data from the whole data set\n",
|
|
"X_train = X_tot[:dim]\n",
|
|
"y_train = y_tot[:dim]\n",
|
|
"\n",
|
|
"# Reshape the data for Keras specifications\n",
|
|
"X_train = X_train.reshape((dim, 1))\n",
|
|
"y_train = y_train.reshape((dim, 1))\n",
|
|
"\n",
|
|
"\n",
|
|
"# Create a recurrent neural network in Keras and produce a summary of the \n",
|
|
"# machine learning model\n",
|
|
"# Set the sequence length to 1 for regular data formatting \n",
|
|
"model = rnn(length_of_sequences = 1)\n",
|
|
"model.summary()\n",
|
|
"\n",
|
|
"# Start the timer. Want to time training+testing\n",
|
|
"start = timer()\n",
|
|
"# Fit the model using the training data genenerated above using 150 training iterations and a 5%\n",
|
|
"# validation split. Setting verbose to True prints information about each training iteration.\n",
|
|
"hist = model.fit(X_train, y_train, batch_size=None, epochs=150, \n",
|
|
" verbose=True,validation_split=0.05)\n",
|
|
"\n",
|
|
"\n",
|
|
"# This section plots the training loss and the validation loss as a function of training iteration.\n",
|
|
"# This is not required for analyzing the couple cluster data but can help determine if the network is\n",
|
|
"# being overtrained.\n",
|
|
"for label in [\"loss\",\"val_loss\"]:\n",
|
|
" plt.plot(hist.history[label],label=label)\n",
|
|
"\n",
|
|
"plt.ylabel(\"loss\")\n",
|
|
"plt.xlabel(\"epoch\")\n",
|
|
"plt.title(\"The final validation loss: {}\".format(hist.history[\"val_loss\"][-1]))\n",
|
|
"plt.legend()\n",
|
|
"plt.show()\n",
|
|
"\n",
|
|
"# Use the trained neural network to predict the remaining data points\n",
|
|
"X_pred = X_tot[dim:]\n",
|
|
"X_pred = X_pred.reshape((len(X_pred), 1))\n",
|
|
"y_model = model.predict(X_pred)\n",
|
|
"y_pred = np.concatenate((y_tot[:dim], y_model.flatten()))\n",
|
|
"\n",
|
|
"# Plot the known data set and the predicted data set. The red box represents the region that was used\n",
|
|
"# for the training data.\n",
|
|
"fig, ax = plt.subplots()\n",
|
|
"ax.plot(X_tot, y_tot, label=\"true\", linewidth=3)\n",
|
|
"ax.plot(X_tot, y_pred, 'g-.',label=\"predicted\", linewidth=4)\n",
|
|
"ax.legend()\n",
|
|
"# Created a red region to represent the points used in the training data.\n",
|
|
"ax.axvspan(X_tot[0], X_tot[dim], alpha=0.25, color='red')\n",
|
|
"plt.show()\n",
|
|
"\n",
|
|
"# Stop the timer and calculate the total time needed.\n",
|
|
"end = timer()\n",
|
|
"print('Time: ', end-start)"
|
|
]
|
|
},
|
|
{
|
|
"cell_type": "markdown",
|
|
"id": "0752ba7f",
|
|
"metadata": {
|
|
"editable": true
|
|
},
|
|
"source": [
|
|
"# Generative Models\n",
|
|
"\n",
|
|
"**Generative models** describe a class of statistical models that are a contrast\n",
|
|
"to **discriminative models**. Informally we say that generative models can\n",
|
|
"generate new data instances while discriminative models discriminate between\n",
|
|
"different kinds of data instances. A generative model could generate new photos\n",
|
|
"of animals that look like 'real' animals while a discriminative model could tell\n",
|
|
"a dog from a cat. More formally, given a data set $x$ and a set of labels /\n",
|
|
"targets $y$. Generative models capture the joint probability $p(x, y)$, or\n",
|
|
"just $p(x)$ if there are no labels, while discriminative models capture the\n",
|
|
"conditional probability $p(y | x)$. Discriminative models generally try to draw\n",
|
|
"boundaries in the data space (often high dimensional), while generative models\n",
|
|
"try to model how data is placed throughout the space.\n",
|
|
"\n",
|
|
"**Note**: this material is thanks to Linus Ekstrøm."
|
|
]
|
|
},
|
|
{
|
|
"cell_type": "markdown",
|
|
"id": "784138f8",
|
|
"metadata": {
|
|
"editable": true
|
|
},
|
|
"source": [
|
|
"## Generative Adversarial Networks\n",
|
|
"\n",
|
|
"**Generative Adversarial Networks** are a type of unsupervised machine learning\n",
|
|
"algorithm proposed by [Goodfellow et. al](https://arxiv.org/pdf/1406.2661.pdf)\n",
|
|
"in 2014 (short and good article).\n",
|
|
"\n",
|
|
"The simplest formulation of\n",
|
|
"the model is based on a game theoretic approach, *zero sum game*, where we pit\n",
|
|
"two neural networks against one another. We define two rival networks, one\n",
|
|
"generator $g$, and one discriminator $d$. The generator directly produces\n",
|
|
"samples"
|
|
]
|
|
},
|
|
{
|
|
"cell_type": "markdown",
|
|
"id": "a42f89ee",
|
|
"metadata": {
|
|
"editable": true
|
|
},
|
|
"source": [
|
|
"<!-- Equation labels as ordinary links -->\n",
|
|
"<div id=\"_auto1\"></div>\n",
|
|
"\n",
|
|
"$$\n",
|
|
"\\begin{equation}\n",
|
|
" x = g(z; \\theta^{(g)})\n",
|
|
"\\label{_auto1} \\tag{1}\n",
|
|
"\\end{equation}\n",
|
|
"$$"
|
|
]
|
|
},
|
|
{
|
|
"cell_type": "markdown",
|
|
"id": "abe7212f",
|
|
"metadata": {
|
|
"editable": true
|
|
},
|
|
"source": [
|
|
"The discriminator attempts to distinguish between samples drawn from the\n",
|
|
"training data and samples drawn from the generator. In other words, it tries to\n",
|
|
"tell the difference between the fake data produced by $g$ and the actual data\n",
|
|
"samples we want to do prediction on. The discriminator outputs a probability\n",
|
|
"value given by"
|
|
]
|
|
},
|
|
{
|
|
"cell_type": "markdown",
|
|
"id": "0821eaee",
|
|
"metadata": {
|
|
"editable": true
|
|
},
|
|
"source": [
|
|
"<!-- Equation labels as ordinary links -->\n",
|
|
"<div id=\"_auto2\"></div>\n",
|
|
"\n",
|
|
"$$\n",
|
|
"\\begin{equation}\n",
|
|
" d(x; \\theta^{(d)})\n",
|
|
"\\label{_auto2} \\tag{2}\n",
|
|
"\\end{equation}\n",
|
|
"$$"
|
|
]
|
|
},
|
|
{
|
|
"cell_type": "markdown",
|
|
"id": "55e0ccf6",
|
|
"metadata": {
|
|
"editable": true
|
|
},
|
|
"source": [
|
|
"indicating the probability that $x$ is a real training example rather than a\n",
|
|
"fake sample the generator has generated. The simplest way to formulate the\n",
|
|
"learning process in a generative adversarial network is a zero-sum game, in\n",
|
|
"which a function"
|
|
]
|
|
},
|
|
{
|
|
"cell_type": "markdown",
|
|
"id": "f37ece14",
|
|
"metadata": {
|
|
"editable": true
|
|
},
|
|
"source": [
|
|
"<!-- Equation labels as ordinary links -->\n",
|
|
"<div id=\"_auto3\"></div>\n",
|
|
"\n",
|
|
"$$\n",
|
|
"\\begin{equation}\n",
|
|
" v(\\theta^{(g)}, \\theta^{(d)})\n",
|
|
"\\label{_auto3} \\tag{3}\n",
|
|
"\\end{equation}\n",
|
|
"$$"
|
|
]
|
|
},
|
|
{
|
|
"cell_type": "markdown",
|
|
"id": "d6d6d5fa",
|
|
"metadata": {
|
|
"editable": true
|
|
},
|
|
"source": [
|
|
"determines the reward for the discriminator, while the generator gets the\n",
|
|
"conjugate reward"
|
|
]
|
|
},
|
|
{
|
|
"cell_type": "markdown",
|
|
"id": "3c74b45c",
|
|
"metadata": {
|
|
"editable": true
|
|
},
|
|
"source": [
|
|
"<!-- Equation labels as ordinary links -->\n",
|
|
"<div id=\"_auto4\"></div>\n",
|
|
"\n",
|
|
"$$\n",
|
|
"\\begin{equation}\n",
|
|
" -v(\\theta^{(g)}, \\theta^{(d)})\n",
|
|
"\\label{_auto4} \\tag{4}\n",
|
|
"\\end{equation}\n",
|
|
"$$"
|
|
]
|
|
},
|
|
{
|
|
"cell_type": "markdown",
|
|
"id": "605ac8c9",
|
|
"metadata": {
|
|
"editable": true
|
|
},
|
|
"source": [
|
|
"During learning both of the networks maximize their own reward function, so that\n",
|
|
"the generator gets better and better at tricking the discriminator, while the\n",
|
|
"discriminator gets better and better at telling the difference between the fake\n",
|
|
"and real data. The generator and discriminator alternate on which one trains at\n",
|
|
"one time (i.e. for one epoch). In other words, we keep the generator constant\n",
|
|
"and train the discriminator, then we keep the discriminator constant to train\n",
|
|
"the generator and repeat. It is this back and forth dynamic which lets GANs\n",
|
|
"tackle otherwise intractable generative problems. As the generator improves with\n",
|
|
" training, the discriminator's performance gets worse because it cannot easily\n",
|
|
" tell the difference between real and fake. If the generator ends up succeeding\n",
|
|
" perfectly, the the discriminator will do no better than random guessing i.e.\n",
|
|
" 50\\%. This progression in the training poses a problem for the convergence\n",
|
|
" criteria for GANs. The discriminator feedback gets less meaningful over time,\n",
|
|
" if we continue training after this point then the generator is effectively\n",
|
|
" training on junk data which can undo the learning up to that point. Therefore,\n",
|
|
" we stop training when the discriminator starts outputting $1/2$ everywhere.\n",
|
|
"\n",
|
|
"At convergence we have"
|
|
]
|
|
},
|
|
{
|
|
"cell_type": "markdown",
|
|
"id": "cfec7462",
|
|
"metadata": {
|
|
"editable": true
|
|
},
|
|
"source": [
|
|
"<!-- Equation labels as ordinary links -->\n",
|
|
"<div id=\"_auto5\"></div>\n",
|
|
"\n",
|
|
"$$\n",
|
|
"\\begin{equation}\n",
|
|
" g^* = \\underset{g}{\\mathrm{argmin}}\\hspace{2pt}\n",
|
|
" \\underset{d}{\\mathrm{max}}v(\\theta^{(g)}, \\theta^{(d)})\n",
|
|
"\\label{_auto5} \\tag{5}\n",
|
|
"\\end{equation}\n",
|
|
"$$"
|
|
]
|
|
},
|
|
{
|
|
"cell_type": "markdown",
|
|
"id": "37c65d4c",
|
|
"metadata": {
|
|
"editable": true
|
|
},
|
|
"source": [
|
|
"The default choice for $v$ is"
|
|
]
|
|
},
|
|
{
|
|
"cell_type": "markdown",
|
|
"id": "c868a092",
|
|
"metadata": {
|
|
"editable": true
|
|
},
|
|
"source": [
|
|
"<!-- Equation labels as ordinary links -->\n",
|
|
"<div id=\"_auto6\"></div>\n",
|
|
"\n",
|
|
"$$\n",
|
|
"\\begin{equation}\n",
|
|
" v(\\theta^{(g)}, \\theta^{(d)}) = \\mathbb{E}_{x\\sim p_\\mathrm{data}}\\log d(x)\n",
|
|
" + \\mathbb{E}_{x\\sim p_\\mathrm{model}}\n",
|
|
" \\log (1 - d(x))\n",
|
|
"\\label{_auto6} \\tag{6}\n",
|
|
"\\end{equation}\n",
|
|
"$$"
|
|
]
|
|
},
|
|
{
|
|
"cell_type": "markdown",
|
|
"id": "ad465af3",
|
|
"metadata": {
|
|
"editable": true
|
|
},
|
|
"source": [
|
|
"The main motivation for the design of GANs is that the learning process requires\n",
|
|
"neither approximate inference (variational autoencoders for example) nor\n",
|
|
"approximation of a partition function. In the case where"
|
|
]
|
|
},
|
|
{
|
|
"cell_type": "markdown",
|
|
"id": "27858a4e",
|
|
"metadata": {
|
|
"editable": true
|
|
},
|
|
"source": [
|
|
"<!-- Equation labels as ordinary links -->\n",
|
|
"<div id=\"_auto7\"></div>\n",
|
|
"\n",
|
|
"$$\n",
|
|
"\\begin{equation}\n",
|
|
" \\underset{d}{\\mathrm{max}}v(\\theta^{(g)}, \\theta^{(d)})\n",
|
|
"\\label{_auto7} \\tag{7}\n",
|
|
"\\end{equation}\n",
|
|
"$$"
|
|
]
|
|
},
|
|
{
|
|
"cell_type": "markdown",
|
|
"id": "86006023",
|
|
"metadata": {
|
|
"editable": true
|
|
},
|
|
"source": [
|
|
"is convex in $\\theta^{(g)} then the procedure is guaranteed to converge and is\n",
|
|
"asymptotically consistent\n",
|
|
"( [Seth Lloyd on QuGANs](https://arxiv.org/pdf/1804.09139.pdf) ).\n",
|
|
"\n",
|
|
"This is in\n",
|
|
"general not the case and it is possible to get situations where the training\n",
|
|
"process never converges because the generator and discriminator chase one\n",
|
|
"another around in the parameter space indefinitely. A much deeper discussion on\n",
|
|
"the currently open research problem of GAN convergence is available\n",
|
|
"[here](https://www.deeplearningbook.org/contents/generative_models.html). To\n",
|
|
"anyone interested in learning more about GANs it is a highly recommended read.\n",
|
|
"Direct quote: \"In this best-performing formulation, the generator aims to\n",
|
|
"increase the log probability that the discriminator makes a mistake, rather than\n",
|
|
"aiming to decrease the log probability that the discriminator makes the correct\n",
|
|
"prediction.\" [Another interesting read](https://arxiv.org/abs/1701.00160)"
|
|
]
|
|
},
|
|
{
|
|
"cell_type": "markdown",
|
|
"id": "2fee38bd",
|
|
"metadata": {
|
|
"editable": true
|
|
},
|
|
"source": [
|
|
"## Writing Our First Generative Adversarial Network\n",
|
|
"Let us now move on to actually implementing a GAN in tensorflow. We will study\n",
|
|
"the performance of our GAN on the MNIST dataset. This code is based on and\n",
|
|
"adapted from the\n",
|
|
"[google tutorial](https://www.tensorflow.org/tutorials/generative/dcgan)\n",
|
|
"\n",
|
|
"First we import our libraries"
|
|
]
|
|
},
|
|
{
|
|
"cell_type": "code",
|
|
"execution_count": 7,
|
|
"id": "004a0b53",
|
|
"metadata": {
|
|
"collapsed": false,
|
|
"editable": true
|
|
},
|
|
"outputs": [],
|
|
"source": [
|
|
"import os\n",
|
|
"import time\n",
|
|
"import numpy as np\n",
|
|
"import tensorflow as tf\n",
|
|
"import matplotlib.pyplot as plt\n",
|
|
"from tensorflow.keras import layers\n",
|
|
"from tensorflow.keras.utils import plot_model"
|
|
]
|
|
},
|
|
{
|
|
"cell_type": "markdown",
|
|
"id": "353af161",
|
|
"metadata": {
|
|
"editable": true
|
|
},
|
|
"source": [
|
|
"Next we define our hyperparameters and import our data the usual way"
|
|
]
|
|
},
|
|
{
|
|
"cell_type": "code",
|
|
"execution_count": 8,
|
|
"id": "8cbaf16a",
|
|
"metadata": {
|
|
"collapsed": false,
|
|
"editable": true
|
|
},
|
|
"outputs": [],
|
|
"source": [
|
|
"BUFFER_SIZE = 60000\n",
|
|
"BATCH_SIZE = 256\n",
|
|
"EPOCHS = 30\n",
|
|
"\n",
|
|
"data = tf.keras.datasets.mnist.load_data()\n",
|
|
"(train_images, train_labels), (test_images, test_labels) = data\n",
|
|
"train_images = np.reshape(train_images, (train_images.shape[0],\n",
|
|
" 28,\n",
|
|
" 28,\n",
|
|
" 1)).astype('float32')\n",
|
|
"\n",
|
|
"# we normalize between -1 and 1\n",
|
|
"train_images = (train_images - 127.5) / 127.5\n",
|
|
"training_dataset = tf.data.Dataset.from_tensor_slices(\n",
|
|
" train_images).shuffle(BUFFER_SIZE).batch(BATCH_SIZE)"
|
|
]
|
|
},
|
|
{
|
|
"cell_type": "markdown",
|
|
"id": "822b8cc7",
|
|
"metadata": {
|
|
"editable": true
|
|
},
|
|
"source": [
|
|
"### MNIST and GANs\n",
|
|
"\n",
|
|
"Let's have a quick look"
|
|
]
|
|
},
|
|
{
|
|
"cell_type": "code",
|
|
"execution_count": 9,
|
|
"id": "52b5965c",
|
|
"metadata": {
|
|
"collapsed": false,
|
|
"editable": true
|
|
},
|
|
"outputs": [],
|
|
"source": [
|
|
"plt.imshow(train_images[0], cmap='Greys')\n",
|
|
"plt.show()"
|
|
]
|
|
},
|
|
{
|
|
"cell_type": "markdown",
|
|
"id": "21c5199c",
|
|
"metadata": {
|
|
"editable": true
|
|
},
|
|
"source": [
|
|
"Now we define our two models. This is where the 'magic' happens. There are a\n",
|
|
"huge amount of possible formulations for both models. A lot of engineering and\n",
|
|
"trial and error can be done here to try to produce better performing models. For\n",
|
|
"more advanced GANs this is by far the step where you can 'make or break' a\n",
|
|
"model.\n",
|
|
"\n",
|
|
"We start with the generator. As stated in the introductory text the generator\n",
|
|
"$g$ upsamples from a random sample to the shape of what we want to predict. In\n",
|
|
"our case we are trying to predict MNIST images ($28\\times 28$ pixels)."
|
|
]
|
|
},
|
|
{
|
|
"cell_type": "code",
|
|
"execution_count": 10,
|
|
"id": "356759c7",
|
|
"metadata": {
|
|
"collapsed": false,
|
|
"editable": true
|
|
},
|
|
"outputs": [],
|
|
"source": [
|
|
"def generator_model():\n",
|
|
" \"\"\"\n",
|
|
" The generator uses upsampling layers tf.keras.layers.Conv2DTranspose() to\n",
|
|
" produce an image from a random seed. We start with a Dense layer taking this\n",
|
|
" random sample as an input and subsequently upsample through multiple\n",
|
|
" convolutional layers.\n",
|
|
" \"\"\"\n",
|
|
"\n",
|
|
" # we define our model\n",
|
|
" model = tf.keras.Sequential()\n",
|
|
"\n",
|
|
"\n",
|
|
" # adding our input layer. Dense means that every neuron is connected and\n",
|
|
" # the input shape is the shape of our random noise. The units need to match\n",
|
|
" # in some sense the upsampling strides to reach our desired output shape.\n",
|
|
" # we are using 100 random numbers as our seed\n",
|
|
" model.add(layers.Dense(units=7*7*BATCH_SIZE,\n",
|
|
" use_bias=False,\n",
|
|
" input_shape=(100, )))\n",
|
|
" # we normalize the output form the Dense layer\n",
|
|
" model.add(layers.BatchNormalization())\n",
|
|
" # and add an activation function to our 'layer'. LeakyReLU avoids vanishing\n",
|
|
" # gradient problem\n",
|
|
" model.add(layers.LeakyReLU())\n",
|
|
" model.add(layers.Reshape((7, 7, BATCH_SIZE)))\n",
|
|
" assert model.output_shape == (None, 7, 7, BATCH_SIZE)\n",
|
|
" # even though we just added four keras layers we think of everything above\n",
|
|
" # as 'one' layer\n",
|
|
"\n",
|
|
" # next we add our upscaling convolutional layers\n",
|
|
" model.add(layers.Conv2DTranspose(filters=128,\n",
|
|
" kernel_size=(5, 5),\n",
|
|
" strides=(1, 1),\n",
|
|
" padding='same',\n",
|
|
" use_bias=False))\n",
|
|
" model.add(layers.BatchNormalization())\n",
|
|
" model.add(layers.LeakyReLU())\n",
|
|
" assert model.output_shape == (None, 7, 7, 128)\n",
|
|
"\n",
|
|
" model.add(layers.Conv2DTranspose(filters=64,\n",
|
|
" kernel_size=(5, 5),\n",
|
|
" strides=(2, 2),\n",
|
|
" padding='same',\n",
|
|
" use_bias=False))\n",
|
|
" model.add(layers.BatchNormalization())\n",
|
|
" model.add(layers.LeakyReLU())\n",
|
|
" assert model.output_shape == (None, 14, 14, 64)\n",
|
|
"\n",
|
|
" model.add(layers.Conv2DTranspose(filters=1,\n",
|
|
" kernel_size=(5, 5),\n",
|
|
" strides=(2, 2),\n",
|
|
" padding='same',\n",
|
|
" use_bias=False,\n",
|
|
" activation='tanh'))\n",
|
|
" assert model.output_shape == (None, 28, 28, 1)\n",
|
|
"\n",
|
|
" return model"
|
|
]
|
|
},
|
|
{
|
|
"cell_type": "markdown",
|
|
"id": "854bcd6b",
|
|
"metadata": {
|
|
"editable": true
|
|
},
|
|
"source": [
|
|
"And there we have our 'simple' generator model. Now we move on to defining our\n",
|
|
"discriminator model $d$, which is a convolutional neural network based image\n",
|
|
"classifier."
|
|
]
|
|
},
|
|
{
|
|
"cell_type": "code",
|
|
"execution_count": 11,
|
|
"id": "41473304",
|
|
"metadata": {
|
|
"collapsed": false,
|
|
"editable": true
|
|
},
|
|
"outputs": [],
|
|
"source": [
|
|
"def discriminator_model():\n",
|
|
" \"\"\"\n",
|
|
" The discriminator is a convolutional neural network based image classifier\n",
|
|
" \"\"\"\n",
|
|
"\n",
|
|
" # we define our model\n",
|
|
" model = tf.keras.Sequential()\n",
|
|
" model.add(layers.Conv2D(filters=64,\n",
|
|
" kernel_size=(5, 5),\n",
|
|
" strides=(2, 2),\n",
|
|
" padding='same',\n",
|
|
" input_shape=[28, 28, 1]))\n",
|
|
" model.add(layers.LeakyReLU())\n",
|
|
" # adding a dropout layer as you do in conv-nets\n",
|
|
" model.add(layers.Dropout(0.3))\n",
|
|
"\n",
|
|
"\n",
|
|
" model.add(layers.Conv2D(filters=128,\n",
|
|
" kernel_size=(5, 5),\n",
|
|
" strides=(2, 2),\n",
|
|
" padding='same'))\n",
|
|
" model.add(layers.LeakyReLU())\n",
|
|
" # adding a dropout layer as you do in conv-nets\n",
|
|
" model.add(layers.Dropout(0.3))\n",
|
|
"\n",
|
|
" model.add(layers.Flatten())\n",
|
|
" model.add(layers.Dense(1))\n",
|
|
"\n",
|
|
" return model"
|
|
]
|
|
},
|
|
{
|
|
"cell_type": "markdown",
|
|
"id": "353af567",
|
|
"metadata": {
|
|
"editable": true
|
|
},
|
|
"source": [
|
|
"Let us take a look at our models."
|
|
]
|
|
},
|
|
{
|
|
"cell_type": "code",
|
|
"execution_count": 12,
|
|
"id": "f899d4e3",
|
|
"metadata": {
|
|
"collapsed": false,
|
|
"editable": true
|
|
},
|
|
"outputs": [],
|
|
"source": [
|
|
"generator = generator_model()\n",
|
|
"plot_model(generator, show_shapes=True, rankdir='LR')"
|
|
]
|
|
},
|
|
{
|
|
"cell_type": "code",
|
|
"execution_count": 13,
|
|
"id": "87ef384b",
|
|
"metadata": {
|
|
"collapsed": false,
|
|
"editable": true
|
|
},
|
|
"outputs": [],
|
|
"source": [
|
|
"discriminator = discriminator_model()\n",
|
|
"plot_model(discriminator, show_shapes=True, rankdir='LR')"
|
|
]
|
|
},
|
|
{
|
|
"cell_type": "markdown",
|
|
"id": "b2bef82d",
|
|
"metadata": {
|
|
"editable": true
|
|
},
|
|
"source": [
|
|
"Next we need a few helper objects we will use in training"
|
|
]
|
|
},
|
|
{
|
|
"cell_type": "code",
|
|
"execution_count": 14,
|
|
"id": "e397847a",
|
|
"metadata": {
|
|
"collapsed": false,
|
|
"editable": true
|
|
},
|
|
"outputs": [],
|
|
"source": [
|
|
"cross_entropy = tf.keras.losses.BinaryCrossentropy(from_logits=True)\n",
|
|
"generator_optimizer = tf.keras.optimizers.Adam(1e-4)\n",
|
|
"discriminator_optimizer = tf.keras.optimizers.Adam(1e-4)"
|
|
]
|
|
},
|
|
{
|
|
"cell_type": "markdown",
|
|
"id": "db3396cc",
|
|
"metadata": {
|
|
"editable": true
|
|
},
|
|
"source": [
|
|
"The first object, *cross_entropy* is our loss function and the two others are\n",
|
|
"our optimizers. Notice we use the same learning rate for both $g$ and $d$. This\n",
|
|
"is because they need to improve their accuracy at approximately equal speeds to\n",
|
|
"get convergence (not necessarily exactly equal). Now we define our loss\n",
|
|
"functions"
|
|
]
|
|
},
|
|
{
|
|
"cell_type": "code",
|
|
"execution_count": 15,
|
|
"id": "931eaced",
|
|
"metadata": {
|
|
"collapsed": false,
|
|
"editable": true
|
|
},
|
|
"outputs": [],
|
|
"source": [
|
|
"def generator_loss(fake_output):\n",
|
|
" loss = cross_entropy(tf.ones_like(fake_output), fake_output)\n",
|
|
"\n",
|
|
" return loss"
|
|
]
|
|
},
|
|
{
|
|
"cell_type": "code",
|
|
"execution_count": 16,
|
|
"id": "0c4a44bb",
|
|
"metadata": {
|
|
"collapsed": false,
|
|
"editable": true
|
|
},
|
|
"outputs": [],
|
|
"source": [
|
|
"def discriminator_loss(real_output, fake_output):\n",
|
|
" real_loss = cross_entropy(tf.ones_like(real_output), real_output)\n",
|
|
" fake_loss = cross_entropy(tf.zeros_liks(fake_output), fake_output)\n",
|
|
" total_loss = real_loss + fake_loss\n",
|
|
"\n",
|
|
" return total_loss"
|
|
]
|
|
},
|
|
{
|
|
"cell_type": "markdown",
|
|
"id": "fcf8f066",
|
|
"metadata": {
|
|
"editable": true
|
|
},
|
|
"source": [
|
|
"Next we define a kind of seed to help us compare the learning process over\n",
|
|
"multiple training epochs."
|
|
]
|
|
},
|
|
{
|
|
"cell_type": "code",
|
|
"execution_count": 17,
|
|
"id": "eea2bbee",
|
|
"metadata": {
|
|
"collapsed": false,
|
|
"editable": true
|
|
},
|
|
"outputs": [],
|
|
"source": [
|
|
"noise_dimension = 100\n",
|
|
"n_examples_to_generate = 16\n",
|
|
"seed_images = tf.random.normal([n_examples_to_generate, noise_dimension])"
|
|
]
|
|
},
|
|
{
|
|
"cell_type": "markdown",
|
|
"id": "94a6e341",
|
|
"metadata": {
|
|
"editable": true
|
|
},
|
|
"source": [
|
|
"Now we have everything we need to define our training step, which we will apply\n",
|
|
"for every step in our training loop. Notice the @tf.function flag signifying\n",
|
|
"that the function is tensorflow 'compiled'. Removing this flag doubles the\n",
|
|
"computation time."
|
|
]
|
|
},
|
|
{
|
|
"cell_type": "code",
|
|
"execution_count": 18,
|
|
"id": "8d48470b",
|
|
"metadata": {
|
|
"collapsed": false,
|
|
"editable": true
|
|
},
|
|
"outputs": [],
|
|
"source": [
|
|
"@tf.function\n",
|
|
"def train_step(images):\n",
|
|
" noise = tf.random.normal([BATCH_SIZE, noise_dimension])\n",
|
|
"\n",
|
|
" with tf.GradientTape() as gen_tape, tf.GradientTape() as disc_tape:\n",
|
|
" generated_images = generator(noise, training=True)\n",
|
|
"\n",
|
|
" real_output = discriminator(images, training=True)\n",
|
|
" fake_output = discriminator(generated_images, training=True)\n",
|
|
"\n",
|
|
" gen_loss = generator_loss(fake_output)\n",
|
|
" disc_loss = discriminator_loss(real_output, fake_output)\n",
|
|
"\n",
|
|
" gradients_of_generator = gen_tape.gradient(gen_loss,\n",
|
|
" generator.trainable_variables)\n",
|
|
" gradients_of_discriminator = disc_tape.gradient(disc_loss,\n",
|
|
" discriminator.trainable_variables)\n",
|
|
" generator_optimizer.apply_gradients(zip(gradients_of_generator,\n",
|
|
" generator.trainable_variables))\n",
|
|
" discriminator_optimizer.apply_gradients(zip(gradients_of_discriminator,\n",
|
|
" discriminator.trainable_variables))\n",
|
|
"\n",
|
|
" return gen_loss, disc_loss"
|
|
]
|
|
},
|
|
{
|
|
"cell_type": "markdown",
|
|
"id": "62015b88",
|
|
"metadata": {
|
|
"editable": true
|
|
},
|
|
"source": [
|
|
"Next we define a helper function to produce an output over our training epochs\n",
|
|
"to see the predictive progression of our generator model. **Note**: I am including\n",
|
|
"this code here, but comment it out in the training loop."
|
|
]
|
|
},
|
|
{
|
|
"cell_type": "code",
|
|
"execution_count": 19,
|
|
"id": "b189ed96",
|
|
"metadata": {
|
|
"collapsed": false,
|
|
"editable": true
|
|
},
|
|
"outputs": [],
|
|
"source": [
|
|
"def generate_and_save_images(model, epoch, test_input):\n",
|
|
" # we're making inferences here\n",
|
|
" predictions = model(test_input, training=False)\n",
|
|
"\n",
|
|
" fig = plt.figure(figsize=(4, 4))\n",
|
|
"\n",
|
|
" for i in range(predictions.shape[0]):\n",
|
|
" plt.subplot(4, 4, i+1)\n",
|
|
" plt.imshow(predictions[i, :, :, 0] * 127.5 + 127.5, cmap='gray')\n",
|
|
" plt.axis('off')\n",
|
|
"\n",
|
|
" plt.savefig(f'./images_from_seed_images/image_at_epoch_{str(epoch).zfill(3)}.png')\n",
|
|
" plt.close()\n",
|
|
" #plt.show()"
|
|
]
|
|
},
|
|
{
|
|
"cell_type": "markdown",
|
|
"id": "ba2c82d5",
|
|
"metadata": {
|
|
"editable": true
|
|
},
|
|
"source": [
|
|
"Setting up checkpoints to periodically save our model during training so that\n",
|
|
"everything is not lost even if the program were to somehow terminate while\n",
|
|
"training."
|
|
]
|
|
},
|
|
{
|
|
"cell_type": "code",
|
|
"execution_count": 20,
|
|
"id": "a0e2fc8a",
|
|
"metadata": {
|
|
"collapsed": false,
|
|
"editable": true
|
|
},
|
|
"outputs": [],
|
|
"source": [
|
|
"# Setting up checkpoints to save model during training\n",
|
|
"checkpoint_dir = './training_checkpoints'\n",
|
|
"checkpoint_prefix = os.path.join(checkpoint_dir, 'ckpt')\n",
|
|
"checkpoint = tf.train.Checkpoint(generator_optimizer=generator_optimizer,\n",
|
|
" discriminator_optimizer=discriminator_optimizer,\n",
|
|
" generator=generator,\n",
|
|
" discriminator=discriminator)"
|
|
]
|
|
},
|
|
{
|
|
"cell_type": "markdown",
|
|
"id": "4d93a0f7",
|
|
"metadata": {
|
|
"editable": true
|
|
},
|
|
"source": [
|
|
"Now we define our training loop"
|
|
]
|
|
},
|
|
{
|
|
"cell_type": "code",
|
|
"execution_count": 21,
|
|
"id": "a1275556",
|
|
"metadata": {
|
|
"collapsed": false,
|
|
"editable": true
|
|
},
|
|
"outputs": [],
|
|
"source": [
|
|
"def train(dataset, epochs):\n",
|
|
" generator_loss_list = []\n",
|
|
" discriminator_loss_list = []\n",
|
|
"\n",
|
|
" for epoch in range(epochs):\n",
|
|
" start = time.time()\n",
|
|
"\n",
|
|
" for image_batch in dataset:\n",
|
|
" gen_loss, disc_loss = train_step(image_batch)\n",
|
|
" generator_loss_list.append(gen_loss.numpy())\n",
|
|
" discriminator_loss_list.append(disc_loss.numpy())\n",
|
|
"\n",
|
|
" #generate_and_save_images(generator, epoch + 1, seed_images)\n",
|
|
"\n",
|
|
" if (epoch + 1) % 15 == 0:\n",
|
|
" checkpoint.save(file_prefix=checkpoint_prefix)\n",
|
|
"\n",
|
|
" print(f'Time for epoch {epoch} is {time.time() - start}')\n",
|
|
"\n",
|
|
" #generate_and_save_images(generator, epochs, seed_images)\n",
|
|
"\n",
|
|
" loss_file = './data/lossfile.txt'\n",
|
|
" with open(loss_file, 'w') as outfile:\n",
|
|
" outfile.write(str(generator_loss_list))\n",
|
|
" outfile.write('\\n')\n",
|
|
" outfile.write('\\n')\n",
|
|
" outfile.write(str(discriminator_loss_list))\n",
|
|
" outfile.write('\\n')\n",
|
|
" outfile.write('\\n')"
|
|
]
|
|
},
|
|
{
|
|
"cell_type": "markdown",
|
|
"id": "6ff3a75a",
|
|
"metadata": {
|
|
"editable": true
|
|
},
|
|
"source": [
|
|
"To train simply call this function. **Warning**: this might take a long time so\n",
|
|
"there is a folder of a pretrained network already included in the repository."
|
|
]
|
|
},
|
|
{
|
|
"cell_type": "code",
|
|
"execution_count": 22,
|
|
"id": "371ed41a",
|
|
"metadata": {
|
|
"collapsed": false,
|
|
"editable": true
|
|
},
|
|
"outputs": [],
|
|
"source": [
|
|
"train(train_dataset, EPOCHS)"
|
|
]
|
|
},
|
|
{
|
|
"cell_type": "markdown",
|
|
"id": "654399f1",
|
|
"metadata": {
|
|
"editable": true
|
|
},
|
|
"source": [
|
|
"Now to avoid having to train and everything, which will take a while depending\n",
|
|
"on your computer setup we now load in the model which produced the above gif."
|
|
]
|
|
},
|
|
{
|
|
"cell_type": "code",
|
|
"execution_count": 23,
|
|
"id": "dec4b560",
|
|
"metadata": {
|
|
"collapsed": false,
|
|
"editable": true
|
|
},
|
|
"outputs": [],
|
|
"source": [
|
|
"checkpoint.restore(tf.train.latest_checkpoint(checkpoint_dir))\n",
|
|
"restored_generator = checkpoint.generator\n",
|
|
"restored_discriminator = checkpoint.discriminator\n",
|
|
"\n",
|
|
"print(restored_generator)\n",
|
|
"print(restored_discriminator)"
|
|
]
|
|
},
|
|
{
|
|
"cell_type": "markdown",
|
|
"id": "296bfa5c",
|
|
"metadata": {
|
|
"editable": true
|
|
},
|
|
"source": [
|
|
"We have successfully loaded in our latest model. Let us now play around a bit\n",
|
|
"and see what kind of things we can learn about this model. Our generator takes\n",
|
|
"an array of 100 numbers. One idea can be to try to systematically change our\n",
|
|
"input. Let us try and see what we get"
|
|
]
|
|
},
|
|
{
|
|
"cell_type": "code",
|
|
"execution_count": 24,
|
|
"id": "eecfbb1f",
|
|
"metadata": {
|
|
"collapsed": false,
|
|
"editable": true
|
|
},
|
|
"outputs": [],
|
|
"source": [
|
|
"def generate_latent_points(number=100, scale_means=1, scale_stds=1):\n",
|
|
" latent_dim = 100\n",
|
|
" means = scale_means * tf.linspace(-1, 1, num=latent_dim)\n",
|
|
" stds = scale_stds * tf.linspace(-1, 1, num=latent_dim)\n",
|
|
" latent_space_value_range = tf.random.normal([number, latent_dim],\n",
|
|
" means,\n",
|
|
" stds,\n",
|
|
" dtype=tf.float64)\n",
|
|
"\n",
|
|
" return latent_space_value_range\n",
|
|
"\n",
|
|
"def generate_images(latent_points):\n",
|
|
" # notice we set training to false because we are making inferences\n",
|
|
" generated_images = restored_generator.predict(latent_points)\n",
|
|
"\n",
|
|
" return generated_images"
|
|
]
|
|
},
|
|
{
|
|
"cell_type": "code",
|
|
"execution_count": 25,
|
|
"id": "333a593d",
|
|
"metadata": {
|
|
"collapsed": false,
|
|
"editable": true
|
|
},
|
|
"outputs": [],
|
|
"source": [
|
|
"def plot_result(generated_images, number=100):\n",
|
|
" # obviously this assumes sqrt number is an int\n",
|
|
" fig, axs = plt.subplots(int(np.sqrt(number)), int(np.sqrt(number)),\n",
|
|
" figsize=(10, 10))\n",
|
|
"\n",
|
|
" for i in range(int(np.sqrt(number))):\n",
|
|
" for j in range(int(np.sqrt(number))):\n",
|
|
" axs[i, j].imshow(generated_images[i*j], cmap='Greys')\n",
|
|
" axs[i, j].axis('off')\n",
|
|
"\n",
|
|
" plt.show()"
|
|
]
|
|
},
|
|
{
|
|
"cell_type": "code",
|
|
"execution_count": 26,
|
|
"id": "2f5f0154",
|
|
"metadata": {
|
|
"collapsed": false,
|
|
"editable": true
|
|
},
|
|
"outputs": [],
|
|
"source": [
|
|
"generated_images = generate_images(generate_latent_points())\n",
|
|
"plot_result(generated_images)"
|
|
]
|
|
},
|
|
{
|
|
"cell_type": "markdown",
|
|
"id": "ff581bf2",
|
|
"metadata": {
|
|
"editable": true
|
|
},
|
|
"source": [
|
|
"We see that the generator generates images that look like MNIST\n",
|
|
"numbers: $1, 4, 7, 9$. Let's try to tweak it a bit more to see if we are able\n",
|
|
"to generate a similar plot where we generate every MNIST number. Let us now try\n",
|
|
"to 'move' a bit around in the latent space. **Note**: decrease the plot number if\n",
|
|
"these following cells take too long to run on your computer."
|
|
]
|
|
},
|
|
{
|
|
"cell_type": "code",
|
|
"execution_count": 27,
|
|
"id": "d3617ad8",
|
|
"metadata": {
|
|
"collapsed": false,
|
|
"editable": true
|
|
},
|
|
"outputs": [],
|
|
"source": [
|
|
"plot_number = 225\n",
|
|
"\n",
|
|
"generated_images = generate_images(generate_latent_points(number=plot_number,\n",
|
|
" scale_means=5,\n",
|
|
" scale_stds=1))\n",
|
|
"plot_result(generated_images, number=plot_number)\n",
|
|
"\n",
|
|
"generated_images = generate_images(generate_latent_points(number=plot_number,\n",
|
|
" scale_means=-5,\n",
|
|
" scale_stds=1))\n",
|
|
"plot_result(generated_images, number=plot_number)\n",
|
|
"\n",
|
|
"generated_images = generate_images(generate_latent_points(number=plot_number,\n",
|
|
" scale_means=1,\n",
|
|
" scale_stds=5))\n",
|
|
"plot_result(generated_images, number=plot_number)"
|
|
]
|
|
},
|
|
{
|
|
"cell_type": "markdown",
|
|
"id": "1a074f93",
|
|
"metadata": {
|
|
"editable": true
|
|
},
|
|
"source": [
|
|
"Again, we have found something interesting. *Moving* around using our means\n",
|
|
"takes us from digit to digit, while *moving* around using our standard\n",
|
|
"deviations seem to increase the number of different digits! In the last image\n",
|
|
"above, we can barely make out every MNIST digit. Let us make on last plot using\n",
|
|
"this information by upping the standard deviation of our Gaussian noises."
|
|
]
|
|
},
|
|
{
|
|
"cell_type": "code",
|
|
"execution_count": 28,
|
|
"id": "4ef8937d",
|
|
"metadata": {
|
|
"collapsed": false,
|
|
"editable": true
|
|
},
|
|
"outputs": [],
|
|
"source": [
|
|
"plot_number = 400\n",
|
|
"generated_images = generate_images(generate_latent_points(number=plot_number,\n",
|
|
" scale_means=1,\n",
|
|
" scale_stds=10))\n",
|
|
"plot_result(generated_images, number=plot_number)"
|
|
]
|
|
},
|
|
{
|
|
"cell_type": "markdown",
|
|
"id": "385a2d0a",
|
|
"metadata": {
|
|
"editable": true
|
|
},
|
|
"source": [
|
|
"A pretty cool result! We see that our generator indeed has learned a\n",
|
|
"distribution which qualitatively looks a whole lot like the MNIST dataset.\n",
|
|
"\n",
|
|
"Another interesting way to explore the latent space of our generator model is by\n",
|
|
"interpolating between the MNIST digits. This section is largely based on\n",
|
|
"[this excellent blogpost](https://machinelearningmastery.com/how-to-interpolate-and-perform-vector-arithmetic-with-faces-using-a-generative-adversarial-network/)\n",
|
|
"by Jason Brownlee.\n",
|
|
"\n",
|
|
"So let us start by defining a function to interpolate between two points in the\n",
|
|
"latent space."
|
|
]
|
|
},
|
|
{
|
|
"cell_type": "code",
|
|
"execution_count": 29,
|
|
"id": "57de87b8",
|
|
"metadata": {
|
|
"collapsed": false,
|
|
"editable": true
|
|
},
|
|
"outputs": [],
|
|
"source": [
|
|
"def interpolation(point_1, point_2, n_steps=10):\n",
|
|
" ratios = np.linspace(0, 1, num=n_steps)\n",
|
|
" vectors = []\n",
|
|
" for i, ratio in enumerate(ratios):\n",
|
|
" vectors.append(((1.0 - ratio) * point_1 + ratio * point_2))\n",
|
|
"\n",
|
|
" return tf.stack(vectors)"
|
|
]
|
|
},
|
|
{
|
|
"cell_type": "markdown",
|
|
"id": "cfb76bb6",
|
|
"metadata": {
|
|
"editable": true
|
|
},
|
|
"source": [
|
|
"Now we have all we need to do our interpolation analysis."
|
|
]
|
|
},
|
|
{
|
|
"cell_type": "code",
|
|
"execution_count": 30,
|
|
"id": "e25decef",
|
|
"metadata": {
|
|
"collapsed": false,
|
|
"editable": true
|
|
},
|
|
"outputs": [],
|
|
"source": [
|
|
"plot_number = 100\n",
|
|
"latent_points = generate_latent_points(number=plot_number)\n",
|
|
"results = None\n",
|
|
"for i in range(0, 2*np.sqrt(plot_number), 2):\n",
|
|
" interpolated = interpolation(latent_points[i], latent_points[i+1])\n",
|
|
" generated_images = generate_images(interpolated)\n",
|
|
"\n",
|
|
" if results is None:\n",
|
|
" results = generated_images\n",
|
|
" else:\n",
|
|
" results = tf.stack((results, generated_images))\n",
|
|
"\n",
|
|
"plot_results(results, plot_number)"
|
|
]
|
|
}
|
|
],
|
|
"metadata": {
|
|
"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.10"
|
|
}
|
|
},
|
|
"nbformat": 4,
|
|
"nbformat_minor": 5
|
|
} |