资讯动态

Java实现基于协同过滤的网上书店推荐系统,从相似度计算到冷启动

发布时间:2026/10/22 8:06:18 来源:尧图企业网站定制
每年到毕设季选题目就成了头等大事。Java方向的“基于协同过滤的网上书店推荐系统”属于那种一眼看去很常见、但真做完能学到大量东西的经典题目——既有完整的Web业务闭环又有核心算法落地还能写进简历当项目经验。我最早带学生做这个题的时候也以为就是个简单电商系统加个推荐接口真正动手才发现坑都在细节里评分矩阵怎么构建相似度用什么算冷启动怎么兜底这些不搞清楚演示的时候分分钟翻车。这篇就把整个开发链路拆开讲清楚从选题思路、技术选型、数据库设计到相似度计算、推荐引擎代码落地、性能优化和排坑实录全部过一遍。适合准备拿这个方向做毕设的同学也适合想系统了解协同过滤在Java项目里怎么用的开发者。1. 选题背后的核心价值与设计思路拆解1.1 为什么推荐系统是毕设里的高性价比选题先说实话毕设选题最怕什么怕“没深度”也怕“撑不起篇幅”。管理系统类题目容易写成增删改查虽然稳妥但答辩老师一问你的创新点在哪容易答不上来。选推荐系统天然就带了一个算法内核能讲的东西非常多——数据怎么处理、相似度怎么算、推荐列表为什么是这个顺序每一个问题都能展开聊。“网上书店”这个业务场景选得也恰到好处。图书品类多、数量大天然适合做个性化推荐用户在购买、评分、浏览时留下的行为数据可以作为推荐依据数据维度丰富但又不至于像短视频那样复杂到需要深度学习才能处理。对于本科阶段来说协同过滤的难度刚好卡在“跳一跳够得着”的位置——有数学公式但不算高深需要写代码实现但数据量和并发量都不至于让你处理不过来。更关键的是这个题目具备完整的“故事线”数据采集 → 数据清洗 → 构建用户行为矩阵 → 计算相似度 → 生成推荐列表 → Web端展示。这条链路和工业界的推荐系统流程是同构的做完之后你对整个推荐体系的理解是成体系的不是零散的知识点。1.2 协同过滤的两条技术路线基于用户还是基于物品协同过滤Collaborative Filtering核心思想就一句话物以类聚人以群分。但具体落地时有两个方向理解它们的区别是做好这个题目的第一步。基于用户的协同过滤User-based CF思路是“和你相似的人喜欢什么就给你推荐什么”。具体操作是先找到与当前用户兴趣最接近的一批“邻居用户”再把这些邻居喜欢过、而当前用户没接触过的物品取出来推荐。这个思路直觉上很好懂但有一个致命弱点当用户数量变大计算每个用户两两之间的相似度开销非常大而且新用户加入后没有行为数据根本找不到邻居。基于物品的协同过滤Item-based CF思路是“你喜欢的东西和它相似的东西也推荐给你”。它不是根据物品的内容属性判断相似比如同作者、同分类而是根据大量用户的集体行为来判断——比如买了《Java编程思想》的人通常也买《Effective Java》那么这两本书就在“行为层面”被认定为相似。这个算法的好处是物品数量通常远小于用户数量相似度矩阵可以离线预先计算好线上直接查表推荐性能和实时性都有保障。对比项基于用户User-based基于物品Item-based核心逻辑找兴趣相似的用户群找行为关联的物品适用规模用户数小于物品数物品数小于用户数实时性需实时算用户相似度相似度可离线预计算推荐解释性弱“与你相似的人”较抽象强“因为你买了X”冷启动表现新用户无行为难算新物品无人评难关联网上书店这个场景下图书数量几千到几万一般远小于用户数量注册用户可能几万甚至更多所以基于物品的协同过滤是更合适的选择。大多数教程会用MovieLens数据集演示那个数据集也是物品电影远少于用户的场景迁移到图书场景完全说得通。这背后其实有一个通用的经验当你的系统里用户增长速度远超物品增长速度时优先考虑Item-based。1.3 为什么集成算法时容易走火入魔有个很常见的误区学生一听到推荐就想上高大上的东西矩阵分解、神经网络、深度学习……我的建议很直接毕设阶段不要碰深度学习。数据集几百条评分记录深度学习模型训出来效果可能还不如热门推荐算力需求还大——指望学校那台老爷机跑Transformer纯属自己给自己挖坑。协同过滤的黄金价值在于用最简单的数据维度通过集体智慧产出个性化结果这个思路本身不输给复杂模型只是在特征表达上没那么丰富而已。毕设拼的是逻辑完整、实现无误、能讲清楚原理不是拼谁用的模型名字长。你只要把协同过滤做到位再辅助以热门推荐兜底、新书基于分类的过渡策略这些已经覆盖了答辩时老师多半会问的“你如何处理冷启动”这个问题。方案不在多在于每一步都站得住脚。2. 系统架构与技术选型复盘2.1 技术栈搭配经典Java Web三件套的合理性Java方向毕设技术栈我推荐用这套组合Spring Boot项目骨架自动配置省掉大量XML配置的繁琐工作内嵌Tomcat让部署也简单。MyBatis或MyBatis-Plus数据访问层手写SQL可控性强尤其是写推荐相关的统计查询时能精确控制执行逻辑。MySQL存储用户、图书、评分、订单等业务数据。前端Thymeleaf模板引擎服务端渲染或者Vue分开前后端按自己熟悉程度选择。毕设复杂度不高用模板引擎就行省去跨域和前后端联调的麻烦。Redis可选缓存热门推荐列表和相似度矩阵。这套组合的优势一句话总结是生态成熟、资料丰富、容错率高。遇到问题搜一下就是解决方案不至于卡在环境问题上浪费毕设答辩前的宝贵时间。有些同学喜欢赶时髦上微服务、上Docker我劝你慎重。单机单体就够用的场景引入微服务只会增加调试负担。毕设的目标之一是顺利通过不是炫技。你要做的是把一个经典问题做完善、做细致——这比堆砌十个框架的效果好得多。2.2 数据库表结构设计最容易被低估的环节表结构设计的好坏直接决定推荐算法的数据从哪来。我见过不少同学把项目搭好了才发现没有存用户行为数据临时拍脑袋造数造出来的数据质量极差最后推荐结果惨不忍睹。推荐引擎是“垃圾进垃圾出”的典型数据的质量比算法本身更重要。核心表至少需要这几张用户表user用户ID、用户名、密码加密存储、注册时间等基础信息。图书表book图书ID、书名、作者、出版社、ISBN、分类、价格、封面图URL、库存、销量。评分表rating用户ID、图书ID、评分值1-5分、评分时间。订单表order/order_item订单关联用户和图书购买行为也是一种隐式强反馈信号。浏览记录表browse_history可选用户浏览图书的记录用于丰富行为数据。这里有个关键点评分表的结构设计直接决定了相似度计算代码的写法。推荐使用联合唯一索引(user_id, book_id)防止同一用户对同一本书重复打分在查询时按用户分组拿出评分记录构造“用户-物品”评分矩阵。图书表里还需要一个“分类”字段。这一列在后面对付冷启动很有用——新书上架还没有用户评分时可以通过同分类下最热门的书来做“过渡相似”至少保证新书有机会出现在推荐列表里。造数的时候注意模拟数据要符合真实分布。比如评分总体呈偏态分布高分多、低分少每个用户的评分数量差异要大有的用户只有一两条有的用户有几十条不同用户对不同类别的偏好要明显。这样推荐算法才有“规律”可挖。我曾经见过一个同学直接用随机函数生成评分结果所有用户对每本书都均匀评分最后相似度矩阵里全是零推荐系统完全跑不出有区分度的结果。2.3 项目模块划分把推荐引擎拆成独立模块目录结构上按下面的方式划分会让代码清晰很多com.bookstore.recommend ├── controller // Web接口层接收请求返回页面或JSON ├── service // 业务逻辑层含推荐服务 │ ├── RecommendService.java // 推荐主流程 │ ├── ItemSimilarityService.java // 物品相似度计算 │ └── RatingService.java // 评分数据服务 ├── repository/mapper // 数据访问层 ├── model/entity // 实体类 ├── common // 工具类、常量、统一返回等 └── RecommendApplication.java // 启动类把推荐模块独立出来的原因很简单它是整个系统的核心价值所在值得单独维护也方便调试。如果你把相似度计算的代码塞进Service层里和业务代码混在一起排查问题时会非常痛苦。推荐引擎的逻辑是独立的——输入是“用户行为数据”输出是“推荐列表”中间不依赖任何Web层的状态这种天然的高内聚低耦合结构分割出来代码会漂亮得多。3. 推荐引擎核心实现上从数据到相似度矩阵3.1 用户行为数据的收集与预处理开始写算法之前有一件事必须做扎实把评分数据从数据库里读出来构建成算法需要的数据结构。协同过滤的标准输入是一个“用户-物品”评分矩阵行是用户列是物品单元格是评分值。但在Java里用二维数组存这个矩阵非常蠢——用户ID和图书ID通常不是从0开始的连续整数而且绝大多数单元格都是空的用户不可能给所有书评分稀疏矩阵用二维数组纯属浪费内存。最常用的方案是用嵌套Map// userId - (bookId - rating) MapInteger, MapInteger, Double userRatingMap new HashMap();读取时按用户分组把该用户对所有书的评分放进内层Map。同时再准备一个反向结构——物品到用户的映射计算物品相似度时用得上// bookId - (userId - rating) MapInteger, MapInteger, Double itemRatingMap new HashMap();预处理环节有一个高频踩坑点评分值需要做归一化处理吗不同用户的评分习惯差异很大。有人是“好人”看什么都给四五分有人是“严苛党”全场很少给超过三分。如果不做处理两个评分习惯不同的用户就算兴趣完全相同算出来的相似度也会偏低。解决思路是均值中心化——把每个用户的评分减去他自己的平均分让数据变成“相对偏好”double avg sum(map.values()) / map.size(); for (Map.EntryInteger, Double entry : map.entrySet()) { entry.setValue(entry.getValue() - avg); }但注意在基于物品的协同过滤里中心化处理的位置和基于用户不太一样。用物品之间的余弦相似度时我们一般直接对row vector做计算稀疏度太高时中心化反而容易引入噪音。一个折中的做法是在预测评分阶段做中心化补偿而不是在相似度计算阶段强行处理。这个细节很多博客都不会讲我实际调试时发现它对结果影响相当微妙——如果你发现推荐结果乱套可以考虑在预测公式里加上用户平均分的基准值。3.2 物品相似度调整余弦相似度才是主力Item-based CF里物品i和物品j的相似度标准算法是用调整余弦相似度Adjusted Cosine Similaritysim(i, j) Σ(u∈Uij) (R_ui - avg_u) * (R_uj - avg_u) / ( sqrt(Σ(u∈Ui)(R_ui - avg_u)^2) * sqrt(Σ(u∈Uj)(R_uj - avg_u)^2) )这个公式看着吓人拆开理解就有三层意思分母是“长度”的乘积为了把相似度归一化到[-1, 1]之间。分子是“同时给物品i和物品j评过分的用户”记为Uij的加权分量。减去的avg_u是用户u在所有已评分物品上的平均分用于消除用户评分尺度差异。为什么要强调“调整”因为普通余弦相似度不考虑用户评分偏差而调整后的版本通过减去用户均值把“评分习惯”这个混淆变量控制住了。实际效果上调整余弦在评分预测任务里通常优于普通余弦——这也是许多经典教材比如项亮的《推荐系统实践》里明确指出的结论。另一种常见做法是用Jaccard系数直接算“同时买过/评过这两本书的用户数”占“评过其中至少一本的用户数”的比例。Jaccard实现简单、计算极快但它完全丢掉了评分值信息只看行为“是否发生”。在只有隐式反馈比如点击、购买的场景下很适合但既然我们已经有评分数据了就别浪费。3.3 相似度计算的Java实现这一段代码可以直接抄进项目里用。核心方法是给定图书ID遍历其它所有图书计算它与目标图书的相似度public class ItemSimilarityCalculator { /** * 计算物品i与物品j的调整余弦相似度 * param itemRatingMap 图书 - (用户 - 评分) * param i 物品ID * param j 物品ID * return 相似度 */ public static double adjustedCosineSimilarity( MapInteger, MapInteger, Double itemRatingMap, int i, int j) { MapInteger, Double ratingsI itemRatingMap.get(i); MapInteger, Double ratingsJ itemRatingMap.get(j); if (ratingsI null || ratingsJ null) return 0.0; // 同时给i和j打分的用户集合 SetInteger commonUsers new HashSet(ratingsI.keySet()); commonUsers.retainAll(ratingsJ.keySet()); if (commonUsers.isEmpty()) return 0.0; // 各自的均值 double avgI ratingsI.values().stream().mapToDouble(d - d).average().orElse(0.0); double avgJ ratingsJ.values().stream().mapToDouble(d - d).average().orElse(0.0); double numerator 0.0; double denominatorI 0.0; double denominatorJ 0.0; // 公共用户计算中心化后的分量乘积 for (Integer u : commonUsers) { double rui ratingsI.get(u) - avgI; double ruj ratingsJ.get(u) - avgJ; numerator rui * ruj; } // 所有评过i的用户 for (Double r : ratingsI.values()) { denominatorI Math.pow(r - avgI, 2); } // 所有评过j的用户 for (Double r : ratingsJ.values()) { denominatorJ Math.pow(r - avgJ, 2); } if (denominatorI 0 || denominatorJ 0) return 0.0; return numerator / (Math.sqrt(denominatorI) * Math.sqrt(denominatorJ)); } }这里有两个细节你写的时候大概率会踩到。第一个是并行流的使用。如果你用Java 8的parallelStream在计算每一对物品的相似度时能大幅加速配合线程池可以提前得到结果。但要小心——如果数据量不大并行流反而比串行慢线程切换开销另外要保证HashMap的线程安全最好用ConcurrentHashMap或者让计算过程不修改共享状态。第二个是稀疏数据的处理。很多物品之间根本没有共同用户相似度天然是0。如果每次都把所有物品都算一遍时间开销很大。实际项目里可以先做一个“过滤”只对“评分数量达到阈值”的物品之间计算相似度或者只对“同分类”的物品计算——这是我个人比较推荐的建树思路能大幅降低时间复杂度同时保证结果不降级太多。3.4 要预计算还是实时计算这是一个很容易被忽视、但在“答辩演示现场”会翻车的问题。如果你在用户点击“推荐给TA”时才去计算相似度矩阵每一次推荐请求都要做O(n^2·m)量级的计算数据库再大一点演示时页面转圈转十秒你的答辩分数已经开始溜走了。正解是离线计算 预存储。系统启动后或者在每天凌晨的定时任务里执行一次全量相似度计算把结果存到数据库的book_similarity表里字段就是(source_book_id, target_book_id, similarity_score)。然后推荐的时候直接从这张表里把相似度查出来用。这一招在工业界同样通用——推荐系统的实时性指的是在线推荐时查预计算结果快而不是每次在线都重算模型。book_similarity表里的数据量要控制好假设有5000本书最多5000×4999/2约1250万对看起来很大但实际只需要每个源物品保留Top-K个相似物品比如Top50数据量就降到25万条了MySQL处理起来毫无压力。如果你连MySQL查询都不想多做一步可以更进一步把热门推荐Top100和每个用户的个性化Top50列表直接缓存到Redis定时过期刷新。这样最极端情况下推荐接口从几十毫秒降到个位数毫秒。4. 推荐引擎核心实现下从相似度到推荐列表4.1 预测评分与Top-N推荐有了相似度矩阵给用户u推荐文章i的评分预测公式是pred(u, i) Σ(j∈N(u)) sim(i, j) * R_uj / Σ(j∈N(u)) |sim(i, j)|意思很直白从用户u已经评过分的书N(u)里找和目标书i相似度最高的几本用相似度做权重对它们的评分做加权平均——再加一个基准分修正。实现代码public ListBook recommendBooksForUser(int userId, int topN) { // 1. 获取该用户评过分的图书及评分 MapInteger, Double ratedBooks ratingService.getRatingsByUser(userId); if (ratedBooks.isEmpty()) { // 冷启动用户返回热门推荐 return recommendService.getHotBooks(topN); } // 2. 从相似度表里查询候选推荐 MapInteger, Double scoreMap new HashMap(); for (Map.EntryInteger, Double entry : ratedBooks.entrySet()) { int ratedBookId entry.getKey(); double rating entry.getValue(); ListBookSimilarity simList similarityMapper.findTopKBySourceBookId(ratedBookId, K); for (BookSimilarity sim : simList) { int targetBookId sim.getTargetBookId(); if (ratedBooks.containsKey(targetBookId)) { continue; // 去掉用户已评分/已购买的书 } double weight sim.getSimilarity(); if (weight 0) continue; // 过滤不相似或负相关 scoreMap.merge(targetBookId, weight * rating, Double::sum); } } // 3. 按加权求和得分排序取topN return scoreMap.entrySet().stream() .sorted((a, b) - Double.compare(b.getValue(), a.getValue())) .limit(topN) .map(entry - bookService.getById(entry.getKey())) .collect(Collectors.toList()); }有几处细节值得留意负相似度过滤。如果两本关于编程的书相似度是-0.2出现负值的原因是评分中心化后造成的比如有些极端的评分组合。如果不过滤负权重会把分数拉低甚至推荐出完全无关的书。稳妥做法是只用正相似度。排除已购买/已评分。这个逻辑看起来很简单但很多新手会写漏。你不希望用户点进推荐页看到的第1本书就是他昨天刚买的那一本。合并得分时使用scoreMap.merge。JDK 8引入的这个方法非常好用一行就能完成“如果key不存在就put存在就累加”的逻辑不用写好几行if-else。4.2 预测评分公式要不要带基准分上一节公式里没有显式加用户平均分。很多教程里会写成pred(u,i) avg_u Σ(sim(i,j) * (R_uj - avg_u)) / Σ|sim(i,j)|。这两个版本的区别在哪带中心化修正的版本更稳——尤其在用户的评分尺度差异很大时效果更明显。比如一个用户把《活着》打了3分但他就是不喜欢文学类作品所有其他文学书都是2分不带基准分时预测得分会被拉低带基准分时相当于把“用户自身评分习惯”和“物品绝对质量”剥离开来。不过我建议你用带基准分的版本毕竟它更正统做理论讲解时也更有说服力。加一行代码的事double userAvg ratedBooks.values().stream().mapToDouble(d - d).average().orElse(3.0); // 在累加时scoreMap.merge(targetBookId, weight * (rating - userAvg), Double::sum); // 排序后最终得分 userAvg weightedSum / totalWeight这里要注意被零除的情况——如果totalWeight为0比如所有相似度取绝对值后求和为0就跳过该物品。实际很少发生但写代码时不能漏。4.3 冷启动的三层兜底方案推荐系统里最重要也最常被提问的就是冷启动问题。答辩时老师基本必问“新用户第一次来你推荐什么”我设计了三层策略每一层都有明确的触发条件第一层热门推荐。用户没有任何行为数据时按“近30天销量好评数评分人数”加权评分排序取出Top N作为默认推荐结果。这个即使不发生在冷启动用户上也可以作为所有推荐接口的“默认二页”反复使用。第二层基于分类的过渡策略。新书上线没有评分数据但可以把它归入某个分类。找到同一分类下已有的热门书把这个热门书关联的相似书籍推荐出来。本质是拿“物品属性相似性”做了一个退而求其次的逻辑内容属性相似的物品至少不会太离谱。第三层混合加权。对有两三条评分记录的不完全冷启动用户用一条规则做混合60%权重放在协同过滤的个性化推荐结果里40%权重放热门池里。等用户行为越来越多个性化权重逐步提升。这三层方案在写论文时也能撑起一个“冷启动方案设计”小节答辩时讲得清楚加分不少。4.4 前端展示交互的加分设计很多毕设项目的推荐接口做出来了但前端页面只是把书本列表铺在那里没有任何反馈机制。这会吃亏——因为答辩时老师会亲手点击如果看到的推荐结果和自己的兴趣完全对不上就要想办法让“推荐结果为何是这样”说得通。最推荐的做法是给每本推荐书加上一个“推荐理由”。比如“因为有你在某分类的偏好”“因为你浏览过《XXX》”等。在item-based的实现里这个推荐解释很好做你推荐《Effective Java》是因为用户给《Java编程思想》打了5分。把这个加权贡献最高的“来源书”存下来渲染到页面上就完成了解释。这不只是演示加分也是真实系统里的核心功能——推荐解释能显著提升用户对系统的信任度。展示层面还可以加一个“热门榜”Tab和“为你推荐”Tab并列这样即使用户不喜欢推荐也能从热门榜里找到自己感兴趣的内容。前端实现上无非是多一个查询参数的问题不值一提但值得做。5. 实操过程中的坑与排查技巧速查5.1 评分数据没有区分度算出来的相似度全是零这是新手里最经典的症状造了200条评分数据以为算法一定能跑出推荐结果页面空无一物控制台日志里全是相似度0。排查路径先看“同时评过物品i和物品j的用户数”。如果每个用户平均只评5本书、每本书平均只有2个人评过那绝大多数物品对没有公共用户相似度自然是0。解决方向造数时考虑“长尾分布”和“集中性”。让少量热门的书被大量用户评分大量冷门的书只有少量用户评分——这才是真实场景。或者直接降低稀疏度把用户行为数据里的“浏览记录”也作为弱评分比如浏览计1分收藏计3分购买计5分多渠道凑出来一个不那么稀疏的矩阵。或者缩小相似度候选池只对同分类内的物品计算相似度分类内的物品数量有限公共用户概率会大很多。这个坑踩一次就长记性了所以我在前面才会反复强调造数质量的重要性——数据才是推荐系统的灵魂。5.2 相似度计算慢得离谱怎么办启动时全量计算5000本书的相似度计算量大约5000×4999/2次余弦计算 —— 大约1250万次。听起来很多但单次余弦计算只是几个循环操作硬件不差的话秒级就能跑完不是性能瓶颈。真正的瓶颈在数据库层。每次从数据库里查“同时给i和j打过分的用户”如果不够细心很容易设计成N1查询。把所有数据一次性load到内存里用Map操作代替反复查询数据库运算速度可以提升两个数量级。另一种优化是加一张“物品共现表”预处理阶段扫描所有用户的评分记录统计任意物品对共同出现的次数这个共现次数可以作为相似度的近似替代品或者用于过滤不可能成为相似邻居的物品对。共现表本质上是对相似度计算的索引可以大幅减少无效计算量。5.3 热点数据冲突热门书永远排在前面Item-based有时会产生一个现象推荐列表里全是“大路货”和用户偏好毫无关系。原因是热门书被大量用户评分导致几乎所有其他物品都和热门书共同出现过计算出的相似度天然偏高。最终修正方案是给热门物品一个惩罚因子业界标准做法是改进的余弦相似度也叫“归一化点击率相似度”sim(i, j) |U(i) ∩ U(j)| / sqrt(|U(i)| * |U(j)|)这个式子不依赖评分值但由于|U(i)|和|U(j)|的差别很大热门物品分母很大分子相对不足热度作用被大幅抑制。你可以把它和调整余弦相似度做加权融合评分相关性占60%权重行为共现性占40%权重——这个配比实际调起来要试不同数据集最优配比不同但至少它是一个可控的平衡方案。5.4 常见问题速查表症状可能原因解决方案推荐结果为空评分矩阵太稀疏相似度全是0增加评分数据密度或改用同分类内相似计算推荐结果全是热门书热门物品主导相似度采用对数惩罚或Jaccard/共现加权同一个用户两次推荐结果不同使用了random排序或并行计算顺序不稳定固定排序规则数据截止相同则结果稳定新用户无推荐冷启动未处理返回热门榜兜底相似度全部为NaN分母为0如某个物品没有评分方差加平滑因子float转换前检查分母接口响应慢实时计算相似度改为预计算存表或缓存评分不生效唯一索引冲突或数据库事务未提交检查联合唯一索引和Service层事务注解5.5 答辩场上老师最爱追问的三个问题“你的系统里协同过滤和传统搜索引擎有什么区别”搜索引擎是“用户主动找信息”推荐系统是“信息主动找用户”。协同过滤不依赖用户输入关键词而是从历史行为中推断偏好核心是“多对多的行为关系挖掘”。“如果用户数量远大于物品数量你的方案还凑效吗”基于物品的协同过滤会先算物品-物品相似度物品数少、计算可控在线推荐时是“查表”所以对用户规模不敏感。但如果物品数量也爆炸式增长则需要引入局部敏感哈希等技术做近邻搜索这是更深一层的优化方向。“不同用户对同一本书打分标准不一样怎么办”用均值中心化减去用户平均分消除评分尺度差异——这在计算相似度时已经做了在预测阶段再加上基准分用户平均分做整体还原——这是我们系统里最终采用的方案。写在最后的体会这套项目从零到一做完我最大的感受是推荐系统真正的难点不是“写一个算法”而是“让一个算法在真实数据约束下稳定可用”。评分稀疏了怎么办、相似度计算耗时怎么办、新用户来了怎么响应——这些坑都是理论课本上不太会细讲的内容却是实际工作中最耗时间的部分。毕设做到位了你收获的不仅是一个“基于协同过滤的网上书店推荐系统”更是一套完整的数据处理思维和系统设计能力。如果你时间充裕做完基础版的协同过滤之后可以考虑再加一个方向上的小扩展比如把浏览记录、收藏、购买三类行为分别赋予权重融合成综合评分或者加一个简单的A/B测试页面记录用户的反馈行为。这些扩展不会让工作量爆炸但能让系统的推荐效果有一个可见的提升答辩的时候也更有话可说。项目代码的组织尽量保持整洁推荐引擎独立成一个模块后面扩展任何功能都不需要动到主业务代码。最后提一个答辩演示的小技巧提前准备好一个“数据效果对比”的页面比如同一个用户在不使用推荐和使用推荐时的页面差异截图。事先录好演示视频万一现场网络抽风也不至于手忙脚乱。这些细节看着不起眼实操时能帮你稳住心态。

读完文章,也想定制专属网站?

尧图设计师 24 小时内与您沟通定制方案

免费获取报价 →
↑