TAI BUI
Back to curriculum
BuildPython

StyleGAN

StyleGAN maps `z` to an intermediate `w`, then *injects* `w` at each resolution level through AdaIN. The split gives a latent space whose axes roughly separate coarse style from fine style.

TIME: ~45 minutesPREREQUISITES: Phase 8 · 03 (GANs), Phase 4 · 08 (Normalization), Phase 3 · 07 (CNNs)

Learning Objectives

  • Explain how a mapping network plus per-resolution AdaIN injection reduces the entanglement of a DCGAN-style latent.
  • Run a toy mapping network, AdaIN, and per-layer noise in pure Python, and say what each printout does and does not show.
  • Apply the truncation trick and choose psi to trade sample diversity against quality.
  • Compare what StyleGAN2 and StyleGAN3 fixed, and decide when StyleGAN (including inversion-based editing) beats diffusion.

The Problem

StyleGAN is a GAN generator that maps z to an intermediate w first, then injects w at each resolution level through AdaIN, instead of feeding z straight into the convolutions.

The motivation is entanglement. A DCGAN maps z to an image through a stack of transposed convolutions, and z controls pose, lighting, identity, and background all at once. Move along one axis of z and all four change, so you can't ask the model for "same person, different pose." The representation doesn't factor that way.

Karras et al. (2019, NVIDIA) changed the plumbing. The network starts from a constant 4×4×512 tensor, and, in the published model, an 8-layer MLP maps z ∈ Z to w ∈ W. Each resolution gets w through adaptive instance normalization (AdaIN): normalize each conv feature map, then scale and shift it with affine projections of w. Per-layer noise adds stochastic detail like skin pores and hair strands.

The result is that W has roughly orthogonal axes for high-level style (pose, identity) versus fine style (lighting, color). To swap styles between two images, use image A's w for the low-resolution levels and image B's w for the high ones. That made editing, cross-domain stylization, and the StyleGAN-inversion line of research possible.

Warm-up0/3
1. In a standard GAN such as DCGAN, what does the generator take as input?Warm-up
A generator box with an unlabelled dashed input on its left and a generated image coming out on its right.
Schematic. The input is deliberately left blank.
2. After normalizing a feature map by subtracting its mean and dividing by its standard deviation, what is true of it?Warm-up
A 2 by 2 feature map with values 2, 4, 6 and 8, whose mean is 5 and standard deviation about 2.236, goes into a step that subtracts the mean and divides by the standard deviation; the result is left as a question mark.
Illustrative values; mean and std computed in the script.
3. What is a multilayer perceptron (MLP)?Warm-up
The toy mapping network drawn as a chain: 8 input numbers z, four layers each computing W times h plus b followed by a leaky ReLU, and 8 output numbers w.
From main.py: depth=4, z_dim = w_dim = 8. The lesson calls this network an MLP.
Check: The Problem0/2
1. A team moves along one axis of a DCGAN's z and sees pose, lighting, and background all shift together. Which StyleGAN design choice targets this problem?Explain
A row of six z entries with the third, zk, highlighted as the only one changed; it goes into a DCGAN generator, and three boxes labelled pose, lighting and background are linked to the output, with a note that all three shift together.
Schematic of the situation in the stem.
2. You want the pose and identity of image A with the lighting and color of image B. Which use of their w vectors does StyleGAN's design allow?Apply
Two boxes, image A (want its pose and identity) and image B (want its lighting and color), above a row of nine synthesis blocks from resolution 4 to 1024 whose w source is left as a question mark under each.
Resolution blocks from the Concept section. The block assignment is left blank.

The Concept

StyleGAN: mapping network + AdaIN + per-layer noise

Mapping network. f: Z → W, an 8-layer MLP in the published model, with Z = N(0, I)^512. W isn't forced to be Gaussian. It learns a data-adapted shape.

Synthesis network. In the published model it starts from a learned constant 4×4×512. Each resolution block runs: upsample → conv → AdaIN(w_i) → noise → conv → AdaIN(w_i) → noise. The resolution doubles from block to block: 4, 8, 16, 32, 64, 128, 256, 512, 1024.

AdaIN.

AdaIN(x, y) = y_scale · (x - mean(x)) / std(x) + y_bias

where, in the published model, y_scale and y_bias come from per-channel affine projections of w. Normalize per feature map, then restyle. "Style" here means the first- and second-order statistics of the feature map.

Per-layer noise. Single-channel Gaussian noise is added to each feature map and scaled by a learned per-channel factor in the published model. It controls stochastic detail and leaves global structure alone.

Truncation trick. At inference, sample z, compute w = mapping(z), then w' = ŵ + ψ·(w - ŵ), where ŵ is the mean w over many samples. ψ < 1 trades diversity for quality. Most StyleGAN demos use ψ ≈ 0.7.

What the toy in main.py does instead. It keeps the data flow and drops almost everything else. The mapping network is 4 layers (depth=4), not 8, with z_dim = w_dim = 8. The constant is a 6-vector drawn once from gauss(0, 0.3), and nothing is ever trained. There are three synthesis blocks, each a matmul, a leaky ReLU, AdaIN, and noise, with no upsampling, convolutions, or resolution ladder. AdaIN's scale and bias are each one scalar per block, computed as a dot product of w with a fixed random vector, and the noise sigma is a single fixed scalar. Everything above in the published-model wording (8 layers, 512-d Z, 4×4×512, resolutions 4 to 1024, learned constant, per-channel parameters) describes the real StyleGAN, not the toy.

Check: The Concept0/10
1. Which statement about the mapping network in the published StyleGAN is correct?Define
In the published model, z from a 512-dimensional standard Gaussian goes into a mapping network f from Z to W, with the number of layers and the shape of W left as question marks, and its output w goes on to the synthesis network.
Published-model wording from the Concept section; the unknowns are what the question asks.
2. What is W space?Define
A sampled 8-number vector z passes through the toy's four-layer mapping network and comes out as an 8-number vector w, which then feeds the scale and bias of the three synthesis blocks.
Real values: the lesson's own functions, seed 3, first z sampled in main().
3. What does AdaIN do to a feature map?Define
The list 2, 4, 6, 8 goes into adain with scale 3 and bias 1, and the output list is shown: -3.025, -0.342, 2.342 and 5.025.
Real output of main.py's adain on illustrative inputs.
4. What does the truncation trick do at inference?Define
Terminal output of main.py: four lines for psi 0.0, 0.5, 0.7 and 1.0, each listing six output values.
Captured by running the lesson's main.py. The section heading line is left out.
5. What does per-layer noise contribute in StyleGAN?Define
A resolution block as two rows of steps with the two noise steps highlighted, above the add_noise formula: each element xi plus sigma times a standard Gaussian draw.
Block order from the Concept section; formula from the Build It snippet.
6. In the published model, what does the synthesis network start from?Define
A row of nine synthesis blocks labelled 4, 8, 16, 32, 64, 128, 256, 512 and 1024. A dashed box labelled start question mark feeds the first block, and a box w feeds every block with dotted lines.
Resolution ladder from the Concept section. The starting input is left blank.
7. Why does AdaIN normalize a feature map before applying the scale and bias from w?Explain
Two feature maps, A with values 2, 4, 6, 8 (mean 5, std 2.24) and B with values 10, 30, 50, 70 (mean 40, std 22.36), each go through the same adain call with scale 3 and bias 1; both outputs are left as question marks.
Illustrative inputs; statistics computed in the script.
8. Why does lowering psi trade diversity for quality?Explain
A square scatter of 60 blue dots, a 2-D sketch of sampled w vectors, with an orange dot at their mean, labelled ŵ.
Seeded 2-D illustration, not real StyleGAN latents.
9. A serving team sees odd artifacts in rare samples and will accept less variety for cleaner outputs. What should they change in the truncation trick?Apply
A bell-shaped curve over one axis of W with a dashed line at the mean w; the two thin tails are shaded red and labelled rare samples with artifacts.
Schematic; the tail cutoff is drawn for illustration. Artifacts in rare samples come from the stem.
10. The mean w is [1, 1], a sampled w is [5, 9], and psi is 0.5. What is the truncated w'?Apply
A 2-D grid from 0 to 10 with an orange point at the mean w, [1, 1], and a blue point at the sampled w, [5, 9]. Beside it, psi = 0.5 and w' = question mark.
Points plotted from the stem's numbers.

StyleGAN 1 → 2 → 3

VersionYearInnovation
StyleGAN2019Mapping network + AdaIN + noise + progressive growing.
StyleGAN22020Weight demodulation replaces AdaIN (fixes droplet artifacts); skip/residual architecture; path-length regularization.
StyleGAN32021Alias-free convolution + equivariant kernels; eliminates texture sticking to pixel grid.
StyleGAN-XL2022Class-conditional, 1024², ImageNet.
R3GAN2024Rebrands with stronger reg; closes gap to diffusion on FFHQ-1024 with 20x fewer params.

In 2026 StyleGAN3 remains the default for three jobs: (a) narrow-domain photorealism at high FPS, (b) few-shot domain adaptation (train on a new dataset with 100 images, freeze mapping), and (c) inversion-based editing (find the w that reconstructs a real photo, then edit that w). For open-domain text-to-image, reach for diffusion instead.

Check: StyleGAN 1 → 2 → 30/3
1. What does path-length regularization penalize?Define
Three boxes for Z, W and the image joined by the mapping f and the generator G; each box shows two nearby points with a bracket labelled delta z, delta w and delta x.
Schematic: a small step in each space.
2. What does weight demodulation do in StyleGAN2?Define
A StyleGAN 1 resolution block drawn as two rows: upsample, conv, AdaIN with w_i, noise, then conv, AdaIN with w_i, noise.
Block order from the Concept section. StyleGAN2 changes this block.
3. What does alias-free mean in StyleGAN3?Define
A fast sine wave with 9 cycles, sampled at 11 evenly spaced dots (10 per unit length); the dots also lie exactly on a slow orange dashed wave with 1 cycle.
Generic signal illustration of aliasing, computed here; not StyleGAN output.

Build It

code/main.py implements a toy "style-GAN lite" in 1-D with three parts: a mapping MLP, a synthesis function that takes a constant vector (drawn once, never trained) and modulates it with w-derived scale/bias, and per-layer noise. The toy shows the mechanics of mapping, AdaIN modulation, truncation, and noise. It has no concatenation baseline and does not train anything, so it can't tell you whether modulating with w beats concatenating z.

The snippets below are illustrative. They show the idea in a compact form and don't match main.py's signatures exactly: there, mapping(z, layers) takes a list of (W, b) pairs, adain(features, scale, bias) uses a mean_std helper, and noise is added inline in stylegan_forward.

Step 1: mapping network

python
def mapping(z, M):
    h = z
    for i in range(num_layers):
        h = leaky_relu(add(matmul(M[f"W{i}"], h), M[f"b{i}"]))
    return h

Step 2: adaptive instance normalization

python
def adain(x, w_scale, w_bias):
    mu = mean(x)
    sd = std(x)
    x_norm = [(xi - mu) / (sd + 1e-8) for xi in x]
    return [w_scale * xi + w_bias for xi in x_norm]

In the published model w supplies a per-channel scale and bias through a learned linear projection. In the toy, each block's scale and bias are single scalars, each a dot product of w with a fixed random vector.

Step 3: per-layer noise

python
def add_noise(x, sigma, rng):
    return [xi + sigma * rng.gauss(0, 1) for xi in x]

In the published model sigma is a learned per-channel factor. In the toy it is one fixed scalar noise_sigma, applied once per block.

Check: Build It0/7
1. Which parts of StyleGAN does the toy in main.py keep?Explain
The source of the toy's stylegan_forward function from main.py: a loop of three blocks, each a matmul with W and b, a leaky ReLU, optional AdaIN with a scale and bias dotted from w, and optional noise.
Copied from main.py by the script; the one-line docstring is left out.
2. In the toy `main.py`, what does the AdaIN on/off comparison actually print?Explain
Excerpt of main(): for each of two modes, five times it samples z with rng.gauss, maps it to w, runs stylegan_forward with adain_on set to the mode, and collects the output. The loop's last lines are elided.
Excerpt of main.py; the loop's final lines are left out on purpose.
3. In the toy's noise printout, w is fixed, sigma is 0.1, and three seeds are used. What does the printout confirm?Explain
Terminal output of main.py under the noise heading: three rows, for seeds 0, 1 and 2, each listing six output values that differ slightly from row to row.
Captured by running the lesson's main.py. The heading line and closing note are left out.
4. The `adain` snippet is called with x = [5, 5, 5], w_scale = 2 and w_bias = 0.5. What does it return?Apply
The lesson's adain snippet: mean, std, normalize with 1e-8 added to the std, then scale and add the bias. Below it, the call adain with x equal to 5, 5, 5, scale 2 and bias 0.5 and its return value left as a question mark.
Snippet from the Build It section; the call is the stem's.
5. A feature map is [1, 2, 3]. With w_scale = 3 and w_bias = 4, what is the mean of the `adain` output (ignoring the 1e-8)?Apply
The lesson's adain snippet, followed by the call adain with the list 1, 2, 3, scale 3 and bias 4, and a line computing the mean of the output with the value left as a question mark.
Snippet from the Build It section; the call is the stem's.
6. The `add_noise` snippet is called on x = [1, 2, 3] with sigma = 0. What does it return?Apply
The lesson's add_noise snippet, which returns each element plus sigma times a standard Gaussian draw, and the call add_noise with the list 1, 2, 3 and sigma 0, with the return value left as a question mark.
Snippet from the Build It section; the call is the stem's.
7. A teammate says the toy's AdaIN on/off printout proves AdaIN beats concatenating z. What is wrong with the claim?Analyze
A teammate's quote claiming the printout proves AdaIN beats concatenating z, above the real output of main.py: two heading lines and one mean-and-std line each for with AdaIN and no AdaIN.
Output captured by running the lesson's main.py.

Pitfalls

  • Droplet artifacts. StyleGAN 1 produced a blobby droplet in the feature maps because AdaIN zeroed out the mean. StyleGAN 2's weight demodulation fixes it by scaling the convolution weights instead.
  • Texture sticking. In StyleGAN 1 and 2, textures followed pixel coordinates instead of object coordinates, which shows when you interpolate. StyleGAN 3's alias-free convolutions fix this with windowed sinc filters.
  • Mode coverage. Truncation ψ < 0.7 looks clean but samples from a narrow cone. Use ψ = 1.0 when you need diversity.
  • Inversion is lossy. Inverting a real photo into W is usually done through optimization or an encoder (e4e, ReStyle, HyperStyle). Results drift over many iterations.
Check: Pitfalls0/5
1. What is inversion in the StyleGAN setting?Define
Top: a latent w goes through a trained generator G to give an image G(w). Below: a real photo x not made by G, with a dashed arrow running from the photo to an unknown box.
Schematic of the setting. The unknown is left blank.
2. What did StyleGAN2's weight demodulation fix?Explain
Three boxes in a row, StyleGAN 2019, StyleGAN2 2020 (highlighted) and StyleGAN3 2021, with the note that StyleGAN2 introduces weight demodulation and a dashed box asking what it fixed.
Years from the version table.
3. An avatar service's samples look clean but are nearly identical to each other. What is the most direct fix?Analyze
Two rows of four identical-looking simple round avatar faces with a note that they are clean but each looks like the last.
Schematic of the symptom in the stem, not generated samples.
4. While interpolating between two latents, fine hair texture stays pinned to screen positions while the face moves. Which change targets this?Analyze
Three frames of an interpolation at t equal 0, 0.5 and 1. A blue ring for the face moves from left to right while a grid of grey texture dots stays at the same positions in every frame.
Sketch of the symptom the stem describes, not StyleGAN output.
5. After many optimization iterations inverting a real photo into W, the result drifts and edits look off. What does this reflect?Analyze
A square sketch of W with a blue path of many small steps from an orange start point to a red end point labelled after many iterations, with notes that each step updates w to bring G(w) closer to the photo and edits are then made on the final w.
Schematic path, not a measured optimization run.

Use It

Use caseApproach
Photoreal human faces (anime, product, narrow)StyleGAN3 FFHQ / custom fine-tune
Face editing from a photoe4e inversion + StyleSpace / InterFaceGAN directions
Face swap / reenactmentStyleGAN + encoder + blending
Avatar pipelinesStyleGAN3 w/ ADA for low-data fine-tune
Domain adaptation from a few imagesFreeze mapping network, fine-tune synthesis
Multi-modal or text-conditioned generationDon't — use diffusion

When the product is a photo of a person's face, StyleGAN beats diffusion on inference cost (single forward pass, roughly 10 ms or less on a high-end GPU; an approximate figure that varies with hardware and implementation) and on sharpness at the same quality bar.

Check: Use It0/4
1. Why is StyleGAN cheaper than diffusion at inference for a face product?Explain
Two cards. StyleGAN3 producing a 1024 by 1024 FFHQ face: roughly 10 ms or less on a 4090, with no VAE decode and no cross-attention pass. An SDXL plus VAE-decode diffusion pipeline at the same resolution: on the order of a few seconds.
Approximate figures quoted from the lesson's Production note.
2. A product must let users change the apparent age of a face in their own uploaded photo. Which approach fits a StyleGAN pipeline?Design
A user's uploaded photo goes into an unknown pipeline, shown dashed, and comes out as the same face with a changed apparent age. A note says a trained StyleGAN with a mapping network and a synthesis network is available.
Schematic of the stem's requirement. The pipeline is left blank.
3. Which product is the best fit for StyleGAN3 rather than diffusion?Design
Two cards. StyleGAN3: input one random z, output one image, no text prompt input. Diffusion: input noise, often plus a text prompt, output one image, built up over many denoising steps.
Schematic of how each generator is driven.
4. A studio has about 100 images in a new art style and wants a generator built from a pretrained StyleGAN. Which approach fits?Design
A pretrained StyleGAN drawn as a mapping network feeding a synthesis network, beside a dashed box for about 100 images in a new art style. The goal is a generator for that style.
Schematic of the stem. Nothing is marked frozen or trained.

Ship It

Save outputs/skill-stylegan-inversion.md. The skill takes a real photo and outputs four things: the inversion method (e4e / ReStyle / HyperStyle), the expected latent loss, an editing budget (how far in W you can move before artifacts), and a list of known-good editing directions (age, expression, pose).

Author's Note

I'd treat main.py as a demo of the mechanics and not as evidence for the claims in the prose. Its mapping network is 4 layers deep where the Concept section says 8, and its constant is 6 numbers drawn once from a Gaussian and never trained. The AdaIN comparison prints the mean and std of five outputs with AdaIN on and off. The two modes draw different z samples, so the printed numbers aren't a paired comparison, and nothing in the file concatenates z.

My bet is the truncation printout is the one worth running. It builds ŵ from 200 sampled w vectors, then prints the output for one fixed w at ψ of 0.0, 0.5, 0.7, and 1.0. At ψ = 0.0 you're feeding the mean w itself, so the rows show the output moving from the mean-w result to the full-w result as ψ rises. It prints no diversity or quality measure, so the trade in the formula is something you'd have to add.

The noise printout is a weaker check than it looks. It holds w fixed and adds noise with sigma 0.1 under three seeds, so the outputs differ slightly by construction. All it confirms is that noise perturbs the output while w stays put.

Exercises

  1. Easy. Run code/main.py. main() already loops over both modes (adain_on is a parameter of stylegan_forward), so you get the AdaIN on/off printout in one run. Then change it to feed the same five z samples to both modes, and compare the spread of outputs for a fixed latent versus a perturbed latent.
  2. Medium. Implement mixing regularization. For a training batch, compute w_a and w_b, apply w_a for the first half of synthesis and w_b for the second half, and check whether the decoder learns disentangled styles.
  3. Hard. Take a pretrained StyleGAN3 FFHQ model (ffhq-1024.pkl). Find the w direction that controls "smile" by training an SVM on labelled samples, and report how far you can push before identity drifts.

Key Terms

TermWhat people sayWhat it actually means
Mapping network"The MLP"f: Z → W, 8 layers in the published model, decouples latent geometry from data statistics.
W space"The style space"Output of the mapping network; roughly disentangled.
AdaIN"Adaptive instance norm"Normalize feature map, then scale + shift by w-projection.
Truncation trick"Psi"w = mean + ψ·(w - mean), ψ<1 trades diversity for quality.
Path-length regularization"PL reg"Penalizes large changes in image per unit change in w; makes W smoother.
Weight demodulation"The StyleGAN2 fix"Normalize conv weights instead of activations; kills droplet artifacts.
Alias-free"StyleGAN3's trick"Windowed sinc filters; eliminates texture sticking to the pixel grid.
Inversion"Find w for a real image"Optimize or encode x → w so G(w) ≈ x.

Production note: why StyleGAN still ships in 2026

StyleGAN3 on a 4090 generates a 1024² FFHQ face in roughly 10 ms or less (an approximate, hardware-dependent figure) with num_steps = 1, no VAE decode, and no cross-attention pass. In production terms that's the floor latency for an image generator. A 50-step SDXL + VAE-decode pipeline at the same resolution takes on the order of a few seconds (again approximate). That is a gap of roughly two orders of magnitude, and for narrow-domain products (avatar services, ID document pipelines, stock face generation) it wins on TCO.

Two operational consequences:

  • No scheduler, no batcher. A static batch at the target occupancy is the right fit. Continuous batching, which matters for LLMs and diffusion, has little to offer when each request takes the same FLOPs.
  • Truncation ψ is the safety knob. ψ < 0.7 samples from a narrow cone of the mapping network's range, and that is the lever the serving layer has over sample variance. Lower ψ at peak load, raise it for premium users.

Further Reading

Code

Ships With