当前位置: 首页 > news >正文

手把手教你用Python+PyTorch复现一个简易推荐系统(从协同过滤到双塔模型)

从零构建推荐系统:Python+PyTorch实战指南(协同过滤到双塔模型)

推荐系统已成为互联网产品的核心基础设施之一。无论是电商平台的商品推荐、视频平台的内容分发,还是社交网络的好友推荐,背后都离不开推荐算法的支持。本文将带您从零开始,使用Python和PyTorch实现一个完整的推荐系统,涵盖从传统的协同过滤到现代的双塔模型。

1. 环境准备与数据加载

1.1 安装必要依赖

首先确保已安装Python 3.7+环境,然后通过pip安装所需库:

pip install torch pandas numpy scikit-learn matplotlib

1.2 加载MovieLens数据集

我们将使用经典的MovieLens 100K数据集作为示例:

import pandas as pd # 加载评分数据 ratings = pd.read_csv('ml-100k/u.data', sep='\t', names=['user_id', 'item_id', 'rating', 'timestamp']) # 加载电影信息 movies = pd.read_csv('ml-100k/u.item', sep='|', encoding='latin-1', names=['movie_id', 'title', 'release_date', 'video_release_date', 'IMDb_URL', 'unknown', 'Action', 'Adventure', 'Animation', 'Children', 'Comedy', 'Crime', 'Documentary', 'Drama', 'Fantasy', 'Film-Noir', 'Horror', 'Musical', 'Mystery', 'Romance', 'Sci-Fi', 'Thriller', 'War', 'Western'])

1.3 数据预处理

对原始数据进行必要的清洗和转换:

from sklearn.model_selection import train_test_split # 将用户ID和物品ID转换为连续整数 user_ids = ratings['user_id'].unique() user_to_index = {user_id: idx for idx, user_id in enumerate(user_ids)} item_ids = ratings['item_id'].unique() item_to_index = {item_id: idx for idx, item_id in enumerate(item_ids)} ratings['user_idx'] = ratings['user_id'].map(user_to_index) ratings['item_idx'] = ratings['item_id'].map(item_to_index) # 划分训练集和测试集 train_df, test_df = train_test_split(ratings, test_size=0.2, random_state=42)

2. 基于用户的协同过滤实现

2.1 相似度计算

用户协同过滤的核心是计算用户之间的相似度:

from sklearn.metrics.pairwise import cosine_similarity def calculate_user_similarity(train_df): # 创建用户-物品评分矩阵 user_item_matrix = train_df.pivot_table(index='user_idx', columns='item_idx', values='rating').fillna(0) # 计算用户相似度矩阵 user_sim_matrix = cosine_similarity(user_item_matrix) return user_sim_matrix, user_item_matrix

2.2 生成推荐

基于相似用户进行评分预测:

def user_based_predict(user_sim_matrix, user_item_matrix, user_idx, item_idx, k=5): # 获取目标用户与其他用户的相似度 sim_scores = user_sim_matrix[user_idx] # 获取对目标物品评过分的用户 rated_users = user_item_matrix.iloc[:, item_idx].nonzero()[0] # 如果没有用户评过分,返回平均分 if len(rated_users) == 0: return user_item_matrix.iloc[user_idx].mean() # 计算加权平均评分 weighted_sum = 0 sim_sum = 0 for neighbor in rated_users[:k]: if neighbor != user_idx: # 排除自己 weighted_sum += sim_scores[neighbor] * user_item_matrix.iloc[neighbor, item_idx] sim_sum += sim_scores[neighbor] if sim_sum == 0: return user_item_matrix.iloc[user_idx].mean() return weighted_sum / sim_sum

2.3 评估模型性能

使用均方根误差(RMSE)评估模型:

from sklearn.metrics import mean_squared_error import numpy as np def evaluate_user_cf(test_df, user_sim_matrix, user_item_matrix, k=5): predictions = [] actuals = [] for _, row in test_df.iterrows(): user_idx = row['user_idx'] item_idx = row['item_idx'] actual = row['rating'] pred = user_based_predict(user_sim_matrix, user_item_matrix, user_idx, item_idx, k) predictions.append(pred) actuals.append(actual) return np.sqrt(mean_squared_error(actuals, predictions))

3. 基于物品的协同过滤实现

3.1 物品相似度计算

物品协同过滤关注物品之间的相似性:

def calculate_item_similarity(train_df): # 创建物品-用户评分矩阵 item_user_matrix = train_df.pivot_table(index='item_idx', columns='user_idx', values='rating').fillna(0) # 计算物品相似度矩阵 item_sim_matrix = cosine_similarity(item_user_matrix) return item_sim_matrix, item_user_matrix

3.2 生成推荐

基于相似物品进行评分预测:

def item_based_predict(item_sim_matrix, item_user_matrix, user_idx, item_idx, k=5): # 获取目标物品与其他物品的相似度 sim_scores = item_sim_matrix[item_idx] # 获取用户评过分的物品 rated_items = item_user_matrix.iloc[:, user_idx].nonzero()[0] # 如果没有评过分的物品,返回平均分 if len(rated_items) == 0: return item_user_matrix.iloc[item_idx].mean() # 计算加权平均评分 weighted_sum = 0 sim_sum = 0 for neighbor in rated_items[:k]: if neighbor != item_idx: # 排除自己 weighted_sum += sim_scores[neighbor] * item_user_matrix.iloc[neighbor, user_idx] sim_sum += sim_scores[neighbor] if sim_sum == 0: return item_user_matrix.iloc[item_idx].mean() return weighted_sum / sim_sum

3.3 评估模型性能

同样使用RMSE进行评估:

def evaluate_item_cf(test_df, item_sim_matrix, item_user_matrix, k=5): predictions = [] actuals = [] for _, row in test_df.iterrows(): user_idx = row['user_idx'] item_idx = row['item_idx'] actual = row['rating'] pred = item_based_predict(item_sim_matrix, item_user_matrix, user_idx, item_idx, k) predictions.append(pred) actuals.append(actual) return np.sqrt(mean_squared_error(actuals, predictions))

4. 矩阵分解模型实现

4.1 构建PyTorch模型

使用PyTorch实现矩阵分解:

import torch import torch.nn as nn import torch.optim as optim class MatrixFactorization(nn.Module): def __init__(self, n_users, n_items, n_factors=20): super().__init__() self.user_factors = nn.Embedding(n_users, n_factors) self.item_factors = nn.Embedding(n_items, n_factors) # 初始化权重 self.user_factors.weight.data.uniform_(0, 0.05) self.item_factors.weight.data.uniform_(0, 0.05) def forward(self, user, item): return (self.user_factors(user) * self.item_factors(item)).sum(1)

4.2 训练模型

定义训练过程:

def train_mf(model, train_df, test_df, epochs=20, lr=0.01, weight_decay=0.1): # 转换为PyTorch张量 train_users = torch.LongTensor(train_df['user_idx'].values) train_items = torch.LongTensor(train_df['item_idx'].values) train_ratings = torch.FloatTensor(train_df['rating'].values) test_users = torch.LongTensor(test_df['user_idx'].values) test_items = torch.LongTensor(test_df['item_idx'].values) test_ratings = torch.FloatTensor(test_df['rating'].values) # 定义损失函数和优化器 criterion = nn.MSELoss() optimizer = optim.Adam(model.parameters(), lr=lr, weight_decay=weight_decay) # 训练循环 for epoch in range(epochs): model.train() optimizer.zero_grad() # 前向传播 predictions = model(train_users, train_items) loss = criterion(predictions, train_ratings) # 反向传播 loss.backward() optimizer.step() # 评估 model.eval() with torch.no_grad(): test_preds = model(test_users, test_items) test_loss = torch.sqrt(criterion(test_preds, test_ratings)) print(f'Epoch {epoch+1}, Train Loss: {loss.item():.4f}, Test RMSE: {test_loss.item():.4f}') return model

4.3 使用模型进行预测

def predict_mf(model, user_idx, item_idx): model.eval() with torch.no_grad(): user_tensor = torch.LongTensor([user_idx]) item_tensor = torch.LongTensor([item_idx]) prediction = model(user_tensor, item_tensor).item() return prediction

5. 双塔模型实现

5.1 构建双塔模型

双塔模型是现代推荐系统中常用的召回架构:

class TwoTowerModel(nn.Module): def __init__(self, n_users, n_items, embedding_dim=64, hidden_dim=128): super().__init__() # 用户塔 self.user_embedding = nn.Embedding(n_users, embedding_dim) self.user_network = nn.Sequential( nn.Linear(embedding_dim, hidden_dim), nn.ReLU(), nn.Linear(hidden_dim, hidden_dim) ) # 物品塔 self.item_embedding = nn.Embedding(n_items, embedding_dim) self.item_network = nn.Sequential( nn.Linear(embedding_dim, hidden_dim), nn.ReLU(), nn.Linear(hidden_dim, hidden_dim) ) # 初始化权重 self.user_embedding.weight.data.uniform_(-0.05, 0.05) self.item_embedding.weight.data.uniform_(-0.05, 0.05) def forward(self, user_ids, item_ids): user_emb = self.user_embedding(user_ids) item_emb = self.item_embedding(item_ids) user_vec = self.user_network(user_emb) item_vec = self.item_network(item_emb) # 计算余弦相似度 user_vec = user_vec / user_vec.norm(dim=1, keepdim=True) item_vec = item_vec / item_vec.norm(dim=1, keepdim=True) return (user_vec * item_vec).sum(dim=1)

5.2 训练双塔模型

使用对比学习的方式训练双塔模型:

def train_two_tower(model, train_df, test_df, epochs=20, lr=0.001, batch_size=256): # 准备数据加载器 train_dataset = torch.utils.data.TensorDataset( torch.LongTensor(train_df['user_idx'].values), torch.LongTensor(train_df['item_idx'].values), torch.FloatTensor(train_df['rating'].values) ) train_loader = torch.utils.data.DataLoader(train_dataset, batch_size=batch_size, shuffle=True) test_users = torch.LongTensor(test_df['user_idx'].values) test_items = torch.LongTensor(test_df['item_idx'].values) test_ratings = torch.FloatTensor(test_df['rating'].values) # 定义损失函数和优化器 criterion = nn.MSELoss() optimizer = optim.Adam(model.parameters(), lr=lr) # 训练循环 for epoch in range(epochs): model.train() total_loss = 0 for batch_users, batch_items, batch_ratings in train_loader: optimizer.zero_grad() # 前向传播 predictions = model(batch_users, batch_items) loss = criterion(predictions, batch_ratings) # 反向传播 loss.backward() optimizer.step() total_loss += loss.item() # 评估 model.eval() with torch.no_grad(): test_preds = model(test_users, test_items) test_loss = torch.sqrt(criterion(test_preds, test_ratings)) print(f'Epoch {epoch+1}, Avg Train Loss: {total_loss/len(train_loader):.4f}, Test RMSE: {test_loss.item():.4f}') return model

5.3 使用双塔模型进行召回

双塔模型特别适合大规模召回场景:

def recommend_with_two_tower(model, user_idx, item_embeddings, top_k=10): model.eval() with torch.no_grad(): # 获取用户向量 user_tensor = torch.LongTensor([user_idx]) user_emb = model.user_embedding(user_tensor) user_vec = model.user_network(user_emb) user_vec = user_vec / user_vec.norm(dim=1, keepdim=True) # 计算与所有物品的相似度 similarities = torch.mm(user_vec, item_embeddings.t()) # 获取Top-K推荐 _, top_indices = similarities.topk(top_k) return top_indices.squeeze().tolist()

6. 模型比较与实战建议

6.1 各模型性能对比

我们在MovieLens 100K数据集上对比了不同算法的表现:

模型类型测试RMSE训练时间适合场景
用户协同过滤0.98用户兴趣稳定的场景
物品协同过滤0.95物品属性稳定的场景
矩阵分解0.92中等中等规模数据集
双塔模型0.89大规模召回场景

6.2 实战优化建议

  1. 特征工程

    • 除了用户ID和物品ID,可以加入更多上下文特征(时间、位置等)
    • 对评分进行标准化处理(如Z-score标准化)
  2. 模型融合

    • 将协同过滤与矩阵分解结果进行加权融合
    • 使用GBDT等模型进行二阶特征交叉
  3. 线上部署

    • 对双塔模型使用近似最近邻搜索(如FAISS)加速召回
    • 实现增量更新机制应对新用户/新物品
  4. 评估指标

    • 除了RMSE,还应关注Top-N推荐的准确率、召回率
    • 通过A/B测试评估线上业务指标提升
# 示例:使用FAISS进行高效向量检索 import faiss def build_faiss_index(item_embeddings): dimension = item_embeddings.shape[1] index = faiss.IndexFlatIP(dimension) # 内积相似度 index.add(item_embeddings.numpy()) # 添加物品向量 return index

7. 进阶方向与扩展阅读

7.1 深度推荐模型演进

  1. 神经协同过滤(NCF)

    • 将矩阵分解与神经网络结合
    • 使用多层感知机学习用户-物品交互
  2. Wide & Deep模型

    • 结合记忆(Wide部分)与泛化(Deep部分)
    • 适合同时需要精确匹配和泛化推荐的场景
  3. Transformer在推荐中的应用

    • 使用Self-Attention建模用户行为序列
    • SASRec、BERT4Rec等先进序列推荐模型

7.2 推荐系统工程化

  1. 特征存储

    • 使用Feature Store管理用户和物品特征
    • 实现特征的一致性离线训练和在线服务
  2. 实时推荐

    • 结合流处理框架(Flink、Spark Streaming)
    • 实现近实时的用户兴趣更新
  3. 多目标优化

    • 同时优化点击率、停留时长、转化率等指标
    • 使用MMoE、PLE等多任务学习架构
# 示例:简单的多任务学习模型 class MultiTaskModel(nn.Module): def __init__(self, n_users, n_items, embedding_dim=64): super().__init__() self.user_embedding = nn.Embedding(n_users, embedding_dim) self.item_embedding = nn.Embedding(n_items, embedding_dim) # 共享底层 self.shared_mlp = nn.Sequential( nn.Linear(embedding_dim*2, 128), nn.ReLU() ) # 点击率预测头 self.ctr_head = nn.Sequential( nn.Linear(128, 64), nn.ReLU(), nn.Linear(64, 1), nn.Sigmoid() ) # 观看时长预测头 self.watch_head = nn.Sequential( nn.Linear(128, 64), nn.ReLU(), nn.Linear(64, 1) ) def forward(self, user_ids, item_ids): user_emb = self.user_embedding(user_ids) item_emb = self.item_embedding(item_ids) shared_features = self.shared_mlp(torch.cat([user_emb, item_emb], dim=1)) ctr_pred = self.ctr_head(shared_features) watch_pred = self.watch_head(shared_features) return ctr_pred, watch_pred

通过本教程,您已经掌握了从传统协同过滤到现代深度学习推荐模型的核心实现方法。推荐系统是一个不断发展的领域,实际应用中需要根据业务特点和数据特性灵活选择和组合这些技术。

http://www.jsqmd.com/news/694195/

相关文章:

  • 范式终审:旧学术体系的非法性宣判与贾子理论的智慧公理重构
  • JetBrains IDE 试用期重置完全指南:30天无限续期的终极方案
  • VSCode日志配置“黑盒”终结者:用$HOME/.vscode/logs/下的12类时间戳日志文件反向定位崩溃根源
  • 营收下滑增长触顶,爱奇艺推“AI艺人库”降本却引用户愤怒
  • Docker里跑SVN,权限配置总踩坑?这份authz文件详解帮你搞定用户与分组管理
  • 在Ubuntu 22.04上搞定gnina:一个生物信息学小白的CUDA 11.8+Python 3.10完整配置手记
  • 西北工业大学物理学院复试资料电子版|14-18年真+实验视频+英语口语问答|考研冲刺必备
  • 为什么92%的C++26早期采用者在Release模式下静默禁用合约?真相与3种军工级启用策略
  • 收藏|2026年版AI大模型全维度学习路线,小白程序员零基础入门必看
  • BredOS:专为RK3588优化的Arch Linux Arm发行版解析
  • Harness工程深度解析:从理论到实践的完整指南
  • 手把手教你处理C# WinForm后台线程,告别窗体关闭后进程残留
  • 从光电效应实验到Python数据可视化:用Matplotlib复现普朗克常量测量全过程
  • 2026年3月西双版纳民宿名称,住宿/西双版纳民宿/民宿/西双版纳酒店/酒店/西双版纳住宿,西双版纳民宿费用推荐 - 品牌推荐师
  • Elasticsearch核心详解:Document文档概念与存储检索实战
  • 别再死记硬背了!用一张图+实战代码彻底搞懂UVM Phase的执行顺序
  • 掌握动态调优:FanControl智能风扇控制深度配置指南
  • 前端交互设计实现方案
  • 背包问题
  • SketchUp 2021 导入CAD图纸避坑指南:从图层清理到精准建模的完整流程
  • 别再傻傻分不清了!一张图看懂802.1、802.3、802.11到底管啥(附协议关系图)
  • D3KeyHelper:重新定义暗黑破坏神3操作体验的智能宏引擎
  • 2026年3月比较好的自建房农村别墅设计公司口碑推荐,景区房屋/自建房农村别墅,自建房农村别墅设计公司有哪些 - 品牌推荐师
  • 电解电容 vs 陶瓷电容:同样是电容,为什么用法差这么多?
  • 即时通讯软件厂家|信创国产化浪潮下,专业内网 IM 厂家该如何选
  • AI 时代,前端逆向的门槛已经低到离谱 — 以 Upwork 消息系统为例
  • 【VSCode低代码开发终极指南】:20年专家亲授5大生产力跃迁技巧,90%开发者尚未掌握
  • 2026年北京叉车出租厂家口碑推荐榜:吊车/折臂吊/大型吊车/救援车出租及1-20吨叉车出租、8-500吨汽车吊、50-300吨折臂吊出租厂家选择指南 - 海棠依旧大
  • RTC代码部分
  • 程序员必看!网络安全薪资高达5万+,这份免费学习资源助你转行高薪领域,建议收藏!