2010-05-17 6 views
19

In che modo un elenco di vettori può essere elegantemente normalizzato, in NumPy?NumPy: come normalizzare rapidamente molti vettori?

Ecco un esempio che non fa lavoro:

from numpy import * 

vectors = array([arange(10), arange(10)]) # All x's, then all y's 
norms = apply_along_axis(linalg.norm, 0, vectors) 

# Now, what I was expecting would work: 
print vectors.T/norms # vectors.T has 10 elements, as does norms, but this does not work 

Le ultime rese operative "forma corrispondente: oggetti non possono essere trasmessi a una singola forma".

Come si può realizzare elegantemente la normalizzazione dei vettori 2D in vectors con NumPy?

Modifica: Perché il precedente non funziona mentre si aggiunge una dimensione a norms funziona (come per la mia risposta di seguito)?

+0

FYI, un commentatore può avere un metodo più veloce, ho modificato il mio rispondi con più dettagli – Geoff

risposta

12

Beh, a meno che non ho perso qualcosa, questo funziona:

vectors/norms 

Il problema nel vostro suggerimento è le regole di radiodiffusione.

vectors # shape 2, 10 
norms # shape 10 

La forma non ha la stessa lunghezza! Quindi la regola è quella di estendere prima la piccola forma di uno sul sinistra:

norms # shape 1,10 

è possibile farlo manualmente chiamando:

vectors/norms.reshape(1,-1) # same as vectors/norms 

Se si voleva calcolare vectors.T/norms, si dovrebbe per fare il rimodellamento manualmente, come segue:

vectors.T/norms.reshape(-1,1) # this works 
+0

perché non solo fare (vettori/norme). T se l'OP vuole questo trasposto. Sembra sia semplice ed elegante per me. –

+0

Ah, ah! quindi l'estensione della dimensione viene eseguita su _left_: questo spiega effettivamente il comportamento osservato. Grazie! – EOL

13

OK: la trasmissione della forma di array di NumPy aggiunge dimensioni allo a sinistra dello della forma dell'array, non alla sua destra. NumPy può tuttavia essere istruito per aggiungere una dimensione alla destra della matrice norms:

print vectors.T/norms[:, newaxis] 

funziona!

+3

Solo una nota, io uso 'norme [..., np.newaxis] nel caso in cui la matrice non sia solo 2D. Funzionerebbe anche con un tensore 3D (o più). – Geoff

23

Calcolo della grandezza

Mi sono imbattuto in questa domanda e sono diventato curioso del tuo metodo di normalizzazione. Io uso un metodo diverso per calcolare le grandezze. Nota: calcolo anche le norme sull'ultimo indice (righe in questo caso, non colonne).

magnitudes = np.sqrt((vectors ** 2).sum(-1))[..., np.newaxis] 

In genere, però, ho appena normalizzare questo modo:

vectors /= np.sqrt((vectors ** 2).sum(-1))[..., np.newaxis] 

Un momento di confronto

Ho eseguito un test per confrontare i tempi, e ha scoperto che il mio metodo è più veloce abbastanza un po ', ma il suggerimento di Freddie Witherdon è ancora più veloce.

import numpy as np  
vectors = np.random.rand(100, 25) 

# OP's 
%timeit np.apply_along_axis(np.linalg.norm, 1, vectors) 
# Output: 100 loops, best of 3: 2.39 ms per loop 

# Mine 
%timeit np.sqrt((vectors ** 2).sum(-1))[..., np.newaxis] 
# Output: 10000 loops, best of 3: 13.8 us per loop 

# Freddie's (from comment below) 
%timeit np.sqrt(np.einsum('...i,...i', vectors, vectors)) 
# Output: 10000 loops, best of 3: 6.45 us per loop 

Attenzione però, come questo StackOverflow answer note, ci sono alcuni controlli di sicurezza non accadendo con einsum, così si dovrebbe essere sicuri che il dtype di vectors è sufficiente per memorizzare la piazza delle grandezze con sufficiente precisione.

+1

Risultati di timing interessanti (ottengo rispettivamente 0,8 se 1,4 s, con la funzione %it timeit più robusta di IPython), grazie! – EOL

+2

Ho trovato 'np.sqrt (np.einsum ('... i, ... i', vettori, vettori))' essere ~ 4 volte più veloce del Metodo 1 come indicato sopra. –

+0

@FreddieWitherden - Grazie per il commento, non sapevo di "einsum". C'è una domanda SO interessante qui: http://stackoverflow.com/questions/18365073/why-is-numpys-einsum-faster-than-numpys-built-in-functions In genere sarà più veloce, ma potrebbe non essere sicuro (dipende dal 'dtype' del vettore). – Geoff

9

c'è già una funzione in scikit imparare:

import sklearn.preprocessing as preprocessing 
norm =preprocessing.normalize(m, norm='l2')* 

Maggiori informazioni su:

http://scikit-learn.org/stable/modules/preprocessing.html

+0

Informazioni interessanti, ma la domanda riguarda esplicitamente NumPy. Sarebbe meglio metterlo in un commento alla domanda originale. – EOL

2

mio modo preferito per normalizzare i vettori è quello di utilizzare inner1d di NumPy di ​​calcolare i loro grandezze. Ecco ciò che è stato suggerito finora rispetto al inner1d

import numpy as np 
from numpy.core.umath_tests import inner1d 
COUNT = 10**6 # 1 million points 

points = np.random.random_sample((COUNT,3,)) 
A  = np.sqrt(np.einsum('...i,...i', points, points)) 
B  = np.apply_along_axis(np.linalg.norm, 1, points) 
C  = np.sqrt((points ** 2).sum(-1)) 
D  = np.sqrt((points*points).sum(axis=1)) 
E  = np.sqrt(inner1d(points,points)) 

print [np.allclose(E,x) for x in [A,B,C,D]] # [True, True, True, True] 

prestazioni Test con Cprofile:

import cProfile 
cProfile.run("np.sqrt(np.einsum('...i,...i', points, points))**0.5") # 3 function calls in 0.013 seconds 
cProfile.run('np.apply_along_axis(np.linalg.norm, 1, points)')  # 9000018 function calls in 10.977 seconds 
cProfile.run('np.sqrt((points ** 2).sum(-1))')      # 5 function calls in 0.028 seconds 
cProfile.run('np.sqrt((points*points).sum(axis=1))')     # 5 function calls in 0.027 seconds 
cProfile.run('np.sqrt(inner1d(points,points))')      # 2 function calls in 0.009 seconds 

inner1d calcolato le grandezze un pelo più veloce di einsum. Quindi, utilizzando inner1d per normalizzare:

n = points/np.sqrt(inner1d(points,points))[:,None] 
cProfile.run('points/np.sqrt(inner1d(points,points))[:,None]') # 2 function calls in 0.026 seconds 

test contro scikit:

import sklearn.preprocessing as preprocessing 
n_ = preprocessing.normalize(points, norm='l2') 
cProfile.run("preprocessing.normalize(points, norm='l2')") # 47 function calls in 0.047 seconds 
np.allclose(n,n_) # True 

Conclusione: usando inner1d sembra essere l'opzione migliore

+0

Per riferimento, la domanda in realtà richiede il calcolo della norma lungo la dimensione _prima_, non la seconda (si veda l'avvertenza aggiunta alla risposta di Geoff). Come cambierebbe i risultati? Potrebbe esserci un impatto, a causa del modo in cui si accede alla memoria, specialmente se si ha una seconda dimensione più grande (invece di 3 nel proprio esempio). – EOL