{ "cells": [ { "cell_type": "markdown", "metadata": {}, "source": [ "## Genetic Algorithm \n", "\n", "In this notebook, we implement a new selection operator (tournament selection) and a new cost function (for the Goodwin oscillator). At the end, there is also code to plot the parameters as they change over the generations." ] }, { "cell_type": "code", "execution_count": 79, "metadata": {}, "outputs": [], "source": [ "import matplotlib.pyplot as plt\n", "import numpy as np\n", "import os.path\n", "import scipy.linalg\n", "import scipy.integrate\n", "import scipy.signal\n", "import matplotlib.cm" ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "### Support function for GA\n", "Copy-paste your copy of generateIndividual, generateChild, and GA below." ] }, { "cell_type": "code", "execution_count": 80, "metadata": {}, "outputs": [], "source": [ "def generateIndividual(cost_fcn, lb,ub):\n", " '''\n", " Generate a viable individual (i.e one whos cost function is finite)\n", " and return it and its cost.\n", " If 1000 tries fails to yield an individual, then the paramets and an infinite cost\n", " are returned.\n", " Each individual is generated by randomly chosing each entry params[i] \n", " so that lb[i] <= params[i] <= ub[i]\n", " Input:\n", " cost_fcn: handle to the cost function (which should take a candidate as input)\n", " lb: lower bounds for parameters (array of length NP)\n", " ub: upper bounds for parameters (array of length NP)\n", " Returns:\n", " (params, cost)\n", " where params is the same shape as the lower bounds\n", " and cost is the cost for that set of parameters\n", " '''\n", " params = np.zeros(lb.shape);\n", " cost = np.inf;\n", " for i in range( 1000 ):\n", " params = lb + np.multiply( np.random.random(lb.size), ub-lb);\n", " cost = cost_fcn(params);\n", " if np.isfinite(cost):\n", " return (params, cost);\n", " return (params, np.inf)\n", "\n", "def generateChild(cost_fcn, P, lb, ub, settings):\n", " \"\"\"\n", " Generate a viable child (i.e one whos cost function is finite)\n", " and return it and its cost function.\n", " If 1000 tries fails to yield a parent, then an infinite cost\n", " is returned.\n", " Input Parameters:\n", " cost_fcn: handle to the cost function (which should take a candidate as input)\n", " P: parents (lam x NP ndarray of parameter values)\n", " lb: lower bounds for parameters (array of length NP)\n", " ub: upper bounds for parameters (array of length NP)\n", " settings: a GASettings object\n", " Returns:\n", " (params, cost)\n", " where params is the same shape as the lower bounds\n", " and cost is the cost for that set of parameters\n", " \"\"\"\n", " params = np.zeros(lb.shape);\n", " cost = np.inf;\n", " for i in range(1000):\n", " # Choose two parenst from the breeding pool.\n", " P1idx = np.random.choice(P.shape[0]);\n", " P1 = P[P1idx,:];\n", " P2idx = np.random.choice(P.shape[0]);\n", " P2 = P[P2idx,:];\n", " for j in range(lb.size):\n", " # uniform cross-over\n", " if np.random.random() < 0.5:\n", " params[j] = P1[j];\n", " else:\n", " params[j] = P2[j];\n", " # Mutate optimization variables\n", " params = np.multiply( 1+np.random.randn(params.size)*settings.mutation, params);\n", " # Make sure the mutation hasn't moved us out of bounds\n", " params = np.maximum(params,lb);\n", " params = np.minimum(params,ub);\n", " cost = cost_fcn(params);\n", " if np.isfinite(cost):\n", " return ( params, cost );\n", " return (np.zeros((lb.size)), np.inf)\n", "\n", "def GA(cost_fcn, lb, ub, settings):\n", " \"\"\" Run the algorithm described in the notes to find a low-cost\n", " population of parameter sets.\n", " Input:\n", " cost_fcn: handle to cost function (should take a paramer set as input)\n", " lb: lower bounds for parameters (numpy array of length NP)\n", " ub: lower bounds for parameters (numpy array of length NP)\n", " settings: GASettings object\n", " Returns\n", " (params, cost, all_params, all_costs)\n", " where \n", " params is a 1xNP vector with the best parameter set found\n", " cost is a scalar with the cost of the best parameter set\n", " all_params is a list with a (settings.numChildren x NP) ndarray of parameters for each generation\n", " all_costs is a list with an NP-length array of costs for each generation\n", " \"\"\"\n", "\n", " mu = settings.numParents;\n", " lam = settings.numChildren;\n", " numGenerations = settings.numGenerations;\n", "\n", " # Create initial population\n", " G = np.zeros( (lam,lb.size) );\n", " Gcost = np.zeros( (lam,) );\n", " for i in range(lam):\n", " (G[i,:], Gcost[i]) = generateIndividual(cost_fcn, lb, ub);\n", " # sort them by cost\n", " idx = np.argsort(Gcost);\n", " Gcost = Gcost[idx]\n", " G = G[idx,:];\n", " all_params = [G]\n", " all_costs = [Gcost]\n", "\n", " for g in range(numGenerations):\n", " # Promote any elites\n", " elites = G[:settings.eliteCount,:]\n", " eliteCosts = Gcost[:settings.eliteCount]\n", " # select breeding pool\n", " (P, Pcost) = select_pool( G, Gcost, settings )\n", " # Generate the children for generation g\n", " G = np.zeros((lam, lb.size));\n", " Gcost = np.zeros((lam,));\n", " G[:settings.eliteCount,:] = elites\n", " Gcost[:settings.eliteCount] = eliteCosts\n", " for i in range(settings.eliteCount,lam):\n", " (G[i,:], Gcost[i]) = generateChild(cost_fcn, P, lb, ub, settings); \n", " # sort the children of this generation\n", " idx = np.argsort(Gcost);\n", " Gcost = Gcost[idx]\n", " G = G[idx,:];\n", " print('Best of generation ', g+1, ' has cost ', Gcost[0]);\n", " all_params.append(G)\n", " all_costs.append(Gcost)\n", "\n", " params = G[0,:];\n", " cost = Gcost[0];\n", " return (params, cost, all_params, all_costs)" ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "## Update code to include linear ranking selection\n", "\n", "Copy-paste your GASettings definition and add to it a field named linearRankingRatio. Give it a default value of 4." ] }, { "cell_type": "code", "execution_count": 103, "metadata": {}, "outputs": [], "source": [ "class GASettings:\n", " def __init__( self, numParents = 10, numChildren = 50 ):\n", " self.numParents = numParents;\n", " self.numChildren = numChildren;\n", " self.numGenerations = 5;\n", " self.selection = 'truncation'; \n", " self.mutation = 0.05; # std of normal distribution computed as mutation*value\n", " self.eliteCount = 1;\n", " self.tournamentSize = 2; \n", " self.linearRankingRatio = 4;" ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "Copy-paste your select_pool below. Add to it support for linear ranking selection (call it \"linearRanking\").\n", "\n", "In linear rankng selection, we compute a discrete probability distribution with the probability of each individual in the previous generation being chosen for the breeding pool. The probabilities are constructed so that the probability of choosing the lowest-cost individual is settings.linearRankingRatio times the probability of choosing the highest cost individual. For each individual we want to add to the pool we use this probability distribution by choosing a number from a uniform distribution between 0 and 1 and then determining which index in the cumulative distribution is the smallest such that the cumulative distribution is larger than the random number.\n", "\n", "The algorithm is this. If $n$ is the number of individuals in the previous generation and $r$ is the linearRankingRatio, then we construct the probability distribution according to\n", "\n", "