Convierta una matriz de índices en una matriz numpy codificada en caliente

227

Digamos que tengo una matriz numpy 1d

a = array([1,0,3])

Me gustaría codificar esto como una matriz 2d 1-hot

b = array([[0,1,0,0], [1,0,0,0], [0,0,0,1]])

¿Hay una manera rápida de hacer esto? Más rápido que simplemente recorrer apara establecer elementos de b, eso es.

James Atwood
fuente

Respuestas:

395

Su matriz adefine las columnas de los elementos distintos de cero en la matriz de salida. También debe definir las filas y luego usar una indexación elegante:

>>> a = np.array([1, 0, 3])
>>> b = np.zeros((a.size, a.max()+1))
>>> b[np.arange(a.size),a] = 1
>>> b
array([[ 0.,  1.,  0.,  0.],
       [ 1.,  0.,  0.,  0.],
       [ 0.,  0.,  0.,  1.]])
YXD
fuente
111
Hermoso. Generalizando un poco:, b = np.zeros((a.size, a.max()+1))luego `b [np.arange (a.size), a] = 1`
James Atwood
10
@JamesAtwood depende de la aplicación, pero haría que max sea un parámetro y no lo calcule a partir de los datos.
Mohammad Moghimi
1
@MohammadMoghimi Claro, tiene sentido para mí.
James Atwood
77
¿y si 'a' fuera 2d? y quieres una matriz 3-d one-hot?
AD
8
¿Alguien puede señalar una explicación de por qué esto funciona, pero la porción con [:, a] no?
N. McA.
168
>>> values = [1, 0, 3]
>>> n_values = np.max(values) + 1
>>> np.eye(n_values)[values]
array([[ 0.,  1.,  0.,  0.],
       [ 1.,  0.,  0.,  0.],
       [ 0.,  0.,  0.,  1.]])
K3 --- rnc
fuente
99
Esta solución es la única útil para una matriz ND de entrada a una matriz N + 1D caliente. Ejemplo: input_matrix = np.asarray ([[0,1,1], [1,1,2]]); np.eye (3) [input_matrix] # salida tensor 3D
Isaías
55
+1 porque esto debería preferirse sobre la solución aceptada. Sin embargo, para una solución más general, valuesdebe ser una matriz Numpy en lugar de una lista de Python, entonces funciona en todas las dimensiones, no solo en 1D.
Alex
8
Tenga en cuenta que np.max(values) + 1es posible que no sea conveniente tomar el número de cubetas si su conjunto de datos se muestrea aleatoriamente y por casualidad puede no contener el valor máximo. El número de depósitos debe ser más bien un parámetro y se puede establecer la aserción / verificación para verificar que cada valor esté dentro de 0 (incl.) Y el conteo de depósitos (excl).
NightElfik
2
Para mí, esta solución es la mejor y se puede generalizar fácilmente a cualquier tensor: def one_hot (x, depth = 10): return np.eye (depth) [x]. Tenga en cuenta que dar el tensor x como índice devuelve un tensor de filas de ojos x.shape.
cecconeurale
44
Manera fácil de "entender" esta solución y por qué funciona para N-dims (sin leer numpydocumentos): en cada ubicación en la matriz original ( values), tenemos un número entero ky "colocamos" el vector 1-hot eye(n)[k]en esa ubicación . Esto agrega una dimensión porque estamos "poniendo" un vector en la ubicación de un escalar en la matriz original.
avivr
35

En caso de que esté usando keras, hay una utilidad incorporada para eso:

from keras.utils.np_utils import to_categorical   

categorical_labels = to_categorical(int_labels, num_classes=3)

Y hace más o menos lo mismo que la respuesta de @ YXD (ver código fuente ).

Jodo
fuente
32

Esto es lo que encuentro útil:

def one_hot(a, num_classes):
  return np.squeeze(np.eye(num_classes)[a.reshape(-1)])

Aquí num_classesrepresenta el número de clases que tiene. Entonces, si tiene un avector con forma de (10000,) esta función lo transforma en (10000, C) . Tenga en cuenta que aestá indexado a cero, es decir one_hot(np.array([0, 1]), 2), dará [[1, 0], [0, 1]].

Exactamente lo que querías tener, creo.

PD: la fuente son los modelos de secuencia - deeplearning.ai

D.Samchuk
fuente
Además, ¿cuál es la razón de hacer np.squeeze () ya que obtener el (tamaño del vector a) muchas matrices codificadas en caliente usando np.eye(num_classes)[a.reshape(-1)]. What you are simply doing is using np.eye` está creando una matriz diagonal con cada índice de clase como 1 resto cero y luego usando los índices proporcionados por a.reshape(-1)la producción de la salida correspondiente al índice en np.eye(). No entendí la necesidad de np.sqeezehacerlo, ya que lo usamos para eliminar simplemente dimensiones individuales que nunca tendremos, ya que en la dimensión de salida siempre será(a_flattened_size, num_classes)
Anu
27

Puedes usar sklearn.preprocessing.LabelBinarizer:

Ejemplo:

import sklearn.preprocessing
a = [1,0,3]
label_binarizer = sklearn.preprocessing.LabelBinarizer()
label_binarizer.fit(range(max(a)+1))
b = label_binarizer.transform(a)
print('{0}'.format(b))

salida:

[[0 1 0 0]
 [1 0 0 0]
 [0 0 0 1]]

Entre otras cosas, puede inicializar sklearn.preprocessing.LabelBinarizer()para que la salida de transformsea ​​escasa.

Franck Dernoncourt
fuente
21

También puede usar la función de ojo de numpy:

numpy.eye(number of classes)[vector containing the labels]

Karma
fuente
1
Para mayor claridad, el uso np.identity(num_classes)[indices]podría ser mejor. ¡Buena respuesta!
Oliver
5

Aquí hay una función que convierte un vector 1-D en una matriz 2-D one-hot.

#!/usr/bin/env python
import numpy as np

def convertToOneHot(vector, num_classes=None):
    """
    Converts an input 1-D vector of integers into an output
    2-D array of one-hot vectors, where an i'th input value
    of j will set a '1' in the i'th row, j'th column of the
    output array.

    Example:
        v = np.array((1, 0, 4))
        one_hot_v = convertToOneHot(v)
        print one_hot_v

        [[0 1 0 0 0]
         [1 0 0 0 0]
         [0 0 0 0 1]]
    """

    assert isinstance(vector, np.ndarray)
    assert len(vector) > 0

    if num_classes is None:
        num_classes = np.max(vector)+1
    else:
        assert num_classes > 0
        assert num_classes >= np.max(vector)

    result = np.zeros(shape=(len(vector), num_classes))
    result[np.arange(len(vector)), vector] = 1
    return result.astype(int)

A continuación se muestra un ejemplo de uso:

>>> a = np.array([1, 0, 3])

>>> convertToOneHot(a)
array([[0, 1, 0, 0],
       [1, 0, 0, 0],
       [0, 0, 0, 1]])

>>> convertToOneHot(a, num_classes=10)
array([[0, 1, 0, 0, 0, 0, 0, 0, 0, 0],
       [1, 0, 0, 0, 0, 0, 0, 0, 0, 0],
       [0, 0, 0, 1, 0, 0, 0, 0, 0, 0]])
stackoverflowuser2010
fuente
Tenga en cuenta que esto solo funciona en vectores (y no hay assertque verificar la forma del vector;)).
johndodo
1
+1 para el enfoque generalizado y la verificación de parámetros. Sin embargo, como práctica común, sugiero NO usar afirmaciones para realizar comprobaciones en las entradas. Utilice afirmaciones solo para verificar condiciones intermedias internas. Por el contrario, convertir todo assert ___en if not ___ raise Exception(<Reason>).
fnunnari
3

Para 1-hot-encoding

   one_hot_encode=pandas.get_dummies(array)

Por ejemplo

DISFRUTE DE LA CODIFICACIÓN

Shubham Mishra
fuente
Gracias por el comentario, ¡pero una breve descripción de lo que está haciendo el código sería muy útil!
Clarus
por favor refiérase al ejemplo
Shubham Mishra
@Clarus Checkout el siguiente ejemplo. Puede acceder a la codificación activa de cada valor en su matriz np haciendo un one_hot_encode [valor]. >>> import numpy as np >>> import pandas >>> a = np.array([1,0,3]) >>> one_hot_encode=pandas.get_dummies(a) >>> print(one_hot_encode) 0 1 3 0 0 1 0 1 1 0 0 2 0 0 1 >>> print(one_hot_encode[1]) 0 1 1 0 2 0 Name: 1, dtype: uint8 >>> print(one_hot_encode[0]) 0 0 1 1 2 0 Name: 0, dtype: uint8 >>> print(one_hot_encode[3]) 0 0 1 0 2 1 Name: 3, dtype: uint8
Deepak
2

Creo que la respuesta corta es no. Para un caso más genérico en ndimensiones, se me ocurrió esto:

# For 2-dimensional data, 4 values
a = np.array([[0, 1, 2], [3, 2, 1]])
z = np.zeros(list(a.shape) + [4])
z[list(np.indices(z.shape[:-1])) + [a]] = 1

Me pregunto si hay una solución mejor. No me gusta que tenga que crear esas listas en las últimas dos líneas. De todos modos, hice algunas mediciones timeity parece que la versión numpybasada en ( indices/ arange) y la iterativa tienen un rendimiento similar.

David Nemeskey
fuente
2

Solo para explicar la excelente respuesta de K3 --- rnc , aquí hay una versión más genérica:

def onehottify(x, n=None, dtype=float):
    """1-hot encode x with the max value n (computed from data if n is None)."""
    x = np.asarray(x)
    n = np.max(x) + 1 if n is None else n
    return np.eye(n, dtype=dtype)[x]

Además, aquí hay un punto de referencia rápido y sucio de este método y un método de la respuesta actualmente aceptada por YXD (ligeramente modificado, para que ofrezcan la misma API, excepto que este último funciona solo con nDray de 1D):

def onehottify_only_1d(x, n=None, dtype=float):
    x = np.asarray(x)
    n = np.max(x) + 1 if n is None else n
    b = np.zeros((len(x), n), dtype=dtype)
    b[np.arange(len(x)), x] = 1
    return b

El último método es ~ 35% más rápido (MacBook Pro 13 2015), pero el primero es más general:

>>> import numpy as np
>>> np.random.seed(42)
>>> a = np.random.randint(0, 9, size=(10_000,))
>>> a
array([6, 3, 7, ..., 5, 8, 6])
>>> %timeit onehottify(a, 10)
188 µs ± 5.03 µs per loop (mean ± std. dev. of 7 runs, 10000 loops each)
>>> %timeit onehottify_only_1d(a, 10)
139 µs ± 2.78 µs per loop (mean ± std. dev. of 7 runs, 10000 loops each)
Emil Melnikov
fuente
2

Puede usar el siguiente código para convertirlo en un vector único:

let x es el vector de clase normal que tiene una sola columna con clases 0 a algún número:

import numpy as np
np.eye(x.max()+1)[x]

si 0 no es una clase; luego elimine +1.

Inaam Ilahi
fuente
1

Recientemente me encontré con un problema del mismo tipo y encontré dicha solución que resultó ser solo satisfactoria si tienes números que van dentro de cierta formación. Por ejemplo, si desea codificar en caliente la siguiente lista:

all_good_list = [0,1,2,3,4]

adelante, las soluciones publicadas ya se mencionaron anteriormente. Pero qué pasa si consideramos estos datos:

problematic_list = [0,23,12,89,10]

Si lo hace con los métodos mencionados anteriormente, es probable que termine con 90 columnas de una sola vez. Esto se debe a que todas las respuestas incluyen algo como n = np.max(a)+1. Encontré una solución más genérica que funcionó para mí y quería compartir con ustedes:

import numpy as np
import sklearn
sklb = sklearn.preprocessing.LabelBinarizer()
a = np.asarray([1,2,44,3,2])
n = np.unique(a)
sklb.fit(n)
b = sklb.transform(a)

Espero que alguien haya encontrado las mismas restricciones en las soluciones anteriores y esto pueda ser útil

Hans T
fuente
1

Tal tipo de codificación suele ser parte de una matriz numpy. Si está utilizando una matriz numpy como esta:

a = np.array([1,0,3])

entonces hay una manera muy simple de convertir eso a codificación 1-hot

out = (np.arange(4) == a[:,None]).astype(np.float32)

Eso es.

Sudeep K Rana
fuente
1
  • p será un 2d ndarray.
  • Queremos saber qué valor es el más alto en una fila, para poner allí 1 y en todas partes 0.

Solución limpia y fácil:

max_elements_i = np.expand_dims(np.argmax(p, axis=1), axis=1)
one_hot = np.zeros(p.shape)
np.put_along_axis(one_hot, max_elements_i, 1, axis=1)
MiFi
fuente
1

Usando un paso de tubería Neuraxle :

  1. Configura tu ejemplo
import numpy as np
a = np.array([1,0,3])
b = np.array([[0,1,0,0], [1,0,0,0], [0,0,0,1]])
  1. Haz la conversión real
from neuraxle.steps.numpy import OneHotEncoder
encoder = OneHotEncoder(nb_columns=4)
b_pred = encoder.transform(a)
  1. Afirma que funciona
assert b_pred == b

Enlace a la documentación: neuraxle.steps.numpy.OneHotEncoder

Guillaume Chevalier
fuente
0

Aquí hay una función de ejemplo que escribí para hacer esto en base a las respuestas anteriores y mi propio caso de uso:

def label_vector_to_one_hot_vector(vector, one_hot_size=10):
    """
    Use to convert a column vector to a 'one-hot' matrix

    Example:
        vector: [[2], [0], [1]]
        one_hot_size: 3
        returns:
            [[ 0.,  0.,  1.],
             [ 1.,  0.,  0.],
             [ 0.,  1.,  0.]]

    Parameters:
        vector (np.array): of size (n, 1) to be converted
        one_hot_size (int) optional: size of 'one-hot' row vector

    Returns:
        np.array size (vector.size, one_hot_size): converted to a 'one-hot' matrix
    """
    squeezed_vector = np.squeeze(vector, axis=-1)

    one_hot = np.zeros((squeezed_vector.size, one_hot_size))

    one_hot[np.arange(squeezed_vector.size), squeezed_vector] = 1

    return one_hot

label_vector_to_one_hot_vector(vector=[[2], [0], [1]], one_hot_size=3)
Aaron Lelevier
fuente
0

Estoy agregando para completar una función simple, usando solo operadores numpy:

   def probs_to_onehot(output_probabilities):
        argmax_indices_array = np.argmax(output_probabilities, axis=1)
        onehot_output_array = np.eye(np.unique(argmax_indices_array).shape[0])[argmax_indices_array.reshape(-1)]
        return onehot_output_array

Toma como entrada una matriz de probabilidad: por ejemplo:

[[0.03038822 0.65810204 0.16549407 0.3797123] ... [0.02771272 0.2760752 0.3280924 0.33458805]]

Y volverá

[[0 1 0 0] ... [0 0 0 1]]

Jordy Van Landeghem
fuente
0

Aquí hay una solución independiente de dimensionalidad independiente.

Esto convertirá cualquier matriz N-dimensional arrde enteros no negativos en una matriz N + 1-dimensional de un solo calor one_hot, donde one_hot[i_1,...,i_N,c] = 1significa arr[i_1,...,i_N] = c. Puede recuperar la entrada a través denp.argmax(one_hot, -1)

def expand_integer_grid(arr, n_classes):
    """

    :param arr: N dim array of size i_1, ..., i_N
    :param n_classes: C
    :returns: one-hot N+1 dim array of size i_1, ..., i_N, C
    :rtype: ndarray

    """
    one_hot = np.zeros(arr.shape + (n_classes,))
    axes_ranges = [range(arr.shape[i]) for i in range(arr.ndim)]
    flat_grids = [_.ravel() for _ in np.meshgrid(*axes_ranges, indexing='ij')]
    one_hot[flat_grids + [arr.ravel()]] = 1
    assert((one_hot.sum(-1) == 1).all())
    assert(np.allclose(np.argmax(one_hot, -1), arr))
    return one_hot
eqzx
fuente
0

Usa el siguiente código. Funciona mejor

def one_hot_encode(x):
"""
    argument
        - x: a list of labels
    return
        - one hot encoding matrix (number of labels, number of class)
"""
encoded = np.zeros((len(x), 10))

for idx, val in enumerate(x):
    encoded[idx][val] = 1

return encoded

Lo encontré aquí PD No es necesario entrar en el enlace.

Inaam Ilahi
fuente
55
Debes evitar usar bucles con numpy
Kenan