協同過濾: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. 輸出每個推薦背後的解釋(為什麼推薦這個)。」

本日總結

在本章中,你學到了:

  1. User-based CF:找相似使用者,推薦他們喜歡的物品
  2. Item-based CF:找相似物品,推薦與已喜歡物品相似的物品
  3. 相似度計算:使用餘弦相似度衡量使用者/物品間的相似程度
  4. 加權推薦:根據相似度與評分進行加權計算
  5. 三種方法比較:理解不同方法的適用場景

下一章,我們將使用 Surprise 函式庫進行更專業的推薦系統評估!

解鎖完整教學內容

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