Reinforcement Learning#

The way positive reinforcement is carried out is more important than the amount.

— Burrhus Frederic Skinner

Reinforcement Learning (RL) ist ein Bereich des maschinellen Lernens, bei dem ein Agent lernt, in einer Umgebung durch Interaktionen zu handeln, um eine maximale Belohnung zu erzielen. Der Agent nimmt Aktionen basierend auf seinem aktuellen Zustand vor und erhält Belohnungen oder Bestrafungen, die er verwendet, um seine Strategie zu verbessern.

Während Supervised Learning aus Beispielen lernt („Was ist das?“), lernt Reinforcement Learning durch Versuch und Irrtum („Was soll ich tun?“), indem es für gute Entscheidungen belohnt wird. Es benötigt keine gelabelten Daten, sondern eine Umgebung und Rückmeldung (Reward).

RL eignet sich besonders für Aufgaben, bei denen ein Agent eigenständig durch Interaktion mit einer Umgebung lernen muss, optimale Entscheidungen zu treffen. Typische Einsatzgebiete sind dynamische Steuerungs- und Optimierungsprobleme, bei denen klassische regelbasierte Ansätze an ihre Grenzen stoßen. RL wird häufig verwendet, wenn die Umgebung komplex, unsicher oder sich verändernd ist und keine gelabelten Trainingsdaten vorliegen.

Beispiele im Bauwesen sind: Ein Heiz-/Kühlsystem in Gebäuden lernt, abhängig von Wetter und Nutzung, effizient zu regeln. Beim 3D-Druck von Betonelementen kann RL den Materialeinsatz optimieren. Ein autonomer Bagger lernt, wie er Erdbewegungen möglichst ressourcenschonend und schnell ausführt.

Das macht auf der einen Seite RL vom Konzept her einfach zu verstehen und zu implementieren. Die Spezifikation der Belohnungsstrategie ist allerdings meist schwierig, da dies Verständnis des Problems erfordert. Der Ansatz benötigt keine gelabelte Trainingsdaten, ist also kein Überwachter ML-Ansatz. Er ist auch kein unüberwachter Ansatz, da ja durchaus Wissen in Form der Belohnungsstrategie notwendig ist.

RL eignet sich insbesondere für dynamische Entscheidungen und wird häufig in der Robotik verwendet, aber wird auch immer beliebter in traditionellen Gebieten.

Folien#

Grundlegende Konzepte#

Reinforcement Learning basiert auf einer iterativen Lernstrategie, bei der die Qualität der Lösung durch positives oder negatives Feedback bewertet wird. Das RL-Modell wird hierbei als Agent gesehen, der mit der Umgebung interagiert. Die Begriffe in diesem Kontext sind:

  • Agent (Agent): Der Lernende oder Entscheidungsträger, der in der Umgebung agiert.

  • Umgebung (Environment): Alles, mit dem der Agent interagiert.

  • Aktion (Action): Eine Entscheidung oder Bewegung, die der Agent treffen kann.

  • Belohnung (Reward): Rückmeldung aus der Umgebung, die angibt, wie gut eine Aktion im gegebenen Zustand war.

  • Wertfunktion (Value Function): Eine Funktion, die angibt, wie gut ein bestimmter Zustand oder eine Aktion ist.

  • Strategie (Policy): Eine Strategie, die der Agent verwendet, um Aktionen zu wählen basierend auf dem Zustand und der Wertfunktion.

  • Zustand (State): Eine Repräsentation der aktuellen Situation der Umgebung und ggf. der Strategie.

Komponenten und Begriffe beim Reinforcement Learning

Zur Modellierung der Umgebung und aktuellen Strategie verwendet man im RL oft ein Markov Decision Processes (MDPs). Das ist ein diskretes Zustandsmodell, bei dem das System sich immer nur in einem einzigen Zustand befinden kann, der die Umgebung (oder Strategie) widerspiegelt und die Aktion bestimmt. Jeder Zustand beschreibt eine bestimmte Bedingung oder Position im Verhalten des Systems. Der Zustand wird auf Basis der Belohnung von der Umgebung gewechselt.

Markov-Modelle sind eine sehr beliebte Modelltyp im Maschinellem Lernen. Sie basieren alle auf der Markov-Bedingung, dass die Übergangswahrscheinlichkeit von einem Zustand in den anderen nur von dem aktuellen Zustand abhängt und nicht vorhergehenden Zuständen (es gibt also keine Autokorrelation). Man spricht dabei auch von der Gedächtnislosigkeit. Dies basiert auf der Idee, dass der Zustand des Systems zu einem bestimmten Zeitpunkt alle Informationen enthält, die notwendig sind, um sein zukünftiges Verhalten vorherzusagen.

Ein MDP besteht aus:

  • \( S \): Menge aller möglichen Zustände.

  • \( A \): Menge aller möglichen Aktionen.

  • \( P(s'|s, a) \): Übergangswahrscheinlichkeit vom Zustand \( s \) zum Zustand \( s' \) bei Aktion \( a \).

  • \( R(s, a) \): Belohnungsfunktion, die die Belohnung angibt, die der Agent erhält, wenn er im Zustand \( s \) die Aktion \( a \) ausführt.

  • \( \gamma \): Diskontierungsfaktor, der zukünftige Belohnungen abwertet.

Beispiel eines MDP Zustandsdiagrams mit drei Zuständen

Der MDP modelliert also die Zustände und die bisherige Übergangswahrscheinlichkeit sowie die erhaltenen Belohnungen. Die entscheidende Frage im Reinforcement Learning ist nun, wie man daraus die beste Aktion und somit den nächsten Zustand ableiten kann. Hierbei gibt es das Dilemma, das aufgrund der Markov-Bedingung das Modell zwar einfach ist, aber wir auch gedächtnislos sind, wir also nicht die Historie der Zustände mit in unserer Entscheidung betrachten können. Das ist problematisch, wenn das Ziel nur erreicht werden kann, wenn eine bestimmte Zustandsfolge eintritt, wie in dem Gridworld-Beispiel unten diskutiert.

Eine wichtige Gleichung ist hierfür die Bellman-Gleichung. Sie beschreibt die Beziehung zwischen dem aktuellen Zustands-Aktionspaar \((s,a)\), der beobachteten Belohnung und den möglichen Nachfolge-Zustands-Aktionspaaren \(s',a'\). Diese Beziehung wird verwendet, um die optimale Wertfunktion zu finden.

Die Bellman-Gleichung basiert auf dem Prinzip der Optimalität, das besagt, dass ein optimaler Policy eine rekursive Struktur aufweist. Das bedeutet, dass der Wert eines Zustands unter einer optimalen Policy aus der sofortigen Belohnung und dem diskontierten Wert der zukünftigen Zustände besteht. Die Bellman-Gleichung für die Wertfunktion \(V(s)\) eines Zustands \(s\) definiert den Wert eines Zustands als die erwartete Belohnung für die beste Aktion in diesem Zustand plus den diskontierten Wert des besten nächsten Zustands, unter der Annahme, dass der Agent die optimale Policy verfolgt. Mathematisch formuliert lautet die Bellman-Optimalitätsgleichung:

\[ V(s) = \max_a \left( R(s, a) + \gamma \sum_{s'} P(s'|s, a) V(s') \right) \]

Diese Gleichung besagt, dass der optimale Wert eines Zustands \(s\) die maximale erwartete Belohnung ist, die der Agent erhalten kann, wenn er im Zustand \(s\) startet, die Aktion \(a\) wählt und danach der optimalen Policy folgt. Dadurch berücksichtigen wir bei der Bewertung des Zustandsüberganges durch die Aktion \(a\) nicht nur den aktuellen Zustand, sondern auch die durch Aktion \(a\) ermöglichten zukünftigen Zustandsübergänge. Damit lösen wir das Dilemma der Gedächtnislosigkeit, das aus der Markov-Bedingung folgt.

Die Bellman-Gleichung wird genutzt, um die optimale Strategie in Form einer Q-Funktion \(Q(s,a)\) im Q-Learning zu erlernen. Das ist ein populärer Off-Policy-Algorithmus, bei dem der Agent eine Q-Funktion \( Q(s, a) \) lernt, welche Belohnungen einer Aktion \( a \) in einem Zustand \( s \) erwartet wird.

\[ Q(s, a) \leftarrow Q(s, a) + \alpha \left( R(s, a) + \gamma \max_{a'} Q(s', a') - Q(s, a) \right) \]

hierbei stellt \(\alpha\) die Lernrate dar. Die ist ein sehr wichtiger Parameter, da er entscheidet, wie schnell das Modell auf eine Lösung konvergiert. Ein niedriger Wert sorgt dazu, dass der Algorithmus mehr positive Beispiele zum Lernen benötigt, was also längeres Training erfordert, insbesondere bei seltenen, positiven Feedback. Ein hoher Wert kann dazu sorgen, dass sich das Modell schnell in einer schlechteren Lösung (lokales Optimum) verrennt.

Deshalb kombiniert man den Algorithmus man meist mit einer Erkundungsstrategie. Statt immer nur die geschätzte optimale Strategie \(Q(s,a)\) zu nehmen, wählt man mit der Wahrscheinlichkeit \(\epsilon\) zufällige Strategien aus, um somit alternative Strategien zu entdecken. Ein hoher \(\epsilon\)-Wert sorgt für eine hohe Erkundungsrate (Exploration), während ein niedriger ein verlässlichere Vorhersage erlaubt. Um hier einen Trade-Off zu finden, macht man oft beides und wählt am Anfang des Trainings ein hohes \(\epsilon\), um den Lösungsbereich zu erkunden und am Ende des Trainings ein niedriges \(\epsilon\) um schneller und verlässlicher zu konvergieren.

Beispiel: Gridworld-Umgebung#

Wir werden eine einfache Gridworld-Umgebung verwenden, um die Grundprinzipien von Reinforcement Learning zu veranschaulichen. In dieser Umgebung versucht ein Agent, in einem Grid von einem Startzustand zu einem Zielzustand zu gelangen und dabei den kürzesten Weg zu finden.

Dies kann zum Beispiel zur Steuerung eines Materialtransports auf einer Baustelle dienen. Ein Agent (z.B. ein Bagger) befindet sich auf einer vereinfachten Baustelle und muss Material von einer Quelle zu einem Zielbereich bringen. Wir simulieren dieses Szenario in einer Gitterwelt mit folgenden Regeln:

  • Der Agent kann sich hoch, runter, links, rechts bewegen.

  • Er erhält +10 Punkte, wenn er das Ziel (Z) erreicht.

  • Für jeden Schritt wird er mit -1 Punkt bestraft.

  • Die Umgebung ist sehr einfach und zufallsbasiert.

Wir erstellen uns als erstes eine Klasse, welche die Umgebung repräsentiert und die Zustände enthält. Die Umgebung initialisiert zuerst unser Raster (Grid) mit der Start- und Endposition. Dieses Raster definiert unseren Zustandsraum, da der Agent, wenn er sich fort bewegt sich in jeder dieser Zellen im Raster aufhalten kann.

Damit wir Experimente beim Lernen wiederholen können gibt es eine reset-Funktion. Mit der step-Funktion kann der Agent sich fortbewegen. Wir übergeben der Funktion als action die Richtung, in der wir uns bewegen wollen. Dadurch wechselt sich die aktuelle Position, also der aktuelle Zustand in unserem Zustandsmodell.

import numpy as np # Import von NumPy

# Definition der Gridworld-Umgebung
class Gridworld:
    def __init__(self, size, start, goal):
        self.size = size
        self.start = start
        self.goal = goal
        self.state = start
        self.actions = ['up', 'down', 'left', 'right']
        self.grid=np.zeros((self.size,self.size))
        self.grid[start]=1
        self.grid[goal]=0

    def reset(self):
        self.state = self.start
        return self.state

    def step(self, action):
        x, y = self.state
        if action == 'up':
            x = max(0, x - 1)
        elif action == 'down':
            x = min(self.size - 1, x + 1)
        elif action == 'left':
            y = max(0, y - 1)
        elif action == 'right':
            y = min(self.size - 1, y + 1)
        self.state = (x, y)
        self.grid[self.state]+=1
        reward = 1 if self.state == self.goal else -0.1
        done = self.state == self.goal
        return self.state, reward, done

Erstellen wir uns als Beispiel ein 3x3 Gridworld und wollen uns vom Punkt \([0,0]\) zum Punkt \([3,3]\) bewegen.

env = Gridworld(size=4, start=(0, 0), goal=(3, 3))
state = env.reset()
print("Startzustand:", state)
env.grid
Startzustand: (0, 0)
array([[1., 0., 0., 0.],
       [0., 0., 0., 0.],
       [0., 0., 0., 0.],
       [0., 0., 0., 0.]])

Der Zustandsraum ist hierbei ein 2D Grid

Zustandsraum von Gridworld

Bewegen wir uns einmal manuell durch das Grid, so sehen wir wie die Zustände (unsere Position) sich ändern und welche Belohnungen wir erhalten. Wichtig hierbei ist, dass wir eine positive Belohnung erst beim letzten Schritt erhalten, vorher immer nur bestraft werden (z.B. Erschöpfung).

# Beispiel für einen Schritt in der Umgebung
steps=['right','down','right','down','right','down']
for step in steps:
    next_state, reward, done = env.step(step)
    print("Nächster Zustand:", next_state, "Belohnung:", reward, "Ziel erreicht:", done)
Nächster Zustand: (0, 1) Belohnung: -0.1 Ziel erreicht: False
Nächster Zustand: (1, 1) Belohnung: -0.1 Ziel erreicht: False
Nächster Zustand: (1, 2) Belohnung: -0.1 Ziel erreicht: False
Nächster Zustand: (2, 2) Belohnung: -0.1 Ziel erreicht: False
Nächster Zustand: (2, 3) Belohnung: -0.1 Ziel erreicht: False
Nächster Zustand: (3, 3) Belohnung: 1 Ziel erreicht: True
import plotly.express as px
px.imshow(env.grid)
/opt/hostedtoolcache/Python/3.11.0/x64/lib/python3.11/site-packages/kaleido/__init__.py:14: UserWarning:



Warning: You have Plotly version 5.24.1, which is not compatible with this version of Kaleido (1.0.0).

This means that static image generation (e.g. `fig.write_image()`) will not work.

Please upgrade Plotly to version 6.1.1 or greater, or downgrade Kaleido to version 0.2.1.

Random Walk#

Ein naiver Lösungsansatz ist der so genannte Random Walk (Zufallsweg), bei dem man bei jedem Schritt in eine zufällig gewählte Richtung läuft, was auch bedeutet, dass man ggf. zurück läuft. Untersuchen wir einmal in 500 Experimenten die Erfolgschancen dieser Lösungsstrategie.

env = Gridworld(size=4, start=(0, 0), goal=(3, 3))
trials = 500

rewards = []
retries = []
for trial in range(trials):
    env.reset()
    n, r_sum = 0, 0
    done = False
    while not done:
        # Wir wählen eine zufällige Aktion
        action = np.random.choice(env.actions)
        # Führe die Aktion aus und erhalte von der Umgebung den neuen Zustand und die Belohnung
        new_state, reward, done = env.step(action)
        # Sammel Erfolgsstatistik
        r_sum += reward
        n += 1
    rewards.append(r_sum)
    retries.append(n)

In allen Experimenten ist unser Agent zum Ziel gekommen, was zeigt, dass diese zufällige Explorations-Strategie durchaus erfolgreich ist. Schauen wir auf die Anzahl der Versuche bis zum Ziel über unsere Experimente, so sehen wir, dass diese gleichbleibend stark variiert. Wir haben also kein Lerneffekt.

import plotly.express as px
px.line(retries)

Entsprechend niedrig sind auch die Belohnungen, die das Programm pro Experiment sammelt.

px.line(rewards)

Wenn wir uns die Häufigkeit ansehen, der besuchten Rasterpunkte, so zeigt sich, dass der Großteil der Versuche um dem Starpunkt herum hängen bleibt, also oft auch wieder sich zurückbewegt.

px.imshow(env.grid)

Q-Learning#

Wir implementieren als nächstes den Q-Learning Ansatz wie er oben beschrieben worden ist. Hierfür initialisieren wir als erstes die Q-Matrix, in welcher wir die erlernten Q-Werte speichern. Da unser Zustandsraum die Große 3x3 hat und wir 4 Aktionen haben, hat die Q-Matrix eine Größe von 3x3x4.

Der Q-Learning Algorithmus besteht nun zum einen in der Möglichkeit mit der Wahrscheinlichkeit \(\epsilon\) ein explorativen zufälligen Schritt zu machen oder den vielversprechendsten Schritt unseres aktuellen Zustands \(s_1,s_2\) mit dem maximalen Q-Wert (argmax).

env = Gridworld(size=4, start=(0, 0), goal=(3, 3))

# Initialisiere Q-Werte
Q = np.zeros((env.size, env.size, len(env.actions)))

# Hyperparameter
alpha = 0.2  # Lernrate
gamma = 0.9  # Diskontierungsfaktor
epsilon = 0.1  # Epsilon für die Epsilon-Greedy-Strategie

rewardsQ=[]
retriesQ=[]
for trial in range(trials):
    state = env.reset()
    done = False
    n, r_sum=0, 0
    while not done:
        if np.random.uniform(0,1) < epsilon:
            # Wähle Aktion entweder Zufällig aus
            action = np.random.randint(0, len(env.actions))
        else:
            # Wähle Aktion mit maximaler erwarteten Belohnung Q(s)
            action = np.argmax(Q[state[0], state[1], :])
        # Führe die Aktion aus und erhalte von der Umgebung den neuen Zustand und die Belohnung
        new_state, reward, done = env.step(env.actions[action])
        # Aktualisiere Q-Werte auf Basis der erhaltenen Belohnung
        Q[state[0], state[1], action] += alpha * (reward + gamma * np.max(Q[new_state[0], new_state[1], :]) - Q[state[0], state[1], action])
        # Aktualisiere den Zustand
        state = new_state
        # Sammel Erfolgsstatistik
        r_sum+=reward
        n += 1
    rewardsQ.append(r_sum)
    retriesQ.append(n)

Wenn wir nun die Wiederholungsversuche uns ansehen, dann sehen wir, dass diese schnell auf das Minimum von nur 6 Schritte abfallen. Es gibt immer noch Schwankungen, die an dem Zufallsanteil \(\epsilon\) liegen.

px.line(retriesQ)

Betrachten wir die Belohnungen, so sehen wir, dass der Ansatz sehr schnell nur noch positive Belohnungen erzieht.

px.line(rewardsQ)

Der resultierende Weg ist hierbei einer von vielen optimalen Wegen.

px.imshow(env.grid)

Zeitreihe#

Mit Reinforcement Learning kann man auch Modelle auf Zeitreihen trainieren. Wir wollen uns zum Beispiel einen Entscheidungsalgorithmus trainieren, welcher lernt, wann er Aktien oder Shorts auf diese kaufen soll und wann er sie verkaufen soll. Hier ein zufälliger Aktienkurs.

import numpy as np
import pandas as pd

# Simulierte Zeitreihe (z.B. Aktienpreise)
np.random.seed(42)
data = np.cumsum(np.random.randn(200) + 0.5)

# Daten visualisieren
px.line(data)

Wir definieren uns wieder eine Trainingsumgebung. Dieses Mal haben wir allerdings die Schwierigkeit, dass der Wert kontinuierlich sind und nicht diskret, wir also keine natürliche Zustandsraumdarstellung haben. Deshalb müssen wir die kontinuierliche Zeitreihe des Aktienwertes zuerst diskretisieren.

class StockTradingEnv:
    def __init__(self, data, num_states=40):
        self.data = data
        self.current_step = 0
        self.state = None
        self.states = np.linspace(min(data), max(data), num_states)
        self.done = False
        self.actions = ['kaufen', 'verkauf']
        self.position = 0  # 1 = long, -1 = short, 0 = neutral
        self.balance = 0
        self.current_price=0
        self.old_price = 0

    def discretize(self):
        return np.digitize(self.data[self.current_step], self.states) - 1

    def reset(self):
        self.current_step = 0
        #self.state = self.data[self.current_step:self.current_step+5]
        self.state = self.discretize()
        self.done = False
        self.position = 0
        self.balance = 0
        self.old_price = 0
        self.current_price=0
        return self.state

    def step(self, action):
        self.current_step += 1
        if self.current_step > len(self.data) - 2:
            self.done = True
        self.current_price = self.data[self.current_step]
        reward = 0
        if action == 0:  # Kaufen
            if self.position == 0: # Kaufe Aktie
                self.position = 1
                self.balance -= self.current_price
                self.old_price = self.current_price
            elif self.position == -1: # Verkaufe Short
                reward = 2 * (self.old_price - self.current_price)
                self.position = 0
                self.balance += self.old_price
        elif action == 1:  # Verkaufen
            if self.position == 0: # Kaufe Short
                self.position = -1
                self.balance += self.current_price
                self.old_price = self.current_price
            elif self.position == 1: # Verkaufe Aktie
                reward = 2 * (self.current_price - self.old_price)
                self.position = 0
                self.balance -= self.old_price
        elif action == 2:  # Halten
            pass
        self.state = self.discretize()
        return self.state, reward, self.done

Als Beispiel kaufen wir Akten, halten sie für 6 Züge und verkaufen sie. Dann kaufen wir Shorts, um auf fallende Aktien zu wetten, halten sie wieder und verkaufen sie.

num_states=40

# Beispiel für die Erstellung und Verwendung der StockTradingEnv-Umgebung
env = StockTradingEnv(data, num_states)
state = env.reset()
print("Startzustand:", state)

# Beispiel für einen Schritt in der Umgebung
for action in [0,2,2,2,2,2,1,1,2,2,2,2,2,0]:#  0 = Kaufen, 2 = Halten, 1 = Verkaufen
    next_state, reward, done = env.step(action)  
    print("Nächster Zustand:", next_state, "Belohnung:", reward, "Ziel erreicht:", done)
Startzustand: 0
Nächster Zustand: 0 Belohnung: 0 Ziel erreicht: False
Nächster Zustand: 0 Belohnung: 0 Ziel erreicht: False
Nächster Zustand: 1 Belohnung: 0 Ziel erreicht: False
Nächster Zustand: 1 Belohnung: 0 Ziel erreicht: False
Nächster Zustand: 1 Belohnung: 0 Ziel erreicht: False
Nächster Zustand: 2 Belohnung: 0 Ziel erreicht: False
Nächster Zustand: 3 Belohnung: 14.098151214993003 Ziel erreicht: False
Nächster Zustand: 3 Belohnung: 0 Ziel erreicht: False
Nächster Zustand: 3 Belohnung: 0 Ziel erreicht: False
Nächster Zustand: 3 Belohnung: 0 Ziel erreicht: False
Nächster Zustand: 3 Belohnung: 0 Ziel erreicht: False
Nächster Zustand: 3 Belohnung: 0 Ziel erreicht: False
Nächster Zustand: 3 Belohnung: 0 Ziel erreicht: False
Nächster Zustand: 2 Belohnung: 1.565646416803098 Ziel erreicht: False

Als nächstes implementieren wir wieder den Q-Learning Algorithmus. Der ist fast identisch zu dem Gridworld-Beispiel. Unterschiede gibt es nur, da wir nur eine zweidimensionale Q-Matrix haben, statt einer dreidimensionalen, da der Zustandsraum weniger Dimensionen hat.

import time

# Beispiel für die Erstellung und Verwendung der StockTradingEnv-Umgebung
tic=time.time()
env = StockTradingEnv(data, num_states)

# Initialisiere Q-Werte
Q = np.zeros((num_states, 3))

# Hyperparameter
alpha = 0.1  # Lernrate
gamma = 0.9  # Diskontierungsfaktor
epsilon = 0.1  # Epsilon für die Epsilon-Greedy-Strategie

# Training des Q-Learning-Agenten
for episode in range(1000):
    state = env.reset()
    done = False
    actions = []
    rewards = []
    total_reward = 0
    
    while not done:
        if np.random.random() < epsilon:
            # Wähle Aktion entweder Zufällig aus
            action = np.random.randint(0, len(env.actions))
        else:
            # Wähle Aktion mit maximaler erwarteten Belohnung Q(s)
            action = np.argmax(Q[env.state])
        # Führe die Aktion aus und erhalte von der Umgebung den neuen Zustand und die Belohnung
        new_state, reward, done = env.step(action)
        new_state_idx = env.current_step
        # Aktualisiere Q-Werte auf Basis der erhaltenen Belohnung
        Q[state, action] += alpha * (reward + gamma * np.max(Q[new_state]) - Q[state, action])
        # Aktualisiere den Zustand
        state = new_state
        # Sammel Erfolgsstatistik
        actions.append(action)
        total_reward+=-reward
        rewards.append(total_reward)
print(f"Execution Time: {time.time()-tic}")
Execution Time: 2.4238998889923096

Auch hier zeigt sich, dass der Q-Learning Algorithmus erfolgreich lernt, wann er Aktien kaufen, verkaufen oder shorten soll, so dass er am Ende Gewinn macht.

px.line(rewards)

Hierbei macht der Algorithmus gar nicht so viele Transaktionen, wie er könnte, um das Ergebnis zu maximieren.

px.line(actions)

RL Frameworks#

RL gehört nicht zu den traditionellen ML-Verfahren und ist deshalb auch nicht in SciKit-Learn oder Statsmodels enthalten. Das liegt unter anderem daran, dass man beim RL nicht einfach ein Modell auf einem Datensatz trainiert, wie es von der Standard-API von SciKit-Learn erwartet wird, sondern manuell ein Umgebungsmodell erstellen muss.

Mit der wachsenden Popularität von RL in den letzten Jahren haben sich aber spezielle Bibliotheken für RL entwickelt, die dies vereinfachen und das Lösen komplexer Probleme vereinfachen. Eine Bibliothek dafür ist Gymnasium oder kurz Gym von OpenAI. Die Gym-Bibliothek ist eine Open-Source-Bibliothek, die speziell für die Entwicklung und das Testen von RL-Algorithmen entwickelt wurde. Sie bietet eine standardisierte API und eine Vielzahl von vordefinierten Umgebungen, die es ermöglichen RL-Algorithmen effizient zu implementieren und zu evaluieren. Hierbei spielt insbesondere die Definition der Umgebung eine wichtige Rolle.

Das Standard-Interface in Gym für eine Umgebung für den Börsenfall ist mehr oder weniger identisch mit der Klasse, die wir oben definiert haben.

import gymnasium as gym
from gymnasium import spaces
import numpy as np
import pandas as pd

class StockTradingEnvGym(gym.Env):
    def __init__(self, data):
        super(StockTradingEnvGym, self).__init__()
        self.data = data.copy()
        self.current_step = 0
        self.balance = 10000  # Startguthaben
        self.position = 0  # 0: neutral, 1: long, -1: short
        self.old_price = 0
        self.current_price = self.data[self.current_step]
        # Actions: 0 = Kaufen, 1 = Verkaufen, 2 = Halten
        self.action_space = spaces.Discrete(3)
        # Observation space: [current price, position, balance]
        self.observation_space = spaces.Box(
            low=np.array([-np.inf, -1, -np.inf]), 
            high=np.array([np.inf, 1, np.inf]), 
            dtype=np.float32
        )

    def reset(self, seed=0, options=None):
        self.current_step = 0
        self.balance = 10000
        self.position = 0
        self.current_price = self.data[self.current_step]
        return self._get_obs(), {}

    def _get_obs(self):
        return np.array([self.current_price, self.position, self.balance])

    def step(self, action):
        prev_price = self.current_price
        self.current_step += 1
        self.current_price = self.data[self.current_step]
        reward = 0
        if action == 0:  # Kaufen
            if self.position == 0: # Kaufe Aktie
                self.position = 1
                self.balance -= self.current_price
                self.old_price = self.current_price
            elif self.position == -1: # Verkaufe Short
                reward = 2 * (self.old_price - self.current_price)
                self.position = 0
                self.balance += self.old_price
        elif action == 1:  # Verkaufen
            if self.position == 0: # Kaufe Short
                self.position = -1
                self.balance += self.current_price
                self.old_price = self.current_price
            elif self.position == 1: # Verkaufe Aktie
                reward = 2 * (self.current_price - self.old_price)
                self.position = 0
                self.balance -= self.old_price
        elif action == 2:  # Halten
            reward = 0
        done = self.current_step >= len(self.data) - 1
        return self._get_obs(), reward, done, False, {}

    def render(self, mode='human'):
        print(f'Step: {self.current_step}, Price: {self.current_price}, Position: {self.position}, Balance: {self.balance}')

Ein Aspekt warum RL in den letzten Jahren so beliebt geworden ist, ist dass sich der Lernvorgang gut parallelisieren lässt. Da wir zum Lernen viele Experimente machen müssen, können wir diese natürlich auch parallel ausführen und dadurch gut in einem Rechenzentrum in der Cloud oder auf Grafikkarten mit ihren tausenden kleinen Prozessoren verteilen.

Eine Bibliothek, die hierbei viel genutzt wird, ist Ray welche auch gerne zur Parallelisierung von ML-Aufgaben genutzt wird, da die Bibliothek das Verteilen der Lernaufgaben im Cluster und das Sammeln der Ergebnisse übernimmt.

Wir nutzen hier den Proximale Policy Optimization (PPO) Algorithmus. Er ist ein iteratives Verfahren, welches zur Optimierung von Richtlinien in Agenten verwendet wird und instabile Updates der Policy vermeidet und ermöglicht so die effiziente Handhabung komplexer Aufgaben mit kontinuierlichen Aktionsräumen. Dies wird durch die Einführung eines Clip-Parameters ε erreicht, der die maximale Änderung der Policy-Parameter begrenzt.

import os
os.environ["PYTHONWARNINGS"]="ignore::DeprecationWarning"
from ray.rllib.algorithms.ppo import PPOConfig
from ray.tune.registry import register_env

tic=time.time()
def env_creator(env_config):
    return StockTradingEnvGym(data)  # return an env instance

register_env("StockTradingEnvGym", env_creator)

config = (
    PPOConfig()
    .environment("StockTradingEnvGym")
    .env_runners(num_env_runners=2)
    .framework("torch")
    .training()
    .evaluation(evaluation_num_env_runners=1)
)

algo = config.build()  # 2. build the algorithm,

for _ in range(10):
    algo.train()  # 3. train it,

#algo.evaluate()  # 4. and evaluate it.
print(f"Execution Time: {time.time()-tic}")
/opt/hostedtoolcache/Python/3.11.0/x64/lib/python3.11/site-packages/tqdm/auto.py:21: TqdmWarning:

IProgress not found. Please update jupyter and ipywidgets. See https://ipywidgets.readthedocs.io/en/stable/user_install.html

2025-08-05 10:56:31,181	INFO util.py:154 -- Missing packages: ['ipywidgets']. Run `pip install -U ipywidgets`, then restart the notebook server for rich notebook output.
2025-08-05 10:56:33,074	INFO util.py:154 -- Missing packages: ['ipywidgets']. Run `pip install -U ipywidgets`, then restart the notebook server for rich notebook output.
2025-08-05 10:56:33,708	WARNING deprecation.py:50 -- DeprecationWarning: `build` has been deprecated. Use `AlgorithmConfig.build_algo` instead. This will raise an error in the future!
2025-08-05 10:56:33,710	WARNING algorithm_config.py:4921 -- You have specified 1 evaluation workers, but your `evaluation_interval` is 0 or None! Therefore, evaluation doesn't occur automatically with each call to `Algorithm.train()`. Instead, you have to call `Algorithm.evaluate()` manually in order to trigger an evaluation run.
2025-08-05 10:56:33,711	WARNING algorithm_config.py:5033 -- You are running PPO on the new API stack! This is the new default behavior for this algorithm. If you don't want to use the new API stack, set `config.api_stack(enable_rl_module_and_learner=False,enable_env_runner_and_connector_v2=False)`. For a detailed migration guide, see here: https://docs.ray.io/en/master/rllib/new-api-stack-migration-guide.html
/opt/hostedtoolcache/Python/3.11.0/x64/lib/python3.11/site-packages/ray/rllib/algorithms/algorithm.py:520: RayDeprecationWarning:

This API is deprecated and may be removed in future Ray releases. You could suppress this warning by setting env variable PYTHONWARNINGS="ignore::DeprecationWarning"
`UnifiedLogger` will be removed in Ray 2.7.

/opt/hostedtoolcache/Python/3.11.0/x64/lib/python3.11/site-packages/ray/tune/logger/unified.py:53: RayDeprecationWarning:

This API is deprecated and may be removed in future Ray releases. You could suppress this warning by setting env variable PYTHONWARNINGS="ignore::DeprecationWarning"
The `JsonLogger interface is deprecated in favor of the `ray.tune.json.JsonLoggerCallback` interface and will be removed in Ray 2.7.

/opt/hostedtoolcache/Python/3.11.0/x64/lib/python3.11/site-packages/ray/tune/logger/unified.py:53: RayDeprecationWarning:

This API is deprecated and may be removed in future Ray releases. You could suppress this warning by setting env variable PYTHONWARNINGS="ignore::DeprecationWarning"
The `CSVLogger interface is deprecated in favor of the `ray.tune.csv.CSVLoggerCallback` interface and will be removed in Ray 2.7.

/opt/hostedtoolcache/Python/3.11.0/x64/lib/python3.11/site-packages/ray/tune/logger/unified.py:53: RayDeprecationWarning:

This API is deprecated and may be removed in future Ray releases. You could suppress this warning by setting env variable PYTHONWARNINGS="ignore::DeprecationWarning"
The `TBXLogger interface is deprecated in favor of the `ray.tune.tensorboardx.TBXLoggerCallback` interface and will be removed in Ray 2.7.
2025-08-05 10:56:36,787	INFO worker.py:1927 -- Started a local Ray instance.
[2025-08-05 10:56:43,850 E 2909 2909] core_worker.cc:2740: Actor with class name: 'SingleAgentEnvRunner' and ID: '2734bb0191246c06db0e09b801000000' has constructor arguments in the object store and max_restarts > 0. If the arguments in the object store go out of scope or are lost, the actor restart will fail. See https://github.com/ray-project/ray/issues/53727 for more details.
[2025-08-05 10:56:43,921 E 2909 2909] core_worker.cc:2740: Actor with class name: 'SingleAgentEnvRunner' and ID: '6856b587ecd5df5b45d22f7501000000' has constructor arguments in the object store and max_restarts > 0. If the arguments in the object store go out of scope or are lost, the actor restart will fail. See https://github.com/ray-project/ray/issues/53727 for more details.
(SingleAgentEnvRunner pid=3055) /opt/hostedtoolcache/Python/3.11.0/x64/lib/python3.11/site-packages/gymnasium/spaces/box.py:235: UserWarning: WARN: Box low's precision lowered by casting to float32, current low.dtype=float64
(SingleAgentEnvRunner pid=3055)   gym.logger.warn(
(SingleAgentEnvRunner pid=3055) /opt/hostedtoolcache/Python/3.11.0/x64/lib/python3.11/site-packages/gymnasium/spaces/box.py:305: UserWarning: WARN: Box high's precision lowered by casting to float32, current high.dtype=float64
(SingleAgentEnvRunner pid=3055)   gym.logger.warn(
(SingleAgentEnvRunner pid=3055) 2025-08-05 10:56:48,201	WARNING deprecation.py:50 -- DeprecationWarning: `RLModule(config=[RLModuleConfig object])` has been deprecated. Use `RLModule(observation_space=.., action_space=.., inference_only=.., model_config=.., catalog_class=..)` instead. This will raise an error in the future!
2025-08-05 10:56:48,416	WARNING deprecation.py:50 -- DeprecationWarning: `RLModule(config=[RLModuleConfig object])` has been deprecated. Use `RLModule(observation_space=.., action_space=.., inference_only=.., model_config=.., catalog_class=..)` instead. This will raise an error in the future!
2025-08-05 10:56:48,446	WARNING algorithm_config.py:4921 -- You have specified 1 evaluation workers, but your `evaluation_interval` is 0 or None! Therefore, evaluation doesn't occur automatically with each call to `Algorithm.train()`. Instead, you have to call `Algorithm.evaluate()` manually in order to trigger an evaluation run.
[2025-08-05 10:56:48,494 E 2909 2909] core_worker.cc:2740: Actor with class name: 'SingleAgentEnvRunner' and ID: 'b72e8dc1a13bae46590f509001000000' has constructor arguments in the object store and max_restarts > 0. If the arguments in the object store go out of scope or are lost, the actor restart will fail. See https://github.com/ray-project/ray/issues/53727 for more details.
(autoscaler +40s) Tip: use `ray status` to view detailed cluster status. To disable these messages, set RAY_SCHEDULER_EVENTS=0.
(autoscaler +40s) Warning: The following resource request cannot be scheduled right now: {'CPU': 1.0}. This is likely due to all cluster resources being claimed by actors. Consider creating fewer actors or adding more nodes to this Ray cluster.
(autoscaler +1m15s) Warning: The following resource request cannot be scheduled right now: {'CPU': 1.0}. This is likely due to all cluster resources being claimed by actors. Consider creating fewer actors or adding more nodes to this Ray cluster.
(autoscaler +1m50s) Warning: The following resource request cannot be scheduled right now: {'CPU': 1.0}. This is likely due to all cluster resources being claimed by actors. Consider creating fewer actors or adding more nodes to this Ray cluster.
(autoscaler +2m25s) Warning: The following resource request cannot be scheduled right now: {'CPU': 1.0}. This is likely due to all cluster resources being claimed by actors. Consider creating fewer actors or adding more nodes to this Ray cluster.
---------------------------------------------------------------------------
KeyboardInterrupt                         Traceback (most recent call last)
Cell In[20], line 21
     10 register_env("StockTradingEnvGym", env_creator)
     12 config = (
     13     PPOConfig()
     14     .environment("StockTradingEnvGym")
   (...)     18     .evaluation(evaluation_num_env_runners=1)
     19 )
---> 21 algo = config.build()  # 2. build the algorithm,
     23 for _ in range(10):
     24     algo.train()  # 3. train it,

File /opt/hostedtoolcache/Python/3.11.0/x64/lib/python3.11/site-packages/ray/rllib/utils/deprecation.py:128, in Deprecated.<locals>._inner.<locals>._ctor(*args, **kwargs)
    121     deprecation_warning(
    122         old=old or obj.__name__,
    123         new=new,
    124         help=help,
    125         error=error,
    126     )
    127 # Call the deprecated method/function.
--> 128 return obj(*args, **kwargs)

File /opt/hostedtoolcache/Python/3.11.0/x64/lib/python3.11/site-packages/ray/rllib/algorithms/algorithm_config.py:5794, in AlgorithmConfig.build(self, *args, **kwargs)
   5792 @Deprecated(new="AlgorithmConfig.build_algo", error=False)
   5793 def build(self, *args, **kwargs):
-> 5794     return self.build_algo(*args, **kwargs)

File /opt/hostedtoolcache/Python/3.11.0/x64/lib/python3.11/site-packages/ray/rllib/algorithms/algorithm_config.py:1001, in AlgorithmConfig.build_algo(self, env, logger_creator, use_copy)
    998 if isinstance(self.algo_class, str):
    999     algo_class = get_trainable_cls(self.algo_class)
-> 1001 return algo_class(
   1002     config=self if not use_copy else copy.deepcopy(self),
   1003     logger_creator=self.logger_creator,
   1004 )

File /opt/hostedtoolcache/Python/3.11.0/x64/lib/python3.11/site-packages/ray/rllib/algorithms/algorithm.py:536, in Algorithm.__init__(self, config, env, logger_creator, **kwargs)
    533 # Evaluation EnvRunnerGroup and metrics last returned by `self.evaluate()`.
    534 self.eval_env_runner_group: Optional[EnvRunnerGroup] = None
--> 536 super().__init__(
    537     config=config,
    538     logger_creator=logger_creator,
    539     **kwargs,
    540 )

File /opt/hostedtoolcache/Python/3.11.0/x64/lib/python3.11/site-packages/ray/tune/trainable/trainable.py:158, in Trainable.__init__(self, config, logger_creator, storage)
    154     logger.debug(f"StorageContext on the TRAINABLE:\n{storage}")
    156 self._open_logfiles(stdout_file, stderr_file)
--> 158 self.setup(copy.deepcopy(self.config))
    159 setup_time = time.time() - self._start_time
    160 if setup_time > SETUP_TIME_THRESHOLD:

File /opt/hostedtoolcache/Python/3.11.0/x64/lib/python3.11/site-packages/ray/rllib/algorithms/algorithm.py:677, in Algorithm.setup(self, config)
    669     _, env_creator = self._get_env_id_and_creator(
    670         self.evaluation_config.env, self.evaluation_config
    671     )
    673     # Create a separate evaluation worker set for evaluation.
    674     # If evaluation_num_env_runners=0, use the evaluation set's local
    675     # worker for evaluation, otherwise, use its remote workers
    676     # (parallelized evaluation).
--> 677     self.eval_env_runner_group: EnvRunnerGroup = EnvRunnerGroup(
    678         env_creator=env_creator,
    679         validate_env=None,
    680         default_policy_class=self.get_default_policy_class(self.config),
    681         config=self.evaluation_config,
    682         logdir=self.logdir,
    683         tune_trial_id=self.trial_id,
    684         # New API stack: User decides whether to create local env runner.
    685         # Old API stack: Always create local EnvRunner.
    686         local_env_runner=(
    687             True
    688             if not self.evaluation_config.enable_env_runner_and_connector_v2
    689             else self.evaluation_config.create_local_env_runner
    690         ),
    691         pg_offset=self.config.num_env_runners,
    692     )
    694 if self.env_runner_group:
    695     self.spaces = self.env_runner_group.get_spaces()

File /opt/hostedtoolcache/Python/3.11.0/x64/lib/python3.11/site-packages/ray/rllib/env/env_runner_group.py:198, in EnvRunnerGroup.__init__(self, env_creator, validate_env, default_policy_class, config, local_env_runner, logdir, _setup, tune_trial_id, pg_offset, num_env_runners, num_workers, local_worker)
    196 if _setup:
    197     try:
--> 198         self._setup(
    199             validate_env=validate_env,
    200             config=config,
    201             num_env_runners=(
    202                 num_env_runners
    203                 if num_env_runners is not None
    204                 else config.num_env_runners
    205             ),
    206             local_env_runner=local_env_runner,
    207         )
    208     # EnvRunnerGroup creation possibly fails, if some (remote) workers cannot
    209     # be initialized properly (due to some errors in the EnvRunners's
    210     # constructor).
    211     except RayActorError as e:
    212         # In case of an actor (remote worker) init failure, the remote worker
    213         # may still exist and will be accessible, however, e.g. calling
    214         # its `sample.remote()` would result in strange "property not found"
    215         # errors.

File /opt/hostedtoolcache/Python/3.11.0/x64/lib/python3.11/site-packages/ray/rllib/env/env_runner_group.py:272, in EnvRunnerGroup._setup(self, validate_env, config, num_env_runners, local_env_runner)
    269     self._ds_shards = None
    271 # Create a number of @ray.remote workers.
--> 272 self.add_workers(
    273     num_env_runners,
    274     validate=config.validate_env_runners_after_construction,
    275 )
    277 # If num_env_runners > 0 and we don't have an env on the local worker,
    278 # get the observation- and action spaces for each policy from
    279 # the first remote worker (which does have an env).
    280 if (
    281     local_env_runner
    282     and self._worker_manager.num_actors() > 0
    283     and not config.create_env_on_local_worker
    284     and (not config.observation_space or not config.action_space)
    285 ):

File /opt/hostedtoolcache/Python/3.11.0/x64/lib/python3.11/site-packages/ray/rllib/env/env_runner_group.py:750, in EnvRunnerGroup.add_workers(self, num_workers, validate)
    747 # Validate here, whether all remote workers have been constructed properly
    748 # and are "up and running". Establish initial states.
    749 if validate:
--> 750     for result in self._worker_manager.foreach_actor(
    751         lambda w: w.assert_healthy()
    752     ):
    753         # Simiply raise the error, which will get handled by the try-except
    754         # clause around the _setup().
    755         if not result.ok:
    756             e = result.get()

File /opt/hostedtoolcache/Python/3.11.0/x64/lib/python3.11/site-packages/ray/rllib/utils/actor_manager.py:461, in FaultTolerantActorManager.foreach_actor(self, func, kwargs, healthy_only, remote_actor_ids, timeout_seconds, return_obj_refs, mark_healthy)
    454 remote_calls = self._call_actors(
    455     func=func,
    456     kwargs=kwargs,
    457     remote_actor_ids=remote_actor_ids,
    458 )
    460 # Collect remote request results (if available given timeout and/or errors).
--> 461 _, remote_results = self._fetch_result(
    462     remote_actor_ids=remote_actor_ids,
    463     remote_calls=remote_calls,
    464     tags=[None] * len(remote_calls),
    465     timeout_seconds=timeout_seconds,
    466     return_obj_refs=return_obj_refs,
    467     mark_healthy=mark_healthy,
    468 )
    470 return remote_results

File /opt/hostedtoolcache/Python/3.11.0/x64/lib/python3.11/site-packages/ray/rllib/utils/actor_manager.py:839, in FaultTolerantActorManager._fetch_result(self, remote_actor_ids, remote_calls, tags, timeout_seconds, return_obj_refs, mark_healthy)
    836 if not remote_calls:
    837     return [], RemoteCallResults()
--> 839 readies, _ = ray.wait(
    840     remote_calls,
    841     num_returns=len(remote_calls),
    842     timeout=timeout,
    843     # Make sure remote results are fetched locally in parallel.
    844     fetch_local=not return_obj_refs,
    845 )
    847 # Remote data should already be fetched to local object store at this point.
    848 remote_results = RemoteCallResults()

File /opt/hostedtoolcache/Python/3.11.0/x64/lib/python3.11/site-packages/ray/_private/auto_init_hook.py:22, in wrap_auto_init.<locals>.auto_init_wrapper(*args, **kwargs)
     19 @wraps(fn)
     20 def auto_init_wrapper(*args, **kwargs):
     21     auto_init_ray()
---> 22     return fn(*args, **kwargs)

File /opt/hostedtoolcache/Python/3.11.0/x64/lib/python3.11/site-packages/ray/_private/client_mode_hook.py:104, in client_mode_hook.<locals>.wrapper(*args, **kwargs)
    102     if func.__name__ != "init" or is_client_mode_enabled_by_default:
    103         return getattr(ray, func.__name__)(*args, **kwargs)
--> 104 return func(*args, **kwargs)

File /opt/hostedtoolcache/Python/3.11.0/x64/lib/python3.11/site-packages/ray/_private/worker.py:3089, in wait(ray_waitables, num_returns, timeout, fetch_local)
   3087 timeout = timeout if timeout is not None else 10**6
   3088 timeout_milliseconds = int(timeout * 1000)
-> 3089 ready_ids, remaining_ids = worker.core_worker.wait(
   3090     ray_waitables,
   3091     num_returns,
   3092     timeout_milliseconds,
   3093     fetch_local,
   3094 )
   3095 return ready_ids, remaining_ids

File python/ray/_raylet.pyx:3512, in ray._raylet.CoreWorker.wait()

File python/ray/includes/common.pxi:83, in ray._raylet.check_status()

KeyboardInterrupt: 
# Evaluierung des Agents
env = StockTradingEnvGym(data)
state = env.reset()
done = False
total_reward = 0

actions = []
rewards = []
while not done:
    action = algo.compute_single_action(env._get_obs(), state)
    state, reward, done, _, _ = env.step(action[0])
    total_reward += reward
    actions.append(action)
    rewards.append(total_reward)
    env.render()

print("Gesamtbelohnung:", total_reward)
Step: 1, Price: 1.358449851840048, Position: 0, Balance: 10000
Step: 2, Price: 2.5061383899407406, Position: 0, Balance: 10000
Step: 3, Price: 4.529168246348766, Position: 0, Balance: 10000
Step: 4, Price: 4.79501487162543, Position: 1, Balance: 9995.204985128374
Step: 5, Price: 5.060877914676249, Position: 1, Balance: 9995.204985128374
Step: 6, Price: 7.1400907301836405, Position: 1, Balance: 9995.204985128374
Step: 7, Price: 8.407525459336549, Position: 1, Balance: 9995.204985128374
Step: 8, Price: 8.438051073401597, Position: 1, Balance: 9995.204985128374
Step: 9, Price: 9.480611116987562, Position: 1, Balance: 9995.204985128374
Step: 10, Price: 9.517193424175101, Position: 1, Balance: 9995.204985128374
Step: 11, Price: 9.551463670604845, Position: 1, Balance: 9995.204985128374
Step: 12, Price: 10.29342594217088, Position: 1, Balance: 9995.204985128374
Step: 13, Price: 8.88014569751308, Position: 1, Balance: 9995.204985128374
Step: 14, Price: 7.6552278650000485, Position: 1, Balance: 9995.204985128374
Step: 15, Price: 7.592940335759076, Position: 1, Balance: 9995.204985128374
Step: 16, Price: 7.0801092154246525, Position: 1, Balance: 9995.204985128374
Step: 17, Price: 7.8943565480199265, Position: 1, Balance: 9995.204985128374
Step: 18, Price: 7.486332472498716, Position: 1, Balance: 9995.204985128374
Step: 19, Price: 6.574028771163424, Position: 1, Balance: 9995.204985128374
Step: 20, Price: 8.539677540084979, Position: 1, Balance: 9995.204985128374
Step: 21, Price: 8.813901239598444, Position: 1, Balance: 9995.204985128374
Step: 22, Price: 9.381429444286368, Position: 1, Balance: 9995.204985128374
Step: 23, Price: 8.456681258072912, Position: 1, Balance: 9995.204985128374
Step: 24, Price: 8.41229853354773, Position: 1, Balance: 9995.204985128374
Step: 25, Price: 9.023221123257596, Position: 1, Balance: 9995.204985128374
Step: 26, Price: 8.372227545835292, Position: 1, Balance: 9995.204985128374
Step: 27, Price: 9.247925564180964, Position: 1, Balance: 9995.204985128374
Step: 28, Price: 9.147286874262159, Position: 1, Balance: 9995.204985128374
Step: 29, Price: 9.355593124468882, Position: 1, Balance: 9995.204985128374
Step: 30, Price: 9.253886512239486, Position: 1, Balance: 9995.204985128374
Step: 31, Price: 11.606164696748424, Position: 1, Balance: 9995.204985128374
Step: 32, Price: 12.09266747201049, Position: 1, Balance: 9995.204985128374
Step: 33, Price: 11.53495654305459, Position: 1, Balance: 9995.204985128374
Step: 34, Price: 12.85750145515778, Position: 1, Balance: 9995.204985128374
Step: 35, Price: 12.136657805186758, Position: 0, Balance: 9990.409970256747
Step: 36, Price: 12.845521400191513, Position: 0, Balance: 9990.409970256747
Step: 37, Price: 11.385851276311739, Position: 1, Balance: 9979.024118980436
Step: 38, Price: 10.557665227413308, Position: 1, Balance: 9979.024118980436
Step: 39, Price: 11.254526463282431, Position: 1, Balance: 9979.024118980436
Step: 40, Price: 12.492993043277842, Position: 1, Balance: 9979.024118980436
Step: 41, Price: 13.164361324467812, Position: 1, Balance: 9979.024118980436
Step: 42, Price: 13.548713042079571, Position: 1, Balance: 9979.024118980436
Step: 43, Price: 13.747609346490282, Position: 1, Balance: 9979.024118980436
Step: 44, Price: 12.769087356122855, Position: 1, Balance: 9979.024118980436
Step: 45, Price: 12.549243147728147, Position: 1, Balance: 9979.024118980436
Step: 46, Price: 12.588604376768359, Position: 1, Balance: 9979.024118980436
Step: 47, Price: 14.145726602987274, Position: 1, Balance: 9979.024118980436
Step: 48, Price: 14.989344892555735, Position: 1, Balance: 9979.024118980436
Step: 49, Price: 13.726304737193, Position: 1, Balance: 9979.024118980436
Step: 50, Price: 14.550388706587796, Position: 1, Balance: 9979.024118980436
Step: 51, Price: 14.66530642617148, Position: 1, Balance: 9979.024118980436
Step: 52, Price: 14.48838442586552, Position: 1, Balance: 9979.024118980436
Step: 53, Price: 15.600060714706387, Position: 1, Balance: 9979.024118980436
Step: 54, Price: 17.131060237202338, Position: 1, Balance: 9979.024118980436
Step: 55, Price: 18.562340356318536, Position: 1, Balance: 9979.024118980436
Step: 56, Price: 18.223122833095896, Position: 1, Balance: 9979.024118980436
Step: 57, Price: 18.413910457244683, Position: 1, Balance: 9979.024118980436
Step: 58, Price: 19.245173888648246, Position: 1, Balance: 9979.024118980436
Step: 59, Price: 20.720719015770605, Position: 1, Balance: 9979.024118980436
Step: 60, Price: 20.741544777925316, Position: 1, Balance: 9979.024118980436
Step: 61, Price: 21.0558858012615, Position: 1, Balance: 9979.024118980436
Step: 62, Price: 20.44955082725547, Position: 1, Balance: 9979.024118980436
Step: 63, Price: 19.7533442031748, Position: 1, Balance: 9979.024118980436
Step: 64, Price: 21.065870025568998, Position: 1, Balance: 9979.024118980436
Step: 65, Price: 22.92211005413982, Position: 1, Balance: 9979.024118980436
Step: 66, Price: 23.350099932559488, Position: 1, Balance: 9979.024118980436
Step: 67, Price: 24.853632830451513, Position: 1, Balance: 9979.024118980436
Step: 68, Price: 25.715268855499147, Position: 1, Balance: 9979.024118980436
Step: 69, Price: 25.570149100894024, Position: 0, Balance: 9967.638267704126
Step: 70, Price: 26.431544706402438, Position: 1, Balance: 9941.206722997724
Step: 71, Price: 28.469581272868407, Position: 1, Balance: 9941.206722997724
Step: 72, Price: 28.933755233758454, Position: 1, Balance: 9941.206722997724
Step: 73, Price: 30.99839888957246, Position: 1, Balance: 9941.206722997724
Step: 74, Price: 28.878653785482715, Position: 1, Balance: 9941.206722997724
Step: 75, Price: 30.20055628985794, Position: 1, Balance: 9941.206722997724
Step: 76, Price: 30.78760335809611, Position: 1, Balance: 9941.206722997724
Step: 77, Price: 30.988596007630242, Position: 0, Balance: 9914.775178291322
Step: 78, Price: 31.580356784165744, Position: 0, Balance: 9914.775178291322
Step: 79, Price: 30.09278786956485, Position: 0, Balance: 9914.775178291322
Step: 80, Price: 30.373115981727338, Position: 1, Balance: 9884.402062309595
Step: 81, Price: 31.230228553239083, Position: 1, Balance: 9884.402062309595
Step: 82, Price: 33.2081225979806, Position: 1, Balance: 9884.402062309595
Step: 83, Price: 33.18985237970695, Position: 1, Balance: 9884.402062309595
Step: 84, Price: 32.88135877681376, Position: 1, Balance: 9884.402062309595
Step: 85, Price: 32.87960173322922, Position: 1, Balance: 9884.402062309595
Step: 86, Price: 34.295003850931295, Position: 1, Balance: 9884.402062309595
Step: 87, Price: 35.12375496059098, Position: 1, Balance: 9884.402062309595
Step: 88, Price: 35.09399475682394, Position: 1, Balance: 9884.402062309595
Step: 89, Price: 36.107262189937295, Position: 1, Balance: 9884.402062309595
Step: 90, Price: 36.70433973928534, Position: 1, Balance: 9884.402062309595
Step: 91, Price: 38.172984729818225, Position: 1, Balance: 9884.402062309595
Step: 92, Price: 37.97093163594087, Position: 1, Balance: 9884.402062309595
Step: 93, Price: 38.143269489343105, Position: 0, Balance: 9854.028946327868
Step: 94, Price: 38.25116133621095, Position: -1, Balance: 9892.280107664079
Step: 95, Price: 37.28764638807883, Position: 0, Balance: 9930.53126900029
Step: 96, Price: 38.0837666651434, Position: 0, Balance: 9930.53126900029
Step: 97, Price: 38.84482193732329, Position: 1, Balance: 9891.686447062966
Step: 98, Price: 39.34993539396575, Position: 1, Balance: 9891.686447062966
Step: 99, Price: 39.6153482605906, Position: 1, Balance: 9891.686447062966
Step: 100, Price: 38.69997751854019, Position: 1, Balance: 9891.686447062966
Step: 101, Price: 38.77933219577483, Position: 0, Balance: 9852.841625125642
Step: 102, Price: 38.93661767924806, Position: 0, Balance: 9852.841625125642
Step: 103, Price: 38.63434041002644, Position: 0, Balance: 9852.841625125642
Step: 104, Price: 38.97305469836043, Position: 0, Balance: 9852.841625125642
Step: 105, Price: 39.877105555174964, Position: 0, Balance: 9852.841625125642
Step: 106, Price: 42.263291456385495, Position: 0, Balance: 9852.841625125642
Step: 107, Price: 42.93786926921734, Position: 0, Balance: 9852.841625125642
Step: 108, Price: 43.6954196599401, Position: 0, Balance: 9852.841625125642
Step: 109, Price: 44.12097374417393, Position: 0, Balance: 9852.841625125642
Step: 110, Price: 42.70220252887489, Position: 1, Balance: 9810.139422596767
Step: 111, Price: 43.175688653425674, Position: 1, Balance: 9810.139422596767
Step: 112, Price: 43.7359188633667, Position: 1, Balance: 9810.139422596767
Step: 113, Price: 46.69916097585198, Position: 1, Balance: 9810.139422596767
Step: 114, Price: 47.00680001107086, Position: 1, Balance: 9810.139422596767
Step: 115, Price: 47.80834735340447, Position: 1, Balance: 9810.139422596767
Step: 116, Price: 48.273635583699225, Position: 1, Balance: 9810.139422596767
Step: 117, Price: 47.604957546079696, Position: 1, Balance: 9810.139422596767
Step: 118, Price: 49.24778036059472, Position: 0, Balance: 9767.437220067892
Step: 119, Price: 50.49971339328149, Position: 1, Balance: 9716.93750667461
Step: 120, Price: 51.790745340324534, Position: 1, Balance: 9716.93750667461
Step: 121, Price: 51.381357885529795, Position: 1, Balance: 9716.93750667461
Step: 122, Price: 53.2841521964659, Position: 1, Balance: 9716.93750667461
Step: 123, Price: 52.382301133673614, Position: 1, Balance: 9716.93750667461
Step: 124, Price: 53.46915822747388, Position: 1, Balance: 9716.93750667461
Step: 125, Price: 56.159613853283865, Position: 1, Balance: 9716.93750667461
Step: 126, Price: 55.669077528153174, Position: 1, Balance: 9716.93750667461
Step: 127, Price: 55.6027797985504, Position: 1, Balance: 9716.93750667461
Step: 128, Price: 56.20243116363804, Position: 1, Balance: 9716.93750667461
Step: 129, Price: 56.19895550952184, Position: 1, Balance: 9716.93750667461
Step: 130, Price: 55.148292078455704, Position: 1, Balance: 9716.93750667461
Step: 131, Price: 55.71685505326173, Position: 1, Balance: 9716.93750667461
Step: 132, Price: 55.15455133953563, Position: 1, Balance: 9716.93750667461
Step: 133, Price: 56.12814377017081, Position: 1, Balance: 9716.93750667461
Step: 134, Price: 55.70871953593701, Position: 1, Balance: 9716.93750667461
Step: 135, Price: 57.758653940954545, Position: 1, Balance: 9716.93750667461
Step: 136, Price: 57.47540064861831, Position: 0, Balance: 9666.43779328133
Step: 137, Price: 57.65333913241263, Position: 0, Balance: 9666.43779328133
Step: 138, Price: 58.966856349782304, Position: 0, Balance: 9666.43779328133
Step: 139, Price: 58.235992033348346, Position: -1, Balance: 9724.673785314679
Step: 140, Price: 58.96345196795247, Position: -1, Balance: 9724.673785314679
Step: 141, Price: 60.770594722234904, Position: -1, Balance: 9724.673785314679
Step: 142, Price: 59.663111487673675, Position: -1, Balance: 9724.673785314679
Step: 143, Price: 60.347745346205976, Position: -1, Balance: 9724.673785314679
Step: 144, Price: 61.1076281404544, Position: -1, Balance: 9724.673785314679
Step: 145, Price: 62.38945101223171, Position: -1, Balance: 9724.673785314679
Step: 146, Price: 61.652500301353626, Position: 0, Balance: 9782.909777348028
Step: 147, Price: 60.83204368826935, Position: 1, Balance: 9722.077733659758
Step: 148, Price: 61.85398525388625, Position: 1, Balance: 9722.077733659758
Step: 149, Price: 62.650969927119434, Position: 1, Balance: 9722.077733659758
Step: 150, Price: 63.40146277746531, Position: 0, Balance: 9661.245689971489
Step: 151, Price: 64.24791098696228, Position: 1, Balance: 9596.997778984527
Step: 152, Price: 64.06788626538379, Position: 1, Balance: 9596.997778984527
Step: 153, Price: 64.80013996254479, Position: 1, Balance: 9596.997778984527
Step: 154, Price: 65.59321243584347, Position: 1, Balance: 9596.997778984527
Step: 155, Price: 65.3788610178171, Position: 1, Balance: 9596.997778984527
Step: 156, Price: 67.74463552896185, Position: 1, Balance: 9596.997778984527
Step: 157, Price: 68.71846844987364, Position: 1, Balance: 9596.997778984527
Step: 158, Price: 68.02716495267099, Position: 1, Balance: 9596.997778984527
Step: 159, Price: 69.18371856130481, Position: 1, Balance: 9596.997778984527
Step: 160, Price: 68.70903689107749, Position: 1, Balance: 9596.997778984527
Step: 161, Price: 69.99612149481995, Position: 1, Balance: 9596.997778984527
Step: 162, Price: 71.65471707382736, Position: 1, Balance: 9596.997778984527
Step: 163, Price: 71.33403475547564, Position: 1, Balance: 9596.997778984527
Step: 164, Price: 72.79741088471997, Position: 1, Balance: 9596.997778984527
Step: 165, Price: 73.71019181165646, Position: 1, Balance: 9596.997778984527
Step: 166, Price: 75.03225197165095, Position: 1, Balance: 9596.997778984527
Step: 167, Price: 77.4290449543049, Position: 1, Balance: 9596.997778984527
Step: 168, Price: 77.68365683830203, Position: 1, Balance: 9596.997778984527
Step: 169, Price: 77.42992067394454, Position: 1, Balance: 9596.997778984527
Step: 170, Price: 77.04040624431902, Position: 1, Balance: 9596.997778984527
Step: 171, Price: 76.72459595935358, Position: 1, Balance: 9596.997778984527
Step: 172, Price: 77.14749424993947, Position: 1, Balance: 9596.997778984527
Step: 173, Price: 77.98864622475611, Position: 1, Balance: 9596.997778984527
Step: 174, Price: 78.76533702408612, Position: 1, Balance: 9596.997778984527
Step: 175, Price: 80.09252027312215, Position: 1, Balance: 9596.997778984527
Step: 176, Price: 80.60552216500005, Position: 1, Balance: 9596.997778984527
Step: 177, Price: 82.55905624215737, Position: 0, Balance: 9532.749867997565
Step: 178, Price: 82.79439940891942, Position: 1, Balance: 9449.955468588645
Step: 179, Price: 86.01456857550903, Position: 1, Balance: 9449.955468588645
Step: 180, Price: 87.14023592327403, Position: 1, Balance: 9449.955468588645
Step: 181, Price: 86.78307836685775, Position: 1, Balance: 9449.955468588645
Step: 182, Price: 86.21218586879664, Position: 1, Balance: 9449.955468588645
Step: 183, Price: 87.19465828403982, Position: 1, Balance: 9449.955468588645
Step: 184, Price: 87.47119549871397, Position: 1, Balance: 9449.955468588645
Step: 185, Price: 88.68519599280606, Position: 1, Balance: 9449.955468588645
Step: 186, Price: 89.6584336173796, Position: 1, Balance: 9449.955468588645
Step: 187, Price: 90.08560470472273, Position: 1, Balance: 9449.955468588645
Step: 188, Price: 89.73881098665431, Position: 1, Balance: 9449.955468588645
Step: 189, Price: 88.72396376196845, Position: 1, Balance: 9449.955468588645
Step: 190, Price: 88.77744880990143, Position: 1, Balance: 9449.955468588645
Step: 191, Price: 90.1338476042249, Position: 1, Balance: 9449.955468588645
Step: 192, Price: 90.8479413483551, Position: 1, Balance: 9449.955468588645
Step: 193, Price: 90.10220256964313, Position: 0, Balance: 9367.161069179725
Step: 194, Price: 90.77538349549431, Position: 0, Balance: 9367.161069179725
Step: 195, Price: 91.66070087522314, Position: 0, Balance: 9367.161069179725
Step: 196, Price: 91.276843439022, Position: 0, Balance: 9367.161069179725
Step: 197, Price: 91.93056854496753, Position: 1, Balance: 9275.230500634758
Step: 198, Price: 92.48877726341352, Position: 1, Balance: 9275.230500634758
Step: 199, Price: 91.8458069655829, Position: 1, Balance: 9275.230500634758
Gesamtbelohnung: 146.08859019547393
px.line([a[0] for a in actions])
px.line(rewards)

Robotik#

DL ist insbesondere in der Robotik ein beliebtes Lernverfahren. Das liegt daran, das zum einen einfache Aufgaben, wie das Greifen von Objekten für Roboter hochkomplex sind und die Regelungen und Steuerungen sehr komplex zu entwickeln sind. Auch wir Menschen brauchen Wochen als Kleinkind um die Grob- und Feinmotorik dafür zu lernen. Trotzdem ist die Aufgabe und die Umgebung eines Roboters einfach zu simulieren. Deshalb setzt man vermehrt auf RL, um solche komplexen Steuerungsprobleme zu erlernen, statt selbstständisch Steuerungen zu entwickeln.

Robot Pusher#

Pusher-v4 ist eine Umgebung aus der gym-Bibliothek, die zur Simulation von Aufgaben im Bereich der Robotersteuerung verwendet wird. In dieser speziellen Umgebung wird ein Roboterarm simuliert, der darauf trainiert wird, ein Objekt zu einer bestimmten Zielposition zu schieben.

Das Ziel des Agenten (Roboterarms) in der Pusher-v4-Umgebung ist es, ein Objekt (in der Regel ein Block) zu einer festgelegten Zielposition zu schieben. Der Agent muss lernen, wie er seine Gelenke bewegen kann, um das Objekt erfolgreich zu schieben.

Der Aktionsraum ist kontinuierlich und repräsentiert die Steuerung des Roboterarms. Typischerweise handelt es sich um einen Vektor von Gelenkbewegungen, die der Agent ausführen kann.

Der Beobachtungsraum umfasst verschiedene Aspekte des Zustands der Umgebung, einschließlich der Position des Endeffektors des Roboterarms; die Position des zu schiebenden Objekts und die Zielposition, zu der das Objekt geschoben werden soll.

Die Belohnung in der Pusher-v4-Umgebung basiert darauf, wie nah das Objekt an der Zielposition ist. Der Agent erhält eine höhere Belohnung, wenn das Objekt näher an der Zielposition ist, und eine geringere Belohnung, wenn es weiter entfernt ist.

Da das Modell in Gym enthalten ist, ist die Initialisierung einfach.

from ray.rllib.algorithms.ppo import PPOConfig

config = (  # 1. Configure the algorithm,
    PPOConfig()
    .environment("Pusher-v4", render_env=True)
    .env_runners(num_env_runners=2)
    .framework("torch")
    .training()
    .evaluation(evaluation_num_env_runners=1)
)

algo = config.build()  # 2. build the algorithm,

for _ in range(5):
    algo.train()  # 3. train it,

#algo.evaluate()  # 4. and evaluate it.
print(f"Execution Time: {time.time()-tic}")
2024-06-24 11:12:43,177	WARNING algorithm_config.py:4078 -- You have specified 1 evaluation workers, but your `evaluation_interval` is 0 or None! Therefore, evaluation will not occur automatically with each call to `Algorithm.train()`. Instead, you will have to call `Algorithm.evaluate()` manually in order to trigger an evaluation run.
/Users/jploennigs/miniconda3/envs/lehre4/lib/python3.11/site-packages/ray/rllib/algorithms/algorithm.py:525: RayDeprecationWarning:

This API is deprecated and may be removed in future Ray releases. You could suppress this warning by setting env variable PYTHONWARNINGS="ignore::DeprecationWarning"
`UnifiedLogger` will be removed in Ray 2.7.

/Users/jploennigs/miniconda3/envs/lehre4/lib/python3.11/site-packages/ray/tune/logger/unified.py:53: RayDeprecationWarning:

This API is deprecated and may be removed in future Ray releases. You could suppress this warning by setting env variable PYTHONWARNINGS="ignore::DeprecationWarning"
The `JsonLogger interface is deprecated in favor of the `ray.tune.json.JsonLoggerCallback` interface and will be removed in Ray 2.7.

/Users/jploennigs/miniconda3/envs/lehre4/lib/python3.11/site-packages/ray/tune/logger/unified.py:53: RayDeprecationWarning:

This API is deprecated and may be removed in future Ray releases. You could suppress this warning by setting env variable PYTHONWARNINGS="ignore::DeprecationWarning"
The `CSVLogger interface is deprecated in favor of the `ray.tune.csv.CSVLoggerCallback` interface and will be removed in Ray 2.7.

/Users/jploennigs/miniconda3/envs/lehre4/lib/python3.11/site-packages/ray/tune/logger/unified.py:53: RayDeprecationWarning:

This API is deprecated and may be removed in future Ray releases. You could suppress this warning by setting env variable PYTHONWARNINGS="ignore::DeprecationWarning"
The `TBXLogger interface is deprecated in favor of the `ray.tune.tensorboardx.TBXLoggerCallback` interface and will be removed in Ray 2.7.

2024-06-24 11:12:47,084	WARNING algorithm_config.py:4078 -- You have specified 1 evaluation workers, but your `evaluation_interval` is 0 or None! Therefore, evaluation will not occur automatically with each call to `Algorithm.train()`. Instead, you will have to call `Algorithm.evaluate()` manually in order to trigger an evaluation run.
2024-06-24 11:12:49,605	WARNING util.py:61 -- Install gputil for GPU system monitoring.
Execution Time: 79.14778399467468

Betrachten wir einmal das Ergebnis einer zufälligen Bewegung, so sehen wir wie der Arm vorerst orientierungslos agiert.

Nach mehreren Trainingsepisoden lernt der Algorithmus allerdings den Arm gut zu benutzen. Hier ein Vergleich unterschiedlicher RL-Ansätze. Zu beobachten ist, dass über die Trainings-Episoden die Modelle durch Zufall die Lösung entdecken und dann wiederholen können. Die finalen Lösungen sind dabei aber auch nach vielen Trainings-Episoden nicht perfekt.

from IPython.display import IFrame
IFrame(width="800", height="413", src="https://www.youtube.com/embed/_QmcH1TyNwg", title="Gymnasium - Pusher-v4, Test with different algorithms",
         frameborder="0", allow="accelerometer; autoplay; clipboard-write; encrypted-media; gyroscope; picture-in-picture; web-share", referrerpolicy="strict-origin-when-cross-origin", allowfullscreen=True)

Pick and Place#

“FetchPickAndPlaceDense-v2” ist eine Umgebung in der ein simulierter Roboterarm verwendet, um Objekte aufzuheben (Pick) und an einem Zielort abzulegen (Place).

Das Hauptziel des Agenten (Roboterarms) in der FetchPickAndPlaceDense-v2-Umgebung ist es, ein Objekt zu greifen (Pick) und es an einer festgelegten Zielposition abzulegen (Place). Der Agent muss lernen, wie er seine Gelenke und den Greifer steuern kann, um diese Aufgabe erfolgreich zu erfüllen.

Der Aktionsraum ist kontinuierlich und besteht aus den Koordinaten \(x, y, z\) der Hand des Roboterarms (Endeffektors) und die Öffnung des Greifers \(w\) (öffnen oder schließen). Der Beobachtungsraum enthält die Position und Geschwindigkeit des Endeffektors; die Position des zu bewegenden Objekts, die Zielposition, zu der das Objekt bewegt werden soll und den Zustand des Greifers (geöffnet oder geschlossen).

Die Umgebung bietet eine dichte Belohnungsstruktur, was bedeutet, dass der Agent kontinuierlich Rückmeldungen erhält, die ihn darauf hinweisen, wie gut er sich in Richtung seines Ziels bewegt. Die Belohnung basiert hauptsächlich darauf, wie nah das Objekt an der Zielposition ist und ob das Objekt erfolgreich gegriffen und bewegt wurde.

Auch hier ist die Initialisierung einfach.

from ray.rllib.algorithms.ppo import PPOConfig

config = (  # 1. Configure the algorithm,
    PPOConfig()
    .environment('FetchPickAndPlaceDense-v2')
    .env_runners(num_env_runners=2)
    .framework("torch")
    .training()
    .evaluation(evaluation_num_env_runners=1)
)

algo = config.build()  # 2. build the algorithm,

for _ in range(5):
    algo.train()  # 3. train it,

#algo.evaluate()  # 4. and evaluate it.
print(f"Execution Time: {time.time()-tic}")
2024-06-24 11:13:13,573	WARNING algorithm_config.py:4078 -- You have specified 1 evaluation workers, but your `evaluation_interval` is 0 or None! Therefore, evaluation will not occur automatically with each call to `Algorithm.train()`. Instead, you will have to call `Algorithm.evaluate()` manually in order to trigger an evaluation run.
2024-06-24 11:13:17,302	WARNING algorithm_config.py:4078 -- You have specified 1 evaluation workers, but your `evaluation_interval` is 0 or None! Therefore, evaluation will not occur automatically with each call to `Algorithm.train()`. Instead, you will have to call `Algorithm.evaluate()` manually in order to trigger an evaluation run.
2024-06-24 11:13:20,204	WARNING util.py:61 -- Install gputil for GPU system monitoring.
Execution Time: 119.0861268043518

Das RL-Modell lernt auch hier den Roboter Arm zu kontrolieren und die Kugel aufzunehmen.