{ "cells": [ { "attachments": {}, "cell_type": "markdown", "metadata": {}, "source": [ "# Ejemplos de algoritmos genéticos\n", "---\n", "\n", "Tutorial completo: http://deap.readthedocs.io/en/master/tutorials/basic/part1.html\n", "\n", "Referencia DEAP: http://deap.readthedocs.io/en/master/api/tools.html\n", "\n", "\n", "## Problema OneMax\n", "\n", "En este problema de optimización nos encontramos con un vector de 100 valores binarios, por lo que el número de posibles casos es de $2^{100}$. La tarea consiste en encontrar el vector con mayor número de $1$'s.\n", "\n", "Antes de meternos a resolver el problema mediante algoritmos genéticos vamos a intentar resolverlo solamente mediante azar. Vamos a crear un millón de vectores de cien elementos y veamos cuál es el mayor fitness que conseguimos." ] }, { "cell_type": "code", "execution_count": 2, "metadata": {}, "outputs": [ { "name": "stdout", "output_type": "stream", "text": [ "73\n" ] } ], "source": [ "import numpy as np\n", "\n", "max = 0\n", "for _ in range(1_000_000):\n", " v = np.random.randint(0,2,100)\n", " s = np.sum(v)\n", " if s > max:\n", " max = s\n", " \n", "print(max)" ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "En este caso el mayor valor ha sido 73. Recuerda que la probabilidad de que obtengamos de forma aleatoria un vector con cien unos es de:\n", "\n", "$$\\frac{1}{2^{100}}$$" ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "Las principales clases de DEAP y que vamos a utilizar son:\n", "\n", "- <code>base</code> acceso al *toolbox* y a las funciones de *fitness*.\n", "- <code>creator</code> permite crear los tipos (*types*).\n", "- <code>tools</code> acceso a los operadores.\n", "- <code>algorithms</code> prepara las iteraciones de los algoritmos genéticos." ] }, { "cell_type": "code", "execution_count": 1, "metadata": {}, "outputs": [], "source": [ "import random\n", "import numpy\n", "from deap import base, creator, tools, algorithms" ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "### Fitness\n", "\n", "La clase \"Fitness\" proporcionada es una **clase abstracta** que necesita un atributo de pesos para ser funcional. Un \"fitness\" a minimizar se construye utilizando pesos negativos, mientras que para maximizar debemos coloar pesos positivos. Es posible que la función de fitness incluya varias funciones internas donde unas deban maximizarse y otras minimizarse. Por esta razón el parámetro \"weights\" es una tupla.\n", "\n", "La función *create()* tiene al menos dos argumentos, un nombre para la clase recién creada y una clase base. Cualquier argumento subsiguiente se convierte en un **atributo de la clase**.\n", "\n", "### Individuos\n", "\n", "El primer individuo que crearemos será una simple lista que contiene flotantes. Para producir este tipo de individuo, necesitamos crear una clase *Individual*, usando el creador, que heredará del tipo de lista estándar y tendrá un atributo fitness." ] }, { "cell_type": "code", "execution_count": 2, "metadata": {}, "outputs": [], "source": [ "creator.create(\"FitnessMax\", base.Fitness, weights=(1.0,))\n", "creator.create(\"Individual\", list, fitness=creator.FitnessMax)" ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "Vemos que <code>Individual</code> es una clase que hereda de <code>list</code> y tiene un método llamado <code>fitness</code>. Podemos crear un individuo <code>ind</code> a partir de <code>creator.Individual</code>, como se muestra a continuación:" ] }, { "cell_type": "code", "execution_count": 3, "metadata": { "scrolled": true }, "outputs": [ { "name": "stdout", "output_type": "stream", "text": [ "[1, 0, 1, 1, 0]\n", "<class 'deap.creator.Individual'>\n", "<class 'deap.creator.FitnessMax'>\n" ] } ], "source": [ "ind = creator.Individual([1, 0, 1, 1, 0])\n", "\n", "print(ind)\n", "print(type(ind))\n", "print(type(ind.fitness))" ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "El valor del <code>fitness</code> de un individuo se calculará simplemente sumando todos sus elementos." ] }, { "cell_type": "code", "execution_count": 4, "metadata": {}, "outputs": [], "source": [ "def evalOneMax(individual):\n", " return sum(individual)," ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "Ahora registraremos varias funciones para crear los atributos de los individuos, los propios individuos y la población. También registraremos las funciones para evaluar los individuos, cruzarlos, mutarlos y seleccionarlos." ] }, { "cell_type": "code", "execution_count": 5, "metadata": {}, "outputs": [], "source": [ "toolbox = base.Toolbox()\n", "toolbox.register(\"attr_bool\", random.randint, 0, 1)\n", "toolbox.register(\"individual\", tools.initRepeat, creator.Individual, toolbox.attr_bool, n=100)\n", "toolbox.register(\"population\", tools.initRepeat, list, toolbox.individual)\n", "\n", "toolbox.register(\"evaluate\", evalOneMax)\n", "toolbox.register(\"mate\", tools.cxTwoPoint)\n", "toolbox.register(\"mutate\", tools.mutFlipBit, indpb=0.03)\n", "toolbox.register(\"select\", tools.selTournament, tournsize=3)\n", "\n", "bit = toolbox.attr_bool()\n", "pop = toolbox.population(n=50)\n", "\n", "# print(\"bit is of type %s and has value\\n%s\" % (type(bit), bit))\n", "# print(\"ind is of type %s and contains %d bits\\n%s\" % (type(ind), len(ind), ind))\n", "# print(\"pop is of type %s and contains %d individuals\\n%s\" % (type(pop), len(pop), pop))" ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "Por ejemplo, veamos cómo crearíamos un individuo y cómo lo mutaríamos. Observa la línea <code>temp = ind[:]</code>, con este \"truco\" logramos crear una nueva copia de la lista. Si hubiéramos hecho <code>temp = ind</code>, entonces <code>temp</code> e <code>ind</code> serían el mismo objeto, cosa que no queremos." ] }, { "cell_type": "code", "execution_count": 6, "metadata": {}, "outputs": [ { "name": "stdout", "output_type": "stream", "text": [ "[1, 1, 1, 0, 0, 0, 0, 0, 0, 1, 0, 0, 1, 0, 1, 1, 0, 0, 0, 0, 1, 0, 1, 1, 0, 1, 1, 1, 1, 0, 0, 0, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 0, 0, 0, 0, 1, 0, 1, 1, 0, 0, 0, 0, 0, 0, 0, 0, 1, 1, 1, 0, 1, 0, 1, 0, 0, 0, 0, 1, 1, 1, 0, 1, 0, 0, 0, 0, 1, 1, 0, 0, 1, 1, 0, 0, 1, 1, 0, 1, 1, 1, 0, 1, 1, 1, 1]\n", "[ 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0\n", " 0 0 0 0 0 0 0 0 1 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0\n", " 0 1 0 0 0 0 -1 0 0 0 0 0 0 1 0 0 0 0 0 0 0 0 0 0\n", " 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0\n", " 0 0 0 0]\n", "4532618728\n", "4531819272\n" ] } ], "source": [ "import numpy as np\n", "\n", "ind = toolbox.individual()\n", "temp = ind[:]\n", "print(ind)\n", "toolbox.mutate(temp)\n", "print(np.array(ind) - np.array(temp))\n", "\n", "print(id(ind))\n", "print(id(temp))" ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "Otra forma de hacer lo mismo pero con el método <code>clone</code> de <code>tolbox</code>." ] }, { "cell_type": "code", "execution_count": 7, "metadata": {}, "outputs": [ { "name": "stdout", "output_type": "stream", "text": [ "False\n", "True\n" ] } ], "source": [ "mutant = toolbox.clone(ind)\n", "print(mutant is ind)\n", "print(mutant == ind)" ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "### Hall of fame\n", "\n", "Si queremos mantener durante toda la evolución del algoritmo los mejores individuos obtenidos hasta el momento, debemos crear un objeto \"hall of fame\". En este caso, vamos a mantener cuatro. También podemos ir mostrando estadístias a medida que el algoritmo avanza." ] }, { "cell_type": "code", "execution_count": 40, "metadata": {}, "outputs": [ { "name": "stdout", "output_type": "stream", "text": [ "gen\tnevals\tavg \tmin\tmax\n", "0 \t0 \t98.08\t93 \t99 \n", "1 \t32 \t98.08\t92 \t100\n", "2 \t23 \t98.66\t95 \t100\n", "3 \t25 \t98.32\t93 \t100\n", "4 \t27 \t98.48\t93 \t100\n", "5 \t29 \t98.76\t93 \t100\n", "6 \t21 \t99.26\t95 \t100\n", "7 \t34 \t99.14\t95 \t100\n", "8 \t39 \t99.16\t92 \t100\n", "9 \t23 \t99.36\t93 \t100\n", "10 \t26 \t99.38\t93 \t100\n", "11 \t36 \t99.3 \t95 \t100\n", "12 \t35 \t99.4 \t94 \t100\n", "13 \t36 \t99.46\t94 \t100\n", "14 \t37 \t99.42\t94 \t100\n", "15 \t32 \t99.4 \t94 \t100\n", "16 \t20 \t99.16\t90 \t100\n", "17 \t36 \t99.26\t94 \t100\n", "18 \t31 \t99.4 \t95 \t100\n", "19 \t27 \t99.72\t96 \t100\n", "20 \t28 \t99.28\t94 \t100\n", "21 \t34 \t99.38\t93 \t100\n", "22 \t30 \t99.46\t95 \t100\n", "23 \t24 \t99.66\t96 \t100\n", "24 \t25 \t99.86\t97 \t100\n", "25 \t33 \t99.2 \t95 \t100\n", "26 \t35 \t99.64\t95 \t100\n", "27 \t25 \t99.28\t95 \t100\n", "28 \t33 \t99.42\t95 \t100\n", "29 \t28 \t99.5 \t94 \t100\n", "30 \t31 \t99.22\t95 \t100\n", "31 \t24 \t99.74\t95 \t100\n", "32 \t30 \t99.42\t95 \t100\n", "33 \t32 \t99.66\t96 \t100\n", "34 \t25 \t99.6 \t92 \t100\n", "35 \t33 \t99.5 \t95 \t100\n", "36 \t27 \t99.42\t94 \t100\n", "37 \t30 \t99.64\t96 \t100\n", "38 \t26 \t99.48\t94 \t100\n", "39 \t32 \t98.88\t94 \t100\n", "40 \t27 \t99.3 \t94 \t100\n", "41 \t29 \t99.6 \t96 \t100\n", "42 \t32 \t99 \t92 \t100\n", "43 \t27 \t99.3 \t95 \t100\n", "44 \t24 \t99.66\t95 \t100\n", "45 \t29 \t99.68\t95 \t100\n", "46 \t26 \t99.42\t94 \t100\n", "47 \t29 \t99.18\t94 \t100\n", "48 \t25 \t99.14\t94 \t100\n", "49 \t29 \t99.5 \t95 \t100\n", "50 \t29 \t99.52\t96 \t100\n", "51 \t34 \t99.16\t93 \t100\n", "52 \t34 \t99.32\t96 \t100\n", "53 \t34 \t99.54\t95 \t100\n", "54 \t28 \t99.6 \t93 \t100\n", "55 \t29 \t99.28\t95 \t100\n", "56 \t27 \t99.46\t92 \t100\n", "57 \t29 \t99.62\t94 \t100\n", "58 \t31 \t99.1 \t93 \t100\n", "59 \t25 \t99.68\t93 \t100\n", "60 \t31 \t99.32\t94 \t100\n", "61 \t37 \t99.38\t95 \t100\n", "62 \t28 \t99.66\t94 \t100\n", "63 \t30 \t99.28\t96 \t100\n", "64 \t30 \t99.48\t96 \t100\n", "65 \t31 \t99.32\t94 \t100\n", "66 \t25 \t99.58\t95 \t100\n", "67 \t25 \t99.54\t93 \t100\n", "68 \t31 \t99.48\t96 \t100\n", "69 \t29 \t99.34\t93 \t100\n", "70 \t28 \t99.32\t92 \t100\n", "71 \t27 \t99.56\t96 \t100\n", "72 \t35 \t99.18\t95 \t100\n", "73 \t20 \t99.5 \t95 \t100\n", "74 \t34 \t99.24\t93 \t100\n", "75 \t29 \t99.42\t94 \t100\n", "76 \t24 \t99.54\t97 \t100\n", "77 \t27 \t99.5 \t95 \t100\n", "78 \t23 \t99.2 \t93 \t100\n", "79 \t26 \t99.38\t93 \t100\n", "80 \t35 \t99.44\t95 \t100\n", "81 \t29 \t99.56\t95 \t100\n", "82 \t20 \t99.66\t95 \t100\n", "83 \t27 \t99.52\t94 \t100\n", "84 \t29 \t99.4 \t95 \t100\n", "85 \t23 \t99.64\t95 \t100\n", "86 \t30 \t99.34\t93 \t100\n", "87 \t34 \t99.36\t94 \t100\n", "88 \t20 \t99.5 \t94 \t100\n", "89 \t32 \t99.12\t94 \t100\n", "90 \t37 \t99.14\t92 \t100\n", "91 \t21 \t99.5 \t96 \t100\n", "92 \t28 \t99.34\t95 \t100\n", "93 \t25 \t99.7 \t95 \t100\n", "94 \t25 \t99.18\t93 \t100\n", "95 \t26 \t99.42\t93 \t100\n", "96 \t29 \t99.48\t95 \t100\n", "97 \t21 \t99.72\t94 \t100\n", "98 \t31 \t99.24\t92 \t100\n", "99 \t35 \t99.52\t95 \t100\n", "100\t30 \t99.48\t93 \t100\n" ] } ], "source": [ "hof = tools.HallOfFame(4)\n", "\n", "stats = tools.Statistics(lambda indiv: indiv.fitness.values)\n", "stats.register(\"avg\", numpy.mean)\n", "stats.register(\"min\", numpy.min)\n", "stats.register(\"max\", numpy.max)\n", "\n", "pop, logbook = algorithms.eaSimple(pop, toolbox, cxpb=0.5, mutpb=0.2, ngen=100, stats=stats, halloffame=hof, verbose=True)" ] }, { "cell_type": "code", "execution_count": 41, "metadata": {}, "outputs": [ { "name": "stdout", "output_type": "stream", "text": [ "Best individual is: [1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1]\n", " with fitness: (100.0,)\n" ] }, { "data": { "image/png": "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0BExHc2NekazppPy4e1wIMyTMvuOhzMDj/Dz8/M544wzmDFjBkuWLGHq1KlceeWV3H333RQWFvLyyy+zefNmT/rsK664gu7du7Nq1SoOHDjAAw88wEUXXUR+fj5z585l48aNzJs3j3fffZfKykpyc3O57bbbqKur48UXXyQxMZH//e9/ZGRkMHv2bP72t78xZcoUioqKmDJlCvn5+a63DxfafNQRMR3NTdqnoNGEm7y8PG699Va2bt3K1q1beeWVV1i0aBF/+9vf+Otf/9ps/f3797No0SLef/99br/9dsd9bty4kbfffpuVK1fyu9/9jpSUFNasWcPMmTN54YUXgraptduHgtYUOiJW7UBrCprOSJARfSQZMmQI48ePB2Ds2LGcfPLJCCEYP348+fn5zdY///zziYmJYcyYMRw8eNBxnyeddBJpaWmkpaWRnp7OOeecA8D48eNZv3590Da1dvtQ0JpCR8SqHWihoNGElcTERM/nmJgYz/eYmBgaGhoCru8vl5ybfcbFxdHU1ATQLL12qG1qDVoodESsEUfafKTRdApycnJYvXo1QFSd1FoodESsgiBOh6RqNJ2B2267jccff5yjjz6aoqKiqLVDp87uiGx5D+b/SH2+dD4cdUZ026PRhIGuljq7rQg1dbbWFDoi2tGs0WgihBYKHREfR7P2KWg0mvChhUJHRGsKGo0mQmih0BHRIakajSZCaKHQEbFGHGmhoNFowogWCh0RrSloNJoIoYVCR8THp6AdzRpNe8ZNqu32hM591BExhYKIgdiE6LZFo9EExE2q7faEFgodkZhYiE2E2HgQItqt0WjCzv0r7mdrydaw7nNUxih+O+23Addxkzob4KabbqKmpobk5GSee+45jjrqKB5++GE2bNjAs88+y4YNG7j00ktZsWIFr7/+uk+q7eTkZNasWUNhYSHPPvssL7zwAkuXLmX69OnMmzcPgNTUVCoqKgCV8uL9999n3rx5rrdvDdp81FGJT9b+BI0mAgRLnT1q1Ci++eYb1qxZw5/+9CfuvPNOQAmKvLw83nnnHa688kqefPJJUlKam3dLS0tZunQpDz/8MOeeey633HILmzZtYsOGDaxduzZo+1q7fTC0ptBRiU9WmoJG0wkJNqKPJMFSZ5eVlXH55ZeTm5uLEIL6+npAZSydN28eEyZM4Nprr+XYY4913P85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", "text/plain": [ "<Figure size 432x288 with 1 Axes>" ] }, "metadata": { "needs_background": "light" }, "output_type": "display_data" } ], "source": [ "print(\"Best individual is: %s\\n with fitness: %s\" % (hof[0], hof[0].fitness))\n", "\n", "import matplotlib.pyplot as plt\n", "\n", "gen, avg, min_, max_ = logbook.select(\"gen\", \"avg\", \"min\", \"max\")\n", "plt.plot(gen, avg, label=\"average\")\n", "plt.plot(gen, min_, label=\"minimum\")\n", "plt.plot(gen, max_, label=\"maximum\")\n", "plt.xlabel(\"Generation\")\n", "plt.ylabel(\"Fitness\")\n", "plt.legend(loc=\"lower right\")\n", "plt.show()" ] }, { "cell_type": "code", "execution_count": 10, "metadata": {}, "outputs": [ { "data": { "text/plain": [ "[deap.creator.FitnessMax((98.0,)),\n", " deap.creator.FitnessMax((99.0,)),\n", " deap.creator.FitnessMax((99.0,)),\n", " deap.creator.FitnessMax((99.0,))]" ] }, "execution_count": 10, "metadata": {}, "output_type": "execute_result" } ], "source": [ "hof.keys" ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "Comparemos, por tanto, este resultado con el intento que hicimos al principio de resolverlo por simple azar. Solo por azar computamos un millón de soluciones y obtuvimos un *fitness* de 73, mediante algoritmos genéticos sólo computamos 5000 soluciones (100 individuos x 50 generaciones) y obtuvimos un *fitness* de 99." ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "---\n", "**Resetear kernel**\n", "\n", "---" ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "## Problema de la mochila\n", "\n", "El **problema de la mochila** es un problema clásico dentro de la IA. Consiste en lo siguiente: existe un número determinado de objetos que tienen un valor y un peso propios. En nuestra mochila solo podemos llevar hasta un peso máximo, por lo tanto, el problema consiste en escoger los objetos que minimicen el peso y maximicen el valor. Es un problema NP-completo." ] }, { "cell_type": "code", "execution_count": 1, "metadata": {}, "outputs": [], "source": [ "import random\n", "import numpy\n", "from deap import base, creator, tools, algorithms\n", "\n", "creator.create(\"Fitness\", base.Fitness, weights=(-1.0, 1.0)) # minimizamos el peso y maximizamos el valor\n", "creator.create(\"Individual\", set, fitness=creator.Fitness)" ] }, { "cell_type": "code", "execution_count": 2, "metadata": {}, "outputs": [], "source": [ "IND_INIT_SIZE = 5\n", "MAX_ITEM = 50 # Número máximo de objetos en la mochila\n", "MAX_WEIGHT = 50 # Peso máximo en la mochila\n", "NBR_ITEMS = 20 # Número total de objetos" ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "Crearemos el conjunto de objetos del que podremos escoger cuáles meter en la mochila." ] }, { "cell_type": "code", "execution_count": 3, "metadata": {}, "outputs": [], "source": [ "# Create the item dictionary: item name is an integer, and value is \n", "# a (weight, value) 2-tuple.\n", "\n", "items = {}\n", "# Create random items and store them in the items' dictionary.\n", "for i in range(NBR_ITEMS):\n", " items[i] = (random.randint(1, 10), random.uniform(0, 100)) # (peso, valor)" ] }, { "cell_type": "code", "execution_count": 4, "metadata": {}, "outputs": [ { "data": { "text/plain": [ "{0: (3, 40.21973789030577),\n", " 1: (5, 14.82673705642511),\n", " 2: (4, 17.84809746774294),\n", " 3: (2, 77.10800522420402),\n", " 4: (9, 68.28988567929494),\n", " 5: (1, 68.86370295584337),\n", " 6: (7, 13.961059341269245),\n", " 7: (8, 31.01136831834118),\n", " 8: (2, 80.16372253099942),\n", " 9: (8, 34.13362867121229),\n", " 10: (6, 96.59866206156678),\n", " 11: (2, 22.525889266595158),\n", " 12: (3, 58.49950981707295),\n", " 13: (10, 32.64928028147466),\n", " 14: (5, 58.69033290714722),\n", " 15: (10, 77.2728251346083),\n", " 16: (4, 89.62514697548662),\n", " 17: (6, 14.444314021405402),\n", " 18: (2, 7.477817620888938),\n", " 19: (9, 76.88910569305926)}" ] }, "execution_count": 4, "metadata": {}, "output_type": "execute_result" } ], "source": [ "items" ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "Las siguientes líneas crean la población inicial del individuos. Con <code>toolbox.attr_item</code> escogemos un objeto, entre 0 y NBR_ITEMS. La función <code>toolbox.individual</code> itera IND_INIT_SIZE veces para ir añadiendo objetos al individuo. Observa que los individuos pueden tener IND_INIT_SIZE objetos o menos. Eso se debe a que si aleatoriamente se vuelve a escoger el mismo objeto, éste no se repite dentro del conjunto (set). Por último, <code>toolbox.population</code> crea una función para generar una lista de individuos." ] }, { "cell_type": "code", "execution_count": 5, "metadata": {}, "outputs": [], "source": [ "toolbox = base.Toolbox()\n", "toolbox.register(\"attr_item\", random.randrange, NBR_ITEMS) # Objeto a escoger, entre 0 y NBR_ITEMS-1\n", "toolbox.register(\"individual\", tools.initRepeat, creator.Individual, \n", " toolbox.attr_item, IND_INIT_SIZE)\n", "toolbox.register(\"population\", tools.initRepeat, list, toolbox.individual)" ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "Evaluamos un individuo como la suma total del peso y valor de sus objetos." ] }, { "cell_type": "code", "execution_count": 6, "metadata": {}, "outputs": [], "source": [ "def evalKnapsack(individual):\n", " weight = 0.0\n", " value = 0.0\n", " for item in individual:\n", " weight += items[item][0]\n", " value += items[item][1]\n", " if len(individual) > MAX_ITEM or weight > MAX_WEIGHT:\n", " return 10000, 0 # Ensure overweighted bags are dominated\n", " return weight, value" ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "Para cruzar dos individuos y generar otros dos nuevos podemos usar las funciones de **intersección** y **diferencia simétrica** propias de los conjuntos. Por ejemplo, si tuviéramos los individuos {16, 9, 18, 3} y {3, 4, 13, 17, 18} los individuos resultantes serían {3, 18} (intersección) y {4, 9, 13, 16, 17} (diferencia)." ] }, { "cell_type": "code", "execution_count": 7, "metadata": {}, "outputs": [], "source": [ "def cxSet(ind1, ind2):\n", " \"\"\"Apply a crossover operation on input sets. The first child is the\n", " intersection of the two sets, the second child is the difference of the\n", " two sets.\n", " \"\"\"\n", " temp = set(ind1) # Used in order to keep type\n", "\n", " ind1.intersection_update(ind2)\n", " ind2.symmetric_difference_update(temp)\n", " \n", " return ind1, ind2" ] }, { "cell_type": "code", "execution_count": 8, "metadata": {}, "outputs": [], "source": [ "def mutSet(individual):\n", " \"\"\"Mutation that pops or add an element.\"\"\"\n", " if random.random() < 0.5:\n", " if len(individual) > 0: # We cannot pop from an empty set\n", " individual.remove(random.choice(sorted(tuple(individual))))\n", " else:\n", " individual.add(random.randrange(NBR_ITEMS))\n", " return individual," ] }, { "cell_type": "code", "execution_count": 9, "metadata": {}, "outputs": [], "source": [ "toolbox.register(\"evaluate\", evalKnapsack)\n", "toolbox.register(\"mate\", cxSet)\n", "toolbox.register(\"mutate\", mutSet)\n", "toolbox.register(\"select\", tools.selNSGA2)" ] }, { "cell_type": "code", "execution_count": 10, "metadata": {}, "outputs": [], "source": [ "def main():\n", " NGEN = 50\n", " MU = 50\n", " LAMBDA = 100\n", " CXPB = 0.7\n", " MUTPB = 0.2\n", "\n", " pop = toolbox.population(n=MU)\n", " hof = tools.ParetoFront()\n", " stats = tools.Statistics(lambda ind: ind.fitness.values)\n", " stats.register(\"avg\", numpy.mean, axis=0)\n", " stats.register(\"std\", numpy.std, axis=0)\n", " stats.register(\"min\", numpy.min, axis=0)\n", " stats.register(\"max\", numpy.max, axis=0)\n", "\n", " algorithms.eaMuPlusLambda(pop, toolbox, MU, LAMBDA, CXPB, MUTPB, NGEN, stats, halloffame=hof)\n", " #algorithms.eaSimple(pop, toolbox, cxpb=0.5, mutpb=0.2, ngen=100, stats=stats, halloffame=hof, verbose=True)\n", "\n", " return pop, stats, hof" ] }, { "cell_type": "code", "execution_count": 11, "metadata": {}, "outputs": [ { "name": "stdout", "output_type": "stream", "text": [ "gen\tnevals\tavg \tstd \tmin \tmax \n", "0 \t50 \t[ 24.56 223.65118954]\t[ 6.52735781 49.03010369]\t[ 11. 147.27473174]\t[ 37. 387.91418152]\n", "1 \t86 \t[ 10.9 139.66364096]\t[ 11.58835623 128.17872618]\t[0. 0.] \t[ 35. 387.91418152]\n", "2 \t91 \t[ 5.28 75.83985356] \t[ 10.69960747 133.39604745]\t[0. 0.] \t[ 39. 477.5393285] \n", "3 \t92 \t[ 5.32 77.44312801] \t[ 10.68352002 132.95591463]\t[0. 0.] \t[ 39. 477.5393285] \n", "4 \t89 \t[ 6.9 99.77271384] \t[ 11.854535 145.74231859]\t[0. 0.] \t[ 39. 477.5393285] \n", "5 \t95 \t[ 5.36 79.73462148] \t[ 11.44160828 140.8811111 ]\t[0. 0.] \t[ 39. 477.5393285] \n", "6 \t92 \t[ 5.54 86.43899891] \t[ 11.41264211 142.29245413]\t[0. 0.] \t[ 39. 477.5393285] \n", "7 \t89 \t[ 7.44 113.60865127]\t[ 12.83457829 157.31261047]\t[0. 0.] \t[ 39. 477.5393285] \n", "8 \t89 \t[ 9.18 129.18604808]\t[ 14.26981429 169.41050186]\t[0. 0.] \t[ 45. 491.98364252]\n", "9 \t92 \t[ 6.02 99.1637026] \t[ 11.6952811 152.2512445] \t[0. 0.] \t[ 45. 491.98364252]\n", "10 \t93 \t[ 4.84 76.90655874] \t[ 11.48801114 150.02707836]\t[0. 0.] \t[ 44. 536.22966141]\n", "11 \t90 \t[ 6.18 102.07026571]\t[ 12.27630237 162.20345695]\t[0. 0.] \t[ 44. 536.22966141]\n", "12 \t92 \t[ 5.42 92.77662911] \t[ 11.61566184 162.1608668 ]\t[0. 0.] \t[ 44. 536.22966141]\n", "13 \t85 \t[ 4.7 94.87495077] \t[ 10.0920761 156.41724008]\t[0. 0.] \t[ 44. 536.22966141]\n", "14 \t93 \t[ 6.94 125.20402933]\t[ 12.38775202 183.91922394]\t[0. 0.] \t[ 44. 536.22966141]\n", "15 \t88 \t[ 6.36 124.93476854]\t[ 11.96120395 180.45129622]\t[0. 0.] \t[ 44. 536.22966141]\n", "16 \t87 \t[ 8.06 183.89719955]\t[ 11.56444551 179.06407376]\t[0. 0.] \t[ 44. 536.22966141]\n", "17 \t90 \t[ 6.54 136.22701035]\t[ 11.72213291 179.34566261]\t[0. 0.] \t[ 44. 536.22966141]\n", "18 \t88 \t[ 8.1 171.29099529]\t[ 12.33896268 188.54264534]\t[0. 0.] \t[ 44. 536.22966141]\n", "19 \t89 \t[ 7.48 167.46250211]\t[ 11.56760995 194.09751438]\t[0. 0.] \t[ 44. 536.22966141]\n", "20 \t84 \t[ 13.92 264.21761589]\t[ 15.41147624 208.15224618]\t[0. 0.] \t[ 44. 536.22966141]\n", "21 \t84 \t[ 21.9 376.8868762] \t[ 16.4599514 171.72283324]\t[0. 0.] \t[ 44. 536.22966141]\n", "22 \t96 \t[ 5.96 178.2515248] \t[ 7.1943311 177.23647004]\t[0. 0.] \t[ 26. 569.76882036]\n", "23 \t90 \t[ 6.32 210.48369428]\t[ 6.40137485 146.83191866]\t[0. 0.] \t[ 26. 569.76882036]\n", "24 \t87 \t[ 6.24 177.80980678]\t[ 7.65391403 189.15897124]\t[0. 0.] \t[ 26. 569.76882036]\n", "25 \t93 \t[ 7.4 206.01786214]\t[ 8.49941174 201.11492962]\t[0. 0.] \t[ 28. 592.29470963]\n", "26 \t86 \t[ 8.6 224.47703565]\t[ 9.66022774 218.71371224]\t[0. 0.] \t[ 32. 610.1428071] \n", "27 \t92 \t[ 9.54 258.17026222]\t[ 8.93579319 202.58868579]\t[0. 0.] \t[ 32. 610.1428071] \n", "28 \t89 \t[ 8.14 225.07140385]\t[ 8.82725325 201.89910246]\t[0. 0.] \t[ 32. 610.1428071] \n", "29 \t90 \t[ 9.26 239.87095585]\t[ 10.0812896 218.50317737]\t[0. 0.] \t[ 32. 610.1428071] \n", "30 \t89 \t[ 11.36 294.93386976]\t[ 9.90103025 204.10409778]\t[0. 0.] \t[ 32. 610.1428071] \n", "31 \t87 \t[ 9.44 233.61914512]\t[ 11.34929073 225.76957289]\t[0. 0.] \t[ 36. 647.0416455] \n", "32 \t80 \t[ 14.52 341.43375232]\t[ 12.05693162 222.28521807]\t[0. 0.] \t[ 36. 647.0416455] \n", "33 \t91 \t[ 17.38 407.01600216]\t[ 11.15148421 186.38791397]\t[0. 0.] \t[ 36. 647.0416455] \n", "34 \t92 \t[ 18.68 418.79451607]\t[ 11.99239759 196.60109366]\t[0. 0.] \t[ 43. 661.00270484]\n", "35 \t87 \t[ 20.9 455.25788722]\t[ 11.74946807 184.91144398]\t[0. 0.] \t[ 41. 687.03191279]\n", "36 \t90 \t[ 14.16 333.10039997]\t[ 12.11669922 225.7506243 ]\t[0. 0.] \t[ 41. 687.03191279]\n", "37 \t93 \t[ 16.1 369.08743845]\t[ 12.5383412 222.25728877]\t[0. 0.] \t[ 41. 687.03191279]\n", "38 \t88 \t[ 14.04 326.0001353] \t[ 12.5665588 232.99459444]\t[0. 0.] \t[ 41. 687.03191279]\n", "39 \t91 \t[ 16.52 378.33886043]\t[ 12.69683425 213.73672059]\t[0. 0.] \t[ 41. 687.03191279]\n", "40 \t88 \t[ 17.54 392.29688329]\t[ 13.11519729 216.50106133]\t[0. 0.] \t[ 41. 687.03191279]\n", "41 \t81 \t[ 19.42 431.50835654]\t[ 12.43557799 193.26899269]\t[0. 0.] \t[ 41. 687.03191279]\n", "42 \t90 \t[ 17.38 392.29931296]\t[ 12.94741673 210.32014831]\t[0. 0.] \t[ 46. 701.85864985]\n", "43 \t84 \t[ 19.36 434.21898908]\t[ 11.80298267 184.5049682 ]\t[0. 0.] \t[ 46. 701.85864985]\n", "44 \t90 \t[ 19.5 434.93754763]\t[ 12.16757987 183.54850873]\t[0. 0.] \t[ 46. 701.85864985]\n", "45 \t92 \t[ 18.52 404.25009175]\t[ 14.09431091 217.9875543 ]\t[0. 0.] \t[ 46. 701.85864985]\n", "46 \t92 \t[ 20.64 437.2873766] \t[ 14.09930495 211.94680132]\t[0. 0.] \t[ 46. 701.85864985]\n", "47 \t95 \t[ 19. 420.27333911]\t[ 13.24688643 203.83243261]\t[0. 0.] \t[ 46. 701.85864985]\n", "48 \t91 \t[ 21.32 452.89023699]\t[ 13.88011527 203.1108143 ]\t[0. 0.] \t[ 46. 701.85864985]\n", "49 \t93 \t[ 18.9 418.19321294]\t[ 13.66784548 205.39357836]\t[0. 0.] \t[ 49. 721.16554146]\n", "50 \t89 \t[ 19.74 436.96100085]\t[ 13.1465737 192.40235649]\t[0. 0.] \t[ 49. 721.16554146]\n" ] } ], "source": [ "pop, stats, hof = main()" ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "# Hall of fame\n" ] }, { "cell_type": "code", "execution_count": 12, "metadata": {}, "outputs": [ { "data": { "text/plain": [ "[Individual(),\n", " {5},\n", " {8},\n", " {5, 8},\n", " {3, 8},\n", " {3, 5, 8},\n", " {3, 5, 8, 11},\n", " {3, 5, 8, 12},\n", " {3, 5, 8, 16},\n", " {0, 3, 5, 8, 12},\n", " {3, 5, 8, 12, 16},\n", " {3, 5, 8, 11, 12, 16},\n", " {0, 3, 5, 8, 12, 16},\n", " {0, 3, 5, 8, 11, 12, 16},\n", " {3, 5, 8, 10, 12, 16},\n", " {3, 5, 8, 10, 11, 12, 16},\n", " {0, 3, 5, 8, 10, 12, 16},\n", " {0, 3, 5, 8, 10, 11, 12, 16},\n", " {3, 5, 8, 10, 11, 12, 14, 16},\n", " {0, 3, 5, 8, 10, 12, 14, 16},\n", " {0, 3, 5, 8, 10, 11, 12, 14, 16},\n", " {0, 2, 3, 5, 8, 10, 11, 12, 14, 16},\n", " {3, 5, 8, 10, 11, 12, 14, 16, 19},\n", " {0, 3, 5, 8, 10, 12, 14, 16, 19},\n", " {0, 3, 5, 8, 10, 12, 14, 15, 16},\n", " {0, 3, 5, 8, 10, 11, 12, 14, 16, 19},\n", " {0, 3, 5, 8, 10, 11, 12, 14, 15, 16},\n", " {0, 2, 3, 5, 8, 10, 11, 12, 14, 16, 19},\n", " {0, 1, 2, 3, 5, 8, 10, 11, 12, 14, 16, 19},\n", " {0, 2, 3, 5, 8, 9, 10, 11, 12, 14, 16, 19}]" ] }, "execution_count": 12, "metadata": {}, "output_type": "execute_result" } ], "source": [ "hof.items" ] }, { "cell_type": "code", "execution_count": 13, "metadata": {}, "outputs": [ { "data": { "text/plain": [ "<Figure size 640x480 with 1 Axes>" ] }, "metadata": {}, "output_type": "display_data" } ], "source": [ "import matplotlib.pyplot as plt\n", "\n", "x = []\n", "y = []\n", "\n", "for v in items.values():\n", " x.append(v[0])\n", " y.append(v[1])\n", "\n", "\n", "plt.scatter(x, y)\n", "plt.show()" ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "# Frente de Pareto\n", "\n", "Se denomina **óptimo de Pareto** a aquel punto de equilibrio en el que ninguno de los agentes afectados podrá mejorar su situación sin reducir el bienestar de cualquiera de los otros agentes ([Wikipedia](https://es.wikipedia.org/wiki/Eficiencia_de_Pareto)). " ] }, { "cell_type": "code", "execution_count": 14, "metadata": {}, "outputs": [ { "data": { "image/png": "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", "text/plain": [ "<Figure size 432x288 with 1 Axes>" ] }, "metadata": { "needs_background": "light" }, "output_type": "display_data" } ], "source": [ "x = []\n", "y = []\n", "\n", "for hof_item in hof.items:\n", " if len(hof_item) > 0:\n", " weight = 0\n", " value = 0\n", " for i in hof_item:\n", " w, v = items[i]\n", " weight += w\n", " value += v\n", " x.append(weight)\n", " y.append(value)\n", " \n", "plt.xlabel(\"Weight\")\n", "plt.ylabel(\"Value\")\n", "plt.scatter(x, y)\n", "plt.show()" ] }, { "cell_type": "code", "execution_count": null, "metadata": {}, "outputs": [], "source": [] } ], "metadata": { "kernelspec": { "display_name": "Python 3", "language": "python", "name": "python3" }, "language_info": { "codemirror_mode": { "name": "ipython", "version": 3 }, "file_extension": ".py", "mimetype": "text/x-python", "name": "python", "nbconvert_exporter": "python", "pygments_lexer": "ipython3", "version": "3.10.7" } }, "nbformat": 4, "nbformat_minor": 4 }