Soddisfacimento di vincoli: backtracking, propagazione dei vincoli e AC-3 su un sudoku e una mappa delle regioni ceche
Variabili, domini e vincoli, il grafo dei vincoli, il backtracking, le euristiche MRV e LCV, il forward checking, AC-3 e min-conflicts — colorando le regioni della Cechia, con un sudoku e con otto regine.
Serie Basi di IA Parte 23 di 52
- Mappa dell’intelligenza artificiale: che cos’è ciascuna cosa e come si incastra tutto
- Breve storia dell’intelligenza artificiale: da Turing ai modelli linguistici
- Dati, modello, addestramento, inferenza: i concetti di base su un unico esempio
- Come leggere la notazione matematica: simboli, indici, sommatorie e quantificatori
- Insiemi e relazioni: i mattoncini con cui si costruiscono le definizioni nell’IA
- Funzioni e applicazioni: perché ogni modello di IA è una funzione
- Logica e dimostrazioni: le regole con cui si ragiona nell’IA
- Grafi e alberi: la struttura su cui poggiano la ricerca e le reti neurali
- Algebra lineare per l’apprendimento automatico: vettori, matrici e perché l’IA soprattutto moltiplica
- Derivate, gradiente e regola della catena: come un modello capisce in quale direzione imparare
- Discesa del gradiente: come impara un modello
- Che cos’è la probabilità: dal lancio di un dado agli assiomi di Kolmogorov
- Probabilità condizionata e teorema di Bayes: come cambiare una stima quando scopri qualcosa di nuovo
- Variabile aleatoria e funzione di ripartizione: una sola funzione che dice tutto sulla distribuzione
- Funzione di densità e funzione di massa: perché una variabile continua ha probabilità zero in un punto
- Valore atteso, varianza e covarianza: dove si trova una distribuzione, quanto è larga e che cosa è legato a che cosa
- Panoramica delle distribuzioni di probabilità: Bernoulli, binomiale, Poisson, uniforme, normale ed esponenziale
- Legge dei grandi numeri e teorema del limite centrale: perché le medie si comportano in modo così prevedibile
- Statistica e stima dei parametri: massima verosimiglianza, MAP e intervalli di confidenza
- Teoria dell’informazione: entropia, entropia incrociata e divergenza KL — da dove vengono le funzioni di perdita
- Ricerca nello spazio degli stati: BFS, DFS, UCS e A* con animazioni
- Giochi e avversari: minimax, potatura alfa-beta e Monte Carlo tree search
- Soddisfacimento di vincoli: backtracking, propagazione dei vincoli e AC-3 su un sudoku e una mappa delle regioni ceche
- Logica e sistemi basati sulla conoscenza: concatenazione in avanti e all’indietro, risoluzione e sistemi esperti
- Pianificazione: STRIPS, il mondo dei blocchi, euristiche dal rilassamento e pianificazione negli agenti IA
- Ricerca locale e ricottura simulata: un commesso viaggiatore tra i capoluoghi di regione cechi
- Algoritmi genetici: popolazione, incrocio, mutazione e selezione su uno zaino per l’escursione
- Strategie evolutive e programmazione genetica: dalla regola di un quinto alla legge di Keplero
- Ottimizzazione con colonie di formiche: come una traccia di feromone trova un percorso breve
- Ottimizzazione a sciame di particelle: inerzia, esperienza propria ed esperienza dello sciame
- Neuroevoluzione: evoluzione dei pesi e della forma delle reti neurali e l’algoritmo NEAT
- Insiemi fuzzy e funzioni di appartenenza: come scrivere «un po’ caldo»
- Logica fuzzy e probabilità: perché «un po’ caldo» non significa «probabilmente caldo»
- Logica fuzzy: t-norme, implicazioni e regole SE–ALLORA
- Controllo fuzzy: Mamdani e Sugeno, dal ventilatore al pendolo inverso su un carrello
- Clustering fuzzy: l’algoritmo fuzzy c-means e i punti tra i cluster
- Sistemi neuro-fuzzy (ANFIS): regole fuzzy che imparano dai dati
- Catene di Markov: matrice di transizione, distribuzione stazionaria e PageRank
- Modelli di Markov nascosti: l’algoritmo di Viterbi e il casinò disonesto
- Reti bayesiane: perché l’erba è bagnata e che cosa significa per l’irrigatore
- Filtro di Kalman: come stimare da misure imprecise dove si trova un’auto e a che velocità va
- Tipi di apprendimento automatico: supervisionato, non supervisionato, semi-supervisionato e altri
- Prompt, contesto e RAG: come dare a un modello le informazioni giuste
- Autoencoder variazionali (VAE): immagini nuove da uno spazio latente continuo
- Reti generative avversarie (GAN): il falsario contro il detective
- Modelli di diffusione: come il rumore puro diventa un’immagine
- Processi decisionali di Markov: stati, azioni, ricompense ed equazioni di Bellman
- Q-learning e DQN: imparare il valore delle azioni, dalla tabella alla rete neurale
- Policy gradient e PPO: imparare direttamente la politica
- Come funziona un agente IA: un modello linguistico in un ciclo con strumenti
- Programmare con agenti IA: incarico, test e controllo
- Valutazione e sicurezza dei sistemi di IA: come misurare e come sorvegliare
Programma completo della serie 52 di 83 argomenti pubblicati
- Come scrivere un orario, un sudoku o la colorazione di una mappa come variabili, domini e vincoli.
- Come il backtracking con euristiche e forward checking riduce la ricerca da milioni di passi a poche decine.
- Come AC-3 propaga le cancellazioni nel grafo dei vincoli e perché min-conflicts se la cava anche con un milione di regine.
Utile sapere prima: Ricerca nello spazio degli stati: BFS, DFS, UCS e A* con animazioni, Grafi e alberi: la struttura su cui poggiano la ricerca e le reti neurali
Un orario scolastico in cui nessun insegnante ha due classi allo stesso tempo. Il sudoku. La disposizione dei componenti su una scheda, i turni in ospedale, l’assegnazione degli aerei ai gate. Tutti questi problemi hanno in comune il fatto che non cerchiamo un percorso, ma un assegnamento: dare a ogni variabile un valore in modo che valgano tutte le regole contemporaneamente. Un problema del genere si chiama problema di soddisfacimento di vincoli e, a differenza della ricerca generica, ha un grande vantaggio — le regole si vedono, quindi l’algoritmo può ragionarci sopra, non solo provare.
Riprende le parti Ricerca nello spazio degli stati e Grafi e alberi.
1. Che cos’è un problema di soddisfacimento di vincoli
Definizione — CSP
Un problema di soddisfacimento di vincoli (constraint satisfaction problem, CSP) è una terna (X, D, C), dove
- X = {X1, …, Xn} è l’insieme delle variabili,
- D = {D1, …, Dn}, dove Di è il dominio — l’insieme dei valori che Xi può assumere,
- C è l’insieme dei vincoli. Ogni vincolo è una coppia (ambito, relazione): l’ambito è un elenco di variabili, la relazione dice quali combinazioni dei loro valori sono ammesse.
Un vincolo con due variabili nell’ambito è binario, per esempio X1 ≠ X2.
Le variabili sono le domande a cui cerchi una risposta («che colore riceve questa regione?»). Il dominio sono le risposte possibili («rosso, verde, blu»). I vincoli sono le regole che le risposte devono rispettare («regioni confinanti non possono avere lo stesso colore»).
Definizione — assegnamento e soluzione
Un assegnamento dà ad alcune variabili un valore del loro dominio. È consistente se non viola alcun vincolo, e completo se ogni variabile ha un valore.
Una soluzione del CSP è un assegnamento completo e consistente.
Un sudoku iniziato in cui per ora nulla è in conflitto è un assegnamento consistente, ma incompleto. Un sudoku compilato in cui nulla è in conflitto è una soluzione.
Perché non trattarlo come una normale ricerca? Si potrebbe — lo stato è l’assegnamento iniziato, l’azione è «dai un valore alla variabile successiva». Solo che la ricerca guarderebbe lo stato come una scatola nera. Il CSP dentro la scatola ci vede: sa quali regole valgono e quali variabili riguardano. Su questo si reggono tutti i trucchi di questa parte.
2. Il grafo dei vincoli: colorare una mappa di regioni
Un problema classico: colorare una mappa in modo che due stati confinanti non abbiano mai lo stesso colore. Prendiamo le quattordici regioni della Cechia. Le variabili sono le regioni, il dominio è un insieme di colori e per ogni confine comune c’è il vincolo «colori diversi».
Definizione — grafo dei vincoli
Il grafo dei vincoli di un CSP binario ha come vertici le variabili e un arco tra Xi e Xj se e solo se le collega un vincolo.
Ogni regione è un cerchietto e una linea tra due cerchietti significa «questi due confinano, non possono avere lo stesso colore». Della mappa resta solo ciò che conta: chi confina con chi.
Con quattro colori va liscio. Con tre non va affatto — e il grafo mostra perché. La Boemia Centrale confina con Praga e con sette regioni che intorno a essa formano un anello chiuso. Un anello con un numero dispari di vertici richiede da solo tre colori (due dovrebbero alternarsi regolarmente, e con un numero dispari non torna). La Boemia Centrale confina con tutte e sette, quindi per lei deve restare un quarto colore:
Che quattro colori bastino per ogni mappa è il famoso teorema dei quattro colori. Lo dimostrarono Appel e Haken nel 1976 — come primo grande teorema matematico con l’aiuto di un computer, che verificò quasi duemila casi.
3. Backtracking
Definizione — backtracking
Il backtracking è una ricerca in profondità che a ogni passo assegna un valore a una variabile in più. Un valore che viola un vincolo con le variabili già assegnate non lo prova nemmeno. Quando per una variabile non resta alcun valore, torna alla variabile precedente e prova il suo valore successivo.
Nel caso peggiore esamina O(dn) assegnamenti, dove d è la dimensione del dominio e n il numero di variabili. Il CSP generico è NP-difficile, quindi nel caso peggiore non sappiamo fare di meglio.
Compili casella dopo casella. Appena qualcosa non torna, non vai avanti — torni indietro di un passo, cambi l’ultima decisione e continui a provare. Come la DFS della parte precedente, solo con un piccolo dettaglio che fa un’enorme differenza: l’ordine delle variabili non conta per il risultato, quindi basta decidere una variabile per piano dell’albero.
def backtracking(assignment, csp):
if len(assignment) == len(csp.variables):
return assignment # tutto ha un valore: soluzione
p = select_variable(assignment, csp)
for h in csp.domains[p]:
if consistent(p, h, assignment, csp):
assignment[p] = h
result = backtracking(assignment, csp)
if result:
return result
del assignment[p] # vicolo cieco: torna indietro
return None
Sulle regioni con quattro colori il backtracking fa 14 assegnamenti, uno per regione, e non torna indietro nemmeno una volta. Con tre colori deve dimostrare che la soluzione non esiste — e questo significa provare tutto: 93 assegnamenti prima di arrendersi.
4. Quale variabile e quale valore
Lo scheletro del backtracking lascia aperte due domande: quale variabile prendere come successiva e in quale ordine provare i suoi valori. Le risposte contano più di quanto sembri.
Definizione — MRV, grado, LCV
MRV (minimum remaining values): scegli la variabile con il minor numero di valori che non sono ancora in conflitto con l’assegnamento.
Euristica del grado: in caso di parità scegli la variabile che ha più vincoli con le variabili non ancora assegnate.
LCV (least constraining value): prova i valori di una variabile a partire da quello che toglie meno possibilità ai vicini.
Come variabile prendi la più difficile: la casella del sudoku in cui ci sta ormai un solo numero compilala subito — e se lì non ci sta niente, lo scopri prima di costruire sopra l’errore altri venti piani. Come valore, al contrario, prendi il più accomodante, quello che lascia agli altri più spazio. La variabile la scegli in modo da fallire in fretta, il valore in modo da riuscire.
Con tre colori sulle regioni, MRV insieme al forward checking del prossimo capitolo riduce il numero di assegnamenti da 93 a 45 — parte dalla regione a cui restano meno colori e trova la contraddizione due volte più in fretta.
5. Forward checking
Definizione — forward checking
Dopo l’assegnamento Xi = v esamina ogni variabile non assegnata Xj collegata a Xi da un vincolo e rimuovi da Dj i valori incompatibili con v. Se qualche dominio resta vuoto, annulla subito l’assegnamento.
Scrivi un sette nel sudoku e lo cancelli subito dalle annotazioni di tutte le caselle nella stessa riga, colonna e quadrato. Se così a qualche casella non restasse nulla, sai che il sette era un errore — ancora prima di passare alla casella successiva.
La differenza si vede sul sudoku qui sotto. Il semplice backtracking sulle caselle per righe ha bisogno di oltre 14 milioni di assegnamenti. Con il forward checking sono 11 400. E se ci aggiungiamo MRV, si scende a 81 — 30 numeri dati e 51 completati, senza un solo ritorno indietro.
6. Arc-consistenza e AC-3
Il forward checking guarda solo un passo più in là: ai vicini della variabile che abbiamo appena assegnato. Si può fare in modo più approfondito — cancellare anche tra variabili che un valore non ce l’hanno ancora.
Definizione — arc-consistenza
La variabile Xi è arc-consistente rispetto a Xj se per ogni valore x ∈ Di esiste y ∈ Dj tale che la coppia (x, y) soddisfa il vincolo tra di esse. Un CSP è arc-consistente se questo vale per ogni arco orientato (Xi, Xj) del grafo dei vincoli.
Ogni valore deve avere presso il vicino almeno un «partner» con cui può convivere. Un valore senza partner è morto — non comparirà in nessuna soluzione — e puoi tranquillamente cancellarlo.
Algoritmo — AC-3
- Inserisci nella coda tutti gli archi orientati (Xi, Xj).
- Prendi un arco dalla coda e rimuovi da Di ogni valore che in Dj non ha un partner.
- Se Di si è ridotto, rimetti nella coda tutti gli archi (Xk, Xi) per i vicini Xk ≠ Xj — i loro valori potrebbero aver appena perso il partner. Se Di è vuoto, il CSP non ha soluzione.
- Ripeti finché la coda non è vuota.
Per c vincoli binari e domini di dimensione al massimo d, AC-3 richiede O(c · d3).
Le cancellazioni si propagano come un’onda: quando a una casella viene a mancare una possibilità, devono ricontrollare anche i suoi vicini, perché potrebbero aver perso il partner. L’onda si ferma quando non c’è più niente da cancellare. Questo si chiama propagazione dei vincoli.
from collections import deque
def ac3(csp):
queue = deque((xi, xj) for xi in csp.variables for xj in csp.neighbors[xi])
while queue:
xi, xj = queue.popleft()
if revise(csp, xi, xj):
if not csp.domains[xi]:
return False # dominio vuoto: la soluzione non esiste
for xk in csp.neighbors[xi]:
if xk != xj:
queue.append((xk, xi))
return True
def revise(csp, xi, xj):
before = len(csp.domains[xi])
csp.domains[xi] = [x for x in csp.domains[xi]
if any(csp.allowed(xi, x, xj, y) for y in csp.domains[xj])]
return len(csp.domains[xi]) < before
7. Sudoku: la propagazione in pratica
Il sudoku è un CSP da manuale: 81 variabili, domini {1, …, 9} e il vincolo «valori diversi» per ogni coppia di caselle nella stessa riga, colonna o quadrato 3 × 3. Ogni casella ha così 20 vicini nel grafo dei vincoli.
Lanciamo su uno schema con 30 numeri il solo AC-3, senza un solo assegnamento alla cieca. Cancella 408 valori — e a ognuna delle 51 caselle vuote ne resta esattamente uno. Il sudoku è risolto con il puro ragionamento:
Con i sudoku facili va così; con quelli più difficili la propagazione da qualche parte si blocca e subentra la ricerca — un backtracking che dopo ogni assegnamento rilancia la propagazione. Questa combinazione (mantenimento dell’arc-consistenza, MAC) è la base della maggior parte dei risolutori reali. Per confronto, sullo stesso schema:
| Algoritmo | Assegnamenti | Ritorni |
|---|---|---|
| Backtracking per righe | 14 740 819 | 14 740 738 |
| + forward checking | 11 400 | 11 319 |
| + forward checking + MRV | 81 | 0 |
| AC-3 senza ricerca | 0 | 0 |
Da quattordici milioni a zero. La forza bruta e il ragionamento intelligente risolvono lo stesso problema — solo uno dei due legge le regole.
8. Ricerca locale: min-conflicts
Una strategia completamente diversa: non costruire la soluzione pezzo per pezzo, ma partire da un assegnamento completo che viola un sacco di vincoli e correggerlo man mano. È la ricerca locale della parte sulla ricerca, solo che la funzione obiettivo è il numero di vincoli violati.
Algoritmo — min-conflicts
- Parti da un assegnamento completo casuale.
- Finché qualche vincolo non vale: scegli a caso una variabile in conflitto e dalle il valore con cui viola meno vincoli (in caso di parità a caso).
Sistemi tutto in una volta, come viene, e poi correggi: prendi una cosa che è in lite con qualcosa e la sposti dove litiga di meno. Non ricordi nessun albero né la storia — solo lo stato attuale e ciò che in esso non va.
La prova classica è il problema delle n regine: disporre n regine degli scacchi su una scacchiera n × n in modo che nessuna coppia si minacci. La variabile è la riga della regina in ogni colonna. Per otto regine la nostra esecuzione è partita con sei coppie che si minacciavano ed è finita dopo 8 passi:
Nota i passi centrali: il numero di conflitti resta per quattro passi a uno — l’algoritmo sposta le regine «di lato» lungo un plateau, prima di trovare la mossa che elimina il conflitto. Sorprende quanto bene scali: un milione di regine min-conflicts lo risolve in media in circa cinquanta passi. Con lo stesso procedimento si pianificavano le osservazioni del telescopio spaziale Hubble.
9. I vincoli in pratica
- Orari e pianificazione. Orari scolastici, turni, assegnazione delle aule e piani di produzione si descrivono come variabili, domini e vincoli. Risolutori come Google OR-Tools combinano propagazione, ricerca e miglioramento locale.
- I risolutori SAT risolvono CSP in cui ogni variabile è vero/falso. Verificano progetti di chip, cercano errori nel software e i gestori di pacchetti (per esempio su Linux) li usano per mettere insieme versioni di librerie che funzionano tra loro.
- I compilatori assegnano le variabili del programma ai registri del processore colorando un grafo: due variabili che vivono nello stesso momento non possono condividere un registro — esattamente come le regioni confinanti un colore.
- I configuratori negli e-shop e nella produzione controllano che i componenti scelti stiano bene insieme.
- Gli agenti IA e i modelli linguistici nei CSP spesso non eccellono: per un sudoku o un orario è più affidabile un risolutore. Un buon agente quindi non ragiona sul problema «a mente» — lo traduce in variabili e vincoli e ci chiama sopra uno strumento.
10. Riepilogo
- Un CSP è una terna (X, D, C): variabili, domini e vincoli. Una soluzione è un assegnamento completo che non viola alcun vincolo.
- Il backtracking assegna le variabili una dopo l’altra e torna indietro dai vicoli ciechi; nel caso peggiore è esponenziale.
- Le euristiche MRV e LCV scelgono la variabile più difficile e il valore più accomodante; il forward checking scopre un vicolo cieco un passo prima.
- AC-3 propaga le cancellazioni nel grafo dei vincoli finché ogni valore non ha un partner; un sudoku facile lo risolve del tutto senza ricerca.
- Min-conflicts corregge un assegnamento completo e se la cava anche con problemi enormi, come un milione di regine.
Vuoi toccarlo con mano? Fai scrivere a un agente IA un risolutore di sudoku con il backtracking e poi aggiungici forward checking, MRV e AC-3 — e conta quanti assegnamenti si risparmiano.
La vedi diversamente? Scrivi a support@coden-app.com.