Здравствуйте, друзья-человеки. В этой серии статей мы собираемся раскрыть таинственный мир систем распознавания речи и использовать сервисы Deepgram в этом контексте. Многих может заинтересовать эта тема на том основании, что многие голосовые помощники, такие как Amazon’s Alexa, Google’s Assistant, Apple’s Siri, соревнуются за право быстро стать доминирующей умной колонкой, используя различные типы глубоких нейронных сетей (сети с прямой и обратной связью). Глубокие нейронные сети были представлены в 2006 году [0] самим крестным отцом: Джеффри Хинтон [1]
? Оглавление (TOC).
- Что такое речь?
- Что такое распознавание речи?
- История распознавания речи
- Что такое Deepgram?
- Уникальные особенности Deepgram
- Распознавание речи с живого микрофона
- Установите deepgram-sdk, pyaudio
- Устройства ввода и вывода
- Аудиозапись и волновые файлы
- Эксперименты
- Собираем все вместе
- Функциональное программирование
- Объектно-ориентированный подход
- Deepgram python sdk
- Соединение Pyaudio и Deepgram
- Обработка исключений
- Обертывание
- Ссылка
Что такое речь?
? Перейти к TOC.
Человеческий голос — это физическое явление, которое мы не можем увидеть. Форма задней стенки глотки и ее вибрация используются для создания звука речи [2]. Когда микрофон улавливает звуки, он преобразует их в электрический сигнал, который может быть передан по проводному или беспроводному соединению в программное обеспечение на компьютере, динамики или устройство распознавания голоса. Мозг инициирует речь, приводя в действие мышцы рта для производства звука [3]. Например, когда человек произносит слово «Привет», он артикулирует его губами и языком, в то время как его голосовые связки вибрируют и воздух проходит между ними.
Что такое распознавание речи?
? Go To TOC.
Распознавание речи — это процесс преобразования произнесенных слов в текст. В некоторых случаях он может использоваться в сочетании с другими технологиями для обеспечения компьютерного ввода или замены клавиатуры и мыши. Эта технология существует с 1950-х годов, но за последние годы она значительно усовершенствовалась. Распознавание речи использует методы DSP (цифровой обработки сигналов) для обработки и анализа аудиосигналов [4].
Распознавание речи часто используется как отдельное приложение или часть большого программного пакета, включающего другие функции, такие как диктовка. Оно позволяет пользователю управлять компьютером или другим устройством с помощью речи. Она также известна под различными терминами, такими как распознавание голоса, преобразование голоса в текст, преобразование речи в текст или распознавание речи,
Кратко ознакомившись с распознаванием речи, теперь давайте взглянем на захватывающую историю распознавания речи, которая, как ни удивительно, насчитывает около 72 лет, начиная с 1950-х годов, как упоминалось выше.
История распознавания речи
? Go To TOC.
В первом десятилетии пятидесятого века ученые компании Bell System создали машину Audrey (Automatic Digit Recognizer), которая состоит из трех основных компонентов:
- Микрофон, улавливающий человеческую речь.
- Аппаратное обеспечение, запрограммированное на выполнение транскрипции.
- Дисплей, на котором отображается число, произносимое в микрофон (правая сторона изображения).
Как следует из названия, эта машина может распознавать цифры (0-9).
В 1962 году IBM выпустила первое устройство под названием Shoebox [7] для распознавания устных слов; оно может распознавать десять цифр и шесть арифметических слов (например, плюс, минус и т.д.). Например, если кто-то скажет через микрофон 2 плюс 2, Shoebox запустит функцию сложения для вычисления и отображения результата.
В те времена эти технологии работали путем преобразования голосовых сигналов в электрические импульсы, а затем каждое слово разбивалось на небольшие фонетические единицы. Например, слово «привет» делилось на «привет», «хе л о» или что-то в этом роде.
В 1970-х годах Министерство обороны США оказало финансовую поддержку исследованиям. DARPA (Defense Advanced Research Projects Agency, то самое агентство, которое якобы было разоблачено за содействие биологическим экспериментам, связанным с s@rs-c0v2 [8]. Черт, чувак. Все эти теории заговора все время были правдой.) финансировала один из самых значительных проектов по распознаванию речи. В результате было распознано более тысячи слов.
В 1982 году синтезатор SAM [9] стал первой коммерческой программой синтеза речи, озвучивающей компьютер Commodore 64 1982 года.
Значительная веха была достигнута в конце 1980-х годов, когда были представлены модели, основанные на статистике (например, Hidden Markov Model.), которые могут распознавать около пяти тысяч слов.
Она работает путем присвоения каждой букве узла с вероятностью предсказания следующей буквы в слове, которое представляет собой край. Как видно из приведенного ниже примера, слово «картофель» может произноситься по-разному, например, «p oh t ah t oh», «p ah t ay t oh» и др.
Недостатком этих алгоритмов является то, что они распознают только дискретную речь, поэтому вы не можете говорить естественно; вам нужно делать паузы между словами, что вызывает сожаление.
В 1990-х годах первый коммерческий продукт стал доступен широким массам, когда компания Dragon выпустила свой продукт под названием Dragon Dictate, способный распознавать около 60 тыс. слов.
В 2000-х годах компания Google выпустила приложение голосового поиска для iPhone [12]. Приложение обрабатывает голосовые запросы на основе облачного центра данных Google, сопоставляя их с большим пулом записей человеческой речи и обучаясь на основе запросов, собранных от пользователей (230 миллиардов слов), обученных нейронными сетями, которые появились в 2006 году, как упоминалось в начале статьи.
Думаю, на сегодня достаточно истории, которая, предположительно, будет продолжена в будущих статьях о распознавании речи. Теперь давайте перейдем к следующему разделу, посвященному изучению сервисов транскрипции Deepgram.
Что такое Deepgram?
? Go To TOC.
Deepgram — это новый перспективный инструмент транскрипции на основе искусственного интеллекта, который использует алгоритмы глубокого обучения и машинного обучения для расшифровки аудиозаписей путем обнаружения слов и фраз, встречающихся в записи. Проще говоря, это сервис распознавания голоса, который принимает записи и преобразует их в текст. Но это гораздо больше, чем просто сервис.
Очевидно, что Deepgram имеет множество вариантов использования. Например, его можно использовать как сервис транскрипции встреч и телефонных звонков, как сервис преобразования речи в текст для видео или как автоматический транскрипт для аудиофайлов. Подробную информацию можно найти на сайте компании [13].
Уникальные возможности Deepgram
? Go To TOC.
Было показано [15], что Deepgram обеспечивает значительно более высокие показатели точности (90%+), чем другие системы перевода. Кроме того, он обеспечивает гораздо более высокую скорость транскрибирования, чем другие системы (3 секунды на транскрибирование часовых записей), и более низкую стоимость (0,78$/час), что делает его привлекательным вариантом для предприятий, которым необходимо регулярно транскрибировать большие объемы контента.
На момент написания статьи этот сервис поддерживает большинство языков с большим разнообразием акцентов и диалектов, он может распознавать и транскрибировать аудио на 16 языках [16].
Самое приятное в Deepgram то, что он предлагает бесплатную пробную версию, которой может воспользоваться любой желающий. Более того, Deepgram предоставляет SDK с открытым исходным кодом и бесплатные инструменты распознавания речи, которые могут быть интегрированы в любое приложение или систему.
С помощью Deepgram нам не придется изобретать колесо и строить модель машинного обучения снизу вверх (это был бы фантастический проект, над которым мы могли бы поработать в будущем). Вместо этого мы будем использовать Python SDK, который позволяет нам взаимодействовать с различными конечными точками API Deepgram, использующими современную модель машинного обучения для транскрипции речи.
По сути, услуги транскрипции Deepgram просты в использовании, точны и быстры. Они помогут вам сэкономить время, деньги и ресурсы, обеспечивая при этом высокое качество контента.
Теперь перейдем к техническим аспектам.
Распознавание речи с живого микрофона
? Go To TOC.
В этом разделе мы узнаем, как преобразовать речь в реальном времени в человекочитаемый текст. Для этого мы будем использовать deepgram-SDK вместе с пакетом PyAudio.
Установите deepgram-sdk, pyaudio
? Go To TOC.
В Python есть удобный встроенный модуль wave
, но он не поддерживает запись, только обработку аудиофайлов на лету. Для записи аудиоданных мы можем воспользоваться сторонним пакетом PyAudio. Официальный сайт является хорошей отправной точкой в том, как установить и использовать эту библиотеку на различных платформах.
Однако PyAudio зависит от другой библиотеки под названием portaudio
, которая не входит в стандартные зависимости Linux. Чтобы установить ее на вашу машину, вам нужно выполнить следующую команду в терминале:
$ sudo apt-get install portaudio19-dev
Если приведенная выше команда выполнена успешно, вы можете загрузить и установить pyaudio
на свою систему. Однако, поскольку ранее мы использовали поэлементно вместо pip для управления зависимостями, мы можем выполнить следующую команду для импорта PyAudio в наш проект:
$ poetry add pyaudio
Если установка прошла успешно, вы можете посмотреть версию portaudio, выполнив команду:
$ python3 -c 'import pyaudio as p; print(p.get_portaudio_version())'
1246720
Чтобы установить deepgram на свою машину, вы можете следовать их GitHub-репо. Аналогично, чтобы импортировать deepgram в наш проект с поэзией, просто выполните:
$ poetry add deepgram-sdk
Если установка прошла успешно, вы можете посмотреть версию deepgram, выполнив:
$ python3 -c 'import deepgram; print(deepgram._version.__version__)'
0.2.5
Теперь пришло время поиграть с этими модулями. Для этого убедитесь, что ваш микрофон включен по умолчанию и не отключен.
Устройства ввода и вывода
? Go To TOC.
Теперь давайте откроем REPL и проверим все на практике.
Мы начнем с импорта модуля pyaudio
и последующего инстанцирования класса PyAudio.
>>> import pyaudio
>>> py_audio = pyaudio.PyAudio()
Если вы работаете в linux, вы можете столкнуться со следующими предупреждениями:
ALSA lib pcm_dmix.c:1089:(snd_pcm_dmix_open) unable to open slave
ALSA lib pcm.c:2642:(snd_pcm_open_noupdate) Unknown PCM cards.pcm.rear
ALSA lib pcm.c:2642:(snd_pcm_open_noupdate) Unknown PCM cards.pcm.center_lfe
ALSA lib pcm.c:2642:(snd_pcm_open_noupdate) Unknown PCM cards.pcm.side
ALSA lib pcm_route.c:869:(find_matching_chmap) Found no matching channel map
ALSA lib pcm_oss.c:377:(_snd_pcm_oss_open) Unknown field port
ALSA lib pcm_oss.c:377:(_snd_pcm_oss_open) Unknown field port
ALSA lib pcm_usb_stream.c:486:(_snd_pcm_usb_stream_open) Invalid type for card
ALSA lib pcm_usb_stream.c:486:(_snd_pcm_usb_stream_open) Invalid type for card
ALSA lib pcm_dmix.c:1089:(snd_pcm_dmix_open) unable to open slave
Давайте пока проигнорируем эти предупреждения.
py_audio
имеет множество ценных атрибутов, которые можно использовать для получения информации о ваших устройствах ввода и вывода.
>>> for attr in dir(py_audio):
... if not attr.startswith("_"):
... print(attr)
...
close
get_default_host_api_info
get_default_input_device_info
get_default_output_device_info
get_device_count
get_device_info_by_host_api_device_index
get_device_info_by_index
get_format_from_width
get_host_api_count
get_host_api_info_by_index
get_host_api_info_by_type
get_sample_size
is_format_supported
open
terminate
Например, чтобы получить информацию об устройстве ввода по умолчанию, вы можете вызвать следующий метод:
>>> py_audio.get_default_input_device_info()
{
'index': 9,
'structVersion': 2,
'name': 'default',
'hostApi': 0,
'maxInputChannels': 32,
'maxOutputChannels': 32,
'defaultLowInputLatency': 0.008684807256235827,
'defaultLowOutputLatency': 0.008684807256235827,
'defaultHighInputLatency': 0.034807256235827665,
'defaultHighOutputLatency': 0.034807256235827665,
'defaultSampleRate': 44100.0
}
Помните о значении ключа defaultSampleRate
. Мы будем использовать ее при записи звука с микрофона.
Аналогично, чтобы получить информацию об устройстве ввода по умолчанию, вы можете вызвать следующий метод:
>>> py_audio.get_default_output_device_info()
{
'index': 9,
'structVersion': 2,
'name': 'default',
'hostApi': 0,
'maxInputChannels': 32,
'maxOutputChannels': 32,
'defaultLowInputLatency': 0.008684807256235827,
'defaultLowOutputLatency': 0.008684807256235827,
'defaultHighInputLatency': 0.034807256235827665,
'defaultHighOutputLatency': 0.034807256235827665,
'defaultSampleRate': 44100.0
}
Если вы хотите получить подробную информацию о каждом устройстве ввода/вывода на вашей машине, вы можете выполнить следующий код:
>>> for index in range(py_audio.get_device_count()):
... device_info = py_audio.get_device_info_by_index(index)
... for key, value in device_info.items():
... print(key, value, sep=": ")
Аудиозапись и волновые файлы
? Go To TOC.
Эксперименты
Для записи аудиоданных с микрофона необходимо вызвать метод open
:
>>> from rich import inspect
>>> inspect(py_audio.open)
╭─ <bound method PyAudio.open of <pyaudio.PyAudio object at 0x7f6c8bed5180>> ─╮
│ def PyAudio.open(*args, **kwargs): │
│ │
│ Open a new stream. See constructor for │
│ :py:func:`Stream.__init__` for parameter details. │
│ │
│ 27 attribute(s) not shown. Run inspect(inspect) for options. │
╰─────────────────────────────────────────────────────────────────────────────╯
Мы собираемся использовать rich для правильного отображения сообщений. Теперь создадим объект stream для записи:
>>> # open stream object as input & output
>>> audio_stream = py_audio.open(
rate=44100, # frames per second,
channels=1, # mono, change to 2 if you want stereo
format=pyaudio.paInt16, # sample format, 8 bytes. see inspect
input=True, # input device flag
output=False, # output device flag, if True, you can play back the audio.
frames_per_buffer=1024 # 1024 samples per frame
)
Теперь вы можете посмотреть на доступные атрибуты для этого объекта потока.
>>> for attr in dir(audio_stream):
... if not attr.startswith("_"):
... print(attr)
...
close
get_cpu_load
get_input_latency
get_output_latency
get_read_available
get_time
get_write_available
is_active
is_stopped
read
start_stream
stop_stream
write
Функции read
и write
являются наиболее полезными функциями для этого учебника. Мы можем вызвать функцию read
для записи образцов звука в терминах кадров.
>>> inspect(audio_stream.read)
╭─ <bound method Stream.read of <pyaudio.Stream object at 0x7f8310a41180>> ─╮
│ def Stream.read(num_frames, exception_on_overflow=True): │
│ │
│ Read samples from the stream. Do not call when using │
│ *non-blocking* mode. │
│ │
│ 27 attribute(s) not shown. Run inspect(inspect) for options. │
╰───────────────────────────────────────────────────────────────────────────╯
Очевидно, что метод read
принимает количество кадров вместо длительности. Поэтому нам нужно преобразовать длительность — заданный период времени для записи данных — в число кадров. Для этого нужно найти, сколько кадров содержится в данной duration
. Для этого используется следующая формула:
num_frames = int(rate / samples_per_frame * duration)
Мы можем убедиться в правильности приведенной выше формулы с помощью анализа размерности:
единица измерения для:
- скорость: сэмплы/секунду
- сэмплы_на_кадр: сэмплы/кадр
- длительность: секунда
Значение в левой части уравнения num_frames
должно иметь единицу измерения в кадрах, что и имеет место в нашей формуле, если вы выполните вычисления. Теперь мы можем перебрать все кадры и считать 1024 выборки за кадр. Функция int
была использована для округления результата до ближайшего целого числа.
>>> frames = []
>>> for _ in range(int(44100 / 1024 * 3)):
... data = audio_stream.read(1024)
... frames.append(data)
...
>>> len(frames)
129
Каждый добавляемый кадр представляет собой поток байтов:
>>> type(frames[0])
<class 'bytes'>
Теперь давайте сохраним этот объект в wav-файл, чтобы убедиться, что это действительно запись длительностью 3 секунды. Для этого импортируем встроенный модуль wave
:
>>> import wave
Посмотрим, какие атрибуты доступны для этого объекта:
>>> for attr in dir(wave):
... if not attr.startswith("_"):
... print(attr)
...
Chunk
Error
WAVE_FORMAT_PCM
Wave_read
Wave_write
audioop
builtins
namedtuple
open
struct
sys
Как вы уже догадались, мы собираемся использовать функцию open
для открытия файла в режиме записи.
>>> wave_file = wave.open("sound.wav", "wb")
Аналогично, давайте посмотрим все атрибуты этого объекта:
>>> for attr in dir(wave_file):
... if not attr.startswith("_"):
... print(attr)
...
close
getcompname
getcomptype
getframerate
getmark
getmarkers
getnchannels
getnframes
getparams
getsampwidth
initfp
setcomptype
setframerate
setmark
setnchannels
setnframes
setparams
setsampwidth
tell
writeframes
writeframesraw
Так как мы собираемся писать в файл, то мы должны использовать либо writeframes
, либо writefranmesraw
. Обратитесь к официальной документации. Вы поймете, что функция writeframes
имеет больше логики, чем writeframesraw
, потому что она проверяет наличие нескольких фреймов записи в файле. Поэтому в данном учебнике мы будем использовать эту функцию.
Но сначала нам нужно задать некоторые параметры для объекта wave_file
:
>>> wave_file.setnchannels(2)
>>> wave_file.setsampwidth(py_audio.get_sample_size(pyaudio.paInt16))
>>> wave_file.setframerate(44100)
Теперь все готово, можно записывать поток данных в файл:
>>> wave_file.writeframes(b"".join(frames))
>>> wave_file.close()
Поэкспериментировав с модулями wave и pyaudio, давайте соберем все вместе.
Собираем все вместе
? Go To TOC.
Есть два подхода к компоновке предыдущего кода: функциональное программирование или объектно-ориентированное программирование.
Функциональное программирование
? Перейти к ТОС.
import wave
from typing import List, Optional, TypeVar, Union, IO
import pyaudio # type: ignore
WaveWrite = TypeVar("WaveWrite", bound=wave.Wave_write)
def init_recording(
file_name: Union[str, IO[bytes]] = "sound.wav", mode: Optional[str] = "wb"
) -> WaveWrite:
wave_file = wave.open(file_name, mode)
wave_file.setnchannels(2)
wave_file.setsampwidth(2)
wave_file.setframerate(44100)
return wave_file
def record(wave_file: WaveWrite, duration: Optional[int] = 3) -> None:
py_audio = pyaudio.PyAudio()
audio_stream = py_audio.open(
rate=44100, # frames per second,
channels=2, # stereo, change to 1 if you want mono
format=8, # sample format, 8 bytes. see inspect
input=True, # input device flag
frames_per_buffer=1024, # 1024 samples per frame
)
frames = []
for _ in range(int(44100 / 1024 * 3)):
data = audio_stream.read(1024)
frames.append(data)
wave_file.writeframes(b"".join(frames))
audio_stream.close()
if __name__ == "__main__":
wave_file = init_recording() # type: ignore
record(wave_file)
wave_file.close()
Объектно-ориентированный подход
? Перейти к ТОС.
Как описано в документах ниже, я предположил, что каждое поле класса AudioRecorder
по умолчанию является приватным и доступно только через геттеры и сеттеры. В python использование геттеров и сеттеров не является обязательным, но мне нравится использовать этот подход, поскольку я раньше писал на статически типизированных языках, в основном на c# и Java.
Обратите внимание на использование магического метода __attrs_post_init__
, который устанавливает атрибут wave_file
в момент инстанцирования после вызова __init__
. Я также использовал подсказку типов, как вы можете заметить. В python вы не обязаны делать все это, но все же это возможно. __init__
генерируется автоматически с помощью модуля atts
(обратите внимание, что каждый атрибут имеет метод define
).
Этот фрагмент кода был адаптирован из модуля audio_record
проекта deepwordle.
import os
import wave
from os import PathLike
from typing import IO, List, Optional, TypeVar, Union
import pyaudio # type: ignore
from attrs import define, field
WaveWrite = TypeVar("WaveWrite", bound=wave.Wave_write)
BASE_DIR = os.path.dirname(os.path.abspath(__file__))
@define
class AudioRecorder:
"""
A brief encapsulation of an audio recorder object attributes and methods.
All fields are assumed to be private by default, and only accessible through
getters/setters, but someone still could hack his/her way around it!
Attrs:
frames_per_buffer: An integer indicating the number of frames per buffer;
1024 frames/buffer by default.
audio_format: An integer that represents the number of bits per sample
stored as 16-bit signed int.
channels: An integer indicating how many channels a microphone has.
rate: An integer indicating how many samples per second: frequency.
py_audio: pyaudio instance.
data_stream: stream object to get data from microphone.
wave_file: wave class instance.
mode: file object mode.
file_name: file name to store audio data in it.
"""
_frames_per_buffer: int = field(init=True, default=1024)
_audio_format: int = field(init=True, default=pyaudio.paInt16)
_channels: int = field(init=True, default=1)
_rate: int = field(init=True, default=44100)
_py_audio: pyaudio.PyAudio = field(init=False, default=pyaudio.PyAudio())
_data_stream: IO[bytes] = field(init=False, default=None)
_wave_file: wave.Wave_write = field(init=False, default=None)
_mode: str = field(init=True, default="wb")
_file_name: Union[str, PathLike[str]] = field(init=True, default="sound.wav")
@property
def frames_per_buffer(self) -> int:
"""
A getter method that returns the value of the `frames_per_buffer` attribute.
:param self: Instance of the class.
:return: An integer that represents the value of the `frames_per_buffer` attribute.
"""
if not hasattr(self, "_frames_per_buffer"):
raise AttributeError(
f"Your {self.__class__.__name__!r} instance has no attribute named frames_per_buffer."
)
return self._frames_per_buffer
@frames_per_buffer.setter
def frames_per_buffer(self, value: int) -> None:
"""
A setter method that changes the value of the `frames_per_buffer` attribute.
:param value: An integer that represents the value of the `frames_per_buffer` attribute.
:return: NoReturn.
"""
setattr(self, "_frames_per_buffer", value)
@property
def audio_format(self) -> int:
"""
A getter method that returns the value of the `audio_format` attribute.
:param self: Instance of the class.
:return: A string that represents the value of the `audio_format` attribute.
"""
if not hasattr(self, "_audio_format"):
raise AttributeError(
f"Your {self.__class__.__name__!r} instance has no attribute named audio_format."
)
return self._audio_format
@audio_format.setter
def audio_format(self, value: int) -> None:
"""
A setter method that changes the value of the `audio_format` attribute.
:param value: An integer that represents the value of the `audio_format` attribute.
:return: NoReturn.
"""
setattr(self, "_frames_per_buffer", value)
@property
def channels(self) -> int:
"""
A getter method that returns the value of the `channels` attribute.
:param self: Instance of the class.
:return: An integer that represents the value of the `channels` attribute.
"""
if not hasattr(self, "_channels"):
raise AttributeError(
f"Your {self.__class__.__name__!r} instance has no attribute named channels."
)
return self._channels
@channels.setter
def channels(self, value: int) -> None:
"""
A setter method that changes the value of the `channels` attribute.
:param value: An integer that represents the value of the `channels` attribute.
:return: NoReturn.
"""
setattr(self, "_channels", value)
@property
def rate(self) -> int:
"""
A getter method that returns the value of the `rate`attribute.
:param self: Instance of the class.
:return: A string that represents the value of the `rate` attribute.
"""
if not hasattr(self, "_rate"):
raise AttributeError(
f"Your {self.__class__.__name__!r} instance has no attribute named rate."
)
return self._rate
@rate.setter
def rate(self, value: int) -> None:
"""
A setter method that changes the value of the `rate` attribute.
:param value: An integer that represents the value of the `rate` attribute.
:return: NoReturn.
"""
setattr(self, "_rate", value)
@property
def py_audio(self) -> pyaudio.PyAudio:
"""
A getter method that returns the value of the `py_audio`attribute.
:param self: Instance of the class.
:return: A PyAudio object that represents the value of the `py_audio` attribute.
"""
if not hasattr(self, "_py_audio"):
raise AttributeError(
f"Your {self.__class__.__name__!r} instance has no attribute named py_audio."
)
return self._py_audio
@py_audio.setter
def py_audio(self, value: int) -> None:
"""
A setter method that changes the value of the `py_audio` attribute.
:param value: A PyAudio object that represents the value of the `py_audio` attribute.
:return: NoReturn.
"""
setattr(self, "_py_audio", value)
@property
def data_stream(self) -> IO[bytes]:
"""
A getter method that returns the value of the `data_stream`attribute.
:param self: Instance of the class.
:return: A string that represents the value of the `data_stream` attribute.
"""
if not hasattr(self, "_data_stream"):
raise AttributeError(
f"Your {self.__class__.__name__!r} instance has no attribute named data_stream."
)
return self._data_stream
@data_stream.setter
def data_stream(self, value: IO[bytes]) -> None:
"""
A setter method that changes the value of the `data_stream` attribute.
:param value: A string that represents the value of the `data_stream` attribute.
:return: NoReturn.
"""
setattr(self, "_data_stream", value)
@property
def wave_file(self) -> wave.Wave_write:
"""
A getter method that returns the value of the `wave_file`attribute.
:param self: Instance of the class.
:return: A string that represents the value of the `wave_file` attribute.
"""
if not hasattr(self, "_wave_file"):
raise AttributeError(
f"Your {self.__class__.__name__!r} instance has no attribute named wave_file."
)
return self._wave_file
@wave_file.setter
def wave_file(self, value: wave.Wave_write) -> None:
"""
A setter method that changes the value of the `wave_file` attribute.
:param value: A string that represents the value of the `wave_file` attribute.
:return: NoReturn.
"""
setattr(self, "_wave_file", value)
@property
def file_name(self) -> Union[str, PathLike[str]]:
"""
A getter method that returns the value of the `file_name`attribute.
:param self: Instance of the class.
:return: A string that represents the value of the `file_name` attribute.
"""
if not hasattr(self, "_mode"):
raise AttributeError(
f"Your {self.__class__.__name__!r} instance has no attribute named file_name."
)
return self._file_name
@file_name.setter
def file_name(self, value: Union[str, PathLike[str]]) -> None:
"""
A setter method that changes the value of the `file_name` attribute.
:param value: A string that represents the value of the `file_name` attribute.
:return: NoReturn.
"""
setattr(self, "_file_name", value)
@property
def mode(self) -> str:
"""
A getter method that returns the value of the `mode`attribute.
:param self: Instance of the class.
:return: A string that represents the value of the `mode` attribute.
"""
if not hasattr(self, "_mode"):
raise AttributeError(
f"Your {self.__class__.__name__!r} instance has no attribute named mode."
)
return self._mode
@mode.setter
def mode(self, value: str) -> None:
"""
A setter method that changes the value of the `mode` attribute.
:param value: A string that represents the value of the `mode` attribute.
:return: NoReturn.
"""
setattr(self, "_mode", value)
def __repr__(self) -> str:
attrs: dict = {
"frames_per_buffer": self.frames_per_buffer,
"audio_format": self.audio_format,
"channels": self.channels,
"rate": self.rate,
"py_audio": repr(self.py_audio),
"data_stream": self.data_stream,
"wave_file": repr(self.wave_file),
"mode": self.mode,
"file_name": self.file_name,
}
return f"{self.__class__.__name__}({attrs})"
def __attrs_post_init__(self) -> None:
wave_file = wave.open(os.path.join(BASE_DIR, self.file_name), self.mode)
wave_file.setnchannels(self.channels)
wave_file.setsampwidth(self.py_audio.get_sample_size(self.audio_format))
wave_file.setframerate(self.rate)
self.wave_file = wave_file
del wave_file
def record(self, duration: int = 3) -> None:
self.data_stream = self.py_audio.open(
format=self.audio_format,
channels=self.channels,
rate=self.rate,
input=True,
output=True,
frames_per_buffer=self.frames_per_buffer,
)
frames: List[bytes] = []
num_frames: int = int(self.rate / self.frames_per_buffer * duration)
for _ in range(num_frames):
data = self.data_stream.read(self.frames_per_buffer)
frames.append(data)
self.wave_file.writeframes(b"".join(frames))
def stop_recording(self) -> None:
if self.data_stream:
self.data_stream.close()
self.py_audio.terminate()
self.wave_file.close()
if __name__ == "__main__":
rec = AudioRecorder()
print(rec)
rec.record()
rec.stop_recording()
Deepgram python sdk.
? Go To TOC.
Давайте вернемся в наш REPL и начнем играть с deepgram SDK.
Мы начнем с импорта модуля deepgram
, а затем создадим экземпляр Deepgram.
>>> from deepgram import Deepgram
>>> for attr in dir(Deepgram):
... if not attr.startswith("_"):
... print(attr)
...
keys
projects
transcription
usage
Как видите, в классе Deepgram
есть четыре основных атрибута. Используя deepgram, вы можете расшифровывать предварительно записанные аудиозаписи или прямые аудиопотоки, как на радио bbc. Вы можете следовать файлу Readme, чтобы получить информацию о настройке учетной записи deepgram и начать работу. Получив секретный ключ, вы сможете взаимодействовать с API для выполнения транскрипции. Получив ключ API, необходимо сохранить его в переменной окружения, чтобы успешно запустить следующий код:
$ export DEEPGRAM_API_KEY="XXXXXXXXX"
from deepgram import Deepgram # type: ignore
import asyncio
import os
from os import PathLike
from typing import Union, IO
async def transcribe(file_name: Union[Union[str, bytes, PathLike[str], PathLike[bytes]], int]):
with open(file_name, "rb") as audio:
source = {"buffer": audio, "mimetype": "audio/wav"}
response = await deepgram.transcription.prerecorded(source)
return response["results"]["channels"][0]["alternatives"][0]["words"]
if __name__ == "__main__":
try:
deepgram = Deepgram(os.environ.get("DEEPGRAM_API_KEY"))
loop = asyncio.new_event_loop()
asyncio.set_event_loop(loop)
words = loop.run_until_complete(transcribe("sound.wav"))
string_words = " ".join(word_dict.get("word") for word_dict in words if "word" in word_dict)
print(f"You said: {string_words}!")
loop.close()
except AttributeError:
print("Please provide a valid `DEEPGRAM_API_KEY`.")
Если аудиофайл содержит только слова «hello» и «world», то приведенный выше скрипт выдаст следующее:
You said: hello world!
Подключение Pyaudio и Deepgram
? Go To TOC.
import wave
from typing import List, Optional, TypeVar, Union, IO
import pyaudio # type: ignore
from deepgram import Deepgram # type: ignore
import asyncio
import os
from os import PathLike
WaveWrite = TypeVar("WaveWrite", bound=wave.Wave_write)
def init_recording(
file_name: Union[str, IO[bytes]] = "sound.wav", mode: Optional[str] = "wb"
) -> WaveWrite:
wave_file = wave.open(file_name, mode)
wave_file.setnchannels(2)
wave_file.setsampwidth(2)
wave_file.setframerate(44100)
return wave_file
def record(wave_file: WaveWrite, duration: Optional[int] = 3) -> None:
py_audio = pyaudio.PyAudio()
audio_stream = py_audio.open(
rate=44100, # frames per second,
channels=2, # stereo, change to 1 if you want mono
format=8, # sample format, 8 bytes. see inspect
input=True, # input device flag
frames_per_buffer=1024, # 1024 samples per frame
)
frames = []
for _ in range(int(44100 / 1024 * 3)):
data = audio_stream.read(1024)
frames.append(data)
wave_file.writeframes(b"".join(frames))
audio_stream.close()
async def transcribe(file_name: Union[Union[str, bytes, PathLike[str], PathLike[bytes]], int]):
with open(file_name, "rb") as audio:
source = {"buffer": audio, "mimetype": "audio/wav"}
response = await deepgram.transcription.prerecorded(source)
return response["results"]["channels"][0]["alternatives"][0]["words"]
if __name__ == "__main__":
# start recording
print("Python is listening...")
wave_file = init_recording() # type: ignore
record(wave_file)
wave_file.close()
# start transcribing
deepgram = Deepgram(os.environ.get("DEEPGRAM_API_KEY"))
loop = asyncio.new_event_loop()
asyncio.set_event_loop(loop)
words = loop.run_until_complete(transcribe("sound.wav"))
string_words = " ".join(word_dict.get("word") for word_dict in words if "word" in word_dict)
print(f"You said: {string_words}!")
loop.close()
Обработка исключений
? Go To TOC.
Теперь нам нужно обработать ошибки, чтобы сделать наше приложение более удобным для пользователя, используя блок try-catch для обработки ожидаемых исключений вместо того, чтобы вызвать аварийное завершение программы. Первая ошибка произойдет, когда ваш DEEPGRAM_API_KEY
будет неправильным, и это приведет к тому, что программа выбросит исключение Unauthorized.
try:
# start recording
print("Python is listening...")
wave_file = init_recording() # type: ignore
record(wave_file)
wave_file.close()
# start transcribing
deepgram = Deepgram(os.environ.get("DEEPGRAM_API_KEY"))
loop = asyncio.new_event_loop()
asyncio.set_event_loop(loop)
words = loop.run_until_complete(transcribe("sound.wav"))
string_words = " ".join(word_dict.get("word") for word_dict in words if "word" in word_dict)
print(f"You said: {string_words}!")
loop.close()
except Exception:
print("Unauthorized user. Please provide a valid `DEEPGRAM_API_KEY` value")
Мы можем построить цикл для бесконечной записи речи до тех пор, пока не будет выполнено условие.
try:
loop = asyncio.new_event_loop()
asyncio.set_event_loop(loop)
while True:
wave_file = init_recording() # type: ignore
print("Python is listening...")
record(wave_file)
wave_file.close()
# start transcribing
deepgram = Deepgram(os.environ.get("DEEPGRAM_API_KEY"))
words = loop.run_until_complete(transcribe("sound.wav"))
string_words = " ".join(word_dict.get("word") for word_dict in words if "word" in word_dict)
print(f"You said: {string_words}!")
if string_words == "stop":
print('Goodbye!')
break
loop.close()
except Exception:
print("Unauthorized user. Please provide a valid `DEEPGRAM_API_KEY` value")
Операции ввода-вывода ограничивают производительность этой программы. Мы улучшим эту версию в следующих статьях, связанных с распознаванием речи.
Подведение итогов
? Перейти к TOC.
В этой статье мы рассмотрели историю распознавания речи, узнали, как использовать deepgram python SDK для распознавания речи и pyaudio для записи звука. С помощью этих библиотек можно сделать гораздо больше, что выходит за рамки данной статьи. Помните, что мы можем улучшить наш проект, чтобы напрямую передавать аудиозаписи с микрофона без записи в волновой файл с помощью веб-сокетов, что является работой будущих статей. На основе этого мы также можем построить поисковую систему с голосовым управлением. Я хочу предложить поиграть с модулем webbrowser
, чтобы найти еще больше интересных идей реализации. Мы будем работать над подобными проектами на протяжении следующих статей этого цикла.
Как всегда, эта статья — подарок для вас, и вы можете поделиться ею с кем угодно или использовать ее любым способом, который будет полезен для вашего личного и профессионального развития. Заранее благодарю вас за поддержку!
Счастливого кодинга, друзья; увидимся в следующей статье.
Ссылка
? Go To TOC.
[0] Википедия. Глубокое обучение.
[1] Википедия. Джеффри Хинтон.
[2] William F. Katz, 2016. What Produces Speech: Анатомия вашей речи, Phonetics For Dummies.
[3] Jacquelyn Cafasso, 2019. Какая часть мозга управляет речью?, healthline.
[4] Steven W. Smith, in Digital Signal Processing: A Practical Guide for Engineers and Scientists, 2003.
[5] Сэм Лоусон, 2018, Bell-Laboratories-invented-Audrey, ClickZ.
[6] Pioneering Speech Recognition, IBM.
[7] IBM Cloud Education, 2020, Что такое распознавание речи.
[8] Project Veritas, 2022, Военные документы об усилении функции противоречат показаниям Фаучи под присягой, Youtube.
[9] Себастьян Макке, Программный автоматический рот — крошечный синтезатор речи, Github.
[10] Dimitrakakis, Christos & Bengio, Samy. (2011). Ансамбли на уровне фонем и предложений для распознавания речи EURASIP J. Audio, Speech and Music Processing. 2011. 10.1155/2011/426792.
[11] Эд Грабиановски, Как работает распознавание речи.
[12] Новости Google, 2008, Новая версия мобильного приложения Google для iPhone, теперь с голосовым поиском.
[13] Deepgram, Различные среды требуют различных моделей распознавания речи.
[14] Deepgram, Высокая точность для лучшего анализа речи.
[15] Deepgram, ПОЧЕМУ DEEPGRAM: Корпоративная аудиосистема сложна, а ASR не обязательно должна быть сложной.
[16] Deepgram, Каждый клиент. Услышанный и понятый).