如何用Surprise的SVD模型为训练集外用户生成预测?
解决Surprise SVD模型对新用户生成推荐的问题
方法一:手动计算新用户的隐因子与偏差(高效推荐)
由于Surprise预训练模型无法直接处理训练集外的新用户,我们可以复用已训练好的物品参数(隐因子、偏差),通过最小二乘法求解新用户的专属参数,无需重新训练整个模型,适合在线部署场景。
步骤说明
- 提前保存训练完成的模型,在API服务启动时加载。
- 接收新用户的评分数据,过滤掉训练集中不存在的电影(模型无对应参数)。
- 构建线性方程组,求解新用户的偏差
bu_new和隐因子pu_new。 - 利用求解出的参数,计算所有未评分电影的预估评分,排序后取TopN推荐。
代码实现
1. 保存/加载预训练模型
import pickle # 保存训练好的最终模型 with open('svd_final_model.pkl', 'wb') as f: pickle.dump(final_algo, f) # 在API服务中加载模型 with open('svd_final_model.pkl', 'rb') as f: final_algo = pickle.load(f)
2. 处理新用户评分并生成推荐
import numpy as np def get_new_user_top_recommendations(new_user_ratings, algo, n=5): """ 为新用户生成TopN推荐 Args: new_user_ratings: 列表,元素为(新用户ID, 电影原始ID, 评分) algo: 预训练好的Surprise SVD模型 n: 推荐数量,默认5 Returns: 列表,元素为(电影原始ID, 预估评分),按评分降序排列 """ # 提取预训练模型的核心参数 mean = algo.trainset.global_mean bi = algo.bi # 物品偏差(内部ID索引) qi = algo.qi # 物品隐因子矩阵(形状:n_items × n_factors) reg_bu = algo.reg_bu # 用户偏差正则化系数 reg_pu = algo.reg_pu # 用户隐因子正则化系数 n_factors = algo.n_factors # 转换新用户评分的电影ID为内部ID,过滤训练集中不存在的电影 inner_ratings = [] for _, iid_raw, r in new_user_ratings: try: iid_inner = algo.trainset.to_inner_iid(iid_raw) inner_ratings.append((iid_inner, r)) except ValueError: # 跳过不在训练集中的电影 continue if not inner_ratings: # 无有效评分时,可返回热门电影或随机推荐 return [] # 构建线性方程组的特征矩阵X和目标向量y X = [] y = [] for iid_inner, r in inner_ratings: # 每行特征:[1, 物品隐因子0, 物品隐因子1, ..., 物品隐因子n-1] row = [1.0] + qi[iid_inner].tolist() X.append(row) # 目标值:真实评分 - 全局均值 - 物品偏差 y_val = r - mean - bi[iid_inner] y.append(y_val) # 添加正则化项,防止过拟合 # 用户偏差的正则化行 X.append([np.sqrt(reg_bu)] + [0.0] * n_factors) y.append(0.0) # 用户隐因子每个维度的正则化行 for j in range(n_factors): reg_row = [0.0] * (n_factors + 1) reg_row[j+1] = np.sqrt(reg_pu) X.append(reg_row) y.append(0.0) # 转换为numpy数组并求解最小二乘 X_np = np.array(X) y_np = np.array(y) params, _, _, _ = np.linalg.lstsq(X_np, y_np, rcond=None) # 提取新用户的参数 bu_new = params[0] pu_new = params[1:] # 预测所有未评分电影的评分 predictions = [] rated_inner_iids = {iid for iid, _ in inner_ratings} for iid_inner in algo.trainset.all_items(): if iid_inner in rated_inner_iids: continue # 计算预估评分 est = mean + bu_new + bi[iid_inner] + np.dot(pu_new, qi[iid_inner]) # 转换回原始电影ID iid_raw = algo.trainset.to_raw_iid(iid_inner) predictions.append((iid_raw, est)) # 按预估评分降序排序,取TopN predictions.sort(key=lambda x: x[1], reverse=True) return predictions[:n] # 示例使用 new_user_ratings = [("new_user_001", "movie_123", 4.0), ("new_user_001", "movie_456", 2.5), ("new_user_001", "movie_789", 5.0)] top5_recs = get_new_user_top_recommendations(new_user_ratings, final_algo, n=5) print(top5_recs)
方法二:合并新用户数据重新训练模型(适合小数据集)
如果数据集规模较小,可直接将新用户的评分合并到原数据中,重新构建训练集并训练模型。这种方法实现简单,但效率较低,不适合大数据量的在线场景。
代码实现
import pandas as pd from surprise import Dataset, Reader # 假设原数据存储在df中,列名:user_id, item_id, rating new_user_ratings = [("new_user_001", "movie_123", 4.0), ("new_user_001", "movie_456", 2.5)] new_user_df = pd.DataFrame(new_user_ratings, columns=["user_id", "item_id", "rating"]) # 合并原数据与新用户数据 combined_df = pd.concat([df, new_user_df], ignore_index=True) # 重新构建训练集 reader = Reader(rating_scale=(1, 5)) data = Dataset.load_from_df(combined_df[["user_id", "item_id", "rating"]], reader) trainset = data.build_full_trainset() # 重新拟合模型(可复用原模型参数初始化,减少训练时间) final_algo.fit(trainset) # 构建新用户的anti-testset(未评分的电影) new_user_raw_id = "new_user_001" new_user_inner_id = trainset.to_inner_uid(new_user_raw_id) anti_testset = [] for iid_inner in trainset.all_items(): # 检查该电影是否已被用户评分 if iid_inner not in [iid for (iid, _) in trainset.ur[new_user_inner_id]]: iid_raw = trainset.to_raw_iid(iid_inner) anti_testset.append((new_user_raw_id, iid_raw, trainset.global_mean)) # 预测并生成Top5推荐 predictions = final_algo.test(anti_testset) top_n = get_top_n(predictions, n=5) new_user_recs = top_n[new_user_raw_id] print(new_user_recs)
内容的提问来源于stack exchange,提问作者Techie5879
相关产品推荐
相关产品推荐

