SI_2020/genetic_algorithm.md
2020-06-15 03:17:57 +02:00

6.1 KiB
Executable File

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

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 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, oraz losowych list paczek i regałó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

Zastosowanie w projekcie

Algorytm uruchamiany jest po wygenerowaniu magazynu. Zwraca on najlepsze znalezione dopasowanie, które następnie zostanie wykorzystane w kolejnych podprojektach innych osób z grupy, które zajmą się optymalizacją rozwożenia paczek na przydzielone im miejsca.

  • amount_of_promotion (wartość jaka zostaje dodana do fitness osobnika, jeśli wykorzysta jakiś regał w pełni)