Vai al contenuto principale

Algoritmo di Reinforcement Learning SARSA in Python: Guida completa

Impara SARSA, un algoritmo on-policy di reinforcement learning. Comprendi la sua regola di aggiornamento, gli iperparametri e le differenze rispetto al Q-learning con esempi pratici in Python e la sua implementazione.
· 15 min leggi

Scopri con l'IA

Il reinforcement learning (RL) è un potente paradigma di machine learning. Nell'RL, un software, di solito chiamato agente, impara a interagire con ambienti per risolvere problemi complessi tramite tentativi ed errori, il tutto senza intervento umano. Tra gli algoritmi di RL, SARSA si distingue per la sua efficiente natura on-policy.

SARSA sta per State-Action-Reward-State-Action, e rappresenta un ciclo che l'agente segue per risolvere problemi. Questo ciclo consente all'agente di imparare dai propri errori passati e, di tanto in tanto, di avventurarsi a provare cose nuove. Questo comportamento rende l'algoritmo particolarmente efficace in specifici tipi di problemi e lo differenzia dagli algoritmi off-policy come il Q-learning.

In questo tutorial imparerai come funziona SARSA e come implementarlo in Python. Per rendere concreta la comprensione, useremo per tutto il tempo il classico problema del Taxi. Parleremo anche dei vantaggi, dei limiti e delle applicazioni reali di SARSA.

Cos'è SARSA? La risposta breve

SARSA, acronimo di State-Action-Reward-State-Action, descrive una sequenza di eventi in un processo di apprendimento. È un metodo efficace affinché i programmi (agenti) prendano buone decisioni in vari scenari.

L'idea principale alla base di SARSA è il tentativo e l'errore. L'agente compie un'azione in una situazione, osserva il risultato e aggiusta la sua strategia in base all'esito (positivo o negativo). Questo processo si ripete molte volte, portando a miglioramenti nelle decisioni dell'agente nel tempo.

Ciò che rende SARSA speciale tra gli algoritmi RL è che impara dalle scelte effettivamente fatte dall'agente, anche quando sta provando cose nuove. Questo approccio è particolarmente utile quando il percorso di apprendimento è importante quanto il risultato finale.

È come un robot che impara ad andare in bici pedalando davvero, cadute comprese, invece di limitarsi a trovare il percorso più breve da A a B usando rotelle protettive.

Preparare l'ambiente per il tutorial

In questo tutorial useremo ampiamente Numpy e la libreria di reinforcement learning Gymnasium. Mentre Numpy ci aiuterà a scrivere l'algoritmo SARSA, Gymnasium ci fornirà ambienti integrati per testare il nostro lavoro.

Installiamoli in un nuovo ambiente virtuale con i seguenti comandi:

$ 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

Prima di continuare, ti incoraggio vivamente a leggere il nostro Introduction to Reinforcement Learning. Copre le idee fondamentali dell'RL, come agenti, ambienti, il dilemma esplorazione contro sfruttamento e il Q-learning.

Spiegare l'ambiente Taxi-V3

Nel corso del tutorial useremo l'ambiente Taxi-v3, un classico problema di reinforcement learning fornito dalla libreria Gymnasium. Simula un tassista che naviga in un mondo a griglia 5x5 per prelevare e lasciare passeggeri.

Per caricare l'ambiente usiamo il metodo .make() di Gymnasium con una modalità di rendering rgb_array (per visualizzare l'ambiente più avanti):

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

L'ambiente è una griglia 5x5 con quattro posizioni designate: rosso (R), verde (G), giallo (Y) e blu (B). Il taxi parte da una casella casuale e deve prelevare un passeggero in una delle posizioni colorate e lasciarlo in un'altra. 

Il taxi può muoversi a Nord, Sud, Est o Ovest e può anche tentare di prelevare o lasciare un passeggero.

Ecco come possiamo visualizzare lo stato iniziale dell'ambiente con 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([]);

A sample initial state of the taxi ride reinforcement learning problem that will be solved using sarsa

Per prima cosa, reimpostiamo l'ambiente per ottenere uno stato iniziale. Poi, mostriamo questo stato iniziale con la funzione render, ottenendo un array immagine Numpy. La funzione imshow() di Matplotlib prende questo array e produce una visualizzazione pulita senza tacche sugli assi.

Prenditi un momento per comprendere il layout del mondo a griglia Taxi-v3, osservando la posizione del taxi, le barriere, le posizioni dei passeggeri e la destinazione.

L'agente (il taxi) riceve +20 punti per aver lasciato con successo un passeggero. Un prelievo o rilascio illegale comporta una ricompensa di -10. Nel frattempo, ogni passo temporale produce -1 di ricompensa per incoraggiare il taxi a completare il compito rapidamente.

Un singolo episodio dell'ambiente Taxi-v3 termina in caso di rilascio riuscito o quando viene raggiunto il numero massimo di passi temporali.

# 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

Ci sono 500 stati nello spazio degli stati dell'ambiente. Ogni stato è rappresentato da:

  • Riga del taxi (0–4)
  • Colonna del taxi (0–4)
  • Posizione del passeggero (0–3 per R, G, Y, B, oppure 4 se dentro il taxi)
  • Posizione di destinazione (0–3 per R, G, Y, B)
  • Stati totali =  5 (righe)×5 (colonne)×5 (posizioni passeggero)×4 (posizioni destinazione)=500.

I codici delle azioni sono:

  • 0: Muovi a Sud
  • 1: Muovi a Nord
  • 2: Muovi a Est
  • 3: Muovi a Ovest
  • 4: Preleva passeggero
  • 5: Lascia passeggero

L'obiettivo è che l'agente impari una policy ottimale per massimizzare la ricompensa totale prelevando e lasciando passeggeri in modo efficiente.

Ciclo di interazione SARSA

Prima di scrivere l'algoritmo completo, vediamo come funziona il ciclo State-Action-Reward-State-Action usando il nostro ambiente.

Per prima cosa, definiamo quante epoche vogliamo che l'interazione continui. Un singolo episodio rappresenta un'esecuzione completa del compito del taxi, dallo stato iniziale fino a quando non lascia con successo il passeggero o raggiunge il numero massimo di passi temporali. Ogni episodio permette all'agente di imparare dalle proprie esperienze e migliorare la strategia nel tempo.

n_episodes = 5000

Ora scriviamo il ciclo di interazione:

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

Il codice sopra mostra il ciclo di interazione SARSA di base senza implementare alcun algoritmo di apprendimento:

  1. Resetta l'ambiente all'inizio di ogni episodio — (S), env.reset().
  2. Esegui un'azione casuale — (A), env.step(action). Questa versione usa azioni casuali per mostrare il ciclo di interazione.
  3. Ricevi la ricompensa (R) e lo stato successivo (S_1), next_state, reward, ... = env.take(action).
  4. Esegui l'azione (A_1) nel nuovo stato.

Nel ciclo di interazione sopra, il taxi continua a muoversi nell'ambiente in tutte le direzioni ed esegue azioni casuali finché i passi temporali non finiscono.

Animare il ciclo di interazione SARSA

Prima di proseguire, implementiamo una funzionalità per animare i nostri cicli di interazione. Questo ci permette di vedere visivamente il taxi che interagisce con l'ambiente.

Creare l'animazione sarà un processo semplice:

  • A ogni passo temporale, catturiamo lo stato dell'ambiente in un array immagine usando la funzione env.render().
  • Raccogliamo gli array immagine in una variabile separata.
  • Usando una libreria chiamata moviepy, concateneremo tutti gli array immagine per creare una singola GIF.

Modifichiamo il codice:

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 questa versione impostiamo il numero di episodi a uno solo, poiché eseguire il rendering degli stati dell'ambiente come immagini richiede troppo tempo. Creiamo anche una lista vuota per archiviare i nostri array immagine. Poi, il ciclo di interazione continua come prima. L'unica differenza sono le due righe extra di codice in cui renderizziamo l'ambiente come immagine e lo aggiungiamo a frames.

Ora dovremmo avere 201 immagini dentro frames (un'immagine in più per lo stato aggiuntivo):

>>> len(frames)
201

Trasformiamo queste immagini in una GIF usando la libreria moviepy:

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

La funzione create_gif() prende una lista di frame e li converte in una GIF usando la classe ImageSequenceClip di moviepy. Un parametro importante è il numero di frame per secondo, fps, che controlla la durata della GIF. Più frame per secondo, più corta sarà la GIF.

Alla fine, convertiamo i frame di un singolo episodio in una GIF a 25 FPS. Ecco il risultato:

A GIF showing the random interactions of an agent (a taxi) in the taxi ride environment, representing the State-Action-Reward-Action-State cycle

Come puoi vedere, il taxi non ha idea di quello che sta facendo e non si è nemmeno avvicinato al passeggero. Diamogli un po' di cervello aggiungendo SARSA al suo sistema di navigazione.

Implementare SARSA in Python passo dopo passo

Costruiremo il codice di SARSA da zero, così ricorderai chiaramente ogni passaggio.

1. Impostare l'ambiente Gymnasium:

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. Inizializzare una Q-table

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

In questo passaggio introduciamo una nuova struttura dati — la Q-table. Ha dimensioni (numero di stati) x (numero di azioni). La tabella apparirebbe così al nostro agente, il tassista:

Visuali representation of Q-table, which is key for both Q-learning and SARSA reinforcement learning algorithms.

Inizialmente, la Q-table sarà riempita di zeri:

>>> Q_table.shape
(500, 6)

Poi, quando l'agente inizia a interagire con l'ambiente guidato da SARSA, aggiornerà la Q-table con i Q-value. I Q-value sono punteggi che dicono all'agente qual è l'azione migliore da intraprendere, dato lo stato corrente.

3. Definire gli iperparametri di SARSA

Dopo aver inizializzato la Q-table, impostiamo gli iperparametri di SARSA su valori comuni (ne parleremo più avanti):

# 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. Definire l'archiviazione per le metriche di performance

Poi, definiamo due liste per memorizzare le metriche di performance: la ricompensa totale e il numero di passi temporali per ogni episodio. L'obiettivo dell'agente è raccogliere quanta più ricompensa possibile nel minor tempo possibile:

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

5. Strategia epsilon-greedy per scegliere l'azione

Nella sezione precedente, il nostro agente era senza guida — stava eseguendo azioni casuali. Vogliamo cambiare questo dando al conducente una strategia Epsilon-Greedy:

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])

Questa strategia serve a controllare l'equilibrio cruciale tra esplorazione e sfruttamento. Con probabilità epsilon, l'agente esplora l'ambiente compiendo un'azione casuale, mentre con probabilità 1-epsilon sfrutta la conoscenza attuale scegliendo l'azione con il Q-value più alto dalla Q-table. Questo approccio permette all'agente di scoprire nuove strategie potenzialmente migliori e, allo stesso tempo, di usare ciò che ha già imparato.

6. Scrivere il ciclo di training SARSA

Infine, scriviamo il ciclo di training di SARSA. L'inizio del ciclo ci è già familiare. L'unica differenza è che usiamo la funzione epsilon_greedy() per dirci quale azione intraprendere nello stato corrente:

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

Poi avviamo il ciclo while che ci consente di eseguire il ciclo di interazione fino a raggiungere uno stato di terminazione:

# 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)

All'interno del ciclo while, eseguiamo l'azione restituita da epsilon_greedy() e riceviamo lo stato successivo, la ricompensa per la nostra azione e un valore booleano che indica se l'episodio è terminato o meno.

Poi arriviamo alla parte cruciale — la regola di aggiornamento di 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]
       )

La regola di aggiornamento deriva dalla seguente formula:

The formula for SARSA's update rule

[Source]

Impareremo l'intuizione dietro questa formula nella sezione successiva. Per ora, considera la formula come una sorta di magia matematica che aggiorna i Q-value della nostra Q-table secondo le regole di SARSA.

Una volta effettuato l'aggiornamento di Q, cambiamo le variabili state e action allo stato e all'azione risultanti, aggiungiamo la ricompensa ricevuta alla ricompensa totale dell'episodio e aumentiamo il numero di passi temporali.

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

Il ciclo while continua fino a raggiungere il numero massimo di passi (200 per l'ambiente taxi) o quando il taxi lascia con successo il passeggero nella posizione corretta.

Una volta che il ciclo termina, registriamo la ricompensa totale dell'episodio e la durata dell'episodio. Stampiamo anche la ricompensa media e la durata media ogni 1000 episodi:

# 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}")

Ecco il ciclo di interazione completo eseguito:

# 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

Come puoi vedere dall'output, la ricompensa media e il numero di passi per episodio sono diminuiti drasticamente man mano che abbiamo eseguito sempre più episodi.

Possiamo osservarlo visivamente tracciando gli array episode_rewards e 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()

Two plots showing two performance metrics for SARSA: total episode reward and episode lengths

Il grafico a sinistra mostra la ricompensa totale ottenuta in ciascun episodio. Possiamo osservare che:

  • Inizialmente, le ricompense sono basse e molto variabili, a indicare che l'agente sta esplorando e imparando.
  • Col tempo, c'è una tendenza al rialzo delle ricompense, il che suggerisce che l'agente sta migliorando la sua policy.
  • Verso la fine, le ricompense sembrano stabilizzarsi su un livello più alto, a indicare che l'agente ha imparato una policy ragionevolmente buona.

Il grafico a destra mostra il numero di passi compiuti in ogni episodio. Possiamo vedere che:

  • All'inizio, gli episodi tendono a essere più lunghi, poiché l'agente compie azioni subottimali.
  • Con il progredire dell'apprendimento, c'è una tendenza generale alla diminuzione della durata degli episodi.
  • Le durate degli episodi alla fine si stabilizzano, suggerendo che l'agente ha imparato a completare il compito in modo più efficiente.

Organizzare il codice in funzioni

In poco tempo, abbiamo coperto molti argomenti. Facciamo ora un passo indietro e organizziamo tutto ciò che abbiamo scritto. Creeremo funzioni per ciascuno dei passaggi eseguiti per implementare SARSA.

Per prima cosa, una funzione per creare un ambiente:

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)

Poi, una funzione per inizializzare una Q-table dato un ambiente:

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))

La strategia epsilon-greedy che accetta un ambiente, una Q-table, lo stato corrente ed epsilon:

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])

La regola di aggiornamento di SARSA, che richiede una Q-table, lo stato corrente, l'azione intrapresa in quello stato, la ricompensa per l'azione, lo stato e l'azione successivi:

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]
   )

Infine, una grande funzione per addestrare l'agente con SARSA che richiede un ambiente, il numero di episodi da eseguire e i parametri alpha, gamma ed epsilon:

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

Inoltre, una funzione per tracciare le metriche di performance:

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()

La nostra precedente funzione 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)

E un'altra funzione per eseguire un singolo episodio con rendering usando una Q-table completa e addestrata (per scopi di animazione):

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

Ora eseguiamo tutto:

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)

Two plots showing two performance metrics for SARSA: total episode reward and episode lengths

I grafici delle performance sembrano buoni. Ora, guardiamo la GIF generata:

A GIF that shows a successful pick-up and drop-off of a passenger by a reinforcement learning agent taxi trained using the SARSA algorithm

Evviva! Il taxi preleva correttamente il passeggero dalla casella blu e lo lascia in quella gialla.

Ho incollato il codice completo e organizzato per implementare SARSA in un gist su GitHub così puoi tornarci quando vuoi.

L'intuizione dietro la regola di aggiornamento di SARSA

Al cuore di SARSA c'è la sua regola di aggiornamento, che controlla come l'agente impara dalle sue esperienze. Scomponiamo questa regola ed esploriamo l'intuizione che la guida:

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

Comprendere i componenti

  • Q(s, a): Il Q-value corrente dell'eseguire l'azione ‘a’ nello stato ‘s’.
  • α (alpha): Il learning rate, controlla quanto velocemente viene incorporata la nuova informazione.
  • R: La ricompensa ricevuta dopo aver eseguito l'azione.
  • γ (gamma): Il fattore di sconto, determina l'importanza delle ricompense future.
  • Q(s’, a’): Il Q-value stimato della coppia stato-azione successiva.

Il processo di apprendimento

La regola di aggiornamento di SARSA mira a raffinare la comprensione dell'ambiente da parte dell'agente, aggiustando i Q-value in base alle nuove esperienze. Ecco come funziona:

  1. L'agente esegue un'azione e osserva la ricompensa e lo stato successivo.
  2. Calcola la differenza tra il suo Q-value corrente e una nuova stima basata sulla ricompensa osservata e sul valore della coppia stato-azione successiva.
  3. Questa differenza, scalata dal learning rate, viene usata per aggiornare il Q-value.

Errore di differenza temporale

Il termine [R + γ * Q(s’, a’) — Q(s, a)] è noto come errore di differenza temporale (TD). Pensalo come una misura di sorpresa:

  • TD positivo: l'esito è stato migliore del previsto.
  • TD negativo: l'esito è stato peggiore del previsto.
  • TD zero: l'esito ha corrisposto perfettamente alla stima attuale.

Questo errore aiuta l'agente a perfezionare continuamente le sue stime, guidando il processo di apprendimento verso una policy ottimale.

Ruolo degli iperparametri

1. Learning rate (α):

  • Controlla la velocità di apprendimento.
  • α alto: apprendimento più veloce, ma potenzialmente instabile.
  • α basso: apprendimento più lento ma più stabile.

2. Fattore di sconto (γ):

  • Bilancia ricompense immediate e future.
  • γ vicino a 1: l'agente valorizza le ricompense future quasi quanto quelle immediate.
  • γ basso: l'agente si concentra di più sulle ricompense immediate.

3. Tasso di esplorazione (ε):

  • Pur non essendo nella regola di aggiornamento, è cruciale per la policy epsilon-greedy.
  • Bilancia esplorazione (provare nuove azioni) e sfruttamento (usare azioni già note buone).
  • ε alto: più esplorazione, potenzialmente scoprendo strategie migliori a costo di performance a breve termine.

La visione d'insieme

La regola di aggiornamento di SARSA permette all'agente di imparare dalle proprie esperienze aggiustando costantemente le stime dei valori stato-azione. A ogni interazione, l'agente diventa un po' più saggio riguardo al suo ambiente. Nel corso di molti episodi, questo processo porta allo sviluppo di una policy ottimale per navigare nell'ambiente.

Regolando gli iperparametri, possiamo controllare vari aspetti del processo di apprendimento, consentendo a SARSA di adattarsi a diversi tipi di problemi e ambienti. Questa flessibilità, combinata con la sua regola di aggiornamento intuitiva, rende SARSA un algoritmo di reinforcement learning potente e ampiamente usato.

SARSA vs. Q-learning: differenze chiave

Sebbene SARSA e Q-learning siano entrambi algoritmi di reinforcement learning ampiamente utilizzati, presentano alcune importanti differenze. E tali differenze sono fondamentali per capire quando e come usare ciascun algoritmo.

1. On-policy vs. off-policy

Un algoritmo on-policy, SARSA, impara il valore della policy che segue, includendo anche i passi compiuti durante l'esplorazione. Il Q-learning è off-policy; impara il valore della policy ottimale — anche quando non sta seguendo quella policy, continua comunque a imparare il valore della policy ottimale quando alla fine raggiunge la fine dell'episodio. Ecco perché abbiamo detto all'inizio che SARSA va usato quando il percorso di apprendimento è importante quanto l'esito stesso. Il Q-learning non si cura molto del percorso di apprendimento.

2. Regola di aggiornamento

  • 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)]

La differenza chiave è che SARSA usa il Q-value della prossima azione effettivamente intrapresa (a’), mentre il Q-learning usa il Q-value massimo del prossimo stato (max(Q(s’, a’))).

3. Considerazione dell'esplorazione

Nel processo di aggiornamento dei Q-value, SARSA non ignora la policy di esplorazione ed è quindi più conservativo. Al contrario, il Q-learning assume sempre che l'agente intraprenderà l'azione ottimale in futuro ed è quindi più aggressivo.

4. Convergenza

Sebbene entrambi gli algoritmi raggiungano alla fine la policy ottimale, il Q-learning può spesso imparare più rapidamente, in particolare in ambienti deterministici.

5. Sicurezza

In ambienti in cui l'esplorazione può portare a esiti negativi, SARSA apprende generalmente policy più sicure perché tiene conto della policy effettivamente seguita.

Nel classico problema della “cliff walking”, SARSA in genere apprende un percorso più sicuro che resta lontano dal bordo della scogliera, mentre il Q-learning potrebbe apprendere un percorso più rischioso che cammina lungo il bordo.

6. Stabilità

In certi ambienti casuali, SARSA può essere più stabile perché tiene conto della prossima azione effettiva, anche quando è un'azione esplorativa subottimale.

7. Sensibilità agli iperparametri

Il Q-learning può essere più sensibile alla scelta del learning rate e del tasso di esplorazione, specialmente in ambienti con forte componente stocastica.

8. Applicazioni nel mondo reale

Quando si tratta di robotica o altri sistemi fisici in cui esplorare l'ambiente è costoso e vogliamo implementare policy più sicure, potremmo preferire l'algoritmo on-policy SARSA. Quando siamo nel mondo simulato, nei giochi o in altri ambienti in cui possiamo ottenere molti feedback e dove vogliamo trovare la policy ottimale in modo rapido e abbastanza sicuro, potremmo preferire il Q-learning.

Potresti quindi pensare che SARSA sia una sorta di approccio più sicuro, mentre il Q-learning è più veloce e spregiudicato. In pratica, spesso dipende dallo specifico problema. Entrambi gli algoritmi sono molto forti, ed è sempre utile conoscere le differenze tra loro.

Conclusione

In questa guida approfondita, abbiamo esplorato l'algoritmo SARSA, coprendo i suoi concetti chiave, i dettagli di implementazione e le applicazioni pratiche usando l'ambiente Taxi-v3.

I punti chiave includono:

  1. La natura on-policy di SARSA e l'intuizione dietro la sua regola di aggiornamento
  2. Implementazione in Python e tecniche di visualizzazione
  3. Confronto con il Q-learning, evidenziando punti di forza e casi d'uso

La capacità di SARSA di apprendere policy più sicure lo rende adatto ad applicazioni reali in cui i costi di esplorazione sono elevati, anche se in alcuni scenari può convergere più lentamente rispetto ad algoritmi off-policy come il Q-learning.

Ricorda, la scelta tra SARSA e altri algoritmi dipende dallo specifico problema e ambiente. Sperimentazione e fine tuning sono cruciali per risultati ottimali.

Ti invitiamo a costruire su queste basi esplorando ambienti più complessi, implementando SARSA con approssimazione di funzione o approfondendo altri algoritmi di reinforcement learning.

Ecco alcune risorse correlate per aiutarti lungo il percorso:

SARSA FAQs

What is the SARSA algorithm in reinforcement learning?

SARSA (State-Action-Reward-State-Action) è un algoritmo on-policy di reinforcement learning che aggiorna i Q-value in base alle esperienze effettive dell'agente, comprese le azioni esplorative.

How does SARSA differ from Q-learning?

SARSA è on-policy e aggiorna i Q-value usando la prossima azione effettivamente intrapresa, mentre il Q-learning è off-policy e usa il Q-value massimo del prossimo stato, portando a comportamenti di apprendimento diversi.

What are the key hyperparameters in SARSA, and how do they affect learning?

Gli iperparametri chiave sono il learning rate (α), il fattore di sconto (γ) e il tasso di esplorazione (ε). Controllano rispettivamente la velocità di apprendimento, l'importanza delle ricompense future e l'equilibrio tra esplorazione e sfruttamento.

How can I implement the SARSA algorithm in Python?

L'articolo fornisce una guida passo dopo passo per implementare SARSA in Python, compresa la configurazione dell'ambiente, l'inizializzazione della Q-table e la scrittura del ciclo di training.

What are the practical applications of SARSA in real-world scenarios?

SARSA è adatto ad applicazioni in cui i costi di esplorazione sono alti, come la robotica e i sistemi fisici, poiché tende ad apprendere policy più sicure rispetto ad algoritmi off-policy come il Q-learning.

Bexruz (Bex) Tuychiev

Autore

Bexruz (Bex) Tuychiev

Sono un creator di contenuti sulla data science con oltre 2 anni di esperienza e uno dei più seguiti su Medium. Mi piace scrivere articoli dettagliati su AI e ML con un pizzico di sarcasmo, perché bisogna pur fare qualcosa per renderli un po’ meno noiosi. Ho pubblicato oltre 130 articoli e un corso su DataCamp, con un altro in arrivo. I miei contenuti sono stati visti da oltre 5 milioni di persone, 20.000 delle quali sono diventate follower sia su Medium che su LinkedIn. 

Argomenti

Correlato