Proyek machine learning yang awalnya satu berkas notebook akan tumbuh: data mentah, data olahan, puluhan eksperimen, beberapa model tersimpan, dan laporan untuk pemangku kepentingan. Tanpa tata letak yang disiplin, berkas-berkas itu menumpuk di satu folder dan tidak seorang pun — termasuk Anda tiga bulan ke depan — yakin mana versi data yang dipakai model tertentu. Chapter terakhir section ini merakit struktur folder standar yang menjawab persoalan tersebut: memisahkan data mentah dari data olahan, kode dari artefak, dan parameter dari logika. Struktur ini pula yang dipakai berulang kali oleh proyek-proyek praktik pada Course ini.

Mengapa Butuh Struktur Standar #

Tiga keuntungan muncul ketika struktur proyek konsisten antarproyek dan antaranggota tim:

  1. Reproduksibilitas — siapa pun dapat mengulang alur dari data mentah menjadi model hanya dengan mengikuti README, karena setiap tahap punya rumah yang jelas.
  2. Kolaborasi — rekan tim langsung tahu ke mana menaruh berkas baru; kebiasaan yang sama berlaku ketika mereka membuka proyek lain di kemudian hari.
  3. Kesiapan otomatisasi (MLOps) — pemisahan kode, data, dan artefak memperlancar transisi menuju pipeline terjadwal, uji otomatis, hingga penyajian model, karena skrip sudah bisa dipanggil tanpa intervensi manual.

Poin ketiga layak ditekankan: struktur yang rapi bukan formalitas kosmetik, melainkan prasyarat praktis ketika proyek naik kelas dari eksperimen menjadi sistem yang dioperasikan.

Tata Letak Direktori Standar #

Berikut tata letak yang kita pakai sepanjang Course, dengan nama proyek contoh proyek-deteksi-spam:

text
proyek-deteksi-spam/
├── config/
│   └── params.yaml
├── data/
│   ├── raw/
│   └── processed/
├── models/
├── notebooks/
├── reports/
├── src/
├── README.md
└── requirements.txt

Peran tiap direktori:

  • data/raw/ — data mentah apa adanya: hasil unduhan, ekspor basis data, tangkapan API. Tidak pernah diubah oleh kode.
  • data/processed/ — data hasil pembersihan dan rekayasa fitur; seluruhnya dapat diregenerasi dari raw/ plus kode di src/.
  • notebooks/ — eksplorasi interaktif dengan nama bernomor (01-eksplorasi-data.ipynb, 02-baseline.ipynb) agar urutannya terbaca.
  • src/ — modul Python yang dapat diimpor: data.py (muat dan bersihkan), latih.py (pelatihan), evaluasi.py (metrik dan laporan).
  • models/ — artefak model terlatih (misalnya berkas joblib), berdampingan dengan metadata ringkas asal-usulnya.
  • reports/ — keluaran untuk manusia: ringkasan eksperimen, grafik final, laporan evaluasi.
  • config/ — parameter yang mungkin berubah (seed, path, hyperparameter awal) dipisahkan dari kode agar eksperimen dikendalikan tanpa menyentuh logika.

Higiene README dan requirements.txt #

Dua berkas di akar proyek menentukan apakah orang lain (atau Anda di masa depan) bisa ikut bekerja. README yang sehat memuat sekurang-kurangnya:

  • Deskripsi masalah satu paragraf: apa yang diprediksi dan ukuran suksesnya.
  • Langkah penyiapan: membuat venv, pip install -r requirements.txt, menjalankan sanity check.
  • Cara menjalankan alur: urutan perintah dari data mentah ke model (python -m src.latih).
  • Struktur folder dan sumber data beserta tanggal pengambilannya.

Sementara itu requirements.txt sudah kita bahas pada Chapter Virtual Environment untuk Proyek Machine Learning dan Menginstal scikit-learn dan Ekosistemnya: daftar pustaka dengan versi terkunci ==, diperbarui setiap kali lingkungan berubah disengaja. Berkas kecil ini adalah jembatan antara “di komputer saya jalan” dan “di mana saja jalan”.

Mengelola Path dengan pathlib #

Modul pathlib menggantikan gaya lama os.path.join dengan objek Path yang bisa dioperasikan langsung. Menyusun path menjadi membaca seperti kalimat:

python
from pathlib import Path

AKAR = Path(__file__).resolve().parent.parent
DATA_RAW = AKAR / "data" / "raw"
BERKAS_DATA = DATA_RAW / "spam.csv"

Dua kebiasaan penting dari fragmen di atas. Pertama, Path(__file__).resolve().parent membuat path berbasis lokasi berkas skrip itu sendiri — skrip dapat dijalankan dari folder mana pun tanpa kehilangan arah, tidak seperti path relatif yang bergantung pada current working directory. Kedua, jangan pernah menuliskan path absolut mesin Anda (misalnya /home/nama/proyek/...) ke dalam kode; path semacam itu pindah mesin pasti patah.

Untuk berkas dan folder, pathlib juga menyediakan operasi tingkat tinggi yang kita pakai pada contoh besar di bawah: mkdir(parents=True) untuk membuat folder beserta leluhurnya, write_text()/read_text() untuk I/O ringkas, dan iterdir() untuk menelusuri isi folder.

Membangun Struktur secara Programatik #

Skrip berikut merakit seluruh struktur di atas di folder sementara, mencetaknya sebagai pohon, lalu membaca kembali isi salah satu berkas — demonstrasi pathlib yang sekaligus bisa Anda pakai sebagai kerangka awal proyek nyata:

python
# Membangun struktur proyek machine learning secara programatik.
# Struktur dibuat di folder sementara lalu dicetak sebagai pohon;
# aman dijalankan berulang karena folder sementara otomatis dihapus.

import tempfile
from pathlib import Path

DIREKTORI = [
    "config",
    "data/raw",
    "data/processed",
    "models",
    "notebooks",
    "reports",
    "src",
]

BERKAS = {
    "README.md": "# Proyek Deteksi Spam\n",
    "requirements.txt": "numpy==2.1.3\npandas==2.2.3\nscikit-learn==1.5.2\n",
    ".gitignore": "data/raw/\nmodels/\n",
    "config/params.yaml": "random_state: 42\n",
    "data/raw/.gitkeep": "",
    "data/processed/.gitkeep": "",
    "models/.gitkeep": "",
    "notebooks/01-eksplorasi-data.ipynb": "",
    "reports/laporan.md": "",
    "src/__init__.py": "",
    "src/data.py": "",
    "src/latih.py": "",
    "src/evaluasi.py": "",
}


def cetak_pohon(direktori, prefiks=""):
    anak = sorted(direktori.iterdir(), key=lambda p: (p.is_file(), p.name))
    for i, item in enumerate(anak):
        terakhir = i == len(anak) - 1
        cabang = "└── " if terakhir else "├── "
        akhiran = "/" if item.is_dir() else ""
        print(f"{prefiks}{cabang}{item.name}{akhiran}")
        if item.is_dir():
            lanjutan = prefiks + ("    " if terakhir else "│   ")
            cetak_pohon(item, lanjutan)


with tempfile.TemporaryDirectory() as tmp:
    akar = Path(tmp) / "proyek-deteksi-spam"

    for rel in DIREKTORI:
        (akar / rel).mkdir(parents=True, exist_ok=True)
    for rel, isi in BERKAS.items():
        (akar / rel).write_text(isi, encoding="utf-8")

    print("proyek-deteksi-spam/")
    cetak_pohon(akar)

    print(f"\n{len(DIREKTORI) + 1} direktori, {len(BERKAS)} berkas "
          "dibuat di folder sementara")

    # Buktikan pathlib bisa membaca kembali isi berkas dengan mudah.
    isi_req = (akar / "requirements.txt").read_text(encoding="utf-8")
    print("isi requirements.txt:")
    print(isi_req.rstrip("\n"))

Output yang Dihasilkan:

text
proyek-deteksi-spam/
├── config/
│   └── params.yaml
├── data/
│   ├── processed/
│   │   └── .gitkeep
│   └── raw/
│       └── .gitkeep
├── models/
│   └── .gitkeep
├── notebooks/
│   └── 01-eksplorasi-data.ipynb
├── reports/
│   └── laporan.md
├── src/
│   ├── __init__.py
│   ├── data.py
│   ├── evaluasi.py
│   └── latih.py
├── .gitignore
├── README.md
└── requirements.txt

8 direktori, 13 berkas dibuat di folder sementara
isi requirements.txt:
numpy==2.1.3
pandas==2.2.3
scikit-learn==1.5.2

Mari kita bedah skrip di atas:

  1. Baris 8–16 (DIREKTORI): daftar folder yang akan dibuat, ditulis relatif terhadap akar proyek — makanya data/raw cukup satu entri, bukan dua.
  2. Baris 18–32 (BERKAS): peta nama berkas ke isi awalnya. Berkas placeholder seperti .gitkeep menandai folder kosong agar Git tetap melacaknya; .gitignore sengaja mengecualikan data/raw/ dan models/ karena data besar dan artefak model sebaiknya tidak masuk repositori.
  3. Baris 35–44 (cetak_pohon): fungsi rekursif yang menelusuri folder dan mencetaknya seperti perintah tree: daftar anak diurutkan folder-dulu lalu berkas lewat kunci (p.is_file(), p.name), dan karakter cabang dipilih sesuai posisi terakhir atau bukan.
  4. Baris 47–53 (perakitan): TemporaryDirectory() menyediakan folder kerja yang dijamin terhapus saat blok with selesai — struktur dibangun lengkap tanpa menyisakan jejak di komputer Anda. mkdir(parents=True, exist_ok=True) membuat folder beserta induknya dan tidak protes bila sudah ada; write_text() menulis isi placeholder.
  5. Baris 55–59 (keluaran): pohon dicetak berurut sehingga hasil selalu identik, lalu ringkasan jumlah direktori dan berkas dihitung langsung dari konstanta — angka yang tampil selalu cocok dengan yang didefinisikan.
  6. Baris 61–64: (akar / "requirements.txt").read_text() membuktikan pembacaan kembali berkas cukup dengan satu rantai operasi pathlib.

Dengan interpreter yang tepat, lingkungan terisolasi, pustaka terverifikasi, alat eksplorasi interaktif, dan struktur proyek yang rapi, seluruh persiapan lingkungan kita tuntas. Saatnya masuk ke jantung materi: Section Dasar Machine Learning dimulai dengan Chapter Terminologi Inti Machine Learning, tempat kosakata teknis seperti fitur, label, dan model kita definisikan dengan presisi.

Referensi Resmi #