¿Cómo normalizar una matriz en NumPy?

205

Me gustaría tener la norma de una matriz NumPy. Más específicamente, estoy buscando una versión equivalente de esta función

def normalize(v):
    norm = np.linalg.norm(v)
    if norm == 0: 
       return v
    return v / norm

¿Hay algo así en skearno numpy?

Esta función funciona en una situación donde ves el vector 0.

Donbeo
fuente
3
¿Qué tiene de malo lo que has escrito?
ali_m
55
Si esto es realmente una preocupación, debe verificar la norma <epsilon, donde epsilon es una pequeña tolerancia. Además, no pasaría silenciosamente un vector de norma cero, ¡sería raiseuna excepción!
Enganchado el
44
mi función funciona pero me gustaría saber si hay algo dentro de la biblioteca más común de Python. Estoy escribiendo diferentes funciones de aprendizaje automático y me gustaría evitar definir demasiado nueva funciones para hacer el código más claro y legible
Donbeo
1
Hice algunas pruebas rápidas y descubrí que x/np.linalg.norm(x)no era mucho más lento (alrededor del 15-20%) que x/np.sqrt((x**2).sum())en Numpy 1.15.1 en una CPU.
Bill

Respuestas:

162

Si usa scikit-learn, puede usar sklearn.preprocessing.normalize:

import numpy as np
from sklearn.preprocessing import normalize

x = np.random.rand(1000)*10
norm1 = x / np.linalg.norm(x)
norm2 = normalize(x[:,np.newaxis], axis=0).ravel()
print np.all(norm1 == norm2)
# True
ali_m
fuente
2
Gracias por la respuesta, pero ¿estás seguro de que sklearn.preprocessing.normalize también funciona con el vector de forma = (n,) o (n, 1)? Tengo algunos problemas con esta biblioteca
Donbeo
normalizerequiere una entrada 2D. Puede pasar el axis=argumento para especificar si desea aplicar la normalización en las filas o columnas de su matriz de entrada.
ali_m
9
Tenga en cuenta que el argumento 'norma' de la función normalizar puede ser 'l1' o 'l2' y el valor predeterminado es 'l2'. Si desea que la suma de su vector sea 1 (por ejemplo, una distribución de probabilidad), debe usar la norma = 'l1' en la función de normalización.
Ash el
2
También tenga en cuenta que np.linalg.norm(x)calcula la norma 'l2' por defecto. Si desea que la suma de su vector sea 1, debe usarnp.linalg.norm(x, ord=1)
Omid
Nota: x debe ser ndarraypara que funcione con la normalize()función. De lo contrario, puede ser un list.
Ramin Melikov
47

Estoy de acuerdo en que sería bueno que tal función fuera parte de las baterías incluidas. Pero no lo es, que yo sepa. Aquí hay una versión para ejes arbitrarios y que ofrece un rendimiento óptimo.

import numpy as np

def normalized(a, axis=-1, order=2):
    l2 = np.atleast_1d(np.linalg.norm(a, order, axis))
    l2[l2==0] = 1
    return a / np.expand_dims(l2, axis)

A = np.random.randn(3,3,3)
print(normalized(A,0))
print(normalized(A,1))
print(normalized(A,2))

print(normalized(np.arange(3)[:,None]))
print(normalized(np.arange(3)))
Eelco Hoogendoorn
fuente
No probé profundamente la solución ali_m pero en algún caso simple parece estar funcionando. ¿Existen situaciones en las que su función funciona mejor?
Donbeo
1
No lo sé; pero funciona sobre ejes arbitrarios, y tenemos control explícito sobre lo que sucede para los vectores de longitud 0.
Eelco Hoogendoorn
1
¡Muy agradable! Esto debería estar en numpy, aunque el orden probablemente debería estar antes del eje en mi opinión.
Neil G
@EelcoHoogendoorn ¿Curioso por entender por qué order = 2 elegido sobre los demás?
Henry Thornton
77
Porque la norma euclidiana / pitagórica resulta ser la más utilizada; no estarías de acuerdo?
Eelco Hoogendoorn
21

Puede especificar ord para obtener la norma L1. Para evitar la división cero, uso eps, pero tal vez eso no sea genial.

def normalize(v):
    norm=np.linalg.norm(v, ord=1)
    if norm==0:
        norm=np.finfo(v.dtype).eps
    return v/norm
Eduard Feicho
fuente
66
normalizando los [inf, 1, 2]rendimientos [nan, 0, 0], pero ¿no debería ser así [1, 0, 0]?
pasbi
12

Esto también podría funcionar para ti

import numpy as np
normalized_v = v / np.sqrt(np.sum(v**2))

pero falla cuando vtiene longitud 0.

mrk
fuente
10

Si tiene datos multidimensionales y desea que cada eje se normalice a su máximo o su suma:

def normalize(_d, to_sum=True, copy=True):
    # d is a (n x dimension) np array
    d = _d if not copy else np.copy(_d)
    d -= np.min(d, axis=0)
    d /= (np.sum(d, axis=0) if to_sum else np.ptp(d, axis=0))
    return d

Utiliza la función numpys pico a pico .

a = np.random.random((5, 3))

b = normalize(a, copy=False)
b.sum(axis=0) # array([1., 1., 1.]), the rows sum to 1

c = normalize(a, to_sum=False, copy=False)
c.max(axis=0) # array([1., 1., 1.]), the max of each row is 1
Jaden Travnik
fuente
Tenga cuidado si todos los valores son iguales en la matriz original, entonces ptp sería 0. La división por 0 devolverá nan.
Milso
8

También existe la función unit_vector()de normalizar vectores en el popular módulo de transformaciones de Christoph Gohlke:

import transformations as trafo
import numpy as np

data = np.array([[1.0, 1.0, 0.0],
                 [1.0, 1.0, 1.0],
                 [1.0, 2.0, 3.0]])

print(trafo.unit_vector(data, axis=1))
Joe
fuente
7

Usted mencionó el aprendizaje de sci-kit, así que quiero compartir otra solución.

kit de ciencia aprender MinMaxScaler

En sci-kit learn, hay una API llamada MinMaxScaler que puede personalizar el rango de valores a su gusto.

También se ocupa de los problemas de NaN para nosotros.

Los NaN se tratan como valores perdidos: no se tienen en cuenta y se mantienen en la transformación. ... ver referencia [1]

Muestra de código

El código es simple, solo escribe

# Let's say X_train is your input dataframe
from sklearn.preprocessing import MinMaxScaler
# call MinMaxScaler object
min_max_scaler = MinMaxScaler()
# feed in a numpy array
X_train_norm = min_max_scaler.fit_transform(X_train.values)
# wrap it up if you need a dataframe
df = pd.DataFrame(X_train_norm)
Referencia
WY Hsu
fuente
6

Sin sklearny usando solo numpy. Simplemente defina una función :.

Suponiendo que las filas son las variables y las columnas las muestras ( axis= 1):

import numpy as np

# Example array
X = np.array([[1,2,3],[4,5,6]])

def stdmtx(X):
    means = X.mean(axis =1)
    stds = X.std(axis= 1, ddof=1)
    X= X - means[:, np.newaxis]
    X= X / stds[:, np.newaxis]
    return np.nan_to_num(X)

salida:

X
array([[1, 2, 3],
       [4, 5, 6]])

stdmtx(X)
array([[-1.,  0.,  1.],
       [-1.,  0.,  1.]])
seralouk
fuente
4

Si desea normalizar n vectores de características dimensionales almacenados en un tensor 3D, también puede usar PyTorch:

import numpy as np
from torch import FloatTensor
from torch.nn.functional import normalize

vecs = np.random.rand(3, 16, 16, 16)
norm_vecs = normalize(FloatTensor(vecs), dim=0, eps=1e-16).numpy()
max0r
fuente
4

Si está trabajando con vectores 3D, puede hacerlo de manera concisa usando el toolbelt vg . Es una capa ligera encima de numpy y admite valores individuales y vectores apilados.

import numpy as np
import vg

x = np.random.rand(1000)*10
norm1 = x / np.linalg.norm(x)
norm2 = vg.normalize(x)
print np.all(norm1 == norm2)
# True

Creé la biblioteca en mi último inicio, donde estaba motivada por usos como este: ideas simples que son demasiado detalladas en NumPy.

paulmelnikow
fuente
3

Si no necesita la máxima precisión, su función se puede reducir a:

v_norm = v / (np.linalg.norm(v) + 1e-16)
sergio verduzco
fuente
3

Si trabaja con una matriz multidimensional, es posible una solución rápida.

Digamos que tenemos una matriz 2D, que queremos normalizar por el último eje, mientras que algunas filas tienen la norma cero.

import numpy as np
arr = np.array([
    [1, 2, 3], 
    [0, 0, 0],
    [5, 6, 7]
], dtype=np.float)

lengths = np.linalg.norm(arr, axis=-1)
print(lengths)  # [ 3.74165739  0.         10.48808848]
arr[lengths > 0] = arr[lengths > 0] / lengths[lengths > 0][:, np.newaxis]
print(arr)
# [[0.26726124 0.53452248 0.80178373]
# [0.         0.         0.        ]
# [0.47673129 0.57207755 0.66742381]]
Stanislav Tsepa
fuente