具有自定义损失函数的模型的参数不会通过其对 epoch 的学习进行升级

The parameters of the model with custom loss function doesn’t upgraded thorough its learning over epochs

感谢您阅读我的 post。 我目前正在开发使用 CNN 的峰值检测算法来确定理想的卷积核,该卷积核可表示为理想的母小波函数,可以最大限度地提高峰值检测精度。

首先,我创建了自己的 IoU 损失函数和简单模型并尝试 运行 学习。执行本身没有任何错误,但不知何故失败了。

具有自定义损失函数的模型的参数未通过其历时学习升级

我自己的损失函数描述如下

def IoU(inputs: torch.Tensor, labels: torch.Tensor, 
             smooth: float=0.1, threshold: float = 0.5, alpha: float = 1.0):
  '''
  - alpha: a parameter that sharpen the thresholding.
    if alpha = 1 -> thresholded input is the same as raw input.
  '''

  thresholded_inputs = inputs**alpha / (inputs**alpha + (1 - inputs)**alpha)
  inputs = torch.where(thresholded_inputs < threshold, 0, 1)
  batch_size = inputs.shape[0]

  intersect_tensor = (inputs * labels).view(batch_size, -1)
  intersect = intersect_tensor.sum(-1)

  union_tensor = torch.max(inputs, labels).view(batch_size, -1)
  union = union_tensor.sum(-1)

  iou = (intersect + smooth) / (union + smooth)  # We smooth our devision to avoid 0/0
  iou_score = iou.mean()

  return 1- iou_score

我的训练模型是,

class MLP(nn.Module):
  def __init__(self):
    super().__init__()
    self.net = nn.Sequential(
        nn.Conv1d(1, 1, kernel_size=32, stride=1, padding=16),
        nn.Linear(257, 256),
        nn.LogSoftmax(1)
    )
  def forward(self, x):
    return self.net(x)

model = MLP()
opt = optim.Adadelta(model.parameters())

# initialization of the kernel of Conv1d
def init_kernel(m):
  if type(m) == nn.Conv1d:
    nn.init.kaiming_normal_(m.weight)
    print(m.weight)
    plt.plot(m.weight[0][0].detach().numpy())

model.apply(init_kernel)

def step(x, y, is_train=True):
  opt.zero_grad()

  y_pred = model(x)
  y_pred = y_pred.reshape(-1, 256)

  loss = IoU(y_pred, y)
  loss.requires_grad = True
  loss.retain_grad = True

  if is_train:
    loss.backward()
    opt.step()

  return loss, y_pred

最后,执行代码是,

from torch.autograd.grad_mode import F

train_loss_arr, val_loss_arr = [], []
valbose = 10
epochs = 200

for e in range(epochs):
  train_loss, val_loss, acc = 0., 0., 0.,
  for x, y in train_set.as_numpy_iterator():
    x = torch.from_numpy(x)
    y = torch.from_numpy(y)
    model.train()
    loss, y_pred = step(x, y, is_train=True)
    train_loss += loss.item()
  train_loss /= len(train_set)

  for x, y ,in val_set.as_numpy_iterator():
    x = torch.from_numpy(x)
    y = torch.from_numpy(y)
    model.eval()
    with torch.no_grad():
      loss, y_pred = step(x, y, is_train=False)
    val_loss += loss.item()
  val_loss /= len(val_set)

  train_loss_arr.append(train_loss)
  val_loss_arr.append(val_loss)

  # visualize current kernel to check whether the learning is on progress safely.
  if e % valbose == 0: 
    print(f"Epoch[{e}]({(e*100/epochs):0.2f}%):  train_loss: {train_loss:0.4f}, val_loss: {val_loss:0.4f}")
    fig, axs = plt.subplots(1, 4, figsize=(12, 4))
    print(y_pred[0], y_pred[0].shape)
    axs[0].plot(x[0][0])
    axs[0].set_title("spectra")
    axs[1].plot(y_pred[0])
    axs[1].set_title("y pred")
    axs[2].plot(y[0])
    axs[2].set_title("y true")
    axs[3].plot(model.state_dict()["net.0.weight"][0][0].numpy())
    axs[3].set_title("kernel1")
    plt.show()

使用这些程序,我尝试评估这个简单的模型,但是,模型参数在各个时期都没有改变。

第 0 轮和第 30 轮结果的可视化。

纪元 0: prediction and kernel at epoch0

纪元 30: prediction and kernel at epoch30

如您所见,内核在历时的学习中没有被修改。

我做了几个小时的调查来找出导致这个问题的原因,但我仍然不确定如何将我的损失函数和模型修复为可训练的

谢谢。

尝试在 loss.backward() 之后打印渐变:

y_pred.grad()

我怀疑您会发现在向后传递之后,y_pred 的梯度为零。这意味着要么 a.) 没有为计算图具有节点的一个或多个变量启用梯度,要么 b.) (更有可能)您正在使用不可微分的操作。

在您的情况下,至少 torch.where 是 non-differentiable,因此您需要替换它。 Thersholding 操作是 non-differentiable,通常替换为“软”阈值操作(请参阅 Softmax 而不是 max 函数进行分类),以便梯度计算仍然有效。尝试用软阈值或根本没有阈值替换它。