Saat pertama membaca istilah “convolution”, saya langsung curiga ini matematika yang tidak akan pernah saya pahami. Kenyataannya konsepnya bisa dijelaskan dengan analogi yang sangat programmer: sliding window. CNN (Convolutional Neural Network) adalah arsitektur yang mendominasi computer vision selama bertahun-tahun, dan intinya cuma dua operasi: convolution untuk mendeteksi pola lokal, dan pooling untuk meringkas. Episode ini catatan saya membedah keduanya.
Masalah: Dense Layer Tidak Cocok untuk Gambar
Coba pikirkan gambar 224×224 piksel berwarna. Itu 224 × 224 × 3 = sekitar 150 ribu angka input. Kalau layer pertama kita dense penuh ke 1000 neuron, parameternya sudah 150 juta — hanya untuk layer pertama. Lebih parah lagi: dense layer buta terhadap posisi. Kucing di pojok kiri atas dan kucing di pojok kanan bawah adalah dua pola angka yang sama sekali berbeda baginya, padahal secara semantik sama.
Dua intuisi tentang gambar yang dilanggar dense layer: pola penting itu lokal (deteksi tepi tidak perlu melihat seluruh gambar), dan pola yang sama bisa muncul di mana saja. CNN dirancang persis untuk kedua sifat ini.
Convolution: Sliding Window Pendeteksi Pola
Convolution bekerja dengan filter kecil — misalnya 3×3 angka — yang digeser melewati seluruh gambar. Di setiap posisi, isi filter dikalikan elemen-per-elemen dengan area gambar di bawahnya lalu dijumlahkan jadi satu angka. Hasilnya disebut feature map: peta lokasi di mana pola tersebut ditemukan.
- Parameter sedikit: filter 3×3 punya hanya 9 bobot, dipakai ulang di seluruh gambar (weight sharing).
- Invarian posisi: filter yang sama mendeteksi tepi di pojok manapun.
- Hierarki otomatis: layer awal belajar tepi dan garis, layer tengah tekstur, layer dalam bentuk objek.
import numpy as np
img = np.array([
[0, 0, 0, 0, 0],
[0, 1, 1, 1, 0],
[0, 1, 1, 1, 0],
[0, 1, 1, 1, 0],
[0, 0, 0, 0, 0],
], dtype=float)
kernel = np.array([ # detektor tepi vertikal
[-1, 0, 1],
[-1, 0, 1],
[-1, 0, 1],
])
H, W = img.shape
out = np.zeros((H - 2, W - 2))
for i in range(H - 2):
for j in range(W - 2):
patch = img[i:i+3, j:j+3]
out[i, j] = np.sum(patch * kernel) # dot product lokal
print(out)Kode di atas adalah convolution 2D yang ditulis manual tanpa library deep learning. Perhatikan loop ganda yang menggeser window 3×3 — itulah seluruh idenya. Di praktik, operasi ini dijalankan GPU pada ratusan filter sekaligus, tapi logikanya persis sama.
Pooling: Meringkas Tanpa Kehilangan Inti
Setelah convolution, biasanya diselingi pooling: mengecilkan ukuran feature map. Max pooling 2×2 mengambil nilai terbesar dari setiap blok 2×2, memotong dimensi menjadi seperempat. Manfaatnya: komputasi lebih murah, dan toleransi kecil terhadap pergeseran — objek yang bergeser satu-dua piksel tetap terdeteksi. Ada juga average pooling yang mengambil rata-rata, dan global average pooling yang sering dipakai sebagai pengganti flatten menjelang output.
| Blok | Tugas | Analogi |
|---|---|---|
| Convolution | Deteksi pola lokal via filter geser | Regex yang discan ke seluruh teks |
| Activation (ReLU) | Non-linearitas | Filter negatif dibuang |
| Pooling | Pengecilan ukuran, ringkasan | Downsampling / thumbnail |
| Stack berulang | Hierarki abstraksi | Abstraksi bertingkat ala software |
Max Pooling vs Average Pooling: Mana yang Kapan?
Pertanyaan yang sering muncul: kapan pakai max pooling, kapan pakai average pooling? Saya baru benar-benar paham setelah melihat visualisasinya. Max pooling menonjolkan fitur paling kuat — piksel dengan nilai terbesar. Average pooling meratakan semua kontribusi. Dalam praktik, max pooling lebih populer karena mampu menangkap fitur dominan (misalnya tepi yang jelas), sementara average pooling lebih cocok untuk global context.
import torch
import torch.nn as nn
feature_map = torch.tensor([
[1.0, 3.0, 2.0, 4.0],
[5.0, 6.0, 1.0, 2.0],
[7.0, 8.0, 3.0, 1.0],
[2.0, 4.0, 5.0, 6.0],
]).unsqueeze(0).unsqueeze(0) # shape: (1, 1, 4, 4)
# Max Pooling 2×2
max_pool = nn.MaxPool2d(kernel_size=2, stride=2)
print("Max Pooling:")
print(max_pool(feature_map))
# Average Pooling 2×2
avg_pool = nn.AvgPool2d(kernel_size=2, stride=2)
print("Average Pooling:")
print(avg_pool(feature_map))
# Global Average Pooling — merangkas seluruh feature map jadi 1 angka
gap = nn.AdaptiveAvgPool2d(1)
print("Global Average Pooling:")
print(gap(feature_map))Blok 2×2 pertama fitur-map di atas bernilai [1,3,5,6]. Max pooling menghasilkan 6 (nilai terbesar), average pooling menghasilkan 3.75. Perhatikan bahwa dimensi output berkurang dari 4×4 menjadi 2×2 — itulah inti pooling: meringkas tanpa kehilangan inti informasi.
Evolusi Arsitektur CNN: LeNet hingga ResNet
CNN bukan lahir dalam satu malam. Setiap dekade membawa lompatan arsitektur yang mengubah arah computer vision. Saya merangkum evolusinya dalam tabel berikut untuk mengingatkan diri sendiri bagaimana ide-ide saling bertumpuk.
| Tahun | Arsitektur | Layer | Parameter | Inovasi Kunci |
|---|---|---|---|---|
| 1998 | LeNet-5 | 7 | ~60K | CNN pertama untuk digit recognition |
| 2012 | AlexNet | 8 | ~60M | ReLU, dropout, GPU training |
| 2014 | VGG-16 | 16 | ~138M | Filter 3×3 uniform, kedalaman lebih |
| 2015 | GoogLeNet | 22 | ~6.8M | Inception module, 1×1 conv |
| 2015 | ResNet-50 | 50 | ~25.6M | Residual connection, training 152+ layer |
Pola yang saya lihat: setiap generasi menambah kedalaman (jumlah layer) sekaligus menemukan cara agar training tetap konvergen. LeNet membuktikan CNN bisa bekerja. AlexNet membuktikan CNN bisa menang di skala besar. VGG menunjukkan kedalaman itu penting — asalkan filternya konsisten. ResNet menyelesaikan masalah vanishing gradient dengan residual connection: output awal blok “dijumpel” ke output akhir sehingga gradien punya jalur pintas. Sejak ResNet, kedalaman ratusan layer bukan lagi masalah.
Transfer Learning dengan Pretrained Models
Poin penting dari saya: jangan melatih CNN dari nol untuk dataset kecil. Model seperti ResNet, VGG, dan EfficientNet sudah dilatih pada ImageNet (14 juta gambar). Kita cukup memanfaatkan fitur yang sudah dipelajari dan menyesuaikan layer terakhir untuk tugas kita sendiri. Ini namanya transfer learning — dan ini penyelamat waktu serta komputasi.
import torch
import torch.nn as nn
import torchvision.models as models
# Load ResNet-18 pretrained pada ImageNet
model = models.resnet18(weights="IMAGENET1K_V1")
# Bekukan semua parameter (feature extractor mode)
for param in model.parameters():
param.requires_grad = False
# Ganti classifier terakhir untuk 10 kelas kita
num_classes = 10
model.fc = nn.Linear(model.fc.in_features, num_classes)
# Sekarang hanya model.fc yang dilatih — sangat cepat!
optimizer = torch.optim.Adam(model.fc.parameters(), lr=1e-3)
criterion = nn.CrossEntropyLoss()
# Contoh training loop
for epoch in range(5):
model.train()
for images, labels in train_loader:
outputs = model(images)
loss = criterion(outputs, labels)
optimizer.zero_grad()
loss.backward()
optimizer.step()
print(f"Epoch {epoch+1}, Loss: {loss.item():.4f}")Trik yang saya pelajari: freeze semua layer di awal, latih hanya classifier selama beberapa epoch. Setelah loss stabil, unfreeze semua layer dengan learning rate kecil (fine-tuning). Cara ini menjaga fitur pretrained tetap utuh sambil sedikit menyesuaikan dengan data baru.
# Fine-tuning: unfreeze semua layer dengan learning rate kecil
for param in model.parameters():
param.requires_grad = True
optimizer = torch.optim.Adam(model.parameters(), lr=1e-5)
# Lanjut training beberapa epoch lagi
for epoch in range(3):
model.train()
for images, labels in train_loader:
outputs = model(images)
loss = criterion(outputs, labels)
optimizer.zero_grad()
loss.backward()
optimizer.step()
print(f"Fine-tune Epoch {epoch+1}, Loss: {loss.item():.4f}")Data Augmentation untuk Gambar
Salah satu kesalahan terbesar yang saya buat di awal: melatih model vision tanpa data augmentation. Dataset kecil (ribuan gambar) sangat rentan overfitting. Augmentasi memperbanyak variasi data secara artifisial — rotasi, flip, crop, perubahan warna — sehingga model tidak menghafal pixel-level tapi benar-benar belajar fitur.
from torchvision import transforms
train_transform = transforms.Compose([
transforms.RandomHorizontalFlip(p=0.5),
transforms.RandomRotation(degrees=15),
transforms.RandomCrop(32, padding=4),
transforms.ColorJitter(brightness=0.2, contrast=0.2),
transforms.ToTensor(),
transforms.Normalize(
mean=[0.4914, 0.4822, 0.4465], # mean per channel RGB
std=[0.2023, 0.1994, 0.2010]
),
])
# Transformasi untuk validasi — tanpa augmentasi!
val_transform = transforms.Compose([
transforms.ToTensor(),
transforms.Normalize(
mean=[0.4914, 0.4822, 0.4465],
std=[0.2023, 0.1994, 0.2010]
),
])Aturan sederhana: augmentasi hanya untuk training. Validasi dan testing harus menggunakan gambar asli tanpa distorsi agar metrik evaluation adil. Normalize dengan mean dan std dataset CIFAR-10 di atas sudah cukup standar untuk percobaan awal.
Project Praktis: Image Classifier dengan CNN
Setelah memahami semua komponen, saatnya merangkai semuanya. Berikut CNN sederhana untuk klasifikasi CIFAR-10 yang saya tulis dari nol — bukan transfer learning — supaya memahami alur dari awal sampai akhir.
import torch
import torch.nn as nn
import torchvision
import torchvision.transforms as transforms
# === Data ===
transform = transforms.Compose([
transforms.RandomHorizontalFlip(),
transforms.RandomCrop(32, padding=4),
transforms.ToTensor(),
transforms.Normalize((0.5, 0.5, 0.5), (0.5, 0.5, 0.5)),
])
trainset = torchvision.datasets.CIFAR10(
root="./data", train=True, download=True, transform=transform
)
trainloader = torch.utils.data.DataLoader(
trainset, batch_size=64, shuffle=True
)
testset = torchvision.datasets.CIFAR10(
root="./data", train=False, download=True, transform=transform
)
testloader = torch.utils.data.DataLoader(
testset, batch_size=64, shuffle=False
)
# === Model ===
class SimpleCNN(nn.Module):
def __init__(self):
super().__init__()
self.features = nn.Sequential(
nn.Conv2d(3, 32, 3, padding=1),
nn.ReLU(),
nn.MaxPool2d(2),
nn.Conv2d(32, 64, 3, padding=1),
nn.ReLU(),
nn.MaxPool2d(2),
nn.Conv2d(64, 128, 3, padding=1),
nn.ReLU(),
nn.AdaptiveAvgPool2d(1), # global average pooling
)
self.classifier = nn.Linear(128, 10)
def forward(self, x):
x = self.features(x)
x = x.view(x.size(0), -1)
return self.classifier(x)
model = SimpleCNN()
criterion = nn.CrossEntropyLoss()
optimizer = torch.optim.Adam(model.parameters(), lr=1e-3)
# === Training ===
for epoch in range(10):
model.train()
total_loss = 0
for images, labels in trainloader:
outputs = model(images)
loss = criterion(outputs, labels)
optimizer.zero_grad()
loss.backward()
optimizer.step()
total_loss += loss.item()
# Evaluasi
model.eval()
correct = 0
total = 0
with torch.no_grad():
for images, labels in testloader:
outputs = model(images)
_, predicted = torch.max(outputs, 1)
total += labels.size(0)
correct += (predicted == labels).sum().item()
acc = 100 * correct / total
print(f"Epoch {epoch+1}/{10} — "
f"Loss: {total_loss/len(trainloader):.4f}, "
f"Accuracy: {acc:.2f}%")CNN sederhana ini saja sudah bisa mencapai sekitar 70-72% akurasi pada CIFAR-10 dalam 10 epoch. Dengan transfer learning (ResNet-18), angka yang sama bisa dicapai dalam 3-5 epoch dengan akurasi akhir di atas 90%. Perbedaannya sangat jelas — transfer learning benar-benar mengubah praktik computer vision.
Object Detection dan Image Segmentation
CNN tidak hanya untuk klasifikasi gambar. Dua ekstensi penting yang saya pelajari:
Object Detection menjawab pertanyaan “apa dan di mana?” — memberi label DAN bounding box. YOLO (You Only Look Once) memecah gambar menjadi grid, memprediksi bounding box + kelas untuk setiap grid cell dalam satu forward pass — sangat cepat, cocok untuk real-time. Faster R-CNN lebih akurat tapi lebih lambat: region proposal network (RPN) menghasilkan kandidat region, lalu classifier menentukan kelas. Pilihan antara keduanya bergantung pada kebutuhan: kecepatan (YOLO) vs akurasi (Faster R-CNN).
Image Segmentation melangkah lebih jauh: setiap piksel diberi label. Semantic segmentation mengelompokkan semua piksel berdasarkan kelas (semua piksel kucing = “kucing”), tanpa membedakan individu. Instance segmentation melakukan keduanya — setiap piksel dilabeli kelas DAN dipisahkan per objek individu. Arsitektur seperti Mask R-CNN menggabungkan Faster R-CNN dengan mask branch per instance.
Evaluasi: Top-1 vs Top-5 Accuracy
Dalam benchmark vision, terutama ImageNet, ada dua metrik utama yang sering saya lihat:
- Top-1 Accuracy: prediksi dengan skor tertinggi harus benar. Ini metrik ketat — model harus yakin dan tepat.
- Top-5 Accuracy: salah satu dari lima prediksi teratas harus benar. Lebih realistis untuk dataset dengan ribuan kelas yang mirip (misalnya puluhan jenis anjing).
Top-5 accuracy selalu lebih tinggi dari Top-1. Misalnya, ResNet-50 pada ImageNet mencapai ~76% Top-1 dan ~93% Top-5. Gap 17% ini menunjukkan bahwa model sering “hampir benar” — memprediksi kelas yang dekat secara visual tapi bukan kelas yang tepat. Ketika memilih model untuk tugas sendiri, perhatikan kedua metrik ini. Top-1 cocok untuk kasus di mana kepastian mutlak diperlukan; Top-5 lebih toleran.
Arsitektur Klasik: Rangkaian Blok
Resep klasik CNN seperti VGG: tumpukan blok (convolution + ReLU) beberapa kali, diselingi max pooling, lalu diakhiri flatten atau global average pooling sebelum dense layer output. Keluarga ResNet menambahkan residual connection — jalur pintas yang menyambungkan output awal blok ke akhirnya — untuk membuat jaringan sangat dalam tetap bisa dilatih. Untuk pekerjaan sendiri, jangan menulis dari nol: pakai transfer learning, ambil model pretrained lalu fine-tune di data Anda.
Catatan penutup: dunia vision kini tidak lagi eksklusif milik CNN — Vision Transformer mulai mengambil alih di banyak benchmark. Tapi CNN tetap fondasi yang wajib dipahami: ringkas, efisien, dan intuisinya (filter geser + hierarki) terpakai sampai arsitektur modern sekalipun.
Catatan Pribadi: Momen Paham Kenapa CNN Cocok untuk Gambar
Ada satu momen yang saya ingat jelas saat belajar CNN. Saya sedang menulis convolution manual dengan filter edge detection pada gambar sederhana. Ketika saya mencetak feature map hasilnya, saya bisa melihat tepi objek “menyala” — piksel bernilai tinggi muncul tepat di batas antara gelap dan terang. Saat itu saya baru menyadari: CNN bukan hanya arsitektur neural network biasa, tapi sebuah mesin untuk mengekstrak fitur visual secara hierarkis. Layer pertama menangkap tepi, layer berikutnya menggabungkan tepi jadi sudut, lalu tekstur, lalu bagian objek, dan akhirnya seluruh objek.
Intuisi ini sama seperti cara manusia melihat: kita tidak menganalisis setiap piksel satu per satu. Otak kita secara otomatis memproses pola lokal, lalu menyusunnya menjadi pemahaman global. CNN meniru proses itu — dan menyadari kemiripan itu membuat saya lebih menghargai desain arsitektur ini.
Komentar Terbaru