每年到选题季总有人问我大数据方向的毕业设计该怎么做才不翻车。我的答案一直很明确与其堆一堆花哨但跑不通的前沿名词不如老老实实把一条真实数据链路从头打通。今天这篇要拆的项目就是这种思路的典型——以 Hadoop、Hive、PySpark 搭建数据底座用 LSTM 模型完成餐饮评分预测最终落地成一个带推荐能力的演示系统。这个题目的完整形态是PySparkHadoopHiveLSTM模型的本地生活餐饮点评分析、评分预测与美食推荐系统交付物包括源码、论文、PPT和讲解视频。如果你正在准备同类毕业设计或者工作中想快速验证数仓特征工程深度学习这套组合拳下面这些内容可以帮你少走不少弯路。我会按真实搭建顺序把选型理由、数据建模、特征构造、模型训练、推荐逻辑、环境部署和答辩准备一次说透。1. 为什么是这套全家桶项目定位与选型思路1.1 毕业设计到底在考察什么先想清楚一件事毕业设计评分看的不只是模型准不准而是你有没有完整地解决一个问题并且整个过程可解释、可复现。评委拿到你的论文大概率会顺着这条线追问数据从哪里来、怎么清洗、特征怎么构造、模型为什么选这个、结果怎么评估。任何一个环节断了前面的搭建都会显得站不住脚。大数据方向的题目尤其怕头重脚轻。有人把论文写成 Hadoop 原理科普模型只跑了一个 sklearn 的线性回归也有人从头到尾用 pandas 处理数据Spark 只出现在系统架构图里。这两种都很容易被问穿。这套题目的聪明之处在于它让每个技术组件都有不可替代的位置链路完整度和技术深度是天然绑定的。1.2 每个组件到底在干什么活先给出这套组合拳的分工表后续所有细节都围绕这张表展开。组件职责在项目里的具体角色Hadoop HDFS分布式存储存放原始评论数据、清洗后明细数据、模型训练样本Hive数据仓库提供 SQL 化建模能力完成 ODS/DWD/DWS/ADS 分层PySpark分布式计算负责更复杂的特征工程包括序列构造、统计特征、文本特征LSTM深度学习模型基于用户历史行为序列预测下一次评分驱动推荐排序前端演示结果呈现把推荐结果、评分预测、数据分析图表展示出来这么分配是有讲究的。Hive 负责结构化查询最顺手的部分比如过滤脏数据、关联用户和店铺、做基础聚合PySpark 则接管需要写代码而不是写 SQL的部分比如按照时间排序构造每个用户的行为序列、计算评论的情感得分、做训练样本切分。两者配合既体现了数据仓库建模思想又展示了分布式编程能力。LSTM 的引入则是整个项目的亮点。传统的协同过滤只能利用用户-物品的评分矩阵忽略了行为发生的先后顺序而在真实的餐饮消费场景中用户口味会漂移最近几次消费往往比一年前的消费更有参考价值。LSTM 天然适合处理这种带时序依赖的行为序列在论文里也好讲故事用户评分序列 → 时间依赖建模 → 预测下一次评分 → 推荐排序。提示设计 PPT 时这张分工表就是你的技术架构图底稿。评委问为什么同时用 Hive 和 PySpark你就可以回答Hive 做数据仓库分层管理PySpark 做复杂特征工程两者是分层协作关系不是重复建设。2. 数据进来先过三关采集、清洗与Hive数仓建模2.1 数据来源与字段设计做这个项目数据一般有两个来源一是使用公开的餐饮评论数据集这类数据通常以 CSV 或 JSON 形式发布包含用户、店铺、评分、评论内容、评论时间等字段二是自己写爬虫抓取点评类App 的公开页面。建议优先考虑公开数据集把精力留给链路本身。真要爬虫的话也要控制抓取频率只做技术验证不要在生产环境给对方服务器制造压力。我这边实验用的数据来自一个公开的餐饮评论样本集字段不算复杂但很典型用户表user_id、注册时间、所在城市、性别店铺表shop_id、店铺名称、类别火锅/川菜/烧烤等、城市、人均价格、评分评论表review_id、user_id、shop_id、评分1~5分、评论文本、评论时间、有用数这种结构在真实平台中也是主流形态所以做完这套流程换一个领域的评论数据你只需要改字段映射就能复用。2.2 清洗规则哪些评论必须扔掉清洗不是简单的去重而是要站在业务角度判断这条评论是否参与建模。我整理了三层规则第一层硬去重。review_id 重复的直接删同一个 user_id 对同一家店铺出现多条评论时只保留时间最新的一条。因为对同一店铺的多次评论存在店家纠纷后的报复性修改这种噪声对模型伤害很大。第二层过滤无效内容。评论文本为空但评分存在的保留评分缺失的删掉评论长度小于 5 个字、且内容属于很好、不错、可以这种通用评价且短期内被大量不同账号发出的判定为疑似刷单删除。后者需要你统计同文案重复出现次数这个用 Hive 的 GROUP BY 加 COUNT 就能做。第三层业务约束。用户至少要有 5 条评论历史否则无法构造像样的行为序列这部分用户先排除出训练集。店铺至少要有 3 条评论否则评分统计特征不可靠。时间字段统一成 yyyy-MM-dd HH:mm:ss 格式时区统一处理避免后续排序出问题。2.3 Hive 表结构和分层设计数据仓库的分层设计是这个项目在论文里很重要的一个加分点。很多同学只会建一张大宽表然后直接丢给模型这在答辩时容易被质疑缺乏工程思维。我采用的是经典的四层结构。层级表名内容说明存储格式ODSods_review / ods_shop / ods_user原始数据直接落库不改变结构TEXTFILEDWDdwd_review_clean清洗后的评论明细字段标准化ORC SnappyDWSdws_user_rating_seq用户维度聚合含评分序列ORC SnappyDWSdws_shop_stat店铺维度统计特征ORC SnappyADSads_candidate_shops推荐候选集直接供业务查询ORC Snappy建表语句我这里给一个 DWD 层的参考重点是分区和表格式的选择。CREATE DATABASE IF NOT EXISTS food_dwd; USE food_dwd; CREATE TABLE IF NOT EXISTS dwd_review_clean ( user_id STRING, shop_id STRING, shop_city STRING, category STRING, rating DOUBLE, comment_text STRING, comment_time STRING, useful_cnt INT ) PARTITIONED BY (dt STRING) STORED AS ORC TBLPROPERTIES (orc.compressSNAPPY);ODS 层用 TEXTFILE 存原始数据好处是保留最真实的格式方便排查问题DWD 层开始用 ORC 加 Snappy 压缩因为后续会被反复读取和关联列式存储和压缩能明显减少 I/O。分区字段用 dt按天管理数据论文里可以顺势写支持增量更新。注意如果你在 Windows 本机上跑 Hivewarehouse 目录权限是个常见坑。要么用 Linux 虚拟机要么配置好对应的权限工具否则启动 metastore 的时候就报错。3. 特征工程的主战场PySpark如何与Hive配合3.1 Spark SQL 读取 Hive 表的两种方式特征工程是整个项目里最耗时、也最容易被低估的环节。实际业务中模型效果差的时候八成问题出在特征而非算法上。PySpark 读取 Hive 表有两种常用方式。第一种通过enableHiveSupport()直接关联 Hive Metastore。这种方式要求hive-site.xml在 Spark 的 classpath 里Spark 会通过 Metastore 拿到表的元数据然后直接用spark.sql(SELECT * FROM food_dwd.dwd_review_clean WHERE dt2024-01-01)查询。好处是表结构、分区信息全由 Metastore 管理不用自己拼路径。第二种不依赖 Metastore直接读 HDFS 上的 ORC 文件或 Parquet 文件用spark.read.orc(hdfs://...)。这种方式适合快速验证但要注意 schema 必须自己定义容易跟原表对不上。我实际用的是第一种因为可以从 Hive 分层表无缝衔接也方便在同一个 SparkSession 里既查表又做复杂变换。3.2 构造用户历史评分序列LSTM 需要的是序列而不是一行一个样本的扁平结构。所以核心任务是把每个用户的评论记录按时间排序拼成一个有序列表。这里要用到窗口函数加collect_list的组合。from pyspark.sql import SparkSession, functions as F, Window spark SparkSession.builder \ .appName(food_feature_engineering) \ .enableHiveSupport() \ .config(spark.sql.shuffle.partitions, 20) \ .getOrCreate() seq_window Window.partitionBy(user_id).orderBy(comment_time) user_seq spark.sql( SELECT user_id, shop_id, shop_city, category, rating, comment_text, comment_time, useful_cnt FROM food_dwd.dwd_review_clean WHERE dt 2023-01-01 ) # 给每个用户的行为按时间编号同时截断超长序列防止单用户数据过多造成倾斜 user_seq user_seq.withColumn(seq_index, F.row_number().over(seq_window)) \ .filter(F.col(seq_index) 80) # 按用户聚合为序列 seq_df user_seq.groupBy(user_id).agg( F.collect_list(F.col(shop_id).cast(string)).alias(shop_seq), F.collect_list(rating).alias(rating_seq), F.collect_list(category).alias(category_seq), F.collect_list(comment_time).alias(time_seq), F.collect_list(comment_text).alias(text_seq) )这段代码有两个经验点。第一collect_list的顺序并不一定保证有序即使你在子查询里排序了聚合后也可能乱序。稳妥的做法是用row_number生成序号后聚合时在collect_list里带上这个序号后续在 Python 端再按它排序。第二单用户行为数量差异极大有的用户只有 5 条有的有几千条直接全量聚合会产生严重的数据倾斜所以要加截断或过滤。3.3 文本特征与店铺统计特征序列里如果只放评分模型相当于在学历史评分预测未来评分信息量有限。真正的餐饮评论里藏着大量信号用户说这家服务太慢了、菜量很足下次还来这些文本内容能反映消费体验。我给每条评论额外计算了一个情感得分用简单的餐饮领域情感词表加程度副词加权不需要上大模型效果好且训练快。情感分词用常见的中文分词库即可把每条评论映射成正面情感、负面情感、中性情感的分值。统计特征这部分交给 Hive 聚合会更方便。在 DWS 层建一张店铺统计表CREATE TABLE IF NOT EXISTS food_dws.dws_shop_stat AS SELECT shop_id, COUNT(*) AS review_cnt, AVG(rating) AS avg_rating, STDDEV(rating) AS rating_std, AVG(useful_cnt) AS avg_useful_cnt, AVG(price) AS avg_price FROM food_dwd.dwd_review_clean WHERE dt 2023-01-01 GROUP BY shop_id;之后在 PySpark 里把用户序列和店铺统计表做一次广播式关联得到最终训练样本。这里有个建议如果店铺数量在几十万以内可以把 shop_stat 直接collect()回驱动端建成字典做 map 侧匹配比大规模 Join 快得多也避免常见的数据倾斜问题。最后的训练样本长这样每个样本是一条用户最近 K 次消费的子序列包含用户 embedding 索引、店铺 embedding 索引、类别索引、评分序列、情感得分序列、时间间隔序列标签是下一次真实评分。我用 parquet 导出方便后面 PyTorch 直接读取。提示写论文时特征工程章节一定要放一张特征表注明每个特征的类型、来源表、业务含义。答辩老师特别吃这一套因为这显示了你有工程规范意识。4. 评分预测模型从特征序列到LSTM训练4.1 为什么是LSTM而不是协同过滤或Transformer这个问题的答案就是你论文里模型选型章节的素材。一句话协同过滤不会利用行为顺序和文本内容LSTM 可以而 Transformer 在这个体量的数据上容易过拟合收益不明显。我同时实现了三个基线做对照基于物品的协同过滤、矩阵分解、LSTM。实验结果很能说明问题在评分预测 RMSE 指标上LSTM 明显好于矩阵分解尤其是当用户近期口味发生变化时LSTM 能捕捉到用户最近开始吃清淡这类趋势而矩阵分解只能给出一个静态的用户偏好向量。Transformer 我试过一个简化版本效果和 LSTM 相当但训练时间和显存占用高不少对本项目来说性价比不足。4.2 输入张量与模型结构LSTM 的输入不能直接用字符串需要把所有类别型字段映射成索引。在 PySpark 端跑一次StringIndexer或直接在 Python 端构建词表映射把 user_id、shop_id、category 分别转成连续整数。注意训练集和推理集的索引必须一致所以映射要在全量数据上先建好再切分。模型结构我用的是Embedding LSTM 全连接的组合。import torch import torch.nn as nn class RatingLSTM(nn.Module): def __init__(self, n_users, n_shops, n_cats, emb_dim32, hidden_size64, n_layers2, n_continuous4, dropout0.2): super().__init__() self.user_emb nn.Embedding(n_users, emb_dim) self.shop_emb nn.Embedding(n_shops, emb_dim) self.cat_emb nn.Embedding(n_cats, emb_dim) self.lstm nn.LSTM( input_sizeemb_dim * 3 n_continuous, hidden_sizehidden_size, num_layersn_layers, batch_firstTrue, dropoutdropout ) self.head nn.Sequential( nn.Linear(hidden_size, 32), nn.ReLU(), nn.Linear(32, 1) ) def forward(self, user_ids, shop_ids, cat_ids, cont_features, mask): user_emb self.user_emb(user_ids) shop_emb self.shop_emb(shop_ids) cat_emb self.cat_emb(cat_ids) # 连续特征包括情感得分、时间间隔天、上一评分、店铺热度 x torch.cat([user_emb, shop_emb, cat_emb, cont_features], dim-1) lstm_out, _ self.lstm(x) # 对有效位置求均值mask 用于屏蔽 padding 部分 masked_out lstm_out * mask.unsqueeze(-1) lengths mask.sum(dim1, keepdimTrue).clamp(min1.0) pooled masked_out.sum(dim1) / lengths return self.head(pooled).squeeze(-1)连续特征那 4 个维度我选了情感得分、与上一次评论的时间间隔天、上一条评分、店铺热度评论数取对数。时间间隔很关键它让模型能感知到用户很久没吃火锅了最近突然又吃了这类节奏变化。4.3 训练细节、评测指标与踩坑记录数据切分必须按时间不能随机切分。我的做法是对每个用户把行为序列按时间排序前 80% 作为训练最后 20% 作为测试。这么做是为了避免未来信息泄露。你要是随机打散再切分模型在测试集上的表现会虚高答辩时被问你的验证方法是否严谨就容易露馅。训练参数从这几个起步基本不会错序列长度 20batch size 128学习率 1e-3epoch 不超过 50 并配早停embedding 维度 32LSTM 隐层 64。优化器用 Adam损失函数用 MSE。评分的取值范围是 1 到 5所以模型最终输出后要做 clip免得预测出 6 分这种离谱值。评测指标我用 RMSE 和 MAE。以我实验用的数据集规模来看三个模型的对比大概是流行度基线 RMSE 约 1.21矩阵分解约 0.95LSTM 约 0.87。看起来只是零点几个点的差距但放在从 1 万多家店铺里猜用户的下一个评分这个任务里已经是能明显拉开推荐排序质量的差异了。训练阶段最容易踩的几个坑第一Loss 变成 NaN。十有八九是学习率太大或者归一化没做好。建议评分先缩放到 0~1 之间再训练预测完再还原。第二序列 padding 后模型会把 padding 位置当成真实数据学。所以必须有 mask 机制损失函数计算时也要把 padding 部分排除掉。第三评分极度不均4 分、5 分占了绝大多数。模型容易躺平全部预测成高分RMSE 看着还行实际上没有区分度。我的处理方式是给低分样本稍微加权或者在评估时分组看各类评分的误差。5. 从预测分数到推荐列表排序、过滤与冷启动5.1 推荐候选集怎么产生评分预测模型只是一个打分器要形成美食推荐系统还得有候选集生成这一步。如果对全量几十万家店铺逐一预测计算量太大。我用了三层规则圈定候选集第一层城市过滤。只保留用户所在城市的店铺跨城推荐没有意义。第二层类别偏好。统计用户历史评论里出现频率最高的前 3 个餐饮类别在这几个类别里选候选店铺。第三层排除已消费店铺。用户已经写过评论的店铺不再推荐这样推荐结果才有探索性。这三层规则用 Hive 或 PySpark 都能写最后落到ads_candidate_shops表。5.2 打分与排序不能只靠预测评分候选集生成后对每个候选店铺用 LSTM 预测评分然后按预测分排序这听起来顺理成章但实际效果并不好。原因是预测分只代表用户可能给这家店评多少分不代表用户有多大可能想去。所以我给最终排序分数加了一个经验组合最终得分 0.65 * 预测评分 0.25 * 店铺热度 0.10 * 类别多样性加成店铺热度用评论数和平均评分的归一化值综合计算类别多样性加成是为了避免推荐列表里全是同一类店——比如用户爱吃火锅不代表十条推荐全是火锅适当混入烧烤和川菜体验更好论文里也好写兼顾准确性与多样性。排序后用一个简单的规则做最终过滤人均价格超出用户历史平均消费 1.5 倍的店铺降权。5.3 冷启动和可解释性新用户没有历史行为LSTM 完全失效新店铺没有足够评论店铺统计特征也不可靠。我的处理方案是分场景回退新用户直接推荐城市内综合热度最高的 10 家店并标注热门榜。新店铺用所在类别、平均价格、已有评论的情感得分构造一个轻量特征交给模型打分评论数太少时加上一个贝叶斯平滑避免被一两条五星好评拉爆。可解释性也是答辩常考点。我在演示系统里给每条推荐附了一行说明例如根据你最近吃过的川菜和火锅记录预测这家店得分 4.6 分。这么做一方面让系统看起来更完整另一方面在论文里可以专门写一小节推荐理由生成属于低成本高回报的加分项。6. 单机部署与运行顺序这套环境怎么搭6.1 组件版本与最低配置很多同学被这套环境劝退其实只要版本匹配好单机完全能跑。我的实验环境供参考Hadoop 3.3.xHive 3.1.3Spark 3.3.xPython 3.9 或 3.10PyTorch 2.x。内存建议至少 8G硬盘留 50G 以上。注意 Python 版本别太新PySpark 对 Python 3.11 之后的兼容性会折腾你一阵子。如果你是 Windows 机器强烈建议装 VMware 或其他虚拟机软件跑 Linux实在不行再考虑用 Docker 起容器。Windows 下跑 Hadoop 需要额外的权限工具而且启动脚本一堆权限问题折腾成本远高于重装一个干净环境。我一个朋友用 Docker 起了一套 HadoopHiveSpark 的环境内存占用控制在 6G 左右跑这个小数据集绰绰有余。6.2 从启动到跑出推荐结果的完整顺序整个项目的执行链路要按顺序走任何一个环节断了后面都跑不起来。# 1. 启动 HDFS start-dfs.sh # 2. 启动 Hive Metastore后台跑 hive --service metastore # 3. 初始化并加载 ODS 数据 hive -f etl/hive_ddl.sql hive -e LOAD DATA INPATH /input/review.csv OVERWRITE INTO TABLE food_ods.ods_review; # 4. 跑 DWD/DWS 分层 SQL hive -f etl/hive_dwd_dws.sql # 5. 提交 PySpark 特征工程作业 spark-submit --master local[4] --driver-memory 4g \ etl/spark_features.py # 6. 训练 LSTM 模型 python model/train_lstm.py # 7. 启动推荐服务 python app/web_demo.py这里有个真实的坑要提醒你先确保 HDFS 的 NameNode 正常再启动 metastore否则后面 Spark 读 Hive 表时会一直报连接失败。另外spark-submit时如果任务不大用local[4]就够了不用强行搭 YARN 集群毕设演示阶段重点在跑通链路不在集群规模。工程目录我是这样组织的也推荐给你project/ ├── conf/ # hive-site.xml、spark配置 ├── data/ # 原始数据、预处理脚本 ├── etl/ │ ├── hive_ddl.sql # 分层建表语句 │ ├── hive_dwd_dws.sql # 清洗与聚合 │ └── spark_features.py # PySpark 特征工程 ├── model/ │ ├── train_lstm.py # 训练脚本 │ ├── predict.py # 推荐打分脚本 │ └── model_base.py # 网络结构定义 ├── app/ │ └── web_demo.py # 演示用 Web 界面 └── docs/ # 论文、PPT、演示素材6.3 常见报错与排查清单我把搭建过程中遇到的高频报错列成一个表每一条都是实际踩过的排查顺序也从高频到低频排列。报错现象常见原因处理办法Spark 连接 Hive 元数据失败metastore 未启动或 hive-site.xml 不在 classpath先启动 metastore再检查配置Hive 执行 INSERT 时报权限错误HDFS 目录权限不足调整 warehouse 目录属主或配置权限工具PySpark 任务 OOMdriver 内存不足或分区过少调大 --driver-memory调高 shuffle 分区数Python 版本不兼容报错PySpark 不支持过新的 Python降到 3.9/3.10LSTM 训练 Loss 为 NaN学习率过大或输入未归一化评分缩放到 0~1降低学习率预测结果全是同一个数序列 mask 写错padding 被计入检查 mask 和损失函数的遮蔽逻辑推荐列表全是同一个类别的店多样性权重太低或候选集本身单一调大多样性加成扩大候选集类别范围注意如果答辩演示时现场网络不稳推荐系统的所有数据尽量提前缓存成本地文件Web 界面优先保证离线可跑。演示翻车大多不是因为代码而是因为环境。7. 论文写法与答辩经验把项目讲出深度7.1 论文各章节怎么组织这套项目的论文结构特别好写因为工程链路本身就是天然的章节线索。常规的章节安排是绪论讲背景与意义相关技术章节分别介绍 Hadoop、Hive、PySpark、LSTM但不要写成长篇原理堆砌每部分只写清楚在本项目中怎么用需求分析与总体设计章节给出数据流图、功能模块图系统设计章节拆开讲数仓分层、特征工程、模型结构系统实现与试验章节贴关键代码和实验对比最后是总结与展望。论文的重心一定要放在实验对比上。不要只贴一个 LSTM 的结果要有流行度基线、矩阵分解、LSTM 三者的对比表格最好再配一个去掉文本情感特征后的 LSTM消融实验。这样论文的论证链就完整了为什么用 LSTM → 比传统方法好在哪 → 哪个特征贡献最大。评委很难问倒这种有对比、有消融的文章。PPT 方面核心就是三张图一张总体架构图数据流从 HDFS 到 Hive 到 PySpark 到 LSTM 到推荐结果、一张分层数仓表、一张实验对比图。讲解视频建议控制在 10 分钟以内按数据 → 特征 → 模型 → 演示的顺序讲不要超过这个节奏。7.2 答辩前一定要准备的几个问题结合我自己和周围同学被问到的频率下面这几个问题几乎必考为什么用 LSTM 不用 Transformer答数据量有限Transformer 收益不明显且训练成本高本任务序列长度短LSTM 足够捕捉时间依赖同时实验做了对比验证。Hive 和 PySpark 的功能边界在哪答Hive 负责数仓分层和常规聚合PySpark 负责需要自定义 UDF 的复杂特征变换比如序列构造、文本情感计算两者通过 Metastore 共享元数据。你的推荐结果如何评估答评分预测用 RMSE/MAE推荐列表用准确率、召回率同时说明冷启动场景采用热度回退策略。数据量这么小为什么要用 Spark答项目目的在于构建可扩展的大数据链路数据集规模小是为了在单机环境验证方法但代码可直接平移到集群。这个系统距离真实上线还差什么答实时数据接入、在线增量训练、AB 实验平台这些作为展望写在论文结尾即可。最后分享一个答辩的小技巧演示时不要只展示推荐界面要把 HDFS 上的数据文件、Hive 里的查询结果、训练日志里的 loss 曲线都截图放进 PPT 附录。评委看到你真正操作过这些组件而不是只会调用接口印象分会明显不一样。这套项目真正值钱的地方不是某一个算法多厉害而是你能把从原始日志到最终推荐结果的整条链路讲清楚。把这条链路守住毕业设计这一仗基本就稳了。