Штраф за градиент активации

Вот простая нейронная сеть, в которой я пытаюсь наказать норму градиентов активации:

class Net(nn.Module):
    def __init__(self):
        super(Net, self).__init__()
        self.conv1 = nn.Conv2d(3, 32, kernel_size=5)
        self.conv2 = nn.Conv2d(32, 64, kernel_size=5)
        self.pool = nn.MaxPool2d(2, 2)
        self.relu = nn.ReLU()
        self.linear = nn.Linear(64 * 5 * 5, 10)

    def forward(self, input):
        conv1 = self.conv1(input)
        pool1 = self.pool(conv1)
        self.relu1 = self.relu(pool1)
        self.relu1.retain_grad()
        conv2 = self.conv2(relu1)
        pool2 = self.pool(conv2)
        relu2 = self.relu(pool2)
        self.relu2 = relu2.view(relu2.size(0), -1)
        self.relu2.retain_grad()
        return self.linear(relu2)

model = Net()
optimizer = torch.optim.SGD(model.parameters(), lr=0.001)

for i in range(1000):
    output = model(input)
    loss = nn.CrossEntropyLoss()(output, label)
    optimizer.zero_grad()
    loss.backward(retain_graph=True)

    grads = torch.autograd.grad(loss, [model.relu1, model.relu2], create_graph=True)

    grad_norm = 0
    for grad in grads:
        grad_norm += grad.pow(2).sum()

    grad_norm.backward()

    optimizer.step()

Однако это не дает желаемого эффекта регуляризации. Если я делаю то же самое для весов (вместо активаций), это работает хорошо. Правильно ли я делаю (с точки зрения механизма pytorch)? В частности, что происходит при вызове grad_norm.backward()? Я просто хочу убедиться, что обновлены градиенты веса, а не градиенты активации. В настоящее время, когда я распечатываю градиенты для весов и активаций непосредственно до и после этой строки, оба изменяются, поэтому я не уверен, что происходит.


person MichaelSB    schedule 16.02.2019    source источник
comment
Что вы подразумеваете под градиентами активации?   -  person Chris Holland    schedule 19.02.2019
comment
@ChrisHolland, градиенты функции потерь по отношению к активациям (self.relu1 и self.relu2). Я хочу наказать их рост.   -  person MichaelSB    schedule 19.02.2019
comment
ReLU не имеет обучаемых параметров, поэтому градиента нет.   -  person Chris Holland    schedule 19.02.2019
comment
Я не уверен, что ты имеешь в виду. Зачем им обучаться, чтобы иметь градиенты? Как ошибка могла распространяться через них, если у них не было градиентов?   -  person MichaelSB    schedule 19.02.2019
comment
Здесь вы запускаете второй процесс оптимизации для оптимизации нормы градиента, который вычисляет градиенты более высокого порядка. Это означает, что вы изменяете свои параметры для создания градиентов, которые становятся все меньше и меньше - градиенты, не веса. Кроме того, это может привести к непредвиденным последствиям, так как я не уверен, что второй процесс оптимизации не мешает основному. Так ты уверен, что это то, что ты хочешь?   -  person cleros    schedule 23.02.2019
comment
Цель состоит в том, чтобы сделать модель устойчивой к активационным искажениям (например, добавлению гауссова шума). Одним из хорошо известных подходов является применение штрафа за градиент веса, и он работает нормально, когда реализуется таким образом. Я хочу сделать то же самое для активаций. Есть ли у вас какие-либо мысли об этом потенциальном вмешательстве в оптимизацию или о любых других проблемах?   -  person MichaelSB    schedule 24.02.2019


Ответы (1)


Я думаю, что ваш код в конечном итоге вычисляет некоторые градиенты дважды на каждом шаге. Я также подозреваю, что он на самом деле никогда не обнуляет градиенты активации, поэтому они накапливаются по шагам.

В целом:

  • x.backward() вычисляет градиент x относительно. листья графа вычислений (например, тензоры весов и другие переменные), а также относительно. узлы, явно отмеченные retain_grad(). Он накапливает вычисленный градиент в атрибутах .grad тензоров.

  • autograd.grad(x, [y, z]) возвращает градиент x относительно. y и z независимо от того, сохранят ли они градус или нет. По умолчанию он также накапливает градиент во всех атрибутах .grad листьев. Вы можете предотвратить это, передав only_inputs=True.

Я предпочитаю использовать backward() только для шага оптимизации и autograd.grad() всякий раз, когда моей целью является получение «овеществленных» градиентов в качестве промежуточных значений для другого вычисления. Таким образом, я могу быть уверен, что никакие нежелательные градиенты не останутся в атрибутах .grad тензоров после того, как я закончу с ними.

import torch
from torch import nn
class Net(nn.Module):
    def __init__(self):
        super(Net, self).__init__()
        self.conv1 = nn.Conv2d(3, 32, kernel_size=5)
        self.conv2 = nn.Conv2d(32, 64, kernel_size=5)
        self.pool = nn.MaxPool2d(2, 2)
        self.relu = nn.ReLU()
        self.linear = nn.Linear(64 * 5 * 5, 10)

    def forward(self, input):
        conv1 = self.conv1(input)
        pool1 = self.pool(conv1)
        self.relu1 = self.relu(pool1)
        conv2 = self.conv2(self.relu1)
        pool2 = self.pool(conv2)
        self.relu2 = self.relu(pool2)
        relu2 = self.relu2.view(self.relu2.size(0), -1)
        return self.linear(relu2)


model = Net()
optimizer = torch.optim.SGD(model.parameters(), lr=0.001)
grad_penalty_weight = 10.

for i in range(1000000):
    # Random input and labels; we're not really learning anything
    input = torch.rand(1, 3, 32, 32)
    label = torch.randint(0, 10, (1,))

    output = model(input)
    loss = nn.CrossEntropyLoss()(output, label)

    # This is where the activation gradients are computed
    # only_inputs is optional here, since we're going to call optimizer.zero_grad() later
    # But it makes clear that we're *only* interested in the activation gradients at this point
    grads = torch.autograd.grad(loss, [model.relu1, model.relu2], create_graph=True, only_inputs=True)

    grad_norm = 0
    for grad in grads:
        grad_norm += grad.pow(2).sum()

    optimizer.zero_grad()
    loss = loss + grad_norm * grad_penalty_weight
    loss.backward()
    optimizer.step()

Этот код похоже работает, поскольку градиенты активации становятся меньше. Я не могу комментировать жизнеспособность этого метода как метода регуляризации.

person Maciej Godek    schedule 01.08.2019
comment
Спасибо, и я извиняюсь, что не ответил раньше. Очень хорошая мысль о параметре only_inputs. Я не совсем понимаю, почему вы думаете, что градиенты не обнулены - я использую optimizer.zero_grad() в своем коде. Не могли бы вы объяснить? - person MichaelSB; 19.09.2019
comment
Если память не изменяет, optimizer.zero_grad() обнуляет только градиенты переменных, указанные во время построения optimizer, в данном случае model.parameters(). Я не уверен, что именно ваш код делает в результате. Полчаса с отладчиком могли бы все прояснить. - person Maciej Godek; 22.09.2019