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.
Séria Základy AI Diel 21 z 52
- Mapa umelej inteligencie: čo je čo a ako to do seba zapadá
- Stručná história umelej inteligencie: od Turinga po jazykové modely
- Dáta, model, tréning, inferencia: základné pojmy na jednom príklade
- Ako čítať matematický zápis: symboly, indexy, sumy a kvantifikátory
- Množiny a relácie: stavebnica, z ktorej sa skladajú definície v AI
- Funkcie a zobrazenia: prečo je každý model v AI funkcia
- Logika a dôkazy: pravidlá, podľa ktorých sa v AI uvažuje
- Grafy a stromy: štruktúra, na ktorej stojí hľadanie aj neurónové siete
- Lineárna algebra pre strojové učenie: vektory, matice a prečo AI hlavne násobí
- Derivácie, gradient a reťazové pravidlo: ako model zistí, ktorým smerom sa učiť
- Gradientný zostup: ako sa model učí
- Čo je pravdepodobnosť: od hodu kockou ku Kolmogorovovým axiómam
- Podmienená pravdepodobnosť a Bayesova veta: ako zmeniť odhad, keď sa dozviete niečo nové
- Náhodná premenná a distribučná funkcia: jedna funkcia, ktorá o rozdelení povie všetko
- Funkcia hustoty a pravdepodobnostná funkcia: prečo má spojitá premenná v bode pravdepodobnosť nula
- Stredná hodnota, rozptyl a kovariancia: kde rozdelenie leží, aké je široké a čo s čím súvisí
- Prehľad pravdepodobnostných rozdelení: Bernoulliho, binomické, Poissonovo, rovnomerné, normálne a exponenciálne
- Zákon veľkých čísel a centrálna limitná veta: prečo sa priemery správajú tak predvídateľne
- Štatistika a odhady parametrov: maximálna vierohodnosť, MAP a intervaly spoľahlivosti
- Teória informácie: entropia, krížová entropia a KL divergencia — odkiaľ sa berú stratové funkcie
- Prehľadávanie stavového priestoru: BFS, DFS, UCS a A* s animáciami
- Hry a protivník: minimax, alfa-beta orezávanie a Monte Carlo tree search
- Splňovanie obmedzujúcich podmienok: backtracking, propagácia obmedzení a AC-3 na sudoku a mape krajov
- Logika a znalostné systémy: dopredné a spätné reťazenie, rezolúcia a expertné systémy
- Plánovanie: STRIPS, svet kociek, heuristiky z relaxácie a plánovanie v AI agentoch
- Lokálne hľadanie a simulované žíhanie: obchodný cestujúci cez krajské mestá
- Genetické algoritmy: populácia, kríženie, mutácia a výber na batohu na výlet
- Evolučné stratégie a genetické programovanie: od pravidla jednej pätiny po Keplerov zákon
- Optimalizácia mravčou kolóniou: ako feromónová stopa nájde krátku cestu
- Optimalizácia rojom častíc: zotrvačnosť, vlastná skúsenosť a skúsenosť roja
- Neuroevolúcia: evolúcia váh aj tvaru neurónových sietí a algoritmus NEAT
- Fuzzy množiny a funkcie príslušnosti: ako zapísať „trochu teplo“
- Fuzzy logika a pravdepodobnosť: prečo „trochu teplo“ neznamená „pravdepodobne teplo“
- Fuzzy logika: t-normy, implikácie a pravidlá AK–TAK
- Fuzzy riadenie: Mamdani a Sugeno od ventilátora po kyvadlo na vozíku
- Fuzzy zhlukovanie: algoritmus fuzzy c-means a body medzi zhlukmi
- Neuro-fuzzy systémy (ANFIS): fuzzy pravidlá, ktoré sa učia z dát
- Markovove reťazce: prechodová matica, stacionárne rozdelenie a PageRank
- Skryté Markovove modely: Viterbiho algoritmus a nepoctivé kasíno
- Bayesovské siete: prečo je tráva mokrá a čo z toho vyplýva pre postrekovač
- Kalmanov filter: ako z nepresných meraní odhadnúť, kde auto je a ako rýchlo ide
- Druhy strojového učenia: s učiteľom, bez učiteľa, s čiastočným učiteľom a ďalšie
- Prompty, kontext a RAG: ako modelu dať správne informácie
- Variačné autoenkodéry (VAE): nové obrázky zo spojitého latentného priestoru
- Generatívne adversariálne siete (GAN): falšovateľ proti detektívovi
- Difúzne modely: ako sa z čistého šumu stane obrázok
- Markovove rozhodovacie procesy: stavy, akcie, odmeny a Bellmanove rovnice
- Q-learning a DQN: učenie hodnoty akcií od tabuľky po neurónovú sieť
- Policy gradient a PPO: učenie priamo stratégie
- Ako funguje AI agent: jazykový model v slučke s nástrojmi
- Programovanie s AI agentmi: zadanie, testy a kontrola
- Vyhodnocovanie a bezpečnosť AI systémov: ako merať a ako strážiť
Celá osnova série Hotovo: 52 z 83 tém
- 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:
- S je neprázdna množina; jej prvky nazývame stavy;
- s0 ∈ S; tento stav nazývame počiatočný stav;
- 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“;
- T : 𝒟 → S je prechodová funkcia; pre (s, a) ∈ 𝒟 je stav s′ = T(s, a) nasledovník stavu s (po akcii a);
- G ⊆ S je množina cieľových stavov;
- 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.
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.
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:
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
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*.
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:
- 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};
- 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í.
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):
- F ← (s0), E ← ∅.
- Ak je F prázdny, skonči: riešenie neexistuje.
- Odober z F stav s, ktorý doň pribudol najskôr.
- Ak je s ∈ G, skonči a vráť cestu z s0 do s.
- 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.
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.
V bludisku je vlna dobre vidieť — rozlieva sa všetkými smermi, kým nenarazí na cieľ:
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):
- F ← (s0), E ← ∅.
- Ak je F prázdny, skonči: riešenie neexistuje.
- Odober z F stav s, ktorý doň pribudol naposledy (vrchol zásobníka).
- Ak je s ∈ G, skonči a vráť cestu z s0 do s.
- 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.
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.
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).
| krok | BFS rozbalí | front potom | DFS rozbalí | zásobník potom |
|---|---|---|---|---|
| 1 | A | B C | A | B C |
| 2 | B | C D E | B | D E C |
| 3 | C | D E F G | D | H I E C |
| 4 | D | E F G H I | H | I E C |
| 5 | E | F G H I | I | E C |
| 6 | F | G H I | E | C |
| 7 | G | H I J | C | F G |
| 8 | H | I J | F | G |
| 9 | I | J | G | J |
| 10 | J | — | 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:
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):
- ℓ ← 0.
- Spusti DLS(ℓ).
- Ak DLS(ℓ) našlo riešenie, skonči a vráť ho.
- ℓ ← ℓ + 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).
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é
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):
- F ← {s0}, g(s0) ← 0, E ← ∅.
- Ak je F = ∅, skonči: riešenie neexistuje.
- Odober z F stav n s najmenším g(n).
- Ak je n ∈ G, skonči a vráť nájdenú cestu do n (jej cena je g(n)).
- 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.
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.
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
- prípustná (admissible) práve vtedy, keď ∀n ∈ S : h(n) ≤ h*(n);
- 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).
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
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
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):
- F ← {s0}, E ← ∅.
- Ak je F = ∅, skonči: riešenie neexistuje.
- Odober z F stav n s najmenším h(n) — stav, ktorý je podľa odhadu najbližšie k cieľu.
- Ak je n ∈ G, skonči a vráť cestu z s0 do n.
- 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é.
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“.
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.
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
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.
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):
| krok | rozbalí | g | h | f | okraj potom |
|---|---|---|---|---|---|
| 1 | (0, 0) | 0 | 21 | 21 | (0, 1) 21, (1, 0) 21 |
| 2 | (0, 1) | 1 | 20 | 21 | (1, 0) 21, (0, 2) 21 |
| 3 | (1, 0) | 1 | 20 | 21 | (0, 2) 21, (2, 0) 21 |
| 4 | (0, 2) | 2 | 19 | 21 | (2, 0) 21, (0, 3) 21 |
| 5 | (2, 0) | 2 | 19 | 21 | (0, 3) 21, (3, 0) 21 |
| 6 | (0, 3) | 3 | 18 | 21 | (3, 0) 21, (1, 3) 21 |
| … | … | ||||
| 23 | (8, 9) | 17 | 4 | 21 | (2, 2) 23, (8, 1) 23, (7, 5) 23, (7, 9) 23, (6, 3) 25, (5, 2) 27 |
| 24 | (2, 2) | 6 | 17 | 23 | (8, 1) 23, (7, 5) 23, (7, 9) 23, (3, 2) 23, (6, 3) 25, (5, 2) 27 |
| 25 | (8, 1) | 11 | 12 | 23 | (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
- ú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 π ∈ Π;
- 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)):
- b = maxs ∈ S |A(s)| — vetviaci faktor;
- d — hĺbka najplytšieho riešenia (najmenšia dĺžka cesty z s0 do G);
- m — najväčšia hĺbka priestoru (najväčšia dĺžka cesty z s0 bez opakovania stavov, prípadne ∞);
- 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.
Ú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 | Čas | Pamäť | V našom bludisku |
|---|---|---|---|---|---|
| BFS | áno | pri rovnakých cenách | O(bd) | O(bd) | 75 políčok, cena 37 |
| DFS | nie* | nie | O(bm) | O(b·m) | 32 políčok, cena 59 |
| IDDFS | áno | pri rovnakých cenách | O(bd) | O(b·d) | — |
| UCS | áno | áno | O(b1+⌊C*/ε⌋) | O(b1+⌊C*/ε⌋) | 49 políčok, cena 25 |
| Pažravé | nie* | nie | O(bm) | O(bm) | 33 políčok, cena 59 |
| A* | áno | áno** | podľa heuristiky | podľa heuristiky | 37 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
- lokálne maximum práve vtedy, keď ∀s′ ∈ N(s) : f(s) ≥ f(s′);
- 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.
- Vyber s′ ∈ argmaxt ∈ N(s) f(t) (najlepšieho suseda).
- Ak je f(s′) ≤ f(s), skonči a vráť s — je to lokálne maximum.
- Inak s ← s′ a pokračuj krokom 1.
Úč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.
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.
| krok | s | f(s − 0,02) | f(s) | f(s + 0,02) |
|---|---|---|---|---|
| 0 | 0,05 | 0,060 | 0,070 | 0,087 |
| 1 | 0,07 | 0,070 | 0,087 | 0,115 |
| 2 | 0,09 | 0,087 | 0,115 | 0,157 |
| 3 | 0,11 | 0,115 | 0,157 | 0,216 |
| 4 | 0,13 | 0,157 | 0,216 | 0,289 |
| 5 | 0,15 | 0,216 | 0,289 | 0,373 |
| 6 | 0,17 | 0,289 | 0,373 | 0,457 |
| 7 | 0,19 | 0,373 | 0,457 | 0,531 |
| 8 | 0,21 | 0,457 | 0,531 | 0,582 |
| 9 | 0,23 | 0,531 | 0,582 | 0,600 |
| 10 | 0,25 | 0,582 | 0,600 | 0,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.
- T ← T0.
- Ak je T ≤ Tmin, skonči a vráť s.
- Vyber náhodne (rovnomerne) suseda s′ ∈ N(s) a vypočítaj Δ = f(s′) − f(s).
- Ak je Δ > 0, polož s ← s′. Ak je Δ ≤ 0, polož s ← s′ s pravdepodobnosťou exp(Δ/T), inak s ponechaj.
- 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.)
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äčší
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.