ARTICLE DETAIL

资讯详情

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

数学建模入门:从时间序列预测到餐厅营业额分析实战

数学建模入门:从时间序列预测到餐厅营业额分析实战 1. 从“看热闹”到“入门”为什么第二天是关键如果你昨天跟着川川的节奏成功安装了MATLAB或者Python并且跑通了第一个“Hello, World”级别的数学建模程序那么恭喜你你已经迈出了从“观望者”到“参与者”的第一步。但很多人的学习热情往往就止步于这第一步。第二天是决定你能否真正“入门”而不是仅仅“看过”数学建模的分水岭。为什么这么说因为第一天的内容更多是仪式感和环境搭建它解决了“从无到有”的问题。而第二天我们开始真正触及数学建模的核心思维模式和工作流骨架。这就像学游泳第一天你知道了泳池在哪、买了泳衣第二天教练开始教你如何在水里漂浮、如何换气——这些基础动作看似简单却决定了你未来是能畅游还是只能泡在水里扑腾。“跟着川川学数模-Day2”这个标题背后隐含的潜在需求非常明确学习者需要一个清晰、可执行、能立刻看到反馈的路径来建立对数学建模全流程的初步认知避免在浩瀚的理论中迷失方向。他们可能刚看完一堆让人眼花缭乱的算法介绍或者被“国赛”、“美赛”那些高大上的题目吓到急需有人拉着他们的手走一遍最精简、最核心的流程建立信心。因此第二天的内容绝不会是深奥的算法推导也不会是复杂的编程技巧。它的核心任务只有一个带你完整地“走一遍”一个微型数学建模项目的全流程让你对“建模-求解-分析”这个闭环产生肌肉记忆。我们选择的案例必定是经典的、数据易于获取的、模型直观的。今天我们就以“餐厅营业额预测”这个生活化场景为例手把手走一遍。你会发现数学建模离你并不遥远。2. 案例切入如何为一家小餐厅建立预测模型我们虚构一个场景你朋友在学校周边开了一家奶茶店他发现生意时好时坏想让你帮忙分析一下能不能预测未来一周的大致营业额好准备原料和排班。这就是一个非常典型的单变量时间序列预测问题是数学建模中最基础的题型之一。2.1 问题定义与简化把现实问题翻译成数学语言接到问题第一步不是马上找数据、写代码而是定义和简化问题。这是新手最容易忽略却最重要的一步。明确目标朋友的需求是“预测未来一周营业额”。这是一个定量预测问题。确定核心变量影响营业额的因素很多天气、节假日、促销活动、竞争对手、甚至微博热搜。作为初始模型我们必须简化。一个最核心、最直接的驱动因素是什么客流量。但客流量数据我们可能没有。那么一个更合理的假设是营业额具有时间上的连续性即今天的营业额和昨天、上周同期的营业额有关。因此我们决定将“时间”作为核心变量建立营业额随时间变化的模型。模型输出输出未来7天一周每天的营业额预测值。模型评估如何知道模型预测得准不准我们需要历史数据。将历史数据一部分用来训练模型一部分用来测试比较预测值和真实值的误差。经过以上分析我们把一个模糊的商业问题转化成了一个清晰的数学任务基于过去N天的每日营业额数据建立一个时间序列模型预测未来7天的营业额。这就是数学建模中的“模型假设”与“问题重述”。2.2 数据获取与探索你的模型质量80%取决于此没有数据一切建模都是空中楼阁。对于这个案例数据可以来自朋友的收银系统最理想或者我们手动模拟一份。为了教学我们使用模拟数据但模拟的规则来源于真实逻辑。我们模拟过去60天的日营业额数据。假设其受以下因素影响趋势新店开业有自然增长趋势。周期性明显的周末效应周五、周六营业额高。随机波动日常的随机因素。在Python中我们可以用pandas和numpy快速生成这样的数据import pandas as pd import numpy as np import matplotlib.pyplot as plt # 设置随机种子确保结果可复现 np.random.seed(2024) # 生成日期范围 dates pd.date_range(start2024-01-01, periods60, freqD) # 模拟趋势成分线性缓慢增长 trend np.linspace(100, 150, 60) # 模拟周期成分每周7天周末高 day_of_week dates.dayofweek # 周一0, 周日6 weekly_pattern np.array([0.8, 0.9, 1.0, 1.0, 1.2, 1.5, 1.2]) # 周一到周日的系数 cycle weekly_pattern[day_of_week] * 50 # 基准周期振幅 # 模拟随机噪声 noise np.random.normal(0, 10, 60) # 合成营业额数据 sales trend cycle noise # 确保营业额不为负 sales np.maximum(sales, 20) # 创建DataFrame df pd.DataFrame({date: dates, sales: sales}) df.set_index(date, inplaceTrue) # 查看前几行 print(df.head()) # 绘制时序图 plt.figure(figsize(12, 5)) plt.plot(df.index, df[sales], markero, linestyle-, linewidth1, markersize3) plt.title(模拟餐厅日营业额时序图 (60天)) plt.xlabel(日期) plt.ylabel(营业额 (元)) plt.grid(True, linestyle--, alpha0.7) plt.tight_layout() plt.show()运行这段代码你会得到一份数据和一张图。看图是数据探索的核心。从图中你应该能直观看到整体有缓慢上升趋势并且每隔7天出现一个波峰周末。这就是数据的“性格”。建模首先要了解数据的性格。注意在实际比赛中数据探索EDA要复杂得多包括查看统计摘要df.describe()、检查缺失值df.isnull().sum()、分析分布直方图、箱线图等。这里我们做了最关键的——可视化时序图。3. 模型初选与实现从最直观的“朴素模型”开始面对一个时间序列预测问题有ARIMA、指数平滑、Prophet甚至LSTM等众多模型。对于第二天入门我们选择最直观、最容易理解的模型移动平均法和季节性朴素预测法。理解它们能为理解更复杂的模型打下坚实基础。3.1 模型一简单移动平均SMA核心思想认为未来的值只和最近一段时间的历史平均值有关。这是一种“懒人”但有时很有效的模型。数学表达预测值(t1) 平均值(观测值(t), 观测值(t-1), ..., 观测值(t-n1))如何选择n窗口大小这是一个超参数。n太小模型对噪声敏感n太大模型反应迟钝。我们可以通过试验来选择。这里我们尝试用过去7天一周的平均值来预测下一天。# 使用过去7天的移动平均作为预测值 df[SMA_7] df[sales].rolling(window7).mean() # 预测将最后7天的移动平均值作为未来第一天的预测值 last_sma_value df[SMA_7].iloc[-1] print(f基于7日移动平均下一天营业额预测为{last_sma_value:.2f}元) # 为了评估我们假设最后7天是测试集看看模型在“已知”数据上的表现 test_start -7 train_set df.iloc[:test_start] test_set df.iloc[test_start:] # 计算训练集上最后一天的SMA作为对测试集第一天的“预测” prediction_sma [] for i in range(len(test_set)): # 在实际预测中我们应该用截至前一天的数据来计算SMA # 这里简化用训练集已预测的部分来滚动计算更真实的模拟 pass # 此处简化下文会在更完整的模型中演示滚动预测逻辑这个模型的优缺点是什么优点简单无需训练计算快对平稳序列有效。缺点完全忽略趋势和季节性。对于我们有明显周期和趋势的数据预测结果会严重滞后。比如在周末它预测的只是上周工作日的平均水平必然低估。3.2 模型二季节性朴素预测法Seasonal Naïve核心思想对于有强季节性的数据比如我们的周度周期最简单的假设是“明天和上周同一天差不多”。这比移动平均更贴合我们的场景。数学表达预测值(t1) 观测值(t1 - 周期长度)在我们的案例中周期长度7天。# 季节性朴素预测直接用上一周同一天的营业额作为预测值 period 7 df[Seasonal_Naive] df[sales].shift(period) # 预测未来7天 future_dates pd.date_range(startdf.index[-1] pd.Timedelta(days1), periods7, freqD) future_predictions [] last_known_data df[sales].copy() for i in range(7): # 预测第i天就取7天前的数据 pred_value last_known_data.iloc[-period] future_predictions.append(pred_value) # 在滚动预测中可以将预测值加入序列用于预测更远的未来这里简化处理 # 更严谨的做法需要扩展索引这里仅作原理演示 print(未来7天预测季节性朴素法:) for date, pred in zip(future_dates, future_predictions): print(f{date.date()}: {pred:.2f}元)这个模型的优缺点是什么优点完美捕捉季节性对于周期性强的数据它往往能提供一个惊人的好基线Baseline。任何复杂模型如果效果比不上它那就没有使用价值。缺点完全忽略趋势和周期内的变化。如果营业额整体在上升它预测的永远是上周的水平会系统性低估。3.3 模型评估用数字说话而不是感觉我们模拟了数据也做出了预测但哪个模型好不能靠猜必须量化评估。这里引入两个最常用的回归预测评估指标均方根误差RMSEsqrt(mean((真实值 - 预测值)^2))。衡量预测值与真实值之间的平均偏差单位与原始数据相同元。它对大误差惩罚更重。平均绝对百分比误差MAPEmean(|(真实值 - 预测值) / 真实值|) * 100%。衡量相对误差是一个百分比便于理解。但当真实值很接近0时MAPE会失真。让我们用最后14天数据作为“模拟测试集”来评估一下季节性朴素模型在历史数据上的表现# 假设我们以倒数第14天为界划分训练和测试时间序列不能随机划分 split_point -14 train df.iloc[:split_point].copy() test df.iloc[split_point:].copy() # 在训练集上为测试集生成季节性朴素预测 # 测试集第一天的预测等于训练集倒数第7天的值 test[pred_seasonal] train[sales].iloc[-period:].values.tolist() * 2 # 简单重复两次以匹配长度 test[pred_seasonal] test[pred_seasonal][:len(test)] # 确保长度一致 # 计算评估指标 from sklearn.metrics import mean_squared_error, mean_absolute_percentage_error rmse_seasonal np.sqrt(mean_squared_error(test[sales], test[pred_seasonal])) mape_seasonal mean_absolute_percentage_error(test[sales], test[pred_seasonal]) * 100 print(f季节性朴素模型在测试集上的表现) print(fRMSE: {rmse_seasonal:.2f} 元) print(fMAPE: {mape_seasonal:.2f}%)通过这两个数字我们就能客观地知道这个简单模型的预测平均偏差大概在多少元误差百分比是多少。比如如果RMSE是20元MAPE是10%意味着预测值平均偏离真实值20元相对误差约10%。这对于奶茶店来说可能已经具备一定的参考价值了。实操心得在数学建模中建立一个简单的基线模型Baseline Model是至关重要的第一步。季节性朴素法就是一个强大的基线。它的意义在于1. 快速提供一个预测结果。2. 作为评估更复杂模型的“及格线”。任何投入大量精力构建的复杂模型其效果必须显著优于这个基线否则你的工作可能就是无效的。4. 模型优化初探引入“趋势”成分我们看到季节性朴素模型抓住了“周度波动”但没抓住“缓慢增长”的趋势。如何改进一个直观的想法是在季节性预测的基础上加上一个趋势项。我们可以用最简单的方式估计趋势计算序列的一阶差分的平均值。一阶差分就是后一天减去前一天的值它代表了每日的平均变化量趋势斜率。# 计算训练集营业额的一阶差分并求平均作为日均增长趋势 train_diff train[sales].diff().dropna() # 计算差分去掉第一个NaN avg_daily_trend train_diff.mean() print(f训练集上估计的日均增长趋势平均差分: {avg_daily_trend:.2f} 元/天) # 构建一个“趋势季节性”的朴素模型 # 预测值 季节性朴素预测值 趋势增量 test[pred_seasonal_trend] test[pred_seasonal].copy() # 为测试集的每一天加上累计的趋势增量 for i in range(len(test)): # 第i天的趋势增量 日均趋势 * (i1)因为距离训练集最后一天有(i1)天 test[pred_seasonal_trend].iloc[i] avg_daily_trend * (i 1) # 评估优化后的模型 rmse_trend np.sqrt(mean_squared_error(test[sales], test[pred_seasonal_trend])) mape_trend mean_absolute_percentage_error(test[sales], test[pred_seasonal_trend]) * 100 print(f\n趋势季节性朴素模型在测试集上的表现) print(fRMSE: {rmse_trend:.2f} 元) print(fMAPE: {mape_trend:.2f}%) # 对比两个模型 print(f\n模型对比数值越小越好) print(fRMSE降低: {rmse_seasonal - rmse_trend:.2f} 元) print(fMAPE降低: {mape_seasonal - mape_trend:.2f} 个百分点)如果avg_daily_trend是正数那么这个新模型的预测值就会比纯季节性模型更高更贴合整体上涨的趋势。通过对比RMSE和MAPE你可以定量地看到加入趋势项后模型性能是否得到了提升。这个过程就是模型迭代和优化。5. 完整工作流复盘与报告雏形走完这个微型案例你已经体验了一个完整数学建模流程的骨架。现在我们来复盘并把它映射到竞赛或实际项目的标准步骤中问题理解与定义将“预测营业额”转化为“单变量时间序列预测”问题。明确输入历史营业额、输出未来7天预测、评估标准RMSE/MAPE。数据准备与探索获取/模拟数据进行可视化时序图了解数据的基本特征趋势、季节性。基线模型建立选择并实现季节性朴素模型。这是一个关键习惯先建立简单可解释的基线。模型优化分析基线模型的不足忽略趋势提出改进思路加入趋势项并实现新模型。模型评估与对比使用预留的测试集用RMSE和MAPE定量评估两个模型通过指标对比说明优化是否有效。预测与报告使用你认为最优的模型对未来7天进行最终预测并形成简要结论。在正式的数学建模论文中你需要将以上每一步清晰地表述出来并配上关键代码、图表和结果表格。例如结果展示表示例日期季节性朴素预测值元趋势季节性预测值元预留实际值元2024-03-01135.20138.50-2024-03-02158.75162.05-.........-模型评估RMSE: 22.5RMSE: 18.3-MAPE: 12.1%MAPE: 9.8%-结论针对该餐厅的营业额数据引入简单的线性趋势项后模型预测误差显著降低RMSE减少4.2元MAPE降低2.3个百分点。建议采用“趋势季节性”模型进行未来一周的预测备货与排班可参考该预测结果并预留约10%的误差缓冲空间。6. 避坑指南第二天实操中一定会遇到的问题当你自己动手复现上述过程时几乎一定会遇到下面几个坑。提前了解能节省你大量时间。6.1 坑一时间序列数据索引混乱在Pandas中如果没有明确设置日期索引df.set_index(date, inplaceTrue)或者索引不是DatetimeIndex类型很多时间序列操作如shift()、rolling()会出错或结果不符合预期。解决方案拿到数据第一件事检查df.index和df[date]列的类型确保核心时间列被正确转换为datetime类型并设为索引。# 正确操作 df[date] pd.to_datetime(df[date]) # 转换类型 df.set_index(date, inplaceTrue) # 设为索引 print(df.index.dtype) # 应该显示 datetime64[ns]6.2 坑二未来信息泄露Look-ahead Bias这是时间序列建模中最致命、也最隐蔽的错误。绝对不能用未来的数据来预测过去。在上面的评估代码中我们刻意将数据按时间顺序划分用前面的数据训练用后面的数据测试。如果你错误地随机划分了数据或者在使用rolling计算特征时没有小心地滞后处理就会导致模型“偷看”到未来的答案从而得到虚假的高精度。解决方案始终牢记“时间箭头”。任何为某一天生成的特征或标签只能使用这一天及之前的信息。在构建特征时使用.shift(n)来获取过去第n天的值。在划分训练/测试集时永远按时间顺序切分。6.3 坑三忽略数据的平稳性我们的简单模型假设趋势是线性的。但真实世界的趋势可能是非线性的先快后慢或者存在突变点如突然的疫情政策。直接使用全局平均差分作为趋势在长期预测中会累积误差。解决方案进阶对于更复杂的趋势可以尝试差分法对原始序列进行一阶或二阶差分使其变得平稳再用模型去拟合差分后的序列。分段拟合识别可能的趋势变化点在不同时间段使用不同的趋势项。使用更高级的模型如Holt-Winters三次指数平滑模型它可以同时估计水平、趋势和季节性成分。这是时间序列预测中一个非常经典且强大的基线模型比我们手搓的“趋势季节性”更稳健。6.4 坑四评估指标选择不当我们使用了RMSE和MAPE。但MAPE在真实值很小甚至为0时会趋向于无穷大失去意义。比如某天营业额为0歇业你的预测是10元MAPE就会爆炸。解决方案了解不同指标的优缺点根据业务场景选择。如果数据没有零值或接近零的值MAPE很直观。如果存在零值可以考虑使用对称平均绝对百分比误差sMAPE或平均绝对误差MAE。如果想更强调大误差的代价使用RMSE。在比赛中务必看清楚题目要求的评估指标是什么并以此为准。7. 从Day2出发你的后续学习路径建议完成这个微型项目你已经掌握了数学建模最核心的“套路”从问题到数学表述从数据到模型从评估到优化。接下来你可以沿着以下几个方向深化模型层面深入学习经典的时间序列模型如指数平滑ETS和ARIMA。这两个模型是数学建模竞赛中处理时间序列问题的“常客”。statsmodels库提供了完美的实现。尝试用它们重新解决餐厅预测问题对比效果。工具层面熟练掌握pandas的数据处理分组、聚合、透视表、matplotlib和seaborn的可视化绘制更专业的统计图形、scikit-learn中的回归模型如果问题涉及多变量预测。流程层面尝试一个更完整的项目。例如Kaggle上的“商店销售额预测”竞赛数据集它包含了多个商店、商品类别和促销信息是一个经典的多变量时序预测问题。完整地走一遍数据清洗、特征工程、多个模型尝试、集成、提交结果。思维层面开始阅读往年优秀的数学建模竞赛论文如国赛、美赛。不要只看他们的模型多复杂重点看他们如何分析问题、如何简化假设、如何将实际问题转化为数学语言、如何解释模型结果。这是比编程和算法更重要的能力。第二天的价值不在于你学会了多高深的算法而在于你亲手完成了一个从问题到解决方案的最小闭环。这个闭环会给你带来实实在在的成就感并成为你学习路上最重要的路标。记住这个感觉保持动手的习惯明天我们就可以开始聊点更具体的比如当数据不止“时间”这一列时我们该怎么办那就是另一个广阔天地了。
返回列表