⚙️ Ovládání
🏗️ Architektura
🎓 Trénink
🔍 Inspekce
(Originál)
OpenTechLab Jablonec nad Nisou · Science Micro Elementary School
Prozkoumejte kompresi dat a generování obsahu pomocí neuronových sítí
Autoencoder je neuronová síť, která se učí zkomprimovat vstupní data do menší reprezentace a pak je zrekonstruovat zpět. Na rozdíl od klasifikátorů, který říká "toto je 3", autoencoder říká "takto vypadá ta 3 po průchodu kompresí".
Vstup (784 pixelů) → Menší reprezentace (např. 2 čísla)
Malá reprezentace (2 čísla) → Výstup (784 pixelů)
Autoencoder je obyčejné MLP, které už znáte z předchozích lekcí. Jediné dva rozdíly jsou:
Self-Supervised Learning: Nepotřebujeme člověka, který by označoval data. "Správná odpověď" je vždy to samé, co vstup!
💡 Klíčový insight: Latentní prostor vzniká přirozeně v bottlenecku! Když síť musí "protlačit" 784 pixelů přes 2 neurony a pak je zrekonstruovat, naučí se tyto 2 neurony používat jako souřadnice [x, y]. Podobné obrázky dostanou podobné souřadnice → vznikají shluky!
Vstup → Jedna odpověď
"Toto JE číslo 3"
Rozpoznává, ale neumí tvořit
Vstup → Komprese → Rekonstrukce
"Takto VYPADÁ číslo 3"
Most mezi oběma světy
Náhodný šum → Nový obsah
"Tady je NOVÉ číslo 3"
Tvoří, co nikdy neviděl
💡 Klíčový insight: Autoencoder je prvním krokem ke generativní AI. Zatímco klasifikátor mapuje obrázky na čísla (784 → 10), autoencoder mapuje obrázky na latentní prostor a zpět (784 → 2 → 784). Decoder už vlastně umí "generovat" - stačí mu dát libovolné souřadnice v latentním prostoru!
Místo 784 čísel stačí uložit 2! To je 392× komprese. Používá se pro kompresi obrázků, zvuku, a dalších dat.
Síť natrénovaná na "normální" data neumí rekonstruovat anomálie. Vysoká chyba = podezřelý vzorek (podvod, defekt, útok).
Vstup = zašuměný obrázek, cíl = čistý obrázek. Bottleneck propustí jen "signál", šum se ztratí.
Bottleneck = 2 umožňuje nakreslit data do 2D grafu. Podobné vzory jsou blízko sebe → shluky!
Latentní prostor je "vnitřní mapa" světa, kterou si síť vytvoří. Každý bod v tomto prostoru odpovídá jednomu možnému obrázku.
Bottleneck = 2 neurony:
Každý obrázek → bod [x, y]
Každý bod [x, y] → obrázek
Síť sama zjistí, že jedničky patří k sobě, nuly k sobě, atd. Nikdo jí to neřekl! Naučila se to sama z dat.
x = vstup (784), z = latentní vektor (2)
z = latentní vektor (2), x̂ = rekonstrukce (784)
Porovnáváme pixel po pixelu: originál vs. rekonstrukce. Čím menší rozdíl, tím lepší komprese.
Komprese 392×! Síť MUSÍ ztratit informace. Otázka je: které? Naučí se zachovat to důležité (tvar číslice) a zahodit to nepodstatné (šum, drobné variace).
Toto není chyba - je to fundamentální limit komprese! Když zmenšíte 784 čísel na 2, některé informace se prostě ztratí. Fyzicky není možné zakódovat všechny detaily.
Experimentujte! Změňte velikost bottlenecku a sledujte, jak se mění kvalita rekonstrukce.
class Autoencoder {
constructor(inputSize, encoderLayers, bottleneckSize, decoderLayers) {
// Sestavení vrstev: vstup → encoder → bottleneck → decoder → výstup
this.layers = [inputSize, ...encoderLayers, bottleneckSize, ...decoderLayers, inputSize];
// Inicializace vah (Xavier initialization)
this.weights = [];
this.biases = [];
for (let i = 0; i < this.layers.length - 1; i++) {
const scale = Math.sqrt(2.0 / (this.layers[i] + this.layers[i + 1]));
this.weights.push(randomMatrix(this.layers[i], this.layers[i + 1], scale));
this.biases.push(randomArray(this.layers[i + 1], 0.1));
}
}
}
forward(input) {
this.activations[0] = input;
for (let l = 0; l < this.weights.length; l++) {
const isOutput = (l === this.weights.length - 1);
const isBottleneck = (l === this.encoderLayers.length);
for (let j = 0; j < this.layers[l + 1]; j++) {
let sum = this.biases[l][j];
for (let i = 0; i < this.layers[l]; i++) {
sum += this.activations[l][i] * this.weights[l][i][j];
}
// Aktivace podle vrstvy
if (isOutput) {
this.activations[l + 1][j] = sigmoid(sum); // Výstup 0-1
} else if (isBottleneck) {
this.activations[l + 1][j] = sum; // Lineární (latentní prostor)
} else {
this.activations[l + 1][j] = relu(sum); // ReLU pro hidden
}
}
}
return this.activations[this.layers.length - 1];
}
calculateLoss(input, target) {
const output = this.forward(input);
let mse = 0;
for (let i = 0; i < output.length; i++) {
mse += Math.pow(target[i] - output[i], 2);
}
return mse / output.length; // Průměrná čtvercová chyba
}
backward(target, learningRate) {
const errors = [];
// 1. Chyba výstupní vrstvy
const outputIdx = this.layers.length - 1;
errors[outputIdx] = [];
for (let i = 0; i < this.outputSize; i++) {
const output = this.activations[outputIdx][i];
errors[outputIdx][i] = (target[i] - output) * sigmoidDerivative(output);
}
// 2. Propagace chyby zpět
for (let l = this.weights.length - 1; l >= 0; l--) {
if (l > 0) {
errors[l] = [];
for (let i = 0; i < this.layers[l]; i++) {
let error = 0;
for (let j = 0; j < this.layers[l + 1]; j++) {
error += this.weights[l][i][j] * errors[l + 1][j];
}
// Derivace podle typu vrstvy
if (l === this.encoderLayers.length + 1) {
errors[l][i] = error; // Bottleneck = lineární
} else {
errors[l][i] = error * reluDerivative(this.activations[l][i]);
}
}
}
// 3. Aktualizace vah
for (let i = 0; i < this.layers[l]; i++) {
for (let j = 0; j < this.layers[l + 1]; j++) {
this.weights[l][i][j] += learningRate * errors[l + 1][j] * this.activations[l][i];
}
}
}
}
// Kliknutí do 2D grafu → souřadnice v latentním prostoru
decode(latentVector) {
// Nastavíme bottleneck na kliknuté souřadnice
const bottleneckIdx = this.encoderLayers.length + 1;
this.activations[bottleneckIdx] = latentVector; // např. [0.5, -1.2]
// Forward pouze přes decoder
for (let l = bottleneckIdx; l < this.weights.length; l++) {
const isOutput = (l === this.weights.length - 1);
for (let j = 0; j < this.layers[l + 1]; j++) {
let sum = this.biases[l][j];
for (let i = 0; i < this.layers[l]; i++) {
sum += this.activations[l][i] * this.weights[l][i][j];
}
this.activations[l + 1][j] = isOutput ? sigmoid(sum) : relu(sum);
}
}
return this.activations[this.layers.length - 1]; // Vygenerovaný obrázek
}