Bagaimana Model "Belajar"

Penjelasan rinci proses learning ketiga algoritma regresi — Multiple Linear Regression, Random Forest, dan XGBoost — dengan matematika nyata dan contoh hitung memakai data invoice PT Kreasi Pandawa Sakti.

Target: lama bayar (hari) Loss: squared error Model terpilih: XGBoost λ=1, η=0,3 (contoh)

01Apa arti "belajar" di sini

“Belajar” berarti mencari sebuah fungsi f yang memetakan fitur invoice (klien, termin, nilai, …) ke perkiraan lama bayar, sedemikian rupa sehingga kesalahannya sekecil mungkin pada data historis. Kesalahan diukur dengan fungsi kerugian (loss). Untuk regresi kita pakai squared error:

$$ \mathcal{L} = \sum_{i=1}^{n} \tfrac{1}{2}\bigl(y_i - \hat{y}_i\bigr)^2, \qquad \hat{y}_i = f(\mathbf{x}_i) $$

dengan y_i = lama bayar aktual invoice ke-i, \hat{y}_i = prediksi model, dan \mathbf{x}_i = vektor fiturnya. Setiap algoritma punya cara berbeda untuk meminimalkan \mathcal{L} — itulah "proses learning" yang dibahas di bawah.

02Data contoh (6 invoice)

Agar bisa dihitung tangan, kita pakai 6 invoice nyata-mirip. Untuk kejelasan, satu fitur numerik dipakai sebagai contoh split: top_days (termin). Target = lama bayar aktual.

InvoiceKlientop_daysLama bayar aktual y (hari)
INV-ASayap Mas1412
INV-BSayap Mas1418
INV-CIchi Tan3027
INV-DIchi Tan3035
INV-EEnergizer4550
INV-FEnergizer4542

Rata-rata target: \bar{y} = (12+18+27+35+50+42)/6 = 30{,}67 hari.

03Pondasi: bagaimana satu pohon regresi belajar

Random Forest dan XGBoost sama-sama dibangun dari pohon keputusan, jadi kita mulai dari sini. Sebuah pohon "belajar" dengan mencari titik pemisah (split) yang paling mengurangi kesalahan. Untuk regresi, ukurannya adalah jumlah kuadrat galat (SSE) dalam tiap simpul; prediksi sebuah daun = rata-rata target anggotanya.

$$ \text{SSE}(node)=\sum_{i\in node}(y_i-\bar{y}_{node})^2, \qquad \text{Reduksi}= \text{SSE}_{parent}-\bigl(\text{SSE}_{L}+\text{SSE}_{R}\bigr) $$

Pohon mencoba semua kandidat ambang (mis. top_days < 22, top_days < 37,5) dan memilih yang memberi reduksi SSE terbesar, lalu mengulang di tiap cabang sampai kedalaman tertentu. Itulah inti "belajar" satu pohon.

04AMultiple Linear Regression — least squares

MLR menganggap target adalah kombinasi linear berbobot dari fitur:

$$ \hat{y} = \beta_0 + \beta_1 x_1 + \beta_2 x_2 + \dots + \beta_p x_p = \mathbf{x}^\top\boldsymbol{\beta} $$

Proses learning = mencari bobot \boldsymbol{\beta} yang meminimalkan total galat kuadrat. Solusinya bisa langsung (closed-form, normal equation):

$$ \boldsymbol{\beta}^{*} = \bigl(\mathbf{X}^\top \mathbf{X}\bigr)^{-1} \mathbf{X}^\top \mathbf{y} $$

atau iteratif via gradient descent — bergerak melawan arah gradien loss:

$$ \boldsymbol{\beta} \leftarrow \boldsymbol{\beta} - \eta\,\nabla_{\boldsymbol{\beta}}\mathcal{L},\qquad \nabla_{\boldsymbol{\beta}}\mathcal{L} = -\mathbf{X}^\top(\mathbf{y}-\mathbf{X}\boldsymbol{\beta}) $$

Kelebihan

Tiap fitur punya bobot yang bisa dibaca; cepat; jadi pembanding dasar.

Kelemahan

Hanya menangkap hubungan lurus. Pola "klien × termin" pada data kita tidak linear.

05BRandom Forest — belajar lewat bagging

Random Forest melatih banyak pohon (300 pada proyek ini) yang masing-masing melihat data berbeda, lalu merata-ratakan hasilnya:

$$ \hat{y} = \frac{1}{T}\sum_{t=1}^{T} f_t(\mathbf{x}) $$
  • Bootstrap: tiap pohon dilatih pada sampel acak (dengan pengembalian) dari data.
  • Feature subsampling: tiap split hanya mempertimbangkan sebagian fitur acak (max_features="sqrt") → pohon jadi beragam.
  • Averaging: rata-rata banyak pohon meredam kesalahan acak (menurunkan variance) tanpa menambah bias berarti.
flowchart LR
  D[Data latih
356 invoice] --> B1[Sampel bootstrap 1] --> T1[Pohon 1] D --> B2[Sampel bootstrap 2] --> T2[Pohon 2] D --> Bn[Sampel bootstrap 300] --> Tn[Pohon 300] T1 --> AVG[Rata-rata prediksi] T2 --> AVG Tn --> AVG AVG --> Y[Estimasi lama bayar]

Gambar 1 — Bagging: pohon-pohon sejajar dilatih independen, hasilnya dirata-rata.

Beda dengan XGBoost: di Random Forest pohon-pohon sejajar & independen; di XGBoost pohon dibangun berurutan, tiap pohon memperbaiki sisa kesalahan pohon sebelumnya (bagian berikutnya).

06CXGBoost — matematika proses belajarnya

XGBoost membangun model secara aditif & bertahap (gradient boosting): mulai dari tebakan awal, lalu tiap pohon baru menambahkan koreksi.

$$ \hat{y}_i^{(t)} = \hat{y}_i^{(t-1)} + \eta\, f_t(\mathbf{x}_i), \qquad f_t \in \mathcal{F}\ (\text{ruang pohon}) $$

6.1 Objektif: loss + regularisasi

Yang diminimalkan bukan hanya loss, tapi juga kompleksitas pohon (agar tidak overfitting):

$$ \mathrm{Obj}^{(t)} = \sum_{i=1}^{n} \mathcal{L}\!\left(y_i,\hat{y}_i^{(t-1)}+f_t(\mathbf{x}_i)\right) + \Omega(f_t),\qquad \Omega(f)=\gamma T + \tfrac{1}{2}\lambda \sum_{j=1}^{T} w_j^2 $$

dengan T = jumlah daun, w_j = nilai daun ke-j, \gamma = penalti per daun (pemangkasan), \lambda = regularisasi L2 (di model kita \lambda=1).

6.2 Pendekatan Taylor orde-2

Loss diuraikan dengan deret Taylor sampai orde dua di sekitar prediksi sebelumnya, memakai gradien g_i dan hessian h_i:

$$ g_i=\partial_{\hat{y}}\mathcal{L}(y_i,\hat{y}_i^{(t-1)}),\quad h_i=\partial^2_{\hat{y}}\mathcal{L}(y_i,\hat{y}_i^{(t-1)}) $$

Untuk squared error \mathcal{L}=\tfrac12(y-\hat{y})^2, keduanya sederhana:

$$ g_i=\hat{y}_i^{(t-1)}-y_i = -\,r_i,\qquad h_i=1 $$

di mana r_i = y_i-\hat{y}_i^{(t-1)} adalah residual (sisa kesalahan). Jadi pohon berikutnya pada dasarnya belajar memprediksi residual.

6.3 Nilai daun optimal

Untuk sebuah daun berisi himpunan instance I_j, dengan G_j=\sum_{i\in I_j} g_i dan H_j=\sum_{i\in I_j} h_i, nilai daun yang meminimalkan objektif adalah:

$$ w_j^{*} = -\,\frac{G_j}{H_j+\lambda} \;=\; \frac{\sum_{i\in I_j} r_i}{\,N_j+\lambda\,} $$

(ruas kanan memakai g_i=-r_i,\ h_i=1, jadi H_j=N_j = jumlah anggota daun).

6.4 Skor kemiripan & Gain sebuah split

Mutu sebuah simpul diukur dengan structure / similarity score, dan sebuah split dinilai dari Gain-nya:

$$ \text{Sim}=\frac{G^2}{H+\lambda}=\frac{\left(\sum_i r_i\right)^2}{N+\lambda}, \qquad \text{Gain}=\tfrac12\!\left[\frac{G_L^2}{H_L+\lambda}+\frac{G_R^2}{H_R+\lambda}-\frac{(G_L+G_R)^2}{H_L+H_R+\lambda}\right]-\gamma $$

Split dengan Gain tertinggi dipilih. Jika Gain < 0 (lebih kecil dari penalti \gamma), cabang dipangkas — inilah regularisasi struktural XGBoost.

6.5 Learning rate (shrinkage)

Tiap pohon baru tidak ditambahkan penuh, melainkan diperkecil oleh \eta (di model kita \eta=0{,}05) agar langkah belajar kecil & stabil — mengurangi risiko overfitting, dengan kompensasi memakai lebih banyak pohon (400).

07Contoh hitung langkah demi langkah (XGBoost)

Memakai 6 invoice di bagian 02, \lambda=1, \gamma=0, dan \eta=0{,}3 (sengaja besar agar perubahan terlihat).

Ronde 0 — tebakan awal

Prediksi awal = rata-rata target: \hat{y}^{(0)}=30{,}67. Residual awal r_i=y_i-30{,}67:

InvoiceABCDEF
r_i−18,67−12,67−3,67+4,33+19,33+11,33

Ronde 1 — cari split terbaik

Skor akar: \text{Sim}_{root}=(\sum r)^2/(6+1)=0 (residual dari rata-rata berjumlah nol). Bandingkan dua kandidat split pada top_days:

SplitKiri (anggota)\text{Sim}_LKanan\text{Sim}_RGain
top_days < 22A,B327,26C,D,E,F196,36523,62
top_days < 37,5A,B,C,D188,09E,F313,48501,57

Split top_days < 22 menang (Gain 523,62). Contoh perhitungan \text{Sim}_L untuk daun {A,B}:

$$ \text{Sim}_L=\frac{(-18{,}67-12{,}67)^2}{2+1}=\frac{(-31{,}33)^2}{3}=327{,}26 $$
flowchart TB
  R{"top_days < 22 ?"}
  R -- "ya (A,B)" --> L["Daun kiri
w* = Σr/(N+λ) = −31,33/3 = −10,44
×η(0,3) = −3,13"] R -- "tidak (C,D,E,F)" --> Rn["Daun kanan
w* = 31,33/5 = 6,27
×η(0,3) = +1,88"]

Gambar 2 — Pohon ronde 1: satu split, dua daun. Nilai daun = w^{*}=\sum r/(N+\lambda), lalu diperkecil oleh \eta.

Perbarui prediksi & lihat residual mengecil

\hat{y}^{(1)}=\hat{y}^{(0)}+\eta\,w^{*}:

Invoiceaktual y\hat{y}^{(0)}daun ×η\hat{y}^{(1)}residual baru|sebelum| → |sesudah|
A1230,67−3,1327,53−15,5318,67 → 15,53 ✓
B1830,67−3,1327,53−9,5312,67 → 9,53 ✓
C2730,67+1,8832,55−5,553,67 → 5,55
D3530,67+1,8832,55+2,454,33 → 2,45 ✓
E5030,67+1,8832,55+17,4519,33 → 17,45 ✓
F4230,67+1,8832,55+9,4511,33 → 9,45 ✓

Total galat absolut turun dari 70,0 → 59,97 hanya dalam satu ronde. Invoice C sempat sedikit naik karena daun kanan merata-ratakan C,D,E,F — persis ini yang diperbaiki ronde berikutnya.

Ronde 2 — perbaiki sisa kesalahan

Pohon ke-2 dibangun atas residual baru. Pada simpul kanan {C,D,E,F}, split terbaik top_days < 37,5 memisahkan {C,D} dari {E,F} dengan Gain ≈ 131,1, menghasilkan daun w_{CD}^{*}=-1{,}03 dan w_{EF}^{*}=8{,}97 — menaikkan prediksi E,F yang memang masih kurang. Proses ini diulang hingga 400 pohon.

08Loop pelatihan penuh

flowchart TB
  S[Mulai: ŷ = rata-rata] --> R[Hitung residual r = y − ŷ]
  R --> G[Hitung g = −r, h = 1]
  G --> T[Bangun pohon: pilih split ber-Gain tertinggi]
  T --> W["Hitung nilai daun w* = −G/(H+λ)"]
  W --> U["Perbarui ŷ ← ŷ + η·w*"]
  U --> C{Sudah 400 pohon
atau galat stabil?} C -- belum --> R C -- sudah --> F[Model final XGBoost]

Gambar 3 — Satu siklus boosting diulang ratusan kali; tiap putaran mengejar sisa kesalahan.

Pada data nyata (446 invoice, 12 fitur) inilah yang menghasilkan R² 0,617 · RMSE 10,34 · MAE 6,64 hari — mengungguli Random Forest dan MLR. Keunggulannya: koreksi berurutan + regularisasi menangkap pola tak-linear "klien × termin" yang tidak bisa ditangkap garis lurus.

09Dari model ke layar — alur saat memprediksi

Setelah dilatih, model dipakai saat halaman dibuka. Berikut urutan panggilannya (diagram sekuens):

sequenceDiagram
  autonumber
  actor U as Finance / BOD
  participant App as Aplikasi (Go)
  participant PP as paymentpred (client)
  participant ML as Sidecar ML (Python)
  participant M as Model XGBoost
  U->>App: Buka AR Aging / Beranda BOD
  App->>App: Ambil invoice belum lunas + bentuk fitur
  App->>PP: PredictBatch(daftar invoice)
  PP->>ML: POST /predict/payment-days/batch
  ML->>M: model.predict(fitur)
  M-->>ML: estimasi hari per invoice
  ML-->>PP: {predictions:[...]}
  PP-->>App: map id → hari (atau "tak tersedia")
  alt sidecar sehat
    App-->>U: Tampilkan estimasi + flag risiko
  else timeout / mati (>3 dtk)
    App-->>U: Sembunyikan estimasi (halaman tetap jalan)
  end
      

Gambar 4 — Sequence diagram inferensi: aplikasi → client → sidecar → model → UI, dengan jalur cadangan bila ML tidak tersedia.