artykuły

Technologia w świecie artystów

10
11 kwietnia 2019, 14:01 Krzysztof Wołk

Jak to działa?

Postaramy się teraz prześledzić użycie zwykłego algorytmu do stworzenia czegoś naprawdę fajnego − poziomów gry wideo, które będą do złudzenia przypominały dzieło człowieka. Do tego celu stworzymy sieć neuronową i dostarczymy danych wejściowych, w tym przypadku już istniejących poziomów gry Mario, i będziemy obserwować, jak będą pojawiać się zupełnie nowe poziomy!

W poprzednich częściach serii stworzyliśmy prosty algorytm, który określał wartość domu zależną od jego cech. Otrzymane dane to:

W rezultacie otrzymaliśmy proste funkcje estymatora (szacunkowe).

Inaczej mówiąc, oszacowaliśmy wartość domu przez pomnożenie każdego atrybutu i nadanie mu innej wagi, a następnie dodanie tych wyników.

Zamiast posługiwać się kodem, zaprezentujmy tę funkcję jako zwykły diagram:

Korzyści z tego algorytmu otrzymamy, rozwiązując proste zależności liniowe z danymi wejściowymi. Załóżmy jednak taką sytuację, w której cena naszego domu wcale nie odzwierciedla prostej zależności. Na przykład może być tak, że dzielnica, w której znajdują się duże i małe domy, ma wpływ na ich cenę, ale gdy weźmiemy pod uwagę średnie domy − już nie ma takiego znaczenia. Jak możemy uchwycić tak skomplikowany szczegół w naszym modelu?

Aby go udoskonalić, możemy posłużyć się tym algorytmem, wielokrotnie nadając danym różne wagi, aby uchwycić różne skrajne przypadki:

Postarajmy się rozwiązać ten problem na cztery różne sposoby.

Teraz mamy cztery różne przybliżone ceny − możemy je uśrednić, aby otrzymać wartość końcową.

Nasza superodpowiedź łączy szacunki z czterech różnych zestawów danych. Takie połączenie danych umożliwia nam modelowanie bardziej złożone od tego, które otrzymalibyśmy w jednym prostym modelu.

Połączmy nasze cztery próby w jeden diagram:

Otrzymaliśmy sieć neuronową! Każde połączenie jest w stanie przyjąć zestaw danych wejściowych, nadać im wagi i wyliczyć dane wyjściowe. Stworzenie połączeń łańcuchowych pozwala w ten sposób tworzyć modele skomplikowanych funkcji.

Pomijam tutaj oczywiście wiele szczegółów, aby nie przedłużać. Najważniejsze jest, aby zrozumieć podstawową koncepcję.

Otrzymaliśmy prostą funkcję szacunkową, która pobiera proste zbiory danych wejściowych i mnoży je przez wagę, aby otrzymać dane wyjściowe. Tę prostą funkcję określimy jako po prostu neuron. Łączenie wielu takich prostych neuronów umożliwia nam modelowanie funkcji, które są zbyt skomplikowane dla pojedynczego modelu. To tak jak klocki Lego! Nie możemy utworzyć modelu z pojedynczego klocka, ale wiele klocków nam to umożliwia.

Stworzona sieć neuronowa zawsze odpowiada w ten sam sposób na te same dane wejściowe. Nie ma ona możliwości zapamiętywania. W terminologii programistów jest to algorytm bezstanowy.

W wielu przypadkach (tak jak w przykładowym wycenianiu domu) jest to pożądane, ale taki model nie jest w stanie uchwycić wzorów danych, które się powtarzają przez dłuższy czas.

Wyobraźmy sobie, że daję ci do dyspozycji klawiaturę z prośbą o napisanie opowiadania. Moim zadaniem jest odgadnięcie, od klawisza której litery zaczniesz opowiadanie.

Do tego celu użyję mojej znajomości polskiego, aby zwiększyć szanse odgadnięcia tej pierwszej litery. Na przykład, mogę określić z dużym prawdopodobieństwem, że użyjesz litery powszechnie używanej w początkach słów. Jeśli posłużę się opowiadaniami, które już stworzyłeś, będę w stanie skoncentrować się na mniejszej liczbie liter, analizując słowa, którymi te opowiadania zaczynasz. Gdy zgromadzę wszystkie dane, mogę zacząć tworzenie modelu sieci neuronowej, która będzie zgadywać prawdopodobieństwo użycia przez ciebie danej litery.

Nasz model może wyglądać tak:

Proponuję, abyśmy skomplikowali nasze zadanie. Powiedzmy, że moim zadaniem nie jest tylko zgadnięcie pierwszej litery, którą posłużysz się w opowiadaniu, ale muszę zgadnąć, jaką następną literą będziesz się posługiwał w obojętnie którym słowie opowiadania. Takie zadanie jest bardziej interesujące.

Użyjmy tutaj dla przykładu pierwszych słów powieści Słońce też wschodzi autorstwa Ernesta Hemingwaya w oryginale:

Robert Cohn was once middleweight boxi...

Jaka jest następna litera urwanego wyrazu?

Prawdopodobnie łatwo zgadłeś, że będzie to „n”, a słowem będzie „boxing”. (Całe zdanie brzmi: Robert Cohn was once middleweight boxing champion of Princeton − „Robert Cohn był kiedyś mistrzem boksu wagi średniej w Princeton”).

Nie jest problemem zgadnięcie litery, która się pojawi w tym słowie, gdy bierzemy pod uwagę sekwencję liter, które wystąpiły po i przed oraz naszą znajomość języka angielskiego.

Jeśli chodzi o sieć neuronową, musimy dodać stan do naszego modelu. Za każdym razem, gdy skierujemy do sieci pytanie, będziemy zapisywać zbiór naszych obliczeń poszczególnych etapów przejściowych i będziemy mogli posłużyć się nimi ponownie jako częścią naszych danych wejściowych. W ten sposób nasz tworzony model będzie dostosowywał swoje przewidywania, biorąc pod uwagę poprzednie wyniki.

Kontrolowanie stanu w modelu umożliwia nie tylko przewidywanie prawdopodobieństwa wystąpienia pierwszej litery, ale pozwala także przewidzieć następne litery na podstawie poprzednich liter.

Powyższy przykład pozwolił nam przybliżyć sobie podstawową zasadę rekurencyjnych sieci neuronowych (z ang. Recurrent Neural Network, RNN). Za każdym razem, gdy używamy tego rodzaju sieci, jest ona uaktualniana. Umożliwia to dysponowanie najbardziej aktualnymi danymi, które będzie ona stosować do przewidywania. Sieć ta będzie w stanie tworzyć modelowanie przewidywalnych powtarzających się schematów, gdy będzie dysponować odpowiednim zasobem pamięci.

Przewidywanie następnej litery w tekście może wydawać się bezużyteczne. Nasuwa się pytanie: o co tutaj chodzi? Tymczasem jednym z zastosowań takich przewidywań są na przykład automatyczne podpowiedzi podczas pisania na klawiaturze telefonu.

A co się stanie, jeśli posuniemy się jeszcze dalej i zaplanujemy, że zadamy naszej sieci przewidywanie liter w nieskończoność, tak aby stworzyła dla nas całe opowiadanie?

Algorytm będzie w stanie wykryć powtarzające się wzory w różnorakich sekwencjach danych. Dla przykładu możemy wygenerować wyglądające jak prawdziwe przemówienia prezydenta Obamy czy przepisy kulinarne. Ale nie musimy się ograniczać do języka ludzkiego. Możemy zastosować tę samą koncepcję do każdego rodzaju danych z sekwencją, która wytwarza jakiś powtarzalny wzorzec.

Zastanówmy się teraz, czy możemy zastosować ten sam model uczący, aby stworzyć poziomy gry Mario o urozmaiconym terenie, wykorzystując do uczenia poziomy gry z 1985 roku. Ta gra ma 32 poziomy, a 70 procent tych poziomów rozgrywa się na takim samym terenie.

Oto pierwszy poziom gry (może go jeszcze pamiętasz, jeśli w nią grałeś):

Po bliższym przyjrzeniu się możemy zauważyć, że poziomy składają się z prostych siatek obiektów:

Możemy z dużą łatwością przetworzyć go na sekwencję znaków, przy czym jeden znak reprezentuje jeden obiekt:

Dokonaliśmy wymiany każdego obiektu w poziomie na znak.

„-” jako powierzchnia

„=” to powierzchnia całego klocka

„#” jako łamiąca się cegła

„?” jest elementem monety... i tak dalej dopasowujemy do każdego elementu na tym poziomie inny znak.

W rezultacie uzyskaliśmy pliki tekstowe − oto one:

Patrząc na poszczególne linie, nie widzimy żadnego zdefiniowanego wzoru. Wiele linii jest pustych.

Wzór na tym poziomie pojawi się, gdy będziemy patrzeć na dany poziom nie jako na rzędy, ale jako na serię pionowych kolumn:

Analizując kolumnę za kolumną, otrzymamy powtarzający się wzór. Na przykład widzimy, że każda kolumna kończy się znakiem „=”.

Dla odwzorowania tych danych przez algorytm uczący musimy dostarczać danych w układzie kolumnowym. Znalezienie najlepszego sposobu reprezentacji danych wejściowych jest jednym z kluczowych zadań w posługiwaniu się algorytmami uczenia maszynowego.

Aby nauczyć ten model, musiałem więc dokonać obrotu mojego tekstu o 90 stopni, co umożliwiło mi stworzenie danych wejściowych do modelu RNN, które w bardziej wyraźny sposób reprezentowały pojawiające się prawidłowości.

Model ulega doskonaleniu się w miarę nauki, stąd w początkowej fazie uczenia sieci nasz model jest zaśmiecony niezrozumiałymi danymi:

Na początku widzimy, że model ma tylko pojęcie o zwiększonej częstotliwości znaków „-” oraz „=”, nie wykrywa konkretnych prawidłowości. Dopiero po kilkunastu tysiącach powtórzeń nasz poziom zaczyna wyglądać następująco:

Tutaj model po wielu powtórzeniach prawie wykrył, że każda linia powinna być takiej samej długości. Widzimy nawet, że odkrywa logikę gry Mario. Rurki w Mario mają zawsze szerokość dwóch cegiełek i co najmniej dwóch cegiełek wysokość. Dane „PP” pojawiają się skupione w częściach 2x2. To jest naprawdę niezłe! Powtarzając algorytm uczący, widzimy, że model dochodzi do poziomu generowania prawidłowych danych.

Prześledźmy dane z całego poziomu, które wytworzył nasz model RNN, i obróćmy je z powrotem w horyzontalne rzędy:

Te dane wyglądają niesamowicie. Możemy tutaj zauważyć kilka niezwykłych prawidłowości:

  • Model umieścił Lakitu (potworka, który lata w chmurach) na niebie, zupełnie tak jak jest w pierwszym poziomie prawdziwej gry Mario.
  • Model przewidział, że unoszące się w powietrzu rurki muszą opierać się na klockach, nie mogą unosić się po prostu w powietrzu.
  • Umieścił wrogów w logicznych punktach.
  • Nie umieścił żadnych przeszkód dla gracza, które utrudniałyby mu posuwanie się naprzód.
4