Come normalizzare un array NumPy entro un certo intervallo?


136

Dopo aver eseguito l'elaborazione su un array di immagini o audio, è necessario normalizzarlo in un intervallo prima di poterlo riscrivere in un file. Questo può essere fatto in questo modo:

# Normalize audio channels to between -1.0 and +1.0
audio[:,0] = audio[:,0]/abs(audio[:,0]).max()
audio[:,1] = audio[:,1]/abs(audio[:,1]).max()

# Normalize image to between 0 and 255
image = image/(image.max()/255.0)

C'è un modo meno prolisso e conveniente per farlo? matplotlib.colors.Normalize()non sembra essere correlato.

Risposte:


137
audio /= np.max(np.abs(audio),axis=0)
image *= (255.0/image.max())

L'uso /=e *=consente di eliminare un array temporaneo intermedio, risparmiando così un po 'di memoria. La moltiplicazione è meno costosa della divisione, quindi

image *= 255.0/image.max()    # Uses 1 division and image.size multiplications

è leggermente più veloce di

image /= image.max()/255.0    # Uses 1+image.size divisions

Dal momento che qui stiamo usando i metodi di base numpy, penso che questa sia una soluzione quanto più efficiente possibile in numpy.


Le operazioni sul posto non modificano il tipo di array contenitore. Poiché i valori normalizzati desiderati sono float, le matrici audioe imagedevono avere un dtype in virgola mobile prima di eseguire le operazioni sul posto. Se non sono già di tipo a virgola mobile, dovrai convertirli utilizzando astype. Per esempio,

image = image.astype('float64')

7
Perché la moltiplicazione è meno costosa della divisione?
endolith

19
Non so esattamente perché. Tuttavia, sono fiducioso del reclamo, dopo averlo verificato con il tempo. Con la moltiplicazione, puoi lavorare con una cifra alla volta. Con la divisione, specialmente con i grandi divisori, devi lavorare con molte cifre e "indovinare" quante volte il divisore entra nel dividendo. Si finisce per fare molti problemi di moltiplicazione per risolvere un problema di divisione. L'algoritmo informatico per fare divisione potrebbe non essere lo stesso della lunga divisione umana, ma credo che sia più complicato della moltiplicazione.
unutbu,

14
Probabilmente vale la pena menzionare una divisione per zero per le immagini vuote.
cjm2671,

7
La moltiplicazione di @endolith è meno costosa della divisione a causa del modo in cui è stata implementata a livello di Assembly. Gli algoritmi di divisione non possono essere parallelizzati così come gli algoritmi di moltiplicazione. en.wikipedia.org/wiki/Binary_multiplier
mjones.udri

5
Ridurre al minimo il numero di divisioni a favore delle moltiplicazioni è una tecnica di ottimizzazione ben nota.
mjones.udri,

73

Se l'array contiene sia dati positivi che negativi, sceglierei:

import numpy as np

a = np.random.rand(3,2)

# Normalised [0,1]
b = (a - np.min(a))/np.ptp(a)

# Normalised [0,255] as integer: don't forget the parenthesis before astype(int)
c = (255*(a - np.min(a))/np.ptp(a)).astype(int)        

# Normalised [-1,1]
d = 2.*(a - np.min(a))/np.ptp(a)-1

Se l'array contiene nan, una soluzione potrebbe essere semplicemente rimuoverli come:

def nan_ptp(a):
    return np.ptp(a[np.isfinite(a)])

b = (a - np.nanmin(a))/nan_ptp(a)

Tuttavia, a seconda del contesto, potresti voler trattare nandiversamente. Ad esempio interpolare il valore, sostituendolo con ad es. 0, o generare un errore.

Infine, vale la pena menzionare anche se non è una domanda di OP, la standardizzazione :

e = (a - np.mean(a)) / np.std(a)

2
A seconda di ciò che si desidera, ciò non è corretto, poiché capovolge i dati. Ad esempio, la normalizzazione su [0, 1] mette il massimo su 0 e il minimo su 1. Per [0, 1], è possibile sottrarre semplicemente il risultato da 1 per ottenere la normalizzazione corretta.
Alan Turing,

Grazie per averlo segnalato @AlanTuring che era molto sciatto. Il codice, come pubblicato, funzionava SOLO se i dati contenevano valori positivi e negativi. Ciò potrebbe essere piuttosto comune per i dati audio. Tuttavia, la risposta viene aggiornata per normalizzare qualsiasi valore reale.
Tactopoda,

1
L'ultimo è disponibile anche come scipy.stats.zscore.
Lewistrick,

d potrebbe capovolgere il segno dei campioni. Se si desidera mantenere il segno, è possibile utilizzare: f = a / np.max(np.abs(a))... a meno che l'intero array non sia a zero (evitare DivideByZero).
Pimin Konstantin Kefaloukos,

1
numpy.ptp()restituisce 0, se questo è l'intervallo, ma nanse ce n'è uno nannell'array. Tuttavia, se l'intervallo è 0, la normalizzazione non è definita. Questo genera un errore mentre tentiamo di dividere per 0.
Tactopoda il

37

Puoi anche ridimensionare usando sklearn. I vantaggi sono che è possibile regolare la deviazione standard, oltre a centrare la media dei dati, e che è possibile farlo su entrambi gli assi, per caratteristiche o per record.

from sklearn.preprocessing import scale
X = scale( X, axis=0, with_mean=True, with_std=True, copy=True )

Gli argomenti chiave axis, with_mean, with_stdsono auto esplicativo, e sono mostrati nel loro stato di default. L'argomento copyesegue l'operazione sul posto se è impostato su False. Documentazione qui .


X = scale ([1,2,3,4], axis = 0, with_mean = True, with_std = True, copy = True) mi dà un errore
Yfiua,

X = scale (np.array ([1,2,3,4]), axis = 0, with_mean = True, with_std = True, copy = True) mi dà un array di [0,0,0,0]
Yfiua,

sklearn.preprocessing.scale () ha il backdraw che non sai cosa sta succedendo. Qual è il fattore? Quale compressione dell'intervallo?
MasterControlProgram

Questi metodi di preelaborazione di scikit (scale, minmax_scale, maxabs_scale) sono pensati per essere utilizzati solo su un asse (quindi ridimensionare i campioni (righe) o le funzionalità (colonne) singolarmente. per calcolare l'intervallo sull'intero array o utilizzare array con più di due dimensioni.
Toby,

11

Puoi usare la versione "i" (come in idiv, imul ..), e non sembra affatto male:

image /= (image.max()/255.0)

Per l'altro caso è possibile scrivere una funzione per normalizzare una matrice n-dimensionale per colonne:

def normalize_columns(arr):
    rows, cols = arr.shape
    for col in xrange(cols):
        arr[:,col] /= abs(arr[:,col]).max()

Puoi chiarire questo? Le parentesi lo fanno comportare diversamente che senza?
endolith

1
le paratie non cambiano nulla. il punto era usare /=invece di = .. / ..
u0b34a0f6ae il

7

Stai cercando di ridimensionare min-max i valori compresi audiotra -1 e +1 e imagetra 0 e 255.

Utilizzando sklearn.preprocessing.minmax_scale, dovrebbe facilmente risolvere il tuo problema.

per esempio:

audio_scaled = minmax_scale(audio, feature_range=(-1,1))

e

shape = image.shape
image_scaled = minmax_scale(image.ravel(), feature_range=(0,255)).reshape(shape)

nota : da non confondere con l'operazione che ridimensiona la norma (lunghezza) di un vettore su un determinato valore (di solito 1), che viene anche comunemente definito normalizzazione.


4

Una soluzione semplice sta usando gli scaler offerti dalla libreria sklearn.preprocessing.

scaler = sk.MinMaxScaler(feature_range=(0, 250))
scaler = scaler.fit(X)
X_scaled = scaler.transform(X)
# Checking reconstruction
X_rec = scaler.inverse_transform(X_scaled)

L'errore X_rec-X sarà zero. È possibile regolare la feature_range in base alle proprie esigenze o persino utilizzare uno scaler standard sk.StandardScaler ()


3

Ho provato a seguire questo , e ho ottenuto l'errore

TypeError: ufunc 'true_divide' output (typecode 'd') could not be coerced to provided output parameter (typecode 'l') according to the casting rule ''same_kind''

L' numpyarray che stavo cercando di normalizzare era un integerarray. Sembra che abbiano deprecato il cast del tipo nelle versioni> 1.10, e devi usarlo numpy.true_divide()per risolverlo.

arr = np.array(img)
arr = np.true_divide(arr,[255.0],out=None)

imgera un PIL.Imageoggetto.

Utilizzando il nostro sito, riconosci di aver letto e compreso le nostre Informativa sui cookie e Informativa sulla privacy.
Licensed under cc by-sa 3.0 with attribution required.