🔮 Autoencoder & Latent Space Explorer

OpenTechLab Jablonec nad Nisou · Science Micro Elementary School

Prozkoumejte kompresi dat a generování obsahu pomocí neuronových sítí

🎯 Co je to Autoencoder?

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í".

🔻 Encoder (komprese)

Vstup (784 pixelů) → Menší reprezentace (např. 2 čísla)

🔺 Decoder (rekonstrukce)

Malá reprezentace (2 čísla) → Výstup (784 pixelů)

⚙️ Ovládání


🏗️ Architektura

Oddělte čárkou (např. 128, 64)
Pro 2D vizualizaci nastav na 2!

🎓 Trénink

Přidat šum (Denoising)
Epocha: 0
Loss (MSE):
Vzorů: 0
📈 Historie Loss
Vstup 784
Bottleneck 2
Výstup 784
Komprese 392×
🔴 Live: Originál → Rekonstrukce

🔍 Inspekce

📥 Vstup
(Originál)
📤 Výstup (Rekonstrukce)

🗺️ Latentní prostor (Bottleneck = 2D)
Klikni do grafu pro generování nového obrázku!
✨ Generovaný obrázek
z = [?, ?]

🧠 Co je autoencoder? (Návaznost na MLP)

Autoencoder je obyčejné MLP, které už znáte z předchozích lekcí. Jediné dva rozdíly jsou:

1️⃣
Zúžený "pas" (Bottleneck)
Uprostřed sítě je vrstva s málo neurony
2️⃣
Target = Vstup
Síť se učí rekonstruovat to, co vidí

Self-Supervised Learning: Nepotřebujeme člověka, který by označoval data. "Správná odpověď" je vždy to samé, co vstup!

Porovnání s MLP z předchozích lekcí:
MLP Klasifikátor
784 → 128 → 64 → 10
Target: label (0-9)
Autoencoder
784 → 128 → 64 → 2 → 64 → 128 → 784
Target: vstup (784 pixelů)
Bottleneck = 2 neurony
= souřadnice [x, y] v 2D prostoru
Jako MLP Grayscale, ale s 2 výstupy!

💡 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!

🌉 Most mezi klasifikací a generováním

🏷️ Klasifikátor (MLP)

Vstup → Jedna odpověď
"Toto JE číslo 3"
Rozpoznává, ale neumí tvořit

🔮 Autoencoder

Vstup → Komprese → Rekonstrukce
"Takto VYPADÁ číslo 3"
Most mezi oběma světy

✨ Generativní AI

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!

🛠️ K čemu je autoencoder v praxi?

📉 Komprese dat

Místo 784 čísel stačí uložit 2! To je 392× komprese. Používá se pro kompresi obrázků, zvuku, a dalších dat.

🔍 Detekce anomálií

Síť natrénovaná na "normální" data neumí rekonstruovat anomálie. Vysoká chyba = podezřelý vzorek (podvod, defekt, útok).

🧹 Odšumování (Denoising)

Vstup = zašuměný obrázek, cíl = čistý obrázek. Bottleneck propustí jen "signál", šum se ztratí.

🗺️ Vizualizace vysokodimenzionálních dat

Bottleneck = 2 umožňuje nakreslit data do 2D grafu. Podobné vzory jsou blízko sebe → shluky!

🗺️ Co je latentní prostor?

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.

Latentní prostor (2D)
shluky číslic
🔴 Nuly   🔵 Jedničky   🟢 Dvojky

📐 Matematický přehled

🔻 Encoder (komprese)

z = fenc(x) = σ(W2 · σ(W1 · x + b1) + b2)

x = vstup (784), z = latentní vektor (2)

🔺 Decoder (rekonstrukce)

x̂ = fdec(z) = σ(W4 · σ(W3 · z + b3) + b4)

z = latentní vektor (2), x̂ = rekonstrukce (784)

📉 Loss funkce (MSE - Mean Squared Error)

L = (1/n) × Σ(xi - x̂i

Porovnáváme pixel po pixelu: originál vs. rekonstrukce. Čím menší rozdíl, tím lepší komprese.

⚠️ Informační bottleneck

784
vstup
2
bottleneck
784
výstup

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).

🤔 Proč loss neklesne pod ~0.03?

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.

📦
Bottleneck = 2
Loss ~0.03
📦📦
Bottleneck = 8
Loss ~0.01
📦📦📦
Bottleneck = 32
Loss ~0.001

Experimentujte! Změňte velikost bottlenecku a sledujte, jak se mění kvalita rekonstrukce.

💻 Implementační průvodce

1. Třída Autoencoder (konstruktor)
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));

        }

    }

}
2. Forward pass (komprese + rekonstrukce)
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];

}
3. Loss funkce (MSE)
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

}
4. Backpropagation
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];

            }

        }

    }

}
5. Generování z latentního prostoru
// 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

}

🎯 Shrnutí klíčových konceptů

Encoder
Komprese: 784 → 2
Bottleneck
Latentní prostor (2D)
Decoder
Rekonstrukce: 2 → 784
MSE Loss
Pixel-by-pixel porovnání