Obsah
1. Framework Torch: vylepšení klasifikace obrázků z databáze CIFAR-10
2. Přidání dalších „běžných“ vrstev neuronů
3. Změna konstruktoru neuronové sítě
4. Normalizace trénovacích a validačních obrázků
6. Výsledky validace pro pevně zadané parametry sítě
7. Variabilita parametrů při tréningu sítě: počet obrázků a počet iterací při učení
8. Vliv počtu obrázků v trénovací množině a počtu iterací
9. Serializace a deserializace natrénované neuronové sítě
10. Modul určený pro serializaci a deserializaci neuronové sítě
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
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ů.
![](https://i.iinfo.cz/images/656/torch12-1.png)
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ě.
![](https://i.iinfo.cz/images/329/torch-nn1-2.png)
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:
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:
16. 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 IR
http://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