Production Notes: Feature Drift, Multi-Modal Content, and Challenges of Turkish NLP
Module 4 closing: real problems you'll face after keeping a content-based recommender in production for 6 months. Feature distribution drift, multi-modal embeddings (image+text+audio) for cold-start power, CLIP/SBERT modern approaches, Turkish NLP specifics with stemming + BERTurk.
Şükrü Yusuf KAYA
24 min read
Advanced🚨 Bu dersin amacı
Modül 4'te bir content-based recommender kurdun. Şimdi hayal et — bunu 6 ay production'da tutuyorsun. Hangi sorunlarla karşılaşırsın? Feature drift (kategori taksonomisi değişti), multi-modal ihtiyacı (kullanıcılar fotoğrafa da bakıyor), Türkçe NLP spesifik dertleri. Bu derste somut çözümler.
1) Feature Drift — Sessiz Performans Düşüşü#
Sorun#
Modelinin bugün ürettiği öneri kalitesi, 6 ay önceki eğitim sırasındaki dağılımdan farklı bir dünyaya çıkar.
Ocak 2026: Model eğitildi - "Smartphone" kategorisi: 5,000 item - Genre dağılımı: %40 Drama, %25 Action, ... - Ortalama review uzunluğu: 120 kelime Temmuz 2026: 6 ay sonra - "Smartphone" kategorisi: 8,500 item (yeni model dalgaları) - "Smartphone" alt-kategorileri eklendi: "Foldable", "Gaming Phone" - Reviews kısaldı (TikTok kuşağı influence): ortalama 45 kelime Model bu değişiklikleri bilmiyor — eski feature distribution'da düşünüyor.
Üç Tip Drift#
a) Concept Drift
Feature-label ilişkisi değişiyor. "Action film" tanımı 2010'da farklı, 2026'da farklı.
b) Data Drift (Covariate Shift)
Feature'ların dağılımı değişiyor. Yeni ürün gözükçe genre dağılımı kayıyor.
c) Schema Drift
Feature'ların kendisi değişiyor. Yeni kategori eklendi, eski silindi, JSON format'ı değişti.
Tespit Yöntemleri#
Statistical Tests
from scipy.stats import ks_2samp, chi2_contingency # Numerik feature için Kolmogorov-Smirnov test stat, p_value = ks_2samp(reference_dist, current_dist) if p_value < 0.01: alert("Feature drift detected") # Kategorik feature için chi-square test contingency = pd.crosstab(reference_categories, current_categories) chi2, p_value, _, _ = chi2_contingency(contingency)
Population Stability Index (PSI)
PSI = Σ (current_pct - reference_pct) * log(current_pct / reference_pct) PSI < 0.1: stable PSI 0.1-0.2: minor drift, watch PSI > 0.2: major drift, retrain
Model-Based Detection
İki dataset'i ayırt etmek için classifier eğit. Eğer accuracy ≈ 0.5 → stable. Eğer accuracy >> 0.5 → drift var.
Müdahale#
- Automatic retraining cycle: Haftalık/aylık tam retrain.
- Online learning: Stream-based feature güncelleme.
- Schema versioning: Feature schema'sını versiyonla, transition planı kur.
python
# monitoring/drift_detector.py — Feature drift detectionimport numpy as npfrom scipy.stats import ks_2samp def psi( reference: np.ndarray, current: np.ndarray, n_bins: int = 10,) -> float: """ Population Stability Index — numerik feature drift ölçümü. Returns: PSI değeri < 0.1: stable 0.1-0.2: minor drift > 0.2: major drift """ # Reference distribution'ın bin'lerini kur bin_edges = np.percentile( reference, np.linspace(0, 100, n_bins + 1) ) # 0 division önlemek için, ilk ve son bin'i biraz genişlet bin_edges[0] = -np.inf bin_edges[-1] = np.inf ref_pct = np.histogram(reference, bin_edges)[0] / len(reference) cur_pct = np.histogram(current, bin_edges)[0] / len(current) # Sıfır division önle eps = 1e-6 ref_pct = np.clip(ref_pct, eps, 1.0) cur_pct = np.clip(cur_pct, eps, 1.0) psi_value = np.sum((cur_pct - ref_pct) * np.log(cur_pct / ref_pct)) return float(psi_value) def detect_categorical_drift( reference_categories: list[str], current_categories: list[str],) -> dict: """ Kategorik feature drift — yeni kategoriler / kayıp kategoriler. """ ref_set = set(reference_categories) cur_set = set(current_categories) new_categories = cur_set - ref_set lost_categories = ref_set - cur_set # Frequency drift from collections import Counter ref_freq = Counter(reference_categories) cur_freq = Counter(current_categories) common = ref_set & cur_set freq_changes = {} for cat in common: ref_pct = ref_freq[cat] / len(reference_categories) cur_pct = cur_freq[cat] / len(current_categories) change_pct = (cur_pct - ref_pct) / ref_pct if ref_pct > 0 else 0 if abs(change_pct) > 0.2: # %20+ change freq_changes[cat] = { "ref_pct": ref_pct, "cur_pct": cur_pct, "change_pct": change_pct, } return { "new_categories": list(new_categories), "lost_categories": list(lost_categories), "significant_freq_changes": freq_changes, } # Testnp.random.seed(42)ref = np.random.normal(100, 15, 10000)cur = np.random.normal(105, 18, 5000) # mean ve std driftprint(f"PSI: {psi(ref, cur):.4f}")# PSI: ~0.15 — minor-to-moderate drift Feature drift detection — PSI ve kategorik drift.
2) Multi-Modal Content — Görsel + Metin + Ses#
Niçin Multi-Modal?#
Geleneksel CB sadece metin kullanır. Modern dünyada:
- E-ticaret: ürün fotoğrafı + başlık + açıklama + kategori
- Müzik: şarkı ses dosyası + metadata
- Video: thumbnail + başlık + transcript + audio
- Mod: kıyafet fotoğrafı + kategori + renk + dokunma
Tek modaliteden daha zengin feature → daha güçlü recommender.
Modern Multi-Modal Encoders#
CLIP (OpenAI 2021)
"Contrastive Language-Image Pre-training" — text ve image'ı aynı embedding uzayında map eder.
import clip import torch from PIL import Image device = "cuda" if torch.cuda.is_available() else "cpu" model, preprocess = clip.load("ViT-B/32", device=device) # Image embedding image = preprocess(Image.open("product.jpg")).unsqueeze(0).to(device) with torch.no_grad(): image_embedding = model.encode_image(image) print(f"Image embedding: {image_embedding.shape}") # (1, 512) # Text embedding text = clip.tokenize(["a red dress with floral pattern"]).to(device) text_embedding = model.encode_text(text) # Similarity similarity = (image_embedding @ text_embedding.T).item()
Use case: Cold-start item için zero-shot embedding. Yeni ürünün sadece fotoğrafı var — CLIP ile anında embedding üret.
Sentence-BERT (SBERT)
Sentence-level metin embedding. Türkçe için: .
paraphrase-multilingual-MiniLM-L12-v2from sentence_transformers import SentenceTransformer model = SentenceTransformer("paraphrase-multilingual-MiniLM-L12-v2") embeddings = model.encode([ "Christopher Nolan'ın yeni filmi", "Yapay zeka konulu bilim kurgu", ]) print(embeddings.shape) # (2, 384)
Audio Embedding (Spotify-tarzı)
Custom CNN ya da OpenAI Whisper-tarzı pretrained audio encoder.
Multi-Modal Fusion Stratejileri#
- Concatenation (basit):
item_vec = [text_emb, image_emb, audio_emb] - Weighted Average (kalibrasyon ile):
item_vec = w_t·text + w_i·image - Cross-Attention (modern): Transformer ile farklı modaliteler etkileşim
- Late Fusion (production): Her modalite ayrı skor, son aşamada birleştir
python
# multimodal/fusion.py — Multi-modal item embeddingimport torchimport torch.nn as nn class MultiModalFusion(nn.Module): """ Concatenation + projection ile multi-modal fusion. """ def __init__( self, text_dim: int = 384, image_dim: int = 512, category_dim: int = 64, output_dim: int = 128, ): super().__init__() total_input = text_dim + image_dim + category_dim self.fusion = nn.Sequential( nn.Linear(total_input, 256), nn.ReLU(), nn.Dropout(0.2), nn.Linear(256, output_dim), ) def forward( self, text_emb: torch.Tensor, image_emb: torch.Tensor, category_emb: torch.Tensor, ) -> torch.Tensor: x = torch.cat([text_emb, image_emb, category_emb], dim=-1) return self.fusion(x) # Eğitim — pozitif/negatif pair'larla contrastive lossdef contrastive_loss( anchor: torch.Tensor, positive: torch.Tensor, negative: torch.Tensor, margin: float = 0.2,) -> torch.Tensor: """Triplet loss — positive yakın, negative uzak.""" pos_dist = (anchor - positive).pow(2).sum(dim=1) neg_dist = (anchor - negative).pow(2).sum(dim=1) loss = torch.relu(pos_dist - neg_dist + margin) return loss.mean() Multi-modal fusion — text + image + category birlikte projection.
3) Türkçe NLP — Agglutinative Dilin Zorlukları#
Türkçe agglutinative (eklemeli) bir dildir. Bir tek kökten muazzam sayıda kelime üretilebilir:
Kök: git- gitmek (to go) gittim (I went) gidiyorum (I am going) gideceğim (I will go) gidemedim (I couldn't go) gidemeyebileceğim (I might not be able to go)
Bu kelimelerin hepsi TF-IDF için farklı kelime. Yani vocabulary 5-10x şişer, ve aynı semantic kelime farklı bucket'larda kalır.
Çözüm Stratejileri#
a) Stemming (Kök Bulma)
Zemberek-NLP veya Snowball Turkish stemmer.
# pip install zemberek-python from zemberek import TurkishMorphology morphology = TurkishMorphology.create_with_defaults() word = "gidemeyebileceğim" analysis = morphology.analyze(word) for parse in analysis: print(parse.get_stem()) # "git"
b) Lemmatization
Daha akıllı — kelimeyi sözlük formuna indir.
# spaCy Turkish model import spacy nlp = spacy.load("tr_core_news_md") # 50MB model doc = nlp("Gideceğim yere ulaşamadım.") lemmas = [token.lemma_ for token in doc] # ["git", "yer", "ulaş", "..."]
c) Subword Tokenization
BPE (Byte-Pair Encoding) veya SentencePiece — kelimeleri sub-unit'lere böler.
"gidemeyebileceğim" → ["gid", "##e", "##me", "##ye", "##bil", "##e", "##ceğ", "##im"]
Avantaj: morfolojik bilgi korunur, vocabulary küçük, OOV yok.
d) Modern Yaklaşım: BERTurk
Türkçe için pretrained BERT modeli (Schweter 2020).
from transformers import AutoTokenizer, AutoModel tokenizer = AutoTokenizer.from_pretrained("dbmdz/bert-base-turkish-cased") model = AutoModel.from_pretrained("dbmdz/bert-base-turkish-cased") text = "Yapay zeka mühendisliği zorludur." tokens = tokenizer(text, return_tensors="pt") embeddings = model(**tokens).last_hidden_state # [CLS] token embedding → cümle representation sentence_emb = embeddings[0, 0] # 768-dim
Türkçe NLP Pratik Tavsiyesi#
| Use Case | Önerilen Yaklaşım |
|---|---|
| Hızlı baseline | Zemberek stem + TF-IDF |
| Orta kalite | spaCy lemma + TF-IDF |
| Production semantic | BERTurk (yavaş ama güçlü) |
| Search engine | BERTurk + BM25 hybrid |
| Real-time | Subword (BPE) + small embeddings |
🎉 Modül 4 Tamamlandı#
4 ders bitti:
- 4.1: CB vs CF felsefesi + hybrid pattern'ler
- 4.2: TF-IDF + BM25 + n-gram + kategorik encoding (NumPy)
- 4.3: MovieLens-100K üzerinde sıfırdan content-based recommender
- 4.4: Production gotcha'lar — drift, multi-modal, Türkçe NLP
İlk benchmark tablomuza ilk satırı ekledik: NDCG@10 = 0.089 (CB), %50 better than popularity baseline.
Bir sonraki modülde Modül 5 — Memory-Based Collaborative Filtering'e geçiyoruz. k-NN ile user-user ve item-item CF. Aynı dataset, NDCG@10'umuz 0.12-0.13'e çıkacak. ☕
Frequently Asked Questions
Endüstri konvansiyonu. Credit risk modeling'den geliyor (1990'lar). Recommender'da kullanırken biraz daha gevşek olabilirsin (örn. 0.15-0.25). Sınır otomatik retrain tetikleyecek seviye — false-positive tetikleme maliyetli.
Yorumlar & Soru-Cevap
(0)Yorum yazmak için giriş yap.
Yorumlar yükleniyor...
Related Content
Module 0: Course Framework & Workshop Setup
Why Do Recommender Systems Matter? Birth, Present, and Future of a Discipline
Start LearningModule 0: Course Framework & Workshop Setup
Who Is a Recommender Engineer? Skill Atlas and Junior → Staff Career Map
Start LearningModule 0: Course Framework & Workshop Setup