iT邦幫忙

2025 iThome 鐵人賽

DAY 6
1
AI & Data

從0開始的MLFLOW應用搭建系列 第 6

Day 06 – 建立 Item-based 推薦模型(TF-IDF)

  • 分享至 

  • xImage
  •  

🎯 背景與目標說明

昨天(Day 5),我們建立了 User-based Collaborative Filtering (協同過濾),並用 Precision@10 與 Recall@10 驗證。
結果顯示:由於資料稀疏與切分比例的限制,baseline 效果偏低。

今天我們要嘗試另一個常見的 baseline:Item-based Collaborative Filtering,這次不用「使用者的相似度」,而是利用 動畫描述文字 建立動畫之間的相似度。

技術重點:

  • 使用 TF-IDF 向量化動畫的 genretype 描述。
  • 計算動畫間的 Cosine Similarity
  • 根據使用者已看過並喜歡的動畫,找到相似動畫作為推薦。

⚙️ 詳細步驟與程式碼範例

建議放在 notebooks/day6_item_based.ipynb

1. 載入資料

import os
import pandas as pd
import numpy as np
from sklearn.feature_extraction.text import TfidfVectorizer
from sklearn.metrics.pairwise import linear_kernel
import mlflow

DATA_DIR = "/usr/mlflow/data"

anime = pd.read_csv(os.path.join(DATA_DIR, "anime_clean.csv"))
ratings_train = pd.read_csv(os.path.join(DATA_DIR, "ratings_train.csv"))
ratings_test = pd.read_csv(os.path.join(DATA_DIR, "ratings_test.csv"))

print("Anime:", anime.shape)
print("Train:", ratings_train.shape)
print("Test:", ratings_test.shape)

2. 建立動畫描述欄位

我們將 genretype 結合,當作動畫的「文字描述」。

anime["text"] = anime["genre"].fillna("") + " " + anime["type"].fillna("")
anime[["name", "text"]].head()

3. TF-IDF 向量化

tfidf = TfidfVectorizer(stop_words="english")
tfidf_matrix = tfidf.fit_transform(anime["text"])

print("TF-IDF shape:", tfidf_matrix.shape)

4. 動畫相似度矩陣

cosine_sim = linear_kernel(tfidf_matrix, tfidf_matrix)
print("Cosine matrix shape:", cosine_sim.shape)

5. Item-based 推薦函數

根據使用者在 train 裡評分過的動畫,找出最相似的動畫推薦。

indices = pd.Series(anime.index, index=anime["anime_id"]).drop_duplicates()

def recommend_item_based(user_id, top_n=10):
    # 找出 user 在 train 裡喜歡的動畫
    user_ratings = ratings_train[ratings_train["user_id"] == user_id]
    liked = user_ratings[user_ratings["rating"] > 7]["anime_id"].tolist()
    
    if len(liked) == 0:
        return pd.DataFrame(columns=["name", "genre"])
    
    sim_scores = np.zeros(cosine_sim.shape[0])
    
    for anime_id in liked:
        if anime_id in indices:
            idx = indices[anime_id]
            sim_scores += cosine_sim[idx]
    
    sim_scores = sim_scores / len(liked)
    sim_indices = sim_scores.argsort()[::-1]
    
    # 排除已看過的動畫
    seen = set(user_ratings["anime_id"])
    rec_ids = [anime.loc[i, "anime_id"] for i in sim_indices if anime.loc[i, "anime_id"] not in seen]
    
    rec_ids = rec_ids[:top_n]
    return anime[anime["anime_id"].isin(rec_ids)][["name", "genre"]]

6. Precision@K 與 Recall@K

與 Day 5 相同的評估方式,但改用 item-based 推薦函數。

def precision_recall_at_k_item(user_id, k=10):
    recs = recommend_item_based(user_id, top_n=k)
    if recs.empty:
        return np.nan, np.nan
    
    user_test = ratings_test[ratings_test["user_id"] == user_id]
    liked = set(user_test[user_test["rating"] > 7]["anime_id"])
    
    if len(liked) == 0:
        return np.nan, np.nan
    
    hit = len(set(recs.index) & liked)
    
    precision = hit / k
    recall = hit / len(liked)
    
    return precision, recall

# 抽樣部分使用者
sample_users = np.random.choice(ratings_train["user_id"].unique(), 100, replace=False)

precisions, recalls = [], []
for u in sample_users:
    p, r = precision_recall_at_k_item(u, 10)
    if not np.isnan(p):
        precisions.append(p)
    if not np.isnan(r):
        recalls.append(r)

mean_precision = np.mean(precisions)
mean_recall = np.mean(recalls)

print("Item-based Precision@10:", mean_precision)
print("Item-based Recall@10:", mean_recall)

7. 使用 MLflow 紀錄

mlflow.set_experiment("anime-recommender-item-based")

with mlflow.start_run(run_name="item_based_tfidf"):
    mlflow.log_param("top_n", 10)
    mlflow.log_param("feature", "genre+type TF-IDF")
    
    mlflow.log_metric("precision_at_10", mean_precision)
    mlflow.log_metric("recall_at_10", mean_recall)

https://ithelp.ithome.com.tw/upload/images/20250915/20178626sl2Y1UY6oP.png


8. 範例:觀察一位使用者的推薦結果

# 隨機挑一個 test user
test_user = ratings_test["user_id"].sample(1, random_state=42).iloc[0]
print("測試使用者 ID:", test_user)

# 該使用者在 test set 裡喜歡的動畫
user_test = ratings_test[(ratings_test["user_id"] == test_user) & (ratings_test["rating"] > 7)]
liked_anime = anime[anime["anime_id"].isin(user_test["anime_id"])][["name", "genre"]]

print("\n🎯 Test set 裡這位使用者喜歡的動畫:")
print(liked_anime.head(10))  # 最多列 10 部

# 模型推薦的動畫
recommended = recommend_item_based(test_user, top_n=10)

print("\n🤖 模型推薦的前 10 部動畫:")
print(recommended)

https://ithelp.ithome.com.tw/upload/images/20250915/20178626X9FdXyrKi8.png

看起來模型可能有學習到使用者喜歡Action類型的作品,但整體效果非常的差,目前兩個模型都還有非常大的進步空間,但我們後續先使用這兩個模型進行比較與註冊流程,相信未來還有很多機會把模型效能提升,這次的分享先以MLFLOW流程的打通為核心,流程打通了,要執行多次實驗就會很方便。

📊 流程概覽(純文字)

anime_clean.csv + ratings_train.csv + ratings_test.csv
        │
        ▼
建立文字描述欄位 (genre + type)
        │
        ▼
TF-IDF 向量化 → 動畫相似度矩陣
        │
        ▼
Item-based 推薦函數
        │
        └── Precision@10 / Recall@10 (test set) → MLflow log

✅ 重點總結

  • Day 6 採用 Item-based CF (TF-IDF),重點放在動畫之間的相似度,而不是使用者之間。
  • 使用動畫的 genre + type 作為文字描述,透過 TF-IDF + Cosine Similarity 建立相似度矩陣。
  • 同樣使用 Precision@10 與 Recall@10 評估,並存到 MLflow。
  • 相較於 User-based CF,Item-based 方法在稀疏資料下通常表現更穩定。

🔮 延伸思考(預告)

Day 7,我們將把推薦結果(例如:Top-10 清單、範例輸入/輸出)存成 MLflow artifacts,讓之後的 API 與 UI 可以直接使用模型輸出。


上一篇
Day 05 – 建立 Baseline 推薦模型(User-based Collaborative Filtering )
下一篇
Day 07 – 將推薦結果存為 MLflow Artifact
系列文
從0開始的MLFLOW應用搭建9
圖片
  熱門推薦
圖片
{{ item.channelVendor }} | {{ item.webinarstarted }} |
{{ formatDate(item.duration) }}
直播中

尚未有邦友留言

立即登入留言