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