diff --git a/genetic_algorithm.md b/genetic_algorithm.md new file mode 100644 index 0000000..2fa0948 --- /dev/null +++ b/genetic_algorithm.md @@ -0,0 +1,144 @@ +## Raport z podprojektu genetic_algorithm +#### Autorka - Magdalena Biadała + Program wykorzystuje algorytm genetyczny w celu znalezienia najlepszego dopasowania paczek do regałów, przy czym: + - każda paczka musi zostać umieszczona na jakimś regale + - waga wszystkich paczek umieszczoanych na regale nie może przekroczyć pojemności regału + - paczki umieszczane są na regałach których parametry, takie jak temperatura czy wilgotność powietrza pozwalają na bezpieczne jej przechowywanie, bez zniszczenia jej zawartości. + +Algorytm jako struktury danych przyjmuje 2 listy: + +Listę *packs* w której indeksy oznaczają numery paczek, a wartości oznaczają wagę poszczególnych paczek. Na przykład: +lista [2,6,2,5] to struktura, gdzie: +- paczka o nr 0 ma wagę 2 +- paczka o nr 1 ma wagę 6 +- paczka o nr 2 ma wagę 2 +- paczka o nr 3 ma wagę 5 + +Listę *racks* w której indeksy oznaczają numery regałów, a wartości oznaczają pojemność poszczególnych regałów. Na przykład: +lista [15,13,20,16] to struktura, gdzie: +- regał o nr 0 ma pojemność 15 +- regał o nr 1 ma pojemność 13 +- regał o nr 2 ma pojemność 20 +- regał o nr 3 ma pojemność 16 + + +#### Program zawiera kilka ważnych funkcji: + +```python +def first_gen(): + first_generation = [] + for individual in range(generation_size): + individual = [] + for pack in range(number_of_packages): + r = random.randint(0,number_of_racks-1) + individual.append(r) + first_generation.append(individual) + return first_generation + ``` +>funkcja *first_gen()* tworzy pierwsze pokolenie osobników; + +```python +def evaluation(individual): + rest_of_capacity = racks.copy() + for i in range(number_of_packages): + rest_of_capacity[individual[i]] -= packages[i] + fitness = 0 + for i in range(number_of_racks): + if rest_of_capacity[i] < 0: + fitness += rest_of_capacity[i] + elif rest_of_capacity[i] == 0: + fitness += amount_of_promotion + return fitness +``` +> funkcja *evaluation()* ocenia wybranego osobnika pod względem tego, czy: +- regały nie są przepełnione +- coś. + +>Dodatkowo delikatnie promuje ona osobniki, króre wykorzystują pojemność regałów w całości, a na koniec zwraca parametr *fitness*. + + +```python +def roulette(generation): + evaluations = [] + for i in range(generation_size): + individual_fitness = evaluation(generation[i]) + evaluations.append(individual_fitness) + maximum = min(evaluations) + normalized = [x+(-1*maximum)+1 for x in evaluations] + sum_of_normalized = sum(normalized) + roulette_tab = [x/sum_of_normalized for x in normalized] + for i in range(1,generation_size-1): + roulette_tab[i] += roulette_tab[i-1] + roulette_tab[generation_size-1] = 1 + survivors = [] + for individual in range(generation_size): + random_number = random.random() + interval_number = 0 + while random_number > roulette_tab[interval_number]: + interval_number += 1 + survivors.append(generation[interval_number]) + return survivors +``` +> funkcja *roulette()*: +- liczy parametr *fitness* (oznaczający dopasowanie) dla każdego osobnika w pokoleniu +- tworzy tablicę z przedziałami, przy czym im większy *fitness* osobnika tym większy jego przedział, a tym samym szansa że zostanie wylosowany +- losuje liczby z zakresu od 0 do 1 (liczb jest tyle ile osobników w pokoleniu) +-zwraca tych osobników, dla krótych wylosoana liczba wpadła w odpowiadający im przedział. + +```python +def crossover(individual1, individual2): + cut = random.randint(1,number_of_packages-1) + new1 = individual1[:cut] + new2 = individual2[:cut] + new1 = new1 + individual2[cut:] + new2 = new2 + individual1[cut:] + return new1, new2 +``` +>funkcja *crossover()* zajmuje się krzyżowaniem osobników. + +```python +def mutation(individual): + locus = random.randint(0,number_of_packages-1) + individual[locus] = random.randint(0,number_of_racks-1) + return individual +``` +>funkcja *mutation()* dokonuje mutacji osobnika. +#### Inicjalizacja pierwszego pokolenia +Pierwsze pokolenie jest tworzone za pomocą funkcji *first_gen()*. Osobnikiem jest lista której indeksy oznaczają numery paczek, a wartości oznaczają numery regałów. Na przykład: +osobnik [3,0,5,5] to przyporządkowanie, gdzie: +- paczka nr 0 leży na regale nr 3 +- paczka nr 1 leży na regale nr 0 +- paczka nr 2 leży na regale nr 5 +- paczka nr 3 leży na regale nr 5. + +Dla każdego osobnika w pierwszym pokoleniu liczony jest parametr *fitness*. + +#### Główna pętla programu +Dla każdego pokolenia wykonywane są kolejno funkcje: +- roulette() +>pozostawia ona tylko część osobników w pokoleniu, część z nich zostaje zmultiplikowana + +- crossover() +>dla pokolenia które pozostało po ruletce + +- mutation() +> ta funkcja wykonywana jest tylko z pewnym prawdopodobieństem określonym w zmiennej *mutation_prob*. +- evaluation() +>dla pokolenia które pozostawiły po sobie poprzednie funkcje znalezione zostaje maksimum (największa wartość *fitness*). + +#### Wynik działania algorytmu +Algorytm zwraca osobnika (przyporządkowanie) dla którego wartość fitness była największa. + +#### Przeprowadzone testy +W celu dobrania wstępnych wartości parametrów: +- *mutation_prob* (prawdopodobieństwo mutacji), +- *generation_size* (wielkość pojedynczego pokolenia), +- *number_of_generations* (liczba pokoleń), + +został przeprowadzony test. Wykonywał on 200 razy algorytm genetyczny dla wybranych parametrów, a następnie liczył średnią i medianę maksymalnej wartości *fitness* znalezionej w każdej z prób. W poniższej tabeli oprócz średniej i mediany można także odczytać czas działania algorytmu. + +![alt text](https://git.wmi.amu.edu.pl/s444360/SI_2020/src/master/wyniki_testu.PNG "tabela wyników") + + + +- *amount_of_promotion* (wartość jaka zostaje dodana do *fitness* osobnika, jeśli wykorzysta jakiś regał w pełni)