☰
机器学习旅游数据分析系统:Python客流量预测与Django可视化实现
2026/10/5 11:08:49 网站建设 项目流程

做毕业设计那阵子,我最怕的不是写代码,而是被人问一句:你这个系统到底能干什么?选“机器学习: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-6
is_holidayint是否节假日,0或1
weather_codeint天气类型编码,如0晴、1雨、2雪
temperaturefloat当日平均气温
visitor_countint当日客流量,预测目标

字段不要贪多。一开始我加了很多东西,比如风力、空气质量、周边酒店价格,结果数据缺失率超过 40%,清洗完根本没剩多少有效样本。后来砍到这几个核心字段,模型效果反而更稳定。

2.3 清洗环节:缺失值、异常值和时间对齐

机器学习中的数据处理,核心就是“对齐、去脏、补缺、造特征”。从不同渠道拿到的数据,日期格式、统计口径都不一样,第一件事是统一日期格式,然后按日期排序。

缺失值处理要分情况:如果是工作日的数据缺失,可以用上一个同星期几的数据来补;如果是节假日缺失,不能简单用平均值,我会单独标记为缺失,或者用前后两天的均值。异常值主要看两种情况:客流为 0,或者某天突然变成平常的 10 倍以上。我当时用滚动窗口判断:

df['ma7'] = df['visitor_count'].rolling(7, min_periods=1).mean() df['std7'] = df['visitor_count'].rolling(7, min_periods=1).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(alpha=1.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_len=7): 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_len=7表示用过去 7 天的特征去预测第 8 天。这也在提醒你,LSTM 本质上是在问“模型的记忆窗口是几天”,而不是默认所有时间依赖都能自动学到。

3.4 评价指标:为什么只用 R2 会被老师追问

分类问题才谈“准确率”,回归问题里经常用的指标是 MAE、RMSE、MAPE:

  • MAE,平均绝对误差,和业务量纲一致,比如“平均每天预测偏差 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,但我实测模块级变量已经够用,因为模型本身不大。

预测接口的写法就是接收目标日期,把日期转成特征,然后返回 JSON:

import 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': '日期格式错误'}, status=400) 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 id="flowChart" style="height: 400px;"></div> <script src="https://cdn.jsdelivr.net/npm/echarts@5/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_delete=CASCADE,删主表会把关联记录一起删掉,写删除接口时一定要提醒自己先确认影响范围。

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锁住主要版本:

Django==4.2.5 pandas==2.0.3 numpy==1.24.3 scikit-learn==1.3.0 xgboost==1.7.6 joblib==1.3.2

为什么不直接用最新版?因为我实测如果把 pandas 升到 2.2,有些依赖库的编译问题会突然冒出来,浪费很多时间。用稳定版本组合,写进 README,任何人拉下来都能跑。

6.2 文档里必须出现的三张图和一组对比表

写论文或设计文档时,图永远比文字重要。我用 ProcessOn 画了三张图,答辩老师看完基本不再追问系统结构:

  • 系统架构图:体现“Django 前端页面 - 视图接口 - 业务逻辑层 - 数据分析/预测模块 - 数据库和模型文件”的分层关系。
  • 数据流程图:从原始数据到清洗、特征工程、训练集测试集划分、模型训练、模型部署到 Django 的完整路径。
  • 预测模块流程图:展示输入特征、模型推理、输出预测值的流程。

比代码更重要的是,这组图要能表达你“为什么这样设计”。然后配上一张模型对比表,把线性回归、随机森林、XGBoost、LSTM 在 MAE、RMSE、MAPE 上的表现放进去。老师看到这张表,就知道你不是把模型硬套上去的,而是做过真实选型实验的。

6.3 答辩高频问题:我怎么组织答案

答辩时被问最多的就是下面几个问题,我的回答思路也分享在这里:

第一个问题:“为什么选这几个特征?” 回答思路:先说业务规律,再补技术细节。节假日、星期、天气是影响景区客流的常识因素,滞后特征是为了让模型能捕捉短期的惯性趋势。

第二个问题:“你怎么避免数据泄漏?” 回答思路:明确回答“按时间划分训练集和测试集,所有滞后特征只使用目标日期之前的信息,缩放器只 fit 训练集。”这句话基本就能兜住大部分追问。

第三个问题:“这个系统还能用在什么地方?” 回答思路:同样的人流量分析框架可以套用到展馆、商场、地铁站,只要把数据字段换成对应的场所人流量,逻辑几乎不用改。这样回答既合理,又展示了你系统的可扩展性。

第四个问题:“模拟数据和真实数据的差距怎么办?” 回答思路:诚实说明“真实项目需要接入景区入口闸机或票务系统的统计结果,毕业设计中用公开数据和模拟数据是为了验证算法链路,未来替换数据源即可。”别慌,这反而是给你加分的点。

最后想说的

这套系统做完,给我的最大感受是:机器学习项目真正耗时间的不是算法和模型,而是把数据处理干净,并且保证训练环境里的数据和 Web 环境里的数据长得一模一样。我最后悔的事情是前期没把“模型上线到 Django”这一步想透,导致返工了一遍。如果你也是正在做类似毕业设计的人,我真心建议按文章里的顺序来:先做数据分析,再训练模型,然后把模型当做一个普通的“函数”接进 Django,最后的可视化反而水到渠成。中间无论卡多久,都别跳过数据清洗那一关——那才是整个系统能不能站住的地基。

需要专业的网站建设服务?

联系我们获取免费的网站建设咨询和方案报价,让我们帮助您实现业务目标

立即咨询