¿Cómo normalizar una matriz NumPy dentro de un cierto rango?

136

Después de realizar un procesamiento en una matriz de audio o imagen, debe normalizarse dentro de un rango antes de poder volver a escribirse en un archivo. Esto se puede hacer así:

# 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)

¿Hay una manera menos verbosa y conveniente de hacer esto? matplotlib.colors.Normalize()no parece estar relacionado

endolito
fuente

Respuestas:

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

Usar /=y le *=permite eliminar una matriz temporal intermedia, ahorrando así algo de memoria. La multiplicación es menos costosa que la división, así que

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

es marginalmente más rápido que

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

Dado que estamos usando métodos básicos de numpy aquí, creo que esta es una solución tan eficiente en numpy como puede ser.


Las operaciones en el lugar no cambian el dtype de la matriz de contenedores. Como los valores normalizados deseados son flotantes, las matrices audioy imagedeben tener un tipo de punto de coma flotante antes de realizar las operaciones en el lugar. Si aún no son de tipo flotante, necesitará convertirlos usando astype. Por ejemplo,

image = image.astype('float64')
unutbu
fuente
77
¿Por qué la multiplicación es menos costosa que la división?
Endolith
19
No sé exactamente por qué. Sin embargo, estoy seguro del reclamo, ya que lo he verificado con tiempo. Con la multiplicación, puedes trabajar con un dígito a la vez. Con la división, especialmente con divisores grandes, debe trabajar con muchos dígitos y "adivinar" cuántas veces entra el divisor en el dividendo. Terminas haciendo muchos problemas de multiplicación para resolver un problema de división. El algoritmo informático para hacer la división puede no ser el mismo que la división larga humana, pero creo que es más complicado que la multiplicación.
unutbu
14
Probablemente valga la pena mencionar una división entre cero para las imágenes en blanco.
cjm2671
77
La multiplicación de @endolith es menos costosa que la división debido a la forma en que se implementa en el nivel de ensamblado. Los algoritmos de división no se pueden paralelizar, así como los algoritmos de multiplicación. en.wikipedia.org/wiki/Binary_multiplier
mjones.udri
55
Minimizar el número de divisiones a favor de las multiplicaciones es una técnica de optimización bien conocida.
mjones.udri
73

Si la matriz contiene datos positivos y negativos, iría con:

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

Si la matriz contiene nan, una solución podría ser simplemente eliminarlos como:

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

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

Sin embargo, dependiendo del contexto, es posible que desee tratar de manera nandiferente. Por ejemplo, interpolar el valor, reemplazarlo con, por ejemplo, 0, o generar un error.

Finalmente, vale la pena mencionar incluso si no es la pregunta de OP, la estandarización :

e = (a - np.mean(a)) / np.std(a)
Tactopoda
fuente
2
Dependiendo de lo que desee, esto no es correcto, ya que voltea los datos. Por ejemplo, la normalización a [0, 1] pone el máximo en 0 y el mínimo en 1. Para [0, 1], puede simplemente restar el resultado de 1 para obtener la normalización correcta.
Alan Turing
Gracias por señalarlo @AlanTuring que fue muy descuidado. El código, tal como se publicó, SOLO funcionaba si los datos contenían valores positivos y negativos. Eso podría ser bastante común para los datos de audio. Sin embargo, la respuesta se actualiza para normalizar los valores reales.
Tactopoda
1
El último también está disponible como scipy.stats.zscore.
Lewistrick
d podría voltear el signo de las muestras. Si desea mantener el signo, puede usar: f = a / np.max(np.abs(a))... a menos que toda la matriz tenga ceros (evite DivideByZero).
Pimin Konstantin Kefaloukos
1
numpy.ptp()devuelve 0, si ese es el rango, pero nansi hay uno nanen la matriz. Sin embargo, si el rango es 0, la normalización no está definida. Esto genera un error cuando intentamos dividir con 0.
Tactopoda
37

También puede reescalar usando sklearn. Las ventajas son que puede ajustar, normalizar la desviación estándar, además de centrar la media de los datos, y que puede hacerlo en cualquier eje, por características o por registros.

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

Tales argumentos axis, with_mean, with_stdson explica por sí mismo, y se muestran en su estado predeterminado. El argumento copyrealiza la operación in situ si está establecido en False. Documentación aquí .

cjohnson318
fuente
X = scale ([1,2,3,4], axis = 0, with_mean = True, with_std = True, copy = True) me da un error
Yfiua
X = scale (np.array ([1,2,3,4]), axis = 0, with_mean = True, with_std = True, copy = True) me da una matriz de [0,0,0,0]
Yfiua
sklearn.preprocessing.scale () tiene el backdraw que no sabes lo que está sucediendo. Cual es el factor? ¿Qué compresión del intervalo?
MasterControlProgram
Estos métodos de preprocesamiento de scikit (scale, minmax_scale, maxabs_scale) están destinados a usarse solo a lo largo de un eje (por lo tanto, escale las muestras (filas) o las características (columnas) individualmente. Esto tiene sentido en una configuración de aprendizaje automático, pero a veces desea para calcular el rango en toda la matriz, o utilizar matrices con más de dos dimensiones.
Toby
11

Puede usar la versión "i" (como en idiv, imul ..), y no se ve nada mal:

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

Para el otro caso, puede escribir una función para normalizar una matriz n-dimensional por columnas:

def normalize_columns(arr):
    rows, cols = arr.shape
    for col in xrange(cols):
        arr[:,col] /= abs(arr[:,col]).max()
u0b34a0f6ae
fuente
¿Puedes aclarar esto? Los paréntesis hacen que se comporte de manera diferente que sin?
endolito el
1
las paréntesis no cambian nada. el punto era usar en /=lugar de = .. / ..
u0b34a0f6ae
7

Está intentando escalar min-max los valores audioentre -1 y +1 y imageentre 0 y 255.

Usando sklearn.preprocessing.minmax_scale, debería resolver fácilmente su problema.

p.ej:

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

y

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

nota : No debe confundirse con la operación que escala la norma (longitud) de un vector a un cierto valor (generalmente 1), que también se conoce comúnmente como normalización.

yellow01
fuente
4

Una solución simple es usar los escaladores que ofrece la biblioteca 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)

El error X_rec-X será cero. Puede ajustar el feature_range para sus necesidades, o incluso usar un escalador estándar sk.StandardScaler ()

Pantelis
fuente
3

Intenté seguir esto y obtuve el error

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

La numpymatriz que estaba tratando de normalizar era una integermatriz. Parece que desaprobaron la conversión de tipos en las versiones> 1.10, y tienes que usar numpy.true_divide()para resolver eso.

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

imgFue un PIL.Imageobjeto.

Mocoso malcriado
fuente