Bulan 9: Machine Learning Dasar
Abbas mulai memasuki wilayah prediktif: dari menjelaskan data masa lalu ke memprediksi masa depan. Bulan ini membangun fondasi ML yang solid, dari konsep dasar hingga model regresi dan klasifikasi pertama.
Apa yang Abbas Pelajari Bulan Ini
Selama delapan bulan terakhir, Abbas sudah mahir menganalisis dan memvisualisasikan data. Bulan 9 adalah babak baru: membuat mesin yang belajar dari data. Machine Learning bukan sihir, melainkan perpanjangan logis dari statistik yang sudah Abbas kuasai. Perbedaannya adalah fokus: statistik menjelaskan, ML memprediksi.
Kompetensi yang Dibangun
- Membedakan pendekatan Machine Learning dari statistik inferensial tradisional dan memilih yang tepat sesuai pertanyaan bisnis
- Memahami perbedaan supervised learning dan unsupervised learning beserta contoh penggunaan nyata masing-masing
- Melakukan train/test split dengan benar dan menjelaskan mengapa pemisahan data itu krusial untuk model yang jujur
- Membangun model regresi linear dengan scikit-learn: fit, predict, dan interpretasi koefisien
- Mengevaluasi kualitas model regresi menggunakan MSE, RMSE, dan R-squared
- Membangun model klasifikasi dasar: Logistic Regression, Decision Tree, dan K-Nearest Neighbors
- Membaca dan menginterpretasikan confusion matrix beserta metrik turunannya: precision, recall, dan F1-score
- Mengenali overfitting dan underfitting secara visual dan mengambil langkah awal untuk mengatasinya
Minggu 1: Konsep ML dan Regresi Linear
Minggu pertama membangun fondasi konseptual sebelum Abbas menulis satu baris kode ML pun. Memahami mengapa jauh lebih penting dari bagaimana, terutama di awal perjalanan ML.
# Contoh sederhana: statistik vs ML
import numpy as np
# Statistik: menghitung rata-rata (menjelaskan masa lalu)
nilai_ujian = [75, 80, 65, 90, 85, 70, 88, 72]
rata_rata = np.mean(nilai_ujian)
print(f"Rata-rata nilai kelas: {rata_rata:.1f}")
# ML: memprediksi nilai berdasarkan jam belajar (prediksi masa depan)
# (ini preview -- nanti Abbas akan belajar cara lengkapnya)
jam_belajar = np.array([2, 4, 3, 6, 5, 3, 5.5, 2.5])
# Korelasi dulu, sebelum masuk ke model
korelasi = np.corrcoef(jam_belajar, nilai_ujian)[0, 1]
print(f"Korelasi jam belajar vs nilai: {korelasi:.3f}")
print("Pertanyaan ML: jika Abbas belajar 7 jam, berapa nilainya?")from sklearn.datasets import load_iris
import pandas as pd
# Load dataset klasik untuk belajar
iris = load_iris()
# Lihat struktur data
df = pd.DataFrame(iris.data, columns=iris.feature_names)
df['target'] = iris.target
df['species'] = df['target'].map({0: 'setosa', 1: 'versicolor', 2: 'virginica'})
print("Bentuk data:", df.shape)
print("\n5 baris pertama:")
print(df.head())
print("\nJumlah data per spesies:")
print(df['species'].value_counts())
# Fitur (X): yang digunakan untuk prediksi
# Label (y): yang ingin diprediksi
print("\nFitur yang tersedia:", iris.feature_names)
print("Label yang diprediksi: jenis spesies bunga")Unsupervised Learning: Model menemukan pola dari data tanpa label. Contoh: mengelompokkan pelanggan berdasarkan perilaku belanja tanpa tahu dulu kategorinya.
../simulasi/overfitting.html dan eksplorasi apa yang terjadi saat model terlalu kompleks.from sklearn.datasets import load_iris
from sklearn.model_selection import train_test_split
import pandas as pd
iris = load_iris()
X = iris.data # fitur: 4 kolom ukuran bunga
y = iris.target # label: jenis spesies
# Split data: 80% untuk training, 20% untuk testing
X_train, X_test, y_train, y_test = train_test_split(
X, y,
test_size=0.2, # 20% untuk test
random_state=42, # agar hasil reproduktif
stratify=y # pastikan proporsi kelas seimbang
)
print(f"Total data: {len(X)}")
print(f"Data training: {len(X_train)} ({len(X_train)/len(X)*100:.0f}%)")
print(f"Data testing: {len(X_test)} ({len(X_test)/len(X)*100:.0f}%)")
# Cek distribusi kelas
import numpy as np
print("\nDistribusi kelas di training set:", np.bincount(y_train))
print("Distribusi kelas di test set: ", np.bincount(y_test))test_size menjadi 0.5 (50:50). Apakah Abbas menduga model akan lebih baik atau lebih buruk?
Jalankan kode dan catat hasilnya. Besok Abbas akan membandingkan performa model dengan berbagai ukuran split ini.
import numpy as np
import pandas as pd
import matplotlib.pyplot as plt
from sklearn.linear_model import LinearRegression
from sklearn.model_selection import train_test_split
# Dataset sederhana: luas (m2) vs harga (juta rupiah)
np.random.seed(42)
luas = np.random.uniform(30, 150, 80)
harga = 200 + 5.5 * luas + np.random.normal(0, 30, 80)
df = pd.DataFrame({'luas_m2': luas, 'harga_juta': harga})
# Siapkan data
X = df[['luas_m2']] # fitur harus 2D
y = df['harga_juta']
X_train, X_test, y_train, y_test = train_test_split(X, y, test_size=0.2, random_state=42)
# Bangun dan latih model
model = LinearRegression()
model.fit(X_train, y_train)
# Interpretasi
print(f"Intercept (titik awal): {model.intercept_:.1f} juta")
print(f"Koefisien (slope): {model.coef_[0]:.2f} juta per m2")
print()
print("Artinya: setiap tambahan 1 m2 luas bangunan,")
print(f"harga naik sekitar Rp {model.coef_[0]:.1f} juta")
# Prediksi
prediksi = model.predict([[75]])
print(f"\nPrediksi harga rumah 75 m2: Rp {prediksi[0]:.1f} juta")Intercept adalah nilai prediksi saat semua fitur bernilai nol (kadang tidak bermakna secara nyata). Koefisien menunjukkan seberapa besar perubahan output untuk setiap satu unit perubahan input.
from sklearn.metrics import mean_squared_error, r2_score
import numpy as np
# Lanjutan dari kode Hari 4
# Asumsikan model, X_test, y_test sudah ada
y_pred = model.predict(X_test)
# Hitung metrik evaluasi
mse = mean_squared_error(y_test, y_pred)
rmse = np.sqrt(mse)
r2 = r2_score(y_test, y_pred)
print("=== Evaluasi Model Regresi ===")
print(f"MSE (Mean Squared Error): {mse:.2f}")
print(f"RMSE (Root Mean Sq. Error): {rmse:.2f} juta rupiah")
print(f"Rยฒ (R-squared): {r2:.4f}")
print()
# Interpretasi R2
if r2 >= 0.9:
print("R2 >= 0.90: Model sangat baik")
elif r2 >= 0.7:
print("R2 >= 0.70: Model cukup baik untuk analisis awal")
elif r2 >= 0.5:
print("R2 >= 0.50: Model moderat, perlu perbaikan")
else:
print("R2 < 0.50: Model lemah, perlu eksplorasi lebih")
# Lihat beberapa prediksi vs aktual
import pandas as pd
hasil = pd.DataFrame({'Aktual': y_test.values[:5],
'Prediksi': y_pred[:5],
'Selisih': y_test.values[:5] - y_pred[:5]})
print("\nSampel prediksi vs aktual:")
print(hasil.round(1))Minggu 2: Klasifikasi Dasar
Regresi memprediksi angka kontinu. Klasifikasi memprediksi kategori: spam atau bukan, lulus atau tidak, produk mana yang akan dibeli. Minggu ini Abbas mempelajari tiga algoritma klasifikasi yang paling sering dipakai dalam pekerjaan analitik nyata.
import numpy as np
import pandas as pd
from sklearn.linear_model import LogisticRegression
from sklearn.model_selection import train_test_split
from sklearn.preprocessing import StandardScaler
# Dataset: prediksi kelulusan siswa
np.random.seed(42)
n = 200
jam_belajar = np.random.uniform(1, 10, n)
nilai_rata_rata = np.random.uniform(40, 100, n)
# Label: lulus (1) atau tidak (0) berdasarkan kombinasi fitur
skor_gabungan = 0.4 * jam_belajar + 0.6 * (nilai_rata_rata / 10)
lulus = (skor_gabungan + np.random.normal(0, 0.5, n) > 5).astype(int)
df = pd.DataFrame({
'jam_belajar': jam_belajar,
'nilai_rata_rata': nilai_rata_rata,
'lulus': lulus
})
print("Distribusi label:")
print(df['lulus'].value_counts().rename({0: 'Tidak Lulus', 1: 'Lulus'}))
X = df[['jam_belajar', 'nilai_rata_rata']]
y = df['lulus']
X_train, X_test, y_train, y_test = train_test_split(X, y, test_size=0.2, random_state=42)
# Scaling penting untuk Logistic Regression
scaler = StandardScaler()
X_train_scaled = scaler.fit_transform(X_train)
X_test_scaled = scaler.transform(X_test)
# Latih model
model_lr = LogisticRegression(random_state=42)
model_lr.fit(X_train_scaled, y_train)
akurasi = model_lr.score(X_test_scaled, y_test)
print(f"\nAkurasi Logistic Regression: {akurasi:.2%}")max_depth dari 2 hingga 10. Catat apa yang terjadi pada akurasi training vs test.from sklearn.tree import DecisionTreeClassifier, export_text
from sklearn.model_selection import train_test_split
import pandas as pd
# Gunakan data yang sama dari Hari 1
# Asumsikan X_train, X_test, y_train, y_test sudah ada (tanpa scaling)
# Decision Tree tidak butuh scaling
model_dt = DecisionTreeClassifier(
max_depth=4, # batasi kedalaman agar tidak overfit
random_state=42
)
model_dt.fit(X_train, y_train)
akurasi_train = model_dt.score(X_train, y_train)
akurasi_test = model_dt.score(X_test, y_test)
print(f"Akurasi Training: {akurasi_train:.2%}")
print(f"Akurasi Testing: {akurasi_test:.2%}")
print()
# Jika akurasi training jauh lebih tinggi dari test, itu tanda overfitting
gap = akurasi_train - akurasi_test
if gap > 0.1:
print(f"Peringatan: gap {gap:.2%} -- kemungkinan overfitting")
else:
print(f"Gap kecil ({gap:.2%}) -- model generalisasi dengan baik")
# Lihat aturan yang dipelajari model
feature_names = ['jam_belajar', 'nilai_rata_rata']
aturan = export_text(model_dt, feature_names=feature_names)
print("\nAturan Decision Tree:")
print(aturan[:500]) # tampilkan 500 karakter pertamafrom sklearn.neighbors import KNeighborsClassifier
from sklearn.preprocessing import StandardScaler
import numpy as np
# KNN butuh scaling karena berbasis jarak
scaler = StandardScaler()
X_train_scaled = scaler.fit_transform(X_train)
X_test_scaled = scaler.transform(X_test)
# Coba beberapa nilai K
print("Eksplorasi nilai K:")
print(f"{'K':>5} | {'Train':>8} | {'Test':>8}")
print("-" * 28)
hasil_k = []
for k in [1, 3, 5, 7, 11, 15, 21]:
knn = KNeighborsClassifier(n_neighbors=k)
knn.fit(X_train_scaled, y_train)
acc_train = knn.score(X_train_scaled, y_train)
acc_test = knn.score(X_test_scaled, y_test)
hasil_k.append({'K': k, 'train': acc_train, 'test': acc_test})
print(f"{k:>5} | {acc_train:>7.2%} | {acc_test:>7.2%}")
# Temukan K terbaik berdasarkan test accuracy
import pandas as pd
df_k = pd.DataFrame(hasil_k)
k_terbaik = df_k.loc[df_k['test'].idxmax(), 'K']
print(f"\nK terbaik: {int(k_terbaik)}")from sklearn.metrics import (confusion_matrix, classification_report,
ConfusionMatrixDisplay)
from sklearn.linear_model import LogisticRegression
from sklearn.preprocessing import StandardScaler
import matplotlib.pyplot as plt
# Gunakan model Logistic Regression dari Hari 1
scaler = StandardScaler()
X_train_s = scaler.fit_transform(X_train)
X_test_s = scaler.transform(X_test)
model = LogisticRegression(random_state=42)
model.fit(X_train_s, y_train)
y_pred = model.predict(X_test_s)
# Confusion Matrix
cm = confusion_matrix(y_test, y_pred)
print("Confusion Matrix:")
print(f" Prediksi: Tidak Lulus | Prediksi: Lulus")
print(f"Aktual: Tidak Lulus {cm[0,0]:>10} {cm[0,1]:>10}")
print(f"Aktual: Lulus {cm[1,0]:>10} {cm[1,1]:>10}")
print()
# Laporan lengkap
print("Laporan Klasifikasi:")
print(classification_report(y_test, y_pred,
target_names=['Tidak Lulus', 'Lulus']))
# Penjelasan metrik
tn, fp, fn, tp = cm.ravel()
precision = tp / (tp + fp)
recall = tp / (tp + fn)
f1 = 2 * precision * recall / (precision + recall)
print(f"Precision: {precision:.2%} (dari semua yang diprediksi lulus, berapa yang benar-benar lulus)")
print(f"Recall: {recall:.2%} (dari semua yang sebenarnya lulus, berapa yang berhasil terdeteksi)")
print(f"F1-Score: {f1:.2%} (keseimbangan precision dan recall)")Jika biaya False Positive tinggi (misal: menuduh orang sehat sakit, lalu operasi), utamakan Precision.
Jika biaya False Negative tinggi (misal: melewatkan diagnosis kanker), utamakan Recall.
Dalam konteks bisnis, Abbas harus selalu bertanya: "Error mana yang lebih mahal bagi organisasi?"
klasifikasi_dasar.py sebagai referensi proyek P5 nanti.from sklearn.linear_model import LogisticRegression
from sklearn.tree import DecisionTreeClassifier
from sklearn.neighbors import KNeighborsClassifier
from sklearn.preprocessing import StandardScaler
from sklearn.metrics import f1_score, precision_score, recall_score
import pandas as pd
import time
# Setup
scaler = StandardScaler()
X_train_s = scaler.fit_transform(X_train)
X_test_s = scaler.transform(X_test)
# Definisikan model yang akan dibandingkan
kandidat = {
'Logistic Regression': LogisticRegression(random_state=42),
'Decision Tree (d=4)': DecisionTreeClassifier(max_depth=4, random_state=42),
'KNN (K=7)': KNeighborsClassifier(n_neighbors=7),
}
hasil = []
for nama, model in kandidat.items():
# Decision Tree tidak butuh scaled data, tapi tidak merusak jika dipakai
mulai = time.time()
model.fit(X_train_s, y_train)
durasi = time.time() - mulai
y_pred = model.predict(X_test_s)
hasil.append({
'Model': nama,
'Akurasi': f"{model.score(X_test_s, y_test):.2%}",
'Precision': f"{precision_score(y_test, y_pred):.2%}",
'Recall': f"{recall_score(y_test, y_pred):.2%}",
'F1-Score': f"{f1_score(y_test, y_pred):.2%}",
'Waktu (ms)': f"{durasi*1000:.1f}",
})
df_hasil = pd.DataFrame(hasil)
print("=== Perbandingan Algoritma Klasifikasi ===")
print(df_hasil.to_string(index=False))Minggu 3 โ Feature Engineering
Abbas akan mempelajari teknik-teknik transformasi fitur agar model machine learning dapat belajar lebih efektif: encoding kategoris, scaling numerik, imputasi nilai hilang, dan seleksi fitur.
Encoding Variabel Kategoris
Memahami perbedaan Label Encoding dan One-Hot Encoding, serta kapan masing-masing digunakan.
- Label Encoding untuk variabel ordinal (mis. Low/Medium/High)
- One-Hot Encoding untuk variabel nominal (mis. kota, jenis kelamin)
- Menghindari dummy variable trap dengan
drop_first=True
import pandas as pd
from sklearn.preprocessing import LabelEncoder
df = pd.DataFrame({
'kota': ['Jakarta', 'Bandung', 'Jakarta', 'Surabaya'],
'grade': ['Low', 'High', 'Medium', 'High']
})
# Label Encoding untuk ordinal
le = LabelEncoder()
df['grade_enc'] = le.fit_transform(df['grade'])
# One-Hot Encoding untuk nominal
df_encoded = pd.get_dummies(df, columns=['kota'], drop_first=True)
print(df_encoded)
Scaling Fitur Numerik
Normalisasi dan standarisasi agar fitur dengan skala berbeda tidak mendominasi proses pembelajaran model.
- MinMaxScaler: skala ke rentang [0, 1], sensitif outlier
- StandardScaler: distribusi mean=0, std=1 โ lebih robust
- RobustScaler: berbasis median dan IQR, tahan outlier ekstrem
from sklearn.preprocessing import MinMaxScaler, StandardScaler, RobustScaler
import numpy as np
data = np.array([[100], [200], [300], [10000]]) # ada outlier
scaler_mm = MinMaxScaler()
scaler_std = StandardScaler()
scaler_rob = RobustScaler()
print("MinMax:", scaler_mm.fit_transform(data).ravel())
print("Standard:", scaler_std.fit_transform(data).ravel())
print("Robust:", scaler_rob.fit_transform(data).ravel())
Imputasi Nilai Hilang (Missing Value Imputation)
Menangani data kosong dengan strategi yang tepat agar model tidak bias.
- Imputasi mean/median/modus via
SimpleImputer - Imputasi berbasis model via
KNNImputerdanIterativeImputer - Kapan harus menghapus baris vs mengisi nilai
import numpy as np
import pandas as pd
from sklearn.impute import SimpleImputer, KNNImputer
df = pd.DataFrame({
'usia': [25, np.nan, 35, 40, np.nan],
'gaji': [3000, 4000, np.nan, 5000, 4500],
'kota': ['Jakarta', 'Bandung', np.nan, 'Jakarta', 'Surabaya']
})
# Numerik: median imputation
imp_num = SimpleImputer(strategy='median')
df[['usia', 'gaji']] = imp_num.fit_transform(df[['usia', 'gaji']])
# Kategorik: modus imputation
imp_cat = SimpleImputer(strategy='most_frequent')
df[['kota']] = imp_cat.fit_transform(df[['kota']])
# KNN Imputer (lebih akurat untuk numerik)
imp_knn = KNNImputer(n_neighbors=2)
# Gunakan setelah encoding jika ada kategorikal
print(df)
Feature Selection: Filter dan Wrapper Methods
Memilih subset fitur yang paling informatif untuk meningkatkan performa model dan mengurangi overfitting.
- Filter Methods: korelasi Pearson, chi-square, mutual information โ cepat, model-agnostic
- Wrapper Methods: RFE (Recursive Feature Elimination) โ lebih akurat, lebih mahal komputasi
- Embedded Methods: feature importance dari tree-based models
from sklearn.datasets import load_breast_cancer
from sklearn.feature_selection import SelectKBest, chi2, RFE
from sklearn.linear_model import LogisticRegression
import pandas as pd
data = load_breast_cancer()
X, y = data.data, data.target
feature_names = data.feature_names
# Filter: SelectKBest dengan chi2
selector = SelectKBest(score_func=chi2, k=10)
X_new = selector.fit_transform(abs(X), y)
selected = [feature_names[i] for i in selector.get_support(indices=True)]
print("Top 10 fitur (chi2):", selected)
# Wrapper: RFE
model = LogisticRegression(max_iter=5000)
rfe = RFE(model, n_features_to_select=10)
rfe.fit(X, y)
selected_rfe = [feature_names[i] for i in range(len(feature_names)) if rfe.support_[i]]
print("Top 10 fitur (RFE):", selected_rfe)
Pipeline: Menggabungkan Semua Langkah Preprocessing
Membuat pipeline yang bersih dan reproducible menggunakan sklearn.pipeline.Pipeline dan ColumnTransformer.
- Menghindari data leakage saat split train/test
ColumnTransformeruntuk preprocessing kolom berbeda secara bersamaan- Export pipeline ke file
.pkluntuk deployment
from sklearn.pipeline import Pipeline
from sklearn.compose import ColumnTransformer
from sklearn.preprocessing import StandardScaler, OneHotEncoder
from sklearn.impute import SimpleImputer
from sklearn.linear_model import LogisticRegression
import joblib
num_features = ['usia', 'gaji']
cat_features = ['kota']
num_pipeline = Pipeline([
('imputer', SimpleImputer(strategy='median')),
('scaler', StandardScaler())
])
cat_pipeline = Pipeline([
('imputer', SimpleImputer(strategy='most_frequent')),
('encoder', OneHotEncoder(handle_unknown='ignore', drop='first'))
])
preprocessor = ColumnTransformer([
('num', num_pipeline, num_features),
('cat', cat_pipeline, cat_features)
])
full_pipeline = Pipeline([
('preprocessor', preprocessor),
('classifier', LogisticRegression())
])
# full_pipeline.fit(X_train, y_train)
# joblib.dump(full_pipeline, 'model_pipeline.pkl')
print("Pipeline berhasil dibuat.")
.pkl.
Minggu 4 โ Evaluasi dan Iterasi Model
Abbas mempelajari cara mengevaluasi model secara menyeluruh: cross-validation, hyperparameter tuning, analisis error, dan iterasi perbaikan yang sistematis.
Cross-Validation dan Strategi Evaluasi Robust
Memahami keterbatasan single train/test split dan menggantinya dengan cross-validation untuk estimasi performa yang lebih stabil.
- k-Fold Cross-Validation: membagi data ke k bagian, rotasi sebagai test set
- Stratified k-Fold: menjaga proporsi kelas di tiap fold โ penting untuk data imbalanced
- Membaca mean dan std dari skor CV untuk deteksi instabilitas model
from sklearn.model_selection import cross_val_score, StratifiedKFold
from sklearn.ensemble import RandomForestClassifier
from sklearn.datasets import load_breast_cancer
import numpy as np
X, y = load_breast_cancer(return_X_y=True)
model = RandomForestClassifier(n_estimators=100, random_state=42)
# Stratified k-Fold
skf = StratifiedKFold(n_splits=5, shuffle=True, random_state=42)
scores = cross_val_score(model, X, y, cv=skf, scoring='f1')
print(f"F1 per fold: {scores.round(3)}")
print(f"Mean F1: {scores.mean():.3f} (+/- {scores.std():.3f})")
Hyperparameter Tuning: Grid Search dan Random Search
Menemukan kombinasi hyperparameter terbaik secara sistematis alih-alih trial and error manual.
- GridSearchCV: exhaustive search โ tepat untuk ruang parameter kecil
- RandomizedSearchCV: sampling acak โ efisien untuk ruang parameter besar
- Menggunakan
refit=Trueuntuk langsung mendapatkan model terbaik
from sklearn.model_selection import GridSearchCV, RandomizedSearchCV
from sklearn.ensemble import RandomForestClassifier
from sklearn.datasets import load_breast_cancer
from scipy.stats import randint
X, y = load_breast_cancer(return_X_y=True)
param_grid = {
'n_estimators': [50, 100, 200],
'max_depth': [None, 5, 10],
'min_samples_split': [2, 5, 10]
}
# Grid Search (27 kombinasi x 5 fold = 135 fits)
grid_search = GridSearchCV(
RandomForestClassifier(random_state=42),
param_grid, cv=5, scoring='f1', n_jobs=-1
)
grid_search.fit(X, y)
print("Best params (Grid):", grid_search.best_params_)
print("Best F1 (Grid):", grid_search.best_score_.round(3))
# Random Search (lebih cepat untuk ruang besar)
param_dist = {
'n_estimators': randint(50, 500),
'max_depth': [None, 3, 5, 10, 20],
'min_samples_split': randint(2, 20)
}
rand_search = RandomizedSearchCV(
RandomForestClassifier(random_state=42),
param_dist, n_iter=20, cv=5, scoring='f1', random_state=42, n_jobs=-1
)
rand_search.fit(X, y)
print("Best params (Random):", rand_search.best_params_)
Analisis Error: Confusion Matrix, Learning Curve, dan Residual Plot
Mendiagnosis di mana model gagal โ apakah karena underfitting, overfitting, atau bias data tertentu.
- Confusion Matrix: visualisasi detail kesalahan klasifikasi per kelas
- Learning Curve: deteksi bias vs variance โ butuh lebih banyak data atau model lebih kompleks?
- Residual Plot: untuk regresi โ pola residual mengindikasikan masalah model
import matplotlib.pyplot as plt
import numpy as np
from sklearn.model_selection import learning_curve, train_test_split
from sklearn.ensemble import RandomForestClassifier
from sklearn.datasets import load_breast_cancer
from sklearn.metrics import ConfusionMatrixDisplay
X, y = load_breast_cancer(return_X_y=True)
X_train, X_test, y_train, y_test = train_test_split(X, y, test_size=0.2, random_state=42)
model = RandomForestClassifier(n_estimators=100, random_state=42)
model.fit(X_train, y_train)
# Confusion Matrix
fig, axes = plt.subplots(1, 2, figsize=(14, 5))
ConfusionMatrixDisplay.from_estimator(model, X_test, y_test, ax=axes[0])
axes[0].set_title("Confusion Matrix")
# Learning Curve
train_sizes, train_scores, val_scores = learning_curve(
model, X, y, cv=5, scoring='f1',
train_sizes=np.linspace(0.1, 1.0, 10), n_jobs=-1
)
axes[1].plot(train_sizes, train_scores.mean(axis=1), label='Train F1')
axes[1].plot(train_sizes, val_scores.mean(axis=1), label='Validation F1')
axes[1].set_xlabel("Training Size")
axes[1].set_ylabel("F1 Score")
axes[1].set_title("Learning Curve")
axes[1].legend()
plt.tight_layout()
plt.savefig("eval_analysis.png", dpi=150)
Strategi Iterasi: Mengatasi Overfitting dan Underfitting
Membangun kebiasaan iterasi yang terstruktur: tahu kapan menambah data, memperumit model, atau menyederhanakan.
- Underfitting: tambah fitur, perbesar model, kurangi regularisasi
- Overfitting: regularisasi (L1/L2), dropout, pruning, tambah data, feature selection
- Teknik augmentasi data sederhana untuk memperbesar training set
from sklearn.linear_model import LogisticRegression, Ridge
from sklearn.datasets import load_breast_cancer
from sklearn.model_selection import cross_val_score
import numpy as np
X, y = load_breast_cancer(return_X_y=True)
# Membandingkan tingkat regularisasi
C_values = [0.001, 0.01, 0.1, 1, 10, 100]
results = []
for C in C_values:
model = LogisticRegression(C=C, max_iter=5000)
score = cross_val_score(model, X, y, cv=5, scoring='f1').mean()
results.append((C, score))
print(f"C={C:6}: F1 = {score:.4f}")
best_C, best_score = max(results, key=lambda x: x[1])
print(f"
C terbaik: {best_C} dengan F1 = {best_score:.4f}")
Mini Project Bulan 09: End-to-End ML Pipeline
Abbas mengintegrasikan semua yang telah dipelajari selama bulan ini dalam satu proyek utuh: dari data mentah hingga model yang dievaluasi dan siap digunakan.
- Pilih dataset klasifikasi atau regresi dari Kaggle (minimal 1.000 baris, 8+ fitur)
- Tahap 1: EDA singkat, identifikasi missing values dan distribusi target
- Tahap 2: Feature engineering โ encoding, scaling, imputasi, seleksi fitur
- Tahap 3: Bandingkan minimal 3 algoritma (LogReg, Decision Tree, Random Forest)
- Tahap 4: Hyperparameter tuning pada model terbaik
- Tahap 5: Evaluasi final dengan confusion matrix, classification report, dan learning curve
- Tahap 6: Simpan pipeline sebagai
.pkldan tulis 1 paragraf kesimpulan
import joblib
from sklearn.pipeline import Pipeline
from sklearn.compose import ColumnTransformer
from sklearn.preprocessing import StandardScaler, OneHotEncoder
from sklearn.impute import SimpleImputer
from sklearn.ensemble import RandomForestClassifier
from sklearn.model_selection import train_test_split, cross_val_score
from sklearn.metrics import classification_report
# import dataset pilihan Abbas di sini
# Contoh kerangka akhir
# X_train, X_test, y_train, y_test = train_test_split(X, y, test_size=0.2, stratify=y, random_state=42)
# full_pipeline.fit(X_train, y_train)
# y_pred = full_pipeline.predict(X_test)
# print(classification_report(y_test, y_pred))
# joblib.dump(full_pipeline, 'bulan09_final_model.pkl')
print("Template mini project siap. Lengkapi dengan dataset dan pipeline Abbas sendiri.")
bulan09_final_model.pkl, dan 1 paragraf kesimpulan: apa yang paling Abbas pelajari bulan ini, dan apa yang masih ingin diperdalam di bulan berikutnya.
Buku Pendukung Bulan 09
Tools yang Digunakan
Tips Praktis dan Jebakan Pemula
Setelah mempelajari algoritma dan pipeline, Abbas perlu memahami kesalahan umum yang dilakukan pemula ML dan cara menghindarinya. Tips ini dikumpulkan dari pola kesalahan yang paling sering muncul di komunitas dan industri.
Ini adalah kesalahan paling fatal dan paling sering dilakukan pemula. Jika Abbas melakukan
scaler.fit(X) pada seluruh data sebelum train/test split, maka informasi distribusi
data test sudah "bocor" ke training. Model akan terlihat sangat bagus di test set, tapi gagal
total di data nyata. Solusi: selalu split data dulu, baru fit preprocessor pada training set saja.
# SALAH: scaler tahu tentang data test!
scaler = StandardScaler()
X_scaled = scaler.fit_transform(X) # fit pada semua data
X_train, X_test = train_test_split(X_scaled)
# BENAR: scaler hanya belajar dari training set
X_train, X_test, y_train, y_test = train_test_split(X, y, test_size=0.2, random_state=42)
scaler = StandardScaler()
X_train_scaled = scaler.fit_transform(X_train) # fit hanya di train
X_test_scaled = scaler.transform(X_test) # transform saja, tidak fit ulangSebelum mencoba Random Forest atau algoritma kompleks, Abbas harus punya angka baseline. Untuk klasifikasi, baseline termudah adalah "selalu prediksi kelas mayoritas". Jika Random Forest hanya lebih baik 2% dari baseline, mungkin fitur yang perlu diperbaiki, bukan modelnya.
from sklearn.dummy import DummyClassifier
from sklearn.metrics import f1_score
# Baseline: selalu prediksi kelas paling sering muncul
dummy = DummyClassifier(strategy='most_frequent')
dummy.fit(X_train, y_train)
baseline_f1 = f1_score(y_test, dummy.predict(X_test), average='weighted')
print(f"Baseline F1: {baseline_f1:.3f}")
# Model Abbas harus secara signifikan mengalahkan angka ini
# Kalau tidak, cek kualitas fitur terlebih dahuluBayangkan dataset deteksi penipuan: 99% transaksi normal, 1% fraud. Model yang selalu prediksi "normal" akan mendapat accuracy 99%, padahal model tersebut sama sekali tidak berguna. Untuk kasus semacam ini, gunakan F1-score, precision, recall, atau AUC-ROC sebagai metrik utama. Accuracy hanya cocok ketika distribusi kelas cukup seimbang (paling tidak 70:30).
Setiap kali Abbas menjalankan ulang kode, hasilnya bisa berbeda karena komponen acak (random forest, train/test split, dll). Tetapkan
random_state=42 di semua fungsi
yang menerimanya agar eksperimen bisa direproduksi dan dibandingkan dengan adil.
Ini bukan takhayul angka 42, hanya konvensi yang dipakai luas di komunitas ML.
Banyak pemula langsung melakukan GridSearch saat model pertama kurang bagus. Padahal urutan yang benar: (1) perbaiki data dan fitur dulu, (2) coba beberapa algoritma, (3) baru tuning hyperparameter pada algoritma terbaik. Tuning model dengan fitur buruk tidak akan memberikan peningkatan signifikan karena batas maksimalnya memang sudah rendah.
sklearn Pipeline memastikan semua langkah preprocessing (imputer, scaler, encoder) hanya di-fit pada training data, bahkan saat menggunakan cross-validation. Ini cara paling aman dan bersih untuk membangun workflow ML karena semua operasi terkapsulasi dalam satu objek.
from sklearn.pipeline import Pipeline
from sklearn.preprocessing import StandardScaler
from sklearn.linear_model import LogisticRegression
from sklearn.model_selection import cross_val_score
pipe = Pipeline([
('scaler', StandardScaler()), # fit hanya pada training fold
('model', LogisticRegression())
])
scores = cross_val_score(pipe, X, y, cv=5, scoring='f1')
# Pipeline otomatis menangani fit/transform per fold dengan benar
print(f"CV F1: {scores.mean():.3f} +/- {scores.std():.3f}")Test set adalah "ujian final" yang idealnya hanya dilihat sekali: setelah model selesai dibangun dan di-tuning. Jika Abbas melihat test set berkali-kali untuk memperbaiki model, secara tidak sadar model menjadi "hafal" distribusi test set. Gunakan validation set atau cross-validation untuk semua keputusan pengembangan model.
Random Forest bisa memberi tahu fitur mana yang paling penting untuk model, tapi angka itu bisa menyesatkan jika ada fitur yang berkorelasi tinggi satu sama lain. Selalu validasi dengan domain expert: "Apakah masuk akal secara bisnis bahwa fitur ini yang paling penting?"
import pandas as pd
from sklearn.ensemble import RandomForestClassifier
model = RandomForestClassifier(n_estimators=100, random_state=42)
model.fit(X_train, y_train)
importance_df = pd.DataFrame({
'fitur': feature_names,
'importance': model.feature_importances_
}).sort_values('importance', ascending=False)
print(importance_df.head(10).to_string(index=False))
# Selalu tanya: apakah urutan ini masuk akal secara bisnis?Latihan Tambahan: Kasus Nyata Konteks Indonesia
Latihan berikut menggunakan konteks Indonesia agar Abbas bisa mengkoneksikan skill ML dengan permasalahan yang relevan secara lokal. Setiap latihan dirancang lebih menantang dari contoh di minggu 1-4 karena melibatkan data simulasi yang lebih realistik, keputusan metrik yang tidak trivial, dan interpretasi bisnis.
Prediksi Harga Kos-Kosan di Kota Besar Indonesia (Regresi End-to-End)
Abbas diminta memprediksi harga sewa bulanan kos-kosan di Jakarta, Bandung, dan Surabaya berdasarkan fitur seperti luas kamar, fasilitas, jarak ke kampus, dan tipe kos. Latihan ini melatih pipeline regresi lengkap termasuk handling fitur kategorikal dan numerik sekaligus.
import numpy as np
import pandas as pd
from sklearn.pipeline import Pipeline
from sklearn.compose import ColumnTransformer
from sklearn.preprocessing import StandardScaler, OneHotEncoder
from sklearn.impute import SimpleImputer
from sklearn.ensemble import GradientBoostingRegressor
from sklearn.model_selection import train_test_split, cross_val_score
from sklearn.metrics import mean_absolute_error, r2_score
np.random.seed(42)
n = 500
# Dataset simulasi kos-kosan Indonesia
kota = np.random.choice(['Jakarta', 'Bandung', 'Surabaya'], n)
tipe = np.random.choice(['Putra', 'Putri', 'Campur'], n)
fasilitas = np.random.choice(['AC+WiFi', 'WiFi saja', 'Standar'], n)
luas_m2 = np.random.normal(12, 3, n).clip(6, 25)
jarak_km = np.random.exponential(2, n).clip(0.1, 10)
lantai = np.random.randint(1, 5, n)
# Harga dasar berdasarkan kota (dalam ribu rupiah per bulan)
base = {'Jakarta': 1500, 'Bandung': 900, 'Surabaya': 800}
harga = np.array([base[k] for k in kota], dtype=float)
harga += luas_m2 * 50
harga -= jarak_km * 80
harga += (fasilitas == 'AC+WiFi').astype(float) * 400
harga += (fasilitas == 'WiFi saja').astype(float) * 150
harga += np.random.normal(0, 150, n)
harga = harga.clip(300, 5000)
df = pd.DataFrame({
'kota': kota, 'tipe': tipe, 'fasilitas': fasilitas,
'luas_m2': luas_m2, 'jarak_km': jarak_km,
'lantai': lantai, 'harga_ribu': harga
})
X = df.drop('harga_ribu', axis=1)
y = df['harga_ribu']
X_train, X_test, y_train, y_test = train_test_split(X, y, test_size=0.2, random_state=42)
num_features = ['luas_m2', 'jarak_km', 'lantai']
cat_features = ['kota', 'tipe', 'fasilitas']
preprocessor = ColumnTransformer([
('num', Pipeline([
('imp', SimpleImputer(strategy='median')),
('scl', StandardScaler())
]), num_features),
('cat', OneHotEncoder(handle_unknown='ignore'), cat_features)
])
pipeline = Pipeline([
('prep', preprocessor),
('model', GradientBoostingRegressor(n_estimators=100, learning_rate=0.1, random_state=42))
])
pipeline.fit(X_train, y_train)
y_pred = pipeline.predict(X_test)
mae = mean_absolute_error(y_test, y_pred)
r2 = r2_score(y_test, y_pred)
print(f"MAE: Rp {mae:.0f} ribu/bulan")
print(f"R-squared: {r2:.3f}")
# Cek apakah error model merata antar kota (fairness check)
for kota_nama in ['Jakarta', 'Bandung', 'Surabaya']:
mask = X_test['kota'] == kota_nama
if mask.any():
mae_kota = mean_absolute_error(y_test[mask], y_pred[mask])
print(f" MAE {kota_nama}: Rp {mae_kota:.0f} ribu")kota dan
fasilitas secara manual sebagai kolom baru sebelum split. Apakah R-squared meningkat?
Lalu buat prediksi untuk kos fiktif: Bandung, tipe Putri, AC+WiFi, 14m2, 1.5km dari kampus, lantai 2.
Berapa estimasi harganya? Apakah terasa masuk akal?
Deteksi Transaksi Mencurigakan di Fintech Indonesia (Klasifikasi Imbalanced)
Ini mensimulasikan masalah nyata yang dihadapi startup fintech Indonesia: 98% transaksi normal, 2% berpotensi fraud. Abbas harus memilih strategi yang tepat untuk kelas yang sangat tidak seimbang. Kasus ini memaksa Abbas memikirkan metrik evaluasi secara kritis, bukan hanya menerima accuracy.
import numpy as np
import pandas as pd
from sklearn.ensemble import RandomForestClassifier
from sklearn.model_selection import train_test_split
from sklearn.metrics import classification_report, roc_auc_score
from sklearn.preprocessing import StandardScaler
from sklearn.pipeline import Pipeline
np.random.seed(42)
n_normal = 9800
n_fraud = 200 # hanya 2% fraud
# Simulasi transaksi normal (pola aktivitas siang hari, jumlah wajar)
normal = pd.DataFrame({
'jumlah_rp': np.random.lognormal(13, 1.5, n_normal),
'jam': np.random.choice(range(8, 22), n_normal),
'frekuensi_harian': np.random.poisson(2, n_normal),
'selisih_lokasi_km': np.random.exponential(5, n_normal),
'label': 0
})
# Simulasi fraud (jumlah besar, tengah malam, frekuensi tinggi, lokasi jauh)
fraud = pd.DataFrame({
'jumlah_rp': np.random.lognormal(15, 2, n_fraud),
'jam': np.random.choice(range(0, 6), n_fraud),
'frekuensi_harian': np.random.poisson(15, n_fraud),
'selisih_lokasi_km': np.random.exponential(200, n_fraud),
'label': 1
})
df = pd.concat([normal, fraud]).sample(frac=1, random_state=42).reset_index(drop=True)
X = df.drop('label', axis=1)
y = df['label']
print(f"Proporsi fraud: {y.mean():.2%}")
X_train, X_test, y_train, y_test = train_test_split(
X, y, test_size=0.2, stratify=y, random_state=42
)
# Model 1: tanpa penanganan imbalanced
pipe_default = Pipeline([
('scl', StandardScaler()),
('clf', RandomForestClassifier(n_estimators=100, random_state=42))
])
pipe_default.fit(X_train, y_train)
# Model 2: dengan class_weight='balanced'
pipe_balanced = Pipeline([
('scl', StandardScaler()),
('clf', RandomForestClassifier(n_estimators=100, class_weight='balanced', random_state=42))
])
pipe_balanced.fit(X_train, y_train)
print("\n--- Model Default ---")
print(classification_report(y_test, pipe_default.predict(X_test),
target_names=['Normal', 'Fraud']))
print("--- Model Balanced ---")
print(classification_report(y_test, pipe_balanced.predict(X_test),
target_names=['Normal', 'Fraud']))
# AUC-ROC: metrik paling tepat untuk data imbalanced
auc_d = roc_auc_score(y_test, pipe_default.predict_proba(X_test)[:,1])
auc_b = roc_auc_score(y_test, pipe_balanced.predict_proba(X_test)[:,1])
print(f"AUC-ROC Default : {auc_d:.4f}")
print(f"AUC-ROC Balanced: {auc_b:.4f}")classification_report, bandingkan
recall kelas Fraud antara kedua model. Recall fraud berarti: dari semua transaksi fraud yang
sesungguhnya ada, berapa persen yang berhasil terdeteksi? Di industri fintech, recall fraud
lebih kritis dari precision. Mengapa? Coba juga ganti threshold dari 0.5 ke 0.3 menggunakan
predict_proba dan lihat bagaimana trade-off precision vs recall berubah.
Prediksi Siswa Berisiko Dropout (Pipeline Lengkap + Interpretasi Konteks Sekolah)
Abbas memprediksi siswa SMA mana yang berisiko tidak melanjutkan sekolah berdasarkan data akademik dan sosial. Latihan ini relevan secara personal dan melatih kemampuan membandingkan beberapa algoritma secara terstruktur dalam satu pipeline yang bersih.
import numpy as np
import pandas as pd
from sklearn.pipeline import Pipeline
from sklearn.compose import ColumnTransformer
from sklearn.preprocessing import StandardScaler, OneHotEncoder
from sklearn.impute import SimpleImputer
from sklearn.ensemble import RandomForestClassifier
from sklearn.linear_model import LogisticRegression
from sklearn.tree import DecisionTreeClassifier
from sklearn.model_selection import StratifiedKFold, cross_val_score
import warnings
warnings.filterwarnings('ignore')
np.random.seed(42)
n = 800
df = pd.DataFrame({
'nilai_rata': np.random.normal(72, 15, n).clip(30, 100),
'absensi_persen': np.random.exponential(8, n).clip(0, 60),
'jarak_sekolah_km': np.random.exponential(3, n).clip(0.5, 25),
'pendapatan_ortu': np.random.choice(['rendah', 'menengah', 'tinggi'], n,
p=[0.45, 0.40, 0.15]),
'ekskul_aktif': np.random.randint(0, 4, n),
'punya_hp': np.random.choice([0, 1], n, p=[0.3, 0.7]),
'kelas': np.random.choice(['X', 'XI', 'XII'], n)
})
# Probabilitas dropout berdasarkan kombinasi faktor risiko
risiko = (
df['nilai_rata'].lt(60).astype(int) * 2 +
df['absensi_persen'].gt(20).astype(int) * 3 +
df['pendapatan_ortu'].eq('rendah').astype(int) * 2 +
df['jarak_sekolah_km'].gt(10).astype(int) +
df['ekskul_aktif'].eq(0).astype(int)
)
prob_dropout = 1 / (1 + np.exp(-(risiko - 4)))
df['dropout'] = (np.random.random(n) < prob_dropout).astype(int)
print(f"Proporsi siswa dropout: {df['dropout'].mean():.2%}")
X = df.drop('dropout', axis=1)
y = df['dropout']
num_cols = ['nilai_rata', 'absensi_persen', 'jarak_sekolah_km', 'ekskul_aktif', 'punya_hp']
cat_cols = ['pendapatan_ortu', 'kelas']
preprocessor = ColumnTransformer([
('num', Pipeline([
('imp', SimpleImputer(strategy='median')),
('scl', StandardScaler())
]), num_cols),
('cat', Pipeline([
('imp', SimpleImputer(strategy='most_frequent')),
('enc', OneHotEncoder(handle_unknown='ignore', sparse_output=False))
]), cat_cols)
])
models = {
'Logistic Regression': LogisticRegression(max_iter=1000),
'Decision Tree': DecisionTreeClassifier(max_depth=5, random_state=42),
'Random Forest': RandomForestClassifier(n_estimators=100, random_state=42)
}
skf = StratifiedKFold(n_splits=5, shuffle=True, random_state=42)
print(f"\n{'Model':<25} {'F1 Mean':>10} {'F1 Std':>10} {'Recall':>10}")
print("-" * 58)
for nama, clf in models.items():
pipe = Pipeline([('prep', preprocessor), ('clf', clf)])
f1 = cross_val_score(pipe, X, y, cv=skf, scoring='f1')
rec = cross_val_score(pipe, X, y, cv=skf, scoring='recall')
print(f"{nama:<25} {f1.mean():>10.3f} {f1.std():>10.3f} {rec.mean():>10.3f}")predict_proba. Apa karakteristik umum siswa berisiko tinggi ini? Tulis 2-3 kalimat
analisis sebagai rekomendasi program intervensi kepada pihak sekolah.
Simpan, Load, dan Gunakan Model untuk Prediksi Baru (Fondasi Deployment)
Membangun model saja tidak cukup. Abbas harus bisa menyimpannya dan menggunakannya kembali tanpa melatih ulang dari nol. Kemampuan ini adalah fondasi deployment ML sederhana dan dibutuhkan di hampir setiap proyek dunia nyata.
import joblib
import numpy as np
import pandas as pd
from sklearn.pipeline import Pipeline
from sklearn.preprocessing import StandardScaler
from sklearn.ensemble import RandomForestClassifier
from sklearn.datasets import load_breast_cancer
from sklearn.model_selection import train_test_split
from sklearn.metrics import classification_report
# Langkah 1: Latih pipeline seperti biasa
X, y = load_breast_cancer(return_X_y=True, as_frame=True)
feature_names = X.columns.tolist()
X_train, X_test, y_train, y_test = train_test_split(X, y, test_size=0.2, random_state=42)
pipeline = Pipeline([
('scaler', StandardScaler()),
('model', RandomForestClassifier(n_estimators=100, random_state=42))
])
pipeline.fit(X_train, y_train)
print("Evaluasi pada test set:")
print(classification_report(y_test, pipeline.predict(X_test),
target_names=['Malignant', 'Benign']))
# Langkah 2: Simpan seluruh pipeline ke satu file
joblib.dump(pipeline, 'model_diagnostik.pkl')
print("Pipeline disimpan ke model_diagnostik.pkl")
# Langkah 3: Hapus dari memori, simulasi sesi baru
del pipeline
# Langkah 4: Load model yang sudah disimpan
pipeline_loaded = joblib.load('model_diagnostik.pkl')
print("Model berhasil di-load. Siap digunakan tanpa training ulang.")
# Langkah 5: Prediksi sampel baru (simulasi pasien baru datang)
sampel_baru = X_test.iloc[[0]]
prediksi = pipeline_loaded.predict(sampel_baru)[0]
prob = pipeline_loaded.predict_proba(sampel_baru)[0]
label_map = {0: 'Malignant (ganas)', 1: 'Benign (jinak)'}
print(f"\nHasil prediksi sampel baru:")
print(f" Kelas : {label_map[prediksi]}")
print(f" Prob Malignant : {prob[0]:.2%}")
print(f" Prob Benign : {prob[1]:.2%}")
print(f" Label asli : {label_map[y_test.iloc[0]]}")joblib.load, lalu buat fungsi
prediksi_kos(kota, tipe, fasilitas, luas_m2, jarak_km, lantai)
yang menerima input dan mengembalikan prediksi harga dalam format "Rp X.XXX.000/bulan".
Uji dengan 5 skenario kos fiktif berbeda yang Abbas tentukan sendiri.
Apakah hasil prediksi terasa masuk akal dibandingkan harga kos di kota tersebut yang Abbas ketahui?