Как векторизовать вычисление среднего значения для определенного набора индексов, заданных в виде строк матрицы? - PullRequest
0 голосов
/ 16 января 2019

У меня проблема с векторизацией некоторого кода в pytorch.Помогло бы и решение с клочками, но решение с кюветами было бы лучше.Я собираюсь использовать array и Tensor взаимозаменяемо.

Проблема, с которой я сталкиваюсь, заключается в следующем:

С учетом двумерного массива с плавающей точкой X размера (n, x)и логический двумерный массив A размера (n, n) вычисляют среднее значение для строк в X, проиндексированных строками в A.Проблема состоит в том, что строки в A содержат переменное число True индексов.

Пример (numpy):

import numpy as np
A = np.array([[0, 1, 0, 0, 0, 0],
              [1, 0, 1, 0, 0, 0],
              [0, 1, 0, 0, 0, 0],
              [0, 0, 0, 0, 1, 0],
              [0, 0, 0, 1, 0, 0],
              [0, 1, 1, 1, 0, 0]])
X = np.arange(6 * 3, dtype=np.float32).reshape(6, 3)

# Compute the mean in numpy with a for loop
means_np = np.array([X[A.astype(np.bool)[i]].mean(axis=0) for i in np.arange(len(A)])

Так что этот пример работает, но эта формулировка имеет 3проблемы:

  1. Цикл for медленный для больших A и X.Мне нужно перебрать несколько 10 тысяч индексов.

  2. Может случиться, что A[i] не содержит True индексов.В результате получается np.mean(np.array([])), что составляет NaN.Вместо этого я хочу, чтобы это было 0.

  3. Реализация этого способа в pytorch приводит к SIGFPE (Ошибка с плавающей запятой) во время обратного прохождения обратного распространения через эту функцию.Причина в том, что ничего не выбрано.

Обходное решение, которое я использую сейчас, (см. Также код ниже):

  • Установите диагональные элементы *От 1043 * до True, чтобы всегда был хотя бы один элемент для выбора
  • суммы всех выбранных элементов, вычтите значения в X из этой суммы (диагональ гарантированно будет False вначало), и разделите на число True элементов - 1, зажатых как минимум до 1 в каждом ряду.

Это работает, дифференцируется в pytorch и не производит NaN, ноМне все еще нужен цикл по всем показателям.Как я могу избавиться от этой петли?

Это мой текущий код pytorch:

 import torch
 A = torch.from_numpy(A).bytes()
 X = torch.from_numpy(X)
 A[np.diag_indices(len(A)] = 1  # Set the diagonal to 1
 means = [(X[A[i]].sum(dim=0) - X[i]) / torch.clamp(A[i].sum() - 1, min=1.)  # Compute the mean safely
          for i in range(len(A))]  # Get rid of the loop somehow
 means = torch.stack(means)

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

1 Ответ

0 голосов
/ 16 января 2019

Мы можем использовать кредит matrix-multiplication -

c = A.sum(1,keepdims=True)
means_np = np.where(c==0,0,A.dot(X)/c)

Мы можем оптимизировать его, преобразовав A в float32 dtype, если это не так, и если потеря точности в порядке, как показано ниже -

In [57]: np.random.seed(0)

In [58]: A = np.random.randint(0,2,(1000,1000))

In [59]: X = np.random.rand(1000,1000).astype(np.float32)

In [60]: %timeit A.dot(X)
10 loops, best of 3: 27 ms per loop

In [61]: %timeit A.astype(np.float32).dot(X)
100 loops, best of 3: 10.2 ms per loop

In [62]: np.allclose(A.dot(X), A.astype(np.float32).dot(X))
Out[62]: True

Таким образом, используйте A.astype(np.float32).dot(X) для замены A.dot(X).

В качестве альтернативы, чтобы решить для случая, когда сумма строк равна zero, и это требует от нас использования np.where, мы могли бы присвоить любое ненулевое значение, скажем, 1 в c, а затем просто поделить на это, вот так -

c = A.sum(1,keepdims=True)
c[c==0] = 1
means_np = A.dot(X)/c

Это также позволило бы избежать предупреждения, которое в противном случае мы получили бы от np.where в этих случаях с нулевой суммой строк.

...