如果你正在学机器学习,线性回归多半是第一个正式接触的算法。而线性回归里最容易被当成“跑个流程”带过、实际上却决定整套方法上限的,就是损失函数。我可以直接说,很多人学到后面卡在梯度下降、过拟合、甚至看不懂训练日志,根子都在这里没打牢。
这篇文章我会把线性回归中的损失函数从头到尾拆开讲清楚:它到底在做什么、为什么线性回归默认用均方误差、怎么从损失函数出发推导出两种求解参数的路线,再带你把代码跑通、把损失曲线画出来。无论你是刚入门想补基础,还是作业平台上被线性回归实验卡住了,或者训练深度学习模型时想搞懂那些loss曲线到底是什么意思,这篇文章都是按你能直接落地的标准写的。
1. 损失函数到底在解决什么问题
1.1 线性回归模型想表达的本质
先回到最原始的起点。线性回归做的是这样一件事:给你一堆数据点,每个点有若干个特征 x 和一个目标值 y,你想找一个直线(或超平面)来近似描述 x 和 y 之间的关系。比如用广告投放过预测销量,用房子面积预测价格,用学习时长预测考试成绩,本质上都是在做同一件事:找到一条“最能代表数据趋势”的线。
这条线在数学上写作:
y = wx + b
如果是多个特征,就写成:
y = w1x1 + w2x2 + ... + wnxn + b
这里的 w 是权重,b 是偏置。所谓“训练模型”,其实就是找出最适合的一组 w 和 b。问题是:计算机怎么判断一组参数比另一组参数更好?光靠肉眼在图上比划肯定不现实,必须有一个统一的、可量化的标准来告诉机器“你现在的预测到底差了多少”。这个标准,就是损失函数。
1.2 损失函数的本质就是给错误打分
损失函数(Loss Function)可以理解成一把尺子,专门用来度量模型预测值和真实值之间的差距。预测得越准,损失值越小;预测得越离谱,损失值越大。训练过程的目标也就变得非常直接:找一组参数,让这个损失值尽可能小。
我用个生活化的例子解释你立刻就懂。假设你是个弓箭手,靶心是真实值,你的箭落点是预测值。损失函数就是在统计“箭离靶心偏了多少”。如果只射一箭,你直接看偏差就行;但如果你射了一百箭,你总得有个综合指标来评价整体水平,是把所有偏差简单加起来?还是把偏差平方后加起来?还是只关心最大偏差?不同的统计方式,就是不同的损失函数,最后会引导你练出不同的射击习惯。
机器学习里的情况完全一样。模型在训练集上会做很多次预测,每次预测都有一个偏差,损失函数把这些偏差汇总成一个数值。训练算法的任务,就是不断调整参数,让这个汇总数值越来越小。
1.3 训练的本质不是“学知识”,而是“不断调参数降低损失”
有一个常见的误区:很多人觉得训练是让模型“记住知识”。实际上,梯度下降这类优化算法干的事情非常朴素,就是反复做两件事:算一下当前的损失值有多大;朝着让损失更小的方向,微调 w 和 b。
这一过程就像你在山上往下走雾很大,看不清整座山的路,只能靠脚下的坡度判断“往前走是不是在下坡”。损失函数就是告诉你当前高度的仪表,梯度就是告诉你哪个方向下坡最陡。参数更新一步,就看一眼仪表;数值在降,说明方向对了;数值不降反升,说明步子迈大了或者方向错了。
理解了这个逻辑,你再看任何机器学习训练过程,思路都会清晰很多。之前有读者问我,说看代码里model.fit()一下就出结果了,感觉像黑魔法。其实fit内部就是在做“前向计算损失-反向求梯度-更新参数”的循环,核心引擎就是损失函数。
需要模型API调用? 免费领10W Token,多模型网关一键接入 Claude、DeepSeek 等主流模型。
2. 线性回归为什么默认选择均方误差MSE
2.1 用真实例子感受平方带来的惩罚力度
线性回归最常用的损失函数是均方误差(Mean Squared Error,MSE),公式长这样:
L = (1/m) * Σ(yi - ŷi)²
其中 yi 是第 i 个样本的真实值,ŷi 是模型预测值,m 是样本数量。翻译过来就是:把所有样本预测偏差的平方加起来,再取平均。
为什么要平方?我算给你看。假设有两个样本,真实值都是10元,模型A预测为9元和11元,绝对偏差都是1元;模型B预测为8元和12元,绝对偏差都是2元。如果只看绝对偏差之和,模型A总偏差为2,模型B总偏差为4,A确实更好。这个结论没问题,但当模型B有两个小偏差(比如预测为9.5和10.5,绝对偏差0.5+0.5=1)和模型A有一个大偏差(预测为7和10,绝对偏差3+0=3)时,简单求和会看不出差别,而平方后大偏差会被放大成9,小偏差只放大成0.25。这样优化器就会优先去处理那些错得离谱的样本,这在实际训练中非常重要。
平方还有一层含义:误差的惩罚是非线性增长的。预测偏差从0.1变成0.2,损失增加得不明显;但从1变成2,损失会翻4倍。这种“错得越离谱,代价越大”的特性,会让模型不敢在任何一个样本上犯大错,从而整体保持稳定。
2.2 数学层面:MSE的三个硬核优点
选择MSE不只是经验习惯,更是数学推导的结果。
第一,MSE是关于 w 和 b 的凸函数。凸函数的意思可以粗略理解成:损失曲面长得像一个碗,碗底只有一个全局最低点,没有那些坑坑洼洼的局部最低点来捣乱。这就保证了你沿着梯度往下走,最终不管从哪里出发,都能到达同一个最优点。这在线性回归里是极强的性质,很多复杂的模型(比如神经网络)用的损失函数是非凸的,所以训练时特别依赖初始化,容易陷入局部最优。线性回归没有这个烦恼。
第二,MSE处处可导且导数形式简单。求导后得到的形式非常规整,几乎不带任何复杂的运算,这让梯度计算变得极其高效。对比一下绝对误差损失 L = |y - ŷ|,它在误差为0的点不可导,梯度方向在0附近还会剧烈跳变,导致参数更新不稳定。
第三,在高斯噪声假设下,MSE对应最大似然估计。如果你假设样本的真实值等于理想线性函数的输出加上一个独立同分布的高斯噪声,那么用最大似然方法去估计参数,推出来的损失函数恰好就是MSE。也就是说,你默认数据里的随机干扰是“围绕真实值呈钟形分布、大偏差少、小偏差多”的形态时,MSE是最合理的度量方式。
2.3 MSE不是唯一选择,实用场景里还要认识它的兄弟姐妹
既然讲损失函数,就不能只知道MSE。线性回归领域还有几个常在论文或面试里出现的选项,我整理了一张对比表,方便你对照:
| 损失函数 | 公式 | 特点 | 适用场景 |
|---|---|---|---|
| MAE(平均绝对误差) | (1/m)Σ | yi - ŷi | 对离群点不敏感,但在0点不可导 |
| MSE(均方误差) | (1/m)Σ(yi - ŷi)² | 处处可导,放大较大误差 | 默认选择,噪声接近高斯分布时最优 |
| RMSE(均方根误差) | sqrt(MSE) | 量纲与原始数据一致,便于解释 | 需要把误差值直接与y的单位对比时 |
| Huber Loss | 分段函数,误差小时用平方,大时用线性 | 兼顾MSE和MAE的优点 | 数据混合,既有普通噪声又有离群点 |
这里说一个实际工程中常见的误区:很多初学者把RMSE当成一个独立的新损失函数,其实它只是对MSE取了根号,并不改变最优参数的求解结果。因为开根号是单调递增运算,能让MSE变小的参数一定也能让RMSE变小。RMSE的实际价值是让误差的数值回到原始量纲,方便向非技术角色解释模型效果,而不是用来改变训练过程的。
另外提一句Huber Loss,它的思想很实用:当误差小于某个阈值δ时,用平方惩罚精细调节;误差大于δ时改用线性惩罚,避免离群点把模型带偏。如果你做回归任务发现MSE训练出来的模型被少量异常数据干扰得厉害,可以试试Huber Loss,参数δ一般取残差标准差的1倍左右,然后手动调一调。
3. 让损失变小:三条可行的优化路线
3.1 损失函数到最优参数的推导思路
有了损失函数这个目标之后,接下来要回答“怎么找到让损失最小的参数”。线性回归的特殊之处在于,它的损失函数形式足够简单,可以用解析的方法直接算出来,也可以通过迭代的方法逐步逼近。我先以只有一个特征的简单回归为例,把推导过程走一遍。
假设损失函数写作 L = (1/m)Σ(wxi + b - yi)²。
要让L最小,按微积分的基本思想,对w和b分别求偏导,令偏导等于0,解这个方程组:
∂L/∂w = (2/m)Σxi(wxi + b - yi) = 0
∂L/∂b = (2/m)Σ(wxi + b - yi) = 0
把第二个式子整理一下会得到一个漂亮的关系:b = ȳ - wx̄,也就是说,最优直线必然穿过样本均值点 (x̄, ȳ)。再把b代回第一个式子,就能解出w的闭式表达式。这个过程本质上叫最小二乘法,它和MSE是配套出现的,因为“平方”恰好让求导变得很简单,解方程变成了纯粹的代数运算。
3.2 多特征场景下的正规方程与NumPy实现
实际数据通常不止一个特征,这时候用矩阵运会方便得多。把每个样本的特征排成矩阵 X,每行是一个样本,每列是一个特征,把目标值排成向量 y,权重和偏置合并成一个参数向量 θ,那么损失函数可以写成矩阵形式:
L(θ) = (1/m) ||Xθ - y||²
对这个向量表达式求导并令导数为零,能得到著名的正规方程:
θ = (XᵀX)⁻¹Xᵀy
意思是说,线性回归的最优参数可以直接用矩阵运算一步算出来,根本不需要像神经网络那样反复迭代。我平时在代码里会按下面这种方式实现,并加上一点数值稳定的处理:
python复制import numpy as np
def linear_regression_normal_equation(X, y, lambda_reg=0.0):
"""
正规方程求解线性回归参数
X: shape (m, n),m为样本数,n为特征数
y: shape (m,)
lambda_reg: L2正则化系数,默认为0,即不加正则
"""
m, n = X.shape
# 在X前面加一列1,用来吸收偏置b
X_b = np.c_[np.ones((m, 1)), X]
# 构造单位矩阵,注意第一行第一列不惩罚偏置
I = np.eye(n + 1)
I[0, 0] = 0
# 套用正规方程公式,加lambda_reg*I是为了防止X^T X不可逆
theta = np.linalg.inv(X_b.T @ X_b + lambda_reg * I) @ X_b.T @ y
return theta
这里有一个极易踩的坑:如果直接用 θ = (XᵀX)⁻¹Xᵀy,当特征数量大于样本数量、或者特征之间存在高度相关性时,XᵀX 会是奇异矩阵,求逆直接报错。即使不报错,算出来的参数也会大得离谱。加一个很小的 lambda_reg(比如1e-6)到对角线上,也就是做岭回归的变体,能有效稳定计算。它是工程实现上的常用套路,正规方程版的代码应该默认加上。
3.3 没有闭式解时的标准做法:梯度下降
正规方程一步到位看起来很美好,但遇到特征数量很多(比如几万维)时,计算 XᵀX 的逆矩阵的开销非常大,时间复杂度大约是 O(n³)。更通用的是用梯度下降法逐步逼近最优解。先写出MSE对参数的梯度:
∂L/∂w = (2/m)Σxi(wxi + b - yi)
∂L/∂b = (2/m)Σ(wxi + b - yi)
然后按这个公式更新参数:
w = w - learning_rate * (2/m)Σxi(wxi + b - yi)
b = b - learning_rate * (2/m)Σ(wxi + b - yi)
写成一个可运行的Python版本:
python复制def gradient_descent(X, y, learning_rate=0.01, epochs=1000):
m, n = X.shape
X_b = np.c_[np.ones((m, 1)), X] # 加一列1对应偏置
theta = np.zeros(n + 1)
loss_history = []
for epoch in range(epochs):
# 预测并计算当前损失
y_pred = X_b @ theta
loss = np.mean((y_pred - y) ** 2)
loss_history.append(loss)
# 计算梯度并更新
gradient = (2 / m) * X_b.T @ (y_pred - y)
theta -= learning_rate * gradient
return theta, loss_history
这里的一个关键设计决策是把偏置 b 并入参数向量 θ,在 X 前加了一列全1。这样做代码更简洁,矩阵运算可以一次完成全部参数的梯度计算。它的实际效果是在特征空间里增加了一个取值恒为1的维度,模型会自动学习出这个维度的权重,等价于偏置。
3.4 正规方程和梯度下降到底选哪个
这个问题被问过太多次,我给你一个可以直接用的判断标准:
| 条件 | 推荐方案 |
|---|---|
| 特征数量少于1万,样本数量适中 | 正规方程,一步到位,不用调学习率 |
| 特征数量非常大,比如图像、文本等高维场景 | 梯度下降,因为矩阵求逆不可承受 |
| 样本数量极大,内存装不下完整矩阵 | 梯度下降(或mini-batch梯度下降) |
| 需要在线更新模型,数据陆续到达 | 梯度下降,正规方程要全部数据重算 |
| 特征存在严重多重共线性 | 正规方程加L2正则,或改用梯度下降配合正则化 |
说到底,线性回归本身是个很简单的问题,选择哪种优化方式取决于你对计算资源和使用场景的限制。但在学习过程中,我建议你至少手写一次梯度下降,不是为了效率,而是为了理解:训练神经网络时看到的那一堆参数更新细节,本质上和这段30行的代码没有任何区别。
4. Python和Sklearn混合实操:从0到1跑通全流程
4.1 准备一个能复现的数据集
很多人学到这里就卡在“拿什么数据练手”。我建议你不用一上来就上Kaggle那种复杂数据集,线性回归更适合用带明显线性趋势的模拟数据来理解行为。这里我生成一个 y = 4x + 3 的数据,再加一点高斯噪声,模拟真实场景中不会给你完美直线的情况:
python复制import numpy as np
import matplotlib.pyplot as plt
# 固定随机种子,保证结果可以复现
np.random.seed(42)
X = np.random.rand(100, 1) * 4 # 特征范围0~4
true_w = 4.0
true_b = 3.0
y = true_w * X.flatten() + true_b + np.random.randn(100) * 1.5
噪声标准差设成1.5,相对于真实系数4来说属于“肉眼能看出趋势但点比较散”的程度,非常适合观察损失曲线收敛过程。
4.2 手写MSE与梯度下降,把每一步损失都记下来
下面的代码会把梯度下降过程完整走一遍,并把每一轮迭代的损失记录成列表,后面画损失曲线用:
python复制def mse_loss(y_true, y_pred):
return np.mean((y_true - y_pred) ** 2)
def gradient_descent_with_history(X, y, learning_rate=0.05, epochs=200):
m = len(X)
X_b = np.c_[np.ones((m, 1)), X]
theta = np.zeros(2)
loss_history = []
for epoch in range(epochs):
y_pred = X_b @ theta
loss = mse_loss(y, y_pred)
loss_history.append(loss)
# 计算梯度,n=1时就是2个参数的梯度
gradient = (2 / m) * X_b.T @ (y_pred - y)
theta -= learning_rate * gradient
return theta, loss_history
theta, loss_hist = gradient_descent_with_history(X, y)
print(f"手写梯度下降得到的参数: w={theta[1]:.4f}, b={theta[0]:.4f}")
print(f"真实参数: w=4.0, b=3.0")
print(f"最后50轮的损失均值: {np.mean(loss_hist[-50:]):.6f}")
一个容易忽略的细节是学习率的选择。上面代码里我用了0.05,这个值对这份数据是合适的。如果learning_rate设成0.5,loss不仅不会下降,还会震荡甚至爆炸。原因在于梯度里带了 (2/m) 这个系数,当m=100时梯度大约是0.02倍的残差和,学习率太大就相当于下山时每一步都迈过了山谷,直接在两边崖壁之间反弹。
4.3 用Sklearn做交叉验证,和手写版本对照
手写版本理解原理,实际干活还是得用Sklearn。它的LinearRegression类在底层用的也是最小二乘法,但经过高度优化,能处理一些退化情况,接口也极其方便:
python复制from sklearn.linear_model import LinearRegression
from sklearn.model_selection import train_test_split
from sklearn.metrics import mean_squared_error
# 划分训练集和测试集,比例7:3,shuffle打乱顺序
X_train, X_test, y_train, y_test = train_test_split(
X, y, test_size=0.3, random_state=42
)
# 创建并训练模型
model = LinearRegression()
model.fit(X_train, y_train)
# 在测试集上评估
y_pred = model.predict(X_test)
test_mse = mean_squared_error(y_test, y_pred)
print(f"Sklearn得到的参数: w={model.coef_[0]:.4f}, b={model.intercept_:.4f}")
print(f"测试集MSE: {test_mse:.6f}")
这里引入训练集和测试集的划分非常关键。如果只用同一份数据来训练又用同一份数据评估,你看到的MSE会特别乐观,因为模型已经在那份数据上做过优化了。用测试集评估才能反映模型面对没见过的数据时的真实表现。这就是机器学习里“不要自己考自己”的原则。
4.4 所谓“Python和Sklearn混合版”是怎么个混法
有些人会把代码写成手写数据结构加Sklearn评估器混用的形式,比如用numpy自己做特征工程,用Pandas做数据清洗,用Sklearn做模型训练,再回过来用numpy计算自定义指标。这种“混合版”在实际项目里很常见,因为没有一个库能包办所有事情。
我自己常用的混合工作流长这样:
python复制import pandas as pd
from sklearn.preprocessing import StandardScaler
from sklearn.pipeline import make_pipeline
# 1. 假设原始数据在一个DataFrame里,先做特征工程
df = pd.DataFrame({'feature': X.flatten(), 'target': y})
df['feature_squared'] = df['feature'] ** 2 # 如果怀疑有非线性关系可以加
# 2. 写一个简单的手工特征处理函数
def add_interaction(df):
df = df.copy()
df['feature_cubic'] = df['feature'] ** 3
return df
df = add_interaction(df)
# 3. 用Sklearn的Pipeline串联标准化和线性回归
# 注意:特征做了平方立方后量纲差异巨大,必须先标准化
pipeline = make_pipeline(
StandardScaler(),
LinearRegression()
)
features = ['feature', 'feature_squared', 'feature_cubic']
pipeline.fit(df[features], df['target'])
这样写的意义在于:手写部分负责灵活扩展特征,Sklearn负责稳定高效的模型求解,你需要监控或解释的部分自己控制。很多在线实验平台里的线性回归题目,其实就是为了让你实践这种“自由做特征、标准化、训练、评测”的完整流程,而不是让你死记某一个函数调用。
5. 损失曲线图:让训练过程自己说话
5.1 只知道最终损失远远不够
训练模型时,最后打印出来的那个数字只代表终点状态。更重要的信息藏在“损失是怎么一步步降下来的”这个过程里。损失曲线的形状能告诉你:模型是否正常收敛、学习率是否合适、是否出现过拟合、训练是否提前停止。
我看过太多新人,训练完就只汇报一个最终Loss,问他训练过程怎么样,完全答不上来。如果你画出损失曲线,很多问题一眼就能看出来:曲线一路平滑下降,说明训练状态健康;曲线下降后在某个值附近来回震荡,说明学习率偏大或者数据噪声太强;曲线先降后升,说明已经过拟合了,模型开始死记训练集上的噪声。损失曲线就是训练过程的体温计和心电图,只看结果不看过程,等于没做训练监控。
5.2 用Matplotlib把训练过程的损失画出来
接着4.2节的代码,loss_hist列表就是每一轮迭代的损失值,画图只需要几行:
python复制import matplotlib.pyplot as plt
plt.figure(figsize=(8, 5))
plt.plot(range(1, len(loss_hist) + 1), loss_hist, color='#2c7fb8', linewidth=2)
plt.xlabel('Epoch')
plt.ylabel('MSE Loss')
plt.title('Linear Regression Training Loss Curve')
plt.grid(True, alpha=0.3)
plt.show()
这段代码画出来的曲线应该呈现快速下降然后逐渐平缓的趋势。前10轮损失可能会从初始值大幅下降,那是因为初始参数是0,预测值离真实值比较远,梯度幅度大,参数更新幅度也大;越往后梯度越小,曲线越平缓,逐渐贴近理论最小值。
画图时有个细节值得注意:不要用点线图去连每一轮的loss,直接画实线就好,否则曲线会很乱。如果训练轮数特别多(比如几千轮),可以每10轮或每50轮记录一次loss再画图,否则数据点太多图像会糊成一团。
5.3 从线性回归的loss曲线到深度学习里的loss曲线
很多人搜“yolov8画损失函数曲线图”,其实是因为深度学习训练时代打印出来的loss看不懂,想把loss值记录下来画成图,通过曲线判断训练状态。虽然yolov8是个目标检测模型,它的损失由边界框损失、分类损失等多个部分组成,但读曲线的方法论和线性回归完全一致。
我强烈建议你在做线性回归时就养成“记录并绘制损失曲线”的习惯。具体做法就是上面代码演示的:训练循环里每轮把loss存到一个列表,训练完一次性画出来。这套思路可以直接平移到深度学习训练脚本里,只不过把循环从自定义的梯度下降换成了model.fit(),把loss从每轮手动计算换成了训练日志里回调返回。
之前我带项目时,经常要求新人在训练任何模型时先打出一张损失曲线再讨论效果。理由很简单:如果损失曲线都不健康,后面那些精确率、召回率再好也是假的。先看曲线是不是平稳收敛,再看最终指标。
5.4 一次真实的loss曲线解读实录
假设你跑出了这样一组loss数据,前50轮从5.2降到1.8,然后开始缓慢下降,到200轮时降到1.2,但最后50轮曲线出现轻微上升,测试集loss反而比训练集高出一截。这个现象该怎么解读?
前半段快速下降是正常收敛;但最后50轮的上升说明优化器在训练集上已经找不到更好的点了,开始把参数往“过度适配训练集噪声”的方向推。这时测试集loss会先降后升,形成一个V字形,这就是典型的过拟合信号。对应线性回归场景,特征数量太多且没有正则化时容易出现这种问题,解决方案包括增加L2正则(岭回归)、减少特征数量、或者用更多样本来训练。
6. 常见问题与排查技巧实录
6.1 高频问题速查表
以下这些问题来自很多人在学习线性回归损失函数时问过的真实场景,我整理成了一张速查表:
| 现象 | 可能原因 | 解决办法 |
|---|---|---|
| 损失值初始就很大且下降极慢 | 特征没有做标准化,导致梯度方向不稳定 | 用StandardScaler对每个特征做标准化 |
| 损失曲线震荡不下降 | 学习率过大 | 把learning_rate调小10倍甚至100倍重试 |
| 损失降到一定程度就不再下降 | 模型容量不足,或已经接近理论最优 | 增加特征;如果确认接近最优就停止训练 |
| 训练loss很低但测试loss很高 | 过拟合 | 加L2正则、减少特征或增加训练数据 |
| 正规方程报错说矩阵不可逆 | XᵀX为奇异矩阵 | 加一点点L2正则(比如lambda=1e-6)来稳定求逆 |
| 损失函数是负数 | 用了带log的损失且y的取值范围不在(0,1)内,一般是分类损失错用在回归上 | 回归任务用MSE/MAE,不要用交叉熵 |
| 梯度爆炸,损失变成inf | 学习率太大或特征取值极端 | 标准化数据、降低学习率、加梯度裁剪 |
6.2 新手最容易犯的三个实操错误
第一个错误是没有划分训练集和测试集就评估模型。有人用全部数据训练,又用全部数据算MSE,得到一个很漂亮的数字,就以为模型效果很好。实际上模型已经“见过”这些数据了,这个评估结果不能代表泛化能力。线性回归还好,到深度学习里这种错误会带来灾难性的误判。
第二个错误是忽略特征标准化,直接套用梯度下降。如果特征A的取值范围是0到1,特征B的取值范围是0到10000,那么特征B对梯度的贡献会远远大于特征A,导致优化路径歪歪扭扭,loss下降得非常慢。正规方程对特征尺度相对不敏感,但梯度下降必须要做标准化,否则你会看到loss像毛毛虫一样蠕动半天也不收敛。
第三个错误是只盯着MSE的绝对值,却不看它与y本身量纲的关系。假设你在预测房价,MSE等于40000,听起来特别大;但如果房价动辄几十万上百万,40000的MSE对应的RMSE是200,说明预测平均偏差200块,其实是很好的结果。评估损失时一定要结合RMSE把量纲还原回去,不要被平方后的数字吓到。
6.3 给已经懂理论的人一些进阶建议
如果你已经把上面代码跑通了,我建议你做三个延伸实验来加深理解。
第一个实验是画不同学习率下的损失曲线。把learning_rate分别设成0.5、0.05、0.005、0.0005,把四条损失曲线画在一张图上。0.5会震荡甚至发散,0.05会平滑下降,0.005下降缓慢但稳定,0.0005在200轮里几乎没怎么下降。做完这个实验,你对“学习率到底影响什么”会有刻骨铭心的理解。
第二个实验是在同一份数据上比较MSE和MAE的行为。找一两个故意设置成离群点的样本,分别用MSE和MAE做损失函数训练,看看谁受离群点影响大。你会发现MSE被离群点拖动的幅度明显大于MAE,这正是平方惩罚对极端误差高度敏感的体现。
第三个实验是给正规方程加上L2正则,观察不同 lambda 对参数大小的影响。把lambda从0开始逐渐增大,你会发现参数的绝对值整体变小,这就是岭回归的收缩效应。理解了这个,以后看Lasso、ElasticNet都会轻松很多。
结尾前再分享一个我的习惯
最后分享一个实际操作中我认为最有价值的习惯:无论训练什么模型,永远把损失曲线当作第一诊断工具,而不是只盯着最后那个数字。我见过太多人花了几个小时调模型,最后才发现问题出在数据没标准化上,而如果训练一开始就画了loss曲线,一眼就能看出梯度下降走得歪歪扭扭。
很多在线平台上的线性回归题目,不会直接告诉你答案,而是在你调试的过程中通过现象让你理解原理。这篇内容本身也是尽量模拟这个过程。你可以按上面的代码顺序跑一遍,观察每一次修改对损失曲线带来的变化,这种从“为什么损失函数长这样”到“损失曲线告诉我模型哪里有问题”的思维链路,会比背住任何公式都更值钱。
