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

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

# 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(), похоже, не связано.


person endolith    schedule 14.11.2009    source источник


Ответы (8)


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')
person unutbu    schedule 14.11.2009
comment
Почему умножение дешевле деления? - person endolith; 15.11.2009
comment
Я не знаю точно, почему. Однако я уверен в этом утверждении, проверив его на timeit. С умножением вы можете работать с одной цифрой за раз. При делении, особенно с большими делителями, приходится работать со многими цифрами, и угадывать, сколько раз делитель входит в делимое. В итоге вы решаете много задач на умножение, чтобы решить одну задачу на деление. Компьютерный алгоритм деления может не совпадать с человеческим делением в длину, но тем не менее я считаю, что он сложнее, чем умножение. - person unutbu; 15.11.2009
comment
Вероятно, стоит упомянуть о делении на ноль для пустых изображений. - person cjm2671; 22.06.2014
comment
Умножение @endolith дешевле, чем деление, из-за того, как оно реализовано на уровне сборки. Алгоритмы деления нельзя распараллелить так же, как алгоритмы умножения. en.wikipedia.org/wiki/Binary_multiplier - person Mat Jones; 27.11.2016
comment
@mjones.udri Да, но если вы делите весь массив на скаляр, разве это не должно экономить время, умножая на скаляр, обратный? - person endolith; 27.11.2016
comment
@endolith, если вы скажете ему вести себя так, то да. - person Mat Jones; 28.11.2016
comment
@mjones.udri Есть ли числовые проблемы с автоматическим выполнением? - person endolith; 28.11.2016
comment
@endolith нет! Подумайте об определении деления: умножение на обратное. 10/5 = 10 * (1/5) - person Mat Jones; 29.11.2016
comment
Минимизация количества делений в пользу умножения — хорошо известный метод оптимизации. - person Mat Jones; 29.11.2016
comment
@mjones.udri Да, это верно для математических понятий, но я спрашиваю, верно ли это для чисел фиксированной длины с плавающей запятой. Вызывает ли это какую-либо числовую ошибку в нечетных случаях, таких как денормалы и т. д.? - person endolith; 29.11.2016
comment
@endolith с числами с плавающей запятой это возможно, но я не уверен. Я думаю, это зависит от того, насколько точность (если она вообще есть) теряется во время манипуляций. - person Mat Jones; 29.11.2016
comment
вы исключаете промежуточный временный массив, но можете получить TypeError: Cannot cast ufunc multiply output from dtype('float64') to dtype('int64') with casting rule 'same_kind', если image имеет тип int. - person RNA; 11.08.2019

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

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)
person user2821    schedule 08.07.2017
comment
В зависимости от того, что вы хотите, это неправильно, так как переворачивает данные. Например, нормализация до [0, 1] помещает максимум в 0, а минимум в 1. Для [0, 1] вы можете просто вычесть результат из 1, чтобы получить правильную нормализацию. - person Alan Turing; 20.05.2018
comment
Спасибо, что указали на @AlanTuring, что это было очень небрежно. Код в опубликованном виде работал ТОЛЬКО, если данные содержали как положительные, так и отрицательные значения. Это может быть довольно распространенным явлением для аудиоданных. Однако ответ обновляется, чтобы нормализовать любые реальные значения. - person user2821; 20.05.2018
comment
Последний также доступен как scipy.stats.zscore. - person Lewistrick; 10.05.2019
comment
d может изменить знак образцов. Если вы хотите сохранить знак, вы можете использовать: f = a / np.max(np.abs(a))... если только весь массив не содержит нулей (избегайте DivideByZero). - person Pimin Konstantin Kefaloukos; 21.12.2019
comment
Пожалуйста, убедитесь, что значение ptp не равно 0, чтобы не получать nan. - person Milso; 11.03.2020
comment
numpy.ptp() возвращает 0, если это диапазон, и nan, если в массиве есть один nan. Однако если диапазон равен 0, нормализация не определена. Это вызывает ошибку, когда мы пытаемся разделить на 0. - person user2821; 12.03.2020

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

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

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

person cjohnson318    schedule 17.01.2014
comment
X = масштаб ([1,2,3,4], ось = 0, with_mean = True, with_std = True, copy = True) дает мне ошибку - person Yfiua; 06.04.2016
comment
X = масштаб(np.array([1,2,3,4]), axis=0, with_mean=True, with_std=True, copy=True) дает мне массив [0,0,0,0] - person Yfiua; 06.04.2016
comment
sklearn.preprocessing.scale() имеет обратный эффект, что вы не знаете, что происходит. Что является фактором? Какое сжатие интервала? - person MasterControlProgram; 29.11.2016
comment
Эти методы предварительной обработки scikit (масштаб, minmax_scale, maxabs_scale) предназначены для использования только вдоль одной оси (поэтому либо масштабируйте выборки (строки) или функции (столбцы) по отдельности. Это имеет смысл при настройке машинного обучения, но иногда вам нужно чтобы вычислить диапазон по всему массиву или использовать массивы с более чем двумя измерениями. - person Toby; 16.11.2017
comment
Не работает для массивов размерностью › 2. - person zfj3ub94rf576hc4eegm; 18.12.2020

Вы можете использовать версию «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()
person u0b34a0f6ae    schedule 14.11.2009
comment
Вы можете это прояснить? Скобки заставляют его вести себя иначе, чем без него? - person endolith; 15.11.2009
comment
скобки ничего не меняют. смысл был в том, чтобы использовать /= вместо = .. / .. - person u0b34a0f6ae; 15.11.2009

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

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

e.g.:

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), что также обычно называют нормализацией.

person fabda01    schedule 05.04.2019

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

person Pantelis    schedule 21.02.2018

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

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.

person srdg    schedule 14.05.2018

Этот ответ на похожий вопрос решил проблему для меня с

np.interp(a, (a.min(), a.max()), (-1, +1))
person Surya Narayanan    schedule 24.04.2021