ARTICLE DETAIL

资讯详情

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

机器学习旅游数据分析系统:Python客流量预测与Django可视化实现

机器学习旅游数据分析系统:Python客流量预测与Django可视化实现 做毕业设计那阵子我最怕的不是写代码而是被人问一句你这个系统到底能干什么选“机器学习python旅游景点数据分析系统”这个题目的时候答辩老师潜意识里想看的其实是一整条链路——用 Python 做数据处理用机器学习算法做人流量分析和客流量预测再套进 Django 框架做成一个能在浏览器里打开的网站。这套组合特别适合想往数据分析、算法方向走的同学也适合想快速搭一个可视化分析产品的朋友。难点从来不在某一个环节而在把“数据清洗、特征工程、模型训练、网页接口、图表展示”像流水线一样串起来。这篇文章我就把完整思路、实现细节和掉过的坑一次性讲清楚。1. 先明白“人流量分析”和“客流量预测”到底差在哪1.1 题目拆解这不是一个算法题而是三个模块的拼装很多人拿到这个题目第一时间就去查“客流量预测算法用什么模型”结果模型调通了系统却拼不起来。原因很简单——这本质上不是一个算法题而是三个模块的拼装数据分析模块要能从历史数据里看出规律比如周一到周五人少、周末人多、节假日井喷、雨天骤降。输出是统计表和可视化图。机器学习预测模块基于历史数据训练模型对未来的客流量给出一个具体数值比如“预测明天下午 3 点景区客流 8200 人”。Web 展示模块用 Django 把前面两个模块包起来让使用者能在网页上选择日期、查看图表、跑出预测结果。单独拎出来任何一个模块都不算难。难的是它们之间要协同工作尤其是数据格式的对接。很多同学在 Notebook 里跑数据很顺手一到 Django 里调模型就报错就是因为没有提前设计好“边界”。1.2 为什么“分析”和“预测”必须分开设计人流量分析是“看过去”客流量预测是“算未来”这两个功能如果混在同一个页面里后期会非常痛苦。我一开始做的时候图省事把统计数据和预测结果放在同一个接口里返回。结果联调时发现预测部分只要模型加载慢一点整个页面的图表都卡死。后来拆成两个接口/api/analysis/负责历史统计/api/forecast/负责预测。前端分开请求各显示各的问题立刻解决。建议在功能设计阶段就把这两者分开分析模块展示按小时、按天、按月的访问趋势、客流热力图、节假日对比预测模块则独立输入一个目标日期输出预测值并展示过去一段时间真实值和预测值的对比。这样模块边界清晰论文里也好画架构图。2. 数据是这类系统的命门来源、清洗和特征工程2.1 没有真实数据怎么办公开数据加规则模拟必须承认绝大多数本科生拿不到真实景区的人流数据这不是什么丢人的事。我当时用了两种方式组合能找到公开历史客流数据的景区就整理成 CSV找不到的就按“季节趋势 星期规律 节假日波动 随机噪声”生成模拟数据。答辩的时候我直接坦白数据是部分公开、部分模拟生成的老师反而觉得这个思路很务实。模拟数据不是乱编要有业务逻辑。比如景区客流一年内有两个高峰五一和十一黄金周周末比工作日明显高恶劣天气会断崖式下跌。我当时的生成规则大概是import numpy as np import pandas as pd base 3000 # 平日基础客流 seasonal 800 * np.sin(2 * np.pi * day_of_year / 365) weekly 600 if weekday 5 else -200 holiday 2500 if is_holiday else 0 noise np.random.normal(0, 300) visitor_count base seasonal weekly holiday noise这种方式的好处是可控能生成十年的历史数据模型训练不会缺样本坏处是数据太“干净”模型结果容易偏高。所以我会在模拟数据里加一些缺失值和异常值再走一遍真实项目的清洗流程。2.2 表结构和字段最少要存哪几个字段不管数据来自哪里最后都要落成一张统一格式的表。我建议最少包含以下字段字段名类型说明datedate日期唯一索引scenic_idint景区编号用于多景区扩展weekdayint星期几0-6is_holidayint是否节假日0或1weather_codeint天气类型编码如0晴、1雨、2雪temperaturefloat当日平均气温visitor_countint当日客流量预测目标字段不要贪多。一开始我加了很多东西比如风力、空气质量、周边酒店价格结果数据缺失率超过 40%清洗完根本没剩多少有效样本。后来砍到这几个核心字段模型效果反而更稳定。2.3 清洗环节缺失值、异常值和时间对齐机器学习中的数据处理核心就是“对齐、去脏、补缺、造特征”。从不同渠道拿到的数据日期格式、统计口径都不一样第一件事是统一日期格式然后按日期排序。缺失值处理要分情况如果是工作日的数据缺失可以用上一个同星期几的数据来补如果是节假日缺失不能简单用平均值我会单独标记为缺失或者用前后两天的均值。异常值主要看两种情况客流为 0或者某天突然变成平常的 10 倍以上。我当时用滚动窗口判断df[ma7] df[visitor_count].rolling(7, min_periods1).mean() df[std7] df[visitor_count].rolling(7, min_periods1).std() df[is_outlier] abs(df[visitor_count] - df[ma7]) 3 * df[std7]识别出来之后不要直接删掉先看一下日期是不是大型节假日如果是保留如果不是再用近 7 天均值替换。清洗完的数据重新落库后面所有模块都用清洗后的版本。2.4 特征工程把日期、天气、节假日变成模型输入原始数据里的date字段不能直接喂给模型必须转换成模型能理解的数值特征。我当时构造了一组不算复杂但非常管用的特征一年中的第几天day_of_year用来捕捉季节性。星期几的 One-Hot 编码或者直接用 0-6 的整数加进模型。是否节假日0/1 二值特征。前一天客流量prev_day_flow这是一个非常重要的滞后特征。近 7 天平均客流量week_avg_flow用来平滑短期波动。这里有个关键点如果要预测“明天的客流量”那么训练样本的特征必须全部来自明天之前已知的信息。我当时用shift(-1)把目标列后移确保模型不会用“未来数据”去预测“过去”。df[target] df[visitor_count].shift(-1) df df.dropna(subset[target]) feature_cols [weekday, is_holiday, day_of_year, temperature, weather_code, prev_day_flow, week_avg_flow] X df[feature_cols] y df[target]特征做出来之后我习惯用相关性热力图扫一眼。如果某个特征跟目标的相关性极其接近 0先不要急着删它可能是没经过非线性变换导致的也可能是纯噪声等模型跑完看特征重要性再决定。3. 客流量预测算法从基准模型到最终选型3.1 先跑线性回归当基准便宜且能暴露问题很多人一上来就上 LSTM这是最容易翻车的路径。我当时的做法相反——先用线性回归把整条链路跑通从数据到模型再到 Django 接口一步不卡之后再去换复杂模型。线性回归在这个场景里虽然预测精度一般但价值很大训练速度快几乎不调参。能立刻暴露特征是否有问题比如某个特征有大量 NaN或者数据存在严重的多重共线性。可以作为基准后面模型效果如果还不如线性回归那一定是数据处理环节出了问题而不是模型不够高级。在 sklearn 里写线性回归是很简单的事但我会加一个正则化from sklearn.linear_model import Ridge model Ridge(alpha1.0) model.fit(X_train, y_train)为什么用岭回归而不是普通线性回归因为特征里有prev_day_flow和week_avg_flow这类高度相关的滞后特征普通最小二乘容易过拟合岭回归的正则项可以压住系数波动。3.2 随机森林和 XGBoost树模型才是这个时候的主力线性回归跑通之后我开始对比随机森林和 XGBoost。在我那批数据上大概的结果是模型MAERMSEMAPE备注线性回归1320186023%速度快误差偏大随机森林980145017%默认参数可接受XGBoost880132015%调参后更好LSTM920140016%训练慢收益有限不同数据下数字会有浮动但相对关系很有参考价值树模型明显优于线性回归而 LSTM 在日粒度客流预测上并没有想象中那么强。随机森林的好处是几乎不用做特征缩放缺失值容忍度也高。我当时先用默认参数重点看特征重要性排序。XGBoost 在调了max_depth、learning_rate、n_estimators之后误差还能再降一点。我会把两个模型的预测结果都存下来再决定选哪个。最终论文里用的也是“以 XGBoost 为主、随机森林作对比”的结论。3.3 LSTM 我建议最后再碰LSTM 听起来跟“时间序列预测”很搭但实际用在日客流预测上训练成本高、可解释性差而且极容易犯数据泄漏的错误。如果非要用有两个坑必须躲开第一数据切分不能用随机打乱。时间序列必须按时间顺序切分否则模型会用测试集的数据参与训练结果虚高。我当时用前 80% 的天数做训练后 20% 做测试。如果交叉验证就用TimeSeriesSplit不要用KFold。第二MinMaxScaler只能 fit 在训练集上。如果先对整个数据集做归一化再划分测试集的信息就提前泄露了。正确做法是先划分再单独 fit 训练集然后用同一套缩放参数去 transform 测试集。LSTM 的输入格式是三维的(samples, timesteps, features)也就是要把每天一行数据整理成“过去 N 天窗口”的切片def build_sequences(X, y, seq_len7): X_seq, y_seq [], [] for i in range(len(X) - seq_len): X_seq.append(X.iloc[i:i seq_len].values) y_seq.append(y.iloc[i seq_len]) return np.array(X_seq), np.array(y_seq)seq_len7表示用过去 7 天的特征去预测第 8 天。这也在提醒你LSTM 本质上是在问“模型的记忆窗口是几天”而不是默认所有时间依赖都能自动学到。3.4 评价指标为什么只用 R2 会被老师追问分类问题才谈“准确率”回归问题里经常用的指标是 MAE、RMSE、MAPEMAE平均绝对误差和业务量纲一致比如“平均每天预测偏差 880 人”。RMSE对偏差比较大的样本更敏感可以用来避免模型“大多数日子准、偶尔差很远”。MAPE百分比误差最好向非技术的人解释比如“预测误差大约 15%”。R² 当然也可以看但它描述的是“模型解释了数据的多少方差”对于旅游景区的管理者来说很难直观理解。我答辩时是人流量预测功能只报了 MAPE 和 MAE老师说这比单纯说“准确率 90%”靠谱得多因为“准确率 90%”在连续数值预测里概念不清。4. Django 对接模型和数据可视化最容易断链的两段4.1 Django 项目结构和 App 怎么划分模型训练是在 Notebook 里完成的但系统运行要靠 Django。很多同学到这一步就卡住Notebook 里好好的df、model到了 Django 里怎么用我推荐的项目结构是tourism_system/ ├── manage.py ├── requirements.txt ├── tourism_system/ # 全局配置 │ ├── settings.py │ ├── urls.py │ └── wsgi.py ├── apps/ │ ├── analysis/ # 历史数据分析 │ ├── forecast/ # 客流量预测 │ └── users/ # 登录注册 ├── ml_models/ │ └── flow_model.pkl # 训练好的模型文件 ├── static/ │ ├── css/ │ └── js/ ├── templates/ │ ├── analysis.html │ └── forecast.html └── data/ ├── raw/ # 原始数据 └── processed/ # 清洗后的数据创建 App 的命令很简单新建项目后分别执行python manage.py startapp analysis、python manage.py startapp forecast。然后把 App 注册到INSTALLED_APPS里。注意 App 划分不要碎尽量按业务模块来一个业务域建一个 App别一个功能建一个 App。4.2 模型文件怎么加载只加载一次别放进视图里反复读模型训练完用 joblib 或 pickle 导出成文件import joblib joblib.dump(model, ml_models/flow_model.pkl)然后在 Django 里写一个全局加载函数。一开始我直接把pickle.load()写在视图函数里结果是每次请求都要读一次文件页面明显卡顿。后来改成模块级缓存from pathlib import Path import joblib from django.conf import settings _model None def get_model(): global _model if _model is None: model_path Path(settings.BASE_DIR) / ml_models / flow_model.pkl _model joblib.load(model_path) return _model视图里调用get_model()即可。这个还可以再加一层缓存比如django.core.cache但我实测模块级变量已经够用因为模型本身不大。预测接口的写法就是接收目标日期把日期转成特征然后返回 JSONimport json from django.http import JsonResponse from django.views.decorators.http import require_GET require_GET def forecast_api(request): date_str request.GET.get(date, ) try: features build_feature_from_date(date_str) except ValueError: return JsonResponse({error: 日期格式错误}, status400) pred int(round(get_model().predict([features])[0])) return JsonResponse({date: date_str, predicted_flow: pred})这里要注意build_feature_from_date必须在 Django 端重新实现一遍特征构造逻辑不能依赖 Notebook 里的全局变量。最稳妥的方式是除了.pkl模型再把特征列名和构造逻辑也固化到一个 Python 模块里比如ml_models/feature_builder.py。4.3 页面图表别用 matplotlib用 ECharts 接 Ajax很多同学在 Notebook 里用 matplotlib 画图然后想在网页上直接展示这是很大的弯路。matplotlib 生成的是静态图片没法交互也没法随数据更新。我的做法是后端返回数据 JSON前端用 ECharts 渲染图表。模板里放一个div容器div idflowChart styleheight: 400px;/div script srchttps://cdn.jsdelivr.net/npm/echarts5/dist/echarts.min.js/script在页面 JS 里请求接口fetch(/api/flow_trend/) .then(response response.json()) .then(data { var chart echarts.init(document.getElementById(flowChart)); chart.setOption({ xAxis: { data: data.dates }, yAxis: {}, series: [{ type: line, data: data.values }] }); });ECharts 的折线图、柱状图、热力图都很适合展示客流数据而且做可视化大屏时图表质感比 matplotlib 好太多。后端需要返回一个标准的 JSON 列表这个接口要写成一个单独的dashboard_data()视图方便统一控制时间范围。4.4 联调阶段最容易翻车的五个细节这个阶段我踩过的坑整理出来基本就是下面这五条坑表现解决办法模型文件路径不对页面报 FileNotFoundError用Path(settings.BASE_DIR)拼接绝对路径ALLOWED_HOSTS没配置浏览器访问被拒绝本地调试时预留localhost、127.0.0.1静态文件丢失CSS/JS 加载失败起服务前先跑python manage.py collectstatic数据库没迁移登录注册表不存在先执行python manage.py migrate特征顺序和训练时不一致预测结果完全离谱把feature_cols固定写死用同一顺序构造另外Django 执行查询时QuerySet 是惰性的比如User.objects.filter(...)不会立刻打数据库真正取值时才执行。删除对象时也要注意级联关系如果外键字段开了on_deleteCASCADE删主表会把关联记录一起删掉写删除接口时一定要提醒自己先确认影响范围。5. 把系统做得像“产品”而不是课程作业5.1 给演示加分的“模拟实时客流”答辩现场最怕的是数据一动不动图表毫无生气。我当时加了一个“实时客流模拟”的小模块从当天早起开始按时间曲线模拟出每隔 10 分钟一个客流点存到独立数据表里。网页端用定时器每隔 10 秒请求一次最新数据图表会缓慢滚动起来“系统正在实时监测客流”的观感一下就出来了。简单实现方式# 在生成模拟数据的函数里叠加一个按小时变化的系数 hour 9 # 上午9点 flow base_flow * (0.6 0.8 * np.exp(-(hour - 14) ** 2 / 20))核心思路是让演示过程看起来是“活的”而不是提前画好的静态图。这不影响模型预测逻辑因为实时数据只做展示预测依然基于历史数据训练出的模型。5.2 权限控制做到什么程度算合格毕业设计里只要有用户概念就该有权限控制。Django 自带的User模型和login_required装饰器已经够用不需要自己写 Session。我的做法是分两种角色管理员可以查看全部数据并触发预测普通用户只能查看分析图表。这实际上用 Django 的User.is_staff字段就能区分不需要引入额外的权限框架。from django.contrib.auth.decorators import login_required login_required def dashboard_view(request): if request.user.is_staff: template admin_dashboard.html else: template user_dashboard.html return render(request, template)答辩老师看到页面有登录拦截、有角色区分会认为你考虑了系统安全而不是一个“裸奔”的报表页。5.3 大屏可视化取舍比炫技更重要很多人做可视化的时候恨不得堆满十个图表最后大屏显得杂乱。我建议只保留四块核心内容今日客流总数与昨日同比。最近 30 天客流趋势折线图。未来 7 天预测客流柱状图。按景区或按时间段分组的客流分布热力图。这四个信息足够支撑一套演示。ECharts 的时间轴组件和定时器刷新已经能给人大屏的感觉没必要为了“炫”去引入 Vue 或 React因为这会大幅增加联调时间且对毕业设计反而影响稳定。6. 源码整理、论文写作和答辩准备的先后顺序6.1 源码目录如何排列让老师 10 分钟看懂老师不可能把你的代码从头到尾读一遍他能迅速 get 到的是你的代码组织是否整洁。我当时最后一天专门花了四个小时重构目录把训练用的 Notebook 移到notebooks/把数据分raw/和processed/两个文件夹README 写清楚“如何安装依赖、如何迁移数据库、如何启动服务”。环境依赖一定要用requirements.txt锁住主要版本Django4.2.5 pandas2.0.3 numpy1.24.3 scikit-learn1.3.0 xgboost1.7.6 joblib1.3.2为什么不直接用最新版因为我实测如果把 pandas 升到 2.2有些依赖库的编译问题会突然冒出来浪费很多时间。用稳定版本组合写进 README任何人拉下来都能跑。6.2 文档里必须出现的三张图和一组对比表写论文或设计文档时图永远比文字重要。我用 ProcessOn 画了三张图答辩老师看完基本不再追问系统结构系统架构图体现“Django 前端页面 - 视图接口 - 业务逻辑层 - 数据分析/预测模块 - 数据库和模型文件”的分层关系。数据流程图从原始数据到清洗、特征工程、训练集测试集划分、模型训练、模型部署到 Django 的完整路径。预测模块流程图展示输入特征、模型推理、输出预测值的流程。比代码更重要的是这组图要能表达你“为什么这样设计”。然后配上一张模型对比表把线性回归、随机森林、XGBoost、LSTM 在 MAE、RMSE、MAPE 上的表现放进去。老师看到这张表就知道你不是把模型硬套上去的而是做过真实选型实验的。6.3 答辩高频问题我怎么组织答案答辩时被问最多的就是下面几个问题我的回答思路也分享在这里第一个问题“为什么选这几个特征” 回答思路先说业务规律再补技术细节。节假日、星期、天气是影响景区客流的常识因素滞后特征是为了让模型能捕捉短期的惯性趋势。第二个问题“你怎么避免数据泄漏” 回答思路明确回答“按时间划分训练集和测试集所有滞后特征只使用目标日期之前的信息缩放器只 fit 训练集。”这句话基本就能兜住大部分追问。第三个问题“这个系统还能用在什么地方” 回答思路同样的人流量分析框架可以套用到展馆、商场、地铁站只要把数据字段换成对应的场所人流量逻辑几乎不用改。这样回答既合理又展示了你系统的可扩展性。第四个问题“模拟数据和真实数据的差距怎么办” 回答思路诚实说明“真实项目需要接入景区入口闸机或票务系统的统计结果毕业设计中用公开数据和模拟数据是为了验证算法链路未来替换数据源即可。”别慌这反而是给你加分的点。最后想说的这套系统做完给我的最大感受是机器学习项目真正耗时间的不是算法和模型而是把数据处理干净并且保证训练环境里的数据和 Web 环境里的数据长得一模一样。我最后悔的事情是前期没把“模型上线到 Django”这一步想透导致返工了一遍。如果你也是正在做类似毕业设计的人我真心建议按文章里的顺序来先做数据分析再训练模型然后把模型当做一个普通的“函数”接进 Django最后的可视化反而水到渠成。中间无论卡多久都别跳过数据清洗那一关——那才是整个系统能不能站住的地基。
返回列表