Layanan API dari chapter sebelumnya dirancang untuk satu permintaan seketika: satu pelanggan ditanya, satu jawaban keluar. Namun kebutuhan prediksi di kedai justru sebaliknya — setiap Senin pagi, pengelola ingin skor churn untuk seluruh pelanggan terdaftar sekaligus, dan tidak ada yang menunggu jawaban itu detik ini juga. Pola kerja semacam ini disebut prediksi batch: seluruh data diproses berkelompok dalam satu eksekusi terjadwal, hasilnya ditulis ke berkas, lalu diambil tim terkait. Pada chapter ini kita menulis skrip batch utuh dari berkas masukan sampai berkas keluaran, menjadwalkannya dengan cron, lalu belajar menjaga memori tetap hemat ketika datanya membesar.
Batch vs Real-Time #
Kedua pola penyajian model ini bukan pesaing, melainkan alat untuk kebutuhan berbeda. Tabel berikut memetakan perbedaannya:
| Aspek | Real-time (API) | Batch |
|---|---|---|
| Pemicu | permintaan masuk dari klien | jadwal (harian, mingguan) atau pemicu lain |
| Latensi jawaban | milidetik sampai detik | menit sampai jam — tidak diharapkan seketika |
| Bentuk keluaran | satu JSON per permintaan | satu berkas hasil berisi seluruh baris |
| Biaya komputasi | mesin harus selalu hidup | mesin bekerja hanya saat jadwal berjalan |
| Contoh di kedai | kasir mengecek pelanggan yang sedang membayar | tim retensi men-skor ulang seluruh pelanggan mingguan |
Karena batch tidak perlu berjalan sepanjang waktu, biayanya jauh lebih murah — cukup satu eksekusi skrip pada waktunya. Konsekuensinya, keputusan yang bersandar pada hasil batch hanya segar sedia jadwalnya; keluhan pelanggan yang baru muncul hari Selasa baru ikut diproses Senin depan.
Skrip Prediksi Batch #
Skrip batch yang sehat punya alur tetap: baca berkas masukan, prediksi seluruh baris, tulis berkas keluaran. Kode berikut menjalankan ketiganya untuk dua belas pelanggan minggu ini — pipeline dari chapter Membuat Pipeline Prediksi
menerima data mentah langsung dari read_csv.
import os
import tempfile
import numpy as np
import pandas as pd
from sklearn.linear_model import LogisticRegression
from sklearn.pipeline import Pipeline
from sklearn.preprocessing import StandardScaler
rng = np.random.default_rng(42)
n = 60
kunjungan = rng.integers(1, 15, n)
belanja = rng.integers(50, 350, n)
skor = 0.8 * (8 - kunjungan) + 0.02 * (belanja - 180) + rng.normal(0, 3, n)
churn = (skor > np.quantile(skor, 0.6)).astype(int)
data_latih = pd.DataFrame({"kunjungan": kunjungan, "belanja": belanja, "churn": churn})
pipeline = Pipeline([
("penskala", StandardScaler()),
("model", LogisticRegression(max_iter=1000)),
]).fit(data_latih[["kunjungan", "belanja"]], data_latih["churn"])
folder_kerja = tempfile.mkdtemp()
df_baru = pd.DataFrame({
"id_pelanggan": [f"P{nomor:03d}" for nomor in range(1, 13)],
"kunjungan": rng.integers(1, 15, 12),
"belanja": rng.integers(50, 350, 12),
})
berkas_masuk = os.path.join(folder_kerja, "pelanggan_minggu_ini.csv")
df_baru.to_csv(berkas_masuk, index=False)
data = pd.read_csv(berkas_masuk)
peluang = pipeline.predict_proba(data[["kunjungan", "belanja"]])[:, 1]
data["peluang_churn"] = peluang.round(3)
data["label"] = np.where(data["peluang_churn"] >= 0.5, "retensi", "aman")
berkas_keluar = os.path.join(folder_kerja, "hasil_prediksi.csv")
data.to_csv(berkas_keluar, index=False)
print(f"berkas masukan : {os.path.basename(berkas_masuk)} ({len(data)} baris)")
print(f"berkas keluaran: {os.path.basename(berkas_keluar)}")
print(f"perlu retensi : {(data['label'] == 'retensi').sum()} pelanggan")
print()
print(data.head(5).to_string(index=False))Output yang Dihasilkan:
berkas masukan : pelanggan_minggu_ini.csv (12 baris)
berkas keluaran: hasil_prediksi.csv
perlu retensi : 7 pelanggan
id_pelanggan kunjungan belanja peluang_churn label
P001 2 106 0.820 retensi
P002 9 148 0.155 aman
P003 2 248 0.942 retensi
P004 10 93 0.063 aman
P005 9 253 0.319 amanMari kita telaah bagian-bagian pentingnya:
- Baris 18-21 (pipeline termuat di skrip): pada produksi, baris ini berganti
joblib.load("model.joblib")— sisanya tidak berubah. Skrip batch memang sebaiknya memuat artefak, bukan melatih ulang. - Baris 23-30 (berkas masukan): data pelanggan baru ditulis dulu sebagai CSV lalu dibaca kembali lewat
read_csv— sengaja meniru alur nyata ketika masukan berasal dari ekspor sistem lain, bukan dari variabel di memori. - Baris 32-35 (prediksi sekaligus): satu panggilan
predict_probamemproses seluruh dua belas baris; hasilnya jadi dua kolom baru — peluang dan label tindakan dengan ambang 0,5. - Baris 37-38 (berkas keluaran):
to_csv(index=False)menuliskan hasil lengkap; lima baris pertama dicetak sebagai bukti, sedangkan berkas penuh milik tim retensi.
Angka penting di ringkasan: tujuh dari dua belas pelanggan melewati ambang retensi. Dalam sekejap, pekerjaan yang tadinya berupa keputusan manual per pelanggan menjadi satu berkas siap tindak lanjut.
Menjadwalkan Eksekusi dengan cron #
Kekuatan batch ada pada pengulangan otomatisnya. Pada server Linux, penjadwal paling sederhana dan paling awet adalah cron: kita mendaftarkan satu baris jadwal, dan sistem menjalankan perintah kita pada waktunya. Pola berikut adalah contoh pendaftarannya — cukup dibaca sebagai gambaran, tidak dijalankan pada sesi materi ini:
# Membuka penyunting jadwal milik pengguna aktif:
# crontab -e
#
# Menjalankan prediksi batch setiap Senin pukul 06:00:
# 0 6 * * 1 /usr/bin/python3 /opt/kedai/prediksi_batch.py >> /opt/kedai/log_batch.txt 2>&1Lima kolom pertama baris jadwal (0 6 * * 1) berarti menit 0, jam 6, tanggal apa pun, bulan apa pun, hari Senin. Bagian >> ... 2>&1 mengalihkan seluruh keluaran skrip — termasuk pesan galat — ke berkas log, sehingga eksekusi Senin pagi yang gagal tetap meninggalkan jejak yang bisa diperiksa. Skrip batch tanpa log adalah jadwal buta: tidak ada yang tahu ia berhasil atau berhenti di tengah jalan.
Ukuran Batch dan Memori #
Skrip di atas memuat seluruh berkas sekaligus — sepenuhnya wajar untuk dua belas baris, dan mulai menjadi masalah pada dua belas juta. read_csv menyediakan parameter chunksize untuk memproses berkas per potongan (chunk): hanya beberapa ribu baris berada di memori pada satu waktu, diproses, lalu digantikan potongan berikutnya.
import os
import tempfile
import numpy as np
import pandas as pd
rng = np.random.default_rng(7)
df_bulanan = pd.DataFrame({
"id_pelanggan": [f"P{nomor:03d}" for nomor in range(1, 13)],
"kunjungan": rng.integers(1, 15, 12),
"belanja": rng.integers(50, 350, 12),
})
folder_kerja = tempfile.mkdtemp()
berkas_bulanan = os.path.join(folder_kerja, "prediksi_bulanan.csv")
df_bulanan.to_csv(berkas_bulanan, index=False)
jumlah_cuplikan = 0
total_baris = 0
pembaca = pd.read_csv(berkas_bulanan, chunksize=4)
for cuplikan in pembaca:
jumlah_cuplikan += 1
total_baris += len(cuplikan)
print(f"cuplikan {jumlah_cuplikan}: {len(cuplikan)} baris dimuat ke memori")
print(f"total cuplikan : {jumlah_cuplikan}")
print(f"total baris : {total_baris}")Output yang Dihasilkan:
cuplikan 1: 4 baris dimuat ke memori
cuplikan 2: 4 baris dimuat ke memori
cuplikan 3: 4 baris dimuat ke memori
total cuplikan : 3
total baris : 12Mari kita telaah bagian-bagian pentingnya:
- Baris 19 (pembaca bercuplikan): dengan
chunksize=4,read_csvtidak lagi mengembalikan satu DataFrame, melainkan iterator yang mengeluarkan DataFrame kecil berisi empat baris setiap kali dipanggil. - Baris 20-23 (proses per cuplikan): di dalam loop inilah pekerjaan sesungguhnya ditempatkan — pada produksi,
pipeline.predict_proba(cuplikan[...])dan penulisan hasil per cuplikan. Contoh kita hanya menghitung baris agar fokus tetap pada mekanismenya. - Baris 25-26 (verifikasi): jumlah baris per cuplikan dijumlahkan kembali menjadi dua belas — pemeriksaan murah bahwa tidak ada baris yang tercecer di antara cuplikan.
Pada data dua belas baris, pola ini terlihat berlebihan; pada berkas transaksi bertahun-tahun, pola inilah yang membuat skrip tetap hidup di mesin ber memorinya 4 GB. Aturan praktisnya: muat sekuerus yang cukup untuk efisiensi (panggilan predict per juta baris ribuan kali lebih lambat daripada per sepuluh ribu), tetapi jangan pernah melebihi apa yang sanggup ditampung memori.
Rangkuman #
- Prediksi batch memproses seluruh baris dalam satu eksekusi terjadwal dan menuliskan hasil ke berkas — jauh lebih murah daripada layanan yang harus hidup terus, dengan konsekuensi hasil hanya segar sesuai jadwal.
- Alur skrip batch yang sehat tetap: baca masukan, prediksi dengan pipeline termuat dari artefak, tulis keluaran, catat ringkasan.
cronmenjadwalkan eksekusi di server; keluaran dan galat wajib dialirkan ke berkas log agar eksekusi gagal tetap terlihat.chunksizepadaread_csvmenjaga memori tetap hemat untuk berkas besar: proses per cuplikan, jumlahkan kembali untuk memastikan tidak ada baris hilang.
Skrip batch mingguan kini berjalan otomatis — tetapi siapa yang memantau hasilnya? Model yang semalam masih sehat bisa diam-diam melorot, dan tanda-tandanya justru muncul lebih dulu pada angka-angka operasional. Kita pasang alat pemantauannya pada chapter berikutnya: Pemantauan Model .
Referensi: