Как нормализовать массив NumPy до определенного диапазона?

136

После некоторой обработки массива аудио или изображений его необходимо нормализовать в пределах диапазона, прежде чем он сможет быть записан обратно в файл. Сделать это можно так:

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

Есть ли способ сделать это менее подробным и удобным способом? matplotlib.colors.Normalize()похоже, не связано.

эндолиты
источник

Ответы:

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

Использование /=и *=позволяет удалить промежуточный временный массив, тем самым экономя память. Умножение дешевле деления, поэтому

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

немного быстрее, чем

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

Поскольку здесь мы используем базовые методы numpy, я думаю, что это настолько эффективное решение в numpy, насколько это возможно.


Операции на месте не изменяют dtype массива контейнеров. Так как желаемые нормированные значения поплавков, то audioи imageмассивы должны иметь с плавающей запятой точкой DTYPE перед операцией в месте выполняется. Если они еще не имеют dtype с плавающей запятой, вам необходимо преобразовать их, используя astype. Например,

image = image.astype('float64')
unutbu
источник
7
Почему умножение дешевле деления?
endolith
19
Не знаю точно почему. Однако я уверен в претензии, проверив ее timeit. С умножением вы можете работать с одной цифрой за раз. При делении, особенно с большими делителями, вы должны работать со многими цифрами и «угадывать», сколько раз делитель входит в делимое. Чтобы решить одну задачу деления, вы выполняете множество задач умножения. Компьютерный алгоритм деления может отличаться от человеческого деления в столбик, но, тем не менее, я считаю, что он сложнее умножения.
unutbu
14
Наверное, стоит упомянуть деление на ноль пустых изображений.
cjm2671
7
Умножение @endolith дешевле деления из-за того, как оно реализовано на уровне сборки. Алгоритмы деления не могут быть распараллелены так же, как алгоритмы умножения. en.wikipedia.org/wiki/Binary_multiplier
mjones.udri
5
Сведение к минимуму количества делений в пользу умножения - хорошо известный метод оптимизации.
mjones.udri
73

Если массив содержит как положительные, так и отрицательные данные, я бы пошел с:

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

Если массив содержит nan, одним из решений может быть просто удалить их как:

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

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

Однако в зависимости от контекста вы можете относиться к нему по- nanразному. Например, интерполируйте значение, заменив, например, на 0, или вызовите ошибку.

Наконец, стоит упомянуть, даже если это не вопрос OP, стандартизация :

e = (a - np.mean(a)) / np.std(a)
Tactopoda
источник
2
В зависимости от того, что вы хотите, это неверно, поскольку оно переворачивает данные. Например, нормализация к [0, 1] ставит max на 0, а min на 1. Для [0, 1] вы можете просто вычесть результат из 1, чтобы получить правильную нормализацию.
Алан Тьюринг
Спасибо, что указали на @AlanTuring, это было очень небрежно. Код в том виде, в котором он был опубликован, работал ТОЛЬКО, если данные содержали как положительные, так и отрицательные значения. Это может быть довольно распространено для аудиоданных. Однако ответ обновляется для нормализации любых реальных значений.
Tactopoda
1
Последний также доступен как scipy.stats.zscore.
Льюистрик
d может перевернуть знак образцов. Если вы хотите сохранить знак, вы можете использовать: f = a / np.max(np.abs(a))... если весь массив не обнуляется (избегайте DivideByZero).
Пимин Константин Кефалукос
1
numpy.ptp()возвращает 0, если это диапазон, но nanесли он есть nanв массиве. Однако, если диапазон равен 0, нормализация не определена. Это вызывает ошибку, когда мы пытаемся разделить на 0.
Tactopoda
37

Вы также можете изменить масштаб, используя sklearn. Преимущества состоят в том, что вы можете настроить нормализацию стандартного отклонения в дополнение к центрированию данных по среднему значению, и что вы можете делать это по любой оси, по функциям или по записям.

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

Ключевое слово arguments axis , with_mean, with_stdсамообъясняющие и приведены в состояние по умолчанию. Аргумент copyвыполняет операцию на месте, если для него установлено значение False. Документация здесь .

cjohnson318
источник
X = scale ([1,2,3,4], axis = 0, with_mean = True, with_std = True, copy = True) выдает ошибку
Yfiua
X = scale (np.array ([1,2,3,4]), axis = 0, with_mean = True, with_std = True, copy = True) дает мне массив [0,0,0,0]
Yfiua 06
sklearn.preprocessing.scale () имеет заднюю часть, что вы не знаете, что происходит. Что это за фактор? Какая компрессия интервала?
MasterControlProgram
Эти методы предварительной обработки scikit (scale, minmax_scale, maxabs_scale) предназначены для использования только по одной оси (поэтому можно масштабировать выборки (строки) или элементы (столбцы) по отдельности. Это имеет смысл в настройке машинного обучения, но иногда вам нужно для вычисления диапазона по всему массиву или используйте массивы с более чем двумя измерениями.
Тоби
11

Вы можете использовать версию "i" (как в idiv, imul ..), и это выглядит неплохо:

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

В другом случае вы можете написать функцию для нормализации n-мерного массива по столбцам:

def normalize_columns(arr):
    rows, cols = arr.shape
    for col in xrange(cols):
        arr[:,col] /= abs(arr[:,col]).max()
u0b34a0f6ae
источник
Вы можете это прояснить? Скобки заставляют его вести себя иначе, чем без?
endolith
1
скобки ничего не меняют. суть заключалась в использовании /=вместо = .. / ..
u0b34a0f6ae
7

Вы пытаетесь масштабировать min-max значения от audio-1 до +1 и imageот 0 до 255.

Использование sklearn.preprocessing.minmax_scale, должно легко решить вашу проблему.

например:

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

и

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

Примечание : не путать с операцией, которая масштабирует норму (длину) вектора до определенного значения (обычно 1), что также обычно называется нормализацией.

yellow01
источник
4

Простое решение - использовать средства масштабирования, предлагаемые библиотекой 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)

Ошибка X_rec-X будет равна нулю. Вы можете настроить feature_range под свои нужды или даже использовать стандартный скейлер sk.StandardScaler ()

Pantelis
источник
3

Я попытался выполнить это и получил ошибку

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

numpyМассив , который я пытался нормализуют был integerмассив. Кажется, они устарели с приведением типов в версиях> 1.10, и вы должны использовать это numpy.true_divide()для решения этой проблемы.

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

imgбыл PIL.Imageобъектом.

Избалованный ребенок
источник