Preskočiť na obsah
coden

Prehľadávanie stavového priestoru: BFS, DFS, UCS a A* s animáciami

Základy umelej inteligencie názorne: čo je stavový priestor, ako fungujú BFS, DFS, UCS, pažravé hľadanie a A*, čo je heuristika a ako s tým súvisí strojové učenie.

14 minút Matěj Žalmánek 2026-10-15

Séria Základy AI Diel 21 z 52
  1. Mapa umelej inteligencie: čo je čo a ako to do seba zapadá
  2. Stručná história umelej inteligencie: od Turinga po jazykové modely
  3. Dáta, model, tréning, inferencia: základné pojmy na jednom príklade
  4. Ako čítať matematický zápis: symboly, indexy, sumy a kvantifikátory
  5. Množiny a relácie: stavebnica, z ktorej sa skladajú definície v AI
  6. Funkcie a zobrazenia: prečo je každý model v AI funkcia
  7. Logika a dôkazy: pravidlá, podľa ktorých sa v AI uvažuje
  8. Grafy a stromy: štruktúra, na ktorej stojí hľadanie aj neurónové siete
  9. Lineárna algebra pre strojové učenie: vektory, matice a prečo AI hlavne násobí
  10. Derivácie, gradient a reťazové pravidlo: ako model zistí, ktorým smerom sa učiť
  11. Gradientný zostup: ako sa model učí
  12. Čo je pravdepodobnosť: od hodu kockou ku Kolmogorovovým axiómam
  13. Podmienená pravdepodobnosť a Bayesova veta: ako zmeniť odhad, keď sa dozviete niečo nové
  14. Náhodná premenná a distribučná funkcia: jedna funkcia, ktorá o rozdelení povie všetko
  15. Funkcia hustoty a pravdepodobnostná funkcia: prečo má spojitá premenná v bode pravdepodobnosť nula
  16. Stredná hodnota, rozptyl a kovariancia: kde rozdelenie leží, aké je široké a čo s čím súvisí
  17. Prehľad pravdepodobnostných rozdelení: Bernoulliho, binomické, Poissonovo, rovnomerné, normálne a exponenciálne
  18. Zákon veľkých čísel a centrálna limitná veta: prečo sa priemery správajú tak predvídateľne
  19. Štatistika a odhady parametrov: maximálna vierohodnosť, MAP a intervaly spoľahlivosti
  20. Teória informácie: entropia, krížová entropia a KL divergencia — odkiaľ sa berú stratové funkcie
  21. Prehľadávanie stavového priestoru: BFS, DFS, UCS a A* s animáciami
  22. Hry a protivník: minimax, alfa-beta orezávanie a Monte Carlo tree search
  23. Splňovanie obmedzujúcich podmienok: backtracking, propagácia obmedzení a AC-3 na sudoku a mape krajov
  24. Logika a znalostné systémy: dopredné a spätné reťazenie, rezolúcia a expertné systémy
  25. Plánovanie: STRIPS, svet kociek, heuristiky z relaxácie a plánovanie v AI agentoch
  26. Lokálne hľadanie a simulované žíhanie: obchodný cestujúci cez krajské mestá
  27. Genetické algoritmy: populácia, kríženie, mutácia a výber na batohu na výlet
  28. Evolučné stratégie a genetické programovanie: od pravidla jednej pätiny po Keplerov zákon
  29. Optimalizácia mravčou kolóniou: ako feromónová stopa nájde krátku cestu
  30. Optimalizácia rojom častíc: zotrvačnosť, vlastná skúsenosť a skúsenosť roja
  31. Neuroevolúcia: evolúcia váh aj tvaru neurónových sietí a algoritmus NEAT
  32. Fuzzy množiny a funkcie príslušnosti: ako zapísať „trochu teplo“
  33. Fuzzy logika a pravdepodobnosť: prečo „trochu teplo“ neznamená „pravdepodobne teplo“
  34. Fuzzy logika: t-normy, implikácie a pravidlá AK–TAK
  35. Fuzzy riadenie: Mamdani a Sugeno od ventilátora po kyvadlo na vozíku
  36. Fuzzy zhlukovanie: algoritmus fuzzy c-means a body medzi zhlukmi
  37. Neuro-fuzzy systémy (ANFIS): fuzzy pravidlá, ktoré sa učia z dát
  38. Markovove reťazce: prechodová matica, stacionárne rozdelenie a PageRank
  39. Skryté Markovove modely: Viterbiho algoritmus a nepoctivé kasíno
  40. Bayesovské siete: prečo je tráva mokrá a čo z toho vyplýva pre postrekovač
  41. Kalmanov filter: ako z nepresných meraní odhadnúť, kde auto je a ako rýchlo ide
  42. Druhy strojového učenia: s učiteľom, bez učiteľa, s čiastočným učiteľom a ďalšie
  43. Prompty, kontext a RAG: ako modelu dať správne informácie
  44. Variačné autoenkodéry (VAE): nové obrázky zo spojitého latentného priestoru
  45. Generatívne adversariálne siete (GAN): falšovateľ proti detektívovi
  46. Difúzne modely: ako sa z čistého šumu stane obrázok
  47. Markovove rozhodovacie procesy: stavy, akcie, odmeny a Bellmanove rovnice
  48. Q-learning a DQN: učenie hodnoty akcií od tabuľky po neurónovú sieť
  49. Policy gradient a PPO: učenie priamo stratégie
  50. Ako funguje AI agent: jazykový model v slučke s nástrojmi
  51. Programovanie s AI agentmi: zadanie, testy a kontrola
  52. Vyhodnocovanie a bezpečnosť AI systémov: ako merať a ako strážiť

Celá osnova série Hotovo: 52 z 83 tém

Čo si odnesiete
  • Ako formálne zapísať úlohu ako hľadanie cesty v grafe stavov.
  • Čím sa líšia BFS, DFS, UCS, pažravé hľadanie a A* — a prečo je to v jadre jediný riadok kódu.
  • Čo je heuristika, kedy je prípustná a prečo s ňou A* nájde najlacnejšiu cestu s najmenšou prácou.

Čo sa hodí vedieť vopred: Mapa umelej inteligencie: čo je čo a ako to do seba zapadá, Ako čítať matematický zápis: symboly, indexy, sumy a kvantifikátory

Keď sa dnes povie umelá inteligencia, väčšina ľudí si vybaví neurónové siete a veľké jazykové modely. Lenže dlho predtým, než sa stroje začali učiť z dát, vedeli hľadať. Navigácia v mape, ťah v šachu, plán robota v sklade — to všetko je hľadanie cesty v priestore možností. Prehľadávanie stavového priestoru je prvá kapitola každej učebnice umelej inteligencie a myšlienky z neho stretnete aj v strojovom učení: heuristiky, optimalizáciu, hľadanie najlepšieho riešenia.

Každý pojem tu nájdete dvakrát: najprv presnú matematickú definíciu, hneď pod ňou to isté ľudsky. Všetky animácie sú skutočný beh algoritmu — nič z toho nie je kreslené ručne.

1. Čo je stavový priestor

Skôr než začneme čokoľvek hľadať, musíme úlohu opísať tak, aby jej rozumel počítač. Na to slúži úloha prehľadávania:

Definícia — úloha prehľadávania

Úloha prehľadávania je šestica

P = (S, s0, A, T, G, c),

ktorá spĺňa:

  1. S je neprázdna množina; jej prvky nazývame stavy;
  2. s0 ∈ S; tento stav nazývame počiatočný stav;
  3. A priraďuje každému stavu s ∈ S konečnú množinu A(s) akcií použiteľných v stave s; označme 𝒟 = {(s, a) : s ∈ S, a ∈ A(s)} množinu všetkých dvojíc „stav, v ňom použiteľná akcia“;
  4. T : 𝒟 → S je prechodová funkcia; pre (s, a) ∈ 𝒟 je stav s′ = T(s, a) nasledovník stavu s (po akcii a);
  5. G ⊆ S je množina cieľových stavov;
  6. c : 𝒟 → ℝ+ = (0, ∞) je cena akcie: c(s, a) > 0 je cena použitia akcie a v stave s.

Úloha teda presne hovorí, odkiaľ začíname, aké ťahy máme v každom stave k dispozícii, kam ktorý ťah vedie, koľko stojí a kedy sme v cieli.

Laicky povedané

Stav je „snímka sveta“ v jednom okamihu — kde stojí panáčik v bludisku, ako ležia kocky hlavolamu. Akcia je ťah, ktorý môžete urobiť. Prechodová funkcia hovorí, kam vás ten ťah dostane. Cieľ je to, kam sa chcete dostať, a cena hovorí, koľko vás ktorý ťah stojí — čas, palivo, kroky.

Definícia — cesta, riešenie, optimum

Nech P = (S, s0, A, T, G, c) je úloha prehľadávania a k ∈ ℕ0. Cesta dĺžky k z počiatočného stavu je postupnosť

π = (s0, a1, s1, a2, …, ak, sk)

taká, že pre každé i = 1, …, k platí ai ∈ A(si−1) a si = T(si−1, ai). (Pre k = 0 je cesta len (s0).)

Cena cesty π je súčet cien jej akcií:

g(π) = ∑i=1k c(si−1, ai).

Cesta π je riešenie práve vtedy, keď sk ∈ G. Označme Π množinu všetkých riešení. Ak je Π ≠ ∅, je optimálna cena

C* = min {g(π) : π ∈ Π}

(ak minimum existuje; v úlohách s konečne veľa stavmi, ako je naše bludisko, existuje vždy) a riešenie π je optimálne práve vtedy, keď g(π) = C*. Optimálne riešenie je teda riešenie, ktoré nie je drahšie než žiadne iné riešenie.

Laicky povedané

Cesta je zoznam ťahov, ktoré idú po sebe. Riešenie je cesta, ktorá dôjde do cieľa. Optimálne riešenie je to najlacnejšie zo všetkých — a práve to väčšinou chceme nájsť.

Stavy spojené akciami tvoria orientovaný graf: stavy sú vrcholy, akcie hrany s váhou c. Hľadať riešenie teda znamená nájsť v grafe cestu z s0 do niektorého vrcholu z G. Klasický príklad je hlavolam 8: kocky 1–8 v rámčeku 3 × 3 a jedno prázdne miesto. Stav je rozloženie kociek, akcia posun prázdneho miesta a cieľ zoradené kocky:

12345678 hore 13425678 vpravo 12345678 dole 12347568 vľavo 12345678
Jeden stav hlavolamu 8 a jeho štyria nasledovníci

Vyzerá to nevinne, ale hlavolam 8 má 181 440 dosiahnuteľných stavov a jeho väčší brat hlavolam 15 vyše desať biliónov. Graf si preto nikdy nepostavíme celý dopredu. Algoritmus ho objavuje postupne — z prechodovej funkcie si vždy vypočíta len nasledovníkov stavu, na ktorom práve stojí.

Príklad — odkiaľ sa berie 181 440

Osem kociek a prázdne miesto sa dá do deviatich políčok rozmiestniť 9! = 9 · 8 · 7 · 6 · 5 · 4 · 3 · 2 · 1 = 362 880 spôsobmi. Ťahom sa ale dostanete len do polovice z nich: ak zapíšete kocky po riadkoch (bez prázdneho miesta), každý ťah zachová, či je v tom poradí párny, alebo nepárny počet dvojíc „väčšia pred menšou“. Posun vodorovne poradie nezmení vôbec, posun zvisle preskočí kockou dve iné a počet takých dvojíc zmení o 0 alebo o ±2. Do stavu s opačnou paritou sa preto dostať nedá. Že je dosiahnuteľná práve polovica stavov (pri hlavolame 15 s trochu zložitejším invariantom, do ktorého vstupuje aj riadok prázdneho miesta), dokázali W. W. Johnson a W. E. Story v roku 1879.

Hlavolam 8: 9!/2 = 362 880/2 = 181 440 stavov.
Hlavolam 15: 16!/2 = 20 922 789 888 000/2 = 10 461 394 944 000 ≈ 1,05 · 1013 stavov — vyše desať biliónov.

2. Zadanie: cesta bludiskom

Všetky algoritmy si ukážeme na rovnakej úlohe. Máme bludisko 9 × 14 políčok a chceme sa dostať zo štartu do cieľa čo najlacnejšie. Najprv, čo ktoré políčko znamená:

  • Voľné políčko — vstup naň stojí 1
  • Blato — prejsť sa dá, ale vstup stojí 5
  • Stena — vstúpiť na ňu nemožno
  • S Štart — počiatočný stav, políčko (0, 0)
  • G Cieľ (goal) — políčko (8, 13)
  • Preskúmané políčko — algoritmus ho rozbalil
  • Nájdená cesta — výsledok algoritmu
S G 0 1 2 3 4 5 6 7 8 9 10 11 12 13 0 1 2 3 4 5 6 7 8
Bludisko zo zadania — čísla pri okraji sú riadky a stĺpce
Formálne zadanie

Políčko zapíšeme ako dvojicu (r, s) — riadok r ∈ {0, …, 8}, stĺpec s ∈ {0, …, 13}. Označme Z množinu stien (zdí) a B množinu políčok s blatom.

  • Stavy: S = ({0, …, 8} × {0, …, 13}) ∖ Z — všetky políčka okrem stien.
  • Počiatočný stav: s0 = (0, 0). Cieľ: G = {(8, 13)}.
  • Posuny: ↑ = (−1, 0), → = (0, 1), ↓ = (1, 0), ← = (0, −1). Akcie použiteľné v stave (r, s) ∈ S sú tie, ktoré vedú na políčko vnútri mriežky, ktoré nie je stena: A((r, s)) = {(Δr, Δs) ∈ {↑, →, ↓, ←} : (r + Δr, s + Δs) ∈ S}.
  • Prechod: T((r, s), (Δr, Δs)) = (r + Δr, s + Δs), napr. T((r, s), →) = (r, s + 1).
  • Cena: c(x, a) = 5, ak T(x, a) ∈ B, inak c(x, a) = 1 — platí sa za políčko, na ktoré vstupujeme.
  • Úloha: nájsť riešenie s najmenšou cenou C*.
Laicky povedané

Panáčik stojí vľavo hore a chce sa dostať vpravo dole. Chodí len hore, dole, doľava a doprava, cez stenu neprejde a každý krok ho stojí 1 — okrem kroku do blata, ten stojí 5. Hľadáme cestu, ktorá ho bude stáť najmenej.

Blato leží schválne na najpriamejšej trase. Uvidíme, že nie každý algoritmus si ho všimne: niektoré počítajú len kroky, iné skutočnú cenu.

3. Spoločná kostra všetkých algoritmov

Všetky algoritmy v článku majú rovnakú kostru. Líšia sa len v jednom rozhodnutí.

Definícia — okraj, uzavreté stavy, rozbalenie

Nech P = (S, s0, A, T, G, c) je úloha prehľadávania. Pre stav s ∈ S označme množinu jeho nasledovníkov

N(s) = {T(s, a) : a ∈ A(s)}.

Algoritmus prehľadávania si v každom okamihu behu udržiava dve množiny stavov:

  1. okraj (frontier) F ⊆ S — stavy, ktoré algoritmus už objavil (vedie do nich nájdená cesta z s0), ale ešte nerozbalil; na začiatku F = {s0};
  2. uzavreté stavy E ⊆ S — stavy, ktoré už rozbalil; na začiatku E = ∅.

Rozbaliť stav s ∈ F znamená vykonať tieto dve zmeny (v tomto poradí):

E ← E ∪ {s},   F ← (F ∖ {s}) ∪ (N(s) ∖ E).

Rozbalený stav sa teda presunie z okraja medzi uzavreté a do okraja pribudnú tí jeho nasledovníci, ktorí ešte neboli rozbalení.

Laicky povedané

Okraj je zoznam miest, o ktorých viete, že tam vedie cesta, ale ešte ste sa tam nepozreli. Uzavreté stavy sú miesta, kde ste už boli — vďaka nim nechodíte dokola. Rozbaliť stav znamená postaviť sa naň a rozhliadnuť sa, kam sa dá ísť ďalej.

okraj = [start]
uzavrene = {}
kým okraj nie je prázdny:
    stav = vyber_z(okraj)          # ← tu sa algoritmy líšia
    ak je stav cieľ: hotovo
    pridaj stav do uzavrene
    pre každého nasledovníka stavu:
        ak ešte nie je uzavretý: pridaj ho do okraja

Celý rozdiel medzi algoritmami je v jedinom riadku: ktorý stav z okraja vyberieme ako ďalší. Front, zásobník, alebo prioritný front podľa nejakého čísla. Na tom stojí všetko ostatné.

4. Prehľadávanie do šírky (BFS)

Definícia — BFS

Vstup: úloha prehľadávania P = (S, s0, A, T, G, c). Výstup: riešenie (cesta z s0 do niektorého stavu z G), alebo správa „riešenie neexistuje“.

Prehľadávanie do šírky (BFS) je spoločná kostra vyššie, v ktorej je okraj F front (FIFO, first in — first out):

  1. F ← (s0), E ← ∅.
  2. Ak je F prázdny, skonči: riešenie neexistuje.
  3. Odober z F stav s, ktorý doň pribudol najskôr.
  4. Ak je s ∈ G, skonči a vráť cestu z s0 do s.
  5. Rozbaľ s: pridaj ho do E a každého nasledovníka s′ ∈ N(s), ktorý nie je v E ani v F, zaraď na koniec F. Pokračuj krokom 2.

Vlastnosti. Označme d(s) hĺbku stavu s — najmenšiu dĺžku cesty z s0 do s. BFS rozbaľuje stavy v neklesajúcom poradí d(s), a preto vráti riešenie s najmenším počtom akcií. Nech b = maxs ∈ S |A(s)| je vetviaci faktor (najväčší počet nasledovníkov stavu) a d hĺbka najplytšieho riešenia. Potom BFS vygeneruje najviac b + b2 + … + bd = O(bd) stavov a rovnako veľa ich drží v pamäti.

Laicky povedané

Kto prišiel prvý, ide prvý na rad — ako rad v obchode. Najprv preskúmate všetko jeden krok od štartu, potom všetko dva kroky, potom tri. Šíri sa to ako vlna na vode, a preto prvá cesta, ktorá dorazí do cieľa, má najmenej krokov. Háčik: vlnu si musíte pamätať celú, a tá rastie strašne rýchlo.

A 1 B 2 C 3 D 4 E 5 F 6 G 7 H 8 I 9 J 10
Poradie návštev pri BFS — po vrstvách A → B → C → D → E → F → G → H → I → J

V bludisku je vlna dobre vidieť — rozlieva sa všetkými smermi, kým nenarazí na cieľ:

S G
Prehľadávanie do šírky preskúmaných políčok: 75 krokov: 25 cena cesty: 37

Všimnite si ale, že cesta vedie priamo blatom. BFS počíta akcie, nie cenu — krok do blata je preň rovnaký ako krok po suchu. Najkratšia cesta tak vyšla na 25 krokov, ale cenu 37. BFS je optimálny, len keď majú všetky akcie rovnakú cenu.

Príklad — cena cesty BFS a ako rýchlo rastie vlna

Cesta BFS má 25 krokov, z toho 3 do blata: g = 22 · 1 + 3 · 5 = 22 + 15 = 37.

Veľkosť vlny pre vetviaci faktor b = 10: v hĺbke d = 5 je to 10 + 100 + 1 000 + 10 000 + 100 000 = 111 110 stavov, v hĺbke d = 10 už 11 111 111 110 ≈ 1,1 · 1010. Keby jeden stav zabral v pamäti 1 kB, potrebovala by vlna v hĺbke 10 vyše 11 TB. Každé ďalšie poschodie to zdesaťnásobí.

from collections import deque

def bfs(start, cil, nasledovnici):
    okraj = deque([start])
    rodic = {start: None}
    while okraj:
        stav = okraj.popleft()          # front: prvý dnu, prvý von
        if stav == cil:
            return cesta(rodic, cil)
        for dalsi in nasledovnici(stav):
            if dalsi not in rodic:
                rodic[dalsi] = stav
                okraj.append(dalsi)
    return None

def cesta(rodic, stav):
    vysledek = []
    while stav is not None:
        vysledek.append(stav)
        stav = rodic[stav]
    return vysledek[::-1]

5. Prehľadávanie do hĺbky (DFS)

Definícia — DFS

Vstup: úloha prehľadávania P = (S, s0, A, T, G, c). Výstup: riešenie, alebo správa „riešenie neexistuje“.

Prehľadávanie do hĺbky (DFS) je spoločná kostra vyššie, v ktorej je okraj F zásobník (LIFO, last in — first out):

  1. F ← (s0), E ← ∅.
  2. Ak je F prázdny, skonči: riešenie neexistuje.
  3. Odober z F stav s, ktorý doň pribudol naposledy (vrchol zásobníka).
  4. Ak je s ∈ G, skonči a vráť cestu z s0 do s.
  5. Rozbaľ s: pridaj ho do E a každého nasledovníka s′ ∈ N(s) ∖ E polož na vrchol F. Pokračuj krokom 2.

Vlastnosti. Nech b je vetviaci faktor a m najväčšia hĺbka stavového priestoru (najväčšia dĺžka cesty bez opakovania stavov, prípadne ∞). V stromovej variante (bez množiny E, opakovanie stavov sa stráži len na aktuálnej vetve) drží DFS v pamäti len aktuálnu vetvu a jej súrodencov: O(b · m) stavov. Čas je v najhoršom prípade O(bm). DFS nie je optimálny a v nekonečnom priestore ani úplný — môže zostupovať donekonečna.

Laicky povedané

Ako keď v bludisku idete stále za pravou rukou: vyberiete si jednu chodbu a idete po nej až na koniec. Až v slepej uličke sa vrátite k poslednej odbočke a skúsite inú. Pamäte to potrebuje málo — pamätáte si len cestu, ktorou práve idete. Ale najkratšiu cestu tým nájdete len náhodou.

A 1 B 2 D 3 H 4 I 5 E 6 C 7 F 8 G 9 J 10
Poradie návštev pri DFS — najprv do hĺbky A → B → D → H → I → E → C → F → G → J

V strome to vidno na poradí: A, B, D, H, I — a až potom E. Prečo, ukáže okraj krok po kroku. Kostra je pri oboch rovnaká, líši sa len to, z ktorého konca okraja sa berie: BFS berie zľava najstarší stav (hlava frontu), DFS zľava najnovší (vrchol zásobníka).

krokBFS rozbalífront potomDFS rozbalízásobník potom
1AB CAB C
2BC D EBD E C
3CD E F GDH I E C
4DE F G H IHI E C
5EF G H IIE C
6FG H IEC
7GH I JCF G
8HI JFG
9IJGJ
10J—J—

V druhom kroku obaja pridajú deti uzla B, teda D a E. BFS ich zaradí na koniec frontu za C a ako ďalší vezme C, pretože čaká najdlhšie. DFS ich položí na vrchol zásobníka nad C a vezme D, pretože prišlo naposledy — a deti H, I sa potom dostanú dokonca pred E aj C. Front narastie až na päť uzlov (E F G H I), zásobník najviac na štyri (H I E C): pri BFS je v okraji celé poschodie, pri DFS len jedna vetva a súrodenci jej uzlov. Na takom malom strome je to rozdiel jedného uzla, s každým poschodím však rastie. V bludisku:

S G
Prehľadávanie do hĺbky preskúmaných políčok: 32 krokov: 31 cena cesty: 59

Tu mal DFS šťastie a k cieľu sa dostal bez toho, aby preskúmal veľa políčok. Cesta je však zbytočne dlhá — 31 krokov namiesto 25 a cena 59.

Definícia — iteratívne prehlbovanie (IDDFS)

Vstup: úloha prehľadávania P = (S, s0, A, T, G, c). Výstup: riešenie s najmenším počtom akcií (ak nejaké riešenie existuje).

Pre ℓ ∈ ℕ0 označme DLS(ℓ) (depth-limited search) prehľadávanie do hĺbky, ktoré nerozbaľuje stavy v hĺbke ℓ a väčšej — cesty dlhšie než ℓ teda neskúma. Iteratívne prehlbovanie (IDDFS):

  1. ℓ ← 0.
  2. Spusti DLS(ℓ).
  3. Ak DLS(ℓ) našlo riešenie, skonči a vráť ho.
  4. ℓ ← ℓ + 1 a pokračuj krokom 2.

Podmienka zastavenia: prvé ℓ, pre ktoré DLS(ℓ) nájde riešenie; to je práve hĺbka d najplytšieho riešenia. (Ak riešenie neexistuje a priestor je nekonečný, algoritmus neskončí.)

Vlastnosti. Nech b je vetviaci faktor a d hĺbka najplytšieho riešenia. Stavy v hĺbke i ∈ {1, …, d} sa vygenerujú (d − i + 1)-krát, celkovo ∑i=1d (d − i + 1) · bi = O(bd) stavov — rovnako ako BFS, ale s pamäťou len O(b · d).

Laicky povedané

Pustíte DFS, ale zakážete mu ísť hlbšie než o jeden krok. Nenašlo nič? Tak o dva kroky, potom o tri… Horné poschodia prechádzate znova a znova, ale väčšina stavov leží vždy v tom najhlbšom, takže opakovanie skoro nič nestojí. Dostanete najkratšiu cestu ako pri BFS a malú pamäť ako pri DFS.

NIDDFS = ∑i=1d (d − i + 1) · bi

NIDDFS
koľko stavov celé iteratívne prehlbovanie vygeneruje — tým sa meria jeho čas
∑i=1d
sčítame po poschodiach stromu, od hĺbky 1 až po hĺbku riešenia d — hlbšie sa nikdy nejde
(d − i + 1)
koľkokrát sa poschodie i prejde: v každom kole s limitom ℓ = i, i + 1, …, d — plytké poschodia sa opakujú najčastejšie
bi
koľko stavov je na poschodí i — s hĺbkou rastie exponenciálne, takže najhlbšie poschodie, prejdené len raz, preváži všetko ostatné
Pre b = 10, d = 5: NIDDFS = 5 · 10 + 4 · 100 + 3 · 1 000 + 2 · 10 000 + 1 · 100 000 = 50 + 400 + 3 000 + 20 000 + 100 000 = 123 450. BFS vygeneruje 111 110 stavov, opakovanie teda stojí len 123 450/111 110 − 1 ≈ 11 % navyše (príklad z učebnice Russell a Norvig, Artificial Intelligence: A Modern Approach).
def iddfs(start, je_cil, nasledovnici):
    limit = 0
    while True:                            # ℓ = 0, 1, 2, …
        cesta = dls(start, je_cil, nasledovnici, limit)
        if cesta is not None:
            return cesta
        limit += 1

def dls(stav, je_cil, nasledovnici, limit):   # DFS s obmedzenou hĺbkou
    if je_cil(stav):
        return [stav]
    if limit == 0:
        return None                        # hlbšie nesmieme
    for dalsi in nasledovnici(stav):
        cesta = dls(dalsi, je_cil, nasledovnici, limit - 1)
        if cesta is not None:
            return [stav] + cesta
    return None

6. Prehľadávanie podľa ceny (UCS)

Definícia — UCS

Vstup: úloha prehľadávania P = (S, s0, A, T, G, c). Výstup: riešenie s najmenšou cenou, alebo správa „riešenie neexistuje“.

Počas behu priraďuje algoritmus každému objavenému stavu n ∈ S číslo g(n) — cenu najlacnejšej doteraz nájdenej cesty z s0 do n. Okraj F je prioritný front usporiadaný podľa g. Prehľadávanie podľa ceny (UCS, uniform-cost search):

  1. F ← {s0}, g(s0) ← 0, E ← ∅.
  2. Ak je F = ∅, skonči: riešenie neexistuje.
  3. Odober z F stav n s najmenším g(n).
  4. Ak je n ∈ G, skonči a vráť nájdenú cestu do n (jej cena je g(n)).
  5. Pridaj n do E. Pre každú akciu a ∈ A(n) a n′ = T(n, a) ∉ E: ak je n′ ∉ F, alebo g(n) + c(n, a) < g(n′), nastav g(n′) ← g(n) + c(n, a), zapamätaj si n ako predchodcu n′ a vlož n′ do F. Pokračuj krokom 2.

Vlastnosti. Nech existuje ε > 0 také, že c(s, a) ≥ ε pre všetky s ∈ S, a ∈ A(s). Potom UCS rozbaľuje stavy v neklesajúcom poradí g, a preto je úplný aj optimálny. Počet rozbalených stavov je O(b1 + ⌊C*/ε⌋), kde b je vetviaci faktor a ⌊x⌋ dolná celá časť čísla x.

Laicky povedané

Namiesto krokov počítate peniaze. Vždy pokračujete z miesta, kam ste sa zatiaľ dostali najlacnejšie. Keď do cieľa dorazíte, viete isto, že lacnejšie to nejde — všetko lacnejšie ste už preskúmali. Vo všetkých grafoch s kladnými cenami to robí známy Dijkstrov algoritmus; UCS je jeho verzia, ktorá sa zastaví v cieli.

S G
Prehľadávanie podľa ceny preskúmaných políčok: 49 krokov: 25 cena cesty: 25

UCS blato obišiel. Našiel cestu s rovnakým počtom krokov ako BFS, ale s cenou 25 namiesto 37 — a tá je zaručene najlacnejšia, teda C* = 25. Platí za to tým, že prehľadáva naslepo do všetkých strán: nevie, kde cieľ leží, takže sa šíri aj smerom od neho.

Príklad — v akom poradí UCS rozbaľuje a čo hovorí odhad zložitosti

Prvých desať rozbalení s hodnotou g: (0, 0) g = 0; (0, 1) g = 1; (1, 0) g = 1; (0, 2) g = 2; (2, 0) g = 2; (0, 3) g = 3; (3, 0) g = 3; (1, 3) g = 4; (4, 0) g = 4; (2, 3) g = 5. Kým nenarazí na blato, rozbaľuje UCS rovnako ako BFS, pretože každý krok stojí 1 a g je jednoducho hĺbka. Blato potom stav „odsunie“ o 5 namiesto o 1 — do blata sa UCS pozrie, až keď vyčerpá všetko lacnejšie.

Nájdená cesta: 25 krokov po suchu, C* = 25 · 1 = 25. Najmenšia cena akcie je ε = 1 a vetviaci faktor b = 4, horný odhad je teda b1 + ⌊C*/ε⌋ = 426 ≈ 4,5 · 1015 stavov. V skutočnosti UCS rozbalil 49 políčok: odhad počíta s nekonečným stromom, kde sa nič neopakuje, kým bludisko má len 83 priechodných políčok a uzavreté stavy sa znova nerozbaľujú.

7. Heuristika: odhad, kadiaľ k cieľu

Algoritmy vyššie sú neinformované — o cieli vedia len to, že ho spoznajú, až naň stúpia. Informované algoritmy dostanú navyše odhad, koľko ešte zostáva.

Definícia — heuristika

Nech P = (S, s0, A, T, G, c) je úloha prehľadávania. Pre stav n ∈ S označme h*(n) ∈ ℝ≥0 ∪ {∞} najmenšiu cenu (presnejšie infimum cien) ciest zo stavu n do niektorého cieľového stavu (cesta je definovaná rovnako ako vyššie, len začína v n namiesto s0; ak z n do G nevedie žiadna cesta, je h*(n) = ∞). Heuristika je funkcia

h : S → ℝ≥0  taká, že  ∀n ∈ G : h(n) = 0.

Hodnotu h(n) chápeme ako odhad h*(n). Heuristika h je

  1. prípustná (admissible) práve vtedy, keď ∀n ∈ S : h(n) ≤ h*(n);
  2. konzistentná (monotónna) práve vtedy, keď ∀n ∈ S ∀a ∈ A(n) : h(n) ≤ c(n, a) + h(T(n, a)).

Prípustná heuristika teda skutočnú zostávajúcu cenu nikdy nepreceňuje; konzistentná heuristika po žiadnom kroku neklesne o viac, než koľko ten krok stál. Každá konzistentná heuristika je prípustná (veta nižšie).

Laicky povedané

Heuristika je kvalifikovaný odhad — ako keď v cudzom meste viete, že stanica je „niekde na severe“. Prípustná znamená optimistická: nikdy netvrdí, že je to ďalej, než to v skutočnosti je. Konzistentná znamená, že si odhad po jednom kroku neprotirečí — nemôže sa zlepšiť viac, než koľko ste za ten krok zaplatili.

h(n) ≤ c(n, a) + h(n′)

h(n)
čo heuristika sľubuje teraz, v stave n — tento sľub nesmie byť prehnaný
c(n, a)
skutočná cena jedného kroku do nasledovníka; za ten krok už naozaj zaplatíme
h(n′)
odhad zvyšku z nasledovníka. Keby bol súčet vpravo menší než h(n), cez n′ by viedla cesta lacnejšia, než h(n) tvrdí, a jeden z oboch odhadov by bol nezmysel
Trojuholníková nerovnosť: priamy odhad z n nesmie byť horší než „jeden krok a odhad odtiaľ“.
Veta — konzistentná heuristika je prípustná

Nech P = (S, s0, A, T, G, c) je úloha prehľadávania a h : S → ℝ≥0 konzistentná heuristika. Potom h je prípustná, t. j. h(n) ≤ h*(n) pre každé n ∈ S.

Dôkaz. Nech n ∈ S. Ak je h*(n) = ∞, nerovnosť platí triviálne. Inak vezmime ľubovoľnú cestu z n do cieľa: n = n0, n1, …, nk ∈ G, kde ni = T(ni−1, ai) a ci = c(ni−1, ai) pre i = 1, …, k. Konzistenciu použijeme k-krát za sebou:

h(n0) ≤ c1 + h(n1) ≤ c1 + c2 + h(n2) ≤ … ≤ c1 + … + ck + h(nk).

V cieli je h(nk) = 0, takže h(n) je najviac cena tejto cesty. To platí pre každú cestu z n do cieľa, teda aj pre tú najlacnejšiu, ktorej cena je h*(n). Teda h(n) ≤ h*(n). ∎

Opačne to neplatí: prípustná heuristika konzistentná byť nemusí.

V našom bludisku sa ponúka manhattanská vzdialenosť — počet políčok vodorovne plus zvisle, ako keď idete po pravouhlých uliciach Manhattanu:

h((r, s)) = |r − 8| + |s − 13|

h((r, s))
odhad zostávajúcej ceny z políčka v riadku r a stĺpci s
|r − 8|
koľko riadkov zostáva k cieľu (8, 13). Absolútna hodnota preto, že vzdialenosť sa počíta rovnako, či je cieľ nad nami, alebo pod nami; zvislý ťah ich uberie najviac jeden
|s − 13|
koľko stĺpcov zostáva. Sčítame, pretože diagonálne chodiť nejde: každý riadok aj stĺpec stojí aspoň jeden samostatný ťah
Zo štartu: h((0, 0)) = |0 − 8| + |0 − 13| = 8 + 13 = 21 ≤ C* = 25. Heuristika je konzistentná (a teda prípustná): jedna akcia zmení h najviac o 1 a stojí najmenej 1.
Laicky povedané

Spočítate, koľko políčok je cieľ dole a koľko doprava, a sčítate to. Steny a blato cestu len predĺžia alebo predražia, nikdy neskrátia — takže odhad je vždy optimistický, presne ako má byť.

8. Pažravé hľadanie

Definícia — pažravé hľadanie

Vstup: úloha prehľadávania P = (S, s0, A, T, G, c) a heuristika h : S → ℝ≥0. Výstup: riešenie, alebo správa „riešenie neexistuje“.

Pažravé hľadanie (greedy best-first search) je spoločná kostra vyššie, v ktorej je okraj F prioritný front podľa

f(n) = h(n):

  1. F ← {s0}, E ← ∅.
  2. Ak je F = ∅, skonči: riešenie neexistuje.
  3. Odober z F stav n s najmenším h(n) — stav, ktorý je podľa odhadu najbližšie k cieľu.
  4. Ak je n ∈ G, skonči a vráť cestu z s0 do n.
  5. Rozbaľ n: pridaj ho do E a každého nasledovníka n′ ∈ N(n), ktorý nie je v E ani v F, vlož do F. Pokračuj krokom 2.

Doterajšia cena g(n) sa neberie do úvahy. Vlastnosti: pažravé hľadanie nie je optimálne; so strážením opakovaných stavov (množinou E) je v konečnom priestore úplné.

Laicky povedané

Idete vždy tam, kde to vyzerá najbližšie k cieľu, a vôbec neriešite, koľko vás cesta zatiaľ stála. Rýchlo to mieri správnym smerom, ale ľahko vás to nachytá — napríklad na blato, ktoré leží „správnym smerom“.

S G
Pažravé hľadanie preskúmaných políčok: 33 krokov: 31 cena cesty: 59

Pažravé hľadanie preskúma málo políčok, lenže ide tam, kde to vyzerá bližšie, a skončí na dlhej a drahej ceste — 31 krokov, cena 59.

Príklad — prvých desať krokov pažravého hľadania

Rozbalené políčka s hodnotou h: (0, 0) 21 → (0, 1) 20 → (0, 2) 19 → (0, 3) 18 → (1, 3) 17 → (2, 3) 16 → (2, 2) 17 → (3, 2) 16 → (4, 2) 15 → (4, 3) 14.

Pažravé hľadanie ide rovno doprava a dole, kým h klesá. V siedmom kroku h na chvíľu stúpne (zo 16 na 17), pretože z (2, 3) ďalej doprava ani dole nevedie cesta (steny) a v okraji už nie je žiadne políčko s menším h. O dva kroky neskôr vstúpi do blata na (4, 2), pretože tadiaľ h znova klesá. Cenu 5 za krok do blata vôbec nevidí: priorita je len h, žiadne g.

9. A*: to najlepšie z oboch svetov

Definícia — A*

Vstup: úloha prehľadávania P = (S, s0, A, T, G, c) a heuristika h : S → ℝ≥0. Výstup: riešenie, alebo správa „riešenie neexistuje“.

A* je UCS (kroky 1–5 jeho definície vyššie, vrátane udržiavania g), v ktorom je okraj F prioritný front podľa

f(n) = g(n) + h(n)

namiesto podľa g(n): v kroku 3 sa z F odoberá stav s najmenším f(n). Hodnota f(n) je odhad ceny celej cesty zo štartu cez n do cieľa. Algoritmus skončí, len čo odoberie z F cieľový stav (alebo keď je F prázdny).

Veta. Nech h je konzistentná heuristika a ceny akcií sú ≥ ε pre nejaké ε > 0. Potom hodnoty f pozdĺž každej cesty neklesajú, A* rozbaľuje stavy v neklesajúcom poradí f a prvý rozbalený cieľový stav leží na optimálnej ceste s cenou C*. Rozbalí pritom každý stav s f(n) < C* a žiadny s f(n) > C*.

Pre h ≡ 0 je A* to isté čo UCS; bez g by to bolo pažravé hľadanie.

Laicky povedané

A* pri každom mieste sčíta, koľko ste už zaplatili a koľko podľa odhadu ešte zaplatíte. Vďaka prvej časti sa nenechá nalákať na drahú skratku, vďaka druhej nestráca čas cestami, ktoré vedú od cieľa. Keď je odhad optimistický, nájde vždy najlacnejšiu cestu — a preskúma pritom len to, čo preskúmať musí.

f(n) = g(n) + h(n)

f(n)
priorita v okraji: odhad ceny celého riešenia, ktoré vedie cez n. Rozbaľuje sa najmenšia, pretože to je najsľubnejší kandidát na optimum
g(n)
koľko už cesta zo štartu do n skutočne stála. Bez neho by A* nevedel o blate, ktorým prešiel, a správal by sa ako pažravé hľadanie
h(n)
odhad, koľko ešte zostáva do cieľa. Bez neho by A* nevedel, kde cieľ leží, a šíril by sa do všetkých strán ako UCS
Zo štartu: f((0, 0)) = 0 + 21 = 21. Na políčku (6, 13), dva kroky pred cieľom: f((6, 13)) = 23 + 2 = 25 = C*.
Veta — s konzistentnou heuristikou hodnota f pozdĺž cesty neklesá

Nech h je konzistentná heuristika, f = g + h, n ∈ S, a ∈ A(n), n′ = T(n, a) a cesta do n′ vedie cez n, takže g(n′) = g(n) + c(n, a). Potom f(n′) ≥ f(n).

Dôkaz. f(n′) =g(n) + c(n, a) + h(n′) ≥ g(n) + h(n) = f(n),

kde nerovnosť je presne konzistencia h(n) ≤ c(n, a) + h(n′). ∎

Z toho (tu len náčrtom) vyplýva, že A* rozbaľuje stavy v neklesajúcom poradí f: keby neskôr rozbalil stav s menším f, musel by naň viesť okraj so stavom s ešte menším f, ktorý mal prísť na rad skôr. Keď sa teda prvýkrát rozbalí cieľ (kde h = 0 a f = g), žiadna lacnejšia cesta už nezostáva.

S G
A* s manhattanskou vzdialenosťou preskúmaných políčok: 37 krokov: 25 cena cesty: 25

Ako A* postupuje, ukazuje tabuľka prvých krokov. Pri každom rozbalenom políčku je g, h a f, vpravo okraj po rozbalení v poradí, v akom ho front vydá (číslo za políčkom je jeho f; pri zhode ide skôr to, čo pribudlo skôr; farby podľa vzorca vyššie):

krokrozbalíghfokraj potom
1(0, 0)02121(0, 1) 21, (1, 0) 21
2(0, 1)12021(1, 0) 21, (0, 2) 21
3(1, 0)12021(0, 2) 21, (2, 0) 21
4(0, 2)21921(2, 0) 21, (0, 3) 21
5(2, 0)21921(0, 3) 21, (3, 0) 21
6(0, 3)31821(3, 0) 21, (1, 3) 21
……
23(8, 9)17421(2, 2) 23, (8, 1) 23, (7, 5) 23, (7, 9) 23, (6, 3) 25, (5, 2) 27
24(2, 2)61723(8, 1) 23, (7, 5) 23, (7, 9) 23, (3, 2) 23, (6, 3) 25, (5, 2) 27
25(8, 1)111223(7, 5) 23, (7, 9) 23, (3, 2) 23, (6, 3) 25, (8, 0) 25, (5, 2) 27

Prvých 23 krokov má f = 21: A* ide po políčkach, kde sa každým krokom priblíži k cieľu, takže čo pribudne v g, ubudne v h. Na políčku (8, 9) chodba končí stenou, a až teraz prídu na rad políčka s f = 23. Krok po suchu, ktorý sa od cieľa vzdiali, zvýši g o 1 a h tiež o 1, takže f o 2; obchádzanie steny preto zvyšuje f po dvojkách. Celkovo rozbalil 23 políčok s f = 21, 5 políčok s f = 23, 9 políčok s f = 25. Presne ako hovorí veta: všetkých 28 políčok s f < C* = 25 rozbaliť musel, z políčok s f = 25 len toľko, kým nenarazil na cieľ, a žiadne s f > 25.

Rovnaká cena 25 ako UCS, ale A* preskúmal 37 políčok namiesto 49. V malom bludisku je to rozdiel pár políčok; v mape s miliónmi križovatiek je to rozdiel medzi okamžitou odpoveďou a minútou čakania. Preto A* (a jeho varianty) nájdete v navigáciách, v hrách aj v plánovaní pohybu robotov.

import heapq

def a_star(start, cil, nasledovnici, h):
    okraj = [(h(start), 0, start)]        # (f, g, stav)
    g = {start: 0}
    rodic = {start: None}
    while okraj:
        _, cena, stav = heapq.heappop(okraj)
        if stav == cil:
            return cesta(rodic, cil), cena
        if cena > g[stav]:
            continue                       # zastaraný záznam, už poznáme lacnejší
        for dalsi, krok in nasledovnici(stav):
            nova = cena + krok
            if nova < g.get(dalsi, float("inf")):
                g[dalsi] = nova
                rodic[dalsi] = stav
                heapq.heappush(okraj, (nova + h(dalsi), nova, dalsi))
    return None

10. Porovnanie algoritmov

Definícia — vlastnosti algoritmu

Nech 𝒜 je algoritmus, ktorý na vstupe dostane úlohu prehľadávania P a vráti riešenie, alebo správu „riešenie neexistuje“. Algoritmus 𝒜 je

  1. úplný práve vtedy, keď pre každú úlohu P, ktorá má aspoň jedno riešenie (Π ≠ ∅), 𝒜 po konečnom počte krokov skončí a vráti riešenie π ∈ Π;
  2. optimálny práve vtedy, keď pre každú úlohu P s Π ≠ ∅ vráti riešenie π s cenou g(π) = C*.

Parametre, od ktorých závisí čas a pamäť (pre úlohu P = (S, s0, A, T, G, c)):

  1. b = maxs ∈ S |A(s)| — vetviaci faktor;
  2. d — hĺbka najplytšieho riešenia (najmenšia dĺžka cesty z s0 do G);
  3. m — najväčšia hĺbka priestoru (najväčšia dĺžka cesty z s0 bez opakovania stavov, prípadne ∞);
  4. C* — optimálna cena a ε > 0 — dolná hranica cien akcií.

Pre funkcie u, v ≥ 0 týchto parametrov píšeme u = O(v) práve vtedy, keď existujú konštanty K > 0 a x0 také, že u(x) ≤ K · v(x) pre všetky x ≥ x0. O(·) teda udáva, ako čas (počet vygenerovaných stavov) alebo pamäť (počet stavov držaných naraz) najviac rastú s týmito parametrami, až na konštantný násobok.

Laicky povedané

Úplný = keď cesta existuje, nájde ju. Optimálny = nájde tú najlacnejšiu. O(bd) znamená „s každým ďalším krokom do hĺbky sa práca znásobí“ — preto na pamäti toľko záleží.

AlgoritmusÚplnýOptimálnyČasPamäťV našom bludisku
BFSánopri rovnakých cenáchO(bd)O(bd)75 políčok, cena 37
DFSnie*nieO(bm)O(b·m)32 políčok, cena 59
IDDFSánopri rovnakých cenáchO(bd)O(b·d)—
UCSánoánoO(b1+⌊C*/ε⌋)O(b1+⌊C*/ε⌋)49 políčok, cena 25
Pažravénie*nieO(bm)O(bm)33 políčok, cena 59
A*ánoáno**podľa heuristikypodľa heuristiky37 políčok, cena 25

* V konečnom priestore so strážením opakovaných stavov nájdu riešenie vždy. ** S konzistentnou heuristikou.

11. Lokálne hľadanie: keď nejde o cestu

Pri niektorých úlohách nás cesta nezaujíma — chceme len dobrý výsledný stav. Rozvrh hodín, rozmiestnenie súčiastok na doske, nastavenie parametrov.

Definícia — lokálne hľadanie

Nech S je neprázdna množina stavov, f : S → ℝ funkcia (účelová funkcia) a N : S → 2S zobrazenie, ktoré každému stavu s ∈ S priradí konečnú neprázdnu množinu N(s) ⊆ S ∖ {s} (okolie stavu s, jeho prvky sú susedia). Úloha lokálneho hľadania je nájsť stav

s* ∈ argmaxs ∈ S f(s) = {s ∈ S : ∀s′ ∈ S : f(s) ≥ f(s′)},

prípadne aspoň stav s čo najväčšou hodnotou f. Stav s ∈ S je

  1. lokálne maximum práve vtedy, keď ∀s′ ∈ N(s) : f(s) ≥ f(s′);
  2. globálne maximum práve vtedy, keď ∀s′ ∈ S : f(s) ≥ f(s′).

Každé globálne maximum je aj lokálne, opačne to neplatí.

Horolezecký algoritmus (hill climbing). Vstup: S, f, N ako vyššie a počiatočný stav s ∈ S. Výstup: lokálne maximum.

  1. Vyber s′ ∈ argmaxt ∈ N(s) f(t) (najlepšieho suseda).
  2. Ak je f(s′) ≤ f(s), skonči a vráť s — je to lokálne maximum.
  3. Inak s ← s′ a pokračuj krokom 1.
Laicky povedané

Účelová funkcia hovorí, aké dobré je riešenie — čím vyššie, tým lepšie. Okolie sú riešenia, ktoré dostanete malou zmenou. Horolezec stojí v hmle na kopci, vidí len kúsok okolo seba a ide vždy tam, kde to stúpa. Keď už všade naokolo klesá, zastaví sa — aj keď opodiaľ môže stáť oveľa vyššia hora.

lokálne maximum globálne maximum stav s (poloha 0–1) účelová funkcia f(s) (bez jednotky)
Horolezecký algoritmus uviazne na lokálnom vrchole
def horolezec(s, okoli, f):
    while True:
        nejlepsi = max(okoli(s), key=f)    # najvyšší sused
        if f(nejlepsi) <= f(s):
            return s                       # nikde naokolo nie je vyššie: vrchol (možno len lokálny)
        s = nejlepsi
Príklad — horolezec z obrázka krok po kroku

Stav je číslo s ∈ ⟨0, 1⟩, okolie sú dva body o 0,02 vľavo a vpravo: N(s) = {s − 0,02, s + 0,02}. Krajina má dva vrcholy (lokálny pri s = 0,25, globálny pri 0,72) a horolezec vychádza z s = 0,05.

kroksf(s − 0,02)f(s)f(s + 0,02)
00,050,0600,0700,087
10,070,0700,0870,115
20,090,0870,1150,157
30,110,1150,1570,216
40,130,1570,2160,289
50,150,2160,2890,373
60,170,2890,3730,457
70,190,3730,4570,531
80,210,4570,5310,582
90,230,5310,5820,600
100,250,5820,6000,582

V každom riadku je pravý sused najvyšší, a tak sa ide doprava. V poslednom riadku sú obaja susedia (0,582) nižšie než f(0,25) = 0,600 a algoritmus skončí — na lokálnom maxime, hoci globálne maximum má f(0,72) = 0,950.

Proti uviaznutiu sa používa náhodný reštart (pustiť horolezca z rôznych miest), genetické algoritmy, ktoré krížia a mutujú celú populáciu riešení, alebo simulované žíhanie:

Definícia — simulované žíhanie

Vstup: S, f, N ako pri lokálnom hľadaní, počiatočný stav s ∈ S, počiatočná teplota T0 > 0, koeficient ochladzovania q ∈ (0, 1) a minimálna teplota Tmin ∈ (0, T0). Výstup: stav s ∈ S.

  1. T ← T0.
  2. Ak je T ≤ Tmin, skonči a vráť s.
  3. Vyber náhodne (rovnomerne) suseda s′ ∈ N(s) a vypočítaj Δ = f(s′) − f(s).
  4. Ak je Δ > 0, polož s ← s′. Ak je Δ ≤ 0, polož s ← s′ s pravdepodobnosťou exp(Δ/T), inak s ponechaj.
  5. T ← q · T a pokračuj krokom 2.

„Teplota“ T teda postupne klesá k nule a s ňou aj ochota prijať horšieho suseda. (Ochladzovanie T ← q · T je najbežnejšia voľba, používajú sa aj iné rozvrhy.)

Laicky povedané

Na začiatku si horolezec občas dovolí ísť aj z kopca — tým sa dostane z malého vrcholu k väčšiemu. Čím dlhšie hľadá, tým je opatrnejší, až nakoniec len stúpa.

P(prijať s′) = exp(Δ/T), keď Δ ≤ 0

P(prijať s′)
pravdepodobnosť, že horší sused bude napriek tomu prijatý — vďaka nej sa dá z lokálneho vrcholu zísť dole
exp
exponenciála exp(x) = ex robí z ľubovoľného záporného čísla hodnotu medzi 0 a 1, teda platnú pravdepodobnosť; prevzaté z fyziky, kde rovnaký tvar (Boltzmannov faktor) hovorí, aký pravdepodobný je stav s vyššou energiou
Δ = f(s′) − f(s)
o koľko je sused horší (záporné číslo). Čím horší, tým menšia šanca: malé zhoršenie sa prijme ľahko, veľký prepad skoro nikdy
T
teplota — ako veľmi sme ochotní riskovať. Vysoká T zmaže rozdiely (exponent je blízko 0, P blízko 1), nízka ich zväčší
Pre zhoršenie Δ = −0,1: pri T = 1 je P = exp(−0,1/1) = e−0,1 ≈ 0,905, pri T = 0,1 je P = exp(−0,1/0,1) = e−1 ≈ 0,368 a pri T = 0,01 už P = exp(−0,1/0,01) = e−10 ≈ 0,000 045 — horolezec skoro len stúpa.
def zihani(s, soused, f, T=1.0, ochlazeni=0.995, T_min=1e-3):
    while T > T_min:
        s2 = soused(s)                     # náhodný sused, nie najlepší
        delta = f(s2) - f(s)
        if delta > 0 or random() < exp(delta / T):
            s = s2                         # lepší vždy, horší len niekedy
        T *= ochlazeni                     # teplota pomaly klesá
    return s

A tu sa kruh uzatvára so strojovým učením. Tréning neurónovej siete je lokálne hľadanie v priestore jej váh θ: gradientný zostup opakuje θ ← θ − η ∇L(θ) — krok veľkosti η tým smerom, ktorým chybová funkcia L klesá najrýchlejšie. Je to horolezec obrátený hore nohami, ktorý namiesto najvyššieho vrcholu hľadá najnižšie údolie — a potýka sa s rovnakými problémami: s lokálnymi minimami a plošinami.

12. Zhrnutie

  • Úloha prehľadávania je šestica (S, s0, A, T, G, c). Riešenie je cesta v grafe stavov, optimálne riešenie to najlacnejšie.
  • Algoritmy sa líšia len tým, ktorý stav z okraja vyberú: front (BFS), zásobník (DFS), najmenšie g (UCS), najmenšie h (pažravé), najmenšie g + h (A*).
  • BFS nájde najmenej krokov, UCS najnižšiu cenu, A* najnižšiu cenu a pritom preskúma najmenej — ak má konzistentnú heuristiku.
  • Keď nejde o cestu, ale o výsledok, nastupuje lokálne hľadanie — a od neho je to už len krok ku gradientnému zostupu a strojovému učeniu.

Chcete si algoritmy ohmatať? Nechajte AI agenta napísať vlastnú vizualizáciu — a potom v kóde hľadajte ten jediný riadok, ktorým sa od seba líšia.

Máte na to iný názor? Napíšte na support@coden-app.com.

Agent na vašom počítači, ovládaný z telefónu