Introduzione: Apprendere dalle Ricompense
Il Reinforcement Learning (RL) e un paradigma di apprendimento fondamentalmente diverso dal supervised e unsupervised learning. Invece di apprendere da dati etichettati, un agente interagisce con un ambiente, esegue azioni e riceve ricompense. L'obiettivo e imparare una policy (strategia) che massimizzi la ricompensa cumulativa nel tempo.
Dal gioco degli scacchi (AlphaZero) alla robotica (manipolazione oggetti), dal trading algoritmico alla guida autonoma, il reinforcement learning e alla base di alcune delle applicazioni AI più impressionanti. In questo articolo esploreremo i concetti fondamentali, dal Q-Learning classico fino agli algoritmi deep RL moderni come DQN e PPO.
Cosa Imparerai
- Markov Decision Process (MDP): stato, azione, ricompensa, transizione
- Q-Learning: la tabella dei valori azione-stato
- Deep Q-Network (DQN): approssimare Q con reti neurali
- Policy Gradient: ottimizzare direttamente la policy
- Proximal Policy Optimization (PPO): stabilità e performance
- Exploration vs exploitation: bilanciare scoperta e sfruttamento
- Implementazione pratica con Gymnasium (OpenAI)
Markov Decision Process (MDP)
Il framework formale del RL e il Markov Decision Process, definito da quattro componenti:
- Stati (S): le possibili situazioni in cui l'agente si può trovare (es. posizione in una griglia, frame di un gioco)
- Azioni (A): le mosse disponibili in ogni stato (es. su, giù, sinistra, destra)
- Ricompense (R): il feedback numerico ricevuto dopo ogni azione (es. +1 per vincere, -1 per perdere)
- Transizioni (P): la probabilità di passare a uno stato successivo data un'azione. La proprietà di Markov stabilisce che il futuro dipende solo dallo stato corrente, non dalla storia
L'agente cerca una policy ottimale che massimizza la somma scontata delle ricompense future. Il discount factor gamma (tra 0 e 1) bilancia ricompense immediate vs future: gamma vicino a 0 rende l'agente miope, vicino a 1 lo rende lungimirante.
Q-Learning: Valori Azione-Stato
Il Q-Learning e un algoritmo off-policy che impara la funzione Q(s, a): il valore atteso della ricompensa cumulativa scegliendo l'azione a nello stato s e seguendo la policy ottimale da quel momento in poi. La regola di aggiornamento e:
Q(s, a) = Q(s, a) + alpha * [R + gamma * max_a'(Q(s', a')) - Q(s, a)]
import numpy as np
import gymnasium as gym
class QLearningAgent:
def __init__(self, n_states, n_actions, lr=0.1, gamma=0.99, epsilon=1.0):
self.q_table = np.zeros((n_states, n_actions))
self.lr = lr
self.gamma = gamma
self.epsilon = epsilon
self.epsilon_min = 0.01
self.epsilon_decay = 0.995
def choose_action(self, state):
"""Epsilon-greedy: esplora con prob epsilon, sfrutta altrimenti"""
if np.random.random() < self.epsilon:
return np.random.randint(self.q_table.shape[1])
return np.argmax(self.q_table[state])
def learn(self, state, action, reward, next_state, done):
"""Aggiornamento Q-table"""
target = reward
if not done:
target += self.gamma * np.max(self.q_table[next_state])
self.q_table[state, action] += self.lr * (target - self.q_table[state, action])
# Decay epsilon
self.epsilon = max(self.epsilon_min, self.epsilon * self.epsilon_decay)
# Training su FrozenLake
env = gym.make('FrozenLake-v1', is_slippery=False)
agent = QLearningAgent(n_states=16, n_actions=4)
for episode in range(5000):
state, _ = env.reset()
total_reward = 0
done = False
while not done:
action = agent.choose_action(state)
next_state, reward, terminated, truncated, _ = env.step(action)
done = terminated or truncated
agent.learn(state, action, reward, next_state, done)
state = next_state
total_reward += reward
if episode % 1000 == 0:
print(f"Episode {episode}, Reward: {total_reward}, "
f"Epsilon: {agent.epsilon:.3f}")
Exploration vs Exploitation
Il dilemma fondamentale del RL: l'agente deve esplorare (provare azioni nuove per scoprire ricompense migliori) e sfruttare (usare la conoscenza attuale per massimizzare la ricompensa). La strategia epsilon-greedy bilancia i due aspetti: con probabilità epsilon sceglie un'azione casuale, altrimenti l'azione migliore nota. Epsilon viene gradualmente ridotto durante il training.
Deep Q-Network (DQN)
Il Q-Learning con tabelle funziona solo per spazi di stato piccoli e discreti. Per ambienti complessi (immagini, stati continui), la Deep Q-Network (DQN) sostituisce la Q-table con una rete neurale che approssima la funzione Q. Due innovazioni chiave stabilizzano il training:
- Experience Replay: le transizioni vengono memorizzate in un buffer e campionate casualmente per il training, rompendo la correlazione temporale tra campioni consecutivi
- Target Network: una copia separata della rete, aggiornata periodicamente, calcola i target Q-values, stabilizzando l'apprendimento
import torch
import torch.nn as nn
from collections import deque
import random
class DQN(nn.Module):
def __init__(self, state_dim, action_dim):
super().__init__()
self.net = nn.Sequential(
nn.Linear(state_dim, 128),
nn.ReLU(),
nn.Linear(128, 128),
nn.ReLU(),
nn.Linear(128, action_dim)
)
def forward(self, x):
return self.net(x)
class DQNAgent:
def __init__(self, state_dim, action_dim, lr=1e-3, gamma=0.99):
self.device = torch.device('cuda' if torch.cuda.is_available() else 'cpu')
self.q_net = DQN(state_dim, action_dim).to(self.device)
self.target_net = DQN(state_dim, action_dim).to(self.device)
self.target_net.load_state_dict(self.q_net.state_dict())
self.optimizer = torch.optim.Adam(self.q_net.parameters(), lr=lr)
self.memory = deque(maxlen=100000)
self.gamma = gamma
self.batch_size = 64
def store(self, state, action, reward, next_state, done):
self.memory.append((state, action, reward, next_state, done))
def learn(self):
if len(self.memory) < self.batch_size:
return
batch = random.sample(self.memory, self.batch_size)
states, actions, rewards, next_states, dones = zip(*batch)
states = torch.FloatTensor(states).to(self.device)
actions = torch.LongTensor(actions).to(self.device)
rewards = torch.FloatTensor(rewards).to(self.device)
next_states = torch.FloatTensor(next_states).to(self.device)
dones = torch.FloatTensor(dones).to(self.device)
q_values = self.q_net(states).gather(1, actions.unsqueeze(1))
with torch.no_grad():
next_q = self.target_net(next_states).max(1)[0]
targets = rewards + (1 - dones) * self.gamma * next_q
loss = nn.functional.mse_loss(q_values.squeeze(), targets)
self.optimizer.zero_grad()
loss.backward()
self.optimizer.step()
def update_target(self):
self.target_net.load_state_dict(self.q_net.state_dict())
Policy Gradient e Actor-Critic
I metodi Policy Gradient ottimizzano direttamente la policy senza passare per la funzione Q. Il teorema del policy gradient stabilisce che il gradiente della ricompensa attesa rispetto ai parametri della policy e proporzionale alla ricompensa pesata per la log-probabilità delle azioni.
Actor-Critic
L'architettura Actor-Critic combina i due approcci: l'Actor (policy network) sceglie le azioni, il Critic (value network) stima quanto e buono lo stato corrente. Il Critic riduce la varianza degli aggiornamenti dell'Actor, rendendo il training più stabile.
PPO: Lo Standard Industriale
Proximal Policy Optimization (PPO), sviluppato da OpenAI, e l'algoritmo RL più usato nella pratica. La sua innovazione chiave e il clipped objective: limita quanto la nuova policy può discostarsi dalla vecchia ad ogni aggiornamento, prevenendo cambiamenti troppo drastici che destabilizzerebbero il training.
PPO e alla base di molti successi: InstructGPT e RLHF (allineamento di LLM), OpenAI Five (Dota 2), addestramento di agenti robotici e molti altri.
Prossimi Passi nella Serie
- Nel prossimo articolo esploreremo il Transfer Learning Avanzato con BERT, GPT e Hugging Face
- Vedremo fine-tuning, prompt engineering e RAG (Retrieval-Augmented Generation)
- Confronteremo modelli open-source: Llama, Mistral, Falcon







