ARTICLE DETAIL

资讯详情

深耕郑州网站建设与运营推广的一线实战洞察。

基于大数据的个性化旅游推荐系统实战:Hive+Spark+ItemCF全链路

基于大数据的个性化旅游推荐系统实战:Hive+Spark+ItemCF全链路 说实话大数据和推荐系统这两个词这几年在各类招聘帖和毕设选题列表里出现频率高得吓人。但真要让一个学生或者刚转行的工程师自己动手把一个完整的个性化旅游推荐系统从零搭起来大多数人其实是发懵的——不是不懂协同过滤公式而是不知道数据从哪来、流程怎么串、集群怎么部署、前端怎么展示。我最近刚好把一个基于大数据技术的个性化旅游推荐系统完整走了一遍从数据清洗、Hive离线数仓、Spark特征计算到Python实现ItemCF推荐算法再用FlaskECharts把结果可视化出来整条链路趟完之后有不少心得。这篇文章就把项目拆解思路、技术选型逻辑、实操细节和踩坑记录完整分享出来不管你是准备做毕设、冲大数据竞赛还是单纯想练手推荐系统这套项目思路都可以直接抄作业。1. 项目整体设计与技术选型思路1.1 个性化旅游推荐到底在解决什么问题传统旅游平台或者说早期OTA网站本质上就是一个搜索工具。用户输入目的地、时间、价格区间系统把符合条件的景点和路线列出来用户自己慢慢筛。这种方式在海量数据面前效率很低因为根本没有“个性化”可言——同一个三亚小学生和背包客看到的结果完全一样。个性化旅游推荐系统要解决的核心问题可以用一句话概括在用户没有明确表达需求或者表达不完整的情况下根据他的历史行为、画像特征和当前场景主动推给他可能感兴趣的旅游产品和景点路线。放到技术层面这个问题会被拆成三层第一层是数据层要回答“用什么数据来描述用户和景点”。用户侧有注册信息、浏览记录、收藏行为、下单记录、评分反馈景点侧有城市、类别、标签、热度、价格、评分。第二层是算法层要回答“怎么计算用户对某个景点的感兴趣程度”。最基础的做法是协同过滤算相似用户或者相似景点进阶一点会引入用户画像、上下文特征甚至深度学习排序模型。第三层是应用层要回答“推荐结果以什么形式展示给用户”。Web页面、App推送、小程序卡片不同的展示形态对应不同的接口和数据需求。很多初学者一上来就盯着算法层刷公式忽略了数据层和应用层结果就是模型跑通了但没有任何说服力。我这个项目的定位很明确做一个数据链完整、算法有解释性、前端能看见效果的最小闭环系统而不是一个纯算法玩具。1.2 大数据架构的四个层次怎么落到这个项目里大数据架构教科书里经常讲四个层次数据采集层、数据存储与计算层、数据服务层、数据应用层。做项目的时候很多人觉得这只是概念但真正设计系统时这四个层次刚好对应了工程上的四条线缺一不可。数据采集层在旅游推荐场景里对应的是日志埋点和业务数据抽取。真实企业里会用Flume监听Web服务器的访问日志用Sqoop从关系型数据库抽业务表实时流则走Kafka。我在毕设级别的项目里做了简化直接使用公开的旅游数据集比如携程景点评分数据、UCI旅游偏好数据手动构造一部分用户行为模拟数据再用Python脚本做数据生成和导出相当于用脚本模拟了埋点采集的过程。采集的数据统一落地到HDFS。数据存储与计算层对应的是Hadoop生态。HDFS负责原始数据的分布式存储Hive做离线数仓的建模和ETLSpark负责需要复杂计算的场景特征工程、推荐结果的批量计算。这一层是整个项目里工作量最大的部分也是最容易出问题的地方。数据服务层在旅游系统里就是推荐引擎对外提供的接口能力。比如离线算好的推荐结果要存储到Redis或者MySQL里在线接口通过查询缓存快速返回TopN列表。我这个项目用MySQL存储用户和景点的基础数据推荐结果则通过Flask的RESTful接口暴露出来。数据应用层最直观就是可视化页面。我选了ECharts做前端图表展示包括热门景点Top10柱状图、用户偏好雷达图、推荐结果列表页面虽然不是重点但它是整个系统“能不能让评委/导师一眼看懂”的关键。1.3 技术栈对比为什么选SparkHive而不是纯Hadoop MapReduce项目最纠结的一个决定就是计算引擎选纯MapReduce还是Spark。很多课程里教的是MapReduceWordCount谁都会写但放到推荐系统场景里MapReduce的短板非常明显一个完整的ItemCF推荐流程至少需要计算用户-物品评分矩阵、物品间相似度矩阵、TopN排序涉及多轮MapReduce作业。每轮作业都要读写HDFSI/O开销巨大而且代码写起来非常绕。Spark的优势在于RDD可以常驻内存多轮计算可以基于缓存数据反复迭代尤其适合协同过滤这种需要反复扫描评分矩阵的算法。另外Spark MLlib里内置了ALS协同过滤模型可以直接调库。但我最终没有直接用ALS而是自己在PySpark上实现了ItemCF逻辑。原因有两点第一ALS是隐语义模型结果偏“黑盒”毕设答辩时不好解释第二自己实现ItemCF可以对相似度计算、TopN截断、过滤逻辑做精细控制而且在论文里可以写清楚每一步的数学原理。Hive在这个项目里的定位是数仓和统计报表。Hive不擅长做复杂的矩阵运算但做数据清洗、去重、聚合统计非常顺手。举一个具体场景原始评分表里有大量重复记录同一个用户对同一个景点提交了多次评分这种情况下我用一条HiveQL做去重并保留最新记录效率远高于手写Spark逻辑。合理的分工是Hive管ETL和统计Spark管特征计算和推荐模型Python管算法原型验证。1.4 项目环境清单我实际跑通这套系统用的是三台虚拟机组成的Hadoop集群节点配置2核4G操作系统是Debian系。如果你只是单机学习完全可以伪分布式部署甚至用Docker起三个容器冒充集群。软件版本我列一下照着这个清单配环境基本不会踩大坑Hadoop 3.3.4HDFS YARNHive 3.1.3MetaStore用MySQLSpark 3.4.0on YARN模式Python 3.8 pandas numpy FlaskMySQL 8.0存业务数据和推荐结果ECharts 5.x前端图表Redis 7.x在线推荐缓存可选版本选择的原则只有一个不要用最新的。Hadoop 3.3.x和Spark 3.4.x配套比较成熟网上资料也多踩坑容易找到解决方案。集群时间不同步会导致Kerberos和HDFS问题所以我都会在部署前统一用NTP同步这个细节后面会再讲。2. 数据获取与预处理推荐系统真正的地基2.1 旅游数据集长什么样做推荐系统最容易忽略的一件事算法决定的是上限数据决定的是下限。我在项目里用了两份数据。第一份是景点基础信息表包含景点编号、名称、城市、所属类别、门票价格、综合评分、评论数量大概3000条记录。第二份是用户行为表用Python脚本模拟生成的包含用户编号、景点编号、行为类型浏览、收藏、评分、行为时间戳、评分值1到5分大概10万条记录。行为表里故意留了一些“脏数据”这反而对做数据清洗很有帮助。比如大概有2%的评分是重复提交有1%的用户编号格式不统一有一些时间戳是2020年之前的老数据还有少数评分值是0非法值旅游平台评分一般是1到5。这些脏数据就是数据清洗模块的素材。用户行为表的字段设计如下字段名类型说明示例user_idstring用户ID可能存在前缀不一致问题U10001 / user_10001spot_idint景点ID关联景点基础表20301action_typeint行为类型1浏览 2收藏 3评分 4下单3ratingfloat评分值1到5仅评分行为有效4.5action_timestring行为时间格式YYYY-MM-DD HH:MM:SS2023-06-15 14:23:012.2 数据清洗实操去重、格式化、过滤异常值数据清洗是整个项目里耗时最长、最不性感但最能体现工程能力的一步。我在这个项目里做的清洗工作可以归纳为四个动作去重、补缺、格式化、过滤。去重比较好理解。用户对景点提交多次评分时保留最近一次用Hive的ROW_NUMBER()窗口函数就能搞定。格式化指的是用户ID统一去掉前缀时间戳统一成标准格式城市名称统一去掉“市”后缀。这些看似琐碎的细节如果不处理后面做特征关联和分组统计时会出现很多莫名其妙的坑。过滤异常值这一点我要重点说说。评分值如果出现0或者6很明显是数据录入错误直接丢掉。门票价格如果出现负数也一并清洗掉。还有一种情况是行为时间在未来的数据比如系统当前时间是2024年但行为时间是2025年这类数据也要剔除。判断标准就是如果一个数据的产生不符合业务常识那它在模型里带来的只会是噪声。这里有一条非常关键的隐私处理原则用户ID一律脱敏处理。真实项目中用户ID关联着手机号、身份证等敏感字段清洗阶段必须把用户维度信息做哈希或者映射后端业务系统只能拿到脱敏后的ID。这个细节在企业里是合规红线在毕设里也是答辩老师常问的点。2.3 Hive建表与离线数仓建模数据清洗逻辑确定之后用Hive建表把原始数据导入数仓。我在项目里建了三张表原始表ods_user_behavior存放未清洗数据清洗表dwd_user_behavior存放清洗后的明细数据聚合表ads_user_preference存放按用户维度的偏好聚合结果。这个分层方式就是数仓领域常说的ODS、DWD、ADS三层架构虽然是个小项目但保持这种层次的规范性对后续扩展很有帮助。Hive建表语句给大家参考CREATE TABLE dwd_user_behavior ( user_id STRING COMMENT 脱敏用户ID, spot_id INT COMMENT 景点ID, action_type INT COMMENT 行为类型1浏览 2收藏 3评分 4下单, rating FLOAT COMMENT 评分值, action_time STRING COMMENT 行为时间, dt STRING COMMENT 分区字段按天分区 ) PARTITIONED BY (dt STRING) ROW FORMAT DELIMITED FIELDS TERMINATED BY \t STORED AS TEXTFILE;分区字段dt是非常实用的设计。做离线推荐时我只需要读取最近90天的行为数据直接指定dt范围即可不需要全表扫描。数据量小的时候体会不到分区的好处但一旦数据量到千万级没有分区的表会让查询慢到怀疑人生。按天分区是数仓最经典的策略。2.4 用Spark做特征工程从行为到用户画像原始行为数据清洗完之后还不能直接喂给推荐算法。协同过滤算法需要的输入是“用户-景点评分矩阵”但原始数据是行为流水必须做特征变换。用户侧特征我统计了这么几项用户活跃度总行为数、平均评分、浏览景点数、下单次数、偏好类别对哪一类景点评分最高。景点侧特征有平均评分、评论总量、热度分综合浏览量、收藏量和下单量加权得到。这些特征用Spark的groupBy和agg算子跑起来非常方便代码量不大但信息量很足。特征工程这一步最妙的用途是解决“推荐结果解释性”。ItemCF算出“你推荐三亚”的时候系统能同时告诉你“因为你之前浏览过亚龙湾而去过亚龙湾的人通常也关注蜈支洲岛”。有了用户偏好类别和景点类别特征解释性文案可以自动生成这在答辩演示时比冷冰冰的推荐列表有说服力得多。3. 推荐算法设计从ItemCF到混合推荐3.1 为什么选择ItemCF作为核心算法协同过滤家族里有两条技术路线UserCF和ItemCF。UserCF是“找与你相似的人把他们喜欢的东西推荐给你”ItemCF是“找你喜欢的物品的相似物品推荐给你”。旅游推荐场景下ItemCF有明显优势。举一个具体的例子用户A在三亚玩过给蜈支洲岛打了5分用户B也在三亚玩过给大小洞天打了4分同时浏览过呀诺达。UserCF会先把A和B归为“相似用户”然后给A推呀诺达。这听起来合理但问题是旅游消费频次极低用户的相似度矩阵会非常稀疏A和B可能只共享了三亚这一个城市的行为计算的相似度并不可靠。而ItemCF算的是景点之间的相似度比如“去过蜈支洲岛的人70%也会去亚龙湾”这种物品间共现关系比用户间相似关系稠密得多推荐结果相对稳定。另外从产品逻辑上看旅游推荐更适合“看见相似惊喜”而不是“看见朋友在玩什么”。ItemCF天然具有这种“物以类聚”的解释性。3.2 Python实现ItemCF的核心代码ItemCF的实现步骤很清晰构建评分矩阵 - 计算物品相似度矩阵 - 根据用户历史评分加权计算推荐得分 - 排序截断TopN。下面这份代码是我在项目里实际使用的基于pandas实现易于理解和调试import pandas as pd import numpy as np # 读取清洗后的行为数据 df pd.read_csv(dwd_user_behavior.csv, sep\t) # 构建用户-景点评分矩阵行用户列景点 rating_matrix df.pivot_table( indexuser_id, columnsspot_id, valuesrating, aggfuncmax ).fillna(0) # 计算景点间的余弦相似度 def cosine_similarity(matrix): # 归一化向量 norm np.sqrt(np.sum(matrix ** 2, axis0)) norm[norm 0] 1e-10 # 避免除零 matrix_norm matrix / norm # 余弦相似度 矩阵内积 sim_matrix np.dot(matrix_norm.T, matrix_norm) return sim_matrix spot_sim cosine_similarity(rating_matrix.values) # 构建相似度DataFrame行列都是景点ID spot_ids rating_matrix.columns sim_df pd.DataFrame(spot_sim, indexspot_ids, columnsspot_ids) # 推荐函数给定用户ID返回TopN推荐景点 def recommend_for_user(user_id, top_n10): # 用户已经评分过的景点及其评分 user_ratings rating_matrix.loc[user_id] rated_spots user_ratings[user_ratings 0] # 候选景点得分 用户对已评分景点的评分 * 相似度加权求和 scores {} for spot, rating in rated_spots.items(): similar_spots sim_df[spot].sort_values(ascendingFalse) for sim_spot, sim_score in similar_spots.items(): if sim_spot not in rated_spots.index: # 排除已去过的景点 scores[sim_spot] scores.get(sim_spot, 0) rating * sim_score # 排序并返回TopN recommendations sorted(scores.items(), keylambda x: x[1], reverseTrue)[:top_n] return recommendations代码里有一个细节需要注意相似度计算时用了归一化的余弦相似度。因为用户对不同景区的评分习惯不同有人喜欢给高分有人偏向中等分如果直接计算原始评分的相似度用户打分偏好会污染景点相似度。归一化后向量方向被保留长度被抹平稳定性和效果都有提升。另一个代码层面的经验候选景点的评分累加时可以直接对相似度矩阵做矩阵乘法效率远高于双重循环。数据量不大的时候双重循环好调试但如果数据量到百万级双重循环的耗时不可接受必须向量化。3.3 冷启动问题与热门榜兜底推荐系统有个无法回避的难题冷启动。新用户没有任何行为数据ItemCF没法给他推荐新景点没有共现记录也没有办法被推荐出去。我在项目里做了三层兜底策略。第一层是用户冷启动兜底。当用户行为数量小于5条时直接给他推热门榜Top10。热门榜的算法是热度分排序热度分 0.4 * 评论数量 0.3 * 平均评分 0.3 * 收藏量权重可以自己调。这一层保证了新用户打开页面一定有内容可看。第二层是景点冷启动兜底。新上线的景点用“同类热门”策略找到它所属类别下热度最高的几个景点打包推荐给喜欢该类别的用户。比如一个新的4A级海滨景区上线可以把它挂在“海滨风光”类别下推给偏好海滨游的用户。第三层是行为稀疏用户兜底。有些用户有一定行为但样本太少ItemCF计算出的相似度方差很大。我的处理方式是平滑给用户行为数据里增加一个虚拟的“系统默认偏好”维度用全量用户的平均偏好做先验把稀疏数据拉回稳定区间。这种做法和推荐算法里的贝叶斯平滑思想是一致的。3.4 混合推荐把规则和算法结合起来只靠ItemCF在真实场景里肯定不够因为协同过滤的本质是“从历史中发现规律”它无法理解“今天下雨所以推荐室内景点”这种即时场景。所以我在系统里加了混合推荐模块规则如下场景规则过滤根据当前季节和天气过滤景点类别。夏季优先推海滨和漂流冬季推温泉和滑雪下雨天推博物馆和室内游乐场。价格区间过滤根据用户历史下单的价格区间把推荐结果的景区门票价格过滤到用户可接受范围内。算法排序与规则加权融合ItemCF计算出一个基础得分规则层对这个得分做加权调整。比如热门景点附加0.1的加权系数4A级以上景区附加0.05的加权系数。混合推荐的调参是一个经验活。因为不同用户关注的维度差异很大单一权重组合很难全局最优。我的建议是把规则权重的默认值先设成1.0跑通整个流程后再结合用户反馈微调。不要一上来就陷入参数调优的泥潭里先解决“有推荐”的问题再解决“推荐准”的问题。4. 系统集成与可视化展示4.1 Flask后端接口设计推荐算法在Spark里算完之后结果需要落库并提供接口供前端调用。基建薄弱时不要自己造轮子直接用Flask写RESTful接口是最省事的方案。我的接口设计很简单三个接口搞定POST /api/recommend传用户ID返回TopN推荐景点列表。GET /api/hotspots返回热门景点排行榜用于冷启动兜底和首页展示。GET /api/user/profile返回用户画像标签供前端画雷达图。推荐接口的核心逻辑是“缓存优先”。因为离线推荐每天算一次结果相对固定如果每次请求都实时算一遍性能和稳定性都很差。我的实现是Spark离线算完结果写入MySQL的recommend_result表Flask接口读数据时先查Redis缓存Redis没有再查MySQL。这个缓存策略能把接口响应时间从几百毫秒降到几十毫秒体验提升非常明显。Flask接口代码骨架from flask import Flask, request, jsonify import redis import pymysql app Flask(__name__) cache redis.Redis(hostlocalhost, port6379, db0) app.route(/api/recommend, methods[POST]) def recommend(): user_id request.json.get(user_id) if not user_id: return jsonify({code: 400, msg: missing user_id}), 400 # 优先读取缓存 cache_key frecommend:{user_id} cached cache.get(cache_key) if cached: return jsonify({code: 0, data: eval(cached)}) # 缓存未命中查MySQL conn pymysql.connect(...) cursor conn.cursor() cursor.execute(SELECT spot_id, score FROM recommend_result WHERE user_id%s ORDER BY score DESC LIMIT 10, (user_id,)) rows cursor.fetchall() # 回写缓存 cache.set(cache_key, str(rows), ex3600) return jsonify({code: 0, data: rows})有一点特别提醒代码里用eval解析缓存内容只是为了演示真实项目里要换成JSON格式化避免安全问题。4.2 ECharts可视化页面推荐系统的计算结果如果只是表格数据展示效果会大打折扣。我做了四个ECharts图表分别对应四种核心信息第一个是热门景点Top10柱状图。纵轴是景点名称横轴是热度分可以直接看到系统冷启动兜底的列表是哪些。第二个是用户偏好雷达图。将用户对不同类别景点的平均评分标准化后映射到雷达图上类别包括自然风光、人文古迹、主题乐园、海滨岛屿、城市观光、乡村田园。这个图一眼就能看出用户是“自然派”还是“城市派”。第三个是推荐结果展示区。左侧列出用户最近浏览过的3个景点右侧列出系统推荐的Top5景点中间用细线连接每条线上标注推荐置信度。这种可视化的表达方式比单纯堆列表要清晰得多也方便答辩时讲推荐逻辑。第四个是协同过滤热度图。用热力图展示30个高频景点之间的相似度矩阵颜色越深表示相似度越高。这个图放在技术文档里很加分直观体现ItenCF算法的中间结果。前端用HTMLJavaScriptECharts实现数据通过fetch调用Flask接口获取。这套前端代码不复杂网上有ECharts官方的现成实例可以抄真正的难点在于后端把前端需要的数据结构组装好。所以我的原则是后端接口率先设计JSON结构前端只做渲染不做二次处理。4.3 Spark作业提交与调度推荐系统的离线计算不能靠人肉手动执行需要自动化调度。我是用crontabSpark-submit脚本实现的每天凌晨2点运行一次全量推荐任务。Spark作业的提交流程需要注意资源参数。集群内存小单节点4G时执行器内存设置太大容易导致OOM设置太小又会频繁GC。我的经验值是executor-memory设置为1Gexecutor-cores设置为1driver-memory设置为1G这三个参数在2核4G的节点上相对稳妥。如果数据量更大优先增加executor数量而不是堆大单个executor的内存。Spark运行完推荐任务后结果数据写回HDFS再用一个Python脚本从HDFS下载结果并写入MySQL。这里两个流程之间的衔接用Shell脚本串起来#!/bin/bash # 每天凌晨2点执行推荐任务 spark-submit \ --master yarn \ --deploy-mode cluster \ --executor-memory 1G \ --executor-cores 1 \ recommend_job.py # 同步结果到MySQL python sync_result_to_mysql.py实际跑任务时我踩过一个大坑YARN的ResourceManager和NodeManager在同一台机器上如果虚拟内存检测参数yarn.nodemanager.vmem-check-enabled没有关闭Spark作业会因为“虚拟内存超限”被强杀。这个问题非常隐蔽网上解决方案也五花八门最终我的做法是在yarn-site.xml里把虚拟内存检测关闭并调大物理内存占比阈值。做大数据集群环境搭建时这类参数必须提前踩一遍坑。5. 推荐效果评估与问题排查5.1 推荐质量怎么量化评估推荐系统不能只看“界面好看”必须有量化的评估指标。我在项目里用了三个指标准确率、召回率和覆盖率。准确率衡量的是推荐列表中有多少是用户真实喜欢的。做法是把用户的历史行为按7:3拆成训练集和测试集用训练集算推荐再检查测试集里用户实际互动过的景点是否出现在推荐Top10中。计算公式准确率 命中个数 / 推荐列表长度。召回率衡量的是用户喜欢的景点中有多少被推荐出来了。计算公式召回率 命中个数 / 测试集用户实际互动景点数。旅游场景里用户互动景点数量本来就不多所以召回率不会太高这是数据属性决定的不用焦虑。覆盖率衡量的是推荐系统是否只推了少数热门景点。如果推荐结果长期集中在热门榜前20那个性化基本失效了。覆盖率 推荐出去的景点数 / 景点总数。一般来说ItemCF的覆盖率远高于热门榜策略因为长尾景点也能通过共现关系被发现。5.2 常见问题与排查方法实录这个项目从头到尾跑下来我整理了五个最典型的坑每一个都是实际遇到了才反应过来的。第一个是Hive和MySQL的时间字段时区问题。Hive默认用UTC时间MySQL默认用系统时区两边差了8小时。排查半天发现是时区配置不一致解决办法是在连接串里加上serverTimezoneAsia/Shanghai。第二个是Spark任务内存溢出。报错信息是OutOfMemoryError最初以为是数据量太大后来发现是因为groupBy时产生了巨大的数据倾斜。解决办法是加salting给key加上随机数前缀再聚合。第三个是Linux系统文件句柄限制。Hadoop在运行高并发任务时报Too many open files错误需要在/etc/security/limits.conf里调高nofile参数。第四个是Flask接口在集群环境中请求超时。原因是我把Spark计算直接丢在接口同步逻辑里离线任务如果没算完接口就一直阻塞。解决方向是接口只读已经算好的结果计算与查询完全分离。第五个是用户ID关联失败。从Hive导出到MySQL后部分推荐结果查不出来排查发现是Hive里的user_id是string类型但MySQL里是int类型两边join时隐式转换不一致。顺手把MySQL字段也改成string类型彻底解决。5.3 前端与接口联调的几个细节前后端联调时最烦人的问题不是功能做不出来而是数据结构和预期不一致。我在项目里遇到过一个经典case前端希望返回的推荐结果是一个数组每个元素包含spot_id、spot_name、score三个字段但后端接口返的是字符串格式的元组。两边的mock数据都能各自跑一到真联调就死活对不上。解决办法是定一个接口契约文档用JSON Schema或者最简单的Markdown把每个字段的类型和含义写清楚。前后端都严格按契约开发联调问题会少一大半。这个习惯最好在读研或者工作的第一天就养成。还有一个细节是前端加载状态。Spark离线计算如果没跑完推荐结果的接口会返回空列表前端如果不做空状态提示页面看起来就像挂了。我加了一个“推荐结果正在生产中请稍后刷新”的占位提示不影响体验也方便定位问题是数据没算完还是接口真的报错。6. 项目后续扩展思路6.1 从离线推荐到实时推荐目前的架构是离线批处理每天凌晨算一次推荐结果。这种方案对旅游这种“低频决策”场景基本够用但局限性也很明显假设用户上午浏览了杭州下午就想去周边游离线结果完全没有感知。实时推荐架构的升级路线是这样的行为日志通过Flume采集到KafkaSpark Streaming或者Flink消费Kafka数据在线更新用户的短期兴趣向量把实时召回结果与离线推荐结果做融合排序。这一套升级的核心是引入消息队列数据链路从“批”变成“流”。6.2 从协同过滤到深度学习排序如果要追求更高的推荐精度协同过滤就不够用了。深度学习方案中深度兴趣网络DIN和DeepFM是近年旅游推荐场景使用最多的两类模型。DIN的特点是能捕捉用户历史行为中与当前候选物品相关的兴趣点比如用户之前看过很多海滨酒店当候选物品是海岛游产品时DIN会重点激活这部分兴趣。DeepFM则擅长处理稀疏特征把类别特征城市、景点类型嵌入到低维向量空间。但我不建议把这个项目从一开始就绑定深度学习。真实经验告诉我数据量不到百万级特征工程和协同过滤的效果不一定比深度学习差而且可解释性强得多。先把推荐系统的主体链路跑扎实再考虑算法升级这个顺序才合理。6.3 数据安全与隐私保护的进一步强化我前面提到过数据清洗阶段要做用户ID脱敏这是数据安全的底线。往深了做还可以引入联邦学习思路用户行为数据不出本地只在本地计算模型梯度把加密后的梯度上传到中心服务器聚合更新。这在旅游行业的合规要求越来越严格的背景下是一个重要的演进方向。对于毕设级的项目至少要做到数据集不包含真实手机号、身份证号导出数据前做脱敏对外展示API不暴露用户隐私字段。做一个项目最深的体会不是某个算法有多难而是要把数据、算法、工程、产品串起来让系统真正“转起来”。大数据推荐系统尤其如此数据清洗占了五成功力算法只占一成剩下的四成在工程和联调。这篇文章把完整链路拆解到这里如果你也想做类似的项目可以从数据清洗开始动手。先把Hive里的数据整理得干干净净推荐结果自然就差不到哪里去。这个细节是我跑完整个项目后最想告诉你的事。
返回列表