SSM+Python双语言构建LFM矩阵分解商品推荐系统

发布时间:2026/9/14 23:54:20
SSM+Python双语言构建LFM矩阵分解商品推荐系统 简介这是一份基于Java SSM与Python LFM矩阵分解协同过滤算法实现的购物网站商品推荐系统毕业设计项目适合计算机相关专业学生用于毕业设计、课程设计或项目初期演示。项目聚焦电商推荐场景将SSM后端与Python推荐算法结合配有论文、数据库脚本和使用文档可帮助读者理解推荐系统从数据存储、算法训练到前端展示的完整链路。资源包共1174个文件压缩后约20.19MB主要包含JSP/Java源码及Spring配置、Python算法脚本、SQL数据库脚本、HTML/CSS/JS前端页面以及论文和操作说明文档等结构清晰便于按模块查阅。已有169人学习/下载。代码已在macOS和Windows10/11环境下运行验证答辩评审分达95分既可直接用于提交或演示也可在此基础上替换数据集、调整算法参数或扩展功能适合做完整项目落地练习与二次开发。1. 为什么用 SSM Python 双语言做商品推荐这年头做电商网站光有增删改查根本拿不出手必须带上“猜你想买”。我拆过的这个高分毕业设计就是一个基于 Java SSM 框架 Python 实现的 LFM 矩阵分解协同过滤推荐系统。Java 负责商品、订单、用户这些业务逻辑Python 专门跑推荐算法两边用 HTTP 接口通信真正做到算法和业务解耦。这套双语言设计不是炫技而是把最稳定的 SSM 架构和最灵活的 Python 生态结合起来适合做毕设、课设也适合在真实项目中当推荐模块的起步模板。下面我按架构、算法、接口和调优四个维度把它拆开新手能按步骤复现老手也能直接拿走参数设置和踩坑经验。2. SSM 项目骨架与 LFM 推荐服务的松耦合设计2.1 数据模型用户、商品与隐式反馈推荐系统最怕没有数据所以数据库设计里不能只存用户和商品还要有一张用户行为表。这个项目里我建议至少建四张核心表user、item、behavior、recommend_result。行为表是关键它记录用户对商品的点击、收藏、加购、购买等操作每一条行为都是一次隐式反馈。表名关键字段说明userid, username, created_at用户基本信息itemid, title, category, price, tags商品基本信息tags 用于冷启动behavioruser_id, item_id, action_type, score, create_time隐式反馈行为action_type 映射为评分recommend_resultuser_id, item_id, score, rank推荐结果离线存储供 Web 端快速读取行为表不能只存“买了什么”要把浏览时长、是否加购也记录进去。常见的做法是在写入时直接给一个 1-5 的评分比如点击 1 分、收藏 3 分、购买 5 分。这个评分矩阵就是后面 LFM 算法的输入。如果你直接照抄开源电商项目很可能漏掉这张表导致算法训练时没有正样本。2.2 SpringMVC Controller 如何暴露推荐接口Java 端不需要关心矩阵分解怎么算它只负责从推荐结果表或 Redis 里取数据。我一般会在 SSM 项目里单独建一个RecommendController下面是核心代码片段RestController RequestMapping(/api/recommend) public class RecommendController { Autowired private RecommendService recommendService; /** * 根据用户ID获取 Top-N 推荐商品 * param userId 用户ID * param n 推荐条数默认 10 */ GetMapping(/{userId}) public ResultListItemVO getRecommend( PathVariable(userId) Integer userId, RequestParam(defaultValue 10) Integer n) { ListItemVO itemList recommendService.getRecommendByUser(userId, n); return Result.ok(itemList); } }这段代码有两个关键点。第一接口返回的ItemVO是专门给前端展示的商品视图对象不直接暴露数据库实体防止序列化字段过多。第二recommendService内部先查 Redis如果缓存命中就直接返回否则再查 MySQL 的recommend_result表。参数n控制推荐数量前端可以动态调整。2.3 MyBatis 动态 SQL 与缓存策略从推荐结果表查询时不能用简单SELECT *因为有的用户可能还没有推荐结果需要回退到热门商品。这时候 MyBatis 的动态 SQL 就派上用场了select idselectRecommendItems resultTypeItemVO SELECT i.id, i.title, i.category, i.price FROM item i WHERE i.id IN foreach collectionitemIds itemid open( separator, close) #{id} /foreach ORDER BY FIELD(i.id, foreach collectionitemIds itemid separator, #{id} /foreach ) /selectFIELD函数可以按传入的 ID 顺序排序保持推荐列表的顺序不变。如果没有推荐结果itemIds就传入热门商品 ID 列表。这种设计让推荐结果的维护成本极低即使 Python 服务挂了用户依然能看到热门商品不至于白屏。注意缓存推荐结果时key 建议设计成recommend:user:{userId}:{n}避免不同条数的结果互相覆盖。3. LFM 矩阵分解算法原理与 Python 训练细节3.1 协同过滤为什么选择 LFM做商品推荐的常见算法有 UserCF用户协同过滤、ItemCF物品协同过滤和 LFM隐语义模型。我推荐在毕设和中小型项目里用 LFM原因是它比 ItemCF 更适合隐式反馈场景。ItemCF 需要计算物品之间的大规模相似度矩阵商品数量一旦到几十万存储开销会爆炸。而 LFM 将用户-物品评分矩阵分解成两个低维稠密矩阵用户隐含特征矩阵P和物品隐含特征矩阵Q每个用户和商品都被映射到一个 K 维特征空间K 通常取 10 到 50 之间。LF 的核心思想是用户对物品的评分可以被这两个矩阵的点积预测。公式上表示为r̂ p_u^T * q_i其中p_u是用户 u 在隐空间中的向量q_i是物品 i 的向量。这个压缩后的空间具有可解释性和泛化能力能捕捉用户的潜在兴趣。加上 Python 生态里有 numpy 和 pandas实现起来远比 Java 简洁。3.2 损失函数与梯度下降实现LFM 需要把每个用户的所有行为构造成训练样本。隐式反馈没有显式负样本常见的做法是从用户没有行为过的物品中随机采样一组负样本数量跟正样本一样或稍微多一点。下面是训练 LFM 模型的完整 Python 代码已经加好注释和参数说明import numpy as np from collections import defaultdict class LFM: def __init__(self, factors20, epochs50, lr0.02, reg_lambda0.1): :param factors: 隐向量维度 K越大拟合能力越强但容易过拟合 :param epochs: 迭代次数 :param lr: 学习率 :param reg_lambda: 正则化系数控制模型复杂度 self.factors factors self.epochs epochs self.lr lr self.reg reg_lambda self.P None self.Q None def train(self, user_item_score, n_users, n_items, n_negatives5): # 初始化隐向量矩阵范围 0~0.1 self.P np.random.rand(n_users, self.factors) * 0.1 self.Q np.random.rand(n_items, self.factors) * 0.1 # 构造正样本和负样本列表 samples [] for user, items in user_item_score.items(): for item, score in items.items(): samples.append((user, item, score)) # 随机采样负样本控制样本平衡 for _ in range(n_negatives): item np.random.randint(n_items) if item not in items: samples.append((user, item, 0)) # 随机梯度下降 for epoch in range(self.epochs): np.random.shuffle(samples) cost 0.0 for user, item, score in samples: predict np.dot(self.P[user], self.Q[item]) error score - predict # 保存原向量用于梯度更新 p_u self.P[user].copy() q_i self.Q[item].copy() # 更新 P 和 Q self.P[user] self.lr * (error * q_i - self.reg * p_u) self.Q[item] self.lr * (error * p_u - self.reg * q_i) cost error ** 2 # 每隔10轮输出损失 if epoch % 10 0: print(fepoch {epoch}, cost {cost / len(samples):.4f})参数方面factors建议取 10-30项目里商品类别多就取大一点lr太大会震荡太小收敛慢0.02 起步比较稳正则化reg_lambda控制在 0.01-0.1 之间防止过拟合。n_negatives是每个正样本对应采样的负样本个数太少模型学不到区分度太多训练时间成倍上涨一般取 3-5。如果训练时 loss 一直下降但实际推荐效果差优先调大factors或正则化系数而不是无脑增加迭代次数。3.3 从训练结果到 Top-N 推荐训练完成后模型会生成两个矩阵可以保存成.npy文件也可以直接写回 MySQL。生产环境建议离线训练然后把结果推到 Redis。下面这段代码负责为每个用户生成 Top-N 推荐列表def predict_top_n(model, user_id, item_ids, n10): 为指定用户返回 top-n 物品 ID :param model: 训练好的 LFM 模型 :param user_id: 用户 ID :param item_ids: 全部物品 ID 列表 :param n: 推荐个数 scores [] item_ids list(item_ids) # 全部物品,可排除用户已购买的物品 for item in item_ids: score np.dot(model.P[user_id], model.Q[item]) scores.append((item, score)) scores.sort(keylambda x: x[1], reverseTrue) return [item for item, score in scores[:n]]注意这里没有过滤用户已经产生过行为的物品。实际项目中你需要把用户已购买的物品 ID 传入在排序前直接set集合过滤掉。另外推荐结果写入 Redis 时可以用 JSON 序列化Java 端接收后直接反序列化成 List省去中间表查询。4. Java 调用 Python 推荐接口的工程化实现4.1 跨语言调用的三种方案对比Java 和 Python 之间传数据常见做法有三种HTTP/RESTful 接口、RPC 框架比如 Thrift、gRPC、命令行子进程调用。我用一张表对这几个方案做个对比这样你能看清为什么选 HTTP。方案维护成本性能适用场景HTTP 接口低Python 用 Flask/FastAPI 开个服务即可毫秒级推荐结果实时性要求不高适合中小项目RPC 框架高需要定义协议和跨语言编译微秒级大型系统推荐引擎被高频调用命令行调用低但每次都要启动 Python 进程秒级离线统计、批处理不适合线上实时推荐毕设和课程设计阶段HTTP 方案性价比最高。这里有一点要注意不要把训练直接放在 Java 里跑因为 Python 的数值计算库和矩阵运算能力远胜于 Java硬写会白白增加开发量。4.2 RestTemplate 调用 Flask 推荐服务实战Python 端我用 Flask 起一个recommend接口代码非常简单from flask import Flask, request, jsonify import json import numpy as np from model import load_lfm_model app Flask(__name__) model load_lfm_model() # 提前加载训练好的 LFM 模型 app.route(/api/recommend/int:user_id, methods[GET]) def recommend(user_id): top_n request.args.get(n, 10, typeint) item_scores [] item_list get_all_items() # 从数据库或缓存中加载物品列表 for item in item_list: score np.dot(model.P[user_id], model.Q[item[id]]) item_scores.append({item_id: item[id], score: round(float(score), 4)}) item_scores.sort(keylambda x: x[score], reverseTrue) return jsonify({user_id: user_id, items: item_scores[:top_n]}) if __name__ __main__: app.run(host0.0.0.0, port5000)Java 端使用 Spring 的 RestTemplate 发起调用时需要配置超时时间否则 Python 服务会因为异常没有响应导致 Java 线程阻塞。下面是推荐的配置方式Bean public RestTemplate recommendRestTemplate() { SimpleClientHttpRequestFactory factory new SimpleClientHttpRequestFactory(); factory.setConnectTimeout(2000); factory.setReadTimeout(3000); return new RestTemplate(factory); }调用逻辑可以写在RecommendService中加上 fallback 处理如果 Python 服务返回异常就直接返回热门商品列表public ListItemVO getRecommendByUser(Integer userId, int n) { String url http://localhost:5000/api/recommend/ userId ?n n; try { ResponseEntityRecommendResponse resp restTemplate.getForEntity(url, RecommendResponse.class); return convertToItemVO(resp.getBody().getItems()); } catch (RestClientException e) { log.warn(Python recommend service unavailable, fallback to hot items, e); return hotItemService.getHotItems(n); } }这里的RecommendResponse需要定义成 DTO字段和 Python 返回的 JSON 中items保持一致。关于跨语言调用的内容我见过的不少 java 面试题里也会问“如何让 Java 和 Python 服务通信”如果你能答出超时设置和降级策略基本就是满分答案。另外要注意Python 服务返回的浮点数转换成 Java 的Double可能会丢失精度业务上可以接受但如果是金额类的推荐评分建议转为BigDecimal。4.3 生产环境中的超时与降级策略推荐服务属于“锦上添花”的功能绝不能因为推荐系统挂了影响下单支付。因此超时和降级是必须考虑的部分。除了 RestTemplate 设置连接超时和读取超时还需要在 Python 服务端做好限流。给 Python 接口加上简单的 QPS 限制比如用Flask-Limiter限制每个 IP 每秒最多 20 次请求在高并发下保护模型内存。配置项推荐值说明connectTimeout2000ms连接 Python 服务的超时时间readTimeout3000ms等待响应数据的超时时间fallback 标记true调用失败返回热门商品推荐结果缓存6 小时离线结果写入 Redis减少 Python 压力还有一个容易被忽略的坑Java 服务如果部署在多台机器上每台机器都可能调用同一个 Python 服务。最好是将 Python 推荐服务单独部署一台实例Java 里配置建议使用负载均衡地址而不是 localhost。否则一旦并发热点用户Python 训练好的模型全部加载到内存里服务内存会迅速飙升。5. 推荐质量调优冷启动与离线评估5.1 冷启动问题的处理技巧LFM 只能对在行为表里有记录的用户和商品产生推荐。对于新注册用户没有行为数据矩阵分解根本无法为其生成向量。常见的做法是用“热门榜”兜底。我会在recommend_result表里固定生成一个user_id 0的虚拟用户专门存热门商品推荐结果。当查询不到某用户的推荐列表时Java 端直接查虚拟用户的记录。对于新商品可以用内容特征补足。在商品表item上增加一个tags字段存放商品类别、品牌等标签。当商品没有行为数据时使用用户过往喜欢商品的标签进行匹配计算标签重合度完成基于内容的冷启动。这个策略实现简单效果提升立竿见影。5.2 离线评估指标与 Python 评测脚本调优前必须量化推荐效果。我把用户历史行为按时间排序前 80% 作为训练集后 20% 作为测试集计算 Top-K 推荐的准确率和召回率。下面是完整的评测脚本输出每个用户的 PrecisionK 和 RecallK 均值def evaluate(model, test_data, train_data, k10): test_data: {user_id: set_of_items}, train_data 同 precision_list [] recall_list [] for user_id, test_items in test_data.items(): # 排除训练集中已经见过的物品 seen train_data.get(user_id, set()) candidate_ids [i for i in test_data_items(item_id) if i not in seen] # 使用 predict_top_n 函数返回 Top-K rec_list predict_top_n(model, user_id, candidate_ids, nk) hit len(set(rec_list) test_items) precision hit / k recall hit / len(test_items) if test_items else 0 precision_list.append(precision) recall_list.append(recall) print(fPrecision{k}: {sum(precision_list)/len(precision_list):.4f}) print(fRecall{k}: {sum(recall_list)/len(recall_list):.4f})这个脚本可以重复跑用来比较不同factors和reg_lambda下的模型效果。实际操作中往往Precision10能到 0.1 以上就已经算不错的推荐效果了不用追求数值太高因为用户兴趣本身是嘈杂的。5.3 一个可落地的折中混合推荐策略最后分享一个多数电商项目常用的技巧不要把 LFM 作为唯一推荐来源而是将 LFM 的结果与热门榜做加权融合。比如最终得分score 0.7 * lfm_score 0.3 * hot_score其中hot_score是商品近 7 天热度归一化后的值。这样既保留了个性化又避免了长尾物品霸榜。融合后的结果同样写回 RedisJava 端无感知。调优0.7 / 0.3这两个权重时可以直接修改 Python 脚本里的配置文件重新生成推荐结果不需要重启 Java 服务。确定权重后再用上面的评估脚本跑一遍离线指标确认没有明显下滑就可以上线验证了。本文还有配套的精品资源点击获取

关于本文作者

来自尧图内容编辑团队

尧图内容编辑团队 内容团队

尧图内容编辑团队

本文由尧图网络内容编辑团队执笔。团队由资深项目经理、前端工程师与设计师组成,所有内容均来自亲手交付的真实项目,先讲清问题、再给出可落地的解法。尧图深耕北京网站建设十年,服务过京华建材集团、智造科技等各行业客户,把一线经验沉淀为可复用的行业观察。

  • 十年建站经验,覆盖建材、制造、服务、文创等
  • 项目经理把关选题与事实准确性
  • 工程师与设计师联合撰写专业细节
  • 统一编辑规范,保证文风与排版一致
  • 每月复盘转化数据,迭代选题方向

延伸阅读

相关资讯与近期热门内容

深度阅读推荐

建站决策前值得细读的三篇

网站改版的5个关键决策
2024-08-12

网站改版的5个关键决策

什么时候该改版、改到什么程度、如何避免流量掉光,京华建材集团改版复盘给出答案。

获取专属建站方案

看完文章,把您的行业与预算告诉我们,免费获取一份量身定制的官网建设方案与报价。

立即免费咨询