SzIProjekt/Kinga Jagodzińska_Algorytm genetyczny.md

6.1 KiB

Sztuczna inteligencja - projekt zespołowy - Autonomiczny Traktor

autorzy: Aleksandra Werda, Natalia Wiśniewska, Kinga Jagodzińska, Aleksandra Jonas


Kinga Jagodzińska - podprojekt: Sadzenie przy użyciu algorytmu genetycznego


Zadaniem tego podprojektu było dobranie nasion do zasadzenia na pustych polach przy użyciu algorytmu genetycznego, tak aby bazując na ich dopasowaniu do sadzonek na sąsiednich polach, wybór był jak najbardziej optymalny.

Najpierw używamy stworzonej funkcji empty() do przeliczenia ilości pustych pól na planszy (length) oraz ich położenia na planszy (index_gen). Jest to konieczne, ponieważ rodzaje sadzonek na polach w programie są dobierane losowo i liczba pustych pól przy każdym uruchomieniu programu się zmienia.

def empty(self):
    for x in range(25):
        if self.game.fields[x] == "puste":
            self.length = self.length + 1
            self.index_gen.append(x)

Następnie tworzymy pierwszą generację rodziców. Każdy rodzic jest tablicą o długości równej liczbie pustych pól i do tej tablicy losowo zostają przydzielone sadzonki. Każdego nowo wygenerowanego rodzica dodajemy do tablicy tablic dna.

def dna_create(self):
    self.dna = []
    for x in range(self.max_gen):
        temp = []
        for y in range(self.length):
            temp.append(random.choice(["żyto", "jęczmień", "owies", "marchew", "rzodkiew", "pietruszka"]))
        self.dna.append(temp)

W funkcji algorytm() tworzymy tablicę l, która będzie zawierać sumę dopasowania dobranych sadzonek do sąsiednich pól. Definiujemy zmienną first jako True. Reszta funkcji znajduje się w pętli while. Jeśli w tablicy tablic dna mamy pierwszą generację rodziców, dodajemy do tablicy l odpowiadający im wynik używając funkcji l_score. Jeśli jest to późniejsza generacja do tablicy dodajemy tylko potomstwo, którego wynik jeszcze nie został dodany.

def algorytm(self):
    l = []
    first = True
    self.empty()
    self.dna_create()
    while True:
        if first == True:
            for x in range(len(self.dna)):
                l.append(self.l_score(self.dna[x]))
            first = False
        elif first == False:
            for x in range(int(self.max_gen * 1 / 3)):
                l.append(self.l_score(self.dna[x + int(self.max_gen * 2 / 3)]))

Jeśli algorytm odnajdzie możliwie najbardziej optymalną możliwość lub przejdziemy przez 200 generacji, algorytm się kończy, zwracając nam odpowiadającą tablicę z tablicy dna.

        if max(l) >= self.length or self.pokolenie > 200:
            return self.dna[l.index(max(l))]

Następnie pozbywamy się 1/3 wszystkich tablic z tablicy dna pozostawiając te, które mają najwyższy odpowiadający im wynik w l.

        for x in range(int(self.max_gen * 1 / 3)):
            del self.dna[l.index(min(l))]
            l.remove(min(l))

Dla każdej pary rodziców w dna tworzymy potomstwo, którego elementy są dobierane naprzemiennie.

        for x in range(int(self.max_gen * 1 / 3)):
            temp = []
            for y in range(self.length):
                if y % 2 == 0:
                    temp.append(self.dna[x][y])
                if y % 2 == 1:
                    temp.append(self.dna[int(self.max_gen * 2 / 3) - 1 - x][y])

Każdy potomek ma 40% szansy na zmianę maksymalnie trzech elementów.

            if random.randint(0, 100) <= 40:
                for x in range(3):
                    temp[random.randint(0, self.length - 1)] = random.choice(["żyto", "jęczmień", "owies", "marchew", "rzodkiew", "pietruszka"])

Następnie dodajemy potomka do tablicy tablic dna oraz zwiększamy numer kolejnego pokolenia.

            self.dna.append(temp)
        self.pokolenie = self.pokolenie + 1

Wcześniej wspomniana funkcja l_score sumuje iloczyny dopasowania wybranego rodzaju nasion do posadzenia na wybranym polu do rodzaju sadzonek na sąsiednich polach, także tych, które mają dopiero zostać zasadzone. Dopasowanie pomiędzy konkretnymi roślinami zostało ustalone odgórnie.

def l_score(self, tab):
    suma1 = 0
    for x in range(len(tab)):
        suma = 1
        for y in self.game.neighbours[self.index_gen[x]]:
            if y in self.index_gen:
                som = tab[self.index_gen.index(y)]
            else:
                som = self.game.fields[y]
            if tab[x] == "żyto":
                if som == "żyto":
                    suma = suma * 0.5
                ...

Funkcję algorytm() wywołujemy w init() znajdującym się w tractor. py, by później wykorzystać ją w sadzeniu podczas poruszania się traktorem po planszy.

self.sadzonka = self.gen.algorytm()
if pygame.key.get_pressed()[pygame.K_SPACE]:
    pole = int(self.pos.y // 144 * 5 + self.pos.x // 144)
    if pole in self.gen.index_gen and pole not in self.road:
        self.game.fields[pole] = self.sadzonka[self.gen.index_gen.index(pole)]
    if len(self.road) != 0:
        if self.road[0] == pole + 1:
            self.pos.x = self.pos.x + 144
        elif self.road[0] == pole - 1:
            self.pos.x = self.pos.x - 144
        elif self.road[0] == pole + 5:
            self.pos.y = self.pos.y + 144
        elif self.road[0] == pole - 5:
            self.pos.y = self.pos.y - 144
        self.road.pop(0)

Dodatkowo została stworzona funkcja best_path(), która wybiera najlepszą drogę pomiędzy pustymi polami zwracając szczególną uwagę na kolejność przejścia.

def best_path(self):
    best = 999999
    best_route = []
    for x in permutations(self.gen.index_gen, self.gen.length):
        # punkt początkowy
        start = 0
        droga = [0]
        dlug = 0
        for y in x:
            temp1 = self.algo(start, y)
            if temp1[0] == droga[len(droga)-1]:
                del temp1[0]
            droga.extend(temp1)
            start = y
            for z in temp1:
                dlug = dlug + self.f_score[z]
        if dlug < best:
            best_route = droga
            best = dlug
    return best_route