{ "cells": [ { "cell_type": "markdown", "metadata": { "id": "V6THckODUGu6" }, "source": [ "# Computational Essays on Artificial Intelligence\n", "## Evolving Robot Control\n", "\n", "In this essay, we explore the Genetic Algorithm to evolve a Robot control network.\n", "\n", "1. A `chromosome` will be composed of a series of `genes`, each representing a weight in a neural network\n", "2. A `population` of chromosomes is generated randomly\n", "3. For a number of generations the chromosomes are `mutated` by a small amount" ] }, { "cell_type": "markdown", "metadata": { "id": "qu0oA5NWUGu-" }, "source": [ "## 1. Setup" ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "[![Open in Binder](https://img.shields.io/badge/-Open%20in%20Binder-579ACA.svg?logo=data:image/png;base64,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)](https://mybinder.org/v2/gh/ArtificialIntelligenceToolkit/aitk/HEAD?filepath=notebooks%2FEvolvingRobotControl.ipynb) [![Open in Colab](https://colab.research.google.com/assets/colab-badge.svg)](https://colab.research.google.com/github/ArtificialIntelligenceToolkit/aitk/blob/master/notebooks/EvolvingRobotControl.ipynb)" ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "
\n", "

Note: if running this notebook on Google Colab, you will need to install the required Python packages each time you start a session. You do not need to do this in Binder. Otherwise on your own computer, you only need to install the packages once.

\n", "

To install the required Python packages:

\n", "
" ] }, { "cell_type": "code", "execution_count": 1, "metadata": { "colab": { "base_uri": "https://localhost:8080/" }, "id": "baekK6oGUGu9", "outputId": "df8356cd-2624-4698-f439-051aed1c82d3" }, "outputs": [ { "name": "stdout", "output_type": "stream", "text": [ "Note: you may need to restart the kernel to use updated packages.\n" ] } ], "source": [ "%pip install aitk --upgrade --quiet" ] }, { "cell_type": "markdown", "metadata": { "id": "1X8RdoEyUGu-" }, "source": [ "For this demonstration, we will need:\n", "\n", "* `GeneticAlgorithm` defined in aitk.algorithms.ga\n", "* `World`, `Scribbler`, `RangeSensor` from aitk.robots \n", "* `SimpleNetwork` from aitk.networks\n", "* a few other support functions" ] }, { "cell_type": "markdown", "metadata": { "id": "Fd1XrIsHUGu_" }, "source": [ "We import everything need in this cell:" ] }, { "cell_type": "code", "execution_count": 2, "metadata": { "id": "AIR4jwkRUGu_" }, "outputs": [], "source": [ "from aitk.algorithms.ga import GeneticAlgorithm\n", "from aitk.robots import World, Scribbler, RangeSensor\n", "from aitk.networks import SimpleNetwork\n", "from aitk.robots.utils import distance\n", "import random" ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "## 2. The Genetic Algorithm" ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "The [Genetic Algorithm](https://en.wikipedia.org/wiki/Genetic_algorithm) (GA) is an approach inside the category of machine learning systems called [Evolutionary Algorithms](https://en.wikipedia.org/wiki/Evolutionary_algorithm) (EA). For this essay, we will use the GA as follows:\n", "\n", "1. We will define a `gene` to be a floating-point number that represents the weight or bias of a Neural Network.\n", "2. We will define a `chromosome` as a sequence of genes. The chromosome (sometimes called a `geneotype`) will represent all of the weights and biases of a network.\n", "3. A `population` will be composed of a set of random chromosomes." ] }, { "cell_type": "markdown", "metadata": { "id": "eyh7XntqUGvA" }, "source": [ "The length of the chromosome is determined by the size of the Neural Network. Therefore, we will call `self.build_model()` (defined below) and see how many weights it has. \n", "\n", "We'll use a ring of 16 laser sensors as senses. We create a small simulated world (100 x 100) to allow the robot to move around.\n", "\n", "The basic idea is that we will have a list of weights (the chromosome) that we load into the neural network. The sensors will be read, and propagated through the neural network. The output of the neural network will be interpreted as movement controls. We'll let the neural network \"drive\" the robot around the world starting from a few poses." ] }, { "cell_type": "code", "execution_count": 3, "metadata": { "id": "QEeYh6UwUGvA" }, "outputs": [], "source": [ "class NNGA(GeneticAlgorithm):\n", " \"\"\"\n", " An example of using the GeneticAlgorithm class to \n", " evolve the weights of a neural network to control \n", " a simulated robot.\n", " \"\"\"\n", " def __init__(self, popsize, sensors=16):\n", " self.show = False\n", " self.sensors = sensors\n", " self.world = World(100, 100)\n", " self.robot = Scribbler()\n", " # We add a ring of RangeSensors, 8cm from\n", " # the robot center, starting and ending \n", " # at the back of the robot\n", " # with 75cm max range, and sensor width 0\n", " # which makes it a laser range finder\n", " self.robot.add_device_ring(\n", " RangeSensor, 8, -180, 180, \n", " self.sensors, max=75, width=0)\n", " self.world.add_robot(self.robot)\n", " self.network = self.build_model()\n", " # Starting poses to test from:\n", " self.poses = [\n", " (20, 20, 0), \n", " (20, 20, 45), \n", " (20, 20, 90), \n", " (20, 20, 180), \n", " (20, 20, -45),\n", " ]\n", " # Length of a chromosome:\n", " length = len(self.network.get_weights(flat=True))\n", " super().__init__(length, popsize)" ] }, { "cell_type": "markdown", "metadata": { "id": "HNvM7oeWUGvB" }, "source": [ "As mentioned, a chromosome will be a sequence of floating-point numbers, between -2 and +2. When we mutate such a gene, we will increase or decrease it a small amount. To define these two aspects of a gene, we define the following methods:" ] }, { "cell_type": "code", "execution_count": 4, "metadata": { "id": "61igCPUsUGvB" }, "outputs": [], "source": [ "class NNGA(NNGA): \n", " def make_random_gene(self):\n", " \"\"\"\n", " Generate a random weight for the neural network.\n", " \"\"\"\n", " # range of [-2, +2]\n", " return 2 - random.random() * 4\n", " \n", " def mutate_gene(self, gene):\n", " \"\"\"\n", " Given a gene, mutate it a little.\n", " \"\"\"\n", " # range of [-0.5, +0.5]\n", " return gene + (0.5 - random.random())" ] }, { "cell_type": "markdown", "metadata": { "id": "8zDTWbLNUGvC" }, "source": [ "### 2.1 The Fitness Function\n", "\n", "To use a GA we need to define a `fitness function`. A fitness function tests a chromosome in the simulated world, and returns a value that represents how \"apt\" (or fit) it is relative to the other chromosomes in the population at this generation.\n", "\n", "Finding a good fitness function is difficult. We don't want to be too prescriptive, or the system will evolve exactly what we have defined it to do. On the other hand, we don't want to be too open, as that can leave the system to evolve solutions that don't really \"solve a problem.\"\n", "\n", "For example, let's say that we want the robot to move around this world and not bump into walls. We could define a fitness that is the total number of steps where the robot did not hit a wall (e.g., `robot.stalled` is not True).\n", "\n", "
\n", " Warning: the implementation of a GA that we use here requires that the fitness function return a number greater or equal to zero.\n", "
\n", "\n", "However, you would very quickly evolve a robot that doesn't move. No movement, no crashing into walls! Well, ok, then. How about we define a fitness function that rewards the robot when it keeps moving. The evolutionary system could \"solve that problem\" by simply spinning the robot in circles. Foiled again!\n", "\n", "You could then define a fitness function that rewards the robot when it has forward movement, thereby circumventing the solution for the robot to spin in circles. However, the system could then just drive the robot in small circles, and we inadvently didn't reward a robot for moving backwards (which should be fine).\n", "\n", "The search the human performs in attempting to find a good fitness function that will drive the robot into interesting control patterns while not settling on simplistic solutions is quite common. However, if you can train yourself to think more like nature, you can end up with some very clever robots.\n", "\n", "To that end, we will define the fitness function to be the distance between where the robot started, and where it ended up after a given number of seconds. Plus, we will add all of the distances traveled on each individual step. However, if it ends up against a wall, it will get a score of zero.\n", "\n", "This is a fairly easy fitness function to compute (the sum of all distance traveled, plus distance from where it started) and should bypass some of the solutions defined above." ] }, { "cell_type": "code", "execution_count": 5, "metadata": { "id": "TN_laHoPUGvC" }, "outputs": [], "source": [ "class NNGA(NNGA): \n", " def fitness(self, chromosome, index=None, poses=None, seconds=None, interrupt=True, real_time=False,\n", " show=False, show_progress=False, quiet=True):\n", " \"\"\"\n", " Fitness is based on the distance the robot travels \n", " from starting pose to ending pose.\n", " \n", " Args:\n", " poses: default None, all of the poses; \n", " otherwise list of poses (x, y, a).\n", " seconds: default 10\n", " interrupt: default True\n", " real_time: default False\n", " show: default True\n", " show_progress: default False\n", " quiet: default True\n", " \"\"\"\n", " if seconds is None:\n", " seconds = min(self.generation / 10, 10)\n", " self.show = show\n", " self.network.set_weights(chromosome)\n", " score = 0\n", " poses = poses if poses is not None else self.poses\n", " for pose in poses:\n", " self.robot.set_pose(*pose, show=self.show)\n", " self.world.seconds(\n", " seconds, self.controller, \n", " interrupt=interrupt, \n", " real_time=real_time, show=show,\n", " show_progress=show_progress, quiet=quiet)\n", " if not self.robot.stalled:\n", " end_pose = self.robot.get_pose()\n", " score += distance(pose[0], pose[1], end_pose[0], end_pose[1])\n", " return score " ] }, { "cell_type": "markdown", "metadata": { "id": "hGDuszABUGvD" }, "source": [ "We can now add some methods for the specific problem:\n", "\n", "* build_model - creates the neural network and takes in the number of sensors, and outputs the movement commands (one for translate, one for rotate)\n", "* controller - the method to drive the robot\n", "* get_sensors - a convenience method to get the sensor readings from the range sensors as a list\n", "* get_move - a method that takes the input to the network (the sensor readings) and propagates them through the network to get the output values, which are scaled to [-1, +1]" ] }, { "cell_type": "code", "execution_count": 6, "metadata": { "id": "gv41Bn_QUGvD" }, "outputs": [], "source": [ "class NNGA(NNGA): \n", " def build_model(self):\n", " \"\"\"\n", " We build a simple network. \n", " \"\"\"\n", " return SimpleNetwork(\n", " self.sensors,\n", " 4,\n", " 2,\n", " activation=\"sigmoid\"\n", " )\n", " \n", " def controller(self, world):\n", " \"\"\"\n", " The controller for the robot.\n", " \"\"\"\n", " sensors = self.get_sensors()\n", " output = self.get_move(sensors)\n", " self.robot.move(output[0], output[1])\n", " \n", " def get_sensors(self):\n", " \"\"\"\n", " We return the sensors from the robot.\n", " \"\"\"\n", " return [sensor.get_reading() \n", " for sensor in self.robot]\n", " \n", " def get_move(self, sensors):\n", " \"\"\"\n", " Given the dataset (a single )\n", " \"\"\"\n", " # Propagate takes a single pattern:\n", " output = self.network.propagate(\n", " sensors, show=self.show)\n", " # Scale the output in [-1, 1]\n", " return 1 - output * 2\n", " " ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "Now, we are ready to create a GA. We'll define the population size to be 30, which isn't too small, but big enough to evolve a solution in a few minutes." ] }, { "cell_type": "code", "execution_count": 7, "metadata": { "colab": { "base_uri": "https://localhost:8080/" }, "id": "IyVFhghhUGvE", "outputId": "2988c304-fc9b-45d8-e793-e180625de46d" }, "outputs": [ { "name": "stdout", "output_type": "stream", "text": [ "Random seed set to: 5594309\n", "Genetic algorithm\n", " Chromosome length: 78\n", " Population size: 30\n" ] } ], "source": [ "ga = NNGA(popsize=30)" ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "We can get a snapshot of the world with:" ] }, { "cell_type": "code", "execution_count": 8, "metadata": {}, "outputs": [ { "data": { "image/png": "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H3/U9fOOO+7wMULLU089Vd/evXv30aNHVz6FrXA1gj0J6yBZO4Q7d+689dZb9e1r1671PufGjRs3btzQt7tzyvYjdJ07d675zx9//HH48WyFqxHsSVgH4dohLIppXxPSfbz9CF3PPffc559/rpS6cuXKa6+99s4779iPOfx4tsIVCesgXM8xoUDnz58/fPjwbbfd9t9//y37bSpfHlsB59ohrLuCka5fv+58hGX+/vvvkY9kK1yNYE/COgjXbkdv3Ljxzz//6NvLuvNbbrll586d+vbFixedj2CPrXA1gj0J6yBcz+zor7/+qm/s27ev9znN+3/77TcfI9hjK1yNYE/COkjWE8KvvvpK3zh06NDu3bu7Dzhy5Eh9+/vvv/cxgj22wtUI9iSsg2jdK2ZOnDhRX77wzDPPdJ/y8ccf659evXr19ttv7z7AfgR7bIWrEexJWAfRuiHcv39//cnL8+fP33XXXc2fHj9+vN6hH330Ue+Y9iN07dmz59ChQ/fff//IKWy2IqetyFw3hEqpM2fO1Pvl3Llzx44d27t374EDB06dOnX16tX6Rw899NCyYe1HaDp8+PDPP/+sn/L+++/3tjRsRd5bkbPeEN57772XLl2qBr3xxhsDw9qP0PTJJ580n3j8+PExz2IrctqKnFVLvt7i8ccfv3LlyrL99e677678FWg/Qq35kdCqql544YWRT2QrctqKbFXLv+jp0KFDX3zxRWtn/fvvvy+//HJ9NeAw+xG0559/vn76xYsXl810sxXZb0WWiqqqhg+vDx48eOTIkb179167du3s2bMffvjh1NOp9iMopY4ePfroo4/+9ddfb7755oULF6Y+na1wNYISsBW5GaiEAALgG7iByAghEBkhBCIjhEBkhBCIjBACkRFCIDJCCERGCIHICCEQGSEEIiOEQGSEEIiMEAKR/T8aNItz7zv2vwAAAABJRU5ErkJggg==\n", "text/plain": [ "" ] }, "metadata": {}, "output_type": "display_data" } ], "source": [ "ga.world.display()" ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "But we can do better than just a snapshot. We can actually dynamically \"watch\" the world update.\n", "\n", "
\n", " Warning: unfortunately the dynamic update may not work on all Jupyter Notebook systems. But it should work in: Jupyter Lab, Jupyter Notebook, Google Colab, and many others.\n", "
\n", "\n", "We'll create two watch windows: one for the world, and one for the network. This will update dynamically when `show=True`. " ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "
\n", " Idea: when running in Jupyter Lab, you can right-click on the two watch cells (after running them) and select \"Create New View for Output\" for each. You can then drag the panels over so that you can see them along side the notebook.\n", "
" ] }, { "cell_type": "code", "execution_count": 9, "metadata": { "colab": { "base_uri": "https://localhost:8080/", "height": 382, "referenced_widgets": [ "686027ec8d104a65a28c531cff802a49", "edfd80f163844472afcad4ac980bb73b" ] }, "id": "npHXLuo8UGvF", "outputId": "e48d4e46-5ea4-4899-947e-c6c85e4d3b4b", "tags": [] }, "outputs": [ { "data": { "application/vnd.jupyter.widget-view+json": { "model_id": "4b6c8d8866cd415fba3eea13999e369d", "version_major": 2, "version_minor": 0 }, "text/plain": [ "Image(value=b'\\xff\\xd8\\xff\\xe0\\x00\\x10JFIF\\x00\\x01\\x01\\x00\\x00\\x01\\x00\\x01\\x00\\x00\\xff\\xdb\\x00C\\x00\\x08\\x06\\x0…" ] }, "metadata": {}, "output_type": "display_data" } ], "source": [ "ga.world.watch()" ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "Likewise, we can get a snapshot of the network architecture:" ] }, { "cell_type": "code", "execution_count": 10, "metadata": {}, "outputs": [ { "data": { "text/html": [ "\n", " \n", " \n", " \n", " \n", " \n", " \n", " Layer: output 'Dense'\n", "Act function: sigmoid\n", "Act output range: (0, 1)\n", "Shape = (None, 2)outputLayer: hidden 'Dense'\n", "Act function: sigmoid\n", "Act output range: (0, 1)\n", "Shape = (None, 4)hiddenLayer: input 'InputLayer'\n", "Shape = [(None, 16)]inputActivations for SimpleNetwork" ], "text/plain": [ "" ] }, "metadata": {}, "output_type": "display_data" } ], "source": [ "ga.network.display()" ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "But, again we can do even better by creating a dynamically updating view:" ] }, { "cell_type": "code", "execution_count": 11, "metadata": { "colab": { "base_uri": "https://localhost:8080/", "height": 345, "referenced_widgets": [ "85948e04362d4b658808951c4635bf00", "dc52a3313d1f45bd9e8e9d10dd2bfcd7", "6203182779304d9c88d259968b51864b" ] }, "id": "9dE6sMacUGvF", "outputId": "4c9b57c0-acb5-42ae-f841-c5a3fc8910fa", "tags": [] }, "outputs": [ { "data": { "application/vnd.jupyter.widget-view+json": { "model_id": "7153bb00bfae40128e51f2a74d9942d3", "version_major": 2, "version_minor": 0 }, "text/plain": [ "HTML(value='
" ] }, "metadata": { "needs_background": "light" }, "output_type": "display_data" } ], "source": [ "ga.plot_stats(\"NNGA\")" ] }, { "cell_type": "markdown", "metadata": { "id": "IPm0T3vOUGvM" }, "source": [ "Let's see the results by trying the best ever over the poses, for 5 seconds each:" ] }, { "cell_type": "code", "execution_count": 18, "metadata": { "colab": { "base_uri": "https://localhost:8080/", "height": 285, "referenced_widgets": [ "8ad2ce156ec745d187f0b182345b655f", "75b5f1165faf4949a9065abec1c2532b", "d9c797c19dd5465ba5e4576f460f11fa", "4bc107b0fdfe431c845e58cf143ac15d", "8e68efa968d849e6b52b408f2cf6785c", "d72ff6dce2f4474caa50979834bba248", "84bb4ccf6acc42d9a48141295b7127f3", "0706330888c84838970071882bb77045", "f01ce7d4998f4cd18793c55b977cd44c", "e0f6d576022b4c3f8731377f4c16b2b2", "c57c8a5a7cf046babd6a571dea10425d", "b700450539e24b3f958ab82cb4c61ba7", "f7880b7cb8234171973ae8fe3d885c5a", "adee9b75ac91429bbda90174d6040d80", "11b25aefe3464744b0363f568b70ff7a", "b19a2c494a144ace9d3abb736fee833f", "9c4c52242d064e90b5da368e1a469632", "14fb98746a0141b3a19d4ddc2f8bfbf7", "6fddbded18e9420ab53fa31b562d97bd", "469f3917098844d0af203e6b977eed1b", "035bbb9735a34758bae96160426f7d9a", "f3e7c40d8aad4e30a33f6f4bc05bd140", "0975fa7125874869bb1ed29c7aea16d1", "8f378d82408e41d287b655a59c952c9a", "ebcfb91f4b434d6ab43cd99e345ce5ec", "3bc9276ac4974a03be28b2833b3c01c4", "b9e7ada87a93489fa6dec933a4d0c569", "29cd109ab212478aa65e835c2817d681", "72c0466433c344be8cd1ca5e2318c683", "bd16fcf2b7c64c468196e8e943597a72", "45ffbc451cc74c828c122c7278667a74", "5ea147a8f055448c8c9db986bb3a4bde", "471f0b80f59e43a39333e263102dc92d", "83f09a58fcd4407684012f5fa19382f2", "ac274c57aaab4939b6c912bcfa37682f", "cf3e41e28e0e40fd84f8ee99a94e16dd", "256452ae51354eaea44d4714edfb8bde", "19497de06b24405fb512395602693b79", "315ee295b7604620b68534d662a5b6d6", "2f3513979be0412c8be18ccae97f113b" ] }, "id": "WmajpIM1UGvM", "outputId": "7b928ca7-7275-4847-9f6b-39a47c5ab00e" }, "outputs": [ { "data": { "text/plain": [ "175.6739394939202" ] }, "execution_count": 18, "metadata": {}, "output_type": "execute_result" } ], "source": [ "ga.fitness(\n", " ga.bestEver, \n", " real_time=True, \n", " show=True, \n", " seconds=5)" ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "Evolution may have created lots of behaviors that crash into walls. \n", "\n", "What is the most interesting behavior that you observer after 10 generations?\n", "\n", "Let's run a few more generations." ] }, { "cell_type": "markdown", "metadata": { "id": "h9hjrUWiUGvM" }, "source": [ "If you wish, you can increase the maximum generations, change any of the parameters, and continue to evolve:" ] }, { "cell_type": "code", "execution_count": null, "metadata": { "id": "xYj9oS8NUGvN" }, "outputs": [ { "name": "stdout", "output_type": "stream", "text": [ " Maximum number of generations: 20\n", " Crossover rate: 0.6\n", " Mutation rate: 0.001\n", " Elite percentage 0.1\n", " Elite count: 3\n", "Generation 11 Best fitness 171.21\n" ] } ], "source": [ "bestFound = ga.evolve(\n", " generations=20, \n", " crossover_rate=0.6, \n", " mutation_rate=0.001, \n", " elite_percent=0.1, \n", " seconds=3,\n", " show=False,\n", ") " ] }, { "cell_type": "code", "execution_count": null, "metadata": {}, "outputs": [], "source": [ "ga.plot_stats(\"NNGA\")" ] }, { "cell_type": "code", "execution_count": null, "metadata": {}, "outputs": [], "source": [ "ga.fitness(\n", " ga.bestEver, \n", " real_time=True, \n", " show=True, \n", " seconds=5,\n", ")" ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "Was the best from the second 10 generations significantly different from the best from the first 10 generations?\n", "\n", "Did the fitness change dramatically during the second 10 generations? \n", "\n", "Do you think there will be a point where the fitness won't get any higher?" ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "## 5. Further Exploration" ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "There is one additional aspect added to the fitness measure: if you don't specify the number of seconds to use explicitly, it will compute the number of seconds based on the generation. This allows the GA to focus on initial movements first, and longer control as the generations increase. You can test this effect by running without explicitly setting seconds. " ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "
\n", " Tip: the best fitness measure will increase with the increased simulation time. So you can't really compare the fitness in the begining generations with following generations when seconds is `None`. \n", "
" ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "Some ideas to try:\n", "\n", "1. Try evolving with seconds=None. How does this compare with a fixed number of seconds?\n", "2. What happens if you try with a larger, fixed number of seconds, such as 10?\n", "3. The above experiments used 0.0 crossover with a high mutation rate. What happens if you try with a higher crossover rate (say 30%) and with a much smaller mutation rate?\n", "4. Anything that you can represent with a list of numbers and create a fitness function for, you could conceivably evole. What else could you try? What are the limitations of the GA?" ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "## 6. References\n", "\n", "1. Turing\n", "2. Holland" ] }, { "cell_type": "code", "execution_count": null, "metadata": {}, "outputs": [], "source": [] } ], "metadata": { "accelerator": "GPU", "colab": { "collapsed_sections": [], "name": "NNGA.ipynb", "provenance": [] }, "kernelspec": { "display_name": "Python 3", "language": "python", "name": "python3" }, "language_info": { "codemirror_mode": { "name": "ipython", "version": 3 }, "file_extension": ".py", "mimetype": "text/x-python", "name": "python", "nbconvert_exporter": "python", "pygments_lexer": "ipython3", "version": "3.8.2" }, "widgets": { "application/vnd.jupyter.widget-state+json": { "state": { "01627eb63e9f48bcaeb16d42ec509165": { "model_module": "@jupyter-widgets/base", "model_module_version": "1.2.0", "model_name": "LayoutModel", "state": { "border": "10px solid rgb(0 177 255)", "margin": "auto" } }, "426932e8f1684d55add824790758e912": { "model_module": "@jupyter-widgets/base", "model_module_version": "1.2.0", "model_name": "LayoutModel", "state": {} }, "4b6c8d8866cd415fba3eea13999e369d": { "buffers": [ { "data": "/9j/4AAQSkZJRgABAQAAAQABAAD/2wBDAAgGBgcGBQgHBwcJCQgKDBQNDAsLDBkSEw8UHRofHh0aHBwgJC4nICIsIxwcKDcpLDAxNDQ0Hyc5PTgyPC4zNDL/2wBDAQkJCQwLDBgNDRgyIRwhMjIyMjIyMjIyMjIyMjIyMjIyMjIyMjIyMjIyMjIyMjIyMjIyMjIyMjIyMjIyMjIyMjL/wAARCAFWASwDASIAAhEBAxEB/8QAHwAAAQUBAQEBAQEAAAAAAAAAAAECAwQFBgcICQoL/8QAtRAAAgEDAwIEAwUFBAQAAAF9AQIDAAQRBRIhMUEGE1FhByJxFDKBkaEII0KxwRVS0fAkM2JyggkKFhcYGRolJicoKSo0NTY3ODk6Q0RFRkdISUpTVFVWV1hZWmNkZWZnaGlqc3R1dnd4eXqDhIWGh4iJipKTlJWWl5iZmqKjpKWmp6ipqrKztLW2t7i5usLDxMXGx8jJytLT1NXW19jZ2uHi4+Tl5ufo6erx8vP09fb3+Pn6/8QAHwEAAwEBAQEBAQEBAQAAAAAAAAECAwQFBgcICQoL/8QAtREAAgECBAQDBAcFBAQAAQJ3AAECAxEEBSExBhJBUQdhcRMiMoEIFEKRobHBCSMzUvAVYnLRChYkNOEl8RcYGRomJygpKjU2Nzg5OkNERUZHSElKU1RVVldYWVpjZGVmZ2hpanN0dXZ3eHl6goOEhYaHiImKkpOUlZaXmJmaoqOkpaanqKmqsrO0tba3uLm6wsPExcbHyMnK0tPU1dbX2Nna4uPk5ebn6Onq8vP09fb3+Pn6/9oADAMBAAIRAxEAPwDzeKJpWKqQDjPNenfEi3d/BngRQVyunkHP/XOGvNrP/XH/AHa9S1j/AJIboH/X+3/oU9a5jjq1LESjC3upNad9zbNsyxFDFShTa92KktOr0Z5b9jk/vL+dH2OT+8v51eorzP7cxndfd/wTx/8AWTH91/4Cv8yj9jk/vL+dH2OT+8v51eoo/tzGd193/BD/AFkx/df+Ar/Mo/Y5P7y/nR9jk/vL+dXqKP7cxndfd/wQ/wBZMf3X/gK/zKP2OT+8v50fY5P7y/nV6ij+3MZ3X3f8EP8AWTH91/4Cv8yj9jk/vL+dH2OT+8v51eoo/tzGd193/BD/AFkx/df+Ar/M7X4YabHpg1fxdeKrJpVuwtlZiiySsp4D9M4+XGD/AKwHrjPB3i3d/ez3lzKslxcSNLK+MbmY5JwBgcntXpfib/ilvh1pPh6L5brU/wDTb4Nw6/dIVkOSvO0ZGP8AVHjk153V1M3xUJaNXsr6F1c9xlOd01zWV9Pnbf8AplH7HJ/eX86Pscn95fzq9RUf25jO6+7/AIJH+smP7r/wFf5lH7HJ/eX86Pscn95fzq9RR/bmM7r7v+CH+smP7r/wFf5lH7HJ/eX86Pscn95fzq9RR/bmM7r7v+CH+smP7r/wFf5lH7HJ/eX86Pscn95fzq9RR/bmM7r7v+CH+smP7r/wFf5lH7HJ/eX86Pscn95fzq9RR/bmM7r7v+CH+smP7r/wFf5lH7HJ/eX86Pscn95fzq9RR/bmM7r7v+CH+smP7r/wFf5lH7HJ/eX86Pscn95fzq9RR/bmM7r7v+CH+smP7r/wFf5lH7HJ/eX86Pscn95fzq9RR/bmM7r7v+CH+smP7r/wFf5lH7HJ/eX86Pscn95fzq9RR/bmM7r7v+CH+smP7r/wFf5lH7HJ/eX86Pscn95fzq9RR/bmM7r7v+CH+smP7r/wFf5lH7HJ/eX86Pscn95fzq9RR/bmM7r7v+CH+smP7r/wFf5lH7HJ/eX86r1rVk17WUY6tiuf2ttLdLdz6HIcyxGN9p7ZrS1rK29yxZ/64/7tepax/wAkN0D/AK/2/wDQp68ts/8AXH/dr1LR/wDkhuv/APX+v/oUFeZmuuMmv7v/AATx871x9Rd4L8rnndFFFeCfMBRRRQAUUUUAFFFFABW/4L0NfEPiuysJUdrbcZJ9qkjy1GSCQRgHhc54LCsCvRPB/wDxTHgbWPFf3L6b/QrHf8vUjLLnIfnnGP8AlkRnrjWjFSnrstX8jfDwUqi5tlq/RGB481xtf8XXlwHRreBjb25RgymNCeQQOQxy3f72M8VzVFFROTlJyfUynNzk5PqFFFFSSFFFFABRRRQAUUUUAFFFFABRRRQAUUUUAFFFFABRRRQAUUUUAFFFFABWTWtWTX0vDv8Ay8+X6n2HCn/L3/t39SxZ/wCuP+7XqWj/APJDdf8A+v8AX/0KCvLbP/XH/dr1L4ef8ih43/68B/6LmrDNP9+a7x/RnNnWuZSXeH/tr/yPO6KKK8E+YCiiigAooooAKKKKAHxRSTzJDDG8ksjBURBlmJ4AAHU133xGlj0ax0jwfZyI9vYwiadlOS8zZ5I5KnlmxnpIOOBVH4Z6RDqHigX14dlnpkZu5JGyEDKfl3NkBcHLc9dh7Zrnde1ebXtcvNUnG17iTcF4OxRwq5AGcKAM45xW69yk31l+S3/E6V7lBvrLT5Lf8bGdRRRWBzBRRRQAUUUUAFFFFABRRRQAUUU0nJwKqMXJlwg5MXdlsClpAMCloly390JuN/dCiiipICiiigAooooAKKKKACiiigArJrWrJr6Xh3/l58v1PsOFP+Xv/bv6liz/ANcf92vUvh5/yKHjf/rwH/ouavLbP/XH/dr1L4N/8jfd/wDXg/8A6MjrDM/+Rjbul+TObOP+RrbukvvTPO6KKK8E+YCiiigAooqKWUxlflJB6mhK50YXC1cXVVGiryd7K6Wyv1/pktFAORkVe0bTJNZ1qz02LeGuZljLKm8oCeWx3AGSfYUJNuyMFFt8vU7aL/ilPhM86fJqHiCTyyH+VlgG4cKc7gVz8wA/1o54GfO67X4manHNr8OjWexdP0iFbaFVfeM7Ru565GFUgk8p6k1xVbV2ublWy0/z/E3xLXPyLaOn+f4hRRRWJzhRRRQAUUUUAFFFFABRRTSSTgVUY8zLhBydgJycClAwKAMClpykrcsdipzVuWO35hRRRUGQUUUUAFFFFABRRRQAUUUUAFFFFABWTWtWTX0vDv8Ay8+X6n2HCn/L3/t39SxZ/wCuP+7XqXwb/wCRvu/+vB//AEZHXltn/rj/ALtd38Mv+Sh6X/21/wDRT1z5o7Zin/h/U5M6ds2i/KP6nJUVreKv+Rv1r/r/AJ//AEY1ZNeHJWbR83JcsmuwUUUUhBTXUOhU96dRQXTqSpTVSDs07r1RXgcqxibqOlek/DSKPTIda8U3MaNFp1sVhEo2q8jc4Vz0bgLwCf3g9cHzi4XAEg6qRXpPis/8I98OdB8PqDHdXn+m3i/6txnkLInU8sBk94unHHRS0bq9vz6H0OPdOuo5pTVuZPmXaorbeTupfeefyyyTzPNNI8ksjFndzlmJ5JJPU0yiiuc+bCiiigAooooAKKKKACiimk9h1pxi5OyKhBydkBPYdaUDH1oAx9aWrlJJcsdi5zSXJDb8wooorMyCiiigAooooAKKKKACiiigAooooAKKKKACsmtasmvpeHf+Xny/U+w4U/5e/wDbv6liz/1x/wB2u7+GX/JQ9L/7a/8Aop64Sz/1x/3a6Twr/wAjfov/AF/wf+jFrkzd2x6f+E4c+fLmiflEPFX/ACN+tf8AX/P/AOjGrJrrfib/AMlD1T/tl/6KSuSryKqtOS82eDWXLUkvN/mFFFFQZhRRRQB0PgfRv7c8YafavHvgSTzpw0XmLsT5iGHTDEBef7w69KZ411v+3vGWp3Kyb4Y5fIhxJvXYnygqemGILYH949etdN4T/wCKd+HWveIW+S6u/wDQrMn9247Fo36nlicD/nl14480X5Lor2cZ/wA/rXRL3aSj1ev+R7mDoQqYOrRfx8vOv+3Wr/8Akt38vvnooornPDCiiigAooooAKKKaT2HWnGLk7IqEHJ2QE9h1pQMfWgDH1pauUklyx2LnNJckNvzCiiiszIKKKKACiiigAooooAKKKKACiiigAooooAKKKKACsmtasmvpeHf+Xny/U+w4U/5e/8Abv6liz/1x/3a6Lw3LHB4p0iaaRI4o72FndzhVAcEkk9BXO2f+uP+7V6uLO3bG38kedxE+XML+UTsPihFJH8QNQZ43VZFiZCwwGHlqMj1GQR9Qa4+vRPjJ/yN9p/14J/6MkrzuvNxCtVkvM8jFK1ea8wooorE5wp8UUk8yQwxvJLIwVEQZZieAAB1NMrtfhfpkd34r/tC62LZ6bC1zJJKmYwcYXLHhSMlwf8AYP1F04c8lHuaUqbqTUF1LnxLlj0yHRfC1tIjRadbBpjEdqvI3GWQdG4Lckn94fXJ84m4kjbpzgmtTWdTk1nWrzUpd4a5maQKz7ygJ4XPcAYA9hWVdf6of71VOfPUbW36HtZPP2uawitpXj8nGUfy19SeiiisjwQooooAKKKaT2HWnGLk7IqEHJ2QE9h1pQMfWgDH1pauUklyx2LnNJckNvzCiiiszIKKKKACiiigAooooAKKKKACiiigAooooAKKKKACiiigArJrWrJr6Xh3/l58v1PsOFP+Xv8A27+pYs/9cf8Adq9VGz/1x/3avVw55/vj9F+p5vEn+/v0j+TPRPjB++8R6feRfPazWC+VMvKP87n5W6HhlPHqPWvO69E+If8AyKHgj/rwP/ouGvO68/E/xW+/+SPKxn8ZvvZ/ekFFFFYHMFeif8i38Hv+ed9rtx/1ykWEfq6EL7DE3vzxOjaZJrOtWemxbw1zMsZZU3lATy2O4AyT7Cun+KGpx3fiv+z7XYtnpsK20ccT5jBxlsKOFIyEI/2B9BvT92Ep/JfP/gHTR9ynKp8l89/w/M4qorgAwn2qWorj/UN+H86xjudOTtrMaDX88fzHoSY1J6kCnU2P/VJ/uinUnucmKSjiKiWylL/0phRRTWbHA604xcnZGUYuTsgY44HWlAx9aRVxyetOq5SSXLEucklyR+fmFFFFZmQUUUUAFFFFABRRRQAUUUUAFFFFABRRRQAUUhYduaPmPtVcj66F+ze70FoooqSAooooAKya1qya+l4d/wCXny/U+w4U/wCXv/bv6liz/wBcf92r1UbP/XH/AHavVw55/vj9F+p5vEn+/v0j+TPRPH3+keCPBdxD+8gS0MTyJyqvsjG0kcA5Vhj/AGT6GvO69E1j/khugf8AX+3/AKFPXndefiPiT7pfkeVitZp90vyCiiisDmPQfhzFHo1jq/jC8jR7exhMMCsMl5mxwDyVPKrnHSQ88GuBllknmeaaR5JZGLO7nLMTySSepr0Hxh/xTHgbR/Cn3L6b/Tb7Z8vUnCtjIfnjOf8AlkDjpjzut63upU+2/qzpxHuKNLtv6v8ApIKiuP8AUN+H86lqK4P7lvwrGO50ZQm8woW/nj+aHx/6pP8AdFOpqAiNQeoAoZscDrTUXJ2Rz4he0xM+XW8pf+lMGbHA60KuOT1oVccnrTqqUklyxIlJRXJD5vuFFFFZmIUUUUAFFFFABRRRQAUUUUAFFFJuHbmmot7FRi5bC0EgdaT5j7UBQKdkt2VyxW7+4Mk9BRtz1OaWijnt8Ogc9vhVgAxRRRUmbdwooooAKKKKACsmtasmvpeHf+Xny/U+w4U/5e/9u/qWLP8A1x/3avVRs/8AXH/dq9XDnn++P0X6nm8Sf7+/SP5M9E1L/SPgbpHkfvfs1+3n7Pm8rLS43Y+799ev94eorzuvRNH/AOSG6/8A9f6/+hQV53Xn1/svyX+R5WJ15H3iv1QV0vgPQ21/xdZ25RGt4GFxcB1DKY0I4IJ5DHC9/vZxxXNV6J4Z/wCKW+HWreIZflutT/0KxK8Ov3gWVxkrzuODj/VDnkVNCKc7vZav5E4eKlUvLZav5f0jmvGmuL4h8V3t/E7tbbhHBuYkeWowCAQMA8tjHBY1gUUVnKTlJyfUynJzk5PdhUNx8wRP7zVNVeXJuUHUAZqqcXKWh6+Qq2NVW/wRlL5qLt+LRMzY4HWhVxyetCrjk9adTlJJcsTzJSUVyQ+b7hRRRWZiFFFFABRRRQAUUUUAFFJu9OaOT7VXI+uhfs39rQCQOtGSeg/OgKBS0XittR3itlcTbnqc0tFFJyb3JlNy3CiiikSFFFFABRRRQAUUUUAFFFFABWTWtWTX0vDv/Lz5fqfYcKf8vf8At39SxZ/64/7tXq67xikWv2OmeNrUIDfRi11CNSP3d0gx03EgMo4HZVBPLVyNednM+bFN+S/U8niCfPjW/JffqeiaF/pHwW8RW8P7ydLtZXjTllTMR3EDkDCsc/7J9DXndeifDz/kUPG//XgP/Rc1ed1xVdYQfl+TPOr606b8vybJrW1mvbyC0t03zzyLHGuQNzMcAZPHU13HxIuobBNJ8KWj5j0q3XzygKLJKyjkp0zjLZyf9Ye+c1vhnpkc2vzazebF0/SIWuZmZN4ztO3jrkYZgQDynqRXMazqcms61ealLvDXMzSBWfeUBPC57gDAHsKF7lK/WX5L/ggvcoX6y/Jf8H8ijRRSE4+tYpNuyOeMXJ2QjNjgdahiGbiRvTj/AD+VSMwjQu3XsKS3XEe49WOa1bUYuMT3sNH6vl1etb4rQT7tvmlbySS+bJaKKKxPACiiigAoopN3pzTUW9ioxcthaQkDrRgnqcfSgACnaK3ZXLFbu/oJknoPzpdvqc0tFHPb4dA9o18OgUUUVJmFFFFABRRRQAUUUUAFFFFABRRRQAUUUUAFFFFABWTWtWTX0vDv/Lz5fqfYcKf8vf8At39TvPhvfRXj6h4Sv5tljq8WImYnEVwvKMBuA5IHHViqDpXP3VrNZXk9pcJsngkaORcg7WU4IyOOorO06WSC8WaGR45Y8MjocMpBBBBHQ16N4ss28Uw6N4l06NGuNTZLK6hVgNt2PlA5Y4DAcDjAUE/ermzqKliGlukvx/4Jx8RRU8XJRWqSfqn/AJO33ln4cfvvDnjKzi+e6msP3UK8u/ySj5V6nllHHqPWvO69A+Gl3pvh/wAR3F3qmuaHbwPaNGrf2tbPli6HGFkJ6A1gaT4M1vVdStLdbC5S2ncf6YsW6ERnkyK/3WXHIIbDcYJyK82dKbpwVnfVfieRUo1HSprld9Vt5/8ABOjl/wCKU+EyQP8AJqHiCTzAU+VlgG08sM7gVx8pI/1p44OfO67j4p3rTeKI7FLaW2s7G3WG3jZWRGGTlkUgADPy5HB2DBxiuGJx9aisrz5I9NP6+ZGITdT2cfs6f5/jcCcfWkA7nrQB3PWmTS7AAvLnoKlvl92JrhcNUxFWOGw+spdennr0S6sZJ++lEYPA5bFWAMDAqKCLy1ycbjUtZvsjpzbEU3KGEw0ualSVk/5pP4pfN6LyX3lFJu9BmjBPU4+lPka30PK9m18WgEgUZJ6DH1oAApad4rZDvFbK/r/kJt9eaWiipcm9yZSctwooopEhRRRQAUUUUAFFFFABRRRQAUUUUAFFFFABRRRQAUUUUAFFFFABWTWtWTX0vDv/AC8+X6n2HCn/AC9/7d/U9c1v4d6FpsVlr1jfOdCmtCzJJOsUsjCEvGUZ15LlRlduQScDHC0tN1a5k0vS5LA2FrDaam882nrfpbLIq+QybvMfc+WV8Md2Og4AFYHiUXMF9b2dx5sZgsrTMEmR5b/ZoQ3ynoflAP0HpWNXk43FSnXk300+5n3dHhTD4vCOdab558r5kldJapK/fq+voaH/AArrw7/z73H/AIVFh/8AG6rt8Ildi6p4pgVjkRHQvNKD+7v81d2Om7aueuB0qvRULHVF/S/yMp8Ef8+8S/nGL/Jo659BsdH8IWsGo2Osqll5VnDdXFt9lcl5LqV2EZLBwNycbgTj7y5rm7nTJIIFvI3Se0dtqyqwyp5wHUElG4PB67SVLAZqrWnojOZr23Cb45rC48xM4zsjaVefZ40bjrtx0JFZOq6krdzlzDgmjDL5yhP98rvm2T8mru3qvnoZEkgjXJ69h61DEuWMsnU8gUqQs5Dynn0qcADpU+7HS9z4ariMPluHlhcNLmqS0nNbW/li+3d9dtgyT0GKNvrzS0VHO+mh8/7R/Z0CiiipICiiigAooooAKKKKACiiigAooooAKKKKACiiigAooooAKKKKACiiigAooooAKKKKACsmtasmvpeHf+Xny/U+w4U/5e/9u/qdZ4iUxa1Jat9+zihs5COheGJYmI9iyEj2xwOlZVa3in/kb9a/6/5//RjVk187Vk5VJSfVv82ftODio4eml/KvyX+ZpXFtZ6VptrqN1HcahbTFfOls32Q2IZtuZ32O6MDxt8rDA5VmwRWhretyC0Oi2Vla2FiNrSfZlw13xuV3fe+5Dkso3MAGGGbAasGCeS2mEsRAYAghlDKwIwVYHhlIJBByCCQeDWld6lY3ukW9sdOjtLm0DCKS14jkVnLlWjJwvzPIwK4AztCgYK6e0g6fLFWf5nnrCYmOM9rVl7Sm9ltyPo7LR+urW9upk1reHjsvbqZuI47C73uei7oHRcntlmVR6lgOprJrZ0eKQ6L4hmEbGJbKNWcD5QTcwkAn1O0/kfSsqfxIef1fZZXiJ/3H+Kt+pheYnr+lHmJ6/pT6Kq9Ps/v/AOAfz1el2f3/APAGiRT3pdy/3h+dKRnrSbV/uj8qP3fn+AXpdn+Abl/vD86AQehBo2r/AHR+VIUU9hR+78/wD9z5/gOopnlp6frR5aen60Wp9393/BC1Lu/u/wCCPopnljsSPoaNn+0350csP5vwDkp/zfg/8x9FM2f7TfnRtYdH/Sjkh0l+DDkh0n+D/wCCPopm1/7/AOlG1/7/AOlHJH+Zfj/kHs4/zr8f8h9FM/ef7NH7z/Zo9n5r7/8AgB7L+8vv/wCAPopn7wdlNG5/7n60eyfRr7w9i+jX3r/gD6KZuf8AufrRuYdUP4c0eyl5fev8w9jLy+9f5j6KZv8A9lvyo3/7LflR7Kfb8v8AMPYVO35f5j6KZ5i98j6ijzE9f0o9jU/lYewq/wArH0UzzE9f0pQ6n+IUnSmt0xOjUW8X9w6ik3L/AHh+dG5f7w/OlyS7C9nPs/uf+QtFFFSQFZNa1ZNfS8O/8vPl+p9hwp/y9/7d/U9R8UWnhnXNWuG8M3l1d6k8RvHjSLetw0kgYqvIYOquSVCnCrzgq1Y1l4dt7q1sm83VJrq5t2uPIsdOE+xBK0eSfMU9U9O4rD8JaHdeIvEEOnWqOTJzJIq5EUeRuc8jgfUZOB1Irv8A4nXehX+qXujG8ulDaZHYy3MIFyY3S4SUKQzKWICEMd55Ydwc8mYYSlSrtbLT8b3PexvEmNwMPqsJ2s42m7N2ad1Zq2llr02MWXwisULyNH4hhVVLGW50UpEgH8TsJGIUdSQCQOx6Vl/2RY/9DJpf/fu5/wDjNVZPhVaab4asPFNp4tuofOuNlqx0/wAto5VLbWZllYqAYydyhiOCATxVvSvDnxC1v7QNN+Jf2h7ePzJI11K+D7fVVMeW/wCAg9R6iuf6rSbspL+vmc9PivM4+6q8ZN94r8LNEd1orww2ktpdwagt1M8EYtVk3eYuzK4dFJJ8xcYzWzo2uzeC7O6jCxXNxqMEZkspoQ0IjYblZ2zliUY/IOMSAk5BUWLbxuul+LpbuCCCeyGq3F0sxiJlMUpAcLkgAlVUjgHIxnBIOv4/8Jx6jC3jDw+6XVjcr5twkS42djIAB04O4HkHJPfbzRhZOdN6r+rmGZcXYrH5d9Xglzfbdlqrv4VrZba7mW8Hg7xXue0l/wCEb1Nst5M7brSQ/OeH42fw+gHQKawNe8K6x4bmK6jZukRbalwnzRP1xhh3O0nBwcdqxq39D8ZaxoMLW0EyXFi6lXsrpfMhYHORtPQHcScEZ75rPnhP4lZ91/l/kfD89Ofxqz7r9V/lYwKK7h4PB3ivc9pL/wAI3qbZbyZ23Wkh+c8Pxs/h9AOgU1ga94V1jw3MV1GzdIi21LhPmifrjDDudpODg47UpUmldaruv60JnRlFcy1Xdf1p8zGooorMyCiiigAooooAKKKKACiiigAooooAKKKKACiiigAooooAKKKKACkKg9QKWihNrYabWwm1f7o/Kjav90flS0VXPLuV7Sfd/e/8wAwMUUUVJG4Vk1rVk19Lw7/y8+X6n2HCn/L3/t39T2nw7BD8MvAbaxfW0S+I9RyIIJwRIEyMKQCTgD52+7yQpwQK8vllknmeaaR5JZGLO7nLMTySSeprS8SeMb7xlq0F1eRpCsFuI0hjZigbguwBPBY/oFBzjJzYopJ5khhjeSWRgqIgyzE8AADqa8/N3U+scs+yf3nlZ86v1vlqb2T+89B8ff6P4I8F28P7uB7QyvGnCs+yM7iBwTlmOf8AaPqay/hlqy6V42tRIUWK8U2rMykkFsFcY7l1UZ6YJ+o0fjFLHJ4xgVJEZo7JFcKclTvc4PocEH6EVwlrdTWV5Bd277J4JFkjbAO1lOQcHjqK46s+SvddLfhY4K1T2eJuvs2/BIveI9JbQvEV/phDhYJiI97AsYzyhJHGSpB/HtW/4B8YQ+Hbiew1OPztIvsLMrAsIzjBbb0IIOGGMkAdcYNv4pWsM2o6X4htk2QavaLJtYnfuUDlhyB8rIOD2P1PA1Mm6NV8vT8v+GIm3h67cOn5P/gM7Px94Ph8O3EF/pknnaRfZaFlJYRnGQu7oQQcqc5IB64yeMr0/wCHvimHU7dvCHiIxT2M0fl2zTk5zkYiz+qnIIIAH8IHGeKfC194U1Q2l2N8L5aC4UYWVf6EcZHb3BBLqwTXtYbP8H/Ww69OLj7an8L3XZ/5djDrf0PxlrGgwtbQTJcWLqVeyul8yFgc5G09AdxJwRnvmsCisIycXeLOeM5Qd4ux3CyeCPE7tG8Evhm+eQlJVczW7bmXhgcberYxtUDknoKxtc8G6xoMK3M8KXFi6hkvbVvMhYHGDuHQHcAMgZ7ZrArZ0HxVrHhuYNp146RFtz27/NE/TOVPc7QMjBx3rTnhL4181/lt+Rr7SE/4is+6/wAtvyMaiu4Sfwd4r2pdxf8ACN6m2F86Bd1pIfkHKcbP4vQDqWNY2ueDdY0GFbmeFLixdQyXtq3mQsDjB3DoDuAGQM9s0pUmlzR1Xl/WgpUZJc0dV5fqt1/WpgUUUVmYhRRRQAUUUUAFFFFABRRRQAUUUUAFFFFABRRRQAUUUUAFFFFABRRRQAVk1rVk19Lw7/y8+X6n2HCn/L3/ALd/UsWf+uP+7XVeDbWa78aaNHAm91u45CMgfKh3sefRVJ/CuVs/9cf92vQ/hTZSXXju2mRkC2sMkzhjyQV2ce+XH4ZrlzaPNmCX+E4s9jzZol5RKPxEuobvx9q0kD70WRYycEfMiKjDn0ZSPwrmKva1ex6jruo30KusVzcyTIHGGAZiRnHfmqNeNUlzTb8zwKsuapKXds9El/4nvwWS4m4n0W78pJH+dnQlRtBPKjEijHP+rHtjzuvQfhVLHdX2saBcSJHb6nZMpIOJCRkYTPGdrueh+7nsa4GWKSCZ4Zo3jljYq6OMMpHBBB6GtavvRjP5fd/wDWv70IVPK33f8CwyvWvD2qWPxD8LjwvrV1KmrQfPb3DtkylQcMOm4hSQVPJHOc5K+S1Na3U1leQXdu+yeCRZI2wDtZTkHB46ippVfZvXVPcihW9lLXVPdeRY1jSbrQtXuNMvQguIGw2xsqQQCCD6EEH155xVGvYLqG3+Kng97+3tYovEVjiMqsgXd3x3Oxhu27sYYEZxknyKWKSCZ4Zo3jljYq6OMMpHBBB6GitS5HeOsXsOvR9m046xez/rqMooorIwCtnQfFWseG5g2nXjpEW3Pbv80T9M5U9ztAyMHHesainGTi7plRlKLvF2Z3CT+DvFe1LuL/hG9TbC+dAu60kPyDlONn8XoB1LGsbXPBusaDCtzPClxYuoZL21bzIWBxg7h0B3ADIGe2awK2dB8Vax4bmDadeOkRbc9u/zRP0zlT3O0DIwcd6054z+NfNf5f8ADGvtIT/iKz7r9Vs/wMaiu4Sfwd4r2pdxf8I3qbYXzoF3Wkh+Qcpxs/i9AOpY1ja54N1jQYVuZ4UuLF1DJe2reZCwOMHcOgO4AZAz2zSlSaXNHVeX9aClRklzR1Xl+q3X9amBRRRWZiFFFFABRRRQAUUUUAFFFFABRRRQAUUUUAFFFFABRRRQAVk1rVk19Lw7/wAvPl+p9hwp/wAvf+3f1LFn/rj/ALtepfCv/QP+Eh17/Wf2fYH9x08zOX+92/1WOh+97c+W2f8Arj/u16l4X/4lvwm8UapD809xItm6vyoQ7VyMc5xM3fsOOucM00x7faN/wZzZzpmjl2in9yZ53RRRXgnzBreF9T/sbxRpt+ZvJjiuF819u7EZOH4wf4S3Tn05rX+JWmf2Z45vtsPlw3W25j+bO7cPmbrxlw/H9MVyVeieN/8AiceA/C3iAfM6xmznkl5lkcDGSecjdHIeT/F05Nbw96lKPbX9GdNP3qM49rP9H+h53RRRWBzGjomt33h7VI9Q0+XZMnBU8rIvdWHcH/AjBANek+MNAt/HOlx+K/DTedOsey4tQoDtt9hz5ig4wc5AGO27yWul8E+LJPCWtfaSjzWcy+XcRK2CRnIYDOCw5xnsSOM5G9Gorezn8L/DzOqhVik6VT4X+D7nNUV6D8QvCNrZw2/iPQonOlXqiSRUTCQlsFSB1VWz0xgHjjIA8+rOpTdOXKzGrSlSlyyCiiioMwooooAK2dB8Vax4bmDadeOkRbc9u/zRP0zlT3O0DIwcd6xqKcZOLumVGUou8XZncJP4O8V7Uu4v+Eb1NsL50C7rSQ/IOU42fxegHUsaxtc8G6xoMK3M8KXFi6hkvbVvMhYHGDuHQHcAMgZ7ZrArW0bxPrXh/eNL1CW3R87o+HQk452sCM8DnGa054y+Na91/lt+Rr7SE/4i17r9Vt+Rk0V3D654W8Vbv7esP7H1Jsn+0LBCY3b5zmSPk9SuSMsT3UVna14F1TS7c39oYtU0s7it7ZN5i7QWyWA5XAU5PKjpmh0na8dV/XQUqDtzQfMvL9VucxRRRWRiFFFFABRRRQAUUUUAFFFFABRRRQAUUUUAFZNa1ZNfS8O/8vPl+p9hwp/y9/7d/UsWf+uP+7XqV5/oHwN0/wCzfu/7Qvz9q7+Zhnx16f6pOmPu+5z4adfUEj7OeP8Ab/8ArV22o+LtX8XeFdG0vSPCWoCy0qMrK1oHljmkwo3sFQANncecn5zzyc64zD0q9Z1FO11bbbzNswwtHE4h1VUtzLl228/nsV6K5KTWzFI0clo6OpwyscEH0IxTf7fX/n3P/fX/ANauH+yaP/P7/wAl/wCCed/YWH/6CP8AyV/5nX16J4N/4nXw68TeHouLpMXsQX53lxtO1U69Y1GRn7449fHZjq1vpianPoOoRae+Ct08LrE2emHK4OfrWl4K+JjeDddbUo9MF0rwtBJE0uwlSQeGwcHKjseM/UVHLaVO8lVvo9LFRyehSvKNa+jVuW17/MuUVyc3iGBp5DBaSJEWJRXlDMq54BIUZOO+B9BTP7fX/n3P/fX/ANap/smj/wA/v/Jf+CT/AGFh/wDoI/8AJX/mdfRXIf2+v/Puf++v/rVd0+41DV5Gj03Rr29dfvLbRtIR9QoNH9k0f+f3/kv/AAQ/sLD/APQR/wCSv/M9g+HfjP8Asy4GhatJE+i3W5D9o5WEsD7EbGPBB4Gc8c5zvHfgy48L6o8sce7S7iQm3kXJCZ58tsknIHQk8gZ9QPK73ULrTbg299plzazgZMc6lGH4EZrsdO+Max+CZvC+s6NPqds/yxy/bvKaJBtKqPkOdrDIzkdBjAxVzy+n7NR9pe3lt/mXUyqj7JQ9tzWemmqX6+hVorDs49a1G2NzY+HdSurcdZYIHdfzC4rOfXPKkaOS0dHU4ZWbBB9CMVH9k0f+f3/kv/BI/sLD/wDQR/5K/wDM62iuQ/t9f+fc/wDfX/1qP7fX/n3P/fX/ANaj+yaP/P7/AMl/4If2Fh/+gj/yV/5nX0VyH9vr/wA+5/76/wDrVPBqVzdKWt9NuJlBwTGCwB/AUf2TR/5/f+S/8EP7Cw//AEEf+Sv/ADOoornGur9FLNo94FAySY2wP0qn/b6/8+5/76/+tR/ZNH/n9/5L/wAEP7Cw/wD0Ef8Akr/zOvrR0jXtU0G4M+l3sts7feCnKvwQNynhsZOMjjNcVHeXssayR6TdujDIZUYgj64pk2oXVtH5k+mXMSZxudSo/MimsqpJ3Vb8H/mNZJQi7rEf+Sv/ADPX31zwt4q3f29Yf2PqTZP9oWCExu3znMkfJ6lckZYnuorO1rwLqml25v7QxappZ3Fb2ybzF2gtksByuApyeVHTNeU/2+v/AD7n/vr/AOtWnovxB1Tw7cm40mee1dvvBXBV+CBuUgq2MnGQcZ4q5ZbRktat33tb7zSeUYecXzVk335Wvv8A+GNyiq3iX4kaf4lto5X8L2tlq5kLT3trMVWYZYkGPGMksCWJJOPTgcz/AG+v/Puf++v/AK1ZRyqk1rVt8v8AgnPDJKDV5V7P/D/wTr6Kw2j1pLD7e3h3Uls9u77QbdxHj13bcYrM/t9f+fc/99f/AFqf9k0f+f3/AJL/AMEv+wsP/wBBH/kr/wAzr6K5D+31/wCfc/8AfX/1qP7fX/n3P/fX/wBaj+yaP/P7/wAl/wCCH9hYf/oI/wDJX/mdfRXIf2+v/Puf++v/AK1H9vr/AM+5/wC+v/rUf2TR/wCf3/kv/BD+wsP/ANBH/kr/AMzr6K5D+31/59z/AN9f/Wo/t9f+fc/99f8A1qP7Jo/8/v8AyX/gh/YWH/6CP/JX/mdfRXIf2+v/AD7n/vr/AOtR/b6/8+5/76/+tR/ZNH/n9/5L/wAEP7Cw/wD0Ef8Akr/zOvrJrG/t9f8An3P/AH1/9aj+31/59z/31/8AWr08uo0cHzfvL3t0tsexlNDD5fz/AL3m5rdGtr+vck8GaAvijxvpmjOSIrm4xKV67Blmx77Qa9l8ffF678C+I18L+FtOsIrLTERJBLGSCSobaoBGAARk9Sc1518GHWP4waIXIALTjn1MMgH61U+Lkbx/FXxArggmdW59Cikfoa5nucb3O8+Ltpp/izwBofxDsLZbe5nKw3Sr/EDkcnuVdSoPcH2FcH8ONU8H6Lqd1qHiuxnvWgjDWUCIHRnyc7gSAT0xnjr7V32p/uP2UdKSUbWkuflB75uJGH6c14ZSEfSHj/xI/i/9npNde2S2N1dDEKNkIqzsijPc4UZ96+b69zvv+TTtN/6+T/6VSV4ZQA6ON5pUiiRnkdgqqoyWJ6ACtfXvCeveGDb/ANtaXPZfaATEZAMNjryO4yOOvNZtndz6ffW97bPsnt5VljbGdrKcg/mK6nxv8Stc8ex2cWqpaRRWmSiW0ZUMxwCxyx54+lAHIxhWlQO21CQGbGcD1r3zxP8AFfSvAem6d4f+HaafcQJEHmuSpdcn6EZc4ySfYfTwGvQvhv8ACvUPG9wL26L2WhRH97dMMGTHVY89T6noPc8UAelf2svxb+DGualrumwQX+krLJBdRKQpZE3/AC5yRn7rDJHIPpjxr4e2+g3PjjTU8Szxw6UHZ5TKcIxCkqrHsCwGa7v4mfEfTI9FHgbwWiRaLAPLnuIuk2Dnap7jPJb+I+3J8doA9x8SftAalZeIJbTwxaad/Y1q3lQmSJj5qjjIwRtX0AHSn/Gmystb8CeHfHAsBY6lemOOePHLq8bMM+uNvB64b6VU8AfCy00vTx4x8fMlnpluBLFZzjBk9DIOuPROrd+ODyPxO+I9x491dBFG1vpFoSLWA9Tnq7f7R9Ow49SQDhK25PB/iGLw4viB9IuV0lsEXJX5cE4B9cZ74xWJXbz/ABV8R3HgNfCD/ZPsCxLD5wiPnGNSMLnOMcAdM470AcRX0L8Dr660z4WeKb6xt/tF3bzSSww7S3mOsKkLgcnJ4wK+eq7/AMBfFbUfAGjX9hZafbXJuZRKkkzNiNsYOQPvDAHcUAd/qfxi+Jek2Rur/wAFxWcGP9dPZzqqntkluPxrwWeZri4knfG+Ri7YGBknPSvd/h18ZNd8S+MbfQfEENndWOo7osJAF2HaTjHRlOMEH1/Py34kaDbeGfiFrGk2Y220MoeJc52K6K4X8N2PwoA9+i8ReIPDfwb8I3HhvRzqt5NDDE0IheTanlsS2EORyBz05rzjx58VPHF3oVzoOveG4NLivV2lpLaVGZQQfl3tjsOeap6b8dtf0bwlp+h6dYWUclnEIftUgZyyjphcgA49c13ngTxNN8Y/C+veHPE1tbyXEMavDcxx7cFshWx2ZSByOoOMdcgHzfRRRQAV6r8CfCtjr3iq71PU40ltNJhE2xxlTIxO0kdwArH6gV5VXuf7PX7zTPGkCcyyW8OxR1PEw/mRQA61/aI1Gbxgiz2Fovh+Sfy9m0+ckZON5bOCe5GMdveuV+N/hS18MeOvMsIlhs9RhFysSDCo+SHAHYZAP/Aq82jjeWVY0Us7EKoHUk17d+0o6nX9Cjz862sjEexYY/kaAPD6KKKACiiigAooooAKKKKACiiigC/pWq3Oh65a6pZsFuLSdZo89CQc4Psehr3bU9U+EnxJe21zXL+bS9TSNVuId5Qtj+EnaQw9CuDjHToPnx/vt9TTab3G9z1D4r/ELTPEVtp3hzw1EY9A03BRtpUSsF2rgHkKoJ68nJrC+HsPgSa7vR44urqCIIv2UxBypOTu3bATn7uO3WuMopCPpWTxV8G5fBUXhFtam/siJ96p5FzuzvL/AHtmerGvF/H8PguHVrceCbm5nszF++MwcAPn+HeA3TrXJUUAWdOFodTtRfs62RmT7QY/vCPI3Y98Zr0D4qW/w8gj0v8A4QiRWmIb7SInkZNvG0nf0br0/HtXm1FAGl4ebTl8SaW2sY/swXcRu8hiPK3DfwvPTPTmvpTWfiN8Ktb0H+xJvEEtvp20IYbS2uIQUHRPlj+77dK+WaKAPfl1T4LaN4a1y00i7Wa5vrR41+0Wk8jbtrbQrNH8vJHPHIHpXn3wi1XwvovjFtQ8VPGltDbM1s0kLyhZ9y4O1QeQu7BI4+uK4KigD6X8V+L/AIReNHg/trxFeSxwf6uGOK5RAf720R8n3NcT8RNX+Gsnw7tNF8ITRS3dtdrIrG1lWQqQ24mR0GeSOCew9K8eooAK9Surb4Yj4QRzQTH/AISrykyN8nmeduG4Ffu7MZ59Md68tooAK9f8Jy/CbXfCFlpfiKN9J1e3yJLtNwMxJ67wCCOnDDjtXkFFAH0Dpd38HvhxcnWdM1K51fU41YQLuMjKSMHBCqg4OMnnnivF9c8QSeIvFt1ruowhzc3AlkgViBsGMID14UAZrGooA93ks/gh4sSO8F9NoM2wCS3jJhwfcFWXPupp13438A/DnwxqGm+BJJb7Vb5Nj3TbiFOCAzMQAcZJAUYz1rwaigAr074YW3w2n0jVD41lC3ob9yJHkUCPb1TZ1bOeDntjvXmNFAEk/lC4kEBYw7jsL9dueM++K6/4ZeOG8B+K0v5Y3lsZ0MF1Gn3ihIIZfcEA/mO9cZRQB9DR/wDCk7PXP+Etj1RmlWT7QliA5QSZzkR7d2c84J2j6V5F8QvGUvjnxbPq7RmG3CiG2iY5KRLnGfckkn61y1FABRRRQAUUUUAFFFFABRRRQAUUUUAOf77fU02iim9xvcKKKKQgooooAKKKKACiiigAooooAKKKKACiiigAooooAKKKKACiiigAooooAKKKKACiiigAooooAKKKKACiiigAooooAKKKKAP/2Q==", "encoding": "base64", "path": [ "value" ] } ], "model_module": "@jupyter-widgets/controls", "model_module_version": "1.5.0", "model_name": "ImageModel", "state": { "layout": "IPY_MODEL_01627eb63e9f48bcaeb16d42ec509165" } }, "7153bb00bfae40128e51f2a74d9942d3": { "model_module": "@jupyter-widgets/controls", "model_module_version": "1.5.0", "model_name": "HTMLModel", "state": { "layout": "IPY_MODEL_426932e8f1684d55add824790758e912", "style": "IPY_MODEL_950cd68e8a634b6ebe100015434163f7", "value": "
\n \n \n \n \n \n \n Layer: output 'Dense'\nAct function: sigmoid\nAct output range: (0, 1)\nShape = (None, 2)outputLayer: hidden 'Dense'\nAct function: sigmoid\nAct output range: (0, 1)\nShape = (None, 4)hiddenLayer: input 'InputLayer'\nShape = [(None, 16)]inputActivations for SimpleNetwork
" } }, "950cd68e8a634b6ebe100015434163f7": { "model_module": "@jupyter-widgets/controls", "model_module_version": "1.5.0", "model_name": "DescriptionStyleModel", "state": { "description_width": "" } } }, "version_major": 2, "version_minor": 0 } } }, "nbformat": 4, "nbformat_minor": 4 }