Ga naar hoofdinhoud

SARSA-reinforcement learning-algoritme in Python: een complete gids

Leer SARSA, een on-policy reinforcement learning-algoritme. Begrijp de update-regel, hyperparameters en verschillen met Q-learning met praktische Python-voorbeelden en de implementatie ervan.
· 15 min lezen

Verken met AI

Reinforcement learning (RL) is een krachtige machinelearning-paradigma. In RL leert software, meestal een agent genoemd, omgaan met omgevingen om complexe problemen op te lossen via trial-and-error, geheel zonder menselijke tussenkomst. Onder de RL-algoritmen valt SARSA op door zijn efficiënte on-policy karakter.

SARSA staat voor State-Action-Reward-State-Action en beschrijft een cyclus die de agent volgt om problemen op te lossen. Deze cyclus stelt de agent in staat te leren van eerdere fouten en af en toe op verkenning te gaan om nieuwe dingen te proberen. Dit gedrag maakt het algoritme bijzonder effectief bij specifieke probleemtypen en onderscheidt het van off-policy algoritmen zoals Q-learning.

In deze tutorial leer je hoe SARSA werkt en hoe je het in Python kunt implementeren. Om je begrip concreet te maken, gebruiken we doorlopend het klassieke Taxi Ride-probleem. We bespreken ook de voordelen, beperkingen en toepassingen van SARSA in de praktijk.

Wat is SARSA? Het korte antwoord

SARSA, kort voor State-Action-Reward-State-Action, beschrijft een sequentie van gebeurtenissen in een leerproces. Het is een effectieve leermethode voor computerprogramma's (agents) om goede beslissingen te nemen in uiteenlopende scenario's.

Het belangrijkste idee achter SARSA is trial-and-error. De agent onderneemt een actie in een situatie, observeert het resultaat en past zijn strategie aan op basis van de uitkomst (goed of slecht). Dit proces wordt vele malen herhaald, waardoor de beslissingen van de agent in de loop van de tijd verbeteren.

Wat SARSA bijzonder maakt onder RL-algoritmen is dat SARSA leert van de daadwerkelijke keuzes van de agent, ook wanneer deze aan het exploreren is. Deze aanpak is vooral nuttig wanneer de leerreis net zo belangrijk is als het eindresultaat.

Het is alsof een robot leert fietsen door echt te fietsen—vallen en opstaan—in plaats van met zijwieltjes de kortste weg van A naar B te zoeken.

Je omgeving instellen voor de tutorial

In deze tutorial gebruiken we Numpy en de Gymnasium-reinforcement learning-bibliotheek uitgebreid. Terwijl Numpy ons helpt het SARSA-algoritme te schrijven, levert Gymnasium ingebouwde omgevingen om ons werk te testen.

Laten we ze installeren in een nieuwe virtuele omgeving met de volgende commando's:

$ conda create -n sarsa python=3.9 -y
$ conda activate sarsa
$ pip install "gymnasium[atari]" numpy matplotlib
$ pip install autorom[accept-rom-license]  # Downloading Gym env data files
$ AutoROM --accept-license  # Accepting the license for data files
$ pip install ipykernel  # Install Jupyter kernel manager
$ ipython kernel install --user --name=sarsa  # Add the new Conda env to Jupyter

Voordat je verdergaat, raad ik je sterk aan ons artikel Introduction to Reinforcement Learning te lezen. Het behandelt de fundamentele ideeën achter RL, zoals agents, omgevingen, het exploratie-versus-exploitatie-dilemma en Q-learning.

De Taxi-V3-omgeving uitgelegd

Door de hele tutorial gebruiken we de Taxi-v3-omgeving, een klassiek reinforcement learning-probleem uit de Gymnasium-bibliotheek. Het simuleert een taxichauffeur die een 5x5 roosterwereld doorkruist om passagiers op te pikken en af te zetten.

Om de omgeving te laden, gebruiken we de .make()-methode van Gymnasium met een rgb_array render-modus (zodat we de omgeving later kunnen visualiseren):

import gymnasium as gym
env = gym.make('Taxi-v3', render_mode='rgb_array')

De omgeving is een 5x5 rooster met vier aangewezen locaties: rood (R), groen (G), geel (Y) en blauw (B). De taxi start op een willekeurig vakje en moet een passagier oppikken op een van de gekleurde locaties en op een andere locatie afzetten. 

De taxi kan naar het noorden, zuiden, oosten of westen bewegen en kan ook proberen een passagier op te pikken of af te zetten.

Zo kunnen we de beginstaat van de omgeving visualiseren met Matplotlib:

import matplotlib.pyplot as plt
# Reset the environment to get an initial state
# Each time we reset the environment, we get a new random state
initial_state, _ = env.reset()
# Render the initial state
img = env.render()
# Create a figure and display the environment
fig, ax = plt.subplots(figsize=(8, 8))
ax.imshow(img)
# Remove axis ticks
ax.set_xticks([])
ax.set_yticks([]);

Een voorbeeld-beginstaat van het taxirit-reinforcement learning-probleem dat met SARSA wordt opgelost

Eerst resetten we de omgeving om een beginstaat te krijgen. Vervolgens tonen we deze beginstaat met de render-functie, die een Numpy-afbeeldingsarray oplevert. Matplotlibs imshow()-functie neemt deze array en produceert een heldere visualisatie zonder asmarkeringen.

Neem even de tijd om de indeling van de Taxi-v3-roosterwereld te begrijpen, let op de positie van de taxi, de barrières, passagierslocaties en de bestemming.

De agent (de taxi) krijgt +20 punten voor het succesvol afzetten van een passagier. Een ongeldige pick-up of drop-off resulteert in -10 beloning. Ondertussen levert elke tijdstap -1 beloning op om de taxi te stimuleren de taak snel te voltooien.

Eén episode van de Taxi-v3-omgeving eindigt bij een succesvolle drop-off of wanneer het maximale aantal tijdstappen is bereikt.

# The number of states and actions
n_states = env.observation_space.n
n_actions = env.action_space.n
print(n_states)
print(n_actions)
500
6

Er zijn 500 toestanden in de toestandsruimte van de omgeving. Elke toestand wordt weergegeven door:

  • Taxi-rij (0–4)
  • Taxi-kolom (0–4)
  • Passagierslocatie (0–3 voor R, G, Y, B, of 4 voor in de taxi)
  • Bestemmingslocatie (0–3 voor R, G, Y, B)
  • Totaal aantal toestanden =  5 (rijen)×5 (kolommen)×5 (passagierslocaties)×4 (bestemmingslocaties)=500.

De actiecodes zijn:

  • 0: Ga naar het zuiden
  • 1: Ga naar het noorden
  • 2: Ga naar het oosten
  • 3: Ga naar het westen
  • 4: Passagier oppikken
  • 5: Passagier afzetten

Het doel is dat de agent een optimale policy leert om zijn totale beloning te maximaliseren door passagiers efficiënt op te pikken en af te zetten.

SARSA-interactie-loop

Voordat we het volledige algoritme schrijven, bekijken we hoe de State-Action-Reward-State-Action-cyclus werkt met onze omgeving.

Eerst definiëren we hoeveel episodes we de interactie willen laten doorgaan. Eén episode vertegenwoordigt een volledige run van de taxitaak, van de beginstaat tot het succesvol afzetten van de passagier of het bereiken van het maximale aantal tijdstappen. Elke episode laat de agent leren van zijn ervaringen en zijn strategie gaandeweg verbeteren.

n_episodes = 5000

Nu schrijven we de interactie-loop:

for episode in range(n_episodes):
   state, _ = env.reset()
   done = False
   total_reward = 0
   steps = 0
   while not done:
       # Choose a random action
       action = env.action_space.sample()
       # Take the action and observe the result
       next_state, reward, terminated, truncated, _ = env.step(action)
       done = terminated or truncated
       # Update total reward and step count
       total_reward += reward
       steps += 1
       # Move to the next state
       state = next_state
   if episode % 1000 == 0:
       print(f"Episode {episode}, Total Reward: {total_reward}, Steps: {steps}")
Episode 0, Total Reward: -812, Steps: 200
Episode 1000, Total Reward: -830, Steps: 200
Episode 2000, Total Reward: -902, Steps: 200
Episode 3000, Total Reward: -522, Steps: 129
Episode 4000, Total Reward: -767, Steps: 200

De bovenstaande code demonstreert de basale SARSA-interactiecyclus zonder een leeralgoritme te implementeren:

  1. Reset de omgeving aan het begin van elke episode — (S), env.reset().
  2. Neem een willekeurige actie — (A), env.step(action). Deze versie gebruikt willekeurige acties om de interactie-loop te tonen.
  3. Ontvang beloning (R) en de volgende toestand (S_1), next_state, reward, ... = env.take(action).
  4. Neem actie (A_1) in de nieuwe toestand.

In de bovenstaande interactie-loop blijft de taxi in alle richtingen door de omgeving bewegen en willekeurige acties uitvoeren totdat de tijdstappen op zijn.

De SARSA-interactie-loop animeren

Voordat we verdergaan, implementeren we een functie om onze interactie-loops te animeren. Hiermee kunnen we visueel zien hoe de taxi met de omgeving interageert.

De animatie maken is eenvoudig:

  • Bij elke tijdstap leggen we de toestand van de omgeving vast in een afbeeldingsarray met de functie env.render().
  • We verzamelen de afbeeldingsarrays in een aparte variabele.
  • Met een bibliotheek genaamd moviepy voegen we alle afbeeldingsarrays samen tot één GIF.

Laten we de code aanpassen:

env = gym.make("Taxi-v3", render_mode="rgb_array")
n_episodes = 1
frames = []  # for animation
for episode in range(n_episodes):
   # Reset the environment
   state, _ = env.reset()
   # Capture the state as an image
   img = env.render()
   frames.append(img)
   done = False
   total_reward = 0
   steps = 0
   while not done:
       # Choose a random action
       action = env.action_space.sample()
       # Take the action and observe the result
       next_state, reward, terminated, truncated, _ = env.step(action)
       done = terminated or truncated
      
       # Capture the next state as an image
       img = env.render()
       frames.append(img)
       # Update total reward and step count
       total_reward += reward
       steps += 1
       # Move to the next state
       state = next_state

In deze versie zetten we het aantal episodes op slechts één, omdat het renderen van omgevingsstaten als afbeeldingen veel tijd kost. We maken ook een lege lijst om onze afbeeldingsarrays op te slaan. Daarna gaat de interactie-loop verder zoals voorheen. Het enige verschil zijn de twee extra regels code waarin we de omgeving als afbeelding renderen en toevoegen aan frames.

Nu zouden we 201 afbeeldingen in frames moeten hebben (één extra afbeelding voor de aanvullende toestand):

>>> len(frames)
201

Laten we deze afbeeldingen omzetten naar een GIF met de moviepy-bibliotheek:

from moviepy.editor import ImageSequenceClip  # pip install moviepy
def create_gif(frames: list, filename, fps=5):
   """
   Creates a GIF animation from a list of RGBA NumPy arrays.
   Args:
       frames: A list of RGBA NumPy arrays representing the animation frames.
       filename: The output filename for the GIF animation.
       fps: The frames per second of the animation (default: 10).
   """
   clip = ImageSequenceClip(frames, fps=fps)
   clip.write_gif(filename, fps=fps)
# Example usage
create_gif(frames, "animation.gif", fps=25)  # saves the GIF locally

De functie create_gif() neemt een lijst met frames en zet deze om in een GIF met behulp van de klasse ImageSequenceClip uit moviepy. Een belangrijke parameter is het aantal frames per seconde, fps, dat de duur van de GIF bepaalt. Hoe meer frames per seconde, hoe korter de GIF.

Uiteindelijk zetten we de frames van één episode om naar een GIF met 25 FPS. Dit is het resultaat:

Een GIF die de willekeurige interacties laat zien van een agent (een taxi) in de taxirit-omgeving, die de State-Action-Reward-Action-State-cyclus weergeeft

Zoals je ziet, heeft de taxi geen idee wat hij doet en kwam hij niet eens in de buurt van de passagier. Laten we hem wat hersens geven door SARSA toe te voegen aan zijn navigatiesysteem.

SARSA stap voor stap in Python implementeren

We bouwen de code voor SARSA from scratch, zodat je elke stap helder onthoudt.

1. De Gymnasium-omgeving instellen:

import gymnasium as gym
import numpy as np
import matplotlib.pyplot as plt
# Create the Taxi environment
env = gym.make("Taxi-v3", render_mode="rgb_array")

2. Een Q-tabel initialiseren

# Initialize Q-table
n_states = env.observation_space.n
n_actions = env.action_space.n
Q_table = np.zeros((n_states, n_actions))

In deze stap introduceren we een nieuwe datastructuur — de Q-tabel. Deze heeft afmetingen (aantal toestanden) x (aantal acties). Zo ziet de tabel eruit voor onze agent, de taxichauffeur:

Visuele weergave van de Q-tabel, die essentieel is voor zowel Q-learning als SARSA-reinforcement learning-algoritmen.

Aanvankelijk is de Q-tabel gevuld met nullen:

>>> Q_table.shape
(500, 6)

Vervolgens, wanneer de agent begint te interageren met de omgeving gestuurd door SARSA, zal hij de Q-tabel bijwerken met Q-waarden. De Q-waarden zijn scores die de agent vertellen welke actie de beste is, gegeven de huidige toestand.

3. SARSA-hyperparameters definiëren

Na het initialiseren van de Q-tabel stellen we de hyperparameters van SARSA in op gangbare waarden (later meer hierover):

# SARSA parameters
alpha = 0.1  # Learning rate
gamma = 0.99  # Discount factor
epsilon = 0.1  # Exploration rate for epsilon-greedy policy
n_episodes = 20000

4. Opslag definiëren voor prestatiemetrics

Daarna definiëren we twee lijsten om prestatiemetrics op te slaan: de totale beloning en het aantal tijdstappen per episode. Het doel van de agent is om zoveel mogelijk beloning te verzamelen in zo weinig mogelijk tijd:

# Lists to store performance metrics
episode_rewards = []
episode_lengths = []

5. Epsilon-greedy policy voor het nemen van acties

In de vorige sectie was onze agent ongeleid — hij nam willekeurige acties. Dat willen we veranderen door de chauffeur een Epsilon-Greedy-strategie te geven:

def epsilon_greedy(state, epsilon):
   if np.random.random() < epsilon:
       # Take random action - explore
       return env.action_space.sample()
   else:
       # Take action with the highest Q-value - exploit
       return np.argmax(Q_table[state])

Deze strategie is bedoeld om de cruciale balans tussen exploratie en exploitatie te sturen. Met kans epsilon verkent de agent de omgeving door een willekeurige actie te nemen, terwijl hij met kans 1-epsilon zijn huidige kennis benut door de actie te kiezen met de hoogste Q-waarde uit de Q-tabel. Deze aanpak laat de agent nieuwe, mogelijk betere strategieën ontdekken en tegelijk benutten wat al is geleerd.

6. De SARSA-trainingsloop schrijven

Tot slot schrijven we de SARSA-trainingsloop. Het begin van de loop is ons al bekend. Het enige verschil is dat we de functie epsilon_greedy() gebruiken om te bepalen welke actie we in de huidige toestand moeten nemen:

# SARSA training loop
for episode in range(n_episodes):
   state, _ = env.reset()
   action = epsilon_greedy(state, epsilon)
   done = False
   total_reward = 0
   steps = 0
   ...

Daarna starten we de while-loop die ons de interactiecyclus laat draaien totdat we een eindtoestand bereiken:

# SARSA training loop
for episode in range(n_episodes):
   ...
   while not done:
       next_state, reward, terminated, truncated, _ = env.step(action)
       done = terminated or truncated
       next_action = epsilon_greedy(next_state, epsilon)

Binnen de while-loop ondernemen we de actie die door epsilon_greedy() wordt teruggegeven en ontvangen we de volgende toestand, de beloning voor onze actie en een booleaanse waarde die aangeeft of de episode is beëindigd.

Dan komen we bij het cruciale onderdeel—de update-regel van SARSA:

# SARSA training loop
for episode in range(n_episodes):
   ...
   while not done:
       next_state, reward, terminated, truncated, _ = env.step(action)
       done = terminated or truncated
       next_action = epsilon_greedy(next_state, epsilon)
       # SARSA update rule
       Q_table[state, action] += alpha * (
           reward + gamma * Q_table[next_state, next_action] - Q_table[state, action]
       )

De update-regel komt uit de volgende formule:

De formule voor de update-regel van SARSA

[Bron]

De intuïtie achter deze formule behandelen we in de volgende sectie. Zie de formule nu als wiskundige magie die de Q-waarden van onze Q-tabel bijwerkt volgens de regels van SARSA.

Zodra we de Q-update hebben gedaan, veranderen we de variabelen state en action naar de resulterende toestand en actie, tellen de ontvangen beloning op bij de totale beloning van de episode en verhogen het aantal tijdstappen.

# SARSA training loop
for episode in range(n_episodes):
   ...
   while not done:
       ...
       state = next_state
       action = next_action
       total_reward += reward
       steps += 1

De while-loop gaat door totdat we het maximum aantal tijdstappen bereiken (200 voor de taxi-omgeving) of wanneer de taxi zijn passagier succesvol op de juiste plek afzet.

Als de loop eindigt, leggen we de totale beloning en de lengte van de episode vast. We printen ook elke 1000 episodes de gemiddelde beloning en lengte:

# SARSA training loop
for episode in range(n_episodes):
   ...
   while not done:
       ...
   episode_rewards.append(total_reward)
   episode_lengths.append(steps)
   if episode % 1000 == 0:
       avg_reward = np.mean(episode_rewards[-1000:])
       avg_length = np.mean(episode_lengths[-1000:])
       print(f"Episode {episode}, Avg Reward: {avg_reward:.2f}, Avg Length: {avg_length:.2f}")

Hier is de volledige interactie-loop uitgevoerd:

# SARSA training loop
for episode in range(n_episodes):
   state, _ = env.reset()
   action = epsilon_greedy(state, epsilon)
   done = False
   total_reward = 0
   steps = 0
   while not done:
       next_state, reward, terminated, truncated, _ = env.step(action)
       done = terminated or truncated
       next_action = epsilon_greedy(next_state, epsilon)
       Q_table[state, action] += alpha * (
           reward + gamma * Q_table[next_state, next_action] - Q_table[state, action]
       )
       state = next_state
       action = next_action
       total_reward += reward
       steps += 1
   episode_rewards.append(total_reward)
   episode_lengths.append(steps)
   if episode % 2000 == 0:
       avg_reward = np.mean(episode_rewards[-1000:])
       avg_length = np.mean(episode_lengths[-1000:])
       print(f"Episode {episode}, Avg Reward: {avg_reward:.2f}, Avg Length: {avg_length:.2f}")
Episode 0, Avg Reward: -551.00, Avg Length: 185.00
Episode 2000, Avg Reward: -4.37, Avg Length: 19.47
Episode 4000, Avg Reward: 1.98, Avg Length: 15.09
Episode 6000, Avg Reward: 2.29, Avg Length: 14.79
Episode 8000, Avg Reward: 2.06, Avg Length: 14.80
Episode 10000, Avg Reward: 2.16, Avg Length: 14.78
Episode 12000, Avg Reward: 2.06, Avg Length: 14.89
Episode 14000, Avg Reward: 2.33, Avg Length: 14.81
Episode 16000, Avg Reward: 2.36, Avg Length: 14.66
Episode 18000, Avg Reward: 2.53, Avg Length: 14.72

Zoals je in de output ziet, daalden de gemiddelde beloning en het aantal tijdstappen per episode drastisch naarmate we meer episodes draaiden.

We kunnen dit visueel zien door de arrays episode_rewards en episode_lengths te plotten:

# Plot the learning curve
plt.figure(figsize=(12, 5))
plt.subplot(1, 2, 1)
plt.plot(episode_rewards)
plt.title("Episode Rewards")
plt.xlabel("Episode")
plt.ylabel("Total Reward")
plt.subplot(1, 2, 2)
plt.plot(episode_lengths)
plt.title("Episode Lengths")
plt.xlabel("Episode")
plt.ylabel("Number of Steps")
plt.tight_layout()
plt.show()

Twee grafieken die twee prestatiemetrics voor SARSA tonen: totale episodebeloning en episodelengtes

De linker grafiek toont de totale beloning per episode. We zien dat:

  • Aanvankelijk zijn de beloningen laag en sterk variabel, wat aangeeft dat de agent aan het exploreren en leren is.
  • In de loop van de tijd is er een opwaartse trend in de beloningen, wat suggereert dat de agent zijn policy verbetert.
  • Tegen het einde lijken de beloningen te stabiliseren op een hoger niveau, wat aangeeft dat de agent een redelijk goede policy heeft geleerd.

De rechter grafiek toont het aantal stappen per episode. We zien dat:

  • In het begin duren episodes langer, omdat de agent suboptimale acties neemt.
  • Naarmate het leren vordert, is er een algemene neerwaartse trend in episodelengte.
  • De episodelengtes stabiliseren uiteindelijk, wat suggereert dat de agent de taak efficiënter uitvoert.

De code organiseren in functies

In korte tijd hebben we veel behandeld. Laten we nu een stap terug doen en alles wat we schreven ordenen. We maken functies voor elk van de stappen die we namen om SARSA te implementeren.

Eerst een functie om een omgeving te creëren:

import gymnasium as gym
import numpy as np
import matplotlib.pyplot as plt
from moviepy.editor import ImageSequenceClip
def create_environment(env_name="Taxi-v3", render_mode="rgb_array"):
   """Create and return a Gymnasium environment."""
   return gym.make(env_name, render_mode=render_mode)

Dan een functie om een Q-tabel te initialiseren voor een gegeven omgeving:

def initialize_q_table(env):
   """Initialize and return a Q-table for the given environment."""
   n_states = env.observation_space.n
   n_actions = env.action_space.n
   return np.zeros((n_states, n_actions))

De Epsilon-greedy-strategie die een omgeving, een Q-tabel, de huidige toestand en epsilon accepteert:

def epsilon_greedy(env, Q_table, state, epsilon=0.1):
   """Epsilon-greedy action selection."""
   if np.random.random() < epsilon:
       return env.action_space.sample()
   else:
       return np.argmax(Q_table[state])

De update-regel van SARSA, die een Q-tabel, de huidige toestand, de genomen actie in die toestand, de beloning voor de actie, de volgende toestand en actie vereist:

def sarsa_update(Q_table, state, action, reward, next_state, next_action, alpha, gamma):
   """Perform SARSA update on Q-table."""
   Q_table[state, action] += alpha * (
       reward + gamma * Q_table[next_state, next_action] - Q_table[state, action]
   )

Tot slot een grote functie om de agent met SARSA te trainen, die een omgeving, het aantal te draaien episodes en de parameters alpha, gamma en epsilon vereist:

def train_sarsa(env, n_episodes=20000, alpha=0.1, gamma=0.99, epsilon=0.1):
   """Train the agent using SARSA algorithm."""
   Q_table = initialize_q_table(env)
   episode_rewards = []
   episode_lengths = []
   for episode in range(n_episodes):
       state, _ = env.reset()
       action = epsilon_greedy(env, Q_table, state, epsilon)
       done = False
       total_reward = 0
       steps = 0
       while not done:
           next_state, reward, terminated, truncated, _ = env.step(action)
           done = terminated or truncated
           next_action = epsilon_greedy(env, Q_table, next_state, epsilon)
           sarsa_update(
               Q_table, state, action, reward, next_state, next_action, alpha, gamma
           )
           state = next_state
           action = next_action
           total_reward += reward
           steps += 1
       episode_rewards.append(total_reward)
       episode_lengths.append(steps)
   return Q_table, episode_rewards, episode_lengths

Ook een functie om prestatiemetrics te plotten:

def plot_learning_curve(episode_rewards, episode_lengths):
   """Plot the learning curve."""
   plt.figure(figsize=(12, 5))
   plt.subplot(1, 2, 1)
   plt.plot(episode_rewards)
   plt.title("Episode Rewards")
   plt.xlabel("Episode")
   plt.ylabel("Total Reward")
   plt.subplot(1, 2, 2)
   plt.plot(episode_lengths)
   plt.title("Episode Lengths")
   plt.xlabel("Episode")
   plt.ylabel("Number of Steps")
   plt.tight_layout()
   plt.show()

Onze eerdere functie create_gif():

def create_gif(frames, filename, fps=5):
   """Creates a GIF animation from a list of frames."""
   clip = ImageSequenceClip(frames, fps=fps)
   clip.write_gif(filename, fps=fps)

En nog een functie om een enkele episode te draaien met rendering met een complete, getrainde Q-tabel (voor animatiedoeleinden):

def run_episode(env, Q_table, epsilon=0):
   """Run a single episode using the learned Q-table."""
   state, _ = env.reset()
   done = False
   total_reward = 0
   frames = [env.render()]
   while not done:
       action = epsilon_greedy(env, Q_table, state, epsilon)
       next_state, reward, terminated, truncated, _ = env.step(action)
       done = terminated or truncated
       frames.append(env.render())
       total_reward += reward
       state = next_state
   return frames, total_reward

Laten we nu alles uitvoeren:

if __name__ == "__main__":
   env = create_environment()
  
   Q_table, episode_rewards, episode_lengths = train_sarsa(env, n_episodes=20000)
   plot_learning_curve(episode_rewards, episode_lengths)
  
   frames, total_reward = run_episode(env, Q_table)
   create_gif(frames, "images/sarsa_final_animation.gif", fps=1)

Twee grafieken die twee prestatiemetrics voor SARSA tonen: totale episodebeloning en episodelengtes

De prestatiegrafieken zien er goed uit. Laten we nu naar de gegenereerde GIF kijken:

Een GIF die een succesvolle pick-up en drop-off laat zien van een passagier door een taxi-agent getraind met het SARSA-algoritme

Hoera! De taxi pikt de passagier correct op bij het blauwe vak en zet hem af bij het gele vak.

Ik heb de volledig geordende code voor het implementeren van SARSA in een GitHub gist geplakt, zodat je er altijd op kunt teruggrijpen.

De intuïtie achter SARSA’s update-regel

In het hart van SARSA ligt de update-regel, die bepaalt hoe de agent leert van zijn ervaringen. Laten we deze regel opsplitsen en de intuïtie erachter verkennen:

Q(s, a) = Q(s, a) + α [R + γ Q(s', a') - Q(s, a)]

De componenten begrijpen

  • Q(s, a): De huidige Q-waarde van het nemen van actie ‘a’ in toestand ‘s’.
  • α (alpha): Het leerrendement, bepaalt hoe snel nieuwe informatie wordt opgenomen.
  • R: De beloning die wordt ontvangen na het nemen van de actie.
  • γ (gamma): De discontovoet, bepaalt het belang van toekomstige beloningen.
  • Q(s’, a’): De geschatte Q-waarde van het volgende toestand-actie-paar.

Het leerproces

De update-regel van SARSA verfijnt het begrip van de agent van de omgeving door Q-waarden aan te passen op basis van nieuwe ervaringen. Zo werkt het:

  1. De agent neemt een actie en observeert de beloning en de volgende toestand.
  2. Hij berekent het verschil tussen zijn huidige Q-waarde en een nieuwe Q-waardeschatting op basis van de geobserveerde beloning en de waarde van het volgende toestand-actie-paar.
  3. Dit verschil, geschaald door het leerrendement, wordt gebruikt om de Q-waarde bij te werken.

Temporal difference error

De term [R + γ * Q(s’, a’) — Q(s, a)] staat bekend als de temporal difference (TD) error. Zie het als een maat voor verrassing:

  • Positieve TD-error: De uitkomst was beter dan verwacht.
  • Negatieve TD-error: De uitkomst was slechter dan verwacht.
  • TD-error nul: De uitkomst kwam perfect overeen met de huidige schatting.

Deze fout helpt de agent zijn schattingen continu te verfijnen en stuurt het leerproces richting een optimale policy.

Rol van hyperparameters

1. Leerrendement (α):

  • Bepaalt de snelheid van leren.
  • Hogere α: Sneller leren, maar mogelijk onstabiel.
  • Lagere α: Langzamer, stabieler leren.

2. Discontovoet (γ):

  • Balanceert directe en toekomstige beloningen.
  • γ dicht bij 1: De agent waardeert toekomstige beloningen bijna evenveel als directe.
  • Lagere γ: De agent focust meer op directe beloningen.

3. Exploratiegraad (ε):

  • Hoewel niet in de update-regel zelf, is deze cruciaal voor de epsilon-greedy policy.
  • Balanceert exploratie (nieuwe acties proberen) en exploitatie (bekend goede acties gebruiken).
  • Hogere ε: Meer exploratie, mogelijk betere strategieën ontdekken ten koste van kortetermijnprestatie.

Het grote plaatje

De update-regel van SARSA laat de agent leren van zijn ervaringen door voortdurend zijn schattingen van toestand-actie-waarden aan te passen. Met elke interactie wordt de agent een beetje wijzer over zijn omgeving. Over vele episodes leidt dit tot de ontwikkeling van een optimale policy voor het navigeren door de omgeving.

Door de hyperparameters af te stemmen, sturen we verschillende aspecten van het leerproces, waardoor SARSA zich kan aanpassen aan verschillende soorten problemen en omgevingen. Deze flexibiliteit, gecombineerd met de intuïtieve update-regel, maakt SARSA tot een krachtig en veelgebruikt reinforcement learning-algoritme.

SARSA vs. Q-learning: de belangrijkste verschillen

Hoewel SARSA en Q-learning beide veelgebruikte reinforcement learning-algoritmen zijn, hebben ze enkele belangrijke verschillen. Die verschillen zijn cruciaal om te begrijpen wanneer en hoe je elk algoritme gebruikt.

1. On-policy vs. off-policy

Een on-policy algoritme, SARSA, leert de waarde van de policy die het volgt, inclusief de stappen tijdens exploratie. Q-learning is off-policy; het leert de waarde van de optimale policy—zelfs als het die policy niet volgt, leert het toch de waarde van de optimale policy wanneer het uiteindelijk het einde van de episode bereikt. Daarom noemden we aan het begin dat SARSA moet worden gebruikt wanneer de leerreis net zo belangrijk is als de uitkomst zelf. Q-learning geeft minder om de leerreis.

2. Update-regel

  • SARSA: Q(s, a) = Q(s, a) + α * [R + γ * Q(s’, a’) — Q(s, a)]
  • Q-learning: Q(s, a) = Q(s, a) + α * [R + γ * max(Q(s’, a’)) — Q(s, a)]

Het belangrijkste verschil is dat SARSA de Q-waarde gebruikt van de daadwerkelijk genomen volgende actie (a’), terwijl Q-learning de maximale Q-waarde van de volgende toestand gebruikt (max(Q(s’, a’))).

3. Overweging van exploratie

Bij het updaten van Q-waarden negeert SARSA de exploratiepolicy niet en is daardoor voorzichtiger. Q-learning daarentegen gaat er altijd van uit dat de agent in de toekomst de optimale actie zal nemen en is dus agressiever.

4. Convergentie

Hoewel beide algoritmen uiteindelijk de optimale policy bereiken, kan Q-learning vaak sneller leren, vooral in deterministische omgevingen.

5. Veiligheid

In omgevingen waar exploratie tot slechte uitkomsten kan leiden, leert SARSA over het algemeen veiligere policies omdat het rekening houdt met de daadwerkelijke gevolgde policy.

In het klassieke “cliff walking”-probleem leert SARSA doorgaans een veiliger pad dat wegblijft van de rand van de klif, terwijl Q-learning een riskanter pad kan leren dat langs de rand loopt.

6. Stabiliteit

In bepaalde willekeurige omgevingen kan SARSA stabieler zijn omdat het rekening houdt met de daadwerkelijke volgende actie, zelfs wanneer die een suboptimale exploratie-actie is.

7. Gevoeligheid voor hyperparameters

Q-learning kan gevoeliger zijn voor de keuze van leerrendement en exploratiegraad, vooral in omgevingen met aanzienlijke stochastiek.

8. Toepassingen in de praktijk

Bij robotica of andere fysieke systemen waar het verkennen van de omgeving kostbaar is en we veiligere policies willen implementeren, geven we mogelijk de voorkeur aan het on-policy algoritme SARSA. In de gesimuleerde wereld, in games of in andere omgevingen waar we veel feedback kunnen krijgen en snel en redelijk veilig de optimale policy willen vinden, is Q-learning vaak aantrekkelijker.

Je zou dus kunnen denken dat SARSA min of meer het veiligere domein is, en Q-learning de snellere, roekelozere aanpak. In de praktijk hangt het vaak af van het specifieke probleem. Beide algoritmen zijn erg sterk, en het is altijd zinvol de verschillen ertussen te kennen.

Conclusie

In deze uitgebreide gids hebben we het SARSA-algoritme verkend, met de kernconcepten, implementatiedetails en praktische toepassingen in de Taxi-v3-omgeving.

Belangrijkste inzichten:

  1. Het on-policy karakter van SARSA en de intuïtie achter de update-regel
  2. Python-implementatie en visualisatietechnieken
  3. Vergelijking met Q-learning, met nadruk op sterke punten en use-cases

Het vermogen van SARSA om veiligere policies te leren maakt het geschikt voor toepassingen in de echte wereld waar exploratiekosten hoog zijn, al kan het in sommige scenario’s trager convergeren dan off-policy algoritmen zoals Q-learning.

Onthoud dat de keuze tussen SARSA en andere algoritmen afhangt van je specifieke probleem en omgeving. Experimenteren en fijn afstemmen zijn cruciaal voor optimale resultaten.

We moedigen je aan om op deze basis voort te bouwen door complexere omgevingen te verkennen, SARSA met functie-approximatie te implementeren of je te verdiepen in andere reinforcement learning-algoritmen.

Hier zijn enkele gerelateerde bronnen om je op weg te helpen:

SARSA Veelgestelde vragen

Wat is het SARSA-algoritme in reinforcement learning?

SARSA (State-Action-Reward-State-Action) is een on-policy reinforcement learning-algoritme dat Q-waarden bijwerkt op basis van de daadwerkelijke ervaringen van de agent, inclusief exploratieve acties.

Hoe verschilt SARSA van Q-learning?

SARSA is on-policy en werkt Q-waarden bij met de daadwerkelijk genomen volgende actie, terwijl Q-learning off-policy is en de maximale Q-waarde van de volgende toestand gebruikt, wat tot verschillend leergedrag leidt.

Wat zijn de belangrijkste hyperparameters in SARSA en hoe beïnvloeden ze het leren?

De sleutel-hyperparameters zijn het leerrendement (α), de discontovoet (γ) en de exploratiegraad (ε). Ze bepalen respectievelijk de leersnelheid, het belang van toekomstige beloningen en de balans tussen exploratie en exploitatie.

Hoe kan ik het SARSA-algoritme in Python implementeren?

Het artikel biedt een stapsgewijze gids om SARSA in Python te implementeren, inclusief het instellen van de omgeving, het initialiseren van de Q-tabel en het schrijven van de trainingsloop.

Wat zijn de praktische toepassingen van SARSA in realistische scenario’s?

SARSA is geschikt voor toepassingen waar exploratie kostbaar is, zoals robotica en fysieke systemen, omdat het de neiging heeft veiligere policies te leren dan off-policy algoritmen zoals Q-learning.

Bexruz (Bex) Tuychiev

Auteur

Bexruz (Bex) Tuychiev

Ik ben een contentmaker op het gebied van data science met meer dan 2 jaar ervaring en een van de grootste achterbannen op Medium. Ik schrijf graag uitgebreide artikelen over AI en ML met een vleugje sarcastische stijl, want je moet toch íets doen om ze wat minder saai te maken. Ik heb meer dan 130 artikelen en een DataCamp-cursus gemaakt, en er is nog een nieuwe in de maak. Mijn content is door meer dan 5 miljoen paar ogen gezien, van wie 20k volgers werden op zowel Medium als LinkedIn. 

Onderwerpen