Apprendimento per rinforzo: agenti, stati, azioni

Nessun dataset etichettato, solo tentativi e ricompense: il reinforcement learning è il modo in cui si impara ad andare in bicicletta. Costruiamo il vocabolario — agente, stato, azione, reward, policy — e implementiamo un Q-learning completo su una griglia, in puro NumPy.

Hai mai insegnato a un cane a dare la zampa? Non gli hai mostrato un dataset di diecimila foto di zampe etichettate «giusto» e «sbagliato». Hai aspettato che per caso alzasse la zampa, gli hai dato un biscotto, e lui ha collegato i puntini: quella mossa, in quella situazione, porta biscotti. Poi ha riprovato, sempre più spesso, sempre meglio. Congratulazioni: hai già fatto reinforcement learning, solo senza Python.

Agente, ambiente e ricompensa, il dilemma esplorazione–sfruttamento e le tappe che hanno fatto la storia: nel libro.

Leggi «Reinforcement Learning: imparare per tentativi» nel libro →

Oggi costruiamo il vocabolario fondamentale — agente, ambiente, stato, azione, ricompensa, policy — e lo mettiamo subito alla prova implementando da zero un algoritmo di Q-learning su una piccola griglia. Ti serve solo Python 3.12 e NumPy (in un progetto nuovo: uv init && uv add numpy, oppure il classico venv + pip). Se non ricordi cos’è un array NumPy, ti basta sapere che è una tabella di numeri su cui si fanno operazioni veloci senza scrivere cicli.

Il terzo paradigma dell’apprendimento

Nel supervised learning qualcuno ti consegna le risposte giuste: ecco mille email, ecco quali sono spam, impara la corrispondenza. Nell’unsupervised nessuno ti dà risposte e cerchi strutture nascoste. Il reinforcement learning è una terza via, più simile alla vita: nessuno ti dice qual è l’azione giusta, ma dopo ogni azione l’ambiente ti dà un voto — la ricompensa (reward). Positiva, negativa o nulla. Sta a te capire, per tentativi, quale sequenza di azioni massimizza i voti accumulati nel tempo.

È la differenza tra imparare a guidare con un istruttore che ti corregge ogni singola manovra (supervised) e imparare ad andare in bicicletta: nessuno può spiegarti a parole come stare in equilibrio, ma ogni sbandata e ogni metro percorso senza cadere ti danno un feedback inequivocabile.

Il loop agente-ambiente: il motore di tutto

Tutto il RL, da un termostato intelligente ad AlphaGo, gira sullo stesso ciclo. L’agente (chi decide) osserva lo stato dell’ambiente (dove si trova, cosa vede), sceglie un’azione, e l’ambiente risponde con due cose: un nuovo stato e una ricompensa. Il ciclo ricomincia, migliaia o milioni di volte.

AGENTE sceglie l’azione (policy) AMBIENTE reagisce e giudica azione aₜ nuovo stato sₜ₊₁ + ricompensa rₜ₊₁ il ciclo si ripete migliaia di volte

Il loop agente-ambiente: l’agente agisce, l’ambiente risponde con nuovo stato e ricompensa. Tutto il reinforcement learning — dal termostato ad AlphaGo — è una variazione su questo ciclo.

Il vocabolario minimo, con l’analogia del cane sempre a portata di mano:

  • Stato (s): la situazione attuale. Per il cane: «il padrone ha detto zampa e ha la mano tesa».
  • Azione (a): una delle mosse possibili. Alzare la zampa, abbaiare, sdraiarsi.
  • Ricompensa (r): il voto immediato. Biscotto = +1, niente = 0.
  • Policy (π): la strategia dell’agente, la mappa «in questo stato faccio questa azione». È ciò che l’apprendimento deve migliorare.
  • Valore Q(s, a): quanto «rende» in prospettiva fare l’azione a nello stato s — non solo il biscotto immediato, ma tutti quelli futuri che quella mossa sblocca.

Il reinforcement learning non impara le risposte giuste: impara quali azioni, alla lunga, portano più biscotti.

Esplorare o sfruttare? Il dilemma del ristorante

C’è una tensione inevitabile in tutto questo. Il sabato sera vai nel solito ristorante che sai essere buono (sfruttamento: incassi una ricompensa sicura) o provi quello nuovo dietro l’angolo (esplorazione: rischi una cena mediocre, ma potresti scoprire il tuo nuovo preferito)? Un agente che sfrutta e basta resta incastrato nella prima strategia decente che trova; uno che esplora e basta non capitalizza mai ciò che ha imparato.

La soluzione più semplice si chiama epsilon-greedy: con probabilità ε (per esempio il 10%) scegli un’azione a caso, altrimenti la migliore che conosci. Nel codice qui sotto la vedrai in due righe.

Q-learning su una griglia, da zero

Mettiamo tutto insieme. L’ambiente è una griglia 4×4: l’agente parte in alto a sinistra e deve raggiungere il tesoro in basso a destra, evitando una trappola. Ogni passo costa −1 (per spingerlo a fare in fretta), la trappola vale −20, il tesoro +30. L’agente impara aggiornando una tabella Q con la regola di Bellman: «il valore di questa mossa è la ricompensa immediata più il meglio che potrò fare dopo, un po’ scontato».

# Python 3.12 — dipendenza: numpy
import numpy as np

rng = np.random.default_rng(0)

# --- L'ambiente: griglia 4x4 ---
LATO = 4
PARTENZA, TESORO, TRAPPOLA = 0, 15, 9   # celle numerate da 0 a 15
AZIONI = [-LATO, +LATO, -1, +1]         # su, giù, sinistra, destra

def passo(stato, azione):
    """Applica un'azione: restituisce (nuovo_stato, ricompensa, finito)."""
    r, c = divmod(stato, LATO)
    # se la mossa esce dalla griglia, l'agente resta fermo (muro)
    if (azione == -LATO and r == 0) or (azione == +LATO and r == LATO - 1) \
       or (azione == -1 and c == 0) or (azione == +1 and c == LATO - 1):
        nuovo = stato
    else:
        nuovo = stato + azione
    if nuovo == TESORO:   return nuovo, +30, True   # obiettivo raggiunto
    if nuovo == TRAPPOLA: return nuovo, -20, True   # episodio fallito
    return nuovo, -1, False                          # costo del passo

# --- L'agente: tabella Q (16 stati x 4 azioni), tutta a zero ---
Q = np.zeros((LATO * LATO, 4))
alpha, gamma, eps = 0.1, 0.9, 0.1
# alpha: quanto fidarsi della nuova esperienza (tasso di apprendimento)
# gamma: quanto pesano le ricompense future (0.9 = lungimirante)
# eps:   quota di mosse esplorative a caso (epsilon-greedy)

for episodio in range(2000):
    stato, finito = PARTENZA, False
    while not finito:
        # epsilon-greedy: a volte esplora, di solito sfrutta
        if rng.random() < eps:
            a = rng.integers(4)              # esplora: mossa a caso
        else:
            a = int(np.argmax(Q[stato]))     # sfrutta: la mossa migliore nota
        nuovo, ricompensa, finito = passo(stato, AZIONI[a])
        # Regola di Bellman: reward immediato + miglior futuro scontato
        obiettivo = ricompensa + gamma * np.max(Q[nuovo]) * (not finito)
        Q[stato, a] += alpha * (obiettivo - Q[stato, a])
        stato = nuovo

# --- La policy imparata: una freccia per ogni cella ---
frecce = np.array(["↑", "↓", "←", "→"])
mappa = frecce[np.argmax(Q, axis=1)].reshape(LATO, LATO)
mappa[divmod(TESORO, LATO)] = "★"
mappa[divmod(TRAPPOLA, LATO)] = "✖"
for riga in mappa:
    print(" ".join(riga))

Esegui lo script: vedrai una griglia di frecce che aggirano la trappola e convergono sul tesoro. Nessuno ha mai detto all’agente «gira a destra alla terza cella»: la strategia è emersa da duemila episodi di tentativi, errori e biscotti. Prova a rieseguire con eps = 0 (niente esplorazione): spesso la policy resterà mediocre, perché l’agente si fossilizza sulla prima strada che gli è andata bene — il ristorante di cui sopra.

Quando il RL non ti serve (cioè quasi sempre)

Doccia fredda, per onestà: nella maggior parte dei problemi aziendali il reinforcement learning è la risposta sbagliata. Se hai dati storici etichettati, il supervised learning è più semplice, più stabile e più economico. Il RL brilla solo quando le decisioni sono sequenziali (ogni azione cambia le situazioni future), il feedback arriva in ritardo, e hai un simulatore o la possibilità di sperimentare senza danni: giochi, robotica, controllo di sistemi, ottimizzazione di risorse. È anche notoriamente instabile e affamato di esperienza — il nostro agente ha avuto bisogno di duemila tentativi per una griglia 4×4. Detto questo, il vocabolario che hai imparato oggi ti serve comunque: il fine-tuning con feedback umano dei modelli linguistici (RLHF) parla esattamente questa lingua — stati, azioni, ricompense, policy.

Prova tu: aggiungi una seconda trappola nella cella 6 e un «vento» che con probabilità 0,2 sposta l’agente di una cella a destra dopo ogni mossa (ambiente stocastico). L’agente impara ancora ad arrivare al tesoro? Quanti episodi servono in più? Poi sperimenta con gamma = 0.5: cosa succede a una policy «miope»?

Per approfondire: il riferimento assoluto è Reinforcement Learning: An Introduction di Sutton e Barto (seconda edizione, gratuita online sul sito degli autori) — i capitoli 1, 3 e 6 coprono esattamente il percorso di questo tutorial. Per sperimentare con ambienti già pronti, guarda la libreria Gymnasium della Farama Foundation.

I commenti sono riservati agli iscritti.

Accedi per commentare