首页 / 资讯中心 / 文章详情

基于协同过滤的Java电影推荐系统:从JSP到TopN推荐落地

基于协同过滤的Java电影推荐系统:从JSP到TopN推荐落地 ★ FEATURED ARTICLE
简介基于协同过滤算法的Java电影推荐系统源码面向Java Web开发者、计算机专业学生以及需要搭建个性化推荐平台的实践者。系统以用户历史行为数据为基础通过协同过滤算法挖掘观影偏好前端采用JSP配合JavaScript、CSS渲染页面后端以Java实现完整推荐逻辑并配有数据库脚本与基础数据适合作为课程设计或毕业设计参考。资源包共76个文件压缩后1.1MB包含38个Java源文件、15个JSP页面、10个XML配置文件、2个SQL脚本以及JS、CSS、properties等辅助文件目录按业务逻辑、页面展示和资源配置划分便于导入IDE后快速启动运行。已有823人学习下载这套源码能帮助开发者完整理解协同过滤从数据预处理、相似度计算到生成推荐列表的工程落地过程并可直接在现有框架上优化算法、扩展功能或替换数据源用于自己的推荐场景。1. 基于协同过滤算法的 Java 电影推荐系统先看懂“猜你喜欢”怎么落地一个基于协同过滤算法的 Java 电影推荐系统刚解压时很容易被它的体积迷惑76 个文件38 个 Java 源文件15 个 JSP 页面看起来就是一个普通的 Java Web 工程。真正把它跑起来后我才意识到它的价值不在页面好不好看而在于用最简单的 JSP Servlet MySQL 组合把协同过滤推荐从数据导入到 TopN 推荐完整串了一遍。这个系统做的事很直白用户注册登录后给电影打分系统根据所有用户的评分历史找到当前用户口味最接近的几个人再把他们看过而你还没看的电影推荐出来。适合想在一周内读懂推荐算法落地流程的开发者也适合需要在毕业设计里快速跑通一个可用系统的同学。2. 项目全景技术栈、目录结构与一个 76 文件的 Java Web 工程2.1 技术栈评估为什么它选 JSP Servlet MySQL这个工程没有使用微服务也没有引入复杂的推荐算法库核心依赖就是 Java 语言、JSP 动态页面、Servlet 请求控制、MySQL 数据存储前端用 JavaScript 和 CSS 做辅助交互。Maven 负责依赖管理和打包部署目标是一个普通的 Tomcat 容器。放在今天看这套组合不算新但作为推荐系统的学习载体反而更合适因为所有逻辑都直接暴露在你能改的位置不会被框架屏蔽成黑匣子。整个压缩包的 76 个文件构成很有代表性。Java 源文件 38 个对应控制层、业务层和数据层JSP 页面 15 个负责登录、注册、电影列表、评分和推荐结果展示XML 配置文件 10 个包含 pom.xml、web.xml 以及可能存在的 Spring 配置JavaScript 脚本 4 个主要处理评分交互和前端校验SQL 脚本 2 个一个建表一个造数据CSS 样式表 2 个、属性配置 1 个、Markdown 文档 1 个外加 PNG 和 ICO 图标各 1 个。从这个比例能看出这是一个功能完整的 Web 应用而不是单纯挂在 Git 上的算法实验。文件类型数量在工程中的典型位置Java 源文件38src/main/javaJSP 页面15src/main/webappXML 配置文件10src/main/resources 与根目录JavaScript 脚本4src/main/webapp/static/jsSQL 数据库脚本2data_baseCSS 样式表2src/main/webapp/static/css表格里出现了一个关键概念src/main/java 下通常会拆出 servlet、service、dao 三个包。dao 层只负责和 JDBC 打交道service 层组织业务规则servlet 层只做请求参数接收和页面跳转。协同过滤算法一般作为 service 层的一个工具类出现方便被多个 Servlet 复用。2.2 readme.txt 与目录结构先按 Maven 惯例还原工程骨架把 upload.zip 解压后最该先打开的是根目录的 readme.txt。它通常会写数据库导入顺序、默认账号密码、访问入口地址。不要一上来就找推荐算法代码先把这份说明读一遍能省掉至少半小时试错时间。data_base 目录下放着 movie.sql 和 data.sql前者是建库建表脚本后者是初始化数据脚本。pom.xml 在根目录说明这是一个标准 Maven 工程。按 Maven 的标准布局我习惯先把文件清单整理成下面这棵目录树再拿它去对照实际解压结果upload/ ├── data_base/ │ ├── movie.sql │ └── data.sql ├── pom.xml ├── readme.txt └── src/ ├── main/ │ ├── java/ │ ├── resources/ │ └── webapp/ └── test/这棵树的逻辑需要解释一下src/main/java 放所有后台 Java 类包括 Servlet、Service、DAO 和协同过滤算法工具类src/main/resources 放 JDBC 配置、日志配置等 XML 和 propertiessrc/main/webapp 放 JSP 页面、JavaScript、CSS 和静态图片src/test 放单元测试。data_base 为什么放在 src 外面因为数据库脚本不属于代码编译范围放在根目录下方便交付时一眼看到。如果解压后的目录结构与这棵树不一致也不用慌。很多交付包会把 src 下的 java 和 resources 再压一层或者把 webapp 直接暴露在最外层。判断依据是 pom.xml 里的打包标签war 代表要往 Tomcat 里部署jar 则说明某些模块被拆成了独立库。这个项目具备 JSP 页面和 webapp 目录基本可以确定是 war 打包方式。2.3 pom.xml 与 web.xml先看清请求由谁处理、依赖从哪来pom.xml 是这套系统的依赖总闸。我根据这类工程的惯例把它还原成下面这段结构具体版本号以解压出的文件为准但依赖项基本是这几类!-- 常见 Java Web 推荐系统的依赖骨架 -- dependencies !-- Servlet 与 JSP 支持打包时不需要带上 -- dependency groupIdjavax.servlet/groupId artifactIdjavax.servlet-api/artifactId scopeprovided/scope /dependency !-- JSTL 标签库JSP 页面里 c:forEach 依赖它 -- dependency groupIdjavax.servlet/groupId artifactIdjstl/artifactId /dependency !-- MySQL 驱动负责 JDBC 连接 -- dependency groupIdmysql/groupId artifactIdmysql-connector-java/artifactId /dependency /dependencies这里最容易出错的是 servlet-api 的 scope。provided 表示编译时需要、打包时排除因为 Tomcat 自带 Servlet 实现如果把这个包打进 war部署时经常出现 ClassCastException 或 LinkageError。jstl 是给 JSP 里的 c:forEach、fmt:formatDate 这类标签用的少了它页面会直接报找不到标签库。mysql-connector-java 是 JDBC 驱动它的版本建议和 MySQL 的大版本匹配用 5.x 驱动去连 MySQL 8 会遇到认证协议问题。再来看 web.xml。它决定了哪些 URL 进入哪些 Servlet。下面是一段简化的映射servlet servlet-namerecommendServlet/servlet-name servlet-classcom.example.web.RecommendServlet/servlet-class /servlet servlet-mapping servlet-namerecommendServlet/servlet-name url-pattern/recommend/url-pattern /servlet-mapping可以看到用户访问 /recommend 这个路径时容器会把请求交给 RecommendServlet 处理。推荐请求在这套系统里只是其中一条链路登录、注册、评分提交一般也各有一个 Servlet 或一个 Servlet 里的不同方法。打开 web.xml 里默认首页标签能直接看出应用启动后到底要访问哪个 URL这对后面部署很关键。如果工程里引入了 Springweb.xml 还会多出 ContextLoaderListener 和 DispatcherServlet 两段配置但即便没有 Spring上面的框架也一样能跑。判断方法很简单看 src/main/java 里的 Servlet 类是继承 HttpServlet 还是使用 Controller 注解前者是纯 JSP/Servlet 路线后者是 Spring MVC 路线。3. 数据库与算法核心协同过滤从建表到推荐结果输出3.1 movie.sql 与 data.sql用户、电影、评分三张表的设计思路协同过滤算法依赖的数据非常简单无论外面包装得多复杂底层一定存在“谁给什么打了多少分”这样的三元关系。所以 data_base/movie.sql 里最核心的是三张表用户表、电影表、评分表。下面这段是我按常见实现还原出的建表语句-- 用户表存登录账号 CREATE TABLE t_user ( uid INT PRIMARY KEY AUTO_INCREMENT COMMENT 用户ID, username VARCHAR(50) NOT NULL COMMENT 登录名, password VARCHAR(50) NOT NULL COMMENT 密码 ); -- 电影表存电影基本信息 CREATE TABLE t_movie ( mid INT PRIMARY KEY AUTO_INCREMENT COMMENT 电影ID, title VARCHAR(200) NOT NULL COMMENT 电影标题, genres VARCHAR(100) COMMENT 类型多个用逗号分隔 ); -- 评分表协同过滤最核心的数据来源 CREATE TABLE t_rating ( uid INT NOT NULL COMMENT 用户ID, mid INT NOT NULL COMMENT 电影ID, rating TINYINT NOT NULL COMMENT 评分 1-5, rating_time DATETIME DEFAULT NULL COMMENT 评分时间, PRIMARY KEY (uid, mid) );表名这里有个细节值得注意user 是 MySQL 的保留字直接建表容易在部分版本上报错所以我习惯用 t_user 这类带前缀的表名。如果原脚本里确实建的是 user 表执行时要么加反引号要么改名。电影表里的 genres 用逗号分隔多个类型这是最简单省事的做法但也意味着类型统计需要再做字符串拆分。评分表的主键是 (uid, mid) 联合主键保证一个用户对同一部电影只会有一条评分记录这也正是用户相似度计算的输入前提。data.sql 的作用是往这三张表里灌演示数据。它通常会造十几个用户和几十部电影再为每个用户随机生成若干条评分。别小看这份数据它决定了推荐效果能不能展示出来。如果评分记录太少比如每个人只评过两三部电影那么任何两个用户之间共同评分数都达不到阈值推荐列表会被判定为空这在 debug 时很像一个算法 bug但其实是数据太稀疏。所以我在本地复现时会先数一下 data.sql 里有多少条 INSERT INTO t_rating低于一百条就先手动补几组密集评分。3.2 协同过滤算法实现皮尔逊相似度与评分预测这个项目的算法主体是 User-Based Collaborative Filtering也就是基于用户的协同过滤。它的核心步骤只有三步计算当前用户与其他用户的相似度找到相似度最高的若干个邻居汇总邻居对某部电影的评分来预测当前用户会打几分。第一步的相似度计算方式很多工程里最常见的是一种叫皮尔逊相关系数的做法它比简单余弦相似度更适合评分尺度不一致的场景。public double pearson(MapInteger, Double userRatings, MapInteger, Double otherRatings) { // 只保留两个用户都评分过的电影 SetInteger commonKeys new HashSet(userRatings.keySet()); commonKeys.retainAll(otherRatings.keySet()); if (commonKeys.size() 2) { return 0.0; } int n commonKeys.size(); double sumUser 0, sumOther 0; double sumUserSq 0, sumOtherSq 0, sumProduct 0; for (Integer movieId : commonKeys) { double uScore userRatings.get(movieId); double oScore otherRatings.get(movieId); sumUser uScore; sumOther oScore; sumUserSq uScore * uScore; sumOtherSq oScore * oScore; sumProduct uScore * oScore; } double numerator n * sumProduct - sumUser * sumOther; double denominator Math.sqrt((n * sumUserSq - sumUser * sumUser) * (n * sumOtherSq - sumOther * sumOther)); if (denominator 0.0) { return 0.0; } return numerator / denominator; }逻辑说明commonKeys 是两人共同评过的电影集合交集小于 2 时直接返回 0因为样本太少算出来的相似度没有统计意义。分母为零说明两个用户的评分向量没有波动比如都给所有共同电影打了同一个分这时无法判断相关性同样返回 0。返回值的范围是 -1 到 1越接近 1 代表口味越一致为负值时则不要用于推荐。这个方法在计算时没有任何外部依赖方便单独跑单元测试。有了相似度下一步是预测当前用户对某部没看过的电影的评分。常见做法是把所有邻居的评分按相似度加权平均代码大概长这样public double predictScore(int uid, int movieId, MapInteger, MapInteger, Double ratingData) { MapInteger, Double currentUserRatings ratingData.get(uid); if (currentUserRatings null || currentUserRatings.containsKey(movieId)) { return 0.0; } double totalSimilarity 0.0; double weightedScore 0.0; for (Map.EntryInteger, MapInteger, Double entry : ratingData.entrySet()) { int otherUserId entry.getKey(); if (otherUserId uid) { continue; } MapInteger, Double otherRatings entry.getValue(); if (!otherRatings.containsKey(movieId)) { // 这个邻居没看过目标电影不能参与预测 continue; } double similarity pearson(currentUserRatings, otherRatings); if (similarity 0) { continue; } weightedScore similarity * otherRatings.get(movieId); totalSimilarity similarity; } if (totalSimilarity 0.0) { return 0.0; } return weightedScore / totalSimilarity; }逻辑说明外层遍历全体用户内层只挑那些同样看过目标电影的邻居参与计算。“没看过目标电影”的邻居被 continue 跳过因为他提供的评分没有意义。weightedScore / totalSimilarity 的意思是相似度越高的用户他打的分数对最终结果影响越大。如果所有相似度都小于等于 0最终返回 0表示无法预测。预测出每个候选电影的分数后按分数从大到小排序取前 N 部电影返回就是推荐列表。这里 N 一般由调用方控制比如传入 10 表示推荐 10 部。排序可以用简单集合操作完成整个算法没有使用第三方推荐库这对理解原理非常有帮助。3.3 Servlet 到 JSP一次推荐请求的完整生命周期推荐算法只是核心它还要通过 Web 层暴露给浏览器。一次完整请求从用户点击“为我推荐”开始到浏览器渲染出电影卡片结束链路大致是JSP 页面提交或跳转Servlet 接收请求Service 层取出评分数据并调用算法类最终把 List 放到 request 属性里转发给 JSP。下面是一个简化后的推荐接口代码WebServlet(/recommend) public class RecommendServlet extends HttpServlet { protected void doGet(HttpServletRequest request, HttpServletResponse response) throws ServletException, IOException { HttpSession session request.getSession(false); if (session null || session.getAttribute(currentUid) null) { response.sendRedirect(login.jsp); return; } int uid (Integer) session.getAttribute(currentUid); ListMovie topN RecommendService.recommendForUser(uid, 10); request.setAttribute(topN, topN); request.getRequestDispatcher(/WEB-INF/jsp/recommend.jsp) .forward(request, response); } }参数说明uid 从登录态里取而不是从 URL 取避免一个用户拿着别人的 ID 去拉推荐列表10 是本次推荐的电影数量RecommendService.recommendForUser 内部会读评分数据并调用前面的相似度与预测方法。最后用 forward 而不是 sendRedirect 跳转目的是让 JSP 能共享到 request 里的 topN 属性重定向会把 request 对象丢掉。对应的 JSP 页面用 JSTL 循环渲染这份列表。工程里能看到整页 15 个 JSP除了 recommend.jsp还有登录页、注册页、电影列表页、评分页、个人中心页。它们之间的关系通过 URL 串起来登录成功跳到电影列表用户点“评分”提交到评分 Servlet评分完成后再跳到推荐页。这个闭环不出 bug整个系统就能演示出“越用越懂你”的效果。4. 本地跑通从解压 upload.zip 到浏览器出现推荐列表4.1 环境准备JDK、Maven、MySQL、Tomcat 一个都不能少在启动之前先确认机器上有四样东西JDK 8 或更高版本Maven 3.6 左右MySQL 5.7 或 8.0以及一个 Tomcat 8.5/9.0。这套组合对版本不算挑剔但 JDK 别用太新的 17 或 21 去跑老旧编译产物容易出现 UnsupportedClassVersionError。我一般先用三条命令把环境探一遍java -version mvn -v mysql --version这三个命令分别确认 Java 运行时、Maven 构建工具和 MySQL 客户端是否可用。Tomcat 未必需要加入 PATH因为后面会直接进它的 bin 目录启动。如果 java -version 显示的不是你期望的版本检查 JAVA_HOME 环境变量Maven 和 Tomcat 都会优先读它。端口方面如果之前跑过别的项目占用了 8080启动 Tomcat 时会直接报 Address already in use这时去 conf/server.xml 里把 Connector 端口改成 8081 就行。4.2 导入数据库movie.sql 是建表data.sql 是造数据打开命令行先进入解压出来的 upload 根目录再执行下面两条命令mysql -uroot -p data_base/movie.sql mysql -uroot -p data_base/data.sql第一条命令创建数据库和表结构第二条命令把演示用户、电影、评分插入表里。注意顺序不能反data.sql 依赖 movie.sql 里已经存在的表先执行会直接报“表不存在”。如果你把数据库脚本放在其他路径把 data_base/movie.sql 替换成对应的绝对路径即可。如果提示“文件无法打开”大概率是当前所在目录不在 upload 根目录下。用 pwd 看一眼或者把命令改成绝对路径。执行成功没有任何输出是正常的MySQL 的批量导入默认静默完成。导入结束后可以用下面这条 SQL 快速检查数据量SELECT COUNT(*) FROM t_rating;如果返回行数大于 0说明评分数据已经就位。这一步可以直接验证之前对 data.sql 的猜测评分记录越多推荐效果越好。4.3 改数据库连接配置记住三个常错点项目里引用数据库配置的 properties 文件一般位于 src/main/resources 下内容大致如下jdbc.drivercom.mysql.cj.jdbc.Driver jdbc.urljdbc:mysql://localhost:3306/movie_db?useUnicodetruecharacterEncodingutf8useSSLfalse jdbc.usernameroot jdbc.password123456这里有三处容易踩坑第一jdbc 密码必须改成你本机 MySQL 的 root 密码第二movie_db 要与 movie.sql 里 CREATE DATABASE 的库名保持一致如果脚本里叫 recommend_db这里就要写 recommend_db第三URL 里的 characterEncodingutf8 参数用来保证中文不出乱码少了它推荐页上的电影标题会变成问号。driver 类名也要对版本MySQL 8 用 com.mysql.cj.jdbc.DriverMySQL 5 则还是旧驱动名。改完之后最好用 IDE 或命令行确认 classpath 里是否包含这个 properties。如果修改不生效常见原因是 Maven 只把 src/main/resources 下的文件打进了 target/classes而配置文件被放在了 webapp 下的 WEB-INF 里。检查方法很简单看 target/classes 下有没有同名文件。每次改完属性必须重新执行 mvn package否则 Tomcat 里跑的仍是旧配置。4.4 Maven 打包并部署到 Tomcatwar 包路径决定了浏览器访问地址配置改完后在项目根目录执行mvn clean packageclean 会删掉上次的 target 目录避免旧 class 文件残留package 会执行编译、测试并打包。如果 pom.xml 里配置了 war 插件命令结束后 target 目录会出现一个 .war 文件比如 movie-recommend.war。这个 war 就是整个项目的可部署产物。如果你的工程里有测试类package 阶段会先跑 mvn test测试失败会导致打包中断这时可以用 mvn clean package -DskipTests 临时跳过但最好还是先看一下测试日志。然后把它复制到 Tomcat 的 webapps 目录cp target/movie-recommend.war ~/apache-tomcat-9.0.x/webapps/Tomcat 在启动时会自动解压 war所以不需要手工建目录。接着启动服务cd ~/apache-tomcat-9.0.x/bin ./startup.sh看到 “Tomcat started” 后浏览器访问 http://localhost:8080/movie-recommend/。这里要提醒一下最后的路径要和 war 包名一致如果 war 叫 recommend.war访问路径就是 /recommend。如果页面返回 404去 Tomcat 的 logs/catalina.out 看启动日志通常能在前三行里找到真正原因。我一直用这个顺序排查先看数据库连接有没有报错再看 web.xml 里首页定义最后确认 war 解压出来的目录是否完整。4.5 可选的 IDE 启动把 Tomcat 集成进去做断点调试如果不想命令行打包复制 war也可以直接在 IDE 里配置 Tomcat。将项目导入为 Maven 工程在 Run Configuration 里选 Tomcat ServerDeployment 里把 war 或 exploded war 加进去然后点启动。这种方式的好处是可以断点调试 RecommendServlet能看到相似度计算中间变量适合后面做二次开发。但要注意IDE 启动时可能不执行 mvn clean package导致你改了 Java 代码后 target 里还是旧 class。我的习惯是每次修改后都先跑 mvn compile再重启 Tomcat或者把热加载组件配好。否则会出现“代码明明改了运行效果没变化”的假 bug。这个坑在改推荐算法时特别容易碰到因为算法代码很少报错只是结果不对。5. 避坑指南跑这套推荐系统最容易踩的五个坑5.1 现象启动后首页空白日志也没有异常用浏览器直接访问 welcome-file 指定的 JSP结果页面白茫茫一片Tomcat 日志里也没报错。原因是 JSP 被放在了 src/main/webapp/WEB-INF/jsp 目录下而这个目录对浏览器不可见必须通过 Servlet 转发才能访问。解决方法是先确认 readme.txt 里给的访问入口通常是一个 /login 或 /index 的 Servlet 路径而不是直接把 JSP 地址敲到浏览器里。如果 web.xml 里定义了 /login 映射就访问 http://localhost:8080/movie-recommend/login让容器把请求转发到对应 JSP。以后再遇到白屏我会先看一眼地址栏是 JSP 还是 Servlet 路径。5.2 现象页面中文全部变成问号或乱码登录后电影标题、用户昵称显示成 ???数据库里看却是正常中文。这是典型的字符集不一致问题。我的排查顺序是先看 JSP 顶部的 pageEncoding 是不是 UTF-8再看 JDBC 连接 URL 有没有 characterEncodingutf8最后确认数据库表本身的字符集不是 latin1。三层任何一层不是 UTF-8都会在某个环节把中文搞乱。解决方法是把三处都统一成 UTF-8同时重启 Tomcat。如果还乱就要检查 MySQL 配置文件里的 default-character-set。这个问题在第一次部署时几乎必现遇到不要急着改代码先查编码。5.3 现象推荐列表一直为空算法好像没生效用文档里的默认账号登录点进推荐页列表是空的。我在 debug 时第一个反应是算法算错了实际上大概率是评分数据太稀疏。协同过滤需要两个用户之间有共同评分才能算相似度如果当前用户只评分过一两部电影而其他用户恰好没看过共同交集小于阈值相似度全是 0推荐结果自然为空。解决方法是先用 data.sql 里已有的高评分用户测试比如从 t_user 里找一个评分记录最多的用户登录如果还是空直接在 t_rating 表里手动插入几条高密度评分。要注意插入后清缓存或重启否则内存里加载的评分数据不会自动更新。5.4 现象Maven 打包报错找不到 JSTL 或 Servlet 类执行 mvn clean package 时报 ClassNotFound 或 Missing artifact javax.servlet:jstl。这个问题的根源是本地 Maven 仓库里没有对应依赖或者 pom.xml 里没写全依赖坐标。解决方法是先执行 mvn clean package -U 强制从远程仓库拉一次看是否解决问题如果本地仓库被断网污染可以删除 ~/.m2/repository/javax/servlet 目录后重新拉取。还有一个容易忽略的细节servlet-api 的 scope 必须是 provided如果误写成 compile打出的 war 会同时带上 servlet 实现类部署后出现类冲突表现形式也是奇怪的启动失败。5.5 现象CSS 和 JS 全部 404页面光秃秃的打开页面后 HTML 结构正常但 CSS 没加载控制台一堆 404。原因是 JSP 里引用了 /css/style.css 这种根路径写法而工程部署后带的上下文路径是 /movie-recommend所以真实地址应该是 /movie-recommend/css/style.css。解决方法是把 JSP 里的静态资源路径统一改成 ${pageContext.request.contextPath}/css/style.css。这套系统里有 2 个 CSS、4 个 JavaScript如果每个页面都写死根路径移动目录后就会集体失效。我后来干脆在 JSP 顶部用一个变量拼好完整前缀彻底避免这个问题。6. 二次开发把基于用户的协同过滤改成基于物品的协同过滤如果你已经跑通了这套系统下一步最容易落地的改造是把它从 UserCF 改成 ItemCF也就是基于物品的协同过滤。核心逻辑改动非常小UserCF 计算的是用户与用户的相似度ItemCF 计算的是电影与电影的相似度。推荐逻辑相应变成“你看过一部电影系统找到与它最相似的其他电影推荐给你”。在现有工程里只需要新建一个 ItemCF 类把前面看到的方法复用一套public double itemSimilarity(MapInteger, Double movieRatingsA, MapInteger, Double movieRatingsB) { // 与用户相似度相同的皮尔逊公式但传入的是“电影-评分”映射 return pearson(movieRatingsA, movieRatingsB); }参数说明movieRatingsA 是某部电影收到的所有用户评分键是 uid值是分数movieRatingsB 是另一部电影的评分映射。pearson 方法可以直接复用因为数学公式只关心两组数字序列是否线性相关不关心这个序列代表的是用户还是电影。改成 ItemCF 之后推荐结果会更偏向“类型相似”的电影适合电影数量多、用户评分少的数据场景。验证这次改动是否有效不要只看页面。我会先写一个最小的 main 方法手动构造一个只有 5 个用户、6 部电影的小评分矩阵分别用 UserCF 和 ItemCF 算出 TopN确认结果符合直觉再打开浏览器看真实数据。如果连小样本都过不了直接 debuge 代码不要在前面绕。改算法时我也建议保留两套实现通过一个 service 开关来切换这样随时能对比效果也方便后面加评分时间权重或类型冷启动规则。从那以后我每次动完推荐逻辑都会强制走一遍最小验证先不管页面直接写一个 main 方法调用 service 层打印 TopN 结果再打开页面看展示页面不对就先改路径和控制台日志不急着怀疑算法。这套流程帮我避开了不少“看起来像玄学”的推荐问题。希望帮到你。本文还有配套的精品资源点击获取
阅读完成 · 觉得有帮助?
咨询建站