En el siguiente código he escrito 2 métodos que teóricamente (en mi opinión) deberían hacer lo mismo. Desafortunadamente no lo hacen, no puedo descubrir por qué no hacen lo mismo según la documentación numpy.

import numpy as np


dW = np.zeros((20, 10))
y = [1 for _ in range(100)]
X =  np.ones((100, 20))

# ===================
# Method 1  (works!)
# ===================
for i in range(len(y)):
  dW[:, y[i]] -=  X[i]


# ===================
# Method 2 (does not work)
# ===================
dW[:, y] -=  X.T
1
Jakobovski 27 feb. 2018 a las 18:18

4 respuestas

La mejor respuesta

Como se indicó, en principio no puede operar varias veces sobre el mismo elemento en una sola operación, debido a cómo funciona el almacenamiento en búfer en NumPy. Para ese propósito, está el at, que se puede usar en cualquier función NumPy estándar (add, {{ X2}}, etc.). Para su caso, puede hacer:

import numpy as np

dW = np.zeros((20, 10))
y = [1 for _ in range(100)]
X =  np.ones((100, 20))
# at modifies in place dW, does not return a new array
np.subtract.at(dW, (slice(None), y), X.T)
1
jdehesa 27 feb. 2018 a las 15:39

Encontré una tercera solución para este problema. Multiplicación matricial normal:

ind = np.zeros((X.shape[0],dW.shape[1]))
ind[range(X.shape[0]),y] = -1
dW = X.T.dot(ind)

Hice algunos experimentos utilizando los métodos propuestos anteriormente en algunos datos de redes neuronales. En mi ejemplo X.shape = (500,3073), W.shape = (3073,10) y ind.shape = (500,10).

La versión de resta dura aproximadamente 0.2 segundos (la más lenta). El método de multiplicación matricial 0.01 s (el más rápido). Bucle normal 0.015 y luego método bincount 0.04 s. Tenga en cuenta que en la pregunta y es un vector de unos. Este no es mi caso. El caso con solo unos puede resolverse con una suma simple.

1
user9240949user9240949 28 feb. 2018 a las 11:55

Esta es una versión en columna de esta pregunta.

La respuesta allí se puede adaptar para trabajar en columnas de la siguiente manera:

Enfoque 1: np.<ufunc>.at

>>> np.subtract.at(dW, (slice(None), y), X.T)

Enfoque 2: np.bincount

>>> m, n = dW.shape
>>> dW -= np.bincount(np.add.outer(np.arange(m) * n, y).ravel(), (X.T).ravel(), dW.size).reshape(m, n)

Tenga en cuenta que la solución basada en bincount, aunque implica más pasos, es más rápida en un factor de ~ 6.

>>> from timeit import repeat
>>> kwds = dict(globals=globals(), number=5000)
>>>
>>> repeat('np.subtract.at(dW, (slice(None), y), X.T); np.add.at(dW, (slice(None), y), X.T)', **kwds)
[1.590626839082688, 1.5769231889862567, 1.5802007300080732]
>>> repeat('_= dW; _ -= np.bincount(np.add.outer(np.arange(m) * n, y).ravel(), (X.T).ravel(), dW.size).reshape(m, n); _ += np.bincount(np.add.outer(np.arange(m) * n, y).ravel(), (X.T).ravel(), dW.size).reshape(m, n)', **kwds)
[0.2582490430213511, 0.25572817400097847, 0.25478115503210574]
1
Paul Panzer 27 feb. 2018 a las 16:12

Opción 1:

for i in range(len(y)):
  dW[:, y[i]] -=  X[i]

Esto funciona porque está recorriendo y actualizando el valor que se actualizó la última vez.

Opción 2:

dW[:, [1,1,1,1,....1,1,1]] -=  [[1,1,1,1...1],
                                [1,1,1,1...1],
                                .
                                .
                                [1,1,1,1...1]]

No funciona porque la actualización ocurre al primer índice al mismo tiempo en paralelo, no en forma serial. Inicialmente, todos son 0, lo que resta los resultados en -1.

0
Sunnysinh Solanki 27 feb. 2018 a las 15:33