尧图网络 高端网站定制 · 原创设计
免费咨询热线
400-888-6620
免费获取方案
旅游推荐系统协同过滤实战:从评分矩阵到算法调优与排错
简介这是一篇西南财经大学学士学位毕业论文面向旅游推荐场景系统阐述协同过滤算法的原理、实现与效果评估可服务于毕业设计参考、算法学习或推荐系统研究。论文内容涵盖数据预处理、用户画像构建、推荐算法模块设计、系统性能优化与测试深入讨论用户-用户与物品-物品两种协同过滤思路以及冷启动、稀疏性等典型问题的应对策略。资源包共1个文件为docx格式体积约37KB便于直接阅读与二次编辑。目前已有1049人学习下载。读者可借论文提供的研究框架快速掌握从系统总体设计到推荐结果展示的完整流程并借鉴其实验设计、指标评估与结果分析方法用于自身算法改进、论文写作或实际系统开发。1. 旅游推荐系统为什么绕不开协同过滤数据越攒越多效果反而越准做旅游推荐的开发者大多经历过这个尴尬用户收藏了一堆冷门景点首页推的还是热门大图。推荐逻辑只看景点像不像没看人都往哪走。协同过滤恰好反过来——只看用户之间的行为交集数据越多推荐越像懂你。这套方案上手不重一张评分矩阵、一个相似度公式、一个 Top-N 输出核心代码不到两百行。真正让项目翻车的多是数据稀疏、热门霸榜、冷启动三个老问题。下文按选型、建矩阵、写算法、排错的顺序完整拆解代码可直接抄。适合正在做课程设计、毕业设计或想在小团队快速落地推荐模块的开发者。读完你能回答三个问题这方向值不值得投入、相似度用哪个公式、线上效果变差先查哪一环。2. 先选路线再写代码三种协同过滤玩法与旅游场景的适配性2.1 基于用户的协同过滤把口味相近的旅友变成推荐依据基于用户的协同过滤UserCF核心一句话找到和目标用户行为最像的 K 个用户把这 K 个人评分过、目标用户还没去过的景点推荐出去。它模拟的是线下口碑传播——你问身边朋友最近去哪玩朋友的判断依据是你们过往出游偏好的一致程度而不是景点宣传页写了什么。落地流程拆成四步第一步构建用户-景点评分矩阵用户一行、景点一列第二步计算用户两两之间的相似度第三步为每个目标用户选出相似度最高的 K 个邻居第四步用邻居的评分加权预测目标用户对没消费过景点的兴趣分取 Top-N 输出。这四步每一项都有对应的数据结构和公式第四章会给完整实现。旅游场景对 UserCF 有一个天然制约普通人一年出游一到三次能产生行为记录的景点通常只有个位数矩阵稀疏度普遍在 95% 以上。相似度算出来普遍偏低K 近邻里容易混进大量只有一个共同景点的假邻居。兴趣漂移也很明显——学生时代喜欢穷游和青旅工作几年后转向度假型目的地拿长期历史行为算相似度会明显滞后。所以我的选型判断比较直接UserCF 适合用户量小、行为记录足够密集的团队或课程设计可解释性能落成一句和你口味相近的用户也喜欢这些地方用户量一旦上到几十万在线算相似度的成本会迅速失控必须改成离线预计算。数据规模没到之前UserCF 调试链路最短最不劝退。2.2 基于物品的协同过滤景点之间的共现关系更稳定基于物品的协同过滤ItemCF换了个计算对象先算景点和景点之间的相似度再依据用户历史上喜欢过的景点把和这些景点最像的新景点推荐出去。景点相似度不是看属性标签是否同类而是看行为共现——被同一个人收藏或下单过的两个景点在用户认知里就是相关的。这个思路在旅游场景有天然优势。景点库的增速远慢于用户和行为的增速物品相似度矩阵完全可以离线算好存起来线上推荐退化成一次矩阵查表和排序响应能压到毫秒级。可解释性也强因为你收藏过某海滨城市所以推荐相邻的古镇这句话运营看得懂用户也愿意点。代价是 ItemCF 更偏向和已有兴趣相似的景点推荐容易收窄。用户只收藏过古城类景区ItemCF 大概率一直推古城缺少把用户带出舒适区的探索性。这个问题单靠 ItemCF 无解后面章节会给出多样性和混合策略的处理。真实数据里最典型的就是海滨城市和附近古镇的强共现这一类关联用 ItemCF 抓得比人工配置准得多。2.3 选型判断一张表和三个条件定方向对比维度UserCFItemCF适用数据形态用户数少、物品数多用户数多、物品数少相似度计算成本用户相似度在线算成本高物品相似度离线算线上查表推荐多样性较好邻居能带出新品类偏差容易困在已有兴趣里可解释性弱兴趣相似的人很模糊强因为喜欢 X推荐 Y新景点冷启动没有行为时无法推荐可借助内容特征快速兜底具体到旅游推荐系统我一般用三个条件定方向第一景点数量是否明显少于用户数量是则优先 ItemCF第二是否需要给新注册用户立刻出推荐注意两个算法都解决不了冷启动必须配规则兜底第三运营是否频繁上架新景点新景点没有任何行为记录ItemCF 至少还能借内容相似度做冷启动。顺带说一句经常被问到的矩阵分解。如果后面追求离线指标继续提升可以上 SVD 或隐语义模型它确实能缓解一部分稀疏问题但调参成本和黑盒属性都比近邻法高不少对课程设计来说还容易在答辩时被追问到答不上来。我的建议很务实先把 UserCF 或 ItemCF 跑通把数据切分和评估流程立起来再考虑要不要换模型。3. 评分矩阵怎么造旅游数据的采集、转换与稀疏处理3.1 显式评分几乎不存在得靠隐式反馈拼出评分协同过滤的输入是评分矩阵但旅游 App 里真正让用户打星的地方少得可怜。订单、收藏、搜索详情、浏览、分享这些隐式反馈才是主要数据源。直接把隐式反馈当评分用会失真第一件事是建立行为权重映射。我的做法是先列一个权重表把不同行为按对用户兴趣的指示强度打分。下单最重因为代表真实消费意愿收藏次之说明做攻略时认可了这个景点浏览最轻误点成本几乎为零。权重不用太精细稳定比精确重要改一次权重要重新评估一轮成本不低。行为类型权重说明下单/预订5.0强意向直接产生消费收藏/加入行程3.0中等意向做攻略时认可搜索详情/停留超时2.0弱意向可能在做对比普通浏览1.0只做加分项不做主信号同一个用户对同一个景点有多条行为时同类型行为取最大值防刷跨类型行为求和得到最终评分。这套映射在模拟项目X里验证过比直接用浏览次数当评分稳定得多至少不会出现一个人刷新了二十次详情页就被当成重度收藏用户这种失真。3.2 把行为日志转成评分矩阵一段可复用的 Python 脚本下面这段代码处理的是最常见的 CSV 行为日志三列user_id、poi_id、action。跑完直接输出 scipy 稀疏矩阵供下一步相似度计算使用。import pandas as pd from scipy.sparse import csr_matrix # 行为权重映射稳定比精确重要改动后要重新评估 weight_map { order: 5.0, # 下单/预订强意向 collect: 3.0, # 收藏/加入行程中等意向 detail: 2.0, # 搜索详情/停留超时弱意向 view: 1.0, # 普通浏览只做加分 } df pd.read_csv(user_behavior.csv) # 三列user_id, poi_id, action df[score] df[action].map(weight_map) # 日志里出现权重表外行为时直接丢弃避免脏数据混进来 if df[score].isna().any(): df df.dropna(subset[score]) # 同一用户对同一景点同行为取最大防刷跨行为求和保留多重信号 df df.groupby([user_id, poi_id, action], as_indexFalse)[score].max() df df.groupby([user_id, poi_id], as_indexFalse)[score].sum() # 过滤噪声用户行为少于3次、景点交互少于5次直接删掉 user_cnt df.groupby(user_id)[poi_id].count() poi_cnt df.groupby(poi_id)[user_id].count() df df[df[user_id].isin(user_cnt[user_cnt 3].index)] df df[df[poi_id].isin(poi_cnt[poi_cnt 5].index)] # 转成数字索引交给 scipy 存稀疏矩阵省内存 df[uid] df[user_id].astype(category).cat.codes df[pid] df[poi_id].astype(category).cat.codes matrix csr_matrix( (df[score], (df[uid], df[pid])), shape(df[uid].nunique(), df[pid].nunique()), )几个参数需要根据数据规模调整。过滤阈值是最关键的低于 3 次行为的用户和低于 5 次交互的景点我测试过不删的话矩阵稀疏度会从 95% 继续恶化到 99% 以上后面相似度计算基本失效数据量大的场景可以把阈值提高到 5 和 10把弱信号再压一压。权重映射表里的 detail 行为如果日志里没有直接删掉对应行即可map 找不到的键会置 NaNdropna 会兜住。astype(category).cat.codes是把字符串 ID 转成连续的整数索引这样 csr_matrix 的 shape 才能对上。这里有一个细节必须是过滤完再转编码不然过滤掉的用户会留下空洞索引虽然不影响计算但相似度矩阵会无谓变大。3.3 矩阵存储和补零陷阱内存与相似度的第一个分岔口评分矩阵构建出来后最忌讳的写法是用 DataFrame 的 pivot 生成稠密矩阵。用户数和景点数到万级稠密矩阵占用就是好几个 GB开发机直接卡死。用 scipy.sparse 只存非零值同样规模内存能降两个数量级后续相似度计算也快得多。还有个隐藏更深的坑很多教程会把矩阵缺失位置补成 0 再算余弦相似度这在旅游数据里是致命的。用户没去过的景点远比去过的多补零会让都没去过变成两个用户最大的共同特征相似度被大量无效共现带偏。正解是计算时只统计两个用户都有评分的维度也就是用调整余弦或者 Jaccard 变体下一章展开讲。注意补零在矩阵分解类算法里是常规操作但在近邻类协同过滤里会严重污染相似度。动手前先确认自己属于哪个算法族再决定要不要补零。4. 核心实现基于用户的协同过滤完整代码与参数调优4.1 相似度公式怎么选余弦、皮尔逊与调整余弦的差异相似度计算是协同过滤的心脏。三个常用公式的差别要分清。余弦相似度只看两个用户评分向量的夹角不管评分绝对值高低。皮尔逊相关系数在余弦基础上先减去每个用户的平均分消除有人习惯打 3 分、有人习惯打 5 分的尺度偏差。调整余弦则是先对每个用户评分做均值中心化再算余弦本质上和皮尔逊等价实现起来更直观。旅游场景的评分大部分是行为映射出来的不是用户亲手打的星。这种情况下我一般这样选映射评分用余弦加共现过滤真实打分数据用皮尔逊。原因很简单映射评分本身就带了行为权重倾向再做均值中心化会把不同行为的权重差异抹掉反而失真。还有个通用技巧无论用哪个公式都只统计两个用户共同有行为的景点数量作为分母修正。单纯余弦会把都去过三个景点和都去过三十个景点的用户对一视同仁后者显然更值得信任。共现次数加权能把这个置信度差异补回来。4.2 完整实现相似度矩阵、K 近邻加权与 Top-N 生成下面是 UserCF 的最小完整实现输入上一章的稀疏矩阵输出每个用户的 Top-N 推荐列表包含相似度计算、邻居筛选、加权预测三个模块。import numpy as np from scipy.sparse import csr_matrix def compute_cosine_similarity(matrix): 用户间余弦相似度只统计共同有行为的维度 # 评分转二值有行为记 1没行为记 0 binary (matrix 0).astype(np.float32) # 共现矩阵 二值矩阵乘二值矩阵转置 co_occur binary binary.T # 每个用户的行为数量开方后做归一化分母 norm np.sqrt(np.asarray(binary.sum(axis1)).ravel()) sim co_occur.toarray() / (norm[:, None] * norm[None, :] 1e-8) # 自己和自己相似度置 0避免被选成最近邻 np.fill_diagonal(sim, 0.0) return sim def predict_and_recommend(user_id, matrix, sim, k20, top_n10): K 近邻加权预测输出 Top-N 景点索引 u_vec matrix.getrow(user_id).toarray().ravel() # 取相似度最高的 K 个邻居 neighbors np.argsort(sim[user_id])[::-1][:k] # 负相似度邻居没有参考价值直接丢掉 neighbors [n for n in neighbors if sim[user_id, n] 0] if not neighbors: return [] # 候选景点 邻居有评分、目标用户没去过的景点 n_vec np.asarray(matrix[neighbors].sum(axis0)).ravel() candidates np.where((n_vec 0) (u_vec 0))[0] scores [] for poi in candidates: # 加权平均相似度高的人评分权重更大 num sum(sim[user_id, n] * matrix[n, poi] for n in neighbors if matrix[n, poi] 0) den sum(abs(sim[user_id, n]) for n in neighbors if matrix[n, poi] 0) if den 0: scores.append((poi, num / den)) scores.sort(keylambda x: x[1], reverseTrue) return [poi for poi, _ in scores[:top_n]]几个关键参数说明。k 是近邻数量取 20 到 50。旅游数据稀疏时我测试过k 小于 20 邻居里会出现大量假相似用户预测方差很大k 大于 50 会混入太多弱相关用户推荐质量反而下降。top_n 是推荐列表长度移动端首屏一般 6 到 8 个Web 端可以给 10 个。这段代码里的细节都值得注意相似度归一化分母加了 1e-8 防除零邻居筛选要求相似度大于 0负相似度会对预测产生反效果预测公式里分母是相似度绝对值之和而不是邻居数这样最像的邻居在预测里话语权最大符合越像的人越有发言权的直觉。如果某个候选景点的有效邻居少于 2 个说明它只在极少数邻居身上出现过我会在业务里直接丢弃避免单点样本带偏结果。4.3 调优顺序先调稀疏过滤再调 K最后调 Top-N不少同学拿到代码第一件事就是调 K这是顺序错误。我的调参顺序是反的先回去调数据过滤阈值保证相似度分布有区分度再看 K 的值最后才看 Top-N 长度和展示策略。数据质量决定相似度上限K 只是在逼近这个上限。判断标准就一条打印相似度矩阵的直方图。如果 90% 的相似度集中在 0 到 0.05说明矩阵太稀加 K 毫无意义应该回去降低过滤阈值或者换相似度定义如果分布能铺到 0.3 以上说明共现信号够了这时候调 K 才会见效。这一步是血泪经验我第一个版本就是顺序搞反白调了两天 K最后发现是过滤阈值把有效边全削掉了。5. 协同过滤在旅游场景的五大翻车现场现象、原因与排查办法这一章列的都是我在模拟项目X里实际踩过的坑。推荐系统有时候看起来像玄学但绝大多数翻车都能定位到具体环节按现象、原因、解决三个步骤排查最省时间。5.1 相似度矩阵大面积为零推荐列表全空现象用户量五千、景点量一千五行为日志不到两万条compute_cosine_similarity 跑完相似度超过 0.1 的用户对不足 1%推荐结果要么为空要么完全随机。原因旅游行为天然低频用户一年两三次出行能产生行为的景点交集很小加上前面过滤阈值设得过高统计上能留下的有效信号所剩无几。解决把用户最少行为数从 5 降到 3、景点最少交互从 10 降到 5先保证有足够边参与计算再把相似度定义换成 Jaccard 变体分母用两个用户去过的景点并集大小如果还不行按城市维度限制相似度计算范围同城用户行为密度能高一个量级。5.2 热门景点霸榜推荐结果像运营位广告现象不管给哪个用户推荐Top-N 里永远是那几个头部景点。离线看精确率还不错但用户根本不买账因为首页本来就有这些推荐系统成了热门榜的搬运工。原因热门景点评分基数大在加权求和预测里天然占优。下单权重设为 5 分后热门景点几乎每个邻居都打过单预测分被拉满冷门景点完全没机会。解决给物品加流行度惩罚项在预测分上乘以一个惩罚系数行为人数越多的景点系数越小或者在候选集阶段直接删掉行为人数超过 P95 的头部景点把它们当作已知热门排除。我两个都做先删后惩罚效果最稳。5.3 新用户没有历史行为协同过滤直接失联现象新注册用户没有一条行为记录找邻居时相似度全是 0推荐接口只能返回空列表或者退回死气沉沉的热门榜。原因协同过滤的本质是从历史行为里找规律没有历史就没有规律可挖。这不是参数能解决的是算法族本身的边界。解决分层兜底。新用户第一屏返回城市热门榜和当季主题榜等用户攒到 3 到 5 条行为再切协同过滤。过渡参数值得记行为数 1 到 3 条时用单条行为的 ItemCF 推荐也就是只推和用户最近收藏景点相似的景点超过 3 条再切 UserCF。这个阈值设太高浪费行为数据设太低推荐不稳定3 是一个稳妥的起点。5.4 相似度矩阵内存爆炸开发机直接卡死现象用户量到五万稠密相似度矩阵五万乘五万float32 存储就是 10GB还没跑到推荐生成就 OOM进程直接被杀。原因把相似度矩阵当稠密矩阵存了或者计算过程中用 toarray() 把稀疏结构展开。即使存非零值用户量上来后非零对数量也很可观一样会撑爆内存。解决相似度矩阵按行分块计算每算完一批用户的相似度就落盘上线后如果用户量持续涨直接转 ItemCF离线预计算物品相似度线上只做查表。另外把相似度低于 0.05 的值剪掉剪完稀疏度能降一个数量级这一步几乎无损。5.5 离线指标很好看线上点击率反而跌了现象离线测试精确率、召回率都不错灰度上线后点击率比原来的热门榜还低用户反馈推得不准。原因离线评估用了随机切分把用户历史行为随机分成训练集和测试集。但旅游行为有强时序性——用户先搜攻略、再收藏、最后下单随机切分等于让模型偷看了未来行为评估分数虚高。解决评估集必须按时间切分前 80% 时间的行为做训练、后 20% 做测试同时把覆盖率、多样性纳入观察指标不能只盯精确率。线上为了防止推荐结果收窄还要加品类打散规则连续推荐列表里不能超过 3 个同品类景点。6. 让推荐真正可用的最后一公里评估、混合与上线验证6.1 离线评估的基本盘精确率、召回率、覆盖率离线评估一定要用时间切分后的数据不能随机切分。随机切分会把用户先搜索后下单的时序打乱等于让模型偷看答案。指标选三个就够精确率推荐列表里用户真正消费了的比例召回率用户实际消费的景点被捞到的比例覆盖率推荐覆盖了多大比例的景点库。旅游场景推荐列表短精确率比召回率更贴近业务。三个指标和 K 的关系很直接K 越大召回率越高、精确率越低。找平衡点要盯覆盖率的拐点——K 从 20 涨到 30 时覆盖率还在快速上升说明推荐在探索新景点涨到 50 后基本不动继续加 K 只会稀释精确率。6.2 混合策略协同过滤为主、规则和内容兜底可上线的推荐服务从来不是单一协同过滤撑起来的。我用三层结构规则层处理冷启动和强时效需求比如节假日专题、城市热门榜协同过滤层处理有历史行为用户的主推荐多样性层对前两层结果做品类打散、去重、同城市过滤。三层加权融合后统一排序输出。权重建议协同过滤占 60% 到 70%规则层占 20% 到 30%剩下留给运营配置位。这套结构在模拟项目X里跑过点击率比纯协同过滤提升约两成主要贡献就是冷启动用户被规则层接住了没有在首屏就流失。6.3 上线后第一个要盯的指标推荐结果的多样性我踩过的最后一个坑是只盯点击率。协同过滤有很强的滚雪球效应——用户点得越多模型越推同类推荐列表越来越窄。上线后每天盯两个信号推荐列表里同品类景点占比是否连续走高单用户连续几天的推荐重合度是否超过 60%。后者一旦超线说明模型收敛太严重该调大探索权重。我现在养成的习惯是每次只动一个变量把变动前后一周的点击率、覆盖率、品类占比记在同一张表里。推荐系统是典型的多变量耦合问题同时改 K 和融合权重出了问题你根本不知道是哪一项引起的。这套排查习惯我保持了两年少走了很多弯路希望帮到你。本文还有配套的精品资源点击获取
RELATED

相关推荐

Python端到端网络舆情分析系统实战:采集→情感→热点→报告

Python端到端网络舆情分析系统实战:采集→情感→热点→报告

简介:这是一套面向本科毕业设计与Python初学者的网络舆情分析系统完整实现,聚焦社交媒体数据采集、情感分析与可视化展示等典型应用场景。资源包含可直接运行的前后端代码、MySQL数据库脚本及配套工具,技术栈覆盖HTML前端界面、Python后端逻辑…

📅 2026/10/10 23:14:30
带差分隐私的协同过滤推荐:Python毕设资源与实验解析

带差分隐私的协同过滤推荐:Python毕设资源与实验解析

简介:面向计算机相关专业学生与推荐系统入门研究者的毕业设计资源包,基于Python实现带差分隐私的协同过滤推荐系统,聚焦推荐流程中的用户隐私保护。从差分隐私与协同过滤的理论背景入手,梳理国内外研究现状,并阐述差分…

📅 2026/10/10 23:14:30
VS Code的C/C++ IntelliSense失灵怎么办?从配置原理到实战排查

VS Code的C/C++ IntelliSense失灵怎么办?从配置原理到实战排查

“VSCode装好了C/C插件,IntelliSense却像个木头一样,敲了半天代码一个提示都不弹”——这大概是C/C开发者日常里最让人恼火的场景之一。其他语言补全得飞起,一到C/C就哑火,头文件路径报红一片,跳转定义也没反应&#x…

📅 2026/10/10 23:09:30
MORE NEWS

更多资讯

📰

黄铁矿微量元素数据挖掘:PCA、随机森林与PLS-DA复现代码实战

/* MD / 富文本中的 .toc(含博客园搬家等嵌套结构);.toc-box 在侧栏,不受影响 */#content_views .toc,/* 编辑器常在目录前后插入空 p(:empty 仍占 20px),一并去掉避免顶空隙 */#content_views.markdown_views > p:empty:has(+ .toc),#content_views.markdown_views …

📰

煤矿井下视频监控系统设计方案:防爆选型、光纤环网与供电防雷工程落地指南

/* MD / 富文本中的 .toc(含博客园搬家等嵌套结构);.toc-box 在侧栏,不受影响 */#content_views .toc,/* 编辑器常在目录前后插入空 p(:empty 仍占 20px),一并去掉避免顶空隙 */#content_views.markdown_views > p:empty:has(+ .toc),#content_views.markdown_views …

📰

安装cursor-vip:免费使用cursor pro

/* MD / 富文本中的 .toc(含博客园搬家等嵌套结构);.toc-box 在侧栏,不受影响 */#content_views .toc,/* 编辑器常在目录前后插入空 p(:empty 仍占 20px),一并去掉避免顶空隙 */#content_views.markdown_views > p:empty:has(+ .toc),#content_views.markdown_views …

📰

RS485网关还是Modbus网关?老设备联网改造选型避坑指南

/* MD / 富文本中的 .toc(含博客园搬家等嵌套结构);.toc-box 在侧栏,不受影响 */#content_views .toc,/* 编辑器常在目录前后插入空 p(:empty 仍占 20px),一并去掉避免顶空隙 */#content_views.markdown_views > p:empty:has(+ .toc),#content_views.markdown_views …

📰

Cursor AI提示词设计建议:构建全覆盖测试用例生成体系(测试用例设计场景安全性能篇)

/* MD / 富文本中的 .toc(含博客园搬家等嵌套结构);.toc-box 在侧栏,不受影响 */#content_views .toc,/* 编辑器常在目录前后插入空 p(:empty 仍占 20px),一并去掉避免顶空隙 */#content_views.markdown_views > p:empty:has(+ .toc),#content_views.markdown_views …

📰

ST语言位操作指令WAND/WOR/WXOR:设备联锁逻辑的掩码化改造

/* MD / 富文本中的 .toc(含博客园搬家等嵌套结构);.toc-box 在侧栏,不受影响 */#content_views .toc,/* 编辑器常在目录前后插入空 p(:empty 仍占 20px),一并去掉避免顶空隙 */#content_views.markdown_views > p:empty:has(+ .toc),#content_views.markdown_views …

TODAY

今日更新

THIS WEEK

本周精选

THIS MONTH

本月热门

读完文章,想聊聊您的网站?

告诉我们您的行业与需求,资深顾问一对一梳理方案与报价,全程免费。

📞 💬