Grafy a stromy: struktura, na které stojí hledání i neuronové sítě
Vrcholy a hrany, stupně, matice sousednosti, cesty a komponenty, BFS a Dijkstra, stromy, minimální kostra a výpočetní graf neuronu — vše na jednom grafu se sedmi městy.
Série Základy AI Díl 8 z 83
- Mapa umělé inteligence: co je co a jak to do sebe zapadá
- Stručná historie umělé inteligence: od Turinga po jazykové modely
- Data, model, trénink, inference: základní pojmy na jednom příkladu
- Jak číst matematický zápis: symboly, indexy, sumy a kvantifikátory
- Množiny a relace: stavebnice, ze které se skládají definice v AI
- Funkce a zobrazení: proč je každý model v AI funkce
- Logika a důkazy: pravidla, podle kterých se v AI uvažuje
- Grafy a stromy: struktura, na které stojí hledání i neuronové sítě
- Lineární algebra pro strojové učení: vektory, matice a proč AI hlavně násobí
- Derivace, gradient a řetízkové pravidlo: jak model zjistí, kterým směrem se učit
- Gradientní sestup: jak se model učí
- Co je pravděpodobnost: od hodu kostkou ke Kolmogorovovým axiomům
- Podmíněná pravděpodobnost a Bayesova věta: jak změnit odhad, když se dozvíte něco nového
- Náhodná veličina a distribuční funkce: jedna funkce, která o rozdělení řekne všechno
- Funkce hustoty a pravděpodobnostní funkce: proč má spojitá veličina v bodě pravděpodobnost nula
- Střední hodnota, rozptyl a kovariance: kde rozdělení leží, jak je široké a co s čím souvisí
- Přehled pravděpodobnostních rozdělení: Bernoulli, binomické, Poisson, rovnoměrné, normální a exponenciální
- Zákon velkých čísel a centrální limitní věta: proč se průměry chovají tak předvídatelně
- Statistika a odhady parametrů: maximální věrohodnost, MAP a intervaly spolehlivosti
- Teorie informace: entropie, křížová entropie a KL divergence — odkud se berou ztrátové funkce
- Prohledávání stavového prostoru: BFS, DFS, UCS a A* s animacemi
- Hry a protivník: minimax, alfa-beta ořezávání a Monte Carlo tree search
- Splňování omezujících podmínek: backtracking, propagace omezení a AC-3 na sudoku a mapě krajů
- Logika a znalostní systémy: dopředné a zpětné řetězení, rezoluce a expertní systémy
- Plánování: STRIPS, svět kostek, heuristiky z relaxace a plánování v AI agentech
- Lokální hledání a simulované žíhání: obchodní cestující přes krajská města
- Genetické algoritmy: populace, křížení, mutace a výběr na batohu na výlet
- Evoluční strategie a genetické programování: od pravidla jedné pětiny po Keplerův zákon
- Optimalizace mravenčí kolonií: jak feromonová stopa najde krátkou cestu
- Optimalizace hejnem částic: setrvačnost, vlastní zkušenost a zkušenost hejna
- Neuroevoluce: evoluce vah i tvaru neuronových sítí a algoritmus NEAT
- Fuzzy množiny a funkce příslušnosti: jak zapsat „trochu teplo“
- Fuzzy logika a pravděpodobnost: proč „trochu teplo“ neznamená „pravděpodobně teplo“
- Fuzzy logika: t-normy, implikace a pravidla KDYŽ–PAK
- Fuzzy řízení: Mamdani a Sugeno od ventilátoru po kyvadlo na vozíku
- Fuzzy shlukování: algoritmus fuzzy c-means a body mezi shluky
- Neuro-fuzzy systémy (ANFIS): fuzzy pravidla, která se učí z dat
- Markovovy řetězce: přechodová matice, stacionární rozdělení a PageRank
- Skryté Markovovy modely: Viterbiho algoritmus a nepoctivé kasino
- Bayesovské sítě: proč je tráva mokrá a co z toho plyne pro postřikovač
- Kalmanův filtr: jak z nepřesných měření odhadnout, kde auto je a jak rychle jede
- Druhy strojového učení: s učitelem, bez učitele, s částečným učitelem a další
- Přeučení a regularizace: proč model, který zná data nazpaměť, selže
- Vyhodnocení modelu: křížová validace, F1 a křivka ROC
- Shlukování a snížení dimenze: k-means, PCA a t-SNE
- Klasifikace a regrese: dvě úlohy učení s učitelem
- Lineární regrese: nejmenší čtverce geometricky i maticově
- Polynomiální a nelineární regrese: jak přímku ohnout
- Logistická regrese: sigmoida, šance a křížová entropie
- Vícetřídní klasifikace a softmax: od dvou tříd k mnoha
- Naivní Bayesův klasifikátor: spamový filtr, který funguje lépe, než by měl
- Ztrátové funkce: co přesně model minimalizuje
- Rozhodovací stromy a k nejbližších sousedů: modely bez vzorců
- Ansámbly: náhodný les a gradient boosting
- SVM a jádrový trik: nejširší hranice a jak ji ohnout
- Signál a vzorkování: proč platí Nyquistův teorém
- Fourierova transformace: rozklad signálu na frekvence
- Filtry a konvoluce: vyhlazení, hrany a odstranění šumu
- Zvuk a řeč: spektrogram, melová škála a MFCC
- Obraz jako signál: pixely, barvy, konvoluce a hrany
- Perceptron a umělý neuron: nejjednodušší neuronová síť a co neumí
- Neuronová síť od nuly: zpětné šíření chyby krok za krokem
- Aktivace, inicializace a normalizace: drobnosti, na kterých hluboká síť stojí
- Konvoluční sítě: jak síť poznává obrázky
- Rekurentní sítě: paměť pro posloupnosti a mizející gradient
- LSTM a GRU: brány, které rozhodují, co si síť zapamatuje
- Autoenkodéry: síť, která se učí data stlačit a zase rozbalit
- Embeddingy a word2vec: jak se slovo stane vektorem
- Mechanismus pozornosti: jak se model naučí, kam se dívat
- Transformery a velké jazykové modely: architektura krok za krokem
- Tokenizace: jak se text rozseká na kousky pro model
- Předtrénování a škálování: proč větší model a víc dat funguje
- Doladění a RLHF: jak se z doplňovače textu stane asistent
- Prompty, kontext a RAG: jak modelu dát správné informace
- Variační autoenkodéry (VAE): nové obrázky ze spojitého latentního prostoru
- Generativní adversariální sítě (GAN): padělatel proti detektivovi
- Difúzní modely: jak se z čistého šumu stane obrázek
- Markovovy rozhodovací procesy: stavy, akce, odměny a Bellmanovy rovnice
- Q-learning a DQN: učení hodnoty akcí od tabulky po neuronovou síť
- Policy gradient a PPO: učení přímo strategie
- Jak funguje AI agent: jazykový model ve smyčce s nástroji
- Programování s AI agenty: zadání, testy a kontrola
- Vyhodnocení a bezpečnost AI systémů: jak měřit a jak hlídat
Celá osnova série 83 z 83 témat hotovo
- Co je graf, stupeň a souvislost — a jak graf uložit do počítače.
- Proč cesta s nejméně hranami nemusí být nejkratší a jak to řeší BFS a Dijkstra.
- Co je strom, minimální kostra a proč je neuronová síť acyklický graf počítaný tam a zpátky.
Co se hodí znát předem: Množiny a relace: stavebnice, ze které se skládají definice v AI
Mapa měst a silnic, síť přátel, stavový prostor hry, neuronová síť i výpočet, ve kterém se počítá gradient — to všechno je graf: body spojené čarami. Grafy jsou nejuniverzálnější struktura v informatice a v umělé inteligenci je potkáte na každém kroku. Stromy jsou jejich nejjednodušší a nejužitečnější zvláštní případ.
Celý díl postavíme na jednom grafu se sedmi městy A až G a silnicemi s délkou. Postupně na něm ukážeme stupně, jak graf uložit, nejkratší cesty i minimální kostru.
1. Co je graf
Definice — graf
Nechť V je konečná neprázdná množina; její prvky nazýváme vrcholy.
- Neorientovaný graf je dvojice G = (V, E), kde E ⊆ {{u, v} : u, v ∈ V, u ≠ v}; prvky E jsou hrany — dvouprvkové podmnožiny V. Vrcholy u, v jsou sousední, právě když {u, v} ∈ E.
- Orientovaný graf je dvojice G = (V, E), kde E ⊆ V × V; hrana je uspořádaná dvojice (u, v) — šipka z u do v. Orientovaný graf je tedy relace na V (viz díl Množiny a relace).
- Ohodnocený graf je trojice G = (V, E, w), kde (V, E) je graf podle 1. nebo 2. a w : E → ℝ je funkce, která každé hraně e ∈ E přiřadí váhu w(e) (délku, cenu, čas).
Kde není řečeno jinak, je v tomto díle graf neorientovaný.
Vrcholy jsou „věci“, hrany „vztahy“ mezi nimi. U neorientovaného grafu vztah platí oběma směry (silnice), u orientovaného jen jedním (jednosměrka, „sleduje na sociální síti“). Váha je číslo na hraně — kolik kilometrů, kolik korun, kolik minut.
2. Stupeň vrcholu
Definice — stupeň a princip sudosti
Nechť G = (V, E) je neorientovaný graf a v ∈ V. Stupeň vrcholu v je počet hran, které ho obsahují (které z něj vycházejí):
deg(v) = |{e ∈ E : v ∈ e}|.
Věta (o podávání rukou). Nechť G = (V, E) je neorientovaný graf. Pak
∑v ∈ V deg(v) = 2 |E|.
Speciálně je součet stupňů v každém neorientovaném grafu sudé číslo.
Každá hrana má dva konce, takže když sečtete, kolik hran vychází z každého vrcholu, započítáte každou hranu přesně dvakrát. Proto je součet stupňů vždycky sudý.
Součet stupňů je 2 + 4 + 4 + 4 + 4 + 4 + 2 = 24 a hran je 12, tedy 24 = 2 · 12 — přesně podle věty (barvy podle vzorce níže). Stupeň má v praxi význam: v sociální síti je to počet přátel, ve webu počet odkazů, a vrcholy s velkým stupněm bývají ty nejdůležitější.
∑v ∈ V deg(v) = 2 · |E|
- ∑v ∈ V
- projdeme všechny vrcholy — počítáme hrany „z pohledu vrcholů“
- deg(v)
- u každého vrcholu konce hran, které se ho dotýkají
- 2
- každá hrana {u, v} má dva konce, takže ji součet potká dvakrát — jednou u u, jednou u v
- |E|
- počet hran — totéž počítání „z pohledu hran“
Věta — vrcholů lichého stupně je sudý počet
Věta. Nechť G = (V, E) je neorientovaný graf a L = {v ∈ V : deg(v) je liché} množina jeho vrcholů lichého stupně. Pak |L| je sudé číslo.
Důkaz. Rozdělme součet stupňů na vrcholy sudého a lichého stupně: ∑v ∈ V deg(v) = ∑v ∈ V ∖ L deg(v) + ∑v ∈ L deg(v). Levá strana je podle věty o podávání rukou 2|E|, sudá; první součet napravo je součet sudých čísel, tedy sudý. Druhý tedy musí být sudý také — a součet |L| lichých čísel je sudý, právě když |L| je sudé. ∎
Proto nejde nakreslit graf s pěti vrcholy, z nichž každý má stupeň 3: součet 15 by byl lichý.
3. Jak graf uložit do počítače
Počítač nevidí obrázek, potřebuje čísla. Obvyklé jsou dvě podoby:
- Matice sousednosti — tabulka |V| × |V|, v buňce (u, v) je váha hrany, nebo 0. Rychlé „je tu hrana?“, ale paměť roste jako |V|2.
- Seznam sousedů — u každého vrcholu seznam jeho sousedů. Paměť roste jen jako |V| + |E|, což je pro řídké grafy (sociální síť, mapa) obrovský rozdíl.
Definice — matice sousednosti a seznam sousedů
Nechť G = (V, E, w) je ohodnocený neorientovaný graf s nenulovými vahami (w(e) ≠ 0 pro všechna e ∈ E) a n = |V| vrcholy, které očíslujeme: V = {v1, …, vn}. Matice sousednosti grafu G je čtvercová matice A ∈ ℝn×n s prvky (pro i, j = 1, …, n)
Aij = w({vi, vj}), je-li {vi, vj} ∈ E; jinak Aij = 0.
Nenulové váhy jsou potřeba, aby se hrana dala odlišit od chybějící hrany. U neohodnoceného grafu se místo váhy píše 1. Protože {vi, vj} = {vj, vi}, má neorientovaný graf Aij = Aji, tedy A = AT.
Pro v ∈ V označme N(v) množinu jeho sousedů:
N(v) = {u ∈ V : {u, v} ∈ E}.
Seznam sousedů je zobrazení, které každému vrcholu v ∈ V přiřadí množinu N(v), u ohodnoceného grafu i s vahami: {(u, w({u, v})) : u ∈ N(v)}. Platí |N(v)| = deg(v).
Příklad — kolik místa zabere graf měst a silniční síť
Graf měst: matice má 7 · 7 = 49 buněk, z toho jen 24 nenulových (každá silnice dvakrát, nad i pod úhlopříčkou). Seznamy sousedů mají dohromady ∑ deg(v) = 2 · 12 = 24 položek (barvy podle věty o podávání rukou výše). Řádek B matice je (4, 0, 5, 3, 7, 0, 0) — čtyři nenulová čísla, protože deg(B) = 4.
Silniční síť s milionem křižovatek, z každé v průměru 3 silnice: |E| = 106 · 3/2 = 1,5 · 106. Matice má (106)2 = 1012 buněk, seznamy jen 2 · 1,5 · 106 = 3 · 106 položek — přes třistatisíckrát méně.
Matice je symetrická podle úhlopříčky — to je znak neorientovaného grafu. V AI se matice sousednosti používá u grafových neuronových sítí, které se učí přímo na grafech (molekuly, sociální sítě, silniční síť).
4. Cesty, cykly a souvislost
Definice — cesta, cyklus, souvislost
Nechť G = (V, E) je neorientovaný graf a u, v ∈ V.
- Cesta z u do v je posloupnost vrcholů (v0, v1, …, vk), k ≥ 0, taková, že v0 = u, vk = v, vi ≠ vj pro i ≠ j, {vi−1, vi} ∈ E pro i = 1, …, k. Její délka je k (počet hran); v ohodnoceném grafu (V, E, w) je to součet vah ∑i=1k w({vi−1, vi}).
- Cyklus je posloupnost vrcholů (v0, v1, …, vk) s k ≥ 3, ve které jsou v0, …, vk−1 navzájem různé, vk = v0 a {vi−1, vi} ∈ E pro i = 1, …, k — cesta, která se po aspoň třech hranách vrátí do výchozího vrcholu.
- G je souvislý, právě když ∀u, v ∈ V existuje cesta z u do v.
- Definujme relaci ∼ na V: u ∼ v, právě když existuje cesta z u do v. Je to ekvivalence (reflexivní díky cestě délky 0, symetrická obrácením cesty, tranzitivní, protože ze spojení dvou cest lze vybrat cestu). Její třídy ekvivalence jsou komponenty souvislosti G; tvoří tedy rozklad V (viz díl Množiny a relace).
Graf je tedy souvislý, právě když má jedinou komponentu souvislosti.
Cesta je trasa, která nikde nejde dvakrát přes stejné místo. Cyklus je okruh. Souvislý graf je takový, kde se dá dojet odkudkoliv kamkoliv; jinak se rozpadne na „ostrovy“ — komponenty.
5. Nejkratší cesty
Úloha „jak se nejrychleji dostat z A do G“ je hledání nejkratší cesty — a to je přesně to, co dělaly algoritmy z dílu o prohledávání stavového prostoru. Záleží ale na tom, co měříme.
Definice — vzdálenost v grafu
Nechť G = (V, E, w) je ohodnocený neorientovaný graf s nezápornými vahami, w : E → [0, ∞), a u, v ∈ V. Pro cestu π označme w(π) = ∑e ∈ π w(e) její délku (součet vah jejích hran). Vzdálenost vrcholů u a v je
d(u, v) = min {w(π) : π je cesta z u do v}, existuje-li aspoň jedna taková cesta; jinak d(u, v) = ∞.
Minimum existuje, protože cest v konečném grafu je konečně mnoho. Pro neohodnocený graf bereme w(e) = 1 pro všechna e ∈ E; vzdálenost je pak nejmenší počet hran cesty z u do v.
d(u, v) = minπ : u → v ∑e ∈ π w(e)
- d(u, v)
- jedno číslo pro dvojici vrcholů — to, co algoritmus hledá
- minπ
- cest z u do v bývá mnoho; nás zajímá ta nejlepší, proto minimum přes všechny
- ∑e ∈ π
- délka cesty se skládá z hran, které na ní leží — sčítáme je
- w(e)
- váha jedné hrany (kilometry); u BFS je každá rovna 1, a součet pak počítá hrany
Když nás zajímá počet hran, stačí prohledávání do šířky: nejdřív sousedé A, pak jejich sousedé… Číslo nad vrcholem je vzdálenost od A v hranách:
from collections import deque
def bfs(sousede, start):
d = {start: 0}
fronta = deque([start])
while fronta:
u = fronta.popleft() # nejdřív ten, kdo přišel nejdřív
for v in sousede[u]:
if v not in d: # první návštěva = nejmenší počet hran
d[v] = d[u] + 1
fronta.append(v)
return d
Z A tak dostaneme vrstvy: B, C ve vzdálenosti 1; D, E, F ve vzdálenosti 2; G ve vzdálenosti 3. Každý vrchol se z fronty vybere jednou a každá hrana se prohlédne dvakrát (z obou konců), takže BFS běží v čase úměrném |V| + |E|.
Do G vedou tři hrany (například A–B–E–G). Když ale počítáme délku silnic, potřebujeme Dijkstrův algoritmus — vždy pokračuje z nejbližšího dosud neuzavřeného vrcholu. Číslo nad vrcholem je teď nejkratší vzdálenost v kilometrech a zvýrazněná je nejkratší cesta do G:
Nejkratší cesta A–B–D–E–G má čtyři hrany, tedy víc než tříhranová cesta z BFS, ale délku jen 12. Cesta A–B–E–G by měřila 4 + 7 + 3 = 14 (barvy podle vzorce vzdálenosti výše). Méně kroků neznamená kratší cestu — přesně tenhle rozdíl mezi BFS a UCS jsme viděli na bludišti s bahnem.
Dijkstrův algoritmus krok za krokem
Algoritmus si u každého vrcholu drží odhad d[v] — délku nejlepší dosud nalezené cesty. Na začátku je d[A] = 0 a všude jinde ∞. Pak opakuje: vezmi neuzavřený vrchol s nejmenším odhadem, uzavři ho a zkus přes něj zlepšit odhady sousedů. Tomu zlepšení se říká relaxace hrany:
d[v] ← min( d[v], d[u] + w(u, v) )
- d[v]
- dosavadní nejlepší známá délka do v; zůstane, pokud nová cesta není lepší — odhad se nikdy nezhorší
- d[u]
- délka do právě uzavřeného u — ta je už jistě nejkratší, takže na ni můžeme stavět
- w(u, v)
- poslední úsek z u do v; dohromady s d[u] je to délka kandidátní cesty „přes 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) # neuzavřený vrchol s nejmenším d
if u in hotove:
continue
hotove.add(u) # d[u] je teď definitivní
for v, w in sousede[u]:
if du + w < d[v]: # relaxace hrany (u, v)
d[v] = du + w
heapq.heappush(halda, (d[v], v))
return d
Na grafu měst z A vypadá průběh takhle. Řádek ukazuje odhady po uzavření vrcholu v prvním sloupci; tučně jsou vzdálenosti, které jsou už definitivní:
| krok | uzavřen | 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 |
Barvy odpovídají členům relaxace výše. Ve třetím kroku se odhad D zlepší z 8 (přes C: 2 + 6) na 7 (přes B: 4 + 3) a ve čtvrtém odhad E z 11 (přes B: 4 + 7) na 9 (přes D: 7 + 2). G dostane 9 + 3 = 12 a už se nezmění: cesta přes F by měřila 10 + 5 = 15.
Věta — Dijkstra je správný pro nezáporné váhy
Věta. Nechť G = (V, E, w) je ohodnocený graf s nezápornými vahami, w : E → [0, ∞), start ∈ V výchozí vrchol a d[·] odhady Dijkstrova algoritmu spuštěného ze startu (popis výše). Pak pro každý vrchol u v okamžiku, kdy ho algoritmus uzavře, platí
d[u] = d(start, u).
Proč: každá jiná cesta do u musí někde poprvé opustit množinu uzavřených vrcholů, a to přes vrchol x, jehož odhad je ≥ d[u] (jinak bychom vybrali x, ne u). Zbytek cesty z x do u má nezápornou délku, takže celá cesta není kratší než d[u]. ∎
Se zápornou hranou tahle úvaha padá — „zbytek cesty“ by mohl délku zkrátit. Pro záporné váhy je proto potřeba jiný algoritmus (Bellman–Ford).
6. Stromy
Definice — strom a kořenový strom
Nechť G = (V, E) je neorientovaný graf.
- G je les, právě když neobsahuje žádný cyklus (každá jeho komponenta souvislosti je pak strom).
- G je strom, právě když je souvislý a neobsahuje žádný cyklus.
Věta. Nechť G = (V, E) je neorientovaný graf s n = |V| ≥ 1 vrcholy. Pak jsou následující tvrzení ekvivalentní:
- G je strom;
- G je souvislý a |E| = n − 1;
- G neobsahuje cyklus a |E| = n − 1;
- ∀u, v ∈ V existuje právě jedna cesta z u do v.
Kořenový strom je dvojice (T, r), kde T = (V, E) je strom a r ∈ V vyznačený vrchol, kořen. Pro v ∈ V:
- hloubka h(v) je délka (počet hran) jediné cesty z r do v; speciálně h(r) = 0;
- děti vrcholu v jsou jeho sousedé směrem od kořene, tj. vrcholy množiny {u ∈ N(v) : h(u) = h(v) + 1}; v je jejich rodič;
- v je list, právě když nemá žádné dítě.
Strom je graf, ve kterém není žádný okruh — mezi dvěma místy vede vždycky právě jedna trasa. Rodokmen, složky na disku, větvení rozhodování. Stačí ubrat jedinou hranu a strom se rozpadne; stačí jednu přidat a vznikne cyklus.
V AI jsou stromy všude. Rozhodovací strom klasifikuje sérií otázek od kořene k listu. Algoritmy prohledávání stavějí strom hledání — strom BFS a DFS z dílu o prohledávání je přesně tohle. A šachový program propočítává strom hry, ve kterém jsou vrcholy pozice a hrany tahy.
Věta — strom s n vrcholy má n − 1 hran (důkaz indukcí)
Věta. Nechť T = (V, E) je strom s n = |V| ≥ 1 vrcholy. Pak |E| = n − 1.
Lemma: nechť T je strom s aspoň dvěma vrcholy. Pak T má list, tj. vrchol stupně 1. Důkaz lemmatu: vezmeme nejdelší cestu ve stromu; její koncový vrchol nemůže mít dalšího souseda mimo cestu (cesta by šla prodloužit), ani na cestě (vznikl by cyklus) — má tedy stupeň 1.
Důkaz věty indukcí podle n. Základ: strom s 1 vrcholem má 0 hran. Krok: ve stromu s n + 1 vrcholy odeberme list i s jeho jedinou hranou. Zbytek je pořád souvislý a bez cyklů, tedy strom s n vrcholy, a podle indukčního předpokladu má n − 1 hran. Původní strom jich měl o jednu víc, n. ∎
Strom hledání roste rychle. Má-li každý vrchol b dětí (větvení), je v hloubce k právě bk vrcholů a v celém stromu do hloubky d:
N(b, d) = ∑k=0d bk = bd+1 − 1/b − 1
- N(b, d)
- kolik vrcholů musí prohledávání nejhůř projít — tedy kolik ho to stojí času i paměti
- ∑k=0d
- sčítáme po patrech, od kořene (k = 0) až po nejhlubší patro d
- bk
- každý z vrcholů patra k − 1 má b dětí, takže počet se s každým patrem násobí b
- bd+1 − 1/b − 1
- součet geometrické řady v uzavřeném tvaru (pro b ≥ 2); ukazuje, že poslední patro převáží všechna předchozí dohromady
7. Minimální kostra
Definice — kostra a Kruskalův algoritmus
Nechť G = (V, E, w) je souvislý ohodnocený neorientovaný graf.
- Kostra grafu G je graf T = (V, F) s F ⊆ E, který je strom — obsahuje všechny vrcholy G a jen některé jeho hrany.
- Váha kostry je w(T) = ∑e ∈ F w(e).
- Kostra T je minimální, právě když w(T) ≤ w(T′) pro každou kostru T′ grafu G.
Kruskalův algoritmus.
- Vstup: souvislý ohodnocený neorientovaný graf G = (V, E, w) s m = |E| hranami.
- Výstup: množina hran F ⊆ E taková, že (V, F) je minimální kostra G.
- Seřaď hrany vzestupně podle váhy: e1, …, em s w(e1) ≤ … ≤ w(em).
- Polož F := ∅.
- Pro i = 1, …, m: neobsahuje-li graf (V, F ∪ {ei}) cyklus, polož F := F ∪ {ei}; jinak hranu ei vynech.
- Vrať F.
Zastavení: algoritmus skončí po projití všech m hran (smí skončit dřív, jakmile |F| = |V| − 1). Jde o hladový algoritmus — v každém kroku bere nejlehčí přípustnou hranu a rozhodnutí už nemění — a přesto vždy najde minimální kostru (věta níže).
Chcete propojit všechna města elektrickým vedením co nejlevněji. Stačí, aby se dalo dojet odkudkoliv kamkoliv — okruhy jsou zbytečné. Berete nejkratší úseky jako první a vynecháte ty, které by uzavřely okruh.
Kostra má 6 hran pro 7 vrcholů — přesně n − 1, jak říká věta o stromech — a celkovou délku 18. Šest zbylých silnic algoritmus odmítl, protože by vytvořily cyklus.
Příklad — Kruskal na grafu měst hranu po hraně
Hrany seřazené podle délky (přijaté tučně): 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).
Prvních šest hran se přijme: A–C a D–E spojí dvojice, B–D přidá B k D–E, E–G přidá G a A–B propojí skupinu {A, C} se skupinou {B, D, E, G}. D–F připojí poslední vrchol F. Teď už jsou všechny vrcholy v jedné komponentě, takže každá další hrana by spojila dva vrcholy téže komponenty — uzavřela by cyklus a algoritmus ji odmítne.
Délka kostry: 2 + 2 + 3 + 3 + 4 + 4 = 18.
def kruskal(vrcholy, hrany):
rodic = {v: v for v in vrcholy} # každý vrchol je zatím sám za sebe
def koren(v): # zástupce komponenty, ve které 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 už jsou spojené: hrana by uzavřela cyklus
rodic[ku] = kv # sloučení dvou komponent
kostra.append((u, v, w))
return kostra
Věta — proč hladový postup funguje (vlastnost řezu)
Nechť G = (V, E, w) je souvislý ohodnocený neorientovaný graf. Řez je rozdělení vrcholů na dvě neprázdné části S a V ∖ S, kde ∅ ≠ S ⊊ V. Hrana {u, v} ∈ E vede přes řez, právě když |{u, v} ∩ S| = 1 (jeden konec v S, druhý mimo).
Věta. Nechť (S, V ∖ S) je řez grafu G a e je hrana, která vede přes řez a má nejmenší váhu ze všech hran vedoucích přes tento řez. Pak existuje minimální kostra G, která hranu e obsahuje.
Důkaz: vezměme libovolnou minimální kostru T; obsahuje-li e, jsme hotovi. Jinak uvažujme T bez e. Přidáním e vznikne cyklus a ten musí řez překročit ještě jednou, nějakou hranou f. Odebráním f dostaneme zase kostru, s vahou w(T) + w(e) − w(f) ≤ w(T), protože w(e) ≤ w(f). Je tedy také minimální a e obsahuje. ∎
Kruskal přijme hranu jen tehdy, když spojuje dvě různé komponenty — a je to nejlehčí hrana přes řez „jedna z těch komponent proti zbytku“, protože všechny lehčí už zpracoval.
8. Acyklické grafy a výpočet neuronové sítě
Definice — DAG a topologické uspořádání
Nechť G = (V, E) je orientovaný graf, E ⊆ V × V, s n = |V| vrcholy.
- Orientovaný cyklus je posloupnost vrcholů (v0, v1, …, vk), k ≥ 1, taková, že (vi−1, vi) ∈ E pro i = 1, …, k a vk = v0 — po šipkách se dá vrátit do výchozího vrcholu.
- G je orientovaný acyklický graf (DAG), právě když neobsahuje žádný orientovaný cyklus.
- Topologické uspořádání grafu G je bijekce π : V → {1, …, n} (pořadí vrcholů) taková, že ∀(u, v) ∈ E : π(u) < π(v), tj. každá hrana vede zleva doprava — u je v pořadí před v.
Věta. Nechť G = (V, E) je orientovaný graf. Pak G má topologické uspořádání, právě když je acyklický.
DAG je graf závislostí, ve kterém se nic netočí v kruhu: nejdřív obléct ponožky, pak boty. Topologické uspořádání je pořadí, ve kterém se dá všechno udělat tak, aby každý krok měl připravené, na čem závisí.
Takhle počítá každá neuronová síť. Tady je výpočet jediného neuronu ŷ = σ(w · x + b) a jeho chyby L(ŷ, y) jako výpočetní graf: vstupy vlevo, operace jako vrcholy, šipky ukazují, co na čem závisí. Čísla jsou topologické pořadí:
Síť počítá dopředu v topologickém pořadí — nejdřív součin, pak součet, sigmoidu a chybu. A když se pak učí, jde stejným grafem pozpátku: zpětné šíření chyby (backpropagation) prochází vrcholy v obráceném topologickém pořadí a v každém použije řetízkové pravidlo. Knihovny jako PyTorch si tenhle graf staví samy při každém výpočtu. Mimochodem, i graf návazností modulů v osnově této série je DAG.
Jak topologické pořadí najít
Vstupní stupeň vrcholu v orientovaného grafu (V, E), deg−(v) = |{u ∈ V : (u, v) ∈ E}|, je počet šipek, které do v vedou — počet věcí, na kterých v závisí. Kahnův algoritmus opakovaně bere vrchol, který už na ničem nečeká:
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: na ničem nezávisí
poradi = []
while fronta:
u = fronta.popleft()
poradi.append(u)
for a, b in hrany:
if a == u:
vstupni[b] -= 1 # u je hotové, b čeká o jednu věc méně
if vstupni[b] == 0:
fronta.append(b)
return poradi if len(poradi) == len(vrcholy) else None # None: graf má cyklus
Na grafu neuronu dá pořadí x, w, b, y, ·, +, σ, L: nejdřív čtyři vstupy, pak součin, součet, sigmoida a ztráta. Když má graf cyklus, vrcholy na cyklu na sebe čekají navzájem, vstupní stupeň jim nikdy neklesne na nulu a do pořadí se nedostanou — odtud poslední řádek.
Příklad — průchod grafem neuronu tam a zpátky
Vstupy x = 2, w = 0,5, b = −1, správná odpověď y = 1, ztráta L = (ŷ − y)2, σ(z) = 1/1 + e−z.
Dopředu (v topologickém pořadí): w · x = 1; z = 1 + (−1) = 0; ŷ = σ(0) = 0,5; L = (0,5 − 1)2 = 0,25.
Pozpátku (v obráceném pořadí; barvy podle rozboru řetízkového pravidla níže): ∂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).
Obě derivace jsou záporné: zvětšením w i b se ztráta zmenší — síť se posune k odpovědi 1.
∂L/∂w = ∂L/∂ŷ · ∂ŷ/∂z · ∂z/∂w
- ∂L/∂w
- jak citlivě reaguje chyba na váhu — podle toho se váha při učení posune
- ∂L/∂ŷ
- hrana σ → L: o kolik se změní chyba, když se změní výstup; počítá se první, protože L je v pořadí poslední
- ∂ŷ/∂z
- hrana + → σ: jak sigmoida přenáší změnu svého vstupu
- ∂z/∂w
- hrany w → · → +: změna součtu na jednotku váhy, tedy x
9. Grafy v umělé inteligenci
- Stavový prostor je graf: stavy jsou vrcholy, akce hrany s cenou. Prohledávání je hledání cesty v grafu.
- Výpočetní grafy neuronových sítí jsou DAG; trénink je průchod tam a zpátky.
- Znalostní grafy ukládají fakta jako hrany „Praha — je hlavní město — Česko“ a vyhledávače z nich skládají odpovědi.
- Bayesovské sítě jsou DAG pravděpodobnostních závislostí — dostaneme se k nim v modulu o pravděpodobnostních modelech.
- Grafové neuronové sítě se učí přímo na grafech: předpovídají vlastnosti molekul, dopravu nebo doporučení.
10. Shrnutí
- Graf je množina vrcholů a hran; hrany mohou mít směr a váhu. Součet stupňů je dvojnásobek počtu hran.
- Graf se ukládá maticí sousednosti, nebo seznamem sousedů — ten je pro řídké grafy mnohem úspornější.
- BFS najde cestu s nejméně hranami, Dijkstra nejkratší podle vah — a to nemusí být totéž.
- Strom je souvislý graf bez cyklů s n − 1 hranami; minimální kostru najde hladový Kruskalův algoritmus.
- Neuronová síť je výpočetní DAG: dopředu se počítá v topologickém pořadí, gradient pozpátku.
Máte na to jiný názor? Napište na support@coden-app.com.