¿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

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

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)

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í .


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

¿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.


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


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.

Al usar nuestro sitio, usted reconoce que ha leído y comprende nuestra Política de Cookies y Política de Privacidad.
Licensed under cc by-sa 3.0 with attribution required.