İçeriğe geç

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
Beyond-Accuracy: Coverage, Diversity (ILS), Novelty, Serendipity ve Popularity Bias Ölçümü
🎨 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 metrikleri
import numpy as np
from 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)
 
 
# Test
import random
random.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 listesi
flat = [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 çok
biased_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
sim(i, j)
item embeddinglerinin cosine similarity'si veya genre overlap gibi domain similarity.

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 np
from 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 = 100
embeddings = 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 ILS
similar_list = [0, 1, 2, 3, 4] # rastgele ama yakın olma garantisi yok
print(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) / total
— global olarak ne kadar popüler.
Item ç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 metrikleri
def 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
 
 
# Test
recommended = [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ı.

4) Serendipity — "Tatlı Şaşırtma"#

Tanım#

Hem yeni hem alakalı öneri. Kullanıcının beklemediği ama beğendiği şey.
Resmi tanım: relevance × unexpectedness.

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.
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:
StratejiNDCG@10CoverageILSNoveltyARP
Sadece popularity0.085%0.4düşükyüksek
k-NN CF0.1225%0.55ortaorta
Implicit ALS0.1635%0.50ortaorta
LightGCN0.1930%0.60düşük-ortaorta-yüksek
+ Diversification (MMR)0.1745%0.30yüksekdüşük
Tam random0.00599%0.05yüksekdüşü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