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

Модель

Я следил за отличной статьей Дэвида Швертфегера об обработке аудио с помощью Tensorflow и пытался воспроизвести ее с помощью Tensorflow JS.



Кроме того, Google любезно включил модель речевых команд в примеры Tensorflow JS, и на основе этих двух источников я придумал следующую модель для своего эксперимента:

_________________________________________________________________
Layer (type)                 Output shape              Param #   
=================================================================
input1 (InputLayer)          [null,17640]              0
_________________________________________________________________
stftLayer (StftLayer)        [null,87,512,1]           0
_________________________________________________________________
conv2d_layer1 (Conv2D)       [null,87,512,8]           72
_________________________________________________________________
max_pooling2d_MaxPooling2D1  [null,43,256,8]           0
_________________________________________________________________
conv2d_layer2 (Conv2D)       [null,43,256,32]          2080
_________________________________________________________________
max_pooling2d_MaxPooling2D2  [null,21,128,32]          0
_________________________________________________________________
conv2d_layer3 (Conv2D)       [null,21,128,32]          8224
_________________________________________________________________
max_pooling2d_MaxPooling2D3  [null,10,64,32]           0
_________________________________________________________________
flatten_Flatten1 (Flatten)   [null,20480]              0
_________________________________________________________________
dense_Dense1 (Dense)         [null,410]                8397210
_________________________________________________________________
dropout_Dropout1 (Dropout)   [null,410]                0
_________________________________________________________________
dense_Dense2 (Dense)         [null,82]                 33702
_________________________________________________________________
dropout_Dropout2 (Dropout)   [null,82]                 0
_________________________________________________________________
dense_Dense3 (Dense)         [null,41]                 3403
=================================================================
Total params: 8444691
Trainable params: 8444691
Non-trainable params: 0
_________________________________________________________________

˛Это довольно простой ванильный подход:

  • Занимает 17640 аудиосэмплов. Ожидается монофонический звук с частотой дискретизации 44100 Гц.
  • Генерирует спектр образца. Вы можете заметить, что у меня есть собственный слой, который использует tf.signal.stft для вычисления преобразования Фурье образца. Он сгенерирует 512 бинов частот и пройдет через всю выборку. Окно Хэмминга не применяется.
  • Лучше всего работать со спектрограммами Log-Mel. Для простоты я этого не делаю.
  • 3 пары слоев двумерных сверточных слоев с двухмерным максимальным объединением для преобразования данных спектрограмм
  • Сгладить, чтобы все было красиво выстроено
  • 2 пары плотных (с активацией ReLU) и выпадающих слоев для распознавания признаков
  • Последний плотный слой с сигмовидной активацией предназначен для определения 41 различных признаков (звуков), которые я ищу.

Реальные параметры слоев CNN, max pooling и dropout сейчас не важны, но вы можете проверить это здесь.

Концепция визуализации

Основная идея в том, что модель внутри работает на тензорах с 4 измерениями:

  1. Идентификатор элемента в пакете. Для этой демонстрации это всегда 1, так как выводится только один образец.
  2. Время. Спектрограммы сгенерированных образцов одна за другой.
  3. Частотный бин. Это следует непосредственно из анализа Фурье.
  4. Канал. Мы используем моноканалы, так что это будет один для первого слоя. Conv2D генерирует каналы во время своей работы (столько же, сколько вы указываете в свойстве «filters».

Значением в матрице является интенсивность звука в этом канале, в этом частотном бине, в то время в элементе пакета, который находится в процессе вывода.

Что мы делаем, так это то, что мы можем создать изображение (изображения) этого 4D-тензора: две оси будут временем и частотой, интенсивность будет закодирована цветом (шкала серого). Каналы будут представлены в виде отдельных картинок.

Важно, что изображения, показанные здесь, не созданы с исходной моделью. Я обучил эту сеть примерно на 8000 выборках с 25 эпохами, и результаты показывают влияние параметров/весов/смещений/и т. д., полученных в процессе обучения.

Визуализация потока данных

Спектрограмма

Начнем с формы волны. Это большая часть венгерского слова «büszkék» (что означает «гордый»).

Первый уровень — это кратковременное преобразование Фурье:

По ссылке с academo.org видно, что трансформация правильная, «полосы» на нижних частотах присутствуют, невооруженным глазом хорошо видны как минимум 3 области.

Извилины

На втором слое мы начинаем с двумерной свертки. Обратите внимание, что параметры свертки являются ключом преобразования, поэтому эти результаты имеют смысл только в том случае, если вы работаете с обученной моделью. Давайте посмотрим на необработанные результаты:

Я изменю это, чтобы сделать его более удобным для глаз в Paint.NET:

Интересные моменты:

  • Только один фильтр (А2) учитывает все спектральные элементы.
  • B1 и B3 практически пусты
  • Похоже, фильтры ищут разные уровни интенсивности в следующем порядке: A2-A1-A4-B4-A3. B2 не подходит под это предположение, но я полагаю, это связано со случайностью алгоритма.

Давайте перейдем к третьему уровню, максимальному объединению результатов:

И снова с более удобной для глаз откорректированной версией:

Здесь мы имеем уменьшенную версию изображений с усредненными пикселями. Ничего интересного.

Четвертый уровень, CNN использует 32 фильтра, таким образом создавая 32 изображения:

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

Интересные моменты:

  • Мы видим нечто похожее на инверсные пары: C3-D3, A7-D7. Мы могли бы быть уверены, если бы исследовали сами фильтры, но это не наша тема.
  • Единственная явно пустая ячейка — это D4, мы не видим в этом образце никаких сигналов, которые фильтр считает значимыми.

Максимальное объединение в пятом слое снова сжимает изображения:

Это немного темно, давайте иметь приятную для глаз версию:

шестой уровень — это последний CNN. Изображения довольно маленькие, поэтому для простоты я покажу только удобную для глаз версию:

Мы приближаемся! Некоторые фильтры по-прежнему напоминают исходное изображение, другие подчеркивают различные аспекты спектрального изображения.

Седьмой уровень снова является максимальным пулом:

Как видите, все свертки с использованием фильтров и сжатием частотных интервалов по оси времени приводят к появлению нескольких «точек», обозначающих интенсивность в это особое время и частотный диапазон. Это напоминает мне шрифт Брайля, правда.

Плотные слои

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

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

Помните: мы начали с 17 640 сэмплов, а после создания CNN у нас осталось 20 480 для подачи плотных слоев. Давай посмотрим что происходит:

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

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

Это то, что вы обычно видите на выходе модели: массив, содержащий числа от 0 до 1. Они соответствуют звукам, которые мы заставили их выучить. Мы можем считать 0,75 хорошим уровнем достоверности для надежной идентификации.

В нашем случае модель правильно идентифицировала звуки [é, sz] (темно-зеленый), [k, ü] выше порога 0,75 (светло-зеленый) и пропустила звук [b] (красноватый, оранжеватый). ). Очевидно, потребуется дополнительная подготовка.

Кодирование

В отличие от мейнстрима, я использую Tensorflow JS для своих экспериментов с машинным обучением, запускаю серверную часть Node.JS и программирую на TypeScript. (Причина проста: я не хочу изучать еще одну экосистему языков программирования, чтобы поиграть с ней.)

Получить изображение должно быть довольно просто: существует callHook, который можно установить на каждом уровне с помощью функции setCallHook(). К сожалению, стандартные слои не используют это (по крайней мере, те, с которыми я работал), и в исходном коде упоминается, что это только для тестирования.

Поэтому нам нужно просто сделать то, что делает model.predict(): вызвать каждый слой с выводом предыдущего в цикле с помощью layer.call():

Хитрость заключается в использовании функции Tensor.unstack(), которая возвращает тензоры с dim-1 в массиве. Для написания PNG вы можете использовать библиотеку pngjs.

Так что это довольно просто, если вы хотите увидеть процесс самостоятельно.

Надеюсь, вам понравилась эта поездка. Если у вас есть какие-либо вопросы, не стесняйтесь задавать их в разделе комментариев.