拓十年匠心定制 · 商业建站与技术教学双线并行 咨询热线:400-886-1026 service@lmnt.cn
ARTICLE DETAIL

资讯详情

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

线性回归实战:PM2.5预测机器学习大作业完整指南

线性回归实战:PM2.5预测机器学习大作业完整指南

简介:一份机器学习课程大作业的完整实现,围绕合肥地区PM2.5浓度预测任务,包含Python源码与配套数据。项目面向机器学习初学者及数据科学相关课程学生,可帮助理解线性回归建模全流程:从历史空气质量数据采集、特征矩阵构造,到梯度下降法优化参数。压缩包共21个文件,大小2.58MB,含12个CSV数据文件,覆盖训练集、测试集、拼接特征及预测结果;3个PY脚本实现数据预处理、模型训练与评估;另有NPY模型参数、说明文档和示意图,目录结构清晰,便于直接运行和二次开发。已有280人学习下载。读者可借此掌握线性回归的矩阵形式与梯度更新公式,获得完整实验记录与可复现预测流程,适用于课程设计或入门回归预测的实战参考。

1. 基于线性回归的PM2.5预测:一次能“跑通也能讲清”的机器学习大作业

机器学习大作业选什么题,直接决定你期末周的心态。在《机器学习》课程里,基于线性回归的PM2.5预测源码这类项目,是数据科学方向最稳的选择之一:数据集公开易得,算法原理课上学过,代码量不大,可视化效果好,论文和答辩都有话可说。它解决的问题很具体——根据气象条件、风速、湿度等观测值预测空气中PM2.5浓度,本质上是“用历史数据拟合一个连续函数”的回归任务。新手能借此完整走一遍数据分析的流程,熟手则可以在特征工程和模型对比上挖深度。这篇笔记,我会把从数据清洗到模型评估的全过程拆开,连同我踩过的坑一起写出来,你可以照着复现,也可以直接作为大作业的源码骨架。

2. 数据与特征准备:PM2.5预测的“地基”决定模型上限

2.1 认识数据:从UCI北京空气质量数据集里选对字段

做PM2.5预测,公开数据集中最常用的是UCI机器学习库里的Beijing PM2.5 Data Set。它记录了2010年到2014年北京某个监测站逐小时的空气质量与气象观测值。常见的做法是拿到一份CSV文件,每行代表一个小时,字段包括year(年)、month(月)、day(日)、hour(时)、pm2.5(目标变量)、DEWP(露点温度)、TEMP(摄氏温度)、PRES(气压)、cbwd(组合风向)、Iws(累计风速)、Is(累计降雪)、Ir(累计降雨)。

我一般先把数据读进来,用Pandas做初步观察。关键的一步是确认数据的时间跨度、字段类型和缺失情况,这决定后面特征工程怎么做。

import pandas as pd df = pd.read_csv('PRSA_data.csv') print(df.shape) # 看有多少行多少列 print(df.dtypes) # 检查每列数据类型 print(df.isnull().sum()) # 统计缺失值 print(df.head(10)) # 看一眼前10行

逻辑说明:df.shape返回一个元组,第一个元素是行数,第二个是列数。PM2.5数据集通常是43800行左右(4年×365天×24小时),但实际会有缺失,所以我们看到的具体行数会略有出入。df.dtypes用来确认数值列和对象列,其中cbwd是字符串对象类型,后面要单独处理。df.isnull().sum()能快速定位哪些列有缺失,预警数据质量。

参数说明:如果你没下载到这个数据集,也可以用pd.read_csv读入任何包含PM2.5浓度和时间字段的表格,但要注意字段名一致。我建议把year、month、day、hour四个字段合并成一个datetime列,方便后续按时间筛选和排序。因为大部分数据处理库的索引操作对datetime列支持得最好。

2.2 缺失值与异常值处理:预测前必须做的两步清洗

PM2.5数据集最常见的缺失有两种:一是pm2.5列本身存在连续的NaN(可能因为设备维护导致停机),二是某些小时记录干脆没有对应行。很多新手上来就dropna(),结果发现可用数据少得可怜,模型根本训不动。这里我给你我的处理思路:先看缺失比例,再决定是填充还是丢弃。

# 先按时间排序,防止原始数据乱序 df['datetime'] = pd.to_datetime(df[['year', 'month', 'day', 'hour']]) df = df.sort_values('datetime').reset_index(drop=True) # pm2.5缺失比例超过30%就直接丢行,否则用前向填充 missing_ratio = df['pm2.5'].isnull().mean() if missing_ratio > 0.3: df = df.dropna(subset=['pm2.5']) else: df['pm2.5'] = df['pm2.5'].fillna(method='ffill') # 观察替换后pm2.5的分布 print(df['pm2.5'].describe())

逻辑说明:pd.to_datetime把拆分的时间字段合成一列,底层的实现是逐字段拼接后解析,效率不算高,但胜在直观。sort_values按时间排序是必须的,因为线性回归假设样本独立同分布,若打乱顺序,训练集中会混入未来数据。fillna(method='ffill')是前向填充,用上一个观测值补当前缺失值,这比均值填充更符合PM2.5的时序特性——污染物浓度在短时间内有连续性。

参数说明:missing_ratio阈值0.3是经验值。缺失超过30%时,前向填充会引入大量重复样本,造成模型对这组数据的过拟合。低于这个阈值,比如某个月的数据断了几小时,前向填充是安全的。你要注意fillna(method='ffill')默认不会填充头部缺失,所以还得加一个bfill()把开头几行的NaN补上。

异常值这部分,很多人会用3σ原则或者IQR去筛PM2.5浓度,但我建议谨慎操作。因为PM2.5的分布右偏严重,重污染日的浓度可能远超均值加三倍标准差,直接删掉反而让模型失去对极端污染事件的预测能力。我一般只删除明显不可能的负值和超过监测上限的异常记录。

2.3 特征工程:时间周期、风向编码与滞后特征

线性回归对特征的要求是“每个特征与目标变量之间存在相对线性的关系”,而PM2.5浓度与气象变量的原始关系并不线性。但有两个特征工程技巧能让线性模型的表现显著提升。

第一个是时间周期特征。PM2.5有明显的小时周期(早晚高峰高、午后低)和季节周期(冬季高、夏季低)。如果把hour直接当作数值特征,模型会学到“小时越大PM2.5越高”的错误关系。标准解法是把时间拆成正弦和余弦分量:

import numpy as np df['hour_sin'] = np.sin(2 * np.pi * df['hour'] / 24) df['hour_cos'] = np.cos(2 * np.pi * df['hour'] / 24) df['month_sin'] = np.sin(2 * np.pi * df['month'] / 12) df['month_cos'] = np.cos(2 * np.pi * df['month'] / 12)

逻辑说明:np.sin和np.cos将线性时间映射到单位圆的坐标上,本质是保留了“23点和0点接近”这个直觉——凌晨23点和0点都是低排放时段,但数值上23和0在普通线性特征里距离很远。周期编码后,相似时间点在二维平面上的距离就小了,线性模型也就学得到这种周期性。

参数说明:分母的24和12分别对应小时和月的周期长度。如果你想表达“工作日和周末的差别”,也可以加一个is_weekend二元特征。但要注意,线性回归对特征尺度敏感,不同特征的取值范围差异较大时,需要考虑标准化,否则梯度下降训练会震荡。

第二个是滞后特征。PM2.5浓度是惯性系统,上一小时的浓度对当前值影响极大。构造滞后特征的方法是对目标变量做shift操作:

df['pm2.5_lag1'] = df['pm2.5'].shift(1) df['pm2.5_lag24'] = df['pm2.5'].shift(24) df = df.dropna() # shift产生的NaN行直接删掉

逻辑说明:shift(1)取上一小时的值,shift(24)取昨天同一小时的值,分别捕捉短期惯性和日周期基准。加入滞后特征后,线性回归的R²通常能从0.4左右提升到0.7以上,这是整个项目投入产出比最高的步骤。

参数说明:shift步长越大,计算时要用越多的历史数据,训练集也会因dropna减少。做大作业时,你可以对比“只加lag1”“加lag1和lag24”“再加lag2”三种配置的效果,写进报告里就是一组漂亮的消融实验。

风向字段cbwd是分类变量,可选项为NE、NW、SE、SW和cv(静风)。线性回归不能直接吃字符串,常见做法是pd.get_dummies做独热编码:

df = pd.get_dummies(df, columns=['cbwd'], drop_first=True)

逻辑说明:drop_first=True会删掉第一个类别列,避免“虚拟变量陷阱”——当分类变量有k个取值时,只需k-1个二元特征就能完整表达,否则线性回归的解析解会因为特征完全共线性而无法计算。

参数说明:如果你用的是statsmodels的OLS,它内部会检测完全共线性并自动处理,但scikit-learn的LinearRegression不会报错,而是给出一个“看似正确但系数不可解释”的模型。所以在大作业里,我会明确加上drop_first=True,说明参考文献里通常引用的是含k-1个哑变量的模型。

2.4 划分训练集与测试集:时间序列数据的切分原则

这是新手最容易翻车的环节,没有之一。很多教程用train_test_split(..., random_state=42)做随机切分,但那是针对独立样本的。PM2.5是时间序列,相邻小时之间的样本高度相关,随机切分会让测试集混入“训练集时刻的后一小时”,模型表现虚高得离谱,答辩时一问就穿帮。

我用的切分方法是按时间顺序取前80%做训练,最后20%做测试:

train_size = int(len(df) * 0.8) train = df.iloc[:train_size].copy() test = df.iloc[train_size:].copy() feature_cols = ['DEWP', 'TEMP', 'PRES', 'Iws', 'Is', 'Ir', 'hour_sin', 'hour_cos', 'month_sin', 'month_cos', 'pm2.5_lag1', 'pm2.5_lag24'] X_train = train[feature_cols].values y_train = train['pm2.5'].values X_test = test[feature_cols].values y_test = test['pm2.5'].values

逻辑说明:iloc[:train_size]是按位置切片,不是按时间筛选,但因为前面已经做了sort_values,所以位置顺序就是时间顺序。这样划分后,测试集完全在训练集的时间之后,模型在测试集上的误差才能真实反映它的泛化能力。

参数说明:0.8这个比例适合数据量为4年的场景。如果你的数据只有一年,建议提高到0.85,保证测试集仍有足够多的样本。注意feature_cols列表里没有包括year、month、day这些原始时间字段,因为它们已经被周期特征替代;如果你保留了原始数值字段,模型会学到“某一年整体偏高”这种虚假关联,这是另一个大作业里常见的错误。

3. 线性回归建模与源码实现:从公式到可运行的完整代码

3.1 最小二乘原理:为什么线性回归能拟合污染扩散

线性回归算法的假设是目标变量y可以用特征X的线性组合加上误差项表示。形式化地写,就是y = X·w + b + ε。PM2.5的浓度变化虽然受化学反应、二次生成等非线性机制影响,但在短时段内,污染物扩散与风速、湍流、湿度等气象条件的关系可以用线性近似描述,这就是为什么线性回归在这个问题上能有不错的基线效果。

训练目标是最小化残差平方和:RSS(w) = Σ(yᵢ - Xᵢ·w)²。它的闭式解是w = (XᵀX)⁻¹Xᵀy,也就是最小二乘估计。当特征数量不多(本例只有十几个),样本量有几万个时,闭式解的计算复杂度为O(n·d²),训练时间在毫秒级,远比训练一个神经网络的成本低。scikit-learn的LinearRegression默认采用最小二乘的奇异值分解实现,数值稳定性好,不需要手动计算矩阵求逆。

这里要提一个概念:线性回归对特征工程的依赖远高于对模型调参的依赖。你做同样的特征处理,用sklearn的LinearRegression和用PyTorch写一个单层全连接网络,结果几乎一样。所以大作业的重点不是换模型,而是把特征解释清楚。

3.2 模型训练代码:sklearn这样用才对

很多人的第一版代码会漏掉标准化,直接裸特征训练。在特征量级差异大时(比如PRES是1000左右的量级,Is是0到几毫米),梯度下降会有收敛问题。虽然sklearn的LinearRegression用正规方程求解理论上对尺度不敏感,但加了L2正则的Ridge或使用梯度优化的变体就受影响了。这里我建议用Pipeline统一处理,既干净又能在答辩时展示“工程规范”。

from sklearn.linear_model import LinearRegression from sklearn.preprocessing import StandardScaler from sklearn.pipeline import make_pipeline model = make_pipeline( StandardScaler(), LinearRegression() ) model.fit(X_train, y_train) y_pred = model.predict(X_test)

逻辑说明:make_pipeline把标准化和线性回归串成一个对象,fit时先计算训练集的均值、方差,然后对X做变换,再送入回归器。测试时predict会自动用同一组标准化参数处理X_test,不会把测试集信息泄漏到训练过程中。

参数说明:StandardScaler对每列做(z - mean) / std,把特征的分布拉成均值0、标准差1。LinearRegression()默认带截距项,参数fit_intercept=True,所以不需要额外加一列常数特征。如果你的特征中存在高度相关的列,可以用Ridge(alpha=1.0)之类加正则的模型替代,后面避坑章会详细说。

训练完成后,我习惯把权重打印出来看一眼正负方向,这一步虽然不产指标,但对理解模型很关键:

import pandas as pd # 取pipeline最后一步的模型 lr = model.named_steps['linearregression'] coef_df = pd.DataFrame({ 'feature': feature_cols, 'coef': lr.coef_ }).sort_values('coef', key=lambda s: s.abs(), ascending=False) print(coef_df)

逻辑说明:model.named_steps可以按名称访问Pipeline里的组件。打印coefficients后,你会看到pm2.5_lag1的系数最大且为正,说明上一小时浓度对当前浓度影响最大,这符合物理直觉;Iws(风速)系数为负,说明风速越大污染物扩散越快,浓度越低。这些观察写进大作业报告里,能显著加分。

参数说明:.sort_values的key参数是pandas 1.1以上版本才支持的功能,功能是按系数绝对值排序。低版本环境可以先生成coef_df['abs_coef'] = coef_df['coef'].abs()再排序,效果一样。

3.3 模型评估:RMSE、MAE与R²怎么解读

线性回归预测PM2.5的评估指标,大作业里最常用三个:均方根误差(RMSE)、平均绝对误差(MAE)和决定系数(R²)。它们的侧重点不同,需要结合起来看。

from sklearn.metrics import mean_squared_error, mean_absolute_error, r2_score import numpy as np rmse = np.sqrt(mean_squared_error(y_test, y_pred)) mae = mean_absolute_error(y_test, y_pred) r2 = r2_score(y_test, y_pred) print(f'RMSE: {rmse:.2f} μg/m³') print(f'MAE: {mae:.2f} μg/m³') print(f'R²: {r2:.4f}')

逻辑说明:mean_squared_error计算平均平方误差,开根号后量纲回到μg/m³,方便直观理解误差大小。mean_absolute_error直接求绝对差平均,受极端值影响比RMSE小。r2_score计算决定系数,表示模型解释了测试集目标变量方差的百分比。一套好的结果大概是:RMSE在30到50 μg/m³之间,R²在0.7到0.85之间(具体取决于特征工程做了多少),如果加入滞后特征后R²还不到0.5,那大概率是数据切分或特征选择出了结构性问题。

参数说明:这三个指标都要求y_test和y_pred长度一致,且都是数值型数组。训练集的R²往往比测试集高5到10个百分点,这是正常现象。如果训练集R²接近0.99而测试集只有0.3,说明过拟合,需要检查是否把未来信息泄漏进了训练集,或者没有做时间切分。

3.4 结果可视化:把预测值和真实值画在一起

可视化是评估回归模型最直观的手段,也是大作业报告里的“门面”。我一般会画三张图:时间序列对比图、散点图、残差图。这里先给时间序列对比图和散点图的代码。

import matplotlib.pyplot as plt plt.figure(figsize=(12, 5)) # 只画最近72小时,避免线条太密 n_show = 72 plt.plot(test['datetime'].iloc[:n_show], y_test[:n_show], label='Actual', linewidth=1.5) plt.plot(test['datetime'].iloc[:n_show], y_pred[:n_show], label='Predicted', linewidth=1.5, alpha=0.8) plt.xlabel('Time') plt.ylabel('PM2.5 (μg/m³)') plt.legend() plt.xticks(rotation=45) plt.tight_layout() plt.savefig('pm25_prediction_trend.png', dpi=150)

逻辑说明:test['datetime'].iloc[:n_show]取测试集的前72个时间点,这样横轴是真实的时间刻度,图例标在右上角。预测值用半透明线条叠加,能直观看到模型能不能跟上浓度起伏的节奏。

参数说明:figsize=(12,5)是宽屏比例,适合横向时间轴。dpi=150保证导出图片清晰。如果你用的是jupyter notebook,加一行%matplotlib inline就能嵌入显示。

散点图用来检查预测值是否存在系统性偏差:

plt.figure(figsize=(6, 6)) plt.scatter(y_test, y_pred, s=3, alpha=0.5) plt.plot([0, max(y_test)], [0, max(y_test)], color='red', linestyle='--', linewidth=1) plt.xlabel('Actual PM2.5') plt.ylabel('Predicted PM2.5') plt.tight_layout() plt.savefig('pm25_scatter.png', dpi=150)

逻辑说明:plt.scatter中横轴是真实值,纵轴是预测值。如果模型很好,点会密集分布在红色对角线附近。当点在对角线上方聚集,说明模型系统性高估;在下方则低估。PM2.5预测的一个规律是:在低浓度段(0到50),点通常较为集中;在高浓度段(大于200),预测值往往偏低,这是因为高浓度事件在数据集中出现频率低,模型“没见过足够多的极端样本”。

参数说明:s=3控制散点大小,在数据量大时防止重叠。alpha=0.5让重叠区域的密度更明显。max(y_test)用于确定对角线的范围,保证对角线从原点到测试集最大浓度值。

4. 线性回归预测PM2.5的5个避坑记录:现象、原因与解决办法

4.1 预测值出现负数

现象:模型跑完,预测结果里有相当一部分PM2.5浓度是负的,比如-15.3 μg/m³。这在物理上完全不合理——浓度不可能是负数。

原因:线性回归的本质是用直线拟合数据,在特征取值落在训练集分布边缘时,直线外推就会越过零轴。PM2.5数据集中在低浓度区域,当风速大、湿度低的组合出现时,模型很容易把预测值推成负的。

解决:最直接的办法是对预测结果做下限截断:

y_pred_clipped = np.clip(y_pred, a_min=0, a_max=None)

np.clip把数组中小于0的元素改成0,大于等于0的保持不变。这样处理后,RMSE会略微下降一点(因为原来的绝对值误差变小了),但指标更贴近实际意义。要注意,这个操作最好只在测试集上做,不参与训练。想在模型内部解决,可以改用Lasso或带非负约束的优化器,但对大作业来说截断就够了。

4.2 用train_test_split随机切分导致评估虚高

现象:测试集R²高达0.9以上,RMSE低得离谱,换成时间切分后又掉到0.7左右。答辩时老师问一句“你的测试集是怎么划分的”,场面会很尴尬。

原因:前面说过,PM2.5是时间序列数据,相邻样本间存在强自相关。随机切分把同一天甚至相邻小时的数据一部分放进训练集、一部分放进测试集,本质上是让模型看了“答案”再去考试。

解决:严格按照时间顺序划分,并且报告中要写清“训练集为前80%的时间段,测试集为后20%的时间段”。比较规范的做法是:先把数据按时间排序,再取连续区间。注意划分之后不要做任何跨区间的特征统计,尤其是标准化时只能用训练集的均值和方差。

4.3 风向类别变量直接编码成0/1/2/3数值

现象:模型训练不报错,但画出系数发现风向特征的系数特别大,且不同随机种子下符号不稳定。

原因:把字符串类别映射成整数,比如{'NE': 0, 'NW': 1, 'SE': 2, 'SW': 3, 'cv': 4},等于强行给类别排序,线性回归会把这个数字当成真实的数量关系——“风向=4”不应该是“风向=1”的四倍效果。这种编码方式既破坏了类别的无序性,又引入了虚假的线性关系。

解决:使用pd.get_dummies做独热编码,或者用sklearn.preprocessing.OneHotEncoder。注意独热编码后特征数量从一个变成k-1(drop_first),所以特征列数会变化,模型输出的coef向量长度也比之前多几个维度,这份不要和旧的参数数组混用。另外,独热编码列之间存在负相关(一个样本只能命中一个风向),但drop_first已经消了解析解问题,不必再担心。

4.4 PRES和TEMP高度相关导致系数符号与直觉相悖

现象:模型跑出来后,PRES(气压)的系数为正,你原本预期气压升高对应天气转晴、污染物易于垂直扩散,系数应该是负的。换一组训练数据后,系数的正负号又变了。

原因:气压和温度之间存在较强的负相关(冷高压天气温度低),多个特征之间存在多重共线性。最小二乘系数在共线性存在时方差会变得非常大,符号翻转完全依赖于训练集中微小扰动,这被戏称为“系数符号不稳定”。

解决:检查特征相关性矩阵,并做取舍:

corr = train[['TEMP', 'DEWP', 'PRES', 'Iws', 'pm2.5']].corr() print(corr)

如果发现PRES和TEMP的相关系数绝对值超过0.7,就只保留其中更可信的一个。我个人习惯保留TEMP而删掉PRES,因为露点温度DEWP在物理上对PM2.5的影响更直接。另一个做法是改用Ridge回归,L2正则能约束系数幅度,一定程度上缓解共线性导致的符号不稳定,代价是R²可能降低0.01到0.02。

4.5 只看R²不检查残差

现象:R²有0.75,报告写得挺满,但把残差(真实值减预测值)按时间画成图,发现一片片连续的正残差或负残差成块出现,模型在一个时间段系统性低估、在另一个时间段系统性高估。

原因:R²衡量的是整体方差解释程度,但时间序列里,模型很可能在特定天气过程或季节里犯相似的错误,这些错误在整体指标中被平均掩盖了。残差的时序自相关正是“模型没捕捉到某些动态因素”的信号。

解决:画出残差图,并计算残差的自相关系数:

residuals = y_test - y_pred plt.figure(figsize=(12, 3)) plt.plot(test['datetime'], residuals, linewidth=0.5) plt.axhline(y=0, color='red', linestyle='--') plt.ylabel('Residual (μg/m³)') plt.tight_layout() plt.savefig('pm25_residuals.png', dpi=150)

逻辑说明:residuals = y_test - y_pred是逐样本求差,plt.axhline(y=0)画一条零参考线。理想的残差图应该像一条均匀分布在零线两侧的“噪声带”,没有明显的长周期波动。如果残差在连续大块时间里都在零线以上,说明模型在那段时间里系统性低估浓度,而在另一些时段系统性高估。这通常提示缺少重要特征,比如季节趋势或一次沙尘事件影响。大作业里,残差分析是展示你“有诊断能力”的亮点,不建议省掉。

5. 从大作业到可展示的项目:模型对比、滚动预测与答辩准备

5.1 用残差的正态性验证模型假设

线性回归的形式化假设里有一条是“误差项独立且服从零均值、同方差的正态分布”。大作业可以做一个简单的正态性检验:对残差做标准化,画出直方图,或者用scipy.stats.shapiro检验。但要注意,样本量大时Shapiro检验很敏感,几千条数据下几乎必然拒绝正态性,所以重点不是“是否通过检验”,而是画出分布看在哪个位置偏离。PM2.5预测的残差通常是右偏的——模型对高浓度事件低估,造成正残差的尾巴更长。知道这一点,在答辩时就能主动说明“线性回归的局限”,而不是等着被老师问。

5.2 滚动预测与多步预测的陷阱

如果你想让大作业更进一步,可以做多步预测并指出滚动预测中的误差累积。常见方案是:用t时刻的真实观测去预测t+1,再用这个预测值作为t+1的输入、预测t+2,依此类推。代码如下:

# 从测试集第一个样本开始滚动预测未来24小时 predictions = [] current_input = X_test[0].copy() for step in range(24): pred = model.predict(current_input.reshape(1, -1))[0] pred = np.clip(pred, 0, None) predictions.append(pred) # 更新滞后特征 current_input[-2] = pred # pm2.5_lag1 # pm2.5_lag24 在步长<24时保持原值;24步后需更新 if step + 1 >= 24: current_input[-1] = predictions[step + 1 - 24]

逻辑说明:current_input最初是X_test的第0行,包含真实历史信息。每次预测后,要把新预测值写入pm2.5_lag1的位置(即特征列表的倒数第二列),供下一次预测使用。pm2.5_lag24在前24小时内仍是真实历史值,超过24步后才用24步前的预测值填充。

参数说明:这段代码用current_input[-2]定位特征是硬编码,它要求特征列表顺序固定。实际项目中,我更建议用一个字典保存特征值,按名称更新,避免索引错位。滚动24步预测的RMSE通常会比单步预测高20%到50%,误差累积是线性模型做多步预测遇到的自然瓶颈。

5.3 与决策树/随机森林对比,让大作业有“讨论深度”

大作业报告只写线性回归,内容会比较单薄。常用的对比方向是随机森林:它对非线性关系和特征交互有更强的拟合能力。在同一套特征、同一个时间切分下跑通随机森林,对比两者在测试集上的RMSE和R²:

from sklearn.ensemble import RandomForestRegressor rf_model = RandomForestRegressor( n_estimators=200, max_depth=15, min_samples_leaf=5, n_jobs=-1, random_state=42 ) rf_model.fit(X_train, y_train) rf_pred = rf_model.predict(X_test) r2_rf = r2_score(y_test, rf_pred)

逻辑说明:RandomForestRegressor对特征量纲不敏感,所以不需要StandardScaler。n_estimators=200表示200棵决策树,max_depth=15限制每棵树的深度防止过拟合,min_samples_leaf=5保证叶子节点最少有5个样本。

参数说明:随机森林在PM2.5上的表现通常会略优于线性回归,R²可能从0.78提升到0.83,但训练时间从不到1秒变成几十秒。你要在报告中把结论写成“线性回归在简洁性与可解释性上有优势,随机森林在高浓度事件的预测上略好”,这是一句既诚实又稳妥的总结。

5.4 答辩前必做的三个验证实验

第一个是预测值的分布对比。把测试集真实值和预测值各自画直方图,检查模型是否“学歪了”,比如预测值整体方差变小(回归到均值效应)。这几乎是线性回归的通病,承认比回避好。

第二个是对极端天气日期做案例分析。选几天重污染事件,把你的预测值和真实值画在一起,讨论模型在哪一步跟丢了。这类分析能展示你不仅会调包,还有一线工程师式的诊断直觉。

第三个是特征删除的敏感性分析。每次删掉一列特征,重新训练并记录R²的变化。删除pm2.5_lag1后R²大幅下降,说明模型对历史浓度依赖强;删除Iws后R²小幅下降,说明风速对扩散过程有增量解释力。这个实验写进报告,等于把特征工程环节从“我用了这些特征”升级成“我验证了这些特征的价值”。

做机器学习大作业这几年,我最大的教训是:数据清洗阶段偷的懒,最后都会变成评估阶段无法解释的误差。线性回归模型本身没什么玄学,真正让结果翻车的,往往是切分、填充和编码这些不起眼的环节。希望这份项目笔记能帮你在期末周少踩几个坑,把时间花在真正能加分的分析和论证上。

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

返回列表