Situs ini dikemudikan dua tombol, seperti mesin pemiliknya. Membaca: j dan k menggulir, d dan u setengah layar, gg ke atas, G ke bawah, H dan L pindah jendela, ? membuka panduan. Spasi adalah kunci leader Neovim dan mengurus isi: Spasi lalu h beranda, r riset, p proyek, g gear, a tentang, / tag, atau Spasi lalu angka untuk melompat ke jendela bernomor itu. Home adalah prefix tmux dan mengurus jendela: Home lalu c membuka terminal, Home lalu & menutup jendela, Home lalu spasi ke jendela berikutnya. Saat fokus ada di papan ketik, h j k l memindahkan pilihan antar tombol dan Enter membukanya.

RisetR TentangA Gear — G untuk gear — papan ketik, terminal, editor, homelab.G
    ProyekP Tag — / seperti di vim: cari./
    ×
    Menu

    CNN Tanpa Framework: Deteksi Jalan Berlubang dengan LeNet-5 dan NumPy

    Konvolusi, pooling, softmax, dan backpropagation diturunkan sendiri lalu ditulis dengan NumPy — tanpa PyTorch, tanpa TensorFlow, tanpa autograd. Dari akurasi 75,7% sampai 94,4%, dan bagaimana cara membuktikan gradiennya memang benar.

    Klasifikasi citra jalan berlubang versus jalan normal, pakai Convolutional Neural Network yang inti algoritmanya ditulis sendiri dengan NumPy. Tidak ada PyTorch, tidak ada TensorFlow, tidak ada autograd. Konvolusi, pooling, ReLU, softmax, cross-entropy, dan seluruh backpropagation-nya diturunkan secara matematis dulu di kertas, baru dikodekan.

    Library luar hanya dipakai di pinggiran: Pillow untuk baca-tulis citra, scikit-learn untuk membagi data, matplotlib untuk gambar. Semua yang belajar ada di src/cnn/.

    Kenapa dikerjakan begini

    Kalau memakai framework, loss.backward() menyembunyikan seluruh bagian yang sebenarnya ingin saya pahami. Menulis backward pass sendiri memaksa saya menjawab pertanyaan yang di framework tidak pernah muncul: apa turunan MaxPool terhadap input yang tidak terpilih, dan ke mana gradien Conv2D mengalir kalau strided.

    Arsitekturnya LeNet-5, dengan dimensi tiap lapisan dihitung dari output = (W − N + 2P)/S + 1:

    Input 3×48×48
      → Conv1 6@5×5 + ReLU   → 6×44×44
      → MaxPool 2×2          → 6×22×22
      → Conv2 16@5×5 + ReLU  → 16×18×18
      → MaxPool 2×2          → 16×9×9
      → Flatten              → 1296
      → FC-120 + ReLU
      → FC-84  + ReLU
      → FC-2   + Softmax     → {normal, pothole}

    Konvolusinya tidak ditulis sebagai empat loop bersarang. tensor_utils.py berisi im2col/col2im, yang mengubah konvolusi jadi satu perkalian matriks — dan itu juga yang bikin backward pass-nya tidak menjadi mimpi buruk indeks, karena gradiennya tinggal col2im dari hasil perkalian matriks yang sama.

    Membuktikan backprop-nya benar

    Ini bagian yang orang biasanya lewati, dan justru bagian yang tidak boleh dilewati kalau tidak ada autograd yang menjamin apa pun.

    src/cnn/gradcheck.py menghitung gradien numerik dengan beda hingga, lalu membandingkannya dengan gradien analitik hasil backward pass saya. Kalau selisihnya di atas 1e-5, ada turunan yang salah. Perintahnya berdiri sendiri di Makefile, sebelum training:

    make gradcheck   # verifikasi kebenaran backprop, galat < 1e-5
    make train

    Menjalankan training tanpa lulus gradcheck itu membuang berjam-jam untuk mencari tahu kenapa loss-nya tidak turun, padahal jawabannya ada di satu tanda minus.

    75,7% → 94,4%

    Model dasarnya jelek dan overfit: grayscale 32×32, satu model, SGD+momentum, 15 epoch — akurasi uji 75,70%, sementara akurasi latihnya mendekati 100%. Perbaikannya bertahap, dan urutan dampaknya tidak seperti dugaan saya:

    1. Sinyal masukan, ini pengungkit terbesar. Pindah dari grayscale ke RGB dan dari 32×32 ke 48×48. Lubang di jalan itu dibedakan oleh bayangan dan tekstur, dan grayscale membuang separuh petunjuknya. Satu perubahan ini menaikkan satu model dari ~79% ke ~92,5%.
    2. Regularisasi. Dropout 0,3 (inverted, dengan mode train/eval terpisah), L2 weight decay 1e-4 hanya pada bobot W, dan augmentasi daring acak per-batch — flip, geser ±3px, kontras, kecerahan, noise. Augmentasi luring yang statis diganti karena model hafal hasil augmentasinya juga.
    3. Optimasi. Adam dengan lr 1e-3 dan jadwal cosine, 40 epoch, bobot validasi-terbaik disimpan lalu dipulihkan di akhir.
    4. Inferensi. Ensembel tiga seed [42, 7, 123] yang softmax-nya dirata-rata, dipadu test-time augmentation (rata-rata citra asli + versi flip-nya). Ini yang mengangkat 92,5% jadi 94,39%.

    Hasil akhir pada 107 citra uji: akurasi 94,39%, presisi 91,53%, recall 98,18%, F1 94,74%. Confusion matrix-nya TP 54 / TN 47 / FP 5 / FN 1.

    Angka yang paling saya perhatikan bukan akurasinya, tapi recall 98,18% dengan hanya satu false negative. Untuk kasus ini, melewatkan lubang jauh lebih mahal daripada salah menandai jalan mulus.

    Dokumentasi yang ikut diperbarui

    Ada satu pelajaran non-teknis yang cukup memalukan. Commit yang menaikkan akurasi hanya mengubah kode dan figur. Makalah, docs/, dan situsnya masih menuliskan 75,7%, arsitektur grayscale 32×32, dan tidak menyebut ensembel sama sekali. Selama beberapa hari repo ini punya dua kebenaran yang saling bertentangan.

    Sekarang docs/ berisi sembilan halaman — arsitektur, forward pass, backpropagation, training loop, struktur kode, rumus, glosarium, catatan pengembangan — dengan diagram Mermaid, ditambah makalah Bahasa Indonesia dan lampiran yang membedah 17 modul kode baris per baris. Glosariumnya ada karena ini harus bisa dibaca orang yang tidak setiap hari melihat notasi ∂L/∂W.

    Menjalankan

    pip install -r requirements.txt
    make data        # unduh dataset
    make label       # susun kelas → labels.csv
    make preprocess  # dataset.npz
    make gradcheck
    make train
    make eval        # metrik test + confusion matrix
    make visualize   # peta fitur antar lapisan

    Web demo-nya (web/server.py, dikemas dengan Dockerfile sendiri) dideploy ke homelab dan bisa dicoba di potholes.arisjirat.com.

    Acuan notasi: R. Munir, “21, Convolutional Neural Network”, IF4073 Pemrosesan Citra Digital, Teknik Informatika ITB, 2024.

    id en
    rss gh in