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的返回是标量。

计算公式如下:

pytorch 自己实现交叉熵损失 pytorch二分类交叉熵_标量

 

 代码:

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:忽略某个类的损失函数

计算公式:

  

pytorch 自己实现交叉熵损失 pytorch二分类交叉熵_标量_02

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,那么返回的结果是和输入元素相同尺寸的。

计算公式:

pytorch 自己实现交叉熵损失 pytorch二分类交叉熵_pytorch 自己实现交叉熵损失_03

适用范围:

  1. 回归任务
  2. 简单的模型
  3. 由于神经网络通常是解决复杂问题,所以很少使用。

3.5.4 MSE损失函数

torch.nn.MSELoss(size_average=None, reduce=None, reduction='mean')

功能: 计算输出y和真实标签target差的平方

计算公式:

  

pytorch 自己实现交叉熵损失 pytorch二分类交叉熵_损失函数_04

适用范围:

  1. 回归任务
  2. 数值特征不大
  3. 问题维度不高

3.5.5 平滑L1 (Smooth L1)损失函数

torch.nn.SmoothL1Loss(size_average=None, reduce=None, reduction = 'mean', beta=1.0)

功能: L1的平滑输出,其功能是减轻离群点带来的影响

计算公式如下:

pytorch 自己实现交叉熵损失 pytorch二分类交叉熵_数据_05

原理:

  SmoothL1Loss其实是L2Loss和L1Loss的结合 ,它同时拥有L2 Loss和L1 Loss的部分优点。  

  1. 当输出y和真实标签target差异较小的时候(绝对值差小于1),梯度不至于太大。(损失函数相较L1 Loss比较圆滑)(L2 Loss)
  2. 当差别大的时候,梯度值足够小(较稳定,不容易梯度爆炸)(L1 Loss的平移。)。

 

 

 平滑L1与L1的对比:

pytorch 自己实现交叉熵损失 pytorch二分类交叉熵_标量_06

 

 

   对于smoothL1来说,在 0 这个尖端处,过渡更为平滑。

适用范围:

  1. 回归
  2. 当特征中有较大的数值
  3. 适合大多数问题

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

pytorch 自己实现交叉熵损失 pytorch二分类交叉熵_数据_07

 

  • 当参数log_input=False

pytorch 自己实现交叉熵损失 pytorch二分类交叉熵_数据_08

 

 

 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 维度求平均值。

计算公式:

  

pytorch 自己实现交叉熵损失 pytorch二分类交叉熵_数据_09

3.5.8 MarginRankingLoss

torch.nn.MarginRankingLoss(margin=0.0, size_average=None, reduce=None, reduction='mean')

功能: 计算两个向量之间的相似度,用于排序任务。该方法用于计算两组数据之间的差异

主要参数:

  margin:边界值,x1和x2之间的差异值

计算公式:

pytorch 自己实现交叉熵损失 pytorch二分类交叉熵_标量_10

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')

功能: 对于多标签分类问题计算损失函数。

计算公式: 

  

pytorch 自己实现交叉熵损失 pytorch二分类交叉熵_标量_11

 

 

 

pytorch 自己实现交叉熵损失 pytorch二分类交叉熵_pytorch 自己实现交叉熵损失_12

 

 

 

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 损失。

计算公式:

  

pytorch 自己实现交叉熵损失 pytorch二分类交叉熵_pytorch 自己实现交叉熵损失_13

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。

计算公式:

pytorch 自己实现交叉熵损失 pytorch二分类交叉熵_损失函数_14

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>。在项目中,也可以表示为< anchorpositive examples , negative examples>

      在这个损失函数中,我们希望让anchor距离接近positive examples,而远离negative examples

计算公式:

pytorch 自己实现交叉熵损失 pytorch二分类交叉熵_损失函数_15

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损失计算

计算公式:

pytorch 自己实现交叉熵损失 pytorch二分类交叉熵_损失函数_16

 

注: 输入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] 。

计算公式:

pytorch 自己实现交叉熵损失 pytorch二分类交叉熵_数据_17

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))