Model hanya dapat belajar dari kolom yang diberikan kepadanya, sehingga kolom baru yang dirancang dari pengetahuan domain sering menggerakkan metrik lebih jauh daripada penggantian model. Topik ini membahas penciptaan fitur turunan pada DataFrame kecil berisi log sensor suhu, pengukuran efeknya terhadap skor model regresi, serta pemindahan logika penciptaan fitur ke dalam pipeline agar aturan yang sama dijalankan saat pelatihan maupun saat prediksi data baru.
Fitur turunan dari pengetahuan domain#
Fitur turunan adalah kolom baru yang dihitung dari kolom yang sudah ada dengan aturan yang bersumber dari pemahaman tentang proses di balik data. Aturan itu tidak bisa ditemukan oleh model sendiri, karena model linear misalnya hanya mampu membentuk kombinasi penjumlahan dari fitur mentahnya. Apabila proses nyata di balik target bersifat agregat, memiliki ambang, atau multiplikatif, bentuk tersebut harus disediakan lebih dahulu sebagai kolom.
Tiga bentuk fitur turunan yang paling sering berguna adalah agregasi, selisih, dan ambang domain. Tabel berikut merangkum ketiganya pada kasus log sensor suhu gedung yang menjadi contoh sepanjang topik ini.
| Fitur turunan | Perhitungan | Alasan domain |
|---|---|---|
suhu_rata_7hari | rata-rata bergulir (rolling window) tujuh hari | bangunan menyimpan panas, sehingga beban pendingin mengikuti rata-rata, bukan suhu sesaat |
amplitudo_termal | suhu_max - suhu_min | rentang suhu harian menentukan berapa lama ruangan perlu didinginkan |
derajat_pendinginan | suhu_rata_7hari - 24 bila positif, selain itu nol | beban pendinginan praktis baru muncul di atas ambang 24 °C |
Rata-rata bergulir menghadap ke belakang, yaitu nilai fitur untuk satu hari hanya menggunakan hari itu beserta enam hari sebelumnya. Sifat ini penting karena fitur semacam itu aman dihitung sebelum pemecahan data, berbeda dari statistik yang perhitungannya menyentuh masa depan deret.
Log sensor suhu dan tiga fitur turunannya#
Program berikut membangun 300 hari catatan sensor dengan tiga kolom mentah, yaitu suhu_min, suhu_max, dan kelembapan, kemudian menambahkan tiga fitur turunan dari tabel di atas. Derau harian (noise) diberikan agar nilai sensor tidak identik setiap hari, dan pola musiman diberikan agar suhu bergerak melintasi ambang 24 °C.
import numpy as np
import pandas as pd
# 300 hari catatan sensor suhu pada satu gedung
n = 300
rng = np.random.default_rng(42)
t = np.arange(n)
# suhu harian mengikuti pola musiman ditambah derau harian
suhu_harian = 26 + 6 * np.sin(2 * np.pi * t / 120) + rng.normal(0, 1.0, n)
amplitudo = rng.uniform(3, 9, n)
kelembapan = rng.uniform(55, 90, n)
df = pd.DataFrame({
"suhu_min": suhu_harian - amplitudo / 2,
"suhu_max": suhu_harian + amplitudo / 2,
"kelembapan": kelembapan,
})
# tiga fitur turunan berbasis pengetahuan domain
rata_harian = (df["suhu_min"] + df["suhu_max"]) / 2
df["suhu_rata_7hari"] = rata_harian.rolling(7, min_periods=1).mean()
df["amplitudo_termal"] = df["suhu_max"] - df["suhu_min"]
df["derajat_pendinginan"] = (df["suhu_rata_7hari"] - 24).clip(lower=0)
print("Bentuk data:", df.shape)
kolom = ["suhu_min", "suhu_max", "suhu_rata_7hari", "amplitudo_termal", "derajat_pendinginan"]
print(df[kolom].head(3).round(2).to_string())Bentuk data: (300, 6)
suhu_min suhu_max suhu_rata_7hari amplitudo_termal derajat_pendinginan
0 24.11 28.50 26.30 4.40 2.30
1 22.67 27.88 25.79 5.21 1.79
2 24.78 29.98 26.32 5.20 2.32Keluaran memperlihatkan tiga kolom turunan yang terisi sejak baris pertama berkat min_periods=1. Nilai suhu_rata_7hari pada baris pertama sama dengan rata-rata harian hari itu sendiri, dan baris berikutnya perlahan menggabungkan jendela yang lebih panjang. Kolom derajat_pendinginan bernilai positif karena rata-ratanya berada di atas 24 °C, sedangkan pada deret nyata kolom ini akan bernilai nol pada minggu yang lebih sejuk.
Efeknya pada skor model#
Target pada contoh ini adalah konsumsi listrik harian gedung dalam kWh, yang pada data nyata memang mengikuti rata-rata suhu beberapa hari terakhir alih-alih suhu satu hari. Karena datanya berupa deret waktu, pemecahan data dilakukan secara kronologis, yaitu 210 hari pertama menjadi data latih dan sisanya menjadi data uji. Model yang digunakan sama persis pada kedua sisi perbandingan, yaitu LinearRegression, sehingga satu-satunya yang berbeda adalah keberadaan fitur turunannya.
import numpy as np
import pandas as pd
from sklearn.linear_model import LinearRegression
from sklearn.metrics import r2_score
# data yang sama dengan berkas fitur-suhu.py
n = 300
rng = np.random.default_rng(42)
t = np.arange(n)
suhu_harian = 26 + 6 * np.sin(2 * np.pi * t / 120) + rng.normal(0, 1.0, n)
amplitudo = rng.uniform(3, 9, n)
kelembapan = rng.uniform(55, 90, n)
df = pd.DataFrame({
"suhu_min": suhu_harian - amplitudo / 2,
"suhu_max": suhu_harian + amplitudo / 2,
"kelembapan": kelembapan,
})
rata_harian = (df["suhu_min"] + df["suhu_max"]) / 2
df["suhu_rata_7hari"] = rata_harian.rolling(7, min_periods=1).mean()
df["amplitudo_termal"] = df["suhu_max"] - df["suhu_min"]
df["derajat_pendinginan"] = (df["suhu_rata_7hari"] - 24).clip(lower=0)
# target: konsumsi listrik (kWh) mengikuti rata-rata 7 hari di atas 24 derajat
rata_7_hakiki = pd.Series(suhu_harian).rolling(7, min_periods=1).mean()
df["konsumsi_kwh"] = 9 * np.maximum(0, rata_7_hakiki - 24) + rng.normal(0, 1.2, n)
# pemecahan kronologis: 210 hari pertama latih, sisanya uji
train = df.iloc[:210]
test = df.iloc[210:]
dasar = ["suhu_min", "suhu_max", "kelembapan"]
turunan = ["suhu_rata_7hari", "amplitudo_termal", "derajat_pendinginan"]
m1 = LinearRegression().fit(train[dasar], train["konsumsi_kwh"])
m2 = LinearRegression().fit(train[dasar + turunan], train["konsumsi_kwh"])
print("R2 data uji tanpa fitur turunan :", round(r2_score(test["konsumsi_kwh"], m1.predict(test[dasar])), 3))
print("R2 data uji dengan fitur turunan:", round(r2_score(test["konsumsi_kwh"], m2.predict(test[dasar + turunan])), 3))R2 data uji tanpa fitur turunan : 0.839
R2 data uji dengan fitur turunan: 0.998Skor pada data uji naik dari 0.839 menjadi 0.998 setelah tiga kolom turunan ditambahkan, dengan model dan data yang sama. Angka kedua mendekati sempurna karena data ini sintetis dan targetnya memang dibangkitkan dari besaran yang sama dengan fitur turunannya, sehingga langkah ini mengungkap kembali hubungan yang membentuk target. Pada data nyata kenaikannya biasanya jauh lebih kecil, tetapi mekanismenya persis sama, yaitu informasi yang tadinya tersamar di antara kolom mentah menjadi eksplisit sehingga model sederhana pun mampu memanfaatkannya.
Penciptaan fitur di dalam pipeline#
Logika fitur yang ditulis terpisah dari model akan mudah tercecer. Bila fitur turunan dihitung manual sebelum fit tetapi tidak ikut ketika model digunakan untuk memprediksi data baru, kedua keadaan menggunakan aturan yang berbeda. FunctionTransformer menyelesaikan ini dengan membungkus fungsi bernama menjadi transformer, sehingga penciptaan fitur menjadi satu langkah Pipeline bersama modelnya. Apabila hanya sebagian kolom yang perlu ditransformasi, ColumnTransformer yang menangani pemilihannya.
import numpy as np
import pandas as pd
from sklearn.linear_model import LinearRegression
from sklearn.pipeline import make_pipeline
from sklearn.preprocessing import FunctionTransformer
def tambah_fitur(df):
"""Menambahkan tiga fitur turunan dari log sensor suhu."""
df = df.copy()
rata_harian = (df["suhu_min"] + df["suhu_max"]) / 2
df["suhu_rata_7hari"] = rata_harian.rolling(7, min_periods=1).mean()
df["amplitudo_termal"] = df["suhu_max"] - df["suhu_min"]
df["derajat_pendinginan"] = (df["suhu_rata_7hari"] - 24).clip(lower=0)
return df
# data yang sama dengan berkas fitur-suhu.py
n = 300
rng = np.random.default_rng(42)
t = np.arange(n)
suhu_harian = 26 + 6 * np.sin(2 * np.pi * t / 120) + rng.normal(0, 1.0, n)
amplitudo = rng.uniform(3, 9, n)
kelembapan = rng.uniform(55, 90, n)
df = pd.DataFrame({
"suhu_min": suhu_harian - amplitudo / 2,
"suhu_max": suhu_harian + amplitudo / 2,
"kelembapan": kelembapan,
})
rata_7_hakiki = pd.Series(suhu_harian).rolling(7, min_periods=1).mean()
df["konsumsi_kwh"] = 9 * np.maximum(0, rata_7_hakiki - 24) + rng.normal(0, 1.2, n)
train = df.iloc[:210]
test = df.iloc[210:]
dasar = ["suhu_min", "suhu_max", "kelembapan"]
# penciptaan fitur hidup di dalam pipeline, bukan di luar model
pipeline = make_pipeline(FunctionTransformer(tambah_fitur), LinearRegression())
pipeline.fit(train[dasar], train["konsumsi_kwh"])
print("R2 data uji pipeline :", round(pipeline.score(test[dasar], test["konsumsi_kwh"]), 3))
print("Kolom keluaran transformer:")
print(list(pipeline[:-1].transform(test[dasar]).columns))R2 data uji pipeline : 0.998
Kolom keluaran transformer:
['suhu_min', 'suhu_max', 'kelembapan', 'suhu_rata_7hari', 'amplitudo_termal', 'derajat_pendinginan']Skor pipeline sama persis dengan perbandingan sebelumnya, yaitu 0.998, karena transformasinya identik. Perbedaannya ada pada lokasi logika transformasi, yaitu di dalam objek pipeline yang dapat disimpan, dimuat, dan dipanggil ulang tanpa menyalin kode transformasinya. Daftar kolom keluaran menegaskan bahwa data yang sampai ke model berisi enam kolom, yaitu tiga kolom asli dan tiga kolom turunan.
Kesalahan umum#
- Menghitung fitur agregat dengan jendela yang menyentuh masa depan. Jendela yang memusat di sekitar satu baris menggunakan nilai hari berikutnya yang belum tersedia saat prediksi, sehingga fiturnya tidak dapat dihitung ulang di produksi. Perbaikannya dengan jendela yang menghadap ke belakang.
- Memakai
rolling(7)tanpamin_periodsdi dalam transformer. Setiap batch baru menghasilkan NaN pada enam baris pertamanya sehinggafitgagal dengan pesanInput X contains NaN. Perbaikannya denganmin_periods=1atau menyediakan buffer riwayat sebelum transformasi. - Menghitung fitur turunan dari target. Rata-rata konsumsi yang dihitung dari kolom target sendiri membuat fitur tersebut memuat jawaban, sehingga skor evaluasi menggelembung. Fitur turunan hanya boleh dihitung dari fitur lain, bukan dari target.
Ringkasan#
- Fitur turunan memindahkan pengetahuan domain, seperti rata-rata tujuh hari dan ambang 24 °C, menjadi kolom yang dapat dipelajari model linear secara langsung.
- Pada log suhu sintetis, data uji naik dari 0.839 menjadi 0.998 setelah tiga fitur turunan ditambahkan, dengan model yang sama persis.
- Pemecahan data deret waktu dilakukan secara kronologis, dan fitur bergulir aman dihitung sebelum pemecahan karena jendelanya menghadap ke belakang.
FunctionTransformerdengan fungsi bernama membawa penciptaan fitur ke dalamPipeline, sehingga pelatihan dan prediksi menjalankan aturan yang sama.
Referensi#
- sklearn.preprocessing.FunctionTransformer — scikit-learn documentation — Dokumentasi transformer yang membungkus fungsi biasa menjadi langkah pipeline.
- sklearn.linear_model.LinearRegression — scikit-learn documentation
— Rujukan model linear beserta makna skor pada metode
score. - pandas.DataFrame — pandas documentation — Struktur tabel yang digunakan untuk membangun kolom mentah dan kolom turunan.
- sklearn.pipeline.Pipeline — scikit-learn documentation — Kontrak pipeline yang merangkai transformasi dan model dalam satu objek.