Grafy a stromy: štruktúra, na ktorej stojí hľadanie aj neurónové siete
Vrcholy a hrany, stupne, matica susednosti, cesty a komponenty, BFS a Dijkstra, stromy, minimálna kostra a výpočtový graf neurónu — všetko na jednom grafe so siedmimi mestami.
Séria Základy AI Diel 8 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
- Čo je graf, stupeň a súvislosť — a ako graf uložiť do počítača.
- Prečo cesta s najmenej hranami nemusí byť najkratšia a ako to riešia BFS a Dijkstra.
- Čo je strom, minimálna kostra a prečo je neurónová sieť acyklický graf počítaný tam a späť.
Čo sa hodí vedieť vopred: Množiny a relácie: stavebnica, z ktorej sa skladajú definície v AI
Mapa miest a ciest, sieť priateľov, stavový priestor hry, neurónová sieť aj výpočet, v ktorom sa počíta gradient — to všetko je graf: body spojené čiarami. Grafy sú najuniverzálnejšia štruktúra v informatike a v umelej inteligencii ich stretnete na každom kroku. Stromy sú ich najjednoduchší a najužitočnejší špeciálny prípad.
Celý diel postavíme na jednom grafe so siedmimi mestami A až G a cestami s dĺžkou. Postupne na ňom ukážeme stupne, ako graf uložiť, najkratšie cesty aj minimálnu kostru.
1. Čo je graf
Definícia — graf
Nech V je konečná neprázdna množina; jej prvky nazývame vrcholy.
- Neorientovaný graf je dvojica G = (V, E), kde E ⊆ {{u, v} : u, v ∈ V, u ≠ v}; prvky E sú hrany — dvojprvkové podmnožiny V. Vrcholy u, v sú susedné práve vtedy, keď {u, v} ∈ E.
- Orientovaný graf je dvojica G = (V, E), kde E ⊆ V × V; hrana je usporiadaná dvojica (u, v) — šípka z u do v. Orientovaný graf je teda relácia na V (pozri diel Množiny a relácie).
- Ohodnotený graf je trojica G = (V, E, w), kde (V, E) je graf podľa 1. alebo 2. a w : E → ℝ je funkcia, ktorá každej hrane e ∈ E priradí váhu w(e) (dĺžku, cenu, čas).
Ak nie je povedané inak, je graf v tomto diele neorientovaný.
Vrcholy sú „veci“, hrany „vzťahy“ medzi nimi. Pri neorientovanom grafe vzťah platí oboma smermi (cesta), pri orientovanom len jedným (jednosmerka, „sleduje na sociálnej sieti“). Váha je číslo na hrane — koľko kilometrov, koľko eur, koľko minút.
2. Stupeň vrcholu
Definícia — stupeň a princíp párnosti
Nech G = (V, E) je neorientovaný graf a v ∈ V. Stupeň vrcholu v je počet hrán, ktoré ho obsahujú (ktoré z neho vychádzajú):
deg(v) = |{e ∈ E : v ∈ e}|.
Veta (o podávaní rúk). Nech G = (V, E) je neorientovaný graf. Potom
∑v ∈ V deg(v) = 2 |E|.
Špeciálne je súčet stupňov v každom neorientovanom grafe párne číslo.
Každá hrana má dva konce, takže keď sčítate, koľko hrán vychádza z každého vrcholu, započítate každú hranu presne dvakrát. Preto je súčet stupňov vždy párny.
Súčet stupňov je 2 + 4 + 4 + 4 + 4 + 4 + 2 = 24 a hrán je 12, teda 24 = 2 · 12 — presne podľa vety (farby podľa vzorca nižšie). Stupeň má v praxi význam: v sociálnej sieti je to počet priateľov, na webe počet odkazov, a vrcholy s veľkým stupňom bývajú tie najdôležitejšie.
∑v ∈ V deg(v) = 2 · |E|
- ∑v ∈ V
- prejdeme všetky vrcholy — počítame hrany „z pohľadu vrcholov“
- deg(v)
- pri každom vrchole konce hrán, ktoré sa ho dotýkajú
- 2
- každá hrana {u, v} má dva konce, takže ju súčet stretne dvakrát — raz pri u, raz pri v
- |E|
- počet hrán — to isté počítanie „z pohľadu hrán“
Veta — vrcholov nepárneho stupňa je párny počet
Veta. Nech G = (V, E) je neorientovaný graf a L = {v ∈ V : deg(v) je nepárne} množina jeho vrcholov nepárneho stupňa. Potom |L| je párne číslo.
Dôkaz. Rozdeľme súčet stupňov na vrcholy párneho a nepárneho stupňa: ∑v ∈ V deg(v) = ∑v ∈ V ∖ L deg(v) + ∑v ∈ L deg(v). Ľavá strana je podľa vety o podávaní rúk 2|E|, párna; prvý súčet napravo je súčet párnych čísel, teda párny. Druhý teda musí byť tiež párny — a súčet |L| nepárnych čísel je párny práve vtedy, keď |L| je párne. ∎
Preto nejde nakresliť graf s piatimi vrcholmi, z ktorých každý má stupeň 3: súčet 15 by bol nepárny.
3. Ako graf uložiť do počítača
Počítač nevidí obrázok, potrebuje čísla. Zvyčajné sú dve podoby:
- Matica susednosti — tabuľka |V| × |V|, v bunke (u, v) je váha hrany, alebo 0. Rýchle „je tu hrana?“, ale pamäť rastie ako |V|2.
- Zoznam susedov — pri každom vrchole zoznam jeho susedov. Pamäť rastie len ako |V| + |E|, čo je pre riedke grafy (sociálna sieť, mapa) obrovský rozdiel.
Definícia — matica susednosti a zoznam susedov
Nech G = (V, E, w) je ohodnotený neorientovaný graf s nenulovými váhami (w(e) ≠ 0 pre všetky e ∈ E) a n = |V| vrcholmi, ktoré očíslujeme: V = {v1, …, vn}. Matica susednosti grafu G je štvorcová matica A ∈ ℝn×n s prvkami (pre i, j = 1, …, n)
Aij = w({vi, vj}), ak {vi, vj} ∈ E; inak Aij = 0.
Nenulové váhy sú potrebné, aby sa hrana dala odlíšiť od chýbajúcej hrany. Pri neohodnotenom grafe sa namiesto váhy píše 1. Keďže {vi, vj} = {vj, vi}, má neorientovaný graf Aij = Aji, teda A = AT.
Pre v ∈ V označme N(v) množinu jeho susedov:
N(v) = {u ∈ V : {u, v} ∈ E}.
Zoznam susedov je zobrazenie, ktoré každému vrcholu v ∈ V priradí množinu N(v), pri ohodnotenom grafe aj s váhami: {(u, w({u, v})) : u ∈ N(v)}. Platí |N(v)| = deg(v).
Príklad — koľko miesta zaberie graf miest a cestná sieť
Graf miest: matica má 7 · 7 = 49 buniek, z toho len 24 nenulových (každá cesta dvakrát, nad aj pod uhlopriečkou). Zoznamy susedov majú spolu ∑ deg(v) = 2 · 12 = 24 položiek (farby podľa vety o podávaní rúk vyššie). Riadok B matice je (4, 0, 5, 3, 7, 0, 0) — štyri nenulové čísla, pretože deg(B) = 4.
Cestná sieť s miliónom križovatiek, z každej v priemere 3 cesty: |E| = 106 · 3/2 = 1,5 · 106. Matica má (106)2 = 1012 buniek, zoznamy len 2 · 1,5 · 106 = 3 · 106 položiek — vyše tristotisíckrát menej.
Matica je symetrická podľa uhlopriečky — to je znak neorientovaného grafu. V AI sa matica susednosti používa pri grafových neurónových sieťach, ktoré sa učia priamo na grafoch (molekuly, sociálne siete, cestná sieť).
4. Cesty, cykly a súvislosť
Definícia — cesta, cyklus, súvislosť
Nech G = (V, E) je neorientovaný graf a u, v ∈ V.
- Cesta z u do v je postupnosť vrcholov (v0, v1, …, vk), k ≥ 0, taká, že v0 = u, vk = v, vi ≠ vj pre i ≠ j, {vi−1, vi} ∈ E pre i = 1, …, k. Jej dĺžka je k (počet hrán); v ohodnotenom grafe (V, E, w) je to súčet váh ∑i=1k w({vi−1, vi}).
- Cyklus je postupnosť vrcholov (v0, v1, …, vk) s k ≥ 3, v ktorej sú v0, …, vk−1 navzájom rôzne, vk = v0 a {vi−1, vi} ∈ E pre i = 1, …, k — cesta, ktorá sa po aspoň troch hranách vráti do východiskového vrcholu.
- G je súvislý práve vtedy, keď ∀u, v ∈ V existuje cesta z u do v.
- Definujme reláciu ∼ na V: u ∼ v práve vtedy, keď existuje cesta z u do v. Je to ekvivalencia (reflexívna vďaka ceste dĺžky 0, symetrická obrátením cesty, tranzitívna, pretože zo spojenia dvoch ciest sa dá vybrať cesta). Jej triedy ekvivalencie sú komponenty súvislosti G; tvoria teda rozklad V (pozri diel Množiny a relácie).
Graf je teda súvislý práve vtedy, keď má jediný komponent súvislosti.
Cesta je trasa, ktorá nikde nejde dvakrát cez to isté miesto. Cyklus je okruh. Súvislý graf je taký, kde sa dá dostať odkiaľkoľvek kamkoľvek; inak sa rozpadne na „ostrovy“ — komponenty.
5. Najkratšie cesty
Úloha „ako sa čo najrýchlejšie dostať z A do G“ je hľadanie najkratšej cesty — a to je presne to, čo robili algoritmy z dielu o prehľadávaní stavového priestoru. Záleží však na tom, čo meriame.
Definícia — vzdialenosť v grafe
Nech G = (V, E, w) je ohodnotený neorientovaný graf s nezápornými váhami, w : E → [0, ∞), a u, v ∈ V. Pre cestu π označme w(π) = ∑e ∈ π w(e) jej dĺžku (súčet váh jej hrán). Vzdialenosť vrcholov u a v je
d(u, v) = min {w(π) : π je cesta z u do v}, ak existuje aspoň jedna taká cesta; inak d(u, v) = ∞.
Minimum existuje, pretože ciest v konečnom grafe je konečne veľa. Pre neohodnotený graf berieme w(e) = 1 pre všetky e ∈ E; vzdialenosť je potom najmenší počet hrán cesty z u do v.
d(u, v) = minπ : u → v ∑e ∈ π w(e)
- d(u, v)
- jedno číslo pre dvojicu vrcholov — to, čo algoritmus hľadá
- minπ
- ciest z u do v býva veľa; nás zaujíma tá najlepšia, preto minimum cez všetky
- ∑e ∈ π
- dĺžka cesty sa skladá z hrán, ktoré na nej ležia — sčítame ich
- w(e)
- váha jednej hrany (kilometre); pri BFS je každá rovná 1 a súčet potom počíta hrany
Keď nás zaujíma počet hrán, stačí prehľadávanie do šírky: najprv susedia A, potom ich susedia… Číslo nad vrcholom je vzdialenosť od A v hranách:
from collections import deque
def bfs(sousede, start):
d = {start: 0}
fronta = deque([start])
while fronta:
u = fronta.popleft() # najprv ten, kto prišiel najskôr
for v in sousede[u]:
if v not in d: # prvá návšteva = najmenší počet hrán
d[v] = d[u] + 1
fronta.append(v)
return d
Z A tak dostaneme vrstvy: B, C vo vzdialenosti 1; D, E, F vo vzdialenosti 2; G vo vzdialenosti 3. Každý vrchol sa z frontu vyberie raz a každá hrana sa prezrie dvakrát (z oboch koncov), takže BFS beží v čase úmernom |V| + |E|.
Do G vedú tri hrany (napríklad A–B–E–G). Keď ale počítame dĺžku ciest, potrebujeme Dijkstrov algoritmus — vždy pokračuje z najbližšieho doteraz neuzavretého vrcholu. Číslo nad vrcholom je teraz najkratšia vzdialenosť v kilometroch a zvýraznená je najkratšia cesta do G:
Najkratšia cesta A–B–D–E–G má štyri hrany, teda viac než trojhranová cesta z BFS, ale dĺžku len 12. Cesta A–B–E–G by merala 4 + 7 + 3 = 14 (farby podľa vzorca vzdialenosti vyššie). Menej krokov neznamená kratšiu cestu — presne tento rozdiel medzi BFS a UCS sme videli na bludisku s blatom.
Dijkstrov algoritmus krok za krokom
Algoritmus si pri každom vrchole drží odhad d[v] — dĺžku najlepšej doteraz nájdenej cesty. Na začiatku je d[A] = 0 a všade inde ∞. Potom opakuje: vezmi neuzavretý vrchol s najmenším odhadom, uzavri ho a skús cezeň zlepšiť odhady susedov. Tomuto zlepšeniu sa hovorí relaxácia hrany:
d[v] ← min( d[v], d[u] + w(u, v) )
- d[v]
- doterajšia najlepšia známa dĺžka do v; zostane, ak nová cesta nie je lepšia — odhad sa nikdy nezhorší
- d[u]
- dĺžka do práve uzavretého u — tá je už určite najkratšia, takže na nej môžeme stavať
- w(u, v)
- posledný úsek z u do v; spolu s d[u] je to dĺžka kandidátskej cesty „cez u“
import heapq
def dijkstra(sousede, start):
d = {v: float("inf") for v in sousede}
d[start] = 0
hotove = set()
halda = [(0, start)]
while halda:
du, u = heapq.heappop(halda) # neuzavretý vrchol s najmenším d
if u in hotove:
continue
hotove.add(u) # d[u] je teraz definitívne
for v, w in sousede[u]:
if du + w < d[v]: # relaxácia hrany (u, v)
d[v] = du + w
heapq.heappush(halda, (d[v], v))
return d
Na grafe miest z A vyzerá priebeh takto. Riadok ukazuje odhady po uzavretí vrcholu v prvom stĺpci; tučne sú vzdialenosti, ktoré sú už definitívne:
| krok | uzavretý | d[A] | d[B] | d[C] | d[D] | d[E] | d[F] | d[G] |
|---|---|---|---|---|---|---|---|---|
| 1 | A | 0 | 4 | 2 | ∞ | ∞ | ∞ | ∞ |
| 2 | C | 0 | 4 | 2 | 8 | ∞ | 10 | ∞ |
| 3 | B | 0 | 4 | 2 | 7 | 11 | 10 | ∞ |
| 4 | D | 0 | 4 | 2 | 7 | 9 | 10 | ∞ |
| 5 | E | 0 | 4 | 2 | 7 | 9 | 10 | 12 |
| 6 | F | 0 | 4 | 2 | 7 | 9 | 10 | 12 |
| 7 | G | 0 | 4 | 2 | 7 | 9 | 10 | 12 |
Farby zodpovedajú členom relaxácie vyššie. V treťom kroku sa odhad D zlepší z 8 (cez C: 2 + 6) na 7 (cez B: 4 + 3) a vo štvrtom odhad E z 11 (cez B: 4 + 7) na 9 (cez D: 7 + 2). G dostane 9 + 3 = 12 a už sa nezmení: cesta cez F by merala 10 + 5 = 15.
Veta — Dijkstra je správny pre nezáporné váhy
Veta. Nech G = (V, E, w) je ohodnotený graf s nezápornými váhami, w : E → [0, ∞), štart ∈ V východiskový vrchol a d[·] odhady Dijkstrovho algoritmu spusteného zo štartu (opis vyššie). Potom pre každý vrchol u v okamihu, keď ho algoritmus uzavrie, platí
d[u] = d(štart, u).
Prečo: každá iná cesta do u musí niekde prvýkrát opustiť množinu uzavretých vrcholov, a to cez vrchol x, ktorého odhad je ≥ d[u] (inak by sme vybrali x, nie u). Zvyšok cesty z x do u má nezápornú dĺžku, takže celá cesta nie je kratšia než d[u]. ∎
So zápornou hranou táto úvaha padá — „zvyšok cesty“ by mohol dĺžku skrátiť. Pre záporné váhy je preto potrebný iný algoritmus (Bellman–Ford).
6. Stromy
Definícia — strom a koreňový strom
Nech G = (V, E) je neorientovaný graf.
- G je les práve vtedy, keď neobsahuje žiadny cyklus (každý jeho komponent súvislosti je potom strom).
- G je strom práve vtedy, keď je súvislý a neobsahuje žiadny cyklus.
Veta. Nech G = (V, E) je neorientovaný graf s n = |V| ≥ 1 vrcholmi. Potom sú nasledujúce tvrdenia ekvivalentné:
- G je strom;
- G je súvislý a |E| = n − 1;
- G neobsahuje cyklus a |E| = n − 1;
- ∀u, v ∈ V existuje práve jedna cesta z u do v.
Koreňový strom je dvojica (T, r), kde T = (V, E) je strom a r ∈ V vyznačený vrchol, koreň. Pre v ∈ V:
- hĺbka h(v) je dĺžka (počet hrán) jedinej cesty z r do v; špeciálne h(r) = 0;
- deti vrcholu v sú jeho susedia smerom od koreňa, t. j. vrcholy množiny {u ∈ N(v) : h(u) = h(v) + 1}; v je ich rodič;
- v je list práve vtedy, keď nemá žiadne dieťa.
Strom je graf, v ktorom nie je žiadny okruh — medzi dvoma miestami vedie vždy práve jedna trasa. Rodokmeň, priečinky na disku, vetvenie rozhodovania. Stačí ubrať jedinú hranu a strom sa rozpadne; stačí jednu pridať a vznikne cyklus.
V AI sú stromy všade. Rozhodovací strom klasifikuje sériou otázok od koreňa k listu. Algoritmy prehľadávania stavajú strom hľadania — strom BFS a DFS z dielu o prehľadávaní je presne toto. A šachový program prepočítava strom hry, v ktorom sú vrcholy pozície a hrany ťahy.
Veta — strom s n vrcholmi má n − 1 hrán (dôkaz indukciou)
Veta. Nech T = (V, E) je strom s n = |V| ≥ 1 vrcholmi. Potom |E| = n − 1.
Lema: nech T je strom s aspoň dvoma vrcholmi. Potom T má list, t. j. vrchol stupňa 1. Dôkaz lemy: vezmeme najdlhšiu cestu v strome; jej koncový vrchol nemôže mať ďalšieho suseda mimo cesty (cesta by sa dala predĺžiť), ani na ceste (vznikol by cyklus) — má teda stupeň 1.
Dôkaz vety indukciou podľa n. Základ: strom s 1 vrcholom má 0 hrán. Krok: v strome s n + 1 vrcholmi odoberme list aj s jeho jedinou hranou. Zvyšok je stále súvislý a bez cyklov, teda strom s n vrcholmi, a podľa indukčného predpokladu má n − 1 hrán. Pôvodný strom ich mal o jednu viac, n. ∎
Strom hľadania rastie rýchlo. Ak má každý vrchol b detí (vetvenie), je v hĺbke k práve bk vrcholov a v celom strome do hĺbky d:
N(b, d) = ∑k=0d bk = bd+1 − 1/b − 1
- N(b, d)
- koľko vrcholov musí prehľadávanie v najhoršom prípade prejsť — teda koľko ho to stojí času aj pamäte
- ∑k=0d
- sčítame po poschodiach, od koreňa (k = 0) až po najhlbšie poschodie d
- bk
- každý z vrcholov poschodia k − 1 má b detí, takže počet sa s každým poschodím násobí b
- bd+1 − 1/b − 1
- súčet geometrického radu v uzavretom tvare (pre b ≥ 2); ukazuje, že posledné poschodie preváži všetky predchádzajúce dohromady
7. Minimálna kostra
Definícia — kostra a Kruskalov algoritmus
Nech G = (V, E, w) je súvislý ohodnotený neorientovaný graf.
- Kostra grafu G je graf T = (V, F) s F ⊆ E, ktorý je strom — obsahuje všetky vrcholy G a len niektoré jeho hrany.
- Váha kostry je w(T) = ∑e ∈ F w(e).
- Kostra T je minimálna práve vtedy, keď w(T) ≤ w(T′) pre každú kostru T′ grafu G.
Kruskalov algoritmus.
- Vstup: súvislý ohodnotený neorientovaný graf G = (V, E, w) s m = |E| hranami.
- Výstup: množina hrán F ⊆ E taká, že (V, F) je minimálna kostra G.
- Zoraď hrany vzostupne podľa váhy: e1, …, em s w(e1) ≤ … ≤ w(em).
- Polož F := ∅.
- Pre i = 1, …, m: ak graf (V, F ∪ {ei}) neobsahuje cyklus, polož F := F ∪ {ei}; inak hranu ei vynechaj.
- Vráť F.
Zastavenie: algoritmus skončí po prejdení všetkých m hrán (smie skončiť skôr, len čo |F| = |V| − 1). Ide o pažravý algoritmus — v každom kroku berie najľahšiu prípustnú hranu a rozhodnutie už nemení — a predsa vždy nájde minimálnu kostru (veta nižšie).
Chcete prepojiť všetky mestá elektrickým vedením čo najlacnejšie. Stačí, aby sa dalo dostať odkiaľkoľvek kamkoľvek — okruhy sú zbytočné. Beriete najkratšie úseky ako prvé a vynecháte tie, ktoré by uzavreli okruh.
Kostra má 6 hrán pre 7 vrcholov — presne n − 1, ako hovorí veta o stromoch — a celkovú dĺžku 18. Šesť zvyšných ciest algoritmus odmietol, pretože by vytvorili cyklus.
Príklad — Kruskal na grafe miest hranu po hrane
Hrany zoradené podľa dĺžky (prijaté tučne): A–C (2), D–E (2), B–D (3), E–G (3), A–B (4), D–F (4), B–C (5), F–G (5), C–D (6), E–F (6), B–E (7), C–F (8).
Prvých šesť hrán sa prijme: A–C a D–E spoja dvojice, B–D pridá B k D–E, E–G pridá G a A–B prepojí skupinu {A, C} so skupinou {B, D, E, G}. D–F pripojí posledný vrchol F. Teraz sú už všetky vrcholy v jednom komponente, takže každá ďalšia hrana by spojila dva vrcholy toho istého komponentu — uzavrela by cyklus a algoritmus ju odmietne.
Dĺžka kostry: 2 + 2 + 3 + 3 + 4 + 4 = 18.
def kruskal(vrcholy, hrany):
rodic = {v: v for v in vrcholy} # každý vrchol je zatiaľ sám za seba
def koren(v): # zástupca komponentu, v ktorom v leží
while rodic[v] != v:
v = rodic[v]
return v
kostra = []
for u, v, w in sorted(hrany, key=lambda h: h[2]):
ku, kv = koren(u), koren(v)
if ku == kv:
continue # u a v sú už spojené: hrana by uzavrela cyklus
rodic[ku] = kv # zlúčenie dvoch komponentov
kostra.append((u, v, w))
return kostra
Veta — prečo pažravý postup funguje (vlastnosť rezu)
Nech G = (V, E, w) je súvislý ohodnotený neorientovaný graf. Rez je rozdelenie vrcholov na dve neprázdne časti S a V ∖ S, kde ∅ ≠ S ⊊ V. Hrana {u, v} ∈ E vedie cez rez práve vtedy, keď |{u, v} ∩ S| = 1 (jeden koniec v S, druhý mimo).
Veta. Nech (S, V ∖ S) je rez grafu G a e je hrana, ktorá vedie cez rez a má najmenšiu váhu zo všetkých hrán vedúcich cez tento rez. Potom existuje minimálna kostra G, ktorá hranu e obsahuje.
Dôkaz: vezmime ľubovoľnú minimálnu kostru T; ak obsahuje e, sme hotoví. Inak uvažujme T bez e. Pridaním e vznikne cyklus a ten musí rez prekročiť ešte raz, nejakou hranou f. Odobraním f dostaneme opäť kostru, s váhou w(T) + w(e) − w(f) ≤ w(T), pretože w(e) ≤ w(f). Je teda tiež minimálna a e obsahuje. ∎
Kruskal prijme hranu len vtedy, keď spája dva rôzne komponenty — a je to najľahšia hrana cez rez „jeden z tých komponentov proti zvyšku“, pretože všetky ľahšie už spracoval.
8. Acyklické grafy a výpočet neurónovej siete
Definícia — DAG a topologické usporiadanie
Nech G = (V, E) je orientovaný graf, E ⊆ V × V, s n = |V| vrcholmi.
- Orientovaný cyklus je postupnosť vrcholov (v0, v1, …, vk), k ≥ 1, taká, že (vi−1, vi) ∈ E pre i = 1, …, k a vk = v0 — po šípkach sa dá vrátiť do východiskového vrcholu.
- G je orientovaný acyklický graf (DAG) práve vtedy, keď neobsahuje žiadny orientovaný cyklus.
- Topologické usporiadanie grafu G je bijekcia π : V → {1, …, n} (poradie vrcholov) taká, že ∀(u, v) ∈ E : π(u) < π(v), t. j. každá hrana vedie zľava doprava — u je v poradí pred v.
Veta. Nech G = (V, E) je orientovaný graf. Potom G má topologické usporiadanie práve vtedy, keď je acyklický.
DAG je graf závislostí, v ktorom sa nič netočí v kruhu: najprv obuť ponožky, potom topánky. Topologické usporiadanie je poradie, v ktorom sa dá všetko urobiť tak, aby každý krok mal pripravené to, od čoho závisí.
Takto počíta každá neurónová sieť. Tu je výpočet jediného neurónu ŷ = σ(w · x + b) a jeho chyby L(ŷ, y) ako výpočtový graf: vstupy vľavo, operácie ako vrcholy, šípky ukazujú, čo od čoho závisí. Čísla sú topologické poradie:
Sieť počíta dopredu v topologickom poradí — najprv súčin, potom súčet, sigmoidu a chybu. A keď sa potom učí, ide tým istým grafom dozadu: spätné šírenie chyby (backpropagation) prechádza vrcholy v obrátenom topologickom poradí a v každom použije reťazové pravidlo. Knižnice ako PyTorch si tento graf stavajú samy pri každom výpočte. Mimochodom, aj graf nadväzností modulov v osnove tejto série je DAG.
Ako nájsť topologické poradie
Vstupný stupeň vrcholu v orientovaného grafu (V, E), deg−(v) = |{u ∈ V : (u, v) ∈ E}|, je počet šípok, ktoré do v vedú — počet vecí, od ktorých v závisí. Kahnov algoritmus opakovane berie vrchol, ktorý už na nič nečaká:
from collections import deque
def topologicke(vrcholy, hrany):
vstupni = {v: 0 for v in vrcholy}
for u, v in hrany:
vstupni[v] += 1
fronta = deque(v for v in vrcholy if vstupni[v] == 0) # vstupy: od ničoho nezávisia
poradi = []
while fronta:
u = fronta.popleft()
poradi.append(u)
for a, b in hrany:
if a == u:
vstupni[b] -= 1 # u je hotové, b čaká o jednu vec menej
if vstupni[b] == 0:
fronta.append(b)
return poradi if len(poradi) == len(vrcholy) else None # None: graf má cyklus
Na grafe neurónu dá poradie x, w, b, y, ·, +, σ, L: najprv štyri vstupy, potom súčin, súčet, sigmoida a strata. Keď má graf cyklus, vrcholy na cykle na seba čakajú navzájom, vstupný stupeň im nikdy neklesne na nulu a do poradia sa nedostanú — odtiaľ posledný riadok.
Príklad — prechod grafom neurónu tam a späť
Vstupy x = 2, w = 0,5, b = −1, správna odpoveď y = 1, strata L = (ŷ − y)2, σ(z) = 1/1 + e−z.
Dopredu (v topologickom poradí): w · x = 1; z = 1 + (−1) = 0; ŷ = σ(0) = 0,5; L = (0,5 − 1)2 = 0,25.
Dozadu (v obrátenom poradí; farby podľa rozboru reťazového pravidla nižšie): ∂L/∂ŷ = 2(ŷ − y) = −1; ∂ŷ/∂z = σ(z)(1 − σ(z)) = 0,25; ∂L/∂z = −1 · 0,25 = −0,25; ∂L/∂b = ∂L/∂z · 1 = −0,25; ∂L/∂w = ∂L/∂z · ∂z/∂w = −0,25 · 2 = −0,5 (∂z/∂w = x = 2).
Obe derivácie sú záporné: zväčšením w aj b sa strata zmenší — sieť sa posunie k odpovedi 1.
∂L/∂w = ∂L/∂ŷ · ∂ŷ/∂z · ∂z/∂w
- ∂L/∂w
- ako citlivo reaguje chyba na váhu — podľa toho sa váha pri učení posunie
- ∂L/∂ŷ
- hrana σ → L: o koľko sa zmení chyba, keď sa zmení výstup; počíta sa prvá, pretože L je v poradí posledná
- ∂ŷ/∂z
- hrana + → σ: ako sigmoida prenáša zmenu svojho vstupu
- ∂z/∂w
- hrany w → · → +: zmena súčtu na jednotku váhy, teda x
9. Grafy v umelej inteligencii
- Stavový priestor je graf: stavy sú vrcholy, akcie hrany s cenou. Prehľadávanie je hľadanie cesty v grafe.
- Výpočtové grafy neurónových sietí sú DAG; tréning je prechod tam a späť.
- Znalostné grafy ukladajú fakty ako hrany „Praha — je hlavné mesto — Česko“ a vyhľadávače z nich skladajú odpovede.
- Bayesovské siete sú DAG pravdepodobnostných závislostí — dostaneme sa k nim v module o pravdepodobnostných modeloch.
- Grafové neurónové siete sa učia priamo na grafoch: predpovedajú vlastnosti molekúl, dopravu alebo odporúčania.
10. Zhrnutie
- Graf je množina vrcholov a hrán; hrany môžu mať smer a váhu. Súčet stupňov je dvojnásobok počtu hrán.
- Graf sa ukladá maticou susednosti, alebo zoznamom susedov — ten je pre riedke grafy oveľa úspornejší.
- BFS nájde cestu s najmenej hranami, Dijkstra najkratšiu podľa váh — a to nemusí byť to isté.
- Strom je súvislý graf bez cyklov s n − 1 hranami; minimálnu kostru nájde pažravý Kruskalov algoritmus.
- Neurónová sieť je výpočtový DAG: dopredu sa počíta v topologickom poradí, gradient dozadu.
Máte na to iný názor? Napíšte na support@coden-app.com.