Model deep learning belajar dari data yang direkam dunia apa adanya, termasuk ketimpangan yang ada di dalamnya. Ketika keputusan historis condong ke satu kelompok, model yang dilatih pada data tersebut mewarisi kecondongan itu dan memperbesarnya lewat otomatisasi — keputusan yang dulu diambil perlahan oleh manusia kini diambil ribuan kali per detik dengan pola yang sama. Chapter ini membongkar tiga sumber bias yang paling umum, mendefinisikan ukuran fairness yang bisa dihitung, lalu mendemonstrasikan keduanya pada dataset kredit fiktif: mengukur ketimpangan laju persetujuan antar kelompok dan meratakannya dengan teknik threshold per kelompok.

Tiga Sumber Bias pada Data #

SumberMekanismeContoh konkret
Data historiskeputusan masa lalu yang timpang menjadi label latihpinjaman dulu lebih sering disetujui untuk kelompok A, model meniru pola itu
Samplingsatu kelompok kurang terwakili dalam sampeldata wajah didominasi satu rentang usia, model lebih buruk pada rentang lain
Labeldefinisi target membawa kecondongan“berhasil” diartikan dari metrik yang tidak berlaku sama untuk semua kelompok

Sumber ketiga sering paling sulit ditemukan karena label terlihat netral: skor kredit, lama bekerja, jumlah transaksi. Bila definisi tersebut bergantung pada akses yang tidak merata antar kelompok, model mempelajari akses, bukan kemampuan — tema yang dibahas rinci pada Dataset Labeling .

Mengukur Fairness dengan Angka #

Dua ukuran yang paling sering dipakai dapat dihitung langsung dari tabel keputusan:

  • Demographic parity membandingkan laju keputusan positif antar kelompok: P(setuju | kelompok A) versus P(setuju | kelompok B). Selisih yang jauh dari nol menandakan ketimpangan laju — angka yang mudah dikomunikasikan ke pihak non-teknis.
  • Equalized odds membedah keputusan terhadap label sejati: TPR = P(setuju | mampu bayar) dan FPR = P(setuju | tidak mampu) per kelompok. Ketimpangan TPR berarti satu kelompok lebih sering kehilangan hak yang seharusnya diberikan; ketimpangan FPR berarti satu kelompok lebih sering diberi beban yang seharusnya ditolak.

Demo: Kredit Kelompok A dan B #

Berkas fairness_kredit.py membangun 4000 pemohon fiktif dengan tiga fitur finansial dan satu kolom kelompok (A/B — label abstrak, bukan atribut sensitif nyata). Kemampuan bayar sejati ditentukan oleh fitur finansial, tetapi label historis yang dipakai melatih model diberi kecondongan +1,2 untuk kelompok A. Model logistic regression kemudian diuji: berapa laju persetujuan tiap kelompok, dan apa yang terjadi bila threshold kelompok B diturunkan sampai lajunya menyamai kelompok A:

python
"""Bias dan fairness pada keputusan kredit: mengukur ketimpangan laju
persetujuan antar kelompok, lalu meratakannya lewat threshold per
kelompok.

Data sepenuhnya fiktif: pemohon kelompok A dan B (label kelompok
abstrak, bukan atribut sensitif nyata). Label historis dibuat condong
ke kelompok A; model yang belajar dari label tersebut mewarisi
kecondongan itu, terlihat dari laju persetujuan per kelompok.
"""

import numpy as np
from sklearn.linear_model import LogisticRegression

rng = np.random.default_rng(7)
n = 4000

# ---------- 1. Data pemohon fiktif ----------
kelompok = rng.choice(["A", "B"], size=n, p=[0.5, 0.5])
penghasilan = rng.normal(8.0, 2.0, n)     # juta rupiah per bulan
rasio_utang = rng.uniform(0.0, 0.8, n)    # cicilan / penghasilan
tenor = rng.exponential(5.0, n)           # lama bekerja (tahun)

# kemampuan bayar sejati (tolok ukur evaluasi, bukan label latih)
skor = penghasilan - 6.0 * rasio_utang + 0.3 * tenor
mampu = (skor > 6.0).astype(int)

# label historis: keputusan petugas yang condong ke kelompok A
kecondongan = np.where(kelompok == "A", 1.2, -1.2)
p_hist = 1.0 / (1.0 + np.exp(-(1.5 * (skor - 6.0) + kecondongan)))
disetujui = (rng.uniform(size=n) < p_hist).astype(int)

# bagi data: 3000 latih, 1000 uji
urutan = rng.permutation(n)
idx_latih, idx_uji = urutan[:3000], urutan[3000:]
fitur = np.column_stack([penghasilan, rasio_utang, tenor,
                         (kelompok == "A").astype(float)])

model = LogisticRegression(max_iter=1000)
model.fit(fitur[idx_latih], disetujui[idx_latih])
print(f"koefisien fitur [penghasilan, utang, tenor, kelompok_A]:")
print(np.round(model.coef_[0], 3).tolist())
print()

prob_uji = model.predict_proba(fitur[idx_uji])[:, 1]
kel_uji = kelompok[idx_uji]
mampu_uji = mampu[idx_uji]


def ukur(prediksi, judul):
    print(f"===== {judul} =====")
    laju, tpr, fpr = {}, {}, {}
    for g in ("A", "B"):
        m = kel_uji == g
        laju[g] = prediksi[m].mean()
        tpr[g] = prediksi[m & (mampu_uji == 1)].mean()
        fpr[g] = prediksi[m & (mampu_uji == 0)].mean()
        print(f"kelompok {g}: laju setuju {laju[g]:.3f} | "
              f"TPR {tpr[g]:.3f} | FPR {fpr[g]:.3f}")
    dp = abs(laju["A"] - laju["B"])
    print(f"demographic parity gap |laju A - laju B| : {dp:.3f}")
    print(f"equalized odds gap |TPR A - TPR B|       : "
          f"{abs(tpr['A'] - tpr['B']):.3f}")
    print(f"kesesuaian dengan kemampuan bayar sejati : "
          f"{(prediksi == mampu_uji).mean():.3f}")
    print()


pred_seragam = (prob_uji >= 0.5).astype(int)
ukur(pred_seragam, "MODEL DENGAN THRESHOLD SERAGAM 0.50")

# ---------- 2. Mitigasi: threshold kelompok B diturunkan ----------
laju_A = pred_seragam[kel_uji == "A"].mean()
prob_B = prob_uji[kel_uji == "B"]
kandidat = np.round(np.arange(0.10, 0.51, 0.01), 2)
t_B = min(kandidat, key=lambda t: abs((prob_B >= t).mean() - laju_A))
print(f"mitigasi: threshold kelompok B = {t_B:.2f} "
      f"(A tetap 0.50) agar laju setuju B menyamai A")
print()
pred_mitigasi = pred_seragam.copy()
mask_B = kel_uji == "B"
pred_mitigasi[mask_B] = (prob_B >= t_B).astype(int)
ukur(pred_mitigasi, "MODEL SETELAH MITIGASI THRESHOLD PER KELOMPOK")

Output yang Dihasilkan:

text
koefisien fitur [penghasilan, utang, tenor, kelompok_A]:
[1.422, -8.125, 0.426, 2.352]

===== MODEL DENGAN THRESHOLD SERAGAM 0.50 =====
kelompok A: laju setuju 0.719 | TPR 1.000 | FPR 0.250
kelompok B: laju setuju 0.532 | TPR 0.807 | FPR 0.000
demographic parity gap |laju A - laju B| : 0.187
equalized odds gap |TPR A - TPR B|       : 0.193
kesesuaian dengan kemampuan bayar sejati : 0.890

mitigasi: threshold kelompok B = 0.10 (A tetap 0.50) agar laju setuju B menyamai A

===== MODEL SETELAH MITIGASI THRESHOLD PER KELOMPOK =====
kelompok A: laju setuju 0.719 | TPR 1.000 | FPR 0.250
kelompok B: laju setuju 0.721 | TPR 1.000 | FPR 0.181
demographic parity gap |laju A - laju B| : 0.001
equalized odds gap |TPR A - TPR B|       : 0.000
kesesuaian dengan kemampuan bayar sejati : 0.922

Contoh di atas diuji nyata dengan scikit-learn 1.5.2 dan NumPy 2.1.3 pada CPU, selesai dalam waktu di bawah satu detik, dengan seed tetap agar angkanya dapat direproduksi. Mari kita telaah kode di atas baris per baris:

  1. Baris 18-21 (data pemohon): kelompok, penghasilan, rasio utang, dan tenor kerja dibangkitkan acak; kedua kelompok sengaja diberi distribusi fitur yang sama supaya seluruh perbedaan hasil murni berasal dari label, bukan dari kekuatan finansial.
  2. Baris 23-25 (kemampuan bayar sejati): skor menggabungkan tiga fitur menjadi tolok ukur evaluasi; kolom mampu berperan sebagai label sejati yang dipakai menghitung TPR dan FPR — bukan sebagai target latih.
  3. Baris 27-30 (label historis): kecondongan +1,2 / -1,2 pada p_hist membuat petugas masa lalu lebih sering menyetujui kelompok A pada skor yang sama; inilah simulasi bias data historis dari tabel di awal chapter.
  4. Baris 32-39 (pembagian data dan pelatihan): 3000 baris melatih, 1000 diuji; kolom kelompok dimasukkan sebagai fitur keempat — koefisien 2.352 pada kelompok_A di awal output adalah bukti langsung bahwa kecondongan label berpindah menjadi bobot model.
  5. Baris 49-65 (fungsi ukur): mencetak laju persetujuan, TPR, dan FPR per kelompok, lalu merangkum dua gap beserta kesesuaian prediksi terhadap kemampuan bayar sejati.
  6. Baris 71-76 (mitigasi threshold): kandidat threshold B dari 0,10 sampai 0,50 discan; t_B dipilih agar laju persetujuan B menyamai laju A — mitigasi paling sederhana yang tetap bekerja pada model yang sudah terlanjur dilatih.

Membaca Angka dan Batas Mitigasi #

Angka output memperlihatkan rantai sebab-akibat yang utuh. Dengan threshold seragam 0,50, laju persetujuan kelompok A mencapai 0,719 sedangkan kelompok B hanya 0,532 — demographic parity gap 0,187, TPR kelompok B tertinggal 0,193. Setelah threshold B diturunkan ke 0,10, kedua gap menyusut ke 0,001 dan 0,000, sementara kesesuaian dengan kemampuan bayar sejati justru naik dari 0,890 ke 0,922 — pada kasus ini keadilan dan akurasi berjalan searah karena kecondongan historis murni merupakan derau.

Untuk skala nyata, dua pustaka open source menyediakan metrik lengkap beserta algoritma mitigasinya: Fairlearn dari Microsoft dan AI Fairness 360 dari IBM — keduanya mengemas konsep yang barusan kita hitung manual menjadi modul pipeline. Audit semacam ini juga prasyarat Evaluasi LLM ketika model dipecah per subkelompok: kecondongan tidak akan pernah tampak pada angka rata-rata.

Dari keadilan keputusan, kita berpindah ke aset yang lebih mendasar: data itu sendiri. Chapter berikutnya menghadapi skenario di mana data “anonim” yang sudah dibuang namanya tetap dapat dilacak kembali ke pemiliknya — lewat teknik join sederhana pada kolom-kolom yang tampak tidak berbahaya.