网站首页 > 技术文章 正文
损失度量对于神经网络非常重要。 由于所有机器学习模型都是这样或那样的优化问题,因此损失是要最小化的目标函数。 在神经网络中,优化是通过梯度下降和反向传播来完成的。 但什么是损失函数,它们如何影响你的神经网络?
推荐:用 NSDT设计器 快速搭建可编程3D场景。
在这篇文章中,我们将学习:
- 什么是损失函数及其在训练神经网络模型中的作用
- 回归和分类问题的常见损失函数
- 如何在 PyTorch 模型中使用损失函数
1、什么是损失函数?
在神经网络中,损失函数有助于优化模型的性能。 它们通常用于衡量模型在预测中产生的一些惩罚,例如预测与真实标签的偏差。 损失函数通常在其域内是可微的(但允许仅对于非常特定的点未定义梯度,例如 x=0,实际中基本被忽略)。 在训练循环中,它们根据参数进行区分,这些梯度用于反向传播和梯度下降步骤,以优化训练集上的模型。
损失函数也与指标略有不同。 虽然损失函数可以告诉你模型的性能,但它们可能不是人类直接感兴趣的或容易解释的。 这就是指标的用武之地。准确性等指标对于人类理解神经网络的性能更有用,尽管它们可能不是损失函数的良好选择,因为它们可能不可微分。
接下来,我们将探讨回归问题和分类问题的一些常见损失函数。
2、回归问题的损失函数
在回归问题中,模型是预测连续范围内的值。 你的模型可以始终预测准确的值,这太好了,但如果该值足够接近,那就足够了。 因此,你需要一个损失函数来衡量它的接近程度。 离准确值越远,你的预测损失就越大。
一个简单的功能就是测量预测值和目标值之间的差异。 在查找差异时,你并不关心该值大于还是小于目标值。 因此,在数学中,我们可以试用平均绝对误差 (MAE):
其中m表示训练样本的数量,yi 和yi_hat分别是所有训练示例的目标值和预测值的平均值。
MAE 永远不会是负数,只有当预测与真实情况完美匹配时才会为零。 它是一种直观的损失函数,也可以用作你的指标之一,特别是对于回归问题,因为你希望最大限度地减少预测中的错误。
然而,绝对值在 0 处不可微分。这并不是真正的问题,因为你很少达到该值。 但有时人们更喜欢使用均方误差 (MSE):
MSE与 MAE 类似,但使用平方函数代替绝对值。
均方误差测量预测值与目标值的偏差。 然而,MSE 对此差进行平方(始终为非负,因为实数的平方始终为非负),这使其属性略有不同。 一个特性是均方误差有利于大量的小误差而不是少量的大误差,这导致模型具有较少的离群值,或者至少离群值比使用 MAE 训练的模型不太严重。 这是因为与小误差相比,大误差会对误差产生明显更大的影响,从而对误差梯度产生更大的影响。
让我们以图形方式看一下平均绝对误差和均方误差损失函数的样子:
平均绝对误差损失函数(蓝色)和梯度(橙色)
均方误差损失函数(蓝色)和梯度(橙色)
与激活函数类似,你可能也对损失函数的梯度感兴趣,因为稍后你将使用梯度进行反向传播来训练模型的参数。 你应该看到,在 MSE 中,较大的误差将导致较大的梯度幅度和较大的损失。 因此,例如,两个偏离其基本事实 1 个单位的训练示例将导致 2 的损失,而偏离其基本事实 2 个单位的单个训练示例将导致 4 的损失,因此有 影响较大。 MAE 中的情况并非如此。
在 PyTorch 中,可以分别使用 nn.L1Loss() 和 nn.MSELoss() 创建 MAE 和 MSE 作为损失函数。 之所以命名为L1,是因为MAE的计算在数学上也被称为L1范数。 下面是计算两个向量之间的 MAE 和 MSE 的示例:
import torch
import torch.nn as nn
mae = nn.L1Loss()
mse = nn.MSELoss()
predict = torch.tensor([0., 3.])
target = torch.tensor([1., 0.])
print("MAE: %.3f" % mae(predict, target))
print("MSE: %.3f" % mse(predict, target))
结果应该如下:
MAE: 2.000
MSE: 5.000
MAE 是 2.0 ,因为:
而 MSE 是 5.0,因为:
请注意,在 MSE 中,预测值为 3 且实际值为 0 的第二个示例在均方误差下贡献了 90% 的误差,而在平均绝对误差下贡献了 75% 的误差。
有时,你可能会看到人们使用均方根误差 (RMSE) 作为衡量标准。 这将取 MSE 的平方根。 从损失函数的角度来看,MSE和RMSE是等价的。 但从数值的角度来看,RMSE 与预测值的单位相同。 如果你的预测是美元金额,MAE 和 RMSE 都会告诉你预测值与美元真实价值的平均偏差程度。 但MSE的单位是平方美元,其物理意义并不直观。
3、分类问题的损失函数
对于分类问题,输出可以采用一小部分离散的数字。 此外,用于对类进行标签编码的数字是任意的并且没有语义意义(例如,使用标签 0 表示猫、1 表示狗、2 表示马并不代表狗是一半猫和一半马)。 因此,它不应该对模型的性能产生影响。
在分类问题中,模型的输出通常是每个类别的概率向量。 通常,该向量通常被期望为“logits”,即使用 softmax 函数转换为概率的实数,或 softmax 激活函数的输出。
两个概率分布之间的交叉熵是两个概率分布之间差异的度量。 准确地说,下面是概率P和Q的交叉熵公式:
在机器学习中,概率P通常由训练数据提供,概率Q则由模型预测,即
1 代表正确的类别,0 代表所有其他类别。 预测概率Q通常是一个介于 0 和 1 之间的浮点。因此,当用于机器学习中的分类问题时,该公式可以简化为:
其中ptarget是该特定样本的真实类别的模型预测概率。
交叉熵度量有一个负号,因为当x趋向0时,log(x) 趋于负无穷。当概率接近 0 时,我们希望获得更高的损失;当概率接近 1 时,我们希望获得更低的损失。从图形上看,
请注意,如果真实类别的概率如预期为 1,则损失恰好为 0。 此外,由于真实类别的概率趋于 0,损失也趋于正无穷大,因此会严重惩罚错误的预测。 你可能会认识到逻辑回归的这个损失函数,除了逻辑回归损失特定于二元类的情况之外,它是相似的。
查看梯度,你可以看到梯度通常为负,这也是预期的,因为为了减少这种损失,您会希望真实类别的概率尽可能高。 回想一下,梯度下降的方向与梯度相反。
在 PyTorch 中,交叉熵函数由 nn.CrossEntropyLoss() 提供。 它将预测的逻辑和目标作为参数并计算分类交叉熵。 请记住,在 CrossEntropyLoss() 函数内部,softmax 将应用于 logits,因此你不应在输出层使用 softmax 激活函数。 使用 PyTorch 的交叉熵损失函数的示例如下:
import torch
import torch.nn as nn
ce = nn.CrossEntropyLoss()
logits = torch.tensor([[-1.90, -0.29, -2.30], [-0.29, -1.90, -2.30]])
target = torch.tensor([[0., 1., 0.], [1., 0., 0.]])
print("Cross entropy: %.3f" % ce(logits, target))
结果如下:
Cross entropy: 0.288
请注意,交叉熵损失函数的第一个参数是 logit,而不是概率。 因此,每一行的总和不等于 1。然而,第二个参数是包含概率行的张量。 如果使用 softmax 函数将上面的 logits 张量转换为概率,则为:
probs = torch.tensor([[0.15, 0.75, 0.1], [0.75, 0.15, 0.1]])
每行的总和为 1.0。 这个张量也揭示了为什么上面计算出的交叉熵是 0.288,即log0.75。
在 PyTorch 中计算交叉熵的另一种方法是不在目标中使用 one-hot 编码,而是使用整数索引标签:
import torch
import torch.nn as nn
ce = nn.CrossEntropyLoss()
logits = torch.tensor([[-1.90, -0.29, -2.30], [-0.29, -1.90, -2.30]])
indices = torch.tensor([1, 0])
print("Cross entropy: %.3f" % ce(logits, indices))
这给出了相同的交叉熵 0.288。 注意,
import torch
target = torch.tensor([[0., 1., 0.], [1., 0., 0.]])
indices = torch.argmax(target, dim=1)
print(indices)
结果为:
tensor([1, 0])
这就是 PyTorch 解释目标张量的方式。 在其他库中,它也被称为“稀疏交叉熵”函数,以区别于它不需要一个one hot向量。
请注意,在 PyTorch 中,你可以使用 nn.LogSoftmax() 作为激活函数。 就是对一层的输出应用softmax,然后对每个元素取对数。 如果这是你的输出层,应该使用 nn.NLLLoss() (负对数似然)作为损失函数。 从数学上讲,这对组合与交叉熵损失相同。 可以通过检查下面的代码产生相同的输出来确认这一点:
import torch
import torch.nn as nn
ce = nn.NLLLoss()
# softmax to apply on dimension 1, i.e. per row
logsoftmax = nn.LogSoftmax(dim=1)
logits = torch.tensor([[-1.90, -0.29, -2.30], [-0.29, -1.90, -2.30]])
pred = logsoftmax(logits)
indices = torch.tensor([1, 0])
print("Cross entropy: %.3f" % ce(pred, indices))
如果分类问题只有两个类别,则变为二元分类。 它很特别,因为该模型现在是一个逻辑回归模型,其中只能有一个输出,而不是两个值的向量。 你仍然可以将二元分类实现为多类分类,并使用相同的交叉熵函数。 但是如果你输出x
作为“正类”的概率(介于 0 和 1 之间),已知“负类”的概率必须为1-x。
在 PyTorch 中,有 nn.BCELoss() 用于二进制交叉熵。 它专门用于二进制情况。 例如:
import torch
import torch.nn as nn
bce = nn.BCELoss()
pred = torch.tensor([0.75, 0.25])
target = torch.tensor([1., 0.])
print("Binary cross entropy: %.3f" % bce(pred, target))
结果如下:
Binary cross entropy: 0.288
这是因为:
请注意,在 PyTorch 中,目标标签 1 被视为“正类”,标签 0 被视为“负类”。 目标张量中不应有其他值。
4、PyTorch 中的自定义损失函数
请注意,上面的损失指标是使用 torch.nn 模块中的对象计算的。 计算出的损失度量是 PyTorch 张量,因此可以对它进行微分并开始反向传播。 只要可以根据模型的输出计算张量,就没有什么可以阻止你创建自己的损失函数。
PyTorch 不会提供所有可能的损失指标。 例如,不包括平均绝对百分比误差。 它就像 MAE,定义为:
有时你可能更喜欢使用 MAPE。 回想一下加州住房数据集的回归示例,预测是针对房价的。 根据百分比差异而不是美元差异来考虑预测的准确性可能更有意义。 你可以定义 MAPE 函数,只需记住使用 PyTorch 函数进行计算并返回 PyTorch 张量即可。
请参阅下面的完整示例:
import copy
import matplotlib.pyplot as plt
import numpy as np
import pandas as pd
import torch
import torch.nn as nn
import torch.optim as optim
import tqdm
from sklearn.model_selection import train_test_split
from sklearn.datasets import fetch_california_housing
from sklearn.preprocessing import StandardScaler
# Read data
data = fetch_california_housing()
X, y = data.data, data.target
# train-test split for model evaluation
X_train_raw, X_test_raw, y_train, y_test = train_test_split(X, y, train_size=0.7, shuffle=True)
# Standardizing data
scaler = StandardScaler()
scaler.fit(X_train_raw)
X_train = scaler.transform(X_train_raw)
X_test = scaler.transform(X_test_raw)
# Convert to 2D PyTorch tensors
X_train = torch.tensor(X_train, dtype=torch.float32)
y_train = torch.tensor(y_train, dtype=torch.float32).reshape(-1, 1)
X_test = torch.tensor(X_test, dtype=torch.float32)
y_test = torch.tensor(y_test, dtype=torch.float32).reshape(-1, 1)
# Define the model
model = nn.Sequential(
nn.Linear(8, 24),
nn.ReLU(),
nn.Linear(24, 12),
nn.ReLU(),
nn.Linear(12, 6),
nn.ReLU(),
nn.Linear(6, 1)
)
# loss function and optimizer
def loss_fn(output, target):
# MAPE loss
return torch.mean(torch.abs((target - output) / target))
optimizer = optim.Adam(model.parameters(), lr=0.0001)
n_epochs = 100 # number of epochs to run
batch_size = 10 # size of each batch
batch_start = torch.arange(0, len(X_train), batch_size)
# Hold the best model
best_mape = np.inf # init to infinity
best_weights = None
for epoch in range(n_epochs):
model.train()
for start in batch_start:
# take a batch
X_batch = X_train[start:start+batch_size]
y_batch = y_train[start:start+batch_size]
# forward pass
y_pred = model(X_batch)
loss = loss_fn(y_pred, y_batch)
# backward pass
optimizer.zero_grad()
loss.backward()
# update weights
optimizer.step()
# evaluate accuracy at end of each epoch
model.eval()
y_pred = model(X_test)
mape = float(loss_fn(y_pred, y_test))
if mape < best_mape:
best_mape = mape
best_weights = copy.deepcopy(model.state_dict())
# restore model and return best accuracy
model.load_state_dict(best_weights)
print("MAPE: %.2f" % best_mape)
model.eval()
with torch.no_grad():
# Test out inference with 5 samples
for i in range(5):
X_sample = X_test_raw[i: i+1]
X_sample = scaler.transform(X_sample)
X_sample = torch.tensor(X_sample, dtype=torch.float32)
y_pred = model(X_sample)
print(f"{X_test_raw[i]} -> {y_pred[0].numpy()} (expected {y_test[i].numpy()})")
与另一篇文章中的示例相比,你可以看到 loss_fn 现在被定义为自定义函数。
原文链接:http://www.bimant.com/blog/pytorch-loss-function/
猜你喜欢
- 2024-10-12 新手教程:在新应用中实践深度学习的最佳建议
- 2024-10-12 一个新的基于样本数量计算的的高斯 softmax 函数
- 2024-10-12 TensorFlow和PyTorch相继发布最新版,有何变化
- 2024-10-12 来自特斯拉AI总监的“秘方”:这些训练神经网络的小技巧不能忽略
- 2024-10-12 小型深度学习框架 | TinyGrad,不到1K行代码(附代码下载)
- 2024-10-12 深度学习实战第四课 深度学习基础教程
- 2024-10-12 Python pytorch 深度学习神经网络 softmax线性回归分类学习笔记
- 2024-10-12 收藏!PyTorch常用代码段合集 pytorch 编程
- 2024-10-12 深度神经网络模型训练中的最新tricks总结【原理与代码汇总】
- 2024-10-12 PyTorch 模型性能分析和优化 - 第 2 部分
你 发表评论:
欢迎- 最近发表
- 标签列表
-
- oraclesql优化 (66)
- 类的加载机制 (75)
- feignclient (62)
- 一致性hash算法 (71)
- dockfile (66)
- 锁机制 (57)
- javaresponse (60)
- 查看hive版本 (59)
- phpworkerman (57)
- spark算子 (58)
- vue双向绑定的原理 (68)
- springbootget请求 (58)
- docker网络三种模式 (67)
- spring控制反转 (71)
- data:image/jpeg (69)
- base64 (69)
- java分页 (64)
- kibanadocker (60)
- qabstracttablemodel (62)
- java生成pdf文件 (69)
- deletelater (62)
- com.aspose.words (58)
- android.mk (62)
- qopengl (73)
- epoch_millis (61)
本文暂时没有评论,来添加一个吧(●'◡'●)