目录
定义
种类
如何选择损失函数?
平方(均方)损失函数(Mean Squared Error, MSE)
均方根误差
交叉熵
对数损失
笔记回馈
逻辑回归中一些注意事项:
定义
损失函数又叫误差函数、成本函数、代价函数,用来衡量算法的运行情况,用符号L表示。
假设我们的回归函数是:y = wx+b,那么损失函数的作用就是用来获取误差,然后来更新w和b,从而使预测值y更贴近真实值。也就是训练过程就是让这个损失越来越小的过程(最小化损失函数)。
作用:1衡量模型性能 2优化参数(w和b)
种类
均方误差(MSE)、均方根误差(RMSE)、平均绝对误差(MAE)、交叉熵误差、对数损失或逻辑回归损失、Hinge损失、Huber损失、余弦相似损失等。
问题一:为什么这么多损失函数?我该怎么选择?
因为损失函数是用于优化权重的函数,好的损失函数决定了训练的速度和质量,因此人们在损失函数下了很大功夫,做了很多研究,就发明了这么多损失函数,用于解决不同的需求和环境。怎么选择损失函数,其实这些损失函数都能优化权重,你可以随便选择一种,当然你也可以跟据损失函数的优缺点,选择对你任务最优的函数来尝试。
公式乱码了:以下是Huber损失的函数
如何选择损失函数?
选择损失函数时,您可以根据以下几个因素来决定:
-
任务类型:如果是回归问题,常用的损失函数有均方误差(MSE)、均方根误差(RMSE)、平均绝对误差(MAE);如果是分类问题,交叉熵(Cross-Entropy)或对数损失(Log Loss)是常见的选择。
-
数据特征:如果数据中包含异常值,使用均方误差可能会让模型过度关注这些异常值。此时可以考虑使用Huber损失或平均绝对误差(MAE)。
-
模型类型:一些特定的模型(如支持向量机)通常使用特定的损失函数,比如Hinge损失。
-
计算要求:一些损失函数(如交叉熵)可能需要计算概率值,适用于概率模型;而像均方误差则适用于普通的回归模型
如果需要更多学习资料可以打开【咸鱼】https://m.tb.cn/h.6h707H6?tk=rRbqVZ2WNdh HU926
点击链接直接打开,三0元以下带你完成人脸识别、关键点识别、手势识别等多个项目,涵盖SVM、knn、yolov8、语义分割!!!零基础适合小白
上面介绍损失函数的公式以及优缺点,但是大家还是难以有个直观感受,以下希望通过代码的形式让你有个直观体验:
平方(均方)损失函数(Mean Squared Error, MSE)
预测值与真实值的差的平方的n分之一。这个函数不适用于分类问题,而适用于预测的输出值是一个实数值的任务中,所以整体而言我们不会和他打太多交道(但是在强化学习的TD算法里,我们会遇到)
如图所示,假设我们有一条直线,如果我们的数据存在误差(比如一张猫的图片被我们标注成了狗),那么均方损失函数会偏向于惩罚较大的点,赋予了较大权重,如图蓝线靠近了红色圆圈区域。MSE对异常值非常敏感,因为较大的误差会受到更大的惩罚(误差的平方会放大差异)。它通常用于需要精确预测的场景,但可能不适用于异常值较多的数据集。
惩罚的意思就是,平方损失函数在误差较大时,由于他是平方,也就是误差的平方,那么训练的时候那些误差较大的数据会影响训练的效果。训练的模型会过度关注误差。
这里补充个知识点,离群点也就是红色圆圈圈起来的点。
代码:
import numpy as np
import matplotlib.pyplot as plt
x = np.linspace(1, 20, 40)
y = x + [np.random.choice(4) for _ in range(40)]
y[-5:] -= 8
X = np.vstack((np.ones_like(x),x)) # 引入常数项 1
m = X.shape[1]
# 参数初始化
W = np.zeros((1,2))# 迭代训练
num_iter = 20
lr = 0.01
J = []
for i in range(num_iter):y_pred = W.dot(X)loss = 1/(2*m) * np.sum((y-y_pred)**2)J.append(loss)W = W + lr * 1/m * (y-y_pred).dot(X.T)# 作图
y1 = W[0,0] + W[0,1]*1
y2 = W[0,0] + W[0,1]*20
plt.scatter(x, y)
plt.plot([1,20],[y1,y2])
plt.show()
这里只是画出了直线和数据集的表示,并不是拿了损失值优化直线的效果。怎么理解这句话呢,可能需要涉及到epoch概念,就好比我现在画出的直线就是一个模型,然后图中的点就是标签(测试集),可见我们的模型和这些点的距离很大,这个距离就是损失。我们拿着这个损失去优化下一个epoch,也就是下一次迭代中我们的直线就可以更好的拟合这些点。
我想说的是,你要分清损失函数起作用是在那个环节,上图显示的只是损失(真实-预测)的大小,并不是均方损失的大小。就是损失可能一般指两者间的距离,并不是损失函数。我们的MSE需要拿着损失再做一些计算,你不能看着上面的图就说是MSE损失函数,上面的图只是真实值与预测值的差距,也就是损失,并不是损失函数!
下面我们拿均方误差来进行优化w,看迭代20次后,分割的效果。原代码在下面。
可见我们20层后直线和数据的预测出现了较大的分割,为什么?这里我们通过debug模式发现计算后的损失很大,究其原因是一开始y - y_pred差距太大,而y_pred = X.dot(W)也就是第一层的时候w为0导致y_pred 等于0。这里就涉及初始化权重概念,请跳转链接。
这里我们需要先初始化权重,但是初始化后好像并没有解决问题。我们通过debug模式发现gradient很大,导致w变得很大,导致欠拟合了。欠拟合和过拟合是什么,请跳转链接
原先w:-50,b:-643
跳整后w:-27,b:-384
第一层接近很多了,但还是导致了梯度爆炸,什么是梯度爆炸,请跳转链接。
# 初始化参数
W = np.array([2.0, 3.0]) # 手动设定初始权重为 [2, 3]
看我们的预测好多了一点点。为什么一开始文章说需要根据数据集来选择不同的损失函数,这就一个方面,但是我们这里不在损失函数上的选择做文章,我们先通过初始化权重看能不能将直线更好的拟合数据。这里我们通过调整w初始值让gradient趋近于0,也就是第一层的损失接近于0。
W = np.array([1.5, 2.0]) # 调整1
W = np.array([4.0, 2.0]) # 后面再调整
由此可见拟合得很好了,但是这有什么意义呢?通过手动调参来使曲线拟合?首先分析问题出在这个梯度很大导致w变化很大,那么我们需要先解决梯度爆炸问题。
现在我们把权重重新设置为W = np.array([0.0, 0.0]),让模型产生梯度爆炸。梯度爆炸主要可以通过降低学习率、梯度裁剪、初始化权重、使用优化器、正则化、改变模型架构。
当我们将学习率调整到initial_lr = 0.001时发现,即使初始化权重为0可以拟合数据。
那么难道不是初始化权重的问题吗?怎么现在学习率更改了也能拟合曲线。我们寻找本质问题其实是由于损失值过大导致的。如果我们一开始的权重就比较好拟合曲线,那么损失值就不会太大,那么后期即使学习率很大,小损失*大学习率-》优化权重,权重变化不会很大,也就是会一值保持较低的损失。但是我们通过MSE评估模型时,发现损失并不是一直下降的。
正常情况下损失应该越来越小,单调递减的,而不是震荡的。导致震荡的原因就是学习率太大了。因此我们还需要调整学习率。那么我们直接调整学习率为什么也能解决问题,还是根据大损失*小学习率-》优化权重,这样我们的权重也不会出现很大变化,因此小的学习率也可以解决梯度爆炸的问题。
现在我们再来评估初始化权重最本质的作用。
当我们设置正常的学习率0.001
我们对比W = np.array([0.0, 0.0])和W = np.array([4.0, 2.0]) 导致MSE的变化。
发现MSE在一开始就变得很小,不会从700多往下降。也就是好的初始化权重能让模型拟合的更快,但是我们多训练很多层也可以拟合模型,只是有了初始化权重会拟合的更快。
这时候我们再来看这个w0w1,分别是4和2,这和我们制造数据集的公式的两个权重很接近?
y = 2 * x + 5 + np.random.normal(0, 4, size=x.shape) # y = 2x + 5 + 噪声
这个公式的两个权重分别是2和5,我们计算的是2和4,为什么没有完全拟合,这是因为你有噪声。如果我们把噪声去掉呢?
看!多完美的直线哈哈。然后我们也关注到他的损失降低到了0.2左右了,但是我们的w0和w1为什么还是4和2呢?
看起来还没有拟合,我们加多训练层数。当训练层数达到60时,w0变成了4.14了,更靠近5了。70层时达到了4.22。到72层发现损失变大了,75层变得更大,这种现象是什么?这是因为长期损失下降,我们不断增加学习率,导致后期的学习率过大,小梯度在大的学习率下也会时权重变化过大。这里我们加入loss在小于0.16的时候就不增加学习率。
if i > 0 and loss < loss_history[i - 1] and loss > 0.16:
但是我们看损失函数在后期还有较大的波动,我们把loss大于0.17就不会出现了。
# 动态调整学习率if i > 0 and loss < loss_history[i - 1] and loss > 0.17:# 如果损失下降,则增加学习率(但不超过最大学习率)initial_lr = min(initial_lr * lr_adjustment_factor, lr_max)else:# 如果损失没有下降,减少学习率(但不低于最小学习率)if loss < 0.17 and initial_lr < 0.003:initial_lr = 0.003initial_lr = max(initial_lr / lr_adjustment_factor, lr_min)
这时候我们再增加层数到190时,损失能继续下降,然后继续修改层数到2000次时,发现此时的w0达到4.9315,已经相当靠近5了。这差不多也就拟合我们的数据了。
但是我们的数据不可能一直这么完美,我们加入点噪声+ np.random.normal(0, 2, size=x.shape)
我们用提前中断的方式,获得一个好的训练效果。那么我们得到一个判断就是训练层数越多,模型越不稳定。很可能在拟合一定效果后得不到提升,这就要提到残差网络了,这里不涉及。
总结如下:
1损失函数是每一次迭代后,更新权重,因此你每次看到的回归直线和数据集间的差距并不是平均损失,而是预测值和真实值之间的差距。我们的损失函数是拿着个差距来计算,更新下次权重的过程。
2梯度爆炸跟学习率和学习步长有关,当然还有初始化权重、正则化、模型、梯度裁剪等方式相关。我们这里尝试了学习率、权重初始化、梯度裁剪等方式。
3梯度爆炸主要看loss曲线。
4训练层数太深了模型可能并不能得到提升
#源代码
#源代码
import numpy as np
import matplotlib.pyplot as plt# 数据生成
np.random.seed(42)
x = np.linspace(1, 20, 40)
y = 2 * x + 5 + np.random.normal(0, 4, size=x.shape) # y = 2x + 5 + 噪声# 添加常数项
X = np.vstack((np.ones_like(x), x)).T # 引入常数项
m = len(x)# 初始化参数
W = np.zeros(2) # 初始权重# 设置超参数
num_iter = 20 # 迭代次数
initial_lr = 0.01 # 初始学习率
lr_adjustment_factor = 1.05 # 学习率调整因子
lr_min = 0.001 # 最小学习率
lr_max = 0.2 # 最大学习率# 存储损失
loss_history = []# 训练模型
for i in range(num_iter):# 预测y_pred = X.dot(W)# 计算MSE损失loss = np.mean((y - y_pred) ** 2)loss_history.append(loss)# 计算梯度gradient = -2 * X.T.dot(y - y_pred) / m# 更新参数W -= initial_lr * gradient# 动态调整学习率if i > 0 and loss < loss_history[i - 1]:# 如果损失下降,则增加学习率(但不超过最大学习率)initial_lr = min(initial_lr * lr_adjustment_factor, lr_max)else:# 如果损失没有下降,减少学习率(但不低于最小学习率)initial_lr = max(initial_lr / lr_adjustment_factor, lr_min)# 每10步打印一次学习率if i % 10 == 0:print(f"Iteration {i+1}/{num_iter}, Loss: {loss:.4f}, Learning Rate: {initial_lr:.5f}")# 创建两个子图,进行并排显示
fig, ax = plt.subplots(1, 2, figsize=(12, 6))
# 输出最终权重
print(f"Final parameters: W0 = {W[0]}, W1 = {W[1]}")
# 绘制损失曲线
ax[0].plot(loss_history)
ax[0].set_xlabel('Iterations')
ax[0].set_ylabel('Loss (MSE)')
ax[0].set_title('Loss vs Iterations with Adaptive Learning Rate')# 绘制回归效果
y_pred_final = X.dot(W)
ax[1].scatter(x, y, label='Data')
ax[1].plot(x, y_pred_final, color='r', label='Fitted Line')
ax[1].set_xlabel('x')
ax[1].set_ylabel('y')
ax[1].set_title('Linear Regression with Adaptive Learning Rate')
ax[1].legend()plt.tight_layout() # 自动调整子图间的间距
plt.show()
#修改后
#修改后
import numpy as np
import matplotlib.pyplot as plt# 数据生成
np.random.seed(42)
x = np.linspace(1, 20, 40)
y = 2 * x + 5 # y = 2x + 5 + 噪声+ np.random.normal(0, 4, size=x.shape)# 添加常数项
X = np.vstack((np.ones_like(x), x)).T # 引入常数项
m = len(x)# 初始化参数
W = np.array([4.0, 2.0]) # 手动设定初始权重为 [2, 3]
# regularization_strength = 0.01#加入正则化# 设置超参数
num_iter = 2000 # 迭代次数
initial_lr = 0.001 # 初始学习率
lr_adjustment_factor = 1.05 # 学习率调整因子
lr_min = 0.001 # 最小学习率
lr_max = 0.005 # 最大学习率# 存储损失
loss_history = []# 训练模型
for i in range(num_iter):# 预测y_pred = X.dot(W)# 计算MSE损失# loss = np.mean((y - y_pred) ** 2) + regularization_strength * np.sum(W**2)loss = np.mean((y - y_pred) ** 2)#MSE# loss = 1/m * np.sum(np.abs(y-y_pred))#MAE# loss = np.sqrt(1/m * np.sum((y - y_pred)**2))#均方根# epsilon = 1e-10 # 避免log(0)的错误 交叉熵 有误 放弃# loss = -np.mean(y * np.log(y_pred + epsilon))loss_history.append(loss)# 计算梯度gradient = -2 * X.T.dot(y - y_pred) / m# 更新参数W -= initial_lr * gradient# 动态调整学习率if i > 0 and loss < loss_history[i - 1] and loss > 0.17:# 如果损失下降,则增加学习率(但不超过最大学习率)initial_lr = min(initial_lr * lr_adjustment_factor, lr_max)else:# 如果损失没有下降,减少学习率(但不低于最小学习率)if loss < 0.17 and initial_lr < 0.003:initial_lr = 0.003initial_lr = max(initial_lr / lr_adjustment_factor, lr_min)# 每10步打印一次学习率if i % 10 == 0:print(f"Iteration {i+1}/{num_iter}, Loss: {loss:.4f}, Learning Rate: {initial_lr:.5f}")# 创建两个子图,进行并排显示
fig, ax = plt.subplots(1, 2, figsize=(12, 6))
# 输出最终权重
print(f"Final parameters: W0 = {W[0]}, W1 = {W[1]}")
# 绘制损失曲线
ax[0].plot(loss_history)
ax[0].set_xlabel('Iterations')
ax[0].set_ylabel('Loss (MSE)')
ax[0].set_title('Loss vs Iterations with Adaptive Learning Rate')# 绘制回归效果
y_pred_final = X.dot(W)
ax[1].scatter(x, y, label='Data')
ax[1].plot(x, y_pred_final, color='r', label='Fitted Line')
ax[1].set_xlabel('x')
ax[1].set_ylabel('y')
ax[1].set_title('Linear Regression with Adaptive Learning Rate')
ax[1].legend()plt.tight_layout() # 自动调整子图间的间距
plt.show()
平均绝对误差(Mean Absolute Error,MAE):绝对误差(MAE)计算的是预测值与真实值之间差的绝对值的平均值。
相比于均方误差,平均绝对误差对于离群点惩罚力度没有MSE那么强烈,因为MSE没有进行平方,而MAE是对误差进行了平方。你可以将MSE直接替换成MAE,然后看一下最后的训练效果,我的2000层最后达到了4.9972,比MSE更接近。我们从损失值来判断两个函数的效果,发现MAE在训练中途上升了。而MSE的loss很快就小于0.17了,也就是很快学习率趋于0.003梯度截断了。这是为什么呢?
两个函数MSE比MAE更快降低loss,让学习率趋于稳定。但是我们还是遇到了一个老问题就是中途为什么损失值又上升了。我们是否可以做一些尝试来避免?其实究其原因还是在较低的损失了,还采用了较高的学习率导致的,即使我们采用了梯度截断。我们把这个截断提前或许可以解决这个问题,但是我们是否可以设置一个学习率最大值降低,这样就可以通过层数来弥补,也不会出现过大的学习率了。
这里我填报了lr_max = 0.005 # 最大学习率
我们看到MAE总是能比MSE更靠近真实值,我是这么认为的,因为MSE是平方,他对loss很敏感。而我们加入了小于0.17就开始减少学习率,也就是说MSE更容易超过0.17,他的步伐迈的比MAE大,因此在小的差距下处理的没有MAE细腻。
代码:
# loss = np.mean((y - y_pred) ** 2)#MSEloss = 1/m * np.sum(np.abs(y-y_pred))#MAE
均方根误差
相比于MSE,由于最后进行开根,所以他的量纲和数据本身是一致的,可以更好理解,而MSE则是误差的平方,RMSE是误差的平方又开根号,维度是没有变化的,也就是量纲没有变化。在效果对于误差的惩罚力度没有MSE强。但是处理效果比MSE细腻。
loss = np.sqrt(1/m * np.sum((y - y_pred)**2))#均方根
这里我们对三个损失进行了比较,发现RMSE损失降低的最慢,得到的权重更接近于5
交叉熵
交叉熵适合用于处理概率问题,就是他比较适合那种归一化的问题,并且他会有两种y和(1-y)两种情况,适合二分类。
# epsilon = 1e-10 # 避免log(0)的错误 交叉熵 有误 放弃# loss = -np.mean(y * np.log(y_pred + epsilon))#由于我们的是回归问题
这是每个样本进行的损失计算,因为y只有0和1两种可能,因此当y=1计算的是-ylog(y^),而右边部分因为(1-y)=0,所以只计算左边部分。
其次为什么是负号,这是因为我们的预测值y^是一个概率,只有0到1,log函数在0到1是负数,我们再加一个负号就是正数了,也就是说损失只有正号。
有人要问了,我们优化不是一个类似凸优化的计算吗,那梯度应该是有正有负的。其实我们的损失计算的只是一个差距,真正的方向(±)是要靠求导后去确定。
对数损失
对数损失其实和交叉熵损失一样。也就是我们上面那种更像是对数损失。
笔记回馈
-
什么梯度爆炸?(基础知识)
-
损失是什么?损失函数和损失的关系是什么?(基础知识)
-
请列举你知道的损失函数,并且公式是什么?他的缺点和优点是什么?适合什么任务?(面试会考)
-
要如何避免梯度爆炸?(面试会考)
逻辑回归中一些注意事项:
y = wx+b逻辑回归公式
这个是做线性回归用的,对于二分类,评分某个类别的概率需要用到激活函数西格玛
逻辑回归
逻辑回归的代价函数
梯度下降法
为什么损失函数会有两部分,我的猜想是二分类需要分别评估他是两种类别的损失,然后加起来。
对数损失和叫交叉熵损失
如果需要更多学习资料可以打开【咸鱼】https://m.tb.cn/h.6h707H6?tk=rRbqVZ2WNdh HU926
点击链接直接打开,三0元以下带你完成人脸识别、关键点识别、手势识别等多个项目,涵盖SVM、knn、yolov8、语义分割!!!零基础适合小白