推薦系統評估與品質分析

推薦系統跟一般機器學習模型最大的不同在於:評估標準不只是「準不準」,還要考慮「多樣性」、「新穎性」與「覆蓋率」。

一個只推薦最受歡迎電影的系統,準確率可能很高,但使用者很快就會覺得無聊。

離線評估指標

1. Precision@K 與 Recall@K

Precision@K 是推薦系統最常用的評估指標:

def precision_at_k(recommended_items, relevant_items, k):
    """
    計算 Precision@K
    
    recommended_items: 推薦的物品 ID 列表(已排序)
    relevant_items: 使用者真正喜歡的物品 ID 集合
    k: 只看前 K 個推薦
    """
    top_k = recommended_items[:k]
    hits = len(set(top_k) & relevant_items)
    return hits / k if k > 0 else 0

def recall_at_k(recommended_items, relevant_items, k):
    """計算 Recall@K"""
    top_k = recommended_items[:k]
    hits = len(set(top_k) & relevant_items)
    return hits / len(relevant_items) if len(relevant_items) > 0 else 0

def evaluate_recommendations(user_id, model, n_recommendations=20):
    """評估對使用者的推薦品質"""
    # 取得使用者在測試集中的真實評分(假設評分 >= 4 為喜歡)
    user_test = test_df[test_df['userId'] == user_id]
    relevant_movies = set(
        user_test[user_test['rating'] >= 4]['movieId'].tolist()
    )
    
    if len(relevant_movies) == 0:
        return None
    
    # 取得推薦列表(假設使用混合推薦)
    recommendations = hybrid_recommend(user_id, n_recommendations)
    recommended_ids = []
    for rec in recommendations:
        movie_info = movies[movies['title'] == rec['title']]
        if len(movie_info) > 0:
            recommended_ids.append(movie_info.iloc[0]['movieId'])
    
    # 計算各種 K 值的 Precision 與 Recall
    ks = [1, 3, 5, 10, 20]
    results = []
    for k in ks:
        p = precision_at_k(recommended_ids, relevant_movies, k)
        r = recall_at_k(recommended_ids, relevant_movies, k)
        results.append({'K': k, 'Precision': p, 'Recall': r})
    
    return pd.DataFrame(results)

# 評估多個使用者
print("=== 推薦系統評估 ===")
all_results = []
for user_id in range(1, 20):
    result = evaluate_recommendations(user_id)
    if result is not None:
        result['UserId'] = user_id
        all_results.append(result)

if all_results:
    final_results = pd.concat(all_results)
    avg_results = final_results.groupby('K')[['Precision', 'Recall']].mean()
    print("\n平均評估結果:")
    print(avg_results.to_string())

2. 多樣性 (Diversity)

推薦結果多樣性評估——推薦的電影是否都屬於同一類型?

def diversity_score(recommended_movies):
    """計算推薦結果的類型多樣性"""
    genres_set = set()
    for movie in recommended_movies:
        for genre in movie['genres'].split('|'):
            genres_set.add(genre)
    
    # 多樣性 = 涵蓋的類型數量 / 總類型數量
    total_genres = len(movies['genres'].str.split('|').explode().unique())
    return len(genres_set) / total_genres

# 比較不同方法的多樣性
recommendations_cb = recommend_for_user_content_based(1, 20)
recommendations_hybrid = hybrid_recommend(1, 20)

print(f"內容為本多樣性: {diversity_score(recommendations_cb):.2%}")
print(f"混合推薦多樣性: {diversity_score(recommendations_hybrid):.2%}")

3. 新穎性 (Novelty)

推薦結果是否包含冷門電影?一直推薦熱門電影使用者很快就膩了。

def novelty_score(recommended_movies, popularity_threshold=0.2):
    """
    計算推薦結果的新穎性
    新穎性 = 推薦結果中非熱門電影的比例
    """
    # 計算每個電影的評分次數
    movie_counts = ratings.groupby('movieId').size()
    max_count = movie_counts.max()
    
    novel_count = 0
    for movie in recommended_movies:
        movie_id = movies[movies['title'] == movie['title']]['movieId'].iloc[0]
        popularity = movie_counts.get(movie_id, 0) / max_count
        if popularity < popularity_threshold:  # 低於熱門閾值
            novel_count += 1
    
    return novel_count / len(recommended_movies)

print(f"內容為本新穎性: {novelty_score(recommendations_cb):.2%}")
print(f"混合推薦新穎性: {novelty_score(recommendations_hybrid):.2%}")

4. 覆蓋率 (Coverage)

推薦系統總共能推薦多少比例的物品?如果只能推薦一小部分物品,覆蓋率就很低。

def coverage_score(all_users, n_recommendations=10):
    """計算系統的覆蓋率"""
    all_recommended = set()
    
    for user_id in all_users[:50]:  # 取前 50 個使用者
        recommendations = hybrid_recommend(user_id, n_recommendations)
        for rec in recommendations:
            movie_id = movies[movies['title'] == rec['title']]['movieId'].iloc[0]
            all_recommended.add(movie_id)
    
    coverage = len(all_recommended) / len(movies)
    return coverage

# 注意:這需要一些時間執行
# print(f"推薦系統覆蓋率: {coverage_score(range(1, 51)):.2%}")

建立評估儀表板

import matplotlib.pyplot as plt
import seaborn as sns

# 綜合比較三種方法
methods = ['Content-Based', 'User-Based CF', 'Hybrid']

metrics = {
    'Precision@5': [],
    'Recall@5': [],
    'Diversity': [],
    'Novelty': []
}

for method_func in [
    lambda uid: recommend_for_user_content_based(uid, 20),
    lambda uid: recommend_user_based(uid, 20),
    lambda uid: hybrid_recommend(uid, 20)
]:
    recs = method_func(1)
    metrics['Precision@5'].append(
        precision_at_k(
            [movies[movies['title'] == r['title']]['movieId'].iloc[0] for r in recs[:5]],
            set(ratings[(ratings['userId'] == 1) & (ratings['rating'] >= 4)]['movieId']),
            5
        )
    )
    metrics['Diversity'].append(diversity_score(recs))
    metrics['Novelty'].append(novelty_score(recs))

# 畫圖
fig, axes = plt.subplots(1, 3, figsize=(15, 5))

for i, metric in enumerate(['Precision@5', 'Diversity', 'Novelty']):
    axes[i].bar(methods, metrics[metric])
    axes[i].set_title(metric)
    axes[i].set_ylim(0, 1)
    for j, v in enumerate(metrics[metric]):
        axes[i].text(j, v + 0.02, f'{v:.2%}', ha='center')

plt.tight_layout()
plt.show()

線上評估:A/B 測試架構

離線評估只能告訴你「模型在歷史資料上表現如何」,但真正的考驗是「使用者在實際使用時的反映」。

# === A/B 測試設計 ===
# 情境:你同時部署了 Content-Based 和 Hybrid 兩種推薦方法
# 你要測試哪一種方法能帶來更高的點擊率 (CTR)

ab_test_config = {
    'experiment_name': '推薦演算法 A/B 測試',
    'variants': [
        {
            'name': '控制組 (Content-Based)',
            'algorithm': 'content_based',
            'traffic': 0.5  # 50% 流量
        },
        {
            'name': '實驗組 (Hybrid)',
            'algorithm': 'hybrid',
            'traffic': 0.5  # 50% 流量
        }
    ],
    'metrics': [
        'click_through_rate',      # 點擊率
        'conversion_rate',          # 轉換率
        'avg_session_duration',     # 平均停留時間
        'diversity_click_rate'      # 多樣化點擊率
    ],
    'minimum_sample_size': 1000,  # 最小樣本數
    'duration_days': 14           # 測試天數
}

print("=== A/B 測試設計 ===")
for variant in ab_test_config['variants']:
    print(f"{variant['name']}: {variant['traffic']*100:.0f}% 流量")
print(f"\n評估指標: {', '.join(ab_test_config['metrics'])}")
print(f"測試期間: {ab_test_config['duration_days']} 天")

使用 Vibe Coding 做評估

🔥 【推薦評估詠唱範例】 「請幫我對推薦系統進行全面評估: 1. 計算 Precision@1, @3, @5, @10。 2. 計算 Recall@5, @10。 3. 計算推薦結果的類型多樣性分數。 4. 計算新穎性分數(非熱門電影比例)。 5. 畫出三種方法(Content/CF/Hybrid)的比較雷達圖。 6. 輸出評估報告。」

本日總結

在本章中,你學到了:

  1. Precision@K / Recall@K:推薦品質的標準衡量方式
  2. 多樣性 (Diversity):推薦結果是否涵蓋多種類型
  3. 新穎性 (Novelty):能否推薦冷門但優質的物品
  4. 覆蓋率 (Coverage):系統總共能推薦多少物品
  5. A/B 測試設計:如何線上驗證推薦系統的實際效果

下一章,我們將把推薦系統包裝成完整的 API 服務!

解鎖完整教學內容

本章為付費內容。加入專案即可解鎖超過 5000 字的深度解析,包含 10 個以上神級 Prompt 與真實 Source Code 範例!