內容為本推薦系統 (Content-Based)

內容為本推薦的核心是:計算物品之間的相似度,然後推薦與用戶過去喜歡的物品最相似的物品。

相似度計算方法

在開始實作之前,我們需要了解兩種最常見的相似度計算方法:

1. 餘弦相似度 (Cosine Similarity)

餘弦相似度測量兩個向量之間的「角度」(方向是否一致),而不是距離。

$$\text{Cosine Similarity}(A, B) = \frac{A \cdot B}{||A|| \times ||B||}$$

  • 範圍:-1(完全相反)到 1(完全相同)
  • 0 代表不相關
  • 不受向量長度影響(適合文字或類別資料)

2. 皮爾森相關係數 (Pearson Correlation)

測量兩個變數之間的線性相關程度。

$$\rho = \frac{\text{cov}(X, Y)}{\sigma_X \sigma_Y}$$

  • 範圍:-1 到 1
  • 適合用來比較兩個使用者的評分模式

實作內容為本推薦

import pandas as pd
import numpy as np
from sklearn.metrics.pairwise import cosine_similarity
from sklearn.preprocessing import MultiLabelBinarizer

# === 1. 建立電影特徵向量(類型 + 電影 ID 作為特徵) ===

# 先確保我們有完整的電影資料
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')

# 解析類型
movies['genres_list'] = movies['genres'].str.split('|')

# One-Hot 編碼
mlb = MultiLabelBinarizer()
genre_matrix = mlb.fit_transform(movies['genres_list'])
genre_df = pd.DataFrame(genre_matrix, columns=mlb.classes_, index=movies['movieId'])

print(f"電影特徵向量維度: {genre_df.shape}")
print(genre_df.head())

計算電影相似度矩陣

# 計算所有電影之間的餘弦相似度
movie_similarity = cosine_similarity(genre_df)

# 轉換為 DataFrame
movie_similarity_df = pd.DataFrame(
    movie_similarity,
    index=genre_df.index,
    columns=genre_df.index
)

print(f"相似度矩陣大小: {movie_similarity_df.shape}")
print(movie_similarity_df.iloc[:5, :5])

根據電影 ID 推薦相似電影

def recommend_content_based(movie_id, n_recommendations=5):
    """根據電影 ID 推薦最相似的 N 部電影"""
    if movie_id not in movie_similarity_df.index:
        return []
    
    # 取得該電影與所有電影的相似度
    similarity_scores = movie_similarity_df[movie_id]
    
    # 排序(由高到低),排除自己
    similar_movies = similarity_scores.sort_values(ascending=False)
    similar_movies = similar_movies.drop(movie_id)
    
    # 取前 N 部
    top_movies = similar_movies.head(n_recommendations)
    
    # 回傳電影資訊
    results = []
    for mid, score in top_movies.items():
        movie_info = movies[movies['movieId'] == mid].iloc[0]
        results.append({
            'movieId': mid,
            'title': movie_info['title'],
            'genres': movie_info['genres'],
            'similarity_score': round(score, 4)
        })
    
    return results

# 測試:推薦與「Toy Story (1995)」相似的電影
# Toy Story 的 movieId = 1
toy_story_recs = recommend_content_based(1, n_recommendations=10)

print("=== 與「Toy Story」相似的電影 ===")
for rec in toy_story_recs:
    print(f"{rec['title']:50s} 相似度: {rec['similarity_score']:.4f}  類型: {rec['genres']}")

為特定用戶做內容為本推薦

def recommend_for_user_content_based(user_id, n_recommendations=10):
    """根據用戶評分過的電影,為用戶推薦最相似的未看過電影"""
    # 取得用戶評分過的電影
    user_ratings = ratings[ratings['userId'] == user_id]
    rated_movie_ids = user_ratings['movieId'].tolist()
    
    if len(rated_movie_ids) == 0:
        return []
    
    # 找出用戶評分最高的幾部電影(作為「喜歡」的代表)
    favorite_movies = user_ratings.sort_values('rating', ascending=False)
    top_rated = favorite_movies.head(5)
    
    print(f"用戶 {user_id} 最喜歡的電影:")
    for _, row in top_rated.iterrows():
        movie_info = movies[movies['movieId'] == row['movieId']].iloc[0]
        print(f"  {movie_info['title']} (評分: {row['rating']})")
    
    # 為每一部喜歡的電影找相似電影
    candidate_scores = {}
    for _, row in top_rated.iterrows():
        movie_id = row['movieId']
        weight = row['rating'] / 5.0  # 高分電影權重更高
        
        if movie_id in movie_similarity_df.index:
            similar = movie_similarity_df[movie_id].sort_values(ascending=False)
            similar = similar.drop(movie_id)
            
            for similar_id, score in similar.items():
                if similar_id not in rated_movie_ids:  # 排除已看過的
                    candidate_scores[similar_id] = candidate_scores.get(similar_id, 0) + score * weight
    
    # 排序取推薦
    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
recommendations = recommend_for_user_content_based(user_id)

print("\n=== 內容為本推薦結果 ===")
for i, rec in enumerate(recommendations, 1):
    print(f"{i}. {rec['title']:50s} 分數: {rec['score']:.4f}  類型: {rec['genres']}")

使用 Vibe Coding 做內容推薦

🔥 【內容推薦詠唱範例】 「我有一個 products.csv 包含商品 ID、名稱、類別、價格區間、品牌。 請幫我: 1. 將類別與品牌進行 One-Hot 編碼,價格進行標準化。 2. 使用餘弦相似度計算所有商品之間的相似度。 3. 寫一個函式 recommend_similar(product_id, n=5):回傳最相似的 N 個商品。 4. 將相似度矩陣儲存為 similarity.pkl 方便之後載入。 5. 寫一個 FastAPI 端點 GET /similar/{product_id} 回傳推薦結果。」

本日總結

在本章中,你學到了:

  1. 餘弦相似度:測量兩個特徵向量的相似程度
  2. 電影特徵向量:將類別資料轉換為機器可讀的向量
  3. 相似度矩陣:計算所有電影之間的相似度
  4. 內容為本推薦:根據用戶喜歡的電影,推薦相似的電影
  5. 權重調整:高分電影的推薦權重更高

下一章,我們將實作協同過濾推薦系統!

會員專屬免費教學

本章節為註冊會員專屬的免費開放內容!請先登入或註冊會員,即可立即解鎖閱讀。

立即登入 / 註冊