Все ли переменные в функции потерь должны быть тензорными с градами в pytorch? - PullRequest
0 голосов
/ 25 октября 2019

У меня есть следующая функция


def msfe(ys, ts):
    ys=ys.detach().numpy() #output from the network
    ts=ts.detach().numpy() #Target (true labels)
    pred_class = (ys>=0.5) 
    n_0 = sum(ts==0) #Number of true negatives
    n_1 = sum(ts==1) #Number of true positives
    FPE = sum((ts==0)[[bool(p) for p in (pred_class==1)]])/n_0 #False positive error
    FNE = sum((ts==1)[[bool(p) for p in (pred_class==0)]])/n_1 #False negative error
    loss= FPE**2+FNE**2

    loss=torch.tensor(loss,dtype=torch.float64,requires_grad=True)


    return loss

, и мне интересно, работает ли автоград в Pytorch правильно, поскольку ys и ts не имеют флага grad.

Итак, мой вопрос: все переменные (FPE,FNE,ys,ts,n_1,n_0) должны быть тензорами, прежде чем optimizer.step() сработает, или это нормально, что это только конечная функция (loss), которая есть?

1 Ответ

3 голосов
/ 25 октября 2019

Все переменные, которые вы хотите оптимизировать с помощью optimizer.step(), должны иметь градиент.

В вашем случае это будет y предсказано сетью, поэтому вы не должны detach его (изграфик).

Обычно вы не меняете свой targets, поэтому для них не нужны градиенты. Вам не нужно их detach, тензоры по умолчанию не требуют градиента и не будут иметь обратного распространения.

Loss будет иметь градиент, если его ингредиенты (по крайней мере, один) имеют градиент.

В целом, вам редко нужно заботиться об этом вручную.

Кстати. не используйте numpy с PyTorch, редко бывает так. Вы можете выполнять большинство операций над массивом numpy с тензором PyTorch.

BTW2. В pytorch больше нет такой вещи, как Variable, только тензоры, которым требуется градиент, и те, которые не требуют.

Недифференцируемость

1.1 Проблемы с существующим кодом

Действительно, вы используете функции, которые нельзя дифференцировать (а именно >= и ==). Это доставит вам неприятности только в случае ваших выходов, поскольку они требуют градиента (хотя вы можете использовать == и >= для targets).

Ниже я добавил вашу функцию потерь и обрисовал в общих чертахПроблемы в нем в комментариях:

# Gradient can't propagate if you detach and work in another framework
# Most Python constructs should be fine, detaching will ruin it though.
def msfe(outputs, targets):
    # outputs=outputs.detach().numpy() # Do not detach, no need to do that
    # targets=targets.detach().numpy() # No need for numpy either
    pred_class = outputs >= 0.5  # This one is non-differentiable
    # n_0 = sum(targets==0) # Do not use sum, there is pytorch function for that
    # n_1 = sum(targets==1)

    n_0 = torch.sum(targets == 0)  # Those are not differentiable, but...
    n_1 = torch.sum(targets == 1)  # It does not matter as those are targets

    # FPE = sum((targets==0)[[bool(p) for p in (pred_class==1)]])/n_0 # Do not use Python bools
    # FNE = sum((targets==1)[[bool(p) for p in (pred_class==0)]])/n_1 # Stay within PyTorch
    # Those two below are non-differentiable due to == sign as well
    FPE = torch.sum((targets == 0.0) * (pred_class == 1.0)).float() / n_0
    FNE = torch.sum((targets == 1.0) * (pred_class == 0.0)).float() / n_1
    # This is obviously fine
    loss = FPE ** 2 + FNE ** 2

    # Loss should be a tensor already, don't do things like that
    # Gradient will not be propagated, you will have a new tensor
    # Always returning gradient of `1` and that's all
    # loss = torch.tensor(loss, dtype=torch.float64, requires_grad=True)

    return loss

1.2 Возможное решение

Итак, нужно избавиться от 3 недифференцируемых частей. В принципе, вы можете попытаться приблизить его к непрерывным выходам из вашей сети (при условии, что вы используете sigmoid в качестве активации). Вот мое мнение:

def msfe_approximation(outputs, targets):
    n_0 = torch.sum(targets == 0)  # Gradient does not flow through it, it's okay
    n_1 = torch.sum(targets == 1)  # Same as above
    FPE = torch.sum((targets == 0) * outputs).float() / n_0
    FNE = torch.sum((targets == 1) * (1 - outputs)).float() / n_1

    return FPE ** 2 + FNE ** 2

Обратите внимание, что для минимизации FPE outputs будет пытаться быть zero в индексах, где targets равны нулю. Аналогично для FNE, если цели 1, сеть также попытается вывести 1.

Обратите внимание на сходство этой идеи с BCELoss (Binary CrossEntropy).

И, наконец, пример, на котором вы можете запустить это, просто для проверки работоспособности:

if __name__ == "__main__":
    model = torch.nn.Sequential(
        torch.nn.Linear(30, 100),
        torch.nn.ReLU(),
        torch.nn.Linear(100, 200),
        torch.nn.ReLU(),
        torch.nn.Linear(200, 1),
        torch.nn.Sigmoid(),
    )
    optimizer = torch.optim.Adam(model.parameters())
    targets = torch.randint(high=2, size=(64, 1)) # random targets
    inputs = torch.rand(64, 30) # random data
    for _ in range(1000):
        optimizer.zero_grad()
        outputs = model(inputs)
        loss = msfe_approximation(outputs, targets)
        print(loss)
        loss.backward()
        optimizer.step()

    print(((model(inputs) >= 0.5) == targets).float().mean())
Добро пожаловать на сайт PullRequest, где вы можете задавать вопросы и получать ответы от других членов сообщества.
...