⚙️ Ovládání
🎨 Generátor
🔍 Diskriminátor
🧠 Architektura sítí:
Real vs Fake porovnání:
✨ Generované obrázky
💡 Co sleduješ:
- Na začátku G generuje šum
- D snadno rozpozná fake (100% přesnost)
- G se učí klamat, D se učí odhalovat
- Ideální stav: oba na ~50%
OpenTechLab Jablonec nad Nisou · Science Micro Elementary School
Naučili jsme se, že Decoder z autoencoderu umí vytvořit obrázek z malého vektoru. Co kdybychom ho naučili generovat nové obrázky bez originálu?
Latentní vektor → Obrázek
Umí rekonstruovat, co viděl
Náhodný šum → Nový obrázek
Učí se tvořit od nuly!
Obrázek → Třída (0-9)
Rozpoznává co vidí
Obrázek → Real/Fake
Rozpoznává padělky!
GAN využívá Decoder z autoencoderu jako svůj Generátor. Ale je tu klíčový rozdíl:
Klíčová změna: Místo rekonstrukce existujícího obrázku se G učí tvořit od nuly - přeměnit náhodný šum na realistický obrázek!
💡 Klíčový insight: V autoencoderu decoder věděl, co má generovat (rekonstruovat vstup). V GAN nemá decoder (G) žádný cíl - učí se ho od D! D říká "tohle vypadá fake" a G se přizpůsobuje.
4 náhodná čísla = "souřadnice v latentním prostoru". Každá kombinace 4 čísel vytvoří jiný obrázek!
1 číslo = pravděpodobnost, že obrázek je skutečný. Sigmoid aktivace → výstup vždy mezi 0 a 1.
💡 Klíčový insight: G transformuje 4D prostor → 64D prostor (rozbaluje šum na obrázek). D transformuje 64D prostor → 1D (komprimuje obrázek na jedno rozhodnutí). Jsou to opačné operace - proto má GAN tu zajímavou symetrii!
Vstup: Náhodný šum z
Výstup: Falešný obrázek
Cíl: Oklamat D (D(G(z)) → 1)
G se snaží oklamat
D se snaží odhalit
Min-Max hra
Vstup: Real nebo Fake obrázek
Výstup: P(real) ∈ [0,1]
Cíl: Správně klasifikovat (real→1, fake→0)
💡 Analogie: Padělatel (G) se učí kreslit bankovky. Detektiv (D) se učí rozpoznávat padělky. Čím lepší je detektiv, tím lepší musí být padělatel - a naopak. Když oba dosáhnou 50% úspěšnosti, padělky jsou k nerozeznání od originálu!
x = real obrázek, z = náhodný šum
D chce: D(x)→1 (real) a D(G(z))→0 (fake)
G chce: D(G(z))→1
(D si myslí, že fake je real)
D maximalizuje V (správně klasifikuje), G minimalizuje V (oklame D). V rovnováze: D(G(z)) = 0.5 pro všechny vzorky.
Nash equilibrium: Ani G ani D nemůže jednostranně zlepšit svou pozici. D jen hádá (50/50), protože fake jsou k nerozeznání od real!
Jeden jasný cíl: minimalizovat chybu.
Síť se přibližuje k "dokonalé" odpovědi.
Loss klesá → trénink úspěšný.
Dva protivníci, dva cíle.
Když jeden vítězí, druhý prohrává.
Loss osciluje → rovnováha = úspěch!
💡 Klíčový insight: V GAN hledáme rovnováhu, ne minimum. Ideální stav je, když D jen hádá (50/50), protože fake jsou k nerozeznání od real. To znamená, že obě sítě jsou stejně silné — a to je přesně to, co chceme! Proto sledujeme "Battle Score" místo ztráty.
G se naučí generovat jen jeden typ obrázku, který oklame D. Přestane explorovat - proč riskovat, když tohle funguje?
D je moc silný a vždy správně identifikuje fake. Gradient pro G je pak ~0 a G se nemůže učit.
Loss osciluje, D a G se "honí" - jakmile se jeden zlepší, druhý se zhorší. Nikdy nedosáhnou rovnováhy.
G produkuje vizuálně různé obrázky, ale všechny jsou ve skutečnosti podobné v latentním prostoru.
StyleGAN, DALL-E, Midjourney - tvorba realistických tváří, umění, fotografií. Neexistující lidé na thispersondoesnotexist.com.
Pix2Pix, CycleGAN - přeměna skic na fotky, koně na zebry, léto na zimu.
SRGAN - zvětšení rozlišení fotek a videí. Remaster starých filmů a her.
Výměna tváří ve videu, voice cloning. ⚠️ Etické obavy - dezinformace, manipulace.
function trainDiscriminator(realSample, lr) {
// 1. Trénuj na REAL (target = 1, s label smoothing = 0.8)
const realOut = D.forward(realSample);
const realError = realOut - 0.8;
D.backward([realError], lr);
// 2. Trénuj na FAKE (target = 0, s label smoothing = 0.2)
const z = sampleLatent();
const fake = G.forward(z);
const fakeOut = D.forward(fake);
const fakeError = fakeOut - 0.2;
D.backward([fakeError], lr);
return (realError**2 + fakeError**2) / 2;
}
function trainGenerator(lr) {
// G chce aby D řekl "tohle je real" (target = 1)
const z = sampleLatent();
const fake = G.forward(z);
const dOut = D.forward(fake);
// Chyba: jak daleko je D od 1?
const error = dOut - 1;
// Backprop přes D (bez update!) a pak přes G (s update)
const dGrad = D.computeGradient([error]); // jen gradient, ne update!
G.backward(dGrad, lr); // update G podle gradientu z D
return error ** 2;
}
function trainStep() {
const realSample = dataset[randomIndex()];
// Nejprve trénuj D (aby uměl rozlišovat)
const dLoss = trainDiscriminator(realSample, lr);
// Pak trénuj G (aby uměl klamat)
const gLoss = trainGenerator(lr);
// Sleduj skóre
updateStats(gLoss, dLoss);
}
function computeBattleScore(batchSize = 32) {
let pRealSum = 0;
for (let i = 0; i < batchSize; i++) {
const z = sampleLatent();
const fake = G.forward(z);
const pReal = D.forward(fake); // Jak moc D věří, že fake je real?
pRealSum += pReal;
}
const avgPReal = pRealSum / batchSize;
// Normalizace z rozsahu [0.2, 0.8] (label smoothing) na [0, 100]%
const genScore = Math.round(((avgPReal - 0.2) / 0.6) * 100);
return { genScore, discScore: 100 - genScore };
}