我正在使用 PyTorch 训练 CNN 架构来解决回归问题,其中我的输出是 25 个值的张量。输入/目标张量可以是全零或 sigma 值为 2 的高斯分布。4 个样本批次的示例如下:
[[0.13534, 0.32465, 0.60653, 0.8825, 1.0000, 0.88250,0.60653, 0.32465, 0.13534, 0.043937, 0.011109, 0., 0., 0., 0., 0., 0., 0., 0., 0., 0., 0., 0., 0., 0.],
[0., 0., 0., 0., 0., 0., 0.13534, 0.32465, 0.60653, 0.8825, 1.0000, 0.88250,0.60653, 0.32465, 0.13534, 0.043937, 0.011109, 0., 0., 0., 0., 0., 0., 0., 0.],
[0., 0., 0., 0., 0., 0., 0., 0., 0., 0., 0., 0., 0., 0., 0., 0., 0.13534, 0.32465, 0.60653, 0.8825, 1.0000, 0.88250,0.60653, 0.32465, 0.13534 ],
[0., 0., 0., 0., 0., 0., 0., 0., 0., 0., 0., 0., 0., 0., 0., 0., 0., 0., 0., 0., 0., 0., 0., 0., 0.]]
我的问题是如何为模型设计损失函数以有效学习具有 25 个值的回归输出。
我尝试了两种类型的损失,
torch.nn.MSELoss()
和torch.nn.MSELoss()-torch.nn.CosineSimilarity()
。他们有点工作。然而,有时网络会很难收敛,尤其是当有很多样本全部为“0”时,这会导致网络输出一个包含所有 25 个小值的向量。
我的问题是,还有其他我们可以尝试的损失吗?
您的价值观在规模上似乎没有很大差异,因此 MSELoss 似乎可以正常工作。由于目标中有许多零,您的模型可能会崩溃。
你可以随时尝试
torch.nn.L1Loss()
(但我不认为它会比torch.nn.MSELoss()
好很多)
我建议您尝试预测高斯平均值/mu,然后如果您确实需要的话,尝试为每个样本重新创建高斯。
因此,如果您选择尝试此方法,您有两种选择。
一个好的替代方案是将您的目标编码为看起来像分类目标。您的 25 个元素向量变成单个值,其中原始目标 == 1(可能的类为 0、1、2、...、24)。然后,我们可以将包含“仅零”的样本分配为最后一个类别“25”。所以你的目标:
[[0.13534, 0.32465, 0.60653, 0.8825, 1.0000, 0.88250,0.60653, 0.32465, 0.13534, 0.043937, 0.011109, 0., 0., 0., 0., 0., 0., 0., 0., 0., 0., 0., 0., 0., 0.],
[0., 0., 0., 0., 0., 0., 0.13534, 0.32465, 0.60653, 0.8825, 1.0000, 0.88250,0.60653, 0.32465, 0.13534, 0.043937, 0.011109, 0., 0., 0., 0., 0., 0., 0., 0.],
[0., 0., 0., 0., 0., 0., 0., 0., 0., 0., 0., 0., 0., 0., 0., 0., 0.13534, 0.32465, 0.60653, 0.8825, 1.0000, 0.88250,0.60653, 0.32465, 0.13534 ],
[0., 0., 0., 0., 0., 0., 0., 0., 0., 0., 0., 0., 0., 0., 0., 0., 0., 0., 0., 0., 0., 0., 0., 0., 0.]]
成为
[4,
10,
20,
25]
如果你这样做,那么你可以尝试常见的
torch.nn.CrossEntropyLoss()
。
我不知道你的数据加载器是什么样的,但给定原始格式的单个样本,你可以使用以下方法将其转换为我建议的格式:
def encode(tensor):
if tensor.sum() == 0:
return len(tensor)
return torch.argmax(tensor)
然后回到高斯:
def decode(value):
n_values = 25
zero = torch.zeros(n_values)
if value == n_values:
return zero
# Create gaussian around value
std = 2
n = torch.arange(n_values) - value
sig = 2*std**2
gauss = torch.exp(-n**2 / sig2)
# Only return 9 values from the gaussian
start_ix = max(value-6, 0)
end_ix = min(value+7,n_values)
zero[start_ix:end_ix] = gauss[start_ix:end_ix]
return zero
(注意我没有尝试过批次,只是样品)
第二个选项是将回归目标(仍然只有 argmax 位置(mu))更改为 0-1 范围内更好的回归值,并有一个单独的神经元输出“掩码值”(也是 0-1)。那么您的批次:
[[0.13534, 0.32465, 0.60653, 0.8825, 1.0000, 0.88250,0.60653, 0.32465, 0.13534, 0.043937, 0.011109, 0., 0., 0., 0., 0., 0., 0., 0., 0., 0., 0., 0., 0., 0.],
[0., 0., 0., 0., 0., 0., 0.13534, 0.32465, 0.60653, 0.8825, 1.0000, 0.88250,0.60653, 0.32465, 0.13534, 0.043937, 0.011109, 0., 0., 0., 0., 0., 0., 0., 0.],
[0., 0., 0., 0., 0., 0., 0., 0., 0., 0., 0., 0., 0., 0., 0., 0., 0.13534, 0.32465, 0.60653, 0.8825, 1.0000, 0.88250,0.60653, 0.32465, 0.13534 ],
[0., 0., 0., 0., 0., 0., 0., 0., 0., 0., 0., 0., 0., 0., 0., 0., 0., 0., 0., 0., 0., 0., 0., 0., 0.]]
成为
# [Mask, mu]
[
[1, 0.1666], # True, 4/24
[1, 0.4166], # True, 10/24
[1, 0.8333], # True, 20/24
[0, 0] # False, undefined
]
如果您使用此设置,那么您应该能够使用经过修改的 MSELoss:
def custom_loss(input, target):
# Assume target and input is of shape [Batch, 2]
mask = target[...,1]
mask_loss = torch.nn.functional.mse_loss(input[...,0], target[...,0])
mu_loss = torch.nn.functional.mse_loss(mask*input[...,1], mask*target[...,1])
return (mask_loss + mu_loss) / 2
如果目标的掩码为 1,则此损失只会查看第二个值 (mu)。否则,它只会尝试优化正确的掩码。
要编码为这种格式,您可以使用:
def encode(tensor):
n_values = 25
if tensor.sum() == 0:
return torch.tensor([0,0])
return torch.argmax(tensor) / (n_values-1)
并解码:
def decode(tensor):
n_values = 25
# Parse values
mask, value = tensor
mask = torch.round(mask)
value = torch.round((n_values-1)*value)
zero = torch.zeros(n_values)
if mask == 0:
return zero
# Create gaussian around value
std = 2
n = torch.arange(n_values) - value
sig = 2*std**2
gauss = torch.exp(-n**2 / sig2)
# Only return 9 values from the gaussian
start_ix = max(value-6, 0)
end_ix = min(value+7,n_values)
zero[start_ix:end_ix] = gauss[start_ix:end_ix]
return zero