Zaktualizowany raport geneticalgorithm.md
This commit is contained in:
parent
464e085ecd
commit
8cdc229409
@ -30,7 +30,7 @@ Algorytm genetyczny, z powodów wymienionych na początku raportu, jako rodzaj a
|
||||
### Algorytm
|
||||
|
||||
```
|
||||
def main(self):
|
||||
def main(self):
|
||||
self.algorytm_genetyczny()
|
||||
self.wykonanie_trasy()
|
||||
```
|
||||
@ -38,7 +38,7 @@ Algorytm genetyczny, z powodów wymienionych na początku raportu, jako rodzaj a
|
||||
Cały algorytm jest wykonywany w funckji self.algorytm_genetyczny, natomiast funkcja self.wykonanie_trasy odpowiada integrację podprojektu w projekcie, realizując wyznaczoną przez algorytm trasę wraz z odpowiednią akcją agenta.
|
||||
|
||||
```
|
||||
self.pola_buraczane = self.wspolrzedne()
|
||||
self.znalezione_pola = self.wspolrzedne()
|
||||
```
|
||||
|
||||
Pierwszy krokiem całego programu jest wyznaczenie współrzędnych pól, przez które będziemy chcieli przejść w najkrótszej trasie. Współrzedne te są reprezentowane macierzą zawierającą liczby dwucyfrową [0,99]: np. pole 32 oznacza współrzędne [2,3].
|
||||
@ -52,10 +52,81 @@ Następnie wyznaczany jest pierwotny koszt trasy wiodącej przez wszystkie pola
|
||||
Poniżej przestawiony został już właściwy algorytm:
|
||||
|
||||
```
|
||||
# Utworzenie pokolenia
|
||||
self.pierwsze_pokolenie = self.tworzenie_pokolenia(self.znalezione_pola,10)
|
||||
|
||||
# Funkcja przystosowania
|
||||
self.przystosowanie, self.najnizszy_koszt, self.najwyzszy_koszt, self.srednie_przystosowanie_pierwszego_pokolenia, self.najtanszy_osobnik = self.ocena_przystosowania(self.pierwsze_pokolenie)
|
||||
|
||||
# Populacja pośrednia wybrana metodą ruletki
|
||||
self.populacja_posrednia = self.wybor_populacji_posredniej(self.pierwsze_pokolenie, self.przystosowanie)
|
||||
|
||||
# Krzyżowanie populacji pośredniej
|
||||
self.populacja_po_krzyzowaniu = self.krzyzowanie(self.populacja_posrednia)
|
||||
|
||||
# Mutacja populacji pośredniej
|
||||
self.populacja_po_mutacji = self.mutacja(self.populacja_po_krzyzowaniu)
|
||||
|
||||
# Optymalizacja populacji pośredniej
|
||||
self.populacja_po_optymalizacji = self.optymalizacja(self.populacja_po_mutacji,self.znalezione_pola)
|
||||
|
||||
if (self.min_koszt)/(self.srednie_przystosowanie_pierwszego_pokolenia) < (0.69):
|
||||
print("Zakończono wykonywanie algorytmu po " + str(i) + " pokoleniach")
|
||||
break
|
||||
```
|
||||
|
||||
Pierwszym krokiem każdego algorytmu genetycznego jest utworzenie pierwszego pokolenia osobników. Każde pokolenie w podprojekcie liczy sobie 10 osobników, z których każdy jest permutacją bez powtórzeń współrzędnych zawartych w tablicy self.znalezione_pola wraz z dodaną na koniec współrzędną 0, jako polem, do którego agent ma powrócić na końcu.
|
||||
Następnie wykonywana jest tzw. funkcja przystosowania, której zadaniem jest ocenić jakość każdego osobnika (czyli potencjalnego rozwiazania):
|
||||
|
||||
```
|
||||
przystosowanie_osobnikow.append(round(((srednie_przystosowanie/koszty_tras_osobnikow[l])*10),2))
|
||||
```
|
||||
|
||||
Przystosowanie każdego z osobników przechowywane jest w tablicy przystosowanie_osobnikow i wyliczane za pomocą ilorazu średniego przystosowania wszystkich osobników w danym pokoleniu i kosztu konkretnego osobnika. Im lepsza jakość osobnika (czyli im niższy koszt proponowanej przez niego trasy), tym wyższy wynik funkcji przystosowania.
|
||||
Po ocenie przystosowania dokonuje się wyboru osobników do populacji pośredniej, z której otrzymamy potomków osobników obecnego pokolenia. Tutaj zdecydowano się na wybór tzw. metodą ruletki, w której każdemu osobnikowi przydziela się zakres należący do przedziału [1,100], proporcjonalny do jego przystosowania. Im większe przystosowanie, tym większy przedział zostanie danemu osobnikowy przydzielony. Następnie losowane jest pięć liczb, właśnie z przedziału [1,100] i do populacji pośredniej trafiają osobnicy, w których zakresach mieszczą się wylosowane liczby. Grupa takich pięciu osobników (do której osobnik może trafić więcej niż raz) staje się populacją pośrednią, która przystąpi do operacji genetycznych w celu poprawienia genotypu.
|
||||
Tymi operacjami genetycznymi są krzyżowanie, mutacja i dodatkowa optymalizacja.
|
||||
|
||||
```
|
||||
czy_krzyzowac = random.randint(1,100)
|
||||
if (czy_krzyzowac < 11) and (rodzic_1 != rodzic_2):
|
||||
```
|
||||
|
||||
Prawdopodobieństwo krzyżowania dwóch kolejnych osobników populacji pośredniej wyznaczono na 10%. Jeśli krzyżowanie nie nastąpi lub osobnicy są identyczni, wówczas przechodzą oni do kolejnego etapu w niezmienionej postaci.
|
||||
Jeśli krzyżowanie dojdzie do skutki, wówczas dla obu osobników wykonuje się następującą operację: wybiera się losowe miejsce krzyżowania w tablicy zawierającej genotyp, sprzed którego wszystkie geny przechodzą w niezmienionej kolejności do potomka. Następnie brakujące geny potomka uzupełnia się genami drugiego osobnika, których jeszcze nie zawiera potomek (tak by geny, czyli współrzędne, się nie powtórzyły w genotypie)
|
||||
|
||||
```
|
||||
k = len(populacja_po_krzyzowaniu) - 1
|
||||
while k >= 0:
|
||||
czy_mutacja = random.randint(0,100)
|
||||
if czy_mutacja < 3:
|
||||
kogo_mutujemy = populacja_po_krzyzowaniu[k]
|
||||
```
|
||||
|
||||
Jeśli chodzi o mutację, tu prawdopodobieństwo jej zajścia dla każdego osobnika ustalono na 2%. Mutacja polega na wylosowaniu dwóch różnych genów osobnika i zamienieniu ich miejscami.
|
||||
Na koniec procesu tworzenia potomków dochodzi do opcjonalnej operacji optymalizacji. Jest to nieobowiązkowa funkcja zaimplemetowana w celu dywersyfikacji potomków. Polega ona na usunięciu z populacji pośredniej potwórzeń wśród osobników i uzupełnienie braku nowym, losowym osobnikiem. Po optymalizacji, pokolenie nadal będzie liczyło 10 osobników, lecz każdy z nich z pewnością będzie różny od pozostałych.
|
||||
Te 10 osobników storzy nową populację potomków. Tę populację poddaje się ocenie, czyli sprawdzeniu warunku końca działania algorytmu.
|
||||
|
||||
```
|
||||
if (self.min_koszt)/(self.srednie_przystosowanie_pierwszego_pokolenia) < (0.69):
|
||||
print("Zakończono wykonywanie algorytmu po " + str(i) + " pokoleniach")
|
||||
break
|
||||
```
|
||||
|
||||
Jeśli zostanie spełniony warunek znacznego poprawienia kosztu najtańszej trasy najnowszego pokolenia względem średniej długości tras pierwszego pokolenia, wówczas można uznać, że znalezione rozwiazanie jest zadowalające i algorytm zostaje przerwany.
|
||||
Jeśli jednak warunek nie jest spełniony, wtedy algorytm wykonuje się na nowo, począwszy od etapu oceny przystosowania nowego pokolenia.
|
||||
|
||||
```
|
||||
def wykonanie_trasy(self):
|
||||
i = len(self.najtansza_trasa) - 1
|
||||
l = 0
|
||||
while l < i:
|
||||
self.pathfinding_tractor.pathfinding_tractor(self.field, self.traktor, self.ui, self.najtansza_trasa, l)
|
||||
l = l + 1
|
||||
```
|
||||
|
||||
Ostatnim elementem mojego programu jest ,,wcielenie w życie" znalezionej trasy. Taką trasę podaje się funkcji pathfinding_tractor, która następnie egzekwuje agentem wykonanie konkretnej akcji na polach danego typu w zadanej kolejności.
|
||||
|
||||
|
||||
### Dobrane parametry
|
||||
|
||||
|
||||
|
Loading…
Reference in New Issue
Block a user