Como normalizar uma matriz NumPy para dentro de um determinado intervalo?

136

Depois de fazer algum processamento em uma matriz de áudio ou imagem, ele precisa ser normalizado dentro de um intervalo antes de poder ser gravado novamente em um arquivo. Isso pode ser feito da seguinte maneira:

# 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 uma maneira menos detalhada de função de conveniência para fazer isso? matplotlib.colors.Normalize()não parece estar relacionado.

endólito
fonte

Respostas:

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

Usando /=e *=permite eliminar uma matriz temporária intermediária, economizando um pouco de memória. A multiplicação é mais barata que a divisão, então

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

é marginalmente mais rápido que

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

Como estamos usando métodos numpy básicos aqui, acho que essa é a solução mais eficiente possível.


As operações no local não alteram o tipo de matriz do contêiner. Como os valores normalizados desejados são flutuantes, as matrizes audioe imageprecisam ter o tipo de ponto de ponto flutuante antes que as operações no local sejam executadas. Se eles ainda não são do tipo de ponto flutuante, será necessário convertê-los usando astype. Por exemplo,

image = image.astype('float64')
unutbu
fonte
7
Por que multiplicação é menos cara que divisão?
endolith 14/11/2009
19
Eu não sei exatamente o porquê. No entanto, estou confiante na reivindicação, depois de a ter verificado com o tempo. Com a multiplicação, você pode trabalhar com um dígito de cada vez. Com a divisão, especialmente com divisores grandes, é necessário trabalhar com muitos dígitos e "adivinhar" quantas vezes o divisor entra no dividendo. Você acaba fazendo muitos problemas de multiplicação para resolver um problema de divisão. O algoritmo de computador para fazer a divisão pode não ser o mesmo que a divisão longa humana, mas, no entanto, acredito que é mais complicado que a multiplicação.
unutbu 15/11/2009
14
Provavelmente vale a pena mencionar uma divisão por zero para imagens em branco.
cjm2671
7
A multiplicação @endolith é mais barata que a divisão, devido à maneira como é implementada no nível da Assembléia. Os algoritmos de divisão não podem ser paralelizados, assim como os algoritmos de multiplicação. pt.wikipedia.org/wiki/multiplicadorBinário
mjones.udri 27/11
5
Minimizar o número de divisões em favor de multiplicações é uma técnica de otimização bem conhecida.
Mjones.udri 29/11
73

Se a matriz contiver dados positivos e negativos, eu usaria:

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 a matriz contiver nan, uma solução pode ser removê-las apenas como:

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

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

No entanto, dependendo do contexto, você pode querer tratar de maneira nandiferente. Por exemplo, interpole o valor, substituindo por, por exemplo, 0 ou gere um erro.

Por fim, vale a pena mencionar, mesmo que não seja uma pergunta do OP, a padronização :

e = (a - np.mean(a)) / np.std(a)
Tactopoda
fonte
2
Dependendo do que você deseja, isso não está correto, pois inverte os dados. Por exemplo, a normalização para [0, 1] coloca o máximo em 0 e min em 1. Para [0, 1], você pode subtrair o resultado de 1 para obter a normalização correta.
Alan Turing
Obrigado por apontar para @AlanTuring que foi muito desleixado. O código, conforme publicado, funcionou SOMENTE se os dados contivessem valores positivos e negativos. Isso pode ser bastante comum para dados de áudio. No entanto, a resposta é atualizada para normalizar quaisquer valores reais.
Tactopoda
1
O último também está disponível como scipy.stats.zscore.
Lewistrick 10/05/19
d pode inverter o sinal das amostras. Se você deseja manter o sinal, pode usar: f = a / np.max(np.abs(a))... a menos que toda a matriz seja zerada (evite DivideByZero).
Pimin Konstantin Kefaloukos
1
numpy.ptp()retorna 0, se esse for o intervalo, mas nanse houver um nanna matriz. No entanto, se o intervalo for 0, a normalização não será definida. Isso gera um erro quando tentamos dividir com 0.
Tactopoda
37

Você também pode redimensionar usando sklearn. As vantagens são que você pode ajustar a normalização do desvio padrão, além de centralizar os dados na média, e fazer isso no eixo, pelos recursos ou pelos registros.

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

Os argumentos de palavra-chave axis, with_mean, with_stdsão auto-explicativas, e são mostrados em seu estado padrão. O argumento copyexecuta a operação no local, se estiver definido como False. Documentação aqui .

cjohnson318
fonte
X = escala ([1,2,3,4], eixo = 0, with_mean = True, with_std = True, copie = True) me dá um erro
Yfiua
X = escala (np.array ([1,2,3,4]), eixo = 0, with_mean = True, with_std = True, copy = True) fornece uma matriz de [0,0,0,0]
Yfiua 6/04/16
sklearn.preprocessing.scale () tem o backdraw de que você não sabe o que está acontecendo. Qual é o fator? Que compressão do intervalo?
MasterControlProgram
Esses métodos de pré-processamento do scikit (scale, minmax_scale, maxabs_scale) devem ser usados ​​ao longo de apenas um eixo (para dimensionar as amostras (linhas) ou os recursos (colunas) individualmente. para calcular o intervalo ao longo de toda a matriz, ou utilizar matrizes com mais do que duas dimensões.
Toby
11

Você pode usar a versão "i" (como em idiv, imul ..) e ela não parece nem um pouco ruim:

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

No outro caso, você pode escrever uma função para normalizar uma matriz n-dimensional por colunas:

def normalize_columns(arr):
    rows, cols = arr.shape
    for col in xrange(cols):
        arr[:,col] /= abs(arr[:,col]).max()
u0b34a0f6ae
fonte
Você pode esclarecer isso? Os parênteses fazem com que se comporte de maneira diferente do que sem?
endolith 14/11/2009
1
parênteses não mudam nada. o ponto era usar em /=vez de = .. / ..
u0b34a0f6ae 15/11/2009
7

Você está tentando escalar min-max os valores audioentre -1 e +1 e imageentre 0 e 255.

Usando sklearn.preprocessing.minmax_scale, deve resolver facilmente o seu problema.

por exemplo:

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 : Não deve ser confundida com a operação que dimensiona a norma (comprimento) de um vetor para um determinado valor (geralmente 1), que também é comumente referido como normalização.

yellow01
fonte
4

Uma solução simples é usar os dimensionadores oferecidos pela 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)

O erro X_rec-X será zero. Você pode ajustar o intervalo_funcionalidade para suas necessidades ou até mesmo usar um scaler padrão sk.StandardScaler ()

Pantelis
fonte
3

Eu tentei seguir isso , e recebi o erro

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

A numpymatriz que eu estava tentando normalizar era uma integermatriz. Parece que eles preteriram a conversão de tipos nas versões> 1.10, e você precisa usar isso numpy.true_divide()para resolver isso.

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

imgfoi um PIL.Imageobjeto.

Miúdo mimado
fonte