Маскировка и нормализация экземпляров в PyTorch - PullRequest
1 голос
/ 13 октября 2019

Предположим, у меня есть тензор PyTorch, расположенный в форме [N, C, L], где N - размер пакета, C - количество каналов или элементов, а L - длина. В этом случае, если кто-то хочет выполнить нормализацию экземпляра, он делает что-то вроде:

N = 20
C = 100
L = 40
m = nn.InstanceNorm1d(C, affine=True)
input = torch.randn(N, C, L)
output = m(input) 

Это выполнит нормализацию по L-образному измерению для каждого N * C = 2000 срезов данных, вычитая 2000означает масштабирование на 2000 стандартных отклонений и повторное масштабирование на 100 усваиваемых параметров веса и смещения (по одному на канал). Здесь невысказанное предположение состоит в том, что все эти значения существуют и имеют смысл.

Но у меня есть ситуация, когда для среза N = 1 я бы хотел исключить все данные после (скажем) L = 35. Для среза N = 2 (скажем) все данные действительны. Для среза N = 3 исключите все данные после L = 30 и т. Д. Это имитирует данные, которые представляют собой одномерные временные последовательности, имеющие несколько признаков, но не одинаковой длины.

Как можноЯ выполняю экземплярную норму для таких данных, получаю правильную статистику и поддерживаю дифференцируемость / информацию AutoGrad в PyTorch?

Обновление: поддерживая производительность графического процессора или, по крайней мере, не убивая его мертвым.

Я не могу ...

  1. ... Маска с нулевыми значениями, так как это уничтожает компьютерные средства и отклонения, давая ошибочные результаты
  2. ... Маскируйте с помощью np.nan или np.inf, поскольку тензоры PyTorch не игнорируют такие значения, а рассматривают их как ошибки. Они липкие и приводят к мусору. В PyTorch в настоящее время отсутствует эквивалент np.nanmean и np.nanvar.
  3. ... Перестановка или преобразование в доступное расположение данных;такой подход не дает мне того, что мне нужно
  4. ... Использовать pack_padded_sequence;нормализация экземпляра не работает с этой структурой данных, и, насколько я знаю, нельзя импортировать данные в эту структуру. Кроме того, перегруппировка данных все еще будет необходима, см. 3 выше.

Мне не хватает подхода, который дал бы мне то, что мне нужно? Или, может быть, я упускаю метод переупорядочения данных, который позволил бы работать 3 или 4, описанным выше?

Это проблема, с которой постоянно сталкиваются повторяющиеся нейронные сети, следовательно, функциональность pack_padded_sequence, но это не так. Т вполне применимо здесь.

1 Ответ

1 голос
/ 13 октября 2019

Я не думаю, что это напрямую возможно реализовать с использованием существующего InstanceNorm1d, проще всего было бы реализовать его самостоятельно с нуля. Я сделал быструю реализацию, которая должна работать. Чтобы сделать его немного более общим, этому модулю требуется логическая маска (булевский тензор того же размера, что и входные данные), который указывает, какие элементы следует учитывать при прохождении через экземплярную норму.

import torch


class MaskedInstanceNorm1d(torch.nn.Module):
    def __init__(self, num_features, eps=1e-6, momentum=0.1, affine=True, track_running_stats=False):
        super().__init__()
        self.num_features = num_features
        self.eps = eps
        self.momentum = momentum
        self.affine = affine
        self.track_running_stats = track_running_stats
        self.gamma = None
        self.beta = None
        if self.affine:
            self.gamma = torch.nn.Parameter(torch.ones((1, self.num_features, 1), requires_grad=True))
            self.beta = torch.nn.Parameter(torch.zeros((1, self.num_features, 1), requires_grad=True))

        self.running_mean = None
        self.running_variance = None
        if self.affine:
            self.running_mean = torch.zeros((1, self.num_features, 1), requires_grad=True)
            self.running_variance = torch.zeros((1, self.num_features, 1), requires_grad=True)

    def forward(self, x, mask):
        mean = torch.zeros((1, self.num_features, 1), requires_grad=False)
        variance = torch.ones((1, self.num_features, 1), requires_grad=False)

        # compute masked mean and variance of batch
        for c in range(self.num_features):
            if mask[:, c, :].any():
                mean[0, c, 0] = x[:, c, :][mask[:, c, :]].mean()
                variance[0, c, 0] = (x[:, c, :][mask[:, c, :]] - mean[0, c, 0]).pow(2).mean()

        # update running mean and variance
        if self.training and self.track_running_stats:
            for c in range(self.num_features):
                if mask[:, c, :].any():
                    self.running_mean[0, c, 0] = (1-self.momentum) * self.running_mean[0, c, 0] \
                                                 + self.momentum * mean[0, c, 0]
                    self.running_variance[0, c, 0] = (1-self.momentum) * self.running_variance[0, c, 0] \
                                                     + self.momentum * variance[0, c, 0]

        # compute output
        x = (x - mean)/(self.eps + variance).sqrt()

        if self.affine:
            x = x * self.gamma + self.beta

        return x
...