
简介这套基于线性回归的PM2.5预测项目Python源码是一份经导师指导并认可的高分机器学习大作业主要面向正在完成期末大作业或课程设计的计算机相关专业学生也适合希望掌握回归项目完整流程的实战学习者。源码共18个文件压缩包仅2.4MB包含12个CSV格式的训练/测试数据集、3个Python核心脚本、1个NPY模型参数文件以及图片、测试文件等辅助内容数据文件覆盖输入特征、拼接结果与答案脚本负责完成数据预处理、线性回归训练、模型保存和结果评估。项目围绕PM2.5浓度预测展开整套代码可直接运行读者可基于数据与脚本快速复现预测流程也能按自身需求调整特征或数据路径作为期末设计模板非常合适。目前已有1067人学习下载内容紧凑、结构清晰适合希望高效参考完整大作业方案的读者。1. 为什么拿线性回归做PM2.5预测而不是直接上LSTM期末大作业拿到“预测PM2.5”这个题目很多同学第一反应是堆LSTM、GRU结果模型没收敛报告倒是写了六十页。实际上课程数据集通常只有一年、每天24小时的气象监测记录样本量撑不起深度网络。线性回归在这类场景里反而更稳参数有显式解训练一遍跑完正规方程不超过两秒特征权重能直接写成公式答辩时老师问“为什么这几个变量影响大”你指着系数就能讲出道理。这个项目的源码正是按这个思路做的从train.csv构造特征矩阵用numpy实现闭式解保存model.npy最后用evalu.py计算RMSE和R2整体代码量不到三百行但把数据清洗、特征拼接、训练、评测、预测的完整链路都走通了。适合计算机专业期末大作业也适合想快速上手机器学习完整流程的人。下面从数据端开始拆。2. 数据集拆分与特征工程从train.csv到concatenateX.csv2.1 原始数据长什么样月份、小时、气象指标与PM2.5浓度训练数据train.csv是按行存储的监测记录每行是一个小时字段包括日期、小时、温度、湿度、气压、风速、各类污染物浓度等。这里拿到的版本里每天有24条记录一年的数据大概8760行。原始表没有直接给你“9点预测10点PM2.5”这种监督学习格式需要自己构造。先看数据头的示意字段示例值说明date2014/1/1日期hour00-23时AMB_TEMP16.2环境温度RH63相对湿度WIND_SPEED2.1风速PM2.584PM2.5浓度目标PM10112可吸入颗粒物目标是根据过去N小时的所有观测值预测下一小时的PM2.5。注意这里有个易错点原始数据里PM2.5自身也是特征之一不能只拿它自己的历史序列做自回归那样会丢掉气象变量的影响。完整做法是把每个小时的多维观测拼成一行形成滑动窗口。2.2 特征矩阵构造为什么把前9小时数据拼成180维向量项目里用了9小时的窗口每个小时有20维特征包含PM2.5和19个气象/污染物变量那么一条样本就是一个180维的向量。有些同学会问为什么是9小时而不是24小时一方面大作业数据量有限窗口太长会让样本数剧减另一方面天气系统的变化周期通常是小时级9小时足够捕捉短时趋势。这180维直接喂给线性回归虽然维度比样本数少但已经能形成有一定表达能力的方程。窗口滑动时第i条样本为[第i小时的特征, 第i1小时的特征, ..., 第i8小时的特征]标签为第i9小时的PM2.5浓度。这样构造出的特征矩阵就是项目里的concatenateX.csv标签对应ans.csv。构造过程可以用pandas做也可以用纯numpy循环。下面给出可运行的构造代码import numpy as np import pandas as pd df pd.read_csv(train.csv) feature_cols [AMB_TEMP, CH4, CO, NO, NO2, NOx, O3, PM10, RAINFALL, RH, SO2, WD_HR, WIND_DIREC, WIND_SPEED, WS_HR, PM2.5] # 20维示例实际按列名调整 def build_dataset(df, window9): X, y [], [] raw df[feature_cols].values # 逐月处理避免跨月拼接 for month in df[month].unique(): month_data raw[df[month] month] for i in range(len(month_data) - window): X.append(month_data[i:iwindow].flatten()) y.append(raw[df[month] month][iwindow][feature_cols.index(PM2.5)]) return np.array(X), np.array(y) X, y build_dataset(df) np.savetxt(concatenateX.csv, X, delimiter,) np.savetxt(ans.csv, y, delimiter,)这段代码先按月份切分数据避免月末和月初跨窗口污染flatten()把20维×9小时展平成180维。这里有个关键点展平顺序必须是“小时优先”即先第i小时的所有特征再第i1小时否则模型学到的位置信息就是乱序的。如果你看到concatenateX.csv里每行前半段全是温度、后半段全是风速说明展平顺序变成了“特征优先”会破坏时间连续性。2.3 训练集、验证集与测试集的划分方式原始数据集里通常把一整年的数据切成train.csv和testdata.csv。大作业常规做法把前10个月做训练集第11个月做验证集第12个月当测试集。注意不能随机打乱后划分因为时间序列存在自相关随机打乱会让模型偷看到未来信息导致评测虚高。下面给出按时间划分的代码total_rows X.shape[0] train_end int(total_rows * 0.8) # 前80%做训练 val_start train_end val_end int(total_rows * 0.9) # 80%~90%做验证 test_start val_end X_train, y_train X[:train_end], y[:train_end] X_val, y_val X[val_start:val_end], y[val_start:val_end] X_test, y_test X[test_start:], y[test_start:] # 保存为模型需要的数据文件 np.save(arrayx.csv, X_train) # 实际项目中用np.loadtxt读取 np.savetxt(x_t.csv, X_test, delimiter,)这里val_start没有加1是因为窗口构造已经从样本上做了对齐直接切分不会让相邻样本信息泄漏。验证集的作用是检查模型的泛化能力如果训练集RMSE很低、验证集很高说明过拟合需要减小特征维度或加正则化。testdata.csv是最后才拿出来评测的在调参阶段绝对不能碰这是大作业拿高分的基本操守。3. 线性回归模型的矩阵求解与numpy实现3.1 正规方程与梯度下降为什么大作业里选正规方程线性回归的目标是找到权重向量w使误差平方和最小。求解w有两种常用路线梯度下降和正规方程。后者直接对损失函数求导置零得到w (X^T X)^(-1) X^T y一次矩阵运算出结果。对大作业这种几百维特征、几万样本的规模正规方程的计算复杂度是O(n^3)n为特征维度180维完全能接受而且没有学习率这个超参数省去调参步骤。梯度下降虽然适合超大样本但你需要在报告里解释迭代次数、学习率怎么选答辩时容易被追问。正规方程则更“一锤定音”配合正则化项的推导也很简洁。下面是两种方法的对比方法优点缺点适用场景正规方程一步求解无学习率矩阵求逆慢n10000时吃力中小规模、特征维度适中梯度下降可扩展大数据需调学习率、迭代次数样本量百万级以上岭回归改进解决不可逆减小方差需选岭参数λ特征间存在多重共线性项目源码里用的是带L2正则化的岭回归闭式解w (X^T X λI)^(-1) X^T y这样做除了防止过拟合还有一个实际好处当特征列存在相关性导致X^T X接近奇异时加λI可以让矩阵可逆不会在np.linalg.inv时报错。3.2 训练代码解析从arrayx.csv到model.npy训练主流程在PredictionofPM2.5.py里。核心代码很短但每一行都有讲究import numpy as np # 读取构造好的特征与标签 X np.loadtxt(concatenateX.csv, delimiter,) y np.loadtxt(ans.csv, delimiter,) # 添加偏置项对应w_0 X np.hstack([np.ones((X.shape[0], 1)), X]) # 验证集划分这里用前80%训练后20%验证 split int(X.shape[0] * 0.8) X_train, X_val X[:split], X[split:] y_train, y_val y[:split], y[split:] # 岭回归闭式解w (X^T X lambda*I)^{-1} X^T y lambda_val 1.0 I np.eye(X_train.shape[1]) I[0, 0] 0 # 不对偏置正则化 w np.linalg.inv(X_train.T X_train lambda_val * I) X_train.T y_train # 保存模型参数 np.save(model.npy, w) # 验证集预测 y_pred X_val w rmse np.sqrt(np.mean((y_pred - y_val) ** 2)) print(fValidation RMSE: {rmse:.3f})I[0,0]0这行很关键偏置项w_0不应该被正则化因为它是全局偏移没有跟任何特征相乘。如果你直接对整个I加λ模型对均值的拟合能力会被削弱验证集RMSE会涨不少。读取model.npy后预测新样本的操作为y np.dot(x_new, w)其中x_new也需要在最前面补1。参数说明lambda_val是正则化强度值越大权重越趋向零。大作业里一般设为1.0左右你可以用验证集跑一个网格搜索lambda取[0.01,0.1,1,10,100]选RMSE最小的。注意lambda太大会让模型变得过于简单预测值整体偏向训练集均值。3.3 参数说明与多项式扩展的取舍原始线性回归的表达能力有限但大作业中不建议贸然上多项式因为9小时×20维的特征已经是180维再做平方项会有180^2/2≈16200维样本数只有几千必过拟合。更好的做法是增加交互项比如风速与PM10的乘积能体现“扬尘”效应。如果你想在报告中体现亮点可以只对PM2.5和PM10这两个特征做二阶展开保持总维度可控。系数解释也是评分点。训练完打印w对应第0个是偏置第1~20个是第1小时的特征权重第21~40是第2小时的以此类推。你会发现距离预测时刻最近的时间窗口权重普遍更大这符合物理直觉。如果特征列没有标准化权重大小不能直接衡量重要性所以有些同学会把特征做z-score归一化后再训练但此时预测时也要用同样的均值方差去缩放输入。源码里arrayy.csv和arrayx.csv这两个文件名看似打印错误其实只是用于保存训练过程的中间结果不影响主流程。4. 评测逻辑与95分是怎么来的evalu.py与predict.csv4.1 评测脚本的指标计算RMSE与R2大作业的评分标准通常看两项预测值与真实值的均方根误差RMSE以及决定系数R²。evalu.py的逻辑就是读入预测文件predict.csv和真实值文件ans.csv逐行对比计算。RMSE的公式是sqrt(mean((y_pred - y_true)^2))R²是1 - SS_res / SS_tot其中SS_res是残差平方和SS_tot是真实值与均值的差平方和。R²越接近1说明模型解释的方差比例越高。下面是一个简化的评测实现import numpy as np y_true np.loadtxt(ans.csv, delimiter,) y_pred np.loadtxt(predict.csv, delimiter,) # 计算均方根误差 rmse np.sqrt(np.mean((y_pred - y_true) ** 2)) # 计算R2 ss_res np.sum((y_true - y_pred) ** 2) ss_tot np.sum((y_true - np.mean(y_true)) ** 2) r2 1 - ss_res / ss_tot print(fRMSE: {rmse:.4f}) print(fR2: {r2:.4f})这里的predict.csv每一行对应一个测试样本的预测值行数必须跟ans.csv完全一致最后不能有额外的空行。evalu.py可能还会输出平均绝对误差MAE但RMSE和R²是最主要的两个指标。老师给的样例提交文件是sampleSubmission.csv建议先读它的格式确保你的predict.csv列头一致。4.2 用模型生成predict.csv的过程训练完model.npy后对testdata.csv做同样的特征构造然后加载权重得到prediction。源码里的测试特征文件是test.csv和arrayy.csv这里有一个新手常踩的坑测试集特征构造时的窗口偏移必须和训练集一致。比如训练时用第i到i8小时预测i9小时那么测试时你有的是1到9小时数据预测第10小时下一行则是2到10小时预测第11小时。如果测试数据本身只有单天的完整序列你只能预测最后时刻之后的值而不能预测存在缺失的中间时段。生成预测的代码import numpy as np w np.load(model.npy) X_test np.loadtxt(x_t.csv, delimiter,) X_test np.hstack([np.ones((X_test.shape[0], 1)), X_test]) y_test_pred X_test w # 保存为提交格式 np.savetxt(predict.csv, y_test_pred, delimiter,, headervalue, comments)注意headervalue这一行许多评测脚本要求提交文件有列名。如果你直接用np.savetxt不加header可能会被判“格式错误”直接扣5分。检查完预测值后再跑一遍evalu.py验证。4.3 常见丢分点数据标准化时机、过拟合、异常值能到95分以上的项目通常避开了下面几个暗坑。第一特征标准化必须在划分训练/测试集之后做否则均值方差包含测试集信息属于数据泄漏。正确做法是只计算训练集的mean和std用它们去缩放验证集和测试集。源码里没有显示标准化说明原始数据量纲差异不大但如果你用温度0-40和降水量0-1000直接拼特征大数值变量会主导梯度正规方程也会产生病态矩阵。第二异常值处理。PM2.5监测数据经常出现600以上的爆表值如果不处理这些离群点会严重拉高RMSE。常见做法是筛选PM2.5大于500的样本如果发生在训练集可以用前两个小时的均值替换如果发生在测试集则保留——因为你不能删除要预测的数据。好的项目会在报告里单独说明用了什么阈值这能加分。第三过拟合。线性回归不会像决策树那样过拟合到极致但当特征维度接近样本数时验证集R²会突然掉到0.6以下。这时候优先减小lambda值而不是删特征。实际上2015年李宏毅机器学习课程作业里的PM2.5预测很多学生发现lambda在0.5~2之间效果最好超过10反而下降因为题目数据本身噪声不大正则化过度会丢失信号。5. 把项目改造成可复用的预测管线参数化与可视化5.1 用命令行参数控制超参原始代码写死了窗口大小、lambda、文件路径换一组数据就得改源码。把它改造成一个可复用的Python脚本并不难用argparse把关键参数提出来import argparse parser argparse.ArgumentParser(descriptionPM2.5线性回归预测) parser.add_argument(--window, typeint, default9, help滑动窗口大小) parser.add_argument(--lambda, destlambda_val, typefloat, default1.0, help正则化系数) parser.add_argument(--train, defaulttrain.csv, help训练数据路径) parser.add_argument(--test, defaulttest.csv, help测试数据路径) args parser.parse_args() # 构建设置 window args.window lambda_val args.lambda_val这么改之后调参可以直接python train.py --window 12 --lambda 0.5不需要反复改代码。注意lambda在Python里有特殊含义用destlambda_val来规避。更进一步你可以把特征构造和训练函数写进一个类用joblib保存特征列的均值与标准差形成完整预测管线。5.2 加一行残差图快速定位系统偏差模型训练完不要只看RMSE把预测值和残差画出来能发现是否在某些浓度区间系统性偏差过。例如在低浓度段残差为负、高浓度段残差为正说明模型的中点被“平均化”了。这种问题线性回归很难避免但你可以通过分组统计来量化把真实值按0-50、50-100、100-200、200分段分别计算平均误差并写进实验报告。下面是一个简化的分组分析代码y_true np.loadtxt(ans.csv) y_pred np.loadtxt(predict.csv) bins [0, 50, 100, 200, 500] labels [0-50, 50-100, 100-200, 200] error_groups {label: [] for label in labels} for t, p in zip(y_true, y_pred): for i in range(len(bins)-1): if bins[i] t bins[i1]: error_groups[labels[i]].append(p - t) break for label in labels: err np.array(error_groups[label]) print(fPM2.5 {label}: 平均误差 {err.mean():.2f}, 样本数 {len(err)})这个输出比单个RMSE更有说服力。如果发现“200”区间平均误差达到-40说明模型在重污染日严重低估你需要检查训练集里高浓度样本数量是否足够或者考虑给高浓度样本更大的权重不过这属于加分项前提是主模型已经稳定。5.3 把训练结果保存为结构化JSON最后再给一个可落地的小技巧除了保存model.npy同时把超参数和训练指标写入JSON方便复现结果。import json report { window: window, lambda: lambda_val, rmse: float(rmse), r2: float(r2), feature_dim: X_train.shape[1], model_file: model.npy } with open(report.json, w) as f: json.dump(report, f, indent2)这样你在最终提交的说明里可以直接贴出report.json老师一眼能看到你跑过的实验配置而不是用一堆截图证明“我调过参”。整个项目到此已经具备从数据清洗、特征构造、模型训练、离线评测到结果报告的全部要素把这个管线套用到其他城市的气象数据只需要改一下列名对应关系剩下的代码完全不用动。本文还有配套的精品资源点击获取