Obsah
1. PyTorch: problematika rozpoznávání a klasifikace obrázků (2. část)
2. Klasifikace rastrových obrázků sítí běžnou neuronovou sítí s „vektorovým“ vstupem a výstupem
3. První skript: příprava datové sady pro trénink neuronové sítě s vektorovým vstupem a výstupem
4. Příprava vstupních dat, která mohou být zašuměna popř. posunuta
5. Druhý skript: příprava zašuměných a posunutých vstupních dat pro neuronovou síť
6. Náhodné rozdělení datové sady funkcí train_test_split
7. Realizace rozdělení datové sady na trénovací a testovací data
8. Třetí skript: rozdělení datové sady na trénovací a testovací data
9. Konstrukce neuronové sítě s vektorovým vstupem i výstupem
11. Čtvrtý skript: trénink neuronové sítě pro rozpoznávání rastrových obrázků
12. Zašuměné obrázky v trénovacích a testovacích datech
14. Posunuté obrázky v trénovacích a testovacích datech
15. Šestý skript: trénink neuronové sítě pro rozpoznání posunutých obrázků
16. Matice záměn pro síť, která odpovídá vždy korektně
17. Matice záměn pro zašuměné obrázky
18. Matice záměn pro posunuté obrázky
19. Repositář s demonstračními příklady
1. PyTorch: problematika rozpoznávání a klasifikace obrázků (2. část)
Na předchozí článek o neuronových sítích realizovaných s využitím knihovny PyTorch dnes navážeme. Ukážeme si trénink sítí, na jejichž vstupu budou rastrové obrázky s číslicemi a na výstupu konkrétní informace o číslici, kterou síť rozezná (nebo naopak nerozezná).
Připomeňme si, že vstupní data s číslicemi zakódovanými do bitmap s rozlišením 8×8 pixelů, vypadají takto:
# číslice reprezentované v masce 8x8 pixelů 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), )
Dále máme k dispozici pomocnou funkci, která tato data převede do formy dvourozměrného pole (NumPy):
def digit_to_array(digits, n): digit = digits[n] rows = [] # převod jednotlivých řádků na osmici bitů for scanline in digit: row = [] # převod bitmapy představující řádek na osmici bitů for _ in range(8): bit = scanline & 0x01 row.append(float(bit)) scanline >>= 1 rows.append(row) # transformace na n-dimenzionální pole return np.array(rows)
Výsledek lze vizualizovat s využitím knihovny Matplotlib:

Obrázek 1: Matice číslice 2 zobrazená v nepravých barvách.
Abychom zjistili, do jaké míry bude neuronová síť naučena na rozlišení reálných obrázků, budeme ji trénovat a validovat s využitím obrázků zašuměných, a to s mírou šumu od 0% do 100%:

Obrázek 2: Vizualizace matice se zašuměnou číslicí 2, úroveň šumu se postupně zvyšuje.
Navíc budeme síť učit a následně validovat i na posunutých obrázcích, což se ukazuje být pro klasické neuronové sítě větším problémem, než šum:

Obrázek 3: Vizualizovaná matice s posunutou číslicí 2.
2. Klasifikace rastrových obrázků sítí běžnou neuronovou sítí s „vektorovým“ vstupem a výstupem
Abychom zjistili limity klasických neuronových sítí pro klasifikaci rastrových obrázků, pokusíme se je skutečně pro klasifikaci použít. Zkonstruujeme tedy neuronovou síť, která bude jak na vstupu, tak i na výstupu obsahovat vektorová data. Konkrétně bude vstupem sítě 64prvkový vektor (s prvky typu float32), který obsahuje pixely původní bitmapy 8×8 pixelů, ovšem v „ploché“ podobě (z matice vytvoříme vektor). A na výstupu sítě bude taktéž vektor. Ten bude obsahovat deset prvků, přičemž pozice (index) největšího prvku určí číslici, kterou síť nalezla ve vstupním obrázku. Ideálně bude tento vektor vypadat například takto:
tensor([0., 0., 1., 0., 0., 0., 0., 0., 0., 0.]))
což odpovídá číslici 2.
V praxi však síť může odpovědět i takto:
tensor([0.1872, 0.1687, 0.9529, 0.0941, 0.0442, 0.1684, 0.0257, 0.0000, 0.0612, 0.2341])
což stále poměrně přesně určuje číslici 2 (prvek 0,9529).
Trénovací a testovací data budou reprezentována instancí třídy Data, která je odvozena od třídy torch.utils.data.Dataset. Tuto třídu jsme již několikrát použili, ovšem pro úplnost si ji ještě jednou ukažme:
# konverze původních dat z NumPy do tenzorů class Data(Dataset): def __init__(self, X, y): self.X = torch.from_numpy(X.astype(np.float32)) self.y = torch.from_numpy(y.astype(np.float32)) self.len = self.X.shape[0] def __getitem__(self, index): return self.X[index], self.y[index] def __len__(self): return self.len
Podívejme se nyní na pomocnou funkci, která trénovací a/nebo testovací data připraví. Tato funkce využívá jak vstupní matici 8×8 pixelů digits, tak i funkci nazvanou digit_to_array a popsanou minule. Povšimněte si způsobu tvorby vektorů x_vector a y_vector, jejichž délka i hodnoty prvků odpovídají popisu uvedeném výše:
def prepare_data(digits, length): # příprava dat pro trénink a testování X = [] y = [] for i in range(length): # cislice digit = i % 10 # vstupy array = digit_to_array(digits, digit) # prevod na vektor x_vector = array.flatten() X.append(x_vector) # očekávané výstupy y_vector = [0.0] * 10 y_vector[digit] = 1.0 y.append(y_vector) return Data(np.array(X), np.array(y))
3. První skript: příprava datové sady pro trénink neuronové sítě s vektorovým vstupem a výstupem
Postup popsaný v předchozí kapitole nyní použijeme ve skriptu, jehož úkolem je příprava datové sady (což je instance třídy Data odvozené od třídy torch.utils.data.Dataset), která bude z vnějšího pohledu obsahovat sekvenci dvojic tenzorů. První tenzor z těchto dvojic bude jednorozměrným vektorem se 64 prvky typu float32, jejichž hodnoty odpovídají intenzitě pixelů původní bitmapy. A druhý tenzor bude taktéž jednorozměrným vektorem, nyní ovšem s deseti prvky typu float32. Takový vektor bude mít devět prvků nulových a jeden z prvků nastavený na jedničku – pozice jedničky odpovídá číslici 0 až 9:
import torch from torch.utils.data import Dataset import numpy as np # konverze původních dat z NumPy do tenzorů class Data(Dataset): def __init__(self, X, y): self.X = torch.from_numpy(X.astype(np.float32)) self.y = torch.from_numpy(y.astype(np.float32)) self.len = self.X.shape[0] def __getitem__(self, index): return self.X[index], self.y[index] def __len__(self): return self.len # číslice reprezentované v masce 8x8 pixelů 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), ) def digit_to_array(digits, n): digit = digits[n] rows = [] # převod jednotlivých řádků na osmici bitů for scanline in digit: row = [] # převod bitmapy představující řádek na osmici bitů for _ in range(8): bit = scanline & 0x01 row.append(float(bit)) scanline >>= 1 rows.append(row) # transformace na n-dimenzionální pole return np.array(rows) def prepare_data(digits, length): # příprava dat pro trénink a testování X = [] y = [] for i in range(length): # cislice digit = i % 10 # vstupy array = digit_to_array(digits, digit) # prevod na vektor x_vector = array.flatten() X.append(x_vector) # očekávané výstupy y_vector = [0.0] * 10 y_vector[digit] = 1.0 y.append(y_vector) return Data(np.array(X), np.array(y)) data = prepare_data(digits, 1000) print(len(data)) for i in range(10): print(data[i])
Skript po svém spuštění připraví data pro neuronovou síť, poté vytiskne jejich počet a nakonec i obsah prvních deseti dvojic. Povšimněte si, jak se ve druhém tenzoru „posunuje“ jednička, která určuje cifru na obrázku:
1000 (tensor([0., 0., 0., 0., 0., 0., 0., 0., 0., 0., 1., 1., 1., 1., 0., 0., 0., 1., 1., 0., 0., 1., 1., 0., 0., 1., 1., 0., 1., 1., 1., 0., 0., 1., 1., 1., 0., 1., 1., 0., 0., 1., 1., 0., 0., 1., 1., 0., 0., 0., 1., 1., 1., 1., 0., 0., 0., 0., 0., 0., 0., 0., 0., 0.]), tensor([1., 0., 0., 0., 0., 0., 0., 0., 0., 0.])) (tensor([0., 0., 0., 0., 0., 0., 0., 0., 0., 0., 0., 1., 1., 0., 0., 0., 0., 0., 1., 1., 1., 0., 0., 0., 0., 0., 0., 1., 1., 0., 0., 0., 0., 0., 0., 1., 1., 0., 0., 0., 0., 0., 0., 1., 1., 0., 0., 0., 0., 1., 1., 1., 1., 1., 1., 0., 0., 0., 0., 0., 0., 0., 0., 0.]), tensor([0., 1., 0., 0., 0., 0., 0., 0., 0., 0.])) (tensor([0., 0., 0., 0., 0., 0., 0., 0., 0., 0., 1., 1., 1., 1., 0., 0., 0., 1., 1., 0., 0., 1., 1., 0., 0., 0., 0., 0., 1., 1., 0., 0., 0., 0., 0., 1., 1., 0., 0., 0., 0., 0., 1., 1., 0., 0., 0., 0., 0., 1., 1., 1., 1., 1., 1., 0., 0., 0., 0., 0., 0., 0., 0., 0.]), tensor([0., 0., 1., 0., 0., 0., 0., 0., 0., 0.])) ... ... ... (tensor([0., 0., 0., 0., 0., 0., 0., 0., 0., 0., 1., 1., 1., 1., 0., 0., 0., 1., 1., 0., 0., 1., 1., 0., 0., 0., 1., 1., 1., 1., 0., 0., 0., 1., 1., 0., 0., 1., 1., 0., 0., 1., 1., 0., 0., 1., 1., 0., 0., 0., 1., 1., 1., 1., 0., 0., 0., 0., 0., 0., 0., 0., 0., 0.]), tensor([0., 0., 0., 0., 0., 0., 0., 0., 1., 0.])) (tensor([0., 0., 0., 0., 0., 0., 0., 0., 0., 0., 1., 1., 1., 1., 0., 0., 0., 1., 1., 0., 0., 1., 1., 0., 0., 0., 1., 1., 1., 1., 1., 0., 0., 0., 0., 0., 0., 1., 1., 0., 0., 0., 0., 0., 1., 1., 0., 0., 0., 0., 1., 1., 1., 0., 0., 0., 0., 0., 0., 0., 0., 0., 0., 0.]), tensor([0., 0., 0., 0., 0., 0., 0., 0., 0., 1.]))
4. Příprava vstupních dat, která mohou být zašuměna popř. posunuta
V praxi však nebudou vstupní obrázky pro neuronovou síť ideální. Ostatně kdyby byly, stačilo by nám naprogramovat jednoduché mapování přesný_vstup→přesný_výstup. Na vstupní data tedy budeme aplikovat šum a popř. i operaci posunu obrázku ve směru horizontální a/nebo vertikální osy. Všechny potřebné pomocné funkce jsme si již vysvětlili v předchozím článku, takže jen ve stručnosti:
def add_noise(array, level): return (1.0 - level) * array + level * np.random.rand(8, 8)
a:
def shift(arr, x_shift, y_shift): # horizontální posun arr = np.roll(arr, x_shift, axis=1) # výplně těch částí, které byly orotovány na druhou stranu if x_shift < 0: arr[:, x_shift:] = 0.0 elif x_shift > 0: arr[:, :x_shift] = 0.0 # vertikální posun arr = np.roll(arr, y_shift, axis=0) # výplně těch částí, které byly orotovány na druhou stranu if y_shift < 0: arr[y_shift:] = 0.0 elif y_shift > 0: arr[:y_shift] = 0.0 return arr
Funkci pro přípravu dat upravíme takovým způsobem, že bude tyto dvě funkce volat a předávat jim vstupní parametry nastavené uživatelem – tedy úroveň šumu a maximální posun obrázků uvedený v pixelech:
def prepare_data(digits, length, noise_level=0.0, x_shift_amount=0, y_shift_amount=0): # příprava dat pro trénink a testování X = [] y = [] for i in range(length): # cislice digit = i % 10 # vstupy array = digit_to_array(digits, digit) # zasumeni array = add_noise(array, noise_level) # posuny x_shift = random.randint(-x_shift_amount, x_shift_amount) y_shift = random.randint(-y_shift_amount, y_shift_amount) array = shift(array, x_shift, y_shift) # prevod na vektor x_vector = array.flatten() X.append(x_vector) # očekávané výstupy y_vector = [0.0] * 10 y_vector[digit] = 1.0 y.append(y_vector) return Data(np.array(X), np.array(y))
5. Druhý skript: příprava zašuměných a posunutých vstupních dat pro neuronovou síť
Opět se podívejme, jak by mohl vypadat skript, který připraví data pro trénink a testování klasické neuronové sítě s 64prvkovými vektory na vstupu a desetiprvkovými vektory na výstupu. Tentokrát však budou vstupní vektory obsahovat hodnoty, které jsou zašuměné, nebo mohou být obrázky posunuté v libovolném směru. Asi správně odhadnete, že právě posun bude kritický, protože jsme ztratili informaci o dvou rozměrech a vstupem je jen jednorozměrný vektor. Ukažme si zdrojový kód upraveného skriptu:
import random import torch from torch.utils.data import Dataset import numpy as np # konverze původních dat z NumPy do tenzorů class Data(Dataset): def __init__(self, X, y): self.X = torch.from_numpy(X.astype(np.float32)) self.y = torch.from_numpy(y.astype(np.float32)) self.len = self.X.shape[0] def __getitem__(self, index): return self.X[index], self.y[index] def __len__(self): return self.len # číslice reprezentované v masce 8x8 pixelů 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), ) def digit_to_array(digits, n): digit = digits[n] rows = [] # převod jednotlivých řádků na osmici bitů for scanline in digit: row = [] # převod bitmapy představující řádek na osmici bitů for _ in range(8): bit = scanline & 0x01 row.append(float(bit)) scanline >>= 1 rows.append(row) # transformace na n-dimenzionální pole return np.array(rows) def add_noise(array, level): return (1.0 - level) * array + level * np.random.rand(8, 8) def shift(arr, x_shift, y_shift): # horizontální posun arr = np.roll(arr, x_shift, axis=1) # výplně těch částí, které byly orotovány na druhou stranu if x_shift < 0: arr[:, x_shift:] = 0.0 elif x_shift > 0: arr[:, :x_shift] = 0.0 # vertikální posun arr = np.roll(arr, y_shift, axis=0) # výplně těch částí, které byly orotovány na druhou stranu if y_shift < 0: arr[y_shift:] = 0.0 elif y_shift > 0: arr[:y_shift] = 0.0 return arr def prepare_data(digits, length, noise_level=0.0, x_shift_amount=0, y_shift_amount=0): # příprava dat pro trénink a testování X = [] y = [] for i in range(length): # cislice digit = i % 10 # vstupy array = digit_to_array(digits, digit) # zasumeni array = add_noise(array, noise_level) # posuny x_shift = random.randint(-x_shift_amount, x_shift_amount) y_shift = random.randint(-y_shift_amount, y_shift_amount) array = shift(array, x_shift, y_shift) # prevod na vektor x_vector = array.flatten() X.append(x_vector) # očekávané výstupy y_vector = [0.0] * 10 y_vector[digit] = 1.0 y.append(y_vector) return Data(np.array(X), np.array(y)) data = prepare_data(digits, 1000, noise_level=0.0, x_shift_amount=2, y_shift_amount=2) print(len(data)) for i in range(10): print(data[i])
Po spuštění skriptu se opět zobrazí počet prvků v trénovací/testovací sadě a posléze hodnoty prvních deseti prvků. Nyní je patrné, že první vektor už neobsahuje pouze hodnoty 0,0 a 1,0, ale více či méně odlišné hodnoty:
1000 (tensor([0.0000, 0.0000, 0.0000, 0.0000, 0.0000, 0.0000, 0.0000, 0.0000, 0.0000, 0.0000, 0.0000, 0.0000, 0.0000, 0.0000, 0.0000, 0.0000, 0.0991, 0.1470, 0.0453, 0.1430, 0.0893, 0.0821, 0.0000, 0.0000, 0.9793, 0.8072, 0.9616, 0.8820, 0.0053, 0.0436, 0.0000, 0.0000, 0.9762, 0.0246, 0.0278, 0.8587, 0.9291, 0.0374, 0.0000, 0.0000, 0.9698, 0.0695, 0.8725, 0.8903, 0.9781, 0.0564, 0.0000, 0.0000, 0.9405, 0.9431, 0.1012, 0.9844, 0.9339, 0.1723, 0.0000, 0.0000, 0.8720, 0.1137, 0.1474, 0.8364, 0.8673, 0.0288, 0.0000, 0.0000]), tensor([1., 0., 0., 0., 0., 0., 0., 0., 0., 0.])) (tensor([0.1282, 0.1286, 0.9159, 0.9999, 0.0604, 0.0830, 0.1627, 0.0000, 0.0997, 0.8669, 0.8799, 0.9667, 0.1710, 0.0166, 0.1434, 0.0000, 0.1858, 0.0872, 0.9059, 0.8710, 0.1191, 0.1385, 0.0505, 0.0000, 0.0617, 0.0100, 0.9928, 0.9849, 0.1692, 0.1496, 0.0553, 0.0000, 0.0863, 0.0691, 0.9267, 0.8241, 0.1422, 0.1098, 0.0381, 0.0000, 0.8753, 0.8869, 0.9612, 0.8800, 0.9099, 0.8483, 0.0810, 0.0000, 0.1768, 0.1332, 0.1881, 0.1756, 0.0358, 0.1965, 0.1333, 0.0000, 0.0000, 0.0000, 0.0000, 0.0000, 0.0000, 0.0000, 0.0000, 0.0000]), tensor([0., 1., 0., 0., 0., 0., 0., 0., 0., 0.])) ... ... ... (tensor([0.0000, 0.0000, 0.0733, 0.1337, 0.9931, 0.9026, 0.8485, 0.9472, 0.0000, 0.0000, 0.1810, 0.8931, 0.8541, 0.0940, 0.1297, 0.1427, 0.0000, 0.0000, 0.1165, 0.9050, 0.8881, 0.8289, 0.9612, 0.9271, 0.0000, 0.0000, 0.0582, 0.8934, 0.9511, 0.1421, 0.0801, 0.8987, 0.0000, 0.0000, 0.1779, 0.8515, 0.8020, 0.1619, 0.0649, 0.8573, 0.0000, 0.0000, 0.1445, 0.1342, 0.9168, 0.8039, 0.9018, 0.9114, 0.0000, 0.0000, 0.1518, 0.0960, 0.0952, 0.0685, 0.1568, 0.1162, 0.0000, 0.0000, 0.0000, 0.0000, 0.0000, 0.0000, 0.0000, 0.0000]), tensor([0., 0., 0., 0., 0., 0., 1., 0., 0., 0.])) (tensor([0.0000, 0.0000, 0.0000, 0.0000, 0.0000, 0.0000, 0.0000, 0.0000, 0.0170, 0.1181, 0.1175, 0.0079, 0.0321, 0.1954, 0.0000, 0.0000, 0.8095, 0.9161, 0.9373, 0.9020, 0.1813, 0.1592, 0.0000, 0.0000, 0.9597, 0.1631, 0.1652, 0.8400, 0.9894, 0.1514, 0.0000, 0.0000, 0.8719, 0.8249, 0.9083, 0.8364, 0.1119, 0.0627, 0.0000, 0.0000, 0.8413, 0.0157, 0.0148, 0.8373, 0.9239, 0.1173, 0.0000, 0.0000, 0.9013, 0.0727, 0.0251, 0.8195, 0.8519, 0.0888, 0.0000, 0.0000, 0.8526, 0.8314, 0.8749, 0.9900, 0.1544, 0.1514, 0.0000, 0.0000]), tensor([0., 0., 0., 0., 0., 0., 0., 0., 1., 0.])) (tensor([0.0936, 0.9051, 0.9126, 0.1106, 0.0446, 0.8483, 0.9484, 0.1938, 0.1982, 0.0617, 0.9297, 0.8475, 0.8500, 0.9321, 0.9868, 0.1909, 0.1818, 0.0986, 0.0904, 0.1327, 0.0302, 0.8697, 0.8073, 0.0833, 0.0850, 0.1306, 0.0666, 0.1750, 0.8532, 0.8485, 0.0680, 0.0881, 0.0256, 0.0323, 0.9974, 0.9051, 0.9478, 0.1259, 0.0662, 0.0194, 0.0463, 0.1651, 0.0389, 0.0865, 0.0547, 0.1028, 0.0711, 0.1404, 0.0000, 0.0000, 0.0000, 0.0000, 0.0000, 0.0000, 0.0000, 0.0000, 0.0000, 0.0000, 0.0000, 0.0000, 0.0000, 0.0000, 0.0000, 0.0000]), tensor([0., 0., 0., 0., 0., 0., 0., 0., 0., 1.]))
6. Náhodné rozdělení datové sady funkcí train_test_split
Pro rozdělení dat, která byla získána postupem popsaným v předchozích kapitolách (dvojice 64prvkový vektor+desetiprvkový vektor) na data trénovací a validační (resp. testovací), použijeme funkci train_test_split. Tato funkce ve svém prvním parametru akceptuje přímo datovou sadu (nemusíme se tedy snažit o ruční získání naměřených dat a očekávaných výsledků), dále velikost testovacích a trénovacích dat (buď jako celé číslo, což je počet záznamů nebo hodnotu typu float, což bude zlomek od 0 do 1, nepovinnou hodnotu, která zamezí různým výsledkům pro několik volání této funkce a dále parametr povolující zamíchání dat (ve výchozím nastavení je povolen, což nám opět vyhovuje):
train_test_split(*arrays, test_size=None, train_size=None, random_state=None, shuffle=True, stratify=None) Split arrays or matrices into random train and test subsets. Quick utility that wraps input validation, ``next(ShuffleSplit().split(X, y))``, and application to input data into a single call for splitting (and optionally subsampling) data into a one-liner. Read more in the :ref:`User Guide <cross_validation>`. Parameters ---------- *arrays : sequence of indexables with same length / shape[0] Allowed inputs are lists, numpy arrays, scipy-sparse matrices or pandas dataframes. test_size : float or int, default=None If float, should be between 0.0 and 1.0 and represent the proportion of the dataset to include in the test split. If int, represents the absolute number of test samples. If None, the value is set to the complement of the train size. If ``train_size`` is also None, it will be set to 0.25. train_size : float or int, default=None If float, should be between 0.0 and 1.0 and represent the proportion of the dataset to include in the train split. If int, represents the absolute number of train samples. If None, the value is automatically set to the complement of the test size. random_state : int, RandomState instance or None, default=None Controls the shuffling applied to the data before applying the split. Pass an int for reproducible output across multiple function calls. See :term:`Glossary <random_state>`. shuffle : bool, default=True Whether or not to shuffle the data before splitting. If shuffle=False then stratify must be None. stratify : array-like, default=None If not None, data is split in a stratified fashion, using this as the class labels. Read more in the :ref:`User Guide <stratification>`.
7. Realizace rozdělení datové sady na trénovací a testovací data
Nyní si funkci pro přípravu dat, s nimiž budeme při konstrukci, tréninku a validaci neuronové sítě pracovat, rozšíříme o volání funkce train_test_split. Vstupem této funkce jsou hodnoty X a y převedené ze seznamů na n-rozměrná pole typu np.array (ale vlastně už nyní by bylo možné použít tenzory). Výsledkem je čtveřice n-rozměrných polí X_train, X_test, y_train a y_test, která jsou použita pro konstrukci dvou objektů typu Data. Tím máme přípravné práce za sebou:
def prepare_data(digits, length, noise_level=0.0, x_shift_amount=0, y_shift_amount=0, test_size=1/2): # příprava dat pro trénink a testování X = [] y = [] for i in range(length): # cislice digit = i % 10 # vstupy array = digit_to_array(digits, digit) # zasumeni array = add_noise(array, noise_level) # posuny x_shift = random.randint(-x_shift_amount, x_shift_amount) y_shift = random.randint(-y_shift_amount, y_shift_amount) array = shift(array, x_shift, y_shift) # prevod na vektor x_vector = array.flatten() X.append(x_vector) # očekávané výstupy y_vector = [0.0] * 10 y_vector[digit] = 1.0 y.append(y_vector) X_train, X_test, y_train, y_test = train_test_split( np.array(X), np.array(y), test_size=test_size, random_state=26 ) # trénovací sada train_data = Data(X_train, y_train) # testovací sada test_data = Data(X_test, y_test) return train_data, test_data
8. Třetí skript: rozdělení datové sady na trénovací a testovací data
Celý skript, který připraví data pro neuronovou síť na základě bitmap s číslicemi, se „natáhl“ na délku přibližně tří kilobajtů, ovšem stále by mělo být zřejmé, jaké operace se v něm provádí a proč:
import random from sklearn.model_selection import train_test_split import torch from torch.utils.data import Dataset import numpy as np # konverze původních dat z NumPy do tenzorů class Data(Dataset): def __init__(self, X, y): self.X = torch.from_numpy(X.astype(np.float32)) self.y = torch.from_numpy(y.astype(np.float32)) self.len = self.X.shape[0] def __getitem__(self, index): return self.X[index], self.y[index] def __len__(self): return self.len # číslice reprezentované v masce 8x8 pixelů 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), ) def digit_to_array(digits, n): digit = digits[n] rows = [] # převod jednotlivých řádků na osmici bitů for scanline in digit: row = [] # převod bitmapy představující řádek na osmici bitů for _ in range(8): bit = scanline & 0x01 row.append(float(bit)) scanline >>= 1 rows.append(row) # transformace na n-dimenzionální pole return np.array(rows) def add_noise(array, level): return (1.0 - level) * array + level * np.random.rand(8, 8) def shift(arr, x_shift, y_shift): # horizontální posun arr = np.roll(arr, x_shift, axis=1) # výplně těch částí, které byly orotovány na druhou stranu if x_shift < 0: arr[:, x_shift:] = 0.0 elif x_shift > 0: arr[:, :x_shift] = 0.0 # vertikální posun arr = np.roll(arr, y_shift, axis=0) # výplně těch částí, které byly orotovány na druhou stranu if y_shift < 0: arr[y_shift:] = 0.0 elif y_shift > 0: arr[:y_shift] = 0.0 return arr def prepare_data(digits, length, noise_level=0.0, x_shift_amount=0, y_shift_amount=0, test_size=1/2): # příprava dat pro trénink a testování X = [] y = [] for i in range(length): # cislice digit = i % 10 # vstupy array = digit_to_array(digits, digit) # zasumeni array = add_noise(array, noise_level) # posuny x_shift = random.randint(-x_shift_amount, x_shift_amount) y_shift = random.randint(-y_shift_amount, y_shift_amount) array = shift(array, x_shift, y_shift) # prevod na vektor x_vector = array.flatten() X.append(x_vector) # očekávané výstupy y_vector = [0.0] * 10 y_vector[digit] = 1.0 y.append(y_vector) X_train, X_test, y_train, y_test = train_test_split( np.array(X), np.array(y), test_size=test_size, random_state=26 ) # trénovací sada train_data = Data(X_train, y_train) # testovací sada test_data = Data(X_test, y_test) return train_data, test_data train, test = prepare_data(digits, 1000, noise_level=0.0, x_shift_amount=2, y_shift_amount=2) print(len(train)) print(len(test)) for i in range(10): print(train[i])
Z prvních dvou vypsaných řádků je patrné, že jak trénovací data, tak i data pro otestování, mají shodně 500 prvků. Ovšem tyto prvky se pochopitelně od sebe odlišují:
500 500
Skript navíc vypíše prvních deset prvků trénovacích dat:
(tensor([0., 0., 0., 0., 0., 0., 0., 0., 0., 0., 0., 0., 0., 0., 0., 0., 0., 0., 1., 1., 1., 1., 1., 1., 0., 0., 0., 0., 0., 1., 1., 0., 0., 0., 0., 0., 1., 1., 0., 0., 0., 0., 0., 0., 0., 1., 1., 0., 0., 0., 1., 1., 0., 0., 1., 1., 0., 0., 0., 1., 1., 1., 1., 0.]), tensor([0., 0., 0., 1., 0., 0., 0., 0., 0., 0.])) (tensor([0., 0., 0., 0., 0., 0., 0., 0., 0., 0., 0., 0., 0., 0., 0., 0., 0., 0., 0., 1., 1., 1., 1., 0., 0., 0., 1., 1., 0., 0., 1., 1., 0., 0., 0., 0., 0., 1., 1., 0., 0., 0., 0., 0., 1., 1., 0., 0., 0., 0., 0., 1., 1., 0., 0., 0., 0., 0., 1., 1., 1., 1., 1., 1.]), tensor([0., 0., 1., 0., 0., 0., 0., 0., 0., 0.])) (tensor([0., 0., 0., 0., 0., 0., 0., 0., 0., 0., 0., 0., 0., 0., 0., 0., 0., 0., 0., 1., 1., 1., 1., 0., 0., 0., 1., 1., 0., 0., 1., 1., 0., 0., 0., 1., 1., 1., 1., 1., 0., 0., 0., 0., 0., 0., 1., 1., 0., 0., 0., 0., 0., 1., 1., 0., 0., 0., 0., 1., 1., 1., 0., 0.]), tensor([0., 0., 0., 0., 0., 0., 0., 0., 0., 1.])) ... ... ... (tensor([0., 0., 0., 0., 0., 0., 0., 0., 0., 0., 0., 0., 0., 0., 0., 0., 0., 0., 0., 1., 1., 1., 1., 0., 0., 0., 1., 1., 0., 0., 1., 1., 0., 0., 0., 0., 0., 1., 1., 0., 0., 0., 0., 0., 1., 1., 0., 0., 0., 0., 0., 1., 1., 0., 0., 0., 0., 0., 1., 1., 1., 1., 1., 1.]), tensor([0., 0., 1., 0., 0., 0., 0., 0., 0., 0.])) (tensor([0., 0., 0., 0., 0., 0., 0., 0., 0., 0., 0., 0., 0., 0., 0., 0., 0., 0., 0., 1., 1., 1., 1., 0., 0., 0., 1., 1., 0., 0., 1., 1., 0., 0., 1., 1., 0., 1., 1., 1., 0., 0., 1., 1., 1., 0., 1., 1., 0., 0., 1., 1., 0., 0., 1., 1., 0., 0., 0., 1., 1., 1., 1., 0.]), tensor([1., 0., 0., 0., 0., 0., 0., 0., 0., 0.])) (tensor([0., 0., 0., 0., 0., 0., 0., 0., 0., 0., 0., 0., 0., 0., 0., 0., 0., 0., 1., 1., 1., 1., 1., 1., 0., 0., 0., 0., 0., 1., 1., 0., 0., 0., 0., 0., 1., 1., 0., 0., 0., 0., 0., 0., 0., 1., 1., 0., 0., 0., 1., 1., 0., 0., 1., 1., 0., 0., 0., 1., 1., 1., 1., 0.]), tensor([0., 0., 0., 1., 0., 0., 0., 0., 0., 0.]))
9. Konstrukce neuronové sítě s vektorovým vstupem i výstupem
V dalším kroku (konečně) zkonstruujeme neuronovou síť, na jejíž vstup budeme posílat 64prvkové vektory s hodnotami zašuměných a posunutých obrázků s číslicemi a na jejímž výstupu bude desetiprvkový vektor, jehož nejvyšší prvek určí index číslice rozeznané touto sítí. Pro jednoduchost a pro rychlé učení bude síť obsahovat pouze jednu skrytou vrstvu. Jako aktivační funkci mezi vstupy a skrytou vrstvou vybereme ReLU a pro dvojici vrstev naopak sigmoid (později tento výběr můžeme kdykoli modifikovat):
class NeuralNetwork(nn.Module): """Třída reprezentující neuronovou síť.""" def __init__(self, input_dim, hidden_dim, output_dim): super().__init__() # vrstvy neuronové sítě self.layer_1 = nn.Linear(input_dim, hidden_dim) self.layer_2 = nn.Linear(hidden_dim, output_dim) def forward(self, x): # propagace hodnot přes neuronovou síť x = torch.nn.functional.relu(self.layer_1(x)) x = torch.nn.functional.sigmoid(self.layer_2(x)) return x
Vstupem jsou 64prvkové vektory, výstupem desetiprvkový vektor. Tím jsou přímo určeny počty neuronů na vstupu i neuronů ve výstupní vrstvě. Skrytá vrstva může mít libovolný počet neuronů. Zvolme hodnotu 10. Ta není ani velká (problém nedoučení) ani malá (problém „hloupé“ sítě s malým množstvím stavů):
# konfigurace vrstev neuronové sítě input_dim = 64 hidden_dim = 10 output_dim = 10 nn_64_10_10 = NeuralNetwork(input_dim, hidden_dim, output_dim) # výpis základních informací o neuronové síti print("Neural network:") print(nn_64_10_10)
Skript v této zkrácené podobě by měl vypsat strukturu neuronové sítě:
Neural network: NeuralNetwork( (layer_1): Linear(in_features=64, out_features=10, bias=True) (layer_2): Linear(in_features=10, out_features=10, bias=True) )
Pokud vás mate, že skrytá vrstva má stejný počet neuronů, jako vrstva výstupní, můžeme skrytou vrstvu rozšířit na 99 neuronů:
Neural network: NeuralNetwork( (layer_1): Linear(in_features=64, out_features=99, bias=True) (layer_2): Linear(in_features=99, out_features=10, bias=True) )
10. Trénink neuronové sítě
Neuronovou síť máme nyní zkonstruovanou, ovšem váhy na vstupech neuronů jsou prozatím nastaveny na náhodnou hodnotu. Síť je tedy nutné natrénovat. Pro tento účel si necháme vygenerovat trénovací a testovací data, což není nic nového, protože jsme se s tímto konceptem seznámili v šesté a sedmé kapitole:
train_data, test_data = prepare_data(digits, 1000, noise_level=0.0, x_shift_amount=0, y_shift_amount=0) print("Train data:") print(len(train_data)) print("Test data:") print(len(test_data))
Povšimněte si, že si sice necháme vygenerovat 1000 záznamů, ale vzhledem k tomu, že úroveň šumu je nulová a obrázky se neposunují, vlastně bude datová sada obsahovat každý záznam ve 100 identických kopiích:
Train data: 666 Test data: 334
Samotný trénink neuronové sítě taktéž není nová metoda. Na vstup sítě předáme jeden vektor vybraný z trénovacích dat, na výstup očekávaný výstup (taktéž vektor) a zpětným šířením chyby se poupraví váhy na vstupech jednotlivých neuronů:
# příprava na trénink neuronové sítě learning_rate = 0.1 loss_fn = nn.BCELoss() optimizer = optim.SGD(nn_64_10_10.parameters(), lr=learning_rate) # zpracovat trénovací data batch_size = 64 train_dataloader = DataLoader(dataset=train_data, batch_size=batch_size, shuffle=True) print("Batches: ", len(train_dataloader)) # vlastní trénink print("Training started") num_epochs = 100 loss_values = [] for epoch in range(num_epochs): print(f" Epoch {epoch}: ", end="") last_lost_value = None for X, y in train_dataloader: optimizer.zero_grad() # dopředný tok + zpětný tok + optimalizace pred = nn_64_10_10(X) # výpočet účelové funkce loss = loss_fn(pred, y) #loss = loss_fn(pred, y.unsqueeze(-1)) loss_values.append(loss.item()) loss.backward() optimizer.step() last_lost_value = loss.item() print(".", end="") print(last_lost_value) print("Training completed")
V průběhu učení by se postupně měla snižovat hodnota vypočtená účelovou funkcí. V ideálním případě by měla klesnout k nule:
Batches: 11 Training started Epoch 0: ...........0.6447266936302185 Epoch 1: ...........0.6193460822105408 Epoch 2: ...........0.587082028388977 Epoch 3: ...........0.5465092658996582 Epoch 4: ...........0.4956221878528595 Epoch 5: ...........0.44743767380714417 Epoch 6: ...........0.38974180817604065 Epoch 7: ...........0.3594760000705719 Epoch 8: ...........0.3338333070278168 Epoch 9: ...........0.3175426423549652 Epoch 10: ...........0.32316410541534424 Epoch 11: ...........0.32127857208251953 Epoch 12: ...........0.3148137927055359 Epoch 13: ...........0.3114672899246216 Epoch 14: ...........0.316869854927063 Epoch 15: ...........0.3137578070163727 Epoch 16: ...........0.3157559633255005 Epoch 17: ...........0.3102682828903198 Epoch 18: ...........0.3075677454471588 Epoch 19: ...........0.30908656120300293 Epoch 20: ...........0.3039756715297699 Epoch 21: ...........0.3026908338069916 Epoch 22: ...........0.30455878376960754 Epoch 23: ...........0.30317121744155884 Epoch 24: ...........0.29968011379241943 Epoch 25: ...........0.2984805703163147 Epoch 26: ...........0.2998690903186798 Epoch 27: ...........0.2988283932209015 Epoch 28: ...........0.28817668557167053 Epoch 29: ...........0.2917144298553467 Epoch 30: ...........0.2874159812927246 Epoch 31: ...........0.2909078598022461 Epoch 32: ...........0.27771422266960144 Epoch 33: ...........0.2821337580680847 Epoch 34: ...........0.28642821311950684 Epoch 35: ...........0.276723176240921 Epoch 36: ...........0.2759454548358917 Epoch 37: ...........0.27059292793273926 Epoch 38: ...........0.2683849036693573 Epoch 39: ...........0.268013060092926 Epoch 40: ...........0.2737174928188324 Epoch 41: ...........0.2644055187702179 Epoch 42: ...........0.26093563437461853 Epoch 43: ...........0.2565652132034302 Epoch 44: ...........0.25182580947875977 Epoch 45: ...........0.2551380693912506 Epoch 46: ...........0.2450740933418274 Epoch 47: ...........0.24896760284900665 Epoch 48: ...........0.24437251687049866 Epoch 49: ...........0.24140697717666626 Epoch 50: ...........0.23914998769760132 Epoch 51: ...........0.23454098403453827 Epoch 52: ...........0.23361629247665405 Epoch 53: ...........0.23802918195724487 Epoch 54: ...........0.223032146692276 Epoch 55: ...........0.2397080361843109 Epoch 56: ...........0.21921715140342712 Epoch 57: ...........0.21958164870738983 Epoch 58: ...........0.22020205855369568 Epoch 59: ...........0.2035556137561798 Epoch 60: ...........0.21375465393066406 Epoch 61: ...........0.1982254534959793 Epoch 62: ...........0.19214391708374023 Epoch 63: ...........0.2059931755065918 Epoch 64: ...........0.1918933093547821 Epoch 65: ...........0.20608077943325043 Epoch 66: ...........0.19234557449817657 Epoch 67: ...........0.1903819739818573 Epoch 68: ...........0.1945323795080185 Epoch 69: ...........0.1832999587059021 Epoch 70: ...........0.18331988155841827 Epoch 71: ...........0.1699090152978897 Epoch 72: ...........0.1823926419019699 Epoch 73: ...........0.17585024237632751 Epoch 74: ...........0.16324113309383392 Epoch 75: ...........0.17733824253082275 Epoch 76: ...........0.17202644050121307 Epoch 77: ...........0.16952469944953918 Epoch 78: ...........0.15982866287231445 Epoch 79: ...........0.16450724005699158 Epoch 80: ...........0.15805110335350037 Epoch 81: ...........0.16729946434497833 Epoch 82: ...........0.14237038791179657 Epoch 83: ...........0.16939890384674072 Epoch 84: ...........0.15275999903678894 Epoch 85: ...........0.14588870108127594 Epoch 86: ...........0.15782424807548523 Epoch 87: ...........0.15238074958324432

Obrázek 4: Postupně klesající hodnoty vypočítané účelovou funkcí naznačují korektní učení sítě – ovšem ne kvalitu jejich predikcí!
11. Čtvrtý skript: trénink neuronové sítě pro rozpoznávání rastrových obrázků
Postup popsaný v předchozích pěti kapitolách byl použit v dnešním čtvrtém skriptu, který vypadá takto:
import random from sklearn.model_selection import train_test_split import torch from torch import nn from torch import optim from torch.utils.data import Dataset, DataLoader import numpy as np import matplotlib.pyplot as plt class NeuralNetwork(nn.Module): """Třída reprezentující neuronovou síť.""" def __init__(self, input_dim, hidden_dim, output_dim): super().__init__() # vrstvy neuronové sítě self.layer_1 = nn.Linear(input_dim, hidden_dim) self.layer_2 = nn.Linear(hidden_dim, output_dim) def forward(self, x): # propagace hodnot přes neuronovou síť x = torch.nn.functional.relu(self.layer_1(x)) x = torch.nn.functional.sigmoid(self.layer_2(x)) return x # konfigurace vrstev neuronové sítě input_dim = 64 hidden_dim = 10 output_dim = 10 nn_64_10_10 = NeuralNetwork(input_dim, hidden_dim, output_dim) # výpis základních informací o neuronové síti print("Neural network:") print(nn_64_10_10) # konverze původních dat z NumPy do tenzorů class Data(Dataset): def __init__(self, X, y): self.X = torch.from_numpy(X.astype(np.float32)) self.y = torch.from_numpy(y.astype(np.float32)) self.len = self.X.shape[0] def __getitem__(self, index): return self.X[index], self.y[index] def __len__(self): return self.len # číslice reprezentované v masce 8x8 pixelů 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), ) def digit_to_array(digits, n): digit = digits[n] rows = [] # převod jednotlivých řádků na osmici bitů for scanline in digit: row = [] # převod bitmapy představující řádek na osmici bitů for _ in range(8): bit = scanline & 0x01 row.append(float(bit)) scanline >>= 1 rows.append(row) # transformace na n-dimenzionální pole return np.array(rows) def add_noise(array, level): return (1.0 - level) * array + level * np.random.rand(8, 8) def shift(arr, x_shift, y_shift): # horizontální posun arr = np.roll(arr, x_shift, axis=1) # výplně těch částí, které byly orotovány na druhou stranu if x_shift < 0: arr[:, x_shift:] = 0.0 elif x_shift > 0: arr[:, :x_shift] = 0.0 # vertikální posun arr = np.roll(arr, y_shift, axis=0) # výplně těch částí, které byly orotovány na druhou stranu if y_shift < 0: arr[y_shift:] = 0.0 elif y_shift > 0: arr[:y_shift] = 0.0 return arr def prepare_data(digits, length, noise_level=0.0, x_shift_amount=0, y_shift_amount=0, test_size=1/3): # příprava dat pro trénink a testování X = [] y = [] for i in range(length): # cislice digit = i % 10 # vstupy array = digit_to_array(digits, digit) # zasumeni array = add_noise(array, noise_level) # posuny x_shift = random.randint(-x_shift_amount, x_shift_amount) y_shift = random.randint(-y_shift_amount, y_shift_amount) array = shift(array, x_shift, y_shift) # prevod na vektor x_vector = array.flatten() X.append(x_vector) # očekávané výstupy y_vector = [0.0] * 10 y_vector[digit] = 1.0 y.append(y_vector) X_train, X_test, y_train, y_test = train_test_split( np.array(X), np.array(y), test_size=test_size, random_state=26 ) # trénovací sada train_data = Data(X_train, y_train) # testovací sada test_data = Data(X_test, y_test) return train_data, test_data train_data, test_data = prepare_data(digits, 1000, noise_level=0.0, x_shift_amount=0, y_shift_amount=0) print("Train data:") print(len(train_data)) print("Test data:") print(len(test_data)) # příprava na trénink neuronové sítě learning_rate = 0.1 loss_fn = nn.BCELoss() optimizer = optim.SGD(nn_64_10_10.parameters(), lr=learning_rate) # zpracovat trénovací data batch_size = 64 train_dataloader = DataLoader(dataset=train_data, batch_size=batch_size, shuffle=True) print("Batches: ", len(train_dataloader)) # vlastní trénink print("Training started") num_epochs = 100 loss_values = [] for epoch in range(num_epochs): print(f" Epoch {epoch}: ", end="") last_lost_value = None for X, y in train_dataloader: optimizer.zero_grad() # dopředný tok + zpětný tok + optimalizace pred = nn_64_10_10(X) # výpočet účelové funkce loss = loss_fn(pred, y) #loss = loss_fn(pred, y.unsqueeze(-1)) loss_values.append(loss.item()) loss.backward() optimizer.step() last_lost_value = loss.item() print(".", end="") print(last_lost_value) print("Training completed") step = range(len(loss_values)) # příprava na vykreslení grafu fig, ax = plt.subplots(figsize=(6.4, 4.8)) plt.plot(step, np.array(loss_values)) plt.title("Průběh tréninku neuronové sítě") plt.xlabel("Epocha") plt.ylabel("Chyba") # uložení do souboru plt.savefig("nn_16.png") # vykreslení grafu plt.show()
12. Zašuměné obrázky v trénovacích a testovacích datech
Naprosto stejnou neuronovou síť nyní natrénujeme s využitím zašuměných obrázků. A nutno říci, že úroveň šumu je nastavena poměrně vysoko – na 50%. To může v některých případech znamenat, že obrázky nebudou dobře rozeznatelné ani tou nejlepší neuronovou sítí pro rozpoznání obrazu – lidským mozkem:
train_data, test_data = prepare_data(digits, 1000, noise_level=0.5, x_shift_amount=0, y_shift_amount=0)
Průběh učení naznačuje, že hodnoty účelové funkce již neklesnou na hodnotu 0,15 tak, jako v příkladu předchozím:
Batches: 11 Training started Epoch 0: ...........0.6567670106887817 Epoch 1: ...........0.6100022792816162 Epoch 2: ...........0.5560295581817627 Epoch 3: ...........0.491263747215271 Epoch 4: ...........0.4276190996170044 Epoch 5: ...........0.39090630412101746 Epoch 6: ...........0.3492359519004822 Epoch 7: ...........0.34494543075561523 Epoch 8: ...........0.3371904492378235 Epoch 9: ...........0.3227657973766327 Epoch 10: ...........0.32668638229370117 ... ... ... Epoch 90: ...........0.28734317421913147 Epoch 91: ...........0.28829020261764526 Epoch 92: ...........0.29428786039352417 Epoch 93: ...........0.27999159693717957 Epoch 94: ...........0.27175065875053406 Epoch 95: ...........0.2788369059562683 Epoch 96: ...........0.2692151367664337 Epoch 97: ...........0.28449100255966187 Epoch 98: ...........0.27109402418136597 Epoch 99: ...........0.28655123710632324 Training completed
Totéž je ostatně velmi dobře patrné i ve vizualizovaném průběhu účelové funkce:

Obrázek 5: Postupně klesající hodnoty vypočítané účelovou funkcí naznačují korektní učení sítě. Nyní je ovšem učení pomalejší a model bude obsahovat chyby v predikci.
Vliv větší úrovně šumu (70%):

Obrázek 6: Postupně klesající hodnoty vypočítané účelovou funkcí naznačují korektní učení sítě.
13. Pátý skript: trénink neuronové sítě pro rozpoznávání rastrových obrázků s využitím zašuměných obrázků
Jen pro úplnost si ukažme celý skript, po jehož spuštění se neuronová síť natrénuje a následně i otestuje s využitím zašuměných obrázků:
import random from sklearn.model_selection import train_test_split import torch from torch import nn from torch import optim from torch.utils.data import Dataset, DataLoader import numpy as np import matplotlib.pyplot as plt class NeuralNetwork(nn.Module): """Třída reprezentující neuronovou síť.""" def __init__(self, input_dim, hidden_dim, output_dim): super().__init__() # vrstvy neuronové sítě self.layer_1 = nn.Linear(input_dim, hidden_dim) self.layer_2 = nn.Linear(hidden_dim, output_dim) def forward(self, x): # propagace hodnot přes neuronovou síť x = torch.nn.functional.relu(self.layer_1(x)) x = torch.nn.functional.sigmoid(self.layer_2(x)) return x # konfigurace vrstev neuronové sítě input_dim = 64 hidden_dim = 10 output_dim = 10 nn_64_10_10 = NeuralNetwork(input_dim, hidden_dim, output_dim) # výpis základních informací o neuronové síti print("Neural network:") print(nn_64_10_10) # konverze původních dat z NumPy do tenzorů class Data(Dataset): def __init__(self, X, y): self.X = torch.from_numpy(X.astype(np.float32)) self.y = torch.from_numpy(y.astype(np.float32)) self.len = self.X.shape[0] def __getitem__(self, index): return self.X[index], self.y[index] def __len__(self): return self.len # číslice reprezentované v masce 8x8 pixelů 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), ) def digit_to_array(digits, n): digit = digits[n] rows = [] # převod jednotlivých řádků na osmici bitů for scanline in digit: row = [] # převod bitmapy představující řádek na osmici bitů for _ in range(8): bit = scanline & 0x01 row.append(float(bit)) scanline >>= 1 rows.append(row) # transformace na n-dimenzionální pole return np.array(rows) def add_noise(array, level): return (1.0 - level) * array + level * np.random.rand(8, 8) def shift(arr, x_shift, y_shift): # horizontální posun arr = np.roll(arr, x_shift, axis=1) # výplně těch částí, které byly orotovány na druhou stranu if x_shift < 0: arr[:, x_shift:] = 0.0 elif x_shift > 0: arr[:, :x_shift] = 0.0 # vertikální posun arr = np.roll(arr, y_shift, axis=0) # výplně těch částí, které byly orotovány na druhou stranu if y_shift < 0: arr[y_shift:] = 0.0 elif y_shift > 0: arr[:y_shift] = 0.0 return arr def prepare_data(digits, length, noise_level=0.0, x_shift_amount=0, y_shift_amount=0, test_size=1/3): # příprava dat pro trénink a testování X = [] y = [] for i in range(length): # cislice digit = i % 10 # vstupy array = digit_to_array(digits, digit) # zasumeni array = add_noise(array, noise_level) # posuny x_shift = random.randint(-x_shift_amount, x_shift_amount) y_shift = random.randint(-y_shift_amount, y_shift_amount) array = shift(array, x_shift, y_shift) # prevod na vektor x_vector = array.flatten() X.append(x_vector) # očekávané výstupy y_vector = [0.0] * 10 y_vector[digit] = 1.0 y.append(y_vector) X_train, X_test, y_train, y_test = train_test_split( np.array(X), np.array(y), test_size=test_size, random_state=26 ) # trénovací sada train_data = Data(X_train, y_train) # testovací sada test_data = Data(X_test, y_test) return train_data, test_data train_data, test_data = prepare_data(digits, 1000, noise_level=0.5, x_shift_amount=0, y_shift_amount=0) print("Train data:") print(len(train_data)) print("Test data:") print(len(test_data)) # příprava na trénink neuronové sítě learning_rate = 0.1 loss_fn = nn.BCELoss() optimizer = optim.SGD(nn_64_10_10.parameters(), lr=learning_rate) # zpracovat trénovací data batch_size = 64 train_dataloader = DataLoader(dataset=train_data, batch_size=batch_size, shuffle=True) print("Batches: ", len(train_dataloader)) # vlastní trénink print("Training started") num_epochs = 100 loss_values = [] for epoch in range(num_epochs): print(f" Epoch {epoch}: ", end="") last_lost_value = None for X, y in train_dataloader: optimizer.zero_grad() # dopředný tok + zpětný tok + optimalizace pred = nn_64_10_10(X) # výpočet účelové funkce loss = loss_fn(pred, y) #loss = loss_fn(pred, y.unsqueeze(-1)) loss_values.append(loss.item()) loss.backward() optimizer.step() last_lost_value = loss.item() print(".", end="") print(last_lost_value) print("Training completed") step = range(len(loss_values)) # příprava na vykreslení grafu fig, ax = plt.subplots(figsize=(6.4, 4.8)) plt.plot(step, np.array(loss_values)) plt.title("Průběh tréninku neuronové sítě") plt.xlabel("Epocha") plt.ylabel("Chyba") # uložení do souboru plt.savefig("nn_17.png") # vykreslení grafu plt.show()
14. Posunuté obrázky v trénovacích a testovacích datech
Ověřme si, jak dobře či špatně se neuronová síť dokáže naučit a predikovat posunuté obrázky. Úroveň šumu vynulujeme a povolíme posun obrázků v obou směrech, ovšem maximálně o dva pixely:
train_data, test_data = prepare_data(digits, 1000, noise_level=0.0, x_shift_amount=2, y_shift_amount=2)
Průběh učení sítě:
Training started Epoch 0: ...........0.662143349647522 Epoch 1: ...........0.6225242018699646 Epoch 2: ...........0.5437997579574585 Epoch 3: ...........0.47505685687065125 Epoch 4: ...........0.4151655435562134 Epoch 5: ...........0.3567219078540802 Epoch 6: ...........0.35883376002311707 Epoch 7: ...........0.3546479344367981 Epoch 8: ...........0.33377087116241455 Epoch 9: ...........0.33360761404037476 Epoch 10: ...........0.32562145590782166 ... ... ... Epoch 70: ...........0.31016451120376587 Epoch 71: ...........0.32512548565864563 Epoch 72: ...........0.3208373486995697 Epoch 73: ...........0.320453405380249 Epoch 74: ...........0.3224222660064697 Epoch 75: ...........0.30925461649894714 Epoch 76: ...........0.3184351325035095 Epoch 77: ...........0.3149249255657196 Epoch 78: ...........0.3213757276535034
Predikce sítě se pravděpodobně ještě více zhorší:

Obrázek 7: Postupně klesající hodnoty vypočítané účelovou funkcí naznačují korektní učení sítě. Nyní je učení ještě pomalejší a model bude obsahovat větší chyby v predikci.
Výsledky pro náhodný posun o ± tři pixely:

Obrázek 8: Postupně klesající hodnoty vypočítané účelovou funkcí naznačují korektní učení sítě.
15. Šestý skript: trénink neuronové sítě pro rozpoznání posunutých obrázků
Opět si, podobně jako ve třinácté kapitole, pro úplnost ukažme celý skript, po jehož spuštění se neuronová síť natrénuje a následně i otestuje s využitím zašuměných obrázků:
import random from sklearn.model_selection import train_test_split import torch from torch import nn from torch import optim from torch.utils.data import Dataset, DataLoader import numpy as np import matplotlib.pyplot as plt class NeuralNetwork(nn.Module): """Třída reprezentující neuronovou síť.""" def __init__(self, input_dim, hidden_dim, output_dim): super().__init__() # vrstvy neuronové sítě self.layer_1 = nn.Linear(input_dim, hidden_dim) self.layer_2 = nn.Linear(hidden_dim, output_dim) def forward(self, x): # propagace hodnot přes neuronovou síť x = torch.nn.functional.relu(self.layer_1(x)) x = torch.nn.functional.sigmoid(self.layer_2(x)) return x # konfigurace vrstev neuronové sítě input_dim = 64 hidden_dim = 10 output_dim = 10 nn_64_10_10 = NeuralNetwork(input_dim, hidden_dim, output_dim) # výpis základních informací o neuronové síti print("Neural network:") print(nn_64_10_10) # konverze původních dat z NumPy do tenzorů class Data(Dataset): def __init__(self, X, y): self.X = torch.from_numpy(X.astype(np.float32)) self.y = torch.from_numpy(y.astype(np.float32)) self.len = self.X.shape[0] def __getitem__(self, index): return self.X[index], self.y[index] def __len__(self): return self.len # číslice reprezentované v masce 8x8 pixelů 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), ) def digit_to_array(digits, n): digit = digits[n] rows = [] # převod jednotlivých řádků na osmici bitů for scanline in digit: row = [] # převod bitmapy představující řádek na osmici bitů for _ in range(8): bit = scanline & 0x01 row.append(float(bit)) scanline >>= 1 rows.append(row) # transformace na n-dimenzionální pole return np.array(rows) def add_noise(array, level): return (1.0 - level) * array + level * np.random.rand(8, 8) def shift(arr, x_shift, y_shift): # horizontální posun arr = np.roll(arr, x_shift, axis=1) # výplně těch částí, které byly orotovány na druhou stranu if x_shift < 0: arr[:, x_shift:] = 0.0 elif x_shift > 0: arr[:, :x_shift] = 0.0 # vertikální posun arr = np.roll(arr, y_shift, axis=0) # výplně těch částí, které byly orotovány na druhou stranu if y_shift < 0: arr[y_shift:] = 0.0 elif y_shift > 0: arr[:y_shift] = 0.0 return arr def prepare_data(digits, length, noise_level=0.0, x_shift_amount=0, y_shift_amount=0, test_size=1/3): # příprava dat pro trénink a testování X = [] y = [] for i in range(length): # cislice digit = i % 10 # vstupy array = digit_to_array(digits, digit) # zasumeni array = add_noise(array, noise_level) # posuny x_shift = random.randint(-x_shift_amount, x_shift_amount) y_shift = random.randint(-y_shift_amount, y_shift_amount) array = shift(array, x_shift, y_shift) # prevod na vektor x_vector = array.flatten() X.append(x_vector) # očekávané výstupy y_vector = [0.0] * 10 y_vector[digit] = 1.0 y.append(y_vector) X_train, X_test, y_train, y_test = train_test_split( np.array(X), np.array(y), test_size=test_size, random_state=26 ) # trénovací sada train_data = Data(X_train, y_train) # testovací sada test_data = Data(X_test, y_test) return train_data, test_data train_data, test_data = prepare_data(digits, 1000, noise_level=0.0, x_shift_amount=2, y_shift_amount=2) print("Train data:") print(len(train_data)) print("Test data:") print(len(test_data)) # příprava na trénink neuronové sítě learning_rate = 0.1 loss_fn = nn.BCELoss() optimizer = optim.SGD(nn_64_10_10.parameters(), lr=learning_rate) # zpracovat trénovací data batch_size = 64 train_dataloader = DataLoader(dataset=train_data, batch_size=batch_size, shuffle=True) print("Batches: ", len(train_dataloader)) # vlastní trénink print("Training started") num_epochs = 100 loss_values = [] for epoch in range(num_epochs): print(f" Epoch {epoch}: ", end="") last_lost_value = None for X, y in train_dataloader: optimizer.zero_grad() # dopředný tok + zpětný tok + optimalizace pred = nn_64_10_10(X) # výpočet účelové funkce loss = loss_fn(pred, y) #loss = loss_fn(pred, y.unsqueeze(-1)) loss_values.append(loss.item()) loss.backward() optimizer.step() last_lost_value = loss.item() print(".", end="") print(last_lost_value) print("Training completed") step = range(len(loss_values)) # příprava na vykreslení grafu fig, ax = plt.subplots(figsize=(6.4, 4.8)) plt.plot(step, np.array(loss_values)) plt.title("Průběh tréninku neuronové sítě") plt.xlabel("Epocha") plt.ylabel("Chyba") # uložení do souboru plt.savefig("nn_18.png") # vykreslení grafu plt.show()
16. Matice záměn pro síť, která odpovídá vždy korektně
To, že účelová funkce klesá k nule, sice znamená, že se síť učí (a to bez velkých problémů), ovšem její skutečné vlastnosti otestujeme jedině tak, že síti předáme pro ni neznámá testovací data, tedy v našem případě takové obrázky, která síť předtím „neviděla“. Tato testovací data již máme připravena v proměnné test_data, takže test sítě můžeme velmi snadno provést následujícím kódem. Povšimněte si, že se zde volá funkce torch.topk, která vrátí indexy k největších prvků v tenzoru. V našem případě potřebujeme index největšího prvku, protože síť vrací desetiprvkový vektor, jehož hodnoty říkají, do jaké míry se obrázek na vstupu podobá jednotlivým číslicím. Například pokud tento vektor bude roven [0.0, 0.9, 0.4, 0.3, …], velmi pravděpodobně je na vstupu obrázek číslice 1. Do seznamu y_pred se ukládají číslice predikované sítí, do seznamu y_test pak správné hodnoty:
# otestování neuronové sítě test_dataloader = DataLoader(dataset=test_data, batch_size=1, shuffle=True) y_pred = [] y_test = [] with torch.no_grad(): for X, y in test_dataloader: outputs = nn_64_10_10(X) correct_value = int(torch.topk(y, 1).indices[0][0]) predicted_value = int(torch.topk(outputs, 1).indices[0][0]) print(correct_value, predicted_value) y_test.append(correct_value) y_pred.append(predicted_value) # výpočet matice záměn disp = ConfusionMatrixDisplay.from_predictions( y_test, y_pred, cmap=plt.cm.Blues, normalize=None, ) # zobrazení matice záměn print(disp.confusion_matrix) # uložení výsledků plt.savefig("confusion_matrix.png") # vykreslení matice záměn plt.show()
Pro síť, která je naučena na klasifikaci nezašuměných obrázků (a je na těchto obrázcích i otestována) bude matice záměn obsahovat nenulové prvky jen na hlavní diagonále. To značí, že je síť na 100% úspěšná:

Obrázek 9: Matice záměn pro síť, která odpovídá vždy korektně.
Ovšem pozor – v případě, že budou trénovací data obsahovat malé množství obrázků (například jen 100 a nikoli 600), hrozí problém nedoučení sítě. Potom výsledky vypadají takto:

Obrázek 10: Matice záměn pro nedoučenou síť.
17. Matice záměn pro zašuměné obrázky
Nyní se podívejme, jak bude vypadat kvalita odpovědí sítě v případě, že obrázky v trénovací i testovací sadě budou zašuměny. Úroveň šumu se postupně zvyšuje od 25% přes 50% až do 75% (což je hodně – takové obrázky nerozpozná ani člověk – viz též obrázek z úvodní kapitoly).

Obrázek 11: Matice záměn pro obrázky zašuměné z 25%. Síť odpovídá korektně.

Obrázek 12: Matice záměn pro obrázky zašuměné z 50%. Vysoké hodnoty na hlavní diagonále značí korektní odpovědi, ovšem je patrné, že například číslice 6 není rozeznána nikdy. Nejlépe síť rozeznává číslice 1, 4 a 7.

Obrázek 13: Matice záměn pro obrázky zašuměné ze 75%. Nyní jsou odpovědi zcela náhodné a tudíž nepoužitelné.
18. Matice záměn pro posunuté obrázky
Na závěr si ukažme matice záměn pro síť, která by měla umět predikovat obrázky posunuté o jeden či o dva pixely:

Obrázek 14: Matice záměn pro číslice posunuté maximálně o jeden pixel.

Obrázek 15: Matice záměn pro číslice posunuté maximálně o dva pixely.
Výsledky jsou zajímavé. Při pohledu na klesající účelovou funkci by se mohlo zdát, že je síť naučena relativně kvalitně, ovšem její odpovědi jsou opět prakticky zcela nepoužitelné. A právě zde se ukazuje teoretický i praktický limit běžných neuronových sítí při práci s jednorozměrnými nebo dvourozměrnými daty. Pro předpovědi na základě těchto dat potřebujeme lepší mechanismus – a tím jsou konvoluční neuronové sítě, které dokážou vliv posunu (a dalších transformací) do značné míry eliminovat.
19. Repositář s demonstračními příklady
Všechny demonstrační příklady využívající knihovnu PyTorch lze nalézt v repositáři https://github.com/tisnik/most-popular-python-libs. Následují odkazy na jednotlivé příklady:
20. Odkazy na Internetu
- Seriál Programovací jazyk Lua na Rootu:
https://www.root.cz/serialy/programovaci-jazyk-lua/ - PDM: moderní správce balíčků a virtuálních prostředí Pythonu:
https://www.root.cz/clanky/pdm-moderni-spravce-balicku-a-virtualnich-prostredi-pythonu/ - PyTorch Tutorial: Building a Simple Neural Network From Scratch
https://www.datacamp.com/tutorial/pytorch-tutorial-building-a-simple-neural-network-from-scratch - Interní reprezentace numerických hodnot: od skutečného počítačového pravěku po IEEE 754–2008:
https://www.root.cz/clanky/interni-reprezentace-numerickych-hodnot-od-skutecneho-pocitacoveho-praveku-po-ieee-754–2008/ - Interní reprezentace numerických hodnot: od skutečného počítačového pravěku po IEEE 754–2008 (dokončení):
https://www.root.cz/clanky/interni-reprezentace-numerickych-hodnot-od-skutecneho-pocitacoveho-praveku-po-ieee-754–2008-dokonceni/ - Brain Floating Point – nový formát uložení čísel pro strojové učení a chytrá čidla:
https://www.root.cz/clanky/brain-floating-point-ndash-novy-format-ulozeni-cisel-pro-strojove-uceni-a-chytra-cidla/ - Stránky projektu PyTorch:
https://pytorch.org/ - Informace o instalaci PyTorche:
https://pytorch.org/get-started/locally/ - Tenzor (Wikipedia):
https://cs.wikipedia.org/wiki/Tenzor - Introduction to Tensors:
https://www.youtube.com/watch?v=uaQeXi4E7gA - Introduction to Tensors: Transformation Rules:
https://www.youtube.com/watch?v=j6DazQDbEhQ - Tensor Attributes:
https://pytorch.org/docs/stable/tensor_attributes.html - Tensors Explained Intuitively: Covariant, Contravariant, Rank :
https://www.youtube.com/watch?v=CliW7kSxxWU - What is the relationship between PyTorch and Torch?:
https://stackoverflow.com/questions/44371560/what-is-the-relationship-between-pytorch-and-torch - What is a tensor anyway?? (from a mathematician):
https://www.youtube.com/watch?v=K7f2pCQ3p3U - Visualization of tensors – part 1 :
https://www.youtube.com/watch?v=YxXyN2ifK8A - Visualization of tensors – part 2A:
https://www.youtube.com/watch?v=A95jdIuUUW0 - Visualization of tensors – part 2B:
https://www.youtube.com/watch?v=A95jdIuUUW0 - What the HECK is a Tensor?!?:
https://www.youtube.com/watch?v=bpG3gqDM80w - Stránka projektu Torch
http://torch.ch/ - 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 - An Introduction to Tensors
https://math.stackexchange.com/questions/10282/an-introduction-to-tensors - 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? - Tensors for Neural Networks, Clearly Explained!!!:
https://www.youtube.com/watch?v=L35fFDpwIM4 - Tensor Processing Unit:
https://en.wikipedia.org/wiki/Tensor_Processing_Unit - Třída Storage:
http://docs.pytorch.wiki/en/storage.html - Funkce torch.dot
https://pytorch.org/docs/stable/generated/torch.dot.html#torch.dot - Funkce torch.narrow
https://pytorch.org/docs/stable/generated/torch.narrow.html - Funkce torch.matmul
https://pytorch.org/docs/stable/generated/torch.matmul.html - Funkce torch.reshape
https://pytorch.org/docs/stable/generated/torch.reshape.html - Funkce torch.arange
https://pytorch.org/docs/stable/generated/torch.arange.html - Funkce torch.range
https://pytorch.org/docs/stable/generated/torch.range.html - Třída torch.Tensor
https://pytorch.org/docs/stable/tensors.html - Atributy tenzorů
https://pytorch.org/docs/stable/tensor_attributes.html - Pohledy vytvořené nad tenzory
https://pytorch.org/docs/stable/tensor_view.html - Broadcasting v knihovně
https://numpy.org/doc/stable/user/basics.broadcasting.html - Broadcasting semantics (v knihovně PyTorch)
https://pytorch.org/docs/stable/notes/broadcasting.html - Dot Product In Physics: What Is The Physical Meaning of It?
https://profoundphysics.com/dot-product-in-physics-what-is-the-physical-meaning-of-it/ - scikit-learn: Getting Started
https://scikit-learn.org/stable/getting_started.html - Support Vector Machines
https://scikit-learn.org/stable/modules/svm.html - Use Deep Learning to Detect Programming Languages
http://searene.me/2017/11/26/use-neural-networks-to-detect-programming-languages/ - Data pro neuronové sítě
http://archive.ics.uci.edu/ml/index.php - 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 - AI vs Machine Learning (Youtube)
https://www.youtube.com/watch?v=4RixMPF4×is - Machine Learning | What Is Machine Learning? | Introduction To Machine Learning | 2024 | Simplilearn (Youtube)
https://www.youtube.com/watch?v=ukzFI9rgwfU - A Gentle Introduction to Machine Learning (Youtube)
https://www.youtube.com/watch?v=Gv9_4yMHFhI - Machine Learning vs Deep Learning
https://www.youtube.com/watch?v=q6kJ71tEYqM - Umělá inteligence (slajdy)
https://slideplayer.cz/slide/12119218/ - Úvod do umělé inteligence
https://slideplayer.cz/slide/2505525/ - Umělá inteligence I / Artificial Intelligence I
https://ktiml.mff.cuni.cz/~bartak/ui/ - Třída torch.nn.Linear
https://pytorch.org/docs/stable/generated/torch.nn.Linear.html - Třída torch.nn.Parameter
https://pytorch.org/docs/stable/generated/torch.nn.parameter.Parameter.html - Třída torch.nn.Sigmoid
https://pytorch.org/docs/stable/generated/torch.nn.Sigmoid.html