Многопоточная дешифровка и транскрибация записей телефонных переговоров системы NICE

В этой статье мы хотим познакомить читателей с опытом применения продукта от компании NICE Systems - системой NICE Interaction Management. Данная система обеспечивает тотальную запись разговоров, экранов и всех сопутствующих CTI-данных работы операторов контактных центров. По завершению разговора с оператором система позволяет клиенту дать обратную связь о качестве работы контактного центра.

Очень важной особенностью системы является наличие в её составе модуля транскрибации телефонных переговоров. Speech-to-text инструмент NICE’a имеет высокую точность, и к результатам его работы можно применять технологии text-mining’а, что позволяет аналитикам и аудиторам компании анализировать большие количества записей переговоров в автоматическом режиме.

Схема работы транскрибатора NICE

Speech-to-text модуль для конечного пользователя представлен в виде клиент-серверного приложения, который несет в себе следующий функционал – дешифровывает записи телефонных разговоров формата .nmf (внутренний формат записи системы), возвращает запись в уже всем привычном звуковом формате wave, и приложенный к ней файл с расширением .ass содержащий транскрибацию разговора в виде текста. Скорость работы модуля примерна равна один к четырем т. е. за минуту работы обрабатывается запись длительностью четыре минуты.

Клиентская часть сервиса – это консольное приложение NiceNmfDecoderConsole управление которым осуществляется при помощи bat-файла. Пример команды –

NiceNmfDecoderConsole.exe 000.000.000.000 in

Где:

-NiceNmfDecoderConsole.exe – путь к исполняемому файлу приложения-клиента (можно не полный, при условии, если bat-файл располагается в той же директории, что и исполняемый файл).

-000.000.000.000 – IP адрес сервера.

- in – путь к папке, содержащей файлы подлежащие дешифровке и транскрибации (в формате .nmf).

В результате запуска bat-файла, NiceNmfDecoderConsole будет поочередно пересылать файлы из папки in на серверную часть приложения для обработки. Ответы сервера (в виде файлов wave и текстовиков с транскрибацией) будут приходить на клиент, и сохраняться в папке in с такими же именами файлов, как был назван исходный файл, различаясь расширением.

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

В одном из проектов нашей команде нужно было проанализировать около тридцати тысяч записей телефонных переговоров из системы Nice. Для работы с таким массивом информации было принято решение немного автоматизировать работу этого приложения. Был разработан Python-скрипт для запуска этой программы в несколько потоков, код приведен ниже:

from os import listdir import subprocess from multiprocessing import Pool def gen_file(files): #Генератор путей к обрабатываемым файлам for file in files: yield file def convert(file): #Функция запуска приложения для обработки одного файла program = [path_exe, ip, path_data + file] process = subprocess.Popen(program) code = process.wait() with open('c:\\Users\\User\\NiceNmfDecoderConsole\\Log_file.txt', 'a') as logfile: logfile.write(file + " " + str(code) + '\n') process.terminate() return path_exe = 'c:\\Users\\User\\NiceNmfDecoderConsole\\NiceNmfDecoderConsole.exe' path_data = 'c: :\\Users\\User\\\NiceNmfDecoderConsole\\IN\\' ip = 'xxx.xxx.xxx.xxx' files = listdir(path_data) files = list(filter(lambda x: x.endswith('.nmf'), files)) file = gen_file(files) threads = 10 #Количество потоков if __name__ == '__main__': with Pool(threads) as p: p.map(convert, file)

Скрипт сканирует папку, путь к которой задается переменной path_data, составляет список файлов с расширением .nmf, после чего запускает NiceNmfDecoderConsole в десять потоков (количество потоков определяется переменной threads). Как только хоть один поток завершит работу, скрипт запишет в лог-файл имя файла над которым работал поток и код завершения (где 0 – успешно). По завершению потоков скрипт будет запускать новые, для последующих файлов в списке, постоянно поддерживая параллельную работу десяти, пока не закончится список файлов.

Таким образом можно обработать большое количество аудиозаписей системы Nice со значительно увеличенной производительностью и небольшой трудоемкостью для конкретного пользователя. Кроме того, работа аналитика в этом случае не будет сильно зависеть от сбоев и обрыва связи с сервером. Надеемся, что информация данная в этой статье будет полезна для Вас!

0
9 комментариев
Написать комментарий...
Yehor Smoliakov

Какую модель Kaldi вы используете, публичная/приватная?

Где можно послушать и посмотреть демо от распознавания?

Ответить
Развернуть ветку
NTA
Автор

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

Ответить
Развернуть ветку
Yehor Smoliakov

Приватность это хорошо, но как оценить качество вашей модели не подписывая NDA ?

Ответить
Развернуть ветку
NTA
Автор

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

Ответить
Развернуть ветку
Max B

А вот разрешите до кода докопаться! )

files = listdir(path_data)
files = list(filter(lambda x: x.endswith('.nmf'), files))
file = gen_file(files)

1. Зачем вы используете генератор? Ведь в вашем случае он просто дергает элементы из явно построенного списка.

with open('c:\\Users\\User\\NiceNmfDecoderConsole\\Log_file.txt',...

2. Разумно ли открывать и закрывать один и тот же лог-файл в параллельных процессах? Есть же модуль logging.

Ответить
Развернуть ветку
NTA
Автор

Генератор использован в связи с тем, что метод map() класса Pool вторым аргументом не принимает list, только итератор. Приходится итерировать либо при помощи file = iter(files), или с помощью def/yield, как в примере. По поводу модуля logging дельное замечание, если скрипт понадобится повторно, стоит доработать.

Ответить
Развернуть ветку
Max B

Нет, вы не правы. Аннотаций типов там нет, но второй аргумент метода Pool.map() называется iterable. Это обычно означает "итерируемый", а не итератор. Обычно iterator = iterable.__iter__(), но это, естественно, не гарантируется языком, только соглашением.
А самое интересное дальше. Вот что происходит с "итератором" в реализации класса Pool. Так что список можно )

Ответить
Развернуть ветку
NTA
Автор

Обратились к документации, перечитали – правда ваша, запустили код:

from multiprocessing import Pool
my_print(arg):
print(arg)
return
l = [1, 2, 3]
if __name__ == '__main__':
with Pool(2) as p:
p.map(my_print, l)

Принты отрабатывают, как предполагается.

При попытке запуска обсуждаемого кода (c subprocess’ом, который в свою очередь вызывает nice), с передачей в Pool.map списка путей скрипт валится, def/yield лечит проблему, оО. До возникновения этого спора навскидку решили, что дело в том, что map внутри вызывает метод next(), сейчас же, после опыта, описанного выше, сложно найти этому объяснения, может дело в большой длине списка и «ленивости» генератора…

Ваши мысли?

Ответить
Развернуть ветку
Max B

Трудно рассуждать, не имея возможности воспроизвести ситуацию, и не видя stacktrace. Но я бы не связывал это с генератором, хотя его использование может обеспечить простую задержку из-за цепочки превращений список-генератор-список. На "ленивость" не похоже. Первое обращение к iterable в Pool.map() - это вызов len(). Так что из генератора сразу же достанут все элементы.

Ответить
Развернуть ветку
6 комментариев
Раскрывать всегда