Obsah
1. Framework Torch: konfigurace konvolučních neuronových sítí
2. Ucelený projekt pro trénink a validaci konvoluční neuronové sítě
3. Moduly umístěné v adresáři image_lib
4. Moduly umístěné v adresáři nn
7. Vliv šumu vneseného do obrázků na odhad sítě
8. Postupné zvyšování šumu a odhad sítě vynesený do grafů
9. Snížení počtu iterací při tréninku sítě
10. Síť naučená s malým počtem iterací a grafy odhadu sítě pro postupné zvyšování šumu
11. Vliv roztřesení obrázků na odhad sítě
12. Postupné zvyšování míry rozstřesení a odhad sítě vynesený do grafů
13. Repositář s demonstračními příklady
1. Framework Torch: konfigurace konvolučních neuronových sítí
V předchozím článku seriálu o frameworku Torch jsme si ukázali způsob konstrukce i natrénování konvoluční neuronové sítě, která relativně bez problémů dokázala rozpoznat obrázky číslic, které byly zašuměné (s volitelnou mírou šumu). Dnešní článek bude tématicky rozdělen na dvě části. V první části nejdříve vytvoříme z příkladu ukázaného minule projekt rozdělený na několik modulů, protože s takto navrženým projektem se nám bude mnohem lépe pracovat. Následně zjistíme závislost přesnosti odhadu neuronové sítě na míře šumu ve validačních obrázcích, na počtu iterací provedených při tréninku sítě a nakonec si ukážeme, jak dobře (či v některým případech spíše špatně) dokáže neuronová síť klasifikovat číslice v obrázcích, které byly „rozstřeseny“ algoritmem jitteringu. Všechny úpravy budou prováděny na jediném místě, konkrétně v hlavním skriptu celého projektu (viz navazující kapitoly).
Tentýž projekt použije i v dalších částech seriálu, pouze v něm nepatrně upravíme parametry konvoluční sítě (velikost konvolučního jádra atd.)
2. Ucelený projekt pro trénink a validaci konvoluční neuronové sítě
Celý projekt s konvoluční sítí je rozdělen do tří částí:
- Moduly s konstruktorem neuronové sítě, funkcemi pro její trénink a taktéž funkcemi pro validaci natrénované sítě. Tyto moduly jsou uloženy v podadresáři nn.
- Moduly pro vygenerování trénovacích i validačních obrázků, aplikaci šumu, roztřesení, posunutí a v neposlední řadě i uložení obrázků do externích souborů a konverzi obrázků do tenzoru. Tyto moduly jsou uloženy v podadresáři image_lib.
- Hlavní modul celého projektu, který obsahuje konstanty použité jak pro konstrukci sítě, tak i pro vytváření obrázků, trénink sítě atd.
Způsob rozdělení projektu do jednotlivých adresářů a souborů je následující:
├── convolution_network_noisy_images.lua ├── image_lib │ ├── image_filters.lua │ ├── image_generator.lua │ ├── image_renderer.lua │ └── image_writer.lua └── nn ├── nn_constructor.lua ├── nn_trainer.lua └── nn_validators.lua
3. Moduly umístěné v adresáři image_lib
V této kapitole je stručně popsána čtveřice modulů umístěných do adresáře image_lib.
3.1 Modul image_renderer
Modul image_renderer obsahuje funkci nazvanou generate_image, kterou již dobře známe. Připomeňme si, že tato funkce slouží pro vytvoření dvourozměrné tabulky představující monochromatický rastrový obrázek číslice 0 až 9. Originální rozlišení generovaných obrázků je 8×8 pixelů, ovšem obrázky lze v případě potřeby zvětšit využitím celočíselného parametru scale, takže je možné vytvořit obrázky o rozlišení 16×16 pixelů, 24×24 pixelů atd. atd. Dále je možné specifikovat úroveň tmavých a světlých pixelů, protože se nemusí jednat o zcela černou či naopak plně bílou barvu (naopak to může neuronovou síť dobře zmást):
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 }, } 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_level, black_level) if bit==1 then image[y][x] = white_level else image[y][x] = black_level end end function generate_image(digit, scale, black_level, white_level) 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_level, black_level) x_offset = x_offset + 1 end end y_offset = y_offset + 1 end end return image end
3.2 Modul image_filters
Další modul se jmenuje image_filters a jak již jeho název napovídá, obsahuje tento modul funkce sloužící pro aplikaci různých filtrů na již existující obrázek (tedy na dvourozměrnou tabulku). Nalezneme zde funkci pro posun obrázku v horizontálním a/nebo vertikálním směru, pro zašumění obrázku Gaussovým šumem i funkci pro roztřesení pixelů (jitter). Zejména tato poslední funkce je zajímavá, protože konvoluční neuronová síť bude mít s rozpoznáním a klasifikací těchto obrázků velké problémy (ostatně při větší míře roztřesení bude mít problémy i člověk :-):
function gaussian(mean, variance) return math.sqrt(-2 * variance * math.log(math.random())) * math.cos(2 * variance * math.pi * math.random()) + mean end function bound(value, min_value, max_value) return math.max(min_value, math.min(value, max_value)) end 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 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 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
3.3 Modul image_generator
Modul image_generator bude přímo používán jak při tréninku, tak i při validaci neuronové sítě, protože jsou v něm implementovány funkce sloužící pro vygenerování trénovacích a validačních obrázků. Tyto obrázky mají jednoduchou strukturu, protože se jedná o pouhé dvourozměrné tabulky (tabulka se v jazyku Lua používá jak pro implementaci běžného pole, tak i pro asociativní pole):
function generate_training_images(scale, noise_variances, repeat_count, black_level, white_level, export_images) local training_images = {} for _, noise_variance in ipairs(noise_variances) do for digit = 0, 9 do for i = 1, repeat_count do local training_image = generate_image(digit, scale, black_level, white_level) training_image = apply_noise(training_image, noise_variance) table.insert(training_images, {digit=digit, data=training_image}) if export_images then local filename = string.format("training_%d_%d_%d.pgm", digit, noise_variance, i) write_image(filename, training_image) end end end end return training_images end function noisy_image_filename(digit, noise_variance) return string.format("validation_%d_noise_%d.pgm", digit, noise_variance) end function generate_noisy_image_for_validation(digit, scale, noise_variance, black_level, white_level, export_image) local validation_image = generate_image(digit, scale, black_level, white_level) validation_image = apply_noise(validation_image, noise_variance) if export_image then local filename = noisy_image_filename(digit, noise_variance) write_image(filename, validation_image) end return validation_image end function jitter_image_filename(digit, jitter_variance) return string.format("validation_%d_jitter_%d.pgm", digit, jitter_variance) end function generate_jittered_image_for_validation(digit, scale, jitter_variance, black_level, white_level, export_image) local validation_image = generate_image(digit, scale, black_level, white_level) validation_image = apply_jitter(validation_image, jitter_variance) if export_image then local filename = jitter_image_filename(digit, jitter_variance) write_image(filename, validation_image) end return validation_image end
Pro převod obrázků do trojrozměrného tenzoru se používá funkce image2tensor. A proč vlastně potřebujeme trojrozměrný vektor a nikoli vektor dvourozměrný? Na vstupu konvolučních neuronových sítí se používají trojrozměrné tenzory z toho důvodu, že se původní obrázky s pixely reprezentovanými v barvovém prostoru RGB, YUV, YCbCr atd. rozdělí do jednotlivých bitových rovin, kde každá rovina obsahuje pouze zvolenou složku pixelu (například tedy červenou barvovou složku). V našem případě máme zjednodušenou práci, protože používáme monochromatické obrázky, takže trojrozměrný tenzor bude mít velikost první dimenze rovnu jedné:
function image2tensor(image) local table3d = {image} return torch.Tensor(table3d):double() end
3.4 Modul image_writer
Modul pojmenovaný image_writer obsahuje jedinou funkci sloužící pro uložení obrázku do externího souboru ve formátu PGM (Portable GrayMap), který dokáže knihovna Torch načíst. Ve skutečnosti však v dnešním projektu použijeme tuto funkci jen pro ladicí účely, protože generované a ukládané obrázky nebudeme načítat zpět, ale přímo si je (v operační paměti) převedeme do trojrozměrného tenzoru:
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
4. Moduly umístěné v adresáři nn
V této kapitole je stručně popsána čtveřice modulů umístěných do adresáře nn.
4.1 Modul nn_constructor
Tento modul obsahuje funkci construct_neural_network určenou pro vytvoření konvoluční neuronové sítě se specifikovanou konfigurací – rozlišením vstupních obrázků, počtem rovin (atributů), počtem skrytých neuronů ve druhé polovině sítě atd. S významem jednotlivých konfiguračních parametrů jsme se seznámili v předchozím článku:
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(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) 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
4.2 Modul nn_trainer
V modulu nn_trainer nalezneme především funkci volanou pro vlastní trénink sítě (train_neural_network), ale taktéž funkci prepare_training_data, která ze sady vygenerovaných testovacích obrázků vytvoří tenzor určený pro trénink sítě. Připomeňme si, že tento tenzor obsahuje dvojice vstup:očekávaný-výstup, přičemž vstupem jsou jednotlivé obrázky (převedené na tenzory) a výstupem pak tenzor s váhou odhadu jednotlivých číslic:
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(scale, noise_variances, repeat_count, black_level, white_level, export_images) local training_images = generate_training_images(scale, noise_variances, repeat_count, black_level, white_level, export_images) local training_data_size = #training_images local training_data = {} function training_data:size() return training_data_size end for i, training_image in ipairs(training_images) do local input = image2tensor(training_image.data) local digit = training_image.digit local output = generate_expected_output(digit) training_data[i] = {input, output} end return training_data end
4.3 Modul nn_validators
Tento modul je ze všech popisovaných modulů nejrozsáhlejší, protože obsahuje několik funkcí, které nejenom validují odhady natrénované neuronové sítě, ale také vykreslí grafy s váhami (pravděpodobnostmi) jednotlivých odhadů. Význam jednotlivých funkcí si ukážeme v navazujících kapitolách:
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 plot_graph(filename, values) gnuplot.pngfigure(filename) gnuplot.imagesc(values, 'color') gnuplot.raw("set terminal pngcairo size 1280, 480") gnuplot.plotflush() gnuplot.close() end function validate_neural_network_using_noise_images(network, scale, noise, black_level, white_level, export_images) local errors = 0 local count = 0 for expected_digit = 0, 9 do local input = image2tensor(generate_noisy_image_for_validation(expected_digit, scale, noise, black_level, white_level, export_images)) 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) count = count + 1 end print("---------------------") print("Errors: " .. errors) print("Error rate: " .. 100.0*errors/count .. "%") end function validate_neural_network_variable_noise(network, scale, digit, black_level, white_level, export_images) local max_noise = 1000 local values = torch.Tensor(max_noise, DIGITS) for noise_variance = 1, max_noise do local input = image2tensor(generate_noisy_image_for_validation(digit, scale, noise_variance, black_level, white_level, export_images)) local output = network:forward(input) values[noise_variance] = output end local output_graph_filename = string.format("digit%d_variable_noise.png", digit) plot_graph(output_graph_filename, values:t()) end function validate_neural_network_using_jittered_images(network, scale, jitter_variance, black_level, white_level, export_images) local errors = 0 local count = 0 for expected_digit = 0, 9 do local input = image2tensor(generate_jittered_image_for_validation(expected_digit, scale, jitter_variance, black_level, white_level, export_images)) 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) count = count + 1 end print("---------------------") print("Errors: " .. errors) print("Error rate: " .. 100.0*errors/count .. "%") end function validate_neural_network_variable_jitter(network, scale, digit, black_level, white_level, export_images) local max_jitter = 20 local values = torch.Tensor(max_jitter, DIGITS) for jitter_variance = 1, max_jitter do local input = image2tensor(generate_jittered_image_for_validation(digit, scale, jitter_variance, black_level, white_level, export_images)) local output = network:forward(input) values[jitter_variance] = output end local output_graph_filename = string.format("digit%d_variable_jitter.png", digit) plot_graph(output_graph_filename, values:t()) end
5. Hlavní modul projektu
Hlavní modul celého projektu je uložen v souboru nazvaném convolution_network_noisy_images.lua. Jedná se o jediný modul, který budeme postupně modifikovat, protože právě zde jsou uloženy všechny konfigurační parametry – rozměry trénovacích i validačních obrázků, počet iterací při tréninku sítě, architektura navržené konvoluční sítě atd. Na konci skriptu je umístěn programový kód určený pro validaci; část kódu bude vždycky zakomentovaná:
require("nn") require("image") require("gnuplot") require("image_lib/image_renderer") require("image_lib/image_writer") require("image_lib/image_filters") require("image_lib/image_generator") require("nn/nn_constructor") require("nn/nn_trainer") require("nn/nn_validators") -- globalni nastaveni DIGITS = 10 -- parametry obrazku WIDTH = 32 HEIGHT = 32 BLACK_LEVEL = 64 WHITE_LEVEL = 192 -- 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 = 20 LEARNING_RATE = 0.01 -- dalsi parametry EXPORT_IMAGES = true network = construct_neural_network(WIDTH, HEIGHT, INPUT_PLANES, MIDDLE_PLANES, HIDDEN_NEURONS, OUTPUT_NEURONS, CONVOLUTION_KERNEL_SIZE, POOLING_SIZE, POOLING_STEP) print(network) NOISE_VARIANCES = {0, 10, 20, 50} REPEAT_COUNT = 3 SCALE = 4 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) validate_neural_network_using_noise_images(network, SCALE, 100, BLACK_LEVEL, WHITE_LEVEL, EXPORT_IMAGES) validate_neural_network_using_noise_images(network, SCALE, 1000, BLACK_LEVEL, WHITE_LEVEL, EXPORT_IMAGES) validate_neural_network_using_noise_images(network, SCALE, 2500, BLACK_LEVEL, WHITE_LEVEL, EXPORT_IMAGES) validate_neural_network_using_noise_images(network, SCALE, 5000, BLACK_LEVEL, WHITE_LEVEL, EXPORT_IMAGES) os.exit(1) for digit = 0, 9 do print("splot for digit " .. digit) validate_neural_network_variable_noise(network, SCALE, digit, BLACK_LEVEL, WHITE_LEVEL) end validate_neural_network_using_jittered_images(network, SCALE, 1, BLACK_LEVEL, WHITE_LEVEL, EXPORT_IMAGES) validate_neural_network_using_jittered_images(network, SCALE, 2, BLACK_LEVEL, WHITE_LEVEL, EXPORT_IMAGES) validate_neural_network_using_jittered_images(network, SCALE, 5, BLACK_LEVEL, WHITE_LEVEL, EXPORT_IMAGES) validate_neural_network_using_jittered_images(network, SCALE, 10, BLACK_LEVEL, WHITE_LEVEL, EXPORT_IMAGES) validate_neural_network_using_jittered_images(network, SCALE, 20, BLACK_LEVEL, WHITE_LEVEL, EXPORT_IMAGES) for digit = 0, 9 do print("splot for digit " .. digit) validate_neural_network_variable_jitter(network, SCALE, digit, BLACK_LEVEL, WHITE_LEVEL, EXPORT_IMAGES) end
6. Průběh učení sítě
Nejprve zkonstruujeme novou konvoluční neuronovou síť a necháme si vytisknout její strukturu:
network = construct_neural_network(WIDTH, HEIGHT, INPUT_PLANES, MIDDLE_PLANES, HIDDEN_NEURONS, OUTPUT_NEURONS, CONVOLUTION_KERNEL_SIZE, POOLING_SIZE, POOLING_STEP) print(network)
Struktura 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) }
Pro učení sítě využijeme sadu trénovacích obrázků, v nichž jsou jednotlivé číslice částečně zašuměny. Obrázky nám dodá funkce generate_training_images volaná z funkce prepare_training_data. Následně pouze zavoláme funkci pro tréning sítě:
NOISE_VARIANCES = {0, 10, 20, 50} REPEAT_COUNT = 3 SCALE = 4 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)
Průběh učení naznačuje, že chyba poměrně rychle klesá, tj. síť se dobře učí (trénuje):
# StochasticGradient: training # current error = 0.076597900332967 # current error = 0.043665505304942 # current error = 0.024003421497494 # current error = 0.012576232816519 # current error = 0.0064539779976954 # current error = 0.0033509310961456 # current error = 0.0017758012965287 # current error = 0.00095901274943609 # current error = 0.00054799449188649 # current error = 0.00033867089602328 ... ... ... # current error = 9.2518066766304e-06 # current error = 9.1928849962747e-06 # current error = 9.1277176146737e-06 # current error = 9.0550642351311e-06 # current error = 8.9960301984168e-06 # current error = 8.9447400204176e-06 # current error = 8.8771303483088e-06 # StochasticGradient: you have reached the maximum number of iterations # training error = 8.8771303483088e-06
7. Vliv šumu vneseného do obrázků na odhad sítě
Podívejme se nyní na to, jak dobře či naopak špatně bude naše konvoluční neuronová síť rozpoznávat zašuměné obrázky. Pro relativně malé úrovně šumu by měl být odhad velmi dobrý, ostatně přesně na takové obrázky byla síť natrénována. Ovšem s rostoucí mírou šumu se bude odhad zhoršovat, což ale platí i pro člověka (uvidíte sami při pohledu na obrázky, jak „snadné“ to někdy je).
Postupně spustíme následující čtveřici funkcí:
validate_neural_network_using_noise_images(network, SCALE, 100, BLACK_LEVEL, WHITE_LEVEL, EXPORT_IMAGES) validate_neural_network_using_noise_images(network, SCALE, 1000, BLACK_LEVEL, WHITE_LEVEL, EXPORT_IMAGES) validate_neural_network_using_noise_images(network, SCALE, 2500, BLACK_LEVEL, WHITE_LEVEL, EXPORT_IMAGES) validate_neural_network_using_noise_images(network, SCALE, 5000, BLACK_LEVEL, WHITE_LEVEL, EXPORT_IMAGES)
Odhady pro úroveň šumu (rozptyl) nastavený na hodnotu 100:
0 0 true 0.92901758582303 1 1 true 0.914798882625 2 2 true 0.90179615034807 3 3 true 0.94704201146714 4 4 true 0.95418884655987 5 5 true 0.94832194965547 6 6 true 0.90328739214275 7 7 true 0.90261509534692 8 8 true 0.9278343906419 9 9 true 0.90061346217945 --------------------- Errors: 0 Error rate: 0%
Obrázek 1: Validační obrázky s úrovní šumu (rozptylem) nastaveným na hodnotu 100.
Odhady pro úroveň šumu (rozptyl) nastavený na hodnotu 1000:
0 0 true 0.71679340667254 1 1 true 0.46674692091274 2 2 true 0.68365225813303 3 3 true 0.61487049242692 4 4 true 0.59118905950355 5 5 true 0.66081431702508 6 6 true 0.4819882432665 7 7 true 0.44086099586168 8 8 true 0.51702969306145 9 9 true 0.44992202255959 --------------------- Errors: 0 Error rate: 0%
Obrázek 2: Validační obrázky s úrovní šumu (rozptylem) nastaveným na hodnotu 1000.
Odhady pro úroveň šumu (rozptyl) nastavený na hodnotu 2500 (zde již došlo k chybnému odhadu):
0 0 true 0.49964226424318 1 2 false 0.31674926934743 2 2 true 0.54998900222653 3 3 true 0.42461270684221 4 4 true 0.42353880107808 5 5 true 0.41895086570902 6 6 true 0.44201805138505 7 7 true 0.35794764982959 8 8 true 0.49182046062964 9 9 true 0.36450208880863 --------------------- Errors: 1 Error rate: 10%
Obrázek 3: Validační obrázky s úrovní šumu (rozptylem) nastaveným na hodnotu 2500.
Odhady pro úroveň šumu (rozptyl) nastavený na hodnotu 5000 (zde se síť spletla třikrát, ovšem sami se podívejte na obrázky):
0 0 true 0.35376672201379 1 2 false 0.29025898930581 2 2 true 0.60442824010849 3 3 true 0.28843739459533 4 4 true 0.31849058724296 5 5 true 0.29310021725369 6 2 false 0.31115736833006 7 2 false 0.44993918004451 8 8 true 0.44275804344617 9 9 true 0.31744106228322 --------------------- Errors: 3 Error rate: 30%
Obrázek 4: Validační obrázky s úrovní šumu (rozptylem) nastaveným na hodnotu 5000.
8. Postupné zvyšování šumu a odhad sítě vynesený do grafů
Zajímavé bude sledovat, jak se bude odhad sítě zhoršovat s rostoucím šumem (což jsme si již ukázali u „obyčejné“ neuronové sítě). Proto použijeme již výše popsanou funkci nazvanou validate_neural_network_variable_noise z modulu nn_validators, v níž vykreslíme podobné grafy pro verifikační data, ovšem nyní se bude s každým měřením zvětšovat míra šumu až na hodnotu 1024.
Výsledkem je pouhých deset grafů pro deset číslic, takže si je uvedeme všechny. Z grafů je patrné, že si je konvoluční síť v odhadu číslic velmi jistá, a to i pro vysoké úrovně šumu:
Obrázek 5: Odhad pro číslici 0 pro střední hodnotu šumu od 0 do 1000.
Obrázek 6: Odhad pro číslici 1 pro střední hodnotu šumu od 0 do 1000.
Obrázek 7: Odhad pro číslici 2 pro střední hodnotu šumu od 0 do 1000.
Obrázek 8: Odhad pro číslici 3 pro střední hodnotu šumu od 0 do 1000.
Obrázek 9: Odhad pro číslici 4 pro střední hodnotu šumu od 0 do 1000.
Obrázek 10: Odhad pro číslici 5 pro střední hodnotu šumu od 0 do 1000.
Obrázek 11: Odhad pro číslici 6 pro střední hodnotu šumu od 0 do 1000.
Obrázek 12: Odhad pro číslici 7 pro střední hodnotu šumu od 0 do 1000.
Obrázek 13: Odhad pro číslici 8 pro střední hodnotu šumu od 0 do 1000.
Obrázek 14: Odhad pro číslici 9 pro střední hodnotu šumu od 0 do 1000.
9. Snížení počtu iterací při tréninku sítě
Pokud snížíme počet iterací až na hodnotu 10 (což je obecně velmi málo, typicky se používá hodnota o dva až tři řády vyšší), kupodivu se odhad sítě razantně nezhorší, alespoň ve chvíli, kdy se snažíme rozpoznat a kvalifikovat zašuměné obrázky. Ostatně se o tom můžeme relativně snadno přesvědčit:
Odhady pro úroveň šumu (rozptyl) nastavený na hodnotu 100:
0 0 true 0.9007039796097 1 1 true 0.84988676812512 2 2 true 0.88670165441306 3 3 true 0.85716831216868 4 4 true 0.96028286120409 5 5 true 0.89073656326969 6 6 true 0.85229764211598 7 7 true 0.84117398125546 8 8 true 0.78770696900435 9 9 true 0.91263127172923 --------------------- Errors: 0 Error rate: 0%
Odhady pro úroveň šumu (rozptyl) nastavený na hodnotu 1000:
0 0 true 0.56502721095338 1 1 true 0.57947685218729 2 2 true 0.73469718583774 3 3 true 0.59729417113505 4 4 true 0.65922615777093 5 5 true 0.55426296544767 6 6 true 0.56374782525079 7 7 true 0.45865045184205 8 8 true 0.36333484963251 9 9 true 0.72257957894121 --------------------- Errors: 0 Error rate: 0%
Odhady pro úroveň šumu (rozptyl) nastavený na hodnotu 2500:
0 0 true 0.36889956402128 1 1 true 0.397362275251 2 2 true 0.54447832474612 3 3 true 0.58269293319752 4 4 true 0.49334754817544 5 5 true 0.47956458843779 6 6 true 0.37250860985059 7 7 true 0.3606193371519 8 8 true 0.35734223008066 9 9 true 0.47358258851812 --------------------- Errors: 0 Error rate: 0%
Odhady pro úroveň šumu (rozptyl) nastavený na hodnotu 5000:
0 2 false 0.27487188812131 1 2 false 0.28982103104273 2 2 true 0.41970028064418 3 3 true 0.4897877895855 4 4 true 0.42139011829457 5 5 true 0.43697124536669 6 5 false 0.2193472198283 7 2 false 0.34779616116913 8 3 false 0.24852067159185 9 9 true 0.33917120557291 --------------------- Errors: 5 Error rate: 50%
10. Síť naučená s malým počtem iterací a grafy odhadu sítě pro postupné zvyšování šumu
Opět si ukažme grafy, v nichž je vynesen odhad sítě (která číslice se na obrázku nachází) pro postupné zvyšování šumu. Tentokrát byla síť naučena velmi rychle, ovšem ukazuje se, že stále poměrně kvalitně:
Obrázek 15: Odhad pro číslici 0 pro střední hodnotu šumu od 0 do 1000.
Obrázek 16: Odhad pro číslici 1 pro střední hodnotu šumu od 0 do 1000.
Obrázek 17: Odhad pro číslici 2 pro střední hodnotu šumu od 0 do 1000.
Obrázek 18: Odhad pro číslici 3 pro střední hodnotu šumu od 0 do 1000.
Obrázek 19: Odhad pro číslici 4 pro střední hodnotu šumu od 0 do 1000.
Obrázek 20: Odhad pro číslici 5 pro střední hodnotu šumu od 0 do 1000.
Obrázek 21: Odhad pro číslici 6 pro střední hodnotu šumu od 0 do 1000.
Obrázek 22: Odhad pro číslici 7 pro střední hodnotu šumu od 0 do 1000.
Obrázek 23: Odhad pro číslici 8 pro střední hodnotu šumu od 0 do 1000.
Obrázek 24: Odhad pro číslici 9 pro střední hodnotu šumu od 0 do 1000.
11. Vliv roztřesení obrázků na odhad sítě
Mnohem větší vliv na schopnosti neuronové sítě rozeznat na validačních obrázcích číslice bude mít operace roztřesení. Ostatně podívejte se sami na následující obrázky a zjistíte, že u posledních dvou sekvencí již nemá neuronová síť ale ani člověk prakticky žádnou šanci rozeznat, jaké číslice se obrázku nacházely:
Obrázek 25: Jittering je nastaven na hodnotu 1.
Obrázek 26: Jittering je nastaven na hodnotu 2.
Obrázek 27: Jittering je nastaven na hodnotu 5.
Obrázek 28: Jittering je nastaven na hodnotu 10.
Obrázek 29: Jittering je nastaven na hodnotu 20.
To, že se síti rozpoznávání už tak dobře nedaří, můžeme vidět i z průběhu validace:
Jittering je nastaven na hodnotu 1:
0 0 true 0.35910954151032 1 1 true 0.57856174459343 2 2 true 0.40019361990113 3 3 true 0.54086705757525 4 4 true 0.58778335183714 5 5 true 0.27008819125063 6 6 true 0.35776762022894 7 7 true 0.4531367614001 8 4 false 0.21041576508232 9 9 true 0.3519680014153 --------------------- Errors: 1 Error rate: 10%
Jittering je nastaven na hodnotu 2:
0 0 true 0.38051842884868 1 1 true 0.38103882055846 2 2 true 0.39405629490146 3 3 true 0.45315678727988 4 4 true 0.50494134273053 5 3 false 0.19587720231237 6 6 true 0.23127709141202 7 7 true 0.31614212052061 8 9 false 0.2558102855285 9 9 true 0.32458093384014 --------------------- Errors: 2 Error rate: 20%
Jittering je nastaven na hodnotu 5:
0 4 false 0.28540120686069 1 1 true 0.30647200639512 2 2 true 0.34205264219205 3 3 true 0.34558813657259 4 4 true 0.34545742784285 5 4 false 0.16762162756383 6 9 false 0.21334603720808 7 9 false 0.23076108525153 8 3 false 0.20003963876897 9 0 false 0.28069596444979 --------------------- Errors: 6 Error rate: 60%
Jittering je nastaven na hodnotu 10:
0 0 true 0.22012625858352 1 1 true 0.32947438362054 2 2 true 0.25733056743774 3 2 false 0.2330541164283 4 4 true 0.30746443359153 5 4 false 0.1976126151968 6 3 false 0.23027809292622 7 9 false 0.23223999329789 8 2 false 0.23371376521436 9 3 false 0.22685291894002 --------------------- Errors: 6 Error rate: 60%
Jittering je nastaven na hodnotu 20:
0 0 true 0.19842203869463 1 2 false 0.25433237722833 2 3 false 0.24291030056728 3 3 true 0.21615011335859 4 4 true 0.21841478740651 5 2 false 0.17700056607258 6 2 false 0.27402457656592 7 9 false 0.23169880374807 8 3 false 0.22503740085146 9 3 false 0.19410856562881 --------------------- Errors: 7 Error rate: 70%
V posledních třech validačních krocích je odhad víceméně náhodný a navíc jsou pravděpodobnosti prakticky stejné.
12. Postupné zvyšování míry rozstřesení a odhad sítě vynesený do grafů
Ještě více jsou patrné problémy konvoluční neuronové sítě při klasifikaci číslic ve chvíli, kdy do grafu vyneseme odhad sítě pro rostoucí míru (úroveň) rozstřesení. Krásně odlišené pruhy, které jsme viděli v předchozích kapitolách, zde již nevidíme, pouze prakticky náhodný odhad sítě při vyšší míře rozstřesení (to je ovšem pochopitelné):
Obrázek 30: Odhad pro číslici 0 pro míru rozstřesení od 0 do 20.
Obrázek 31: Odhad pro číslici 1 pro míru rozstřesení od 0 do 20.
Obrázek 32: Odhad pro číslici 2 pro míru rozstřesení od 0 do 20.
Obrázek 33: Odhad pro číslici 3 pro míru rozstřesení od 0 do 20.
Obrázek 34: Odhad pro číslici 4 pro míru rozstřesení od 0 do 20.
Obrázek 35: Odhad pro číslici 5 pro míru rozstřesení od 0 do 20.
Obrázek 36: Odhad pro číslici 6 pro míru rozstřesení od 0 do 20.
Obrázek 37: Odhad pro číslici 7 pro míru rozstřesení od 0 do 20.
Obrázek 38: Odhad pro číslici 8 pro míru rozstřesení od 0 do 20.
Obrázek 39: Odhad pro číslici 9 pro míru rozstřesení od 0 do 20.
13. Repositář s demonstračním 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ů:
14. 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