Вычесть среднее значение из многомерного Numpy-массива

В настоящее время я изучаю вещание в Numpy и в книге, которую читаю (Python for Data Analysis by Wes McKinney автор упомянул следующий пример, чтобы "унизить" двумерный массив:

import numpy as np

arr = np.random.randn(4, 3)
print(arr.mean(0))
demeaned = arr - arr.mean(0)
print(demeaned)
print(demeand.mean(0))

Что фактически приводит к тому, что массив demeaned имеет среднее значение 0.

У меня возникла идея применить это к трехмерному массиву, похожему на изображение:

import numpy as np

arr = np.random.randint(0, 256, (400,400,3))
demeaned = arr - arr.mean(2)

Что, конечно же, не удалось, потому что, согласно правилу вещания, конечные размеры должны совпадать, а здесь это не так:

print(arr.shape)  # (400, 400, 3)
print(arr.mean(2).shape)  # (400, 400)

Теперь я заставил его работать в основном, вычитая среднее значение из каждого отдельного индекса в третьем измерении массива:

demeaned = np.ones(arr.shape)

for i in range(3):
    demeaned[...,i] = arr[...,i] - means

print(demeaned.mean(0))

На данный момент возвращаемые значения очень близки к нулю, и я думаю, что это ошибка точности. Я действительно прав с этой мыслью или есть еще одна оговорка, которую я пропустил?

Кроме того, это не кажется самым чистым, самым «numpy» способом достичь того, чего я хотел достичь. Есть ли функция или принцип, который я могу использовать для улучшения кода?


person Tim Hilt    schedule 28.11.2019    source источник
comment
Что означает, что вы пытаетесь вычесть? x.mean(2) — это массивы 400x400 средних значений для каждого пикселя. Вместо этого вы хотели получить 3-элементное среднее для каждой плоскости изображения?   -  person Mad Physicist    schedule 28.11.2019
comment
Нет, хотел доказать концепцию, в том смысле, что я мог бы снова вычислить среднее значение каждого пикселя после вычитания и показать, что оно действительно равно 0 на всем протяжении.   -  person Tim Hilt    schedule 28.11.2019
comment
Вы можете использовать arr.mean(2)[:, :, np.newaxis] для создания соответствующей формы. See Try it online !   -  person 301_Moved_Permanently    schedule 28.11.2019
comment
@409_Conflict При таком подходе я получаю форму (400 400,1). То же самое было бы возвращено, если бы я вызвал np.mean с keepdims=True.   -  person Tim Hilt    schedule 28.11.2019
comment
@409_Conflict спасибо за комментарий. Теперь я понял вашу точку зрения. Не понял, что я могу вычесть полученный массив! Также спасибо за указатель на tio! Я не знал, что один!   -  person Tim Hilt    schedule 28.11.2019


Ответы (2)


Начиная с numpy версии 1.7.0, np.mean, и несколько других функций принимают кортеж в параметре axis. Это означает, что вы можете выполнить операцию сразу на всех плоскостях изображения:

m = arr.mean(axis=(0, 1))

Это среднее значение будет иметь форму (3,) с одним элементом для каждой плоскости изображения.

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

n = arr.mean(axis=2)
n = n.reshape(*n.shape, 1)

Or

n = arr.mean(axis=2)[..., None]
person Mad Physicist    schedule 28.11.2019
comment
Ладно, я понял! Это также возможно, если скалярные значения для каждого пикселя сами содержатся в массиве. Таким образом, вызов arr.mean(2, keepdims=True) также приведет к правильному массиву, который я могу вычесть из исходного массива пикселей. Это правильно? - person Tim Hilt; 28.11.2019
comment
@TimHilt абсолютно - person Mad Physicist; 28.11.2019
comment
Большое спасибо! Это было то, что я искал. - person Tim Hilt; 28.11.2019

Попробуйте np.apply_along_axis().

np.apply_along_axis(lambda x: x - np.mean(x), 2, arr)

Вывод: вы получаете массив той же формы, где каждая ячейка уменьшена в нужном вам измерении (второй параметр, здесь это 2).

person sergzach    schedule 28.11.2019
comment
Это мило! На самом деле, такой подход мне нравится немного больше, потому что вы избавляетесь от (иногда сбивающего с толку) вещания! Вы знаете, как это отличается по производительности от широковещательного подхода? - person Tim Hilt; 28.11.2019
comment
@TimHilt Прости, я не знаю. Вы можете проверить это с помощью модуля timeit. - person sergzach; 28.11.2019
comment
apply_along_axis в этом случае медленнее примерно в 470 раз. 2,09 с против 4,42 мс. - person Tim Hilt; 28.11.2019
comment
@TimHilt Может быть, для этих данных да. Может быть для всех данных да. - person sergzach; 28.11.2019
comment
@TimHilt Обратите внимание, это не очень большой объем данных. Могут быть подготовительные процедуры (выделение памяти и т.д.) перед основной задачей, которые тратят некоторое время. Также потенциально он может работать намного лучше на компьютерах с некоторыми функциями. - person sergzach; 28.11.2019
comment
apply_along_axis — это оболочка для запуска цикла Python. Это в основном отбрасывает преимущества использования numpy. - person Mad Physicist; 28.11.2019