Skip to content

Instantly share code, notes, and snippets.

@vedraiyani
Created September 2, 2019 11:15
Show Gist options
  • Save vedraiyani/f99be52f7ec4009a3b992f980adb49f8 to your computer and use it in GitHub Desktop.
Save vedraiyani/f99be52f7ec4009a3b992f980adb49f8 to your computer and use it in GitHub Desktop.
simple genetic algorithm.ipynb
Display the source blob
Display the rendered blob
Raw
{
"nbformat": 4,
"nbformat_minor": 0,
"metadata": {
"colab": {
"name": "simple genetic algorithm.ipynb",
"version": "0.3.2",
"provenance": [],
"include_colab_link": true
},
"kernelspec": {
"name": "python3",
"display_name": "Python 3"
}
},
"cells": [
{
"cell_type": "markdown",
"metadata": {
"id": "view-in-github",
"colab_type": "text"
},
"source": [
"<a href=\"https://colab.research.google.com/gist/vedraiyani/f99be52f7ec4009a3b992f980adb49f8/simple-genetic-algorithm.ipynb\" target=\"_parent\"><img src=\"https://colab.research.google.com/assets/colab-badge.svg\" alt=\"Open In Colab\"/></a>"
]
},
{
"cell_type": "code",
"metadata": {
"id": "0DRjsgWLqYon",
"colab_type": "code",
"colab": {}
},
"source": [
"import random\n",
"import numpy as np\n",
"\n",
"def create_reference_solution(chromosome_length):\n",
"\n",
" number_of_ones = int(chromosome_length / 2)\n",
"\n",
" # Build an array with an equal mix of zero and ones\n",
" reference = np.zeros(chromosome_length)\n",
" reference[0: number_of_ones] = 1\n",
"\n",
" # Shuffle the array to mix the zeros and ones\n",
" np.random.shuffle(reference)\n",
" \n",
" return reference"
],
"execution_count": 0,
"outputs": []
},
{
"cell_type": "code",
"metadata": {
"id": "pWMktUBSqaEr",
"colab_type": "code",
"colab": {}
},
"source": [
"def create_starting_population(individuals, chromosome_length):\n",
" # Set up an initial array of all zeros\n",
" population = np.zeros((individuals, chromosome_length))\n",
" # Loop through each row (individual)\n",
" for i in range(individuals):\n",
" # Choose a random number of ones to create\n",
" ones = random.randint(0, chromosome_length)\n",
" # Change the required number of zeros to ones\n",
" population[i, 0:ones] = 1\n",
" # Sfuffle row\n",
" np.random.shuffle(population[i])\n",
" \n",
" return population"
],
"execution_count": 0,
"outputs": []
},
{
"cell_type": "code",
"metadata": {
"id": "jg2Z1HPKqlmy",
"colab_type": "code",
"colab": {}
},
"source": [
"def calculate_fitness(reference, population):\n",
" # Create an array of True/False compared to reference\n",
" identical_to_reference = population == reference\n",
" # Sum number of genes that are identical to the reference\n",
" fitness_scores = identical_to_reference.sum(axis=1)\n",
" \n",
" return fitness_scores"
],
"execution_count": 0,
"outputs": []
},
{
"cell_type": "code",
"metadata": {
"id": "UuslHVGQqpwt",
"colab_type": "code",
"colab": {}
},
"source": [
"def select_individual_by_tournament(population, scores):\n",
" # Get population size\n",
" population_size = len(scores)\n",
" \n",
" # Pick individuals for tournament\n",
" fighter_1 = random.randint(0, population_size-1)\n",
" fighter_2 = random.randint(0, population_size-1)\n",
" \n",
" # Get fitness score for each\n",
" fighter_1_fitness = scores[fighter_1]\n",
" fighter_2_fitness = scores[fighter_2]\n",
" \n",
" # Identify undividual with highest fitness\n",
" # Fighter 1 will win if score are equal\n",
" if fighter_1_fitness >= fighter_2_fitness:\n",
" winner = fighter_1\n",
" else:\n",
" winner = fighter_2\n",
" \n",
" # Return the chromsome of the winner\n",
" return population[winner, :]"
],
"execution_count": 0,
"outputs": []
},
{
"cell_type": "code",
"metadata": {
"id": "ibuoLGKEquKT",
"colab_type": "code",
"colab": {}
},
"source": [
"def breed_by_crossover(parent_1, parent_2):\n",
" # Get length of chromosome\n",
" chromosome_length = len(parent_1)\n",
" \n",
" # Pick crossover point, avoding ends of chromsome\n",
" crossover_point = random.randint(1,chromosome_length-1)\n",
" \n",
" # Create children. np.hstack joins two arrays\n",
" child_1 = np.hstack((parent_1[0:crossover_point],\n",
" parent_2[crossover_point:]))\n",
" \n",
" child_2 = np.hstack((parent_2[0:crossover_point],\n",
" parent_1[crossover_point:]))\n",
" \n",
" # Return children\n",
" return child_1, child_2"
],
"execution_count": 0,
"outputs": []
},
{
"cell_type": "code",
"metadata": {
"id": "NjxukWrtqywC",
"colab_type": "code",
"colab": {}
},
"source": [
"def randomly_mutate_population(population, mutation_probability):\n",
" \n",
" # Apply random mutation\n",
" random_mutation_array = np.random.random(\n",
" size=(population.shape))\n",
" \n",
" random_mutation_boolean = \\\n",
" random_mutation_array <= mutation_probability\n",
"\n",
" population[random_mutation_boolean] = \\\n",
" np.logical_not(population[random_mutation_boolean])\n",
" \n",
" # Return mutation population\n",
" return population"
],
"execution_count": 0,
"outputs": []
},
{
"cell_type": "code",
"metadata": {
"id": "0fyQSYQAqI3Z",
"colab_type": "code",
"colab": {
"base_uri": "https://localhost:8080/",
"height": 51
},
"outputId": "36aab8a3-97ff-4b4a-e0cf-a2fb2ebe63f6"
},
"source": [
"# Set general parameters\n",
"chromosome_length = 75\n",
"population_size = 500\n",
"maximum_generation = 200\n",
"best_score_progress = [] # Tracks progress\n",
"\n",
"# Create reference solution \n",
"# (this is used just to illustrate GAs)\n",
"reference = create_reference_solution(chromosome_length)\n",
"\n",
"# Create starting population\n",
"population = create_starting_population(population_size, chromosome_length)\n",
"\n",
"# Display best score in starting population\n",
"scores = calculate_fitness(reference, population)\n",
"best_score = np.max(scores)/chromosome_length * 100\n",
"print ('Starting best score, percent target: %.1f' %best_score)\n",
"\n",
"# Add starting best score to progress tracker\n",
"best_score_progress.append(best_score)\n",
"\n",
"# Now we'll go through the generations of genetic algorithm\n",
"for generation in range(maximum_generation):\n",
" # Create an empty list for new population\n",
" new_population = []\n",
" \n",
" # Create new popualtion generating two children at a time\n",
" for i in range(int(population_size/2)):\n",
" parent_1 = select_individual_by_tournament(population, scores)\n",
" parent_2 = select_individual_by_tournament(population, scores)\n",
" child_1, child_2 = breed_by_crossover(parent_1, parent_2)\n",
" new_population.append(child_1)\n",
" new_population.append(child_2)\n",
" \n",
" # Replace the old population with the new one\n",
" population = np.array(new_population)\n",
" \n",
" # Score best solution, and add to tracker\n",
" scores = calculate_fitness(reference, population)\n",
" best_score = np.max(scores)/chromosome_length * 100\n",
" best_score_progress.append(best_score)\n",
"\n",
"# GA has completed required generation\n",
"print ('End best score, percent target: %.1f' %best_score)"
],
"execution_count": 11,
"outputs": [
{
"output_type": "stream",
"text": [
"Starting best score, percent target: 65.3\n",
"End best score, percent target: 100.0\n"
],
"name": "stdout"
}
]
},
{
"cell_type": "code",
"metadata": {
"id": "F1pBD7M8qPVX",
"colab_type": "code",
"colab": {
"base_uri": "https://localhost:8080/",
"height": 283
},
"outputId": "66efb063-7a44-4465-baa3-8f1af54bfe2b"
},
"source": [
"# Plot progress\n",
"import matplotlib.pyplot as plt\n",
"%matplotlib inline\n",
"plt.plot(best_score_progress)\n",
"plt.xlabel('Generation')\n",
"plt.ylabel('Best score (% target)')\n",
"plt.show()"
],
"execution_count": 12,
"outputs": [
{
"output_type": "display_data",
"data": {
"image/png": "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\nZHgAAAAASUVORK5CYII=\n",
"text/plain": [
"<Figure size 432x288 with 1 Axes>"
]
},
"metadata": {
"tags": []
}
}
]
}
]
}
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment