Setiap estimator scikit-learn menetapkan kontrak bentuk data sejak dilatih, dan kontrak yang sama berlaku pada saat inferensi, yaitu saat model digunakan untuk memprediksi data baru. Topik ini membahas jebakan yang paling sering muncul pada tahap tersebut: data prediksi satu baris yang berbentuk satu dimensi, peringatan nama fitur yang mudah diabaikan, keunggulan prediksi batch daripada satu per satu, serta kesesuaian kolom antara pelatihan dan inferensi pada pipeline. Seluruh demonstrasi menggunakan dataset bawaan scikit-learn dengan keluaran asli yang dapat dijalankan ulang.
Kontrak dua dimensi#
Metode fit menerima data dalam bentuk dua dimensi, yaitu matriks dengan baris sebagai sampel dan kolom sebagai fitur. Metode predict menerapkan kontrak yang sama, tetapi kesalahan bentuk justru paling sering terjadi pada metode ini: inferensi biasanya memproses satu baris, dan satu baris yang ditulis sebagai larik biasa berbentuk (n_features,), bukan (1, n_features). scikit-learn menolak bentuk itu dengan ValueError, yaitu error karena nilai yang tidak sesuai, alih-alih menebak maksud pemanggilnya.
Program berikut melatih LogisticRegression pada dataset iris, lalu mencoba memprediksi satu bunga yang dikirim tanpa pembungkus dimensi. Pesan errornya ditangkap dengan blok try dan dicetak utuh.
from sklearn.datasets import load_iris
from sklearn.linear_model import LogisticRegression
iris = load_iris()
model = LogisticRegression(max_iter=1000)
model.fit(iris.data, iris.target)
# satu bunga iris dikirim sebagai larik satu dimensi berbentuk (4,)
baris = iris.data[0]
print("bentuk tanpa reshape:", baris.shape)
try:
model.predict(baris)
except ValueError as e:
print("ValueError:", e)
# perbaikan: bungkus baris menjadi matriks berbentuk (1, 4)
prediksi = model.predict(baris.reshape(1, -1))
print("prediksi setelah reshape :", iris.target_names[prediksi[0]])bentuk tanpa reshape: (4,)
ValueError: Expected 2D array, got 1D array instead:
array=[5.1 3.5 1.4 0.2].
Reshape your data either using array.reshape(-1, 1) if your data has a single feature or array.reshape(1, -1) if it contains a single sample.
prediksi setelah reshape : setosaPesan error tersebut sekaligus menawarkan dua perbaikan, dan pemilihannya bergantung pada makna data. Metode reshape(-1, 1) digunakan apabila yang dimiliki adalah satu fitur dengan banyak sampel, sedangkan reshape(1, -1) digunakan apabila yang dimiliki adalah satu sampel dengan banyak fitur, seperti satu bunga iris dengan empat angka pengukurannya. Pada keluaran, prediksi baru berhasil setelah baris itu dibungkus menjadi matriks berbentuk (1, 4), dan hasilnya setosa.
Perbedaan kedua bentuk itu dirangkum pada diagram berikut.
Nama fitur pada data inferensi#
Pelatihan pada DataFrame membuat estimator mengingat nama kolomnya. Apabila prediksi dikirim sebagai array polos tanpa nama, scikit-learn tetap menjalankan prediksi, tetapi memunculkan peringatan X does not have valid feature names. Peringatan ini bermakna: model tidak dapat memastikan urutan kolom yang dikirim sama dengan urutan kolom saat pelatihan, sehingga kesesuaian itu menjadi tanggung jawab pemanggil.
Keberbahayaannya terletak pada sifat peringatan yang bukan error. Prediksi tetap menghasilkan angka, dan sistem dengan banyak peringatan lain mudah melewatinya. Apabila sumber data ternyata menukar urutan kolom, model menghitung dengan fitur yang salah posisi dan hasilnya salah tanpa sinyal apa pun. Perbaikannya sederhana, yaitu mengirim DataFrame satu baris dengan nama kolom yang sama seperti pelatihan.
Program berikut mendemonstrasikan kedua kondisi itu pada pipeline yang dilatih dengan DataFrame iris.
import warnings
import pandas as pd
from sklearn.datasets import load_iris
from sklearn.linear_model import LogisticRegression
from sklearn.pipeline import make_pipeline
from sklearn.preprocessing import StandardScaler
iris = load_iris()
X = pd.DataFrame(iris.data, columns=iris.feature_names)
y = iris.target
# pipeline dilatih pada DataFrame sehingga mengenal nama fitur
pipeline = make_pipeline(StandardScaler(), LogisticRegression(max_iter=1000))
pipeline.fit(X, y)
# jalur pertama: array polos tanpa nama fitur
with warnings.catch_warnings(record=True) as tercatat:
warnings.simplefilter("always")
hasil = pipeline.predict(X.iloc[:1].values)
print("prediksi dengan array polos :", iris.target_names[hasil[0]])
for w in tercatat:
print("peringatan :", w.message)
# jalur kedua: DataFrame satu baris dengan nama kolom yang sama
baris = pd.DataFrame([iris.data[0]], columns=iris.feature_names)
hasil2 = pipeline.predict(baris)
print("prediksi dengan DataFrame :", iris.target_names[hasil2[0]])prediksi dengan array polos : setosa
peringatan : X does not have valid feature names, but StandardScaler was fitted with feature names
prediksi dengan DataFrame : setosaKeluaran memperlihatkan kedua jalur itu. Prediksi array polos tetap menghasilkan setosa, tetapi peringatan feature names tercetak di bawahnya sebagai tanda bahwa kesesuaian kolom tidak lagi diperiksa. Prediksi DataFrame satu baris bersih tanpa peringatan karena nama dan urutan kolomnya identik dengan pelatihan. Aturan yang sama berlaku pada pipeline yang lebih besar: input inferensi harus memuat kolom yang sama dengan kolom pelatihan, karena langkah transform di dalam pipeline mengenali kolom berdasarkan namanya. Kolom yang hilang membuat tahap transform berhenti dengan error, sedangkan perlakuan terhadap kolom tambahan bergantung pada transformer yang digunakan.
Prediksi batch daripada satu per satu#
Metode predict menerima banyak baris sekaligus, dan pemanggilan batch, yaitu satu pemanggilan untuk sekelompok sampel, jauh lebih efisien daripada memanggil predict satu kali per baris. Setiap pemanggilan membayar biaya tetap yang tidak terkait ukuran datanya, misalnya pemeriksaan bentuk array, pemeriksaan nama fitur, penyalinan data, dan pembuatan objek hasil. Biaya tetap itu dibayar satu kali pada pemanggilan batch, tetapi dibayar 569 kali apabila dataset breast_cancer diprediksi baris demi baris.
Program berikut membandingkan kedua cara pada 569 sampel breast_cancer. Nilai waktu sengaja tidak dicetak karena bergantung pada mesin yang menjalankannya, sedangkan hubungan lebih cepat dan kesamaan hasilnya bersifat tetap untuk contoh ini.
import time
import numpy as np
from sklearn.datasets import load_breast_cancer
from sklearn.linear_model import LogisticRegression
from sklearn.pipeline import make_pipeline
from sklearn.preprocessing import StandardScaler
bc = load_breast_cancer()
pipeline = make_pipeline(StandardScaler(), LogisticRegression(max_iter=1000))
pipeline.fit(bc.data, bc.target)
X = bc.data
# prediksi satu per satu dalam perulangan, satu sampel per pemanggilan
mulai = time.perf_counter()
satu_per_satu = np.array([pipeline.predict(x.reshape(1, -1))[0] for x in X])
durasi_loop = time.perf_counter() - mulai
# prediksi batch, seluruh sampel dalam satu pemanggilan
mulai = time.perf_counter()
batch = pipeline.predict(X)
durasi_batch = time.perf_counter() - mulai
print("jumlah sampel :", X.shape[0])
print("hasil identik :", np.array_equal(satu_per_satu, batch))
print("batch lebih cepat :", durasi_batch < durasi_loop)jumlah sampel : 569
hasil identik : True
batch lebih cepat : TrueBaris “hasil identik : True” menegaskan bahwa batch bukan kompromi kualitas, sedangkan baris “batch lebih cepat : True” menunjukkan perbedaan efisiensinya. Untuk layanan yang menerima permintaan satu baris, kebiasaan yang baik adalah menampung permintaan sesaat lalu memprediksi sekumpulan kecil sekaligus, sehingga biaya tetap per pemanggilan tidak dibayar untuk setiap baris.
Kesalahan umum#
Kesalahan pertama adalah memilih arah reshape yang keliru. reshape(-1, 1) yang seharusnya untuk satu fitur kerap digunakan pada satu sampel, sehingga larik (4,) berubah menjadi matriks (4, 1). Error yang muncul berikutnya bahkan lebih menyesatkan, yaitu pesan ketidakcocokan jumlah fitur, karena scikit-learn membaca (4, 1) sebagai empat sampel dengan satu fitur. Kuncinya adalah mengingat makna tiap sumbu, yaitu baris untuk sampel dan kolom untuk fitur.
Kesalahan kedua adalah memperlakukan peringatan feature names sebagai kebisingan yang boleh diabaikan. Prediksi tanpa nama kolom tetap berjalan, dan selama urutan kolom kebetulan benar, hasilnya benar pula. Ketika sumber data berubah urutan, hasilnya salah tanpa sinyal. Mengikat pelatihan dan inferensi pada daftar kolom yang sama menutup kemungkinan itu.
Kesalahan ketiga adalah membiarkan daftar kolom inferensi tidak sinkron dengan daftar kolom pelatihan. Skema berkas data yang berubah, misalnya satu kolom diganti namanya, membuat inferensi berhenti dengan error pada tahap transform. Definisi daftar kolom sebaiknya ditulis satu kali dan digunakan bersama oleh kode pelatihan maupun kode inferensi.
Ringkasan#
- fit dan predict sama-sama menuntut matriks dua dimensi, dan satu baris dikirim dengan reshape(1, -1) atau DataFrame satu baris.
- Pelatihan pada DataFrame mengikat nama kolom, dan prediksi tanpa nama memunculkan peringatan yang menandai risiko hasil salah tanpa sinyal.
- Prediksi batch memangkas biaya tetap per pemanggilan tanpa mengubah hasilnya.
- Input inferensi wajib berupa kolom yang sama dengan pelatihan, dan pipeline menjaga kesesuaian itu selama transformasinya tidak ditiru di luar objeknya.
Referensi#
- 6.1. Common pitfalls and recommended practices — scikit-learn documentation — Pitfall data dan rekomendasi praktik yang menjadi dasar sebagian besar saran topik ini.
- pandas.DataFrame — pandas documentation — Rujukan DataFrame satu baris yang digunakan untuk mengirim nama kolom saat inferensi.
- sklearn.pipeline.Pipeline — scikit-learn documentation — Rujukan pipeline yang menuntut kolom inferensi sama dengan kolom pelatihannya.
- sklearn.datasets.load_iris — scikit-learn documentation — Dataset iris yang digunakan pada contoh bentuk data dan peringatan nama fitur.
- sklearn.datasets.load_breast_cancer — scikit-learn documentation — Dataset breast_cancer yang digunakan pada perbandingan prediksi batch.