Хотите узнать больше о Cython? Следуйте за мной в этой серии, и я покажу вам практические примеры реализаций C-Cython-Python.



Я так люблю Cython, поскольку он сочетает в себе лучшее из двух основных миров программирования: C и Python. Оба языка можно легко комбинировать вместе, чтобы предоставить вам более эффективные с точки зрения вычислений API или сценарии. Кроме того, кодирование на Cython и C помогает вам понять, что скрывается за общими пакетами Python, такими как sklearn, выводя специалистов по данным на следующий шаг, который довольно далек от простого import torch и использования предопределенных алгоритмов.

В этой серии я собираюсь показать вам, как реализовать алгоритмы C и Cyhton для анализа музыки, руководствуясь соответствующими пакетами sklearn и scipy.

Этот урок посвящен Кратковременному преобразованию Фурье или STFT. Этот алгоритм широко используется для анализа частот сигнала и их эволюции во времени. Коды хранятся в этом репозитории:



Коды чрезвычайно полезны для понимания того, как структурировать проект Cython, разделить коды по папкам и установить окончательный пакет.

Этот пост содержит внешнюю ссылку на партнерскую программу Amazon.

Теория непрофессионала

Не стесняйтесь пропустить этот раздел, если хотите сразу же запачкать руки кодом. Это всего лишь легкое введение в STFT и его теорию.

Основным аспектом преобразования Фурье является отображение (или, скажем, набросок) сигнала в частотной области с указанием наиболее важных частот, составляющих сам сигнал. Это отображение имеет широкое применение во многих областях: в биомедицинской инженерии (например, изучение частотных вкладов в электрокардиограмме (ЭКГ) для обнаружения возможных заболеваний или сердечных сбоев » «² ³), в вычислительной науке (например, алгоритмы сжатия, такие как mp3, jpeg ) или финансов (например, изучение цен на акции, поведение цен на облигации). Это сопоставление также полезно для изучения музыкальных сигналов, так как основной частотный контент может быть извлечен и проанализирован, например, для создания классификаторов жанров или приложения, такого как Shazam (например, проверьте мой пост). Однако иногда бывает интересно и полезно понять эволюцию частоты во времени и амплитуде, чтобы найти определенные шумы или выровнять частоты в сеансе записи, или создать алгоритмы нейронной сети для преобразования речевого сигнала в текст (например, DeepPavlov ).

На практике STFT делит сингал времени на короткие сегменты равной длины (window_length), а затем вычисляется преобразование Фурье каждого сегмента. Результирующее частотное содержание сегмента может быть отображено в зависимости от времени, и оно называется спектрограммой.

Практически STFT можно свести к следующим этапам:

  • Возьмите входной сигнал (например, mp3-файл)
  • Умножьте сигнал на оконную функцию (например, функцию Хэмминга). Это поможет вычислить преобразование Фурье на крайних точках каждого сегмента, чтобы избежать возможных разрывов в сигнале, которые могут блокировать вычисление преобразования Фурье.
  • Проведите с окном и окном размера прыжка по сингалу / времени и вычислите преобразование Фурье

Рис. 1 помогает лучше понять, что делает STFT. Входной сигнал с определенной амплитудой в децибелах и временем в секундах инкапсулируется в N окон размером windowSize. Каждые HopSize, окно определяет сегмент сигнала, который преобразуется Фурье. Выходная частота в герцах может быть отображена как функция времени.

Последнее, что нужно помнить, - это частота Найквиста. Если вы посмотрите на спектрограмму, вы обнаружите, что максимальная отображаемая частота составляет половину частоты дискретизации сигнала, чтобы избежать проблемы, известной как наложение в преобразовании Фурье. Это означает, что из N комплексных коэффициентов Фурье, извлеченных из сигнала с частотой дискретизации fs (например, аудиофайл обычно имеет частоту дискретизации 44100 Гц), только половина они полезны, представляя частоты от 0 до fs / 2

Для получения дополнительной информации я определенно рекомендую эти очень ценные и полезные книги:

STFT в Cython: повеселимся

План действий

На рисунке 2 показан план действий по реализации STFT в Cython и C. Весь процесс можно разделить на три части, которые используют Python, Cython и C соответственно:

  1. Интерфейс Python имеет дело с вводом / выводом и манипулированием сигналом:
  • Входной файл mp3 / wav открывается с помощью scipy или pydub
  • Левый и правый каналы изолированы, анализируется только channel[:,0]
  • Заполнение выполняется для сигнала, поэтому общее количество элементов равно степени 2, что улучшает производительность библиотеки преобразования Фурье fftw

2. Интерфейс Cython переводит входные данные Pythonic в представления памяти, которые затем могут быть легко переданы в качестве указателей на набор C:

  • Чтобы иметь более конкретное представление, на рис.3 показан пример создания представления памяти в Cython из массива нулей np.zeros длины n_elements.

3. Интерфейс C выполняет основные операции STFT. В двух словах:

  • Определите fftw3 библиотечный домен с fftw элементами и планом. План необходим для выполнения быстрого преобразования Фурье:
  • Создайте окно Хэмминга
  • Выполните цикл STFT for: умножьте входной фрагмент на окно Хэмминга, вычислите быстрое преобразование Фурье на выходе, сохраните половину этого результата.

Очень важно подчеркнуть, что текущая структура обеспечивает выполнение наиболее интенсивных вычислительных операций в коде C, а затем вся информация возвращается обратно в Python. Это позволяет снизить вычислительные затраты - поскольку многие алгоритмы реализованы только на Python, а не на C - и повысить общую производительность кода Python.

Скрипт Python

Код: https://github.com/Steboss/music_retrieval/blob/master/stft/installer/tester.py

Скрипт Python обрабатывает входные аудиофайлы и подготавливает всю информацию для выполнения STFT.

Во-первых, pydub можно использовать для открытия и чтения mp3-файлов, а scipy предлагает встроенную функцию для чтения расширений wav:

Затем мы можем определить все «константы» в нашем коде и вызвать STFT:

На рис. 6 определены windowSize и hopSize. Как показано на рис.1, эти две величины могут перекрываться. В общем, большее перекрытие дает больше точек анализа и более плавные результаты во времени, но расплачиваться за это приходится более высокими вычислительными затратами. В целом для этого урока мы можем достичь того же размера, но не стесняйтесь экспериментировать, насколько это возможно.

Код Cython

Код: https://github.com/Steboss/music_retrieval/blob/master/stft/installer/stft.pyx

Коды Cython обычно имеют два расширения: pyx и pxd. В первом написан основной код, а во втором - для объявлений. В этом руководстве мы собираемся использовать только расширение pyx, чтобы лучше познакомиться с Cython и иметь дело только с одним фрагментом кода.

Первый шаг - импортировать наш C-код в Cython - позже мы увидим, как структурирован c-код:

Код C импортируется с использованием объявления cdef extern from YOUR_C_CODE:. Далее необходимо объявить имя функции C, которую мы хотим использовать, вместе со всеми типами, точно так же, как если бы мы были в коде C. Таким образом, функция stft возвращает массив как указатель, поэтому double* для stft C-функции. Аргументами являются входной аудиоканал double *wav_data, количество выборок в конечном сигнале STFT int samples, окно и размер скачка int windowSize, int hop_Size, частота дискретизации и длина аудиоканала int sample_freq, int length. samples можно вычислить как:

samples = int((length/windowSize/2))*((windowSize/2)+1))

Следующим шагом будет создание и объявление нашей основной функции., Которая будет вызываться в нашем скрипте python (имя которого здесь play):

cpdef play(audData, rate, windowSize, hopSize):

cpdef заявляет, что следующая функция будет содержать код и типы как C, так и Python. Дополнительные параметры: def, как в обычном Python, на случай, если мы хотим напрямую вызвать функцию Python, и cdef, который используется для объявления чистой функции C.

Вторая важная концепция - передать все аргументы основной функции stft C, как показано на рис.

В принципе, memoryviews можно легко передать функции C, и они будут обрабатываться как одномерные массивы. Во-первых, чтобы создать представление памяти, необходимо определить тип и размер. Строки 2 и 6 рис. 8 показано, как создать одномерное представление памяти: cdef double[:] name_of_memory_view = content где [:] определяет одномерное представление памяти. Наконец, memoryviews можно передать функции C как &name_of_memory_view[0], как это сделано в строке 8 для &audData_view[0] и &magnitude[0]. В следующих уроках я покажу вам более подробно, как работать с двумерными представлениями памяти и передавать их в C, поскольку это имеет дело с буфером памяти. Стоит заметить, что в строке 8 &magnitude[0] передается в C как вектор нулей. Это позволяет работать с уже инициализированным указателем в C и заполнять их значениями STFT.

Код C

Код: https://github.com/Steboss/music_retrieval/blob/master/stft/c_code/stft.c

Первым делом нужно импортировать необходимые библиотеки:

fftw3 - самая важная библиотека, которая позволяет вычислять быстрое преобразование Фурье, а также косинусное преобразование и так далее.

Во-вторых, нам нужно определить некоторую константу, такую ​​как значение pi и оконную функцию Хэмминга, для создания значений окна Хэмминга. Это легко сделать так:

На этом этапе мы можем начать играть с fftw3. Как показано на рис. 11, нам нужно создать массив fftw_complex для входных данных stft_data, размер которого равен windowSize; выходной массив fft_result с теми же размерами stft_data для хранения оконного сигнала преобразования Фурье и, наконец, storage массив для хранения всех преобразованных окон вместе, размер которого составляет samples. Чтобы позволить fftw вычислить преобразование Фурье для оконного входного сигнала, нам нужен объект плана Фурье, который создается в строке 17 следующим образом:

fftw_plan plan_forward;
plan_forward = fftw_plan_dft_1d(number_of_elements_in_output, 
                                input_data_to_be_transformed, 
                                output_transformed_data, 
                                flag_for_forward_transform, 
                                flag_for_inverse_transform);

Последний шаг - реализовать настоящий алгоритм STFT:

Рис. 12 поможет вам понять, как реализован STFT. Прежде всего, массив stft_data заполняется текущими значениями амплитуды сигнала размером с окно (строка 10). Затем этот сегмент преобразуется Фурье, вызывая fftw_execute(plan_forward). Действительно, fftw_execute запустит выполнение плана преобразования Фурье, который был определен выше, чтобы преобразовать текущий ввод и сохранить результат в массиве вывода fft_result. Наконец, половина выборок Фурье сохраняется в массиве storage, который будет возвращен Cython позже, а chunkPosition обновляется hop_size/2, так что можно перейти к следующему окну - вы помните частоту Найквиста?

На этом этапе можно заполнить массив magnitude, который был представлением памяти, созданным в Cython, сигналами преобразования Фурье из storage

Таким образом, значения величины будут перенесены из C в Cython. Затем в Cython можно манипулировать массивом magnitude для возврата массива numpy, который можно прочитать в Python:

Подготовьте setup.py и установите все

Это самый последний шаг перед тем, как приступить к анализу STFT. Чтобы установить пакет create Python в Cython, я обычно разделяю свои коды на две папки: c_code, где хранится stft.c, и installer, где я сохраняю файлы pyx и Python. В папке installer я создаю файл setup.py, который представляет собой файл Python для установки и компиляции кодов Cython и C. Код довольно прост:

В коде рис. 15, мы видим, что основными элементами для компиляции кода являются Extension и setup. Первые определяют имя текущего расширения stft.stft, список pyx файлов [stft.pyx], список библиотек и дополнительный аргумент, необходимый для компиляции кода C. В частности, в этом контексте нам нужно указать fftw3 и m в качестве дополнительных библиотек, где одна объявляет использование библиотеки быстрого преобразования Фурье, а вторая - математическую библиотеку (когда код C компилируется, обычно m добавляется как -lm и fftw3 as-lfftw3). Дополнительные аргументы гарантируют, что у нас есть O3 процесс оптимизации при компиляции C, а std=C99 должен сообщить компилятору, что мы используем стандартную версию C99. Наконец, setup хранит информацию о пакете, а именно имя последнего модуля, поэтому Python импортирует текущие модули как import stft - контроль версий, расширения для C и любые директивы для Cython. По поводу директив Cython рекомендую взглянуть на руководство.

Когда код будет готов, мы можем запустить setup.py как:

python setup.py build_ext --inplace

build_ext - это команда для создания текущего расширения, а --inplace позволяет установить текущий пакет в рабочий каталог.

Забавный пример с STFT

Код: https://github.com/Steboss/music_retrieval/tree/master/stft/examples

Хорошим примером для изучения STFT является музыка Aphex Twin «Equation».

Если проанализировать спектрограмму между 300 и 350 с:

start = 300*rate 
end   = 350*rate 
channel1 = audData[:,0][start:end]

при установке windowSize=4096 и hopSize=4096 лицо демона будет обнаружено в музыкальной спектрограмме:

Надеюсь, вам понравится это первое знакомство с Cython :) Не стесняйтесь присылать мне электронное письмо с вопросами или комментариями по адресу: [email protected]

Библиография

  1. Https://onlinelibrary.wiley.com/doi/full/10.1002/jmri.1160
  2. Https://www.sciencedirect.com/science/article/abs/pii/S0010482516302104
  3. Https://www.scirp.org/html/4708.html
  4. Https://www.roe.ac.uk/japwww/teaching/fourier/compression.html
  5. Http://citeseerx.ist.psu.edu/viewdoc/download?doi=10.1.1.307.8732&rep=rep1&type=pdf