Někdy počátkem tisíciletí si kolega kamarád Martin pověsil na stěnu naší kanceláře plakát s principy Extrémního Programování. "Začni jednoduše", "Nejprve testy", "Neboj se refaktorovat" a podobně. O agilních metodách jsme tehdy moc nevěděli, bylo to nové a teprve se to ve světě formovalo. Intuitivně jsme k podobným principům sice jako tým směřovali ("ty unit testy jsou super"), ale nešlo o nic metodického. Četli jsme o XP a diskutovali o zvláštních věcech, jako je párové programování.
"Co je to nesmysl? Budete ještě pomalejší, než jste teď, a to je co říct. Já platím dva lidi a oni si sednou k jedný klávesnici, to je fakt extrémní," zhodnotil tuto techniku náš šéf Tomáš.
"Ale ten kód je kvalitnější, bude tam míň chyb, víc očí víc vidí. Navíc mu budeme líp společně rozumět," oponovali jsme mu.
Nepřekvapí příliš, že extrémní programování jsme v naší malé firmě tehdy neprosadili.
Mou krátkou zkušenost s párovým programováním si ale vybavuji i dnes: Během sezení nad klávesnicí se naše role střídaly mezi řidičem a navigátorem. Řidič psal kód, navigátor komentoval, byl v tu chvíli víc nad věcí. Když řidič nevěděl, kam dál, navigátor měl jasno, drapnul klávesnici a pokračoval.
S Martinem jsme si ujasnili několik sporných názvů tříd a metod (pojmenovávání věcí je, jak známo, jeden z největších problémů ve vývoji software). Vytvořili jsme během dlouhé diskuse nad několika málo řádky kódu sdílený mentální model, s nímž jsme byli oba spokojeni, a dokázali ho pak prosadit u ostatních. Rozdělili jsme si práci a většinu implementace pak napsali u vlastních klávesnic.
Byl to sice "pomalejší" způsob vývoje, zato ale byl *zásadnější*.
Přeskočme dvacet let do současnosti. Sdílení kódu je žádané, základní technikou agilního vývoje se staly pull requesty, tedy návrhy změn od jednoho vývojáře, které schvaluje jiný člověk z týmu. Pull requesty jsou, na rozdíl od párového programování, svou povahou asynchronní: Autor píše, když na to má čas, hodnotitel komentuje, když na to má čas. Oba odděleně u vlastních klávesnic. Komunikace a vytváření sdílených abstrakcí probíhá formou diskuse nad kódem.
Nepřekvapuje, že se tento způsob vývoje promítl i do nástrojů pro "Programování s AI": Člověk sází prompty a schvaluje pull requesty, velký jazykový model pak obstarává pracnou implementaci.
Při experimentech s Cursor AI přesně tohle očekávám.
Snažím se nejprve krok po kroku budovat implementaci. Tuším, že čím podrobnější a uzavřenější mé prompty budou, tím lepší a zaměřenější pull requesty mi nástroj připraví. Nejsem pravý "vibe coder", jenž jedenkrát zavelí "Dej mi produkt, očekávám zázraky" a pak je ochoten brodit se tunami vyhalucinovaného kódu. Raději se smířím s tím, že budu postupovat krok po kroku.
Výsledky jsou překvapivě dobré. Cursor produkuje čitelný, dobře okomentovaný kód, přiléhavé názvy identifikátorů. Občas "ukročí stranou" a předloží pull request (což je v podání editoru změna existujícího kódu), který musím odmítnout, ale nikdy nejde o něco, co bychom si se Sonnet modelem nevysvětlili.
Nastává čas na odvážnější refaktorování, na větší změnu architektury. Protože jsem stále opatrný, rozdělím úpravu do dvou fází: V prvním promptu požaduji odstranění a výměnu jednoho podsystému za jakési statické lešení, které má pouze zajistit, aby celá stavba v mezikroku nespadla. Ve druhém promptu pak připravuji, že modelu vysvětlím, jak lešení nahradit funkčním podsystémem s novými vlastnostmi. Jako člověk bych podobně postupoval, s tím, že za "hotový stav" bych považoval až výsledek druhé fáze.
Cursor mi ale do připraveného dvoufázového postupu hází vidle: Ve výstupu po prvním promptu dostávám implementaci obou fází! Je to, jako by LLM "nesnesl pomyšlení" na to, že v mezikroku je projekt napůl rozbitý, a tak pokračuje dál bez znalostí detailů druhé fáze a překvapivě se částečně trefuje do toho, co chci jako celkový výsledek.
Následuje upřesnění detailů z mé strany a ušetříme si i nějaký čas pro užitečnější úkoly, protože nemusím tolik ladit druhou fázi.
Ten okamžik znamená AHA moment v mé krátké zkušenosti s AI nástroji pro softwarový vývoj: LLM nemusí být jen otrokem, autistickým juniorem. A člověk nemusí být jen seniorem vydávajícím příkazy a schvalujícím pull requesty. Místo povelů je možno vést dialog se strojem rovnou nad kódem.
"Ale jen jestli si tu spolupráci s modelem příliš neantropomorfizuju," zakončuji celou historku nad kávou s dalším kolegou a kamarádem. "Snadno jeden zapomene, že má co do činění jen s hromadou čísel a astronomickým množstvím násobení a sčítání, k tomu sem tam nějaká ta nelinearita. Jazykový model, jakkoliv sofistikovaný, prostě nemyslí, to je jen naše snaha v tom myšlení vidět," ujišťuji sám sebe.
"No, dřív se tomu říkalo párové programování, vzpomínáš?" poznamenává kolega, jenž se náhodou jmenuje také Martin. A dále diskutujeme o tom, jak je dnes situace v některých aspektech podobná tomu, co se dělo v dávných dobách XP: Člověk je v roli navigátora v páru, určuje směr, LLM pak zastává roli řidiče, autora kódu. Oba hluboce sdílejí stejný kontext, oba znají současnou implementaci. Oba mají motivaci v tom, aby budovaný celek nepřetržitě fungoval, nebojí se rozsáhlých úprav.
Tentokrát je to ale, oproti původnímu párovému programování, extrémně rychlé. Soudobí Tomášové jistě ocení i fakt snížení nákladů za párek takových vývojářů: K mzdě navigátora postačí jen zaplatit podstatně méně peněz za to, že řidič se stal službou.
A ještě je tu jeden důležitý detail: Na rozdíl od původního párového programování se člověk se strojem v rolích řidiče a navigátora nestřídají.
Zatím.
Zobrazují se příspěvky se štítkemsoftware. Zobrazit všechny příspěvky
Zobrazují se příspěvky se štítkemsoftware. Zobrazit všechny příspěvky
10. června 2025
Návrat extrémního programování
12. srpna 2017
Synaptický příběh
TL;DR
Tento text, jenž vznikl asi před rokem, nebyl určen pro blog a v šuplíku beznadějně zastarává, vzhledem k tomu, jak rychle se proměňuje celá oblast hlubokého učení (GAN, attention, reinforcement...). Protože nemám motivaci k jeho dokončení a aktualizaci a protože se některé jeho predikce mezitím stihly vyplnit, umisťuji ho alespoň sem, bez obrázků a redakčních úprav, aby z něho měl užitek alespoň náhodný kolemjdoucí.
Tento text, jenž vznikl asi před rokem, nebyl určen pro blog a v šuplíku beznadějně zastarává, vzhledem k tomu, jak rychle se proměňuje celá oblast hlubokého učení (GAN, attention, reinforcement...). Protože nemám motivaci k jeho dokončení a aktualizaci a protože se některé jeho predikce mezitím stihly vyplnit, umisťuji ho alespoň sem, bez obrázků a redakčních úprav, aby z něho měl užitek alespoň náhodný kolemjdoucí.
Neuronové sítě
Zajímáte-li se o umělou
inteligenci a moderní počítačovou vědu, nemohl vám uniknout
pojem hluboké učení (Deep Learning). Tento půl století
starý termín vyvolává dnes velkou vlnu zájmu a nerealistická
očekávání. Jde o neuvěřitelný příběh – od počátečního
nadšení, přes dvakrát téměř zapomenutou větev výzkumu, až k
dnešnímu masivně paralelnímu hardwaru a korporátnímu
přetahování mozků.
Protože to není poprvé, co
se odborná veřejnost a novináři popularizující vědu předhánějí
v tvrzeních, že jde o "opravdový průlom v umělé
inteligenci", je opatrnost a skepse samozřejmě na místě.
Když si ale veškeré zlatokopecké nadšení odmyslíte a seznámíte
se "hlouběji s hlubokým učením", zjistíte, že v
oblasti umělých neuronových sítí, jichž se hluboké učení
týká, došlo skutečně k zásadnímu pokroku.
Proč až dnes, když nejde o
nijak nové myšlenky? První teoretické práce o perceptronech
– archetypálních umělých neuronových sítích – se objevují
již koncem padesátých let. Jak je tedy možné, že tyto systémy,
o něž se donedávna zajímali jen doktorandi sbírající materiál
pro přehledové kapitoly svých disertací, jsou najednou opět
populární?
Zpětné šíření chyby
Web je plný popisů
jednoduchých neuronových sítí, takže princip toho, jak funguje
samostatný neuron, určitě znáte. Neuron není opravdu nic
jiného než součet součinů vstupů a vah vybavený
nelinearitou – tzv. aktivační funkcí – na výstupu.
Přesně řečeno, v součtu figuruje i práh, což je jen
váha speciálního vstupu, na který je trvale připojen signál s
hodnotou 1. Nic zázračného. Moderní neuronová síť ale takových
prvků obsahuje velké množství a představuje bez nadsázky
systém, jenž je mnohem více než jen prostý úhrn svých částí.
Pokud neurony uspořádáte do
jediné vrstvy, můžete klasifikovat jen tzv. "lineárně
separovatelné" problémy. Co to znamená? Představíme-li si
vstupní hodnoty jako jakousi souřadnici v prostoru – což
dovedeme jenom, pokud jsou vstupy právě tři – je množina prvků
jedné třídy lineárně separovatelná, jestliže můžeme
prostorem vytyčit rovinu tvořící hranici oddělující tyto prvky
od prvků ostatních tříd. Všechny prakticky zajímavé úlohy do
této třídy separovatelnosti ani náhodou nespadají a jsou tedy
pro jednoduché perceptronové sítě nepřekonatelnou výzvou. Tato
skutečnost mj. stála i za prvním ochlazením zájmu o umělé
neurony, které trvalo až do poloviny sedmdesátých let minulého
století.
K prvnímu oživení dochází
díky nové technice učení pomocí zpětného šíření chyby
(backpropagation), dovolující trénovat sítě o několika
vrstvách. Články vedoucí k objevu tohoto typu učení publikovalo
nezávisle více vědců, nejznámějším z nich je zřejmě Paul
Werbos. Ten předchozí teoretické výsledky aplikoval na neuronové
sítě a ukázal, že díky této technice je možno zamířit na
mnohem ambicióznější cíle. V osmdesátých letech se tak zrodil
princip neuronové sítě tak, jak ho známe dnes.
Základním trikem zpětného
šíření je tzv. řetízkové pravidlo (chain rule), na
které si možná vzpomenete z hodin matematiky. Jde o obecný
princip, dovolující derivovat složenou funkci, jíž je v našem
případě vyjádření chyby neuronové sítě. Při učení sítě
nejprve provedeme tzv. dopředný běh s aktuálními parametry sítě,
tedy přiložíme data na vstupní vrstvu, vyhodnotíme hodnoty
výstupů neuronů první vrstvy, které použijeme jako vstupy pro
další vrstvu, a postup opakujeme, dokud nezískáme hodnoty výstupů
neuronů poslední vrstvy. Dopředný běh – tzv. inference
– je základní režim sítě, v němž natrénovaná sít se
zafixovanými parametry (váhami a prahy) např. umí klasifikovat
vstupní vzory do tříd. Poté vypočteme chybu výstupu sítě tak,
že výstupy neuronů porovnáme se správným vzorem, který síť
měla v tomto případě vyprodukovat.
Chybu můžeme určit různě,
přičemž tzv. chybová funkce (často se používá pojem "loss
function") poměrně zásadním způsobem ovlivňuje učení
sítě. V tehdejší době znamenala kvadratická chyba (MSE – mean
squared error) naprosto přirozenou volbu: Čistý rozdíl vzoru a
výstupů sítě obsahuje nepříjemné znaménko, ale umocníme-li
tento rozdíl, získáme elegantní a jednoduchou metriku.
Díky řetízkovému pravidlu
dokážeme tuto chybu přenést na vstupy poslední vrstvy a podobně
i do vrstev předchozích. Po provedené fázi zpětného šíření
máme uvnitř sítě informace o tom, jak chybovaly jednotlivé
neurony. Jakmile víme, jakým dílem se podílel na chybě celku
každý parametr sítě, můžeme ho jemně doladit. Tento postup je
třeba vícekrát opakovat pro každý vzor tzv. trénovací množiny.
Pro to, aby učení zpětným
šířením chyby fungovalo, je zapotřebí vybavit neurony spojitou
(snadno derivovatelnou) aktivační funkcí. Původní perceptronovou
nelinearitu tedy vystřídala hladká křivka podobného tvaru –
nejčastěji sigmoida (známá též jako logistická
funkce) nebo hyperbolická tangenta. Dvou- až třívrstvé
neuronové sítě se sigmoidou a desítkami, maximálně stovkami
neuronů optimalizované minimalizací kvadratické chyby tak tvoří
klasickou představu o neuronové síti, jež se usadila v učebnicích
na mnoho dalších let.
Přes počáteční optimismus
vyvolaný objevem algoritmu
zpětného šíření chyby
se brzy však ukázalo, že nasazení neuronových sítí má své
meze. Omezení bylo hned několik: Chyba sítě definovaná jako MSE
trpěla nepříjemnou vlastností způsobující, že učení se
zpomalovalo, čím přesnější naučená síť dávala výsledky.
Horší skutečností se ale ukázal být efekt tzv. mizejících
gradientů (vanishing gradients), jenž omezoval prakticky použitelný
počet vrstev sítě. Aktivační funkce měly svůj operační režim
pouze v okolí počátku souřadnic. Tento jev se zesiloval ve
vrstvách s rostoucí
vzdáleností od výstupu sítě, což ve výsledku znamenalo, že
pokud měla síť více vrstev, první z nich se nemohly učit.
Přesné vysvětlení mizejících gradientů je sice poměrně
komplikované, ale v podstatě jde hlavně o násobení velmi malých
čísel velmi malými čísly – užitečná hodnota zpětně
propagované chyby klesala k nule exponenciálně s počtem vrstev.
Jak se ukázalo později, umět natrénovat vícevrstevné sítě ale
bylo pro úspěch v praktických úlohách naprosto zásadní.
Zřejmě nejdůležitějším
– ale ne tak viditelným – omezením byla vysoká výpočetní
náročnost procesu trénování sítí kombinovaná s nedostatkem
trénovacích dat. Pro výkonné počítače té doby znamenalo
trénování sítě, která nám dnes připadá směšně malá, dny
a dny práce. Velké sítě byly pro tehdejší hardware tak
nepraktické, že experimentovat s nimi bylo nemožné a jejich
reálné aplikace byly jednoduše nemyslitelné. Přestože se
ukazovalo, že s rostoucí velikostí sítě – tj. s rostoucím
počtem jejich parametrů – většinou roste i její přesnost,
byly neuronové sítě brzo odsouzeny do role luxusních hraček a s
výjimkou několika úzce vymezených oblastí (např. jednoduchá
klasifikace fonémových stavů v rozpoznávání řeči) ustoupily
efektivnějším metodám.
Naštěstí i přes další
útlum zájmu o umělé neurony, jenž nastal během devadesátých
let a trval nejméně deset let, se několik výzkumníků drželo
zuby nehty své víry v to, že neuronové sítě zkrátka jen
předběhly dobu a že se jednou opět vrátí.
Učení bez učitele
Jedním z hrstky vytrvalých
byl určitě Geoffrey Hinton, jenž se věnuje neuronovým sítím po
celou svou vědeckou kariéru. Hinton rád používá při práci
fyzikálních analogií a v jeho článcích nacházíme pojmy jako
jsou "teplota systému", "energie stavu",
"žíhání", apod. Jeden z nejdůležitějších konceptů,
jehož je spoluautorem a jenž předznamenal současnou renesanci
sítí, se jmenuje Restricted Boltzmann Machine (RBM): Jedinou
vrstvu sítě je možno trénovat – velmi zjednodušeně řečeno
– pouze pomocí vstupních dat. Jde o typ učení bez
učitele, tedy trénování bez předem známých očekávaných
výstupních dat. Při učení RBM parametry vrstvy v každém kroku
postupně měníme, aby vrstva trénovacím datům co nejlépe
vyhovovala. Systém vychází ze stavu "s vysokou teplotou"
a postupně chladne, až dojde k "teplotní rovnováze".
Díky tomuto postupu je možné obejít omezení dané mizejícími
gradienty a konstruovat vícevrstevné sítě postupně, vrstvu po
vrstvě.
Nejznámějším užitím
neuronové sítě je bezesporu klasifikace. V této úloze se snažíme
pomocí sítě vstupní data – většinou popisující objekty
reálného světa – roztřídit do jednotlivých kategorií tím,
že každému neuronu výstupní vrstvy přidělíme štítek jedné
třídy. Neuron, jehož aktivační funkce nabývá pro daný vstup
největší hodnoty, jej pak přiřadí do dané třídy. Například
chceme-li automaticky třídit fotografie domácích mazlíčků,
výstupní vrstva naší sítě bude mít tři výstupy: neuron psů,
neuron koček a neuron papoušků. Pokud je síť dobře natrénovaná
a ukážeme-li jí fotografii britské modré, měl by kočičí
neuron produkovat nejvyšší hodnotu a oba ostatní neurony by měly
zůstat neaktivní, tedy vykazovat minimální hodnoty.
Výstupům sítě ale můžeme
určit i jiný význam. Například během učení můžeme chtít,
aby poslední vrstva věrně reprodukovala vektor vstupních dat.
K čemu by to bylo dobré?
Pokud by všechny vrstvy sítě měly shodný počet neuronů, pak
snad jen k tomu, abychom si ověřili, že software pro trénování
sítě funguje. Nabízí se ale zajímavá možnost – počet
neuronů prostřední vrstvy může být výrazně nižší než
počet neuronů první a poslední vrstvy. Pokud jsme schopni
kvalitně natrénovat takovou síť, znamená to, že vstupní a
výstupní data jsou na prostřední vrstvě reprezentována
úsporněji, pomocí vektoru o menším počtu prvků. První
polovina sítě od vstupu k úzkému místu tak tvoří enkodér,
zatímco druhá polovina od úzké vrstvy k výstupu pak dekodér.
Takto můžeme komprimovat data – tzv. autoenkodér
– případně dokonce získat jejich robustní reprezentaci
odstraňující šum z poškozených dat (denoising autoencoder).
Tímto postupem je možné
natrénovat i klasifikující síť bez toho, aby trénovací data
obsahovala značky výstupní třídy.
Pomocí RBM se podařilo
oživit zájem o autoenkodéry a nejen to – tento generativní
postup (bez učitele) brzo poté dovolil vytvářet i vícevrstevné
sítě, které se daly dotrénovat pomocí algoritmu zpětného
šíření (s učitelem) pro klasifikaci. Pro tyto sítě se objevuje
označení Deep Belief Networks (DBN). Vědci si uvědomují,
že se jim podařilo odblokovat zásadní omezení, jež doposud
bránilo plnému využití potenciálu neuronových sítí. Hintonův
kolega Yoshua Bengio publikuje v r. 2013 článek o učení
reprezentací – jeho základní myšlenkou je, že modelování
velmi složitého chování je možno docílit opakovanou interakcí
faktorů, které jsou realizovány ve vrstvách. Jinými slovy: Každá
vrstva sítě představuje jistou úroveň abstrakce, posouváme-li
se od vstupních vrstev k výstupních, posouváme se od hrubých dat
přes užitečné heuristiky abstrahující informaci až ke
kvalitním znalostem na výstupech. Každá vrstva tak reprezentuje
jistou úroveň zobecnění, nad kterou může následující vrstva
stavět efektivní kombinace.
Zkrocení nelineární bestie
Seznam důvodů vyvolávajících
opětovný zájem o trénování sítí by nebyl úplný, kdybychom
nezmínili SoftMax (normalizovanou exponenciálu) na pozici
chybové funkce. Jde o funkci známou i v jiných oborech strojového
učení (LDA, naive Bayes). Její podstatnou vlastností je, že
výstupy sítě normalizuje: Bez ohledu na to, jak vysoké hodnoty
výstupní neurony produkují, díky SoftMaxu jsou jejich hodnoty
namapovány do intervalu (0, 1) – a co víc – jejich součet je
vždy 1. Jinými slovy, SoftMax vrstva při klasifikaci zohledňuje,
že jednotlivé třídy jsou na sobě vzájemně závislé. Díky
tomu se mohou neuronovými sítěmi zabývat i statistici, neboť
celková pravděpodobnost všech možností je vždy úhrnem 1, tj.
statisticky vyjádřená jistota. SoftMax jako slušně vychovaná
shora omezená chybová funkce nejenže pomáhá v souboji s
explodujícími gradienty, ale má i další výhodu: SoftMax
produkuje použitelné gradienty i v oblasti jemného ladění, když
je síť skoro natrénovaná (na rozdíl od klasické MSE).
Dalším zásadním zjištěním
bylo, že původní shora omezené aktivační funkce (sigm,
tanh) je možné nahradit jednoduchou lineární lomenou
funkcí, která pro všechny záporné vstupy produkuje nulu, zatímco
kladná čísla kopíruje ze vstupu na výstup. V literatuře se pro
tuto aktivační funkci používá zkratka ReLU (rectified
linear unit). O takto "nepřirozené" nelinearitě
neuronoví odborníci nejprve neuvažovali, kvůli tomu, že trpí
tzv. problémem "explodujících gradientů". To je dáno
tím, že ReLU není nijak shora omezená – s rostoucím argumentem
stále roste. Vypadá to, že nic není zadarmo: Ve složitém
nelineárním systému, jakým jsou neuronové sítě v režimu
učení, dochází ke dvěma principiálním problémům: Signál se
buď snadno ztrácí (vanishing gradients) nebo naopak roste nade
všechny meze (exploding gradients). Pokud vám to něco připomíná,
tak vězte, že články o této problematice často citují teorii
dynamických systémů, stabilitu zpětnovazebních systémů,
atraktory, chaos, atd.
Zatímco na mizející
gradienty neexistují účinné léky bez vedlejších efektů (i
když o jednom takovém bude řeč v kapitole o rekurentních
sítích), na explodující gradienty lék známe. Je to svým
způsobem jakási zpětnovazební brzda zabraňující vahám a
prahům neuronové sítě v tom, aby rostly do nekonečna. Tímto
lékem je regularizace: Pokud chybovou funkci sítě doplníme
o člen závislý na tom, jak rychle rostou nebo jaké jsou
momentální hodnoty parametrů sítě, poskytneme tím procesu učení
důležitou informaci. Neboli: Doposud během učení s učitelem
známkoval náš učitel jen to, jak věrně síť papouškuje
předkládané vzory. Nově bude dávat dobré známky i za plynulost
recitace.
Nelinearita ReLU dovolila opět
trénovat zpětným učením hluboké sítě bez předchozí RBM
fáze, pouze s regularizací parametrů. Je příznačné, že
podobně jednoduché řešení problému mizejících gradientů bylo
po celou dobu na dosah ruky a ke vzkříšení neuronové teorie
muselo dojít podstatně obtížnějším způsobem.
V letech 2006-2010 se již o
neuronových sítích píše opět optimisticky. Praktický pokrok,
zejména v počítačovém vidění a porozumění řeči, na sebe
nenechá dlouho čekat – brzo jsou publikovány rekordní výsledky
a za state of the art je v těchto oblastech považován model
hluboké sítě.
Hluboké šílenství
Samotnou myšlenku
vícevrstevných sítí publikoval ukrajinský teoretik Alexej
Ivaněnko se svými kolegy v roce 1965. Že je hloubka důležitá
pro úspěch modelu, se v článcích objevovalo již během
sedmdesátých let. Jenže to nikdy pořádně nefungovalo, až nyní.
Rozjíždí se poslední a
zatím největší vlna neuronového nadšení, jež trvá dodnes.
Nejaktivnějšími propagátory "hlubokého učení", jak
se tomuto opět atraktivnímu oboru snažení začalo říkat, jsou
právě Yoshua Bengio, Geoffrey Hinton a Yann Le Cun z CIFAR
(Kanadský institut pokročilého výzkumu) v Torontu, známí mezi
kolegy též jako "kanadská mafie".
Použít pro renesanci
neuronových sítí zastřešující název kombinující hluboké
sítě (deep nets) a strojové učení (machine learning) je
vynikající marketingová strategie. Nejde o nic nového –
přejmenování oblasti výzkumu dovoluje odpoutat se od minulých
neúspěchů tak, jako se např. "strojové učení"
distancovalo od původní "umělé inteligence". Aby to
nebylo jednoduché, nálepkou Deep Learning jsou často
označovány i sítě, které vlastně hluboké v pravém slova
smyslu ani nejsou. Pěkným příkladem jsou rekurentní jazykové
modely (RNNLM), hluboké jen během učení, o nichž ještě bude
řeč. Hluboké učení se tak stává obecným pojmenováním pro
"to vše nové neuronové".
Díky shrnujícímu článku snázvem "Deep Learning" v časopisu Nature 2015
se s myšlenkami hlubokých sítí seznamuje odborná veřejnost a z
hlubokého učení se definitivně stává "ta věc".
Teoretický průlom se potkává
s dalšími šťastnými okolnostmi: Kvůli fyzikálním důvodům
přestává technologie
výroby integrovaných obvodů dosahovat
stále vyšších hodinových frekvencí CPU a orientuje se spíše
na zvyšující se paralelismus. Procesor získává stále
více jader,
dovolujících vykonávat souběžně více úloh. Navíc se postupně
prosazuje nový, široce dostupný masivně paralelní hardware –
grafická karta (GPU). Tyto specializované součástky do PC,
původně navrhované výhradně
se záměrem zvýšit
snímkovou frekvenci
grafiky počítačových
her, je možné programovat pro zpracování paralelních algoritmů,
které pak díky tomu běží řádově desetkrát rychleji než
jejich sekvenční
varianty na CPU.
Neuronové sítě, vzhledem ke své povaze, představují velice
dobře paralelizovatelnou úlohu, která se ideálně implementuje
právě na GPU. Toho využívá dominantní výrobce GPU firma NVIDIA
na svých konferencích, kam zve odborníky na hluboké učení
a dál popularizuje
neuronové sítě.
Dalším faktorem
akcelerujícím zájem o hluboké sítě je globálně rostoucí
produkce dat. Díky sociálním sítím a vznikajícímu Internetu
věcí se korporace vlastnící, generující nebo sbírající tato
data začínají rozhlížet po způsobech jejich efektivního
využití. Objevuje se zájem data vytěžit (data mining),
destilovat obrovské hromady hrubých dat o realitě na modely
abstrahující pravidla v této realitě ukrytá, získat člověkem
uchopitelné informace o chování utopeném v moři šumu.
Zde se hluboké učení
potkává s dalším atraktivním termínem – velkými daty
(Big Data).
My máme framework, ó, my se máme
Napsat program simulující
dopředný běh jednoduché neuronové sítě je práce maximálně
na pár hodin. Její učení je sice o dost složitější, ale
zkušený programátor bude mít první verzi zprovozněnou třeba za
několik dní. Bez ohledu na zvolený programovací jazyk ale platí,
že výsledek bude pouze nepraktickou hračkou, demo ukazující, že
"také umíme trénovat neuronové sítě". Hlavní
bariérou je především výpočetní čas. Bez zásadních
optimalizací není možné trénovat na velké množině dat sítě
o velikostech, které dovolují řešit praktické úlohy.
Výhodou neuronových sítí
je, jak bylo zmíněno, jejich snadná paralelizovatelnost. Jádro
výpočtů představuje obrovské množství násobení a sčítání.
Uvažujeme-li vrstvu jako celek, představuje výpočet vrstvy
operaci známou z lineární algebry – násobení vektoru maticí.
Algoritmy trénování typicky zpracovávají větší počet vzorů
naráz, v dávkách (minibatches). Díky tomu je kritickou
operací násobení matice vstupních dat maticí parametrů,
maticové násobení chyb a transponovaných parametrů, sčítání
gradientů, apod. Operace lineární algebry jsou tradičně
implementované v knihovnách rodiny BLAS (OpenBLAS, ATLAS, Intel
MKL, Nvidia CUBLAS), jejichž rozhraní bylo kdysi navrženo s
ohledem na programovací jazyk FORTRAN, případně v knihovnách s
jiným rozhraním (Eigen, cuDNN). Společnou vlastností těchto
knihoven je, že se snaží v maximální možné míře využít
hardware, tedy ve značné míře spoléhají na vícevláknové
zpracování nebo na efektivní operace GPU. Tento způsob
paralelizace je označován termínem model-parallel.
Další urychlení trénovacího
procesu na velkém množství dat spočívá v tom, že trénovací
množinu je možno rozdělit na části (shards) a na nich trénovat
vedle sebe nezávislé sítě, pokud nalezneme způsob, jak výsledky
opět synchronizovat. Tato paralelizace je označována jako
data-parallel a algoritmy trénování využívající tento princip
se souhrnně označují jako distribuované učení.
Před pěti lety bylo ještě
rozumné uvažovat o vlastní implementaci učení sítí, pokud k
tomu byl dobrý důvod a žádný z tehdy existujících frameworků
nevyhovoval momentálním potřebám. Tím důvodem mohla být
specializace – znali jste dobře svou aplikační doménu,
vlastnosti dat nebo požadovaná specializovaná funkcionalita nebyla
nikde implementovaná. Dnes je díky existujícím univerzálním
frameworkům taková snaha velmi pošetilá.
Frameworků je k dispozici
celá řada, nejstarší z nich vznikly v akademickém prostředí.
Jde především o Caffe z Univerzity v Berkeley orientovaný
na úlohy počítačového vidění. Dalším je Theano,
obecný framework pro Python z Univerzity v Montrealu, nabízející,
navzdory svému uvedení v r. 2008, pokročilou architekturu.
K frameworkům s čistě
akademickým původem bylo donedávna možné zařadit i Torch, v
němž se skriptuje v jazyce Lua. Začátkem loňského roku ovšem
Facebook vydal jako open source vlastní optimalizované deep
learning moduly pro Torch a tím odstartoval závody v hlubokém
zbrojení. Během velmi krátké doby otevřela svůj software pro
hluboké učení ve zdrojovém tvaru snad každá velká společnost
s vlastním výzkumným oddělením umělé inteligence. Do dnešního
dne tak může kdokoliv použít CNTK (Computational Network Toolkit)
– univerzální framework od Microsoftu; Veles – framework od
Samsungu se škálováním pomocí knihovny ZeroMQ; CaffeOnSpark od
Yahoo – deep learning "na obláčku" propojující Caffe
s fremeworky pro velká data (Hadoop, Spark); projekt DSSTNE od
Amazonu zaměřený na doporučovací systémy; Warp CTC od Baidu –
efektivní knihovna specializovaná na rozpoznávání řeči; a
TensorFlow od Google – obecný framework pro algoritmy umělé
inteligence.
Ačkoliv mají komerční
frameworky různé autory z konkurujících si firem, jsou si často
v mnoha ohledech podobné. To je
dáno nejspíš tím,
že vznikaly přibližně ve stejné době, autoři četli stejné
vědecké články, účastnili se stejných konferencí a
inspirovali se stejným dostupným softwarem. Některé nápady
prostě visely ve vzduchu: Skriptování na vyšší úrovní probíhá
většinou v jazyce Python, který se díky nedávnému velkému
uveřejňování stává de
facto jazykem volby
neuronové komunity, zatímco výkonné jádro je napsané v C++ nebo
C. Základní datovou strukturou je tensor,
což je jakési zobecnění matice z lineární algebry. (Zhruba
řečeno jde o n-rozměrný hyperkvádr čísel.) Tensory jsou
propojeny tzv. grafem výpočtu, jenž dovoluje efektivní
paralelismus a distribuované výpočty (Flow-Based Programming).
Často je výbavou frameworku také zabudovaná podpora zpětného
učení (algorithmic differentiation). Uživatel definuje dopředný
běh sítě a framework sám dopočítá zpětný běh, protože u
každé dopředné operace má implementovanou odpovídající
derivaci potřebnou pro výpočet gradientů. Většina frameworků
dovede pracovat s GPU prostřednictvím knihovny cuDNN nebo přímo
pomocí CUDA.
Neuronový hardware - nový trh?
Algoritmy neuronových sítí
díky uvedeným vlastnostem představují ideální cíl pro
specializovaný hardware, což je koncept starý jako umělý neuron
sám (viz Mark 1 perceptron z konce padesátých let minulého
století). Pro ambiciózní sítě a velká trénovací data je často
jedna univerzální GPU karta málo a hledají se způsoby, jak co
možná nejlépe distribuovat trénování mezi jejich větší
množství. V této situaci nepřekvapuje, že návrháře hardware
láká možnost využít specifických rysů trénovacích algoritmů.
Pokud by např. existoval levný neuronový čip s nízkou spotřebou,
otevřela by se obrovská příležitost pro mobilní neuronové
aplikace.
Velké firmy si hardwarový
potenciál uvědomují. Není asi náhoda, že v květnu tohoto roku,
krátce poté, co NVIDIA představila novou grafickou kartu GTX 1080
s mimořádně výhodným poměrem cena/výkon pro trénování sítí,
prozrazuje Google, že už více než rok používá v datovém
centru vlastní ASIC hardware pro tenzorové výpočty – Tensor
Processing Unit (TPU). Kromě RankBrain a Street View, které dnes
TPU využívají, můžeme spekulovat i o tom, že jednou Google
oznámí službu "TensorFlow as a Service." Mnoho detailů
řečeno nebylo, zjevně šlo o načasovanou strategickou informaci,
uvolněnou v souvislosti se zveřejněním TensorFlow.
NVIDIA vzápětí kontruje
uvedením akcelerátoru P100 se špičkovým výkonem 21 TFLOPS ve
FP16, tj. podporující aritmetiku s plovoucí čárkou o poloviční
16bitové přesnosti. Ta je pro trénování sítí postačující a
přitom výrazně rychlejší než klasická FP32 nebo dvojnásobná
přesnost FP64.
Rýsuje se střet NVIDIA vs.
Google? Nejspíš ne. Nic nenaznačuje tomu, že by v dohledné době
byla pozice NVIDIA coby bezkonkurenčního dodavatele hardware pro
trénování sítí ohrožena. Ze strany Google šlo zřejmě o jiný
záměr – upoutat pozornost odborníků na hluboké učení a
informované veřejnosti, demonstrovat možnosti svých neuronových
týmů, včetně jím zakoupeného startupu Deep Mind, a prosadit
TensorFlow. Je jisté, že ostatní velcí hráči s velkými daty
(Facebook, Amazon a Microsoft) vidí v neuronových sítích také
budoucnost a vynášejí karty podle potřeby.
Ostatně neměli bychom
zapomínat ani na to, že v následujících letech možná do
hardware specializovaného pro umělou inteligenci výrazněji
promluví také Intel. Jeho Xeon Phi s vektorovými instrukcemi
AVX-512 dává tušit, že procesorový gigant tento segment trhu
nepodceňuje.
Šéfové na poloviční úvazek
Nikde není lépe vidět vliv
korporací tak, jako na kariérách výzkumníků z Toronta: Firmu
Geoffreyho Hintona DNNresearch Inc. koupil Google, Hinton se ale
nepřestává věnovat univerzitnímu výzkumu. Yann LeCun, původně
Hintonův asistent, je nyní ředitelem Facebook AI Research v New
Yorku. Yoshua Bengio zůstává univerzitním profesorem v Montrealu,
zároveň ale radí startupu Nervana Systems s hlubokým učením.
Typické jsou také časté
prvoligové přestupy: Alexander Ng, jenž založil tým Google
Brain, nyní pracuje jako vedoucí vědec (Chief Scientist) v
laboratoři Baidu Research v Silicon Valley (a je mimochodem i
spoluzakladatelem úspěšné vzdělávací firmy Coursera). Asi
nejznámější Čech v oboru Tomáš Mikolov – autor často
citovaného algoritmu word2vec – pracoval po stáži
Microsoftu pro Google, v současnosti je výzkumníkem ve Facebooku.
Hlad po mozcích je zřejmý:
V současné době je na tom, co se týče nabídek práce, dobře
každý, kdo se věnoval neuronovým sítím before it was cool.
Větší firmy jsou si už nějakou dobu vědomy ceny svých dat a
rozhlížejí se, jak je co nejlépe využít. Hluboké učení –
velmi viditelné téma na technologicko-inovativně-marketingových
webech poslední dva roky – do této skládačky perfektně zapadá
a tak v luxusních zasedačkách po celém světě jistě zaznívá
"A neměli bychom mít také vlastní oddělení na Deep
Learning? Znám jednoho doktoranda..." v nejrůznějších
podobách.
A podobně se v těch samých
zasedačkách skeptici ptají naprosto logicky: "A k čemu je to
hluboké učení vlastně dobré?"
Aplikace
Královskou doménou
neuronových sítí je odjakživa počítačové vidění.
Klasifikace objektů na obrázku je dovedena do dokonalosti, počty
tříd jdou do tisíců a schopnosti hlubokých sítí se vyrovnávají schopnostem člověka.
Aplikace Twiggle například
pomáhá ornitologům – stačí nahrát obrázek, systém umí
rozpoznávat zhruba z devíti set druhů ptáků.
Slibná je i automatickákolorizace černobílých obrázků. Dočkáme se barevných němých
grotesek?
Pomocí sítí můžeme mj.
predikovat, jak bude vypadat naše podoba, až zestárneme.
Zajímavý je i generátor
napodobující styl rukopisu pro vložený text.
Perspektivní směr výzkumu
obrázkových sítí představuje identifikace a lokalizace víceobjektů na scéně.
Spojené trénování
jazykových a obrázkových modelů posouvá automatické porozumění
obsahu na zcela novou úroveň. Pomocí slovního popisu umíme
automaticky dohledat obrázek a naopak – pro libovolný obrázek
bude možné získat textový popis. Jako vedlejší produkt
automaticky vzniká reprezentace společná pro oba modely, kterou se
obrázková a jazyková síť spolu dovedou "domluvit".
Moderní rekurentní sítě
excelují v modelování přirozeného jazyka, čehož se využívá
jak při automatickém překladu, tak v rozpoznávání mluvené
řeči, tj. při strojovém přepisu řeči do textu. Během let
2010-14 se hlavní metodou volby v konstrukci rozpoznávačů spojité
řeči s velkými slovníky (LVCSR – Large Vocabulary Continuous
Speech Recognition) staly hluboké sítě a překonaly tak dlouhodobě
úspěšné systémy založené na statistických metodách GMM/HMM
(Gaussian Mixture Models, Hidden Markov Models). Všechny hlavní
rozpoznávače řeči společností Google (Google Now), Apple
(Siri), Amazon (Alexa), Baidu a Nuance jsou nyní založeny na
hlubokých sítích.
Na rozpoznávání řeči
navazují např. dialogové systémy a digitální asistenti.
Řeč není jediným zvukem,
který hluboké sítě dovedou zpracovávat. Již zmíněná aplikace
Twiggle umí identifikovat druh ptáků i podle ptačího zpěvu.
Neuronové sítě se ukrývají
také v doporučovacích systémech a své místo nalezly i v
bioinformatice.
Hluboké učení se prosazuje
dnes i do "měkkých disciplín," jako jsou hraní her,
výtvarné umění a hudba.
Nejviditelnějším úspěchem
v této oblasti je vítězství systému AlphaGO nad lidským
hráčem, mistrem v deskové hře GO Lee Se Dolem. Jde ale jen o
vrcholek ledovce, Deep Mind umí trénovat umělé hráče videoher
pouze za pomoci vstupu z displeje a informace o aktuálním dosaženém
skóre ve hře.
Jazykové neuronové modely
lze použít i pro automatické generování textu. Krásnou ukázku
předvedl Jiří Materna na letošní první české konferenci o
strojovém učení. Básně jeho automatického básníka,
natrénovaného na české poezii, vznikly jako výstup znakového modelu, na němž Andrej Karpathy demonstruje účinnost rekurence.
Asi nejznámějším
generátorem neuronového umění je Deep Dream.
Stranou nezůstává ani
hudba, komponovaná v podobném stylu jako hudba reprezentovaná
trénovacími daty.
Topologická hra
Popsat činnost neuronu je
triviální. Popsat činnost celku propojených neuronů, pokud jsou
jich tisíce, je prakticky nemožné. Proč je hluboká síť tou
nejčernější černou skříňkou a proč je tak efektivní v tom,
co dělá? Abychom se dokázali alespoň přiblížit k povrchu
odpovědí na podobné otázky a nalezli pár užitečných metafor,
musíme se nejprve zmínit o parametrizacích, reprezentacích a
jejich rozměrech.
Objekty reálného světa je
možno digitalizovat. Obrázek naskenujeme a pixely ze scaneru –
trojice hodnot pro červenou, zelenou a modrou barvu – uložíme na
disk tak, abychom o každém
z nich věděli, kde bylo
na původním obrazu jeho místo. Podobné
je to s lidským
hlasem:
Analogový
signál mikrofonu navzorkujeme, pro každý vzorek známe jeho polohu
v čase. Pokud je náš objekt digitálně popsán
a umíme jej
digitálními
periferiemi opět zrekonstruovat (vytisknout, přehrát, atd.),
máme jeho reprezentaci, která může – po nutném zpracování,
tzv. parametrizaci – sloužit jako vstup neuronové sítě.
Na obrázky uložené v
pixelech se dají použít filtry odstraňující šum, v podstatě
ale máme to, co může neuronová síť přímo konzumovat. V jiných
situacích je to složitější, hrubá data je nutno předzpracovat
(parametrizovat) do podoby pro neuronovou síť vhodnější. Např.
v případě rozpoznávání řeči nepoužíváme přímo vzorky
hlasového signálu přímo, ale využijeme toho, že víme, jak
funguje lidské ucho, a nejprve komprimujeme signál do frekvenční
podoby, do popisu připomínajícího blikající světélka na
ekvalizéru reprodukční soustavy. Získáme tak mnohem méně
čísel, odstraníme některý šum a převedeme původně
jednokanálový signál do sekvence vícerozměrných vektorů.
Mluvit o rozměrech v případě
vektorů je pro matematiky nebo programátory něčím přirozeným,
jestliže je ale naše představa vybudovaná pouze na základě
zkušeností s třírozměrným světem, začínáme mít už zde
problémy. Lidská intuice je totiž, co se týče rozměrů, vysoce
nespolehlivá a deformovaná, protože 3 je malé číslo.
Kupříkladu geometrie tvarů,
povrchů a objemů se ve více rozměrech zdaleka nechová tak, jak
bychom očekávali: Poměr obsahu kruhu vepsaného do čtverce k
obsahu tohoto čtverce můžeme snadno spočítat. Je to číslo
nezávislé na průměru kružnice, tj. na délce hrany čtverce.
Podobně jsme schopni vyjádřit poměr objemu koule vepsané do
krychle k objemu této krychle, známe-li průměr koule.
Porovnáme-li oba poměry, zjistíme, že poměr obsahů je větší
než poměr objemů, tedy proporce kruh/čtverec je větší než
proporce koule/krychle. Pokračujeme-li takto do vyšších rozměrů,
začne být zřejmé, že hyperobjemy vepsaných hypersfér jsou
nepodstatné vzhledem k objemům hyperkrychlí. Předchozí
nečitelnou větu je možno zjednodušit jako "ve vyššich
dimenzích skoro nejsou vniřky" nebo "veškerá hmota je
vždy v mnoha rozích poblíž povrchu".
Podobně zjistíme, že ve
vyších rozměrech jsou téměř vždy libovolné dva směry na sebe
kolmé, atd.
Exkurze do podivností
dimenzionality je nutná, abychom pochopili, proč je obtížné
chápat neuronové sítě. Každý vstupní vzor odpovídající
paremetrizovanému objektu reálného světa je možno si představit
(nebo se o to spíše můžeme pokusit) jako jeden bod v
hyperprostoru o rozměru daném počtem prvků vektoru parametrizace.
Navrhneme-li např. síť rozpoznávající cifry z černobílého
obrázku o velikosti 20x20 pixelů (což jsou vlastnosti snad
nejznámější databáze ručně psaných cifer MNIST), vytvořili
jsme vstupní vrstvu, která má 400-rozměrnou reprezentaci. Podobné
je to s každou další skrytou vrstvou – počet dimenzí
reprezentace je roven počtu neuronů dané vrstvy.
Každý vrstva sítě má
lineární a nelineární část. Ta lineární – váhy a prahy
neuronů vrstvy – slouží jako geometrická transformace
vstupujících bodů. Umí vstupní objekty posunovat a škálovat.
Pokud takto transformujeme např. trojúhelník, na výstupu získáme
opět trojúhelník, jen třeba pootočený s jinými délkami stran.
Z kružnice se po transformaci stane elipsa, ze čtverce čtyřúhelník,
atd. Nelineární část – aktivační funkce vrstvy – objekt
deformuje, je jakýmsi křivým zrcadlem: Z trojúhelníku udělá
podle potřeby čtverec nebo kružnici, nikdy ale dva trojúhelníky.
Důležité je, že vrstva nemůže změnit topologii – souvislost
objektu není narušena, jen ho vrstva nějak "pomačká",
třeba i zásadním způsobem. Matematik by řekl, že jde o
homeomorfní transformaci.
http://colah.github.io/posts/2014-03-NN-Manifolds-Topology/
Trik je v tom, že předchozí
vrstvy mohou usnadnit práci vrstvám následujícím díky tomu, že
převedou původní reprezentaci do jiné reprezentace, přičemž
dimenze obou se mohou lišit. Problém lineární separovatelnosti
můžeme snadno vyřešit přidáním dalšího rozměru: Mějme
kapesník, který má uprostřed kulatou skvrnu. Skvrna není
lineárně separovatelná od zbytku kapesníku ve dvou dimenzích –
není možné odstřihnout skvrnu rovným střihem nůžek, dokud je
kapesník narovnaný (2D). Převedeme-li kapesník do trojrozměrné
reprezentace tím, že jej uchopíme uprostřed skvrny a zbytek
srovnáme tak, aby se rohy kapesníku dotýkaly, můžeme oddělit
skvrnu jedním střihem.
Velmi vágně řečeno: Více
vrstev pomáhá měnit topologii reprezentací.
Data a model
Realita má svůj odraz v
datech. Objekty reálného světa se řídí různými zákonitostmi,
které na první pohled nejsou zřejmé. Cílem strojového učení
je extrahovat chování těchto objektů z dostupných trenovacich
dat tak, aby se natrenovany model předvídal jejich chování i na
datech, ktera během trénování k dispozici neměl. Tento proces
můžeme nazvat algoritmickou kompresí.
Skoro to vypadá, že pro
praktické aplikace neuronových sítí platí, že čím větší
síť, tím lépe funguje. Neuronová magie se začíná projevovat
teprve při zapojení stovek až tisíců neuronů do mnoha vrstev.
Řekněme, že máme dostatečný výpočetní výkon a odstranili
jsme teoretické překážky, co nám brání naučit extrémně
velkou síť? Kde jsou meze současného hlubokého učení?
Než se pokusíme zamyslet nad
odpovědí, vraťme se na okamžik k postupu tréhování pomocí
zpětného šířění chyby. Nejpopulárnějším algoritmem pro
trénování sítí je Stochastic Gradient Descent (SGD), jenž v
základní nedistribuované podobě vypadá takto:
1. Parametry
sítě náhodně inicializujeme. Nejčastěji se používá normální
distribuce pravděpodobnosti, ale není náhoda jako náhoda a
správná inicializace (volba střední hodnoty a směrodatné
odchylky) závisí na typu nelinearity a zvolené normalizaci vstupů
(tedy zhruba řečeno na tom, jak velké
hodnoty jsou k
dispozici na vstupu sítě). Obecně platí, že každý typ vrstvy
může vyžadovat odlišnou inicializaci.
Z celé trénovací množiny
jsou pak náhodně vybírány dávky vzorů (jednu dávku tvoří
např. dvě stovky dvojic vstup-výstup),
2. pro které je najednou spočítán gradient chyby a upraveny váhy v jeho směru. Velikost úpravy vah je řízena důležitými hyperparametry – zejména tzv. rychlostí učení (learning rate). Náhodný výběr dávek a úpravy se opakují, dokud jsou k dispozici dvojice vzorů.
2. pro které je najednou spočítán gradient chyby a upraveny váhy v jeho směru. Velikost úpravy vah je řízena důležitými hyperparametry – zejména tzv. rychlostí učení (learning rate). Náhodný výběr dávek a úpravy se opakují, dokud jsou k dispozici dvojice vzorů.
3. Po vyčerpání dat se celý
postup opakuje v další tzv. epoše (iteraci). Sledujeme-li průběžně
během učení, jak se mění chyba sítě na trénovací množině,
měli bychom vidět křivku, která zpravidla klesá nejprve rychle,
později její pokles zpomaluje, v ideálních podmínkách po
dostatečném počtu epoch klesne k nule.
Pro reálné potřeby musíme
vyhodnocovat i to, jak síť umí zobecňovat, tj. jak se chová po
naučení pro neznámé vstupy. V praxi se určité menší množství
dat vyčlení z trénovacích dat a prohlásí za testovací množinu.
Musí jít o reprezentativní vzorek, protože pokud je nějak
vychýlený, nedostáváme přesná měření. Na tomto vzorku je pak
měřena chyba – jestliže síť dobře zobecňuje, je chyba po
natrénování na testovací množině také malá.
V praxi ale nastává jeden ze
scénářů:
1. Chyba na trénovací
množině vůbec neklesá nebo dokonce roste: Něco je špatně,
učení vůbec neprobíhá.
2. Chyba na trénovací
množině klesá a roste, divoce osciluje: Pravděpodobně je vysoká
rychlost učení nebo je třeba doladit jiný hyperparametr (např.
zvětšit velikost dávky).
3. Chyba na trénovací
množině sice klesá, ale ani zdaleka se neblíží k nule, bez
ohledu na to, kolik epoch proběhlo: Může jít o nevhodnou
parametrizaci objektů nebo normalizaci vstupů. Nejspíš je ale
nedostatečně velká síť, má málo vrstev, málo neuronů ve
vrstvách nebo jinak nevhodnou konfiguraci Došlo k jevu označovanému
jako podtrénování (underfitting).
4. Chyba na trénovací
množině utěšeně klesá stále k nule, chyba na testovací
množině nejprve také klesá, po nějaké době ale začne vytrvale
stoupat.
Poslední případ je
nejzajímavější. Znamená to, že síť je schopna se dobře učit,
ale špatně zobecňuje. Dochází k přetrénování
(overfitting), kdy se síť příliš specializuje na trénovací
množinu. Pokud vnímáme celou trénovací množinu jako pouhé
přiblížení reálného jevu (jeho vzorkování), je přetrénování
důsledkem toho, že naše přiblížení není dostatečné. Tuto
disproporci právě odhalí testovací množina, která je sama o
sobě jiným vzorkem reálného jevu.
S přetrénováním se
neuronáři naučili bojovat mnoha způsoby: Nejjednodušším z nich
je tzv. časné zastavení (early stopping). Spočívá v jednoduché
detekci stavu, v němž síť nejlépe zobecňuje, a ukončení
dalšího učení. Za tímto účelem je vyčleněna mimo trénovací
a testovací množinu třetí množina, tzv. validační. Ta se
používá pro průběžnou validaci – nárůst chyby znamená, že
pravděpodobně začíná fáze přetrénování. Testovací množina
pak vědecky poctivě testuje až naučenou síť, na jejím základě
nesmí být během trénování učiněna žádná rozhodnutí.
K dalším způsobům
zajištění obecnosti patří tzv. dropout: Během učení se
určitému počtu náhodně vybraných neuronů ve skrytých vrstvách
"vypíná" aktivační funkce. Tyto neurony pak nepřispějí
v konkrétním dopředném běhu svým výstupem a do trénování se
tak vnese náhodný šum. Původní jednoduchý dropout má řadu
modifikací a teorie za tím, proč poškozování neuronů funguje,
je poměrně komplikovaná. Jejím argumentem je zjednodušeně
řečeno to, že díky neustálým náhodným kvazi-rekonfiguracím
probíhá trénování nad mnoha různými příbuznými sítěmi
současně.
Ve většině tutoriálů
hlubokého učení naleznete jako základní lék na overfitting výše
zmíněnou regularizaci. Pokud je chybová funkce doplněna o
regularizační člen, jsou parametry sítě "tlačeny"
směrem k počátku souřadnic. Na příkladu polynomu a přetrénování
klasické regrese je pak dokládáno, že pokud jsou koeficienty
polynomu malé, je polynomiální křivka vyhlazenější a lépe
zobecňuje. Je ovšem otázka, do jaké míry je možné použít
analogii z regrese na neuronovou síť.
Nejspíš nejsprávnějším
způsobem, jak se vyrovnat s efektem přetrénování, je zvětšení
trénovací množiny. Vzhledem k tomu, že trénovacích dat není
nikdy dostatek, je alternativou i zmenšení konfigurace sítě
(dokud nedojde k podtrénování). Najít ovšem správnou velikost
sítě je velmi výpočetně náročné.
Natrénovaná síť je modelem
reality vytvořeným na základě trénovacích dat reflektujících
reálné jevy. Jsou-li tato data dostatečnou aproximací reality,
což je obtížné zjistit kvůli jejich vícerozměrné povaze, je
tento natrénovaný model věrný.
Přihlédneme-li k problémům
s přetrénováním, objeví se přirozená otázka: Existuje nějaká
exaktní závislost mezi optimální velikostí modelu a velikostí
dostupných trénovacích dat?
Již v roce 1984 tento problém
mnohem obecněji prozkoumal britský počítačový vědec Leslie
Valiant v článku "Teorie naučitelného". O co jde? U každého modelu strojového učení nám jde během
trénování o to,
abychom nastavili správně jeho parametry, tj. konečný počet
čísel, která jsou později ve fázi inference konstatntní. V
případě neuronové sítě jsou těmito
parametry váhy a
prahy, inferencí je pak dopředný běh sítě, například
klasifikace do tříd. Konečný počet čísel s konečnou přesností
dává konečný počet kombinací, tj. konečný počet unikátních
modelů. Jde o obrovský
počet možných konfigurací
– pokud má síť N vah a prahů a pracuje s 16bitovou přesností,
existuje 216N
jedinečných sítí.
Důležitým Valiantovým
závěrem je, že pro M možných konfigurací modelu postačí
log(M) trénovacích vzorů k tomu, aby se chyby na trénovací a
testovací množině k sobě blížily. Pro konkrétní příklad
sítě, kde M=16N, tak postačí více
než 16N jedinečných
trénovacích vzorů,
aby nedocházelo k přetrénování. Nenechme se mýlit, M je v
případě moderních hlubokých sítí stále poměrně velké číslo
a navíc nemáme žádný
exaktní způsob, jak určit jeho správnou velikost, aby nedošlo k
podtrénování.
Velikost dostupných
trénovacích dat tak zásadně určuje kvalitu modelu. Nemáme-li
dat dostatek a modelovaná doména je komplikovaná, měli bychom
místo hlubokých sítí zvolit nějakou jinou metodu strojového
učení s menším počtem trénovatelných parametrů. Jakou, to
záleží na povaze problému.
Honba za efektivní architekturou
Nadšení z hlubokých sítí
doprovází do jisté míry povrchní, skoro až zlatokopecký,
přístup. Známé "pravdy" o neuronových sítích jsou
papouškovány ve všemožných tutoriálech a jen málokdy se objeví
odvážný autor, jenž si dovolí kritizovat zavedené a všemi
dodržované postupy (best practices). Příkladem je právě
přehlížení Valiantovy práce – zřejmě jen proto, že s
neurony přímo nesouvisí a všichni na ni dávno zapomněli:
Neuronáři po celém světě raději budou navrhovat a trénovat
zbytečně velké modely se zbytečně velkým počtem zbytečně
velkých vrstev, aby se spolehlivě trefili do režimu, v němž je
síť přetrénovaná, a aby pak mohli uplatňovat dobře známé
léky proti overfittingu. Nalezení rovnováhy mezi velikostí dat a
modelu je jistě drahé, ale není nakonec opakované používání
nadbytečně velké sítě ve fázi inference ještě dražší?
Toto téma rozebírá
Alexander Ng ve skvělém vystoupení na loňské konferenci NVIDIA
(GPU Technology Conference 2015).
Jedna z částí přednášky je věnovaná právě konfiguracím
sítí. Základní recept pro strojové učení lze jednoduše
shrnout do dvou pravidel:
1. Pokud není model dobrý na
trénovacích datech, použij větší model.
2. Pokud je dobrý na
trénovacích datech, ale není dobrý na testovacích datech, použij
víc dat.
Ng ale tímto receptem zdaleka
nekončí a zdůrazňuje také zásadní vliv dobré architektury
modelu. Výše zmíněné poučky platí v jednom směru a tím je
zvětšování modelu. Není ale možné často model naopak zmenšit,
aniž bychom bojovali z podtrénováním? Odpovědí je jasné ano,
navíc je snadné ukázat proč: Jestliže například do libovolné
skryté vrstvy přidáme jeden neuron a jeho výstup nezapojíme
(této situaci odpovídá nastavení všech odpovídajících vah
následující vrstvy na nulu), činnost sítě jako celku
neovlivníme, ale počet parametrů se zvětšil. V přetrénované
síti je zřejmě přítomná informace v redundantní formě a pokud
bychom ji dovedli odstranit, třeba vhodným návrhem topologie před
procesem učení, získali bychom efektivnější model s menším
počtěm parametrů.
Této možnosti jsou si
neuronáři vědomi už dávno. V počítačovém vidění se
používají tzv. konvoluční neuronové sítě, jejich
nejznámějším propagátorem je Yann Le Cun. V principu jde o
vrstvy neuronů (konvoluční vrstvy), jejichž vstup zpracovává
pouze malou oblast obrazu (tzv. receptive field), tyto vrstvy jsou
opakovaně aplikovány s posunutím tak, aby zpracovaly celý obraz.
Díky posouvání vrstvy může vstupní obraz obsahovat objekty také
v různém geometrickém posunutí, což usnadňuje práci dalším
vrstvám sítě. Zásadním důvodem, proč konvoluční vrstvy
fungují dobře, je sdílení vah – konvoluční vrstva uplatňuje
stejné parametry bez ohledu na konkrétní posunutí. Teoretici
přirovnávají funkci konvolučních vrstvech k filtrům používaným
v předzpracovávání obrazu. Konvoluční neurony se naučí
nízkoúrovňovou operaci – např. hranovou detekci – při učení
v rámci celé sítě.
Úsporná konvoluční
architektura má své využití i v rozpoznávání řeči. Jak bylo
zmíněno, pro parametrizaci řeči se s výhodou využívají tzv.
banky filtrů. Výstupem parametrizace může být spektogram –
vizuální reprezentace vývoje energie signálu ve frekvenčních
pásmech. Jednu osu obrázku tvoří frekvenční složky signálu,
druhou aktuální čas promluvy, barvy pak hodnoty energie.
Spektogram si můžeme představit jako obrázek a tak připodobnit
úlohu rozpoznávání řeči úloze počítačového vidění.
Konvoluční vrstvy pak můžeme uplatnit v obou směrech,
frekvenčním i časovém. Samotná myšlenka je stará, článek o
klasifikaci fonémů pomocí konvoluce v čase publikoval v r. 1989
Alexander Waibel se spoluautory (jedním z nich je G. Hinton).
Konvoluce není jediným
způsobem, jak sdílet parametry. Mnoho úloh, kde dnes neuronové
sítě excelují, souvisí se zpracováním sekvencí. Kromě
zmíněného akustického modelování jde o prakticky všechny
prediktory. Např. jazykové modely odhadují, jaké slovo bude
pravděpodobně následovat při znalosti předchozí historie
promluvy (textu), tj. pokud byla vyřčena (napsána) nějaká
konkrétní předchozí slova.
Kritickým prvkem při učení
sekvenčních modelů je paměť. Konvoluci v čase můžeme chápat
právě jako takový typ paměti – síť se může rozhodovat na
základě nějakého časového okna, které se po sekvenci posouvá,
kapacita takové paměti pak odpovídá délce okna. Pro náročnější
aplikace však konvoluce nedostačuje a je nutno použít mnohem
náročnější prvek – zpětnou vazbu.
Chaos rekurence
Nejjednodušší rekurentní
vrstva vznikne zapojením výstupů některých neuronů zpět,
vzniknou tím další vstupy vrstvy. V dnešní době existuje více
typů rekurentních sítí (RNN), jejichž společnou vlastností je,
že se vzruch (signál) nešíří sítí pouze v dopředném směru,
ale vrací se do vstupů a uzavírá tak jakýsi obvod. Signál ze
vstupů může v modelu setrvávat po delší dobu, mluvíme tak o
vnitřním stavu sítě – o její rekurentní paměti.
Na první pohled tedy
rekurence není nic složitého. Pokud se ale nad tím zamyslíte,
dojde vám, že obtížné na rekurentních architekturách bude
jejich učení: Jak zpětně šířit chybu ve zpětnovazební
smyčce? Algoritmus zpětného šíření chyby vyžaduje, aby výstup
byl jednoznačnou funkcí vstupů. Jestliže je navíc závislý na
vnitřím stavu, máme problém. Naštěstí je k dispozici
jednoduchý trik, jenž předpokládá, že paměť je časově
omezená, tj. že vliv libovolného vstupu na vnitřní stav modelu s
časem odeznívá. Jinými slovy: Model zapomíná. Uplatněním
tohoto předpokladu můžeme smyčku pro režim učení opět
rozpojit. Režim inference použijeme stejně jako u čistě
dopředných sítí k získání odezvy sítě v jednotlivých
časových krocích. Navíc ale získáme pro každý krok hodnoty
vnitřních stavů, tedy hodnoty signálu na zpětnovazebním
propojení, které v režimu zpětného šíření chyby považujeme
za další vstupy sítě. Tento typ rozvinutí zpětné vazby je v
literatuře znám jako "zpětné šíření napříč časem"
(BPTT, BackPropagation Through Time) a věnovali se mu už Rumelhart
a Werbos koncem osmdesátých let.
Na rozvinutou rekurenci v BPTT
můžeme nahlížet jako na speciální hlubokou síť.
Hyperparametrem učení určujícím počet vrstev je zde počet
časových kroků, které bereme do úvahy, tedy maximální hloubku
paměti. Důležité je, že vrsvy v backpropagation rozvinutí jsou
v podstatě kopiemi těchže parametrů – uplatňuje se zde opět
efektnivní sdílení vah a prahů jako v případě konvoluce.
Rekurentní sítě jsou tedy
speciálním typem hlubokých sítí, se všemi přednostmi i
nedostatky. Pokud se o nich chcete dozvědět víc, naleznete
především velké množství článků rozebírajících to, jak je
obtížné jejich učení. Problém mizejících a explodujících
gradientů nebo předčasný konce učení v lokálním maximu jsou
důvody, proč se výzkumníci snažili najít jiné typy rekurencí
vhodnější z pohledu učení.
Koncem devadesátých let
přišli Sepp Hochreiter a Jürgen Schmidhuber s poměrně
komplikovanou architekturou LSTM (Long Short Term Memory), která
dovoluje dobře modelovat i vzdálené časové závislosti. Na
rozdíl od klasické RNN je LSTM navržena tak, aby bylo možno
naučit síť odděleně strategie zapamatování, vybavení a
zapomenutí vnitřní paměti. Je to, jakoby měl proces učení k
dispozici samostatná tlačítka na klasické kalkulačce, dovolující
uložit nějaké číslo do paměti, pak ho vyvolat a nakonec
vymazat. V architektuře LSTM se těmto funkcím říká brány
(input, output, forget gates). Díky branám je učení LSTM bloku
efektivnější a dovoluje vytvořit dlouhodobou paměť. LSTM sítě
jsou dnes velice populární i navzdory své implementační
složitosti a výpočetní náročnosti a neobejde se bez nich žádný
neuronový framework.
Přestože LSTM pro mnohé
neuronáře představuje vrchol rekurentních sítí, ukazuje se, že
podobně dobrých výsledků je možno dosáhnout s pomocí odlišných
architektur. Roste popularita nejrůznějších modifikací, např.
přidáním dalších cest, tzv. špehýrek (LSTM with peephole
connections) nebo sloučením vstupní a zapomínací brány (GRU -
Gated Recurrent Unit), atd. Dobrý přehled současných rekurentních
sítí nabízejí autoři MGU (Minimal Gated Unit), kteří ve své
práci poukazují na to, že srovnatelných výsledků je možno
dosáhnout jednoduššími cestami. https://arxiv.org/abs/1603.09420
Pokročilá neuronová alchymie
V předchozích částech bylo
naznačeno, že oblast výzkumu neuronových sítí je poměrně
rozsáhlá. Na závěr je třeba ještě zmínit další směry,
kterými se ubírá moderní neuronový výzkum.
Vraťme se na okamžik k
algoritmu učení sítě: Samotný algoritmus SGD výzkumníci
neustále rozvíjejí s cílem vylepšit dynamiku učení, tj.
dosáhnout stejných výsledků v menším výpočetním čase.
Zaměřují se na různé způsoby hledání optima v hyperprostoru
parametrů modelu a přicházejí s novými nápady opírajícími se
o prastarou Newtonovu metodu (využití vyšších řádů derivací
chybové funkce, tzv. bez-hessiánové metody) nebo např. o
fyzikální analogie – Nestěrovův moment pohybu, adaptivní
momenty využívající meta-učení samotného procesu učení
(ADAGRAD, ADADELTA...). Popis dalších vylepšení SGD týkajících
se např. dynamiky distribuovaného učení by byl velice
vyčerpávající.
Inovace má ale řadu jiných
podob: V letmém popisu SGD bylo zmíněno, že inicializace
parametrů je náhodná. Dalším stochastickým prvkem SGD je
náhodný výběr dávek trénovacích vzorů. Toto dvojí použití
náhody vede k tomu, že žádné dvě natrénované sítě nejsou
stejné, díky vlastnostem vícerozměrných
prostorů dostáváme po skončení tréninku modely dosahujících
podobných výsledků, avšak se zcela odlišnými parametry.
Sledujeme-li charakter zbytkové chyby, tj. to, jak odpovídají
konkrétní naučené modely na jeden vstupní vzor, pozorujeme
určité odlišnosti. Konkrétní síť se dopouští konkrétních
chyb, jiná zase jiných. Máme-li k dispozici více sítí, můžeme
dokonce jejich výstupy vzájemně kombinovat (průměrovat výstupy,
vybírat maximum, atd.). Zmíněná technika se nazývá sbor
modelů (model
ensemble) a dovoluje dosáhnout zvýšení testovací úspěšnosti
sboru až o několik procent oproti úspěšnostem jednotlivých
sítí.
Sbor modelů představuje
řešení hrubou silou – v inferenční fázi je jediná síť
samozřejmě rychlejší než sítí několik, zpřesnění tak není
zadarmo. Naštěstí ale existuje postup dovolující dosáhnout s
jednoduchou sítí úspěšnosti srovnatelné se sborem sítí. G.
Hinton tomuto procesu říká destilace znalosti a někdy
mluví i o temné znalosti (dark knowledge).
V zásadě jde o to, že pečlivě vybraný sbor modelů natrénovaný
na původních datech použijeme jako generátor nových trénovacích
dat pro novou trénovací fázi, v níž ale učíme model s nižším
počtem parametrů, přip. vrstev. Důležitým detailem je, že
narozdíl od výstupních vzorů daných učitelem-člověkem, která
poskytují "ostrá černobílá rozhodnutí" (one-hot
target vectors), data generovaná učitelem-sborem obsahují výstupní
vzory vyjádřené ve stupních šedi (soft targets). Pokud tedy sbor
modelů váhá s rozpoznáním konkrétního vstupního vzoru,
projeví se tato váhavost na výstupu nejasnou odpovědí.
Jednoduchý model žák-učedník pak tuto nejednoznačnost snáze
vstřebá z trénovacích dat, k čemuž mu postačí méně vah a
prahů.
Další oblastí výzkumu je
modelování sekvencí a dynamických procesů. Jakmile je součástí
trénovacích dat časová dimenze, je trénování klasifikátoru
pomocí učení s učitelem nákladné, protože vyžaduje přítomnost
časových značek ve výstupních vzorech. Tento problém se snaží
překonat technika zvaná CTC (Connectionist Temporal Classification).
Tento algoritmus Alexe Gravese mj. dovolil vytvořit společnosti
Baidu rozpoznávač spojité řeči s rekordní úspěšností a je
důležitou metodou konstrukce moderních kompletně neuronových
rozpoznávačů (end-to-end speech recognition).
Podobný cíl mají tzv.
mechanismy pozornosti (attention mechanisms) umožňující
"soustředit pozornost" neuronové sítě na zajímavé
oblasti dat, v nichž je ukryt význam – přičemž síť sama umí
tato místa hledat – a omezit tak zbytečné zpracování dat bez
informační hodnoty.
Perspektivní je i strategie
zvaná Deep Reinforcement Learning
založená na myšlence posilujícího učení
(reinforcement learning) používané již dlouho v robotice. Jde
o metodu na pomezí učení s učitelem a bez učitele,
dovolující zhruba řečeno "odložit spotřebu" coby
signál zpětné vazby, tedy odměnu danou učitelem. Nejznámější
důkazy užitečnosti tohoto postupu nabídl zmiňovaný Google
DeepMind.
Posledním směrem, který
stojí za zmínku, je neuroevoluce. Tímto souhrnným názvem je
označovaná řada metod, snažících se propojit umělé neuronové
sítě s evolučními algoritmy, přičemž umělá evoluce je sama o
sobě rozsáhlou oblastí výzkumu. Příkladem je NEAT
(NeuroEvolution of Augmenting Topolgies), snaha o automatický návrh
topologie sítě. Zdá se, že pro vhodné nastavení hyperparametrů
modelu (počtu vrstev, počtu neuronů ve vrstvách, rychlosti učení,
atd.) je umělá evoluce jak stvořená. Bohužel, evoluční
strategie, jako jsou genetické algoritmy nebo genetické
programování, obecně předpokládají, že vyhodnocení evoluční
úspěšnosti jedince v populaci je levná operace, což v případě
klasického trénování sítí pomocí zpětného šíření chyby
zcela jistě splněno není.
Zajímavým příspěvkem z
letošního roku je práce porovnávající klasický algoritmus SGD
s vlastním evolučním postupem zvaným LEEA (Limited Evaluation
EA). Ten
vyhodnocuje jedince-sítě pouze na malé podmnožině trénovacích
dat s tím, že je pro konkrétní model je do celkové úspěšnosti
započítaná i úspěšnosti na předchozích vzorech. Autoři
dokládají, že díky levné operaci vyhodnocení je jejich postup konkurenceschopný ve srovnání se standardním SGD. Nutno ovšem
podotknout, že konference GECCO (Genetic and Evolutionary
Computation Conference), na níž byl příspěvek prezentován, je
zaměřená na výzkumníky evolučních strategiií a ti, jak známo,
nahlížejí na hluboké učení s velkou nedůvěrou: Částečně
možná proto, že evoluční strategie byly "in" v době
největšího útlumu neuronových sítí, zatímco dnes se zdá, že
se naopak nacházíme v období "evoluční zimy" –
hranice evolučních strategií jsou určující pro současné
aplikace, ty ale nevybočují z kategorie hraček (toy problems).
Možná se ale díky propojení
hlubokých sítí a umělé evoluce, což je obor nabízející už
dnes mnoho velmi pozoruhodných nástrojů a metod, dočkáme
naprosto zásadních překvapení. Stačí jen, aby došlo k několika
málo teoretickým průlomům, např. pokud se objeví opravdu
funkční evoluční alternativa k algoritmu zpětného šíření
chyby. A pak, až hardware dozraje…
Ostatně, nebylo by to poprvé.
14. dubna 2016
Nesouvislé rozjímání programátorovo II
Dejte někomu
program a frustrujete ho na den. Naučte ho programovat a frustrujete
ho na celý život. – David Leinweber
V r. 2012 jsem
shrnul pár postřehů o softwarovém vývoji a zároveň slíbil, že
se nedopustím pokračování. Protože sliby se mají porušovat a
protože se mi nashromáždilo několik dalších poznámek, o něž
se chci podělit, zde je další dávka.
Architektura
Dali byste si do
svého CV “Junior SW Architect”? Přijali byste takového člověka
do firmy? Asi sotva. Slovo Architekt evokuje někoho
seriózního, někoho staršího. A je to odůvodněný požadavek –
architekt má (měl by mít) v první řadě zkušenosti s nejméně
několika komplexními projekty. Měl by mít vizi, přehled o celku,
měl by umět konzervativně tlumit naivní nadšení juniorních
vývojářů na jedné straně a naopak argumentovat ve prospěch
zdánlivě nerozumných rozhodnutí, pokud jsou dlouhodobě užitečná.
Pokud slyší „získáme tím to a to“, měl by vždy dodat „a
tímto za to zaplatíme“. Měl by… jistě umíte pokračovat.
Pokud se jen trochu
orientujete v zásadách moderního vývoje software, nemohl vám
uniknout rezervovaný názor agilních věrozvěstů na softwarovou
architekturu a částečně i určitý despekt k SW architektům jako
takovým. Je to odůvodněné – tato nedůvěra pramení z doby,
kdy se plánovalo stylem
waterfall. Architekt býval mudrc v bílém plášti, který
vše na základě zadání vyprojektoval v průběhu jediného
geniálního tvůrčího aktu, předal vývojářům nějaký plán
(v dávné minulosti štos vývojových diagramů, dnes nejspíš
nějaká UML schemata), nemyslící poslušní vývojáři plán
uchopili a realizovali, nemyslící poslušní testeři otestovali, a
pak se to nasadilo.
Dnes jsme dál.
Základním předpokladem agile je, že jakákoliv změna musí
zůstat levná po dobu celého vývoje (tedy po dobu života
projektu, vč. fáze údržby). O architektuře se nemluví, vzniká
jaksi za pochodu. Architektura není něco, co můžete naúčtovat
zákazníkovi, architektura není feature. Není to něco, co
by si product management objednal, tak proč bychom to vůbec měli
řešit? V předchozím postu jsem toto téma jen nadhodil:
Důvěra v projekt vzniká agilními iteracemi. Že je tato
inkrementální cesta "mnoha sprinty k cíli" často
pomalejší než trasa pro poctivé maratonce, pak nakonec nikoho
nezajímá.
Ano, určitě dnes
nepotřebujete mudrce v bílém plášti. Nepotřebujete knížecí
rady někoho z venku. A zvláště nepotřebujete architekta,
jenž se odmítá dotknout zdrojového kódu a nechce se ušpinit
konkrétními problémy, tzv. „implementačními detaily“
všedních dní.
To ale neznamená,
že nepotřebujete člověka, jenž má architektonické cítění.
Někoho, kdo má zkušenostmi vypěstovaný cit pro rovnováhu mezi
krátkodobými a dlouhodobými cíli, mezi celkem a detailem. Musí
to být někdo uvnitř týmu se znalostmi o projektu, ale i s
dobrým přehledem o nových technologiích. V ideálním případě
je to člověk, jenž stojí u zrodu projektu a má představu o jeho
organickém růstu. Je to mediátor, jenž se na každý technický
spor dokáže podívat z pohledu obou stran konfliktu a dovede
nezaujatě posoudit pro a proti každého řešení. Také je
kompetentní, co se týče technologického dluhu. Dává pozor, aby
tento příliš nenarostl a neprodražoval změny, naopak si
uvědomuje, že rychlý postup občas vyžaduje jeho krátkodobý nárůst
(pilotní experimenty). Umí včas naplánovat fázi refaktorování
a rozpustit technologické úkoly v rámci normální agilní
iterace, aby okolí nenabylo dojmu, že se příliš
snížila velocita
projektu. Protože
kvůli tajm tu market jsme přešli na skram, ale proč to
nakódili tak zabagovaný, že to musej teď furt jen
fixovat?
Jistě vás napadne,
že zatímco instantních manažerů s agilními
certifikáty je všude plno, jejich protihráč –
architekt-programátor-superhrdina – se nedá sehnat, a pokud ano,
nedá se zaplatit.
Pravidla
Co když si ale roli
architekta rozdělí několik členů týmu? Co když je
architektonické rozhodnutí záměrně konsensuální?
Zkuste s touto
představou chvíli pracovat: Architektura není dílo jednotlivce,
jde o kolektivní proces. Místo ustavení jediného osvíceného
diktátora zodpovědného za dlouhodobou technickou strategii
posílíte svědomí všech vývojářů tak, aby ke špatným
rozhodnutím pokud možno nedocházelo. Myslíte, že je to nemožné?
Dejte dvěma programátorům stejné zadání a jako odpověd získáte
šest navzájem odlišných způsobů, jak ho lze implementovat. Tak
jak by mohlo docházet ke shodě v architektuře?
Trik je v tom, že
sice při vývoji software skoro vždy existuje více cest, jak
dosáhnout jednoho cíle, vývojáři se ale dokážou snadno
shodnout v jejich hodnocení („když použiji tuto knihovnu,
znamená to...“, „když přejdeme na toto API, musíme řešit...“,
atd.). Důležité je, aby za zásadnější rozhodnutí (např. o
závislosti na konkrétní technologii třetí strany) nebyl
zodpovědný jediný člověk. Aby nedocházelo k prosazení
individuální vůle na úkor skřípání zubů ostatních.
Samozřejmě, že čím více lidí se podílí na rozhodovacích
procesech, tím je větší riziko paralýzy analýzou.
Rozhodnutí prodiskutované v okruhu několika zainteresovaných je
ale vždy žádoucí a v dobrém týmu je tento postup zpravidla
efektivní.
S rostoucím počtem
členů týmu začne být zjevné, že některá rozhodnutí mají
obecnou platnost. Není třeba stále opakovat malé debaty, pokud
existuje pozitivní zkušenost s důsledky jejich závěrů. Začne
vznikat sada zvyků (best practices),
ať už v psané nebo neformální podobě. Tato sada mívá většinou
povahu detailních pokynů z agilní praxe – např. jednotný
způsob formátování kódu (coding standards),
pravidla revize kódu (peer review), automatizace, atd.
Často ale tímto
postupem organicky vznikají i vysokoúrovňová pravidla s dopady na
architekturu řešení. Zde je nutné zdůraznit jednu důležitou
věc: Architektonické pravidlo není – na rozdíl od agilních
dogmat – nějaké jedno pevné schema, spíš jde o návod k tvorbě
schemat, mají-li být životaschopná. Nejde tedy o stav, ale o
proces. Architektura je tak flexibilní, což se cení, protože
schopnost konat rychlé změny je jedním ze základních principů.
Když se časem
ukáže, že se nějaký postup přežil, mělo by být samozřejmé
ho po dohodě revidovat. Podobně, jako nikomu nepřijde divné mazat
řádky nepotřebného kódu, mělo by být snané zrušit
rozhodnutí, když se ukáže, že už nevyhovuje novým podmínkám.
Pravidla musí být
jednoduchá, aby fungovala a nesmí jich nikdy být
mnoho. Jejich jednoduchost a nízký počet dovolí, aby byla snadno
bez velkého přemýšlení dodržována. Komplikované a od reality
odtržené zásady jsou běžně obcházeny, realita je vždy
složitější než znění sebedelšího zákona. Libovolný proces
je snadno dodržován, pokud tvůrčímu člověku pomáhá a
zbytečně ho neomezuje.
Na architekturu a
agilní pravidla se můžeme dívat jako na jakési metaprogramování
programátorů. Pokud dobrý programátor produkuje dobrý software,
pak dobrá architektura a rozumné zásady produkují dobré
programátory. Abych nebyl špatně pochopen – nejde o
technokratický styl řízení lidí, ono „produkovat programátory“
znamená především je naučit, jak se co nejlépe mohou sami
zorganizovat v rámci týmu, aby si navzájem nepřekáželi. A
protože vývojáři jsou většinou otevřeni novému, vnímají
tyto procesy jako novou zajímavou společenskou hru, jejíž
pravidla si musí osvojit a mohou se zároveň spolupodílet na
jejich tvorbě.
Důležité je stále
mít na paměti, že příliš byrokracie brzdí, zvlášť v malých
týmech. Programátor se chce plně soustředit na svůj problém,
cokoliv mimo něj ho zdržuje. A obrovskou brzdou je pocit nesouladu, pocit zbytečné nebo rutinní práce,
nedůvěra v cíl snažení. Konsensus v zásadních otázkách
naopak vnímá vývojář jako něco přirozeného.
Elegance
Tím se dostávám k
poslední oblasti, osobnosti a emocím programátora. Mohlo by se
zdát, že v dnešním SW vývoji emoce nemají své místo. Vývojář
jako disciplinovaný profesionál má zdánlivě činit objektivní
kroky od dobrého zadání k dokonalé implementaci. Domnívám se,
že tomu tak ale není a že individuální preference a intuice
programátora se do výsledného software promítají na každém
kroku, téměř na každém řádku zdrojového kódu. Jsme odlišní
v tom, jaké programovací jazyky upřednostňujeme, lišíme se v
tom, co považujeme za jednoduché a co naopak za složité, jinak
důvěřujeme novým postupům a technologiím, máme odlišnou
potřebu psát komentáře, neshodujeme se často v tom, co znamená
dostatečné pokrytí testy, atd.
Věcí, které může
osobnost a zkušenosti vývojáře ovlivnit, je příliš mnoho na
to, aby se dalo od emocí a intuicí snadno abstrahovat.
Opět zde platí, že
přestože se dva programátoři jen málokdy shodnou v tom, jaký
kód je dobrý, často se shodují v názoru na to, že nějaký kód
je špatný. Znakem špatného kódu je jeho nepřehlednost,
nekonzistence, množství podmínek, příliš mnoho varovných
komentářů (beware, fixme, ugly…). Špatné řešení
jednoduše vzbuzuje pocit, že zdrojový kód je obtížně
udržovatelný a přerostl autorovi přes hlavu, že se v něm
dopustí chyby každý, kdo se pokusí o jeho úpravu. Pocit, že
nejlepší by bylo to celé přepsat.
Technologický dluh
je v podstatě hromadění ošklivého. Vzniká
plíživě – jsou to denodenní malá rozhodnutí, nevinně
vypadající kompromisy, nánosy prachu rostoucí ve zdrojovém kódu,
které se v nejméně vhodnou chvíli jako lavina utrhnou a zavalí
všechny zúčastněné. Aby zůstal dluh pod kontrolou, je nutné
čas od času uklízet, zjednodušovat kód, zpřehledňovat a
opravovat drobné defekty. Pokud se z této činnosti stane
dlouhodobý návyk, dovoluje to projektu růst organickou cestou, od
jednoduchého ke složitějšímu, při udržení konstantní
kvality. Pořádek (čistota kódu) umožňuje lepší spolupráci.
Když se naopak
stane nepořádek normou, všichni se naučí tolerovat malé
nedostatky, ignorovat varování, vypínat automatické testy.
Kvalita software postupně upadá, takže je časem snadné
přehlédnout nějakou fatální chybu.
Dalším těžko
sdělitelným pocitem je elegance kódu. Co je a co není
elegantní, je značně subjektivní záležitost. Např. ve volbě programovacích
jazyků panuje mezi programátory téměř dokonalá neshoda: Ruby mi
připadá v mnoha ohledech elegantnější a ergonomičtější než
Python, přestože jsou oba jazyky podobné. Naučil jsem se ale
respektovat, že je mnoho lidí s opačným názorem, o čemž
vypovídá nezpochybnitelné vítězství Pythonu ve světě
strojového učení. Ve vlastním kódu se snažím nevynalézat kolo
a rád se opřu o cizí řešení, na druhou stranu rozumím tomu,
když si kolega napíše vlastní implementaci něčeho velmi
základního, jen proto, aby se vyhnul závislosti na nové knihovně,
atd.
V programování
platí, podobně jako v jiných lidských činnostech, že vkus a
soudnost roste se zkušeností. Nerodíme se se schopností poznat
dobrou hudbu od špatné, jsme vychováváni k tomu poznat kvalitu od
škváry tím, jak posloucháme. Podobně se z nás gurmán nestane,
dokud neochutnáme opravdu dobrá jídla.
Cit pro eleganci a
jednoduchost řešení tak najdete snáze u zkušeného vývojáře
než u začátečníka. Opačně to ale neplatí – i zkušený
programátor může odbýt návrh rozhraní, nemá-li čas nebo je mu
to jedno.
Dobrá, jak se tedy
snadno stanete vnímavým k estetické stránce software?
Nejde to snadno, ale
stojí to za tu námahu: Čtěte cizí zdrojový kód, snažte se
pochopit složitější systémy. Proč je API navrženo právě
takto a ne jinak, jak jsou části odděleny a jak propojeny, jak
vypadají úspěšné refaktorizace, atd. Uvažujte nad komunikačními
protokoly, datovými strukturami, relačními schematy. Snažte se
vidět, jak se systém vyvíjí v čase – sledujte, které jeho
části se mění pomalu a které rychle. Co je jednoduchá změna a
jak vypadá drahá oprava. Proč a kde se schovávají chyby.
Kde je užitečné optimalizovat a kde je to naopak škodlivé. Přečtěte si o tom nějaké chytré texty a buďte kritičtí.
A nejen to. Čtěte
dobré knihy nejen o SW, foťte nebo kreslete, cestujte a
naslouchejte, poslouchejte nebo provozujte muziku, obdivujte krásné
věci, přemýšlejte o symetrii, o řádu světa, o eleganci.
A Captain
Obvious by
ještě dodal: Pokud
se chcete stát dobrým softwarovým architektem, především velmi
programujte!
Přihlásit se k odběru:
Příspěvky (Atom)