资讯详情

资讯详情

协同过滤图书推荐系统毕业设计:ItemCF算法到Java工程实现

简介本系统以协同过滤算法为核心基于Java与SSM框架Spring、SpringMVC、MyBatis实现图书个性化推荐覆盖首页展示、用户管理、书籍分类、收藏、订单、系统管理等模块适合用于毕业设计、课程设计及推荐系统入门学习。压缩包内含829个文件以Java源码、Vue前端页面、JavaScript脚本、CSS样式、HTML页面、SQL数据库脚本及XML配置文件为主整体约23.39MB结构完整便于部署与二次开发。目前已有57人学习下载资料包含完整前后端代码及数据库脚本并附带安装运行批处理文件可帮助快速搭建环境对于想了解协同过滤推荐流程、SSM整合开发或完成课设毕设的读者具有直接参考价值。 把“基于协同过滤算法的图书推荐系统源码Java毕业设计完整源码LW.zip”拆开看这个压缩包交付的是三样东西一套结构完整的 Java Web 工程、一个答辩必问的推荐算法、一份和源码对得上的论文文档LW。很多同学拿到这类源码包第一反应是导入 IDE 跑起来截图但答辩时评委大概率只盯着三个问题相似度怎么算为什么选 ItemCF 而不是 UserCF推荐结果有没有验证这篇文章按我带毕业设计的思路把协同过滤从公式落到 Java 代码再从数据库表结构写到 Spring Boot 接口最后给出一套能直接写进论文的评估方法和避坑清单。适合想在最短时间内跑通项目、又不想在答辩时被问倒的 Java 方向毕业生。2. 先定算法协同过滤的两种路线图书场景该选谁2.1 UserCF 与 ItemCF一个看人一个看书协同过滤算法在图书推荐系统里有两个主流分支UserCF基于用户的协同过滤和 ItemCF基于物品的协同过滤。UserCF 的逻辑是“人以群分”找到和当前用户评分习惯最接近的一批人把这些人高分评价过的书推荐出来。ItemCF 的逻辑则是“物以类聚”先算出图书之间的相似度再从用户已经评价过的书出发找出“和这本像”的其他书。两种算法落地后的行为差异很明显。UserCF 偏社交动态适合新闻资讯这种用户兴趣变化很快的场景因为新闻的生命周期只有几个小时必须靠相似用户实时拉新。ItemCF 偏内容沉淀适合图书、电商这种物品属性稳定、用户兴趣也相对稳定的场景——一本书的类别、作者、风格基本不变用户半年前喜欢读的题材今天大概率还感兴趣。在图书推荐系统里ItemCF 还有一个不可忽略的优点推荐结果可解释性强。系统给用户推荐一本《深入理解Java虚拟机》可以打上一行字“因为你看过《Java并发编程实战》”。评委看到这种推荐理由比看一个黑匣子打分更容易接受。UserCF 很难解释你只能说“因为和你相似的人也看了这本书”这种话在答辩现场很容易被追问“到底哪里相似”。图书场景的评分数据天然稀疏。一个学生用户可能只给十几本书评过分但学校图书馆的藏书上万册。UserCF 需要先找相似用户再在相似用户集合里筛书数据一稀疏相似用户往往只有一两个人推荐范围很小。ItemCF 只需要用户给少数几本书打过评分就能从这几本书出发扩散出去对稀疏数据的容忍度高很多。这也是图书推荐系统里 ItemCF 更常见的原因。2.2 图书场景的选择理由Session 短、兴趣稳ItemCF 更合适做毕业设计时选哪条路线我一般建议直接用 ItemCF 作为主算法理由有三点。第一图书的“物品特征”比用户特征稳定一本书的类别、作者、风格几乎不变但用户的口味会变、活跃周期很短。毕业设计的数据量通常只有几百到几千条评分记录UserCF 构建出来的用户相似度矩阵会被稀疏数据直接拉垮。第二推荐理由能直接展示在页面上这是天然的答辩素材。ItemCF 的推荐理由是“和你读过的某本书相似”可以在 UI 上完整展示出来。第三从实现角度看ItemCF 只要维护一个离线算好的物品相似度矩阵用户访问时实时计算量很小UserCF 则要在请求时实时算用户相似度用户一多成本明显上升。这不代表 UserCF 完全没用。很多合格的毕设会做混合推荐冷启动阶段用热门图书榜老用户用 ItemCF登录后首页展示“猜你喜欢”。这个方案在真实系统里很常见也容易讲清楚。但我见过一个典型误区主算法用了 ItemCF论文里却只字不提为什么不用 UserCF也没做对比实验。建议在论文里加一小节 UserCF 与 ItemCF 的对比哪怕只是同一个测试集上的准确率和召回率数字也比空泛地写“协同过滤效果良好”有说服力得多。2.3 相似度计算的三种公式与 Java 代码实现协同过滤的核心是相似度计算常见的有三种余弦相似度、皮尔逊相关系数、Jaccard 相似度。余弦相似度计算两个向量夹角的余弦值只看方向不看模长适合评分尺度比较统一的场景。皮尔逊相关系数先对向量做中心化再算余弦能抵消不同用户的打分习惯偏差——比如一个人习惯打 4 分以上另一个人习惯打 2 分以下他们的实际偏好可能一致皮尔逊能把这层“尺度差异”剥掉。Jaccard 相似度只看两个物品被哪些共同用户评价过完全不看分数适合只有隐式反馈点击、收藏没有评分数据的场景。对于图书推荐系统我通常用调整后的余弦相似度作为默认方案。先对每个用户的评分做均值中心化再用余弦公式。原因是不同用户的评分尺度差异太大有人只打 1 分和 5 分有人永远打 3 分如果不做中心化相似度会被评分习惯带偏。下面这段 Java 代码可以直接用在 ItemCF 的相似度计算里public double adjustedCosineSimilarity( MapInteger, Double itemARatings, MapInteger, Double itemBRatings, MapInteger, Double userAvgRating) { // itemARatings: keyuserId, value该用户对itemA的评分 // itemBRatings: keyuserId, value该用户对itemB的评分 // userAvgRating: keyuserId, value该用户对所有物品评分的均值 double numerator 0.0; // 分子中心化后乘积之和 double sumASq 0.0; // itemA 中心化后的平方和 double sumBSq 0.0; // itemB 中心化后的平方和 for (Integer userId : itemARatings.keySet()) { if (!itemBRatings.containsKey(userId)) { continue; // 只统计同时对两个物品评过分的用户 } double avg userAvgRating.get(userId); double adjustedA itemARatings.get(userId) - avg; double adjustedB itemBRatings.get(userId) - avg; numerator adjustedA * adjustedB; sumASq adjustedA * adjustedA; sumBSq adjustedB * adjustedB; } if (sumASq 0.0 || sumBSq 0.0) { return 0.0; // 防止除零方差为0时相似度无意义 } return numerator / (Math.sqrt(sumASq) * Math.sqrt(sumBSq)); }逻辑说明这个方法的关键在中心化。对于同时给 itemA 和 itemB 评过分的每个用户把两个评分分别减去该用户自己的评分均值再做点积和模长计算。如果用户 A 习惯打高分、用户 B 习惯打低分中心化后双方的数据就处于同一比较平面。返回结果在 -1 到 1 之间越接近 1 表示两本书相似度越高。一个重要的参数细节是 userAvgRating 的构建方式。不能在这个方法内部临时遍历用户的历史评分去算均值否则每对物品都要全表扫描一次几万条评分数据就能让系统卡死。正确做法是先在启动阶段统一算好每个用户的均值存成 Map 传进来。分母里的 sumASq 和 sumBSq 只要有一个为 0说明该物品的评分没有区分度直接返回 0.0 比抛异常更安全。相似度公式是否处理评分尺度偏差适合数据形态计算成本余弦相似度不处理评分范围接近低皮尔逊相关系数处理用户评分习惯差异大中调整后余弦处理图书评分稀疏且习惯差异大中写论文时评委会对相似度公式特别感兴趣我建议三种公式都跑一遍把效果最好的写进正文另外两种的实验结果放附录。这样既显得你做过对比又不浪费篇幅。3. 把推荐引擎写进 Java数据模型、矩阵构建与 Top-N 推荐逻辑3.1 数据表先设计好用户表、图书表、评分表教材里的协同过滤算法只讲矩阵但落到工程里第一步是表结构。图书推荐系统的核心表是评分表算法全靠它驱动。我一般会设计四张表用户表、图书表、评分表外加一张可选的收藏表。用户表和图书表是基础数据评分表承载算法的输入。用户表的设计要注意密码不能存明文角色字段要留出来管理员和普通用户的权限在毕业设计里虽然用得不深但表结构里要有CREATE TABLE user ( id INT PRIMARY KEY AUTO_INCREMENT, username VARCHAR(50) NOT NULL UNIQUE, password VARCHAR(100) NOT NULL COMMENT BCrypt加密后的密文, nickname VARCHAR(50) DEFAULT NULL, role TINYINT DEFAULT 1 COMMENT 1-普通用户 2-管理员, create_time DATETIME DEFAULT CURRENT_TIMESTAMP );图书表除了标题、作者、ISBN 这些常规字段一定要加click_count点击量字段。这个字段在冷启动阶段会用到新用户没有评分记录时直接用点击量最高的图书做兜底推荐。如果遗漏这个字段后面冷启动逻辑就得再补一张热门表绕远路。评分表的 unique 约束是很多人忽略的细节CREATE TABLE rating ( id INT PRIMARY KEY AUTO_INCREMENT, user_id INT NOT NULL, book_id INT NOT NULL, rating DOUBLE NOT NULL COMMENT 评分1-5允许0.5分粒度, create_time DATETIME DEFAULT CURRENT_TIMESTAMP, UNIQUE KEY uk_user_book (user_id, book_id) );uk_user_book这个唯一索引非常重要。如果没有它用户在前端重复点击评分就会插入两条记录算法加载数据后同一用户对同一本书的评分出现两次相似度计算直接出错。很多人花一整天排查推荐结果莫名其妙最后发现是评分表里有重复数据这种翻车现场我见得太多了。3.2 构建用户-物品评分矩阵的 Java 代码算法层不直接频繁查询数据库而是把评分表一次性加载到内存构建两个方向的数据结构按用户查评分、按物品查评分。这一步做得好坏直接影响后续算法性能。我习惯写一个 RatingMatrix 类public class RatingMatrix { // 按用户维度: userId - (bookId - rating) private final MapInteger, MapInteger, Double userRatings new HashMap(); // 按物品维度: bookId - (userId - rating) private final MapInteger, MapInteger, Double itemRatings new HashMap(); // 每个用户的评分均值用于调整后的余弦相似度 private final MapInteger, Double userAvg new HashMap(); public void addRating(int userId, int bookId, double rating) { userRatings.computeIfAbsent(userId, k - new HashMap()).put(bookId, rating); itemRatings.computeIfAbsent(bookId, k - new HashMap()).put(userId, rating); } public void build() { for (Map.EntryInteger, MapInteger, Double entry : userRatings.entrySet()) { double avg entry.getValue().values().stream() .mapToDouble(Double::doubleValue).average().orElse(0.0); userAvg.put(entry.getKey(), avg); } } public MapInteger, Double getUserRatings(int userId) { return userRatings.getOrDefault(userId, Collections.emptyMap()); } public MapInteger, Double getItemRatings(int bookId) { return itemRatings.getOrDefault(bookId, Collections.emptyMap()); } }逻辑说明addRating 同时维护两个维度的 Map相当于一次写入、两处索引后面无论按用户查还是按物品查都是 O(1) 级别的 HashMap 查找。build 方法在数据全部加载完后统一计算均值避免相似度计算阶段重复遍历。getOrDefault 返回空 Map 而不是 null这样调用方不需要每次都做空指针判断。RatingMatrix 通常放在 Spring Boot 的 Service 层在应用启动时一次性加载Service public class RecommendService { Autowired private RatingMapper ratingMapper; private RatingMatrix ratingMatrix; PostConstruct public void init() { ratingMatrix new RatingMatrix(); ListRating allRatings ratingMapper.selectAll(); for (Rating r : allRatings) { ratingMatrix.addRating(r.getUserId(), r.getBookId(), r.getRating()); } ratingMatrix.build(); } }逻辑说明PostConstruct 保证 Spring 完成依赖注入后自动执行初始化逻辑项目启动时一次性把评分数据读进内存。这种全量加载方案在几千到几万条评分记录下完全够用也符合毕业设计的体量。如果以后数据量涨到百万级再考虑用缓存或分布式计算现阶段不要过度设计。3.3 物品相似度矩阵与推荐得分计算有了 RatingMatrix下一步是计算物品相似度矩阵。这里只关心出现过的图书两两计算相似度。一个常见的性能优化是只保留相似度大于阈值的对阈值通常取 0.1过滤掉接近 0 的噪音数据public MapInteger, MapInteger, Double buildItemSimMatrix(RatingMatrix matrix) { ListInteger bookIds matrix.getAllBookIds(); MapInteger, MapInteger, Double simMatrix new HashMap(); for (int i 0; i bookIds.size(); i) { int bookA bookIds.get(i); MapInteger, Double row new HashMap(); for (int j i 1; j bookIds.size(); j) { int bookB bookIds.get(j); double sim adjustedCosineSimilarity( matrix.getItemRatings(bookA), matrix.getItemRatings(bookB), matrix.getUserAvg() ); if (sim 0.1) { row.put(bookB, sim); } } simMatrix.put(bookA, row); } return simMatrix; }逻辑说明内层循环从 i1 开始保证每对图书只算一次相似度节省一半计算量。当然最后 simMatrix 里只有 A 到 B 的方向没有反向所以推荐打分时无论从哪个物品出发都要同时查两个方向。阈值 0.1 是经验值设太大会丢推荐结果设太小矩阵稠密后续查询变慢。推荐得分的计算逻辑是遍历当前用户评分过的所有书对每本已评图书找到相似图书把相似度乘以用户对该书的评分再按候选书累加得分public ListInteger recommendByItemCF(RatingMatrix matrix, MapInteger, MapInteger, Double simMatrix, int userId, int topN) { MapInteger, Double scoreMap new HashMap(); MapInteger, Double ratedBooks matrix.getUserRatings(userId); for (Map.EntryInteger, Double rated : ratedBooks.entrySet()) { int ratedBookId rated.getKey(); double userRating rated.getValue(); MapInteger, Double similarBooks simMatrix.get(ratedBookId); if (similarBooks null) { continue; } for (Map.EntryInteger, Double sim : similarBooks.entrySet()) { int candidateBookId sim.getKey(); if (ratedBooks.containsKey(candidateBookId)) { continue; // 过滤掉已经看过的书 } scoreMap.merge(candidateBookId, sim.getValue() * userRating, Double::sum); } } return scoreMap.entrySet().stream() .sorted(Map.Entry.Integer, DoublecomparingByValue().reversed()) .limit(topN) .map(Map.Entry::getKey) .collect(Collectors.toList()); }逻辑说明外层循环遍历用户已评图书内层循环遍历与这些书相似的候选书。同一个候选书可能从多个方向进入推荐池比如用户看过两本书都和候选书相似scoreMap.merge 的第三个参数 Double::sum 负责把多条路径的得分累加起来。最后按得分倒序取 topN 个 bookId 返回。Stream 排序这里有一个容易踩的坑Map.Entry.comparingByValue()如果 value 类型是 Double 且存在 null 值排序会抛 NullPointerException。merge 累加的结果理论上不会是 null但如果你在别的 Service 里传入了允许 null 的 Map这个错误会非常隐蔽。4. 跑通完整毕业设计工程Spring Boot MyBatis 的项目结构与关键配置4.1 项目目录结构与 Maven 依赖拿到源码包第一步不是改代码而是确认项目结构。常见的毕设工程是 Maven 管理的 Spring Boot MyBatis前端页面用 Thymeleaf 模板引擎。目录结构如下book-recommend/ ├── pom.xml ├── src/main/java/com/example/bookrecommend/ │ ├── BookRecommendApplication.java │ ├── entity/User.java │ ├── entity/Book.java │ ├── entity/Rating.java │ ├── mapper/UserMapper.java │ ├── mapper/BookMapper.java │ ├── mapper/RatingMapper.java │ ├── service/RecommendService.java │ ├── service/UserService.java │ └── controller/UserController.java │ └── controller/BookController.java │ └── controller/RecommendController.java └── src/main/resources/ ├── application.yml ├── mapper/UserMapper.xml ├── mapper/BookMapper.xml ├── mapper/RatingMapper.xml └── templates/index.htmlentity 层只放字段和 getter/setter不写业务逻辑mapper 层用 Mapper 标注接口SQL 统一写进 XML 而不是注解里。推荐系统涉及联表查询和动态条件XML 维护起来比注解舒服很多。pom.xml 里的核心依赖就这几个不要贪多dependencies dependency groupIdorg.springframework.boot/groupId artifactIdspring-boot-starter-web/artifactId /dependency dependency groupIdorg.mybatis.spring.boot/groupId artifactIdmybatis-spring-boot-starter/artifactId version2.3.2/version /dependency dependency groupIdcom.mysql/groupId artifactIdmysql-connector-j/artifactId scoperuntime/scope /dependency dependency groupIdorg.springframework.boot/groupId artifactIdspring-boot-starter-thymeleaf/artifactId /dependency dependency groupIdorg.projectlombok/groupId artifactIdlombok/artifactId optionaltrue/optional /dependency /dependenciesSpring Boot 版本和 MyBatis Starter 版本要配对如果父工程是 Spring Boot 2.7.xMyBatis Starter 用 2.3.x如果换 Spring Boot 3.xMyBatis Starter 必须升到 3.0 以上否则启动直接报错。这是初学者最容易在环境上翻车的地方。4.2 数据库初始化脚本一套能直接导入 MySQL 的 SQL源码包里的 SQL 文件一般是初始化脚本直接导入 MySQL 就能跑。脚本逻辑通常是先删库、再建库、建表、插入演示数据DROP DATABASE IF EXISTS book_recommend; CREATE DATABASE book_recommend DEFAULT CHARSET utf8mb4; USE book_recommend; CREATE TABLE user ( id INT PRIMARY KEY AUTO_INCREMENT, username VARCHAR(50) NOT NULL UNIQUE, password VARCHAR(100) NOT NULL COMMENT BCrypt密文, nickname VARCHAR(50) DEFAULT NULL, role TINYINT DEFAULT 1, create_time DATETIME DEFAULT CURRENT_TIMESTAMP ); CREATE TABLE book ( id INT PRIMARY KEY AUTO_INCREMENT, title VARCHAR(200) NOT NULL, author VARCHAR(100) DEFAULT NULL, isbn VARCHAR(20) DEFAULT NULL, category VARCHAR(50) DEFAULT NULL, publisher VARCHAR(100) DEFAULT NULL, cover_url VARCHAR(300) DEFAULT NULL, description TEXT, click_count INT DEFAULT 0 ); CREATE TABLE rating ( id INT PRIMARY KEY AUTO_INCREMENT, user_id INT NOT NULL, book_id INT NOT NULL, rating DOUBLE NOT NULL, create_time DATETIME DEFAULT CURRENT_TIMESTAMP, UNIQUE KEY uk_user_book (user_id, book_id) ); -- 管理员账号密码用BCrypt加密不要存明文 INSERT INTO user (username, password, nickname, role) VALUES (admin, $2a$10$E1Zg8LmYb5vLQxKjXt3sH.abcdefghijklmnopqrstuvwx, 管理员, 2);密码这里特别提醒如果源码里是明文密码要么用 Spring Security 的 BCryptPasswordEncoder 生成密文替换要么在论文里说明这是一个简化处理。评委如果看到数据库里存着一串明文密码会认为你工程素养不够。加密串百度一下就能生成成本很低但观感完全不同。4.3 推荐接口与前端页面让评委看到真实效果接推荐算法和数据库打通接口设计得尽量简单清晰。一个 /recommend/books 接口接收 userId 和 topN 两个参数返回推荐图书列表RestController RequestMapping(/recommend) public class RecommendController { Autowired private RecommendService recommendService; GetMapping(/books) public Result recommendBooks(RequestParam Integer userId, RequestParam(defaultValue 10) int topN) { ListBook books recommendService.recommendByItemCF(userId, topN); return Result.success(books); } }接口字段说明topN 默认值是 10前端可以根据页面布局传不同数值。值得注意的是 RecommendService 里要同时注入 RatingMapper 和 BookMapper拿到算法返回的 bookId 列表后再批量查图书详情组装成完整对象。如果只返回 ID前端还要二次请求显得接口设计不完整。前端页面用 Thymeleaf 渲染即可不需要前后端分离。页面从当前登录用户拿 userId请求 /recommend/books?userId1topN10把返回的图书列表循环渲染成卡片。假如用了前后端分离跨域问题会非常麻烦要么在 Controller 上逐方法加 CrossOrigin要么在全局配置类里统一处理。毕业生喜欢追求前后端分离但在这个体量的项目里同源方案省下一大堆配置。application.yml 里有一个必开配置mybatis: mapper-locations: classpath:mapper/*.xml configuration: map-underscore-to-camel-case: truemapUnderscoreToCamelCase 开启后数据库里的 create_time 字段能自动映射到 Java 属性的 createTime不用手写 resultMap。不开这个配置你会发现所有时间的字段全是 null还会牵连其他驼峰字段的映射排查起来非常恼火。5. 避坑记录协同过滤图书推荐系统最容易翻车的 5 个现场5.1 评分数据全是同一个值相似度计算出 NaN现象系统能跑通但推荐列表经常为空或者接口偶尔报错。原因如果演示数据里所有用户对同一本书的评分全是 5 分或者某个用户对所有书的评分都相同调整后余弦的中心化操作会让分子分母同时变成 0计算结果是 NaN。NaN 进入排序后Comparator 无法比较Stream 直接抛异常或者返回空结果。解决相似度计算里必须加分母为 0 的判断sumASq 0 或 sumBSq 0 时直接返回 0.0。另外造演示数据时不要制造大量全 5 分的记录要让不同用户对同一本书评分有差异给算法留出区分度。我在 2.3 的代码里已经写了这个保护逻辑但如果你拿到手的源码包里没有务必自己加上。5.2 新用户没有评分记录推荐列表为空现象注册后第一次登录首页“猜你喜欢”区域一片空白。原因ItemCF 是从历史评分出发的新用户没有任何评分算法无从下手。这不是算法 bug是推荐系统的冷启动问题。解决在 recommendByItemCF 入口处加一个 ratedBooks 为空的判断走热门图书兜底if (ratedBooks.isEmpty()) { return bookMapper.selectHotBooks(topN); // 按点击量倒序 }这是最通用的冷启动方案答辩时还可以主动补充冷启动问题在真实推荐系统里会结合用户注册时的兴趣标签、图书分类偏好来综合处理目前毕业设计阶段先用热门榜兜底。这段话一说评委就知道你考虑过这个边界问题。5.3 相似度矩阵越算越大内存溢出现象评分数据量到几万条后项目启动越来越慢甚至直接 OOM。原因ItemCF 要两两计算物品相似度n 本书的理论计算量是 O(n²)。图书 3000 本、评分 5 万条时矩阵里可能存了几十万对相似度内存很快就撑不住了。解决常见做法是每个物品只保留相似度最高的 K 个邻居其余丢弃。在 buildItemSimMatrix 里row 每 put 一个值就判断大小超过 K 就移除最小的一项。K 一般取 30 到 50既保证推荐效果又控制内存占用。如果数据量再大可以把相似度矩阵换到 Redis 存但毕业设计阶段用内存加阈值截断就够了。5.4 MyBatis 映射字段对不上查询结果全是 null现象图书列表能显示标题和作者但分类、出版社、点击量全是 null。原因Java 属性是 clickCount数据库字段是 click_count中间隔了一层下划线。如果 application.yml 里没开 map-underscore-to-camel-case这些字段就映射不上去。解决两种方案二选一一是建表时字段直接用驼峰命名二是开启下划线转驼峰配置。我更推荐后者因为数据库命名习惯是下划线。另外检查实体类字段名是否和表字段完全对应比如数据库是isbnJava 属性也叫isbn没问题但如果你写成了ISBNMyBatis 的自动映射会失效。5.5 答辩被问“数据哪来的”才发现没给论文准备测试集现象评委问测试数据来源你只能说是自己录的200 条评分撑不起论文的实验表格。原因毕业设计只做了演示数据没有准备公开数据集做实验验证。解决先从演示数据里抽一部分跑通代码然后再找公开数据集做实验。图书推荐领域最常用的是 Book-Crossing 数据集包含用户对图书的评分数据规模适中非常适合离线测试。论文里写明数据来源、规模、预处理过程再给出算法在这个数据集上的精确率和召回率这三样东西远比截图有说服力。数据预处理代码也要留着答辩时能讲清楚原始数据是怎么清洗成算法输入格式的。6. 一个能写进论文的验证方法离线评估与参数选择技巧6.1 用精确率和召回率评估推荐效果答辩时最怕评委问“你的推荐效果怎么衡量”。解决办法是做一个离线评估把评分数据集按 8:2 分成训练集和测试集训练集用来算相似度矩阵测试集用来验证推荐命中率。每次给用户推荐 topN 本书如果推荐列表里有测试集里用户真实评分过的书就算命中一条。精确率是命中数量除以推荐列表长度召回率是命中数量除以测试集里用户实际评分过的图书数量。这两个指标能直接放进论文的表格里配合不同 topN 值做一组对比实验。页面上也可以加一个很小的“推荐理由”展示位把 ItemCF 的可解释性利用起来——这算是毕业设计里性价比最高的加分项。6.2 邻居数 K 与 Top-N 的选择我的默认参数与调试顺序两个参数最值得调相似度邻居数 K 和推荐结果条数 topN。K 太小推荐范围窄K 太大噪音多topN 太短覆盖不到用户真实兴趣太长精度下降。我的默认实验顺序是先固定 topN10把 K 从 5 调到 50观察精确率和召回率的变化曲线确定 K 的最佳区间后再调 topN。我自己的血泪经验是不要在演示数据上反复调参。演示数据通常只有几十条评分怎么调看起来都合理一换到真实数据集立刻翻车。正确做法是先跑公开数据集定参数再把参数迁移到演示数据上。如果论文里有“参数实验”这一节把不同 K 值下的效果表格放进去评委基本不会再在这个方向追问。希望帮到你。本文还有配套的精品资源点击获取
觉得有用,分享给同行:

为您的企业打造数字门面

稳重轻奢商务风格,端正雅致视觉,长效耐看不易过时。

立即咨询 →