03 — CNN & RNN
Estimasi: 5 jam Tujuan: Paham arsitektur CNN (image) dan RNN (sequence) — sebelum masuk Transformer.
Kenapa Materi Ini Penting?
Sebelum era Transformer (2017), dua arsitektur ini adalah "raja" deep learning: CNN untuk gambar, RNN untuk teks dan time series. Walaupun sekarang Transformer mendominasi, pengetahuan CNN dan RNN tetap esensial — banyak production system masih pakai mereka karena lebih ringan, dan banyak konsep modern (attention, residual connection, embedding) lahir dari pemahaman keterbatasan dua arsitektur ini.
Bayangkan dua "indera" yang berbeda: CNN melihat ruang (pixel berdekatan saling berhubungan), RNN merasakan waktu (kata sekarang tergantung kata sebelumnya). Memahami inductive bias mereka membantu kamu paham kenapa Transformer kemudian menggabungkan keduanya — Transformer sejatinya adalah RNN tanpa rekurensi yang juga bisa pakai positional encoding (kesadaran ruang/urutan).
Tiga insight kunci yang akan kamu kuasai: (1) Convolution sebagai cap berpola yang menggeser di gambar untuk capture local pattern, (2) Recurrence sebagai cara membaca sequence dengan "memori" hidden state, dan (3) Limitations mereka yang memunculkan ide self-attention.
Peta Mental: CNN vs RNN
flowchart LR
subgraph CNN["🖼️ CNN — Spasial"]
I1["Input image"] --> C1["Conv (cap pola)"]
C1 --> P1["Pool (ringkas)"]
P1 --> F1["Flatten + FC"]
end
subgraph RNN["📜 RNN — Temporal"]
I2["Token t-1"] --> H1["Hidden state"]
I3["Token t"] --> H1
H1 --> H2["Hidden state'"]
I4["Token t+1"] --> H2
end
style CNN fill:#dbeafe
style RNN fill:#fef3c7
Bagian 1 — CNN (Convolutional Neural Network)
Kenapa Conv untuk Image?
Image punya spatial structure — pixel berdekatan berhubungan. Linear layer tidak tangkap ini.
Conv = filter sliding di image, capture local pattern.
Konsep Filter
Analogi Convolution: Bayangkan kamu punya stempel/cap kecil dengan pola tertentu (misal pola "tepi vertikal"). Kamu tempelkan cap itu ke seluruh permukaan gambar, geser sedikit demi sedikit, dan tiap posisi catat seberapa cocok pola cap dengan area gambar di bawahnya. Hasilnya adalah feature map — peta yang menunjukkan "di mana saja pola cap ini muncul di gambar". CNN punya banyak cap berbeda (filter berbeda), tiap cap belajar sendiri pola apa yang dia tangkap.
Image: 6×6 Filter: 3×3 Output: 4×4
[1 2 3 4 5 6] [1 0 -1] [...]
[7 8 9 0 1 2] [1 0 -1] [...]
[3 4 5 6 7 8] [1 0 -1] [...]
... (vertical edge)
Filter geser di image, hasilkan feature map.
Visualisasi Operasi Convolution
Cara Membaca Diagram:
- Kiri = input image, tengah = bank filter, kanan = stacked output
- Tiap filter belajar pola spesifik (tepi, sudut, tekstur, warna)
- Hasil semua filter di-stack jadi tensor multi-channel
- Bobot filter di-share di seluruh image (translation equivariance)
Walkthrough Step-by-Step:
- Input image masuk dengan shape
(C_in, H, W), contoh(3, 32, 32) - Filter 1, 2, ..., N — tiap satu cap kecil dengan pola tertentu
- Tiap filter di-slide ke seluruh image, hasilnya feature map 2-D
- Stack semua feature map jadi tensor
(N, H', W')— banyak filter = banyak channel - Output siap di-pool dan dimasukkan ke conv layer berikutnya
Analogi Sehari-hari: Convolution = ratusan stempel berbeda yang ditempelkan ke seluruh gambar. Tiap stempel mencatat "seberapa cocok pola saya dengan area ini". Hasilnya: peta berlapis yang menunjukkan di mana setiap pola muncul.
Diagram statis Mermaid sebagai fallback:
flowchart LR
Img["🖼️ Input<br/>32×32×3"] --> K1["🔍 Filter 1<br/>(deteksi tepi)"]
Img --> K2["🔍 Filter 2<br/>(deteksi sudut)"]
Img --> K3["🔍 Filter N<br/>(deteksi tekstur)"]
K1 --> FM1["📊 Feature Map 1"]
K2 --> FM2["📊 Feature Map 2"]
K3 --> FM3["📊 Feature Map N"]
FM1 --> Stack["📦 Stack:<br/>30×30×N"]
FM2 --> Stack
FM3 --> Stack
style Img fill:#dbeafe
style Stack fill:#d1fae5
Pooling — Meringkas Tetangga
Analogi Pooling: Bayangkan kamu punya foto resolusi tinggi tapi mau kirim via WhatsApp — kamu kompres. MaxPool = "ambil pixel paling terang dari tiap 2×2 area". AvgPool = "rata-ratakan tiap 2×2 area". Hasilnya: gambar lebih kecil tapi fitur penting tetap terjaga. Di CNN, pooling membuat network: (1) lebih ringan, (2) invariant terhadap pergeseran kecil — kucing yang bergeser 2 pixel tetap terdeteksi sebagai kucing.
Cara Membaca Diagram:
- Kiri = input grid, tengah = jenis pooling, kanan = output yang lebih kecil
- MaxPool dan AvgPool dua opsi paling umum
- Stride 2 = output 1/2 ukuran input di tiap dimensi spasial
- Tidak ada parameter yang dipelajari (pure operasi)
Walkthrough Step-by-Step:
- Input — feature map 4×4 dari conv sebelumnya
- MaxPool 2×2 — bagi jadi 4 area 2×2, ambil nilai paling besar dari tiap area
- AvgPool 2×2 — bagi jadi 4 area 2×2, hitung rata-rata tiap area
- Hasilnya 2×2, ukuran setengah dari input
- Manfaat: ringan, invariant terhadap pergeseran kecil
Analogi Sehari-hari: Pooling = kompres foto WhatsApp. MaxPool = ambil pixel paling terang per area (highlight). AvgPool = blur ringan. Ukuran turun, tapi info penting tetap kebaca.
Diagram statis Mermaid sebagai fallback:
flowchart LR
Input["🟦 4×4<br/>1 2 3 4<br/>5 6 7 8<br/>9 0 1 2<br/>3 4 5 6"] -->|"MaxPool 2×2"| Output["🟦 2×2<br/>6 8<br/>9 6"]
style Input fill:#fef3c7
style Output fill:#d1fae5
Full CNN Architecture (Big Picture)
Cara Membaca Diagram:
- Atas = pipeline conv+pool yang menyusutkan ukuran spasial
- Bawah = head FC untuk klasifikasi
- Tiap conv block: Conv → ReLU → MaxPool, diulang 3 kali
- Spatial size turun 32 → 16 → 8 → 4, depth naik 3 → 32 → 64 → 128
Walkthrough Step-by-Step:
- Input image 3×32×32 (RGB CIFAR-style)
- Conv1 dengan 32 filter → output 32×32×32
- MaxPool → 32×16×16 (spatial half)
- Conv2 + Pool → 64×8×8
- Conv3 + Pool → 128×4×4
- Flatten ke vector 2048
- FC + Softmax → probability tiap class
- Output prediksi class
Analogi Sehari-hari: CNN = pyramid info. Layer awal nangkap detail kecil (garis, warna). Layer tengah gabungkan jadi pola (mata, telinga). Layer akhir gabungkan pola jadi konsep (kucing, anjing). FC head jadi "juri" yang vote.
Diagram statis Mermaid sebagai fallback:
flowchart LR
I["🖼️ Image<br/>3×32×32"] --> C1["Conv1<br/>32 filter"]
C1 --> A1["ReLU"]
A1 --> P1["MaxPool<br/>16×16"]
P1 --> C2["Conv2<br/>64 filter"]
C2 --> A2["ReLU"]
A2 --> P2["MaxPool<br/>8×8"]
P2 --> C3["Conv3<br/>128 filter"]
C3 --> A3["ReLU"]
A3 --> P3["MaxPool<br/>4×4"]
P3 --> F["Flatten"]
F --> FC["FC + Softmax"]
FC --> Out["🎯 Class"]
style I fill:#dbeafe
style Out fill:#d1fae5
CNN Architecture
import torch.nn as nn
class SimpleCNN(nn.Module):
def __init__(self, num_classes=10):
super().__init__()
self.conv1 = nn.Conv2d(3, 32, kernel_size=3, padding=1) # input 3 channel (RGB)
self.conv2 = nn.Conv2d(32, 64, kernel_size=3, padding=1)
self.conv3 = nn.Conv2d(64, 128, kernel_size=3, padding=1)
self.pool = nn.MaxPool2d(2, 2)
self.fc1 = nn.Linear(128 * 4 * 4, 256) # untuk image 32×32
self.fc2 = nn.Linear(256, num_classes)
self.dropout = nn.Dropout(0.5)
def forward(self, x):
x = self.pool(torch.relu(self.conv1(x))) # 32×32 → 16×16
x = self.pool(torch.relu(self.conv2(x))) # 16×16 → 8×8
x = self.pool(torch.relu(self.conv3(x))) # 8×8 → 4×4
x = x.view(x.size(0), -1) # flatten
x = torch.relu(self.fc1(x))
x = self.dropout(x)
x = self.fc2(x)
return x
Common Components
- Conv layer — extract local features
- Pooling — downsample, reduce dimension
- Activation — ReLU klasik
- BatchNorm — stabilize training
Famous Architectures (Sekedar Tahu)
- AlexNet (2012) — winner ImageNet, mulai era deep learning
- VGG (2014) — deeper, simple
- ResNet (2015) — skip connection, allow very deep
- Inception (GoogLeNet)
- EfficientNet — modern, efficient
Transfer Learning (Pakai Pretrained)
import torchvision.models as models
# Pretrained on ImageNet
model = models.resnet50(weights="DEFAULT")
# Freeze backbone
for param in model.parameters():
param.requires_grad = False
# Replace head
model.fc = nn.Linear(2048, num_classes_yours)
# Train head saja
optimizer = optim.Adam(model.fc.parameters(), lr=1e-3)
Wajib pakai transfer learning untuk image task. Train dari nol = boros, jelek.
CNN dengan nn.Sequential (Lebih Singkat)
cnn = nn.Sequential(
nn.Conv2d(3, 32, 3, padding=1),
nn.BatchNorm2d(32),
nn.ReLU(),
nn.MaxPool2d(2), # 32→16
nn.Conv2d(32, 64, 3, padding=1),
nn.BatchNorm2d(64),
nn.ReLU(),
nn.MaxPool2d(2), # 16→8
nn.Conv2d(64, 128, 3, padding=1),
nn.BatchNorm2d(128),
nn.ReLU(),
nn.AdaptiveAvgPool2d((1, 1)), # global pool → (B, 128, 1, 1)
nn.Flatten(),
nn.Dropout(0.5),
nn.Linear(128, 10),
)
Tip:
AdaptiveAvgPool2d((1,1))membuat network tidak peduli ukuran input — bisa terima image 32×32 atau 224×224 dengan FC head yang sama. Pattern ini dipakai di ResNet & EfficientNet.
Bagian 2 — RNN (Recurrent Neural Network)
Kenapa RNN untuk Sequence?
Text, audio, time series punya temporal structure. Output kata ke-N tergantung kata ke-1...N-1.
Konsep Vanilla RNN
Analogi RNN: Bayangkan kamu membaca buku sambil ngomong sendiri — tiap kata baru, kamu bisikkan ringkasan apa yang kamu mengerti sejauh ini. "Hidden state" itu = bisikan ringkasan kamu. Tiap kali baca kata baru, bisikan di-update berdasarkan kata baru + bisikan sebelumnya. Begitu sampai akhir kalimat, bisikan terakhir = pemahaman keseluruhan kalimat. Itulah RNN — pembaca yang ingat kata-kata sebelumnya.
h_t = tanh(W_h × h_{t-1} + W_x × x_t + b)
Hidden state h membawa "memori" dari past.
Visualisasi RNN Unrolled
Cara Membaca Diagram:
- Kiri ke kanan = waktu (timestep), tiap kolom = satu langkah
- Atas = input token, tengah = RNN cell, bawah = hidden state mengalir
- Bobot
W_h, W_xsama di semua RNN cell — itu yang bikin "recurrent" - Hidden state membawa "memori" dari kata-kata sebelumnya
Walkthrough Step-by-Step:
- Mulai dengan
h₀(biasanya zeros) - t=1: input
x₁='Saya'+h₀→ cell hitungh₁ - t=2: input
x₂='suka'+h₁→ cell hitungh₂ - t=3: input
x₃='belajar'+h₂→ cell hitungh₃ h₃= ringkasan seluruh sequence, siap dipakai untuk klasifikasi/generation
Analogi Sehari-hari: RNN = kamu baca novel sambil ngomong sendiri. Tiap kalimat baru, kamu update bisikan ringkasan di kepala. Bisikan sebelumnya + kalimat baru → bisikan baru. Begitu terus sampai akhir bab.
Diagram statis Mermaid sebagai fallback:
flowchart LR
H0["🧠 h₀<br/>(awal kosong)"] --> R1["⚙️ RNN cell"]
X1["📝 x₁<br/>'Saya'"] --> R1
R1 --> H1["🧠 h₁"]
H1 --> R2["⚙️ RNN cell"]
X2["📝 x₂<br/>'suka'"] --> R2
R2 --> H2["🧠 h₂"]
H2 --> R3["⚙️ RNN cell"]
X3["📝 x₃<br/>'belajar'"] --> R3
R3 --> H3["🧠 h₃<br/>(final)"]
style H0 fill:#fef3c7
style H3 fill:#d1fae5
style R1 fill:#dbeafe
style R2 fill:#dbeafe
style R3 fill:#dbeafe
Yang membuat RNN "recurrent": bobot yang sama (W_h, W_x) dipakai di tiap timestep. Ini efisien — bisa proses sequence panjang tanpa nambah parameter.
import torch.nn as nn
rnn = nn.RNN(input_size=10, hidden_size=20, num_layers=2, batch_first=True)
x = torch.randn(32, 50, 10) # (batch, seq, dim)
output, hidden = rnn(x)
# output: (32, 50, 20) — output per timestep
# hidden: (2, 32, 20) — final hidden state
Masalah Vanilla RNN
- Vanishing gradient — sulit belajar long-range dependency
- Exploding gradient — gradient meledak saat backprop
Solusi: LSTM dan GRU.
Bagian 3 — LSTM (Long Short-Term Memory)
LSTM punya gate mechanism untuk handle long-range dependency.
Analogi LSTM: Kalau RNN biasa = pembaca yang cuma bisikan ringkasan singkat (gampang lupa kata di awal kalimat panjang), LSTM = pembaca dengan buku catatan. Di tiap timestep, dia punya 3 keputusan: (1) Forget gate — apa yang dihapus dari catatan ("ah, ini tidak penting"), (2) Input gate — apa yang ditambahkan ke catatan ("ini perlu kuingat"), (3) Output gate — apa yang diomongkan keluar ("yang ini cocok jadi output"). Cell state
c= isi buku catatan yang awet melintasi waktu, beda dari hidden statehyang lebih "sementara".
Visualisasi LSTM Cell
Cara Membaca Diagram:
- Kiri = input ke cell (
h_{t-1},x_t), kanan = output (h_t,c_t) - Tiga gate kuning di tengah = forget, input, output (semua learnable)
c(cell state) = "buku catatan" awet, jalur lurus dari kiri ke kananh(hidden state) = output yang lebih sementara, dipakai layer/timestep berikut
Walkthrough Step-by-Step:
- Concat
h_{t-1}danx_tjadi satu vektor input - Forget gate (sigmoid) — putuskan info di
c_{t-1}mana yang dibuang - Input gate — putuskan info baru apa yang ditambahkan ke cell state
- Update cell state:
c_t = forget × c_{t-1} + input_value - Output gate — putuskan apa yang dilihat keluar sebagai
h_t h_t = tanh(c_t) × output_gate
Analogi Sehari-hari: LSTM = ngerjain skripsi sambil bawa notebook. Forget gate = coret bagian tak relevan. Input gate = tulis insight baru. Output gate = pilih apa yang ditampilkan ke pembimbing. Cell state (c) = isi notebook yang lengkap, hidden state (h) = ringkasan yang ditunjukkan.
Diagram statis Mermaid sebagai fallback:
flowchart LR
HPrev["🧠 h_{t-1}"] --> Concat["⚙️ Concat"]
XT["📝 x_t"] --> Concat
Concat --> Forget["🗑️ Forget Gate<br/>(σ)"]
Concat --> Input["➕ Input Gate<br/>(σ × tanh)"]
Concat --> Output["📤 Output Gate<br/>(σ)"]
CPrev["📔 c_{t-1}<br/>memory"] --> Mul1["× forget"]
Forget --> Mul1
Mul1 --> Add["+"]
Input --> Add
Add --> CNew["📔 c_t<br/>new memory"]
CNew --> Tanh["tanh"]
Tanh --> Mul2["× output"]
Output --> Mul2
Mul2 --> HNew["🧠 h_t"]
style CPrev fill:#fef3c7
style CNew fill:#d1fae5
style HNew fill:#dbeafe
lstm = nn.LSTM(input_size=10, hidden_size=20, num_layers=2, batch_first=True)
x = torch.randn(32, 50, 10)
output, (h, c) = lstm(x)
# output: (32, 50, 20)
# h: (2, 32, 20) — hidden
# c: (2, 32, 20) — cell state
LSTM Gates
- Forget gate — apa yang dilupa
- Input gate — apa yang ditambah ke memori
- Output gate — apa yang di-output
Detail teknis: tidak perlu hafal, paham konsep "gating".
Bagian 4 — GRU (Lebih Sederhana, Sering Cukup)
gru = nn.GRU(input_size=10, hidden_size=20, num_layers=2, batch_first=True)
output, hidden = gru(x)
GRU = simplified LSTM, lebih cepat, sering performance mirip.
Bagian 5 — Bidirectional
lstm = nn.LSTM(input_size=10, hidden_size=20, bidirectional=True, batch_first=True)
# Output dim = 2 × hidden_size = 40
Process dari kiri dan kanan. Bagus untuk task yang butuh konteks dua arah (NER, sentiment analysis).
Bagian 6 — Sequence-to-Sequence (Encoder-Decoder)
Pattern untuk translation, summarization (sebelum era transformer).
Input: "I love AI"
↓
[Encoder RNN]
↓
Context vector
↓
[Decoder RNN]
↓
Output: "Saya cinta AI"
Attention (Sebelum Transformer)
Vanilla seq2seq punya bottleneck di context vector. Attention allows decoder "lihat" semua encoder output, weighted.
Ini cikal bakal Transformer. Self-attention = attention tanpa encoder/decoder split.
Bagian 7 — Kapan Pakai Apa?
Sebelum era Transformer (~2018):
- Image → CNN
- Sequence → RNN/LSTM
- Translation → Seq2Seq + attention
Sekarang (2026):
- Image → CNN masih dominan untuk many task, tapi ViT (Vision Transformer) menang di scale besar
- Sequence/text → Transformer (file 05) hampir selalu menang
RNN/LSTM masih dipakai di:
- Edge devices (low-resource)
- Time series sederhana
- Audio processing dengan WaveNet
Tapi untuk NLP modern, langsung lompat ke Transformer.
Bagian 7 — Kapan Pakai Apa?
Sebelum era Transformer (~2018):
- Image → CNN
- Sequence → RNN/LSTM
- Translation → Seq2Seq + attention
Sekarang (2026):
- Image → CNN masih dominan untuk many task, tapi ViT (Vision Transformer) menang di scale besar
- Sequence/text → Transformer (file 05) hampir selalu menang
RNN/LSTM masih dipakai di:
- Edge devices (low-resource)
- Time series sederhana
- Audio processing dengan WaveNet
Tapi untuk NLP modern, langsung lompat ke Transformer.
Comparison Table: CNN vs RNN vs Transformer
| Aspek | CNN | RNN/LSTM | Transformer |
|---|---|---|---|
| Inductive bias | Local pattern (translation invariance) | Sequential, recency | Global, position-agnostic |
| Parallelize | ✅ Tinggi (per pixel) | ❌ Rendah (sequential) | ✅✅ Sangat tinggi |
| Long-range dependency | Terbatas (receptive field) | Sulit (vanishing gradient) | ✅ Native (self-attention) |
| Compute per token | O(k²) (kernel kecil) | O(d²) | O(n²·d) — quadratic di seq |
| Best for | Image, audio spectrogram | Time series kecil, edge | Text, image (ViT), apa saja di scale besar |
| Modern share | Image task tertentu | Niche | LLM, modern NLP, ViT |
LSTM vs GRU
| Aspek | LSTM | GRU |
|---|---|---|
| Gates | 3 (forget, input, output) | 2 (update, reset) |
| State | hidden + cell | hidden saja |
| Parameter | Lebih banyak | Lebih sedikit (~25% lebih kecil) |
| Speed | Lebih lambat | Lebih cepat |
| Performance | Sedikit lebih baik di task panjang | Sering setara, cocok data terbatas |
| Default pick | Default klasik | Pilihan modern saat data sedikit |
Bagian 8 — Common Mistakes & FAQ
CNN Side
1. Tidak Pakai Transfer Learning untuk Image
# ❌ Train ResNet-50 dari nol di dataset 5000 gambar = waste of GPU
model = models.resnet50(weights=None)
# ✅ Mulai dari ImageNet weights
model = models.resnet50(weights="DEFAULT")
model.fc = nn.Linear(2048, num_classes)
2. Lupa Normalize Input
# ❌ Image 0-255 langsung masuk model = aktivasi meledak
img_tensor = transforms.ToTensor()(img) # 0-1 OK, tapi belum normalize
# ✅ Pakai ImageNet stats (kalau pakai pretrained)
transform = transforms.Compose([
transforms.ToTensor(),
transforms.Normalize(mean=[0.485, 0.456, 0.406],
std=[0.229, 0.224, 0.225]),
])
3. Conv2d In/Out Channel Salah
# ❌ RGB image (3 channel) tapi conv1 expect 1 channel
self.conv1 = nn.Conv2d(1, 32, 3) # untuk grayscale
# ✅
self.conv1 = nn.Conv2d(3, 32, 3) # untuk RGB
RNN Side
4. Tidak Pakai batch_first=True
# Default PyTorch: (seq, batch, feature) — bingung
rnn = nn.LSTM(10, 20)
x = torch.randn(50, 32, 10) # (T, B, F)
# ✅ Lebih intuitif
rnn = nn.LSTM(10, 20, batch_first=True)
x = torch.randn(32, 50, 10) # (B, T, F)
5. Variable Length Sequences Tanpa Padding
# ❌ Sequence panjang berbeda → error stack di DataLoader
batch = [torch.randn(10), torch.randn(15), torch.randn(8)] # mixed length
# ✅ Pakai pad_sequence + pack_padded_sequence
from torch.nn.utils.rnn import pad_sequence, pack_padded_sequence
padded = pad_sequence(batch, batch_first=True) # pad ke 15
lengths = torch.tensor([10, 15, 8])
packed = pack_padded_sequence(padded, lengths, batch_first=True, enforce_sorted=False)
output, (h, c) = lstm(packed)
6. Exploding Gradient — Wajib Clip
# ✅ Gradient clipping untuk RNN/LSTM
loss.backward()
torch.nn.utils.clip_grad_norm_(model.parameters(), max_norm=1.0)
optimizer.step()
Bug klasik: training RNN mendadak loss = NaN setelah beberapa epoch. Hampir selalu = exploding gradient. Clip dulu, baru langkah.
Cek Pemahaman
- Tahu kenapa CNN bagus untuk image?
- Bisa bikin simple CNN untuk klasifikasi?
- Tahu kenapa RNN untuk sequence?
- Tahu beda LSTM dan GRU?
- Tahu kenapa Transformer mengalahkan RNN?
Challenge 6.3
Challenge 1 — CIFAR-10 CNN
from torchvision.datasets import CIFAR10
Build CNN untuk klasifikasi 10 class. Target accuracy >70%.
Challenge 2 — Transfer Learning
Pakai ResNet50 pretrained. Fine-tune untuk dataset gambar custom (atau Cats vs Dogs). Bandingkan dengan train from scratch.
Challenge 3 — RNN Sentiment Analysis
Dataset IMDB (review film). Build LSTM untuk klasifikasi sentiment. Pakai embedding layer.
Challenge 4 — Karpathy Episode 2
Tonton "Building makemore" Episode 1 (bigram model). Memahami language modeling fundamental.
Selanjutnya: 04-nlp-fundamentals.md