☰
时间序列预测实战:气象预报毕业设计从数据到展示全链路
2026/10/1 5:39:29 网站建设 项目流程

简介:基于Python与机器学习方法构建的毕业设计项目,内容为气象预报及动态展示系统,面向计算机、数据科学相关专业学生,尤其适合需要完成气象类课题或想实践机器学习完整流程的开发者。资源共94个文件、2.1MB,以42个JavaScript脚本、14个Python源码、14个pyc编译文件为主,辅以3个CSV气象数据集、1个pkl模型和1个pb模型,此外还有HTML/CSS界面文件、XML配置及README文档,目录组织清晰,便于按模块查阅。目前已有104人学习下载。包内代码覆盖从气象数据爬取、数据清洗、特征处理、模型训练、结果预测到前端展示的完整流程,包含可直接加载的模型文件、数据操作类与Web服务接口,配合模板页面可动态呈现气象信息;从数据采集到界面展示各环节均有对应模块支撑。读者可依托该项目理解时间序列预测算法(如回归、随机森林等)的实际应用,掌握Flask/Django等框架的集成方式,并在此骨架上扩展功能,完成自己的毕业设计或课程设计。

1. 气象预报毕业设计不是玄学:一套从数据到动态展示的完整链路

"气象预报"四个字听起来像要上超算,但用 Python 和机器学习方法做毕业设计,真实目标不是跟中央气象台比精度,而是拿真实气象观测数据训练一个能用的短时预报模型,再用可视化把预报结果动态展示出来。这个系统的项目价值在于完整闭环:数据清洗、特征工程、时间序列验证、模型训练、接口设计、前端展示,每一环都能写成答辩时的技术亮点。适合两类人:想证明自己具备数据工程全流程能力的同学,以及会调 sklearn 但不知道气象数据怎么入手的同学。先说结论:温度短期预报能做到 2 度以内 MAE 就算合格,别追求一步登天。

2. 先搞定数据,再谈模型:气象数据获取与预处理的落地做法

气象预报系统里最容易被低估的是数据环节。早期我把报表里几十万行数据直接丢给 sklearn,结果模型分数奇高,后来发现是按时间排序后发生了数据泄漏。所以这一章先解决一件事:什么样的数据能喂给机器学习模型,以及如何处理才能让后面的训练和展示不翻车。

2.1 数据源选型:地面站观测、再分析资料还是自建采集

气象数据源不是越高级越好,而是越匹配你的开发周期越好。常见做法是用地面气象观测站的历史数据做训练集。这类数据字段规整,通常包含温度、湿度、气压、风速、风向、降水和站点的观测时间,完全覆盖回归模型所需的特征空间。中国气象数据网和 NOAA 的 GHCN 数据集都可以作为来源,下载后是标准 CSV 或文本格式,处理成本低,用 pandas 读进来就能开始干活。

再分析资料比如 ERA5 是时空连续的格点数据,物理过程完整,但单个文件体量很大,处理时间成本和磁盘开销都不小,而且与地面站数据对齐到站点坐标需要额外插值。对毕业设计的 8 到 12 周周期来说,除非题目明确要求网格预报,否则我一般建议优先用地面站数据。再分析资料更适合做研究型课题的对照实验,不适合作为主数据源。

自建采集适合做验证集而不是训练集。用开发板挂温湿度传感器,在室外收集两周左右的数据,用来验证模型在你所在地点的真实表现。原因是地面站数据覆盖的是站点周围区域,而你的传感器在微尺度环境里读数会和站点有偏差,这个偏差本身就是很好的讨论素材。训练集尽量保持单一来源,混用不同口径的数据会让模型学到源差异而不是气象规律。

2.2 用 pandas 把散乱观测数据清洗成模型能吃的 DataFrame

拿到原始 CSV 之后,第一件事不是调模型,而是把日期列解析成时间索引、按时间排序、处理重复和缺失。气象站数据常见的三种脏情况:同一日期被重复入库、关键要素在降水日前后缺测、时间列是字符串格式不能直接运算。运行环境上只需要 pandas、numpy、scikit-learn 和 flask 这几个库,pip 安装 scikit-learn 时会自动带上 numpy,不用单独处理依赖。开发环境我习惯用 VSCode 配一个虚拟环境,新手照这个组合来,不会在环境上花太多时间。

import pandas as pd import numpy as np raw = pd.read_csv('station_54511_daily.csv', parse_dates=['date']) raw = raw.sort_values('date').reset_index(drop=True) # 同一观测时刻只保留最后一条,避免重复入库污染时序 raw = raw.drop_duplicates(subset=['date'], keep='last') # 关键列缺失统计,先看清数据缺在哪 print(raw[['temp_avg', 'pressure', 'humidity', 'wind_speed']].isna().sum()) # 对连续 3 条以内的缺口做线性插值,超过 3 条的样本整行丢弃 for col in ['temp_avg', 'pressure', 'humidity', 'wind_speed']: raw[col] = raw[col].interpolate(method='linear', limit=3) raw = raw.dropna().reset_index(drop=True) # 构造模型能直接使用的时间特征 raw['doy'] = raw['date'].dt.dayofyear raw['hour'] = raw['date'].dt.hour raw['month'] = raw['date'].dt.month raw.to_csv('weather_clean.csv', index=False)

代码里的关键是interpolate的limit参数和dropna的顺序。interpolate只补连续三个以内的缺口,因为气象序列里长缺口通常对应设备故障,插值补出来的值没有物理意义,会让模型在故障时段学出虚假模式。drop_duplicates用keep='last'保留最后一次入库记录,实际观测系统里重复写入的往往不是同一条数据,而是有细微差别的补传记录,保留最后一条比较合理。doy和hour是后续特征工程的基础,如果不能从日期列拆出这两个字段,模型很难感知季节和昼夜周期。

清洗结束的标准是先用head和describe各看一遍:时间单调递增、没有空值、数值范围符合常识。比如温度不应该有 60 度的记录,气压通常在一千百帕上下波动,如果发现量级不对,先回到原始数据核对单位,而不是让模型去消化异常值。

提示:清洗后如果有效样本不足 5000 条,优先去扩展历史数据的时间跨度,不要急着调模型参数。时间跨度带来的信息量远大于同一时段内的样本密度。

2.3 特征工程:时间特征与滞后特征怎么加才不泄漏

气象预报特征工程的核心是构造时间上下文。只用当前时刻的气压、湿度去预测未来温度,模型会退化成只学历史均值的平庸模型。常见做法有三类:时间周期特征、滞后特征和滚动窗口统计量。

时间周期特征直接用doy和hour送入模型,但要注意周期边界。doy=1和doy=365代表邻近的日期,hour=23和hour=0也是邻近时段,直接输入数值会让模型认为差距巨大。我一般对这两个字段做正弦或余弦变换,同时保留原始值,让树模型有机会自己选择边界切分。

滞后特征是预报问题里最重要的一组特征。预测 t+1 时刻的温度,可以用 t 时刻温度、t-1 时刻温度、以及过去 7 天的滚动均值来刻画温度惯性。这里有一个很容易踩的坑:构造滚动窗口特征时,必须对结果再 shift 一次,否则rolling窗口会包含当前时刻自己,模型在训练时看到了未来的信息。

# 在清洗后的 DataFrame 上构造特征 feature_df = raw.copy() feature_df['temp_lag1'] = feature_df['temp_avg'].shift(1) feature_df['temp_lag24'] = feature_df['temp_avg'].shift(24) # 小时级数据:昨天同一时刻 feature_df['temp_roll7'] = feature_df['temp_avg'].rolling(window=7).mean().shift(1) feature_df['doy_sin'] = np.sin(2 * np.pi * feature_df['doy'] / 365) feature_df['doy_cos'] = np.cos(2 * np.pi * feature_df['doy'] / 365) # 滞后构造会引入新的空值,统一丢弃头部样本 feature_df = feature_df.dropna().reset_index(drop=True) print(feature_df[['temp_lag1', 'temp_lag24', 'temp_roll7']].describe())

lag1捕捉的是温度惯性,lag24捕捉的是日循环规律,roll7捕捉的是天气过程的中期趋势,三组变量各有明确的物理含义。如果数据集是小时级,shift(24)正好对齐昨天同一时刻;如果数据集是日级,shift(24)就超过了一个月,没有物理意义,这点一定要根据采样频率调整。特征构造完成后,用dropna丢弃头部因为滞后而缺失的样本,这部分样本数量很小,不用可惜。

如果数据是小时级,我还会补一个temp_roll24,也就是过去 24 小时的滑动平均,它比 7 日平均更贴近短临预报的需求。特征数量控制在 10 个左右就够,不要把几十个气象要素全塞进去,树模型在冗余特征多的时候会降低对重要特征的敏感度。

3. 从基线到机器学习:温度预报模型的选型与训练细节

模型环节真正要解决的问题不是"用什么算法最好看",而是"怎么证明机器学习方法比朴素方案更有效"。气象预报里最朴素也最稳的方案是持续性预报,它是检验一切模型的地板。这一步不做扎实,后面的动态展示再漂亮,答辩时也会被一句"你凭什么说模型有用"问住。

3.1 先立基线:持续性预报的及格线

持续性预报的思路很简单:用当前观测值直接作为下一时刻的预报值,即把 t+1 时刻的预测直接设为 t 时刻的温度。听起来不聪明,但在短期预报里它强得可怕,因为温度本身是连续物理量,短时间内惯性很大。

我在项目里的做法是这样:把数据集按时间切出最后 30 天作为独立测试窗口,先算一遍持续性预报的 MAE,把这个值记为baseline_mae。然后训练机器学习模型,只有模型在测试窗口上的 MAE 明显低于baseline_mae,这个模型才算有存在意义。气象数据集的时序依赖很强,如果机器学习模型的预测效果连持续性预报都不如,绝大多数情况下不是算法问题,而是上面的特征构造漏了关键信息,比如没有滞后特征、时间特征编码错误、或者数据在训练前被随机打乱了。

这个基线还有一个附加作用:它能帮你判断数据量是否足够。如果训练集只有几百条样本,随机森林的 MAE 大概率贴着baseline_mae波动,这时不要急着调参,先去扩大数据的时间跨度,而不是加密采样。一天 24 个小时的数据并不能创造更多信息,跨年观测才能让模型学会季节变化。

3.2 模型选型与参数:为什么毕业设计主推随机森林

气象短时预报的常用模型路线有三条:树模型、线性模型和神经网络。线性模型在特征与目标呈近似线性关系时表现稳定,但气象要素之间的交互作用很复杂,比如湿度对温度的影响依赖气压和云量,线性模型难以刻画。LSTM 这类深度模型在长序列上理论效果更好,但训练不稳定、调参周期长,对一个需要同时完成数据、建模、展示三个模块的毕业设计来说,性价比不高。

随机森林是中间地带。它对特征量纲不敏感,不需要归一化,能直接输出特征重要性,对异常值有天然的容错能力,而且训练速度快。如果后续想提升精度,可以换成 LightGBM,但 LightGBM 的调参空间更大,树叶数、学习率、正则项都要试,时间成本不低。我的习惯是先用随机森林把数据链路和评估框架跑通,再在论文里补一组 LightGBM 对比实验,而不是上来就追逐最佳效果。

随机森林的参数里最值得动的是n_estimators、max_depth和min_samples_leaf。n_estimators设到 200 到 300 就够,再增大收益递减;max_depth限制在 10 到 15 之间,防止树对历史样本死记硬背;min_samples_leaf设到 4 到 8,强制每个叶节点覆盖一定量的样本,减少局部过拟合。n_jobs=-1让训练用满全部核心,能省不少时间。

3.3 TimeSeriesSplit:时间序列交叉验证的正确姿势

气象数据不能像普通表格数据那样随机打乱后切分训练集和验证集。随机切分会让训练集里混入验证集时间之后的样本,模型相当于提前看到了未来走势,验证分数虚高。时间序列交叉验证要用TimeSeriesSplit,它按时间顺序切分,训练集永远在验证集之前。

from sklearn.ensemble import RandomForestRegressor from sklearn.model_selection import TimeSeriesSplit from sklearn.metrics import mean_absolute_error features = ['doy_sin', 'doy_cos', 'hour', 'pressure', 'humidity', 'wind_speed', 'temp_lag1', 'temp_lag24', 'temp_roll7'] X = feature_df[features] y = feature_df['temp_avg'] tscv = TimeSeriesSplit(n_splits=5, gap=24) rf = RandomForestRegressor(n_estimators=200, max_depth=10, min_samples_leaf=4, n_jobs=-1, random_state=42) mae_scores = [] for train_idx, val_idx in tscv.split(X): rf.fit(X.iloc[train_idx], y.iloc[train_idx]) pred = rf.predict(X.iloc[val_idx]) mae_scores.append(mean_absolute_error(y.iloc[val_idx], pred)) print('TimeSeriesSplit MAE:', mae_scores) print('平均 MAE:', sum(mae_scores) / len(mae_scores))

TimeSeriesSplit的gap参数很多人会忽略。gap=24表示训练集结束的 24 条样本不参与任何一折的训练和验证,专门用来隔开训练与验证在时间上的粘连。因为预测未来 1 小时时,验证集第一个样本的特征里包含了训练集末端 1 小时前的观测值,这本身没问题,但如果不留gap,某些滚动特征会跨越切分边界,把训练集末端的统计量带到验证集里。gap的具体数值建议与预测时效一致,比如预测未来 24 小时就把gap设为 24。

注意:gap的数值要和预测时效一致。预测未来 24 小时,gap就设为 24,不是越大越好。

这段代码跑完看两个指标:mae_scores列表里各折的稳定性,以及平均 MAE 是否低于持续性预报的baseline_mae。各折分数波动过大,说明模型在某些季节时段失效,需要按季度拆分看误差分布;平均分低于基线,说明特征和模型是有效的。到这里机器学习预报模型的闭环就通了,接下来把模型输出变成展示系统能消费的数据。

4. 动态展示不是堆图表:气象展示系统的前后端设计

气象动态展示是整个系统的门面,但很多人的实现方式是把历史曲线和预测曲线叠在一张静态图上,这只能叫图表,不叫动态展示。动态的价值在于时间轴可以缩放、预报结果可以切换、夜昼时段能被识别出来。这一章我用 Flask 加 ECharts 讲一个最省力又能出效果的实现路径。

还有一个容易被忽略的点:动态展示不是纯前端的事,它反过来约束后端的数据结构。比如要给夜间时段画标记区域,后端就得返回能区分白昼和夜晚的时间字段;要做多站点切换,接口必须支持城市参数。所以我会先定数据结构,再写前端,顺序不要反。

4.1 Flask 后端接口:把预报结果变成 API

模型训练完成后,要为前端准备一个干净的 JSON 接口,而不是让前端直接读训练用的 DataFrame。Flask 是这里最合适的选择,它轻量、单文件能跑,和 pandas、joblib 的衔接都很顺手。接口的设计原则是职责单一:一个接口返回站点列表,另一个接口返回指定站点的预报序列。

from flask import Flask, jsonify, render_template import json app = Flask(__name__) @app.route('/api/forecast/<city>') def api_forecast(city): # 实际项目中在这里加载训练好的模型与最近观测值 # model = joblib.load('models/rf_forecast.joblib') # 然后调用预测函数,生成未来 72 小时序列 with open(f'outputs/forecast_{city}.json', encoding='utf-8') as f: data = json.load(f) return jsonify(data) @app.route('/') def index(): return render_template('index.html') if __name__ == '__main__': app.run(host='0.0.0.0', port=8080, debug=False)

接口返回的 JSON 里至少要包含站点名、预报生成时间、预报序列,以及每个时间点对应的温度预测值。把模型加载放在请求外面而不是请求内部,这是很重要的性能细节。如果每次请求都重新加载模型文件,并发访问时接口延迟会成倍上升,放在模块外层只需加载一次。

4.2 ECharts 动态时间轴:三个交互设计

前端用 ECharts 是因为它对时间轴和图表的支持最省心,而且和 Flask 的jsonify输出天然配合。动态展示需要实现的三个交互是时间轴缩放、夜间时段标记和多站点序列切换。

时间轴缩放用dataZoom组件,一个 slider 一个 inside,用户可以用鼠标滚轮缩放,也可以拖拽底部滑块浏览特定时段。夜间时段标记用markArea,把 18 点到次日 6 点的时间区域填充成灰色,观察者一眼就能看出昼夜边界,也能直观检查模型在夜间到清晨转换段的误差。多站点序列切换则通过下拉框切换接口参数,前端重新请求/api/forecast/城市名即可。

fetch('/api/forecast/北京') .then(res => res.json()) .then(json => { const chart = echarts.init(document.getElementById('main')); chart.setOption({ title: { text: `${json.city} 未来72小时温度预报`, left: 'center' }, tooltip: { trigger: 'axis' }, legend: { data: ['预报温度', '历史观测'], top: 30 }, xAxis: { type: 'time', name: '时间' }, yAxis: { type: 'value', name: '温度(°C)' }, series: [ { name: '预报温度', type: 'line', data: json.series.map(item => [item.fc_time, item.temp]), smooth: true }, { name: '历史观测', type: 'line', data: json.obs.map(item => [item.time, item.temp]), lineStyle: { type: 'dashed' } } ], dataZoom: [ { type: 'slider', bottom: 10 }, { type: 'inside' } ] }); });

这段代码里smooth设置为true会让曲线更平滑,但只在展示端生效,不会改变真实预测值。预报和历史观测用实线与虚线区分,避免两条线交织时难以辨认。如果数据集支持气温预报上下限,还可以在预报线周围加一个 band,用 stack 或自定义 series 实现,展示效果会比单线更有层次。

dataZoom的start和end参数也值得调。预报 72 小时的数据如果默认显示全部,细小的误差波动根本看不出来,我一般把start设为 0、end设为 25,让页面加载后只显示最近 18 小时的曲线,用户再通过滑块看全局。这个初始窗口看起来是小细节,但直接影响答辩演示时第一眼的观感。

4.3 把预报结果落盘:模型输出的数据结构设计

展示系统跑起来之后,一个容易被忽略的问题是模型输出文件的结构。建议固定成一份统一的 JSON,字段名保持稳定,这样前后端解耦,后续换模型也不会牵动前端。

{ "city": "北京", "model": "random_forest_v1", "created_at": "2025-01-01T00:00:00", "series": [ {"fc_time": "2025-01-01T00:00:00", "temp": 5.2, "temp_min": 3.1, "temp_max": 7.4}, {"fc_time": "2025-01-01T06:00:00", "temp": 6.8, "temp_min": 5.0, "temp_max": 8.6} ], "obs": [ {"time": "2024-12-31T18:00:00", "temp": 4.5} ] }

落盘文件里带model和created_at字段是很有必要的。答辩时别人看到这份 JSON 就能知道你用的模型版本和预报生成时间,这比口头解释更有说服力。temp_min和temp_max可以用训练过程中模型的置信区间近似,随机森林可以收集每棵树的预测结果取分位数,不需要额外训练模型。

还有一个常踩的坑是文件名里的编码。Windows 环境下json.dump默认用本地编码写文件,中文城市名容易乱码,打开文件时统一用encoding='utf-8'读写,否则前端展示时城市名变成乱码,会被误认为是接口 bug。

5. 避坑手册:气象预报系统最常见的 5 个翻车现场

前面几章把正向流程走通了,但真实开发里花时间的往往不是流程,而是调试那些看起来莫名其妙的错误。这一章总结我踩过的 5 个具体坑,每条都说清楚现象、原因和解决路径。这 5 个问题不是偶发的小毛病,它们在时间序列预测类项目里出现频率极高,而且每个都直接影响答辩时模型可信度,值得单独排查。

5.1 现象:交叉验证分数很高,一画图预测曲线全部滞后

这是时间序列预测最容易踩的坑:MAE 在验证集上很低,甚至比基线还低,但把预测曲线和真实曲线画在一起,发现预测值整体向右平移了一段时间,形状完全一样。业内管这个叫滞后预测,验证集上的低误差是假象,模型根本没有学会预测,只是学会把上一时刻的值抄了一遍。

原因基本可以锁定在特征构造上。如果把 t 时刻的观测值直接作为特征来预测 t 时刻或 t+1 时刻,模型会学到"答案就在特征里"的捷径。具体来说,temp_lag1在构造时如果没有对训练标签保持一致的时间偏移,或者rolling窗口包含了当前样本本身,模型就提前看到了目标。

解决方案是检查所有滞后特征是否都做了shift。temp_lag1表示的是上一时刻的观测,必须对目标序列shift(1);rolling窗口必须再shift(1)。此外,在训练集和验证集切分处保留TimeSeriesSplit的gap,避免切分边界上的特征跨越。修复后重新跑交叉验证,MAE 通常会小幅上升,但预测曲线不再滞后,这个上升是真实信息量的代价,接受它。

5.2 现象:预测值超出历史极值,温度报了 45 度

某个站点夏季温度预测突然冲到 45 度,但历史最高温只有 38 度,数据字典里也看不出异常。这属于模型输出了物理上不可能的数值,答辩时被看到会非常尴尬。

原因通常是特征或目标混入了异常观测值。气象站数据在强降水、传感器故障时会出现物理上不可能的读数,比如相对湿度超过 100、温度突变成负数。如果清洗阶段只做了缺失值插补,没有做极值过滤,模型就会把这些离群点当真实规律。随机森林对训练数据里的极值记忆很牢,一旦某个异常值被用于切分,它会影响一片区域。

解决路径是在清洗代码里加一步物理约束过滤。温度在 -50 到 50 度之外、湿度在 0 到 100 之外的数据直接标记为缺失再插补,或者直接删除。这一步放在缺失值插补之前,否则插补会把这些异常值传播到邻近样本。如果只是个别离群点,也可以用clip函数做截断而不是直接删除,保留数据量。

5.3 现象:ECharts 时间轴错位,预报曲线整体偏移几小时

前端展示时,历史观测曲线和预报曲线在相同时间点对不上,整体偏移了数小时。你肉眼看着曲线形状是对的,但横轴上的标签全部错位。

原因是时间字符串的时区处理不一致。后端jsonify返回的 ISO 格式时间带时区后缀,ECharts 会按 UTC 解析,而历史观测数据用的可能是本地时间字符串,两者混在一起,时间轴就差了 8 个小时。这个坑在冬天还好发现,一旦进入夏令时地区,偏移量还会随时间变动,更难排查。

解决路径是统一时间格式。最简单的方式是后端统一输出YYYY-MM-DD HH:mm:ss的本地时间字符串,不携带时区后缀;或者全部输出 ISO 格式并在前端用 dayjs 转换。关键是全链路只使用一种约定,前后端代码里都写清楚这个约定,避免各改各的。排查时先用 curl 请求接口,直接看 JSON 里同一时刻的历史和预报时间字符串是否一致。

5.4 现象:夜间低温预报恒高,模型不会降温

模型在白天表现尚可,但夜间到凌晨的温度预报明显偏高,最低温时刻总是被抹平。凌晨 5 点附近的 MAE 显著高于下午时段,这说明模型在昼夜转换段失效。

原因是特征里缺少辐射冷却相关信息。夜间温度下降主要由云量、湿度和地表辐射决定,如果数据集只有温度、湿度、气压、风速,模型只能靠日期做粗浅推断,无法学会晴空夜里的强辐射降温过程。尤其在秋冬晴夜,地表辐射冷却强烈,温度可以一夜降 10 度以上,只靠滞后特征是学不出这个过程的。

解决路径是增加特征:日较差、天空云量(如果有)、或计算理论日照时长。没有云量数据时,可以用前一天的日较差做近似替代,因为晴空日的昼夜温差显著大于阴天。添加温差特征后,把残差按小时分组,重点看凌晨 5 到 7 点的 MAE 是否下降,这个验证方式比整体 MAE 更敏锐。

5.5 现象:项目后期模型越跑越慢,一次预测要好几分钟

到后期,历史数据累积到几十万条,模型单次预测从秒级变成分钟级,展示系统接口直接超时。你以为是模型太大,其实是预测脚本的写法有问题。

原因不是模型变大,而是特征构造和预测分离得不彻底。有人在每次预测时重新读取全量历史数据、重新构造全部滞后特征,再调用模型预测单个时间点;当历史数据累积到几十万条,rolling计算就成了性能瓶颈。这个问题用python -m cProfile跑一次预测脚本就能定位,耗时最长的往往不是rf.predict,而是前面的rolling和interpolate。

解决路径是建立增量预测的脚本结构。每次预测只读取最近 72 小时的数据窗口,在窗口内构造所需特征,再用 joblib 加载训练好的模型做推理,最后把结果写入 JSON。模型训练和预测分开,预测只做预热和推理,延迟会从分钟级降到秒级。另外,把随机森林换成 LightGBM,推理速度还能再上一个台阶。

6. 进阶:给预报系统加一个残差分析,定位模型的短板时段

模型跑通之后,不要急着把图贴进论文。先用残差分析把模型的短板时间带找出来,这一步能让你的论文讨论部分写出真实内容,也能让你在答辩时回答"模型哪里不好、为什么"而不是笼统地说"效果还可以"。

残差分析的做法是把验证集上的预测残差按小时、按月份分组统计。按小时分组能看出昼夜转换段是否有系统性偏差,按月份分组能看出季节交替时模型是否反应迟钝。下面这段代码可以直接接在前面TimeSeriesSplit的验证集后面使用。

# 假设 val_idx 是最后一次交叉验证的验证集索引 residual = y.iloc[val_idx] - pred res_df = pd.DataFrame({ 'datetime': feature_df['date'].iloc[val_idx], 'residual': residual }) # 按小时统计绝对误差,定位昼夜短板 hourly_mae = res_df.groupby(res_df['datetime'].dt.hour)['residual'].apply(lambda s: np.abs(s).mean()) print(hourly_mae) # 按月份统计,看季节切换是否失效 monthly_mae = res_df.groupby(res_df['datetime'].dt.month)['residual'].apply(lambda s: np.abs(s).mean()) print(monthly_mae)

groupby内的dt.hour是从datetime列提取的小时数,lambda里先取绝对值再求均值,得到的是该小时所有验证样本的 MAE。如果清晨 5 到 7 点的 MAE 明显高于下午时段,说明模型在日出前后的温度回升过程模拟得不好,可以在特征里加时间到日出距离的特征,或者单独为夜间时段训练一个子模型。月份分组同理,如果春秋换季时段误差飙升,说明数据里这些时段的样本量不足,需要补充对应月份的历史数据。

我自己的习惯是每次实验都先生成一张残差热力图,横轴是小时,纵轴是月份,颜色深浅表示绝对误差大小。这张图放在论文里能直接支撑"模型在何种天气过程下失效"的结论,比一张漂亮的预测曲线更能体现工程深度。最后再检查一次验证集是否严格按时间切分、滞后特征有没有泄漏,确认这两点都没问题,整个系统的可信度就立住了。希望这个方案能帮你在气象预报毕业设计这条路上少走几个弯路。

本文还有配套的精品资源,点击获取

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

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

立即咨询