Přeskočit na obsah
coden

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.

14 minut Matěj Žalmánek 2026-10-15

Série Základy AI Díl 8 z 83
  1. Mapa umělé inteligence: co je co a jak to do sebe zapadá
  2. Stručná historie umělé inteligence: od Turinga po jazykové modely
  3. Data, model, trénink, inference: základní pojmy na jednom příkladu
  4. Jak číst matematický zápis: symboly, indexy, sumy a kvantifikátory
  5. Množiny a relace: stavebnice, ze které se skládají definice v AI
  6. Funkce a zobrazení: proč je každý model v AI funkce
  7. Logika a důkazy: pravidla, podle kterých se v AI uvažuje
  8. Grafy a stromy: struktura, na které stojí hledání i neuronové sítě
  9. Lineární algebra pro strojové učení: vektory, matice a proč AI hlavně násobí
  10. Derivace, gradient a řetízkové pravidlo: jak model zjistí, kterým směrem se učit
  11. Gradientní sestup: jak se model učí
  12. Co je pravděpodobnost: od hodu kostkou ke Kolmogorovovým axiomům
  13. Podmíněná pravděpodobnost a Bayesova věta: jak změnit odhad, když se dozvíte něco nového
  14. Náhodná veličina a distribuční funkce: jedna funkce, která o rozdělení řekne všechno
  15. Funkce hustoty a pravděpodobnostní funkce: proč má spojitá veličina v bodě pravděpodobnost nula
  16. Střední hodnota, rozptyl a kovariance: kde rozdělení leží, jak je široké a co s čím souvisí
  17. Přehled pravděpodobnostních rozdělení: Bernoulli, binomické, Poisson, rovnoměrné, normální a exponenciální
  18. Zákon velkých čísel a centrální limitní věta: proč se průměry chovají tak předvídatelně
  19. Statistika a odhady parametrů: maximální věrohodnost, MAP a intervaly spolehlivosti
  20. Teorie informace: entropie, křížová entropie a KL divergence — odkud se berou ztrátové funkce
  21. Prohledávání stavového prostoru: BFS, DFS, UCS a A* s animacemi
  22. Hry a protivník: minimax, alfa-beta ořezávání a Monte Carlo tree search
  23. Splňování omezujících podmínek: backtracking, propagace omezení a AC-3 na sudoku a mapě krajů
  24. Logika a znalostní systémy: dopředné a zpětné řetězení, rezoluce a expertní systémy
  25. Plánování: STRIPS, svět kostek, heuristiky z relaxace a plánování v AI agentech
  26. Lokální hledání a simulované žíhání: obchodní cestující přes krajská města
  27. Genetické algoritmy: populace, křížení, mutace a výběr na batohu na výlet
  28. Evoluční strategie a genetické programování: od pravidla jedné pětiny po Keplerův zákon
  29. Optimalizace mravenčí kolonií: jak feromonová stopa najde krátkou cestu
  30. Optimalizace hejnem částic: setrvačnost, vlastní zkušenost a zkušenost hejna
  31. Neuroevoluce: evoluce vah i tvaru neuronových sítí a algoritmus NEAT
  32. Fuzzy množiny a funkce příslušnosti: jak zapsat „trochu teplo“
  33. Fuzzy logika a pravděpodobnost: proč „trochu teplo“ neznamená „pravděpodobně teplo“
  34. Fuzzy logika: t-normy, implikace a pravidla KDYŽ–PAK
  35. Fuzzy řízení: Mamdani a Sugeno od ventilátoru po kyvadlo na vozíku
  36. Fuzzy shlukování: algoritmus fuzzy c-means a body mezi shluky
  37. Neuro-fuzzy systémy (ANFIS): fuzzy pravidla, která se učí z dat
  38. Markovovy řetězce: přechodová matice, stacionární rozdělení a PageRank
  39. Skryté Markovovy modely: Viterbiho algoritmus a nepoctivé kasino
  40. Bayesovské sítě: proč je tráva mokrá a co z toho plyne pro postřikovač
  41. Kalmanův filtr: jak z nepřesných měření odhadnout, kde auto je a jak rychle jede
  42. Druhy strojového učení: s učitelem, bez učitele, s částečným učitelem a další
  43. Přeučení a regularizace: proč model, který zná data nazpaměť, selže
  44. Vyhodnocení modelu: křížová validace, F1 a křivka ROC
  45. Shlukování a snížení dimenze: k-means, PCA a t-SNE
  46. Klasifikace a regrese: dvě úlohy učení s učitelem
  47. Lineární regrese: nejmenší čtverce geometricky i maticově
  48. Polynomiální a nelineární regrese: jak přímku ohnout
  49. Logistická regrese: sigmoida, šance a křížová entropie
  50. Vícetřídní klasifikace a softmax: od dvou tříd k mnoha
  51. Naivní Bayesův klasifikátor: spamový filtr, který funguje lépe, než by měl
  52. Ztrátové funkce: co přesně model minimalizuje
  53. Rozhodovací stromy a k nejbližších sousedů: modely bez vzorců
  54. Ansámbly: náhodný les a gradient boosting
  55. SVM a jádrový trik: nejširší hranice a jak ji ohnout
  56. Signál a vzorkování: proč platí Nyquistův teorém
  57. Fourierova transformace: rozklad signálu na frekvence
  58. Filtry a konvoluce: vyhlazení, hrany a odstranění šumu
  59. Zvuk a řeč: spektrogram, melová škála a MFCC
  60. Obraz jako signál: pixely, barvy, konvoluce a hrany
  61. Perceptron a umělý neuron: nejjednodušší neuronová síť a co neumí
  62. Neuronová síť od nuly: zpětné šíření chyby krok za krokem
  63. Aktivace, inicializace a normalizace: drobnosti, na kterých hluboká síť stojí
  64. Konvoluční sítě: jak síť poznává obrázky
  65. Rekurentní sítě: paměť pro posloupnosti a mizející gradient
  66. LSTM a GRU: brány, které rozhodují, co si síť zapamatuje
  67. Autoenkodéry: síť, která se učí data stlačit a zase rozbalit
  68. Embeddingy a word2vec: jak se slovo stane vektorem
  69. Mechanismus pozornosti: jak se model naučí, kam se dívat
  70. Transformery a velké jazykové modely: architektura krok za krokem
  71. Tokenizace: jak se text rozseká na kousky pro model
  72. Předtrénování a škálování: proč větší model a víc dat funguje
  73. Doladění a RLHF: jak se z doplňovače textu stane asistent
  74. Prompty, kontext a RAG: jak modelu dát správné informace
  75. Variační autoenkodéry (VAE): nové obrázky ze spojitého latentního prostoru
  76. Generativní adversariální sítě (GAN): padělatel proti detektivovi
  77. Difúzní modely: jak se z čistého šumu stane obrázek
  78. Markovovy rozhodovací procesy: stavy, akce, odměny a Bellmanovy rovnice
  79. Q-learning a DQN: učení hodnoty akcí od tabulky po neuronovou síť
  80. Policy gradient a PPO: učení přímo strategie
  81. Jak funguje AI agent: jazykový model ve smyčce s nástroji
  82. Programování s AI agenty: zadání, testy a kontrola
  83. 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 si odnesete
  • 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.

  1. 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.
  2. 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).
  3. 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ý.

Laicky řečeno

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.

4 2 5 3 7 6 2 4 8 3 5 6 A B C D E F G
Sedm měst a dvanáct silnic; čísla jsou délky

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.

Laicky řečeno

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ý.

4 2 5 3 7 6 2 4 8 3 5 6 A 2 B 4 C 4 D 4 E 4 F 4 G 2
Stupeň každého vrcholu

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“
Na grafu měst: 24 = 2 · 12. Důkaz je dvojí spočítání téže věci — dvojic (vrchol, hrana, která ho obsahuje).
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).

ABCDEFG A 4 2 B 4 5 3 7 C 2 5 6 8 D 3 6 2 4 E 7 2 6 3 F 8 4 6 5 G 3 5
Matice sousednosti grafu měst — symetrická, protože silnice vedou oběma směry
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.

  1. 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}).
  2. 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.
  3. G je souvislý, právě když ∀u, v ∈ V existuje cesta z u do v.
  4. 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.

Laicky řečeno

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.

1 2 3 4 5 6 7 8
Graf se třemi komponentami souvislosti

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
Pro A a G: cesta A–B–E–G má součet 4 + 7 + 3 = 14, cesta A–B–D–E–G 4 + 3 + 2 + 3 = 12 — a menší už žádná není, tedy d(A, G) = 12.

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:

4 2 5 3 7 6 2 4 8 3 5 6 A 0 B 1 C 1 D 2 E 2 F 2 G 3
Prohledávání do šířky z A: vrstvy podle počtu hran
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:

4 2 5 3 7 6 2 4 8 3 5 6 A 0 B 4 C 2 D 7 E 9 F 10 G 12
Dijkstrův algoritmus z A: nejkratší cesta do G má délku 12

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“
Odhad je vždy délka nějaké skutečné cesty, takže shora omezuje vzdálenost; relaxace ho jen stahuje dolů.
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í:

krokuzavřend[A]d[B]d[C]d[D]d[E]d[F]d[G]
1A042∞∞∞∞
2C0428∞10∞
3B04271110∞
4D0427910∞
5E042791012
6F042791012
7G042791012

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.

  1. G je les, právě když neobsahuje žádný cyklus (každá jeho komponenta souvislosti je pak strom).
  2. 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í:

  1. G je strom;
  2. G je souvislý a |E| = n − 1;
  3. G neobsahuje cyklus a |E| = n − 1;
  4. ∀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:

  1. hloubka h(v) je délka (počet hran) jediné cesty z r do v; speciálně h(r) = 0;
  2. 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č;
  3. v je list, právě když nemá žádné dítě.
Laicky řečeno

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
Šachy mají v průměru asi b ≈ 35 tahů na pozici. Na čtyři tahy dopředu (d = 4): 354 = 1 500 625 pozic v posledním patře a N(35, 4) = 355 − 1/34 = 1 544 761 v celém stromu — 97 % připadá na poslední patro.

7. Minimální kostra

Definice — kostra a Kruskalův algoritmus

Nechť G = (V, E, w) je souvislý ohodnocený neorientovaný graf.

  1. 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.
  2. Váha kostry je w(T) = ∑e ∈ F w(e).
  3. 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.
  1. Seřaď hrany vzestupně podle váhy: e1, …, em s w(e1) ≤ … ≤ w(em).
  2. Polož F := ∅.
  3. Pro i = 1, …, m: neobsahuje-li graf (V, F ∪ {ei}) cyklus, polož F := F ∪ {ei}; jinak hranu ei vynech.
  4. 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).

Laicky řečeno

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.

4 2 5 3 7 6 2 4 8 3 5 6 A B C D E F G
Kruskalův algoritmus přidává hrany od nejkratší; kostra má délku 18

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.

  1. 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.
  2. G je orientovaný acyklický graf (DAG), právě když neobsahuje žádný orientovaný cyklus.
  3. 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ý.

Laicky řečeno

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í:

x 1 w 2 b 3 y 4 · 5 + 6 σ 7 L 8
Výpočetní graf neuronu a jeho topologické pořadí x → w → b → y → · → + → σ → L

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
Jeden činitel za každou hranu na cestě od w k L. Kdyby z w do L vedlo cest víc, příspěvky všech cest se sečtou. Na příkladu: −1 · 0,25 · 2 = −0,5.

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.

Agent na vašem počítači, ovládaný z telefonu