Versuchen Sie, das Problem des Handlungsreisenden mit einem genetischen Algorithmus (Python-Code) zu lösen.

Einführung

Ich habe versucht, viele Treffer zu erzielen, indem ich mit "Circular Salesman Problem Genetic Algorithm" gegoogelt habe.

Dieser Artikel beschreibt den Python-Code.

Spezifikation

Breed

Aufzählung für Crossover / Mutation

--ScrambleMutation ... Scramble --TranslocationMutation ... Translokation --SwapAdjacentPoints ... Tauschen Sie benachbarte Punkte aus --SwapMutation ... Swap --InversionMutation ... Inversion --CyclicCrossover ... Cycle Crossover (CX)

Artenklasse

Eine Klasse, die die Samen eines Verkäufers darstellt. Enthält die Koordinaten von $ N $ zweidimensionalen Punkten und misst die Länge und Anpassungsfähigkeit des Pfades. Wenn Sie den Punkttyp ändern oder ihn messen möchten, können Sie ihn aus dieser Klasse ableiten.

Attribute / Eigenschaften

--Punkte ... Punktgruppe. Wenn nicht angegeben, wird eine zufällige Punktgruppe verwendet

Methode

--Species (Punkte: np.ndarray = Keine, N: int = 10, Seed = Keine) ... Konstruktor --Punkte ... Punktgruppe. Wenn nicht angegeben, wird eine zufällige Punktgruppe verwendet

Verkäuferklasse

Stellt eine einzelne Route durch alle Punkte dar.

Attribute / Eigenschaften

--Spezies: Art ... Verkäufer Samen --path: numpy.ndarray ... path --picked_indeces: numpy.ndarray ... Route, dargestellt durch Ordnungscodierung --fitness: float ... Anpassungsfähigkeit --length: float ... Pfadlänge

Methode

Generationsklasse

Es ist eine Gruppe von Verkäufern für eine Generation.

Attribute / Eigenschaften

--Spezies: Art ... Verkäufer Samen --salesmans: Sequenz von Salesman ... Alle Individuen dieser Generation --leader: Salsesman ... das anpassungsfähigste Individuum dieser Generation --population: int ... die Anzahl der Generationen

Methode

Modellklasse

Suche nach geeigneten Personen durch Generationenwechsel.

Attribute / Eigenschaften

--Spezies: Art ... Verkäufer Samen

Methode

--Modell (Spezies: Spezies, max_population: int = Keine) ... Generiere die 0. Generation und erstelle ein Modell --Spezies ... Verkäufer Samen --max_population ... Die Anzahl der Personen in der Generation. Wenn Keine, $ N (\ log_2 N) ^ 2 \ le N ^ 2 $ --fit (nepochs: int = None, loggers = ()) ... Suche nach geeigneten Personen durch Generationswechsel --nepochs ... Anzahl der Generationswechsel. Wenn Keine, $ N (\ log N) ^ 2 \ le N ^ 2 $ --loggers ... Logger untergeordnete Klassenobjekte, die Informationen während des Generationswechsels speichern --_ evoluate (epoch) ... Generiere und wähle die nächste Generation von Nachkommen aus

Logger-Klasse

Zeichnet die laufenden Informationen von Model.fit () auf und stellt sie grafisch dar.

Attribute / Eigenschaften

--_ is_subplot: bool ... True: Verwenden Sie plot (), um 1 zu üben und Ihr eigenes Diagramm zu zeichnen. Zeichnen Sie ein Diagramm mit False -- nlogs: int ... Anzahl der aufzuzeichnenden Informationstypen

Methode

--log_begin (Modell: Modell) ... fit () Verarbeitung beim Start --Verarbeitung in log_end (Modell: Modell) ... fit () --log (Modell: Modell, Epoche: int) ... fit () Verarbeitung am Ende --plot (ax = Keine, Epochen = Keine) ... Zeichnen Sie ein Diagramm --ax ... Fläche für ein Diagramm, erhalten von matplotlib.pyplot.subplots () usw. --epochs ... Die zum Zeichnen von Graphen verwendete Generation. Wenn keine, (0. Generation, 1/4 Generation, 1/2 Generation, letzte Generation) --_ plot (ax = None, epochs = None) ... Eine Funktion, die tatsächlich ein Diagramm in einer untergeordneten Klasse zeichnet --plot_all (Logger, Epochen = Keine) ... Zeichnen Sie alle Informationen in das Diagramm --loggers ... Objekte, die an Model.fit () übergeben wurden, um Informationen zu sammeln --epochs ... Die zum Zeichnen von Graphen verwendete Generation. Wenn keine, (0. Generation, 1/4 Generation, 1/2 Generation, letzte Generation)

Die folgenden untergeordneten Klassen sind verfügbar.

--Logger_trace (level: int = 1) ... Zeigt den Fortschritt von Model.fit () an --level ... 0: Nichts anzeigen, 1: Ergebnis nur am Ende anzeigen, 2: Punkt während der Ausführung anzeigen, 3: Ergebnis alle 10 Generationen anzeigen --Logger_leaders () ... Zeichnet den obersten Pfad der Generation --Logger_fitness () ... Zeichnen Sie die Spitze und den Durchschnitt der Anpassungsfähigkeit der Generationen --Logger_population (show_breeded = False) ... Zeichnen Sie den Übergang der Anzahl der Personen in der Generation

Hilfsfunktion

--get_subplots (nax = 1) ... Gibt eine Liste mehrerer Diagrammbereiche zurück --nax ... Anzahl der Grafikbereiche

Implementierung

Dieser Code wurde in Python 3.7.5 und Google Colaboratory überprüft.

Aus Platzgründen wurden verschiedene Elemente weggelassen. --Verfahren Sie, um die Nutzungsrate für jede Crossover-Methode anzupassen (die Reste befinden sich in Model.breed_rates).

GaTsp.py


from datetime import datetime
import enum
import functools as ft
import itertools as it
import math
import random
import matplotlib.pyplot as plt
# from mpl_toolkits.mplot3d import Axes3D  # enable if use 3D plotting.
import numpy as np


@enum.unique
class Breed(enum.Enum):
    ScrambleMutation = enum.auto()
    TranslocationMutation = enum.auto()
    SwapAdjacentPoints = enum.auto()
    SwapMutation = enum.auto()
    InversionMutation = enum.auto()
    CyclicCrossover = enum.auto()
    PartialCrossover = enum.auto()
    OrderCrossover = enum.auto()
    # OrderBasedCrossover = enum.auto()
    PositionBasedCrossover = enum.auto()
    # PartiallyMappedCrossOver = enum.auto()
    # SubtourExchangeCrossOver = enum.auto()
    # EdgeRecombinationCrossOver = enum.auto()
    # EdgeAssemblyCrossOver = enum.auto()
    SinglePtCrossover = enum.auto()
    TwoPtsCrossover = enum.auto()
    MultiPtsCrossover = enum.auto()
    UniformCrossover = enum.auto()
    SubstitutionMutation = enum.auto()


## Coords of customers, all of which each salesman must travel.
## Generic property of the Salesmans
class Species:
    MIN_FITNESS = -100000
    fig_projection = None  # '3d' to plot a salesman's path on 3D

    def __init__(self, points:np.ndarray=None, N:int=10, seed=None, *args, **kwargs):
        if points is None:
            np.random.seed(seed)
            points = np.random.randn(N, 2)
            np.random.seed(None)
        self.points = points
        self.N = self.points.shape[0]
        self.indeces = np.arange(self.N, dtype=int)
        self._prepare(*args, **kwargs)

    def _prepare(self, *args, **kwargs):
        self._to_origin = np.sqrt(np.sum(self.points ** 2, axis=1))
        self._distance_map = np.zeros((self.N, self.N), dtype=np.float)
        for idx in range(self.N):
            xy_dist = np.sqrt(np.sum((self.points[idx]-self.points[:])**2, axis=1))
            self._distance_map[idx] = xy_dist

    def measure(self, path, *args, **kwargs):
        length = self._distance_map[path[:-1], path[1:]].sum()
        length += self._to_origin[path[0]]
        length += self._to_origin[path[-1]]
        return length

    def bonus(self, path, *args, **kwargs):
        bonus = 0.0
        return bonus

    def penalty(self, path, *args, **kwargs):
        penalty = 0.0
        return penalty

    def evaluate(self, path, *args, **kwargs):
        length = self.measure(path=path, *args, **kwargs)
        bonus = self.bonus(path=path, *args, **kwargs)
        penalty = self.penalty(path=path, *args, **kwargs)
        fitness = - length + bonus - penalty
        return fitness, length

    def plot_a_path(self, ax, path, plot_kwargs={}, *args, **kwargs):
        coords = self.points[path]
        ax.plot(coords[:,0], coords[:,1], **plot_kwargs)
        ax.plot((coords[-1,0], 0, coords[0,0]), (coords[-1,1], 0, coords[0,1]), **plot_kwargs)
        ax.scatter([coords[0,0]], [coords[0,1]])
        abs_max_val = np.max(np.abs(self.points)) * 1.1
        lim = (-abs_max_val, abs_max_val)
        ax.set_xlim(lim)
        ax.set_ylim(lim)


## Indivisual Salesman
class Salesman:
    _breeders = None  # initialized by Salesman.breed()

    def __init__(self, species:Species, path=None, picked_indeces=None, *args, **kwargs):
        self.species = species
        self._fitness = None
        self._length = None
        self.__indeces = None
        if path is not None:
            self.path = path if isinstance(path, np.ndarray) else np.array(path)
            self._picked_indeces = None
        elif picked_indeces is not None:  # encode picked_indeces to path.
            self._picked_indeces = picked_indeces if isinstance(picked_indeces, np.ndarray) else np.array(picked_indeces)
            indeces = np.arange(self.species.N, dtype=int)
            self.path = np.zeros(self.species.N, dtype=int)
            for idx, gene in enumerate(picked_indeces):
                self.path[idx] = indeces[gene]
                indeces[gene:-1] = indeces[gene+1:]
        else:  # generate new.
            self.path = np.random.permutation(self.species.N)
            self._picked_indeces = None
        self._hash = tuple(self.path).__hash__()

    def __hash__(self):
        return self._hash

    def __eq__(self, theother):
        return self.__hash__() == theother.__hash__()

    def __repr__(self):
        return f'- fitness: {self.fitness:.3f}\n- length: {self.length:.3f}\n- path: {self.path}'

    @property
    def picked_indeces(self):
        if self._picked_indeces is None:  # encode path to picked_indeces.
            bits = np.ones(self.species.N, dtype=np.int)
            self._picked_indeces = np.zeros(self.species.N, dtype=int)
            for idx, p in enumerate(self.path):
                self._picked_indeces[idx] = np.sum(bits[:p], initial=0)
                bits[p] = 0
        return self._picked_indeces

    @property
    def fitness(self):
        self._evaluate()
        return self._fitness

    @property
    def length(self):
        self._evaluate()
        return self._length

    def _evaluate(self):
        if self._fitness is None:
            self._fitness, self._length = self.species.evaluate(self.path)

    @property
    def _indeces(self):
        if self.__indeces is None:
            self.__indeces = np.zeros(self.species.N, dtype=int)
            self.__indeces[self.path] = self.species.indeces
        return self.__indeces

    def plot(self, ax, show_title:bool=True, plot_kwargs={}, *args, **kwargs):
        self.species.plot_a_path(ax=ax, path=self.path, plot_kwargs=plot_kwargs, *args, **kwargs)
        ax.legend()
        if show_title:
            title = plot_kwargs.get('label', '') + f': fit {self.fitness:.3f} / len {self.length:.3f}'
            ax.set_title(title)

    @staticmethod
    def sample_betters(salesmans, k:int, nelites:int=0, *args, **kwargs):
        salesmans = salesmans if isinstance(salesmans, tuple) or isinstance(salesmans, list) else tuple(salesmans)
        fits = np.array([s.fitness for s in salesmans])
        # select some part of k by roulette.
        fit_ave = fits.mean()
        fit_std = fits.std()
        weights = 1.0 / (1.0 + np.exp((fit_ave - fits) / fit_std))
        k1 = k if nelites == 0 else random.randrange(k - nelites)
        yield from random.choices(salesmans, weights=weights, k=k1)
        # select elites.
        if nelites > 0:
            fit_elite = np.sort(fits)[-nelites]
            yield from (s for s in salesmans if s.fitness >= fit_elite)
            # select lest of k by roulette.
            k2 = k - k1 - nelites
            yield from random.choices(salesmans, weights=weights, k=k2)

    @staticmethod
    def scramble_mutation(mother:'Salesman', father:'Salesman', *args, **kwargs):
        idx0, idx1 = sorted(random.sample(range(mother.species.N-1), k=2))  # at least 3 points may be shuffled.
        child1 = mother._scramble_mutation(idx0, idx1)
        child2 = father._scramble_mutation(idx0, idx1)
        return child1, child2

    def _scramble_mutation(self, idx0:int, idx1:int, *args, **kwargs):
        child_path = self.path.copy()
        np.random.shuffle(child_path[idx0:idx1+2])
        child = Salesman(species=self.species, path=child_path)
        return child

    @staticmethod
    def translocation_mutation(mother:'Salesman', father:'Salesman', *args, **kwargs):
        idx0, idx1, idx2 = sorted(random.sample(range(mother.species.N+1), k=3))
        child1 = mother._translocation_mutation(idx0, idx1, idx2)
        child2 = father._translocation_mutation(idx0, idx1, idx2)
        return child1, child2

    def _translocation_mutation(self, idx0:int, idx1:int, idx2:int, *args, **kwargs):
        child_path = np.concatenate((self.path[:idx0], self.path[idx1:idx2], self.path[idx0:idx1], self.path[idx2:]))
        child = Salesman(species=self.species, path=child_path)
        return child

    @staticmethod
    def swap_adjacent_points(mother:'Salesman', father:'Salesman', *args, **kwargs):
        indeces = np.array(random.sample(range((mother.species.N-1)//2), k=mother.species.N//10), dtype=int) * 2
        child1 = mother._swap_adjacent_points(indeces)
        child2 = father._swap_adjacent_points(indeces)
        return child1, child2

    def _swap_adjacent_points(self, indeces, *args, **kwargs):
        child_path = self.path.copy()
        child_path[indeces], child_path[indeces+1] = child_path[indeces+1], child_path[indeces]
        child = Salesman(species=self.species, path=child_path)
        return child

    @staticmethod
    def swap_mutation(mother:'Salesman', father:'Salesman', *args, **kwargs):
        idx0, idx1, idx2, idx3 = sorted(random.sample(range(mother.species.N), k=4))
        child1 = mother._swap_mutation(idx0, idx1, idx2, idx3)
        child2 = father._swap_mutation(idx0, idx1, idx2, idx3)
        return child1, child2

    def _swap_mutation(self, idx0:int, idx1:int, idx2:int, idx3:int, *args, **kwargs):
        child_path = np.concatenate((self.path[:idx0], self.path[idx2:idx3], self.path[idx1:idx2], self.path[idx0:idx1], self.path[idx3:]))
        child = Salesman(species=self.species, path=child_path)
        return child

    @staticmethod
    def inversion_mutation(mother:'Salesman', father:'Salesman', *args, **kwargs):
        idx0, idx1 = sorted(random.sample(range(mother.species.N-1), k=2))  # at least 3 points may be reversed.
        child1 = mother._inversion_mutation(idx0, idx1)
        child2 = father._inversion_mutation(idx0, idx1)
        return child1, child2

    def _inversion_mutation(self, idx0:int, idx1:int, *args, **kwargs):
        child_path = self.path.copy()
        child_path[idx0:idx1+2] = np.flip(self.path[idx0:idx1+2])
        child = Salesman(species=self.species, path=child_path)
        return child

    @staticmethod
    def cyclic_crossover(mother:'Salesman', father:'Salesman', *args, **kwargs):
        child1_path = mother.path.copy()
        child2_path = father.path.copy()
        idx = random.randrange(mother.species.N)
        val_1st = mother.path[idx]
        val_next = -1
        while val_next != val_1st:
            val_next = father.path[idx]
            child1_path[idx] = val_next
            child2_path[idx] = mother.path[idx]
            idx = mother._indeces[val_next]
        child1 = Salesman(species=mother.species, path=child1_path)
        child2 = Salesman(species=mother.species, path=child2_path)
        return child1, child2

    @staticmethod
    def partial_crossover(mother:'Salesman', father:'Salesman', *args, **kwargs):
        child1_path = mother.path.copy()
        child2_path = father.path.copy()
        child1_indeces = np.zeros(mother.species.N, dtype=int)
        child2_indeces = np.zeros(father.species.N, dtype=int)
        idx0, idx1 = sorted(random.sample(range(mother.species.N+1), k=2))  # at least 3 points may be reversed.
        for idx in range(idx0, idx1):
            child1_indeces[child1_path] = mother.species.indeces
            child2_indeces[child2_path] = father.species.indeces
            pt1 = child1_path[idx]
            pt2 = child2_path[idx]
            child1_path[idx] = pt2
            child2_path[idx] = pt1
            child1_path[child1_indeces[pt2]] = pt1
            child2_path[child2_indeces[pt1]] = pt2
        child1 = Salesman(species=mother.species, path=child1_path)
        child2 = Salesman(species=mother.species, path=child2_path)
        return child1, child2

    @staticmethod
    def order_crossover(mother:'Salesman', father:'Salesman', *args, **kwargs):
        idx = random.randrange(mother.species.N)
        child1 = mother._order_crossover(father, idx)
        child2 = father._order_crossover(mother, idx)
        return child1, child2

    def _order_crossover(self, other:'Salesman', idx, *args, **kwargs):
        head = self.path[:idx]
        tail = np.delete(other.path, other._indeces[head])
        child_path = np.concatenate((head, tail))
        child = Salesman(species=self.species, path=child_path)
        return child

    @staticmethod
    def position_based_crossover(mother:'Salesman', father:'Salesman', *args, **kwargs):
        flags = random.choices((0,1), k=mother.species.N)
        child1 = mother._position_based_crossover(father, flags)
        child2 = father._position_based_crossover(mother, flags)
        return child1, child2

    def _position_based_crossover(self, other:'Salesman', flags, *args, **kwargs):
        child_path = self.path.copy()
        swap_pts = self.path[flags == 1]
        child_path[flags == 0] = np.delete(other.path, other._indeces[swap_pts])
        child = Salesman(species=self.species, path=child_path)
        return child

    @staticmethod
    def single_pt_crossover(mother:'Salesman', father:'Salesman', *args, **kwargs):
        return Salesman._n_pts_crossover(mother, father, 1)

    @staticmethod
    def two_pts_crossover(mother:'Salesman', father:'Salesman', *args, **kwargs):
        return Salesman._n_pts_crossover(mother, father, 2)

    @staticmethod
    def multi_pts_crossover(mother:'Salesman', father:'Salesman', *args, **kwargs):
        return Salesman._n_pts_crossover(mother, father, mother.species.N // 4)

    @staticmethod
    def _n_pts_crossover(mother:'Salesman', father:'Salesman', k:int, *args, **kwargs):
        indeces = [None] + sorted(random.sample(range(mother.species.N), k=k)) + [None]
        child1_picked_indeces, child2_picked_indeces, *_ = ft.reduce(
                lambda gs, ii: (gs[0] + gs[2][ii[0]:ii[1]], gs[1] + gs[3][ii[0]:ii[1]], gs[3], gs[2]),
                zip(indeces[:-1], indeces[1:]),
                ([], [], list(mother.picked_indeces), list(father.picked_indeces)))
        child1 = Salesman(species=mother.species, picked_indeces=child1_picked_indeces)
        child2 = Salesman(species=mother.species, picked_indeces=child2_picked_indeces)
        return child1, child2

    @staticmethod
    def uniform_crossover(mother:'Salesman', father:'Salesman', *args, **kwargs):
        indeces = mother.species.indeces[random.choices((0,1), k=mother.species.N) == 1]
        child1 = mother._uniform_crossover(indeces)
        child2 = father._uniform_crossover(indeces)
        return child1, child2

    def _uniform_crossover(self, indeces, *args, **kwargs):
        child_picked_indeces = self.picked_indeces.copy()
        child_picked_indeces[indeces] = self.picked_indeces[indeces]
        child = Salesman(species=self.species, picked_indeces=child_picked_indeces)
        return child

    @staticmethod
    def substitution_mutation(mother:'Salesman', father:'Salesman', *args, **kwargs):
        child1 = mother._substitution_mutation()
        child2 = father._substitution_mutation()
        return child1, child2

    def _substitution_mutation(self, *args, **kwargs):
        N = self.species.N
        indeces = np.array([random.randint((idx-N)*N, N-idx-1) for idx in range(N)])
        child_picked_indeces = self.picked_indeces.copy()
        child_picked_indeces[indeces >= 0] = indeces[indeces >= 0]
        child = Salesman(species=self.species, picked_indeces=child_picked_indeces)
        return child

    @classmethod
    def breed(cls, breed:Breed, mother:'Salesman', father:'Salesman', *args, **kwargs):
        if cls._breeders is None:
            cls._breeders = {
                Breed.ScrambleMutation: Salesman.scramble_mutation,
                Breed.TranslocationMutation: Salesman.translocation_mutation,
                Breed.SwapAdjacentPoints: Salesman.swap_adjacent_points,
                Breed.SwapMutation: Salesman.swap_mutation,
                Breed.InversionMutation: Salesman.inversion_mutation,
                Breed.CyclicCrossover: Salesman.cyclic_crossover,
                Breed.PartialCrossover: Salesman.partial_crossover,
                Breed.OrderCrossover: Salesman.order_crossover,
                # Breed.OrderBasedCrossover: Salesman.order_based_crossover,
                Breed.PositionBasedCrossover: Salesman.position_based_crossover,
                Breed.SinglePtCrossover: Salesman.single_pt_crossover,
                Breed.TwoPtsCrossover: Salesman.two_pts_crossover,
                Breed.MultiPtsCrossover: Salesman.multi_pts_crossover,
                Breed.UniformCrossover: Salesman.uniform_crossover,
                Breed.SubstitutionMutation: Salesman.substitution_mutation,
            }
        breeder = cls._breeders.get(breed, lambda *_: None)
        return breeder(mother, father, *args, **kwargs)


## Group of Salesmans
class Generation:
    def __init__(self, species:Species, salesmans, *args, **kwargs):
        self.species = species
        self.salesmans = salesmans if isinstance(salesmans, tuple) else tuple(salesmans)
        self.leader = max(self.salesmans, key=lambda s: s.fitness)
        self.population = len(self.salesmans)

    @staticmethod
    def generate_0th(species:Species, population:int=1000, *args, **kwargs):
        children = set(Salesman(species=species) for _ in range(population - 1))
        children.add(Salesman(species=species, path=np.arange(species.N)))  # add the original path (0, 1, 2, ..., N-1)
        generation = Generation(species=species, salesmans=children)
        return generation


## Model of Genetic Algorithm for Traveling Salesman Problem.
class Model:
    def __init__(self, species:Species, max_population:int=None, *args, **kwargs):
        self.species = species
        self.max_population = int(species.N * math.log2(species.N)**2) if max_population is None else int(max_population)
        self.nelites = self.max_population // 10
        self.generation = Generation.generate_0th(species, self.max_population)
        self.breeded_population = 0
        self.leader_changed_epoch = 0
        self.stopped_epoch = 0
        self.breed_rates = {
            Breed.ScrambleMutation: 0.6,
            Breed.TranslocationMutation: 0.6,
            Breed.SwapAdjacentPoints: 0.6,
            Breed.SwapMutation: 0.6,
            Breed.InversionMutation: 0.6,
            Breed.CyclicCrossover: 0.6,
            Breed.PartialCrossover: 0.6,
            Breed.OrderCrossover: 0.6,
            # Breed.OrderBasedCrossover: 0.6,
            Breed.PositionBasedCrossover: 0.6,
            Breed.SinglePtCrossover: 0.6,
            Breed.TwoPtsCrossover: 0.6,
            Breed.MultiPtsCrossover: 0.6,
            Breed.UniformCrossover: 0.6,
            Breed.SubstitutionMutation: 0.6,
        }

    def fit(self, nepochs:int=None, loggers=(), *args, **kwargs):
        nepochs = int(self.species.N * math.log(self.species.N)**2) if nepochs is None else int(nepochs)
        epoch_to_stop = nepochs
        last_best_fitness = -1000000
        any(p.log_begin(self) for p in loggers)
        for epoch in range(nepochs):
            evoluated = self._evoluate(epoch)
            self.generation = Generation(species=self.species, salesmans=evoluated)
            if self.generation.leader.fitness > last_best_fitness:
                last_best_fitness = self.generation.leader.fitness
                self.leader_changed_epoch = epoch + 1
                epoch_to_stop = epoch + self.species.N
            any(p.log(self, epoch+1) for p in loggers)
            if epoch > epoch_to_stop:
                self.stopped_epoch = epoch + 1
                break
        any(p.log_end(self) for p in loggers)

    def _evoluate(self, epoch, *args, **kwargs):
        children = set(self.generation.salesmans)
        nparents = self.max_population - epoch * self.species.N // 10
        mothers = Salesman.sample_betters(self.generation.salesmans, nparents, self.nelites)
        fathers = Salesman.sample_betters(self.generation.salesmans, nparents, self.nelites)
        for _ in (children.update(Salesman.breed(breed, mother, father))
                for mother, father in zip(mothers, fathers) if mother != father
                for breed, rate in self.breed_rates.items() if random.random() < rate):
            pass
        self.breeded_population = len(children)
        betters = set(Salesman.sample_betters(children, self.max_population, self.nelites))
        return betters


## Loggers for Model.fit()
class Logger:
    COLORS=('red','green','blue','magenta','cyan')
    STYLES=('-','--','-.')
    _is_subplot = True
    _nlogs = 1

    def __init__(self, *args, **kwargs):
        pass

    def log_begin(self, model:Model, *args, **kwargs):
        self._logs = [[] for _ in range(self._nlogs)]
        self.log(model=model, epoch=0)

    def log_end(self, model:Model, *args, **kwargs):
        self._stopped_epoch = model.stopped_epoch

    def log(self, model:Model, epoch:int, *args, **kwargs):
        pass

    def plot(self, ax=None, epochs=None, *args, **kwargs):
        if epochs is None:
            nepochs = self._stopped_epoch
            epochs = (0, nepochs // 4, nepochs // 2, nepochs) if nepochs > 3 else (0, nepochs)
        self._plot(ax=ax, epochs=epochs, *args, **kwargs)

    def _plot(self, ax=None, epochs=None, *args, **kwargs):
        pass

    def _plot_epoch_lines(self, ax, epochs, y_lim, *args, **kwargs):
        ax.set_ylim(y_lim)
        for epoch in epochs:
            ax.plot((epoch, epoch), y_lim, color='gray', linestyle=':')

    def _offset_lim(self, y_min, y_max, *args, **kwargs):
        offset = (y_max - y_min) / 20
        return y_min - offset, y_max + offset

    @staticmethod
    def plot_all(loggers, epochs=None, *args, **kwargs):
        any(p.plot(ax=None, epochs=epochs, *args, **kwargs) for p in loggers if not p._is_subplot)
        subloggers = [p for p in loggers if p._is_subplot]
        if len(subloggers) > 0:
            axes = get_subplots(nax=len(subloggers))
            any(p.plot(ax=axes.pop(0), epochs=epochs, *args, **kwargs) for p in subloggers)
            plt.show()


class Logger_trace(Logger):
    _is_subplot = False

    def __init__(self, level:int=1, *args, **kwargs):
        self._level = level
        self._trace = self._l3 if level == 3 else self._l2 if level == 2 else (lambda *_: None)

    def log_begin(self, model:Model, *args, **kwargs):
        self._st = datetime.now()
        if self._level == 1:
            print('start at', self._st.strftime('%Y/%m/%d %H:%M:%S'))
        elif self._level == 2:
            print('start at', self._st.strftime('%Y/%m/%d %H:%M:%S'), end='')
        super().log_begin(model=model, *args, **kwargs)

    def log_end(self, model:Model, *args, **kwargs):
        et = datetime.now()
        super().log_end(model=model, *args, **kwargs)
        if self._level > 0:
            print(f'\n- time: {str(et - self._st)[:-3]}')
            print(f'- epoch: {model.stopped_epoch}({model.leader_changed_epoch})')
            print(f'{model.generation.leader}')

    def log(self, model:Model, epoch:int, *args, **kwargs):
        self._trace(model, epoch)

    def _l2(self, model:Model, epoch:int, *args, **kwargs):
        dot = '+' if epoch % 10 == 0 else '.'
        print(dot, end='')
        if epoch % 100 == 0:
            ct = datetime.now()
            print(f' fitness: {model.generation.leader.fitness:.3f}({model.leader_changed_epoch}) {str(ct - self._st)[:-3]}')

    def _l3(self, model:Model, epoch:int, *args, **kwargs):
        if epoch % 10 == 0:
            print(f'{epoch}: {model.generation.leader}')


class Logger_leaders(Logger):
    _is_subplot = False

    def log_begin(self, model:Model, *args, **kwargs):
        super().log_begin(model=model, *args, **kwargs)
        self._original = Salesman(model.species, path=model.species.indeces)
        self._projection = model.species.fig_projection

    def log(self, model:Model, epoch:int, *args, **kwargs):
        self._logs[0].append(model.generation.leader)

    def _plot(self, ax=None, epochs=None, *args, **kwargs):
        nplots = len(epochs)
        axes = get_subplots(nax=nplots+1, projection=self._projection)
        self._original.plot(ax=axes.pop(0), plot_kwargs=dict(label=f'original'))
        for epoch in epochs:
            self._logs[0][epoch].plot(ax=axes.pop(0), plot_kwargs=dict(label=f'{epoch}th'))
        plt.show()


class Logger_fitness(Logger):
    _nlogs = 2

    def log(self, model:Model, epoch:int, *args, **kwargs):
        self._logs[0].append(model.generation.leader.fitness)
        self._logs[1].append(sum(s.fitness for s in model.generation.salesmans) / model.generation.population)

    def _plot(self, ax, epochs=None, *args, **kwargs):
        xs = np.arange(len(self._logs[0]))
        ax.plot(xs, self._logs[0], label='best in the history', color=(0,0,1))
        ax.plot(xs, self._logs[1], label='average of each generation', color=(0,0,1), linestyle='--')
        y_lim = self._offset_lim(min(self._logs[0]), max(self._logs[0]))
        self._plot_epoch_lines(ax, epochs, y_lim)
        ax.set_xlabel('generation')
        ax.set_ylabel('fitness')
        ax.set_title('fitness')
        ax.legend()


class Logger_population(Logger):
    _nlogs = 2

    def __init__(self, show_breeded:bool=False, *arggs, **kwargs):
        self._show_breeded = show_breeded

    def log_begin(self, model:Model, *args, **kwargs):
        super().log_begin(model=model, *args, **kwargs)
        self._max_population = model.max_population

    def log(self, model:Model, epoch:int, *args, **kwargs):
        self._logs[0].append(model.generation.population)
        self._logs[1].append(model.breeded_population)

    def _plot(self, ax=None, epochs=None, *args, **kwargs):
        xs = np.arange(len(self._logs[0]))
        ax.plot(xs, [v for v in self._logs[0]], label='population')
        if self._show_breeded:
            ax.plot(xs, [v for v in self._logs[1]], label='breeded')
            y_max = max(self._logs[1])
        else:
            y_max = max(self._logs[0])
        ax.plot((epochs[0], epochs[-1]), (self._max_population, self._max_population), color='gray', linestyle=':')
        y_lim = self._offset_lim(min(self._logs[0]), y_max)
        self._plot_epoch_lines(ax, epochs, y_lim)
        ax.set_xlabel('epoch')
        ax.set_ylabel('population')
        ax.set_title('population')
        ax.legend()


class Logger_last_fitness_histgram(Logger):
    def log_end(self, model:Model, *args, **kwargs):
        super().log_end(model=model, *args, **kwargs)
        self._logs[0] = [s.fitness for s in model.generation.salesmans]

    def _plot(self, ax=None, epochs=None, *args, **kwargs):
        ax.hist(self._logs[0], 20)
        ax.set_xlabel('fitness')
        ax.set_ylabel('count of salesmans')
        ax.set_title('fitnesses at last')


class Logger_breed_legend(Logger):
    def _plot(self, ax=None, epochs=None, *args, **kwargs):
        for breed, color, style in zip(Breed, it.cycle(self.COLORS), it.cycle(self.STYLES)):
            ax.plot((0, 1), (0, 0), label=f'{breed.value} {breed}', color=color, linestyle=style)
        ax.legend()
        ax.set_xlim((0.0, 1.0))
        ax.set_xticks(())
        ax.set_ylim((0.0, 1.0))
        ax.set_yticks(())
        ax.set_title('legend')


## support functions.
def get_subplots(nax=1, *args, **kwargs):
    rows = 1 if nax == 5 else (nax + 3) // 4
    cols = 5 if nax == 5 else 4 if nax > 4 else nax
    fig = plt.figure()
    fig.set_figwidth(20.0)
    fig.set_figheight(rows * (20 // cols))
    axes = [fig.add_subplot(rows, cols, ridx * cols + cidx + 1, *args, **kwargs) for ridx in range(rows) for cidx in range(cols)]
    return axes

abschließend

Ich habe viele Frequenzweichen implementiert, wodurch der Code länger wird. Das Ausführungsergebnis wird in einem separaten Artikel veröffentlicht.

Recommended Posts

Versuchen Sie, das Problem des Handlungsreisenden mit einem genetischen Algorithmus (Python-Code) zu lösen.
Versuchen Sie, das Problem des Handlungsreisenden mit einem genetischen Algorithmus zu lösen (Theorie)
Versuchen Sie, das Problem des Handlungsreisenden mit einem genetischen Algorithmus zu lösen (Ausführungsergebnis)
Ich habe versucht, "einen genetischen Algorithmus (GA) in Python zu implementieren, um das Problem des Handlungsreisenden (TSP) zu lösen".
Versuchen Sie, das Problem der Zuweisung von Schulungsärzten mit Python zu lösen
Lösen Sie das asymmetrische Python-Problem für reisende Verkäufer mit der Branch-and-Bound-Methode
Suche nach einer Lösung für das N-Queen-Problem mit einem genetischen Algorithmus (2)
Suche nach einer Lösung für das N-Queen-Problem mit einem genetischen Algorithmus (1)
Lösen Sie das Problem des Handlungsreisenden mit OR-Tools
Ich wollte das ABC164 A ~ D-Problem mit Python lösen
Versuchen Sie, das Problem der Python-Klassenvererbung zu lösen
Versuchen Sie, das Mensch-Maschine-Diagramm mit Python zu lösen
Versuchen Sie, ein festgelegtes Problem der High-School-Mathematik mit Python zu lösen
Eine Geschichte, in der der Algorithmus zu einem lächerlichen Ergebnis kam, als er versuchte, das Problem der reisenden Verkäufer richtig zu lösen
Versuchen Sie, das Programmier-Herausforderungsbuch mit Python3 zu lösen
Ich habe versucht, das Problem mit Python Vol.1 zu lösen
Lösung des Problems des Handlungsreisenden mit dem genetischen Algorithmus (GA) und seiner Bibliothek (vcopt)
[Python] Versuchen Sie, die FX-Systolenparameter mit einem genetischen Algorithmus zu optimieren
Lösung des Planungsproblems der Krankenschwester (Schichtoptimierung) mit einem genetischen Algorithmus
Versuchen Sie, das N Queen-Problem mit SA von PyQUBO zu lösen
Versuchen Sie, eine Python-Umgebung mit Visual Studio Code & WSL zu erstellen
Durchsuche das Labyrinth mit dem Python A * -Algorithmus
[AtCoder] Löse ABC1 ~ 100 Ein Problem mit Python
Python: Ich habe das Problem des Handlungsreisenden ausprobiert
Lösen Sie das Python-Rucksackproblem mit der Branch-and-Bound-Methode
Versuchen Sie, den kürzesten Weg mit Python + NetworkX + Social Data zu lösen
Lösen Sie Teilsummenprobleme mit der vollständigen Suche in Python
Das 16. Offline-Echtzeit-Schreiben eines Referenzproblems zur Lösung mit Python
Wie man die anfängliche Population mit einem genetischen Algorithmus unter Verwendung von DEAP fixiert
Das 19. Offline-Echtzeit-Schreiben eines Referenzproblems zur Lösung mit Python
[AtCoder] Lösen Sie ein Problem von ABC101 ~ 169 mit Python
Berechnen wir das statistische Problem mit Python
Versuchen Sie, mit Python eine Lebenskurve zu zeichnen
So testen Sie den Friends-of-Friends-Algorithmus mit pyfof
Versuchen Sie, in Python einen "Entschlüsselungs" -Code zu erstellen
Versuchen Sie, mit Python eine Diedergruppe zu bilden
Ich habe versucht, das Problem des Handlungsreisenden umzusetzen
Lösen Sie das Python-Rucksackproblem mit dem Greedy-Algorithmus
[Python] Versuchen Sie, die coole Antwort auf das FizzBuzz-Problem zu lesen
Lassen Sie uns ein Befehls-Standby-Tool mit Python erstellen
Python Ver. Einführung in WebPay mit ein wenig Code
Ich habe versucht, Soma Cube mit Python zu lösen
Verwenden Sie tkinter, um den Ausgabecode in Python als "A und vorgeben, B zu sein" zu verschieben
Über das Problem der reisenden Verkäufer
Versuchen Sie, mit Python eine Wellenform (Audiospektrum) zu erstellen, die sich entsprechend dem Klang bewegt
Beim 15. Offline-Echtzeitversuch habe ich versucht, das Problem des Schreibens mit Python zu lösen
Eine Python-Probe zum Lernen von XOR mit einem genetischen Algorithmus in einem neuronalen Netz
Ich wollte den Panasonic Programming Contest 2020 mit Python lösen
Eine Sammlung wettbewerbsfähiger Pro-Techniken, die mit Python gelöst werden können
Wahrscheinlich der einfachste Weg, um mit Python 3 ein PDF zu erstellen
Ich habe versucht, das Problem der Kombinationsoptimierung mit Qiskit zu lösen
Löse das Spiralbuch (Algorithmus und Datenstruktur) mit Python!
Versuchen Sie, ein Unterfenster mit PyQt5 und Python zu öffnen
Versuchen Sie, den Betrieb von Netzwerkgeräten mit Python zu automatisieren
Eine Geschichte über den Umgang mit dem CORS-Problem
Versuchen Sie, eine multimodale Verteilung mithilfe des EM-Algorithmus zu modellieren
Versuchen Sie, die verstümmelten Zeichen im angehängten Dateinamen mit Python zu entschlüsseln