16 KiB
Wprowadzenie do uczenia ze wzmocnieniem i Q-Learningu
Sketchnote autorstwa Tomomi Imura
Uczenie ze wzmocnieniem opiera się na trzech kluczowych pojęciach: agencie, stanach oraz zestawie akcji dla każdego stanu. Wykonując akcję w określonym stanie, agent otrzymuje nagrodę. Wyobraź sobie grę komputerową Super Mario. Jesteś Mario, znajdujesz się na poziomie gry, stojąc obok krawędzi klifu. Nad tobą jest moneta. Ty, jako Mario, w poziomie gry, w określonej pozycji... to twój stan. Przesunięcie się o krok w prawo (akcja) spowoduje, że spadniesz z klifu, co da ci niską wartość punktową. Jednak naciśnięcie przycisku skoku pozwoli ci zdobyć punkt i pozostać przy życiu. To pozytywny wynik, który powinien nagrodzić cię dodatnią wartością punktową.
Korzystając z uczenia ze wzmocnieniem i symulatora (gry), możesz nauczyć się grać w grę, aby maksymalizować nagrodę, czyli pozostawać przy życiu i zdobywać jak najwięcej punktów.
🎥 Kliknij obrazek powyżej, aby posłuchać Dmitry'ego omawiającego uczenie ze wzmocnieniem
Quiz przed wykładem
Wymagania wstępne i konfiguracja
W tej lekcji będziemy eksperymentować z kodem w Pythonie. Powinieneś być w stanie uruchomić kod z Jupyter Notebook z tej lekcji, zarówno na swoim komputerze, jak i w chmurze.
Możesz otworzyć notebook lekcji i przejść przez tę lekcję, aby ją zbudować.
Uwaga: Jeśli otwierasz ten kod z chmury, musisz również pobrać plik
rlboard.py
, który jest używany w kodzie notebooka. Dodaj go do tego samego katalogu co notebook.
Wprowadzenie
W tej lekcji zagłębimy się w świat Piotrusia i Wilka, inspirowany muzyczną bajką rosyjskiego kompozytora, Siergieja Prokofiewa. Wykorzystamy uczenie ze wzmocnieniem, aby pozwolić Piotrusiowi eksplorować swoje otoczenie, zbierać smaczne jabłka i unikać spotkania z wilkiem.
Uczenie ze wzmocnieniem (RL) to technika uczenia, która pozwala nam nauczyć się optymalnego zachowania agenta w określonym środowisku poprzez przeprowadzanie wielu eksperymentów. Agent w tym środowisku powinien mieć jakiś cel, zdefiniowany przez funkcję nagrody.
Środowisko
Dla uproszczenia, wyobraźmy sobie świat Piotrusia jako kwadratową planszę o rozmiarze szerokość
x wysokość
, jak poniżej:
Każda komórka na tej planszy może być:
- ziemią, po której Piotruś i inne stworzenia mogą chodzić.
- wodą, po której oczywiście nie można chodzić.
- drzewem lub trawą, miejscem, gdzie można odpocząć.
- jabłkiem, które Piotruś chętnie znajdzie, aby się nakarmić.
- wilkiem, który jest niebezpieczny i należy go unikać.
Istnieje osobny moduł Pythona, rlboard.py
, który zawiera kod do pracy z tym środowiskiem. Ponieważ ten kod nie jest istotny dla zrozumienia naszych koncepcji, zaimportujemy moduł i użyjemy go do stworzenia przykładowej planszy (blok kodu 1):
from rlboard import *
width, height = 8,8
m = Board(width,height)
m.randomize(seed=13)
m.plot()
Ten kod powinien wydrukować obraz środowiska podobny do powyższego.
Akcje i polityka
W naszym przykładzie celem Piotrusia będzie znalezienie jabłka, unikając wilka i innych przeszkód. Aby to zrobić, może zasadniczo chodzić po planszy, aż znajdzie jabłko.
Dlatego w dowolnej pozycji może wybrać jedną z następujących akcji: góra, dół, lewo i prawo.
Zdefiniujemy te akcje jako słownik i przypiszemy je do par odpowiadających zmian współrzędnych. Na przykład, ruch w prawo (R
) odpowiada parze (1,0)
. (blok kodu 2):
actions = { "U" : (0,-1), "D" : (0,1), "L" : (-1,0), "R" : (1,0) }
action_idx = { a : i for i,a in enumerate(actions.keys()) }
Podsumowując, strategia i cel tego scenariusza są następujące:
-
Strategia naszego agenta (Piotrusia) jest zdefiniowana przez tzw. politykę. Polityka to funkcja, która zwraca akcję w dowolnym stanie. W naszym przypadku stan problemu jest reprezentowany przez planszę, w tym aktualną pozycję gracza.
-
Cel uczenia ze wzmocnieniem to ostatecznie nauczenie się dobrej polityki, która pozwoli nam efektywnie rozwiązać problem. Jednak jako punkt odniesienia rozważmy najprostszą politykę zwaną losowym spacerem.
Losowy spacer
Najpierw rozwiążmy nasz problem, implementując strategię losowego spaceru. W przypadku losowego spaceru będziemy losowo wybierać następną akcję z dozwolonych akcji, aż dotrzemy do jabłka (blok kodu 3).
-
Zaimplementuj losowy spacer za pomocą poniższego kodu:
def random_policy(m): return random.choice(list(actions)) def walk(m,policy,start_position=None): n = 0 # number of steps # set initial position if start_position: m.human = start_position else: m.random_start() while True: if m.at() == Board.Cell.apple: return n # success! if m.at() in [Board.Cell.wolf, Board.Cell.water]: return -1 # eaten by wolf or drowned while True: a = actions[policy(m)] new_pos = m.move_pos(m.human,a) if m.is_valid(new_pos) and m.at(new_pos)!=Board.Cell.water: m.move(a) # do the actual move break n+=1 walk(m,random_policy)
Wywołanie
walk
powinno zwrócić długość odpowiadającej ścieżki, która może się różnić w zależności od uruchomienia. -
Uruchom eksperyment spaceru kilka razy (np. 100) i wydrukuj wynikowe statystyki (blok kodu 4):
def print_statistics(policy): s,w,n = 0,0,0 for _ in range(100): z = walk(m,policy) if z<0: w+=1 else: s += z n += 1 print(f"Average path length = {s/n}, eaten by wolf: {w} times") print_statistics(random_policy)
Zauważ, że średnia długość ścieżki wynosi około 30-40 kroków, co jest dość dużo, biorąc pod uwagę fakt, że średnia odległość do najbliższego jabłka wynosi około 5-6 kroków.
Możesz również zobaczyć, jak wygląda ruch Piotrusia podczas losowego spaceru:
Funkcja nagrody
Aby nasza polityka była bardziej inteligentna, musimy zrozumieć, które ruchy są "lepsze" od innych. Aby to zrobić, musimy zdefiniować nasz cel.
Cel można zdefiniować w kategoriach funkcji nagrody, która zwróci pewną wartość punktową dla każdego stanu. Im wyższa liczba, tym lepsza funkcja nagrody. (blok kodu 5)
move_reward = -0.1
goal_reward = 10
end_reward = -10
def reward(m,pos=None):
pos = pos or m.human
if not m.is_valid(pos):
return end_reward
x = m.at(pos)
if x==Board.Cell.water or x == Board.Cell.wolf:
return end_reward
if x==Board.Cell.apple:
return goal_reward
return move_reward
Interesującą rzeczą dotyczącą funkcji nagrody jest to, że w większości przypadków otrzymujemy znaczącą nagrodę dopiero na końcu gry. Oznacza to, że nasz algorytm powinien jakoś zapamiętać "dobre" kroki, które prowadzą do pozytywnej nagrody na końcu, i zwiększyć ich znaczenie. Podobnie, wszystkie ruchy prowadzące do złych wyników powinny być zniechęcane.
Q-Learning
Algorytm, który omówimy tutaj, nazywa się Q-Learning. W tym algorytmie polityka jest definiowana przez funkcję (lub strukturę danych) zwaną Q-Tablicą. Rejestruje ona "dobroć" każdej z akcji w danym stanie.
Nazywa się ją Q-Tablicą, ponieważ często wygodnie jest ją reprezentować jako tablicę lub wielowymiarową macierz. Ponieważ nasza plansza ma wymiary szerokość
x wysokość
, możemy reprezentować Q-Tablicę za pomocą tablicy numpy o kształcie szerokość
x wysokość
x len(actions)
: (blok kodu 6)
Q = np.ones((width,height,len(actions)),dtype=np.float)*1.0/len(actions)
Zauważ, że inicjalizujemy wszystkie wartości Q-Tablicy równą wartością, w naszym przypadku - 0.25. Odpowiada to polityce "losowego spaceru", ponieważ wszystkie ruchy w każdym stanie są równie dobre. Możemy przekazać Q-Tablicę do funkcji plot
, aby zwizualizować tablicę na planszy: m.plot(Q)
.
W centrum każdej komórki znajduje się "strzałka", która wskazuje preferowany kierunek ruchu. Ponieważ wszystkie kierunki są równe, wyświetlany jest punkt.
Teraz musimy uruchomić symulację, zbadać nasze środowisko i nauczyć się lepszego rozkładu wartości Q-Tablicy, który pozwoli nam znacznie szybciej znaleźć drogę do jabłka.
Istota Q-Learningu: Równanie Bellmana
Gdy zaczniemy się poruszać, każda akcja będzie miała odpowiadającą jej nagrodę, tj. teoretycznie możemy wybrać następną akcję na podstawie najwyższej natychmiastowej nagrody. Jednak w większości stanów ruch nie osiągnie naszego celu, jakim jest dotarcie do jabłka, i dlatego nie możemy od razu zdecydować, który kierunek jest lepszy.
Pamiętaj, że nie liczy się natychmiastowy wynik, ale raczej ostateczny wynik, który uzyskamy na końcu symulacji.
Aby uwzględnić tę opóźnioną nagrodę, musimy skorzystać z zasad programowania dynamicznego, które pozwalają nam myśleć o naszym problemie w sposób rekurencyjny.
Załóżmy, że teraz znajdujemy się w stanie s, i chcemy przejść do następnego stanu s'. Wykonując to, otrzymamy natychmiastową nagrodę r(s,a), zdefiniowaną przez funkcję nagrody, plus jakąś przyszłą nagrodę. Jeśli założymy, że nasza Q-Tablica poprawnie odzwierciedla "atrakcyjność" każdej akcji, to w stanie s' wybierzemy akcję a, która odpowiada maksymalnej wartości Q(s',a'). Tak więc najlepsza możliwa przyszła nagroda, jaką możemy uzyskać w stanie s, będzie zdefiniowana jako max
Sprawdzanie polityki
Ponieważ Q-Table zawiera "atrakcyjność" każdej akcji w każdym stanie, łatwo jest wykorzystać ją do zdefiniowania efektywnej nawigacji w naszym świecie. W najprostszym przypadku możemy wybrać akcję odpowiadającą najwyższej wartości w Q-Table: (blok kodu 9)
def qpolicy_strict(m):
x,y = m.human
v = probs(Q[x,y])
a = list(actions)[np.argmax(v)]
return a
walk(m,qpolicy_strict)
Jeśli uruchomisz powyższy kod kilka razy, możesz zauważyć, że czasami "zawiesza się" i musisz nacisnąć przycisk STOP w notebooku, aby go przerwać. Dzieje się tak, ponieważ mogą wystąpić sytuacje, w których dwa stany "wskazują" na siebie nawzajem pod względem optymalnej wartości Q-Value, w wyniku czego agent porusza się między tymi stanami w nieskończoność.
🚀Wyzwanie
Zadanie 1: Zmodyfikuj funkcję
walk
, aby ograniczyć maksymalną długość ścieżki do określonej liczby kroków (np. 100) i zobacz, jak powyższy kod czasami zwraca tę wartość.
Zadanie 2: Zmodyfikuj funkcję
walk
, aby nie wracała do miejsc, w których już wcześniej była. Zapobiegnie to zapętleniuwalk
, jednak agent nadal może utknąć w miejscu, z którego nie może się wydostać.
Nawigacja
Lepszą polityką nawigacji byłaby ta, którą stosowaliśmy podczas treningu, łącząca eksploatację i eksplorację. W tej polityce wybieramy każdą akcję z określonym prawdopodobieństwem, proporcjonalnym do wartości w Q-Table. Ta strategia może nadal prowadzić do powrotu agenta do pozycji, którą już eksplorował, ale, jak widać w poniższym kodzie, skutkuje bardzo krótką średnią ścieżką do pożądanej lokalizacji (pamiętaj, że print_statistics
uruchamia symulację 100 razy): (blok kodu 10)
def qpolicy(m):
x,y = m.human
v = probs(Q[x,y])
a = random.choices(list(actions),weights=v)[0]
return a
print_statistics(qpolicy)
Po uruchomieniu tego kodu powinieneś uzyskać znacznie krótszą średnią długość ścieżki niż wcześniej, w zakresie 3-6.
Badanie procesu uczenia
Jak wspomnieliśmy, proces uczenia to balans między eksploracją a wykorzystaniem zdobytej wiedzy o strukturze przestrzeni problemowej. Widzieliśmy, że wyniki uczenia (zdolność do pomocy agentowi w znalezieniu krótkiej ścieżki do celu) poprawiły się, ale interesujące jest również obserwowanie, jak średnia długość ścieżki zachowuje się podczas procesu uczenia:
Podsumowanie wniosków:
-
Średnia długość ścieżki rośnie. Na początku średnia długość ścieżki rośnie. Wynika to prawdopodobnie z faktu, że gdy nic nie wiemy o środowisku, łatwo jest utknąć w złych stanach, takich jak woda czy wilk. Gdy uczymy się więcej i zaczynamy korzystać z tej wiedzy, możemy eksplorować środowisko dłużej, ale nadal nie znamy dobrze lokalizacji jabłek.
-
Długość ścieżki maleje, gdy uczymy się więcej. Gdy nauczymy się wystarczająco dużo, agentowi łatwiej jest osiągnąć cel, a długość ścieżki zaczyna się zmniejszać. Jednak nadal jesteśmy otwarci na eksplorację, więc często odbiegamy od najlepszej ścieżki i eksplorujemy nowe opcje, co wydłuża ścieżkę ponad optymalną.
-
Długość nagle wzrasta. Na wykresie można również zauważyć, że w pewnym momencie długość nagle wzrasta. Wskazuje to na stochastyczny charakter procesu i na to, że w pewnym momencie możemy "zepsuć" współczynniki Q-Table, nadpisując je nowymi wartościami. Idealnie powinno się to minimalizować, zmniejszając współczynnik uczenia (na przykład pod koniec treningu dostosowujemy wartości Q-Table tylko o niewielką wartość).
Ogólnie rzecz biorąc, ważne jest, aby pamiętać, że sukces i jakość procesu uczenia w dużej mierze zależą od parametrów, takich jak współczynnik uczenia, jego zmniejszanie oraz współczynnik dyskontowy. Często nazywa się je hiperparametrami, aby odróżnić je od parametrów, które optymalizujemy podczas treningu (na przykład współczynniki Q-Table). Proces znajdowania najlepszych wartości hiperparametrów nazywa się optymalizacją hiperparametrów i zasługuje na osobny temat.
Quiz po wykładzie
Zadanie
Zastrzeżenie:
Ten dokument został przetłumaczony za pomocą usługi tłumaczeniowej AI Co-op Translator. Chociaż dokładamy wszelkich starań, aby tłumaczenie było precyzyjne, prosimy pamiętać, że automatyczne tłumaczenia mogą zawierać błędy lub nieścisłości. Oryginalny dokument w jego rodzimym języku powinien być uznawany za wiarygodne źródło. W przypadku informacji krytycznych zaleca się skorzystanie z profesjonalnego tłumaczenia wykonanego przez człowieka. Nie ponosimy odpowiedzialności za jakiekolwiek nieporozumienia lub błędne interpretacje wynikające z korzystania z tego tłumaczenia.