AlexNet membuktikan bahwa CNN yang lebih besar bisa menang. Namun arsitekturnya masih campuran: kernel 11x11 di awal, 5x5 dan 3x3 di tengah, tanpa pola yang mudah ditiru. Tahun 2014, dua tim memberikan jawaban berbeda atas pertanyaan yang sama — bagaimana merakit CNN secara sistematis? VGG dari Universitas Oxford menjawab dengan kedalaman seragam: tumpuk kernel 3x3 yang kecil sebanyak mungkin. GoogLeNet (arsitektur Inception) dari Google menjawab dengan percabangan: jalankan beberapa ukuran kernel secara paralel lalu gabungkan hasilnya. Keduanya mengubah cara praktisi merancang CNN, dan kedua prinsipnya masih terasa sampai sekarang.
Prinsip VGG: Kedalaman Seragam dengan Kernel 3x3 #
Tim VGG menemukan sesuatu yang tampak sederhana tetapi berdampak besar: dua convolution 3x3 yang ditumpuk memiliki receptive field yang sama dengan satu convolution 5x5. Receptive field adalah wilayah citra masukan yang memengaruhi satu piksel keluaran. Dua lapis 3x3 melihat wilayah 5x5 karena lapis pertama melihat 3x3, lalu setiap piksel hasilnya melihat 3x3 dari lapis sebelumnya. Keuntungannya dua lapis. Pertama, parameter lebih sedikit — kita buktikan dengan hitungan nyata di bawah. Kedua, ada satu fungsi aktivasi ReLU ekstra di antara kedua convolution sehingga transformasi menjadi lebih kaya.
Berkas vgg_mini.py membuktikan hitungan tersebut dengan angka konkret, lalu merangkai prinsipnya menjadi model kecil yang benar-benar dilatih pada dataset digits:
"""VGG-mini: blok berulang 3x3 + hitungan parameter 3x3 vs 5x5.
Bagian A membuktikan dua conv 3x3 bertumpuk memiliki receptive field
sama dengan satu conv 5x5 tetapi dengan parameter lebih sedikit, serta
peran conv 1x1 sebagai bottleneck penghemat parameter ala Inception.
Bagian B melatih VGG-mini (blok [conv3x3, conv3x3, maxpool] berulang,
channel naik dua kali lipat tiap blok) pada dataset digits.
"""
import torch
import torch.nn as nn
from sklearn.datasets import load_digits
from sklearn.model_selection import train_test_split
torch.manual_seed(42)
# ---------- A. Hitungan parameter ----------
C = 8 # channel tetap untuk perbandingan
dua_konv3 = nn.Sequential(
nn.Conv2d(C, C, kernel_size=3, padding=1),
nn.Conv2d(C, C, kernel_size=3, padding=1),
)
satu_konv5 = nn.Conv2d(C, C, kernel_size=5, padding=2)
param_3 = sum(p.numel() for p in dua_konv3.parameters())
param_5 = sum(p.numel() for p in satu_konv5.parameters())
print("dua conv 3x3 (channel tetap 8):")
print(f" parameter = 2 x (8x8x3x3 + 8) = {param_3}")
print("satu conv 5x5 (channel tetap 8):")
print(f" parameter = 8x8x5x5 + 8 = {param_5}")
print(f" rasio parameter = {param_3 / param_5:.3f}")
print(" receptive field keduanya = 5x5\n")
masuk, sempit, keluar = 128, 32, 32 # bottleneck 1x1 ala Inception
langsung = nn.Conv2d(masuk, keluar, kernel_size=3, padding=1)
bottleneck = nn.Sequential(
nn.Conv2d(masuk, sempit, kernel_size=1), # 1x1: kurangi channel
nn.Conv2d(sempit, keluar, kernel_size=3, padding=1),
)
param_l = sum(p.numel() for p in langsung.parameters())
param_b = sum(p.numel() for p in bottleneck.parameters())
print(f"conv 3x3 langsung 128 -> 32 : {param_l} parameter")
print(f"bottleneck 1x1(128->32) + 3x3 : {param_b} parameter")
print(f" rasio parameter = {param_b / param_l:.3f}\n")
# ---------- B. VGG-mini ----------
def blok_vgg(channel_masuk, channel_keluar):
"""Satu blok VGG: dua conv 3x3 + maxpool; spatial turun setengah."""
return nn.Sequential(
nn.Conv2d(channel_masuk, channel_keluar, kernel_size=3, padding=1),
nn.ReLU(),
nn.Conv2d(channel_keluar, channel_keluar, kernel_size=3, padding=1),
nn.ReLU(),
nn.MaxPool2d(2),
)
# input 8x8 hanya muat 2 blok sebelum spatial menyusut ke 1x1
model = nn.Sequential(
blok_vgg(1, 16), # 8x8 -> 4x4
blok_vgg(16, 32), # 4x4 -> 2x2
nn.Flatten(),
nn.Linear(32 * 2 * 2, 10),
)
print("struktur VGG-mini (channel naik, spatial turun):")
with torch.no_grad():
jejak = torch.randn(1, 1, 8, 8)
print(f" input {tuple(jejak.shape)}")
for nama, bagian in [("blok 1", model[0]), ("blok 2", model[1]),
("flatten", model[2]), ("classifier", model[3])]:
jejak = bagian(jejak)
print(f" {nama:10s} {tuple(jejak.shape)}")
total = sum(p.numel() for p in model.parameters())
print(f" parameter total: {total}\n")
# ---------- C. Latih VGG-mini pada digits ----------
digits = load_digits()
X = digits.data.astype("float32") / 16.0
y = digits.target.astype("int64")
X_latih, X_uji, y_latih, y_uji = train_test_split(
X, y, test_size=0.25, random_state=42, stratify=y
)
mu, sigma = X_latih.mean(), X_latih.std()
X_latih = (X_latih - mu) / sigma
X_uji = (X_uji - mu) / sigma
X_latih_t = torch.tensor(X_latih).reshape(-1, 1, 8, 8)
y_latih_t = torch.tensor(y_latih)
X_uji_t = torch.tensor(X_uji).reshape(-1, 1, 8, 8)
y_uji_t = torch.tensor(y_uji)
criterion = nn.CrossEntropyLoss()
optimizer = torch.optim.Adam(model.parameters(), lr=1e-3)
indeks = torch.randperm(len(X_latih_t),
generator=torch.Generator().manual_seed(42))
JUMLAH_EPOCH = 12
ukuran_batch = 32
for epoch in range(1, JUMLAH_EPOCH + 1):
model.train()
total_loss = 0.0
jumlah_batch = 0
for awal in range(0, len(indeks), ukuran_batch):
pilih = indeks[awal:awal + ukuran_batch]
optimizer.zero_grad()
loss = criterion(model(X_latih_t[pilih]), y_latih_t[pilih])
loss.backward()
optimizer.step()
total_loss += loss.item()
jumlah_batch += 1
model.eval()
with torch.no_grad():
akurasi = (model(X_uji_t).argmax(dim=1) == y_uji_t).float().mean().item()
print(f"epoch {epoch:2d} | loss latih {total_loss / jumlah_batch:.4f} "
f"| akurasi uji {akurasi:.3f}")
print(f"\nakurasi akhir VGG-mini pada digits: {akurasi:.3f}")Output yang Dihasilkan:
dua conv 3x3 (channel tetap 8):
parameter = 2 x (8x8x3x3 + 8) = 1168
satu conv 5x5 (channel tetap 8):
parameter = 8x8x5x5 + 8 = 1608
rasio parameter = 0.726
receptive field keduanya = 5x5
conv 3x3 langsung 128 -> 32 : 36896 parameter
bottleneck 1x1(128->32) + 3x3 : 13376 parameter
rasio parameter = 0.363
struktur VGG-mini (channel naik, spatial turun):
input (1, 1, 8, 8)
blok 1 (1, 16, 4, 4)
blok 2 (1, 32, 2, 2)
flatten (1, 128)
classifier (1, 10)
parameter total: 17658
epoch 1 | loss latih 2.0923 | akurasi uji 0.702
epoch 2 | loss latih 0.6838 | akurasi uji 0.853
epoch 3 | loss latih 0.2954 | akurasi uji 0.902
epoch 4 | loss latih 0.2052 | akurasi uji 0.922
epoch 5 | loss latih 0.1401 | akurasi uji 0.927
epoch 6 | loss latih 0.1051 | akurasi uji 0.924
epoch 7 | loss latih 0.0875 | akurasi uji 0.929
epoch 8 | loss latih 0.0678 | akurasi uji 0.967
epoch 9 | loss latih 0.0584 | akurasi uji 0.969
epoch 10 | loss latih 0.0491 | akurasi uji 0.973
epoch 11 | loss latih 0.0417 | akurasi uji 0.962
epoch 12 | loss latih 0.0340 | akurasi uji 0.969
akurasi akhir VGG-mini pada digits: 0.969Mari kita telaah kode di atas baris per baris:
- Baris 17-28 (bagian A): dua convolution 3x3 bertumpuk (
dua_konv3) dibandingkan dengan satu convolution 5x5 (satu_konv5) pada jumlah channel yang sama. Hasilnya 1168 berbanding 1608 parameter — rasio 0,726 — padahal receptive field keduanya identik 5x5. - Baris 31-42: bottleneck ala Inception — convolution 1x1 menurunkan channel 128 menjadi 32 sebelum convolution 3x3 bekerja — memangkas 36.896 parameter menjadi 13.376 (rasio 0,363).
- Baris 49-58 (
blok_vgg): satu blok VGG adalahconv 3x3 → ReLU → conv 3x3 → ReLU → MaxPool2d. Seluruh model dibangun dari blok identik ini — inilah yang dimaksud “seragam”. - Baris 61-67: karena citra digits hanya 8x8, model hanya memuat dua blok sebelum resolusi spatial menyusut ke 1x1. Pada VGG asli yang memproses citra 224x224, pola sama diulang lima kali.
- Baris 70-79: jejak bentuk membuktikan heuristik desain dari chapter Arsitektur CNN : channel naik dua kali lipat (1 → 16 → 32) tepat ketika resolusi spatial turun setengah (8x8 → 4x4 → 2x2).
- Baris 82-96: data disiapkan mengikuti cetakan chapter sebelumnya — pembagian
stratify, standardisasi memakai statistik data latih saja, dan reshape ke format NCHW. - Baris 103-121: training loop memakai
torch.randpermdenganGeneratorber-seed agar urutan batch deterministik,Adamdengan learning rate 1e-3, dan evaluasi modeeval()tanpa gradien.
Membaca Hasil: Seragaman yang Efektif #
Tiga sinyal patut dicermati dari keluaran. Pertama, hitungan parameter bagian A menegaskan klaim teori: menumpuk kernel kecil selalu lebih hemat daripada satu kernel besar pada receptive field yang sama. Kedua, jejak bentuk memperlihatkan disiplin VGG — setiap blok menurunkan resolusi setengah dan menggandakan channel, sehingga volume fitur per layer tetap terkendali. Ketiga, VGG-mini mencapai akurasi uji 0,969 dalam 12 epoch — performa setara CNN mini dari chapter Arsitektur CNN tetapi dengan struktur yang sepenuhnya bisa diramal dari resep bloknya.
VGG-16 asli mengulang blok serupa hingga 16 layer dengan 138 juta parameter. Kelemahannya justru di sana: ukurannya besar dan mahal untuk dipakai. Kritik inilah yang membuka jalan bagi arsitektur yang lebih efisien seperti EfficientNet pada chapter selanjutnya di section ini .
Prinsip Inception: Percabangan Multi-Skala #
Tim Google mengamati bahwa ukuran objek pada foto bervariasi — kucing bisa memenuhi bingkai atau hanya duduk di pojok. CNN dengan satu ukuran kernel harus memilih kompromi. GoogLeNet menjawab dengan Inception module: dalam satu modul, empat jalur berjalan paralel — convolution 1x1, 3x3, 5x5, dan max pooling — lalu seluruh hasilnya digabung sepanjang dimensi channel. Tiap ukuran objek ditangani jalur yang paling sesuai.
Masalahnya, menjalankan banyak jalur paralel memakan banyak sekali parameter dan komputasi. Solusi GoogLeNet adalah bottleneck 1x1: sebelum setiap jalur 3x3 dan 5x5, convolution 1x1 menurunkan jumlah channel terlebih dahulu. Bagian A berkas vgg_mini.py membuktikan efeknya dengan angka: jalur 128→32 yang tadinya membutuhkan convolution 3x3 seharga 36.896 parameter kini cukup 13.376 parameter lewat pasangan 1x1 + 3x3 — rasio 0,363.
Diagram di atas memperlihatkan struktur inti modul Inception versi sederhana. Jalur 1x1 murni bertindak sebagai kombinasi channel linier yang murah. Jalur pooling memberi kemampuan “melihat wilayah lebih luas” tanpa parameter sama sekali, lalu 1x1 menyamakan jumlah channelnya agar bisa digabung. Gabungan keempat jalur menjadi input bagi modul Inception berikutnya.
Perbandingan Dua Filosofi #
| Aspek | VGG | GoogLeNet (Inception) |
|---|---|---|
| Tahun dan tim | 2014, Universitas Oxford | 2014, Google |
| Ide inti | kedalaman seragam, semua kernel 3x3 | percabangan multi-skala dalam satu modul |
| Parameter (versi asli) | 138 juta (VGG-16) | 6,8 juta (9x lebih hemat) |
| Struktur | blok berulang yang mudah ditiru | modul Inception bertingkat + classifier aux |
| Warisan | resep “3x3 + tumpuk” jadi standar | bottleneck 1x1 dipakai di hampir semua CNN modern |
| Kekurangan | sangat besar dan lambat | struktur lebih rumit diimplementasi |
Kedua filosofi tidak saling meniadakan. Arsitektur modern memakai keduanya sekaligus: blok berulang yang rapi ala VGG, di dalamnya jalur hemat ala Inception. Makalah aslinya layak dibaca langsung: Very Deep Convolutional Networks (VGG) dan Going Deeper with Convolutions (GoogLeNet) .
Kedalaman seragam VGG dan percabangan Inception sama-sama menggandakan kapasitas model. Namun ada masalah yang tidak dapat dijawab keduanya: semakin dalam jaringan, semakin sulit gradien sampai ke layer awal. AlexNet sanggup 8 layer; VGG-19 sudah 19 layer; mencoba 30 layer tanpa bekal justru membuat akurasi turun. Bagaimana jaringan 100 layer bisa dilatih? Jawabannya adalah skip connection — pokok chapter Residual Network .