5.6 KiB
5.6 KiB
Wyznaczanie trasy algorytmem genetycznym
Autor: Mateusz Tylka
Cel algorytmu
Celem tego algorytmu jest wyznaczenie optymalnej trasy w zbieraniu ziół o konkretnych pozycjach, które są generowane losowo. Algorytm decyduje po które zioło udać się najpierw, starając się, aby końcowa suma odległości pomiędzy odwiedzonymi pozycjami była jak najmniejsza.
Osobnik Traveling
Osobnik jest to jednostka polegająca ewolucji za pomocą operacji genetycznych. W mojej implementacji osobnika reprezentuje obiekt Traveling.py. Ten obiekt przechowuje następujące metody:
class Traveling:
def __init__(self, coords):
self.coords = coords
self.fitness = self.evaluate()
- W konstruktorze przyjmowany jako parametr jest zestaw koordynatów, który zostaje zapisany jako atrybut, następnie tworzymy atrybut reprezentujący sprawność danego osobnika, który jest wynikiem metody określającej poprawność danej trasy.
def evaluate(self):
sum = 0
for i, c in enumerate(self.coords):
if i + 1 > len(self.coords) - 1:
break
nextCoord = self.coords[i + 1]
# calculate distance
sum += sqrt((nextCoord[0] - c[0]) ** 2 + (nextCoord[1] - c[1]) ** 2)
return sum
- Metoda evaluate odpowiedzialna jest za ocenę osobnika. Liczymy w niej odległość od punktu startu do pierwszego punktu, następnie odległość między drugim a trzecim miejscem i tak dalej..., aż do końca listy pozycji ziół z rozważanej trasy. Uzyskane wyniki sumujemy, czyli uzyskujemy długość konretnej drogi.
def crossover(self, parentCoords):
childCoords = self.coords[1:int(len(self.coords) / 2)]
for coord in parentCoords.coords:
if coord not in childCoords and coord not in END_COORD + START_COORD:
childCoords.append(coord)
if len(childCoords) == len(parentCoords.coords):
break
return Traveling(START_COORD + childCoords + END_COORD)
- Metoda crossover reprezentuję operację genetyczną krzyżowania osobników. Bierzemy w niej z pierwszego osobnika część punktów jego trasy (w naszym przypadku połowę) i dobieramy w pętli kolejne koordynaty z drugiego osobnika tak, aby się one nie powtarzały. Gdy już osiągniemy odpowiednią długość nowego osobnika kończymy pętlę i zwracamy go.
def mutation(self):
first = randint(1, len(self.coords) - 2)
second = randint(1, len(self.coords) - 2)
self.coords[first], self.coords[second] = self.coords[second], self.coords[first]
self.fitness = self.evaluate()
- Ta metoda przedstawia proces mutacji. Polega on po prostu na zamianę miejscami dwóch losowych koordynatów na trasie.
def __repr__(self):
return str(self.coords)
- Obiekt ten zwracany jest w formie tekstowej listy koordynatów.
Obiekt GeneticAlgorithm
W pliku GeneticAlgorithm.py znajduje się model selekcji osobników, warunek stopu, oraz główna pętla algorytmu.
class GeneticAlgorithm:
def __init__(self, firstPopulation, mutationProbability):
self.firstPopulation = firstPopulation
self.mutationProbability = mutationProbability
- Obiekt ten przyjmuje pierwszą populację oraz prawdopodobieństwo mutacji jako parametry i zapisuje je w odpowiednich atrybutach.
def selectionModel(self, generation):
max_selected = int(len(generation) / 10)
sorted_by_assess = sorted(generation, key=lambda x: x.fitness)
return sorted_by_assess[:max_selected]
- Model w mojej implementacji opiera się na elitaryzmie - czyli wybraniu pewnej ilości najlepszych chromosomów, które z pewnością przetrwają do następnej generacji. Definiujemy w niej 10% spośród przyjętej generacji jako parametr. Sortujemy naszą generację według odległości (metody evaluate) czyli wartości atrybutu fitness.
def stopCondition(self, i):
return i == 64
- Warunkiem końca algorytmu jest osiągnięcie 64 generacji.
def run(self):
population = self.firstPopulation
population.sort(key=lambda x: x.fitness)
populationLen = len(population)
i = 0
while True:
selected = self.selectionModel(population)
newPopulation = selected.copy()
while len(newPopulation) != populationLen:
child = choice(population).crossover(choice(population))
if random() <= self.mutationProbability:
child.mutation()
newPopulation.append(child)
population = newPopulation
theBestMatch = min(population, key=lambda x: x.fitness)
print("Generation: {} S: {} fitness: {}".format(i+1, theBestMatch, theBestMatch.fitness))
i += 1
if self.stopCondition(i):
return str(theBestMatch)
- W metodzie run zaimplementowana jest główna pętla algorytmu
genetycznego. Na początku wskazujemy pierwszą populację i sortujemy ją według dopasowania fitness,
a następnie obliczamy długość populacji i deklarujemy iterator pętli, która przebiega w następujących krokach;
- Wybieramy najlepszych osobników według modelu selekcji (metody selectionModel)
- Tworzymy nową populację z najlepszych wybranych osobników, jednak do pełnej populacji brakuje nam kilku chromosomów
- Dopełniamy do pełnej liczności populacji, poprzez operację krzyżowania (metoda crossover), oraz ewentualną mutację (metodą mutation).
- Wybieramy najlepszego osobnika z populacji po minimalnej odległości, oraz wyświetlamy wynik.
- Przeprowadzamy w ten sposób kolejną generację dopóki nie będzie ich 64.