ARTICLE · INTELLIGENCE

战地情报 · 详情页

来自尧图项目组的一线实战观察与深度解析

机器学习大作业天气预测:从数据清洗到随机森林的完整路线

机器学习大作业天气预测:从数据清洗到随机森林的完整路线 简介机器学习大作业-预测天气.zip是一份面向机器学习课程作业或算法实践的资源以天气预测为任务展示从数据准备到模型部署的完整流程。压缩包约603KB按项目常规结构通常包含数据集、Python代码与实验报告文件内容涵盖缺失值处理、异常值检测、归一化等预处理滑动平均值、滞后特征、气象指数等特征工程线性回归、决策树、随机森林、支持向量机、LSTM等模型的选择与训练验证以及网格搜索/随机搜索超参数调优、MSE/RMSE/MAE指标评估和预测部署与持续监控环节。资源已有7276人学习下载适合作为课程设计参考或个人练手项目。通过学习其中的实现思路与代码组织方式读者可快速搭建天气预测原型掌握时间序列建模的关键技巧并借鉴其报告结构与结论分析方法完成同类大作业有效提升数据处理、模型调优和项目实战能力。1. 机器学习大作业“预测天气”一份能直接照做的交付方案第一次打开“机器学习大作业-预测天气.zip”的人多半是被两个问题卡住的数据怎么处理才算干净模型怎么选才不算“玄学”。天气预报听起来是深度学习的主场但课程大作业里真正稳定拿到分数的是经典表格路线——用历史观测数据做特征工程再用 scikit-learn 里的回归或分类模型做预测。这篇文按“数据清洗→特征构造→模型训练→避坑→打包”的顺序给你一条能直接照做的路线新手能一步步跑通熟手能直接抄参数和检查清单。适合正要交机器学习课程作业、或想用一套完整流程验证天气预测想法的人。2. 天气预测的数据从哪来先解决“有没有”再谈“准不准”数据质量决定了天气预测作业的上限。很多同学拿到 zip 包之后第一步就打开模型库调参结果发现无论怎么调MAE 都降不下去——问题经常不是模型而是原始数据里日期格式混乱、记录乱序、缺失值一大片。先花半小时把数据管好比多调一百个参数都值。2.1 数据来源与字段选择预测天气先得有历史课程大作业的数据来源常见做法是两种从公开气象数据平台下载历史观测记录或者自己写脚本抓取。对大作业来说我更推荐前者理由有三个数据量大、字段完整、能在报告里写清楚数据出处。注意下载时选“逐小时”或“逐日”的观测数据不要选预报数据否则相当于拿着答案做题。我一般会选下面几列作为原始特征字段含义类型date观测时间时间类型temperature气温数值humidity相对湿度数值pressure海平面气压数值wind_speed风速数值weather天气现象晴/雨/阴类别weather 字段用于分类任务temperature 字段用于回归任务。如果你的 zip 包里没有现成的 CSV也可以从公开数据里导出相同结构。拿到数据后第一步是加载和排序。气象记录必须按时间升序排列乱序会导致后面所有时间窗口特征全部算错。import pandas as pd df pd.read_csv(weather_data.csv, parse_dates[date]) df df.sort_values(date).reset_index(dropTrue) df[date] pd.to_datetime(df[date]) # 检查时间间隔分布正常数据大部分间隔应为1小时或1天 print(df[date].diff().value_counts().head())这段代码的逻辑很容易理解parse_dates 直接把 date 列解析成 datetime 类型sort_values 按时间升序排列后重建索引。最后的 diff().value_counts().head() 是查看时间间隔分布正常气象数据大部分间隔应该是 1 小时或 1 天如果出现大量随机间隔说明原始文件可能混入了多站点数据需要先按站点拆分。参数说明parse_dates 可以传列名也可以传列名列表比如 parse_dates[date, record_time]如果不写后续所有时间计算都会变成字符串操作效率低且容易出错。2.2 数据清洗与缺失值处理三个必写步骤气象数据里最常见的脏数据有三类缺失、重复、异常。缺失值我会优先用前向填充而不是直接删除行。原因很简单气象观测是连续过程短时间缺失时温度、湿度不会突变用前一个时刻的值填充比用全局均值填充更符合物理规律。# 1. 删除完全重复的记录 df df.drop_duplicates(subset[date]) # 2. 缺失值前向填充连续缺失超过3小时则删除 df[temperature] df[temperature].ffill() df[humidity] df[humidity].ffill() df[pressure] df[pressure].ffill() # 3. 异常值用IQR规则标记并裁剪 for col in [temperature, humidity, pressure, wind_speed]: q1 df[col].quantile(0.25) q3 df[col].quantile(0.75) iqr q3 - q1 low q1 - 3 * iqr high q3 3 * iqr df[col] df[col].clip(low, high)三个步骤各承担一个任务。drop_duplicates 按 date 去重保证同一时刻只有一条记录。ffill() 是前向填充对连续观测数据非常友好这里要注意如果一段数据连续缺失很长比如一天连着缺失再 ffill 就会把一天前的温度“搬”过来所以我一般会设置一个窗口限制缺失超过 3 个时间步直接删除避免填充出假数据。clip 用 IQR 的 3 倍上下界把极端值拉回边界而不是直接删除这样能保留数据量也避免极端风速干扰后续归一化。这里的参数不是固定值3 倍 IQR 比较宽松适合气温这类分布稳定的变量风速这类偏态分布我一般会用 2 倍或者单独看 95 分位。交作业时把这组参数写进报告能体现你不是在套模板。2.3 切分数据的正确顺序先切分再谈其他这是机器学习应用流程里最容易被忽略的一步。天气数据是时间序列不能用 train_test_split 随机切分。随机切分会让训练集里混入“未来”的数据模型等于开了天眼验证分数好看一到预测真实未来就翻车。正确做法是严格按时间点切分。from sklearn.model_selection import train_test_split # 用时间点切分而不是随机切分 split_date df[date].quantile(0.8) train df[df[date] split_date].copy() test df[df[date] split_date].copy() print(f训练数据{train[date].min()} ~ {train[date].max()}) print(f测试数据{test[date].min()} ~ {test[date].max()})这里用 quantile(0.8) 求出 80% 分位时间点而不是直接取第 80% 行是因为原始数据可能存在少量缺失按行切分会把时间点切歪。test 是未来一段时间的样本模型训练时完全看不到评估结果才有说服力。后续的特征工程、归一化都必须在 train 上 fit、再 transform 到 test这个顺序会在第 3 章和第 5 章反复出现属于天气预测作业里最重要的几个“坑”之一。3. 把天气变成特征编码、滞后窗口与归一化原始数据不能直接喂给 scikit-learn。天气现象是字符串模型不认识温度数值和气压数值量纲差距又很大直接丢进随机森林虽然能跑但遇到线性模型就完全没法用。特征工程质量决定了整个学习曲线的上限这一章把三类核心特征说清楚。3.1 类别特征编码天气现象不能直接当数字天气现象字段有“晴”“雨”“阴”等取值。很多新手直接 map 成 0、1、2看起来没问题实际上给模型引入了不存在的顺序关系——可能让模型以为“雨(2)阴(1)晴(0)”但晴和雨之间没有大小之分。对于树模型LabelEncoder 还能勉强用但如果你后面想对比逻辑回归就必须用独热编码。from sklearn.preprocessing import OneHotEncoder, LabelEncoder # 方法A独热编码适合线性模型 ohe OneHotEncoder(handle_unknownignore) weather_ohe ohe.fit_transform(df[[weather]]) # 方法B标签编码适合树模型 le LabelEncoder() df[weather_code] le.fit_transform(df[weather]) # 注意fit只能用训练数据 ohe.fit(train[[weather]]) train_weather ohe.transform(train[[weather]]) test_weather ohe.transform(test[[weather]])这段代码的关键在最后三行先 fit 再 transform。OneHotEncoder 在训练集上 fit记住训练集里出现过的天气类别然后分别 transform 训练集和测试集。测试集如果冒出训练集里没见过的类别handle_unknownignore 会让它全部编码成 0避免报错。handle_unknown 这个参数在真实天气数据里很常用因为测试时段可能出现训练时段没记录的天气现象。对于随机森林这类树模型我会优先用 LabelEncoder 生成的 weather_code 列因为树模型做的是条件切分独热编码会让树变得又深又碎。这一点在机器学习实战类教程里经常被一句话带过实际对比下来差异很明显。3.2 数值特征归一化先切分再 fit顺序不能反温度、气压、风速的量纲完全不同。随机森林和树模型对量纲不敏感但线性回归、SVM、KNN 都对量纲敏感。KNN 算距离时如果气压范围是 1000 而温度范围是 30气压会主导距离模型实际上只看了气压。归一化用 StandardScaler 最省事让每个特征变成均值 0、方差 1。from sklearn.preprocessing import StandardScaler # 先切分再对训练集fit scaler StandardScaler() feature_cols [temperature, humidity, pressure, wind_speed] train_features scaler.fit_transform(train[feature_cols]) test_features scaler.transform(test[feature_cols])注意 scaler.transform(test) 不能写成 scaler.fit_transform(test)。fit_transform 在测试集上会重新计算均值和方差把测试集自身的分布也归一化了这是一种数据泄漏——等于测试集的信息提前参与了数据处理。我在检查作业时经常看到这种写法验证分数虚高真实预测却对不上。判别方法很简单代码里只要出现两次 fit_transform就要警惕。3.3 时间窗口与滞后特征预测明天的核心预测天气本质上是用过去推未来。单独把“当前湿度”作为特征模型无法知道天气变化的趋势。所以需要从历史序列里构造滞后特征和滑动窗口特征。# 过去24小时的温度滞后特征 df[temp_lag_24h] df[temperature].shift(24) # 过去24小时的平均温度、湿度滑动窗口 df[temp_rolling_24h] df[temperature].rolling(24).mean() df[humidity_rolling_24h] df[humidity].rolling(24).mean() # 过去24小时的温度标准差波动情况 df[temp_std_24h] df[temperature].rolling(24).std()shift(24) 表示取 24 行之前的值如果数据是逐小时就是 24 小时前rolling(24).mean() 表示过去 24 小时的均值。temp_std_24h 表达的是气温波动这是大作业里很常用的特征——冷空气到来前往往伴随温度标准差增大。这三个特征分别捕捉了“上次的绝对值”“近期的均值”“近期的波动”组合起来比单纯用当前值稳定得多。这里有个隐蔽的边界问题一个稳健的做法是先排序再做 shift 和 rolling。如果第 2 章的排序步骤被跳过shift(24) 取到的其实是随机偏序的相邻行滞后特征立刻变废。我在帮人调试时还见过一种错误滚动窗口把测试集未来的行也滚进去了——因为 rolling 是按行位置算的只要排序正确且测试集在原始数据尾部滚动窗口只会用到“过去”的行但如果用 train_test_split 乱切测试集的滚动均值会被训练集的数据污染。这也是为什么 2.3 节强调先按时间切分再做特征顺序不能反过来。特征做完之后建议做一次基线对比只把当前值当特征和加上滞后窗口特征分别跑同一模型看 MAE 下降幅度把对比结果写进报告这是一个成本很低的加分项。4. 从线性回归到随机森林选模型与调参数很多教程一上来就是神经网络但对天气预测大作业来说scikit-learn 路线才是稳定且足够拿分的方案。原因有三数据量往往只有几千到几万条深度模型容易过拟合随机森林这类树模型能直接吃混合类型的特征不用做复杂的嵌入评估和交叉验证有成熟 API写进报告也方便。先把简单模型跑通作为基线再逐步增加复杂度。4.1 先跑一个基线模型线性回归我一般先做两步用均值预测当“傻子基线”再上线性回归。均值预测就是拿历史平均气温当预测值它给了一个最低参考线。如果线性回归连这个最低线都超不过问题通常不在模型而在特征或数据清洗。from sklearn.linear_model import LinearRegression from sklearn.metrics import mean_absolute_error # 简单基线用过去一个月的平均温度预测未来 baseline_mae (test[temperature] - train[temperature].tail(720).mean()).abs().mean() # 线性回归 model LinearRegression() model.fit(train_features, train[temperature]) pred model.predict(test_features) lr_mae mean_absolute_error(test[temperature], pred) print(f均值基线 MAE: {baseline_mae:.2f} °C) print(f线性回归 MAE: {lr_mae:.2f} °C)baseline_mae 这段代码用过去 30 天720 个逐小时样本的均值去预测测试集的温度得到的 MAE 就是“不建模也能做到的误差”。线性回归的 MAE 如果比它小说明特征里有真实信号如果比它还大说明数据处理有泄漏或特征没做好。tail(720) 这个参数是按逐小时数据算的如果你的数据是逐日就改成 tail(30)。这行代码顺便说明一个道理基线模型越简单越好让评分的人一眼看懂对比。4.2 随机森林天气预测里性价比最高的模型线性回归能跑通但要拿高分我通常直接换成随机森林回归。天气状态受非线性交互影响——比如湿度对温度的影响在晴天和雨天完全不同线性模型很难表达这种交互随机森林可以在切分中自动找到交互关系。from sklearn.ensemble import RandomForestRegressor from sklearn.model_selection import TimeSeriesSplit, cross_val_score model RandomForestRegressor( n_estimators200, max_depth10, min_samples_leaf2, random_state42, n_jobs-1, ) # 时间序列交叉验证而不是K折 tscv TimeSeriesSplit(n_splits5) scores cross_val_score( model, X, y, cvtscv, scoringneg_mean_absolute_error ) print(f训练 MAE: {-scores.mean():.2f} °C)四个参数值得说一下。n_estimators200树的数量通常 100 到 300 之间增长带来的提升会变缓太大只是浪费计算时间max_depth10限制单棵树深度防止树记住训练集的噪声数据min_samples_leaf2 要求叶子节点至少两个样本能明显降低过拟合n_jobs-1 让所有 CPU 核参与训练避免大作业在老旧笔记本上跑很久。random_state42 是为了结果可复现交作业时这个参数尤其重要不然老师跑一次一个数字报告和代码对不上。交叉验证这里用的是 TimeSeriesSplit 不是 KFold。时间序列数据的交叉验证必须保持时间顺序每一折的训练集都是前面一段、验证集是后面一段。如果你用 KFold 随机打乱又会出现第 2 章说过的“看到未来”问题。4.3 评估指标怎么选回归看 MAE分类看 F1预测天气既可以是回归任务预测温度也可以是分类任务预测是否下雨。两个任务的评估指标完全不一样这一节做个明确区分。任务模型评估指标适合场景预测气温RandomForestRegressorMAE / RMSE温度连续值MAE 对异常值不敏感更容易解释预测是否下雨RandomForestClassifierF1 / ROC-AUC下雨样本通常少准确率高但 F1 可能很低预测天气类别RandomForestClassifiermacro-F1多个类别时类别不均衡用宏平均更可靠如果你的 zip 要求预测 weather 类别分类任务的精度评估尤其要注意。比如你的数据里晴天占 80%、雨天 20%模型全预测晴天准确率也有 80%但 F1 可能是 0。训练时用 class_weightbalanced 可以让模型自动补偿少数类。from sklearn.ensemble import RandomForestClassifier clf RandomForestClassifier( n_estimators200, max_depth10, min_samples_leaf2, class_weightbalanced, random_state42, ) clf.fit(X_train, y_train) pred clf.predict(X_test)class_weightbalanced 是处理天气不均衡样本时最直接的手段在降水预测里雨天样本通常只有晴天的三分之一平衡权重可以把雨天召回率从 0.2 拉到 0.6代价是晴天会误报一些。大作业报告里把这个权衡写清楚是很明显的加分点。5. 天气预测的大作业避坑五条最常见的翻车记录这一章我挑了五个在天气预测大作业里出现频率最高的坑。每一条都是先描述现象再给出原因和解决方法按调试顺序看就行。5.1 随机切分导致“看到未来”验证分数虚高现象用 train_test_split 切分数据验证集 MAE 只有 1.5°C但拿到新数据上预测偏差直接到了 6°C。原因天气是强时间序列随机切分让测试集的时间点散落在训练集中间模型见过了未来。解决按 2.3 节的时间点切分或者用 TimeSeriesSplit。这个坑是天气预测作业里最常见的等于是提前告诉你答案然后你开着卷考——验证没意义。5.2 StandardScaler 在全局 fit造成数据泄漏现象训练出来的 MAE 很好看但每次重新运行结果都不稳定换一段数据就失效。原因在切分数据之前对全量数据做了 scaler.fit_transformscaler 偷看了测试集的均值和方差。解决先切分再对训练集 fit然后 transform 测试集。检查方法代码里搜索 fit_transform 出现了几次如果超过一次基本就是泄漏了。这条属于代码层面最隐蔽的坑我见过很多人模型和特征都对就挂在归一化这一步。5.3 预测明天却拿明天的数据当特征现象模型的精度高得不真实比公开论文的误差还低。原因构造滞后特征时用的是 shift(-1) 而不是 shift(24)——shift(-1) 把未来一小时的温度拉到了当前行作为特征模型直接看到了答案。解决做任何滞后特征之前先确认 shift 参数只能是正数滚动窗口也只能用 rolling(window).mean()不要用未来窗口。一个比较好的自查办法用 shift(1) 构造一列未来值检查这一列有没有被包含进特征矩阵。这个坑在大作业里属于“被老师一眼看穿”的类型。5.4 zip 工程在别人电脑上跑不起来绝对路径和依赖版本现象代码在自己电脑上运行正常交上去之后在老师或同学机器上报 ModuleNotFoundError 或者 FileNotFoundError。原因代码里写了类似 C:/Users/xxx/Desktop/weather/data.csv 的绝对路径或者没有提供 requirements.txt别人环境里的 scikit-learn 版本太老。解决统一用相对路径用 pathlib 替代字符串拼接在 requirements.txt 里锁定主依赖版本。要求不高的写法scikit-learn1.0pandas1.3。这个坑不看技术含量但挂了很冤。5.5 下雨预测 F1 为 0分类任务不能用准确率现象分类模型预测是否下雨准确率 86%但 F1 只有 0.1。原因数据里晴天比例高模型学到的策略是“全部预测晴天”准确率虚高但完全没有预测能力。解决用 class_weightbalanced同时报告 F1 / ROC-AUC 而不是准确率。顺手还可以做一个混淆矩阵把“漏报下雨”和“误报下雨”的数量列出来这在报告里比一个准确率数字有说服力得多。天气预测的噪声数据很多模型本身就是在一个不确定的环境里学习F1 比准确率更真实。6. 把预测天气的 zip 包交出去目录组织、回测验证与三个加分项模型跑通只是第一步大作业最终是以 zip 包形式交付。老师解压后看到的目录结构直接影响评分印象代码东一块西一块路径写死结果没法复现即便模型调得再好也容易被扣分。我一般会按下面这套结构组织工程。文件/目录作用data/raw/原始数据只读不改data/processed/清洗后特征数据notebooks/01_eda.ipynb探索性分析与可视化src/data.py数据加载与清洗src/features.py特征工程代码src/model.py模型训练与评估requirements.txt依赖清单README.md运行说明与结果摘要之所以把 notebooks 单独放是因为大作业的评分人往往先看 notebook 里的可视化再决定要不要读源码。data/raw 和 data/processed 分开是为了保证原始数据不被覆盖后期回溯时能确认每一步改动。交 zip 前最后做一次回测验证这是个屡试不爽的检查手段。以逐日数据为例从测试时段起点开始每天只用当天之前的数据训练然后预测当天再把预测结果和真实值画成折线图观察误差是否集中在冷暖空气交替的日子。import pandas as pd from sklearn.ensemble import RandomForestRegressor df_history df[df[date] 2024-01-01].copy() results [] for day in pd.date_range(2024-01-01, 2024-01-10, freqD): # 每天只用截止到当天的数据训练 train_day df_history[df_history[date] day] model RandomForestRegressor( n_estimators100, max_depth10, min_samples_leaf2, random_state42, n_jobs-1, ) model.fit(build_features(train_day), train_day[temperature]) pred model.predict(build_features(df_history[df_history[date] day])) results.append({date: day, pred: pred[0], true: df[df[date] day][temperature].values[0]})这段代码里有个细节df_history 在每次循环中不断增长前半段的训练数据会重复使用后半段逐渐包含更近的历史这正是滚动回测的含义。实际写作业时建议把 build_features 封装成函数并保证它只能接收“截止到某一天”的数据而不是从全局变量里取。我在第 3 章特别强调先排序再构造滞后特征在滚动回测里同样成立每天重新调用 build_features才能避免未来数据混进窗口。三个加分项供参考。第一在报告里用一张图展示不同特征组合的 MAE 对比可以直观体现特征工程的价值。第二把特征重要性打印出来找出“温度滞后24小时”和“气压滑动均值”这类关键特征写一句话解释为什么它们对天气预测重要。第三把模型误差最大的几天挑出来分析是不是极端天气日——这能让报告读起来像一个真实的机器学习应用流程而不是代码堆砌。最后说一个我自己的教训我早年做这类作业时把时间序列数据当成普通样本处理性能看起来很完美交上去被老师追问“未来数据怎么用到了特征里”。那次之后养成了一个习惯——每次写处理数据的代码前先问一句“这个操作会不会用到未来信息”再配合一个简单断言保证特征列的时间戳严格小于预测目标的时间戳。这套习惯后来在工作里排查数据泄漏问题时也一直用。预测天气的大作业不值得你把时间花在调参上把数据处理流程做对、把回测做扎实分数和实际能力都会稳。希望帮到你。本文还有配套的精品资源点击获取
RELATED READING

延伸阅读

更多一线实战笔记与深度复盘,助您持续精进