日常pytorch编写“自创”的MRR损失函数

Posted 囚生CY

tags:

篇首语:本文由小常识网(cha138.com)小编为大家整理,主要介绍了日常pytorch编写“自创”的MRR损失函数相关的知识,希望对你有一定的参考价值。

序言

最近重新开始学习Pytorch, 期初抽时间把 PyTorch 中文文档给过了一遍, 主要是把几个重要的模块中的方法熟悉了一下, markdown记了数千行, 但是纸上谈兵的学习方法终究还是浮于表面, 在阅读 RE2RNNRE-NET 的过程中, 笔者发现已经无法理解作者模型代码里的逻辑(菜), 很是苦恼;

之前翻pypi发现PyTorch1.4.0后就没有给windowswheel安装文件了, TensorflowGPU版本更新到2.0.x版本之后就不再支持CUDA9的显卡配置了, 加上旧笔记本一直没有安装Anaconda, Python的版本停滞在3.6.1, 里的TensorflowPyTorch的版本一直停留在1.8.01.4.0, 之后其实更新的方法跟之前有很大出入, 许多最新论文的项目代码已经不能再跑通; 新笔记本还没舍得装CUDA, 不过最近笔者发现一个 repository 囊括了从最老到最新, 所有平台上各种Python版本的torchtorchvisionwheel安装文件, 实在是帮了大忙;

于是本文主要围绕一个由MRR指标(Section 2.1)衍生出来的MRR损失函数的自定义编写问题展开, 第一部分从PyTorch的损失函数及优化器使用开始, 第三部分就笔者近期记录的一些零散的torchtorchvision库的使用点作为补充结束, 也许会持续更新, 也许会新开博客记录, 只是持续更新总成空谈, 很无奈; 第二部分将详细解析该损失函数的纯torch函数的编写思路, 并介绍一种Python内置的比较少见的排序方法, 希望能对各位朋友有所帮助;


目录


1 PyTorch中的损失函数与优化器

1.1 torch.nn中定义的损失函数

PyTorch中文文档
文档中似乎没有写出所有的损失函数, 详细可以查看E:\\Anaconda3\\Lib\\site-packages\\torch\\nn\\modules\\loss.py中的定义;

  1. class torch.nn.L1Loss(size_average=True): 绝对平均误差(MAE);
  2. torch.nn.MSELoss(size_average=True): 均方误差(MSE);
  3. torch.nn.CrossEntropyLoss(weight=None, size_average=True): 交叉熵损失(常用);
  • weight可以输入一个1D张量, 包含n个权重值, 作为n个类别各自的权重, 这往往再样本标签分布不均时是很有意义的;
  1. torch.nn.NLLLoss(weight=None, size_average=True): 负对数似然损失;
  2. torch.nn.NLLLoss2d(weight=None, size_average=True): 通常针对图片类型的2D负对数似然损失;
  • 使用示例:
    m = nn.Conv2d(16, 32, (3, 3)).float()
    loss = nn.NLLLoss2d()
    # input is of size nBatch x nClasses x height x width
    input = autograd.Variable(torch.randn(3, 16, 10, 10))
    # each element in target has to have 0 <= value < nclasses
    target = autograd.Variable(torch.LongTensor(3, 8, 8).random_(0, 4))
    output = loss(m(input), target)
    output.backward()
    
  1. class torch.nn.KLDivLoss(weight=None, size_average=True): KL散度损失;
  • KL散度即相对熵, 与交叉熵类似, 通常是针对离散概率分布的估计损失;
  • 如真实分布为 [ 0.1 , 0.4 , 0.5 ] [0.1, 0.4, 0.5] [0.1,0.4,0.5], 预测分布为 [ 0.4 , 0.2 , 0.4 ] [0.4, 0.2, 0.4] [0.4,0.2,0.4], 则KL散度计算公式如下: D K L ( P ∣ Q ) = 0.1 × log ⁡ ( 0.1 0.4 ) + 0.4 × log ⁡ ( 0.4 0.2 ) + 0.5 × log ⁡ ( 0.5 0.4 ) = 0.25 D K L ( Q ∣ P ) = 0.4 × log ⁡ ( 0.4 0.1 ) + 0.2 × log ⁡ ( 0.2 0.4 ) + 0.4 × log ⁡ ( 0.4 0.5 ) = 0.327 (1) D_KL(P|Q)=0.1×\\log(\\frac0.10.4)+0.4×\\log(\\frac0.40.2)+0.5×\\log(\\frac0.50.4)=0.25\\\\D_KL(Q|P)=0.4×\\log(\\frac0.40.1)+0.2×\\log(\\frac0.20.4)+0.4×\\log(\\frac0.40.5)=0.327\\tag1 DKL(PQ)=0.1×log(0.40.1)+0.4×log(0.20.4)+0.5×log(0.40.5)=0.25DKL(QP)=0.4×log(0.10.4)+0.2×log(0.40.2)+0.4×log(0.50.4)=0.327(1)
  • 由(1)式可以看出KL散度是不满足交换律的, 这是一般用交叉熵而非相对熵的原因之一;
  • 另一个原因是, 从计算过程可以看出, 如果有某个离散分布概率为0, KL散度将趋于无穷;
  1. torch.nn.BCELoss(weight=None, size_average=True): 二进制交叉熵;
  • 文档中表述为用于计算Auto-EncoderReconstruction Error;
  1. torch.nn.MarginRankingLoss(margin=0, size_average=True)
  2. torch.nn.HingeEmbeddingLoss(size_average=True)
  3. torch.nn.MultiLabelMarginLoss(size_average=True)
  4. class torch.nn.SmoothL1Loss(size_average=True)
  5. torch.nn.SoftMarginLoss(size_average=True)
  6. torch.nn.MultiLabelSoftMarginLoss(weight=None, size_average=True)
  7. torch.nn.CosineEmbeddingLoss(margin=0, size_average=True)
  8. torch.nn.MultiMarginLoss(p=1, margin=1, weight=None, size_average=True)

1.2 torch.optim中定义的优化器

PyTorch中文文档
文档中似乎没有详细写出所有的优化器, 常用的是Adam, 可以观察E:\\Anaconda3\\Lib\\site-packages\\torch\\optim目录下的所有函数即可;

  1. torch.optim.Adadelta(params, lr=1.0, rho=0.9, eps=1e-6, weight_decay=0)
  2. torch.optim.Adagrad(params, lr=1.0, lr_decay=0, weight_decay=0, initial_accumulator_value=0, eps=1e-10)
  3. torch.optim.Adam(params, lr=1e-3, betas=(0.9, 0.999), eps=1e-8, weight_decay=0, amsgrad=False)
  4. torch.optim.Adadelta(params, lr=1.0, rho=0.9, eps=1e-6, weight_decay=0)
  5. torch.optim.Adamax(params, lr=2e-3, betas=(0.9, 0.999), eps=1e-8, weight_decay=0)
  6. torch.optim.AdamW(params, lr=1e-3, betas=(0.9, 0.999), eps=1e-8, weight_decay=1e-2, amsgrad=False)
  7. torch.optim.ASGD(params, lr=1e-2, lambd=1e-4, alpha=0.75, t0=1e6, weight_decay=0)
  8. torch.optim.LBFGS(params, lr=1, max_iter=20, max_eval=None, tolerance_grad=1e-7, tolerance_change=1e-9, history_size=100, line_search_fn=None)
  9. torch.optim.RMSprop(params, lr=1e-2, alpha=0.99, eps=1e-8, weight_decay=0, momentum=0, centered=False)
  10. torch.optim.Rprop(params, lr=1e-2, etas=(0.5, 1.2), step_sizes=(1e-6, 50))
  11. torch.optim.SGD(params, lr=required, momentum=0, dampening=0, weight_decay=0, nesterov=False)
  12. torch.optim.SparseAdam(params, lr=1e-3, betas=(0.9, 0.999), eps=1e-8)

上述所有的params参数一般设置为model.parameters();

1.3 损失函数与优化器在模型训练中的使用方法

以下代码转载自 CSDN@dongyangY 的博客 使用PyTorch实现CNN , 笔者觉得写得非常详细, 非常适合初学者对Pytorch从数据处理到模型训练以及最终的评估全流程的熟悉, 侵删!

import torch
from torch.utils import data
from torch.autograd import Variable
import torchvision as tv
from torchvision.datasets import mnist
from maplotlib import pyplot as plt

data_path = r'D:\\code\\python\\project\\other\\torch\\data'
model_saving_path = r'D:\\code\\python\\project\\other\\torch\\model\\cnnnet.model'

# 导入数据
transformer = tv.transforms.Compose([
    tv.transforms.ToTensor(),
    tv.transforms.Normalize([.5], [.5])
])

train_data = mnist.MNIST(data_path, train=True, transform=transformer, download=False)
test_data = mnist.MNIST(data_path, train=False, transform=transformer, download=False)
train_loader = data.DataLoader(train_data, batch_size=128, shuffle=True)
test_loader = data.DataLoader(test_data, batch_size=100, shuffle=True)

# 构建网络模型
class CNNnet(torch.nn.Module):
    def __init__(self):
        super(CNNnet,self).__init__()
        self.conv1 = torch.nn.Sequential(
			torch.nn.Conv2d(in_channels=1, out_channels=16, kernel_size=3, stride=2, padding=1),
            torch.nn.BatchNorm2d(16),
            torch.nn.ReLU(),
        )
        self.conv2 = torch.nn.Sequential(
            torch.nn.Conv2d(in_channels=16, out_channels=32, kernel_size=3, stride=2, padding=1),
            torch.nn.BatchNorm2d(32),
            torch.nn.ReLU()
        )
        self.conv3 = torch.nn.Sequential(
            torch.nn.Conv2d(in_channels=32, out_channels=64, kernel_size=3, stride=2, padding=1),
            torch.nn.BatchNorm2d(64),
            torch.nn.ReLU()
        )
        self.conv4 = torch.nn.Sequential(
            torch.nn.Conv2d(in_channels=64, out_channels=64, kernel_size=2, stride=2, padding=0),
            torch.nn.BatchNorm2d(64),
            torch.nn.ReLU()
        )
        self.mlp1 = torch.nn.Linear(in_features=2*2*64, out_features=100)
        self.mlp2 = torch.nn.Linear(in_features=100, out_features=10)   
    def forward(self, x):
        x = self.conv1(x)
        x = self.conv2(x)
        x = self.conv3(x)
        x = self.conv4(x)
        x = self.mlp1(x.view(x.size(0), -1))
        x = self.mlp2(x)
        return x
model = CNNnet()

# 定义损失函数与优化器
loss_func = torch.nn.CrossEntropyLoss()
opt = torch.optim.Adam(model.parameters(), lr=0.001)

# 训练网络: 损失函数调用后需要backward传播, 优化器则每次需要清零后再前进一步
loss_count = []
for epoch in range(2):
    for i, (x, y) in enumerate(train_loader):
        batch_x = Variable(x)          # torch.Size([128, 1, 28, 28])
        batch_y = Variable(y)          # torch.Size([128])
        out = model(batch_x)           # 获取最后输出: torch.Size([128, 10])        
        loss = loss_func(out, batch_y) # 获取损失
        opt.zero_grad() # 清空上一步残余更新参数值
        loss.backward() # 误差反向传播, 计算参数更新值
        opt.step()      # 将参数更新值施加到net的parmeters上
        if i % 20 == 0:
            loss_count.append(loss)
            print(':\\t'.format(i), loss.item())
            torch.save(model, model_saving_path)
        if i % 100 == 0:
            for a,b in test_loader:
                test_x = Variable(a)
                test_y = Variable(b)
                out = model(test_x)
                # print('test_out:\\t', torch.max(out,1)[1])
                # print('test_y:\\t', test_y)
                accuracy = torch.max(out, 1)[1].numpy() == test_y.numpy()
                print('accuracy:\\t', accuracy.mean())
                break

# 损失函数绘图
plt.figure('PyTorch_CNN_Loss')
plt.plot(loss_count, label='Loss')
plt.xlabel('step')
plt.ylabel('loss value')
plt.legend()
plt.show()

1.4 自定义损失函数的编写方法

  1. 方法: 继承torch.nn.Module类后, 编写其中的forward函数, 输入参数为真实标签值与预测标签值;
  2. 以交叉熵损失的源码为例:
from .. import functional as F

class CrossEntropyLoss(_WeightedLoss):
	__constants__ = ['ignore_index', 'reduction']
	ignore_index: int

	def __init__(self, weight: Optional[Tensor] = None, size_average=None, ignore_index: int = -100,
				 reduce=None, reduction: str = 'mean') -> None:
		super(CrossEntropyLoss, self).__init__(weight, size_average, reduce, reduction)
		self.ignore_index = ignore_index

	def forward(self, input: Tensor, target: Tensor) -> Tensor:
		return F.cross_entropy(input, target, weight=self.weight,
							   ignore_index=self.ignore_index, reduction=self.reduction)
  1. 以下代码转载自 CSDN@我的辉 的博客 pytorch系列教程(四)-自定义损失函数 , 侵删!
class Loss_yolov1(nn.Module):
	def __init__(self):
		super(Loss_yolov1,self).__init__()

	def forward(self, pred, labels):
		"""
		:param pred: (batchsize,30,7,7)的网络输出数据
		:param labels: (batchsize,30,7,7)的样本标签数据
		:return: 当前批次样本的平均损失
		"""
		num_gridx, num_gridy = labels.size()[-2:]  # 划分网格数量
		num_b = 2  # 每个网格的bbox数量
		num_cls = 20  # 类别数量
		noobj_confi_loss = 0.  # 不含目标的网格损失(只有置信度损失)
		coor_loss = 0.  # 含有目标的bbox的坐标损失
		obj_confi_loss = 0.  # 含有目标的bbox的置信度损失
		class_loss = 0.  # 含有目标的网格的类别损失
		n_batch = labels.size()[0]  # batchsize的大小

		# 可以考虑用矩阵运算进行优化, 提高速度, 为了准确起见, 这里还是用循环
		for i in range(n_batch):  # batchsize循环
			for n in range(7):  # x方向网格循环
				for m in range(7):  # y方向网格循环
					if labels[i,4,m,n]==1:# 如果包含物体
						# 将数据(px,py,w,h)转换为(x1,y1,x2,y2)
						# 先将px,py转换为cx,cy, 即相对网格的位置转换为标准化后实际的bbox中心位置cx,xy
						# 然后再利用(cx-w/2,cy-h/2,cx+w/2,cy+h/2)转换为xyxy形式, 用于计算iou
						bbox1_pred_xyxy = ((pred[i,0,m,n]+m)/num_gridx - pred[i,2,m,n]/2,(pred[i,1,m,n]+n)/num_gridy - pred[i,3,m,n]/2,
										   (pred[i,0,m,n]+m)

以上是关于日常pytorch编写“自创”的MRR损失函数的主要内容,如果未能解决你的问题,请参考以下文章

学习笔记Pytorch十二损失函数与反向传播

上一批Pytorch损失函数错误

说话人识别损失函数的PyTorch实现与代码解读

如何在 PyTorch 中添加自定义定位损失函数?

pytorch 自定义损失函数 nn.CrossEntropyLoss

Pytorch:损失函数