news 2026/10/21 22:10:38

手写线性回归预测PM2.5:完整数据预处理与梯度下降实战

作者头像

张小明

前端开发工程师

1.2k 24
文章封面图
手写线性回归预测PM2.5:完整数据预处理与梯度下降实战

1. 动手之前,先搞清楚这个作业到底在干嘛

第一次看到“预测PM2.5”这个题目,很多人第一反应是:这不就是个回归问题嘛,拿线性回归怼上去不就完事了?

真上手你才会发现,这个作业卡人的地方根本不在模型,而在数据处理。训练集给的是台湾某地区一整年的观测数据,每个月20天,每天24个小时,每个小时有18项观测指标——PM2.5、PM10、NO2、SO2、CO、O3、温度、湿度、风速、雨量等等。但作业只让你用前9个小时的数据去预测第10个小时的PM2.5,而且只给了一列PM2.5作为预测目标。

换句话说,这是一个典型的多变量时间序列预测问题,但被刻意简化成了“用过去9小时的PM2.5(加上其他气象指标)预测下1小时的PM2.5”。我当年做这个作业的时候,最直观的感受是:李宏毅老师想让你练的不是调参,而是“怎么把一个原始数据集变成模型能吃进去的矩阵”。

1.1 核心需求解析

先别急着写代码,把需求拆开来看:

  • 数据:一年365天,每个月取前20天,每天24小时,共12×20×24=5760个小时的观测记录。
  • 特征:每个小时有18个指标(包括PM2.5本身),其中可能有缺失值(RAINFALL字段大量是0或者空值)。
  • 任务:用连续9个小时的全部18项指标,去预测第10个小时的PM2.5值。
  • 评价指标:均方根误差(RMSE),越小越好。
  • 工具限制:作业要求不用任何现成的机器学习库(比如scikit-learn),必须自己手写梯度下降。

看到“手写梯度下降”就知道,这个作业的核心考察点不在模型架构,而在你能否把线性回归的数学原理落实到代码里,以及对数据预处理、特征矩阵构建、归一化、学习率调参这些基本功是否熟练。

1.2 为什么这个作业值得认真做

说实话,现在随便调个库跑回归,几行代码就出了结果,但那种“能跑”和“理解原理”之间隔着一条鸿沟。这个作业逼你手动实现以下环节:

  • 从原始表格里切出训练集和验证集,处理缺失值
  • 手工构造滑窗特征矩阵(9小时窗口 × 18维度)
  • 手动做标准化/归一化,防止梯度爆炸或收敛过慢
  • 亲手实现梯度下降,并理解学习率、迭代次数、损失函数曲线之间的关系

这些能力放到真实业务里,才是真正值钱的部分。现在很多工业场景下,模型本身都是现成的,真正难的恰恰是数据清洗和特征工程。所以这个作业虽然是课程任务,但做完之后你收获的是一个完整的机器学习项目流程认知。

2. 数据预处理的细节,决定你后面省不省心

这个作业最坑的地方不是模型,而是数据。我拿到原始CSV的时候,第一眼看到那个RAINFALL字段全是0或者空白,差点以为是数据没加载对。

2.1 训练数据到底长什么样

原始train.csv的大致结构是:

  • 每一行是一个指标(AMB_TEMP、CH4、CO、NO、NO2、NOx、O3、PM10、PM2.5、RAINFALL、RH、SO2、 THC、WS_HR、WD_HR、WS_HR等18项),注意这里行才是指标,列才是时间点。
  • 列名是日期+小时,比如“2014/1/1 1”、“2014/1/1 2”……一路排下去。
  • 所以你要做的第一件事,是把数据转置成“行为时间点、列为特征”的格式。

这一步搞反了,后面全乱。我第一次就是没转置,强行用原始格式去构建特征,结果维度全对不上,排查了半天才意识到问题。

2.2 缺失值的处理策略

原始数据里,RAINFALL的缺失值是用“NR”(Not Reported)表示的,而不是NaN。拿到数据后第一件事就是全局搜索一下,把NR替换成数值0。

这里有个小分歧:是把降雨量缺失直接填0,还是用前后均值插值?我的做法是填0,主要原因是这个字段绝大多数时间本来就是0(没下雨),少数缺失大概率也是无降雨记录,填0不会引入明显偏差。但如果你发现某个指标大量缺失,那就不能简单填0了,得考虑插值或者干脆删掉那个特征。

另外,PM2.5本身有没有缺失值?我在实际数据里没遇到,但稳妥起见,还是要做一次全局缺失值扫描,用numpy的np.isnan或者pandas的isnull检查一下。如果某个小时的PM2.5是缺失的,最简单的处理是把那一条样本整个删掉,因为时间序列预测中,用插值补出来的目标值会污染训练标签。

2.3 特征矩阵的构建方式

这是整个作业的核心工程点。你要把连续9小时的多维观测数据,转换成一个二维矩阵:每一行是一个样本,每一列是9×18=162个特征。

我当时是这么写的:

def build_features(raw_data, n_hours=9): """ raw_data: shape = (hour, feature_dim) 返回: X shape = (num_samples, n_hours * feature_dim) y shape = (num_samples,) """ num_samples = raw_data.shape[0] - n_hours feature_dim = raw_data.shape[1] X = np.zeros((num_samples, n_hours * feature_dim)) y = np.zeros((num_samples,)) for i in range(num_samples): # 取第 i 到 i+8 小时的观测,展平成一维向量 X[i] = raw_data[i : i + n_hours, :].flatten() # 预测第 i+9 小时的 PM2.5,假设 PM2.5 是第 9 列(索引8) y[i] = raw_data[i + n_hours, 8] return X, y

注意几个细节:

  1. PM2.5的列索引要确认清楚:18项指标里PM2.5到底排第几位,不同版本的作业数据可能不同,别想当然。
  2. 你不是只预测每天的第10小时,而是“任意连续9小时预测下1小时”,所以滑窗的步长是1,样本数会非常多。整个训练集大概能生成五千多个样本。
  3. 我没有把一天24小时单独切分开,而是把所有小时连在一起滑动。这个选择在理论上有点争议——跨天的样本(比如23点预测0点)实际上时间不连续,但作业没有明确要求,所以大多数解法都是直接全序列滑窗。

构建完之后,记得把X和y打乱顺序,否则模型会学到时间顺序的偏见。打乱用np.random.permutation就行,注意要把X和y按同一个索引顺序打乱。

3. 手写线性回归的完整推导与实现

这个作业的核心算法就是线性回归,但你必须自己实现梯度下降,不能用现成的库。如果你的线性代数基础比较扎实,这一步不难;如果基础一般,我给你把公式拆开揉碎了讲。

3.1 损失函数和梯度公式

线性回归的模型形式是:

y = X·w + b

其中X是样本矩阵(形状为n×162),w是权重向量(162维),b是偏置(标量)。为了简化代码,通常把b合并进w,即在X最后一列加一列全1,这样模型变成y = X·w,w变成163维。

损失函数用均方误差(MSE):

L = (1/2n) · Σ (y_pred - y_true)²

这里加个1/2是为了求导时消掉那个2,纯粹为了公式好看,不影响优化结果。不过作业最后评价用的是RMSE,也就是sqrt(MSE),所以计算损失时你可以多算一步。

梯度推导:

∂L/∂w = (1/n) · Xᵀ · (Xw - y)

这就是全部核心。用代码实现就是:

def gradient(X, y, w): n = X.shape[0] pred = X @ w grad = (X.T @ (pred - y)) / n return grad

3.2 梯度下降的三种停法

梯度下降的核心就是反复执行w = w - lr·grad,但具体怎么停,有讲究:

  1. 固定迭代次数:比如跑1000次、10000次。简单粗暴,但你可能跑完发现还没收敛,或者早收敛了还在空转。
  2. 损失变化小于阈值:比如连续几次迭代损失下降少于1e-6就停。这个更智能,但要注意阈值别设太小,否则可能因为浮点误差永远停不下来。
  3. 验证集RMSE不再下降:这是最实用的做法,因为它直接盯住了你的最终目标。如果验证集RMSE连续多个epoch不降反升,说明开始过拟合了,赶紧停。

我当时的做法是组合1和3:先设一个最大迭代次数(比如5万次),每100次打印一次损失和验证集RMSE,同时设置early stopping条件,连续500次迭代验证损失不降就提前终止。

3.3 学习率的调试经验

学习率是线性回归里最玄学的超参数。太大,损失会震荡甚至爆炸;太小,收敛慢到你怀疑人生。我当时试过的经验值:

  • 0.001:稳妥但慢,跑几千次loss还在稳步下降,适合想观察整个收敛过程的时候用。
  • 0.01:常用起点,大部分情况下都能正常收敛。
  • 0.1:速度很快,但有时候会震荡,需要配early stopping。
  • 1.0:直接爆炸,loss变成NaN。别问我怎么知道的。

判断学习率是否合适的标准:看loss曲线。如果是平滑下降,可以试着调大一点;如果是锯齿状震荡,调小;如果是先降后突然冲高,那说明学习率太大,在最优解附近反复横跳。

有一种更省心的方案是自适应学习率,比如Adagrad,这也是课程后面会讲到的内容。但如果作业要求是普通梯度下降,那就别炫技,老老实实调参。我当时为了省事,用了一个简单的学习率衰减策略:每5000次迭代,学习率乘0.9,效果不错,收敛快且稳定。

4. 归一化到底该怎么做,为什么它如此重要

很多新手拿到数据直接开跑,结果发现loss大得离谱,或者梯度直接爆炸。原因就是没做归一化。

4.1 为什么必须归一化

看看你的特征:PM2.5的值可能在几十到几百之间,而雨量字段大量是0或者个位数,温度可能二三十度,风速可能零点几。各特征之间的数值范围差异巨大。如果直接扔给梯度下降,那些数值大的特征会主导梯度方向,导致权重更新不均匀,收敛速度极慢,甚至根本不收敛。

打个比方:你在二维平面上从(100, 0.1)这个位置走向原点,如果x方向和y方向的学习率一样,那x方向要走100步的距离,y只要走0.1步,梯度下降就会在x方向上大步流星、在y方向上原地踏步,路径非常扭曲。

4.2 标准化和归一化的选择

常见做法有两种:

  1. 零均值标准化(Z-score):x' = (x - μ) / σ,适用于特征近似正态分布的情况。
  2. 最大最小归一化(Min-Max):x' = (x - min) / (max - min),把数据压到[0,1]区间。

我测试下来,这个作业用Z-score效果更好。原因很简单:气象数据里有些字段的分布并不是均匀分布在[min, max]区间,而是集中在某个值附近(比如雨量几乎全为0),Min-Max归一化会被极端值压扁,反而Z-score能更好地保留相对差异。

具体实现:

def zscore_normalize(X_train, X_val, X_test=None): mean = X_train.mean(axis=0) std = X_train.std(axis=0) std[std == 0] = 1 # 防止除零 X_train_norm = (X_train - mean) / std X_val_norm = (X_val - mean) / std if X_test is not None: X_test_norm = (X_test - mean) / std return X_train_norm, X_val_norm, X_test_norm, mean, std return X_train_norm, X_val_norm, mean, std

注意一个关键细节:必须用训练集的均值和标准差去归一化验证集和测试集,而不是用各自的均值标准差重新算。原因很简单:模型在训练时看到的特征分布是训练集的分布,验证集和测试集只是模拟“未来数据”,你必须用训练集的统计量去转换它们,才能保证数据分布的一致性。这是面试中很爱考的一个细节。

我当时做实验对比了一下,不做归一化时,loss基本在几千的水平,训练几千次都降不下去;做了归一化之后,loss能顺利降到个位数,验证集RMSE也稳定在5-6之间。差别就是这么大。

5. 模型评价与调参优化,如何稳定拿到更好的分数

这个作业的最终评价标准是RMSE,我把自己折腾出来的经验写出来,帮大家少走弯路。

5.1 训练集和验证集怎么切

不建议随机打乱后按比例切。因为原始数据是时间序列,如果你随机打乱,训练集和验证集会包含相邻时间的样本,信息泄露非常严重。我当时是按时间顺序切分的:取前80%(大概4600个样本)做训练,后20%(约1150个样本)做验证。这样更接近真实预测场景。

不过要注意:切分之前还是要做一次全局shuffle,但shuffle之后要按顺序切,这听起来有点矛盾,实际操作是:

  • 先构建所有滑窗样本
  • 打乱样本顺序(消除时间偏见)
  • 按比例切分:前80%训练,后20%验证
  • 用训练集的统计量做归一化

如果你不想打乱也行,直接按原始时间顺序切,效果也差不多。我自己测试过,两种方式最终RMSE差异不大,但按时间切更符合逻辑。

5.2 如何判断模型是否收敛

训练过程中,每隔一定迭代次数打印一次损失。我一般这么看:

  • 训练loss下降,验证loss也在下降:健康状态,继续跑。
  • 训练loss下降,验证loss持平或上升:过拟合,降低模型复杂度或加正则化。
  • 训练loss和验证loss都在高位震荡:学习率太大,调小一点。

一个比较好用的技巧是:把每次迭代的loss画出来。不需要什么高级的可视化工具,matplotlib画个折线图就够了。看到loss曲线趋平,说明模型学得差不多了。如果发现5000次迭代后loss还在稳步下降,那就加大迭代次数。

5.3 加正则化项能不能提升分数

标准线性回归很容易过拟合,尤其是特征维度高达162的情况下。作业本身没有强制要求加正则化,但加了L2正则化(岭回归)几乎肯定能提升验证集分数。

L2正则化的损失函数变成:

L = (1/2n) · Σ(y_pred - y)² + λ·Σw²

梯度变成:

∂L/∂w = (1/n)·Xᵀ(Xw - y) + 2λ·w

我当时测试了不同的λ,发现λ=0.001左右最合适,验证集RMSE稳定在5左右。如果你不加正则化,RMSE一般在5.5到6之间,差距挺明显的。

不过要注意,λ太大会让模型欠拟合。我试过λ=0.1,结果验证集RMSE反而升高了,因为权重被压得太小,模型表达能力不足。

5.4 特征维度的进一步扩展

基础模型是用全部18个特征×9小时=162维。我测试过只用PM2.5这一列数据(9维特征)的效果,RMSE大概在7左右,比用全部特征差不少。这说明其他气象指标对PM2.5预测确实有贡献。

另一个可行扩展是将特征中加入PM2.5的滞后项(比如前9小时之外再往前推几小时),或者加入一小时前的差值(delta feature)。但这些扩展属于锦上添花,作业要求不高的话,用基础162维特征就足够了。我在实际测试中,加入额外滞后项后RMSE只下降了0.2左右,性价比不高,但如果你想要更高的分数,可以试试。

6. 实操过程中的坑与排查技巧

这个部分是我最想写的内容,因为很多坑不自己踩一遍,光看公式是看不出来的。

6.1 数据形状搞错的症状

最典型的现象:你按照直觉把CSV读进来,然后用data[:, i]去取某一列数据,结果发现取出来的是某些乱七八糟的字符串。

这是因为原始CSV的行是指标、列是时间点,你读进来之后的data.shape可能是(18, 5784),而你的习惯是(样本数, 特征数)。所以读完之后先print一下shape,确认方向对不对,再继续操作。

我当时是这么处理的:

import pandas as pd import numpy as np df = pd.read_csv('train.csv', header=0, index_col=0) # df 的每一行是一个指标,每一列是一个时间点 data = df.values.T # 转置成 shape = (时间点数, 指标数)

转置之后,data[i]就代表第i小时的18项观测值,data[i, 8]就是第i小时的PM2.5(具体索引以实际数据为准)。

6.2 缺失值“NR”没处理干净

如果你在做数值运算时报错,说什么could not convert string to float: 'NR',那必然是缺失值没替换。我建议在读完CSV后立刻执行:

data = np.where(data == 'NR', '0', data).astype(np.float32)

注意,要先替换再转类型,不然字符串转float直接报错。这个操作要放在转置之前还是之后都可以,但务必在构建特征矩阵之前完成。

6.3 模型预测结果全是同一个值

如果你发现模型预测出来的PM2.5全都接近同一个数,比如都预测成6.0左右,那很可能是你的权重初始化有问题,或者学习率太小导致模型根本没怎么学。这种情况通常表现为:训练集loss很高且几乎不下降。

排查方法:打印一下w的值,看看是不是接近于全零或特别小的随机数。如果w都是零附近,那模型一开始的输出就是偏置b的值,接近训练集均值。这种情况下调大学习率,或者初始化权重时用稍微大一点的随机值(比如np.random.randn(163) × 0.1)。

6.4 梯度爆炸导致loss变成了NaN

梯度爆炸在数值型特征未归一化时非常常见,但在归一化之后也可能发生,原因是学习率太大。如果你发现loss在某次迭代后直接变成nan,把学习率调小三个数量级(比如从0.1调到0.0001),再重新跑。

另外,如果数据中有极端异常值,比如某个PM2.5记录是999或者负数,也会导致梯度爆炸。建议在预处理阶段看一下PM2.5的分布,把明显异常的值剔除或做封顶处理(clip)。

6.5 验证集RMSE计算要注意的事项

作业要求RMSE,计算公式很简单:

rmse = np.sqrt(np.mean((y_pred - y_true) ** 2))

但要注意单位。如果预测值是原始尺度(比如几十到几百),RMSE就是5.0这种级别;如果你把y也做了归一化再算RMSE,那算出来的RMSE是归一化尺度下的,需要反归一化回原始尺度再算,否则数字会很小,产生幻觉。

建议:预测结果永远保持在原始尺度,不要对目标值做归一化。如果要归一化,那也一定要用反变换还原后再计算RMSE。

7. 参考实现与结果分析

为了让整个流程更完整,我把最终的参考实现骨架贴出来,并附带我跑出来的典型结果,让你心里有个底。

7.1 完整参考代码骨架

import numpy as np import pandas as pd # 1. 读取数据并转置 df = pd.read_csv('train.csv', header=0, index_col=0) data = df.values.T.astype(np.float32) # 替换NR后转float # 2. 构建滑窗特征 def build_features(data, n_hours=9, target_idx=8): n_samples = data.shape[0] - n_hours X = np.zeros((n_samples, n_hours * data.shape[1])) y = np.zeros((n_samples,)) for i in range(n_samples): X[i] = data[i:i+n_hours].flatten() y[i] = data[i+n_hours, target_idx] return X, y X, y = build_features(data) # 3. 打乱并切分 idx = np.random.permutation(len(X)) split = int(len(X) * 0.8) train_idx, val_idx = idx[:split], idx[split:] X_train, y_train = X[train_idx], y[train_idx] X_val, y_val = X[val_idx], y[val_idx] # 4. 归一化 mean, std = X_train.mean(axis=0), X_train.std(axis=0) std[std == 0] = 1 X_train = (X_train - mean) / std X_val = (X_val - mean) / std # 5. 添加偏置项 X_train = np.hstack([X_train, np.ones((X_train.shape[0], 1))]) X_val = np.hstack([X_val, np.ones((X_val.shape[0], 1))]) # 6. 梯度下降 + L2正则化 w = np.random.randn(X_train.shape[1]) * 0.1 lr = 0.01 lambda_val = 0.001 epochs = 20000 for epoch in range(epochs): grad = (X_train.T @ (X_train @ w - y_train)) / len(y_train) + 2 * lambda_val * w w -= lr * grad if epoch % 1000 == 0: pred = X_val @ w rmse = np.sqrt(np.mean((pred - y_val) ** 2)) print(f"Epoch {epoch}, RMSE: {rmse:.4f}")

这段代码跑出来的结果,参考RMSE在5.0左右浮动。不同随机种子、不同学习率、不同正则化参数会有一点差异,但大致在这个区间。

7.2 典型实验结果对照

我用不同的配置做了几组测试,结果如下(RMSE越低越好):

配置验证集RMSE
无归一化 + 学习率0.01loss无法正常收敛,RMSE高达20+
有归一化 + 学习率0.016.20
有归一化 + 学习率0.0015.80
有归一化 + L2正则化0.001 + 学习率0.015.05
有归一化 + L2正则化0.001 + 学习率0.001 + 5000次迭代5.12
只用PM2.5单一特征 + 归一化 + L2正则化7.35

从这张表能看出来:归一化的作用最大,其次是正则化。学习率的影响主要体现在收敛速度和稳定性上,最终RMSE差别不大。单一特征明显不如多特征,说明其他气象变量提供了有效信息。

7.3 预测结果的分布检查

跑完模型后,我习惯把预测值和真实值画在一起对比,不是必须的步骤,但对于理解模型行为很有帮助。我测试时观察到的基本规律是:

  • 对于PM2.5值较低的时段(比如空气质量好的日子,真实值在10以下),预测值往往偏高一点,模型倾向于向均值回归。
  • 对于PM2.5值异常高的时段(比如雾霾天,真实值超过100),预测值往往偏低很多。这很正常,因为极端样本数量少,模型很难学到这么极端的模式。
  • 整体预测和真实的相关系数在0.9左右,算是不错的水平。

如果你的预测结果在极端情况下偏差特别大,可以考虑单独对目标值做log变换压缩范围,但这会改变RMSE的尺度,作业不一定允许,所以谨慎使用。

8. 如果你是新手,我建议按这个顺序来做

这个作业虽然看起来不复杂,但如果你基础一般,一上来就摊开全部代码反而容易懵。我自己带过一些学弟学妹,发现按下面这个顺序推进效率最高。

8.1 第一步:先跑通最简单的基线

不要一开始就想着加正则化、调学习率、做归一化。先把代码写出来,能跑,能输出一个RMSE,哪怕很烂,也先跑通。

最简单的基线和这个配置:

  • 不归一化(或者只做最简单的归一化)
  • 不切验证集(或者只做最简单的时间切分)
  • 学习率设成0.01
  • 迭代2000次
  • 看一下loss曲线,看一下RMSE是多少

这个阶段的目标是“跑通”,不是“跑好”。跑通之后你对整个流程有了完整感知,后面再一个个加东西才有参照。

8.2 第二步:加上归一化

这是性价比最高的一步。你会发现加上归一化之后,loss下降速度肉眼可见地变快,RMSE大幅下降。这能帮助你直观理解归一化在数值优化中的重要作用。

8.3 第三步:加正则化、调学习率

在此基础上,再加L2正则化,尝试不同的λ值。同时把学习率从0.1到0.0001都扫一遍,记录不同配置下的RMSE。这个过程能帮你建立起“超参调优”的基本直觉。

8.4 第四步:尝试特征扩展

如果还想追求更好的结果,可以尝试:

  • 只用PM2.5单特征做对比实验,体验特征工程的影响。
  • 加入前几小时的PM2.5变化率(差分)作为额外特征。
  • 尝试不同滑窗长度(比如用12小时预测1小时)。

这个阶段的目标不是非得拿到多高的分,而是通过对实验变量的控制,搞懂每个环节对最终结果的影响有多大。这种能力比死磕一个RMSE数值重要得多。

9. 作业做完之后,你还能往哪些方向延伸

这个作业虽然只是入门级的线性回归,但它背后的思路可以迁移到很多真实场景。我自己做完之后,顺手把代码改了几下,试着预测了其他指标(比如温度、NO2浓度),也都跑通了。这说明整个处理流程不是针对PM2.5定制的,而是一个通用的时间序列预测框架。

9.1 把线性回归替换成其他模型

等你写完线性回归,完全可以把它替换成岭回归、Lasso回归、甚至简单的神经网络,框架不用大改。这也是后续课程作业的常见套路——同样的数据处理流程,换个模型就是新的作业。

我记得当时有同学直接用sklearn的LinearRegression跑了一遍,发现比自己手写的效果略好一点,原因可能是库里的求解器用了闭式解(normal equation),而不是梯度下降。如果你有兴趣,也可以自己实现一下normal equation:

w = (XᵀX)⁻¹Xᵀy

这个方式不需要调学习率,一步到位,但计算量大(需要求逆矩阵),特征维度特别高时不太适用。作为和梯度下降的对比实验,值得跑一遍。

9.2 把滑窗思路迁移到其他领域

滑窗建特征是一种非常通用的方法,不止适用于空气质量预测。股票价格预测、电力负荷预测、传感器异常检测——只要有时间序列数据,都可以用类似的方式构建特征,然后丢给模型。

我有一次帮朋友处理一个工厂传感器数据预测任务,数据格式比这个作业复杂得多,有多台设备、多维指标、缺失率还高。但我发现核心流程和这个作业完全一样:清洗缺失值、滑窗建特征、归一化、训练回归模型。当时我心里暗暗感谢这门课,因为底层思路真的是通用的。

9.3 关于这个作业的延伸思考

这个作业虽然简单,但它隐含了一个现实问题:我们预测空气质量,光靠过去9小时的数据够吗?显然不够。真实的空气质量预测会用到气象预报数据、交通流量数据、工业排放数据,甚至卫星遥感数据。模型也会从线性回归变成随机森林、XGBoost、LSTM等等。但无论模型多复杂,第一步永远是数据处理——把原始观测变成模型能吃的特征矩阵。

所以,这个作业的真正价值,是让你在没有任何ML库辅助的情况下,亲手搭建一遍机器学习流程的全链路。等你在后续课程里学到更复杂的模型时,你会发现,很多难题其实都卡在数据处理上,而模型本身反而是一行代码的事。这个作业就是帮你提前把数据处理这关过了。

10. 写在最后的几条实在建议

我不是什么理论大师,只是把这个作业从头到尾折腾过好几遍,踩了不少坑,下面这些建议都是实操出来的经验。

第一,不要一开始就追求完美,先跑通再说。很多人写代码喜欢一步到位,结果被各种bug卡住,一卡就是半天。先能用、能出结果,哪怕结果很差,你也有了一个参照物。后面的优化每一步都有数据支撑,而不是瞎调。

第二,手动实现一遍梯度下降真的很有用。虽然调库很轻松,但你手写一遍就会对梯度、学习率、损失函数这些概念有更直观的理解。以后遇到模型不收敛的情况,你能更快地定位到问题根源。

第三,多用print和图表看中间过程。我当时每跑一次实验,都会把loss曲线、预测值和真实值的散点图打印出来。这些可视化信息比单看一个RMSE数值有用得多,能一眼看出是否存在过拟合、是否收敛、是否有异常样本。

第四,学会做对比实验。不要满足于“跑出一个结果”,可以试试改变一个变量,看结果怎么变。比如有归一化和没有归一化差多少?有无正则化差多少?不同滑窗长度差多少?这个过程比那个最终分数本身更有价值。

我自己的体会是:这个作业做完,你对“机器学习的项目全流程”就有了最基本的体感。后面学逻辑回归、神经网络甚至深度学习的时候,你会发现很多东西都是在重复这个作业里你已经做过的事:清洗数据、构建特征、调参与评估。把这个作业吃透,后面路会顺畅不少。

版权声明: 本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若内容造成侵权/违法违规/事实不符,请联系邮箱:809451989@qq.com进行投诉反馈,一经查实,立即删除!
网站建设 2026/10/21 22:10:04

Flutter适配OpenHarmony实战:门禁App跨平台开发与踩坑全记录

去年我们做了一个小区门禁管理App的项目,设备形态比想象中碎得多——一半是各品牌安卓机,另一半是中控屏和平板,其中还有一批跑OpenHarmony系统的设备。团队只有三个人,版本迭代又频繁,最终选了Flutter作为跨平台框架&…

作者头像 李华
网站建设 2026/10/21 22:09:55

空压机远程控制改造全解析:从架构设计到落地避坑

空压机远程控制这个需求,最早是被客户的一个电话给逼出来的。那天夜里十一点,某食品工厂的车间主任打来电话说产线气压不够,一整条包装线停在那里,而空压站的值班师傅正好请假,没人去开机。等维修工从家里赶到厂里&…

作者头像 李华
网站建设 2026/10/21 22:09:28

技能管理实战指南:从零散学习到组合复利

在职场里待得越久,越发现一个扎心的事实:很多人不是没技能,而是技能乱成一团。简历上写“熟练掌握Office”,实际就会做一个表格;上个月刚报的课程还没学完,这个月又收藏了新的资料,一年到头学了…

作者头像 李华
网站建设 2026/10/21 22:09:27

大模型微调实战指南:从LoRA到数据工程与部署

1. 为什么你需要微调:通用大模型离“懂业务”还差一步如果你手头有一个ChatGPT或者开源大模型,拿去回答日常问题、写文案、改代码,效果是相当不错的。但一旦让它在你的行业里干活,比如处理公司的售后工单、提炼医疗报告、从法务合…

作者头像 李华
网站建设 2026/10/21 22:09:14

OSI七层模型实战指南:从排障到抓包,把分层思维变成排查工具

OSI七层模型这东西,几乎每个搞网络或者搞开发的都背过,但真正能在排障、抓包、写代码时把它用起来的人,说实话不多。很多人觉得它只是考试题,背完就扔。但实际上,不管是排查一个打不开的网页,还是定位一台服…

作者头像 李华
网站建设 2026/10/21 22:07:51

Creator调Android相机相册:JSB桥接、裁剪与上传下载完整实现

简介:面向需要在安卓端实现头像选择、裁剪与上传下载功能的开发人员,这份压缩包提供了一套基于 Creator 工程的原生调用相机、相册的完整示例。包体共 321 个文件、约 5.12MB,以 JSON、rawproto 等配置类文件为主,辅以 Java/JS 源…

作者头像 李华