{
  "cells": [
    {
      "cell_type": "code",
      "execution_count": null,
      "metadata": {
        "collapsed": false
      },
      "outputs": [],
      "source": [
        "%matplotlib inline"
      ]
    },
    {
      "cell_type": "markdown",
      "metadata": {},
      "source": [
        "\n# Minimal example for evolutionary regression using hurdles\n\nExample demonstrating the use of Cartesian genetic programming for a\nsimple regression task where we use hurdles to implement early\nstopping for low-performing invididuals.\n\nHurdles are implemented by introducing multiple objectives, here two,\nwhich are sequentially evaluated. Only those individuals with fitness\nin the upper 50th percentile on the first objective are evaluated on\nthe second objective.\n\nReferences:\n\n- Real, E., Liang, C., So, D., & Le, Q. (2020, November). AutoML-zero:\n  evolving machine learning algorithms from scratch. In International\n  Conference on Machine Learning (pp. 8007-8019). PMLR.\n"
      ]
    },
    {
      "cell_type": "code",
      "execution_count": null,
      "metadata": {
        "collapsed": false
      },
      "outputs": [],
      "source": [
        "# The docopt str is added explicitly to ensure compatibility with\n# sphinx-gallery.\ndocopt_str = \"\"\"\n   Usage:\n     example_minimal.py [--max-generations=<N>]\n\n   Options:\n     -h --help\n     --max-generations=<N>  Maximum number of generations [default: 300]\n\"\"\"\n\nimport matplotlib.pyplot as plt\nimport numpy as np\nimport scipy.constants\nfrom docopt import docopt\n\nimport cgp\n\nargs = docopt(docopt_str)"
      ]
    },
    {
      "cell_type": "markdown",
      "metadata": {},
      "source": [
        "We first define a target function.\n\n"
      ]
    },
    {
      "cell_type": "code",
      "execution_count": null,
      "metadata": {
        "collapsed": false
      },
      "outputs": [],
      "source": [
        "def f_target(x):\n    return x ** 2 + 1.0"
      ]
    },
    {
      "cell_type": "markdown",
      "metadata": {},
      "source": [
        "Then we define two objective functions for the evolution. They use\nthe mean-squared error between the output of the expression\nrepresented by a given individual and the target function evaluated\non a set of random points. The first objective uses only few samples\n(100) to get a fast estimate how well an individual performs. The\nsecond objective uses may samples (99900) to determine the fitness\nprecisely.\n\n"
      ]
    },
    {
      "cell_type": "code",
      "execution_count": null,
      "metadata": {
        "collapsed": false
      },
      "outputs": [],
      "source": [
        "def objective_one(individual):\n\n    if not individual.fitness_is_None():\n        return individual\n\n    n_function_evaluations = 100000\n\n    np.random.seed(1234)\n    values = np.random.uniform(-4, 4, n_function_evaluations)\n\n    f = individual.to_func()\n    loss = 0\n    for x in values[:100]:\n        # the callable returned from `to_func` accepts and returns\n        # lists; accordingly we need to pack the argument and unpack\n        # the return value\n        y = f(x)\n        loss += (f_target(x) - y) ** 2\n\n    individual.fitness = -loss / n_function_evaluations\n\n    return individual\n\n\ndef objective_two(individual):\n\n    if not individual.fitness_is_None():\n        return individual\n\n    n_function_evaluations = 100000\n\n    np.random.seed(1234)\n    values = np.random.uniform(-4, 4, n_function_evaluations)\n\n    f = individual.to_func()\n    loss = 0\n    for x in values[100:]:\n        # the callable returned from `to_func` accepts and returns\n        # lists; accordingly we need to pack the argument and unpack\n        # the return value\n        y = f(x)\n        loss += (f_target(x) - y) ** 2\n\n    individual.fitness = -loss / n_function_evaluations\n\n    return individual"
      ]
    },
    {
      "cell_type": "markdown",
      "metadata": {},
      "source": [
        "Next, we set up the evolutionary search. We first define the\nparameters for the population, the genome of individuals, and the\nevolutionary algorithm.\n\n"
      ]
    },
    {
      "cell_type": "code",
      "execution_count": null,
      "metadata": {
        "collapsed": false
      },
      "outputs": [],
      "source": [
        "population_params = {\"n_parents\": 1, \"seed\": 8188211}\n\ngenome_params = {\n    \"n_inputs\": 1,\n    \"n_outputs\": 1,\n    \"n_columns\": 12,\n    \"n_rows\": 1,\n    \"levels_back\": 5,\n    \"primitives\": (cgp.Add, cgp.Sub, cgp.Mul, cgp.ConstantFloat),\n}"
      ]
    },
    {
      "cell_type": "markdown",
      "metadata": {},
      "source": [
        "We define the upper percentile of individuals which are evaluated on\nthe (n+1)th objective by a list of numbers between 0 and 1.\n\n"
      ]
    },
    {
      "cell_type": "code",
      "execution_count": null,
      "metadata": {
        "collapsed": false
      },
      "outputs": [],
      "source": [
        "ea_params = {\n    \"n_offsprings\": 4,\n    \"mutation_rate\": 0.03,\n    \"n_processes\": 1,\n    \"hurdle_percentile\": [0.5, 0.0],\n}\n\nevolve_params = {\"max_generations\": int(args[\"--max-generations\"]), \"termination_fitness\": 0.0}"
      ]
    },
    {
      "cell_type": "markdown",
      "metadata": {},
      "source": [
        "We create a population that will be evolved\n\n"
      ]
    },
    {
      "cell_type": "code",
      "execution_count": null,
      "metadata": {
        "collapsed": false
      },
      "outputs": [],
      "source": [
        "pop = cgp.Population(**population_params, genome_params=genome_params)"
      ]
    },
    {
      "cell_type": "markdown",
      "metadata": {},
      "source": [
        "and an instance of the (mu + lambda) evolutionary algorithm\n\n"
      ]
    },
    {
      "cell_type": "code",
      "execution_count": null,
      "metadata": {
        "collapsed": false
      },
      "outputs": [],
      "source": [
        "ea = cgp.ea.MuPlusLambda(**ea_params)"
      ]
    },
    {
      "cell_type": "markdown",
      "metadata": {},
      "source": [
        "We define a callback for recording of fitness over generations\n\n"
      ]
    },
    {
      "cell_type": "code",
      "execution_count": null,
      "metadata": {
        "collapsed": false
      },
      "outputs": [],
      "source": [
        "history = {}\nhistory[\"fitness_champion\"] = []\n\n\ndef recording_callback(pop):\n    history[\"fitness_champion\"].append(pop.champion.fitness)"
      ]
    },
    {
      "cell_type": "markdown",
      "metadata": {},
      "source": [
        "and finally perform the evolution\n\n"
      ]
    },
    {
      "cell_type": "code",
      "execution_count": null,
      "metadata": {
        "collapsed": false
      },
      "outputs": [],
      "source": [
        "cgp.evolve(\n    pop,\n    [objective_one, objective_two],\n    ea,\n    **evolve_params,\n    print_progress=True,\n    callback=recording_callback\n)"
      ]
    },
    {
      "cell_type": "markdown",
      "metadata": {},
      "source": [
        "After finishing the evolution, we plot the result and log the final\nevolved expression.\n\n"
      ]
    },
    {
      "cell_type": "code",
      "execution_count": null,
      "metadata": {
        "collapsed": false
      },
      "outputs": [],
      "source": [
        "width = 9.0\nfig, axes = plt.subplots(1, 2, figsize=(width, width / scipy.constants.golden))\n\nax_fitness, ax_function = axes[0], axes[1]\nax_fitness.set_xlabel(\"Generation\")\nax_fitness.set_ylabel(\"Fitness\")\n\nax_fitness.plot(history[\"fitness_champion\"], label=\"Champion\")\n\nax_fitness.set_yscale(\"symlog\")\nax_fitness.set_ylim(-1.0e2, 0.1)\nax_fitness.axhline(0.0, color=\"0.7\")\n\nf = pop.champion.to_func()\nx = np.linspace(-5.0, 5.0, 20)\ny = [f(x_i) for x_i in x]\ny_target = [f_target(x_i) for x_i in x]\n\nax_function.plot(x, y_target, lw=2, alpha=0.5, label=\"Target\")\nax_function.plot(x, y, \"x\", label=\"Champion\")\nax_function.legend()\nax_function.set_ylabel(r\"$f(x)$\")\nax_function.set_xlabel(r\"$x$\")\n\nfig.savefig(\"example_minimal.pdf\", dpi=300)"
      ]
    }
  ],
  "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.8.6"
    }
  },
  "nbformat": 4,
  "nbformat_minor": 0
}