Pytorch自定义标准取决于目标



我正在做一个研究项目,我想根据目标创建一个自定义损失函数。即,我想用BCEWithLogitsLoss加上添加超参数lambda进行惩罚。我只想在模型没有正确检测到类的情况下添加这个超参数。

更详细地说,我有一个预训练的模型,我想重新训练冻结一些层。该模型以一定的概率检测图像中的人脸。我想惩罚某些类型的图像,如果它们被错误地用因子lambda分类(假设需要惩罚的图像在名称中有一个特殊字符左右)

来自pytorch:的源代码

import torch.nn.modules.loss as l
class CustomBCEWithLogitsLoss(l._Loss):
def __init__(self, weight: Optional[Tensor] = None, size_average=None, reduce=None, reduction: str = 'mean',
pos_weight: Optional[Tensor] = None) -> None:
super(BCEWithLogitsLoss, self).__init__(size_average, reduce, reduction)
self.register_buffer('weight', weight)
self.register_buffer('pos_weight', pos_weight)
self.weight: Optional[Tensor]
self.pos_weight: Optional[Tensor]
def forward(self, input: Tensor, target: Tensor) -> Tensor:
return F.binary_cross_entropy_with_logits(input, target,
self.weight,
pos_weight=self.pos_weight,
reduction=self.reduction)

这里,forward有两个张量作为输入,所以我不知道如何在这里添加我想用lambda惩罚的图像类。将lambda添加到构造函数中是可以的,但如果它只允许张量,如何进行正向传递?

编辑:为了澄清这个问题,假设我有一个包含图像的训练/测试文件夹。文件名中包含字符@的文件是我想要正确分类的文件,而不是不包含字符的文件,因子为lambda

我怎么能像在pytorch中训练模型的常规方式一样,判断这些文件必须使用lambda惩罚(假设损失函数是lambda*BCEWithLogitLoss),而其他文件则不需要?我正在使用DataLoader

您可以为数据集创建一个自定义类,也可以在现有内置数据集的基础上构建。例如,可以使用datasets.ImageFolder作为基类。顶部添加的逻辑是识别文件名是否包含特殊令牌,例如@,并在__getitem__返回的元素中提供此信息。从datasets.DatasetFolder查看父__getitem__函数,最小的工作实现可能是:

class Dataset(datasets.ImageFolder):
def __init__(self, token, *args, **kwargs):
super().__init__(*args, **kwargs)
self.token = token

def __getitem__(self, index):
path, _ = self.samples[index]      # retrieve path of instance
match = self.token in path         # determine if there is a match 
x, y = super().__getitem__(index)  # call parent to get input and label
return x, int(match), y

每个数据集元素由输入图像张量和标签01组成,该标签指定该输入的文件名中是否具有token

>>> ds = Dataset(token='@', root='root_to_dataset')

与任何其他数据集一样使用:使用DataLoader包装

>>> dl = DataLoader(ds, batch_size=2)

现在,当在这个dalloader上迭代时,您将得到:

>>> for x, m, y in dl:
...    # x is the batch of images (b, c, h, w)
...    # m is the batch of {0,1} whether inputs have the pattern in their path (b,)
...    # y is the batch of labels (b,) 

现在我们有了这个,我们需要在损失项上应用lambda因子。然而,我们不能在这里假设给定的迷你批次中的所有元素都将遵循标准(,即在其文件名中有模式),因此我们需要按元素处理,而不是以简化的形式。

如果您查看内置损失函数nn.modules.loss的源文件,您会注意到所有损失函数都基于一个名为_Loss的类,该类需要一个reduction参数。这将对我们有用。

首先,考虑关闭减少损失功能:

>>> bce = nn.BCEWithLogitLoss(reduction='none') # provide additional args if necessary

考虑到我们有";匹配";当输入在其文件名中具有令牌时包含1m,否则包含0。给定λ因子lamb,我们想用它来加权m=1中的元素,我们可以为我们的损失项提供以下系数来执行所需的运算:

>>> coeff = lamb*m + 1-m 
# if m=0 => coeff=1; 
# if m=1 => coeff=lamb;

为了正确应用损失策略,我们简单地将coeff与未减少的损失项(其形状为(b,))逐点相乘。

>>> weighted = coeff*bce_loss

总而言之,这看起来是这样的:

>>> for x, m, y in dl:
...    y_pred = model(x)
...    bce_loss = bce(y_pred, y)
...    coeff = lamb*m + 1-m 
...    bce_weighted = torch.mean(coeff*bce_loss)
...    bce_weighted.backward()

感谢您的提问。你为什么不做一个自定义的损失函数?。你仍然局限于PyTorch的功能;也许写下损失的自定义方程式,就会有人帮忙。(这不是一个答案,但我还不能留下评论)

您似乎遇到了根据训练结果修改动态权重的问题。我不确定你的公式(lambda项),让我们看看pytorch文档中的数学公式,我想你想要:

l(xi,yi) = lambda * loss(i) if (xi>0.5==yi) else loss(i) # correct class condition

如果它不正确,我认为你可以很容易地修改它以适应你的项目

import torch.nn.modules.loss as l
from typing import Optional
from torch import Tensor
from torch.nn import functional as F
class CustomBCEWithLogitsLoss(l._Loss):
def __init__(self, alpha: float, weight: Optional[Tensor] = None, size_average=None, reduce=None, reduction: str = 'mean',
pos_weight: Optional[Tensor] = None) -> None:
super(CustomBCEWithLogitsLoss, self).__init__(size_average, reduce, reduction)
self.register_buffer('weight', weight)
self.register_buffer('pos_weight', pos_weight)
#         self.weight: Optional[Tensor]
self.pos_weight: Optional[Tensor]

self.alpha = alpha # replace lambda by alpha to avoid the unwanted bug with lambda function

def forward(self, input: Tensor, target: Tensor) -> Tensor:
reg = torch.ones_like(target, dtype=torch.float32)
pred = input.ge(0.5).int()
mask = pred == target
reg[~mask] *= self.alpha # only apply to correct class
return F.binary_cross_entropy_with_logits(input, target,
weight = reg, # replace weight by alpha
pos_weight=self.pos_weight,
reduction=self.reduction)

应该可以使用your_loader.dataset.samples[i]元组(假设训练循环中的for i, (images, labels) in enumerate(your_loader)shuffle=False)访问文件名,并根据其将loss乘以lambda,如果批处理大小为1,则您甚至不需要自定义loss函数。

理想的解决方案可能需要一个从ImageFolder继承的自定义Dataset,以在__getitem__()中返回一个附加特性并将其进一步传递。

最新更新