146 lines
5.8 KiB
Markdown
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)
|