Zadanie domowe nr 3 Sieci neuronowe sterujące czołgami Spis treści Rzeczy organizacyjne... 1 Ogólny opis i cel zadania... 2 Jak pisać skrypt sterujący czołgiem?... 2 Na jakich danych pracuję?... 3 Jak mam stworzyć skrypt z siecią neuronową?... 10 A. Zbieranie danych... 10 B. Trenowanie sieci... 10 C. Przenoszenie sieci do JS... 11 D. Kilka praktycznych wskazówek... 13 Wysyłanie plików... 13 Ocenianie... 13 Zadania bonusowe... 14 Rzeczy organizacyjne Maks. punktów: 7 pkt (w tym projekcie: spóźnienie powoduje brak zaliczenia projektu i 0 punktów) Termin oddania: dla wszystkich grup bezwzględnym terminem oddania zadania jest 6 stycznia (niedziela) do godz. 23:59. W poniedziałek rano zbieram Państwa rozwiązania i przeprowadzam turniej, w którym nawzajem będą ze sobą walczyły Państwa skrypty. W razie problemów zachęcam do przyjścia na konsultacje 3 stycznia, czwartek, godz. 11, pokój 30. Język programowania: JavaScript Zasady: Skrypt (sieć neuronową) należy stworzyć samodzielnie, nie ściągać od innych. Dozwolone jest wykorzystanie bibliotek do wytrenowania sieci. Ocenianie: Państwa sieci neuronowe będą brały udział w turnieju, im więcej przeciwników (innych studentów) pokona sieć, tym dostaje więcej punktów. Email do wysłania do 6 stycznia: tytuł: PD-3-tank, Jan Kowalski, nr indeksu: 123456 treść: (pusta) załączniki: sprawozdanie.pdf, skrypt.txt
Ogólny opis i cel zadania Na stronie https://inf.ug.edu.pl/~gmadejsk/tanks/tanks.html dostępna jest prosta gra: strzelanka z czołgami (działa dość dobrze w Chrome, i dość dobrze pod Firefox, inne przeglądarki mogą sprawiać problemy). Gra jest na dwóch graczy. Czołgami steruje się za pomocą klawiatury (klawisze do sterowania są podane na stronie pod apletem do gry). Można jechać do przodu i tyłu, skręcać, obracać lufę niezależnie od czołgu i strzelać (co 100 tików procesora). Celem gry jest zestrzelenie przeciwnika w ciągu 40 sekund. Po 20 sekundach czołg bliżej środka planszy dostaje bonus do strzelania (strzela 2x szybciej). Po 40 sekundach, gdy oba czołgi są nadal na planszy, gra się kończy zwycięża czołg bliżej środka planszy lub jest remis gdy są jednakowo oddalone od środka. Ciekawą dodatkową opcją tej strony jest pisanie skryptów, które sterują czołgiem zamiast gracza. Trzeba wówczas zaznaczyć okienko Check the box to switch control to bot i wkleić Państwa skrypt do okna. Na stronie umieszczone są już proste skrypty, które działają wg prostych zasad np. - jedź zawsze do przodu i do tyłu - obracaj zawsze lufą - strzelaj gdy tylko możliwe Proszę przetestować grę, grając z botem. Celem pracy domowej jest stworzenie sieci neuronowej (w formie skryptu sterującego czołgiem) tak, aby grała jak najlepiej w daną grę i, o ile szczęście dopisze, pokonywała inne skrypty lub ludzkich graczy. Bardzo prostą sieć neuronową, która robiła niewiele więcej niż jeżdżenia w kółko mieliśmy już stworzyć na jednym z poprzednich laboratoriów. Jak pisać skrypt sterujący czołgiem? Musimy zmodyfikować w odpowiednim oknie zawartość funkcji function(e) {. Przede wszystkim, żeby czołg cokolwiek robił należy wydać mu polecenia przekazując je w obiekcie response. Mamy do wyboru siedem poleceń do wydawania: ruch w przód i tył, skręt w lewo i prawo, strzelenie, lufa w lewo lub prawo. Poniższy skrypt ilustruje jak należy je wlączać (ustawiamy im flagę na 1, domyślna to 0). function(e) { var response = {; response.goforward = 1; //lub w drugą stronę: response.goback = 1; response.turnleft = 1; //lub w drugą stronę: response.turnright = 1; response.shoot = 1;
response.cannonleft = 1; //lub w drugą stronę: response.cannonright = 1 self.postmessage(response); Powyższy skrypt jest bardzo prymitywny bo czołg nie bada co się dzieje wokół niego, jaka jest sytuacja na planszy, jaki czas gry itd. Tymczasem mamy dziesiątki parametrów, od których możemy uzależnić działanie skryptu i zachowanie czołgu. Wszystkie te dane siedzą w obiekcie e, przekazywanym jako parametr do funkcji. A dokładniej musimy wejść do obiektu mytank, enemytank i currentgametime. Zwróć uwagę jak wygląda jeden ze skryptów wklejonych na stronie: function(e) { var response = {; if (currentgametime>20000) { response.cannonleft = 1; response.goforward = 1; else { response.cannonright = 1; response.goback = 1; if(mytank.shootcooldown == 0) { response.shoot = 1; self.postmessage(response); Uzależnia on działanie czołgu od czasu gry. Strzela tylko wtedy, gdy może (tzn. pocisk został załadowany i cooldown wynosi 0). Szczegółowy spis wszystkich 50 parametrów do wykorzystania znajduje się w następnym rozdziale. Oczywiście naszym celem nie jest napisanie skryptu w dowolnej formie, tylko sieci neuronowej. To bardzo specyficzny skrypt! (o tym też w dalszych rozdziałach). Na jakich danych pracuję? Poniżej opisujemy jakie dane można wykorzystać w każdym skrypcie do zbadania planszy, i jednocześnie są to dane, jakie nasza sieć neuronowa powinna brać pod uwagę żeby się uczyć. Do dyspozycji mamy 9 parametrów opisujących stan naszego czołgu, 9 parametrów dla wrogiego czołgu, 7 flag sprawdzających jaki przycisk jest naciśnięty dla naszego czołgu, 12 parametrów dla naszych pocisków i 12 parametrów wrogich pocisków oraz 1 parametr będący czasem gry. Łącznie to 50 parametrów!
Podczas gry parametry są na bieżąco (co tick procesora) wyświetlane w konsoli programistycznej, skąd można je w wygodny sposób przekopiować do pliku csv, na bazie którego można zacząć uczyć sieci neuronowe (np. w R). By włączyć konsolę wciśnij przycisk F12 i kliknij na zakładkę Console przed uruchomieniem gry. Większość przeglądarek oferuje narzędzia dla deweloperów: https://en.wikipedia.org/wiki/web_development_tools Korzystając z tych danych w skrypcie musimy odwołać się do obiektu e.data, o czym wspominaliśmy wcześniej. W konsoli nazwy parametrów są nieco skrócone. Ważna uwaga: w konsoli mytank to zawsze czołg brązowy, a enemytank to czołg zielony! Jeśli chcemy wiec pozyskiwać dane do nauki dla sieci neuronowej to grajmy dobrze brązowym czołgiem. Natomiast w skryptach sterujących, mytank to oczywiście czołg odpowiadający danemu skryptowi/okienku/graczowi. Poniższa tabela szczegółowo opisuje wszystkie 50 parametrów gry: Zmienna Opis Typ i zakres Parametry mojego czołgu mytank.x mytank.x mytank.y mytank.y mytank.rotation współrzędna x środka mojego czołgu na planszy współrzędna y środka mojego czołgu na planszy kąt obrotu mojego czołgu wokół własnej osi [0,360)
mytank.rotation mytank.rotation mytank.rotation mytank.velocityx mytank.velocityx mytank.velocityy mytank.velocityy mytank.accelerationx mytank.accelerationx mytank. accelerationy mytank. accelerationy mytank.shootcooldown mytank.shootcooldown Przyciski/flagi sterujące moim czołgiem mytank.controls.turnleft mytank.controls.turnleft Uwaga: obracając czołg zmienia się też kąt lufy kąt celowania lufy na moim czołgu względem planszy prędkość mojego czołgu pozioma (rzut wektora prędkości na oś X) prędkość mojego czołgu pionowa (rzut wektora prędkości na oś Y) przyspieszenie mojego czołgu poziome (rzut wektora przyspieszenia na oś X) przyspieszenie mojego czołgu pionowe (rzut wektora przyspieszenia na oś Y) czas jaki pozostał potrzebny mi do wystrzelenia kolejnego pocisku czy mój czołg ma włączony przycisk/flagę skręcania w lewo stopnie [0,360) stopnie [-15,15] [-15,15] [0,0.05] [0,0.05] int [0,100] ticki bool {0,1 0 = nie 1 = tak mytank.controls.turnright mytank.controls.turnright czy mój czołg ma włączony przycisk/flagę skręcania w prawo bool {0,1 0 = nie 1 = tak mytank.controls.goforward mytank.controls.goforward czy mój czołg ma włączony przycisk/flagę przyspieszania/jazdy na przód bool {0,1 0 = nie 1 = tak
mytank.controls.goback mytank.controls.goback czy mój czołg ma włączony przycisk/flagę cofania/jazdy na wstecznym biegu bool {0,1 0 = nie 1 = tak mytank.controls.shoot mytank.controls.shoot czy mój czołg ma włączony przycisk/flagę strzelania (wystrzał możliwy tylko gdy shootcooldown = 0) bool {0,1 0 = nie 1 = tak mytank.controls.cannonleft mytank.controls. cannonleft mytank.controls.cannonright mytank.controls.cannonright Ostatnie 3 pociski mojego czołgu mybullet1.x mytank.bullets[0].x mybullet1.y mytank.bullets[0].y mybullet1.velocityx mytank.bullets[0].velocityx mybullet1.velocityy mytank.bullets[0].velocityy mybullet2.x mytank.bullets[1].x czy mój czołg ma włączony przycisk/flagę skręcania lufy w lewo czy mój czołg ma włączony przycisk/flagę skręcania lufy w prawo Współrzędna x mojego wystrzelonego pocisku (najnowszego, wystrzelony jako ostatni). Jeśli =0 to znaczy, że pocisk nie istnieje Współrzędna y mojego wystrzelonego pocisku (najnowszego, wystrzelony jako ostatni). Jeśli =0 to znaczy, że pocisk nie istnieje. Prędkość pozioma mojego wystrzelonego pocisku (najnowszego, wystrzelony jako ostatni). Jeśli =0 to Prędkość pionowa mojego wystrzelonego pocisku (środkowego). Jeśli =0 to Współrzędna x mojego wystrzelonego pocisku (środkowego). Jeśli =0 to bool {0,1 0 = nie 1 = tak bool {0,1 0 = nie 1 = tak
mybullet2.y mytank.bullets[1].y mybullet2.velocityx mytank.bullets[1].velocityx mybullet2.velocityy mytank.bullets[1].velocityy mybullet3.x mytank.bullets[2].x mybullet3.y mytank.bullets[2].y mybullet3.velocityx mytank.bullets[2].velocityx mybullet3.velocityy mytank.bullets[2].velocityy Wrogi czołg - parametry enemytank.x enemytank.x enemytank.y enemytank.y enemytank.rotation Współrzędna y mojego wystrzelonego pocisku (środkowego). Jeśli =0 to Prędkość pozioma mojego wystrzelonego pocisku (środkowego). Jeśli =0 to Prędkość pionowa mojego wystrzelonego pocisku (środkowego). Jeśli =0 to Współrzędna x mojego wystrzelonego pocisku (najstarszego, wystrzelony jako pierwszy). Jeśli =0 to znaczy, że pocisk nie istnieje Współrzędna y mojego wystrzelonego pocisku (najstarszego, wystrzelony jako pierwszy). Jeśli =0 to znaczy, że pocisk nie istnieje Prędkość pozioma mojego wystrzelonego pocisku (najstarszego, wystrzelony jako pierwszy). Jeśli =0 to Prędkość pionowa mojego wystrzelonego pocisku (najstarszego, wystrzelony jako pierwszy). Jeśli =0 to współrzędna x środka wrogiego czołgu na planszy współrzędna y środka wrogiego czołgu na planszy kąt obrotu wrogiego czołgu wokół własnej osi [0,360)
enemytank.rotation enemytank.rotation enemytank.rotation enemytank.velocityx enemytank.velocityx enemytank.velocityy enemytank.velocityy enemytank.accelerationx enemytank.accelerationx enemytank. accelerationy enemytank. accelerationy enemytank.shootcooldown enemytank.shootcooldown Wrogie pociski ostatnie 3 enemybullet1.x enemytank.bullets[0].x enemybullet1.y enemytank.bullets[0].y enemybullet1.velocityx enemytank.bullets[0].velocityx enemybullet1.velocityy Uwaga: obracając czołg zmienia się też kąt lufy kąt celowania lufy na wrogim czołgu względem planszy prędkość wrogiego czołgu pozioma (rzut wektora prędkości na oś X) prędkość wrogiego czołgu pionowa (rzut wektora prędkości na oś Y) przyspieszenie wrogiego czołgu poziome (rzut wektora przyspieszenia na oś X) przyspieszenie wrogiego czołgu pionowe (rzut wektora przyspieszenia na oś Y) czas jaki pozostał potrzebny wrogowi do wystrzelenia kolejnego pocisku Współrzędna x wrogiego wystrzelonego pocisku (najnowszego, wystrzelony jako ostatni). Jeśli =0 to znaczy, że pocisk nie istnieje Współrzędna y wrogiego wystrzelonego pocisku (najnowszego, wystrzelony jako ostatni). Jeśli =0 to znaczy, że pocisk nie istnieje. Prędkość pozioma wrogiego wystrzelonego pocisku (najnowszego, wystrzelony jako ostatni). Jeśli =0 to Prędkość pionowa wrogiego wystrzelonego pocisku (środkowego). Jeśli =0 to stopnie [0,360) stopnie [-15,15] [-15,15] [0,0.05] [0,0.05] int [0,100] ticki
enemytank.bullets[0].velocityy enemybullet2.x enemytank.bullets[1].x enemybullet2.y enemytank.bullets[1].y enemybullet2.velocityx enemytank.bullets[1].velocityx enemybullet2.velocityy enemytank.bullets[1].velocityy enemybullet3.x enemytank.bullets[2].x enemybullet3.y enemytank.bullets[2].y enemybullet3.velocityx enemytank.bullets[2].velocityx enemybullet3.velocityy enemytank.bullets[2].velocityy Czas gry currentgametime currentgametime Współrzędna x wrogiego wystrzelonego pocisku (środkowego). Jeśli =0 to Współrzędna y wrogiego wystrzelonego pocisku (środkowego). Jeśli =0 to Prędkość pozioma wrogiego wystrzelonego pocisku (środkowego). Jeśli =0 to Prędkość pionowa wrogiego wystrzelonego pocisku (środkowego). Jeśli =0 to Współrzędna x wrogiego wystrzelonego pocisku (najstarszego, wystrzelony jako pierwszy). Jeśli =0 to znaczy, że pocisk nie istnieje Współrzędna y wrogiego wystrzelonego pocisku (najstarszego, wystrzelony jako pierwszy). Jeśli =0 to znaczy, że pocisk nie istnieje Prędkość pozioma wrogiego wystrzelonego pocisku (najstarszego, wystrzelony jako pierwszy). Jeśli =0 to Prędkość pionowa wrogiego wystrzelonego pocisku (najstarszego, wystrzelony jako pierwszy). Jeśli =0 to Czas gry w milisekundach. Zaczyna się od 40000 i maleje do 0. Int [0,40000]
Jak mam stworzyć skrypt z siecią neuronową? Zdradzanie wszystkich tajemnic nie byłoby dobre, ale kilka bardzo pomocnych wskazówek znajduje się poniżej. Krok po kroku podpowiem jak należy (można) podejść do problemu. A. Zbieranie danych Wiesz już, że dane do trenowania sieci pojawią się w konsoli narzędzi programistycznych. Ich skopiowanie i wczytanie w R (lub innym programie) nie powinno być trudne. Ile tych danych zebrać? Odpowiedź: dużo! To muszą być solidne megabajty informacji. Im więcej, tym sieć będzie sprawniejsza i lepiej wyuczona. Ale nie tylko ilość danych jest ważna, ale też jakość. Sieć można nauczyć się grać od Ciebie (pamiętaj, żeby grać brązowym czołgiem, bo to jego dane pojawiają się w konsoli do treningu jako mytank). Może się też nauczyć od innego skryptu niebędącego siecią neuronową. Wówczas jednak musisz najpierw napisać skrypt nauczycielski po czym przeszkolić sieć neuronową na danych z konsoli dostarczonych przez grę skryptu. B. Trenowanie sieci W R korzystając z paczki neuralnet można prosto wytrenować sieć. Na dane wyjściowe dajemy parametry controls dla mytank (7 kolumn), a cała reszta może służyć jako input. Ważny jest tutaj preprocessing. Czy znormalizować dane liczbowe? Czy pozbyć się niepotrzebnych kolumn? A może jakieś przerobić czy połączyć? Odrzucenie części kolumn wydaje się dobrym pomysłem. Oto jak wygląda moja sieć na wszystkich dostępnych parametrach. Jest olbrzymia, z setkami wag. Ten moloch nie będzie szybko sterował czołgiem i pewnie zamuli grę.
Odrzucenie części kolumn daje już sieć nieco przyjemniejszą. Może obcięto za dużo, może za mało. Trzeba eksperymentować. Gdy wytrenowaliśmy sieć np. o nazwie nn, można wyciągnąć z niej różnorakie informacje np. wagi: wpisując nn[10] czy funkcję aktywacji dla neuronów ukrytych: nn[6]. Informacje te powinniśmy przeklepać do JavaScriptowego okienka. C. Przenoszenie sieci do JS Przenoszenie sieci do JS może być trochę żmudne, ale nie powinno być trudne. Próbkę tego mieliśmy w zadaniu 3 na jednych z poprzednich laboratoriów. Przyjrzymy się tamtemu skryptowi i wskażemy co należy zmodyfikować. Możesz sobie pomóc wprowadzając zmienne pomocnicze, o krótszych nazwach. function(e) { var response = {; var enemy = enemytank; var me = mytank; Wprowadź dane do warstwy wejściowej sieci. Tutaj są tylko 3 wartości. W Twojej sieci może być ich kilkadziesiąt. Dodatkowo, dokonaj na nich wszelkich ważnych operacji preprocessingowych (np. normalizacja, sklejanie, modyfikacje). var input = [me.x/500.0, me.y/500.0, me.rotation/360.0];
Uzupełnij wagi między warstwą wejściową a ukrytą oraz bias. Dane te znajdziesz w sieci neuronowej w R pod zmienną nn[10]. Ustaw warstwę hidden na odpowiednio dłuższy wektor. Oblicz wszystkie wartości w ukrytych neuronach (upewnij się, że pętle działają poprawnie, być może trzeba je zmodyfikować). Sprawdź czy funkcja aktywacji się zgadza w kodzie z tą w R (nn[6]). var weights1 = [0,0,0,0,0,0,0,0,0]; var bias1 = [0,0,0]; var hidden = [0,0,0]; var i,j; for (i=0; i<3; i++){ for (j=0; j<3; j++){ hidden[i] = input[j]*weights1[i*3+j]; hidden[i] = hidden[i]+bias1[i]; hidden[i] = 1/(1+Math.pow(Math.E, - hidden[i])); Uzupełnij wagi i bias między warstwą ukrytą a warstwą wyjściową, korzystając z macierzy z nn[10]. Dodaj warstwę ukrytą (teraz to będzie pewnie 7 liczb). Zmodyfikuj pętle jeśli źle działają na wagach. Tutaj dodatkowo dano funkcję aktywacji, by wyniki wpadły w przedział [0,1], a następnie zaokrąglane są do {0,1. W razie czego, zmodyfikuj. Ustaw wszystkie response (u nas 7) i odpal skrypt! var weights2 = [0,0,0,0,0,0,0,0,0,0,0,0]; var bias2 = [0,0,0,0]; var output = [0,0,0,0]; for (i=0; i<4; i++){ for (j=0; j<3; j++){ output[i] = hidden[j]*weights2[i*3+j]; output[i] = output[i]+bias2[i]; output[i] = 1/(1+Math.pow(Math.E, -output[i])); output[i] = Math.round(output[i]); response.turnleft = output[0]; response.turnright = output[1]; response.goforward = output[2]; response.goback = output[3]; self.postmessage(response);
D. Kilka praktycznych wskazówek Jeśli sieci działają źle albo wolno postaraj się obciąć jak najwięcej niepotrzebnych kolumn. Dodatkowo zastanów się czy nie skupić się na pomniejszym tasku. Zamiast uczyć sieć neuronową atakować wroga, i jednocześnie obracać się, unikać pocisków, zbliżać do środka, okrążać wroga, itp. wybierz jakie z zadań są priorytetowe i daj sieci neuronowej tylko takie dane, by specjalizowała się w określonym zadaniu. Może, przykładowo, w ogóle nie uczyć jej jeździć, ale tylko dobrze strzelać? Albo na odwrót? Albo coś pomiędzy? Jeśli czujesz, ze ten temat Cię przerasta, możesz też ręcznie stworzyć niewielką sieć, tak jak robiliśmy na laboratoriach, wstawiając własnoręcznie wagi metoda prób i błędów. Wysyłanie plików Na email należy wysłać sprawozdanie z kilkoma odpowiedziami na pytania: Jak trenowałem sieci? Skąd pozyskałem dane do trenowania? Ile MB danych wyszło? Jeśli wykorzystywałem narzędzie (poza R) to jakie? Oraz dodatkowo zawrzeć macierze z wagami oraz graficzne ilustracje stworzonych sieci (grafy), które powstawały w drodze Twoich eksperymentów (mogą być nawet złe opisz co takiego, niezdarnego robiły pierwsze wersje). Na końcu wskaż najlepszą sieć i oceń jaki ma poziom gry (czy lepszy niż człowiek?). Dodatkowo należy w pliku z rozszerzeniem txt (nie.js!) zamieścić skrypt ze swoją siecią neuronową i dołączyć do sprawozdania. Skrypt z pliku txt będzie kopiowany do okienka z grą. Przypominam jeszcze raz, że email musi dojść do mnie najpóźniej 6 stycznia (niedziela) do godz. 23:59. Po tym terminie mogę zacząć oceniać rozwiązania tworząc turniej skryptów. Ocenianie Urządzane są trzy turnieje, w których Państwa skrypty będą rywalizowały pomiędzy sobą. skrypty wszystkich osób z grup wtorkowych + 7 botów skrypty wszystkich osób z grupy środowej + 7 botów skrypty wszystkich osób z grupy czwartkowej + 7 botów W przypadku gdy wpłynie mało rozwiązań (mniej lub równo 7) możliwe jest połączenie dwóch turniejów w jeden, albo trzech w jeden. W danym turnieju każda sieć neuronowa gra z każdą inną (oraz z botami). Tworzona jest tabela punktowa. Pięć najlepszych skryptów (łącznie z botami) dostaje 7 punktów. Siedem najgorszych skryptów (łącznie z botami) dostaje kolejno 4,3.5,3,2.5,2,1,0 punktów.
Cała reszta pomiędzy dostaje (w kolejności od najlepszego): 4 x 6.5 pkt, 4 x 6 pkt, 4 x 5.5 pkt, 4 x 5pkt, 4 x 4.5 pktów. Siedem botów dodatkowo uczestniczących w turnieju będzie wykonywało proste akcje np. kręciło się w kółko i strzelało jechało do przodu i do tyłu stało w miejscu rozpoczęcie stało w środku planszy kręciło się i strzelało w środku planszy Z turnieju postaram się zrobić film i wrzucić na Youtube. Zadania bonusowe Za ponadprogramowe punkty można zrobić dodatkowe zadania. Treść poniżej: Napisanie bota, który w trzech rundach pokona prowadzącego zajęcia. Bot nie musi być siecią neuronową. + 1.5 punkta Napisanie bota, który odtwarza krok po kroku Kontroler Rozmyty tzn. tworzone są zmienne lingwistyczne na podstawie parametrów wejściowych dla czołgów (mytank, enemytank, currentgametime). Dodane są sensowne reguły, które sterować będą czołgiem. Zwracana będzie wyostrzona (defuzzify) odpowiedź, jaki ruch ma podjąć czołg w danym tiku procesora. + 2 punkty Dokonaj eksperymentów z algorytmami do Neuroewolucji (https://en.wikipedia.org/wiki/neuroevolution ). Jest to technika, która łączy algorymtu genetyczne z sieciami neuronowymi. W skrócie: mamy populacje sieci neuronowych, które w procesie ewolucji (pokolenia, krzyżowanie) tworzą coraz to lepsze i inteligentne osobniki. Patrz niżej: https://www.youtube.com/watch?v=qv6uvoq0f44 https://towardsdatascience.com/gas-and-nns-6a41f1e8146d https://blog.coast.ai/lets-evolve-a-neural-network-with-a-genetic-algorithm-codeincluded-8809bece164 W zależności od złożoności eksperymentu: od +1 do +5 punktów.