基于内容的推荐算法实战:TF-IDF + 余弦相似度实现电影推荐(附Python代码)

当你在深夜打开流媒体平台,系统精准推送给你的那部电影,很可能就是基于内容推荐算法的杰作。这种不依赖其他用户行为、仅通过分析内容特征就能实现个性化推荐的技术,正在重塑我们获取信息的方式。本文将带你从零实现一个基于电影内容的推荐系统,核心工具是自然语言处理领域的经典技术——TF-IDF算法与余弦相似度计算。

1. 理解基于内容推荐的核心逻辑

基于内容的推荐算法(Content-Based Recommendation)本质上是 信息过滤系统 的延伸。与协同过滤不同,它不需要用户-物品评分矩阵,而是通过挖掘内容本身的特征建立推荐逻辑:

  • 物品表征 :将每部电影转化为可量化的特征向量(如类型、导演、演员、剧情关键词等)
  • 用户画像 :根据用户历史行为(如评分、观看记录)构建兴趣偏好向量
  • 推荐生成 :计算候选物品与用户画像的相似度,返回最匹配的结果

这种方法的优势在于:

  • 冷启动友好 :新电影只要具备基础元数据即可被推荐
  • 可解释性强 :"因为您喜欢科幻片"比"与您相似的用户也喜欢"更直观
  • 规避数据稀疏 :不依赖用户群体行为数据
# 示例:电影特征向量空间示意
import pandas as pd

movies = pd.DataFrame({
    'title': ['盗梦空间', '星际穿越', '泰坦尼克号'],
    'genre_sci-fi': [1, 1, 0],
    'genre_romance': [0, 1, 1],
    'director_nolan': [1, 1, 0],
    'actor_dicaprio': [1, 0, 1]
})

2. 关键算法:TF-IDF的工程实现

TF-IDF(Term Frequency-Inverse Document Frequency)是处理文本特征的黄金标准,它能有效衡量词语在文档中的重要程度:

  • 词频(TF) :单词在文档中出现的频率
  • 逆文档频率(IDF) :降低常见词权重的调节因子
  • 计算公式 :TF-IDF = TF × log(N/df)
    (N为总文档数,df为包含该词的文档数)

电影描述的TF-IDF处理流程

  1. 文本预处理:分词、去除停用词、词干提取
  2. 构建词表:统计所有电影描述中的独特词汇
  3. 计算TF-IDF矩阵:每个电影对应一个高维稀疏向量
from sklearn.feature_extraction.text import TfidfVectorizer

# 示例电影描述
descriptions = [
    "建筑师通过梦境窃取机密 科幻悬疑",
    "宇航员穿越虫洞拯救人类 科幻亲情",
    "豪华邮轮上的跨越阶级爱情 灾难浪漫"
]

# TF-IDF向量化
tfidf = TfidfVectorizer(token_pattern=r'(?u)\b\w+\b')
tfidf_matrix = tfidf.fit_transform(descriptions)

# 查看特征词
print(tfidf.get_feature_names_out())
# ['人类' '亲情' '窃取' '科幻' '跨越' '阶级' '豪华' '邮轮' '梦境' '灾难' '爱情' '悬疑' '拯救' '建筑师' '浪漫' '宇航员' '虫洞' '机密']

3. 相似度计算的工程细节

余弦相似度衡量的是向量间的夹角而非绝对距离,这对高维稀疏的文本特征尤为重要:

  • 计算公式 :cosθ = (A·B) / (||A|| × ||B||)
  • 取值范围 :[-1, 1],通常文本处理结果为[0,1]
  • 优化技巧
    • 对稀疏矩阵使用 linear_kernel cosine_similarity 快20倍
    • 提前计算并缓存相似度矩阵
from sklearn.metrics.pairwise import linear_kernel

# 计算余弦相似度
cosine_sim = linear_kernel(tfidf_matrix, tfidf_matrix)

# 构建相似度映射表
sim_df = pd.DataFrame(cosine_sim, 
                     index=['盗梦空间', '星际穿越', '泰坦尼克号'],
                     columns=['盗梦空间', '星际穿越', '泰坦尼克号'])

print(sim_df)

4. 完整电影推荐系统实现

我们使用MovieLens数据集中的电影元数据构建端到端推荐流程:

4.1 数据准备与清洗

import pandas as pd
from ast import literal_eval

# 加载数据集
md = pd.read_csv('movies_metadata.csv')
md['genres'] = md['genres'].fillna('[]').apply(literal_eval)
md['genres'] = md['genres'].apply(lambda x: [i['name'] for i in x] if isinstance(x, list) else [])
md['description'] = md['overview'] + ' ' + md['tagline'].fillna('')

# 关键字段处理
md = md[['title', 'genres', 'description']]
md['description'] = md['description'].fillna('')

4.2 特征工程与模型构建

from sklearn.feature_extraction.text import TfidfVectorizer
from sklearn.metrics.pairwise import linear_kernel

# 创建TF-IDF矩阵
tfidf = TfidfVectorizer(stop_words='english')
tfidf_matrix = tfidf.fit_transform(md['description'])

# 计算相似度矩阵
cosine_sim = linear_kernel(tfidf_matrix, tfidf_matrix)

# 建立标题索引映射
indices = pd.Series(md.index, index=md['title']).drop_duplicates()

4.3 推荐函数实现

def content_recommender(title, cosine_sim=cosine_sim, md=md, indices=indices):
    # 获取电影索引
    idx = indices[title]
    
    # 获取相似度评分
    sim_scores = list(enumerate(cosine_sim[idx]))
    
    # 按评分排序
    sim_scores = sorted(sim_scores, key=lambda x: x[1], reverse=True)
    
    # 取Top10(排除自身)
    sim_scores = sim_scores[1:11]
    
    # 获取电影索引
    movie_indices = [i[0] for i in sim_scores]
    
    # 返回推荐结果
    return md['title'].iloc[movie_indices]

# 示例推荐
print(content_recommender('The Dark Knight'))

5. 性能优化与生产级改进

基础版本在实际业务中可能需要以下增强:

5.1 特征增强策略

# 组合多种特征
md['soup'] = md['genres'].apply(' '.join) + ' ' + md['description']

# 使用CountVectorizer增强类型权重
from sklearn.feature_extraction.text import CountVectorizer
count = CountVectorizer(stop_words='english')
count_matrix = count.fit_transform(md['soup'])

# 混合相似度计算
from sklearn.metrics.pairwise import cosine_similarity
final_sim = cosine_similarity(tfidf_matrix, tfidf_matrix) * 0.5 + \
            cosine_similarity(count_matrix, count_matrix) * 0.5

5.2 实时推荐优化

# 使用近似最近邻(ANN)加速查询
from sklearn.neighbors import NearestNeighbors

# 构建ANN索引
knn = NearestNeighbors(metric='cosine', algorithm='brute')
knn.fit(tfidf_matrix)

# 实时查询函数
def realtime_recommend(title, n_recommendations=10):
    idx = indices[title]
    distances, indices = knn.kneighbors(
        tfidf_matrix[idx], 
        n_neighbors=n_recommendations+1
    )
    return md['title'].iloc[indices.flatten()[1:]]

5.3 混合推荐架构

# 伪代码:结合协同过滤
def hybrid_recommend(user_id, title):
    # 内容相似度权重
    content_weight = 0.6
    
    # 获取内容推荐
    content_rec = content_recommender(title)
    
    # 获取协同过滤推荐
    cf_rec = collaborative_filtering(user_id)
    
    # 混合排序
    hybrid_rec = content_weight * content_rec + \
                (1-content_weight) * cf_rec
    return hybrid_rec.sort_values(ascending=False)[:10]

6. 评估推荐质量的方法论

没有评估的推荐系统就像没有罗盘的航行,我们需要量化推荐效果:

6.1 离线评估指标

指标 计算公式 说明
准确率 TP/(TP+FP) 推荐列表中用户真正喜欢的比例
召回率 TP/(TP+FN) 系统找出用户喜欢物品的能力
F1分数 2*(P*R)/(P+R) 准确率与召回率的调和平均
NDCG ∑(rel_i/log2(i+1)) 考虑排序位置的加权评分
from sklearn.metrics import ndcg_score

# 假设有测试集真实评分
true_relevance = np.array([[3, 2, 1, 0, 0]])  # 用户真实偏好
scores = np.array([[0.9, 0.8, 0.7, 0.6, 0.5]]) # 系统预测评分

print("NDCG得分:", ndcg_score(true_relevance, scores))

6.2 在线A/B测试框架

# 伪代码:推荐系统A/B测试
def ab_test_recommend(user_id, algorithm='A'):
    if algorithm == 'A':
        return content_recommender(user_id)
    else:
        return collaborative_filtering(user_id)

# 指标追踪
metrics = {
    'click_through_rate': [],
    'watch_time': [],
    'conversion_rate': []
}

7. 工程实践中的挑战与解决方案

在实际部署推荐系统时,会遇到一些典型问题:

冷启动问题

  • 新电影:引入混合推荐,初期侧重内容特征
  • 新用户:利用人口统计信息或引导选择兴趣标签

数据稀疏性

# 使用SVD降维
from sklearn.decomposition import TruncatedSVD

svd = TruncatedSVD(n_components=100)
reduced_matrix = svd.fit_transform(tfidf_matrix)

实时性要求

  • 增量更新:每隔几小时更新TF-IDF矩阵
  • 流处理:使用Spark Streaming处理新加入内容

可解释性增强

def explain_recommendation(movie_title, recommended_title):
    # 获取共同特征
    vec1 = tfidf.transform([md[md['title']==movie_title]['description'].iloc[0]])
    vec2 = tfidf.transform([md[md['title']==recommended_title]['description'].iloc[0]])
    
    # 提取重要共同词
    common_terms = set(tfidf.inverse_transform(vec1)[0]) & \
                  set(tfidf.inverse_transform(vec2)[0])
    
    return f"推荐《{recommended_title}》因为共同特征:{', '.join(common_terms)}"
Logo

Agent 垂直技术社区,欢迎活跃、内容共建。

更多推荐