協同過濾:User-based 與 Item-based
協同過濾 (Collaborative Filtering) 的核心思想很簡單:物以類聚,人以群分。
- User-based CF:找到與你評分模式相似的使用者,推薦他們喜歡但你還沒看過的物品
- Item-based CF:找到與你喜歡的物品相似的物品(根據其他使用者的評分行為)
User-based 協同過濾
計算使用者相似度
import pandas as pd
import numpy as np
from sklearn.metrics.pairwise import cosine_similarity
# 載入資料
movies = pd.read_csv('https://files.grouplens.org/datasets/movielens/ml-latest-small/movies.csv')
ratings = pd.read_csv('https://files.grouplens.org/datasets/movielens/ml-latest-small/ratings.csv')
# 建立使用者-電影評分矩陣
user_movie_matrix = ratings.pivot(
index='userId',
columns='movieId',
values='rating'
)
# 填補空值為 0(表示沒看過)
user_movie_matrix_filled = user_movie_matrix.fillna(0)
# 計算使用者之間的餘弦相似度
user_similarity = cosine_similarity(user_movie_matrix_filled)
user_similarity_df = pd.DataFrame(
user_similarity,
index=user_movie_matrix.index,
columns=user_movie_matrix.index
)
print(f"使用者相似度矩陣大小: {user_similarity_df.shape}")
print(user_similarity_df.iloc[:5, :5])
User-based CF 推薦
def recommend_user_based(user_id, n_recommendations=10):
"""
為指定使用者推薦電影(User-based Collaborative Filtering)
流程:
1. 找到與目標使用者最相似的前 N 個使用者
2. 找出這些相似使用者喜歡的電影
3. 排除目標使用者已經看過的電影
4. 根據相似使用者的評分加權計算推薦分數
"""
if user_id not in user_similarity_df.index:
return []
# 找到最相似的使用者(排除自己)
similar_users = user_similarity_df[user_id].sort_values(ascending=False)
similar_users = similar_users.drop(user_id)
# 取前 20 個相似使用者
top_similar_users = similar_users.head(20)
print(f"與使用者 {user_id} 最相似的前 5 個使用者:")
for uid, score in top_similar_users.head(5).items():
print(f" 使用者 {uid}: 相似度 {score:.4f}")
# 取得目標使用者已看過的電影
watched_movies = ratings[ratings['userId'] == user_id]['movieId'].tolist()
# 計算推薦分數
candidate_scores = {}
for similar_uid, similarity_score in top_similar_users.items():
# 取得相似使用者的評分
similar_user_ratings = ratings[ratings['userId'] == similar_uid]
# 只看評分 >= 4 的電影(代表喜歡)
liked_movies = similar_user_ratings[
similar_user_ratings['rating'] >= 4
]
for _, row in liked_movies.iterrows():
movie_id = row['movieId']
# 排除目標使用者已看過的
if movie_id not in watched_movies:
# 加權分數 = 相似度 × 評分
score = similarity_score * row['rating']
candidate_scores[movie_id] = candidate_scores.get(movie_id, 0) + score
# 排列推薦
sorted_candidates = sorted(
candidate_scores.items(),
key=lambda x: x[1],
reverse=True
)
top_candidates = sorted_candidates[:n_recommendations]
results = []
for movie_id, score in top_candidates:
movie_info = movies[movies['movieId'] == movie_id].iloc[0]
results.append({
'title': movie_info['title'],
'genres': movie_info['genres'],
'score': round(score, 4)
})
return results
# 測試
user_id = 1
print(f"\n=== User-based CF 推薦結果(使用者 {user_id})===")
recommendations = recommend_user_based(user_id)
for i, rec in enumerate(recommendations, 1):
print(f"{i}. {rec['title']:50s} 分數: {rec['score']:.4f} 類型: {rec['genres']}")
Item-based 協同過濾
Item-based CF 是 Amazon 首創的推薦方法,也是實務上最常用的協同過濾變體。它的核心概念是:「買這個商品的人也買了⋯」
Item-based CF 的優勢
- ✅ 物品之間的相似度比使用者之間的相似度更穩定(物品不會隨著時間改變)
- ✅ 可以事先計算物品相似度矩陣,推薦時只需要查表,速度極快
- ✅ 適合物品數量遠少於使用者的場景
def recommend_item_based(user_id, n_recommendations=10):
"""
使用 Item-based CF 為使用者推薦
流程:
1. 找出使用者評分過的所有電影
2. 對每部評分過的電影,找出相似的電影
3. 根據評分高低加權計算推薦分數
"""
# 取得使用者評分過的電影
user_ratings = ratings[ratings['userId'] == user_id]
if len(user_ratings) == 0:
return []
watched_movies = user_ratings['movieId'].tolist()
# 計算推薦分數
candidate_scores = {}
for _, row in user_ratings.iterrows():
movie_id = row['movieId']
rating = row['rating']
if movie_id not in movie_similarity_df.index:
continue
# 找與這部電影相似的電影
similar_movies = movie_similarity_df[movie_id].sort_values(ascending=False)
similar_movies = similar_movies.drop(movie_id)
# 取前 10 部最相似的
top_similar = similar_movies.head(10)
for similar_id, similarity in top_similar.items():
if similar_id not in watched_movies:
# 加權分數 = 相似度 × 使用者評分
score = similarity * rating
candidate_scores[similar_id] = candidate_scores.get(similar_id, 0) + score
# 排序
sorted_candidates = sorted(
candidate_scores.items(),
key=lambda x: x[1],
reverse=True
)
top_candidates = sorted_candidates[:n_recommendations]
results = []
for movie_id, score in top_candidates:
movie_info = movies[movies['movieId'] == movie_id].iloc[0]
results.append({
'title': movie_info['title'],
'genres': movie_info['genres'],
'score': round(score, 4)
})
return results
# 測試
print(f"\n=== Item-based CF 推薦結果(使用者 {user_id})===")
recommendations = recommend_item_based(user_id)
for i, rec in enumerate(recommendations, 1):
print(f"{i}. {rec['title']:50s} 分數: {rec['score']:.4f} 類型: {rec['genres']}")
比較三種推薦方法
def compare_methods(user_id, n_recommendations=5):
"""比較三種推薦方法的結果"""
print(f"\n{'='*80}")
print(f"使用者 {user_id} 的推薦結果比較")
print(f"{'='*80}")
# Content-based
cb_results = recommend_for_user_content_based(user_id, n_recommendations)
print("\n📌 內容為本推薦:")
for i, rec in enumerate(cb_results, 1):
print(f" {i}. {rec['title']}")
# User-based CF
ub_results = recommend_user_based(user_id, n_recommendations)
print("\n👥 User-based 協同過濾:")
for i, rec in enumerate(ub_results, 1):
print(f" {i}. {rec['title']}")
# Item-based CF
ib_results = recommend_item_based(user_id, n_recommendations)
print("\n📦 Item-based 協同過濾:")
for i, rec in enumerate(ib_results, 1):
print(f" {i}. {rec['title']}")
compare_methods(1, 5)
使用 Vibe Coding 實作協同過濾
🔥 【協同過濾詠唱範例】
「請幫我實作一個協同過濾推薦系統:1. 從 ratings.csv 載入評分資料,建立使用者-物品矩陣。2. 使用皮爾森相關係數計算使用者相似度(不是餘弦相似度)。3. 實作 User-based CF:找到前 10 個最相似的使用者,加權推薦。4. 實作 Item-based CF:計算物品相似度矩陣並快取。5. 比較兩種方法對使用者 1 的推薦結果。6. 輸出每個推薦背後的解釋(為什麼推薦這個)。」
本日總結
在本章中,你學到了:
- ✅ User-based CF:找相似使用者,推薦他們喜歡的物品
- ✅ Item-based CF:找相似物品,推薦與已喜歡物品相似的物品
- ✅ 相似度計算:使用餘弦相似度衡量使用者/物品間的相似程度
- ✅ 加權推薦:根據相似度與評分進行加權計算
- ✅ 三種方法比較:理解不同方法的適用場景
下一章,我們將使用 Surprise 函式庫進行更專業的推薦系統評估!