ARTICLE DETAIL

资讯详情

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

基于Django和LSTM的淘宝用户行为分析与预测系统设计

基于Django和LSTM的淘宝用户行为分析与预测系统设计 做这个项目之前我其实刚被一个类似的问题卡了很久——手头有一份淘宝用户行为数据字段不多但量级不小怎么把它变成一套既能展示、又能预测的系统后来我完整走完了一遍数据清洗 → 特征工程 → 可视化 → 深度学习建模 → Django整合的流程把项目交付成了一套可以直接演示的毕业设计系统。这篇就完整复盘一下从架构选型到具体实现细节再到联调阶段那些坑尽量写得细一点给正在做类似方向的朋友一个能直接参考的路线。这个项目核心做的事情可以概括成一句话基于淘宝用户行为日志数据用Django搭建后端服务通过ECharts输出可视化大屏同时利用深度学习模型对用户的下一步行为做预测。简单说它既是一个数据分析平台又是一个行为预测系统。整体偏工程实践但算法部分又不至于浅尝辄止非常契合毕设或者课程设计的定位。1. 系统整体框架从原始日志到行为预测的完整链路要理解这套系统到底是怎么工作的首先得把整条数据链路捋清楚。原始数据是淘宝的用户行为日志常见的数据集中每一行记录包含用户ID、商品ID、商品类目ID、行为类型、时间戳这几个字段。行为类型一般分四种浏览、收藏、加购、购买。这条链路如果画成流程大概是这样的数据导入与清洗原始CSV导入MySQL做时间戳格式化、去重、切分训练集和测试集。特征工程把用户行为按时间排序生成统计特征和行为序列特征。可视化模块Django后端读取MySQL数据按商品、类目、用户、行为类型等维度做聚合统计通过接口返回给前端前端由ECharts渲染大屏。行为预测模块对每个用户构建行为序列送入深度学习模型输出预测结果。模型以用户ID和候选商品ID为输入预测用户在特定行为类型上的概率。1.1 Django在这里的角色不只是后端框架很多人在做类似项目时容易犯一个错误——把Django当成纯粹的接口提供方所有数据都在前端写死或由前端直接查库。这个项目里Django要做的事情远不止这些。Django在这里承担了四个职责数据管理用ORM管理数据表原始CSV清洗后灌入数据库所有查询走ORM。接口服务为可视化大屏提供JSON接口比如按小时统计PV/UV商品类目点击排行用户转化漏斗等。模型服务加载训练好的深度学习模型接收前端传来的用户行为数据返回预测结果。后台管理Django自带的Admin后台可以管理数据表、查看用户行为明细这在毕设答辩时是非常加分的演示点。在技术选型上我用的是Django 3.2 LTS版本配合MySQL 8.0。为什么不用SQLite因为这套数据量级是百万行级别的SQLite在并发查询和聚合统计时性能短板非常明显而且毕设演示时一般用的是开发服务器SQLite频繁读写会把CPU直接打满。MySQL 8.0的窗口函数在处理用户行为时间序列时也更好用。1.2 这种系统最容易被忽视的问题数据流方向整个项目里最核心的设计决策在于——可视化查询和深度学习预测需要的数据格式是完全不同的。可视化模块需要的是聚合好的统计结果而预测模块需要的是按用户分组的有序行为序列。如果一开始就把数据按可视化维度存储后面做特征工程时就会痛苦不堪。我的做法是在MySQL里保留原始行为明细表所有的聚合统计都通过SQL实时计算配合Redis做结果缓存而特征工程阶段单独从明细表中抽取数据生成独立的特征文件供训练使用。这样两条数据链路互不干扰调试时思路也清晰。2. 淘宝用户行为数据的清洗与特征工程清洗是这种项目最耗时但最不出彩的部分可一旦做不好后面所有环节都会跟着出错。这里值得单独拿出来讲一下因为大部分初次做这类项目的人都会在数据环节翻车。2.1 原始数据集的特征与挑战常用的是淘宝UserBehavior公开数据集我用的这份数据大约有一百万行左右完整版有几千万行毕设场景建议不仅是为了跑通、更为了演示流畅取百万级子集就够了。原始数据长这样字段名含义示例user_id用户ID100010item_id商品ID12296480category_id商品类目ID462522behavior行为类型pv / cart / fav / buytimestamp时间戳1511544075这份数据在真实项目里有几个明显的坑时间戳是Unix毫秒级/秒级格式需要转换成年月日和小时字段否则没法按时间维度做统计。存在重复记录尤其是浏览(pv)行为同一用户同一商品在短时间内可能被重复记录。行为分布极不均衡pv占比通常超过90%buy行为可能不到2%这在后面训练模型时会导致严重的类别不均衡。百万级数据灌入MySQL时如果不用批量插入而是逐条insert性能会差到让人怀疑人生。我在清洗时用了pandas做预处理处理完再批量灌入MySQL耗时比逐条插入快了将近三十倍。具体步骤是用pd.read_csv读取原始文件转换时间戳为datetime类型按(user_id, item_id, behavior, timestamp)去重再过滤掉异常时间戳记录。2.2 可视化模块需要的聚合特征可视化模块要用到的数据主要有这么几个维度用户行为漏斗浏览 → 收藏 → 加购 → 购买的转化率商品类目热度排行每个类目下的PV、购买量、加购量用户行为时间分布一天24小时的行为量热力图用户活跃度分布用户行为频次的分布直方图这些聚合指标在后端查询时如果用ORM逐条统计会非常慢。我的经验是直接写原生SQL用GROUP BY配合DATE_FORMAT等函数处理时间维度再把结果以JSON格式缓存到Redis里缓存有效期设置为5分钟。这样前端刷新大屏时响应速度基本在毫秒级别。2.3 预测模型需要的序列特征预测部分要处理的是另一类特征。这里要思考的问题是给定一个用户的最近N条行为记录能否预测他下一步会对什么商品产生行为我构造的特征有三类用户统计特征用户的总行为数、浏览/加购/收藏/购买各自的数量、用户最近一次行为距当前时间的天数。商品统计特征商品的总行为数、商品被购买次数、商品被加购次数、商品类目的热度。序列特征把用户最近的行为记录按时间排序取最近的5~10条作为序列输入。这里有个非常重要的细节很多人在构建序列时直接把原始行为类型(如pv, cart)丢给模型这种做法效果很差。原因是行为类型只是分类值没有任何语义信息。我的做法是把每个行为映射成一个向量——例如购买行为可以赋予更高权重浏览行为权重最低这种方式叫做行为类型embedding。配合商品ID的embedding把两个向量拼接作为序列中每个位置的输入特征。3. 可视化大屏用ECharts搭建淘宝用户购物行为分析面板可视化部分是最直观展示项目成果的模块也是在答辩时最吸引眼球的环节。这个模块的思路并不复杂但想要做得好看、好演示、有说服力还是有一些门道的。3.1 大屏布局与图表选型我的大屏采用的是经典的三栏布局顶部是标题和核心KPI指标左侧是用户行为漏斗和类目热力排行中间是用户行为时间趋势图和商品TOP10右侧是用户活跃度分布和转化率折线。对应到ECharts的图表类型用户行为漏斗用ECharts的funnel图展示浏览→收藏→加购→购买的逐层转化。类目热力排行横向条形图按类目行为量从高到低排列。24小时行为趋势用热力图heatmap横轴是小时纵轴是行为类型颜色深浅代表行为量。商品TOP10用柱状图展示热销商品。用户活跃度分布用散点图或者饼图展示不同活跃区间用户的占比。这套组合图表的优势在于每一张图都对应一个具体的业务问题答辩时老师问你为什么要用这个图你能讲出背后的业务逻辑而不是简单说这个图好看。3.2 后端接口设计可视化模块的后端接口设计我按照一个图表一个接口的方式做了拆分。比如/api/dashboard/summary返回总用户数、总商品数、总行为数、总购买数。/api/dashboard/funnel返回转化漏斗数据。/api/dashboard/hourly_trend返回24小时行为分布热力图数据。/api/dashboard/category_rank返回类目排行。/api/dashboard/top_items返回商品热度TOP10。每个接口都是一个独立的视图函数内部先查Redis缓存如果命中则直接返回否则执行SQL统计并写入缓存。前端页面加载时并行发起多个请求视觉上给人数据同时加载出来的感觉体验很好。3.3 前端页面的组织方式前端部分我用了原生HTML ECharts AJAX没有引入Vue或React。为什么不用前端框架因为这种数据可视化大屏页面的核心逻辑在于页面加载 → 请求数据 → 渲染图表交互并不复杂引入前端框架反而增加了打包和部署的复杂度。直白一点说对毕设项目来说原生写法足够也更容易让答辩老师看到你的代码组织能力。页面结构上是index.html引入echarts.min.js的CDN文件每个图表的实例都在script标签中初始化。所有图表数据都通过fetch或axios请求后端接口获得拿到数据后调用setOption填充。为了避免图表宽度自适应问题我在window.resize事件里绑定了每个实例的resize方法。4. 深度学习行为预测模型从LSTM到注意力机制这是整个项目里技术含量最高的部分。如果你也是第一次做序列类预测这部分可能会给你省下一大把调参的时间。4.1 为什么选择LSTM而不是随机森林最开始我也纠结过这个问题。从特征上看行为预测可以做成传统的机器学习二分类问题为每个(用户, 商品)对构造特征然后用XGBoost或随机森林训练分类器。但这种方法有个致命缺陷——它无法捕捉到行为的顺序关系。举例来说用户A的行为序列是浏览A商品 → 加购A商品 → 购买A商品用户B的行为序列是购买A商品 → 加购A商品 → 浏览A商品这两种行为的含义是完全不同的。前者代表一个完整的购物流程后者更像是事后浏览。传统模型把特征打平之后这种先后顺序信息就丢失了。LSTM的天然优势在于能够学习序列中相邻节点之间的依赖关系。它能通过门控机制记住用户最近加购过什么而遗忘掉更久远的信息。这正是用户行为预测的本质——通过历史行为的顺序关联推断下一步最可能的动作。4.2 模型结构的详细拆解我最后采用的模型结构是embedding层 两层LSTM 注意力层 全连接输出层。输入格式是这样的每个样本是一个(用户ID, 商品序列, 行为序列)的三元组。用户ID和商品ID分别映射为128维的embedding向量。行为类型映射为8维的embedding向量。三个向量拼接后输入两层LSTM隐藏单元数分别为64和32。LSTM输出经过一个简单的加性注意力层对序列中不同位置的隐状态做加权平均。最后一层是全连接softmax输出预测概率。训练时用交叉熵损失函数优化器用Adam初始学习率0.001训练轮数控制在20轮左右。我在训练过程中还加入了一个很关键的操作——对负样本做采样。因为购买行为在数据集中占比极低如果直接把所有样本都扔进去训练模型会倾向于把所有输入预测为不购买准确率看起来很高但没有任何实际意义。4.3 负样本采样与评估指标对于行为预测来说评估指标的选择直接影响你对模型好坏的判断。如果只看准确率在正样本占比不足2%的数据集上一个全预测为负的模型就能拿到98%的准确率这种模型毫无价值。我使用的评估指标有两个AUCROC曲线下面积衡量模型对正负样本的区分能力不受类别不均衡影响。F1-Score综合精确率和召回率的评估指标对类别不均衡更敏感。负样本采样策略上我从全部非购买行为的样本中随机抽取值使得正样本和负样本的比例维持在1:10。这个比例经过实测效果比较好正样本过少会导致训练信号稀疏正样本过多又会让模型过度偏向记忆而失去泛化能力。4.4 模型保存与在Django中的加载训练完成后用PyTorch的torch.save保存模型权重和词表映射。在Django中加载模型时需要注意一个问题——模型加载是有IO开销的如果每个预测请求都重新加载一次模型服务器很快就会崩溃。我的做法是在Django应用的AppConfig.ready()中做模型初始化把模型对象和词表映射作为全局变量加载到内存中。第一次请求时模型加载后续请求直接复用。实际测试下来单次预测耗时大约在50~100毫秒左右完全能够满足实时交互演示的需求。5. Django与模型的整合异步任务和接口性能优化Django与深度学习模型的整合表面上看起来只是在视图函数里调用模型但实际做起来有几个很棘手的问题这里也分享下我的踩坑经验。5.1 模型预测请求的阻塞问题Django的开发服务器是单进程多线程的如果某个请求处理时间较长会占用一个线程。在预测接口中模型推理加上数据预处理如果耗时超过1秒在并发请求较多时会明显感觉到页面卡顿。我的方案是模型推理放到了一个单独的线程池中处理。具体做法是使用Python的concurrent.futures.ThreadPoolExecutor视图函数接收预测请求后把预处理好的数据提交给线程池返回Future对象再等待结果。这样Django主线程能够快速释放不会影响其他请求的处理。当然更复杂的方案是引入Celery做异步任务队列把预测任务发送到队列中前端通过轮询获取结果。但毕设场景下线程池的解决方案已经足够且实现起来简单得多。5.2 缓存策略Redis在可视化接口中的应用前面提到可视化接口会用Redis做缓存这里具体说一下缓存的设计。原始数据表有百万行每次聚合统计都需要扫描全表耗时大约几百毫秒到几秒不等。如果大屏同时加载6个图表每个图表都做实时统计页面首屏加载时间会达到十几秒体验很差。在Redis里每个图表的统计结果以JSON字符串存储key的格式为dashboard:funnel、dashboard:category_rank等。我为每个key设置了300秒的有效期。在Django视图中先通过cache.get获取数据如果获取不到再执行SQL并写入缓存。5.3 数据更新的处理避免缓存脏数据Redis缓存带来的一个直接问题是如果数据库数据更新了缓存中的数据可能仍然是旧数据。这个项目里数据在系统运行期间一般是静态的通过管理后台批量导入所以缓存过期后自然重建即可。如果你后续要接入实时数据流建议在数据导入接口中主动调用cache.delete删除相关key。6. 联调阶段最容易踩的坑三条排查链路复盘这部分是我最想分享的内容。在自己完整走完这个项目之前我一度以为最难的是模型训练结果发现真正折磨人的是联调。这里挑三个最有代表性的问题完整还原排查过程。6.1 坑一接口超时问题——SQL写得太随意系统联调时我第一步测试可视化大屏接口发现部分接口响应时间超过3秒最慢的类目排行接口甚至达到了8秒。一开始以为是MySQL性能不行后来通过EXPLAIN分析SQL才发现问题出在查询写法上。用ORM的filter().count()等方式会自动生成子查询在大数据量下效率极低。我改用原生SQL后响应时间从8秒降到了200毫秒左右。具体的优化技巧有这么几个对(user_id, item_id, behavior)建联合索引避免全表扫描。按小时统计时把时间字段先转成DATE_FORMAT字符串再GROUP BY。对只用到部分字段的查询用SELECT指定字段而不是SELECT *。合理利用MySQL的覆盖索引避免回表查询。6.2 坑二模型推理结果明显不合理模型训练完以后我在Django里做了个简单的测试页面输入一个用户ID查看预测结果。结果发现模型对几乎所有用户都输出了同样的预测结果——购买概率约为0.04。这个现象说明模型已经收敛到了一个死区也就是它学会了一刀切预测为负样本。排查后发现问题出在负样本采样比例过高。1:10的负采样比例在训练集上效果很好但在推理阶段模型面对的是原始分布的数据——正样本占比不足2%所以它倾向于输出一个接近负样本比例的概率值。我最后的解决办法是在推理阶段对模型的输出做了阈值校准。通过验证集计算出一个最优分类阈值而不是默认用0.5。具体做法是遍历0.01到0.5之间的阈值选取使F1分数最大的那个阈值作为最终预测阈值。这个方案在测试集上将F1分数提升了约12%。6.3 坑三前端ECharts图表渲染失败但接口正常接口数据一切正常但ECharts图表面板图标一直空白。打开浏览器控制台才发现是后端返回的JSON字段名与前端读取的字段名不一致导致的。我在后端把pv字段命名为pv_count而前端代码里读的是pv导致数据取不到。这类错误通常体感上像是接口没问题、图表有问题但根因是前后端数据契约不一致。这是典型的团队协作问题——在一个人做全部开发时也容易发生因为你很容易在后端写的时候就默认前端会按你的思路来。解决方法是在后端接口设计文档中固定好字段名前端严格按照接口文档取字段。如果前后端都是你自己做在切换上下文时一定要回顾一下接口约定。7. 如果你也拿这个项目做毕设几个实在建议整个项目做下来从数据清洗到系统交付大概花了三周左右的业余时间。如果你准备参考这个方向做自己的毕设我有几点比较实在的建议。7.1 源码之外如何组织你的文档和答辩材料毕设交付不只是写代码文档和演示环境往往比代码本身更影响最终成绩。论文写得再漂亮演示时系统崩了前面所有努力都会打折扣。所以演示环境一定要提前稳定好最好准备一套固定的演示数据不要现场跑真实数据集。文档里一定要写清楚系统架构图、数据流图和接口设计说明。很多同学代码写得不错但文档里连系统架构图都没有老师印象分会差很多。可以在系统里留一个手动触发缓存刷新按钮演示时如果数据有变动刷新缓存再展示会比等缓存自动过期更可控。答辩前把LSTM为什么适合处理序列数据、数据类别不均衡怎么处理这两个问题弄透这是这个项目最容易被深入追问的两个技术点。7.2 可以继续扩展的方向做完这个基础版本之后如果你想进一步加深度有几个方向可以参考更换更强的模型用Transformer替换LSTM或者引入BERT风格的用户行为预训练模型。增加推荐功能在预测出用户可能购买的商品后直接把候选列表展示出来形成完整的分析-预测-推荐闭环。接入实时数据流用Kafka Flink模拟实时用户行为流让大屏动态刷新而不是静态展示历史数据。增加用户画像模块基于用户的长期行为序列生成用户画像标签比如高消费倾向用户价格敏感型用户。最后说点我个人的体会这种类型的项目真正考验的并不是你用了多高级的算法而是你能否把数据处理、后端服务、前端展示、模型部署这几个环节完整地串联起来。一个能稳定跑通全流程的系统哪怕模型用的是经典LSTM也远远强过一个只写了炫酷模型却无法落地的展示项目。希望你做完之后也能感受到把整条链路真正打通的那种踏实感。
返回列表