Hasil pelatihan machine learning belum dapat dipercaya sepenuhnya sebelum angkanya dapat diperoleh ulang dengan langkah yang sama. Topik ini membahas reproducibility, yaitu kemampuan menghasilkan keluaran yang identik dari pelatihan yang diulang, melalui tiga kendali utamanya, yaitu seed acak yang dipatok, sidik jari data, dan pencatatan versi lingkungan. Praktiknya memakai random forest pada dataset kanker payudara yang dilatih berulang, hash SHA-256 untuk menyatakan isi data, dan daftar versi pustaka untuk menyatakan lingkungannya.
Tiga sumber ketidakpastian#
Sumber pertama adalah angka acak itu sendiri. Pemisahan data latih dan data uji, inisialisasi parameter, serta pemilihan fitur acak pada random forest semuanya memakai generator angka acak. Tanpa seed, yaitu angka awal yang menentukan seluruh rangkaian hasil generator, dua pelatihan dengan skrip yang sama menghasilkan pemisahan berbeda dan skor yang berbeda pula.
Sumber kedua adalah data yang berubah. Dataset yang diberi baris baru, nilai yang dikoreksi, atau kolom yang dihapus membuat skrip yang sama melatih model yang berbeda, bahkan dengan seed yang sama. Nama berkas tidak menyatakan isinya, karena berkas dengan nama yang sama dapat berubah tanpa jejak.
Sumber ketiga adalah lingkungan pustaka. Pembaruan scikit-learn, pandas, atau NumPy dapat mengubah algoritme di balik fungsi yang sama, sehingga hasil pelatihan bergeser meskipun seed dan datanya tetap. Ketiga sumber ini bekerja bersamaan, dan satu saja yang tidak tercatat sudah cukup membuat hasil tidak dapat diperoleh ulang.
Seed yang dipatok#
scikit-learn menyediakan parameter random_state pada fungsi yang memuat unsur acak, sedangkan NumPy menyediakan generator melalui np.random.default_rng. Skrip berikut melatih random forest tiga kali, yaitu dua kali dengan seed yang sama dan sekali dengan seed berbeda, lalu menyatakan isi datasetnya melalui hash dan lingkungannya melalui daftar versi pustaka.
"""Memeriksa reproduktivitas: seed yang dipatok, sidik jari data, versi pustaka."""
import hashlib
import json
import numpy as np
import pandas
import sklearn
from sklearn.datasets import load_breast_cancer
from sklearn.ensemble import RandomForestClassifier
from sklearn.model_selection import train_test_split
def latih_dan_uji(data, seed=42):
"""Satu siklus pelatihan penuh dengan seed yang diterima sebagai argumen."""
X_latih, X_uji, y_latih, y_uji = train_test_split(
data.data, data.target, test_size=0.25, random_state=seed, stratify=data.target
)
model = RandomForestClassifier(n_estimators=100, random_state=seed)
model.fit(X_latih, y_latih)
return model.score(X_uji, y_uji)
# sidik jari data: hash dari isi dataset yang benar-benar dipakai
data = load_breast_cancer()
sidik_data = hashlib.sha256(data.data.tobytes()).hexdigest()
# tiga pelatihan: dua kali dengan seed yang sama, sekali dengan seed berbeda
skor_sama_1 = latih_dan_uji(data)
skor_sama_2 = latih_dan_uji(data)
skor_bedabiji = latih_dan_uji(data, seed=7)
lingkungan = {
"scikit-learn": sklearn.__version__,
"pandas": pandas.__version__,
"numpy": np.__version__,
}
print(f"Sidik jari data (sha256) : {sidik_data[:16]}...")
print(f"Skor seed 42, pelatihan 1 : {skor_sama_1:.4f}")
print(f"Skor seed 42, pelatihan 2 : {skor_sama_2:.4f}")
print(f"Seed 42 dapat direproduksi: {skor_sama_1 == skor_sama_2}")
print(f"Skor seed 7 : {skor_bedabiji:.4f}")
print(f"Versi pustaka : {json.dumps(lingkungan)}")Sidik jari data (sha256) : 6b202a2072f9a038...
Skor seed 42, pelatihan 1 : 0.9580
Skor seed 42, pelatihan 2 : 0.9580
Seed 42 dapat direproduksi: True
Skor seed 7 : 0.9510
Versi pustaka : {"scikit-learn": "1.5.2", "pandas": "2.2.3", "numpy": "2.1.3"}Dua pelatihan dengan seed 42 menghasilkan skor yang sama persis, yaitu 0.9580, karena pemisahan data dan seluruh keputusan acak random forest berulang dalam urutan yang sama. Skor seed 7 terbaca 0.9510, dan selisih kecil semacam itu wajar karena seed yang berbeda menghasilkan pemisahan data yang berbeda. Laporan hasil yang jujur selalu menyebut seed yang dipakai, sehingga angkanya dapat diperiksa ulang oleh pihak lain.
Nilai seednya sendiri tidak diatur secara baku, karena seed hanya menandai satu titik di antara banyak pelatihan yang mungkin. Yang dipatuhi adalah konsistensinya, yaitu seed yang sama untuk pelatihan yang sama pada seluruh siklus hidupnya, mulai dari eksperimen, evaluasi, sampai pelatihan versi yang disajikan. Parameter random_state pada scikit-learn menerima bilangan apa pun, dan angka 42 pada jalur ini hanya kesepakatan yang dipakai konsisten.
Sidik jari data#
Hash adalah sidik jari digital yang panjangnya tetap dan dihitung dari isi data. Perubahan satu byte saja mengubah nilainya total. Fungsi hashlib.sha256 menghitungnya dari array fitur yang diserialisasi menjadi byte, sehingga dua pelatihan dapat dinyatakan memakai data yang sama dengan membandingkan sidik jarinya, bukan dengan memercayai nama berkasnya. Pada keluaran skrip, sidik jari tersebut tercetak sebagai 16 karakter pertama, cukup untuk dicatat tanpa memenuhi baris.
Sidik jari ini menjadi penghubung antara model dan datanya. Catatan hasil pelatihan yang menyebut sidik jari data memungkinkan pemeriksaan belakangan, yaitu memastikan dataset yang tersimpan hari ini masih persis dataset yang melatih model tersebut. Apabila nilainya berbeda, dataset sudah berubah dan model lama tidak lagi dapat dinilai pada datanya sendiri.
Cara yang sama dapat diterapkan pada artefak modelnya, misalnya dengan menghitung hash dari berkas model, sehingga satu catatan pelatihan menyatukan data, model, dan hasilnya dalam tiga sidik jari. Kebiasaan ini menghubungkan catatan dengan pemberian versi pada artefak, karena nomor versi pada nama berkas menjadi bermakna hanya bila isinya dapat diverifikasi melalui sidik jari yang tercatat.
Pencatatan lingkungan#
Versi pustaka dicetak oleh skrip melalui atribut __version__, dan cara yang lebih lengkap adalah berkas requirements.txt di dalam proyek yang mengunci versi setiap pustaka. Berkas semacam ini dibuat dari lingkungan yang sedang dipakai melalui perintah pip freeze, lalu dipakai ulang oleh pihak lain untuk memasang lingkungan yang sama. Contoh berikut mengunci versi pustaka yang dipakai sepanjang jalur ini.
scikit-learn==1.5.2
pandas==2.2.3
numpy==2.1.3
matplotlib==3.9.2
joblib==1.5.3pip freeze mencatat seluruh lingkungan yang terpasang, termasuk pustaka turunan yang tidak dipanggil langsung oleh kode, sehingga daftar hasilnya biasanya lebih panjang daripada daftar yang ditulis tangan. Kelebihan baris semacam itu bukan kelemahan, karena setiap baris mencegah satu sumber pergeseran versi. Pemasangan ulang lingkungan dijalankan dengan perintah pip install -r requirements.txt, sehingga mesin mana pun memperoleh versi yang persis sama.
Penguncian versi tidak berhenti pada pustaka Python. Versi bahasa Pythonnya sendiri juga dicatat, karena penerjemah yang berbeda dapat mengubah urutan operasi dan angka hasilnya. Penulisan yang lazim menyebut versi Python di awal berkas requirements atau di README proyek, sehingga pemasangan ulang lingkungan di mesin mana pun mengikuti acuan yang sama, dan bukan menebak dari versi yang kebetulan terpasang.
Kesalahan umum#
- Mencoba banyak seed lalu melaporkan yang skornya tertinggi. Angka terbaik itu lahir dari kebetulan pemisahan data dan tidak mewakili kemampuan model. Pilih satu seed di awal, laporkan apa adanya, dan selisih antar seed masuk ke pembacaan hasil.
- Menyamakan nama berkas dengan isinya. Nama tidak berubah ketika isinya berubah, sehingga catatan berdasarkan nama cepat menyesatkan. Nyatakan data dengan sidik jari hash.
- Mencatat versi pustaka hanya pada pesan percakapan atau catatan lepas. Catatan itu terpisah dari proyek dan cepat usang. Letakkan berkas
requirements.txtdi dalam proyek bersama kodenya. - Melaporkan hasil perbandingan dari lingkungan yang berbeda-beda. Model yang diperbandingkan harus dilatih dengan versi pustaka yang sama, karena pergeseran versi menggeser hasil sebesar pergeseran pemisahan data. Kunci lingkungannya terlebih dahulu, lalu jalankan seluruh perbandingan pada lingkungan yang sama.
- Menyimpan hasil pelatihan tanpa ketiga catatannya. Skor tanpa seed, sidik jari data, dan versi pustaka tidak dapat diperiksa ulang dan cepat kehilangan nilainya. Lampirkan ketiga catatan itu pada setiap hasil yang dilaporkan, termasuk pada eksperimen yang gagal.
Ringkasan#
- Seed yang dipatok membuat pemisahan data dan pelatihan berulang menghasilkan skor yang identik, sebagaimana dua pelatihan seed 42 pada contoh memberi 0.9580.
- Sidik jari hash menyatakan isi data, bukan namanya, sehingga perubahan sekecil apa pun terdeteksi.
- Versi pustaka dikunci melalui berkas
requirements.txtagar lingkungan pelatihan dapat dipasang ulang dengan sama. - Ketiga catatan itu, yaitu seed, sidik jari data, dan lingkungan, menyertai setiap laporan hasil yang ingin dapat diperiksa ulang.
Referensi#
- Random sampling (numpy.random) — NumPy — Dokumentasi generator angka acak NumPy yang menjadi dasar penetapan seed.
- hashlib — Secure hashes and message digests — Python — Dokumentasi pustaka hashlib untuk sidik jari data.
- Requirements File Format — pip — Format berkas requirements untuk mengunci versi pustaka proyek.