Convertir una matriz 2d en una matriz caliente 3d

Tengo np matrix y quiero convertirla en una matriz 3d con una encoding en caliente de los elementos como tercera dimensión. ¿Hay una manera de hacerlo sin hacer un bucle en cada fila, por ejemplo,

a=[[1,3], [2,4]] 

debe hacerse en

 b=[[1,0,0,0], [0,0,1,0], [0,1,0,0], [0,0,0,1]] 

Enfoque # 1

Aquí hay un descaro de una sola línea que abusa de la comparación broadcasted

 (np.arange(a.max()) == a[...,None]-1).astype(int) 

Ejecución de la muestra

 In [120]: a Out[120]: array([[1, 7, 5, 3], [2, 4, 1, 4]]) In [121]: (np.arange(a.max()) == a[...,None]-1).astype(int) Out[121]: array([[[1, 0, 0, 0, 0, 0, 0], [0, 0, 0, 0, 0, 0, 1], [0, 0, 0, 0, 1, 0, 0], [0, 0, 1, 0, 0, 0, 0]], [[0, 1, 0, 0, 0, 0, 0], [0, 0, 0, 1, 0, 0, 0], [1, 0, 0, 0, 0, 0, 0], [0, 0, 0, 1, 0, 0, 0]]]) 

Para la indexación 0-based , sería –

 In [122]: (np.arange(a.max()+1) == a[...,None]).astype(int) Out[122]: array([[[0, 1, 0, 0, 0, 0, 0, 0], [0, 0, 0, 0, 0, 0, 0, 1], [0, 0, 0, 0, 0, 1, 0, 0], [0, 0, 0, 1, 0, 0, 0, 0]], [[0, 0, 1, 0, 0, 0, 0, 0], [0, 0, 0, 0, 1, 0, 0, 0], [0, 1, 0, 0, 0, 0, 0, 0], [0, 0, 0, 0, 1, 0, 0, 0]]]) 

Si el límite de un solo interés es cubrir el rango de valores que van desde el mínimo al máximo, entonces compense el valor mínimo y luego aliméntelo al método propuesto para la indexación 0-based en 0-based . Esto también sería aplicable para el rest de los enfoques que se analizan más adelante en esta publicación.

Aquí hay una muestra de ejecución en el mismo

 In [223]: a Out[223]: array([[ 6, 12, 10, 8], [ 7, 9, 6, 9]]) In [224]: a_off = a - a.min() # feed a_off to proposed approaches In [225]: (np.arange(a_off.max()+1) == a_off[...,None]).astype(int) Out[225]: array([[[1, 0, 0, 0, 0, 0, 0], [0, 0, 0, 0, 0, 0, 1], [0, 0, 0, 0, 1, 0, 0], [0, 0, 1, 0, 0, 0, 0]], [[0, 1, 0, 0, 0, 0, 0], [0, 0, 0, 1, 0, 0, 0], [1, 0, 0, 0, 0, 0, 0], [0, 0, 0, 1, 0, 0, 0]]]) 

Si está de acuerdo con una matriz booleana con True para 1's y Falso para 0's , puede omitir la .astype(int) .

Enfoque # 2

También podemos inicializar matrices de ceros e indexar en la salida con advanced-indexing . Por lo tanto, para la indexación 0-based en 0-based , tendríamos –

 def onehot_initialization(a): ncols = a.max()+1 out = np.zeros(a.shape + (ncols,), dtype=int) out[all_idx(a, axis=2)] = 1 return out 

Función de ayuda

 # https://stackoverflow.com/a/46103129/ @Divakar def all_idx(idx, axis): grid = np.ogrid[tuple(map(slice, idx.shape))] grid.insert(axis, idx) return tuple(grid) 

Esto debería ser especialmente más eficaz cuando se trata de un mayor rango de valores.

Para la indexación 1-based , simplemente alimente en a-1 como entrada.

Enfoque # 3: Solución de matriz dispersa

Ahora, si está buscando una matriz dispersa como salida y AFAIK ya que las matrices dispersas incorporadas de scipy solo admiten formatos 2D , puede obtener una salida dispersa que es una versión remodelada de la salida que se mostró anteriormente con los dos primeros ejes fusionados y el tercer eje Mantenido intacto. La implementación para la indexación 0-based en 0-based se vería así:

 from scipy.sparse import coo_matrix def onehot_sparse(a): N = a.size L = a.max()+1 data = np.ones(N,dtype=int) return coo_matrix((data,(np.arange(N),a.ravel())), shape=(N,L)) 

Nuevamente, para la indexación 1-based en 1-based , simplemente introduzca a a-1 como entrada.

Ejecución de la muestra

 In [157]: a Out[157]: array([[1, 7, 5, 3], [2, 4, 1, 4]]) In [158]: onehot_sparse(a).toarray() Out[158]: array([[0, 1, 0, 0, 0, 0, 0, 0], [0, 0, 0, 0, 0, 0, 0, 1], [0, 0, 0, 0, 0, 1, 0, 0], [0, 0, 0, 1, 0, 0, 0, 0], [0, 0, 1, 0, 0, 0, 0, 0], [0, 0, 0, 0, 1, 0, 0, 0], [0, 1, 0, 0, 0, 0, 0, 0], [0, 0, 0, 0, 1, 0, 0, 0]]) In [159]: onehot_sparse(a-1).toarray() Out[159]: array([[1, 0, 0, 0, 0, 0, 0], [0, 0, 0, 0, 0, 0, 1], [0, 0, 0, 0, 1, 0, 0], [0, 0, 1, 0, 0, 0, 0], [0, 1, 0, 0, 0, 0, 0], [0, 0, 0, 1, 0, 0, 0], [1, 0, 0, 0, 0, 0, 0], [0, 0, 0, 1, 0, 0, 0]]) 

Esto sería mucho mejor que los dos enfoques anteriores si está de acuerdo con tener una salida dispersa.

Comparación de tiempo de ejecución para la indexación basada en 0

Caso 1 :

 In [160]: a = np.random.randint(0,100,(100,100)) In [161]: %timeit (np.arange(a.max()+1) == a[...,None]).astype(int) 1000 loops, best of 3: 1.51 ms per loop In [162]: %timeit onehot_initialization(a) 1000 loops, best of 3: 478 µs per loop In [163]: %timeit onehot_sparse(a) 10000 loops, best of 3: 87.5 µs per loop In [164]: %timeit onehot_sparse(a).toarray() 1000 loops, best of 3: 530 µs per loop 

Caso # 2:

 In [166]: a = np.random.randint(0,500,(100,100)) In [167]: %timeit (np.arange(a.max()+1) == a[...,None]).astype(int) 100 loops, best of 3: 8.51 ms per loop In [168]: %timeit onehot_initialization(a) 100 loops, best of 3: 2.52 ms per loop In [169]: %timeit onehot_sparse(a) 10000 loops, best of 3: 87.1 µs per loop In [170]: %timeit onehot_sparse(a).toarray() 100 loops, best of 3: 2.67 ms per loop 

Exprimiendo el mejor rendimiento

Para exprimir el mejor rendimiento, podríamos modificar el enfoque # 2 para usar la indexación en una matriz de salida con forma 2D y también usar uint8 dtype para la eficiencia de la memoria y que lleva a asignaciones mucho más rápidas, como por ejemplo:

 def onehot_initialization_v2(a): ncols = a.max()+1 out = np.zeros( (a.size,ncols), dtype=np.uint8) out[np.arange(a.size),a.ravel()] = 1 out.shape = a.shape + (ncols,) return out 

Tiempos –

 In [178]: a = np.random.randint(0,100,(100,100)) In [179]: %timeit onehot_initialization(a) ...: %timeit onehot_initialization_v2(a) ...: 1000 loops, best of 3: 474 µs per loop 10000 loops, best of 3: 128 µs per loop In [180]: a = np.random.randint(0,500,(100,100)) In [181]: %timeit onehot_initialization(a) ...: %timeit onehot_initialization_v2(a) ...: 100 loops, best of 3: 2.38 ms per loop 1000 loops, best of 3: 213 µs per loop 

Edit: Me acabo de dar cuenta de que mi respuesta ya está cubierta en la respuesta aceptada. Desafortunadamente, como usuario no registrado, no puedo eliminarlo más.

Como un addendum a la respuesta aceptada: si tiene un número muy pequeño de clases para codificar y si puede aceptar matrices np.bool como salida, encontré que lo siguiente es incluso un poco más rápido:

 def onehot_initialization_v3(a): ncols = a.max() + 1 labels_one_hot = (a.ravel()[np.newaxis] == np.arange(ncols)[:, np.newaxis]).T labels_one_hot.shape = a.shape + (ncols,) return labels_one_hot 

Tiempos (para 10 clases):

 a = np.random.randint(0,10,(100,100)) assert np.all(onehot_initialization_v2(a) == onehot_initialization_v3(a)) %timeit onehot_initialization_v2(a) %timeit onehot_initialization_v3(a) # 102 µs ± 1.66 µs per loop (mean ± std. dev. of 7 runs, 10000 loops each) # 79.3 µs ± 815 ns per loop (mean ± std. dev. of 7 runs, 10000 loops each) 

Esto cambia, sin embargo, si el número de clases aumenta (ahora 100 clases):

 a = np.random.randint(0,100,(100,100)) assert np.all(onehot_initialization_v2(a) == one_hot_initialization_v3(a)) %timeit onehot_initialization_v2(a) %timeit onehot_initialization_v3(a) # 132 µs ± 1.4 µs per loop (mean ± std. dev. of 7 runs, 10000 loops each) # 639 µs ± 3.12 µs per loop (mean ± std. dev. of 7 runs, 1000 loops each) 

Entonces, dependiendo de tu problema, cualquiera de las dos podría ser la versión más rápida.