Numpy свернуть с действительным

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

Если это отображено на графике, это проблема, возникающая на границах фрагментов.

Чтобы это исправить, я добавляю N образцов из предыдущего фрагмента в начало массива.

Вопрос в том, удаляет ли приведенная ниже операция свертки образцы из начала или конца исходного массива?

arr = np.array([0.9, 0.9, 0.9, 0.1, 0.1, 0.1, 1.0, 0.1, 0.1, 0.1, 0.9, 0.9, 0.9, ])
print(f"length input array {len(arr)}")
result = np.convolve(arr, [1]*3, 'valid')/3
print(f"length result array {len(result)}")
print(result)
plt.plot(result)

Я настроил приведенный выше пример, чтобы попытаться подтвердить, что добавление образцов в начало массива является правильным местом, но это не помогло. Входная длина — 13, выходная длина — 11.

С перекрытием свертка выглядит так:

result = np.convolve(np.concatenate(previous_samples[-2:], arr), [1]*3, 'valid')/3

Вы, кажется, не понимаете, что такое свертка. Никакие данные не «отбрасываются». Может быть, еще раз прочитать документацию? При использовании аргумента «действительный» размер вывода должен быть равен input_size — kernel_size + 1 по определению.

Julien 09.04.2024 04:16

В любом случае это похоже на проблему XY. Почему бы вам не объяснить свою настоящую конечную цель?

Julien 09.04.2024 04:18

Я могу попробовать переписать более широкую проблему, если это не противоречит правилам, или перенести ее в чат?

Al Grant 09.04.2024 04:26

@Julien, никакие данные не могут быть «отброшены», но факт в том, что выходной массив не такой же длины, как входной массив.

Al Grant 09.04.2024 04:30

измените valid на same, и вы получите вывод, размер которого такой же, как и вход. Но края могут быть не такими, как вы хотите.

Onyambu 09.04.2024 04:33

Затем вам нужно объяснить, как вы хотите, чтобы выходной массив соответствовал размеру, как вы говорите, используя какое-то дополнение для обращения к краю ваших данных, но это ваше решение, нет внутреннего «хорошего» или «плохого» способ сделать это.

Julien 09.04.2024 04:33

@Julien Итак, вопрос в том, должно ли отступы быть в начале или в конце?

Al Grant 09.04.2024 04:34

Стоит ли носить синюю или красную рубашку?

Julien 09.04.2024 04:35

@julien, как исправить проблему с границами, показанную на картинке.

Al Grant 09.04.2024 04:39
Почему в Python есть оператор "pass"?
Почему в Python есть оператор "pass"?
Оператор pass в Python - это простая концепция, которую могут быстро освоить даже новички без опыта программирования.
Некоторые методы, о которых вы не знали, что они существуют в Python
Некоторые методы, о которых вы не знали, что они существуют в Python
Python - самый известный и самый простой в изучении язык в наши дни. Имея широкий спектр применения в области машинного обучения, Data Science,...
Основы Python Часть I
Основы Python Часть I
Вы когда-нибудь задумывались, почему в программах на Python вы видите приведенный ниже код?
LeetCode - 1579. Удаление максимального числа ребер для сохранения полной проходимости графа
LeetCode - 1579. Удаление максимального числа ребер для сохранения полной проходимости графа
Алиса и Боб имеют неориентированный граф из n узлов и трех типов ребер:
Оптимизация кода с помощью тернарного оператора Python
Оптимизация кода с помощью тернарного оператора Python
И последнее, что мы хотели бы показать вам, прежде чем двигаться дальше, это
Советы по эффективной веб-разработке с помощью Python
Советы по эффективной веб-разработке с помощью Python
Как веб-разработчик, Python может стать мощным инструментом для создания эффективных и масштабируемых веб-приложений.
2
9
319
1
Перейти к ответу Данный вопрос помечен как решенный

Ответы 1

Ответ принят как подходящий

Я предполагаю, что (1) читатель имеет базовое представление о том, что такое свертка, и (2) используется ядро ​​с нечетным числом элементов > 1.

Короткий ответ:

  • Используйте np.convolve(…, mode = "valid").

  • Прежде чем сворачивать, дополните куски

    • … в начале с (len(kernel) - 1) / 2 последними элементами предыдущего фрагмента,
    • … в конце первые элементы (len(kernel) - 1) / 2 из следующего фрагмента,

    где kernel относится к вашему скользящему ядру ([1]*3 в вашем вопросе).

Длинный ответ:

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

Функция numpy.convolve() имеет 3 режима обработки границ, как описано здесь:

  • mode = "valid" гарантирует, что скользящее ядро ​​никогда не покинет сигнал. Однако это означает, что результат будет содержать меньше элементов, чем сигнал; а именно len(kernel) - 1 меньше элементов. Итак, если у вас есть сигнал из 100 элементов и ядро ​​из 5 элементов, ваш результат будет содержать 100-(5-1)=96 элементов. Вопрос о том, где операция «удаляет» недостающие элементы, может немного ввести в заблуждение; однако один из способов представить результат - записать его значения в центры скользящего ядра (что имеет смысл для симметричного ядра с нечетным числом элементов, как в случае с вашим вопросом), поэтому с этой интерпретацией можно было бы симметрично удалить элементы: (len(kernel) - 1) / 2 элементы в начале, (len(kernel) - 1) / 2 элементы в конце сигнала. Мы увидим, что это содержательная интерпретация в примере кода ниже.
  • mode = "same" гарантирует, что результирующий сигнал будет иметь то же количество значений, что и входной сигнал. Однако это подразумевает, что нам придется «придумать» значения, чтобы компенсировать эффект меньшего количества элементов, который мы видели с помощью mode = "valid". Как следствие, в этом режиме сигнал дополняется нулями – (len(kernel) - 1) / 2 как в начале, так и в конце – перед операцией свертки.
  • mode = "full", наконец, гарантирует, что будут созданы все потенциальные комбинации перекрытия между сигналом и скользящим ядром. Это означает, что (а) нам придется «составить» еще больше значений и (б) что результат будет больше, чем входной сигнал. «Придуманные» значения здесь снова равны нулям, а результат будет содержать элементы len(signal) + len(kernel) - 1, то есть 100+5-1=104 элемента для предыдущего примера.

Если вы обрабатываете свой сигнал частями, лучше всего использовать mode = "valid": вы можете гарантировать, что полученные свернутые фрагменты будут иметь ту же длину, что и входные фрагменты, без необходимости «составлять» значения (т. е. без необходимости заполнения нулями) , путем заполнения конечными значениями из предыдущего фрагмента и ведущими значениями из следующего фрагмента. Это справедливо для всех фрагментов, кроме первого и последнего, и в этих случаях вам придется самостоятельно решать, как следует обрабатывать граничную ситуацию. Во всех остальных случаях дополняйте значениями предыдущего и последующего фрагментов на половину единицы меньше, чем длина ядра.

Код ниже демонстрирует, что описанное заполнение фрагментов дает точно такие же результаты, как если бы сигнал вообще не дробился на фрагменты. Обратите внимание, что я сместил полученные свернутые сигналы на графиках на значения (len(kernel) - 1) / 2, чтобы учесть описанные недостающие значения с mode = "valid" в самом начале сигнала.

import matplotlib.pyplot as plt
import numpy as np

len_signal, len_kernel = 100, 5
num_chunks = 10

rand = np.random.default_rng(seed=42)
signal = np.cumsum(rand.normal(size=len_signal))
kernel = np.ones(len_kernel) / len_kernel

# Produce convolution result without chunking for reference
signal_convolved = np.convolve(signal, kernel, mode = "valid")

# Split into chunks, then pad them: (1) start with `(len_kernel - 1) / 2`
# last values from preceding chunk (except first chunk), (2) end with
# `(len_kernel - 1) / 2` first values from following chunk (except last chunk)
len_arm = (len_kernel - 1) // 2  # Length of a "kernel arm"
chunks_convolved = []
for i, chunk in enumerate(chunks := np.split(signal, num_chunks)):
    p1 = [] if i == 0 else chunks[i - 1][-len_arm:]  # Start padding
    p2 = [] if i == len(chunks) - 1 else chunks[i + 1][:len_arm]  # End padding
    chunk_conv = np.convolve(np.r_[p1, chunk, p2], kernel, mode = "valid")
    chunks_convolved = np.r_[chunks_convolved, chunk_conv]  # Append

assert np.allclose(signal_convolved, chunks_convolved)  # Check: same result?
# Show: convolved w/o chunking (left) vs chunked, padded, and convolved (right)
x_c = np.arange(len(signal_convolved)) + len_arm   # Right-shift conv. result
plt.subplot(121)
plt.plot(signal, "--"); plt.plot(x_c, signal_convolved); plt.title("no chunks")
plt.subplot(122)
plt.plot(signal, "--"); plt.plot(x_c, chunks_convolved); plt.title("chunks")
plt.show()

Как вы можете видеть (и как мы проверили с помощью assert), оба результата одинаковы:

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

Это отличный ответ. Спасибо. Единственный вопрос, который у меня остается, это как подойти к заполнению «внешней части» обрабатываемого фрагмента? Первый вариант, который приходит на ум, — не обрабатывать фрагмент до тех пор, пока следующий фрагмент не будет поставлен в очередь — и, следовательно, следующий фрагмент не будет доступен для захвата образцов заполнения?

Al Grant 10.04.2024 21:48

@AlGrant Это хороший момент. Обычно я вижу 3 проблемы: (1) При использовании самого первого фрагмента у вас никогда не будет предшествующих данных. Таким образом, вам нужно подумать, приемлемо ли удаление нескольких образцов или вы хотите использовать какой-либо режим заполнения (либо нули, либо любой из convolve1d(), упомянутых выше, я бы сказал, что все это является обычной практикой). (2) Для каждого фрагмента вы можете дополнить его конец только после того, как станет доступен преемник. Я бы подумал, приемлемо ли «бесконечное ожидание данных» или через определенное время вам снова захочется найти другой режим работы с недостающими данными…

simon 11.04.2024 09:20

@AlGrant… например, удаление или дополнение другими значениями. (3) Для самого последнего фрагмента есть два вопроса: (3a) знаете ли вы, что это последний фрагмент, и (3b) как вы будете обрабатывать недостающие последующие данные? Re 3a: Если вы не знаете, что это последний фрагмент, проблема, по сути, такая же, как и в случае, если вам придется слишком долго ждать преемника (см. 2). В любом случае (в отношении 3b) вам снова придется решить, как обрабатывать недостающие данные заполнения в конце. Я бы сказал, что нет правильных или неправильных вариантов, а есть лучшие и менее идеальные варианты для вашего конкретного случая использования. Надеюсь, это поможет.

simon 11.04.2024 09:25

Другие вопросы по теме

Визуализация данных временных рядов с помощью тепловых карт и трехмерных графиков поверхности
Как упаковать несколько данных int8 в строку, которая может быть инициализатором переменной C++?
Как избежать «RuntimeWarning: деление на ноль, обнаруженное в log10» в цикле for с более чем 20 000 элементов?
Найти среднее значение группы значений индекса в фрейме данных pandas на основе близости к другим значениям
Векторизация вложенных циклов for с условием в NumPy
Как превратить динамически распределенный массив C в массив Numpy и вернуть его в Python в модуле расширения C
Реализация 2D-скользящего окна поверх np.array без перекрытий (плитка, среднее объединение)
Улучшение качества подгонки функции ошибки с помощью Python
Скольжение стандартного отклонения всех столбцов, игнорируя NaN
Почему векторизованная оценка NumPy выполняется медленнее при хранении векторов в качестве атрибутов класса?