Chapter sebelumnya memakai kernel [[0,1,0],[1,2,1],[0,1,0]] tanpa menjelaskan asal angkanya. Kenyataannya, setiap susunan angka pada kernel adalah sebuah detektor pola: jendela yang cocok dengan polanya memberi respons besar, jendela yang tidak cocok memberi respons kecil. Selama puluhan tahun para peneliti merancang kernel manual — Sobel untuk tepi, Gaussian untuk blur — hingga CNN membalik logika tersebut: biarkan angka kernel dipelajari dari data. Pada chapter ini kita menguji kernel-kernel klasik pada citra nyata agar intuisi “kernel = detektor” terlihat angka demi angka.
Empat Kernel Klasik #
- Tepi vertikal
[[1,0,-1],[1,0,-1],[1,0,-1]]: kolom kiri berbobot positif dan kolom kanan negatif. Jendela yang intensitasnya seragam menghasilkan jumlah nol; jendela yang melintasi perubahan intensitas mendatar menghasilkan angka besar — positif atau negatif tergantung arah perubahan. - Tepi horizontal
[[1,1,1],[0,0,0],[-1,-1,-1]]: prinsip yang sama diputar 90 derajat, peka terhadap perubahan intensitas tegak lurus. - Blur
1/9 x ones(3,3): rata-rata seluruh tetangga; detail tajam terhaluskan dan keluaran bernilai di antara rata-rata lokal. - Sharpen
[[0,-1,0],[-1,5,-1],[0,-1,0]]: piksel tengah diperbesar bobotnya sedangkan tetangga dikurangkan — tepi dan detail menjadi lebih tegas.
Berkas filter_kernel.py menerapkan keempat kernel pada citra angka nol dari dataset digits menggunakan fungsi konvolusi2d dari chapter sebelumnya, lalu menyimpan visualisasinya:
"""Kernel sebagai detektor pola: tepi, blur, dan sharpen pada citra digits."""
import matplotlib
matplotlib.use("Agg")
import matplotlib.pyplot as plt
import numpy as np
from sklearn.datasets import load_digits
np.random.seed(42)
def konvolusi2d(x, kernel):
"""Konvolusi 2D stride 1 tanpa padding (sama seperti chapter sebelumnya)."""
H, W = x.shape
Fh, Fw = kernel.shape
out = np.zeros((H - Fh + 1, W - Fw + 1))
for i in range(out.shape[0]):
for j in range(out.shape[1]):
jendela = x[i:i + Fh, j:j + Fw]
out[i, j] = np.sum(jendela * kernel)
return out
# Empat kernel klasik yang dulu dirancang manual, kini dipelajari model
kernel_tepi_vertikal = np.array([[1, 0, -1],
[1, 0, -1],
[1, 0, -1]], dtype=np.float64)
kernel_tepi_horizontal = np.array([[ 1, 1, 1],
[ 0, 0, 0],
[-1, -1, -1]], dtype=np.float64)
kernel_blur = np.ones((3, 3), dtype=np.float64) / 9.0
kernel_sharpen = np.array([[ 0, -1, 0],
[-1, 5, -1],
[ 0, -1, 0]], dtype=np.float64)
digits = load_digits()
gambar = digits.images[0] # citra angka 0, nilai 0-16
hasil = {
"tepi vertikal": konvolusi2d(gambar, kernel_tepi_vertikal),
"tepi horizontal": konvolusi2d(gambar, kernel_tepi_horizontal),
"blur": konvolusi2d(gambar, kernel_blur),
"sharpen": konvolusi2d(gambar, kernel_sharpen),
}
print("input: digits[0] (8x8, angka 0):")
print(np.round(gambar).astype(int))
print()
for nama, fm in hasil.items():
print(f"feature map kernel {nama} (6x6):")
print(np.round(fm, 1))
print(f" statistik: min {fm.min():.1f}, max {fm.max():.1f}, "
f"rata-rata {fm.mean():.2f}")
print()
# Visualisasi input dan keempat feature map ke satu berkas PNG
panel = [("input", gambar)] + list(hasil.items())
fig, axes = plt.subplots(1, 5, figsize=(12, 3))
for ax, (nama, data) in zip(axes, panel):
ax.imshow(data, cmap="gray")
ax.set_title(nama, fontsize=10)
ax.set_xticks([])
ax.set_yticks([])
plt.tight_layout()
plt.savefig("filter_hasil.png", dpi=110, bbox_inches="tight")
print("berkas filter_hasil.png tersimpan")Output yang Dihasilkan:
input: digits[0] (8x8, angka 0):
[[ 0 0 5 13 9 1 0 0]
[ 0 0 13 15 10 15 5 0]
[ 0 3 15 2 0 11 8 0]
[ 0 4 12 0 0 8 8 0]
[ 0 5 8 0 0 9 8 0]
[ 0 4 11 0 1 12 7 0]
[ 0 2 14 5 10 12 0 0]
[ 0 0 6 13 10 0 0 0]]
feature map kernel tepi vertikal (6x6):
[[-33. -27. 14. 3. 6. 27.]
[-40. -10. 30. -17. -11. 34.]
[-35. 10. 35. -26. -24. 28.]
[-31. 13. 30. -29. -22. 29.]
[-33. 6. 22. -28. -4. 33.]
[-31. -12. 10. -6. 14. 24.]]
statistik: min -40.0, max 35.0, rata-rata -1.42
feature map kernel tepi horizontal (6x6):
[[-13. -2. 10. 10. -9. -18.]
[ -3. 12. 26. 32. 14. 4.]
[ 5. 7. 9. 4. 2. 2.]
[ 1. 1. 0. -5. -4. -3.]
[ -3. -8. -21. -18. -5. 5.]
[ 9. -4. -17. -10. 10. 19.]]
statistik: min -21.0, max 32.0, rata-rata 1.08
feature map kernel blur (6x6):
[[4. 7.3 9.1 8.4 6.6 4.4]
[5.2 7.1 7.4 6.8 7.2 6.1]
[5.2 5.4 4.1 3.3 5.8 5.8]
[4.9 4.9 3.6 3.3 5.9 5.8]
[4.9 5.4 5.4 5.4 6.6 5.3]
[4.1 6.1 7.8 7. 5.8 3.4]]
statistik: min 3.3, max 9.1, rata-rata 5.70
feature map kernel sharpen (6x6):
[[-16. 30. 37. 11. 48. 2.]
[ -4. 45. -20. -23. 24. 16.]
[ 0. 33. -14. -8. 12. 16.]
[ 9. 12. -8. -10. 17. 16.]
[ 2. 29. -17. -17. 31. 15.]
[ -8. 46. -12. 22. 38. -19.]]
statistik: min -23.0, max 48.0, rata-rata 9.31
berkas filter_hasil.png tersimpan
Mari kita telaah kode di atas baris per baris:
- Baris 3-4 (
matplotlib.use("Agg")): backend tanpa tampilan agar skrip bisa berjalan di server tanpa monitor dan tetap menghasilkan berkas PNG. - Baris 12-21 (
konvolusi2d): fungsi jendela-geser-kali-jumlah yang sama persis dengan chapter Convolution Layer — kita tinggal menukar isinya tanpa menulis ulang mekanismenya; inilah buah weight sharing. - Baris 25-34: empat kernel dideklarasikan sebagai array NumPy. Kernel tepi memakai bobot bulat positif-negatif, blur membagi rata
1/9, dan sharpen memusatkan bobot5pada piksel tengah. - Baris 36-37:
digits.images[0]mengambil satu citra 8x8 bernilai 0-16 — perhatikan bahwa kernel tetap bekerja pada rentang nilai apa pun, bukan hanya 0-255. - Baris 39-44: keempat kernel dijalankan; hasilnya dikumpulkan dalam dictionary
hasil— setiap entri adalah satu feature map 6x6. - Baris 49-54: setiap feature map dicetak beserta statistik ringkasnya; nilai minimum dan maksimum memperlihatkan seberapa kuat kernel “menyala” pada daerah tertentu.
- Baris 57-66: lima panel (input + empat feature map) digambar dengan
imshowskala abu-abu lalu disimpan kefilter_hasil.pngpada resolusidpi=110.
Membaca keluarannya: feature map tepi vertikal bernilai ekstrem (-40 hingga 35) tepat pada kolom tempat intensitas berubah tajam — batas kiri dan kanan lubang angka nol — dan mendekati nol pada area rata. Feature map blur hanya berkisar 3.3 sampai 9.1 karena rata-rata lokal tidak pernah seketat nilai aslinya, sedangkan sharpen justru memperlebar rentang hingga 48.
Dirancang versus Dipelajari #
| Kernel | Peran pada gambar | Status pada CNN modern |
|---|---|---|
| Sobel/tepi | deteksi batas objek | dipelajari otomatis di layer awal |
| Blur/box | penghalusan | dipelajari sebagai kombinasi channel |
| Sharpen | penegasan detail | muncul dari pelatihan |
| Kernel 1x1 | campuran antar channel tanpa melihat tetangga | dipakai eksplisit (bottleneck Inception) |
| Depthwise | satu kernel per channel, bukan campuran | dasar arsitektur mobile |
Kita kini tahu apa yang dihitung convolution; kontrol di mana jendela berjalan dan seberapa besar keluarannya diatur lewat tiga knob tambahan — stride, padding, dan dilation — yang dibedah lengkap pada chapter Stride, Padding, dan Dilation . Materi rujukan klasik soal filter visual tersedia pada catatan CS231n dan dokumentasi citra digits di scikit-learn.org/stable/modules/generated/sklearn.datasets.load_digits.html .