Corso

Il reinforcement learning (RL) è l’area del machine learning in cui un agente impara interagendo con l’ambiente per ottenere la strategia ottimale per raggiungere gli obiettivi. È piuttosto diverso dagli algoritmi di machine learning supervisionato, dove dobbiamo ingerire ed elaborare i dati. Il reinforcement learning non richiede dati preesistenti: impara dall’ambiente e dal sistema di ricompense per prendere decisioni migliori.
Per esempio, nel videogioco di Mario, se un personaggio compie un’azione casuale (ad es. si muove a sinistra), in base a quell’azione può ricevere una ricompensa. Dopo aver agito, l’agente (Mario) si trova in un nuovo stato e il processo si ripete finché il personaggio non raggiunge la fine del livello o muore.
Questo episodio si ripeterà più volte finché Mario non impara a muoversi nell’ambiente massimizzando le ricompense.

Immagine dell’autore
Possiamo scomporre il reinforcement learning in cinque semplici passaggi:
- L’agente si trova nello stato zero in un ambiente.
- Esegue un’azione basata su una strategia specifica.
- Riceve una ricompensa o una penalità in base a quell’azione.
- Impara dalle mosse precedenti e ottimizza la strategia.
- Il processo si ripete finché non si trova una strategia ottimale.
Per saperne di più, leggi il nostro tutorial, un’Introduzione al Reinforcement Learning. Esplorerai meglio come funziona il reinforcement learning con esempi di codice.
In questo tutorial impareremo il Q-learning e capiremo perché serve il Deep Q-learning. Inoltre, creeremo e alleneremo algoritmi di Q-learning da zero usando Numpy e Gymnasium.
Nota: se sei alle prime armi con il machine learning, ti consigliamo di seguire il nostro percorso di carriera Machine Learning Scientist with Python per comprendere meglio Reinforcement learning e Q-Learning.
Che cos’è il Q-Learning?
Il Q-learning è un algoritmo model-free, value-based e off-policy che trova la migliore sequenza di azioni in base allo stato attuale dell’agente. La “Q” sta per quality. La qualità rappresenta quanto sia utile un’azione nel massimizzare le ricompense future.
Gli algoritmi model-based usano funzioni di transizione e ricompensa per stimare la policy ottimale e creare il modello. Al contrario, gli algoritmi model-free apprendono le conseguenze delle loro azioni attraverso l’esperienza, senza funzioni di transizione e ricompensa.
Il metodo value-based allena la funzione di valore per imparare quali stati sono più preziosi e agire di conseguenza. I metodi policy-based, invece, allenano direttamente la policy per imparare quale azione intraprendere in un dato stato.
Nell’off-policy, l’algoritmo valuta e aggiorna una policy diversa da quella usata per agire. Al contrario, l’algoritmo on-policy valuta e migliora la stessa policy usata per agire.
Terminologia chiave nel Q-learning
Prima di vedere come funziona il Q-learning, dobbiamo chiarire alcune terminologie utili per comprenderne le basi.
- Stati (s): la posizione attuale dell’agente nell’ambiente.
- Azione (a): un passo compiuto dall’agente in un particolare stato.
- Ricompense: per ogni azione, l’agente riceve una ricompensa o una penalità.
- Episodi: la fine del livello, quando l’agente non può più compiere nuove azioni. Succede quando l’agente ha raggiunto l’obiettivo o ha fallito.
- Q(St+1, a): valore Q ottimale atteso dell’eseguire l’azione in un particolare stato.
- Q(St, At): stima attuale di Q(St+1, a).
- Q-Table: la tabella che l’agente mantiene con gli insiemi di stati e azioni.
- Temporal Differences (TD): usato per stimare il valore atteso di Q(St+1, a) usando stato e azione correnti e quelli precedenti.
Come funziona il Q-Learning?
Vedremo nel dettaglio come funziona il Q-learning usando l’esempio di un lago ghiacciato. In questo ambiente, l’agente deve attraversare il lago ghiacciato dalla partenza all’obiettivo, senza cadere nelle buche. La strategia migliore è raggiungere l’obiettivo seguendo il percorso più breve.

Gif dell’autore
Q-Table
L’agente userà una Q-table per scegliere l’azione migliore possibile in base alla ricompensa attesa per ogni stato nell’ambiente. In parole semplici, una Q-table è una struttura dati di insiemi di azioni e stati, e usiamo l’algoritmo di Q-learning per aggiornare i valori nella tabella.
Q-Function
La Q-function usa l’equazione di Bellman e prende stato (s) e azione (a) in input. L’equazione semplifica il calcolo dei valori di stato e dei valori stato-azione. 
Immagine da freecodecamp.org
Algoritmo di Q-learning

Immagine dell’autore
Inizializzare la Q-Table
Per prima cosa inizializzeremo la Q-table. Costruiremo la tabella con colonne basate sul numero di azioni e righe sul numero di stati.
Nel nostro esempio, il personaggio può muoversi su, giù, sinistra e destra. Abbiamo quattro azioni possibili e quattro stati (inizio, inattivo, percorso sbagliato e fine). Puoi considerare il percorso sbagliato come la caduta in una buca. Inizializzeremo la Q-Table con valori pari a 0.

Immagine dell’autore
Scegliere un’azione
Il secondo passaggio è abbastanza semplice. All’inizio, l’agente sceglierà un’azione casuale (giù o destra) e, alla seconda esecuzione, userà la Q-Table aggiornata per selezionare l’azione.
Eseguire un’azione
Scegliere ed eseguire l’azione si ripeterà più volte finché il ciclo di training non si interrompe. La prima azione e il primo stato sono selezionati usando la Q-Table. Nel nostro caso, tutti i valori della Q-Table sono zero.
Poi l’agente si muoverà verso il basso e aggiornerà la Q-Table usando l’equazione di Bellman. A ogni mossa, aggiorneremo i valori nella Q-Table e li useremo anche per determinare la migliore linea d’azione.
Inizialmente, l’agente è in modalità esplorazione e sceglie un’azione casuale per esplorare l’ambiente. La strategia Epsilon Greedy è un metodo semplice per bilanciare esplorazione ed exploitation. Epsilon rappresenta la probabilità di scegliere di esplorare e l’exploitation quando le possibilità di esplorare sono più piccole.
All’inizio, il tasso di epsilon è più alto, quindi l’agente è in modalità esplorazione. Mentre esplora l’ambiente, l’epsilon diminuisce e l’agente inizia a sfruttare l’ambiente. Durante l’esplorazione, a ogni iterazione, l’agente diventa più sicuro nella stima dei valori Q.

Immagine dell’autore
Nell’esempio del lago ghiacciato, l’agente non conosce l’ambiente, quindi all’inizio compie un’azione casuale (muoversi in giù). Come si vede nell’immagine sopra, la Q-Table viene aggiornata usando l’equazione di Bellman.
Misurare le ricompense
Dopo aver compiuto l’azione, misureremo l’esito e la ricompensa.
- La ricompensa per raggiungere l’obiettivo è +1
- La ricompensa per prendere il percorso sbagliato (cadere nella buca) è 0
- La ricompensa per rimanere inattivo o muoversi sul lago ghiacciato è anch’essa 0.
Aggiornare la Q-Table
Aggiorneremo la funzione Q(St, At) usando l’equazione. Usa i valori Q stimati nell’episodio precedente, il learning rate e l’errore di Temporal Differences. L’errore di Temporal Differences è calcolato usando la ricompensa immediata, la ricompensa futura attesa massima scontata e la precedente stima del valore Q.
Il processo si ripete più volte finché la Q-Table non è aggiornata e la funzione di valore Q è massimizzata.

Immagine dell’autore | Visual della formula di Thomas Simonini
All’inizio, l’agente esplora l’ambiente per aggiornare la Q-table. E quando la Q-Table è pronta, l’agente inizia a sfruttarla e a prendere decisioni migliori. 
Immagine dell’autore
Nel caso del lago ghiacciato, l’agente imparerà a prendere il percorso più breve per raggiungere l’obiettivo ed evitare di saltare nelle buche.
Tutorial Q-Learning in Python
In questa sezione costruiremo il nostro modello di Q-learning da zero usando l’ambiente Gymnasium, Pygame e Numpy. Il tutorial in Python è una versione modificata del Notebook di Thomas Simonini. Include l’inizializzazione dell’ambiente e della Q-Table, la definizione della policy greedy, l’impostazione degli iperparametri, la creazione ed esecuzione del ciclo di training e della valutazione, e la visualizzazione dei risultati.
Se riscontri problemi nel creare ed eseguire il tuo ciclo di training, puoi consultare il codice sorgente con l’output.
Configurazione
Configura un display virtuale
Per prima cosa installeremo tutte le dipendenze per generare un video di replay (Gif). Ci serve uno schermo virtuale (pyvirtualdisplay) per renderizzare l’ambiente e registrare i frame.
Nota: usando %%capture sopprimiamo l’output della cella Jupyter.
%%capture
!pip install pyglet==1.5.1
!apt install python-opengl
!apt install ffmpeg
!apt install xvfb
!pip3 install pyvirtualdisplay
# Virtual display
from pyvirtualdisplay import Display
virtual_display = Display(visible=0, size=(1400, 900))
virtual_display.start()
Installa le dipendenze
Ora installeremo le dipendenze che ci aiuteranno a creare, eseguire e valutare il ciclo di training.
- gymnasium: usato per inizializzare l’ambiente FrozenLake-v1.
- pygame: usato per l’interfaccia di FrozenLake-v1.
- numPy: usato per creare e gestire la Q-table.
%%capture
!pip install gymnasium
!pip install pygame
!pip install numpy
!pip install imageio imageio_ffmpeg
Importa i pacchetti
Ora importeremo le librerie necessarie.
- Imageio è usata per creare l’animazione.
- tqdm è usata per le barre di avanzamento.
import numpy as np
import gymnasium as gym
import random
import imageio
from tqdm.notebook import trange
Ambiente Gymnasium Frozen Lake
Creeremo un ambiente 4x4 non scivoloso usando la libreria Frozen Lake di Gymnasium.
- Ci sono due versioni della griglia, “4x4” e “8x8”.
- Se
is_slippery=True, l’agente potrebbe non muoversi nella direzione prevista a causa della natura scivolosa del lago ghiacciato.
Dopo aver inizializzato l’ambiente, faremo un’analisi dell’ambiente.
env = gym.make("FrozenLake-v1",map_name="4x4",is_slippery=False)
print("Observation Space", env.observation_space)
print("Sample observation", env.observation_space.sample()) # display a random observation
Ci sono 16 spazi unici nell’ambiente mostrati in posizioni casuali.
Observation Space Discrete(16)
Sample observation 15
Scopriamo il numero di azioni e mostriamo un’azione casuale.
Action space:
- 0: muovi a sinistra
- 1: muovi in giù
- 2: muovi a destra
- 3: muovi in su
Funzione di ricompensa:
- Raggiungere l’obiettivo: +1
- Cadere nella buca: 0
- Rimanere sul lago ghiacciato: 0
print("Action Space Shape", env.action_space.n)
print("Action Space Sample", env.action_space.sample())
Action Space Shape 4
Action Space Sample 1
Crea e inizializza la Q-table
La Q-Table ha come colonne le azioni e come righe gli stati. Possiamo usare Gymnasium per trovare action space e state space. Useremo poi queste informazioni per creare la Q-Table.
state_space = env.observation_space.n
print("There are ", state_space, " possible states")
action_space = env.action_space.n
print("There are ", action_space, " possible actions")
There are 16 possible states
There are 4 possible actions
Per inizializzare la Q-Table, creeremo un array Numpy di state_space e action space. Creeremo un array 16 X 4.
def initialize_q_table(state_space, action_space):
Qtable = np.zeros((state_space, action_space))
return Qtable
Qtable_frozenlake = initialize_q_table(state_space, action_space)
Policy epsilon-greedy
Nella sezione precedente abbiamo visto la strategia epsilon greedy che gestisce il compromesso tra esplorazione ed exploitation. Con probabilità 1 - ɛ, facciamo exploitation, e con probabilità ɛ, facciamo esplorazione.
Nella epsilon_greedy_policy faremo:
- Generare un numero casuale tra 0 e 1.
- Se il numero casuale è maggiore di epsilon, faremo exploitation. Significa che l’agente sceglierà l’azione con il valore più alto dato uno stato.
- Altrimenti, faremo esplorazione (azione casuale).
def epsilon_greedy_policy(Qtable, state, epsilon):
random_int = random.uniform(0,1)
if random_int > epsilon:
action = np.argmax(Qtable[state])
else:
action = env.action_space.sample()
return action
Definire la policy greedy
Ora sappiamo che il Q-learning è un algoritmo off-policy, il che significa che la policy usata per agire e quella usata per aggiornare la funzione sono diverse.
In questo esempio, la policy Epsilon Greedy è la policy di azione e la policy Greedy è quella di aggiornamento.
La policy Greedy sarà anche la policy finale quando l’agente è addestrato. Si usa per selezionare dallo stato il valore di azione più alto nella Q-Table.
def greedy_policy(Qtable, state):
action = np.argmax(Qtable[state])
return action
Iperparametri del modello
Questi iperparametri sono usati nel ciclo di training e ottimizzarli ti darà risultati migliori.
L’agente deve esplorare a sufficienza lo spazio degli stati per imparare una buona approssimazione dei valori; serve quindi un decadimento progressivo di epsilon. Se il tasso di decadimento è alto, l’agente potrebbe bloccarsi perché non ha esplorato abbastanza lo spazio degli stati.
- Ci sono 10.000 episodi di training e 100 di valutazione.
- Il learning rate è 0,7.
- Usiamo "FrozenLake-v1" come ambiente con 99 passi massimi per episodio.
- Il gamma (tasso di sconto) è 0,95.
- eval_seed: seed di valutazione per l’ambiente.
- La probabilità epsilon di esplorazione all’inizio è 1,0, e la probabilità minima sarà 0,05.
- Il tasso di decadimento esponenziale per la probabilità epsilon è 0,0005.
# Training parameters
n_training_episodes = 10000
learning_rate = 0.7
# Evaluation parameters
n_eval_episodes = 100
# Environment parameters
env_id = "FrozenLake-v1"
max_steps = 99
gamma = 0.95
eval_seed = []
# Exploration parameters
max_epsilon = 1.0
min_epsilon = 0.05
decay_rate = 0.0005
Training del modello
Nel ciclo di training, faremo:
- Creare un ciclo per gli episodi di training.
- Ridurre per prima cosa epsilon. Con ogni episodio serve meno esplorazione e più exploitation.
- Resettare l’ambiente.
- Creare un ciclo annidato per i passi massimi.
- Scegliere l’azione usando la policy epsilon greedy.
- Eseguire l’azione (At) e osservare la ricompensa attesa (Rt+1) e lo stato (St+1).
- Eseguire l’azione (a) e osservare lo stato di esito (s') e la ricompensa (r).
- Aggiornare la funzione Q usando la formula.
- Se
done= True, terminare l’episodio e uscire dal ciclo. - Infine, cambiare lo stato corrente con il nuovo stato.
- Dopo aver completato tutti gli episodi di training, la funzione restituirà la Q-Table aggiornata.
def train(n_training_episodes, min_epsilon, max_epsilon, decay_rate, env, max_steps, Qtable):
for episode in trange(n_training_episodes):
epsilon = min_epsilon + (max_epsilon - min_epsilon)*np.exp(-decay_rate*episode)
# Reset the environment
state = env.reset()
step = 0
done = False
# repeat
for step in range(max_steps):
action = epsilon_greedy_policy(Qtable, state, epsilon)
new_state, reward, done, info = env.step(action)
Qtable[state][action] = Qtable[state][action] + learning_rate * (reward + gamma * np.max(Qtable[new_state]) - Qtable[state][action])
# If done, finish the episode
if done:
break
# Our state is the new state
state = new_state
return Qtable
Ci sono voluti 3 secondi per completare 10.000 episodi di training.
Qtable_frozenlake = train(n_training_episodes, min_epsilon, max_epsilon, decay_rate, env, max_steps, Qtable_frozenlake)
Come possiamo vedere, la Q-Table addestrata ha dei valori e l’agente ora userà questi valori per muoversi nell’ambiente e raggiungere l’obiettivo.
Qtable_frozenlakearray([[0.73509189, 0.77378094, 0.77378094, 0.73509189], [0.73509189, 0. , 0.81450625, 0.77378094], [0.77378094, 0.857375 , 0.77378094, 0.81450625], [0.81450625, 0. , 0.77378094, 0.77378094], [0.77378094, 0.81450625, 0. , 0.73509189], [0. , 0. , 0. , 0. ], [0. , 0.9025 , 0. , 0.81450625], [0. , 0. , 0. , 0. ], [0.81450625, 0. , 0.857375 , 0.77378094], [0.81450625, 0.9025 , 0.9025 , 0. ], [0.857375 , 0.95 , 0. , 0.857375 ], [0. , 0. , 0. , 0. ], [0. , 0. , 0. , 0. ], [0. , 0.9025 , 0.95 , 0.857375 ], [0.9025 , 0.95 , 1. , 0.9025 ], [0. , 0. , 0. , 0. ]])Valutazione
La funzione evaluate_agent esegue per
n_eval_episodesepisodi e restituisce media e deviazione standard della ricompensa.
- Nel ciclo, per prima cosa verificheremo se c’è un seed di valutazione. In caso contrario, resetteremo l’ambiente senza seed.
- Il ciclo annidato verrà eseguito fino a max_steps.
- L’agente eseguirà l’azione che ha la massima ricompensa futura attesa in un dato stato usando la Q-Table.
- Calcolare la ricompensa.
- Cambiare lo stato.
- Se done (l’agente cade nella buca o l’obiettivo è stato raggiunto), interrompere il ciclo.
- Accodare i risultati.
- Alla fine useremo questi risultati per calcolare media e deviazione standard.
def evaluate_agent(env, max_steps, n_eval_episodes, Q, seed): episode_rewards = [] for episode in range(n_eval_episodes): if seed: state = env.reset(seed=seed[episode]) else: state = env.reset() step = 0 done = False total_rewards_ep = 0 for step in range(max_steps): # Take the action (index) that have the maximum reward action = np.argmax(Q[state][:]) new_state, reward, done, info = env.step(action) total_rewards_ep += reward if done: break state = new_state episode_rewards.append(total_rewards_ep) mean_reward = np.mean(episode_rewards) std_reward = np.std(episode_rewards) return mean_reward, std_rewardCome puoi vedere, abbiamo ottenuto il punteggio perfetto con deviazione standard zero. Significa che il nostro agente ha raggiunto l’obiettivo in tutti i 100 episodi.
# Evaluate our Agent mean_reward, std_reward = evaluate_agent(env, max_steps, n_eval_episodes, Qtable_frozenlake, eval_seed) print(f"Mean_reward={mean_reward:.2f} +/- {std_reward:.2f}")Mean_reward=1.00 +/- 0.00Visualizzare il risultato
Finora abbiamo lavorato con numeri; per una demo dobbiamo creare una Gif animata dell’agente dall’inizio fino al raggiungimento dell’obiettivo.
- Per prima cosa creeremo lo stato resettando l’ambiente con un intero casuale 0-500.
- Renderizzeremo l’ambiente usando rdb_array per creare un array di immagini.
- Poi aggiungeremo
imgall’arrayimages.- Nel ciclo, faremo lo step usando la Q-Table e renderizzeremo l’immagine a ogni passo.
- Alla fine useremo questo array e imageio per creare una Gif a un fotogramma al secondo.
def record_video(env, Qtable, out_directory, fps=1): images = [] done = False state = env.reset(seed=random.randint(0,500)) img = env.render(mode='rgb_array') images.append(img) while not done: # Take the action (index) that have the maximum expected future reward given that state action = np.argmax(Qtable[state][:]) state, reward, done, info = env.step(action) # We directly put next_state = state for recording logic img = env.render(mode='rgb_array') images.append(img) imageio.mimsave(out_directory, [np.array(img) for i, img in enumerate(images)], fps=fps)Se sei in un Jupyter notebook, puoi visualizzare la Gif usando la funzione Image di
IPython.display.video_path="/content/replay.gif" video_fps=1 record_video(env, Qtable_frozenlake, video_path, video_fps) from IPython.display import Image Image('./replay.gif')Ora puoi condividere questi risultati con colleghi e compagni di corso o pubblicarli sui social.
Domande frequenti su Q-Learning
Qual è lo svantaggio del Q-learning?
Il processo di apprendimento nel Q-learning è costoso per l’agente, soprattutto agli inizi. Perché? Per convergere alla policy ottimale, ogni coppia stato-azione deve essere visitata frequentemente.
Perché si chiama Q-learning?
Nel Q-learning, la “Q” sta per quality. Rappresenta quanto sia utile una data azione nel conseguire ricompense future, e viene usata per creare una mappatura di stati e azioni per massimizzare le ricompense attese.
Perché il Q-Learning è off-policy?
Nel Q-learning, la policy aggiornata è diversa dalla policy di comportamento (azione), ed è per questo che è chiamato algoritmo off-policy.
Il Q-learning converge sempre?
Sì. Durante l’addestramento, l’algoritmo converge sempre alla policy ottimale.
Perché abbiamo bisogno del deep Q-learning?
Il Q-learning è un algoritmo semplice pensato per ambienti più piccoli e discreti. In caso di un ambiente più grande, servirebbe una Q-table enorme di stati e azioni che richiederebbe molta memoria e calcolo per l’addestramento. Il Deep Q-learning, invece, sostituisce la Q-table con una rete neurale per gestire ambienti ampi che coinvolgono azioni e stati continui.


