Beyond-Accuracy: Coverage, Diversity (ILS), Novelty, Serendipity ve Popularity Bias Ölçümü
NDCG'si yüksek ama kullanıcı sıkılan recommender'ın sebebi: tek metrik 'doğruluk' ile ölçüldü. Coverage, intra-list similarity (ILS), novelty, serendipity ve gini coefficient ile sistemin tüm yüzlerini ölç.
Şükrü Yusuf KAYA
26 dakikalık okuma
İleri🎨 Bu dersin amacı
Doğruluk-only ölçüm = bir araba sadece hız ile ölçmek. Hız önemli, ama yakıt verimliliği, konfor, güvenlik de var. Recommender'da: NDCG önemli, ama coverage, diversity, novelty, serendipity de var. Bu derste bu 'gizli metrikleri' tanıtıyor, NumPy ile yazıyor ve gerçek bir trade-off matrisi kuruyoruz.
Filter Bubble Problemi#
Bir gerçek hikaye: Spotify mühendisleri Discover Weekly'yi 2015'te yayınladıkları zaman ilk versiyonda NDCG çok yüksekti. Ama kullanıcılar şikayet etti — "hep benzer şarkıları öneriyor, sıkıldım".
Sorun: model tam doğru öneriyor — kullanıcının beğeneceği şeyleri. Ama hep aynı tarz. Bu filter bubble — kullanıcının dünyası daralıyor.
Çözüm: doğruluk + diversity + novelty trade-off'unu bilinçli yönet.
Beyond-Accuracy Aile Ağacı#
Accuracy: "İyi içerik mi?" (NDCG, HR) │ ├─ Coverage: "Kataloğun yüzde kaçını kullanıyorum?" ├─ Diversity: "Liste içinde çeşitlilik var mı?" ├─ Novelty: "Yeni şeyler öneriyor muyum?" ├─ Serendipity: "Şaşırtıcı + iyi öneriyor muyum?" └─ Fairness: "Provider'lara eşit mi davranıyorum?"
1) Coverage — Katalog Kapsama#
Tanım#
Sistem tüm item kataloğunun yüzde kaçını önerdi?
Coverage@N = |⋃_u TopN(u)| / |I|
Tüm kullanıcılara verilen önerilerin union'unun boyutu / total item sayısı.
Tablolu Örnek#
Toplam item: 10,000 Tüm kullanıcılara top-10 önerildi. Önerilen unique item'lar (union): 850 Coverage@10 = 850 / 10,000 = 8.5%
Production'da %10-30 sağlıklı. %1 — sistem aynı 100 item'ı her kullanıcıya öneriyor (problem). %80+ — sistem tek bir kullanıcıyı bile iyi tanıyamıyor (rastgele).
İki Tip Coverage#
User Coverage
Sistemin kaç kullanıcıya öneri verebildiği — cold-start kullanıcılar dışlanıyor mu?
Item Coverage (yukarıda tanımladığımız)
Kataloğun yüzdesi.
python
# metrics/beyond_accuracy.py — Beyond-accuracy metrikleriimport numpy as npfrom collections import Counter def item_coverage( all_recommendations: list[list[int]], total_items: int,) -> float: """Item coverage@K — katalogun yüzde kaçı önerildi.""" unique_recommended = set() for recs in all_recommendations: unique_recommended.update(recs) return len(unique_recommended) / total_items def user_coverage( n_recommended_users: int, n_total_users: int,) -> float: """User coverage — yüzde kaç user'a öneri verildi.""" return n_recommended_users / n_total_users def gini_coefficient(items_recommended: list[int]) -> float: """ Gini coefficient — recommendation dağılımının eşitsizliği. 0 = mükemmel eşit (her item aynı sayıda önerildi) 1 = mükemmel eşitsiz (tüm öneriler tek item'a) Yüksek gini = popularity bias. """ counts = np.array(sorted(Counter(items_recommended).values())) n = len(counts) if n == 0: return 0.0 cumcounts = np.cumsum(counts) total = cumcounts[-1] if total == 0: return 0.0 return (2 * np.sum((np.arange(1, n + 1)) * counts) - (n + 1) * total) / (n * total) # Testimport randomrandom.seed(42)all_recs = [ random.sample(range(1, 1001), 10) for _ in range(100)]print(f"Item coverage: {item_coverage(all_recs, total_items=1000):.4f}") # Toplam recommendation'lar listesiflat = [item for recs in all_recs for item in recs]print(f"Gini coefficient: {gini_coefficient(flat):.4f}") # Bias'lı dağılım — popüler item'lar daha çokbiased_flat = ( [1] * 100 + [2] * 80 + [3] * 60 + # popüler list(range(4, 1004)) * 1 # diğerleri 1 kez)print(f"Gini (biased): {gini_coefficient(biased_flat):.4f}") Coverage + Gini coefficient — popularity bias ölçümü.
2) Intra-List Similarity (ILS) — Diversity Ölçümü#
Tanım#
Bir tek listede item'lar birbirine ne kadar benzer? Yüksek similarity = düşük diversity.
ILS(L) = (1 / (|L|(|L|-1))) Σ_{i ∈ L} Σ_{j ∈ L, j ≠ i} sim(i, j)
Burada item embeddinglerinin cosine similarity'si veya genre overlap gibi domain similarity.
sim(i, j)Tablolu Örnek#
Liste 1: [Inception, Memento, Interstellar, The Prestige, Tenet] (hepsi Nolan filmi, hepsi sci-fi/mystery) ILS yüksek (~0.85) → diversity düşük Liste 2: [Inception, Toy Story, The Notebook, Saw, Casablanca] (her biri farklı türde) ILS düşük (~0.15) → diversity yüksek
Diversity = 1 - ILS#
Yüksek-diversity istiyorsan ILS'i minimize etmen lazım.
python
# Intra-List Similarity (ILS)import numpy as npfrom typing import Callable def intra_list_similarity( recommended: list[int], similarity_fn: Callable[[int, int], float],) -> float: """ ILS — listedeki item'ların ortalama pairwise similarity'si. similarity_fn: (item_a, item_b) -> float in [0, 1] """ if len(recommended) < 2: return 0.0 n = len(recommended) total_sim = 0.0 count = 0 for i in range(n): for j in range(i + 1, n): total_sim += similarity_fn(recommended[i], recommended[j]) count += 1 return total_sim / count if count > 0 else 0.0 def diversity_score( recommended: list[int], similarity_fn: Callable[[int, int], float],) -> float: """Diversity = 1 - ILS.""" return 1.0 - intra_list_similarity(recommended, similarity_fn) # Test (cosine similarity ile item embedding'leri varsayarak)np.random.seed(42)n_items = 100embeddings = np.random.randn(n_items, 32)embeddings /= np.linalg.norm(embeddings, axis=1, keepdims=True) # normalize def cosine_sim(a: int, b: int) -> float: return float(np.dot(embeddings[a], embeddings[b])) # Benzer item'lardan oluşan liste — high ILSsimilar_list = [0, 1, 2, 3, 4] # rastgele ama yakın olma garantisi yokprint(f"Random list ILS: {intra_list_similarity(similar_list, cosine_sim):.4f}")print(f"Random list div: {diversity_score(similar_list, cosine_sim):.4f}") ILS — diversity ölçümü.
3) Novelty — Yenilik Ölçümü#
Tanım#
Önerilen item'lar kullanıcı için yeni mi? Genel popüler item'lar yeni değil. Long-tail item'lar yeni.
İki versiyon:
User-Level Novelty
Kullanıcının kişisel geçmişinde görmediği item'lar.
NoveltyUser@K(u) = |TopK_u \ History_u| / K
Self-Information Novelty
Item'ın popülerliğinin tersi — az popüler = yüksek novelty.
P(i) = freq(i) / totalItem çok popüler (P=0.5) → -log₂(0.5) = 1.0 (düşük novelty) Item orta popüler (P=0.01) → -log₂(0.01) = 6.6 (orta novelty) Item nadir (P=0.0001) → -log₂(0.0001) = 13.3 (yüksek novelty)
Bilgi teorisi yorumu: Surprise = -log₂(P). Düşük olasılıklı bir şey beklenmedik = yüksek bilgi içeriği.
python
# Novelty metrikleridef user_level_novelty( recommended: list[int], user_history: set[int], k: int,) -> float: """User'ın görmediği item oranı.""" top_k = recommended[:k] new_items = [item for item in top_k if item not in user_history] return len(new_items) / k def self_information_novelty( recommended: list[int], item_popularity: dict[int, int], total_interactions: int, k: int,) -> float: """Self-information (entropy) novelty.""" top_k = recommended[:k] total = 0.0 for item in top_k: pop = item_popularity.get(item, 1) prob = pop / total_interactions total += -np.log2(prob) return total / k # Testrecommended = [101, 102, 103, 104, 105]user_history = {101, 200, 300}item_pop = {101: 1000, 102: 50, 103: 10, 104: 5, 105: 2}total = 100_000 print(f"User-level novelty: {user_level_novelty(recommended, user_history, 5):.4f}")print(f"Self-information novelty: {self_information_novelty(recommended, item_pop, total, 5):.4f}") # User-level novelty: 0.8000 (101 history'de, 4/5 yeni)# Self-information novelty: yüksek (çoğu az popüler) Novelty — iki versiyonun karşılaştırması.
Unexpected nasıl tanımlanır?#
İki popüler yaklaşım:
1. Baseline'a göre — "popularity baseline'dan ne kadar uzak?"
unexpected(i, u) = 1 if i ∉ TopK_popularity_baseline
2. User'ın geçmiş profiline göre — "user'ın geçmişiyle benzer mi?"
unexpected(i, u) = 1 - max_{h ∈ History_u} sim(i, h)
Pratikte#
Serendipity ölçmek zor çünkü "beklenmedik + iyi" subjective. A/B test'te kullanıcıya direkt "bu öneri seni şaşırttı mı?" sormak yaygın değil. Genelde proxy: diversity + novelty + accuracy birlikte yüksek → serendipity yüksek.
5) Popularity Bias Ölçümü#
Daha önce (Modül 2.3) popularity bias'ı tanıttık. Burada ölçümünü veriyoruz.
Avg Recommended Item Popularity (ARP)#
ARP@K = (1/|U|) Σ_u (1/K) Σ_{i ∈ TopK_u} popularity(i)
Yüksek ARP = sistem popüler item'ları öneriyor = bias var.
Long-Tail Coverage#
Top %20 popüler item dışındaki item'ların oranı.
Long-Tail Coverage@K = |{i ∈ Recommended : i ∈ Tail}| / |Recommended|
Yüksek long-tail coverage = system "tail"i de kullanıyor = bias düşük.
Trade-Off Matrisi — Tüm Metriklerin İlişkisi#
Aynı recommender üzerinde 6 metric birlikte ölçüldüğünde tipik tablo:
| Strateji | NDCG@10 | Coverage | ILS | Novelty | ARP |
|---|---|---|---|---|---|
| Sadece popularity | 0.08 | 5% | 0.4 | düşük | yüksek |
| k-NN CF | 0.12 | 25% | 0.55 | orta | orta |
| Implicit ALS | 0.16 | 35% | 0.50 | orta | orta |
| LightGCN | 0.19 | 30% | 0.60 | düşük-orta | orta-yüksek |
| + Diversification (MMR) | 0.17 | 45% | 0.30 | yüksek | düşük |
| Tam random | 0.005 | 99% | 0.05 | yüksek | düşük |
Gözlem: Saf NDCG'ye optimize edersen popularity bias'a kayarsın. MMR (Modül 16) ile diversification eklediğinde NDCG biraz düşer, coverage ve diversity ciddi artar.
Pareto Frontier#
Hangi noktayı seçmek tasarım kararı:
- Genç startup: coverage öncelik (long-tail satıcı destek)
- Olgun e-ticaret: NDCG öncelik (CTR / revenue)
- News/social: novelty + accuracy birlikte
📖 Production örnek — Pinterest
Pinterest 2018'de PinSage yayınlandığında engagement +%40 — harika. Ama 2 yıl sonra (2020) iç metrikler 'pin diversity declining' alarm verdi. Çünkü PinSage çok 'doğru' önerdiği için kullanıcılar tek niş'e sıkışıyordu. Çözüm: re-ranking layer'da intentional diversification + 'fresh content' boost. Bu içsel hikaye sonradan WSDM 2022 paper'ında yayınlandı.
Sıradakİ Ders#
Bir sonraki derste (3.3) — veri bölme stratejileri. Random vs time vs user split. Yanlış split, en parlak model bile bir hayal kırıklığı haline getirebilir. Bu ders Modül 6, 7, 10'da elimizdeki dataset'leri doğru bölmek için zorunlu.
Sık Sorulan Sorular
Üç yol: (1) Content similarity — genre overlap, kategori benzerliği, TF-IDF cosine. Hızlı, ilk yaklaşım. (2) Embedding cosine — modelin item embedding'inden cosine similarity. Modelin kalitesine bağlı. (3) Co-occurrence — aynı kullanıcı tarafından beğenilen item çiftlerinin Jaccard'ı. Davranışsal, güçlü. Production'da genelde (1) veya (3).
Yorumlar & Soru-Cevap
(0)Yorum yazmak için giriş yap.
Yorumlar yükleniyor...
İlgili İçerikler
Modül 0: Kurs Çerçevesi ve Atölye Kurulumu
Öneri Sistemleri Neden Bu Kadar Önemli? Bir Disiplinin Doğuşu, Bugünü ve Yarını
Öğrenmeye BaşlaModül 0: Kurs Çerçevesi ve Atölye Kurulumu
Recommender Engineer Kimdir? Yetkinlik Atlası ve Junior → Staff Kariyer Haritası
Öğrenmeye BaşlaModül 0: Kurs Çerçevesi ve Atölye Kurulumu