SI_2020/genetic_algorithm.md
2020-05-10 16:11:54 +02:00

146 lines
5.8 KiB
Markdown

## 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 rozeznania i 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.
![Tabela wyników](wyniki_testu.PNG)
- *amount_of_promotion* (wartość jaka zostaje dodana do *fitness* osobnika, jeśli wykorzysta jakiś regał w pełni)