推薦系統評估與品質分析
推薦系統跟一般機器學習模型最大的不同在於:評估標準不只是「準不準」,還要考慮「多樣性」、「新穎性」與「覆蓋率」。
一個只推薦最受歡迎電影的系統,準確率可能很高,但使用者很快就會覺得無聊。
離線評估指標
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. 輸出評估報告。」
本日總結
在本章中,你學到了:
- ✅ Precision@K / Recall@K:推薦品質的標準衡量方式
- ✅ 多樣性 (Diversity):推薦結果是否涵蓋多種類型
- ✅ 新穎性 (Novelty):能否推薦冷門但優質的物品
- ✅ 覆蓋率 (Coverage):系統總共能推薦多少物品
- ✅ A/B 測試設計:如何線上驗證推薦系統的實際效果
下一章,我們將把推薦系統包裝成完整的 API 服務!