上一章,我们构建了第一个神经网络模型,并用它预测了冰激凌的销量。但这个预测靠不靠谱,我们还不知道。
想知道预测是否准确,首先需要能够量化预测的误差。这正是损失函数(Loss Function)的作用:衡量模型预测值与真实值之间的差距。
损失函数是模型训练的核心。它不仅告诉我们模型“错了多少”,更是指引模型自动改进的信号(我们将在之后的两章进行探讨)。
误差¶
最直接的量化办法就是把预测值和真实值相减,它们之间的差距称为误差(Error):
这里:
:真实结果,称为标签(Label);
:网络模型的预测结果,即预测值。
平方误差¶
直接使用误差有一个问题:当我们面对多个样本时,正的误差和负的误差会相互抵消,导致整体误差被低估。在实践中,我们通常改用误差的平方,称为平方误差(Squared Error):
使用平方误差有三个优点:
消除正负号:所有误差都变为正数,避免相互抵消;
惩罚大误差:误差越大,平方后惩罚越重,迫使模型优先修正大错误;
处处可求导:这是下一章使用梯度下降法自动优化模型参数的数学前提。
💡 提示:为什么“处处可导”如此关键?因为梯度下降需要计算损失函数的导数来确定参数调整的方向。如果损失函数在某一点不可导,梯度就无从计算,模型训练就会失败。
均方误差(MSE)¶
当面对多个样本(例如 天的销售数据)时,我们需要一个整体指标来衡量模型的性能。常用的方法是计算所有样本平方误差的平均值,称为均方误差(Mean Squared Error,MSE):
这里:
: 个真实结果;
: 个预测结果。
MSE 越小,说明模型的整体预测越准确。MSE 为 0 意味着模型的预测与真实值完全一致,这在实践中几乎不可能,也不一定是好事(可能意味着过拟合)。
import numpy as np数据¶
现在,让我们回到小明的冰激凌店。
当天晚上,销售数据出炉:一共卖出了 165 个冰激凌。
特征、标签¶
我们把真实结果称为标签,同样用 NumPy 数组来保存。从现在起,我们的每条数据都将由两部分组成:
特征(Feature):输入数据,即天气情况;
标签(Label):对应的真实结果,即实际销量。
feature = np.array([28.1, 58.0])
label = np.array([165])模型¶
我们继续沿用上一章搭建的模型框架(参数和推理函数)。
class Linear:
def __init__(self, in_size, out_size):
self.weight = np.ones((out_size, in_size)) / in_size
self.bias = np.zeros(out_size)
def __call__(self, x):
return self.forward(x)
def forward(self, x):
return x @ self.weight.T + self.bias损失函数(均方误差)¶
损失函数根据预测值 和标签 ,计算出均方误差,结果称为损失值(Loss)。
损失值是一个非负数:
损失值越大,说明预测误差越大,模型越不准确;
损失值越小,说明预测越接近真实值,模型越准确。
class MSELoss:
def __call__(self, p, y):
return self.loss(p, y)
# 损失函数(前向传播的终点)
def loss(self, p, y):
# 损失值
return np.mean(np.square(y - p))建模¶
创建网络模型的实例的同时,我们也需要创建一个损失函数的实例。
model = Linear(2, 1)
loss_fn = MSELoss()推理¶
prediction = model(feature)
print(f'prediction:\t{prediction}')prediction: [43.05]
评估¶
有了损失函数,我们就可以量化模型推理的误差。
loss = loss_fn(prediction, label)
print(f'loss:\t{loss}')loss: 14871.802500000002
损失值约为 14,872。这个数字意味着什么?
由于我们使用的是平方误差,损失值的基准是销量的平方(个²)。它本身不像误差那样直观,但在计算时非常直接:
损失值越小,表示模型越准确;
损失值的变化趋势才是关键:我们的目标是让它在训练学习的过程中不断下降。
模型预测 43 个,真实销量是 165 个,误差高达 122 个。因为我们目前只有一组数据,均方误差就是这组数据的平方误差:,和损失值 14,872 基本就对上了。
我们的第一个模型预测严重偏低。那么,如何让模型变得更准确? 这正是下一章要开始解决的问题。
课后练习¶
尝试修改权重和偏置的数值,看看能否让损失值降低?