Danh sách bài viết

Bài 41: Autoencoder — học biểu diễn nén dữ liệu

Mở Module 6 — Nền tảng Generative. Sau khi đã có MLP (Module 3), CNN (Module 4), RNN (Module 5), bốn bài cuối Series 3 chuyển sang generative model: Autoencoder → Denoising Autoencoder → Variational Autoencoder → GAN. Autoencoder là một neural network gồm hai phần học cùng nhau: encoder \( f: x \mapsto z \) nén input về latent vector \( z \) chiều thấp hơn, decoder \( g: z \mapsto \hat{x} \) tái tạo lại input từ \( z \). Mục tiêu là \( \hat{x} \approx x \) trên reconstruction loss \( \|x - g(f(x))\|^2 \). Cấu trúc bottleneck buộc network học một representation cô đọng giữ lại thông tin cốt yếu. Bài này dẫn ra kiến trúc, loss, biến thể (Deep, Convolutional, Sparse, Contractive), so sánh AE vs PCA, code PyTorch trên MNIST, các use case (anomaly detection, denoising, pretraining), giới hạn của AE cơ bản (latent space không có cấu trúc để sinh dữ liệu mới) — chính là motivation cho VAE (B43).

24/05/2026
13 phút đọc
2 lượt xem
1

Mục tiêu bài học

Sau bài học, bạn sẽ:

  • Vẽ được kiến trúc Autoencoder: encoder \( f \), latent \( z \), decoder \( g \).
  • Viết được reconstruction loss \( L = \|x - g(f(x))\|^2 \) và biết khi nào dùng MSE vs binary cross-entropy.
  • Phân biệt Linear AE, Deep AE, Convolutional AE, Sparse AE, Contractive AE.
  • Hiểu vì sao Linear AE với MSE tương đương PCA và Deep AE có thể vượt qua nhờ tính phi tuyến.
  • Code Autoencoder PyTorch trên MNIST với latent 32 chiều và train được bằng Adam.
  • Liệt kê 4 use case thực dụng: dimensionality reduction, denoising, anomaly detection, pretraining.
  • Giải thích vì sao AE cơ bản không sinh ra ảnh mới chất lượng — latent space không có cấu trúc — và đó là motivation của VAE (B43).

Bài này nối từ Bài 40 — Seq2Seq intuition (kết Module 5 RNN) và mở Module 6. Bài kế tiếp Bài 42 — Denoising Autoencoder mở rộng trực tiếp ý tưởng này.

2

Mở đầu Module 6 — Generative foundation

Bốn module trước trang bị backbone cho mọi neural network hiện đại:

  • Module 3 (B17–B25): MLP, forward/backward, optimizer, regularization — pipeline train cơ bản.
  • Module 4 (B26–B33): CNN cho ảnh — Conv, Pool, transfer learning, augmentation.
  • Module 5 (B34–B40): RNN/LSTM/GRU + Seq2Seq + attention cho dữ liệu chuỗi.

Module 6 (B41–B44) chuyển sang generative model — học phân phối của dữ liệu \( p(x) \) thay vì chỉ học mapping \( x \to y \). Bốn bài đi tuần tự, mỗi bài là tiền đề bài sau:

Bài Model Đóng góp chính
B41 Autoencoder (AE) Encoder–decoder, latent bottleneck, reconstruction loss.
B42 Denoising Autoencoder (DAE) Thêm noise vào input để học representation robust.
B43 Variational Autoencoder (VAE) Latent có cấu trúc xác suất → sample được dữ liệu mới.
B44 GAN intuition Generator vs Discriminator, adversarial training.

Phân biệt discriminative vs generative (B5 Series 2 đã đề cập sơ): discriminative học \( p(y \mid x) \) cho prediction; generative học \( p(x) \) hoặc \( p(x, y) \) để có thể sinh sample mới và biểu diễn dữ liệu. AE là cầu nối — bản thân không phải generative model thuần (không sample được tốt từ latent ngẫu nhiên), nhưng cung cấp kiến trúc encoder–decoder mà VAE, MAE, Diffusion đều kế thừa.

3

Autoencoder — định nghĩa

Autoencoder là neural network học hàm \( \mathrm{id}_x \approx g \circ f \) — tức là cố reproduce input chính nó qua một bottleneck chiều thấp. Hai thành phần:

  • Encoder \( f_\phi: \mathbb{R}^{d_x} \to \mathbb{R}^{d_z} \), tham số \( \phi \).
  • Decoder \( g_\theta: \mathbb{R}^{d_z} \to \mathbb{R}^{d_x} \), tham số \( \theta \).

Với \( d_z \ll d_x \) — đây là undercomplete autoencoder, dạng phổ biến nhất. Có cả overcomplete (\( d_z \ge d_x \)) nhưng phải kèm regularization (Sparse, Contractive, Denoising) vì nếu không network học identity tầm thường, không có ích.

Train không cần label — chỉ cần dataset input \( \{x^{(1)}, \ldots, x^{(N)}\} \). Đây là self-supervised learning: nhãn được tạo ra từ chính dữ liệu (target = input).

Lịch sử: ý tưởng autoencoder có từ Rumelhart, Hinton, Williams (1986) cùng với backpropagation, được đẩy mạnh trong giai đoạn 2006–2010 với stacked autoencoder cho pretraining deep network (Hinton & Salakhutdinov, 2006).

4

Kiến trúc: encoder, decoder, latent

Sơ đồ luồng dữ liệu:

  x ∈ R^784        z ∈ R^32           x̂ ∈ R^784
   ┌─────┐  encoder  ┌──┐    decoder    ┌─────┐
   │     │  ───────► │  │  ───────────► │     │
   │ 28×28│   f_φ    │ z│      g_θ      │28×28│
   │ MNIST│          │  │               │recon│
   └─────┘          └──┘               └─────┘
                  bottleneck

Với encoder MLP đơn giản cho ảnh MNIST flatten thành 784 chiều:

\[ z = f_\phi(x) = \sigma(W_2 \, \sigma(W_1 x + b_1) + b_2), \quad z \in \mathbb{R}^{32} \] \[ \hat{x} = g_\theta(z) = \sigma'(W_4 \, \sigma(W_3 z + b_3) + b_4), \quad \hat{x} \in \mathbb{R}^{784} \]

Một vài ghi chú:

  • Hidden activation thường là ReLU (B14).
  • Output activation phụ thuộc miền giá trị input: sigmoid cho ảnh normalize \( [0, 1] \), tanh cho ảnh normalize \( [-1, 1] \), linear (không activation) cho continuous data bất kỳ.
  • Encoder và decoder không bắt buộc đối xứng kích thước, nhưng giữ đối xứng giúp dễ thiết kế và recon ổn định.
  • \( z \) còn được gọi là code, latent representation, embedding, hay bottleneck.

Trực giác về bottleneck: nếu \( d_z = d_x \) và mạng linear, model có thể học \( g = f^{-1} \) (identity) — không học gì hữu ích. Bottleneck \( d_z \ll d_x \) buộc encoder vứt bỏ thông tin không cần thiết và giữ lại structure cốt lõi để decoder dùng để reconstruct.

5

Reconstruction loss

Tham số \( (\phi, \theta) \) được tối ưu để \( \hat{x} \) gần \( x \) trên toàn dataset:

\[ \mathcal{L}(\phi, \theta) = \frac{1}{N} \sum_{i=1}^{N} \ell\bigl(x^{(i)}, \, g_\theta(f_\phi(x^{(i)}))\bigr) \]

Hai lựa chọn loss phổ biến:

  • MSE cho continuous data hoặc ảnh normalize tuỳ ý: \[ \ell_{\mathrm{MSE}}(x, \hat{x}) = \|x - \hat{x}\|^2 = \sum_{j=1}^{d_x} (x_j - \hat{x}_j)^2 \]
  • Binary cross-entropy cho ảnh normalize \( [0, 1] \) (vd MNIST), giả định mỗi pixel là Bernoulli: \[ \ell_{\mathrm{BCE}}(x, \hat{x}) = -\sum_{j=1}^{d_x} \bigl[ x_j \log \hat{x}_j + (1 - x_j) \log(1 - \hat{x}_j) \bigr] \] Output activation phải là sigmoid để \( \hat{x}_j \in (0, 1) \).

BCE thường hội tụ nhanh hơn MSE trên ảnh nhị phân/gần nhị phân (MNIST) vì gradient mạnh ở vùng giá trị gần 0 và 1; MSE ổn định và dễ dùng cho continuous data nói chung. Cả hai đều dùng đại trà trong thực hành — chọn theo miền giá trị pixel.

6

Vì sao hữu ích

Một mạng học cách "copy" input nghe vô nghĩa, nhưng bottleneck biến nó thành công cụ học representation. Sáu ứng dụng chính:

  • Dimensionality reduction: \( z \) là biểu diễn chiều thấp của \( x \) — thay thế phi tuyến cho PCA (B36 Series 2). Dùng cho visualization, downstream classifier nhẹ.
  • Feature learning unsupervised: train AE trên dataset lớn không nhãn, lấy \( z \) làm feature cho task supervised có ít nhãn.
  • Denoising: train với input nhiễu, target sạch → mô hình học khôi phục cấu trúc gốc (B42).
  • Anomaly detection: train AE trên dữ liệu "bình thường"; khi gặp sample bất thường, reconstruction loss cao bất thường → flag.
  • Pretraining: trước khi có ImageNet pretrain, người ta dùng stacked autoencoder để khởi tạo trọng số deep network (Hinton 2006). Ngày nay ít dùng cho thị giác máy tính nhưng Masked Autoencoder (He và cộng sự, 2022) là biến thể đang đứng đầu nhiều benchmark.
  • Generative: bản thân AE không sinh tốt, nhưng VAE (B43) gắn cấu trúc xác suất vào latent để sinh dữ liệu mới.
7

PyTorch implementation

Skeleton Autoencoder MLP cho MNIST (784 → 32 → 784):

import torch.nn as nn

class Autoencoder(nn.Module):
    def __init__(self, input_dim=784, latent_dim=32):
        super().__init__()
        self.encoder = nn.Sequential(
            nn.Linear(input_dim, 256),
            nn.ReLU(),
            nn.Linear(256, latent_dim),
        )
        self.decoder = nn.Sequential(
            nn.Linear(latent_dim, 256),
            nn.ReLU(),
            nn.Linear(256, input_dim),
            nn.Sigmoid(),         # MNIST normalize [0, 1]
        )

    def forward(self, x):
        z = self.encoder(x)
        x_hat = self.decoder(z)
        return x_hat, z

Lưu ý implementation:

  • Input MNIST shape gốc \( (B, 1, 28, 28) \) — cần x.view(B, -1) để flatten trước khi đưa vào encoder MLP.
  • Sigmoid ở cuối decoder bắt cặp với BCE loss; nếu dùng MSE và muốn pixel \( [0, 1] \) thì giữ sigmoid, còn nếu input scaled \( [-1, 1] \) thì đổi sang tanh.
  • Return cả \( \hat{x} \) và \( z \) để tiện visualize / extract feature mà không phải forward lại encoder.
8

Variants — Deep, Sparse, Contractive

Biến thể Ý tưởng chính Khi nào dùng
Linear AE Encoder/decoder không có activation phi tuyến. Hiếm dùng thực tế; quan trọng vì tương đương PCA (mục 12).
Deep AE Nhiều layer ẩn, hidden activation phi tuyến. Default — học được representation phong phú hơn PCA.
Convolutional AE Encoder = Conv + Pool; decoder = ConvTranspose + Upsample. Ảnh, video — preserves cấu trúc không gian (mục 9).
Sparse AE Thêm \( \lambda \|z\|_1 \) hoặc KL với Bernoulli sparse vào loss. Học từng feature unit chuyên về 1 pattern; overcomplete \( d_z > d_x \) vẫn dùng được.
Contractive AE Penalize Frobenius norm của Jacobian: \( \|\partial f_\phi / \partial x\|_F^2 \). Latent ít nhạy với nhiễu nhỏ ở input (Rifai 2011).
Denoising AE Input = \( x + \) noise, target = \( x \). Bài tiếp theo (B42) — robust representation.

Sparse AE loss tổng quát:

\[ \mathcal{L}_{\mathrm{sparse}} = \underbrace{\|x - \hat{x}\|^2}_{\text{reconstruction}} + \lambda \underbrace{\|z\|_1}_{\text{sparsity}} \]

Penalty \( \ell_1 \) đẩy phần lớn unit của \( z \) về 0 — chỉ một số nhỏ unit kích hoạt cho mỗi input, tương tự sparse coding cổ điển.

9

Convolutional Autoencoder

MLP autoencoder flatten ảnh phá vỡ cấu trúc 2D. Với ảnh, dùng Convolutional Autoencoder:

  • Encoder: stack Conv2d + ReLU + (Pool hoặc stride-2 conv) → kích thước không gian giảm dần, số kênh tăng dần. Ví dụ MNIST: \( (1, 28, 28) \to (16, 14, 14) \to (32, 7, 7) \to z \).
  • Decoder: nn.ConvTranspose2d (transposed conv, còn gọi không chính xác là "deconv") hoặc nn.Upsample + Conv → tăng kích thước không gian, giảm số kênh ngược về \( (1, 28, 28) \).
import torch.nn as nn

class ConvAutoencoder(nn.Module):
    def __init__(self):
        super().__init__()
        self.encoder = nn.Sequential(
            nn.Conv2d(1, 16, 3, stride=2, padding=1),   # 28 -> 14
            nn.ReLU(),
            nn.Conv2d(16, 32, 3, stride=2, padding=1),  # 14 -> 7
            nn.ReLU(),
        )
        self.decoder = nn.Sequential(
            nn.ConvTranspose2d(32, 16, 3, stride=2,
                               padding=1, output_padding=1),  # 7 -> 14
            nn.ReLU(),
            nn.ConvTranspose2d(16, 1, 3, stride=2,
                               padding=1, output_padding=1),  # 14 -> 28
            nn.Sigmoid(),
        )

    def forward(self, x):
        z = self.encoder(x)
        x_hat = self.decoder(z)
        return x_hat, z

Lưu ý: output_padding cần thiết để ConvTranspose ra đúng spatial size mong muốn vì stride-2 forward có nhiều cấu hình input cùng tạo ra cùng output. Convolutional AE giữ được cấu trúc cục bộ, ít param hơn MLP cho ảnh lớn và là kiến trúc chuẩn cho mọi AE trên ảnh.

10

Training Autoencoder

Pattern train chuẩn (đã quen từ B18):

import torch
import torch.nn as nn

model = Autoencoder(input_dim=784, latent_dim=32).to(device)
optim = torch.optim.Adam(model.parameters(), lr=1e-3)
loss_fn = nn.BCELoss()                # hoặc nn.MSELoss()

for epoch in range(20):
    model.train()
    for x, _ in train_loader:         # bỏ label — AE là unsupervised
        x = x.view(x.size(0), -1).to(device)
        x_hat, _ = model(x)
        loss = loss_fn(x_hat, x)
        optim.zero_grad()
        loss.backward()
        optim.step()

Chú ý:

  • Target chính là input → DataLoader không cần label, chỉ cần data.
  • Adam lr=1e-3 ổn cho khởi đầu; nếu loss dao động giảm xuống 5e-4.
  • Validation: chia tập train/val và track reconstruction loss trên val để phát hiện overfit (hiếm với AE, nhưng có).
  • Số epoch 10–50 đủ cho MNIST với latent 16–64.
11

Visualize latent space

Sau khi train, encode tập test ra \( z \) và quan sát:

  • Latent 2D: nếu \( d_z = 2 \), plot scatter trực tiếp, mỗi điểm color theo class. Cluster theo class xuất hiện rõ với MNIST.
  • Latent cao chiều: dùng t-SNE (van der Maaten 2008) hoặc UMAP (McInnes 2018) để chiếu xuống 2D rồi plot.
  • Reconstruction grid: lấy 10 ảnh random, plot cạnh nhau với reconstruction để kiểm tra chất lượng định tính.
  • Latent traversal: lấy 2 ảnh \( x_1, x_2 \), encode ra \( z_1, z_2 \), interpolate \( z_t = (1 - t) z_1 + t z_2 \) với \( t \in [0, 1] \), decode mỗi \( z_t \). Kết quả thường nửa nét — AE cơ bản không "tròn" trong latent (mục 14).
import matplotlib.pyplot as plt
from sklearn.manifold import TSNE

model.eval()
zs, ys = [], []
with torch.no_grad():
    for x, y in test_loader:
        x = x.view(x.size(0), -1).to(device)
        _, z = model(x)
        zs.append(z.cpu()); ys.append(y)
zs = torch.cat(zs).numpy()
ys = torch.cat(ys).numpy()

z2 = TSNE(n_components=2, init='pca').fit_transform(zs)
plt.scatter(z2[:, 0], z2[:, 1], c=ys, cmap='tab10', s=5)
12

AE vs PCA

PCA (B36 Series 2) chiếu \( x \) lên subspace tuyến tính \( k \) chiều cực đại variance giữ lại, tương đương minimum reconstruction error trong họ tuyến tính.

Kết quả lý thuyết quan trọng (Baldi & Hornik, 1989; Bourlard & Kamp, 1988): với encoder/decoder tuyến tính một layer, không activation, loss MSE, autoencoder hội tụ về cùng subspace của \( k \) thành phần chính đầu của PCA. Nói gọn: Linear AE \(=\) PCA về capacity biểu diễn.

Khác biệt với Deep AE:

  • Deep AE có activation phi tuyến → học được manifold cong, không bị giới hạn ở subspace tuyến tính.
  • Với cùng \( d_z \), Deep AE thường đạt reconstruction error thấp hơn PCA trên data có cấu trúc phi tuyến (vd ảnh).
  • PCA có solution dạng đóng (eigendecomposition) — không cần train, deterministic; AE cần gradient descent, kết quả phụ thuộc init.
  • PCA components ortho-normal, sắp xếp theo variance giảm dần; latent AE không có thứ tự / không trực giao.

Khi nào dùng cái nào? Data nhỏ, gần tuyến tính, cần interpretability → PCA. Data lớn, phi tuyến, có sẵn GPU → Deep AE.

13

Chọn bottleneck dimension

\( d_z \) là hyperparameter quan trọng nhất:

  • Quá nhỏ: bottleneck thắt chặt, model không đủ dung lượng → reconstruction loss cao, ảnh blur, mất chi tiết.
  • Quá lớn: gần với identity mapping, không học compression hữu ích. Trong overcomplete (\( d_z \ge d_x \)) không có regularization, AE có thể học \( g = f^{-1} \) trivial.
  • Vừa: chọn nhỏ nhất mà loss còn chấp nhận được cho ứng dụng.

Quy trình thực dụng: huấn luyện AE với \( d_z \in \{8, 16, 32, 64, 128\} \), plot final validation reconstruction loss vs \( d_z \). Đường cong thường giảm nhanh ban đầu rồi bão hoà — chọn \( d_z \) ở "khuỷu" của đường cong, tương tự elbow method cho K-means.

Với MNIST 784 chiều, \( d_z = 32 \) thường đủ cho recon tốt; \( d_z = 16 \) đã chấp nhận được; \( d_z = 2 \) chỉ dùng cho visualization, loss khá cao.

14

Giới hạn của AE cơ bản

AE cơ bản không phải generative model thuần. Lý do:

  • Latent space không liên tục: encoder map mỗi training sample về một điểm cụ thể, không ràng buộc các điểm phân bố theo cấu trúc nào (vd Gaussian). Vùng giữa các điểm có thể là "khoảng trống" mà decoder không học cách reconstruct.
  • Sample ngẫu nhiên cho kết quả tệ: lấy \( z \sim \mathcal{N}(0, I) \) hay \( z \sim \mathrm{Uniform} \) rồi decode thường ra ảnh nhiễu, không giống MNIST digit. Vì \( z \) đó nằm ngoài vùng encoder ánh xạ tới.
  • Interpolation không trơn: \( z_t = (1-t) z_1 + t z_2 \) decode ra ảnh nửa vời, đột ngột thay vì morph mượt.
  • Không có likelihood: không có \( p_\theta(x) \) tường minh để đánh giá xác suất một sample mới.

Lời giải: gắn ràng buộc xác suất vào latent. Variational Autoencoder (Kingma & Welling, 2014; B43) yêu cầu \( q_\phi(z \mid x) \) gần \( p(z) = \mathcal{N}(0, I) \) qua KL divergence — kết quả là latent space "tròn" hơn, sample ngẫu nhiên từ \( \mathcal{N}(0, I) \) ra ảnh hợp lý, interpolation mượt.

15

Tied weights

Một AE đối xứng có thể chia sẻ trọng số giữa encoder và decoder: nếu encoder layer dùng ma trận \( W \), decoder tương ứng dùng \( W^\top \).

\[ z = \sigma(W x + b_e), \qquad \hat{x} = \sigma'(W^\top z + b_d) \]

Lợi:

  • Giảm một nửa số param.
  • Hoạt động như regularization nhẹ.
  • Phù hợp với phân tích lý thuyết AE \(=\) PCA (linear case).

Hạn chế: nhiều framework hiện đại (PyTorch nn.Linear, nn.Conv2d) không có flag tied weights mặc định, phải implement thủ công bằng cách share weight tensor giữa 2 module. Với GPU rẻ và dataset lớn, tied weights ít cần thiết — modern AE thường giữ encoder/decoder độc lập để encoder và decoder có thể bất đối xứng theo nhu cầu.

16

Use case thực tế

  • Anomaly detection: train AE trên sample "bình thường" (vd ảnh sản phẩm pass QC, log network traffic hợp lệ, transaction credit card chuẩn). Khi inference, tính reconstruction loss \( \|x - \hat{x}\|^2 \). Loss cao bất thường so với phân phối train → flag. Threshold thường đặt ở percentile 95–99 của loss trên validation set. Ứng dụng trong manufacturing defect detection, network intrusion detection, fraud.
  • Recommendation systems: AutoRec (Sedhain 2015) dùng AE trên ma trận user-item rating sparse để dự đoán rating thiếu — gốc của nhiều collaborative filtering deep.
  • Image compression: lossy compression, lưu \( z \) thay vì \( x \) full. Practical khi cần custom format; còn cho file ảnh chuẩn thì JPEG/WebP/AVIF vẫn tốt hơn về tỷ số nén/chất lượng cho dữ liệu tổng quát.
  • Pretraining semi-supervised: train AE trên 1M sample không nhãn, fine-tune encoder cho task supervised có 10k nhãn. Ít dùng cho ảnh hiện nay (đã có ImageNet/foundation model), nhưng vẫn hữu ích cho domain hẹp (medical imaging, sensor data) không có pretrain sẵn.
  • Embedding generic: lấy \( z \) làm feature vector cho clustering, retrieval, nearest neighbor.
17

Modern context

AE cổ điển (MLP / Conv) ngày nay ít dùng trực tiếp như model chính. Nhưng kiến trúc encoder–decoder + latent bottleneck là khung gốc của nhiều dòng model đang đứng đầu:

  • VAE (Kingma & Welling, 2014; B43) — gắn xác suất vào latent, generative thực sự.
  • VQ-VAE (van den Oord 2017) — latent rời rạc qua codebook lookup, tiền thân của DALL·E gốc và nhiều speech model.
  • Masked Autoencoder (MAE) (He và cộng sự, 2022) — mask 75% patch ảnh, reconstruct phần thiếu; pretraining ViT mạnh.
  • BERT (Devlin 2018) — masked language model, về bản chất là denoising autoencoder cho text.
  • Diffusion model (Ho 2020, Stable Diffusion 2022) — denoising autoencoder lặp nhiều bước; U-Net (Ronneberger 2015) có encoder–decoder + skip connection cũng cùng họ kiến trúc.
  • Latent Diffusion (Rombach 2022, kiến trúc Stable Diffusion) — chạy diffusion trên latent của VAE (image autoencoder), tiết kiệm tính toán so với pixel-space.

Hiểu AE cơ bản là điều kiện để đọc được các kiến trúc trên — chúng đều dựa trên cùng ý tưởng encode về latent, decode trở lại, train bằng reconstruction.

18

Code end-to-end MNIST

import torch
import torch.nn as nn
from torch.utils.data import DataLoader
from torchvision import datasets, transforms

device = "cuda" if torch.cuda.is_available() else "cpu"

# 1) Data
tfm = transforms.ToTensor()
train_ds = datasets.MNIST("./data", train=True,  download=True, transform=tfm)
test_ds  = datasets.MNIST("./data", train=False, download=True, transform=tfm)
train_loader = DataLoader(train_ds, batch_size=128, shuffle=True)
test_loader  = DataLoader(test_ds,  batch_size=512)

# 2) Model
class Autoencoder(nn.Module):
    def __init__(self, latent_dim=32):
        super().__init__()
        self.encoder = nn.Sequential(
            nn.Linear(784, 256), nn.ReLU(),
            nn.Linear(256, latent_dim),
        )
        self.decoder = nn.Sequential(
            nn.Linear(latent_dim, 256), nn.ReLU(),
            nn.Linear(256, 784), nn.Sigmoid(),
        )
    def forward(self, x):
        z = self.encoder(x); return self.decoder(z), z

model = Autoencoder(latent_dim=32).to(device)
optim = torch.optim.Adam(model.parameters(), lr=1e-3)
loss_fn = nn.BCELoss()

# 3) Train
for epoch in range(20):
    model.train(); total = 0.0
    for x, _ in train_loader:
        x = x.view(x.size(0), -1).to(device)
        x_hat, _ = model(x)
        loss = loss_fn(x_hat, x)
        optim.zero_grad(); loss.backward(); optim.step()
        total += loss.item() * x.size(0)
    print(f"epoch {epoch:02d}  loss={total / len(train_ds):.4f}")

# 4) Anomaly demo — ảnh noise pure
model.eval()
with torch.no_grad():
    x, _ = next(iter(test_loader))
    x = x.view(x.size(0), -1).to(device)
    x_hat, _ = model(x)
    in_dist = ((x - x_hat) ** 2).mean(dim=1)

    noise = torch.rand_like(x)                  # uniform noise, không phải digit
    n_hat, _ = model(noise)
    out_dist = ((noise - n_hat) ** 2).mean(dim=1)

print(f"In-dist  recon MSE: {in_dist.mean():.4f}")
print(f"Noise    recon MSE: {out_dist.mean():.4f}")     # cao hơn rõ rệt

Trên MNIST với latent 32, sau 20 epoch loss BCE ~0.10. Reconstruction trên test set rõ nét, đủ phân biệt digit. MSE trên ảnh noise pure cao hơn ảnh MNIST 5–10 lần — minh hoạ anomaly detection.

19

Bài tập

  1. Train Autoencoder MNIST với latent_dim lần lượt là 16, 32, 64. Plot final validation reconstruction loss vs latent dim. Mức nào cho ra đường cong elbow?
  2. Lấy 10 ảnh random từ test set, hiển thị grid 2 hàng: hàng 1 input, hàng 2 reconstruction. Chi tiết nào bị mất ở \( d_z = 16 \) mà còn được giữ ở \( d_z = 64 \)?
  3. Tính reconstruction MSE cho 1 ảnh MNIST và 1 ảnh ngoài phân phối (pure noise uniform, ảnh ngẫu nhiên CIFAR-10 resize 28×28 grayscale). Out-of-distribution loss có cao hơn không? Tỷ số bao nhiêu?
  4. Thiết lập anomaly detector đơn giản: train AE chỉ trên digit "0–8" (loại class "9"), tính ngưỡng \( \tau \) ở percentile 99 của recon loss trên train. Với test set đầy đủ, tỷ lệ digit "9" có loss \( > \tau \) là bao nhiêu? Precision/recall?
  5. Implement Convolutional Autoencoder mục 9, train trên MNIST. So sánh recon loss và số param với MLP AE cùng \( d_z = 32 \). Conv version có hơn không?
  6. Lấy 2 ảnh test \( x_1 \) (digit "1") và \( x_2 \) (digit "7"), encode ra \( z_1, z_2 \), interpolate 10 bước \( z_t = (1-t) z_1 + t z_2 \), decode. Đường interpolation có mượt không, hay có ảnh "giữa" trông không giống digit nào?
  7. Sample \( z \sim \mathcal{N}(0, I) \in \mathbb{R}^{32} \) ngẫu nhiên 16 lần, decode. Có ảnh nào nhìn ra digit hợp lệ không? Nếu không, vì sao? (Liên hệ mục 14.)
Gợi ý đáp án ngắn
  1. Loss giảm mạnh từ 16 → 32, chậm lại từ 32 → 64. Elbow ~32 cho MNIST.
  2. \( d_z = 16 \) mất chi tiết nét mảnh (vd phần đuôi của "9", nét ngang giữa của "4"); \( d_z = 64 \) sắc hơn rõ. Một số stroke variant cá nhân khó học ở mọi \( d_z \).
  3. Out-of-distribution loss thường cao hơn 3–10× tuỳ ảnh. Pure noise rất cao (random pixel không có cấu trúc spatial nào AE biết). CIFAR resize cũng cao nhưng ít hơn vì có cấu trúc spatial dù khác MNIST.
  4. Threshold percentile 99 thường cho recall 60–80% trên class "9" với precision tuỳ baseline. Performance không cao bằng classifier supervised — AE-based anomaly detection mạnh khi không có dữ liệu anomaly để train, nhưng kém khi có nhãn.
  5. ConvAE thường recon tốt hơn MLP AE ở cùng latent dim, ít param hơn (~3–5×). Đặc biệt rõ khi tăng kích thước ảnh.
  6. Interpolation AE cơ bản không mượt — vùng giữa thường blur, không tạo ra digit hợp lệ. Đây là motivation chính cho VAE.
  7. Phần lớn sample random từ Gaussian decode ra ảnh blob mờ không phải digit. Vùng \( \mathcal{N}(0, I) \) chỉ trùng với một phần nhỏ vùng encoder ánh xạ tới; đa số là khoảng trống. VAE giải bằng cách ép \( q(z \mid x) \) gần \( \mathcal{N}(0, I) \).
20

Tóm tắt

  • Mở Module 6 — generative foundation. Bốn bài: AE (B41) → DAE (B42) → VAE (B43) → GAN (B44).
  • Autoencoder gồm encoder \( f: x \to z \) và decoder \( g: z \to \hat{x} \), tối ưu reconstruction loss \( \|x - g(f(x))\|^2 \) (MSE) hoặc BCE cho input \( [0, 1] \).
  • Bottleneck \( d_z \ll d_x \) buộc model học representation cô đọng — undercomplete AE.
  • Variants: Deep AE (default), Convolutional AE (ảnh, dùng ConvTranspose ở decoder), Sparse AE (\( \ell_1 \) trên \( z \)), Contractive AE (penalize Jacobian).
  • Linear AE với MSE \(=\) PCA về capacity (Baldi & Hornik 1989). Deep AE vượt qua nhờ activation phi tuyến.
  • PyTorch implementation: nn.Sequential cho encoder và decoder, train với Adam lr=1e-3, BCE hoặc MSE.
  • Chọn \( d_z \) bằng elbow method trên loss vs \( d_z \). MNIST: 32 thường đủ.
  • Use case: dimensionality reduction (so PCA), denoising (B42), anomaly detection (threshold trên recon loss), pretraining, feature learning unsupervised, embedding generic.
  • Visualize latent: 2D scatter, t-SNE/UMAP, latent traversal, reconstruction grid.
  • Giới hạn: latent space không có cấu trúc xác suất → sample ngẫu nhiên decode ra ảnh nhiễu, interpolation không mượt. Lời giải: VAE (B43) ép \( q(z \mid x) \) gần \( \mathcal{N}(0, I) \).
  • Tied weights chia sẻ \( W \) giữa encoder/decoder để giảm param và regularize — ít dùng modern.
  • Modern context: AE cổ điển ít dùng trực tiếp, nhưng kiến trúc encoder–decoder + reconstruction là khung gốc của VAE, VQ-VAE, MAE, BERT (masked LM), U-Net, Diffusion, Latent Diffusion (Stable Diffusion).
  • Bài kế tiếp B42: Denoising Autoencoder thêm noise vào input để học representation robust hơn.