Convolution, filter, stride, dan pooling kini sudah kita kuasai satu per satu. Pertanyaan penutup rangkaian dasar ini: apa sebenarnya yang mengalir di antara layer-layer CNN? Jawabannya adalah feature map — satu bidang respons untuk setiap filter, dan tumpukan feature map inilah yang membangun hierarki pengenalan visual. Pada chapter ini kita menjalankan nn.Conv2d sungguhan pada dataset digits, membaca statistik tiap channel keluarannya, lalu melacak pertumbuhan receptive field yang membuat layer dalam “melihat” lebih luas daripada kernel kecilnya.
Satu Filter, Satu Feature Map #
Feature map adalah keluaran satu filter terhadap seluruh gambar: angka besar berarti pola yang dicari filter tersebut hadir di posisi itu, angka kecil berarti tidak. Karena satu layer convolution memproses banyak filter sekaligus, keluarannya berbentuk tensor (N, out_channels, H', W') — jumlah channel keluaran persis sama dengan jumlah filter, dan setiap channel adalah feature map tersendiri. Ukuran spasial H' x W' ditentukan oleh kombinasi stride, padding, dan dilation dari chapter sebelumnya.
Berkas feature_map_demo.py menjalankan satu layer nn.Conv2d dengan 6 filter pada 8 citra digits, lalu menumpuk dua layer tambahan di atasnya:
"""Feature map: banyak filter menghasilkan banyak channel keluaran."""
import torch
import torch.nn as nn
from sklearn.datasets import load_digits
torch.manual_seed(42)
digits = load_digits()
batch = torch.tensor(digits.images[:8], dtype=torch.float32).reshape(8, 1, 8, 8)
# Satu layer Conv2d: 1 channel masuk, 6 filter keluar
conv = nn.Conv2d(in_channels=1, out_channels=6, kernel_size=3, padding=1)
with torch.no_grad():
fitur = conv(batch)
print("input batch :", tuple(batch.shape))
print("feature map :", tuple(fitur.shape))
print("6 filter -> 6 channel keluaran, ukuran spasial dijaga padding=1")
print()
print("statistik per channel (8 contoh, channel c):")
for c in range(6):
fc = fitur[:, c]
print(f" channel {c}: mean {fc.mean():7.3f} std {fc.std():6.3f} "
f"min {fc.min():7.3f} max {fc.max():7.3f}")
print()
print("feature map contoh pertama, channel 0 (8x8):")
fm0 = torch.round(fitur[0, 0] * 100) / 100
print(fm0)
print()
# Tumpukan layer: channel bertambah, receptive field ikut bertambah
conv2 = nn.Conv2d(6, 12, kernel_size=3, padding=1)
conv3 = nn.Conv2d(12, 24, kernel_size=3, padding=1)
with torch.no_grad():
fitur2 = conv2(fitur)
fitur3 = conv3(fitur2)
print("setelah layer ke-2:", tuple(fitur2.shape))
print("setelah layer ke-3:", tuple(fitur3.shape))
print()
# Receptive field: 1 + jumlah (kernel - 1) untuk stride 1
print("pertumbuhan receptive field (semua kernel 3x3, stride 1):")
rf = 1
for layer_ke in (1, 2, 3):
rf += 3 - 1
print(f" setelah layer ke-{layer_ke}: receptive field = {rf}x{rf}")
print()
print("satu kernel 7x7 : 7*7 = 49 bobot per channel, RF 7x7")
print("tiga kernel 3x3 bertumpuk: 3*9 = 27 bobot per channel, RF 7x7")Output yang Dihasilkan:
input batch : (8, 1, 8, 8)
feature map : (8, 6, 8, 8)
6 filter -> 6 channel keluaran, ukuran spasial dijaga padding=1
statistik per channel (8 contoh, channel c):
channel 0: mean 4.791 std 4.462 min -2.176 max 17.167
channel 1: mean 3.817 std 3.810 min -2.833 max 16.589
channel 2: mean -2.776 std 3.109 min -11.410 max 2.278
channel 3: mean -0.138 std 2.505 min -10.839 max 7.601
channel 4: mean 3.927 std 3.806 min -0.586 max 13.979
channel 5: mean -1.979 std 2.676 min -10.535 max 3.266
feature map contoh pertama, channel 0 (8x8):
tensor([[ 0.1000, 4.2500, 7.5600, 5.0500, 7.4200, 5.5600, -1.0500, -0.7200],
[ 0.9800, 5.5700, 3.5600, 5.7800, 13.6000, 9.4700, 4.3600, 0.3300],
[ 1.4700, 4.1800, 4.1800, 9.2800, 9.2200, 10.0600, 8.3500, 2.5200],
[ 1.6000, 3.6000, 5.9600, 6.8500, 2.9300, 6.5800, 7.0800, 3.2900],
[ 1.3000, 4.4500, 6.8900, 4.1100, 3.8000, 5.8100, 5.9400, 3.4500],
[ 0.5600, 5.8100, 7.8900, 7.2200, 4.8000, 2.5900, 5.8200, 4.2800],
[-0.0800, 2.9000, 9.0800, 11.9300, 0.8900, 3.6900, 8.7700, 1.8800],
[-0.0600, -0.0400, 4.5200, 5.8300, 6.4500, 9.0300, 3.1500, 0.1000]])
setelah layer ke-2: (8, 12, 8, 8)
setelah layer ke-3: (8, 24, 8, 8)
pertumbuhan receptive field (semua kernel 3x3, stride 1):
setelah layer ke-1: receptive field = 3x3
setelah layer ke-2: receptive field = 5x5
setelah layer ke-3: receptive field = 7x7
satu kernel 7x7 : 7*7 = 49 bobot per channel, RF 7x7
tiga kernel 3x3 bertumpuk: 3*9 = 27 bobot per channel, RF 7x7Mari kita telaah kode di atas baris per baris:
- Baris 9-10: delapan citra digits dijadikan tensor
(8, 1, 8, 8)— format NCHW batch 8, channel 1, gambar 8x8, mengikuti konvensi chapter Representasi Gambar . - Baris 13 (
nn.Conv2d): argumenout_channels=6berarti 6 filter dipelajari; bobot layer berbentuk(6, 1, 3, 3)— 6 filter, masing-masing menyapu 1 channel masukan dengan kernel 3x3.padding=1menjaga resolusi 8x8 tetap. - Baris 14-15: forward pass dibungkus
torch.no_grad()karena kita hanya mengamati, bukan melatih — kebiasaan yang dijelaskan pada chapter Membangun Model dengan nn.Module . - Baris 17-19: keluaran berbentuk
(8, 6, 8, 8)— 8 contoh, 6 feature map berukuran 8x8. Angka 6 pada dimensi kedua persis mengikutiout_channels. - Baris 22-26: statistik per channel berbeda nyata: channel 2 bernada negatif (mean
-2.776, maksimum hanya2.278) sedangkan channel 0 bernada positif (maksimum17.167). Keragaman ini tanda tiap filter merespons pola berbeda — satu filter bisa menyukai garis terang, filter lain menyukai daerah gelap. - Baris 29-31: satu feature map dicetak penuh sebagai matriks 8x8 — angka tinggi memusat di wilayah tertentu, itulah “peta” lokasi pola.
- Baris 35-41: dua layer tambahan menaikkan channel dari 6 menjadi 12 lalu 24; tiap layer baru mengolah seluruh channel layer sebelumnya sekaligus, bukan satu-satu.
- Baris 46-52: receptive field dihitung bertambah 2 tiap layer 3x3 stride 1, dan perbandingan penutup menunjukkan tiga kernel 3x3 bertumpuk (27 bobot per channel) menjangkau area yang sama dengan satu kernel 7x7 (49 bobot per channel).
Hierarki Fitur Visual #
Menumpuk layer bukan sekadar memperdalam jaringan; setiap tingkat mengubah jenis pola yang bisa dideteksi, karena filter layer ke-n mengamati feature map — bukan piksel mentah — dari layer ke-n-1:
| Kedalaman | Receptive field | Pola khas yang terdeteksi |
|---|---|---|
| Layer pertama | 3x3 piksel | tepi, bercak, gradien |
| Layer tengah | belasan piksel | tekstur, sudut gabungan, motif berulang |
| Layer akhir | puluhan piksel | bagian objek: lingkaran digit, lengkung |
| Classifier | seluruh gambar | kelas objek utuh |
Pertumbuhan receptive field tersebut berjalan seperti ini:
Satu neuron pada layer ketiga tidak melihat 7x7 piksel mentah sekaligus — neuron tersebut membaca kombinasi 12 feature map layer kedua yang masing-masing sudah merangkum 5x5 piksel. Efek komposisinya sama dengan jendela 7x7 pada gambar asli, itulah makna “receptive field tumbuh”.
Peta besar mekanisme CNN kini lengkap: gambar menjadi tensor, convolution mengekstraksi pola, stride-padding-dilation mengatur bentuk, pooling meringkas, dan feature map bertumpuk menjadi hierarki fitur. Seluruh komponen ini tinggal dirangkai menjadi arsitektur utuh — tugas chapter Arsitektur CNN
beserta penelusuran evolusinya dari LeNet hingga EfficientNet. Katalog resmi nn.Conv2d dan ratusan layer lain bisa dirujuk di docs.pytorch.org/docs/stable/nn.html
, dengan pendalaman visual di CS231n
.