Spełnianie ograniczeń: backtracking, propagacja ograniczeń i AC-3 na sudoku i mapie czeskich krajów
Zmienne, dziedziny i ograniczenia, graf ograniczeń, backtracking, heurystyki MRV i LCV, sprawdzanie w przód, AC-3 i min-conflicts — na kolorowaniu czeskich krajów, sudoku i ośmiu hetmanach.
Seria Podstawy AI Część 23 z 52
- Mapa sztucznej inteligencji: co jest czym i jak to do siebie pasuje
- Krótka historia sztucznej inteligencji: od Turinga do modeli językowych
- Dane, model, trening, inferencja: podstawowe pojęcia na jednym przykładzie
- Jak czytać zapis matematyczny: symbole, indeksy, sumy i kwantyfikatory
- Zbiory i relacje: klocki, z których składają się definicje w AI
- Funkcje i odwzorowania: dlaczego każdy model AI jest funkcją
- Logika i dowody: reguły, według których rozumuje AI
- Grafy i drzewa: struktura, na której opierają się przeszukiwanie i sieci neuronowe
- Algebra liniowa dla uczenia maszynowego: wektory, macierze i dlaczego AI głównie mnoży
- Pochodne, gradient i reguła łańcuchowa: jak model ustala, w którą stronę się uczyć
- Spadek gradientu: jak uczy się model
- Czym jest prawdopodobieństwo: od rzutu kostką do aksjomatów Kołmogorowa
- Prawdopodobieństwo warunkowe i twierdzenie Bayesa: jak zmienić ocenę, gdy dowiesz się czegoś nowego
- Zmienna losowa i dystrybuanta: jedna funkcja, która mówi o rozkładzie wszystko
- Funkcja gęstości i funkcja prawdopodobieństwa: dlaczego zmienna ciągła ma w punkcie prawdopodobieństwo zero
- Wartość oczekiwana, wariancja i kowariancja: gdzie leży rozkład, jak jest szeroki i co z czym się wiąże
- Przegląd rozkładów prawdopodobieństwa: Bernoulliego, dwumianowy, Poissona, jednostajny, normalny i wykładniczy
- Prawo wielkich liczb i centralne twierdzenie graniczne: dlaczego średnie zachowują się tak przewidywalnie
- Statystyka i estymacja parametrów: największa wiarygodność, MAP i przedziały ufności
- Teoria informacji: entropia, entropia krzyżowa i dywergencja KL — skąd się biorą funkcje straty
- Przeszukiwanie przestrzeni stanów: BFS, DFS, UCS i A* z animacjami
- Gry i przeciwnik: minimax, przycinanie alfa-beta i Monte Carlo tree search
- Spełnianie ograniczeń: backtracking, propagacja ograniczeń i AC-3 na sudoku i mapie czeskich krajów
- Logika i systemy z bazą wiedzy: wnioskowanie w przód i wstecz, rezolucja i systemy ekspertowe
- Planowanie: STRIPS, świat klocków, heurystyki z relaksacji i planowanie w agentach AI
- Przeszukiwanie lokalne i symulowane wyżarzanie: komiwojażer przez czeskie miasta wojewódzkie
- Algorytmy genetyczne: populacja, krzyżowanie, mutacja i selekcja na plecaku na wycieczkę
- Strategie ewolucyjne i programowanie genetyczne: od reguły jednej piątej do prawa Keplera
- Optymalizacja kolonią mrówek: jak ślad feromonowy znajduje krótką drogę
- Optymalizacja rojem cząstek: bezwładność, własne doświadczenie i doświadczenie roju
- Neuroewolucja: ewolucja wag i kształtu sieci neuronowych oraz algorytm NEAT
- Zbiory rozmyte i funkcje przynależności: jak zapisać „trochę ciepło”
- Logika rozmyta a prawdopodobieństwo: dlaczego „trochę ciepło” nie znaczy „prawdopodobnie ciepło”
- Logika rozmyta: t-normy, implikacje i reguły JEŻELI–TO
- Sterowanie rozmyte: Mamdani i Sugeno od wentylatora po wahadło na wózku
- Klasteryzacja rozmyta: algorytm fuzzy c-means i punkty między skupieniami
- Systemy neuro-rozmyte (ANFIS): reguły rozmyte, które uczą się z danych
- Łańcuchy Markowa: macierz przejścia, rozkład stacjonarny i PageRank
- Ukryte modele Markowa: algorytm Viterbiego i nieuczciwe kasyno
- Sieci bayesowskie: dlaczego trawa jest mokra i co z tego wynika dla zraszacza
- Filtr Kalmana: jak z niedokładnych pomiarów oszacować, gdzie jest samochód i jak szybko jedzie
- Rodzaje uczenia maszynowego: nadzorowane, nienadzorowane, półnadzorowane i inne
- Prompty, kontekst i RAG: jak dać modelowi właściwe informacje
- Wariacyjne autoenkodery (VAE): nowe obrazki z ciągłej przestrzeni latentnej
- Generatywne sieci przeciwstawne (GAN): fałszerz kontra detektyw
- Modele dyfuzyjne: jak z czystego szumu powstaje obraz
- Procesy decyzyjne Markowa: stany, akcje, nagrody i równania Bellmana
- Q-learning i DQN: uczenie się wartości akcji od tabeli po sieć neuronową
- Policy gradient i PPO: uczenie się bezpośrednio polityki
- Jak działa agent AI: model językowy w pętli z narzędziami
- Programowanie z agentami AI: zadanie, testy i kontrola
- Ocena i bezpieczeństwo systemów AI: jak mierzyć i jak pilnować
Pełny program serii Gotowe: 52 z 83 tematów
- Jak zapisać plan lekcji, sudoku albo kolorowanie mapy jako zmienne, dziedziny i ograniczenia.
- Jak backtracking z heurystykami i sprawdzaniem w przód skraca przeszukiwanie z milionów kroków do dziesiątek.
- Jak AC-3 propaguje skreślenia po grafie ograniczeń i dlaczego min-conflicts radzi sobie nawet z milionem hetmanów.
Warto wiedzieć wcześniej: Przeszukiwanie przestrzeni stanów: BFS, DFS, UCS i A* z animacjami, Grafy i drzewa: struktura, na której opierają się przeszukiwanie i sieci neuronowe
Plan lekcji, w którym żaden nauczyciel nie uczy dwóch klas naraz. Sudoku. Rozmieszczenie elementów na płytce, dyżury w szpitalu, przydział samolotów do bramek. Wszystkie te zadania łączy to, że nie szukamy ścieżki, lecz przypisania: każdej zmiennej trzeba dać wartość tak, by wszystkie reguły obowiązywały jednocześnie. Takie zadanie nazywa się problemem spełniania ograniczeń i w przeciwieństwie do ogólnego przeszukiwania ma jedną wielką zaletę — reguły są widoczne, więc algorytm może z nich wnioskować, a nie tylko próbować.
Nawiązuje do części Przeszukiwanie przestrzeni stanów i Grafy i drzewa.
1. Czym jest problem spełniania ograniczeń
Definicja — CSP
Problem spełniania ograniczeń (constraint satisfaction problem, CSP) to trójka (X, D, C), gdzie
- X = {X1, …, Xn} to zbiór zmiennych,
- D = {D1, …, Dn}, gdzie Di to dziedzina — zbiór wartości, które może przyjmować Xi,
- C to zbiór ograniczeń. Każde ograniczenie to para (zakres, relacja): zakres to lista zmiennych, relacja mówi, które kombinacje ich wartości są dozwolone.
Ograniczenie z dwiema zmiennymi w zakresie jest binarne, na przykład X1 ≠ X2.
Zmienne to pytania, na które szukasz odpowiedzi („jaki kolor dostanie ten kraj?”). Dziedzina to możliwe odpowiedzi („czerwony, zielony, niebieski”). Ograniczenia to reguły, których odpowiedzi muszą przestrzegać („sąsiednie kraje nie mogą mieć tego samego koloru”).
Definicja — przypisanie i rozwiązanie
Przypisanie nadaje wartość z dziedziny niektórym zmiennym. Jest spójne, gdy nie narusza żadnego ograniczenia, i pełne, gdy wartość ma każda zmienna.
Rozwiązanie CSP to pełne spójne przypisanie.
Rozpoczęte sudoku, w którym na razie nic ze sobą nie koliduje, to spójne, ale niepełne przypisanie. Wypełnione sudoku, w którym nic nie koliduje, to rozwiązanie.
Dlaczego nie potraktować tego jak zwykłego przeszukiwania? Dałoby się — stan to rozpoczęte przypisanie, akcja to „daj kolejnej zmiennej wartość”. Tyle że przeszukiwanie patrzyłoby na stan jak na czarną skrzynkę. CSP zagląda do środka: wie, które reguły obowiązują i których zmiennych dotyczą. Na tym opierają się wszystkie sztuczki w tej części.
2. Graf ograniczeń: kolorowanie mapy krajów
Klasyczne zadanie: pokolorować mapę tak, by żadne dwa sąsiednie państwa nie miały tego samego koloru. Weźmy czternaście czeskich krajów. Zmienne to kraje, dziedzina to zestaw kolorów, a dla każdej wspólnej granicy jest ograniczenie „różne kolory”.
Definicja — graf ograniczeń
Graf ograniczeń binarnego CSP ma za wierzchołki zmienne i krawędź między Xi a Xj wtedy i tylko wtedy, gdy łączy je jakieś ograniczenie.
Każdy kraj to kółko, a linia między dwoma kółkami znaczy „te dwa ze sobą sąsiadują, nie mogą mieć tego samego koloru”. Z mapy zostaje tylko to, co ważne: kto z kim sąsiaduje.
Z czterema kolorami idzie gładko. Z trzema nie da się wcale — a graf pokaże dlaczego. Kraj środkowoczeski sąsiaduje z Pragą i z siedmioma krajami, które tworzą wokół niego zamknięty krąg. Krąg o nieparzystej liczbie wierzchołków sam w sobie potrzebuje trzech kolorów (dwa musiałyby się regularnie przeplatać, a przy nieparzystej liczbie to nie wychodzi). Kraj środkowoczeski sąsiaduje ze wszystkimi siedmioma, więc dla niego musi zostać czwarty kolor:
To, że cztery kolory wystarczą na każdą mapę, mówi słynne twierdzenie o czterech barwach. Udowodnili je Appel i Haken w 1976 roku — jako pierwsze wielkie twierdzenie matematyczne z pomocą komputera, który sprawdził prawie dwa tysiące przypadków.
3. Backtracking
Definicja — backtracking
Backtracking to przeszukiwanie w głąb, które w każdym kroku przypisuje wartość jednej kolejnej zmiennej. Wartości, która narusza ograniczenie z już przypisanymi zmiennymi, w ogóle nie próbuje. Gdy dla zmiennej nie zostanie żadna wartość, wraca do poprzedniej zmiennej i próbuje jej następnej wartości.
W najgorszym przypadku przechodzi O(dn) przypisań, gdzie d to rozmiar dziedziny, a n liczba zmiennych. Ogólny CSP jest NP-trudny, więc w najgorszym przypadku lepiej nie potrafimy.
Wypełniasz pole po polu. Gdy tylko coś nie pasuje, nie idziesz dalej — cofasz się o krok, zmieniasz ostatnią decyzję i próbujesz dalej. Jak DFS z poprzedniej części, tylko z jednym drobiazgiem, który robi ogromną różnicę: kolejność zmiennych nie ma wpływu na wynik, więc wystarczy decydować o jednej zmiennej na piętro drzewa.
def backtracking(assignment, csp):
if len(assignment) == len(csp.variables):
return assignment # wszystko ma wartość: rozwiązanie
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] # ślepy zaułek: wróć
return None
Na krajach z czterema kolorami backtracking wykonuje 14 przypisań, jedno na kraj, i ani razu się nie cofa. Z trzema kolorami musi udowodnić, że rozwiązanie nie istnieje — a to oznacza wypróbowanie wszystkiego: 93 przypisania, zanim się podda.
4. Którą zmienną i jaką wartość
Szkielet backtrackingu pozostawia otwarte dwa pytania: którą zmienną wziąć jako następną i w jakiej kolejności próbować jej wartości. Odpowiedzi mają większe znaczenie, niż mogłoby się wydawać.
Definicja — MRV, stopień, LCV
MRV (minimum remaining values): wybierz zmienną z najmniejszą liczbą wartości, które jeszcze nie są w konflikcie z przypisaniem.
Heurystyka stopnia: przy remisie wybierz zmienną, która ma najwięcej ograniczeń z dotąd nieprzypisanymi zmiennymi.
LCV (least constraining value): wartości zmiennej próbuj od tej, która sąsiadom skreśli najmniej możliwości.
Jako zmienną bierz tę najtrudniejszą: pole sudoku, w które zmieści się już tylko jedna cyfra, wypełnij od razu — a jeśli nie da się tam wpisać nic, odkryjesz to wcześniej, niż zbudujesz nad błędem kolejnych dwadzieścia pięter. Wartość natomiast bierz tę najbardziej ustępliwą, która pozostawi innym najwięcej miejsca. Zmienną wybierasz tak, by szybko ponieść porażkę, wartość tak, by odnieść sukces.
Przy trzech kolorach na krajach MRV razem ze sprawdzaniem w przód z następnego rozdziału zmniejsza liczbę przypisań z 93 do 45 — zaczyna od kraju, któremu zostało najmniej kolorów, i sprzeczność znajduje dwa razy szybciej.
5. Sprawdzanie w przód
Definicja — sprawdzanie w przód
Po przypisaniu Xi = v przejdź każdą nieprzypisaną zmienną Xj, która jest połączona z Xi ograniczeniem, i usuń z Dj wartości niezgodne z v. Jeśli któraś dziedzina zostanie pusta, natychmiast cofnij przypisanie.
Wpisujesz do sudoku siódemkę i od razu skreślasz ją z notatek wszystkich pól w tym samym wierszu, kolumnie i kwadracie. Gdyby któremuś polu nic przez to nie zostało, wiesz, że siódemka była błędem — jeszcze zanim zabierzesz się za kolejne pole.
Różnicę widać na sudoku poniżej. Prosty backtracking idący po wierszach potrzebuje ponad 14 milionów przypisań. Ze sprawdzaniem w przód jest ich 11 400. A gdy dodamy do tego MRV, spada to do 81 — 30 podanych cyfr i 51 uzupełnionych, bez ani jednego powrotu.
6. Spójność łukowa i AC-3
Sprawdzanie w przód patrzy tylko o krok dalej: na sąsiadów zmiennej, którą właśnie przypisaliśmy. Można to zrobić dokładniej — skreślać także między zmiennymi, które jeszcze nie mają wartości.
Definicja — spójność łukowa
Zmienna Xi jest łukowo spójna względem Xj, gdy dla każdej wartości x ∈ Di istnieje y ∈ Dj taka, że para (x, y) spełnia ograniczenie między nimi. CSP jest łukowo spójny, gdy zachodzi to dla każdej skierowanej krawędzi (Xi, Xj) grafu ograniczeń.
Każda wartość musi mieć u sąsiada co najmniej jednego „partnera”, z którym może współistnieć. Wartość bez partnera jest martwa — nie pojawi się w żadnym rozwiązaniu — i możesz ją spokojnie skreślić.
Algorytm — AC-3
- Wstaw do kolejki wszystkie skierowane krawędzie (Xi, Xj).
- Wyjmij krawędź z kolejki i usuń z Di każdą wartość, która w Dj nie ma partnera.
- Jeśli Di się zmniejszyła, wstaw z powrotem do kolejki wszystkie krawędzie (Xk, Xi) dla sąsiadów Xk ≠ Xj — ich wartości mogły właśnie stracić partnera. Jeśli Di jest pusta, CSP nie ma rozwiązania.
- Powtarzaj, dopóki kolejka nie jest pusta.
Dla c binarnych ograniczeń i dziedzin rozmiaru najwyżej d AC-3 trwa O(c · d3).
Skreślanie rozchodzi się jak fala: gdy polu ubędzie możliwość, muszą się ponownie przyjrzeć także jego sąsiedzi, bo mógł im zniknąć partner. Fala zatrzymuje się, gdy nie ma już czego skreślać. Nazywa się to propagacją ograniczeń.
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 # pusta dziedzina: rozwiązanie nie istnieje
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: propagacja w praktyce
Sudoku to CSP jak z podręcznika: 81 zmiennych, dziedziny {1, …, 9} i ograniczenie „różne wartości” dla każdej pary pól w tym samym wierszu, kolumnie lub kwadracie 3 × 3. Każde pole ma więc 20 sąsiadów w grafie ograniczeń.
Uruchommy na łamigłówce z 30 cyframi samo AC-3, bez ani jednego przypisania na ślepo. Skreśli 408 wartości — i każdemu z 51 pustych pól zostanie dokładnie jedna. Sudoku jest rozwiązane czystym rozumowaniem:
Przy łatwych sudoku tak to wygląda; przy trudniejszych propagacja gdzieś utknie i do akcji wkracza szukanie — backtracking, który po każdym przypisaniu ponownie uruchamia propagację. To połączenie (utrzymywanie spójności łukowej, MAC) jest podstawą większości prawdziwych solverów. Dla porównania na tej samej łamigłówce:
| Algorytm | Przypisania | Powroty |
|---|---|---|
| Backtracking po wierszach | 14 740 819 | 14 740 738 |
| + sprawdzanie w przód | 11 400 | 11 319 |
| + sprawdzanie w przód + MRV | 81 | 0 |
| AC-3 bez szukania | 0 | 0 |
Od czternastu milionów do zera. Brutalna siła i sprytne rozumowanie rozwiązują to samo zadanie — tylko jedno z nich czyta reguły.
8. Przeszukiwanie lokalne: min-conflicts
Zupełnie inna strategia: nie budować rozwiązania kawałek po kawałku, lecz zacząć od pełnego przypisania, które narusza mnóstwo ograniczeń, i stopniowo je poprawiać. To przeszukiwanie lokalne z części o przeszukiwaniu, tylko funkcją celu jest liczba naruszonych ograniczeń.
Algorytm — min-conflicts
- Zacznij od losowego pełnego przypisania.
- Dopóki jakieś ograniczenie nie jest spełnione: wybierz losowo zmienną w konflikcie i daj jej wartość, z którą narusza najmniej ograniczeń (przy remisie losowo).
Rozstawiasz wszystko naraz, jak popadnie, a potem poprawiasz: bierzesz jedną rzecz, która się z czymś kłóci, i przesuwasz ją tam, gdzie kłóci się najmniej. Nie pamiętasz żadnego drzewa ani historii — tylko bieżący stan i to, co w nim zgrzyta.
Klasycznym sprawdzianem jest problem n hetmanów: rozstawić n szachowych hetmanów na szachownicy n × n tak, by żadne dwa się nie atakowały. Zmienną jest wiersz hetmana w każdej kolumnie. Dla ośmiu hetmanów nasz przebieg zaczął się od sześciu atakujących się par i skończył po 8 krokach:
Zwróć uwagę na środkowe kroki: liczba konfliktów przez cztery kroki utrzymuje się na jednym — algorytm przesuwa hetmany „w bok” po płaskowyżu, zanim znajdzie ruch, który usunie sprzeczność. Zaskakujące jest, jak dobrze to się skaluje: milion hetmanów min-conflicts rozwiązuje średnio w mniej więcej pięćdziesięciu krokach. Tą samą metodą planowano obserwacje Kosmicznego Teleskopu Hubble’a.
9. Ograniczenia w praktyce
- Plany i harmonogramy. Szkolne plany lekcji, grafiki dyżurów, przydział sal i plany produkcji zadaje się jako zmienne, dziedziny i ograniczenia. Solvery takie jak Google OR-Tools łączą propagację, szukanie i lokalne ulepszanie.
- Solvery SAT rozwiązują CSP, w których każda zmienna to prawda/fałsz. Weryfikują projekty układów scalonych, szukają błędów w oprogramowaniu, a menedżery pakietów (na przykład w Linuksie) składają nimi wersje bibliotek, które ze sobą współpracują.
- Kompilatory przydzielają zmienne programu do rejestrów procesora przez kolorowanie grafu: dwie zmienne, które żyją jednocześnie, nie mogą dzielić rejestru — dokładnie jak sąsiednie kraje koloru.
- Konfiguratory w sklepach internetowych i produkcji pilnują, by wybrane komponenty do siebie pasowały.
- Agenci AI i modele językowe w CSP często nie błyszczą: do sudoku czy planu lekcji bardziej niezawodnie nadaje się solver. Dobry agent dlatego nie rozwiązuje zadania „z głowy” — przekłada je na zmienne i ograniczenia i wywołuje na nim narzędzie.
10. Podsumowanie
- CSP to trójka (X, D, C): zmienne, dziedziny i ograniczenia. Rozwiązanie to pełne przypisanie, które nie narusza żadnego ograniczenia.
- Backtracking przypisuje zmienne jedną po drugiej i wycofuje się ze ślepych zaułków; w najgorszym przypadku jest wykładniczy.
- Heurystyki MRV i LCV wybierają najtrudniejszą zmienną i najbardziej ustępliwą wartość; sprawdzanie w przód wykrywa ślepy zaułek o krok wcześniej.
- AC-3 rozprowadza skreślenia po grafie ograniczeń, dopóki każda wartość nie ma partnera; łatwe sudoku rozwiązuje całkowicie bez szukania.
- Min-conflicts poprawia pełne przypisanie i radzi sobie nawet z ogromnymi zadaniami, takimi jak milion hetmanów.
Chcesz to wypróbować? Poproś agenta AI o napisanie solvera sudoku z backtrackingiem, a potem dodawaj do niego sprawdzanie w przód, MRV i AC-3 — i licz, ile przypisań ubędzie.
Masz inne zdanie? Napisz na support@coden-app.com.