Getting started mit Reinforcement Learning und RLlib

Der Einsatz von Reinforcement Learning wird durch neue Software Bibliotheken immer einfacher. Vorgemacht hat dies schon Scikit-Learn für das Supervised Learning. Hierbei ist ein detailliertes Verständnis der einzelnen Algorithmen nicht mehr notwendig. Sobald man die Bibliothek und Supervised Learning verstanden hat, kann man mit aufbereiteten Daten loslegen. Denselben Weg wollen wir in diesem Blogbeitrag beschreiten: Wir werden verstehen, worum es sich bei Reinforcement Learning grundsätzlich handelt und die Werkzeuge für eine Implementierung in Python kennenlernen. Dieser Blogbeitrag wurde zuerst als Talk bei der Big Data Konferenz JOTB in Marbella vorbereitet. Eine ausführlichere Version als Video findet sich hier (JOTB19 – Getting started with Deep Reinforcement Learning by Nicolas Kuhaupt).

Inhalt

Reinforcement Learning – Einleitung

Stellen wir uns zuerst einmal vor, Sie schaffen sich einen kleinen Hund an. Dieser muss natürlich gerade am Anfang erzogen werden, damit er brav bei Fuß geht, nicht in der Wohnung wütet und natürlich diverse Kunststücke meistert. Das typische Vorgehen ist hier, den Hund für gutes Verhalten zu belohnen und für schlechtes entsprechend zu bestrafen. Bei einem gelungenen Kunststück bekommt er ein Leckerli und wenn er bei Fuß geht, wird er anschließend gestreichelt. Dies ist bereits die Crux hinter dem Reinforcement Learning – dem bestärkenden Lernen: Gutes Verhalten wird durch Belohnungen verstärkt, schlechtes Verhalten durch Bestrafungen vermindert. Dadurch überzeugen wir den Hund auf natürliche Weise, sich so zu verhalten, wie wir es gerne möchten.
Beim Reinforcement Learning (RL) sprechen wir von Rewards (die als Belohnungen positiv und als Bestrafungen negativ sind), die ein Agent erhält. Dieser Agent ist nicht mehr ein Hund, sondern der Teil des Reinforcement Learnings, der für das Auswählen von Aktionen zuständig ist. Der Agent handelt jedoch nicht im leeren Raum, sondern die Aktionen beeinflussen seine Umgebung (Environment). Von dieser Umgebung bekommt er zurückgespiegelt, wie der aktuelle Stand (state) der Umgebung ist und den bereits angesprochenen Reward für seine Aktion. Sobald der Agent beides beobachtet hat, wählt er erneut eine Aktion und immer so weiter.


Abbildung 1: Reinforcement Learning

Manche können sich sicher noch die Zeit von zwei dimensionalen Videospielen erinnern und insbesondere an die Atari Spiele mit dem prominenten Beispiel Pac-Man: Ein fressender Kopf wandert durch ein Labyrinth und muss auf seinem Weg Monstern ausweichen und möglichst viele Belohnungen aufsammeln. Aus irgendeinem Grund mochten die RL Forscher diese Spiele sehr gerne und haben sie als Spielwiese für ihre Reinforcement Learning Experimente genutzt (genauer: Für das Deep Reinforcement Learning, das in 2013 erstmals in einem Nature Artikel veröffentlicht wurde (https://www.nature.com/articles/nature14236) und aus eine Kopplung von Deep Learning mit Reinforcement Learning besteht. Diese Kopplung hat den gegenwärtigen Enthusiasmus gegenüber der Methode erst ermöglichst., Wir werden an dieser Stelle auch von Deep Reinforcement Learning sprechen, ohne dass uns die Details kümmern müssen). Bei dem Spiel Pac-Man ist nun also Pac-Man unser Agent. Die Aktionen sind die möglichen Züge des Spiels: nach rechts gehen, unten, oben, links. Der aktuelle Status der Umgebung ist das Bild, das wir in dem Augenblick beobachten. Die Rewards sind schlicht die Punkte, die Pac-Man einsammelt. Reinforcement Learning ist nun der Algorithmus, der die Aktionen von Pac-Man optimiert, um möglichst hohe Rewards zu erhalten.


Abbildung 2: Umgebung Pac-Man

OpenAI Gym

Es werden zwei Teile für eine Implementierung benötigt: (1) Eine Implementierung des Agenten, der Aktionen auswählt und (2) eine Implementierung der Umgebung, die ihren aktuellen Status und die Rewards zurück gibt. Für letzteres haben sich die OpenAI Gyms als Standard durchgesetzt. Diese ermöglichen eine Kopplung mit den meisten Frameworks für Agenten (und insbesondere RLlib, das wir im nächsten Teil nutzen werden).
OpenAI stellt eine Reihe von Gyms zur Verfügung. Darunter befinden sich natürlich die bereits angesprochenen Atari Spiele. Weiterhin gibt es Umgebungen für die Steuerung von Roboter Händen oder zur Steuerung des Bewegungsapparates diverser Figuren (um beispielsweisen Menschen das Laufen beizubringen). Eine Übersicht gibt es hier: https://gym.openai.com/envs/
Jede Umgebung stellt die Gleichen Methoden zur Verfügung. Diese werden wir im folgenden am Beispiel Pac-Man kennenlernen.
Voraussetzung ist natürlich die Installation des Gyms. Die geschieht wie gewohnt mit

pip install 'gym[all]'

In unserem Python Notebook können wir dann das gym importieren:

import gym

Nun können wir unsere Umgebung mit der Methode „make“ erstellen. Als Argument wird hier ein String für die entsprechende Umgebung benötigt. In userem Fall also Pac-Man:

env = gym.make('MsPacman-v0')

Nun können wir loslegen in der Umgebung zu spielen. Wir starten mit der Methode „reset“, um die Umgebung wieder auf Start zu setzen:

env.reset()

Dies liefert uns gleich die erste Beobachtung des Status der Umgebung zurück. Diese Beobachtung gibt uns die Pixel Werte des aktuellen Bildes. Dies ist ein 210x160x3 Array, wobei x3 für die Farb Werte Rot-Gelb-Blau stehen und 210×160 die Höhe und Breite des Bildes sind.
Wir können nun unsere erste Aktion ausführen. Jede Aktion wird durch einen Integer repräsentiert. Die Aktion „nach links bewegen“ wird beispielsweise durch die 0 repräsentiert. Durch den Aufruf der Methode „step“ gehen wir also nach links:

env.step(0)

Daraufhin bekommen wir eine Liste mit vier Informationen zurück. An erster Stelle befindet sich wieder die Beobachtung als 210x160x3 Array. An zweiter Stelle befindet sich der Reward (als float). An dritter Stelle befindet sich ein Boolean. Dieser besitzt den Wert „true“, falls das Spiel verloren ist. Im Spiel Pac-Man ist das der Fall wenn Pac-Man gestorben ist. Sonst befindet sich der Werte auf „false“. An vierter und letzter Stelle befindet sich eine Meta-Information. Diese ist nicht standardisiert und kann für jede Umgebung individuelle Informationen enthalten. Im Spiel Pac-Man wird hier die Anzahl der verbleibenden Leben angegeben.
Falls das Spiel nun vorbei ist (der dritte Wert ist true) rufen wir einfach die reset Methode auf und können von vorne starten. Das Spiel endgültig beenden können wir mit

env.close()

Wir können uns übrigens das aktuelle Bild der Umgebung anzeigen lassen mit:

env.render()

Das Bild muss nach dem Auruf der step Methode immer wieder mit render aktualisiert werden.
Zusätzlich gibt es noch ein paar nützliche Parameter, die uns Informationen über die Umgebung bereit stellen.
Die möglichen Aktionen bekommen wir durch „action_space“ angezeigt.

env.action_space

Hier liefert die Umgebung eine eigene Klasse „discrete“ zurück, die schlicht für die diskreten Werte steht.
Die möglichen Werte für den Reward werden durch „reward_range“ aufgerufen:

env.reward_range

Diese sind in Pac-Man mit -inf bis +inf angegeben und damit nicht eingeschränkt.

Die vorgestellten Methoden und Parameter sind übrigens ausreichend, um eine eigene Umgebung zu implementieren. Hierfür kann man von der Klasse „gym.Env“ erben. Ein einfaches Beispiel findet sich unter https://github.com/ray-project/ray/blob/master/python/ray/rllib/examples/custom_env.py (die Klasse „SimpleCorridor“).

Nachdem wir nun wissen, wie die Umgebungen funktionieren, können wir im nächsten Teil den DRL Agenten auf die Umgebung loslassen.

Ray

Ray ist ein Python Framework, das zunächst einmal für verteiltes Rechnen entwickelt wurde. Unter diesem findet sich Tune (für die Hyperparameter Optimierung) und RLlib, welche uns die DRL Agenten zur Verfügung stellt und daher für uns von Interesse ist. Wie wir noch sehen werden bietet Ray ein paar tolle Features. Legen wir los mit einer minimalen Implementierung. Zuerst importieren wir ray:

import ray
from ray import tune

Ray muss zuerst mit den Rechenressourcen verbunden werden. Dies ist der Prozessor eures Computers, wenn es auf dem lokalen PC läuft. Dies geschieht mit:

ray.init()

Dabei bekommt ihr auch einigeInformationen angezeigt (’node_ip_address‘ etc.), die wir hier nicht weiter benötigen. Nun können wir das Training des DRL Agenten mit run_experiments starten. Hierbei übergeben wir die Beschreibung des Experiments als JSON:

tune.run_experiments({
"Pac-Man": {
"env": "MsPacman-v0",
"run": "DQN",
},
})

An erster Stelle steht der Name des Experiments („Pac-Man“), den wir selbst vergeben können. Im nächsten Einschub bestimmen wir dann die Umgebung („env“: „MsPacman-v0“) und den DRL Algorithmus den wir verwenden („run“: „DQN“). Hierfür stehen mittlerweile auch eine Menge verschiedener Möglichkeiten bereit und auch für Fortgeschrittene ist es nicht einfach zu bestimmen, welcher DRL Algorithmus der geeignetste ist. Grundsätzlich lassen sich Deep Q Networks (DQN) für Umgebungen mit diskreten Aktionen anwenden und bilden einen guten Startpunkt. Für kontinuierliche Aktionen ist Proximal Policy Optimization (PPO) ein guter Ansatzpunkt. Eine gute Übersicht zu den Algorithmen findet sich hier: https://spinningup.openai.com/en/latest/spinningup/rl_intro2.html
Nun zurück zu unserem Code-Beispiel. Unter eurem Benutzerordner sollte nun automatisch ein Ordner „ray_results“ erstellt worden sein. Dort befindet sich wiederum ein Unterordner mit dem Namen eures Experiments. In diesem Fall also „Pac-Man“. Hier befindet sich unter anderem die „progress.csv“, die den Fortschritt des Trainings verfolgt. Interessant ist beispielsweise der erste Wert „episode_reward_max“, der uns angibt, wie viele Rewards maximal gesammelt wurden, bis Pac-Man gestorben ist (das Spiel wurde bereits mehrere Male gespielt). Ebenso interessant ist die durchschnittliche Anzahl an Aktionen, bevor Pac-Man stirbt („episode_len_mean“). Neben dieser Datei ist auch noch die „params.json“ interessant, um einen Überblick über die einzelne Experimente und deren Einstellungen zu behalten.
Ein anderes Feature von Ray sehen wir, wenn wir auf der Kommandozeile das Tensorboard starten:

• tensorboard –logdir=~/ray_results –port 6007

Hier bekommen wir einen Link zurück, den wir in den Webbrowser kopieren können und anschließend die Informationen aus der „progress.csv“ grafisch aufbereitet angezeigt bekommen.
Hiermit haben wir alles, was wir für das Training eines Agenten für die Umgebung brauchen. Allerdings ist es im Moment noch nicht sehr spannend, da wir nicht nachverfolgen können, was der Agent eigentlich tut. Auch dafür hat Ray ein nettes Feature. Wir übergeben in dem JSON, der das Experiment beschreibt den Parameter „monitor“: True.

tune.run_experiments({
"Pac-Man": {
"run": "DQN",
"env": 'Pong-v0',
"config": {
"monitor": True
},
},
})

Damit erhalten wir in dem schon bekannten Ordner „ray_results“ ein Video von Pac-Man. Viel Spaß beim Ansehen!

Über den Autor

Dies ist ein Beitrag von Nicolas Kuhaupt. Nicolas hat Mathematik studiert und sich schon während des Studiums mit dem Themengebiet Big Data und Data Science beschäftigt. Anschließend war er ein Jahr lang als Big Data Consultant tätig und ist als Research Data Scientist zum Fraunhofer IEE gewechselt. Dort treibt er die Digitalisierung der Energiewende voran.

Mehr zum Thema Reinforcement Learning mit Python in unserem Training.