Obsah
1. Framework Torch: využití konvolučních sítí pro rozpoznávání a klasifikaci obrázků
2. Vrstvy konvoluční neuronové sítě
5. „Obyčejné“ vrstvy pro klasifikaci dat
6. Praktická ukázka konvoluční neuronové sítě
7. Výpočet počtu vstupů do „obyčejné“ vrstvy pro klasifikaci dat
8. Funkce pro vytvoření a uložení trénovacích i validačních rastrových obrázků
9. Posun rastrových dat (translate)
11. Aplikace roztřesení (jitter)
14. Funkce pro načtení trénovacích obrázků a vytvoření trénovacích dat pro síť
15. Funkce pro vlastní tréning sítě
17. Validace sítě a výpočet chybných úsudků
18. Úplný zdrojový kód příkladu s konvoluční neuronovou sítí
19. Repositář s demonstračními příklady
1. Framework Torch: využití konvolučních sítí pro rozpoznávání a klasifikaci obrázků
V předchozím článku jsme si vyzkoušeli použití neuronové sítě pro rozpoznávání číslic 0 až 9 v obrázcích s malým rozlišením. Mj. jsme zjistili, že běžné neuronové sítě založené na vrstvách „obyčejných“ neuronů nemusí být pro rozpoznávání a klasifikaci obrázků tím nejlepším řešením. Aby totiž bylo možné pracovat s posunutými, otočenými, zkosenými atd. vstupními obrázky, bylo by nutné síť udělat velmi hlubokou (mnoho vrstev, mnohdy dosahující 20 až 50 vrstev) a i počet neuronů by musel řádově odpovídat počtu pixelů. To je sice skutečně možné zařídit (ostatně zaplatíme za to „jen“ strojovým časem), ovšem stále zde narážíme na principiální omezení klasických vrstvených neuronových sítí – jednotlivé neurony se učí izolovaně od ostatních neuronů, zatímco na vstupu máme „plovoucí“ obrázek. Bylo by tedy výhodnější se zaměřit na vylepšení samotné architektury neuronové sítě specializované právě na to, že na vstupu bude mít bitmapy a tudíž by sousední neurony měly nějakým způsobem sdílet své váhy na vstupech. Taková architektura již ve skutečnosti byla dávno vymyšlena a jmenuje se konvoluční neuronová síť.
2. Vrstvy konvoluční neuronové sítě
Konvoluční neuronová síť používá minimálně dvě speciální vrstvy neuronů. Jedná se o konvoluční vrstvy a o takzvané subsamplingové vrstvy (někdy se setkáme s označením pooling layers). Úkolem těchto vrstev je získat z obrázku sadu koeficientů, které se posléze předají do několika klasických vrstev neuronů. Počet koeficientů vstupujících do posledních vrstev se může pohybovat od několika desítek až po stovky tisíc, což je částečně závislé na rozlišení vstupních obrázků (alespoň v síti, kterou si popíšeme dnes).
Typicky se vrstvy střídají takto:
- Vstupní vrstva
- Konvoluční vrstva #1
- Subsamplingová vrstva #1
- Konvoluční vrstva #2
- Subsamplingová vrstva #2
- …
- …
- Klasická skrytá vrstva
- Výstupní vrstva
3. Konvoluční vrstva
Úkolem konvoluční vrstvy je získání lokálních informací o rastrovém obrázku. Někdy se mohou používat i skutečně malé oblasti o velikosti 3×3 pixely, 5×5 pixelů atd. Celá tato oblast tvoří vstup do jednoho neuronu, což znamená 9 vstupů při volbě oblasti 3×3 pixely, 25 vstupů pro oblast o velikosti 5×5 pixelů apod. K těmto vstupům samozřejmě musíme připočítat i práh, jehož významem jsme se již poměrně dopodrobna zabývali. Jednotlivé oblasti se překrývají, takže pro obrázek o rozměrech x×y a pro oblasti o velikosti kx×ky budeme mít celkem (x-kx+1)×(y-ky+1) oblastí a tudíž stejný počet neuronů v konvoluční vrstvě (pro větší obrázky a menší jádra je tedy počet neuronů prakticky totožný s počtem pixelů).
Z konvolučních vrstev požadujeme získat n rovin, kde n by mělo zhruba odpovídat míře informací, které v obrázku hledáme. Když si uvědomíme, že n může být v řádu desítek až stovek a ve vrstvě bude (x-kx+1)×(y-ky+1) neuronů, zjistíme, že se jedná o obrovské číslo (násobené počtem vah). Ovšem v konvolučních vrstvách se váhy mezi neurony sdílí (včetně prahové hodnoty). To například znamená, že při volbě oblasti 5×5 pixelů bude nutné si zapamatovat pouze 5×5+1=26 vah (krát počet rovin).
4. Subsamplingová vrstva
Subsamplingové (též poolovací) vrstvy na svém vstupu akceptují výstup z předchozí konvoluční vrstvy. Tyto vrstvy taktéž používají neurony napojené na podoblasti vstupních bitmap (těch je již n), ovšem jednotlivé oblasti se v tomto případě nepřekrývají. Pokud je tedy oblast větší než 1×1 pixel, dochází ke zmenšování počtu výstupů. Příkladem může být subsamplingová vrstva s podoblastmi 2×2 pixely. Tato vrstva efektivně zmenšuje horizontální i vertikální rozlišení na polovinu. Navíc tyto vrstvy provádí jen jednoduché výpočty, například výběr pixelu s maximální hodnotou atd. Tím, že se vrstvy do sítě vkládají v pořadí konvoluční→subsamplingová→konvoluční→subsamplingová, dochází k postupnému zmenšování množství dat, takže vstupy do poslední části sítě jsou vhodným způsobem omezeny.
5. „Obyčejné“ vrstvy pro klasifikaci dat
Na konci neuronové sítě se nachází obyčejné vrstvy neuronů, což znamená, že celou síť můžeme vlastně považovat za dvě sítě – první síť zpracovává rastrové obrázky a generuje jednorozměrný (!) tenzor koeficientů, druhá síť se pak snaží z těchto koeficientů odhadnout, jaké objekty vlastně byly na obrázku nalezeny. Ovšem nemusí být zcela jisté, co který koeficient znamená; to je již záležitost tréningu sítě (existují možnosti, jak graficky zobrazit význam koeficientů; ale to je již problematika, kterou se budeme zabývat v dalších dílech). V naší demonstrační síti použijeme jedinou „obyčejnou“ vrstvu neuronů, ovšem velmi často se setkáme i s větším množstvím vrstev (řekněme 2–3).
6. Praktická ukázka konvoluční neuronové sítě
Ukažme si nyní, jak se vytvoří prakticky použitelná konvoluční neuronová síť. Použijeme několik vrstev, konkrétně:
- Konvoluční vrstva #1
- Subsamplingová vrstva #1
- Konvoluční vrstva #2
- Subsamplingová vrstva #2
- Mezikrok s převodem 3D tenzorů na 1D tenzor
- Klasická skrytá vrstva
- Výstupní vrstva
Parametry sítě budou reprezentovány konstantami, s nimiž je samozřejmě možné manipulovat:
-- parametry neuronove site INPUT_PLANES = 1 MIDDLE_PLANES = {64, 64} HIDDEN_NEURONS = 100 OUTPUT_NEURONS = 10 -- parametry konvolucni vrstvy CONVOLUTION_KERNEL_SIZE = 5 -- parametry pooling vrstvy POOLING_SIZE = 2 POOLING_STEP = 2
Vlastní kód, který vytvoří novou konvoluční neuronovou síť, sice vypadá poměrně složitě, ale jednotlivé vrstvy a jejich význam je popsán v komentářích:
function construct_neural_network() local network = nn.Sequential()
Vytvoříme první čtyři vrstvy v pořadí: konvoluční, subsamplingová, konvoluční a konečně subsamplingová:
-- prvni konvolucni vrstva ocekavajici na vstupu 3D tenzor -- o velikosti: -- INPUT_PLANES x vyska x sirka -- -- vysledkem je 3D tenzor o velikosti: -- MIDDLE_PLANES_1 x (vyska - CONVOLUTION_KERNEL_SIZE + 1) x (sirka - CONVOLUTION_KERNEL_SIZE + 1) network:add(nn.SpatialConvolution(INPUT_PLANES, MIDDLE_PLANES[1], CONVOLUTION_KERNEL_SIZE, CONVOLUTION_KERNEL_SIZE)) -- nyni mame mezivysledky 64 x (32-5+1) x (32-5+1) = 64 x 28 x 28 -- nelinearni funkce network:add(nn.Tanh()) -- hledani maxima v regionech o velikosti 2x2 pixely -- s krokem nastavenym na 2 pixely v horizontalnim i 2 pixely ve vertikalnim smeru network:add(nn.SpatialMaxPooling(POOLING_SIZE, POOLING_SIZE, POOLING_STEP, POOLING_STEP)) -- nyni mame mezivysledky 64 x 28/2 x 28/2 = 64 x 14 x 14 -- druha konvolucni vrstva ocekavajici na vstupu 3D tenzor -- o velikosti MIDDLE_PLANES_1 x vyska x sirka network:add(nn.SpatialConvolution(MIDDLE_PLANES[1], MIDDLE_PLANES[2], CONVOLUTION_KERNEL_SIZE, CONVOLUTION_KERNEL_SIZE)) -- nyni mame mezivysledky 64 x (14-5+1) x (14-5+1) = 64 x 10 x 10 -- nelinearni funkce network:add(nn.Tanh()) -- opetovne hledani maxima v regionech o velikosti 2x2 pixely -- s krokem nastavenym na 2 pixely v horizontalnim i 2 pixely ve vertikalnim smeru network:add(nn.SpatialMaxPooling(POOLING_SIZE, POOLING_SIZE, POOLING_STEP, POOLING_STEP)) -- nyni mame mezivysledky 64 x 10/2 x 10/2 = 64 x 5 x 5
Nyní nastává důležitý mezikrok – konverze 3D tenzoru na 1D tenzor. To je možné provést více způsoby, ale typicky se používá reshape nebo view:
-- nyni mame mezivysledek o velikosti MIDDLE_PLANES_2 x 5 x 5 -- zmena tvaru: z 3D tenzoru AxBxC na 1D tenzor s A*B*C elementy network:add(nn.View(MIDDLE_PLANES[2]*5*5))
Výsledkem předchozího bloku je 1D tenzor, takže můžeme do sítě vložit zcela obyčejnou vrstvu neuronů a na závěr vrstvu výstupní:
-- bezne vrstvy, jak je jiz zname network:add(nn.Linear(MIDDLE_PLANES[2]*5*5, HIDDEN_NEURONS)) -- pridana nelinearni funkce network:add(nn.ReLU()) -- bezne vrstvy, jak je jiz zname network:add(nn.Linear(HIDDEN_NEURONS, OUTPUT_NEURONS)) return network end
Nyní nám již stačí síť zkonstruovat a vypsat její strukturu:
network = construct_neural_network() print(network)
Na standardní výstup by se měla vytisknout struktura právě zkonstruované neuronové sítě, která vypadá následovně:
nn.Sequential { [input -> (1) -> (2) -> (3) -> (4) -> (5) -> (6) -> (7) -> (8) -> (9) -> (10) -> output] (1): nn.SpatialConvolution(1 -> 64, 5x5) (2): nn.Tanh (3): nn.SpatialMaxPooling(2x2, 2,2) (4): nn.SpatialConvolution(64 -> 64, 5x5) (5): nn.Tanh (6): nn.SpatialMaxPooling(2x2, 2,2) (7): nn.View(1600) (8): nn.Linear(1600 -> 100) (9): nn.ReLU (10): nn.Linear(100 -> 10) }
7. Výpočet počtu vstupů do „obyčejné“ vrstvy pro klasifikaci dat
V předchozím kódu jsme použili magickou hodnotu MIDDLE_PLANES[2]*5*5, která měla v našem případě výsledek 64×5×5 = 1600. Odkud se ale tato konstanta vzala? Hodnota 64 je jasná – jedná se o počet kvantifikovaných atributů z předchozí vrstvy. Ovšem obě hodnoty 5 a 5 již mají mnohem zajímavější způsob vzniku. Musíme si totiž uvědomit, jak se původní rastrové obrázky o rozlišení 32×32 pixelů postupně zpracovávají v jednotlivých vrstvách:
Vrstva | Vstup | Výstup |
---|---|---|
SpatialConvolution | 32×32 | 64 × (32–5+1) × (32–5+1) = 64 × 28 × 28 |
SpatialMaxPooling | 64 ×28 × 28 | 64 × 28/2 × 28/2 = 64 × 14 × 14 |
SpatialConvolution | 64 × 14 × 14 | 64 × (14–5+1) × (14–5+1) = 64 × 10 × 10 |
SpatialMaxPooling | 64 × 10 × 10 | 64 × 10/2 × 10/2 = 64 × 5 × 5 = 1600 |
Konvoluční vrstva totiž skutečně má na vstupu jednotlivých neuronů hodnotu pixelu a jeho okolí, tj. například pro velikost konvolučního jádra 5×5 se jedná o stejně velké okolí. A vzhledem k tomu, že pixely na okraji bitmapy nemají z jedné nebo dvou stran žádné rozumně definované okolí, je na výstupu bitmapa zmenšena o dva pixely na každé straně, tedy celkově o hodnotu –5+1:
U subsamplingové vrstvy se defacto zmenšuje rozlišení na polovinu, takže například pro vstupních 64 bitmap o rozlišení 28×28 pixelů dostaneme 64 jiných bitmap o rozlišení 14×14 pixelů.
Stejný postup se aplikuje i pro další dvě vrstvy. Výpočet velikosti tedy můžeme provést i programově, například následujícím způsobem:
-- parametry neuronove site INPUT_PLANES = 1 MIDDLE_PLANES = {64, 64} -- parametry konvolucni vrstvy CONVOLUTION_KERNEL_SIZE = 5 -- parametry pooling vrstvy POOLING_SIZE = 2 POOLING_STEP = 2 function calculate_size_after_convolution(input_size, middle_planes, convolution_kernel_size) local size = input_size for i=1,#middle_planes do -- velikost po projiti konvolucni vrstvou size = size - convolution_kernel_size + 1 -- velikost po projiti pooling vrstvou size = size / 2 end return size end print(calculate_size_after_convolution(32, MIDDLE_PLANES, CONVOLUTION_KERNEL_SIZE)) print(calculate_size_after_convolution(64, MIDDLE_PLANES, CONVOLUTION_KERNEL_SIZE)) print(calculate_size_after_convolution(128, MIDDLE_PLANES, CONVOLUTION_KERNEL_SIZE)) print(calculate_size_after_convolution(256, MIDDLE_PLANES, CONVOLUTION_KERNEL_SIZE))
Výsledek výpočtů:
Vstupní bitmapa | Bitmapy na výstupu z poslední subsamplingové vrstvy |
---|---|
32×32 | 5×5 |
64×64 | 13×13 |
128×128 | 29×29 |
256×256 | 61×61 |
Náš program pro konstrukci neuronové sítě tedy můžeme upravit tak, aby nebyl přímo závislý na rozlišení vstupních bitmap:
require("nn") WIDTH = 200 HEIGHT = 200 -- parametry neuronove site INPUT_PLANES = 1 MIDDLE_PLANES = {64, 64} HIDDEN_NEURONS = 100 OUTPUT_NEURONS = 10 -- parametry konvolucni vrstvy CONVOLUTION_KERNEL_SIZE = 5 -- parametry pooling vrstvy POOLING_SIZE = 2 POOLING_STEP = 2 function calculate_size_after_convolution(input_size, middle_planes, convolution_kernel_size) local size = input_size for i=1,#middle_planes do -- velikost po projiti konvolucni vrstvou size = size - convolution_kernel_size + 1 -- velikost po projiti pooling vrstvou size = size / 2 end return size end function construct_neural_network() local network = nn.Sequential() local size_x = calculate_size_after_convolution(WIDTH, MIDDLE_PLANES, CONVOLUTION_KERNEL_SIZE) local size_y = calculate_size_after_convolution(HEIGHT, MIDDLE_PLANES, CONVOLUTION_KERNEL_SIZE) print("Size x: " .. size_x) print("Size y: " .. size_y) -- prvni konvolucni vrstva ocekavajici na vstupu 3D tenzor -- o velikosti: -- INPUT_PLANES x vyska x sirka -- -- vysledkem je 3D tenzor o velikosti: -- MIDDLE_PLANES_1 x (vyska - CONVOLUTION_KERNEL_SIZE + 1) x (sirka - CONVOLUTION_KERNEL_SIZE + 1) network:add(nn.SpatialConvolution(INPUT_PLANES, MIDDLE_PLANES[1], CONVOLUTION_KERNEL_SIZE, CONVOLUTION_KERNEL_SIZE)) -- nyni mame mezivysledky 64 x (vyska-5+1) x (sirka-5+1) -- nelinearni funkce network:add(nn.Tanh()) -- hledani maxima v regionech o velikosti 2x2 pixely -- s krokem nastavenym na 2 pixely v horizontalnim i 2 pixely ve vertikalnim smeru network:add(nn.SpatialMaxPooling(POOLING_SIZE, POOLING_SIZE, POOLING_STEP, POOLING_STEP)) -- druha konvolucni vrstva ocekavajici na vstupu 3D tenzor -- o velikosti MIDDLE_PLANES_1 x vyska x sirka network:add(nn.SpatialConvolution(MIDDLE_PLANES[1], MIDDLE_PLANES[2], CONVOLUTION_KERNEL_SIZE, CONVOLUTION_KERNEL_SIZE)) -- nelinearni funkce network:add(nn.Tanh()) -- opetovne hledani maxima v regionech o velikosti 2x2 pixely -- s krokem nastavenym na 2 pixely v horizontalnim i 2 pixely ve vertikalnim smeru network:add(nn.SpatialMaxPooling(POOLING_SIZE, POOLING_SIZE, POOLING_STEP, POOLING_STEP)) -- zmena tvaru: z 3D tenzoru AxBxC na 1D tenzor s A*B*C elementy network:add(nn.View(MIDDLE_PLANES[2]*size_x*size_y)) -- bezne vrstvy, jak je jiz zname network:add(nn.Linear(MIDDLE_PLANES[2]*size_x*size_y, HIDDEN_NEURONS)) -- pridana nelinearni funkce network:add(nn.ReLU()) -- bezne vrstvy, jak je jiz zname network:add(nn.Linear(HIDDEN_NEURONS, OUTPUT_NEURONS)) return network end
network = construct_neural_network() print(network)
Výsledná síť pro vstupní bitmapy o rozlišení 200×200 pixelů bude vypadat takto. Hodnota 141376 vznikla výpočtem 64×47×47:
Size x: 47 Size y: 47 nn.Sequential { [input -> (1) -> (2) -> (3) -> (4) -> (5) -> (6) -> (7) -> (8) -> (9) -> (10) -> output] (1): nn.SpatialConvolution(1 -> 64, 5x5) (2): nn.Tanh (3): nn.SpatialMaxPooling(2x2, 2,2) (4): nn.SpatialConvolution(64 -> 64, 5x5) (5): nn.Tanh (6): nn.SpatialMaxPooling(2x2, 2,2) (7): nn.View(141376) (8): nn.Linear(141376 -> 100) (9): nn.ReLU (10): nn.Linear(100 -> 10) }
8. Funkce pro vytvoření a uložení trénovacích i validačních rastrových obrázků
Pro vytvoření testovacích a validačních rastrových obrázků použijeme funkci, s jejímž principem jsme se již seznámili minule. Funkci však rozšíříme takovým způsobem, aby mohla vytvářet zvětšené obrázky s celočíselným zvětšením. Funkce bude pro každou číslici získávat barvy pixelů (tmavý/světlý) z následující tabulky:
digits = { {0x00, 0x3C, 0x66, 0x76, 0x6E, 0x66, 0x3C, 0x00 }, {0x00, 0x18, 0x1C, 0x18, 0x18, 0x18, 0x7E, 0x00 }, {0x00, 0x3C, 0x66, 0x30, 0x18, 0x0C, 0x7E, 0x00 }, {0x00, 0x7E, 0x30, 0x18, 0x30, 0x66, 0x3C, 0x00 }, {0x00, 0x30, 0x38, 0x3C, 0x36, 0x7E, 0x30, 0x00 }, {0x00, 0x7E, 0x06, 0x3E, 0x60, 0x66, 0x3C, 0x00 }, {0x00, 0x3C, 0x06, 0x3E, 0x66, 0x66, 0x3C, 0x00 }, {0x00, 0x7E, 0x60, 0x30, 0x18, 0x0C, 0x0C, 0x00 }, {0x00, 0x3C, 0x66, 0x3C, 0x66, 0x66, 0x3C, 0x00 }, {0x00, 0x3C, 0x66, 0x7C, 0x60, 0x30, 0x1C, 0x00 }, }
Samotné vytvoření bitmapy je již poměrně přímočaré, musíme jen zajistit potřebné zvětšení (proto se používá dvojice počitadel):
function get_codes_for_digit(digit) if digit < 0 or digit > 9 then return nil end return digits[digit+1] end function setpixel(image, x, y, bit, white_value, black_value) if bit==1 then image[y][x] = white_value else image[y][x] = black_value end end function generate_image(digit, scale, black_value, white_value) local codes = get_codes_for_digit(digit) local image = {} local y_offset = 1 for _, code in ipairs(codes) do for y = 1,scale do -- vytvorit dalsi obrazovy radek image[y_offset] = {} local x_offset = 1 local byte = code -- vypocet barev jednotlivych pixelu v obrazku for _ = 1,8 do -- zjistit hodnotu n-teho bitu + posun bajtu s maskou znaku local bit = byte % 2 byte = (byte - bit)/2 for x = 1,scale do -- obarveni konkretniho pixelu setpixel(image, x_offset, y_offset, bit, white_value, black_value) x_offset = x_offset + 1 end end y_offset = y_offset + 1 end end return image end
Následuje funkce, která vytvořenou bitmapu uloží do externího souboru ve formátu PGM (Portable GrayMap). S touto funkcí jsme se již seznámili minule, dnes jen nepatrně upravíme logiku pro odřádkování a oddělení jednotlivých hodnot od sebe (není to však nutné, jen ušetříme 32 bajtů pro každý soubor):
function write_image(filename, image) local fout = io.open(filename, "w") if not fout then return end -- rozliseni obrazku local x_resolution = #image[1] local y_resolution = #image -- zapis hlavicky fout:write("P2\n") fout:write(x_resolution) fout:write(" ") fout:write(y_resolution) fout:write("\n255\n") -- zapis jednotlivych pixelu for j, row in ipairs(image) do for i, pixel in ipairs(row) do -- logika pro oddeleni hodnot if i ~= 1 then fout:write(" ") end fout:write(pixel) end -- odradkovani neni nutne fout:write("\n") end fout:close() end
Obrázek 1: Číslice 2 vygenerovaná předchozími funkcemi.
Ukázka výsledného souboru s bitmapou:
P2 32 32 255 68 58 70 66 63 56 62 67 68 72 64 66 63 56 64 56 57 77 64 57 64 66 57 72 60 64 64 59 68 63 63 68 67 62 67 60 61 72 65 61 68 63 55 63 66 61 64 64 62 63 69 66 64 67 64 72 53 61 67 59 69 66 69 60 ... ... ... 66 64 70 64 67 65 65 59 64 70 59 59 55 51 64 58 64 59 67 61 66 58 67 55 65 67 62 61 61 59 70 67 64 58 65 56 58 67 64 66 63 64 56 64 65 68 57 56 59 65 67 68 61 70 65 70 64 63 64 66 64 63 68 62
9. Posun rastrových dat (translate)
S obrázky samozřejmě můžeme různým způsobem manipulovat, čehož využijeme jak při tvorbě trénovacích dat, tak (a to především) při generování validačních obrázků. Základní operací, která manipuluje s celým rastrovým obrázkem, je operace jeho posunu o zadaný počet pixelů v horizontálním směru (doleva, doprava) i ve směru vertikálním (nahoru, dolů). Poněkud naivní implementace může vypadat následovně:
function translate(source_image, x_offset, y_offset) local target_image = {} local max_x = #source_image[1] local max_y = #source_image for y = 1,max_y do target_image[y] = {} for x = 1,max_x do xs = x - x_offset ys = y - y_offset xs = bound(xs, 1, max_x) ys = bound(ys, 1, max_y) target_image[y][x] = source_image[ys][xs] end end return target_image end
Povšimněte si, že vytváříme nový obrázek, zatímco původní obrázek zůstane nedotčen. To je u našich malých obrázků s rozlišením pouhých 32×32 pixelů nepodstatné, protože stejně nejvíce času bude systém trávit ve funkci pro tréning neuronové sítě.
Pomocná funkce bound pouze zajišťuje, aby se souřadnice pixelů nacházely v zadaném intervalu, a to za všech okolností:
function bound(value, min_value, max_value) return math.max(min_value, math.min(value, max_value)) end
Tuto pomocnou funkci použijeme i při dalších operacích.
Obrázek 2: Posunutá číslice 2 vygenerovaná předchozími funkcemi.
10. Aplikace šumu (noise)
Funkce provádějící posun rastrových dat v rámci obrázku s předem daným rozlišením byla velmi jednoduchá. Podobně snadná je implementace funkce, která do obrázku vnese šum, čímž ideální (uměle vyrendrované obrázky) alespoň v malé míře přiblíží realitě. Zatímco minule jsme pro napodobení reálného šumu používali výsledek volání funkce math.random (bez dalších úprav), dnes použijeme šum s normálním rozložením. Jedna z možných implementací generátoru náhodných čísel s náhodným rozložením může vypadat následovně. Parametry této funkce jsou střední hodnota a rozptyl:
function gaussian(mean, variance) return math.sqrt(-2 * variance * math.log(math.random())) * math.cos(2 * variance * math.pi * math.random()) + mean end
Funkci gaussian použijeme pro vnesení šumu do obrázku, přičemž opět nebudeme modifikovat původní obrázek, ale raději vytvoříme obrázek nový:
function apply_noise(source_image, variance) local target_image = {} for y, row in ipairs(source_image) do target_image[y] = {} for x, pixel in ipairs(row) do local delta = math.floor(gaussian(0, variance)) target_image[y][x] = bound(pixel + delta, 0, 255) end end return target_image end
Opět si povšimněte použití pomocné funkce bound, protože nechceme, aby se nám při zadání většího rozptylu a střední hodnoty hodnoty pixelů dostaly mezi meze 0..255.
Obrázek 3: Zašuměná číslice 2 vygenerovaná předchozími funkcemi.
11. Aplikace roztřesení (jitter)
Poslední funkcí, kterou použijeme pro modifikaci rastrových obrázků určených pro tréning a/nebo validaci neuronové sítě, je funkce provádějící roztřesení (jitter) pixelů, tj. posun pixelů náhodným směrem. Pro určení směru opět použijeme výše deklarovanou funkci gaussian:
function apply_jitter(source_image, variance) local target_image = {} local max_x = #source_image[1] local max_y = #source_image for y = 1,max_y do target_image[y] = {} for x = 1,max_x do xs = x + math.floor(gaussian(0, variance)) ys = y + math.floor(gaussian(0, variance)) xs = bound(xs, 1, max_x) ys = bound(ys, 1, max_y) target_image[y][x] = source_image[ys][xs] end end return target_image end
Poznámka: ve skutečnosti není tato funkce napsána zcela korektně, protože je závislá na směru – offset pixelů pro jittering se totiž počítá pro horizontální a vertikální směr, navíc se posléze musí provést zaokrouhlení souřadnic na celá čísla. Nicméně pro první přiblížení nám může tato funkce pomoci.
Obrázek 4: Chuťovka pro neuronovou síť – zašuměná a současně i roztřesená číslice 2 vygenerovaná předchozími funkcemi.
12. Sada trénovacích obrázků
Nyní již máme vše připravené pro vygenerování trénovacích obrázků pro naši novou konvoluční neuronovou síť. Trénovací obrázky budou pouze zašuměné, přičemž úroveň šumu (rozptyl) budeme postupně zvyšovat od nuly (nezašuměný obrázek) až po hodnotu 200. Obrázky budou jak v horizontálním, tak i vertikálním směru zvětšeny 4×, což znamená, že namísto pidibitmap o rozlišení 8×8 pixelů získáme obrázky o rozměrech ikon 32×32 pixelů:
NOISE_VARIANCES = {0, 10, 20, 50, 100, 200} REPEAT_COUNT = 3 SCALE = 4
Samotné vygenerování trénovacích obrázků je již snadné:
function generate_training_images() for _, variance in ipairs(NOISE_VARIANCES) do for digit = 0, 9 do for i = 1, REPEAT_COUNT do local image = generate_image(digit, SCALE, 64, 192) image = apply_noise(image, variance) local filename = string.format("training_%d_%d_%d.pgm", digit, variance, i) write_image(filename, image) end end end end
generate_training_images()
Výsledkem byl mělo být 180 bitmap, což sice není mnoho, ale pokud máte k dispozici více času pro tréning sítě a/nebo používáte CUDA, lze jejich počet samozřejmě zvýšit změnou parametrů NOISE_VARIANCES a REPEAT_COUNT.
Obrázek 5: příklady výstupu (zvětšeno).
13. Sada validačních obrázků
Podobným způsobem vytvoříme sadu validačních obrázků, ovšem aby to námi vytvořená konvoluční neuronová síť neměla tak jednoduché, bude na validační obrázky použita funkce pro roztřesení pixelů. Síť se tedy bude muset vyrovnat s (prozatím) jí neznámým vstupem. Vytvoření validačních obrázků je provedeno následovně:
Parametry:
NOISE_VARIANCES = {20, 50, 100, 200} JITTER_VARIANCE = {0, 1, 2} REPEAT_COUNT = 2
Vlastní vygenerování:
function generate_images_for_validation() for _, noise_variance in ipairs(NOISE_VARIANCES) do for _, jitter_variance in ipairs(JITTER_VARIANCE) do for digit = 0, 9 do for i = 1, REPEAT_COUNT do local image = generate_image(digit, SCALE, 64, 192) image = apply_noise(image, noise_variance) image = apply_jitter(image, jitter_variance) local filename = string.format("validation_%d_%d_%d_%d.pgm", digit, noise_variance, jitter_variance, i) write_image(filename, image) end end end end end
generate_images_for_validation()
Obrázek 6: příklady výstupu (zvětšeno).
14. Funkce pro načtení trénovacích obrázků a vytvoření trénovacích dat pro síť
Při načítání trénovacích obrázků nejdříve zjistíme jejich seznam na základě předané masky:
function read_filelist(mask) local command = "ls -1 " .. mask local handle = io.popen(command) local filelist = {} for line in handle:lines() do table.insert(filelist, line) end handle:close() return filelist end
Trénovací obrázky mají jména odpovídající masce training*.pgm, takže:
training_files = read_filelist("training*.pgm")
O načtení všech obrázků, zjištění správného výstupu (pro trénink sítě) a vytvoření pole trénovacích dat (dvojic tenzorů) se postará tato funkce:
function prepare_training_data(training_files) local training_data_size = #training_files local training_data = {} function training_data:size() return training_data_size end for i, training_file in ipairs(training_files) do local input = image.load(training_file, 1, "byte"):double() local digit = tonumber(string.match(training_file, "%d+")) local output = generate_expected_output(digit) training_data[i] = {input, output} end return training_data end
Poznámka: správná číslice je zjištěna z prvního čísla nalezeného ve jméně souboru s obrázkem.
Pomocná funkce pro vytvoření tenzoru s očekávaným výstupem:
function generate_expected_output(digit) local result = torch.zeros(DIGITS) result[digit+1] = 1 return result end
15. Funkce pro vlastní tréning sítě
Tréning sítě je postaven na funkci nazvané train_neural_network, která se od minulých příkladů nezměnila. Jen se snížil počet iterací na 200, což se zdá být dostačující, pokud ovšem budete chtít provádět tréning s využitím CUDA, můžete tuto hodnotu zvýšit:
-- parametry pro uceni neuronove site MAX_ITERATION = 200 LEARNING_RATE = 0.01
Samotná funkce provádějící trénink vypadá následovně:
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
16. Průběh tréningu
Po spuštění tréninku se začnou vypisovat informace o jednotlivých chybách, na které síť reaguje změnou vah neuronů. Můžeme vidět, že chyba postupně klesá a to dosti výrazně (síť se dobře učí):
# StochasticGradient: training # current error = 0.085056116904068 # current error = 0.058735151391114 # current error = 0.040517938210007 # current error = 0.028050846785708 # current error = 0.019813127132151 ... ... ... # current error = 5.1229304386063e-06 # current error = 5.0627943767942e-06 # current error = 5.0088192479628e-06 # current error = 4.9373411963832e-06 # current error = 4.886157527272e-06 # StochasticGradient: you have reached the maximum number of iterations # training error = 4.886157527272e-06
Výsledná chyba má dosti optimistickou hodnotu 4,8×10-6, ovšem samozřejmě ještě musíme provést validaci.
17. Validace sítě a výpočet chybných úsudků
Pro validaci sítě si vytvoříme další funkci nazvanou validate_neural_network. Této funkci se předá seznam validačních obrázků a samozřejmě i vlastní síť. Obrázky se postupně posílají na vstup sítě, z jejího výstupu se zjistí tenzor s deseti prvky, v něm se najde největší prvek a jeho index se prohlásí za nalezenou číslici. Navíc si ještě spočítáme jednoduchou statistiku o míře chybných odhadů:
function validate_neural_network(network, filelist) local errors = 0 for i, filename in ipairs(filelist) do local input = image.load(filename, 1, "byte"):double() local expected_digit = tonumber(string.match(filename, "%d+")) local output = network:forward(input) local result, weight = find_largest_item(output) if expected_digit ~= result then errors = errors + 1 end print(expected_digit, result, expected_digit==result, weight, filename) end print("---------------------") print("Errors: " .. errors) print("Error rate: " .. 100.0*errors/#filelist .. "%") end
Pomocná funkce pro nalezení indexu prvku tenzoru s největší hodnotou:
function find_largest_item(tensor) local index = -1 local value = -math.huge for i = 0, 9 do if tensor[i+1] > value then index = i value = tensor[i+1] end end return index, value end
Vše již máme připravené, takže můžeme spustit validaci:
validation_files = read_filelist("validation*.pgm") validate_neural_network(network, validation_files)
Výsledky nejsou vůbec špatné, zvláště když si uvědomíme, že síť vůbec nebyla natrénována na rozpoznávání číslic v obrázcích, na které se aplikoval jittering:
0 0 true 0.95748445630588 validation_0_100_0_1.pgm 0 0 true 0.92065186393107 validation_0_100_0_2.pgm 0 0 true 0.45594509522907 validation_0_100_1_1.pgm 0 0 true 0.51737246567833 validation_0_100_1_2.pgm 0 0 true 0.31705108359018 validation_0_100_2_1.pgm 0 0 true 0.39825404762665 validation_0_100_2_2.pgm ... ... ... 8 8 true 0.4499938604602 validation_8_200_1_2.pgm 8 8 true 0.98181280205716 validation_8_20_0_1.pgm 8 7 false 0.20192176973614 validation_8_200_2_1.pgm 8 8 true 0.2397209058962 validation_8_200_2_2.pgm 8 8 true 0.98915324380974 validation_8_20_0_2.pgm 8 8 true 0.42960465297511 validation_8_20_1_1.pgm 8 8 true 0.33564594476512 validation_8_20_1_2.pgm 8 8 true 0.2004579255463 validation_8_20_2_1.pgm 8 8 true 0.1496889366345 validation_8_20_2_2.pgm 8 8 true 0.98902163781293 validation_8_50_0_1.pgm 8 8 true 0.96138545388153 validation_8_50_0_2.pgm 8 8 true 0.35222736158076 validation_8_50_1_1.pgm 8 8 true 0.31740552859615 validation_8_50_1_2.pgm 8 8 true 0.26109952804475 validation_8_50_2_1.pgm 8 3 false 0.18653662825211 validation_8_50_2_2.pgm --------------------- Errors: 3 Error rate: 1.25%
Seznam chyb (záměn) je pochopitelný, když se podíváme na tvar číslic:
8 → 3 8 → 7 8 → 3
18. Úplný zdrojový kód příkladu s konvoluční neuronovou sítí
Pod tímto odstavcem je vypsán úplný zdrojový kód příkladu, v němž je vytvořena konvoluční neuronová síť, tato síť je následně natrénována a posléze je provedena její validace. Všechny podstatné parametry sítě naleznete na začátku zdrojového kódu a můžete s nimi manipulovat pro dosažení lepších (nebo samozřejmě i horších) výsledků:
require("nn") require("image") -- globalni nastaveni DIGITS = 10 WIDTH = 32 HEIGHT = 32 -- parametry neuronove site INPUT_PLANES = 1 MIDDLE_PLANES = {64, 64} HIDDEN_NEURONS = 100 OUTPUT_NEURONS = 10 -- parametry konvolucni vrstvy CONVOLUTION_KERNEL_SIZE = 5 -- parametry pooling vrstvy POOLING_SIZE = 2 POOLING_STEP = 2 -- parametry pro uceni neuronove site MAX_ITERATION = 200 LEARNING_RATE = 0.01 function calculate_size_after_convolution(input_size, middle_planes, convolution_kernel_size) local size = input_size for i=1,#middle_planes do -- velikost po projiti konvolucni vrstvou size = size - convolution_kernel_size + 1 -- velikost po projiti pooling vrstvou size = size / 2 end return size end function construct_neural_network() local network = nn.Sequential() local size_x = calculate_size_after_convolution(WIDTH, MIDDLE_PLANES, CONVOLUTION_KERNEL_SIZE) local size_y = calculate_size_after_convolution(HEIGHT, MIDDLE_PLANES, CONVOLUTION_KERNEL_SIZE) print("Size x: " .. size_x) print("Size y: " .. size_y) -- prvni konvolucni vrstva ocekavajici na vstupu 3D tenzor -- o velikosti: -- INPUT_PLANES x vyska x sirka -- -- vysledkem je 3D tenzor o velikosti: -- MIDDLE_PLANES_1 x (vyska - CONVOLUTION_KERNEL_SIZE + 1) x (sirka - CONVOLUTION_KERNEL_SIZE + 1) network:add(nn.SpatialConvolution(INPUT_PLANES, MIDDLE_PLANES[1], CONVOLUTION_KERNEL_SIZE, CONVOLUTION_KERNEL_SIZE)) -- nyni mame mezivysledky 64 x (vyska-5+1) x (sirka-5+1) -- nelinearni funkce network:add(nn.Tanh()) -- hledani maxima v regionech o velikosti 2x2 pixely -- s krokem nastavenym na 2 pixely v horizontalnim i 2 pixely ve vertikalnim smeru network:add(nn.SpatialMaxPooling(POOLING_SIZE, POOLING_SIZE, POOLING_STEP, POOLING_STEP)) -- druha konvolucni vrstva ocekavajici na vstupu 3D tenzor -- o velikosti MIDDLE_PLANES_1 x vyska x sirka network:add(nn.SpatialConvolution(MIDDLE_PLANES[1], MIDDLE_PLANES[2], CONVOLUTION_KERNEL_SIZE, CONVOLUTION_KERNEL_SIZE)) -- nelinearni funkce network:add(nn.Tanh()) -- opetovne hledani maxima v regionech o velikosti 2x2 pixely -- s krokem nastavenym na 2 pixely v horizontalnim i 2 pixely ve vertikalnim smeru network:add(nn.SpatialMaxPooling(POOLING_SIZE, POOLING_SIZE, POOLING_STEP, POOLING_STEP)) -- zmena tvaru: z 3D tenzoru AxBxC na 1D tenzor s A*B*C elementy network:add(nn.View(MIDDLE_PLANES[2]*size_x*size_y)) -- bezne vrstvy, jak je jiz zname network:add(nn.Linear(MIDDLE_PLANES[2]*size_x*size_y, HIDDEN_NEURONS)) -- pridana nelinearni funkce network:add(nn.ReLU()) -- bezne vrstvy, jak je jiz zname 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 generate_expected_output(digit) local result = torch.zeros(DIGITS) result[digit+1] = 1 return result end function prepare_training_data(training_files) local training_data_size = #training_files local training_data = {} function training_data:size() return training_data_size end for i, training_file in ipairs(training_files) do local input = image.load(training_file, 1, "byte"):double() local digit = tonumber(string.match(training_file, "%d+")) local output = generate_expected_output(digit) training_data[i] = {input, output} end return training_data end function read_filelist(mask) local command = "ls -1 " .. mask local handle = io.popen(command) local filelist = {} for line in handle:lines() do table.insert(filelist, line) end handle:close() return filelist end function find_largest_item(tensor) local index = -1 local value = -math.huge for i = 0, 9 do if tensor[i+1] > value then index = i value = tensor[i+1] end end return index, value end function validate_neural_network(network, filelist) local errors = 0 for i, filename in ipairs(filelist) do local input = image.load(filename, 1, "byte"):double() local expected_digit = tonumber(string.match(filename, "%d+")) local output = network:forward(input) local result, weight = find_largest_item(output) if expected_digit ~= result then errors = errors + 1 end print(expected_digit, result, expected_digit==result, weight, filename) end print("---------------------") print("Errors: " .. errors) print("Error rate: " .. 100.0*errors/#filelist .. "%") end training_files = read_filelist("training*.pgm") network = construct_neural_network() print(network) training_data = prepare_training_data(training_files) train_neural_network(network, training_data, LEARNING_RATE, MAX_ITERATION) validation_files = read_filelist("validation*.pgm") validate_neural_network(network, validation_files)
Mimochodem: celý zdrojový kód má velikost přibližně 5,5 kB a délku necelých 200 řádků. To vlastně není mnoho, když si uvědomíme, že je ve skriptu implementována jak celá konvoluční neuronová síť, tak i funkce pro její trénink a validaci.
19. Repositář s demonstračními příklady
Všechny demonstrační příklady, které jsme si popsali v předchozích kapitolách, najdete v GIT repositáři dostupném na adrese https://github.com/tisnik/torch-examples.git. Následují odkazy na zdrojové kódy jednotlivých příkladů:
Příklad | Adresa |
---|---|
make_training_images2.lua | https://github.com/tisnik/torch-examples/blob/master/nn/bitmapnn/make_training_images2.lua |
03_convolution_network_construction.lua | https://github.com/tisnik/torch-examples/blob/master/nn/bitmapnn/03_convolution_network_construction.lua |
04_compute_output_size_from_cnn.lua | https://github.com/tisnik/torch-examples/blob/master/nn/bitmapnn/04_compute_output_size_from_cnn.lua |
05_convolution_network_proper_size.lua | https://github.com/tisnik/torch-examples/blob/master/nn/bitmapnn/05_convolution_network_proper_size.lua |
06_convolution_network_usage.lua | https://github.com/tisnik/torch-examples/blob/master/nn/bitmapnn/06_convolution_network_usage.lua |
Poznámka: první skript je možné spouštět přímo z interpretru jazyka Lua, není tedy nutné používat framework Torch. Ostatní skripty se pouští přes th.
20. Odkazy na Internetu
- THE MNIST DATABASE of handwritten digits
http://yann.lecun.com/exdb/mnist/ - MNIST database (Wikipedia)
https://en.wikipedia.org/wiki/MNIST_database - MNIST For ML Beginners
https://www.tensorflow.org/get_started/mnist/beginners - Stránka projektu Torch
http://torch.ch/ - Torch: Serialization
https://github.com/torch/torch7/blob/master/doc/serialization.md - Torch: modul image
https://github.com/torch/image/blob/master/README.md - Data pro neuronové sítě
http://archive.ics.uci.edu/ml/index.php - LED Display Domain Data Set
http://archive.ics.uci.edu/ml/datasets/LED+Display+Domain - Torch na GitHubu (několik repositářů)
https://github.com/torch - Torch (machine learning), Wikipedia
https://en.wikipedia.org/wiki/Torch_%28machine_learning%29 - Torch Package Reference Manual
https://github.com/torch/torch7/blob/master/README.md - Torch Cheatsheet
https://github.com/torch/torch7/wiki/Cheatsheet - Neural network containres (Torch)
https://github.com/torch/nn/blob/master/doc/containers.md - Simple layers
https://github.com/torch/nn/blob/master/doc/simple.md#nn.Linear - Transfer Function Layers
https://github.com/torch/nn/blob/master/doc/transfer.md#nn.transfer.dok - Feedforward neural network
https://en.wikipedia.org/wiki/Feedforward_neural_network - Biologické algoritmy (4) – Neuronové sítě
https://www.root.cz/clanky/biologicke-algoritmy-4-neuronove-site/ - Biologické algoritmy (5) – Neuronové sítě
https://www.root.cz/clanky/biologicke-algoritmy-5-neuronove-site/ - Umělá neuronová síť (Wikipedia)
https://cs.wikipedia.org/wiki/Um%C4%9Bl%C3%A1_neuronov%C3%A1_s%C3%AD%C5%A5 - Učení s učitelem (Wikipedia)
https://cs.wikipedia.org/wiki/U%C4%8Den%C3%AD_s_u%C4%8Ditelem - Plotting with Torch7
http://www.lighting-torch.com/2015/08/24/plotting-with-torch7/ - Plotting Package Manual with Gnuplot
https://github.com/torch/gnuplot/blob/master/README.md - An Introduction to Tensors
https://math.stackexchange.com/questions/10282/an-introduction-to-tensors - Gaussian filter
https://en.wikipedia.org/wiki/Gaussian_filter - Gaussian function
https://en.wikipedia.org/wiki/Gaussian_function - Laplacian/Laplacian of Gaussian
http://homepages.inf.ed.ac.uk/rbf/HIPR2/log.htm - Odstranění šumu
https://cs.wikipedia.org/wiki/Odstran%C4%9Bn%C3%AD_%C5%A1umu - Binary image
https://en.wikipedia.org/wiki/Binary_image - Erosion (morphology)
https://en.wikipedia.org/wiki/Erosion_%28morphology%29 - Dilation (morphology)
https://en.wikipedia.org/wiki/Dilation_%28morphology%29 - Mathematical morphology
https://en.wikipedia.org/wiki/Mathematical_morphology - Cvičení 10 – Morfologické operace
http://midas.uamt.feec.vutbr.cz/ZVS/Exercise10/content_cz.php - Differences between a matrix and a tensor
https://math.stackexchange.com/questions/412423/differences-between-a-matrix-and-a-tensor - Qualitatively, what is the difference between a matrix and a tensor?
https://math.stackexchange.com/questions/1444412/qualitatively-what-is-the-difference-between-a-matrix-and-a-tensor? - BLAS (Basic Linear Algebra Subprograms)
http://www.netlib.org/blas/ - Basic Linear Algebra Subprograms (Wikipedia)
https://en.wikipedia.org/wiki/Basic_Linear_Algebra_Subprograms - Comparison of deep learning software
https://en.wikipedia.org/wiki/Comparison_of_deep_learning_software - TensorFlow
https://www.tensorflow.org/ - Caffe2 (A New Lightweight, Modular, and Scalable Deep Learning Framework)
https://caffe2.ai/ - PyTorch
http://pytorch.org/ - Seriál o programovacím jazyku Lua
http://www.root.cz/serialy/programovaci-jazyk-lua/ - LuaJIT – Just in Time překladač pro programovací jazyk Lua
http://www.root.cz/clanky/luajit-just-in-time-prekladac-pro-programovaci-jazyk-lua/ - 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/ - 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/ - 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/ - 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/ - 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/ - 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/ - 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/ - 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/ - 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/ - 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/ - 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/ - Lua Profiler (GitHub)
https://github.com/luaforge/luaprofiler - Lua Profiler (LuaForge)
http://luaforge.net/projects/luaprofiler/ - ctrace
http://webserver2.tecgraf.puc-rio.br/~lhf/ftp/lua/ - The Lua VM, on the Web
https://kripken.github.io/lua.vm.js/lua.vm.js.html - Lua.vm.js REPL
https://kripken.github.io/lua.vm.js/repl.html - lua2js
https://www.npmjs.com/package/lua2js - lua2js na GitHubu
https://github.com/basicer/lua2js-dist - Lua (programming language)
http://en.wikipedia.org/wiki/Lua_(programming_language) - LuaJIT 2.0 SSA IRhttp://wiki.luajit.org/SSA-IR-2.0
- The LuaJIT Project
http://luajit.org/index.html - LuaJIT FAQ
http://luajit.org/faq.html - LuaJIT Performance Comparison
http://luajit.org/performance.html - LuaJIT 2.0 intellectual property disclosure and research opportunities
http://article.gmane.org/gmane.comp.lang.lua.general/58908 - LuaJIT Wiki
http://wiki.luajit.org/Home - LuaJIT 2.0 Bytecode Instructions
http://wiki.luajit.org/Bytecode-2.0 - Programming in Lua (first edition)
http://www.lua.org/pil/contents.html - Lua 5.2 sources
http://www.lua.org/source/5.2/ - REPL
https://en.wikipedia.org/wiki/Read%E2%80%93eval%E2%80%93print_loop - The LLVM Compiler Infrastructure
http://llvm.org/ProjectsWithLLVM/ - clang: a C language family frontend for LLVM
http://clang.llvm.org/ - LLVM Backend („Fastcomp“)
http://kripken.github.io/emscripten-site/docs/building_from_source/LLVM-Backend.html#llvm-backend - Lambda the Ultimate: Coroutines in Lua,
http://lambda-the-ultimate.org/node/438 - Coroutines Tutorial,
http://lua-users.org/wiki/CoroutinesTutorial - Lua Coroutines Versus Python Generators,
http://lua-users.org/wiki/LuaCoroutinesVersusPythonGenerators