Hlavní navigace

Framework Torch: vylepšení klasifikace obrázků z databáze CIFAR-10

Pavel Tišnovský

Dnes se s konvolučními sítěmi využívanými pro klasifikaci obrázků setkáme naposledy. Vylepšíme projekt pro klasifikaci obrázků z databáze CIFAR-10 a ukážeme si vliv počtu trénovacích obrázků a počtu iterací na kvalitu odhadu sítě.

Doba čtení: 28 minut

11. Hlavní skript projektu po všech provedených úpravách

12. Získání informací o vahách a biasech přiřazených neuronům nebo konvolučním vrstvám

13. Zobrazení a serializace vah konvoluční vrstvy

14. Rozměr tenzorů s vahami konvolučních vrstev

15. Repositář s demonstračním projektem

16. Odkazy na Internetu

1. Framework Torch: vylepšení klasifikace obrázků z databáze CIFAR-10

Dnešní článek o frameworku Torch je věnován poslednímu většímu projektu, v němž se budeme zabývat klasifikací obrázků s využitím konvolučních neuronových sítí. Nejprve vylepšíme základní architekturu neuronové sítě popsané minule, řekneme si, jak lze snadno normalizovat barvové kanály trénovacích i validačních obrázků a také zjistíme, jaký vliv má počet trénovacích obrázků a počet iterací při tréningu na klasifikační schopnosti neuronové sítě. Také si ukážeme, jak lze (a to velmi snadno) naučenou síť serializovat a tak ji uchovat pro budoucí použití, popř. pro prohlížení parametrů (váhy, bias) jednotlivých konvolučních vrstev či běžných neuronů.

Obrázek 1: Několik trénovacích obrázků s kočkami (kategorie cat). Obrázky mají velikost pouze 32×32 pixelů a pocházejí z databáze CIFAR-10.

2. Přidání dalších „běžných“ vrstev neuronů

Prvním krokem, který nám do jisté míry pomůže vylepšit vytvářenou konvoluční neuronovou síť, je přidání dalších „běžných“ vrstev neuronů. Připomeňme si, že konvoluční sítě většinou obsahují konvoluční vrstvy na začátku, kdežto na konci sítě se nachází běžné neurony s klasickými přechodovými funkcemi. Tyto vrstvy slouží jak pro převod naučených parametrů z konvolučních vrstev na výstupní tenzor, tak i pro zapamatování dalších informací zjištěných v rámci tréningu. Původně měla naše síť vlastně pouze jednu vrstvu běžných neuronů navíc, protože její struktura vypadala následovně:

Struktura neuronove site
nn.Sequential {
  [input -> (1) -> (2) -> (3) -> (4) -> (5) -> (6) -> (7) -> (8) -> (9) -> (10) -> output]
  (1): nn.SpatialConvolution(3 -> 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)
}

„Běžná“ vrstva neuronů je představována řádky 8 a 9, které jsou ve výpisu struktury sítě zvýrazněny. Před touto vrstvou se nachází struktura sloužící pro vytvoření pohledu z pooling vrstvy na 1600 výstupů, které vstupují do běžné vrstvy. Výsledek je přes aktivační funkci předán do poslední vrstvy, která provede namapování na tenzor s deseti prvky (protože klasifikujeme jen deset typů objektů).

3. Změna konstruktoru neuronové sítě

Aby do neuronové sítě bylo možné snadno vložit další mezivrstvy, nepatrně upravíme konstruktor sítě, tj. funkci construct_neural_network z modulu nn_constructor.lua. Pro jednoduchost budou mít všechny vkládané mezivrstvy stejný počet neuronů předaný v parametru hidden_neurons. Počet nově vkládaných vrstev se nastavuje parametrem additional_layers:

function construct_neural_network(width, height, input_planes, middle_planes,
                                  hidden_neurons, output_neurons,
                                  convolution_kernel_size, pooling_size, pooling_step, additional_layers)
    local network = nn.Sequential()
 
    local size_x = calculate_size_after_convolution(width, middle_planes, convolution_kernel_size, pooling_size)
    local size_y = calculate_size_after_convolution(height, middle_planes, convolution_kernel_size, pooling_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 MIDDLE_PLANES_1 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())
 
    if additional_layers then
        for i = 1, additional_layers do
        -- bezne vrstvy, jak je jiz zname
        network:add(nn.Linear(hidden_neurons, hidden_neurons))
 
        -- pridana nelinearni funkce
        network:add(nn.ReLU())
        end
    end
 
    -- bezne vrstvy, jak je jiz zname
    network:add(nn.Linear(hidden_neurons, output_neurons))
 
    return network
end

Struktura neuronové sítě, do níž není vložena žádná další mezivrstva:

nn.Sequential {
  [input -> (1) -> (2) -> (3) -> (4) -> (5) -> (6) -> (7) -> (8) -> (9) -> (10) -> output]
  (1): nn.SpatialConvolution(3 -> 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)
}

Struktura neuronové sítě s jednou novou mezivrstvou:

nn.Sequential {
  [input -> (1) -> (2) -> (3) -> (4) -> (5) -> (6) -> (7) -> (8) -> (9) -> (10) -> (11) -> (12) -> output]
  (1): nn.SpatialConvolution(3 -> 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 -> 100)
  (11): nn.ReLU
  (12): nn.Linear(100 -> 10)
}

Struktura neuronové sítě se dvěma novými mezivrstvami:

nn.Sequential {
  [input -> (1) -> (2) -> (3) -> (4) -> (5) -> (6) -> (7) -> (8) -> (9) -> (10) -> (11) -> (12) -> (13) -> (14) -> output]
  (1): nn.SpatialConvolution(3 -> 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 -> 100)
  (11): nn.ReLU
  (12): nn.Linear(100 -> 100)
  (13): nn.ReLU
  (14): nn.Linear(100 -> 10)
}

Struktura neuronové sítě se třemi novými mezivrstvami:

nn.Sequential {
  [input -> (1) -> (2) -> (3) -> (4) -> (5) -> (6) -> (7) -> (8) -> (9) -> (10) -> (11) -> (12) -> (13) -> (14) -> (15) -> (16) -> output]
  (1): nn.SpatialConvolution(3 -> 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 -> 100)
  (11): nn.ReLU
  (12): nn.Linear(100 -> 100)
  (13): nn.ReLU
  (14): nn.Linear(100 -> 100)
  (15): nn.ReLU
  (16): nn.Linear(100 -> 10)
}

Struktura neuronové sítě se čtyřmi novými mezivrstvami:

nn.Sequential {
  [input -> (1) -> (2) -> (3) -> (4) -> (5) -> (6) -> (7) -> (8) -> (9) -> (10) -> (11) -> (12) -> (13) -> (14) -> (15) -> (16) -> (17) -> (18) -> output]
  (1): nn.SpatialConvolution(3 -> 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 -> 100)
  (11): nn.ReLU
  (12): nn.Linear(100 -> 100)
  (13): nn.ReLU
  (14): nn.Linear(100 -> 100)
  (15): nn.ReLU
  (16): nn.Linear(100 -> 100)
  (17): nn.ReLU
  (18): nn.Linear(100 -> 10)
}

V dalších příkladech použijeme právě čtyři nové mezivrstvy.

4. Normalizace trénovacích a validačních obrázků

Další úprava provedená v projektu spočívá v tom, že trénovací i validační obrázky budeme normalizovat, a to samostatně pro každý barvový kanál. Před vlastní normalizací se spočítá průměr a směrodatná odchylka jednotlivých barvových složek (pro všechny obrázky v množině), následně se provede normalizace trénovacích obrázků a potom i normalizace obrázků validačních. Výsledkem bude tenzor, který sice bude obsahovat trénovací popř. validační obrázky, ty však již nebudou snadno zobrazitelné, neboť prvky tenzorů budou nabývat hodnot mezi mezními hodnotami –1 až 1 (vlivem zaokrouhlovacích chyb ovšem mohou tuto mez překročit). Normalizaci se obecně doporučuje provádět i pro jiné typy neuronových sítí.

Výpočet průměru a směrodatné odchylky je ve skutečnosti velmi jednoduchý, protože můžeme použít selektory vybírající konkrétní dimenzi ze čtyřrozměrného tenzoru, který představuje vstupní a validační data (počet obrázků × počet barvových rovin × počet řádků × počet pixelů na řádku). Na takto zúžený výběr pak můžeme zavolat metody tensor:mean() a tensor:std(), které již výpočet provedou za nás (tyto funkce se aplikují na vybrané prvky/pohled, což je taktéž tenzor):

function calculate_mean_and_sd(training_set, channels)
    -- prumer
    local mean = {}
 
    -- smerodatna odchylka
    local sd = {}
 
    for channel = 1,channels do
        mean[channel] = training_set.data[{ {}, {channel}, {}, {}  }]:mean()
        sd[channel] = training_set.data[{ {}, {channel}, {}, {}  }]:std()
    end
 
    return mean, sd
end

Další funkce na základě průměru a směrodatné odchylky provede normalizaci dat. Samozřejmě opět musíme normalizaci provádět po jednotlivých barvových kanálech:

function normalize_data(training_set, mean, sd, channels)
    for channel = 1,channels do
        training_set.data[{ {}, {channel}, {}, {}  }]:add(-mean[channel])
        training_set.data[{ {}, {channel}, {}, {}  }]:div(sd[channel])
    end
end

5. Normalizace v praxi

Zkusme si tedy nejprve načíst trénovací data, následně vypočítat průměr a směrodatnou jednotlivých barvových kanálů, provést normalizaci dat v jednotlivých kanálech a opět (pro již normalizovaná data) vypočítat průměr a směrodatnou odchylku. Všechny tyto operace provedou následující řádky skriptu. Povšimněte si, že původně celočíselné hodnoty (pixelů) musíme převést na typ double, jinak by výpočty nebylo možné provést (došlo by ke vzniku běhové chyby):

input_training_set = torch.load('cifar10-train.t7')
input_training_set.data = input_training_set.data:double()
 
print("Prumer a standardni odchylka hodnot pixelu pred normalizaci")
-- spocitat prumer a smerodatnou odchylku pro vsechny kanaly
mean, sd = calculate_mean_and_sd(input_training_set, CHANNELS)
print_mean_and_sd(mean, sd, CHANNELS)
print()
 
-- normalizace trenovacich dat
normalize_data(input_training_set, mean, sd, CHANNELS)
 
print("Prumer a standardni odchylka hodnot pixelu po normalizaci")
-- nyni by mel byt prumer prakticky nulovy a odchylka rovna jedne
new_mean, new_sd = calculate_mean_and_sd(input_training_set, CHANNELS)
print_mean_and_sd(new_mean, new_sd, CHANNELS)
print()

Výsledky před a po normalizaci budou následující:

Prumer a standardni odchylka hodnot pixelu pred normalizaci
 
Kanal: 1
    prumer:   125.83175029297
    odchylka: 63.143400842608
Kanal: 2
    prumer:   123.26066621094
    odchylka: 62.369209019077
Kanal: 3
    prumer:   114.03068681641
    odchylka: 66.965808411077
 
 
 
Prumer a standardni odchylka hodnot pixelu po normalizaci
 
Kanal: 1
    prumer:   1.0727918705287e-14
    odchylka: 1.0000000000011
Kanal: 2
    prumer:   -6.6096161096402e-15
    odchylka: 0.99999999998982
Kanal: 3
    prumer:   1.381815042706e-14
    odchylka: 1.0000000000009

Vidíme, že po normalizaci se průměr hodnot v jednotlivých kanálech skutečně blíží nule a směrodatná odchylka zase jedničce, což je přesně ten výsledek, který jsme očekávali.

6. Výsledky validace pro pevně zadané parametry sítě

Podívejme se nyní na to, jak se úprava parametrů neuronové sítě projevila v praxi, tj. na výsledcích rozpoznávání. Použijeme čtyři nové mezivrstvy sítě, počet iterací bude nastaven na hodnotu 500 (což je ještě relativně nízká hodnota) a počet trénovacích obrázků bude nastaven na 1000 (opět poměrně nízká hodnota – viz navazující kapitoly). První výsledky jsou byly získány tak, že se po natrénování sítě 1000 obrázky použilo dalších 3000 obrázků, ovšem ze stejné množiny. Nejedná se tedy o korektní validaci, ale spíš o test, jestli je „mozková kapacita“ (celkový počet měnitelných vah neuronů) vůbec dostatečný:

...
...
...
deer       deer      true    0.57292878624107
truck      truck     true    0.65336862019788
frog       ship      false   0.55004974851733
bird       cat       false   0.27070009703079
automobile truck     false   0.92400873005579
deer       deer      true    0.50868852545658
airplane   bird      false   0.36460117462903
frog       cat       false   0.48030977276765
frog       frog      true    0.57933830233959
deer       airplane  false   0.72151339786828
---------------------
Errors: 550 out of 3000 images
Error rate: 18.333333333333%
Success rate: 81.66666666666%

Výsledek je velmi slušný a pro reálná trénovací data se k němu budeme snažit přiblížit.

Poznámka: numerické hodnoty jsou vypsány bez explicitního formátování, proto mají tak divný tvar (x/3).

Pro reálné testovací obrázky dostaneme horší hodnoty, což se ale dalo očekávat, protože pouhých 1000 trénovacích obrázků je pro tréning databáze CIFAR-10 málo. Ovšem na druhou stranu nejsou výsledné hodnoty zcela špatné, protože si musíme uvědomit, že při náhodných odpovědích by byla úspěšnost jen 10% (navíc se odhad sítě od minulé verze zlepšil):

...
...
...
dog        dog       true    0.67961377184359
dog        deer      false   1.0299916596353
deer       bird      false   0.42982775116141
cat        dog       false   0.69753079601915
deer       truck     false   0.36139970707089
airplane   airplane  true    0.69419025612601
ship       truck     false   0.37459072060959
deer       bird      false   0.42741501460605
ship       ship      true    0.59149941480064
bird       dog       false   0.48824661801863
dog        bird      false   0.40314882720612
frog       deer      false   0.45798343305038
---------------------
Errors: 1624 out of 3000 images
Error rate: 54.133333333333%
Success rate: 45.866666666%

7. Variabilita parametrů při tréningu sítě: počet obrázků a počet iterací při učení

Kromě samotné struktury sítě má velký vliv na její klasifikační schopnosti celkový počet trénovacích obrázků a také počet iterací provedených při tréninku. Proto tyto dva parametry, které byly původně konstantami, budeme načítat z příkazového řádku, například následujícím způsobem (bez dalších doplňkových kontrol):

-- parametry pro uceni neuronove site
MAX_ITERATION = tonumber(arg[1])
LEARNING_RATE = 0.005
 
MAX_SIZE_OF_TRAINING_SET = tonumber(arg[2])
MAX_SIZE_OF_VALIDATION_SET = 10000

Celý skript (hlavní modul) projektu nyní bude vypadat následovně:

require("nn")
require("image")
require("gnuplot")
 
require("image_lib/image_writer")
 
require("nn/nn_constructor")
require("nn/nn_trainer")
require("nn/nn_validators")
require("nn/nn_serialization")
 
require("utils/cifar_downloader")
require("utils/classification_classes")
require("utils/data_normalizer")
 
 
-- globalni nastaveni
CLASSES = 10
 
 
-- parametry obrazku
CHANNELS = 3
WIDTH = 32
HEIGHT = 32
 
 
-- parametry neuronove site
INPUT_PLANES = 3
 
MIDDLE_PLANES = {64, 64}
 
HIDDEN_NEURONS = 100
OUTPUT_NEURONS = 10
 
ADDITIONAL_LAYERS = 4
 
-- parametry konvolucni vrstvy
CONVOLUTION_KERNEL_SIZE = 5
 
-- parametry pooling vrstvy
POOLING_SIZE = 2
POOLING_STEP = 2
 
-- parametry pro uceni neuronove site
MAX_ITERATION = tonumber(arg[1])
LEARNING_RATE = 0.005
 
MAX_SIZE_OF_TRAINING_SET = tonumber(arg[2])
MAX_SIZE_OF_VALIDATION_SET = 10000
 
setup_cifar_dataset(original_data_address, zip_file_name)
 
input_training_set = torch.load('cifar10-train.t7')
input_training_set.data = input_training_set.data:double()
 
print("Prumer a standardni odchylka hodnot pixelu pred normalizaci")
-- spocitat prumer a smerodatnou odchylku pro vsechny kanaly
mean, sd = calculate_mean_and_sd(input_training_set, CHANNELS)
print_mean_and_sd(mean, sd, CHANNELS)
print()
 
-- normalizace trenovacich dat
normalize_data(input_training_set, mean, sd, CHANNELS)
 
print("Prumer a standardni odchylka hodnot pixelu po normalizaci")
-- nyni by mel byt prumer prakticky nulovy a odchylka rovna jedne
new_mean, new_sd = calculate_mean_and_sd(input_training_set, CHANNELS)
print_mean_and_sd(new_mean, new_sd, CHANNELS)
print()
 
--write_training_images_with_label(input_training_set, "cat")
--write_training_images_with_label(input_training_set, "truck")
 
print("Struktura vstupnich dat")
print(input_training_set.data:size())
 
training_data = prepare_training_data(input_training_set, MAX_SIZE_OF_TRAINING_SET)
 
 
network = construct_neural_network(WIDTH, HEIGHT, INPUT_PLANES, MIDDLE_PLANES,
                                   HIDDEN_NEURONS, OUTPUT_NEURONS,
                                   CONVOLUTION_KERNEL_SIZE, POOLING_SIZE, POOLING_STEP,
                                   ADDITIONAL_LAYERS)
 
print("Struktura neuronove site")
print(network)
train_neural_network(network, training_data, LEARNING_RATE, MAX_ITERATION)
 
serialize_nn(network)
 
 
validation_set = torch.load('cifar10-test.t7')
--validation_set = torch.load('cifar10-train.t7')
 
validation_set.data = validation_set.data:double()
-- normalizace validacnich dat
normalize_data(validation_set, mean, sd, CHANNELS)
 
validate_neural_network(network, validation_set, mean, sd, MAX_SIZE_OF_VALIDATION_SET)

8. Vliv počtu obrázků v trénovací množině a počtu iterací

Pro zjištění, jaký vliv má počet obrázků v trénovací množině i celkový počet iterací při tréningu sítě, si připravíme jednoduchý shell skript, který bude náš projekt volat s různými parametry. Výsledky se budou ukládat do pomocných textových souborů. Skript může vypadat například takto:

max_iterations="10 20 50 100 200 500 1000 2000 5000"
training_set_sizes="10 20 50 100 200 500 1000 2000 5000 10000"
 
for max_iteration in $max_iterations
do
    for training_set_size in $training_set_sizes
    do
        th cifar_data_classificator.lua $max_iteration $training_set_size > "${max_iteration}_${training_set_size}.txt"
    done
done

Výsledky validace sítě, tj. procento úspěšně rozpoznaných/klasifikovaných obrázků, jsou uvedeny v následující tabulce. Ideální by samozřejmě bylo dosažení stoprocentní úspěšnosti, ovšem ani zde prezentované výsledky (pravý dolní roh) nejsou vůbec špatné s ohledem na již minule zmiňovanou kvalitu a variabilitu vstupních obrázků:

Training set/Max iter. 10 20 50 100 200 500 1000 2000 5000 10000
10 10% 10% 10% 10% 10% 10% 12% 20% 27% 40%
20 10% 10% 10% 10% 10% 10% 18% 24% 40% 52%
50 10% 10% 10% 10% 10% 20% 26% 39% 52% 53%
100 10% 10% 11% 10% 11% 31% 38% 43% 49% 54%
200 10% 10% 11% 14% 23% 34% 37% 41% 48% 52%
500 10% 10% 19% 26% 32% 35% 40% * * *
1000 10% 15% 19% 22% 26% 34% * * * *
2000 15% 18% 20% 22% 27% 34% * * * *

Poznámka: vyšší hodnoty iterací i počtu trénovacích obrázků již naráží na výkonnostní limity současných mikroprocesorů (tréning trvá desítky minut), takže je výhodnější výpočty provádět přes CUDA na grafickém akcelerátoru.

Zajímavé jsou výsledky v trojúhelníku v levém horním rohu, které ukazují, že úspěšnost klasifikace dosáhla 10%. Důvod pro toto přesné číslo je jednoduchý – neuronová síť je v těchto případech natrénována tak špatně, že objekty u všech obrázků klasifikuje stejně, například vrací „automobil“ pro všech 10000 validačních obrázků. A vzhledem k tomu, že každý typ objektu se ve validačních obrázcích vyskytuje s četností 10%, bude síť úspěšná přesně v těchto deseti procentech. Při větším natrénování sítě samozřejmě dostaneme i lepší výsledky.

9. Serializace a deserializace natrénované neuronové sítě

S principem serializace a deserializace objektů ve frameworku Torch jsme se již seznámili v jednom z úvodních článků tohoto seriálu. Serializovat je samozřejmě možné i samotnou neuronovou síť, ať již před jejím tréninkem (potom bude obsahovat náhodné hodnoty), nebo po tréninku, což je praktičtější a mnohem užitečnější. Úprava hlavního skriptu projektu takovým způsobem, aby se neuronová síť po natrénování uložila na disk v čitelné podobě, je relativně snadná, což ukazuje následující fragment kódu (nejedná se o celý skript, zobrazena je pouze jeho změněná část):

-- globalni nastaveni
USE_SERIALIZED_NN = false
 
if not USE_SERIALIZED_NN then
    network = construct_neural_network(WIDTH, HEIGHT, INPUT_PLANES, MIDDLE_PLANES,
                                       HIDDEN_NEURONS, OUTPUT_NEURONS,
                                       CONVOLUTION_KERNEL_SIZE, POOLING_SIZE, POOLING_STEP,
                                       ADDITIONAL_LAYERS)
 
    print("Struktura neuronove site")
    print(network)
    train_neural_network(network, training_data, LEARNING_RATE, MAX_ITERATION)
 
    serialize_nn(network)
else
    network = deserialize_nn()
end

Pokud je konstanta USE_SERIALIZEDNN nastavena na pravdivostní hodnotu false či nil, bude síť zkonstruována a natrénována tak, jak to již známe z předchozích kapitol. Posléze se serializuje na disk do čitelného (textového, ASCII) souboru pro pozdější použití. Pokud je naopak konstanta USE_SERIALIZEDNN nastavena na true, bude síť deserializována.

10. Modul určený pro serializaci a deserializaci neuronové sítě

Samotný modul určený pro serializaci a deserializaci neuronové sítě se jmenuje nn_serialization a nevyžaduje bližší popis, protože v něm použité metody writeObject() a readObject() již známe:

FILENAME = "neural_network.asc"
 
function serialize_nn(network)
    local fout = torch.DiskFile(FILENAME, "w"):ascii()
    fout:writeObject(network)
    fout:close()
end
 
function deserialize_nn()
    local fin = torch.DiskFile(FILENAME, "r")
    return fin:readObject()
end

11. Hlavní skript projektu po všech provedených úpravách

Hlavní modul dnešního projektu bude po všech v něm provedených změnách vypadat následovně:

require("nn")
require("image")
require("gnuplot")
 
require("image_lib/image_writer")
 
require("nn/nn_constructor")
require("nn/nn_trainer")
require("nn/nn_validators")
require("nn/nn_serialization")
 
require("utils/cifar_downloader")
require("utils/classification_classes")
require("utils/data_normalizer")
 
 
-- globalni nastaveni
CLASSES = 10
USE_SERIALIZED_NN = False
 
 
-- parametry obrazku
CHANNELS = 3
WIDTH = 32
HEIGHT = 32
 
 
-- parametry neuronove site
INPUT_PLANES = 3
 
MIDDLE_PLANES = {64, 64}
 
HIDDEN_NEURONS = 100
OUTPUT_NEURONS = 10
 
ADDITIONAL_LAYERS = 4
 
-- parametry konvolucni vrstvy
CONVOLUTION_KERNEL_SIZE = 5
 
-- parametry pooling vrstvy
POOLING_SIZE = 2
POOLING_STEP = 2
 
-- parametry pro uceni neuronove site
MAX_ITERATION = tonumber(arg[1])
LEARNING_RATE = 0.005
 
MAX_SIZE_OF_TRAINING_SET = tonumber(arg[2])
MAX_SIZE_OF_VALIDATION_SET = 10000
 
setup_cifar_dataset(original_data_address, zip_file_name)
 
input_training_set = torch.load('cifar10-train.t7')
input_training_set.data = input_training_set.data:double()
 
print("Prumer a standardni odchylka hodnot pixelu pred normalizaci")
-- spocitat prumer a smerodatnou odchylku pro vsechny kanaly
mean, sd = calculate_mean_and_sd(input_training_set, CHANNELS)
print_mean_and_sd(mean, sd, CHANNELS)
print()
 
-- normalizace trenovacich dat
normalize_data(input_training_set, mean, sd, CHANNELS)
 
print("Prumer a standardni odchylka hodnot pixelu po normalizaci")
-- nyni by mel byt prumer prakticky nulovy a odchylka rovna jedne
new_mean, new_sd = calculate_mean_and_sd(input_training_set, CHANNELS)
print_mean_and_sd(new_mean, new_sd, CHANNELS)
print()
 
--write_training_images_with_label(input_training_set, "cat")
--write_training_images_with_label(input_training_set, "truck")
 
print("Struktura vstupnich dat")
print(input_training_set.data:size())
 
training_data = prepare_training_data(input_training_set, MAX_SIZE_OF_TRAINING_SET)
 
 
if not USE_SERIALIZED_NN then
    network = construct_neural_network(WIDTH, HEIGHT, INPUT_PLANES, MIDDLE_PLANES,
                                       HIDDEN_NEURONS, OUTPUT_NEURONS,
                                       CONVOLUTION_KERNEL_SIZE, POOLING_SIZE, POOLING_STEP,
                                       ADDITIONAL_LAYERS)
 
    print("Struktura neuronove site")
    print(network)
    train_neural_network(network, training_data, LEARNING_RATE, MAX_ITERATION)
 
    serialize_nn(network)
else
    network = deserialize_nn()
end
 
 
validation_set = torch.load('cifar10-test.t7')
--validation_set = torch.load('cifar10-train.t7')
 
validation_set.data = validation_set.data:double()
-- normalizace validacnich dat
normalize_data(validation_set, mean, sd, CHANNELS)
 
validate_neural_network(network, validation_set, mean, sd, MAX_SIZE_OF_VALIDATION_SET)

12. Získání informací o vahách a biasech přiřazených neuronům nebo konvolučním vrstvám

Již v úvodním článku o neuronových sítích jsme si vysvětlili roli vah přiřazených neuronům i roli takzvaného biasu. Podobné parametry existují i u konvolučních neuronových sítích. Váhy i biasy jsou při konstrukci neuronové sítě zvoleny náhodně, ovšem v průběhu učení se postupně modifikují a tím se i zpřesňují rozpoznávací schopnosti sítě. Přitom se nejedná o žádné tajné informace – váhy i biasy můžeme velmi snadno získat a konkrétně u konvolučních vrstev mají strukturu tenzorů. Nejprve ovšem musíme zařídit, abychom měli přístup k jednotlivým vrstvám sítě. To lze zařídit relativně snadno, například následující úpravou funkce pro konstrukci neuronové sítě (změněné řádky jsou zvýrazněny):

function construct_neural_network(width, height, input_planes, middle_planes,
                                  hidden_neurons, output_neurons,
                                  convolution_kernel_size, pooling_size, pooling_step)
    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)
    c1 = nn.SpatialConvolution(input_planes, middle_planes[1], convolution_kernel_size, convolution_kernel_size)
    network:add(c1)
 
    -- 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
    c2 = nn.SpatialConvolution(middle_planes[1], middle_planes[2], convolution_kernel_size, convolution_kernel_size)
    network:add(c2)
 
    -- 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, c1, c2
end

Poznámka: funkci jsme upravili pro síť určenou pro rozpoznávání číslic, ale princip je stále stejný i pro složitější sítě.

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

13. Zobrazení a serializace vah konvoluční vrstvy

Ve chvíli, kdy již máme k dispozici objekty představující konvoluční vrstvy neuronové sítě, lze váhy (po natrénování) získat snadno; stejně snadno je můžeme serializovat:

network, c1, c2 = construct_neural_network(WIDTH, HEIGHT, INPUT_PLANES, MIDDLE_PLANES,
                                           HIDDEN_NEURONS, OUTPUT_NEURONS,
                                           CONVOLUTION_KERNEL_SIZE, POOLING_SIZE, POOLING_STEP)
 
training_data = prepare_training_data(SCALE, NOISE_VARIANCES, REPEAT_COUNT,
                                      BLACK_LEVEL, WHITE_LEVEL, EXPORT_IMAGES)
 
train_neural_network(network, training_data, LEARNING_RATE, MAX_ITERATION)
 
print(c1.weight)
print(c2.weight)
 
function serialize(data, filename)
    local fout = torch.DiskFile(filename, "w"):ascii()
    fout:writeObject(data)
    fout:close()
end
 
serialize(c1.weight, "c1.asc")
serialize(c2.weight, "c2.asc")

Na příklad výstupu můžeme vidět, že konvoluční vrstva skutečně obsahuje množství konvolučních jader (kernel), každé o zadané velikosti 5×5 prvků:

...
...
...
(1,63,.,.) =
 -3.7841e-04  1.9185e-03  1.2704e-03 -1.2442e-02 -1.8779e-02
 -5.1994e-03 -4.9628e-03 -2.4966e-02  4.1733e-03  1.2175e-02
  2.1420e-03 -4.9453e-03 -1.6388e-02 -2.3170e-02  3.3196e-03
  1.5688e-02  7.0532e-03  3.3450e-03 -1.0674e-02  3.0495e-03
 -3.8021e-03 -1.9843e-02 -1.6602e-02  1.5282e-02 -2.5436e-02
 
(2,63,.,.) =
 -9.5145e-03  2.1055e-02 -1.6311e-02 -2.0666e-02  1.9270e-02
  4.1829e-03  1.8305e-03  6.5574e-03  1.2624e-02  1.6650e-02
 -2.4766e-02 -1.2444e-02  5.8772e-03 -1.1041e-02 -1.7269e-02
 -2.0450e-02  1.3396e-02 -1.3379e-02 -1.2112e-02 -1.6390e-02
  2.0790e-02 -5.1574e-03 -1.0249e-02 -2.2770e-02 -2.1044e-02
 
(3,63,.,.) =
 -8.3722e-03 -1.0714e-02  7.8625e-03  2.7706e-02  2.6522e-02
  7.9277e-03 -2.6712e-02 -2.5026e-02  1.7980e-02  1.9504e-02
 -4.9129e-03 -2.5328e-02 -2.4687e-02  1.1672e-02 -7.5201e-03
  1.6656e-03  2.1806e-02  2.0424e-03 -1.0453e-02 -7.4789e-03
  1.3030e-02 -3.8616e-03 -8.7773e-04  8.9792e-03 -1.4806e-02
 
(4,63,.,.) =
 -1.3717e-02 -7.0788e-03  7.5901e-03  1.4140e-02  1.7764e-02
  8.8145e-03  1.3725e-02 -1.2065e-02  8.5516e-03 -2.2490e-02
  1.7917e-02  2.0467e-02  8.4158e-03 -7.4859e-03 -1.6332e-02
  7.9493e-03  1.9769e-02 -1.0942e-03 -7.3233e-03 -1.8331e-03
  6.6321e-03 -1.2397e-02  2.0086e-02  1.8128e-02  1.3454e-02
 
(5,63,.,.) =
  1.1128e-02 -7.4809e-03  2.1087e-02  2.6392e-03  1.5307e-02
 -4.4263e-03 -7.0609e-03  1.6372e-02  1.3728e-02  1.5818e-02
 -6.6785e-03 -2.5182e-02 -1.8356e-02 -1.4481e-02  5.0062e-03
  1.8460e-02  1.0207e-02 -4.7329e-03 -5.8213e-03  1.2957e-02
  1.6118e-02 -1.2725e-02 -1.5452e-02 -2.1715e-02 -5.2119e-03
...
...
...

14. Rozměr tenzorů s vahami konvolučních vrstev

Rozměry tenzorů obou konvolučních vrstev získáme snadno pomocí metody size():

print(c1.weight:size())
print(c2.weight:size())

Výsledky:

MIF18 tip v článku témata

 64
  1
  5
  5
 
odpovídá typu [torch.DoubleTensor of size 64x1x5x5]
 
 
 64
 64
  5
  5
 
odpovídá typu [torch.DoubleTensor of size 64x64x5x5]

První tenzor má rozměry 64×1×5×5 elementů, kde 5×5 jsou velikosti konvolučních jader, 64 je počet rovin na výstupu z konvoluční vrstvy a 1 je počet vstupních kanálů (jen jeden, protože zde používáme monochromatické obrázky). Ovšem druhá konvoluční vrstva již má odlišný vstup – 64 kanálů namísto jednoho, takže tenzor s vahami má rozměry 64×64×5×5 elementů.

15. Repositář s demonstračním projektem

Demonstrační projekt, 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 souborů projektu:

Soubor Adresa
image_lib/image_writer.lua https://github.com/tisnik/torch-examples/blob/master/nn/ci­far_data_classificator2/i­mage_lib/image_writer.lua
   
nn/nn_constructor.lua https://github.com/tisnik/torch-examples/blob/master/nn/ci­far_data_classificator2/nn/nn_con­structor.lua
nn/nn_serialization.lua https://github.com/tisnik/torch-examples/blob/master/nn/ci­far_data_classificator2/nn/nn_se­rialization.lua
nn/nn_trainer.lua https://github.com/tisnik/torch-examples/blob/master/nn/ci­far_data_classificator2/nn/nn_tra­iner.lua
nn/nn_validators.lua https://github.com/tisnik/torch-examples/blob/master/nn/ci­far_data_classificator2/nn/nn_va­lidators.lua
   
utils/cifar_downloader.lua https://github.com/tisnik/torch-examples/blob/master/nn/ci­far_data_classificator2/u­tils/cifar_downloader.lua
utils/classification_classes.lua https://github.com/tisnik/torch-examples/blob/master/nn/ci­far_data_classificator2/u­tils/classification_classes­.lua
utils/data_normalizer.lua https://github.com/tisnik/torch-examples/blob/master/nn/ci­far_data_classificator2/u­tils/data_normalizer.lua
   
cifar_data_classificator.lua https://github.com/tisnik/torch-examples/blob/master/nn/ci­far_data_classificator2/ci­far_data_classificator.lua
   
clean.sh https://github.com/tisnik/torch-examples/blob/master/nn/ci­far_data_classificator2/cle­an.sh
run.sh https://github.com/tisnik/torch-examples/blob/master/nn/ci­far_data_classificator2/run­.sh
run_variable_params.sh https://github.com/tisnik/torch-examples/blob/master/nn/ci­far_data_classificator2/run_va­riable_params.sh

16. Odkazy na Internetu

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