
搞过大数据方向毕设的同学应该都有体会每年“地铁客流量预测”这种题目都被选爆原因很简单它把 Spark 分布式计算、机器学习/深度学习建模、大数据可视化三个大热门全部串在了一起做出来的效果又特别直观答辩时一张客流热力图甩上去老师基本都会点头。但我带过不少毕设发现很多同学拿到这个题目后第一反应是“Spark 我还没装明白怎么预测”然后就开始慌。这篇东西我就按自己做项目的完整流程来写从选题拆解、技术栈选型、数据预处理、模型训练到可视化大屏和答辩避坑全部过一遍。核心不是给你堆概念而是告诉你每一步到底怎么落地、为什么这么选、遇到问题去哪排查。如果你正准备做这个题目或者已经在开发中卡住了这篇内容应该能帮你省下大量试错时间。1. 项目拆解与整体设计思路1.1 先搞清楚这题到底考你什么很多同学看到“Spark 地铁客流量预测”这个标题脑子里跳出来的第一个词是“Spark”然后就开始疯狂研究 RDD、DataFrame、底层原理结果做到中期发现模型还没开始训练。这是最大的误区。作为毕设题目它真正考察的是三件事第一你是否具备用大数据技术处理海量数据的能力。地铁刷卡数据一天就是几百万条传统单机 Pandas 明显吃力所以需要 Spark 来做分布式预处理和特征工程这是“大数据”三个字的落点。第二你是否理解机器学习/深度学习建模的基本流程。流量预测本质是个时间序列回归问题特征怎么构造、模型怎么选、指标怎么评估这些是“计算机”三个字的落点。第三你能否把结果直观地展示出来。地铁客流量预测不能只输出一个数字而是需要通过 ECharts 大屏把不同站点、不同时段的客流分布、预测趋势呈现出来这是“可视化”的落点。所以整体思路是采集/获取数据 → Spark 清洗与特征工程 → 训练预测模型 → 可视化展示 → 封装成完整的演示系统。一旦你把这个链路想清楚了整个毕设的骨架就立住了。1.2 技术栈选型不要盲目堆新技术我见过有人在这个项目里引入 Flink、Kafka、ClickHouse 的理论上没问题但如果你是本科生、需要在一个学期内完成并且要能讲清楚原理我建议克制一点只用一个最成熟的组合。我的推荐组合是存储层HDFS MySQL。HDFS 存放原始刷卡数据MySQL 存放聚合后的统计结果和预测结果方便前端读取。计算层Spark Core Spark SQL。负责数据清洗、聚合统计、特征提取也是你论文里“大数据处理”章节的核心。算法层Spark MLlib 做传统机器学习基线再用 TensorFlow 或 PyTorch 训练 LSTM 类深度学习模型。为什么不直接在 Spark 里做深度学习因为 Spark 的深度学习支持远不如专用框架成熟实操中还是分开更省心。可视化层Spring Boot 写后端接口 ECharts 画图表或者直接用 Vue ECharts 做纯前端大屏后端只提供 JSON 数据。调度层如果要做全流程自动化可以加一个简单的 Shell 脚本或者 Airflow但这不是必需项毕设里加了反而是给自己挖坑。这套组合的优点是每层都有成熟生态问题排查资料多而且每一项都能在论文里单独写一节工作量容易铺开也容易讲清楚。1.3 架构分层数据怎么流动心里要有数在做任何代码之前先在文档里画清楚数据流向。我当时是这么设计的原始 CSV/文本数据 → 上传 HDFS → Spark 作业读入并清洗 → 按站点/时间粒度聚合 → 特征工程产出训练集 → 训练模型并产出一份预测结果表 → 写回 MySQL → 后端接口读取 MySQL → 前端 ECharts 展示。每一层做一件独立的事层与层之间通过“数据落表”衔接。这样设计的好处是你可以在没做完可视化的情况下先用命令行验证数据是否正确也可以在模型效果不理想时只调整算法层不用动前后端。模块解耦对你后期写论文、做 PPT 也有很大帮助每一层都能对应一个章节工作量一目了然。2. 数据获取与预处理实操2.1 数据集从哪里来字段长什么样“地铁客流量预测”听起来高大上但大部分人手里并没有真实的地铁 AFC自动售检票数据。这里分三种情况第一种学校实验室或导师提供脱敏数据这是最理想的信息维度全还能在致谢里提一句。第二种用公开数据集。比较常见的是某城市地铁刷卡记录包含交易时间、站点编号、进出站标识、卡类型等字段。这类数据在 Kaggle 或一些高校开放数据平台能找到搜索关键词直接用“metro passenger flow dataset”就行。第三种实在找不到合适的数据集可以用模拟数据生成器。根据经验设定工作日早高峰 7:30-9:00、晚高峰 17:30-19:00 客流量偏高周末下午出现小高峰等规律然后叠加随机噪声生成小时级或 15 分钟级客流数据。模拟数据作为毕设演示是可行的但论文里一定要如实说明数据来源不要伪造真实数据。以典型的刷卡数据为例核心字段通常包括transaction_time交易时间station_id站点编号device_id闸机编号card_type卡类型普通卡/老年卡/学生卡等transaction_type进站还是出站计算客流量时通常以“进站人次”或“出站人次”为准也可以按线路把进出站都算进去看你的选题侧重。2.2 数据清洗脏数据是默认状态不是例外真实数据永远比你想象中脏。我在处理时就遇到过时间格式不统一、站点编号出现未知值、同一条记录重复上传等问题。这里分享几个必做的清洗步骤格式统一时间字段全部转换成 yyyy-MM-dd HH:mm:ss 格式如果原始数据是时间戳就先用 Spark SQL 的 from_unixtime 做转换。去重用 station_id transaction_time device_id card_id 的组合字段做 distinct去除重复刷卡记录。注意一个人在同一分钟内在同一个闸机刷两次卡极大概率是重复数据。缺失值处理站点编号缺失的记录删除因为无法反推是哪个站时间缺失的记录也直接删客流量预测对时间连续性要求很高补缺失时间意义不大。异常值处理例如某站一天进站量突然变成平时的 100 倍这种记录很可能是上游系统故障导致可以用统计方法剔除比如超出均值 3 倍标准差且持续超过 2 小时的记录。不要小看清洗这一步。我见过有同学直接拿原始数据建模结果模型 MAPE 高得离谱白白消耗了大量精力调参后来才发现是数据里混进了几十万条重复记录。2.3 特征工程做时间序列预测到底要哪些特征特征工程决定了模型效果的上限。地铁客流量有很强的周期性规律核心特征可以分成三类。时间类特征是最重要的小时0-23、星期几0-6、是否周末、是否节假日、一天中的时段早高峰/晚高峰/平峰。这些特征直接用 Spark SQL 的 hour()、date_format() 函数就能生成几乎零成本。历史统计类特征包括前三天的同时段客流、前一周同一天的同时段客流、过去 1 小时总客流量等。这类“滞后特征”对时间序列模型特别有效但要注意避免数据泄露——在构造训练集时只能用 T 时刻之前的数据去预测 T 时刻不能把 T 时刻的真实值当特征喂给模型。外部影响类特征天气温度、降水、是否大型活动日、地铁线路是否发生突发事件等。真实数据里往往没有这些信息如果要做扩展可以额外爬取对应城市的天气数据按日期 join 进来。还有一类容易忽略的特征是站点自身属性商业区站点早晚高峰特征明显居民区站点晚上和早上的方向相反交通枢纽站点全天都比较均匀。可以用站点的历史日均客流来代表站点规模或者对站点做 embedding 编码让模型自动学习站点差异。2.4 客流规律观察建模之前先“看数据”我强烈建议在训练模型之前先做一次简单的数据探索。用 Spark SQL 按站点、按小时统计平均客流量把结果导出来用 Excel 或者 Python 画几张小图你会立刻发现几个重要规律工作日的客流量呈现明显的双峰形态早高峰集中在 8 点前后晚高峰集中在 18 点前后周末则是单峰午后 14-16 点达到最高。换乘站明显高于普通站。节假日期间商业中心附近站点客流不降反升。这些规律会直接指导你怎么划分训练集和测试集。例如如果只拿一周数据训练、拿下一周做预测结果通常不好因为模型没见过“周末长什么样”。所以训练集至少覆盖完整的自然周最好包含节假日。3. 预测模型实现从机器学习到深度学习3.1 先跑通一个简单的基线模型很多同学上来就上 LSTM结果训练半天效果还不如线性回归就开始怀疑人生。正常现象。正确的做法是先用简单的机器学习模型快速跑通全流程拿到一个可用的基线结果再逐步升级模型。为什么因为基线模型可以帮你验证代码链路是否正确——数据特征是否构造成功、训练集和测试集是否划分正确、评估指标是否计算正确。这些基础环节没问题了再上复杂模型才能把模型之间的差异归因于模型本身而不是某些诡异的 bug。在 Spark MLlib 里最简单的基线可以先试线性回归。核心代码大致如下import org.apache.spark.ml.feature.VectorAssembler import org.apache.spark.ml.regression.LinearRegression val assembler new VectorAssembler() .setInputCols(Array(hour, dayOfWeek, isWeekend, lag_1h, lag_1day)) .setOutputCol(features) val data assembler.transform(featureDF) val lr new LinearRegression() .setLabelCol(passengerFlow) .setFeaturesCol(features) .setMaxIter(100) val lrModel lr.fit(trainDF) val predictions lrModel.transform(testDF)这个流程跑通后再尝试更复杂的模型比如随机森林回归和梯度提升树import org.apache.spark.ml.regression.RandomForestRegressor import org.apache.spark.ml.regression.GBTRegressor val rf new RandomForestRegressor() .setLabelCol(passengerFlow) .setFeaturesCol(features) .setNumTrees(100) .setMaxDepth(10) val gbt new GBTRegressor() .setLabelCol(passengerFlow) .setFeaturesCol(features) .setMaxIter(100) .setMaxDepth(8)从实操反馈来看梯度提升树GBDT在这类表格型特征上的表现通常优于随机森林原因在于 GBDT 按梯度下降逐步拟合残差对复杂非线性关系的拟合能力强于简单的随机平均。但它的训练速度较慢且对参数比较敏感需要调好 maxDepth 和 maxIter。3.2 深度模型用 LSTM但别从零手写深度模型推荐用 LSTM天然适合时间序列。这一步建议直接跳出 Spark 生态用 TensorFlow 或 PyTorch 单独训练训练好之后把模型预测结果导回 MySQL。注意这里有个细节你用 Spark 构造好的训练数据一般是 CSV/Parquet 格式深度学习框架直接读 CSV 是没问题的。不要再把数据导到 Spark 里两头切换会很麻烦。LSTM 建模的输入格式很关键。假设你按“小时 站点”为粒度预测下一小时客流量则需要把每个站点的序列整理成固定的时间窗口。例如用过去 24 小时的客流量预测未来 1 小时的客流量那么每个样本就是 [batch_size, 24, 1] 的三维张量。参考代码片段import numpy as np from tensorflow.keras.models import Sequential from tensorflow.keras.layers import LSTM, Dense, Dropout def create_sequences(data, window_size24, horizon1): X, y [], [] for i in range(len(data) - window_size - horizon 1): X.append(data[i:i window_size]) y.append(data[i window_size horizon - 1]) return np.array(X), np.array(y) model Sequential() model.add(LSTM(64, activationrelu, return_sequencesTrue, input_shape(24, 1))) model.add(Dropout(0.2)) model.add(LSTM(32, activationrelu)) model.add(Dense(1)) model.compile(optimizeradam, lossmse)训练时重点关注两件事一是训练集与测试集不能随机打乱时间序列必须按时间顺序切分否则会造成严重的数据泄露二是验证集最好单独留出一段连续的时间例如最后两周数据完全不参与训练用来模拟真实预测场景。按我的经验在数据量足够比如 3 个月以上的小时级数据时LSTM 的效果能比 GBDT 提升 10%-20% 的 RMSE但也仅此而已。如果数据量只有一两周LSTM 反而可能打不过 GBDT。所以在论文里不必硬吹深度学习“碾压”机器学习如实分析两种模型在不同条件下的表现反而更容易拿高分。3.3 模型评估这几个指标必须搞懂答辩时老师必问的一个问题就是“你怎么评估模型好坏”。所以你必须熟练掌握以下指标的含义和计算方式MAE平均绝对误差预测值和真实值之差的绝对值取平均。优点是直观单位就是“人次”答辩时方便解释。RMSE均方根误差预测值和真实值之差的平方取平均再开方。因为平方放大了较大误差的权重所以 RMSE 对“峰值预测不准”更敏感。早高峰这种大客流时段一旦预测偏了RMSE 会明显升高。MAPE平均绝对百分比误差误差占真实值的比例取平均。这个指标最适合答辩展示——你可以直接说“模型的平均预测误差在 8% 左右”非专业人士也能听懂。但要小心如果真实值很小比如凌晨客流接近 0MAPE 会异常大所以一般只在工作时间段内计算 MAPE。建议论文中放一张三种指标对比表把线性回归、随机森林、GBDT、LSTM 的 MAE/RMSE/MAPE 列在同一张表里直观又有说服力。答辩时老师看到这张表基本就不会再追问“为什么选这个模型”了因为你已经用数据证明了。3.4 超参数调优的几个实战技巧模型调优是毕设中最耗时的环节。分享几个我实际操作过的高性价比调参方向。第一先调输入窗口长度。地铁客流有“近因效应”昨天的客流对今天的影响比上周同一天大。窗口太短会丢失周期性信息太长又会引入噪声。一般可以尝试 12、24、48、72 小时四档用验证集评估选择 RMSE 最小的。第二LSTM 的层数和隐藏单元数不必太夸张。很多同学一上来就堆 3 层 128 个单元训练又慢又容易过拟合。我实测下来2 层 64 单元 Dropout 0.2 在大多数情况下已经够用。如果训练集不大可以再降到 32 单元。第三学习率优先于迭代次数。如果你发现 loss 下降得很慢先调小学习率而不是加训练轮数。LSTM 的默认 Adam 学习率是 0.001数据量小时可以试 0.0005。第四特征标准化很关键。客流量数值范围可能是 0 到几千直接喂给神经网络会让训练不稳定。在进入 LSTM 之前用 MinMaxScaler 把数据缩放到 [0, 1]训练完后再反标准化还原。4. 交通可视化与大屏实现4.1 可视化这件事比你想的重要毕设评分中可视化系统的完成度往往直接决定了答辩的第一印象。老师不一定懂你的模型细节但一定能看懂你的大屏。很多团队代码写得不错但界面随便套了个模板按钮还对不齐最后分数反而不如那些“代码一般但展示特别漂亮”的同学。做交通可视化首先要明确展示目标面上要有“宏观态势”即全网客流总量的时序变化线上要有“线路负载”即不同线路的拥挤程度点上要有“站点详情”即某个站点的进出站客流与预测结果。围绕这三个层次你就能合理组织大屏的布局而不是想到什么图就放什么图。4.2 用 ECharts 实现核心图表ECharts 是大屏项目的事实标准因为它配置简单、图表类型丰富、社区案例多。做地铁客流预测大屏时以下几类图几乎必用折线图展示某个站点 24 小时的实际客流与预测客流对比。这是整块大屏的核心图要用两条线把真实值和预测值放在一起老师一眼就能看出模型拟合效果。热力图以小时为横轴、站点为纵轴颜色深浅表示客流量大小。这种图在展示全网客流时空分布时非常直观。地图 散点图如果有站点经纬度数据可以用 ECharts 的地图组件把站点标注在地图上通过气泡大小或颜色表示实时客流量。柱状图对比各线路早晚高峰客流或者对比不同站点的日均客流排名。代码不复杂核心是组织好数据格式。以折线图为例option { xAxis: { type: category, data: [00:00, 01:00, 02:00, 03:00, 04:00, ...] }, yAxis: { type: value, name: 客流量人次 }, series: [ { name: 实际客流, type: line, data: [12, 8, 3, 2, 5, 18, ...] }, { name: 预测客流, type: line, data: [10, 9, 4, 3, 6, 20, ...] } ] };可视化部分最耗时间的其实是数据接口的字段对齐。前端要的 JSON 格式和后端 MySQL 查出来的字段往往不一致建议在 Spring Boot 接口层做一层 DTO 转换统一输出格式不要在前端写太多数据处理逻辑。4.3 大屏部署方案怎么选大屏部署有两种主流方式。第一种是纯前端静态页面 后端接口。前端用 Vue ECharts打包成静态文件放在 Nginx 里后端用 Spring Boot 提供数据接口跨域配置用 CrossOrigin 注解就能解决。这种方案比较传统适合需要展示“后端开发能力”的毕设。第二种是可视化工具。如果你时间紧也可以用 DataV 或帆软这类的可视化平台快速搭建大屏。但这里有个风险这类工具的免费版往往有水印和功能限制而且答辩时老师可能会问“底层数据怎么接入的”如果你对工具背后的原理说不清楚反而减分。我更推荐第一种因为整个链路从 MySQL 到 Spring Boot 到 Nginx 到 ECharts 都可以写进论文工作量看得见摸得着。启动时也就是一行命令npm run build # 生成的 dist 目录放到 Nginx 的 html 目录下即可5. 集群搭建、部署与答辩避坑指南5.1 没有多台服务器怎么做 Spark 集群很多同学觉得“做 Spark 大数据项目必须要有 4 台以上服务器”实际上毕设完全可以在一台电脑上完成。第一种做法是部署单机伪分布式 Spark也就是 Spark standalone 模式下的一个 master 和一个 worker 都跑在同一台机器上。配置只需要改 conf/spark-env.sh 里的 SPARK_MASTER_HOST 为 localhost然后正常启动即可。这种方式适合开发调试也能在论文里说“部署了 Spark 集群1 主 1 从”不算造假。第二种做法是本地模式。如果你的数据量不大比如几百万行以内完全可以不使用集群直接在代码里通过 SparkSession 的 master(local[*]) 运行。开发调试优先用本地模式速度更快也不会有网络通信问题。如果想要更好的演示效果可以申请两台云服务器一台做 master一台做 worker数据放 HDFS然后用浏览器访问 Spark Web UI 展示 job 执行过程。这更多是为了答辩演示加分不是必须项。5.2 项目打包与部署的几个坑Spark 作业部署时最容易踩的坑就是依赖冲突。你在 pom.xml 或 build.sbt 里引用了 Spark 依赖打包时又把 Spark 本身打进了 fat jar提交时就会报各种 ClassNotFoundException 或 jar 冲突。正确的做法是编译打包时把 Spark 相关依赖设为 provided只把自己的业务代码打成 jar运行时由集群提供 Spark 环境。另一个常见坑是 Windows 本地调试时缺少 Hadoop 相关组件会报缺少 winutils.exe。解决方法是下载对应版本的 winutils 放到一个目录然后在程序里设置System.setProperty(hadoop.home.dir, D:\\hadoop-common\\bin);这个坑几乎每个用 Spark 开发的人都会遇到提前知道能节省很多时间。5.3 答辩时老师最爱问的几个问题提前准备下面这些问题的答案答辩基本不会冷场。问你的数据量是多少为什么用 Spark答可以从数据量规模和分布式计算角度回答即使数据量不大“从技术选型角度事先考虑了未来扩展”也是合理的理由。千万别回答“因为毕设题目要求用 Spark”那是送命答案。问为什么用 LSTM 不用 ARIMA答ARIMA 适合单变量线性时间序列而地铁客流受天气、节假日、站点属性等多因素影响LSTM 能捕捉非线性关系和多变量特征。如果能再补一句“我在实验中对比了 ARIMA预测精度明显低于 LSTM”效果最好。问你的模型误差来源有哪些答节假日突发事件难预测、数据噪声、极端天气影响等。承认模型有局限并给出改进方向比强行自夸更得体。问数据预处理做了哪些工作答这就是前面第二节的内容格式统一、去重、缺失值处理、异常值剔除按实际做过的讲干净即可。5.4 数据倾斜与 OOM 排查速查表Spark 作业运行中最常见的两类问题就是数据倾斜和 OOM。前者是由于某些站点的客流量远高于其他站点导致 partition 间数据量差异过大后者通常是内存配置不足或者缓存使用不当导致的。我整理了一份速查表格备查问题现象可能原因排查方法常用解决思路某个 Stage 卡住很久数据倾斜查看 Spark UI 中该 Stage 的最大耗时与 shuffle 数据量对热点 key 加随机前缀、增加分区数执行器 OOM内存溢出查看日志中的 GC 异常或 Container 被杀记录增大 spark.executor.memory、减少一次性缓存的数据量作业运行极慢小文件过多查看输入数据文件数量coalesce 或 repartition 合并分区解决小文件问题序列化报错对象不可序列化检查报错堆栈中涉及的类使用 case class 或只传输必要字段这些内容写进论文的“系统调试与优化”章节也非常合适既是实际工作量又能体现你对 Spark 运行原理的理解。6. 从开题到答辩的完整时间线建议如果还有人卡在“不知道从哪里下手”的阶段我按个人经验给一个 12 周的时间线参考每周的产出都是可验证的第 1-2 周确定选题和数据集搭好开发环境JDK、Hadoop、Spark、MySQL、IDE跑通 Spark 本地模式的 WordCount 样例。第 3-4 周完成数据清洗和特征工程产出第一版训练数据集用 Spark SQL 做基础统计画出客流曲线完成论文的数据预处理章节。第 5-6 周跑通机器学习基线模型线性回归、随机森林、GBDT记录评估指标论文完成模型对比章节的初稿。第 7-8 周训练深度学习模型LSTM调参优化与机器学习模型对比完成核心实验部分。第 9-10 周开发可视化大屏接入 MySQL 数据完成前后端联调。第 11-12 周打包部署撰写论文剩余部分制作 PPT反复演练讲解流程。这个时间线看起来很宽松但很多同学在第 3 周就会因为环境问题浪费掉一半时间所以前面阶段尽量留足缓冲。如果某个环节卡住超过两天果断切换方案不要死磕。我个人在带毕设时最深刻的体会是这个项目真正的难点不在于某一个技术点有多高深而在于你是否能在一套完整链路里让每一环都可靠工作。多花时间在数据清洗和特征工程上模型的提升空间远超你在调参上花的功夫。不管最后选的是机器学习还是深度学习路线先把全流程跑通你才真正掌握了这个项目的主动权。