推薦の評価指標
「モデルを作っただけでは意味がない。推薦の良し悪しをどう測るかが重要だ。」
田中VPoEが評価ダッシュボードを開く。
「推薦システムの評価は通常の分類・回帰とは異なる。ランキングの質を測る指標を理解しよう。」
オフライン評価指標の全体像
| 指標 | 何を測るか | 値域 | 高いほど良い |
|---|---|---|---|
| Precision@K | 上位K件中の適合率 | 0〜1 | Yes |
| Recall@K | 全正解中のK件以内での再現率 | 0〜1 | Yes |
| NDCG@K | 順位を考慮した適合度 | 0〜1 | Yes |
| MAP@K | 平均適合率の平均 | 0〜1 | Yes |
| Hit Rate@K | 1件でも正解を含むユーザーの割合 | 0〜1 | Yes |
| Coverage | 推薦できるアイテムのカバー率 | 0〜1 | Yes |
Precision@K と Recall@K
import numpy as np
def precision_at_k(recommended, relevant, k):
"""上位K件の適合率"""
rec_k = recommended[:k]
hits = len(set(rec_k) & set(relevant))
return hits / k
def recall_at_k(recommended, relevant, k):
"""上位K件の再現率"""
rec_k = recommended[:k]
hits = len(set(rec_k) & set(relevant))
return hits / len(relevant) if relevant else 0
# 例: 推薦10件、正解5件
recommended = ['A', 'B', 'C', 'D', 'E', 'F', 'G', 'H', 'I', 'J']
relevant = ['A', 'C', 'F', 'K', 'L']
print(f"Precision@5: {precision_at_k(recommended, relevant, 5)}") # 2/5 = 0.4
print(f"Recall@5: {recall_at_k(recommended, relevant, 5)}") # 2/5 = 0.4
print(f"Precision@10: {precision_at_k(recommended, relevant, 10)}") # 3/10 = 0.3
print(f"Recall@10: {recall_at_k(recommended, relevant, 10)}") # 3/5 = 0.6
NDCG(Normalized Discounted Cumulative Gain)
def dcg_at_k(relevance_scores, k):
"""DCG@K: 順位による割引付き累積利得"""
scores = relevance_scores[:k]
gains = np.array(scores)
discounts = np.log2(np.arange(2, len(gains) + 2))
return np.sum(gains / discounts)
def ndcg_at_k(recommended, relevant, k):
"""NDCG@K: 正規化DCG"""
# 実際の順位での関連度スコア
relevance = [1 if item in relevant else 0 for item in recommended[:k]]
actual_dcg = dcg_at_k(relevance, k)
# 理想的な順位での関連度スコア
ideal_relevance = sorted(relevance, reverse=True)
ideal_dcg = dcg_at_k(ideal_relevance, k)
return actual_dcg / ideal_dcg if ideal_dcg > 0 else 0
# NDCG は上位に正解があるほど高スコア
print(f"NDCG@10: {ndcg_at_k(recommended, relevant, 10):.4f}")
MAP(Mean Average Precision)
def average_precision(recommended, relevant, k):
"""Average Precision@K"""
hits = 0
sum_precision = 0
for i, item in enumerate(recommended[:k]):
if item in relevant:
hits += 1
sum_precision += hits / (i + 1)
return sum_precision / min(len(relevant), k) if relevant else 0
def mean_average_precision(all_recommended, all_relevant, k):
"""MAP@K: 全ユーザーのAPの平均"""
aps = [
average_precision(rec, rel, k)
for rec, rel in zip(all_recommended, all_relevant)
]
return np.mean(aps)
ビジネス指標との対応
| オフライン指標 | ビジネス指標 | 関係 |
|---|---|---|
| NDCG@10 | CTR(クリック率) | NDCG↑ → CTR↑ |
| Recall@20 | 購入率 | Recall↑ → 購入機会↑ |
| Coverage | カタログ活用率 | Coverage↑ → ロングテール販売↑ |
| Diversity | ユーザー満足度 | 多様性↑ → 発見体験↑ |
まとめ
| 項目 | ポイント |
|---|---|
| ランキング指標 | NDCG/MAPで順位の質を評価 |
| カバレッジ | Precision/Recallで網羅性を評価 |
| ビジネス連動 | オフライン指標をCTR/CVRに対応づける |
| 複合評価 | 単一指標ではなく複数指標で多角的に評価 |
チェックリスト
- Precision@KとRecall@Kの違いを説明できる
- NDCGが順位をどう考慮するか理解した
- MAPの計算手順を説明できる
- オフライン指標とビジネス指標の対応を理解した
次のステップへ
推薦の評価指標を理解した。次は演習で推薦アルゴリズムを実装・評価しよう。
推定読了時間: 30分