5.8 KiB
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:
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;
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.
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ł.
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.
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.
- amount_of_promotion (wartość jaka zostaje dodana do fitness osobnika, jeśli wykorzysta jakiś regał w pełni)