131 lines
5.6 KiB
Markdown
131 lines
5.6 KiB
Markdown
# 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:
|
|
|
|
```python
|
|
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.
|
|
|
|
```python
|
|
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.
|
|
|
|
```python
|
|
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.
|
|
|
|
```python
|
|
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.
|
|
|
|
```python
|
|
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.
|
|
|
|
```python
|
|
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.
|
|
|
|
```python
|
|
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**.
|
|
|
|
```python
|
|
def stopCondition(self, i):
|
|
return i == 64
|
|
```
|
|
|
|
* Warunkiem końca algorytmu jest osiągnięcie 64 generacji.
|
|
|
|
```python
|
|
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. |