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:

python
"""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:

text
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

Citra digits dan empat feature map hasil kernel klasik

Mari kita telaah kode di atas baris per baris:

  1. Baris 3-4 (matplotlib.use("Agg")): backend tanpa tampilan agar skrip bisa berjalan di server tanpa monitor dan tetap menghasilkan berkas PNG.
  2. 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.
  3. Baris 25-34: empat kernel dideklarasikan sebagai array NumPy. Kernel tepi memakai bobot bulat positif-negatif, blur membagi rata 1/9, dan sharpen memusatkan bobot 5 pada piksel tengah.
  4. 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.
  5. Baris 39-44: keempat kernel dijalankan; hasilnya dikumpulkan dalam dictionary hasil — setiap entri adalah satu feature map 6x6.
  6. Baris 49-54: setiap feature map dicetak beserta statistik ringkasnya; nilai minimum dan maksimum memperlihatkan seberapa kuat kernel “menyala” pada daerah tertentu.
  7. Baris 57-66: lima panel (input + empat feature map) digambar dengan imshow skala abu-abu lalu disimpan ke filter_hasil.png pada resolusi dpi=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 #

KernelPeran pada gambarStatus pada CNN modern
Sobel/tepideteksi batas objekdipelajari otomatis di layer awal
Blur/boxpenghalusandipelajari sebagai kombinasi channel
Sharpenpenegasan detailmuncul dari pelatihan
Kernel 1x1campuran antar channel tanpa melihat tetanggadipakai eksplisit (bottleneck Inception)
Depthwisesatu kernel per channel, bukan campurandasar 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 .