3.5 损失函数
损失函数:也称模型的负反馈,是数据输入到模型当中,产生的结果与真实标签的评价指标,我们的模型可以按照损失函数的目标来做出改进。
3.5.1 二分类交叉熵损失函数
torch.nn.BCELoss(weight = None, size_average = None, reduce = None, reduction = 'mean')
功能:计算二分类任务时的交叉熵(Cross Entropy)函数。在二分类中,label是{0,1}。对于进入交叉熵函数的input为概率分布的形式。一般来说,input为sigmoid激活层的输出,或者softmax的输出。
主要参数:
weight
: 每个类别的loss设置权值
size_average
: 数据为bool,为True时,返回的loss为平均值;为False时,返回的各样本的loss之和。
reduce
: 数据类型为bool,为True时,loss的返回是标量。
计算公式如下:
代码:
m = nn.Sigmoid()
loss = nn.BCELoss()
input = torch.randn(3, requires_grad = True)
target = torch.empty(3).random_(2)
output = loss(m(input), target)
out.backward()
print('BCELoss损失函数的计算结果为',output)
输出:
BCELoss损失函数的计算结果为 tensor(0.5732, grad_fn=<BinaryCrossEntropyBackward>)
3.5.2 交叉熵损失函数
torch.nn.CrossEntropyLoss(weight = None, size_average=None, ignore_index = -100, reduce = None, reduction='mean')
功能:计算交叉熵函数
主要参数:
ignore_index:忽略某个类的损失函数
计算公式:
loss = nn.CrossEntropyLoss()
input = torch.randn(3, 5, requires_grad=True)
target = torch.empty(3, dtype=torch.long).random_(5)
output = loss(input, target) # 注意与BCELoss()输入的区别,它不需要为sigmoid或softmax的输出
output.backward()
print(output)
输出:
tensor(2.0115, grad_fn=<NllLossBackward>)
3.5.3 L1损失函数
torch.nn.L1Loss(size_average=None, reduce = None, reduction='mean')
功能: 计算输出y
和真实标签target
之间的差值的绝对值。
reduction
参数决定了计算模式:
- none:逐个元素计算
- sum:所有元素求和,返回标量。
- mean:加权平均,返回标量。(默认)
- 选择
none
,那么返回的结果是和输入元素相同尺寸的。
计算公式:
适用范围:
- 回归任务
- 简单的模型
- 由于神经网络通常是解决复杂问题,所以很少使用。
3.5.4 MSE损失函数
torch.nn.MSELoss(size_average=None, reduce=None, reduction='mean')
功能: 计算输出y
和真实标签target
之差的平方。
计算公式:
适用范围:
- 回归任务
- 数值特征不大
- 问题维度不高
3.5.5 平滑L1 (Smooth L1)损失函数
torch.nn.SmoothL1Loss(size_average=None, reduce=None, reduction = 'mean', beta=1.0)
功能: L1的平滑输出,其功能是减轻离群点带来的影响
计算公式如下:
原理:
SmoothL1Loss其实是L2Loss和L1Loss的结合 ,它同时拥有L2 Loss和L1 Loss的部分优点。
- 当输出
y
和真实标签target差异较小
的时候(绝对值差小于1),梯度不至于太大。(损失函数相较L1 Loss比较圆滑)(L2 Loss) - 当差别大的时候,梯度值足够小(较稳定,不容易梯度爆炸)(L1 Loss的平移。)。
平滑L1与L1的对比:
对于smoothL1
来说,在 0 这个尖端处,过渡更为平滑。
适用范围:
- 回归
- 当特征中有较大的数值
- 适合大多数问题
3.5.6 目标泊松分布的负对数似然损失
torch.nn.PoissonNLLLoss(log_input=True, full=False, size_average=None, eps=1e-8, reduce=None, reduction='mean')
功能: 泊松分布的负对数似然损失函数
主要参数:
log_input
:输入是否为对数形式,决定计算公式。
full
:计算所有 loss,默认为 False。
eps
:修正项,避免 input 为 0 时,log(input) 为 nan 的情况。
数学公式:
- 当参数
log_input=True
:
- 当参数
log_input=False
:
3.5.7 KL散度
torch.nn.KLDivLoss(size_average=None, reduce=None, reduction='mean',log_target=False)
功能: 计算KL散度,也就是计算相对熵。用于不同的连续分布的距离度量,并且对离散采用的连续输出空间分布进行回归通常很有用。
主要参数:
reduction
:计算模式,可为 none
/sum
/mean
/batchmean
。
- none:逐个元素计算。
- sum:所有元素求和,返回标量。
- mean:加权平均,返回标量。
- batchmean:batchsize 维度求平均值。
计算公式:
3.5.8 MarginRankingLoss
torch.nn.MarginRankingLoss(margin=0.0, size_average=None, reduce=None, reduction='mean')
功能: 计算两个向量之间的相似度,用于排序任务。该方法用于计算两组数据之间的差异。
主要参数:
margin:边界值,x1和x2之间的差异值
计算公式:
loss = nn.MarginRankingLoss()
input1 = torch.randn(3, requires_grad=True)
input2 = torch.randn(3, requires_grad=True)
target = torch.randn(3).sign()
output = loss(input1, input2, target)##Loss支持三个变量
output.backward()
3.5.9 多标签边界损失函数
torch.nn.MultiLabelMarginLoss(size_average=None, reduce=None, reduction='mean')
功能: 对于多标签分类问题计算损失函数。
计算公式:
loss = nn.MultiLabelMarginLoss()
x = torch.FloatTensor([[0.9, 0.2, 0.4, 0.8]])
# 对于目标y,只考虑标签3和0,而不是标签-1之后
y = torch.LongTensor([[3, 0, -1, 1]])# 真实的分类是,第3类和第0类
output = loss(x, y)
3.5.10 二分类损失函数
torch.nn.SoftMarginLoss(size_average=None, reduce=None, reduction='mean')
功能: 计算二分类的 logistic 损失。
计算公式:
inputs = torch.tensor([[0.3, 0.7], [0.5, 0.5]]) # 两个样本,两个神经元
target = torch.tensor([[-1, 1], [1, -1]], dtype=torch.float) # 该 loss 为逐个神经元计算,需要为每个神经元单独设置标签
loss_f = nn.SoftMarginLoss()
output = loss_f(inputs, target)
3.5.11 多分类的折页损失
torch.nn.MultiMarginLoss(p=1, margin=1.0, weight=1.0, size_average=None, reduce=None, reduction='mean')
功能: 计算多分类的折页损失
主要参数:
p:
可选 1 或 2。
计算公式:
3.5.12 三元组损失
torch.nn.TripletMarginLoss(margin=1.0, p=2.0, eps=1e-6, swap=False, size_average=None, reduce=None, reduction='mean')
功能: 计算三元组损失。
三元组: 是一种数据的存储或者使用格式。<实体1,关系,实体2>。在项目中,也可以表示为< anchor
, positive examples
, negative examples
>
在这个损失函数中,我们希望让anchor
的距离更接近positive examples
,而远离negative examples
计算公式:
triplet_loss = nn.TripletMarginLoss(margin=1.0, p=2)
anchor = torch.randn(100, 128, requires_grad=True)
positive = torch.randn(100, 128, requires_grad=True)
negative = torch.randn(100, 128, requires_grad=True)
output = triplet_loss(anchor, positive, negative)
output.backward()
3.5.13 HingEmbeddingLoss
torch.nn.HingeEmbeddingLoss(margin=1.0, size_average=None, reduce=None, reduction='mean')
功能: 对输出的embedding结果做Hing损失计算
计算公式:
注: 输入x应为两个输入之差的绝对值。
公式理解:输出的是正例yn=1,那么loss就是x,如果输出的是负例y=-1,那么输出的loss就是要做一个比较。
3.5.14 余弦相似度
torch.nn.CosineEmbeddingLoss(margin=0.0, size_average=None, reduce=None, reduction='mean')
功能: 对两个向量做余弦相似度,将余弦相似度作为一个距离的计算方式,如果两个向量的距离近,则损失函数值小,反之亦然。
主要参数:
margin
:可取值[-1,1] ,推荐为[0,0.5] 。
计算公式:
3.5.15 CTC损失函数
torch.nn.CTCLoss(blank=0, reduction='mean', zero_infinity=False)
功能: 用于解决时序类数据的分类。计算连续时间序列和目标序列之间的损失。
CTCLoss对输入和目标的可能排列的概率进行求和,产生一个损失值,这个损失值对每个输入节点来说是可分的。输入与目标的对齐方式被假定为 "多对一",限制 目标序列的长度 ≤ 输入长度。
主要参数:
blank
:空白标签所在的label值,默认为0,需要根据实际的标签定义进行设定;
zero_infinity
:无穷大的值或梯度值
# 目标被填充
T = 50 # 输入序列长度
C = 20 # 类别数数(包括空白(blank))
N = 16 # Batch size
S = 30 # 批处理中最长目标的目标序列长度(填充长度))
S_min = 10 # 最小目标长度,用于演示目的
# 初始化输入向量的随机批次,对于 *size = (T,N,C)
input = torch.randn(T, N, C).log_softmax(2).detach().requires_grad_()
# 初始化随机批次的目标(0 = 空白,1:C = 类)
target = torch.randint(low=1, high=C, size=(N, S), dtype=torch.long)
input_lengths = torch.full(size=(N,), fill_value=T, dtype=torch.long)
target_lengths = torch.randint(low=S_min, high=S, size=(N,), dtype=torch.long)
ctc_loss = nn.CTCLoss()
loss = ctc_loss(input, target, input_lengths, target_lengths)
loss.backward()
# 目标是未填充的
T = 50 # Input sequence length
C = 20 # Number of classes (including blank)
N = 16 # Batch size
# Initialize random batch of input vectors, for *size = (T,N,C)
input = torch.randn(T, N, C).log_softmax(2).detach().requires_grad_()
input_lengths = torch.full(size=(N,), fill_value=T, dtype=torch.long)
# Initialize random batch of targets (0 = blank, 1:C = classes)
target_lengths = torch.randint(low=1, high=T, size=(N,), dtype=torch.long)
target = torch.randint(low=1, high=C, size=(sum(target_lengths),), dtype=torch.long)
ctc_loss = nn.CTCLoss()
loss = ctc_loss(input, target, input_lengths, target_lengths)
loss.backward()
3.6 训练和评估
训练模型首先应该设置模型的状态:训练 或 测试。
训练:模型的参数支持反向传播的修改
测试:不应该修改模型参数
model.train() #训练状态
model.eval() # 测试状态
## 在训练过程中使用for循环读取DataLoader中的全部数据。
for data, label in train_loader:
# 使用GPU,将数据放到GPU上进行计算
data, label = data.cuda(), label.cuda()
# 开始用当前批次数据做训练时,应当先将优化器的梯度置零
optimizer.zero_grad()
# 送入模型训练
output = model(data)
# 计算loss
loss = criterion(output, label)
# 反向传播
loss.backward()
# 使用优化器进行参数优化和更新
optimizer.step()
验证/测试的流程基本与训练过程一致,不同点在于:
- 需要预先设置torch.no_grad,以及将model调至eval模式
- 不需要将优化器的梯度置零
- 不需要将loss反向回传到网络
- 不需要更新optimizer
一个完整的图像分类的训练过程如下所示:
def train(epoch):
model.train()# 训练模式
train_loss = 0
for data, label in train_loader:
data, label = data.cuda(), label.cuda()
optimizer.zero_grad()
output = model(data)
loss = criterion(label, output)
loss.backward()
optimizer.step()
train_loss += loss.item()*data.size(0)
train_loss = train_loss/len(train_loader.dataset)
print('Epoch: {} \tTraining Loss: {:.6f}'.format(epoch, train_loss))
一个完整图像分类的验证过程如下所示:
def val(epoch):
model.eval() # 测试模式
val_loss = 0
with torch.no_grad():# 测试过程不需要反向传播,不需要构建计算图,该语句强制之后的内容不进行计算图构建。
for data, label in val_loader:
data, label = data.cuda(), label.cuda()
output = model(data)
preds = torch.argmax(output, 1)
loss = criterion(output, label)
val_loss += loss.item()*data.size(0)
running_accu += torch.sum(preds == label.data)
val_loss = val_loss/len(val_loader.dataset)
print('Epoch: {} \tTraining Loss: {:.6f}'.format(epoch, val_loss))