Hlavní navigace

Framework Torch: základy práce s neuronovými sítěmi

Pavel Tišnovský

Dnes si ukážeme, jak je možné vytvořit jednoduchou neuronovou síť s jednou skrytou vrstvou neuronů, naučit ji řešit zvolený problém s využitím sady trénovacích dat a následně ji použít nad další sadou (validačních) dat.

Obsah

1. Framework Torch: základy práce s neuronovými sítěmi

2. Idealizovaný model neuronu používaný v umělých neuronových sítích

3. Role biasu

4. Aktivační funkce

5. Vytvoření feed-forward sítě z jednotlivých neuronů

6. Vstupní vrstva, výstupní vrstva a skryté vrstvy neuronů

7. Trénink (učení) sítě s využitím trénovacích dat

8. Tvorba neuronové sítě ve frameworku Torch

9. Příprava trénovacích dat

10. Konstrukce neuronové sítě se třemi vrstvami

11. Trénink sítě

12. Použití neuronové sítě na sadě validačních dat

13. Úplný zdrojový kód dnešního prvního demonstračního příkladu

14. Parametry ovlivňující chování sítě

15. Příliš malý počet neuronů ve skryté vrstvě

16. Jednoduchá síť odhadující součet dvou prvků

17. Odhad součtu sítí využívající aktivační funkci Tanh

18. Použití aktivační funkce ReLU namísto Tanh

19. Úplný zdrojový kód dnešního druhého demonstračního příkladu

20. Odkazy na Internetu

1. Framework Torch: základy práce s neuronovými sítěmi

V dnešním článku o frameworku Torch se budeme zabývat problematikou vytvoření jednoduchých umělých neuronových sítí (zkráceně jen neuronových sítí neboli neural network, nn) a taktéž způsobem tréninku (učení) těchto sítí. Již na začátku je nutné říct, že se jedná o dosti rozsáhlou problematiku, pro jejíž pochopení se navíc očekává alespoň základní znalost teorie neuronových sítí. I z tohoto důvodu se zpočátku zaměříme na jednoduché sítě typu feed-forward se třemi vrstvami neuronů, přičemž neurony na sousedních vrstvách budou propojeny (synapsemi) systémem „každý s každým“ a informace mezi neurony potečou pouze jedním směrem (forward). V dalších částech tohoto seriálu si popíšeme i další typy sítí, v nichž bude použito více vrstev neuronů, neurony budou propojeny odlišným způsobem, budou použity jiné aktivační funkce atd.

Neuronové sítě se používají zejména v těch projektech, v nichž je zapotřebí vytvořit funkční systém už ve chvíli, kdy ještě neznáme všechny možné kombinace vstupů a výstupů, popř. když je chování systému, který se implementuje, tak složité, že klasický návrh a implementace algoritmů by byl příliš zdlouhavý nebo s danými prostředky (čas, počet vývojářů a testerů) neefektivní. Jednou z nevýhod neuronových sítí může být to, že je někdy velmi obtížné zjistit, jaký problém jsme vlastně neuronovou síť naučili řešit. Výsledná síť se totiž (pokud se nebudeme hodně snažit zjistit více) chová jako blackbox, o němž není snadné říct, jaké konkrétní rozhodování ten který neuron provádí (v některých případech to ovšem možné je). Kritickou úlohu zde sehrává výběr vhodné množiny trénovacích dat. K tomu se však dostaneme až v dalších částech seriálu, v nichž se zmíníme o již existujících trénovacích datech.

2. Idealizovaný model neuronu používaný v umělých neuronových sítích

Při práci s umělými neuronovými sítěmi je vhodné vědět, jak je vlastně taková síť zkonstruována a z jakých prvků se skládá. Základním stavebním prvkem je umělý neuron, resp. velmi zjednodušený a idealizovaný model skutečného neuronu. Původní model neuronu byl navržen Warrenem McCullochem a Walterem Pittsem (MCP) již ve čtyřicátých letech minulého století, z čehož plyne, že neuronové sítě nejsou jen módním výstřelkem poslední doby (naopak, moderní GPU umožňují jejich nasazení i tam, kde to dříve nebylo možné, to je však téma na další článek.). Na dalším obrázku jsou naznačeny prvky modelu neuronu:

Obrázek 1: Idealizovaný model neuronu.

Vidíme, že neuron může mít libovolný počet vstupů (na obrázku jsou tři vstupy x1, x2 a x3, ovšem může to být jen jeden vstup nebo i sto vstupů) a má pouze jeden výstup y. Vstupem a výstupem jsou reálná čísla; typicky bývá výstup upraven aktivační funkcí tak, že leží v rozsahu ← 1..1> nebo <0..1>. Dále na schématu vidíme váhy w1, w2 a w3. Těmito váhami jsou vynásobeny vstupní hodnoty. Váhy vlastně představují stav neuronu, tj. o funkci, na kterou byl neuron natrénován (naučen). Vstupní hodnoty x1xn jsou tedy postupně vynásobeny váhami w1wn a výsledky součinu jsou v neuronu sečteny, takže získáme jediné reálné číslo. Toto číslo je zpracováno aktivační funkcí (ta již většinou žádný stav nemá, ostatně stejně jako funkce pro výpočet sumy) výsledek této funkce je poslán na výstup neuronu.

Neuron tedy provádí tento výpočet:

y = f(w1x1 + w2x2 + … + wnxn)

3. Role biasu

Ve skutečnosti není stav neuronu pro n vstupů x1xn určen pouze n vahami w1wn. Musíme přidat ještě váhu w0, na kterou je připojena konstanta 1 (někdy se proto můžeme setkat s nákresem neuronové sítě, v níž se nachází speciální neurony bez vstupů a s jedničkou na výstupu). Model neuronu se přidáním nového vstupu nepatrně zkomplikuje:

Obrázek 2: Idealizovaný model neuronu s biasem.

I výpočet bude vypadat odlišně, neboť do něho přidáme nový člen:

y = f(w0 + w1x1 + w2x2 + … + wnxn)

Tato přidaná váha se někdy nazývá bias, protože vlastně umožňuje posouvat průběh aktivační funkce nalevo a napravo, v závislosti na jeho hodnotě.

4. Aktivační funkce

Bez aktivační funkce by se neuron choval jednoduše – spočítal by vážený součet vstupů a výsledek by poslal na výstup. Aktivační funkce, kterou jsme v předchozích dvou kapitolách označovali symbolem f, do celého výpočtu vnáší nelinearitu. Nejjednodušší aktivační funkce může pro vstupní hodnoty <0 vracet –1 a pro hodnoty ≥0 vracet 1, což vlastně říká, že je nutné dosáhnout určité hraniční hodnoty váženého součtu vstupů, aby byl neuron aktivován (tj. na výstup vyslal jedničku a nikoli –1). Ostatně právě zde znovu vidíme význam biasu, který onu hraniční hodnotu posunuje.

Obrázek 3: Aktivační funkce ReLU.

V praxi je však aktivační funkce složitější, než zmíněný jednotkový skok. Často se používá ReLU (rectified linear unit), sigmoid nebo hyperbolický tangent. Pro specializované účely se však používají i další funkce, které dokonce nemusí mít monotonní průběh. S dalšími podporovanými funkcemi se seznámíme příště.

Obrázek 4: Aktivační funkce Tanh.

5. Vytvoření feed-forward sítě z jednotlivých neuronů

Samostatné neurony i s aktivační funkcí stále provádí velmi jednoduchou činnost, ovšem aby se mohly stát součástí složitějšího systému (řekněme automatického řízení auta), musíme z nich vytvořit síť. Jedna z nejjednodušších forem umělé neuronové sítě se nazývá feed-forward, a to z toho důvodu, že informace (tedy vstupní hodnoty, mezihodnoty i hodnoty výstupní) touto sítí tečou jen jedním směrem (při učení je tomu jinak). Neurony jsou uspořádány pravidelně do vrstev:

Obrázek 5: Uspořádání neuronů do vrstev ve feed-forward síti.

Kolečka na obrázku představují jednotlivé neurony, přičemž žlutě jsou označeny neurony na vstupu, zeleně „interní“ (skryté) neurony a červeně neurony, které produkují kýžený výstup neuronové sítě.

Zcela nalevo jsou šipkami naznačeny vstupy. Jejich počet je prakticky zcela závislý na řešeném problému. Může se jednat jen o několik vstupů (viz naše testovací síť popsaná níže, která zpracuje dvě reálná čísla), ovšem pokud například budeme tvořit síť určenou pro rozpoznání objektů v rastrovém obrázku, může být počet vstupů roven počtu pixelů.

6. Vstupní vrstva, výstupní vrstva a skryté vrstvy neuronů

Vraťme se ještě jednou k obrázku číslo 5.

Povšimněte si, že vstupní neurony mají vlastně zjednodušenou funkci, protože mají jen jeden vstup. V mnoha typech sítí tyto neurony jen rozesílají vstup na další neurony a neprovádí žádný složitější výpočet, například u nich není použita aktivační funkce, ovšem to již záleží na konkrétní konfiguraci sítě. Dále stojí za povšimnutí, že neurony posílají svůj výstup neuronům na nejbližší další vrstvě; nejsou zde tedy žádné zkratky, žádné zpětné vazby atd. Existují samozřejmě složitější typy sítí, těmi se teď ale nebudeme zabývat. Dále tato síť propojuje neurony na sousedních vrstvách systémem „každý s každým“. V našem konkrétním příkladu mají neurony na prostřední vrstvě dva vstupy, protože předchozí vrstva má jen dva neurony. Ovšem neurony na poslední vrstvě již musí mít tři vstupy.

Poznámka: může se stát, že síť bude po naučení obsahovat neurony, jejichž váhy na vstupu budou nulové. To vlastně značí, že ze sítě některé spoje (šipky) zmizí, protože vynásobením jakéhokoli vstupu nulou dostaneme zase jen nulu.

První vrstva s jednoduchými („hloupými“) neurony se nazývá vstupní vrstva, poslední vrstva je vrstva výstupní. Vrstvy mezi vrstvou vstupní a výstupní, kterých může být teoreticky libovolné množství, se nazývají skryté vrstvy.

„Paměť“ neuronové sítě je tvořena vahami na vstupech neuronů (včetně biasu):

Vrstva Neuronů Počet vstupů/neuron Počet vah/neuron Celkem
1 2 1 2 4
2 3 2 3 9
3 2 3 4 8
7     21

V praxi se používají sítě s více vrstvami a především s větším počtem neuronů v každé vrstvě. Stavový prostor a tím i schopnosti sítě se tak prudce rozšiřují (viz již zmíněná problematika rozpoznávání objektů v rastrových obrázcích).

Poznámka: někdy se počet neuronů v umělých sítích porovnává s počtem neuronů v mozku, ale to je ve skutečnosti dost zavádějící, neboť záleží spíše na uspořádání sítě, složitosti neuronů (více výstupů) atd.

7. Trénink (učení) sítě s využitím trénovacích dat

Nejzajímavější je proces tréninku (učení) sítě. Ten může probíhat několika způsoby, ovšem nejčastější je učení založené na tom, že na vstup sítě přivedeme data, u nichž dopředu známe očekávaný výsledek (v Torchi se jedná o tenzor). Síť pro tato vstupní data provede svůj odhad a na základě rozdílů mezi odhadem sítě a očekávaným výsledkem se více či méně sofistikovanými algoritmy nepatrně pozmění váhy wi na vstupech do neuronů (včetně biasu, tedy w0). Konkrétní míra změn váhy na vstupech neuronů je globálně řízena dalším parametrem či parametry, z nichž ten nejdůležitější ovlivňuje rychlost učení. Ta by neměla být příliš nízká (to vyžaduje objemná trénovací data nebo jejich opakování), ale ani příliš vysoká. Základní algoritmus učení sítě se jmenuje backpropagation, protože se váhy skutečně mění v opačném směru – od výstupů (na něž se přivede vypočtená chyba) ke vstupům. Asi nejlépe je tento koncept popsán v článku dostupném na adrese https://mattmazur.com/2015/03/17/a-step-by-step-backpropagation-example/, tuto část za nás však vykoná Torch automaticky (navíc alternativně s dopomocí GPU).

8. Tvorba neuronové sítě ve frameworku Torch

V následujících čtyřech kapitolách si ukážeme, jak lze vytvořit jednoduchou neuronovou síť v využitím frameworku Torch. Navrhovaná síť bude mít dva vstupy, jeden výstup a jedinou skrytou vrstvu. Bude tedy vypadat takto (počet neuronů na skryté vrstvě je ve skutečnosti větší, tím pádem i počet spojů/synapsí):

Obrázek 6: Neuronová síť, kterou se budeme snažit vytvořit v dalších kapitolách.

Síť bude natrénována pro výpočet zobecněné funkce xor, což je poměrně typický „školní“ příklad, mnohdy zadávaný tak, aby studenti museli naučit síť sami (tj. „ručním“ provedením algoritmu backpropagace).

Při práci s Torchem se budeme používat objekty z těchto tří modulů:

  1. Neural network containers (kontejnery pro vrstvy neuronové sítě)
    https://github.com/torch/nn/blob/mas­ter/doc/containers.md
  2. Simple layers (jednotlivé vrstvy sítě)
    https://github.com/torch/nn/blob/mas­ter/doc/simple.md#nn.Line­ar
  3. Transfer Function Layers (nelineární funkce zabudované do sítě)
    https://github.com/torch/nn/blob/mas­ter/doc/transfer.md#nn.tran­sfer.dok

9. Příprava trénovacích dat

V první fázi našeho projektu jednoduché neuronové sítě provedeme přípravu trénovacích dat. Bude se jednat o dvojice čísel generovaných náhodně s normálním rozložením a o návratovou hodnotu funkce compute_xor. Dvojice náhodných čísel bude představovat vstupní hodnoty do trénované sítě, návratová hodnota funkce compute_xor pak očekávaný výsledek. Připravíme si 2000 takových záznamů:

TRAINING_DATA_SIZE = 2000

Naše varianta do značné míry zobecněné funkce xor vrátí hodnotu –1 v případě, že znaménka vstupních číselných hodnot jsou shodná a hodnotu 1 v opačném případě:

function compute_xor(x, y)
    if x * y > 0 then
        return -1
    else
        return 1
    end
end

Funkce nazvaná prepare_training_data skutečně připraví trénovací data pro neuronovou síť. Povšimněte si, jak musí být výsledný objekt zkonstruován – základem je objekt představovaný poli dvojic, přičemž první prvek dvojice tvoří vstupní data (tenzor!) a druhý prvek pak data očekávaná, tedy výstupní. V případe výstupních dat se taktéž musí jednat o tenzor, v našem případě pak o tenzor s jediným prvkem. Navíc musíme do výsledného objektu přidat metodu size vracející počet záznamů (mimochodem – protože se jedná o metodu, je data možné připravit i generátorem):

function prepare_training_data(training_data_size)
 
    -- objekt který reprezentuje trénovací data
    local training_data = {}
 
    -- metoda size vrací počet záznamů
    function training_data:size() return training_data_size end
 
    -- příprava jednotlivých záznamů
    for i = 1,training_data_size do
 
        -- vstupem je tenzor se dvěma prvky/komponentami
        local input = torch.randn(2)
 
        -- výstupem je tenzor s jediným prvkem/komponentou
        local output = torch.Tensor(1)
        output[1] = compute_xor(input[1], input[2])
 
        -- dvojice vstup+výstup tvoří jeden záznam trénovacích dat
        training_data[i] = {input, output}
 
    end
    return training_data
end

To je vše – stačí jen zavolat funkci prepare_training_data a uložit si její výsledek do proměnné:

training_data = prepare_training_data(TRAINING_DATA_SIZE)

10. Konstrukce neuronové sítě se třemi vrstvami

Trénovací data máme připravena, takže se nyní zaměříme na poněkud složitější úkol. Budeme totiž muset vytvořit neuronovou síť se třemi vrstvami neuronů. Počet vstupních neuronů (neuronů na vstupní vrstvě) bude roven dvěma, protože odpovídá počtu vstupních hodnot. Výstupní neuron bude jediný, protože optimisticky na výstupu očekáváme hodnotu –1 nebo 1. Na prostřední skryté vrstvě vytvoříme dvacet neuronů, což je v tomto případě až absurdně velký počet, protože teoreticky budou postačovat tři neurony. Pro snadnou modifikaci kódu si tyto údaje zapíšeme do globálních proměnných:

INPUT_NEURONS = 2
HIDDEN_NEURONS = 20
OUTPUT_NEURONS = 1

Ve funkci pro konstrukci neuronové sítě nejprve deklarujeme typ sítě konstruktorem Sequential. Tento konstruktor vytvoří síť, v níž jsou jednotlivé vrstvy propojeny jednosměrně (feed-forward) a všechny neurony z jedné vrstvy jsou propojeny se všemi neurony na vrstvě další. Dále metodou add do sítě postupně přidáme:

  1. Propojení mezi vstupní vrstvou a skrytou vrstvou (nn.Linear, lineární transformace y=Ax+b)
  2. Aktivační funkci (nn.Tanh, ovšem pouze do prostřední/skryté vrstvy)
  3. Propojení mezi skrytou vrstvou a výstupní vrstvou (nn.Linear)
function construct_neural_network(input_neurons, hidden_neurons, output_neurons)
    -- typ sítě typu feed-forward, v níž jsou propojeny všechny neurony mezi vrstvami
    local network = nn.Sequential()
 
    -- specifikace vstupní vrstvy: počet vstupů, počet neuronů na skryté vrstvě
    network:add(nn.Linear(input_neurons, hidden_neurons))
    -- aktivační funkce pro neurony na skryté vrstvě
    network:add(nn.Tanh())
    -- specifikace výstupní vrstvy: počet vstupů ze skryté vrstvy, počet výstupních neuronů
    network:add(nn.Linear(hidden_neurons, output_neurons))
 
    -- síť máme vytvořenou
    return network
end

Zkusme si nyní zkonstruovat výše popsanou umělou neuronovou síť a následně si nechat vypsat její strukturu:

network = construct_neural_network(INPUT_NEURONS, HIDDEN_NEURONS, OUTPUT_NEURONS)
print(network)

Příkaz print(network) by měl symbolicky naznačit strukturu neuronové sítě:

nn.Sequential {
  [input -> (1) -> (2) -> (3) -> output]
  (1): nn.Linear(2 -> 20)
  (2): nn.Tanh
  (3): nn.Linear(20 -> 1)
}

Jediná nelinearita byla přidána do prostřední vrstvy, což není – jak uvidíme z výsledků – optimální. Jak tento problém napravit si ukážeme ve čtrnácté kapitole.

11. Trénink neuronové sítě

Neuronovou síť již máme vytvořenou, ovšem musíme ji něco „naučit“, což znamená postupně doupravit váhy wi vstupů jednotlivých neuronů. Váhy většinou jsou na začátku učení nastaveny náhodně, cílem učení je pak v ideálním případě dosáhnout globálního minima chyby odhadu sítě vůči očekávaným výsledkům. Pro trénink (s využitím algoritmu backpropagation) potřebujeme stanovit kritérium a taktéž určit směr hledání globálního minima. Parametrem learning_rate se stanoví rychlost učení (zjednodušeně řečeno míra ovlivnění vah wi v každé iteraci učení), která však nemůže být příliš vysoká, neboť by to ovlivnilo celkový výsledek (opět velmi zjednodušeně řečeno: poslední trénovací vzorky by ho příliš ovlivnily):

function train_neural_network(network, training_data, learning_rate, max_iteration)
    local criterion = nn.MSECriterion()
    local trainer = nn.StochasticGradient(network, criterion)
    trainer.learningRate = learning_rate
    trainer.maxIteration = max_iteration
    trainer:train(training_data)
end

Spustíme celý trénink:

train_neural_network(network, training_data, LEARNING_RATE, MAX_ITERATION)

Můžeme sledovat průběh tréninku, kdy nám systém vypisuje, jaká je velikost chyby pro jednotlivé vstupy, tj. pro trénovací data (dvojice vstupů a očekávaných výstupů). Zpočátku může být chyba hodně velká, protože v prvotním stavu jsou váhy na vstupech neuronů nastaveny na náhodnou hodnotu:

# StochasticGradient: training
# current error = 0.54242233556529
# current error = 0.37642565798334
# current error = 0.34209057999057
# current error = 0.30198045696604
# current error = 0.27091862342749
# current error = 0.25200259699324

Velká chyba na začátku nás však nemusí trápit, protože v ideálním případě by velikost chyby měla postupně (a poměrně rychle) klesat. Pokud chyba klesá pomalu, což je mimochodem i náš případ, může to znamenat, že je v síti málo nelineárních přechodů. Může se také stát, že velikost chyby bude oscilovat – klesat a zase růst. To může znamenat příliš malý počet neuronů, které nestačí pro zapamatování „funkce“ celé sítě:

# current error = 0.11337547166271
# current error = 0.11332347785516
# current error = 0.11327187176746
# current error = 0.11322063535328
# StochasticGradient: you have reached the maximum number of iterations
# training error = 0.11322063535328

12. Použití neuronové sítě na sadě validačních dat

Neuronovou síť jsme s využitím trénovacího vzorku dat naučili vrátit výsledek zobecněné funkce xor, i když popravdě řečeno byla chyba na konci stále příliš velká. Pokusme se nyní validovat, jestli je tento předpoklad správný a jestli síť dává alespoň trošku rozumné výsledky. Validace může být velmi snadná, protože můžeme použít metodu forward, která pošle vstupní data (tenzor) do vstupní vrstvy neuronů, nechá síť vypočítat výstup a tento výstup vrátí, opět ve formě tenzoru. V našem případě musí být na vstup poslán tenzor se dvěma prvky (komponentami) a na výstupu očekáváme jednoprvkový tenzor:

x=torch.Tensor({0.5, -0.5})
prediction = network:forward(x)
print(prediction)

Na standardní výstup by se měl vypsat výsledek výpočtu, například:

 1.0478
[torch.DoubleTensor of size 1]

Očekávaná ideální hodnota je přitom rovna 1.

Poznámka: ve vašem případě může být výsledek odlišný, protože je závislý na stavu sítě na začátku tréninku i na vlastních trénovacích datech, které byly vygenerovány pomocí funkce randn.

Lepší bude si na validaci sítě vytvořit novou uživatelskou funkci, která bude na vstup sítě předávat zvolená data, vypočte exaktní výsledek funkcí compute_xor, zjistí predikci neuronové sítě, oba výsledky porovná a navíc vypočte relativní chybu (v procentech, což obecně znevýhodňuje predikce v okolí nuly; to ovšem není náš případ). Tato funkce může vypadat následovně:

function validate_neural_network(network, validation_data)
    for i,d in ipairs(validation_data) do
        -- dvě hodnoty, pro něž se vypočte xor
        d1, d2 = d[1], d[2]
 
        -- vstupem do sítě musí být tenzor
        input = torch.Tensor({d1, d2})
 
        -- predikce sítě je také tenzor, takže přečteme jeho jediný prvek
        prediction = network:forward(input)[1]
 
        -- korektní výsledek
        correct = compute_xor(d1, d2)
 
        -- relativní chyba
        err = math.abs(100.0 * (prediction-correct)/correct)
 
        -- vše vypíšeme
        msg = string.format("%2d  %+6.3f  %+6.3f  %+6.3f  %+6.3f  %4.0f%%", i, d1, d2, correct, prediction, err)
        print(msg)
    end
end

Data pro validaci mohou vypadat například takto:

validation_data = {
    { 1.0,  1,0},
    { 0.5,  0.5},
    { 0.2,  0.2},
    -------------
    {-1.0,  1,0},
    {-0.5,  0.5},
    {-0.2,  0.2},
    -------------
    { 1.0, -1,0},
    { 0.5, -0.5},
    { 0.2, -0.2},
    -------------
    {-1.0, -1,0},
    {-0.5, -0.5},
    {-0.2, -0.2},
}

Spustíme validaci:

validate_neural_network(network, validation_data)

A dostaneme tyto výsledky, které ovšem nejsou příliš přesné (viz vypočtená relativní chyba):

 1  +1.000  +1.000  -1.000  -1.278    28%
 2  +0.500  +0.500  -1.000  -1.022     2%
 3  +0.200  +0.200  -1.000  -1.116    12%
 4  -1.000  +1.000  +1.000  +1.128    13%
 5  -0.500  +0.500  +1.000  +0.861    14%
 6  -0.200  +0.200  +1.000  +0.814    19%
 7  +1.000  -1.000  +1.000  +0.722    28%
 8  +0.500  -0.500  +1.000  +1.048     5%
 9  +0.200  -0.200  +1.000  +0.745    25%
10  -1.000  -1.000  -1.000  -0.947     5%
11  -0.500  -0.500  -1.000  -0.685    32%
12  -0.200  -0.200  -1.000  -0.975     3%

V navazujících kapitolách si ukážeme možná vylepšení (i zhoršení kvality) naší sítě.

13. Úplný zdrojový kód dnešního prvního demonstračního příkladu

V předchozích kapitolách jsme si ukázali jednotlivé části příkladu s jednoduchou umělou neuronovou sítí. Úplný zdrojový kód tohoto příkladu můžete získat z adresy https://github.com/tisnik/torch-examples/blob/master/nn/01_xor_pro­blem.lua, popř. si ho zkopírovat z následujícího výpisu:

require("nn")
 
TRAINING_DATA_SIZE = 2000
 
INPUT_NEURONS = 2
HIDDEN_NEURONS = 20
OUTPUT_NEURONS = 1
 
MAX_ITERATION = 200
LEARNING_RATE = 0.01
 
 
function compute_xor(x, y)
    if x * y > 0 then
        return -1
    else
        return 1
    end
end
 
 
function prepare_training_data(training_data_size)
    local training_data = {}
    function training_data:size() return training_data_size end
    for i = 1,training_data_size do
        local input = torch.randn(2)
        local output = torch.Tensor(1)
        output[1] = compute_xor(input[1], input[2])
        training_data[i] = {input, output}
    end
    return training_data
end
 
 
function construct_neural_network(input_neurons, hidden_neurons, output_neurons)
    local network = nn.Sequential()
 
    network:add(nn.Linear(input_neurons, hidden_neurons))
    network:add(nn.Tanh())
    network:add(nn.Linear(hidden_neurons, output_neurons))

    return network
end
 
 
function train_neural_network(network, training_data, learning_rate, max_iteration)
    local criterion = nn.MSECriterion()
    local trainer = nn.StochasticGradient(network, criterion)
    trainer.learningRate = learning_rate
    trainer.maxIteration = max_iteration
    trainer:train(training_data)
end
 
 
function validate_neural_network(network, validation_data)
    for i,d in ipairs(validation_data) do
        d1, d2 = d[1], d[2]
        input = torch.Tensor({d1, d2})
        prediction = network:forward(input)[1]
        correct = compute_xor(d1, d2)
        err = math.abs(100.0 * (prediction-correct)/correct)
        msg = string.format("%2d  %+6.3f  %+6.3f  %+6.3f  %+6.3f  %4.0f%%", i, d1, d2, correct, prediction, err)
        print(msg)
    end
end
 
 
network = construct_neural_network(INPUT_NEURONS, HIDDEN_NEURONS, OUTPUT_NEURONS)
training_data = prepare_training_data(TRAINING_DATA_SIZE)
train_neural_network(network, training_data, LEARNING_RATE, MAX_ITERATION)
print(network)
 
 
x=torch.Tensor({0.5, -0.5})
prediction = network:forward(x)
print(prediction)
 
validation_data = {
    { 1.0,  1,0},
    { 0.5,  0.5},
    { 0.2,  0.2},
    -------------
    {-1.0,  1,0},
    {-0.5,  0.5},
    {-0.2,  0.2},
    -------------
    { 1.0, -1,0},
    { 0.5, -0.5},
    { 0.2, -0.2},
    -------------
    {-1.0, -1,0},
    {-0.5, -0.5},
    {-0.2, -0.2},
}
 
validate_neural_network(network, validation_data)

14. Parametry ovlivňující chování sítě

Mezi základní parametry, které ovlivňují chování sítě, tj. zejména rychlost a přesnost učení i přesnost předpovědi výsledků, patří počet vrstev sítě, počet neuronů na jednotlivých vrstvách (kromě vrstvy první a poslední, protože tam je počet ovlivněn velikostí vstupních a výstupních vektorů). Počty neuronů můžeme ovlivnit snadno, například zmenšením počtu neuronů na prostřední (skryté) vrstvě:

INPUT_NEURONS = 2
HIDDEN_NEURONS = 10
OUTPUT_NEURONS = 1

Dále si povšimněte, že nelineární funkce byla původně nakonfigurována pouze pro prostřední (skrytou) vrstvu:

function construct_neural_network(input_neurons, hidden_neurons, output_neurons)
    -- typ sítě typu feed-forward, v níž jsou propojeny všechny neurony mezi vrstvami
    local network = nn.Sequential()
 
    -- specifikace vstupní vrstvy: počet vstupů, počet neuronů na skryté vrstvě
    network:add(nn.Linear(input_neurons, hidden_neurons))
    -- aktivační funkce pro neurony na skryté vrstvě
    network:add(nn.Tanh())
    -- specifikace výstupní vrstvy: počet vstupů ze skryté vrstvy, počet výstupních neuronů
    network:add(nn.Linear(hidden_neurons, output_neurons))
 
    -- síť máme vytvořenou
    return network
end

Můžete si samozřejmě přidat stejnou funkci i pro výstupní neurony:

function construct_neural_network(input_neurons, hidden_neurons, output_neurons)
    -- typ sítě typu feed-forward, v níž jsou propojeny všechny neurony mezi vrstvami
    local network = nn.Sequential()
 
    -- specifikace vstupní vrstvy: počet vstupů, počet neuronů na skryté vrstvě
    network:add(nn.Linear(input_neurons, hidden_neurons))
    -- aktivační funkce pro neurony na skryté vrstvě
    network:add(nn.Tanh())
    -- specifikace výstupní vrstvy: počet vstupů ze skryté vrstvy, počet výstupních neuronů
    network:add(nn.Linear(hidden_neurons, output_neurons))
    -- druhá sada aktivačních funkcí
    network:add(nn.Tanh())
 
    -- síť máme vytvořenou
    return network
end

Struktura sítě se pozmění následovně:

nn.Sequential {
  [input -> (1) -> (2) -> (3) -> (4) -> output]
  (1): nn.Linear(2 -> 10)
  (2): nn.Tanh
  (3): nn.Linear(10 -> 1)
  (4): nn.Tanh
}

Zajímavé je, že výsledky se přidáním další nelinearity do systému sítě razantně zlepší:

 1  +1.000  +1.000  -1.000  -1.000     0%
 2  +0.500  +0.500  -1.000  -1.000     0%
 3  +0.200  +0.200  -1.000  -0.995     1%
 4  -1.000  +1.000  +1.000  +1.000     0%
 5  -0.500  +0.500  +1.000  +1.000     0%
 6  -0.200  +0.200  +1.000  +0.996     0%
 7  +1.000  -1.000  +1.000  +1.000     0%
 8  +0.500  -0.500  +1.000  +1.000     0%
 9  +0.200  -0.200  +1.000  +0.999     0%
10  -1.000  -1.000  -1.000  -1.000     0%
11  -0.500  -0.500  -1.000  -1.000     0%
12  -0.200  -0.200  -1.000  -0.951     5%

Co to znamená? Počet neuronů nemusí být vždy tím rozhodujícím parametrem určujícím kvalitu sítě.

15. Příliš malý počet neuronů ve skryté vrstvě

Neustálé zvyšování počtu neuronů ve skryté vrstvě nemusí vést k lepším výsledkům, ovšem snížení neuronů pod určitou hranici je ještě horší. Upravme si (již jednou upravený) příklad tak, že do prostřední vrstvy umístíme pouhé dva neurony:

INPUT_NEURONS = 2
HIDDEN_NEURONS = 2
OUTPUT_NEURONS = 1

Takto malý počet neuronů (resp. jejich vah, což je stav sítě) je již velmi malý a síť se nedokáže naučit správně předpovídat výslednou hodnotu. I po několika stech iteracích je chyba stále velká (a nebude klesat):

# current error = 0.68643851757531
# StochasticGradient: you have reached the maximum number of iterations
# training error = 0.68643851757531

Chyby jsou nízké pro ty vstupní hodnoty, na které byly neurony naučeny v posledních iteracích, ovšem pro ostatní hodnoty jsou výsledky neakceptovatelné:

 1  +1.000  +1.000  -1.000  -1.000     0%
 2  +0.500  +0.500  -1.000  -0.871    13%
 3  +0.200  +0.200  -1.000  -0.051    95%
 4  -1.000  +1.000  +1.000  +0.005   100%
 5  -0.500  +0.500  +1.000  +0.003   100%
 6  -0.200  +0.200  +1.000  +0.001   100%
 7  +1.000  -1.000  +1.000  +1.000     0%
 8  +0.500  -0.500  +1.000  +0.924     8%
 9  +0.200  -0.200  +1.000  +0.107    89%
10  -1.000  -1.000  -1.000  +0.005   101%
11  -0.500  -0.500  -1.000  +0.008   101%
12  -0.200  -0.200  -1.000  +0.013   101%

16. Jednoduchá síť odhadující součet dvou prvků

Zkusme si nyní vytvořit ještě jednodušší síť a naučit ji sčítat dva prvky (v omezeném intervalu okolo nuly). Od předchozích příkladů bude zdrojový kód odlišný jen v několika maličkostech.

Snížíme množství trénovacích dat na čtvrtinu:

TRAINING_DATA_SIZE = 500

Počet neuronů na prostřední vrstvě snížíme na pouhé dva (zde to však nebude tak vadit):

HIDDEN_NEURONS = 2

Funkce pro přípravu trénovacích dat se nepatrně pozmění:

function prepare_training_data(training_data_size)
    local training_data = {}
    function training_data:size() return training_data_size end
    for i = 1,training_data_size do
        local input = torch.randn(2)
        local output = torch.Tensor(1)
        output[1] = input[1] + input[2]
        training_data[i] = {input, output}
    end
    return training_data
end

A samozřejmě se změní i výpočet chyby při ověřování funkce sítě:

function validate_neural_network(network, validation_data)
    for i,d in ipairs(validation_data) do
        d1, d2 = d[1], d[2]
        input = torch.Tensor({d1, d2})
        prediction = network:forward(input)[1]
        correct = d1 + d2
        err = math.abs(100.0 * (prediction-correct)/correct)
        msg = string.format("%2d  %+6.3f  %+6.3f  %+6.3f  %+6.3f  %4.0f%%", i, d1, d2, correct, prediction, err)
        print(msg)
    end
end

17. Odhad součtu sítí využívající aktivační funkci Tanh

Takto minimalisticky pojatá síť s pouhými pěti neurony se naučí sčítat dobře, což ale není příliš překvapivé, protože právě suma je jednou ze základních funkcí neuronů:

# StochasticGradient: training
# current error = 0.23437645781551
# current error = 0.058120529550668
# current error = 0.037139957667369
# current error = 0.027343471089676
# current error = 0.021707754740601
# current error = 0.017588320527942
# current error = 0.014216556244307
...
...
...
# current error = 0.00072523375142578
# StochasticGradient: you have reached the maximum number of iterations
# training error = 0.00072523375142578

Výsledky po natrénování:

 1  +1.000  +1.000  +2.000  +2.011     1%
 2  +0.500  +0.500  +1.000  +0.992     1%
 3  +0.200  +0.200  +0.400  +0.394     2%
 4  -1.000  +1.100  +0.100  +0.098     2%
 5  -0.500  +0.600  +0.100  +0.097     3%
 6  -0.200  +0.300  +0.100  +0.097     3%
 7  +1.000  -1.100  -0.100  -0.103     3%
 8  +0.500  -0.600  -0.100  -0.102     2%
 9  +0.200  -0.300  -0.100  -0.101     1%
10  -1.000  -1.000  -2.000  -2.038     2%
11  -0.500  -0.500  -1.000  -1.003     0%
12  -0.200  -0.200  -0.400  -0.399     0%

18. Použití aktivační funkce ReLU namísto Tanh

Pokusme se nyní v prostřední (tj. ve skryté) vrstvě neuronů nahradit aktivační funkci Tanh za funkci nazvanou poněkud záhadně ReLU. Zkratka ReLU byla odvozena od jména „rectified linear unit“ a jedná se o nelineární funkci složenou ze dvou lineárních částí. Výpočet funkce ReLU je triviální:

f(x) = max(0,x)

Proč by měla být tato funkce výhodnější, než Tanh? Při výpočtu součtu je nám vlastně nelineární průběh funkcí spíše na obtíž, takže zkusíme zvolit funkci, která sice obsahuje „zlom“, ale budeme doufat, že díky biasu se tento zlom posune mimo vstupní hodnoty (vše je nutné ověřit na testovacích datech):

function construct_neural_network(input_neurons, hidden_neurons, output_neurons)
    local network = nn.Sequential()
 
    network:add(nn.Linear(input_neurons, hidden_neurons))
    -- výběr nelineární funkce
    network:add(nn.ReLU())
    network:add(nn.Linear(hidden_neurons, output_neurons))
 
    return network
end

Už při tréninku sítě můžeme vidět, že jsme zvolili správně, neboť chyba je po dokončení tréninku prakticky nulová (konkrétně 5,1×10-31):

# StochasticGradient: training
# current error = 0.99281388376557
# current error = 0.10862510985273
# current error = 0.036692037190132
# current error = 0.026179566474767
# current error = 0.020993037406808
# current error = 0.015662650169726
# current error = 0.0096425797438203
# current error = 0.0048635968724402
...
...
...
# current error = 5.1172035659567e-31
# StochasticGradient: you have reached the maximum number of iterations
# training error = 5.1172035659567e-31

Ještě lépe dopadne otestování sítě, neboť výpočet je přesný minimálně na tři desetinná místa:

 1  +1.000  +1.000  +2.000  +2.000     0%
 2  +0.500  +0.500  +1.000  +1.000     0%
 3  +0.200  +0.200  +0.400  +0.400     0%
 4  -1.000  +1.100  +0.100  +0.100     0%
 5  -0.500  +0.600  +0.100  +0.100     0%
 6  -0.200  +0.300  +0.100  +0.100     0%
 7  +1.000  -1.100  -0.100  -0.100     0%
 8  +0.500  -0.600  -0.100  -0.100     0%
 9  +0.200  -0.300  -0.100  -0.100     0%
10  -1.000  -1.000  -2.000  -2.000     0%
11  -0.500  -0.500  -1.000  -1.000     0%
12  -0.200  -0.200  -0.400  -0.400     0%

Příště si mj. ukážeme, že je navíc možné drasticky snížit počet neuronů na prostřední vrstvě, a to bez vlivu na výsledky.

19. Úplný zdrojový kód dnešního druhého demonstračního příkladu

Podobně jako jsme si ukázali úplný kód prvního příkladu s neuronovou sítí pro odhad funkce xor, si ukážeme i druhý příklad, v němž je síť natrénována pro součet dvou čísel, ovšem v dosti omezeném rozsahu. Tento příklad v případě zájmu opět naleznete na GitHubu, konkrétně na adrese https://github.com/tisnik/torch-examples/blob/master/nn/04_adder.lua:

require("nn")
 
TRAINING_DATA_SIZE = 500
 
INPUT_NEURONS = 2
HIDDEN_NEURONS = 2
OUTPUT_NEURONS = 1
 
MAX_ITERATION = 200
LEARNING_RATE = 0.01
 
 
function prepare_training_data(training_data_size)
    local training_data = {}
    function training_data:size() return training_data_size end
    for i = 1,training_data_size do
        local input = torch.randn(2)
        local output = torch.Tensor(1)
        output[1] = input[1] + input[2]
        training_data[i] = {input, output}
    end
    return training_data
end
 
 
function construct_neural_network(input_neurons, hidden_neurons, output_neurons)
    local network = nn.Sequential()
 
    network:add(nn.Linear(input_neurons, hidden_neurons))
    -- výběr nelineární funkce
    network:add(nn.ReLU())
    --network:add(nn.Tanh())
    network:add(nn.Linear(hidden_neurons, output_neurons))
 
    return network
end
 
 
function train_neural_network(network, training_data, learning_rate, max_iteration)
    local criterion = nn.MSECriterion()
    local trainer = nn.StochasticGradient(network, criterion)
    trainer.learningRate = learning_rate
    trainer.maxIteration = max_iteration
    trainer:train(training_data)
end
 
 
function validate_neural_network(network, validation_data)
    for i,d in ipairs(validation_data) do
        d1, d2 = d[1], d[2]
        input = torch.Tensor({d1, d2})
        prediction = network:forward(input)[1]
        correct = d1 + d2
        err = math.abs(100.0 * (prediction-correct)/correct)
        msg = string.format("%2d  %+6.3f  %+6.3f  %+6.3f  %+6.3f  %4.0f%%", i, d1, d2, correct, prediction, err)
        print(msg)
    end
end
 
 
network = construct_neural_network(INPUT_NEURONS, HIDDEN_NEURONS, OUTPUT_NEURONS)
training_data = prepare_training_data(TRAINING_DATA_SIZE)
train_neural_network(network, training_data, LEARNING_RATE, MAX_ITERATION)
print(network)
 
 
x=torch.Tensor({0.5, -0.5})
prediction = network:forward(x)
print(prediction)
 
validation_data = {
    { 1.0,  1,0},
    { 0.5,  0.5},
    { 0.2,  0.2},
    -------------
    {-1.0,  1.1},
    {-0.5,  0.6},
    {-0.2,  0.3},
    -------------
    { 1.0, -1.1},
    { 0.5, -0.6},
    { 0.2, -0.3},
    -------------
    {-1.0, -1,0},
    {-0.5, -0.5},
    {-0.2, -0.2},
}
 
validate_neural_network(network, validation_data)

20. Odkazy na Internetu

  1. Stránka projektu Torch
    http://torch.ch/
  2. Torch: Serialization
    https://github.com/torch/tor­ch7/blob/master/doc/seria­lization.md
  3. Torch: modul image
    https://github.com/torch/i­mage/blob/master/README.md
  4. Torch na GitHubu (několik repositářů)
    https://github.com/torch
  5. Torch (machine learning), Wikipedia
    https://en.wikipedia.org/wi­ki/Torch_%28machine_learnin­g%29
  6. Torch Package Reference Manual
    https://github.com/torch/tor­ch7/blob/master/README.md
  7. Torch Cheatsheet
    https://github.com/torch/tor­ch7/wiki/Cheatsheet
  8. Neural network containres (Torch)
    https://github.com/torch/nn/blob/mas­ter/doc/containers.md
  9. Simple layers
    https://github.com/torch/nn/blob/mas­ter/doc/simple.md#nn.Line­ar
  10. Transfer Function Layers
    https://github.com/torch/nn/blob/mas­ter/doc/transfer.md#nn.tran­sfer.dok
  11. Feedforward neural network
    https://en.wikipedia.org/wi­ki/Feedforward_neural_net­work
  12. Biologické algoritmy (4) – Neuronové sítě
    https://www.root.cz/clanky/biologicke-algoritmy-4-neuronove-site/
  13. Biologické algoritmy (5) – Neuronové sítě
    https://www.root.cz/clanky/biologicke-algoritmy-5-neuronove-site/
  14. Umělá neuronová síť (Wikipedia)
    https://cs.wikipedia.org/wi­ki/Um%C4%9Bl%C3%A1_neuronov%C3%A1_s%C3%AD%C5%A5
  15. Učení s učitelem (Wikipedia)
    https://cs.wikipedia.org/wi­ki/U%C4%8Den%C3%AD_s_u%C4%8Di­telem
  16. Plotting with Torch7
    http://www.lighting-torch.com/2015/08/24/plotting-with-torch7/
  17. Plotting Package Manual with Gnuplot
    https://github.com/torch/gnu­plot/blob/master/README.md
  18. An Introduction to Tensors
    https://math.stackexchange­.com/questions/10282/an-introduction-to-tensors
  19. Gaussian filter
    https://en.wikipedia.org/wi­ki/Gaussian_filter
  20. Gaussian function
    https://en.wikipedia.org/wi­ki/Gaussian_function
  21. Laplacian/Laplacian of Gaussian
    http://homepages.inf.ed.ac­.uk/rbf/HIPR2/log.htm
  22. Odstranění šumu
    https://cs.wikipedia.org/wi­ki/Odstran%C4%9Bn%C3%AD_%C5%A­1umu
  23. Binary image
    https://en.wikipedia.org/wi­ki/Binary_image
  24. Erosion (morphology)
    https://en.wikipedia.org/wi­ki/Erosion_%28morphology%29
  25. Dilation (morphology)
    https://en.wikipedia.org/wi­ki/Dilation_%28morphology%29
  26. Mathematical morphology
    https://en.wikipedia.org/wi­ki/Mathematical_morphology
  27. Cvičení 10 – Morfologické operace
    http://midas.uamt.feec.vut­br.cz/ZVS/Exercise10/conten­t_cz.php
  28. Differences between a matrix and a tensor
    https://math.stackexchange­.com/questions/412423/dif­ferences-between-a-matrix-and-a-tensor
  29. Qualitatively, what is the difference between a matrix and a tensor?
    https://math.stackexchange­.com/questions/1444412/qu­alitatively-what-is-the-difference-between-a-matrix-and-a-tensor?
  30. BLAS (Basic Linear Algebra Subprograms)
    http://www.netlib.org/blas/
  31. Basic Linear Algebra Subprograms (Wikipedia)
    https://en.wikipedia.org/wi­ki/Basic_Linear_Algebra_Sub­programs
  32. Comparison of deep learning software
    https://en.wikipedia.org/wi­ki/Comparison_of_deep_lear­ning_software
  33. TensorFlow
    https://www.tensorflow.org/
  34. Caffe2 (A New Lightweight, Modular, and Scalable Deep Learning Framework)
    https://caffe2.ai/
  35. PyTorch
    http://pytorch.org/
  36. Seriál o programovacím jazyku Lua
    http://www.root.cz/serialy/pro­gramovaci-jazyk-lua/
  37. LuaJIT – Just in Time překladač pro programovací jazyk Lua
    http://www.root.cz/clanky/luajit-just-in-time-prekladac-pro-programovaci-jazyk-lua/
  38. LuaJIT – Just in Time překladač pro programovací jazyk Lua (2)
    http://www.root.cz/clanky/luajit-just-in-time-prekladac-pro-programovaci-jazyk-lua-2/
  39. LuaJIT – Just in Time překladač pro programovací jazyk Lua (3)
    http://www.root.cz/clanky/luajit-just-in-time-prekladac-pro-programovaci-jazyk-lua-3/
  40. LuaJIT – Just in Time překladač pro programovací jazyk Lua (4)
    http://www.root.cz/clanky/luajit-just-in-time-prekladac-pro-programovaci-jazyk-lua-4/
  41. LuaJIT – Just in Time překladač pro programovací jazyk Lua (5 – tabulky a pole)
    http://www.root.cz/clanky/luajit-just-in-time-prekladac-pro-programovaci-jazyk-lua-5-tabulky-a-pole/
  42. LuaJIT – Just in Time překladač pro programovací jazyk Lua (6 – překlad programových smyček do mezijazyka LuaJITu)
    http://www.root.cz/clanky/luajit-just-in-time-prekladac-pro-programovaci-jazyk-lua-6-preklad-programovych-smycek-do-mezijazyka-luajitu/
  43. LuaJIT – Just in Time překladač pro programovací jazyk Lua (7 – dokončení popisu mezijazyka LuaJITu)
    http://www.root.cz/clanky/luajit-just-in-time-prekladac-pro-programovaci-jazyk-lua-7-dokonceni-popisu-mezijazyka-luajitu/
  44. LuaJIT – Just in Time překladač pro programovací jazyk Lua (8 – základní vlastnosti trasovacího JITu)
    http://www.root.cz/clanky/luajit-just-in-time-prekladac-pro-programovaci-jazyk-lua-8-zakladni-vlastnosti-trasovaciho-jitu/
  45. LuaJIT – Just in Time překladač pro programovací jazyk Lua (9 – další vlastnosti trasovacího JITu)
    http://www.root.cz/clanky/luajit-just-in-time-prekladac-pro-programovaci-jazyk-lua-9-dalsi-vlastnosti-trasovaciho-jitu/
  46. LuaJIT – Just in Time překladač pro programovací jazyk Lua (10 – JIT překlad do nativního kódu)
    http://www.root.cz/clanky/luajit-just-in-time-prekladac-pro-programovaci-jazyk-lua-10-jit-preklad-do-nativniho-kodu/
  47. LuaJIT – Just in Time překladač pro programovací jazyk Lua (11 – JIT překlad do nativního kódu procesorů s architekturami x86 a ARM)
    http://www.root.cz/clanky/luajit-just-in-time-prekladac-pro-programovaci-jazyk-lua-11-jit-preklad-do-nativniho-kodu-procesoru-s-architekturami-x86-a-arm/
  48. LuaJIT – Just in Time překladač pro programovací jazyk Lua (12 – překlad operací s reálnými čísly)
    http://www.root.cz/clanky/luajit-just-in-time-prekladac-pro-programovaci-jazyk-lua-12-preklad-operaci-s-realnymi-cisly/
  49. Lua Profiler (GitHub)
    https://github.com/luafor­ge/luaprofiler
  50. Lua Profiler (LuaForge)
    http://luaforge.net/projec­ts/luaprofiler/
  51. ctrace
    http://webserver2.tecgraf.puc-rio.br/~lhf/ftp/lua/
  52. The Lua VM, on the Web
    https://kripken.github.io/lu­a.vm.js/lua.vm.js.html
  53. Lua.vm.js REPL
    https://kripken.github.io/lu­a.vm.js/repl.html
  54. lua2js
    https://www.npmjs.com/package/lua2js
  55. lua2js na GitHubu
    https://github.com/basicer/lua2js-dist
  56. Lua (programming language)
    http://en.wikipedia.org/wi­ki/Lua_(programming_langu­age)
  57. LuaJIT 2.0 SSA IRhttp://wiki.luajit.org/SSA-IR-2.0
  58. The LuaJIT Project
    http://luajit.org/index.html
  59. LuaJIT FAQ
    http://luajit.org/faq.html
  60. LuaJIT Performance Comparison
    http://luajit.org/performance.html
  61. LuaJIT 2.0 intellectual property disclosure and research opportunities
    http://article.gmane.org/gma­ne.comp.lang.lua.general/58908
  62. LuaJIT Wiki
    http://wiki.luajit.org/Home
  63. LuaJIT 2.0 Bytecode Instructions
    http://wiki.luajit.org/Bytecode-2.0
  64. Programming in Lua (first edition)
    http://www.lua.org/pil/contents.html
  65. Lua 5.2 sources
    http://www.lua.org/source/5.2/
  66. REPL
    https://en.wikipedia.org/wi­ki/Read%E2%80%93eval%E2%80%93prin­t_loop
  67. The LLVM Compiler Infrastructure
    http://llvm.org/ProjectsWithLLVM/
  68. clang: a C language family frontend for LLVM
    http://clang.llvm.org/
  69. LLVM Backend („Fastcomp“)
    http://kripken.github.io/emscripten-site/docs/building_from_source/LLVM-Backend.html#llvm-backend
  70. Lambda the Ultimate: Coroutines in Lua,
    http://lambda-the-ultimate.org/node/438
  71. Coroutines Tutorial,
    http://lua-users.org/wiki/CoroutinesTutorial
  72. Lua Coroutines Versus Python Generators,
    http://lua-users.org/wiki/LuaCorouti­nesVersusPythonGenerators
Našli jste v článku chybu?