资讯中心

协同过滤推荐算法原理与旅游场景落地实践

📅 2026/9/16 15:57:26
协同过滤推荐算法原理与旅游场景落地实践
简介本资源是一份面向计算机专业本科生及毕业设计学生的协同过滤推荐系统研究实践项目聚焦于UserCF与ItemCF算法的原理实现、相似度改进及K值调优实验。资源包含完整Java源码、配套论文全文及多组实验结果可视化图示帮助学习者深入理解推荐系统核心逻辑与评测指标Precision/Recall/Coverage等的实际影响因素。压缩包共35个文件含25张公式与结果图jpg、3个数据集data、2个Python主程序py、1份README说明md等结构清晰便于分模块研读算法实现与实验分析整体体积仅3.32MB轻量易下载。已有822人学习下载适合需完成课程设计、毕设开题或夯实推荐算法工程能力的学习者可直接复现实验、对比不同相似度计算方式与K值设定对推荐效果的影响。1. 协同过滤不是“猜你喜欢”而是用用户行为数据构建可计算的相似关系网很多人第一次接触“基于协同过滤的推荐系统算法研究项目源码论文.zip”时下意识点开就找main.py或run.ipynb想立刻跑出一个“电影推荐列表”。但真正卡住的从来不是代码运行失败而是不理解为什么两个用户看过《阿凡达》和《盗梦空间》就该被划为同一类为什么物品相似度要用余弦而不是欧氏距离为什么稀疏评分矩阵里 98% 是空值模型却还能给出稳定预测这个问题的本质不是调参或写 Python而是把“人以群分、物以类聚”的朴素经验翻译成可建模、可验证、可部署的数学结构。本项目聚焦于显式反馈场景下的经典协同过滤实现路径——即用户对物品如电影、商品、景点给出明确评分1~5星不依赖内容特征、不引入深度学习纯靠用户-物品交互矩阵挖掘隐含关联。适合刚学完线性代数与概率统计、能写基础 Python 的算法入门者也适合需要快速复现基线模型、用于旅游推荐、电商冷启或课程设计的工程师。它不解决“如何让推荐更酷炫”而是回答“在数据有限、算力普通、逻辑必须可解释的前提下协同过滤最扎实的落地形态是什么”。2. 从原始评分矩阵到用户/物品相似度协同过滤的三步数学推演与Python实现协同过滤的核心不在“过滤”而在“协同”——即利用群体行为反推个体偏好。其数学骨架由三个不可跳过的环节构成构建用户-物品评分矩阵、计算用户或物品间的相似度、加权聚合生成预测评分。这三步环环相扣任何一步的偏差都会放大最终推荐误差。下面以 MovieLens-100k 数据集含 943 用户 × 1682 电影 × 10 万条评分为基准逐层展开可复现的 Python 实现。2.1 构建稀疏但结构化的用户-物品评分矩阵原始数据是三元组(user_id, item_id, rating)需转换为二维矩阵行用户列物品。直接用numpy.zeros((n_users, n_items))会因稀疏性MovieLens-100k 稀疏度达 93.7%导致内存爆炸。正确做法是使用scipy.sparse.csr_matrix仅存储非零值及其坐标。import pandas as pd import numpy as np from scipy.sparse import csr_matrix # 加载 u.dataMovieLens-100k 格式user_id\titem_id\trating\ttimestamp df pd.read_csv(u.data, sep\t, headerNone, names[user, item, rating, timestamp]) n_users, n_items df[user].max(), df[item].max() # 构建 CSR 矩阵行索引user_id-1转0基列索引item_id-1值rating ratings_matrix csr_matrix( (df[rating].values, (df[user].values - 1, df[item].values - 1)), shape(n_users, n_items) ) print(f评分矩阵形状: {ratings_matrix.shape}) print(f非零元素数: {ratings_matrix.nnz}) print(f稀疏度: {1 - ratings_matrix.nnz / (n_users * n_items):.3f})提示csr_matrix的.data、.indices、.indptr三数组分别存数值、列索引、行指针。后续所有相似度计算都基于此结构避免.toarray()全量加载——这是处理百万级用户时内存不崩的关键。2.2 用户相似度 vs 物品相似度选择依据与皮尔逊相关系数实现协同过滤分 User-Based 和 Item-Based 两类。User-Based 假设“相似用户喜欢相似物品”Item-Based 假设“被同一用户喜欢的物品彼此相似”。实际选型取决于场景User-Based 更适合用户兴趣稳定、物品更新快的场景如新闻推荐但用户数增长时计算开销剧增Item-Based 更适合物品数相对稳定、用户行为稀疏的场景如旅游景点推荐、电商商品且支持离线预计算相似度矩阵线上响应快。本项目采用Item-Based 协同过滤因其在旅游推荐等长尾场景中鲁棒性更强景点数量远少于用户数且用户对景点的评分模式更趋同。相似度选用皮尔逊相关系数Pearson Correlation Coefficient而非余弦相似度原因在于皮尔逊能消除用户评分偏置如用户A习惯打高分用户B习惯打低分只关注评分趋势的一致性。公式为$$ \text{sim}(i,j) \frac{\sum_{u \in U_{ij}} (r_{ui} - \bar{r}i)(r{uj} - \bar{r}j)}{\sqrt{\sum{u \in U_{ij}} (r_{ui} - \bar{r}i)^2} \sqrt{\sum{u \in U_{ij}} (r_{uj} - \bar{r}_j)^2}} $$其中 $U_{ij}$ 是同时对物品 $i$ 和 $j$ 评分的用户集合$\bar{r}_i$ 是物品 $i$ 的平均评分。from sklearn.metrics.pairwise import pairwise_distances import numpy as np # 将稀疏矩阵转为密集矩阵仅用于物品相似度计算因物品数 n_items1682 n_users943 # 注意此处转 dense 是可接受的若 n_items 10^4 则需改用稀疏近似算法 ratings_dense ratings_matrix.toarray() # shape: (n_users, n_items) # 计算物品相似度矩阵每列是一个物品行是用户评分 # metriccorrelation 即皮尔逊相关系数nan_policyomit 自动忽略缺失值 item_sim_matrix 1 - pairwise_distances(ratings_dense.T, metriccorrelation, nan_policyomit) # 修复 NaN当某物品无共同评分用户时和对角线自身相似度应为1 np.fill_diagonal(item_sim_matrix, 1.0) item_sim_matrix np.nan_to_num(item_sim_matrix, nan0.0) print(f物品相似度矩阵形状: {item_sim_matrix.shape}) print(f相似度范围: [{item_sim_matrix.min():.3f}, {item_sim_matrix.max():.3f}])参数说明pairwise_distances(..., metriccorrelation)内部已实现皮尔逊计算自动中心化每列即减去物品均值 $\bar{r}_i$。nan_policyomit确保仅用共同评分用户参与计算避免全零向量导致除零错误。若物品 $i$ 和 $j$ 无共同评分用户结果为NaN故需nan_to_num置零——这符合业务直觉无交集即无相似性。2.3 加权聚合生成预测评分Top-K 相似物品筛选与归一化预测用户 $u$ 对未评分物品 $i$ 的评分 $\hat{r}_{ui}$公式为$$ \hat{r}{ui} \bar{r}u \frac{\sum{j \in N^k(i;u)} \text{sim}(i,j) \cdot (r{uj} - \bar{r}u)}{\sum{j \in N^k(i;u)} |\text{sim}(i,j)|} $$其中 $N^k(i;u)$ 是用户 $u$ 评过分、且与物品 $i$ 相似度最高的 $k$ 个物品集合$\bar{r}_u$ 是用户 $u$ 的平均评分。关键点有三Top-K 截断不使用全部相似物品计算量大且噪声多只取前 $k20$ 个最相似的用户均值中心化先减去 $\bar{r}_u$再加回消除用户打分习惯偏差分母归一化用相似度绝对值之和防止正负相似度抵消。def predict_rating(user_id, item_id, ratings_matrix, item_sim_matrix, k20): 预测用户 user_id 对物品 item_id 的评分 :param user_id: int, 0-based 用户索引 :param item_id: int, 0-based 物品索引 :param ratings_matrix: csr_matrix, (n_users, n_items) :param item_sim_matrix: np.ndarray, (n_items, n_items) :param k: int, 取 top-k 相似物品 :return: float, 预测评分 # 获取用户对该用户评过分的所有物品及评分 user_ratings ratings_matrix[user_id].toarray().flatten() rated_items np.where(user_ratings 0)[0] # 用户评过分的物品索引 if len(rated_items) 0: return np.mean(ratings_matrix.data) # 无历史返回全局均值 # 获取这些已评物品与目标物品 item_id 的相似度 similarities item_sim_matrix[item_id, rated_items] # 取 top-k 相似物品按相似度降序 k_idx np.argsort(similarities)[::-1][:k] top_k_items rated_items[k_idx] top_k_sims similarities[k_idx] # 用户 u 的平均评分 user_mean np.mean(user_ratings[user_ratings 0]) # 加权求和sim * (r_uj - r_u_mean) weighted_sum np.sum(top_k_sims * (user_ratings[top_k_items] - user_mean)) sim_sum np.sum(np.abs(top_k_sims)) if sim_sum 0: return user_mean # 无有效相似物品返回用户均值 return user_mean weighted_sum / sim_sum # 示例预测用户0对物品5的评分 pred predict_rating(0, 5, ratings_matrix, item_sim_matrix, k20) print(f用户0对物品5的预测评分: {pred:.3f})注意predict_rating函数中user_ratings[user_ratings 0]提取非零评分确保 $\bar{r}_u$ 计算准确。np.argsort(...)[::-1]实现降序索引[:k]截断。若sim_sum 0如所有 top-k 相似度为0则退化为用户均值预测——这是健壮性设计避免除零或异常值污染。3. 旅游推荐场景下的协同过滤落地数据预处理、冷启动缓解与Top-N生成将通用协同过滤迁移到“旅游推荐”这一具体领域不能只套用 MovieLens 流程。旅游数据天然具有高稀疏性用户只去过极少数城市、强地域性北京用户大概率不评巴黎景点、长尾分布热门景点占80%评分小众景点难获曝光。本节聚焦三个旅游场景专属问题如何清洗原始旅游评分数据、如何缓解新用户/新景点冷启动、如何生成可交付的 Top-N 推荐列表。3.1 旅游数据清洗剔除地域冲突与时间衰减加权假设原始数据来自某旅游 App格式为(user_id, city_id, rating, visit_date)。直接使用visit_date会导致模型过度拟合近期行为忽略用户长期偏好。更合理的是引入时间衰减因子越早的访问对当前推荐的贡献越小。常用指数衰减$w_t e^{-\lambda \cdot \Delta t}$其中 $\Delta t$ 是距今天的天数$\lambda$ 控制衰减速度$\lambda0.001$ 表示约 2 年后权重降至 0.37。import pandas as pd from datetime import datetime import numpy as np # 加载旅游数据user_id, city_id, rating, visit_date (格式: YYYY-MM-DD) df_tour pd.read_csv(tour_ratings.csv) df_tour[visit_date] pd.to_datetime(df_tour[visit_date]) today datetime.now() # 计算天数差并加权 df_tour[days_diff] (today - df_tour[visit_date]).dt.days df_tour[weight] np.exp(-0.001 * df_tour[days_diff]) # 过滤地域冲突剔除用户对非本国城市的评分若业务限定国内游 # 假设 city_country_map.csv 包含 city_id - country_code 映射 city_country pd.read_csv(city_country_map.csv) df_tour df_tour.merge(city_country, oncity_id, howinner) df_tour df_tour[df_tour[country_code] CN] # 仅保留中国城市 # 按权重重采样使高频用户不过度影响矩阵 df_tour_weighted df_tour.sample(frac1, weightsweight, random_state42).reset_index(dropTrue) df_tour_final df_tour_weighted[[user_id, city_id, rating]].copy() print(f清洗后旅游数据量: {len(df_tour_final)} 条) print(f时间衰减后权重范围: [{df_tour_final[weight].min():.3f}, {df_tour_final[weight].max():.3f}])提示地域过滤country_code CN是旅游推荐的硬约束。若不做此步模型会学习到“上海用户喜欢巴黎”这在业务上无效且误导相似度计算。时间衰减权重weight后续用于构建加权评分矩阵替代原始rating值。3.2 冷启动缓解基于城市属性的混合相似度初始化新用户无历史评分或新城市无被评分记录无法参与协同过滤。纯靠内容特征如城市GDP、景点数量、气候又违背“协同”本质。折中方案是为新实体赋予一个基于属性的初始相似度待积累足够行为后再切换为纯协同相似度。例如对新城市 $c_{new}$计算其与已有城市 $c_i$ 的地理距离相似度$\text{sim}{geo}(c{new}, c_i) \exp(-d(c_{new}, c_i)/1000)$其中 $d$ 是公里距离1000 是尺度参数。import geopy.distance # 假设 city_geo.csv 包含 city_id, lat, lon city_geo pd.read_csv(city_geo.csv) city_geo.set_index(city_id, inplaceTrue) def geo_similarity(city_a, city_b, city_geo_df): 计算两城市地理相似度 try: coord_a (city_geo_df.loc[city_a, lat], city_geo_df.loc[city_a, lon]) coord_b (city_geo_df.loc[city_b, lat], city_geo_df.loc[city_b, lon]) dist_km geopy.distance.geodesic(coord_a, coord_b).km return np.exp(-dist_km / 1000) except KeyError: return 0.0 # 为新城市 city_999 初始化与所有已有城市的相似度 new_city city_999 existing_cities city_geo.index.tolist() geo_sims [geo_similarity(new_city, c, city_geo) for c in existing_cities] # 取 top-10 地理相似城市作为初始协同推荐源 top_geo_idx np.argsort(geo_sims)[::-1][:10] top_geo_cities [existing_cities[i] for i in top_geo_idx] print(f新城市 {new_city} 的地理相似城市: {top_geo_cities})参数说明geopy.distance.geodesic计算球面距离比平面距离更准。exp(-dist/1000)中 1000 是经验值距离 1000km 时相似度为 0.372000km 时为 0.14符合“邻近城市更可能被同一用户访问”的直觉。此方法不替代协同过滤而是为冷启动提供可解释的 fallback。3.3 生成 Top-N 推荐列表过滤、排序与多样性控制协同过滤输出的是单个物品的预测评分但产品需要的是Top-10 推荐城市列表。直接取预测分最高 10 个会带来问题重复推荐用户刚去过杭州模型仍因相似度高而推荐苏州地理邻近但用户已去过缺乏多样性全推荐长三角城市忽略用户潜在兴趣如曾评过高海拔景点。解决方案是先过滤已访问城市再按预测分排序最后用 Maximal Marginal RelevanceMMR注入多样性。MMR 公式$$ \text{score}(i) \alpha \cdot \hat{r}{ui} (1-\alpha) \cdot \max{j \in S} \text{sim}_{geo}(i,j) $$其中 $S$ 是已选推荐集合$\alpha0.7$ 平衡相关性与多样性。def get_top_n_recommendations(user_id, ratings_matrix, item_sim_matrix, city_geo_df, n10, alpha0.7): 为用户生成 Top-N 旅游城市推荐 :param user_id: int, 0-based 用户索引 :param ratings_matrix: csr_matrix :param item_sim_matrix: np.ndarray :param city_geo_df: pd.DataFrame, 城市地理信息 :param n: int, 推荐数量 :param alpha: float, 相关性权重 :return: list of tuples (city_id, pred_score, diversity_bonus) # 步骤1获取用户已访问城市非零评分 user_ratings ratings_matrix[user_id].toarray().flatten() visited_cities np.where(user_ratings 0)[0] # 步骤2预测所有未访问城市的评分 all_cities list(range(ratings_matrix.shape[1])) candidates [c for c in all_cities if c not in visited_cities] pred_scores [] for city in candidates: pred predict_rating(user_id, city, ratings_matrix, item_sim_matrix, k20) pred_scores.append((city, pred)) # 步骤3MMR 多样性排序 recommendations [] selected [] # 已选城市ID列表 for _ in range(n): if not pred_scores: break # 计算每个候选的 MMR score mmr_scores [] for city, pred_score in pred_scores: if not selected: diversity_bonus 0.0 else: # 计算与已选城市的最小地理距离相似度越远越多样 geo_sims [geo_similarity(city, s, city_geo_df) for s in selected] diversity_bonus 1.0 - max(geo_sims) # 距离越远diversity_bonus越高 mmr_score alpha * pred_score (1 - alpha) * diversity_bonus mmr_scores.append((city, pred_score, diversity_bonus, mmr_score)) # 选 MMR score 最高的 best max(mmr_scores, keylambda x: x[3]) recommendations.append((best[0], best[1], best[2])) selected.append(best[0]) # 从候选池移除已选 pred_scores [(c, s) for c, s in pred_scores if c ! best[0]] return recommendations # 示例为用户0生成 Top-5 推荐 top5 get_top_n_recommendations(0, ratings_matrix, item_sim_matrix, city_geo, n5, alpha0.7) for i, (city_id, pred_score, div_bonus) in enumerate(top5, 1): print(f{i}. 城市ID {city_id}: 预测评分 {pred_score:.2f}, 多样性加分 {div_bonus:.2f})注意get_top_n_recommendations中diversity_bonus 1.0 - max(geo_sims)将地理相似度转化为多样性奖励——与已选城市越不相似奖励越高。alpha0.7表示优先保证推荐相关性但强制注入一定多样性。此逻辑可直接嵌入推荐 API无需额外训练。4. 协同过滤效果验证离线评估指标、A/B测试设计与常见失效场景排查模型上线前必须量化效果。协同过滤的评估不能只看 RMSE均方根误差因为 RMSE 优化的是评分预测精度而产品关心的是“用户是否点击/预订推荐城市”。本节提供一套完整的验证闭环从离线指标计算、A/B测试分流设计到三个最常导致协同过滤失效的场景及修复方案。4.1 离线评估用 RecallK 和 NDCGK 替代 RMSERMSE 在稀疏数据上易受均值主导如所有预测都接近全局均值RMSE 也能很低无法反映推荐排序质量。旅游推荐应关注Top-K 推荐命中率即用户真实访问的城市是否出现在推荐列表前 K 位。核心指标是RecallK用户真实访问城市中有多少比例出现在 Top-K 推荐里。公式$\text{RecallK} \frac{| \text{Relevant} \cap \text{RecommendedK} |}{| \text{Relevant} |}$NDCGKNormalized Discounted Cumulative Gain考虑推荐位置排在第1位的命中比第K位价值更高。公式$\text{NDCGK} \frac{\text{DCGK}}{\text{IDCGK}}$其中 $\text{DCGK} \sum_{i1}^{K} \frac{2^{rel_i} - 1}{\log_2(i1)}$$rel_i$ 是第 $i$ 位的相关性命中1否则0。import numpy as np def evaluate_recall_ndcg(y_true_list, y_pred_list, k10): 批量计算 RecallK 和 NDCGK :param y_true_list: list of sets, 每个用户的真实访问城市集合city_id :param y_pred_list: list of lists, 每个用户的 Top-K 推荐城市列表city_id :param k: int, K值 :return: tuple (recallk, ndcgk) recalls, ndcgs [], [] for y_true, y_pred in zip(y_true_list, y_pred_list): # RecallK hit_set set(y_true) set(y_pred[:k]) recall len(hit_set) / len(y_true) if y_true else 0.0 recalls.append(recall) # NDCGK dcg 0.0 for i, city in enumerate(y_pred[:k]): rel 1.0 if city in y_true else 0.0 dcg (2 ** rel - 1) / np.log2(i 2) # i2 因为 log2(1) 未定义 # IDCG假设所有相关项都在最前 idcg 0.0 sorted_rel sorted([1.0]*len(y_true) [0.0]*(k-len(y_true)), reverseTrue) for i, rel in enumerate(sorted_rel[:k]): idcg (2 ** rel - 1) / np.log2(i 2) ndcg dcg / idcg if idcg 0 else 0.0 ndcgs.append(ndcg) return np.mean(recalls), np.mean(ndcgs) # 示例用测试集计算 # y_true_list [set([101, 102]), set([201, 202, 203])] # 用户真实访问 # y_pred_list [[101, 301, 401], [201, 202, 501]] # 用户 Top-3 推荐 # recall, ndcg evaluate_recall_ndcg(y_true_list, y_pred_list, k3) # print(fRecall3: {recall:.3f}, NDCG3: {ndcg:.3f})提示evaluate_recall_ndcg中y_true_list应来自测试期用户真实行为如最近7天访问记录y_pred_list是模型对同一时期用户的预测。k10是旅游推荐常用值——用户通常只浏览前10个推荐。若Recall10 0.15说明模型基本失效需检查数据或逻辑。4.2 A/B测试设计流量分桶、核心指标与灰度发布策略离线指标达标后必须通过线上 A/B 测试验证业务价值。旅游推荐的 A/B 测试需关注三个层级指标层级指标目标监控频率曝光层推荐位点击率CTR≥ 基线 110%实时转化层点击后城市页停留时长、收藏率≥ 基线 105%小时级业务层点击后7天内该城市订单量≥ 基线 103%日级流量分桶规则实验组5%流量使用新协同过滤模型对照组5%流量使用旧规则模型如热门城市轮播兜底组90%流量使用当前线上模型确保主流量稳定。灰度发布步骤第1天实验组仅对新注册用户开放验证冷启动逻辑第3天扩展至近30天活跃用户验证长尾覆盖第7天全量5%流量监控核心指标72小时若 CTR 提升 ≥2%且订单量无下降则扩至20%否则回滚。注意A/B 测试必须隔离推荐模块避免其他功能如搜索排序干扰。所有指标需通过埋点统一采集字段包括user_id,rec_position,city_id,click_ts,order_ts。4.3 三大失效场景排查表从数据到代码的逐层诊断协同过滤在旅游场景中最常因以下三类问题失效按发生概率排序并提供诊断命令场景表征根本原因诊断命令修复方案用户评分均值漂移Recall10持续低于 0.05且预测分集中在 3.2~3.5用户打分习惯随时间变化如疫情后普遍打高分导致皮尔逊相似度失真echo 用户均值分布: python -c import pandas as pd; dfpd.read_csv(u.data,sep\t,headerNone); print(df.groupby(0)[2].mean().describe())在predict_rating中改用Z-score 标准化替代均值中心化$(r_{uj} - \bar{r}_u) / \sigma_u$物品相似度矩阵退化item_sim_matrix中 90% 以上值为 0 或 1物品间共同评分用户过少5人pairwise_distances返回 NaN 后被nan_to_num置 0python -c import numpy as np; mnp.load(item_sim.npy); print(零值占比:, np.mean(m0))增加最小共同用户阈值仅当 Top-N 推荐全为热门城市推荐列表中前10名城市8个是北京/上海/广州相似度计算未加权热门城市因被更多人评分而天然相似度高python -c import pandas as pd; dfpd.read_csv(u.data,sep\t,headerNone); print(各城市评分频次:, df[1].value_counts().head(10))在相似度公式中加入Inverse User FrequencyIUF$\text{sim}(i,j) \text{pearson}(i,j) \times \log(N /提示执行诊断命令时务必使用与训练相同的原始数据文件如u.data。若发现“物品相似度矩阵退化”立即检查ratings_matrix.nnz / (n_users * n_items)是否 0.05——低于此值协同过滤已不适用应转向内容过滤或图神经网络。本文还有配套的精品资源点击获取

看完文章,想为自己的企业也做一次专业网站诊断?

尧图顾问免费为您评估现有网站,并给出建站/改版建议与报价方案。

免费获取方案