Dataset publik yang siap pakai memang banyak, tetapi sebagian besar proyek nyata berhenti pada data mentah tanpa label: citra medis yang belum ditandai, transaksi yang belum diklasifikasikan, rekaman yang belum ditranskripsi. Kualitas label menjadi batas atas kualitas model — pelabel yang keliru mengajarkan kesalahan kepada model secara sistematis. Chapter ini membandingkan metode pelabelan, memperkenalkan kontrol kualitas lewat double annotation, lalu menghitung Cohen’s kappa secara manual dari nol agar rumusnya transparan.
Metode Pelabelan #
Empat jalur berikut menutup spektrum biaya-kualitas:
| Metode | Cara kerja | Biaya | Kualitas |
|---|---|---|---|
| Manual oleh pakar | ahli domain memberi label satu per satu | sangat tinggi | tertinggi |
| Crowdsourcing | banyak pekerja paruh waktu, tugas kecil per item | rendah-menengah | beragam, butuh kontrol |
| Weak supervision | aturan/heuristik menulis label secara massal (konsep Snorkel) | sangat rendah | kasar tapi luas |
| Model-assisted | model awal memberi pralabel, manusia mengoreksi | menengah | baik bila model awal layak |
Pola kerja profesional menggabungkan jalur tersebut: pakar menyusun panduan anotasi dan contoh sulit, crowdsourcing atau weak supervision mengisi label massal, lalu model-assisted mempercepat iterasi berikutnya. Kerangka weak supervision populer dibahas pada proyek Snorkel , di mana “labeling function” berupa aturan ditulis sebagai kode dan digabung menjadi label probabilistik.
Kontrol Kualitas: Double Annotation #
Cara paling andal mengukur kualitas pelabelan adalah meminta dua anotator independen memberi label pada item yang sama. Persen kesepakatan mentah, misalnya 90%, ternyata menyesatkan: dua anotator yang sama-sama malas memilih kelas terpopuler akan mencapai persen tinggi tanpa keahlian apa pun. Cohen’s kappa memperbaiki hal tersebut dengan mengurangi kesepakatan yang memang diharapkan dari kebetulan:
kappa = (po - pe) / (1 - pe)Nilai po adalah kesepakatan teramati dan pe adalah kesepakatan yang diharapkan bila kedua anotator memilih label secara acak menurut distribusi marginal masing-masing. Kappa bernilai 1 saat kesepakatan sempurna dan mendekati 0 saat kesepakatan tidak lebih baik dari kebetulan.
Demo: Kappa dari Nol #
Dua anotator fiktif — Budi dan Siti — melabeli 30 item ke dalam tiga kelas. Berkas kappa_agreement.py menghitung persen kesepakatan, matriks kebingungan antar anotator, hingga kappa langkah demi langkah:
"""Kontrol kualitas pelabelan: persen setuju + Cohen's kappa manual.
Dua anotator (Budi dan Siti) memberi label pada 30 item yang sama untuk
tugas 3 kelas. Kita hitung persen kesepakatan dan Cohen's kappa dari nol
dengan NumPy supaya rumusnya terlihat transparan.
"""
import numpy as np
rng = np.random.default_rng(42)
# ---------- 1. Label dua anotator pada 30 item (kelas 0/1/2) ----------
budi = rng.choice([0, 1, 2], size=30, p=[0.4, 0.35, 0.25])
siti = np.where(
rng.random(30) < 0.80, # 80% item disetujui secara kebetulan terkontrol
budi,
rng.choice([0, 1, 2], size=30),
)
print("===== DATA ANOTASI =====")
print("label Budi (10 pertama):", budi[:10].tolist())
print("label Siti (10 pertama):", siti[:10].tolist())
# ---------- 2. Persen kesepakatan mentah ----------
setuju = int((budi == siti).sum())
persen = setuju / len(budi) * 100
print()
print("===== PERSEN KESEPAKATAN =====")
print(f"item setuju: {setuju}/30 -> {persen:.1f}%")
# ---------- 3. Matriks kebingungan antar anotator ----------
K = 3
matriks = np.zeros((K, K), dtype=int)
for b, s in zip(budi, siti):
matriks[b, s] += 1
print()
print("===== MATRIKS KEBINGUNGAN (baris=Budi, kolom=Siti) =====")
print(matriks)
# ---------- 4. Cohen's kappa dari nol ----------
n = matriks.sum()
po = np.trace(matriks) / n # kesepakatan teramati
pb = matriks.sum(axis=1) / n # distribusi marginal Budi
ps = matriks.sum(axis=0) / n # distribusi marginal Siti
pe = float((pb * ps).sum()) # kesepakatan harapan
kappa = (po - pe) / (1 - pe)
print()
print("===== COHEN'S KAPPA =====")
print(f"po (teramati) = {po:.4f}")
print(f"pe (harapan) = {pe:.4f}")
print(f"kappa = (po - pe) / (1 - pe) = ({po:.4f} - {pe:.4f})"
f" / (1 - {pe:.4f}) = {kappa:.4f}")
# ---------- 5. Interpretasi ----------
print()
print("===== INTERPRETASI =====")
if kappa < 0.20:
level = "sangat lemah"
elif kappa < 0.40:
level = "lemah"
elif kappa < 0.60:
level = "cukup"
elif kappa < 0.80:
level = "kuat"
else:
level = "sangat kuat"
print(f"kappa {kappa:.4f} -> kesepakatan {level}")
print(f"persen setuju {persen:.1f}% vs kappa {kappa:.2f}:")
print("kappa lebih rendah karena sebagian kesepakatan memang")
print("bisa terjadi dari distribusi label semata (pe), bukan keahlian.")Output yang Dihasilkan:
===== DATA ANOTASI =====
label Budi (10 pertama): [2, 1, 2, 1, 0, 2, 2, 2, 0, 1]
label Siti (10 pertama): [2, 2, 2, 1, 0, 2, 2, 2, 0, 1]
===== PERSEN KESEPAKATAN =====
item setuju: 27/30 -> 90.0%
===== MATRIKS KEBINGUNGAN (baris=Budi, kolom=Siti) =====
[[ 9 0 0]
[ 0 7 2]
[ 0 1 11]]
===== COHEN'S KAPPA =====
po (teramati) = 0.9000
pe (harapan) = 0.3433
kappa = (po - pe) / (1 - pe) = (0.9000 - 0.3433) / (1 - 0.3433) = 0.8477
===== INTERPRETASI =====
kappa 0.8477 -> kesepakatan sangat kuat
persen setuju 90.0% vs kappa 0.85:
kappa lebih rendah karena sebagian kesepakatan memang
bisa terjadi dari distribusi label semata (pe), bukan keahlian.Contoh di atas diuji nyata dengan NumPy 2.1.3 pada mesin CPU dan selesai dalam waktu di bawah satu detik. Mari kita telaah kode di atas baris per baris:
- Baris 10 (seed):
default_rng(42)mengunci urutan label sintetis sehingga 27/30 item setuju setiap kali kode dijalankan. - Baris 13-19 (dua anotator): label Budi diambil dari distribusi tak seragam
p=[0.4, 0.35, 0.25]; label Siti menyalin label Budi pada 80% item dan diacak pada sisanya — pola ini mensimulasikan anotator yang sepaham sebagian besar waktu. - Baris 24-28 (persen mentah): 27 dari 30 item bernilai sama sehingga persen kesepakatan 90% — angka yang terdengar mengesankan sebelum dikoreksi kebetulan.
- Baris 31-38 (matriks kebingungan): sel
(b, s)menghitung item yang dilabeliboleh Budi dansoleh Siti; diagonalnya berisi item yang disepakati (9, 7, 11) dan dua sel luar diagonal memuat tiga ketidaksesuaian. - Baris 40-50 (kappa):
np.tracemenjumlahkan diagonal menjadipo = 0.9; marginal barispbdan marginal kolompsmenghasilkanpe = 0.3433— peluang dua anotator acak berakhir pada label yang sama; kappa akhir0.8477. - Baris 55-70 (interpretasi): ambang kualitas memetakan kappa ke lima tingkat; hasil 0.85 berarti kesepakatan “sangat kuat”, bukan sekadar 90% angka mentah.
Mengelola Biaya #
Biaya pelabelan tumbuh linear dengan jumlah item dan jumlah anotator per item. Double annotation menggandakan biaya, tetapi hanya diterapkan pada sampel (misalnya 5-10% item acak) untuk memantau kualitas secara berkelanjutan; sisanya dilabeli tunggal. Item yang keduanya berselisih dikirim ke pakar sebagai penengah — pola tiga tahap ini menjaga biaya tetap terkendali tanpa kehilangan sinyal kualitas.
Label yang rapi dan terukur, bagaimanapun, sering membawa satu masalah baru: jumlah antar kelas tidak seimbang, karena kejadian menarik memang jarang terjadi. Kita bedah gejala dan penanganannya pada chapter berikutnya.