使用 Surprise 函式庫進行專業評估
到目前為止,我們手動實作了推薦系統。但手動實作有幾個問題:
- 難以進行標準化的評估
- 缺乏交叉驗證
- 沒有內建的超參數最佳化
Surprise 是一個專門為推薦系統設計的 Python 函式庫,它內建了多種協同過濾演算法與評估工具。
安裝 Surprise
pip install scikit-surprise
Surprise 的核心概念
Surprise 的 API 與 Scikit-Learn 非常相似:
- Dataset:載入評分資料
- Reader:定義評分範圍
- Trainset / Testset:訓練集與測試集
- 演算法:SVD、NMF、KNNBasic、KNNWithMeans 等
- Cross Validation:交叉驗證
載入資料到 Surprise
from surprise import Dataset, Reader
from surprise.model_selection import train_test_split
import pandas as pd
# 載入 MovieLens 資料
ratings = pd.read_csv('https://files.grouplens.org/datasets/movielens/ml-latest-small/ratings.csv')
# 定義 Surprise Reader(評分範圍 0.5-5.0)
reader = Reader(rating_scale=(0.5, 5.0))
# 載入資料
data = Dataset.load_from_df(
ratings[['userId', 'movieId', 'rating']],
reader
)
# 分割訓練/測試集
trainset, testset = train_test_split(data, test_size=0.2, random_state=42)
print(f"訓練集: {trainset.n_users} 位使用者, {trainset.n_items} 部電影, {trainset.n_ratings} 筆評分")
print(f"測試集: {len(testset)} 筆評分")
訓練 SVD 模型
SVD (奇異值分解) 是 Netflix Prize 冠軍使用的核心演算法,也是目前最流行的協同過濾方法。
from surprise import SVD
from surprise import accuracy
# 建立 SVD 模型
svd_model = SVD(
n_factors=100, # 隱含特徵數量
n_epochs=20, # 訓練迭代次數
lr_all=0.005, # 學習率
reg_all=0.02 # 正則化強度
)
# 訓練
svd_model.fit(trainset)
# 預測測試集
predictions = svd_model.test(testset)
# 評估
rmse = accuracy.rmse(predictions)
mae = accuracy.mae(predictions)
print(f"SVD 模型 RMSE: {rmse:.4f}")
print(f"SVD 模型 MAE: {mae:.4f}")
SVD 的直覺理解
SVD 的核心思想是:使用者和物品之間存在一些隱含的「因子」。
例如,電影的隱含因子可能是:
- 因子 1:動作場面 vs 文戲
- 因子 2:嚴肅 vs 輕鬆
- 因子 3:現代 vs 古典
- 因子 4:特效 vs 劇情
SVD 會自動從評分資料中「學習」出這些因子,以及每個使用者/電影對每個因子的偏好程度。
比較多種演算法
from surprise import SVD, NMF, KNNBasic, KNNWithMeans
from surprise.model_selection import cross_validate
# 定義要比較的演算法
algorithms = [
('SVD', SVD()),
('NMF', NMF()),
('KNN Basic', KNNBasic()),
('KNN With Means', KNNWithMeans()),
]
# 進行交叉驗證
results = []
for name, algo in algorithms:
cv_results = cross_validate(
algo, data,
measures=['RMSE', 'MAE'],
cv=5, # 5 折交叉驗證
verbose=False
)
avg_rmse = cv_results['test_rmse'].mean()
avg_mae = cv_results['test_mae'].mean()
fit_time = cv_results['fit_time'].mean()
results.append({
'演算法': name,
'RMSE': round(avg_rmse, 4),
'MAE': round(avg_mae, 4),
'訓練時間(s)': round(fit_time, 2)
})
print(f"{name:20s} RMSE: {avg_rmse:.4f} MAE: {avg_mae:.4f} 時間: {fit_time:.2f}s")
# 顯示比較表格
import pandas as pd
results_df = pd.DataFrame(results)
print("\n=== 演算法比較 ===")
print(results_df.to_string(index=False))
超參數最佳化(Grid Search)
from surprise.model_selection import GridSearchCV
# 定義參數網格
param_grid = {
'n_factors': [50, 100, 150],
'n_epochs': [20, 30],
'lr_all': [0.005, 0.01],
'reg_all': [0.02, 0.1]
}
# 執行 Grid Search
gs = GridSearchCV(
SVD,
param_grid,
measures=['rmse', 'mae'],
cv=3,
n_jobs=-1 # 使用所有 CPU 核心
)
gs.fit(data)
# 最佳參數
print("最佳參數 (RMSE):")
print(gs.best_params['rmse'])
print(f"最佳 RMSE: {gs.best_score['rmse']:.4f}")
print("\n最佳參數 (MAE):")
print(gs.best_params['mae'])
print(f"最佳 MAE: {gs.best_score['mae']:.4f}")
為使用者產生推薦
# 使用最佳參數重新訓練
best_params = gs.best_params['rmse']
best_svd = SVD(**best_params)
# 在完整資料集上訓練
full_trainset = data.build_full_trainset()
best_svd.fit(full_trainset)
# 為特定使用者推薦未看過的電影
def predict_for_user(user_id, n_recommendations=10):
"""為使用者預測所有未評分電影的評分,並推薦最高的 N 部"""
# 取得使用者已評分的電影
user_ratings = ratings[ratings['userId'] == user_id]
rated_movies = user_ratings['movieId'].tolist()
# 所有電影
all_movies = movies['movieId'].tolist()
# 未評分的電影
unrated_movies = [m for m in all_movies if m not in rated_movies]
# 預測每部未評分電影的評分
predictions = []
for movie_id in unrated_movies:
predicted_rating = best_svd.predict(user_id, movie_id).est
predictions.append((movie_id, predicted_rating))
# 排序取最高的 N 部
predictions.sort(key=lambda x: x[1], reverse=True)
top_predictions = predictions[:n_recommendations]
results = []
for movie_id, predicted_rating in top_predictions:
movie_info = movies[movies['movieId'] == movie_id].iloc[0]
results.append({
'title': movie_info['title'],
'genres': movie_info['genres'],
'predicted_rating': round(predicted_rating, 2)
})
return results
# 測試
print("\n=== SVD 推薦結果 ===")
recommendations = predict_for_user(1, 10)
for i, rec in enumerate(recommendations, 1):
print(f"{i}. {rec['title']:50s} 預測評分: {rec['predicted_rating']:.2f} 類型: {rec['genres']}")
使用 Vibe Coding 做 SVD 推薦
🔥 【SVD 推薦詠唱範例】
「請幫我使用 Surprise 函式庫建立電影推薦系統:1. 從 MovieLens 資料集載入評分資料。2. 比較 SVD、NMF、KNNWithMeans 三種演算法的 RMSE。3. 對 SVD 進行超參數最佳化(n_factors、n_epochs、lr_all、reg_all)。4. 用最佳模型為使用者 1 預測 10 部未看過電影的評分。5. 輸出推薦列表與預測評分。6. 計算 Precision@5 與 Recall@5。」
本日總結
在本章中,你學到了:
- ✅ Surprise 函式庫:專門為推薦系統設計的 Python 套件
- ✅ SVD 演算法:Netflix Prize 冠軍演算法,使用隱含因子
- ✅ 演算法比較:SVD、NMF、KNN 的效能對比
- ✅ Grid Search:自動尋找最佳超參數組合
- ✅ 產生推薦:用訓練好的模型為使用者預測評分
下一章,我們將學習如何解決推薦系統的冷啟動問題!