00 Etiketleme Maliyeti Problemi
Supervised learning'in gizli maliyeti: kaliteli etiketli veri üretmek, genellikle model geliştirme maliyetinin %80'ini oluşturabilir.
1 milyon metin için sentiment analizi etiketi: profesyonel annotator başına saatte 200 örnek, saat başı 20$ maliyet varsayımıyla yaklaşık 100.000$ ve 3 ay. Bu maliyet, ML projelerinin büyük çoğunluğunun mümkün olamamasına neden olur.
| Yaklaşım | Maliyet | Kalite | Hız |
|---|---|---|---|
| İnsan Annotasyon | Yüksek | En yüksek | Yavaş |
| Crowdsourcing (MTurk) | Orta | Değişken | Hızlı |
| Weak Supervision | Düşük | Orta-İyi | Çok hızlı |
| Active Learning | Orta | Yüksek | İteratif |
| Semi-Supervised | Düşük | İyi | Hızlı |
| Data Augmentation | Çok düşük | Değişken | Çok hızlı |
01 Label Studio Kurulumu ve Proje
Label Studio, metin, görüntü, ses ve video için açık kaynak annotation aracıdır. Docker ile dakikalar içinde çalıştırılabilir.
# Docker ile kurulum (önerilen)
docker run -it -p 8080:8080 \
-v $(pwd)/label-studio-data:/label-studio/data \
heartexlabs/label-studio:latest
# Veya pip ile
pip install label-studio
label-studio start --port 8080
# Python SDK kurulumu
pip install label-studio-sdk
from label_studio_sdk import Client
import json
# ── Bağlantı ────────────────────────────────────────────────────────
ls = Client(url="http://localhost:8080", api_key="YOUR_API_KEY")
ls.check_connection()
# ── NER Projesi Oluştur ──────────────────────────────────────────────
NER_CONFIG = """
<View>
<Labels name="label" toName="text">
<Label value="PER" background="#F44336"/>
<Label value="ORG" background="#2196F3"/>
<Label value="LOC" background="#4CAF50"/>
<Label value="MISC" background="#FF9800"/>
</Labels>
<Text name="text" value="$text"/>
</View>
"""
project = ls.start_project(
title="Türkçe NER Etiketleme",
label_config=NER_CONFIG,
description="Haber metinlerinde kişi, kurum ve lokasyon tespiti",
)
# ── Veri yükle ───────────────────────────────────────────────────────
tasks = [
{"text": "Türkiye Cumhurbaşkanı Ankara'da açıklama yaptı."},
{"text": "Apple CEO'su Tim Cook İstanbul'u ziyaret etti."},
{"text": "Borsa İstanbul bugün rekor kırdı."},
]
project.import_tasks(tasks)
# ── Tamamlanan annotation'ları dışa aktar ────────────────────────────
annotations = project.export_tasks(export_type="JSON")
with open("annotations.json", "w") as f:
json.dump(annotations, f, ensure_ascii=False, indent=2)
02 Annotasyon Türleri
Label Studio farklı görev türleri için farklı label config şablonları sunar.
| Görev Türü | Label Config Elementi | Çıktı Formatı |
|---|---|---|
| Text Classification | <Choices> | Kategori listesi |
| NER (Named Entity) | <Labels> + <Text> | start/end/label dizileri |
| Image Classification | <Choices> + <Image> | Kategori |
| Bounding Box | <RectangleLabels> + <Image> | x, y, w, h, label |
| Segmentation | <PolygonLabels> + <Image> | Polygon noktaları |
| Relation | <Relations> | Span çiftleri arası ilişki |
<View>
<Image name="image" value="$image" zoom="true"/>
<RectangleLabels name="label" toName="image">
<Label value="kişi" background="#FF6B6B"/>
<Label value="araç" background="#4ECDC4"/>
<Label value="bina" background="#45B7D1"/>
<Label value="ağaç" background="#96CEB4"/>
</RectangleLabels>
</View>
import json
from typing import List, Tuple
def ls_to_spacy(annotations_file: str) -> List[Tuple]:
"""Label Studio NER çıktısını spaCy training formatına çevir."""
with open(annotations_file) as f:
data = json.load(f)
training_data = []
for task in data:
text = task["data"]["text"]
entities = []
for ann in task.get("annotations", []):
for result in ann.get("result", []):
if result["type"] == "labels":
start = result["value"]["start"]
end = result["value"]["end"]
label = result["value"]["labels"][0]
entities.append((start, end, label))
if entities:
training_data.append((text, {"entities": entities}))
return training_data
spacy_data = ls_to_spacy("annotations.json")
print(f"{len(spacy_data)} etiketli örnek hazır.")
03 Annotation Guideline Yazma
İyi annotation guideline yüksek inter-annotator agreement'ın temelidir. Belirsiz örnekler için karar ağacı zorunludur.
01 Tanım → Her kategori için net, tek cümlelik tanım yaz 02 Örnekler → Her kategori için 5+ pozitif ve 3+ negatif örnek ekle 03 Sınır Durumlar → Belirsiz örnekler için karar ağacı yaz 04 Pilot Round → 50 örnek üzerinde annotator'larla pilot; soruları topla 05 Güncelle → Pilot sonuçlarına göre guideline'ı revize et 06 Kalibrasyon → Tüm annotator'lar aynı 30 örnek üzerinde kalibre edilir
NER Örnek Kural: Şirket adı (ORG) ile ürün adı (MISC) arasındaki karar: "Apple" bağımsız geçiyorsa ORG; "Apple iPhone" gibi ürün bağlamında sadece "iPhone" MISC, "Apple" ORG. Tam unvan belirtiliyorsa tüm unvan tek span olarak işaretlenir: "Türkiye Büyük Millet Meclisi" → tek LOC değil, ORG.
04 Inter-Annotator Agreement
Annotator'lar arasındaki tutarlılığı ölçmek annotation kalitesinin nesnel göstergesidir.
from sklearn.metrics import cohen_kappa_score
import numpy as np
# ── Cohen's Kappa (text classification) ────────────────────────────
ann1 = ["POS", "NEG", "NEU", "POS", "NEG", "POS", "NEU"]
ann2 = ["POS", "NEG", "POS", "POS", "NEU", "POS", "NEU"]
kappa = cohen_kappa_score(ann1, ann2)
print(f"Cohen's Kappa: {kappa:.4f}")
# ── Fleiss' Kappa (birden fazla annotator) ──────────────────────────
def fleiss_kappa(ratings: np.ndarray) -> float:
"""
ratings: (n_subjects, n_categories) — her kategori için oy sayısı
"""
n, k = ratings.shape
N = ratings[0].sum()
p_j = ratings.sum(axis=0) / (n * N)
P_i = ((np.sum(ratings ** 2, axis=1) - N) / (N * (N - 1)))
P_bar = P_i.mean()
P_e = (p_j ** 2).sum()
return (P_bar - P_e) / (1 - P_e)
# 7 metin, 3 kategoriye (POS/NEG/NEU) 3 annotator'ın oyları
ratings = np.array([
[2, 1, 0], [3, 0, 0], [0, 0, 3],
[2, 1, 0], [0, 2, 1], [3, 0, 0], [1, 1, 1],
])
print(f"Fleiss' Kappa: {fleiss_kappa(ratings):.4f}")
# ── Yorumlama ───────────────────────────────────────────────────────
def interpret_kappa(k: float) -> str:
if k > 0.8: return "Mükemmel anlaşma"
if k > 0.6: return "İyi anlaşma"
if k > 0.4: return "Orta anlaşma"
if k > 0.2: return "Zayıf anlaşma"
return "Anlaşma yok — guideline gözden geçirin"
05 Weak Supervision — Snorkel
Programatik etiketleme: kural tabanlı, uzak denetim ve diğer heuristikleri birleştirerek gürültülü ama bol etiket üret.
Snorkel, birden fazla zayıf denetim kaynağını (Labeling Function) birleştirerek her örnek için olasılıksal etiket üretir. Her LF bazı örnekleri etiketler, bazılarını atlar (abstain). Çakışma ve anlaşmazlıklar Label Model ile çözülür.
import re
from snorkel.labeling import labeling_function, LabelingFunction, PandasLFApplier
from snorkel.labeling.model import LabelModel
import pandas as pd
# Etiket sabitleri
ABSTAIN = -1
NEGATIF = 0
POZITIF = 1
# ── Kural tabanlı LF'ler ─────────────────────────────────────────────
@labeling_function()
def lf_positive_keywords(x):
pos_words = ["harika", "mükemmel", "süper", "çok iyi", "beğendim", "tavsiye"]
text = x.text.lower()
return POZITIF if any(w in text for w in pos_words) else ABSTAIN
@labeling_function()
def lf_negative_keywords(x):
neg_words = ["berbat", "rezalet", "kötü", "çöp", "hayal kırıklığı", "tavsiye etmem"]
text = x.text.lower()
return NEGATIF if any(w in text for w in neg_words) else ABSTAIN
@labeling_function()
def lf_rating_high(x):
# "5/5", "5 yıldız", "5 puan" gibi ifadeler
if re.search(r'[45]\s*[/⭐★]\s*5', x.text):
return POZITIF
return ABSTAIN
@labeling_function()
def lf_rating_low(x):
if re.search(r'[12]\s*[/⭐★]\s*5', x.text):
return NEGATIF
return ABSTAIN
@labeling_function()
def lf_negation(x):
# "beğenmedim", "iyi değil" gibi olumsuzlama
if re.search(r'(beğenmedim|iyi değil|çalışmıyor|gelmedi)', x.text.lower()):
return NEGATIF
return ABSTAIN
@labeling_function()
def lf_emoji_positive(x):
pos_emojis = ["😍", "🥰", "👍", "❤️", "✨", "🔥"]
return POZITIF if any(e in x.text for e in pos_emojis) else ABSTAIN
@labeling_function()
def lf_emoji_negative(x):
neg_emojis = ["😡", "👎", "💩", "😤", "🤮"]
return NEGATIF if any(e in x.text for e in neg_emojis) else ABSTAIN
06 Majority Vote & Label Model
LF çıktılarını birleştirmenin iki yolu: basit çoğunluk oyu ve Snorkel'in olasılıksal Label Model'i.
from snorkel.labeling import PandasLFApplier, LFAnalysis
from snorkel.labeling.model import LabelModel, MajorityLabelVoter
import pandas as pd
# ── Dataset ──────────────────────────────────────────────────────────
df = pd.read_csv("reviews.csv") # 'text' kolonu
df_train = df[df["split"] == "train"]
df_valid = df[df["split"] == "valid"] # 200 insan etiketli örnek
# ── LF'leri uygula ───────────────────────────────────────────────────
lfs = [
lf_positive_keywords, lf_negative_keywords,
lf_rating_high, lf_rating_low,
lf_negation, lf_emoji_positive, lf_emoji_negative,
]
applier = PandasLFApplier(lfs=lfs)
L_train = applier.apply(df_train)
L_valid = applier.apply(df_valid)
# ── LF analizi ───────────────────────────────────────────────────────
print(LFAnalysis(L=L_train, lfs=lfs).lf_summary())
# Coverage, Conflicts, Overlaps, Accuracy gösterir
# ── Majority Vote (baseline) ─────────────────────────────────────────
mv = MajorityLabelVoter(cardinality=2)
mv_labels = mv.predict(L_valid)
print(f"Majority Vote doğruluk: {(mv_labels == df_valid['label'].values).mean():.3f}")
# ── Label Model (daha iyi) ───────────────────────────────────────────
label_model = LabelModel(cardinality=2, verbose=True)
label_model.fit(L_train=L_train, n_epochs=500, lr=0.01, seed=42)
# Validation accuracy
lm_acc = label_model.score(L=L_valid, Y=df_valid["label"].values, metric="accuracy")
print(f"Label Model doğruluk: {lm_acc:.3f}")
# ── Olasılıksal etiket üret ──────────────────────────────────────────
probs = label_model.predict_proba(L=L_train) # (n_samples, 2)
labels = label_model.predict(L=L_train)
df_train = df_train.copy()
df_train["weak_label"] = labels
df_train["label_prob_pos"] = probs[:, 1]
# Düşük güven örnekleri filtrele (active learning için kuyruğa al)
uncertain = df_train[(df_train["label_prob_pos"] > 0.3) & (df_train["label_prob_pos"] < 0.7)]
print(f"Belirsiz {len(uncertain)} örnek insan incelemesine gönderilecek.")
07 Active Learning Döngüsü
Model belirsizliğini rehber olarak kullanarak en değerli örnekleri insan annotasyonuna gönder — minimum maliyet, maksimum kazanç.
01 Seed Set → 100–500 insan etiketli örnekle modeli başlat 02 Tahmin → Etiketlenmemiş veri üzerinde model olasılıkları hesapla 03 Seçim Stratejisi → En belirsiz örnekleri seç (uncertainty sampling) 04 Annotasyon → Label Studio'ya gönder, insan etiketlesin 05 Yeniden Eğit → Yeni etiketlerle modeli güncelle 06 Değerlendir → Validasyon setinde performansı ölç 07 Tekrar → Yeterli performansa veya bütçe bitene dek tekrarla
import numpy as np
from sklearn.linear_model import LogisticRegression
from sklearn.feature_extraction.text import TfidfVectorizer
def uncertainty_sampling(model, X_pool: np.ndarray, n: int = 50) -> np.ndarray:
"""En belirsiz n örneğin indeksini döndür (least confidence)."""
probs = model.predict_proba(X_pool)
# En yüksek sınıf olasılığını al — düşük = belirsiz
confidence = probs.max(axis=1)
return np.argsort(confidence)[:n] # en düşük confidence'lar
def entropy_sampling(model, X_pool: np.ndarray, n: int = 50) -> np.ndarray:
"""Entropi bazlı seçim — çok sınıflı için daha iyi."""
probs = model.predict_proba(X_pool)
probs = np.clip(probs, 1e-10, 1)
entropy = -(probs * np.log(probs)).sum(axis=1)
return np.argsort(entropy)[-n:] # en yüksek entropi
# ── Döngü ────────────────────────────────────────────────────────────
vectorizer = TfidfVectorizer(max_features=5000)
model = LogisticRegression(max_iter=1000)
labeled_indices = list(range(200)) # başlangıç seed set
unlabeled_indices = list(range(200, 10000))
for iteration in range(10):
X_lab = vectorizer.fit_transform([texts[i] for i in labeled_indices])
y_lab = [labels[i] for i in labeled_indices]
model.fit(X_lab, y_lab)
X_pool = vectorizer.transform([texts[i] for i in unlabeled_indices])
query_indices = uncertainty_sampling(model, X_pool, n=50)
real_indices = [unlabeled_indices[i] for i in query_indices]
# Label Studio'ya gönder (otomatik)
send_to_label_studio(real_indices) # API çağrısı
new_labels = wait_for_annotations(real_indices) # webhook bekle
labeled_indices.extend(real_indices)
unlabeled_indices = [i for i in unlabeled_indices if i not in real_indices]
print(f"İterasyon {iteration+1}: {len(labeled_indices)} etiketli örnek")
08 Semi-Supervised Learning ve Practical
Az etiketli veriyle eğitilen modeli etiketlenmemiş veriye uygulayarak pseudo-label üret; bu pseudo-label'ları yeniden eğitimde kullan.
import numpy as np
from sklearn.semi_supervised import LabelPropagation, LabelSpreading
from transformers import pipeline
# ── Pseudo labeling — confidence threshold ──────────────────────────
def pseudo_label(model, X_unlabeled, threshold=0.9):
probs = model.predict_proba(X_unlabeled)
max_probs = probs.max(axis=1)
high_conf = max_probs >= threshold
pseudo_labels = model.predict(X_unlabeled)
# Yalnızca yüksek güvenli pseudo etiketleri kabul et
return X_unlabeled[high_conf], pseudo_labels[high_conf]
# ── Label Propagation (grafik tabanlı) ──────────────────────────────
# -1 = etiketlenmemiş
y_mixed = np.concatenate([y_labeled, -1 * np.ones(n_unlabeled, dtype=int)])
X_mixed = np.concatenate([X_labeled, X_unlabeled])
lp = LabelSpreading(kernel="knn", n_neighbors=7, alpha=0.2, max_iter=30)
lp.fit(X_mixed, y_mixed)
all_labels = lp.transduction_
# ── Transformers ile zero-shot pseudo labeling ──────────────────────
classifier = pipeline(
"zero-shot-classification",
model="facebook/bart-large-mnli",
)
candidate_labels = ["pozitif", "negatif", "nötr"]
text = "Bu ürün beklentilerimi tamamen karşıladı."
result = classifier(text, candidate_labels)
print(result["labels"][0]) # En yüksek skorlu etiket