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

endolithe
la source

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')
unutbu
la source
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)
Tactopoda
la source
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 .

cjohnson318
la source
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()
u0b34a0f6ae
la source
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.

jaune01
la source
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 ()

Pantelis
la source
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.

SpoiledBrat
la source