Comment normaliser un tableau NumPy dans une certaine plage?


136

Après avoir effectué un traitement sur un tableau audio ou d'image, il doit être normalisé dans une plage avant de pouvoir être réécrit dans un fichier. Cela peut être fait comme ceci:

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

Existe-t-il une manière pratique et moins verbeuse de faire cela? matplotlib.colors.Normalize()ne semble pas lié.

Réponses:


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

Utilisation /=et*= vous permet d'éliminer une matrice temporaire intermédiaire, économisant ainsi de la mémoire. La multiplication est moins chère que la division, donc

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

est légèrement plus rapide que

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

Puisque nous utilisons ici des méthodes numpy de base, je pense qu'il s'agit d'une solution numpy aussi efficace que possible.


Les opérations sur place ne modifient pas le dtype du tableau de conteneurs. Étant donné que les valeurs normalisées souhaitées sont des nombres flottants, les tableaux audioet imagedoivent avoir un dtype à virgule flottante avant que les opérations sur place ne soient effectuées. S'ils ne sont pas déjà de type dtype à virgule flottante, vous devrez les convertir en utilisant astype. Par exemple,

image = image.astype('float64')

7
Pourquoi la multiplication est-elle moins chère que la division?
endolith

19
Je ne sais pas exactement pourquoi. Cependant, je suis convaincu de l'affirmation, après l'avoir vérifié avec le temps. Avec la multiplication, vous pouvez travailler avec un chiffre à la fois. Avec la division, en particulier avec de grands diviseurs, vous devez travailler avec de nombreux chiffres et «deviner» combien de fois le diviseur entre dans le dividende. Vous finissez par faire de nombreux problèmes de multiplication pour résoudre un problème de division. L'algorithme informatique pour faire la division n'est peut-être pas le même que la division humaine longue, mais je pense néanmoins que c'est plus compliqué que la multiplication.
unutbu

14
Il vaut probablement la peine de mentionner une division par zéro pour les images vierges.
cjm2671

7
La multiplication @endolith est moins coûteuse que la division en raison de la façon dont elle est mise en œuvre au niveau de l'assemblage. Les algorithmes de division ne peuvent pas être parallélisés ainsi que les algorithmes de multiplication. en.wikipedia.org/wiki/Binary_multiplier
mjones.udri

5
Minimiser le nombre de divisions au profit des multiplications est une technique d'optimisation bien connue.
mjones.udri

73

Si le tableau contient à la fois des données positives et négatives, j'irais avec:

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 le tableau contient nan, une solution pourrait être de simplement les supprimer comme suit:

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

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

Cependant, selon le contexte, vous voudrez peut-être traiter nandifféremment. Par exemple, interpolez la valeur, remplacez par par exemple 0, ou générez une erreur.

Enfin, il convient de mentionner même si ce n'est pas la question d'OP, la standardisation :

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

2
Selon ce que vous voulez, ce n'est pas correct, car cela retourne les données. Par exemple, la normalisation à [0, 1] met le max à 0 et le min à 1. Pour [0, 1], vous pouvez simplement soustraire le résultat de 1 pour obtenir la normalisation correcte.
Alan Turing

Merci de l'avoir signalé @AlanTuring qui était très bâclé. Le code, tel que publié, fonctionnait UNIQUEMENT si les données contenaient à la fois des valeurs positives et négatives. Cela peut être assez courant pour les données audio. Cependant, la réponse est mise à jour pour normaliser toutes les valeurs réelles.
Tactopoda

1
Le dernier est également disponible en tant que scipy.stats.zscore.
Lewistrick

d pourrait inverser le signe des échantillons. Si vous souhaitez conserver le signe, vous pouvez utiliser: f = a / np.max(np.abs(a))... sauf si le tableau entier est entièrement à zéro (évitez DivideByZero).
Pimin Konstantin Kefaloukos

1
numpy.ptp()renvoie 0, si c'est la plage, mais nans'il y en a une nandans le tableau. Cependant, si la plage est 0, la normalisation n'est pas définie. Cela soulève une erreur lorsque nous tentons de diviser par 0.
Tactopoda

37

Vous pouvez également redimensionner en utilisant sklearn. Les avantages sont que vous pouvez ajuster la normalisation de l'écart type, en plus du centrage moyen des données, et que vous pouvez le faire sur l'un ou l'autre des axes, par entités ou par enregistrements.

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

Les arguments de mots clés axis, with_mean, with_stdsont explicites et sont présentés dans leur état par défaut. L'argument copyeffectue l'opération sur place s'il est défini sur False. Documentation ici .


X = scale ([1,2,3,4], axis = 0, with_mean = True, with_std = True, copy = True) me donne une erreur
Yfiua

X = scale (np.array ([1,2,3,4]), axis = 0, with_mean = True, with_std = True, copy = True) me donne un tableau de [0,0,0,0]
Yfiua

sklearn.preprocessing.scale () a le backdraw que vous ne savez pas ce qui se passe. Quel est le facteur? Quelle compression de l'intervalle?
MasterControlProgram

Ces méthodes de prétraitement scikit (scale, minmax_scale, maxabs_scale) sont destinées à être utilisées le long d'un seul axe (donc mettez à l'échelle les échantillons (lignes) ou les entités (colonnes) individuellement. Cela a du sens dans une configuration d'apprentissage automatique, mais parfois vous le souhaitez pour calculer la plage sur l'ensemble du tableau ou utiliser des tableaux de plus de deux dimensions.
Toby

11

Vous pouvez utiliser la version "i" (comme dans idiv, imul ..), et cela n'a pas l'air à moitié mauvais:

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

Dans l'autre cas, vous pouvez écrire une fonction pour normaliser un tableau à n dimensions par des colonnes:

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

Pouvez-vous clarifier cela? Les parenthèses font qu'il se comporte différemment de celui sans?
endolith

1
les parantheses ne changent rien. le but était d'utiliser à la /=place de = .. / ..
u0b34a0f6ae

7

Vous essayez de mettre à l'échelle min-max les valeurs audiocomprises entre -1 et +1 et imageentre 0 et 255.

L'utilisation sklearn.preprocessing.minmax_scale, devrait facilement résoudre votre problème.

par exemple:

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

et

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

Remarque : à ne pas confondre avec l'opération qui met à l'échelle la norme (longueur) d'un vecteur à une certaine valeur (généralement 1), qui est également communément appelée normalisation.


4

Une solution simple consiste à utiliser les scalers proposés par la bibliothèque 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'erreur X_rec-X sera nulle. Vous pouvez ajuster feature_range pour vos besoins, ou même utiliser un scaler standart sk.StandardScaler ()


3

J'ai essayé de suivre ça et j'ai eu l'erreur

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

Le numpytableau que j'essayais de normaliser était uninteger tableau. Il semble qu'ils aient désapprouvé la conversion de type dans les versions> 1.10, et vous devez utiliser numpy.true_divide()pour résoudre cela.

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

imgétait un PIL.Imageobjet.

En utilisant notre site, vous reconnaissez avoir lu et compris notre politique liée aux cookies et notre politique de confidentialité.
Licensed under cc by-sa 3.0 with attribution required.