Обратное распространение y = x / sum(x, dim=0), где размер тензора x равен (H,W)

Q1.

Я пытаюсь сделать свою пользовательскую функцию autograd с помощью pytorch.

Но у меня возникла проблема с аналитическим обратным распространением с y = x/sum(x, dim=0)

где размер тензора x равен (высота, ширина) (x двумерный).

Вот мой код

class MyFunc(torch.autograd.Function):
@staticmethod
def forward(ctx, input):
  ctx.save_for_backward(input)
  input = input / torch.sum(input, dim=0)

  return input

@staticmethod
def backward(ctx, grad_output):
  input = ctx.saved_tensors[0]
  H, W = input.size()
  sum = torch.sum(input, dim=0)
  grad_input = grad_output * (1/sum - input*1/sum**2)

  return grad_input

Я использовал (torch.autograd import) gradcheck для сравнения матрицы Якоби,

from torch.autograd import gradcheck
func = MyFunc.apply
input = (torch.randn(3,3,dtype=torch.double,requires_grad=True))
test = gradcheck(func, input)

и результат был

введите здесь описание изображения

Пожалуйста, помогите мне получить правильный результат обратного распространения

Спасибо!


Q2.

Спасибо за ответы!

Благодаря вашей помощи я смог реализовать обратное распространение в случае тензора (H,W).

Однако, пока я реализовал обратное распространение в случае тензора (N,H,W), у меня возникла проблема. Я думаю, проблема будет в инициализации нового тензора.

Вот мой новый код

import torch
import torch.nn as nn
import torch.nn.functional as F

class MyFunc(torch.autograd.Function):
  @staticmethod
  def forward(ctx, input):
    ctx.save_for_backward(input)
    
    N = input.size(0)
    for n in range(N):
      input[n] /= torch.sum(input[n], dim=0)

    return input

  @staticmethod
  def backward(ctx, grad_output):
    input = ctx.saved_tensors[0]
    N, H, W = input.size()
    I = torch.eye(H).unsqueeze(-1)
    sum = input.sum(1)

    grad_input = torch.zeros((N,H,W), dtype = torch.double, requires_grad=True)
    for n in range(N):
      grad_input[n] = ((sum[n] * I - input[n]) * grad_output[n] / sum[n]**2).sum(1)

    return grad_input

Код Gradcheck

from torch.autograd import gradcheck
func = MyFunc.apply
input = (torch.rand(2,2,2,dtype=torch.double,requires_grad=True))
test = gradcheck(func, input)
print(test)

и результат: введите здесь описание изображения

Я не знаю, почему возникает ошибка...

Ваша помощь будет мне очень полезна для реализации моей собственной сверточной сети.

Спасибо! Хорошего дня.


person Jin    schedule 07.02.2021    source источник
comment
Почему вы хотите реализовать обратный проход самостоятельно?   -  person Ivan    schedule 07.02.2021
comment
@Ivan, потому что я хочу создать свою собственную свёрточную сеть   -  person Jin    schedule 08.02.2021


Ответы (3)


Давайте рассмотрим пример с одним столбцом, например: [[x1], [x2], [x3]].

Пусть sum будет x1 + x2 + x3, тогда нормализация x даст y = [[y1], [y2], [y3]] = [[x1/sum], [x2/sum], [x3/sum]]. Вы ищете dL/dx1, dL/x2 и dL/x3 — мы просто напишем их как: dx1, dx2 и dx3. То же самое для всех dL/dyi.

Итак, dx1 равно dL/dy1*dy1/dx1 + dL/dy2*dy2/dx1 + dL/dy3*dy3/dx1. Это потому, что x1 вносит свой вклад во все элементы вывода в соответствующем столбце: y1, y2 и y3.

У нас есть:

  • dy1/dx1 = d(x1/sum)/dx1 = (sum - x1)/sum²

  • dy2/dx1 = d(x2/sum)/dx1 = -x2/sum²

  • аналогично, dy3/dx1 = d(x3/sum)/dx1 = -x3/sum²

Поэтому dx1 = (sum - x1)/sum²*dy1 - x2/sum²*dy2 - x3/sum²*dy3. То же самое для dx2 и dx3. В результате якобиан равен [dxi]_i = (sum - xi)/sum² и [dxi]_j = -xj/sum² (для всех j, отличных от i).

В вашей реализации отсутствуют все недиагональные компоненты.

Сохраняя тот же пример с одним столбцом, с x1=2, x2=3 и x3=5:

>>> x = torch.tensor([[2.], [3.], [5.]])

>>> sum = input.sum(0)
tensor([10])

Якобиан будет:

>>> J = (sum*torch.eye(input.size(0)) - input)/sum**2
tensor([[ 0.0800, -0.0200, -0.0200],
        [-0.0300,  0.0700, -0.0300],
        [-0.0500, -0.0500,  0.0500]])

Для реализации с несколькими столбцами это немного сложнее, особенно для формы диагональной матрицы. Проще оставить ось column последней, чтобы нам не приходилось возиться с трансляциями:

>>> x = torch.tensor([[2., 1], [3., 3], [5., 5]])
>>> sum = x.sum(0)
tensor([10.,  9.])

>>> diag = sum*torch.eye(3).unsqueeze(-1).repeat(1, 1, len(sum))
tensor([[[10.,  9.],
         [ 0.,  0.],
         [ 0.,  0.]],

        [[ 0.,  0.],
         [10.,  9.],
         [ 0.,  0.]],

        [[ 0.,  0.],
         [ 0.,  0.],
         [10.,  9.]]])

Выше diag имеет форму (3, 3, 2), где два столбца находятся на последней оси. Обратите внимание, что нам не нужно было транслировать sum.

Чего я не стал бы делать, так это: torch.eye(3).unsqueeze(0).repeat(len(sum), 1, 1). Так как с такой формой - (2, 3, 3) - вам придется использовать sum[:, None, None], и потребуется дальнейшая трансляция в будущем...

Якобиан просто:

>>> J = (diag - x)/sum**2
tensor([[[ 0.0800,  0.0988],
         [-0.0300, -0.0370],
         [-0.0500, -0.0617]],

        [[-0.0200, -0.0123],
         [ 0.0700,  0.0741],
         [-0.0500, -0.0617]],

        [[-0.0200, -0.0123],
         [-0.0300, -0.0370],
         [ 0.0500,  0.0494]]])

Вы можете проверить результаты путем обратного распространения через операцию, используя произвольный вектор dy (но не с torch.ones, вы получите 0s из-за J!). После обратного распространения x.grad должно равняться torch.einsum('abc,bc->ac', J, dy).

person Ivan    schedule 07.02.2021
comment
Я вижу, вы сделали упражнение;) +1 - person Shai; 08.02.2021
comment
Спасибо за ваш ответ! Я мог бы реализовать обратное распространение в случае тензора (H, W) - person Jin; 08.02.2021
comment
Однако у меня возникла проблема с тензором (N, H, W)... Хотя я использовал цикл for для каждого тензора в пакете (N), произошла ошибка. Я загружу свой код и изображение. Не могли бы вы помочь мне получить правильный ответ для тензорного случая (N, H, W) ?? Спасибо! - person Jin; 08.02.2021

Ваш якобиан неточен: это четырехмерный тензор, вы вычислили только его двумерный фрагмент.

Вы пренебрегли второй строкой якобиана:

введите здесь описание изображения

person Shai    schedule 07.02.2021

Ответ на вопрос 2.

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

размер ввода: (N,H,W) (N - размер партии)

forward:
  out = input / torch.sum(input, dim=1).unsqueeze(1)

backward:
  diag = torch.eye(input.size(1),  dtype=torch.double, requires_grad=True).unsqueeze(-1)
  sum = input.sum(1)
  grad_input = ((sum.unsqueeze(1).unsqueeze(1) * diag - input.unsqueeze(1)) * grad_out.unsqueeze(1) / (sum**2).unsqueeze(1).unsqueeze(1)).sum(2)
person Jin    schedule 08.02.2021