Оглавление:

· Диаризация говорящего в автоматическом распознавании речи
· Что такое диаризация говорящего?
· Практическая диаризация говорящего с помощью Pyannote
· Возможности Pyannote
· Голосовая активность Обнаружение»
· Обнаружение наложения речи
· Переход к сути: диаризация говорящего с помощью ASR
· Заключение
· Ссылки

Диаризация говорящего в автоматическом распознавании речи

Автоматическое распознавание речи (ASR) — это способность машины преобразовывать аудиоданные в машиночитаемый (или человек!) формат. Алгоритмы ASR обычно достигают этого за счет комбинации трех типов алгоритмов: акустического моделирования, языкового моделирования и моделирования произношения.

Акустическое моделирование в ASR отвечает за идентификацию отдельных единиц речи (таких как фонемы) и их связь со звуковыми сигналами.

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

Моделирование произношения в ASR обеспечивает сопоставление между обычной символической расшифровкой речи и акустически/фонетически мотивированной.

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

Что такое диаризация говорящего?

«Диаризировать» означает делать заметки, по сути, отслеживать события в дневнике. Таким образом, диаризация говорящего — это не что иное, как ведение записей о произнесенном событии, чтобы ответить на ключевой вопрос «кто сказал, что и когда?».

Диаризация говорящего означает регистрацию событий, связанных с говорящим, в аудиоданных с несколькими участниками (или несколькими говорящими). На протяжении всего процесса диаризации аудиоданные разделяются и группируются в группы речевых сегментов с одним и тем же идентификатором говорящего. В результате другие важные события, такие как переходы между неречевыми и речевыми режимами или смена говорящих, также могут быть обнаружены автоматически.

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

  • записи совещаний, конференций или лекций
  • транслируемый и предварительно записанный контент из СМИ
  • судебное разбирательство
  • записи колл-центра
  • и многое другое

Традиционно система диаризации динамиков представляет собой сложную серию из нескольких независимых модулей:

  1. Внешняя обработка: аудиоданные предварительно обрабатываются для устранения артефактов в аудиосигнале, таких как шум, реверберация и фоновая музыка. Кроме того, могут использоваться другие методы, такие как улучшение речи или извлечение целевого говорящего.
  2. Обнаружение речевой активности (SAD): аудиоданные сегментируются на речевые и неречевые сегменты.
  3. Сегментация: речевые сегменты дополнительно делятся на сегменты речевых событий.
  4. Встраивание динамика: необработанные сигналы речевых сегментов преобразуются в векторы встраивания или акустические характеристики.
  5. Кластеризация: векторы встраивания группируются в группы речевых сегментов с одной и той же идентификационной меткой говорящего.

Всю схему системы можно увидеть ниже:

Практическая диаризация говорящего с помощью Pyannote

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

Pyannote — это набор современных моделей диаризации говорящего и обнаружения речевой активности, а также набор инструментов для обучения и оценки этих моделей. Pyannote построен на основе PyTorch, среды глубокого обучения, которая предоставляет несколько высокоуровневых функций.

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

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

Возможности Pyannote

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

Зависимости для этой статьи следующие:

  • pyannote.аудио
  • факел
  • панды
  • трансформеры
  • Huggungface_hub
  • либроса

Мы можем начать с простой визуализации файла, с которым мы работаем:

import matplotlib.pyplot as plt
import librosa as lr

audio_filepath = "news_article.wav"
waveform, sample_rate = lr.load(audio_filepath, sr=16000)
sample_rate
16000
def plot_waveform(speech, sample_rate):
    X = [i / sample_rate for i in range(len(speech))]
    plt.plot(X, speech)
    plt.xlabel("Time (s)")
    plt.ylabel("Amplitude")
    plt.title("Waveform")
    plt.show()

plot_waveform(waveform, sample_rate)

У нас есть несколько минут звука, но без какой-либо дополнительной информации трудно различить моменты тишины и невозможно различить динамики только по форме волны. Однако Pyannote может нам в этом помочь.

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

Обнаружение голосовой активности

VAD состоит из обнаружения областей речи в данном сегменте аудио. Это очень полезный инструмент при работе с аудиоданными, так как он позволяет нам отфильтровать неречевые сегменты и сосредоточиться на речевых сегментах, если мы того пожелаем.

import json
from itertools import groupby
import pandas as pd
import torch
from huggingface_hub import HfApi
from pyannote.audio import Pipeline
from pyannote.core import Annotation, Segment
from pydub import AudioSegment
from transformers import HubertForCTC, Wav2Vec2Processor


pipeline = Pipeline.from_pretrained("pyannote/voice-activity-detection")

output = pipeline(audio_filepath)
output

Предварительно обученную модель VAD Pyannote можно использовать для обнаружения и визуализации точек аудиофайла, в которых присутствует речь (любого говорящего). Это можно использовать для фильтрации неречевых сегментов и фокусировки на речевых сегментах, если мы того пожелаем.

Обнаружение перекрывающейся речи

Еще одним полезным инструментом является обнаружение перекрывающейся речи (OSD). OSD — это метод обнаружения речевых сегментов, перекрывающихся с другими речевыми сегментами. Это полезно, когда мы хотим знать, когда два или более говорящих говорят одновременно. Как обрабатывать эти перекрывающиеся сегменты, в конечном счете, зависит от пользователя. Например, можно разделить звук на сегменты, в которых одновременно говорит только один говорящий, или можно оставить перекрывающиеся сегменты без изменений или пропустить ASR для этих сегментов.

Конвейер OSD можно использовать следующим образом:

pipeline = Pipeline.from_pretrained("pyannote/overlapped-speech-detection")
output = pipeline(audio_filepath)
output

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

Переходим к делу: диаризация говорящего с помощью ASR

Теперь, когда мы увидели некоторые возможности Pyannote, мы можем использовать его функцию диаризации говорящего и объединить ее с ASR для создания расшифровки аудиофайла, которая правильно назначает каждого говорящего каждому предложению. Для этого мы также будем использовать предварительно обученную акустическую модель ASR, основанную на архитектуре wav2vec2 под названием HuBERT.

Первое, что мы сделаем, это создадим эмбеддинг из аудиофайла. Эти вложения будут использоваться для транскрипции. Для этого воспользуемся Wav2Vec2Processor из библиотеки transformers. Этот процессор позволит нам преобразовать аудиофайл в формат, который может использоваться моделью ASR.

# load model & audio and run audio through model
processor = Wav2Vec2Processor.from_pretrained("facebook/hubert-large-ls960-ft")
model = HubertForCTC.from_pretrained("facebook/hubert-large-ls960-ft").cuda()
input_values = processor(waveform, return_tensors="pt", sampling_rate=sample_rate).input_values.cuda()

Однако следует иметь в виду, что эти модели используют много видеопамяти, и, поскольку для этого требуется довольно много необработанной вычислительной мощности, мы также хотим использовать для этого CUDA. Если у вас нет графического процессора, вы можете воспользоваться сервисом Google Colab, который предоставляет бесплатный доступ к графическому процессору. В любом случае, мы разделим аудиофайл на 10 фрагментов. Это сделано для того, чтобы избежать нехватки памяти и ускорить процесс. Количество фрагментов выбрано, поскольку мы знаем продолжительность контента, и для справки: для расшифровки около 90 секунд за раз с этой архитектурой потребуется около 12 ГБ видеопамяти.

def return_n_chunks_of_tensor(tensor, n):
    return torch.chunk(tensor, n, dim=1)

input_chunks = return_n_chunks_of_tensor(input_values, 10)

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

logits = None

for chunk in input_chunks:
    with torch.no_grad():
        if logits is None:
            logits = model(chunk.cuda()).logits.cpu()
        else:
            logits = torch.cat((logits, model(chunk.cuda()).logits.cpu()), dim=1)

Чтобы получить predicted_ids (которое затем можно декодировать в текст), мы можем взять максимальное значение логитов для каждого временного шага. Это даст нам прогнозируемые_идентификаторы для каждого временного шага. Затем логиты можно декодировать в текст, используя метод decode Wav2Vec2Processor.

predicted_ids = torch.argmax(logits, dim=-1)
transcription = processor.decode(predicted_ids[0]).lower()
transcription
"lebanon and israel have ended a long standing dispute over their shared maritime border the two countries are still formerly at war so leaders signe the agreement separately both nations hope to benefit from mineral resources within the formerly disputed area  wstanie kramer report explains now what the dispute was about the mediterranean sea of the coast between israel and lebanon these were contested waters both countries have long been locked in dispute over where the maritime border lies behind the scenes negotiations have gone on for several years now israel and libanon have agreed on a maritime border deal mediated by the united states this gamsho disagreement strengthens israel's security and our freedom of action against hesbla and the threats to our north there is rare consensus in the security establishment regarding the necessity of this agreement the dispute is about a relatively small triungle shaped area with each side claiming their part as exclusive economic zone the areas expected to rich in offshore gas israel and libanon have a long history of conflict the two countries fought a war in two thousand and six and there have been many security incidents between israel and the libanese sheite militant group hasbola since parts of the country's land border the blue line a demarcation line by the u n is also disputed whether the maritime border deal could be a step towards a wider peace agreement is unclear but the deal paves the way to morgaexploration a potential economic benefit for both countries while our correspondents in bayroot and jerusalem have been following developments rebecca wittas in jerusalem told me what this deal means for israel well fill this deal has been in the making for more than a decade there've been numerous rounds of negotiations all of which have failed until a couple of weeks ago when a deal was finally reached between the two sides and we have now seen it signed in by michel arun the lebonese president nd the caretaker prime minister herein israelalapede now all science and in fact it was a u s broker deal all parties involved are calling this deal historic and let's not forget that these two countries as you rightly mentioned are still technically at war er they have no diplomatic relations in fact lebanon doesn't even recognize isr israel as a sovereign state so the fact that two countries in this situation could sign a deal like this maritime boarder deal is being heralded as a historic step er the benefits will be of course for both sides and very far reaching for israel er for example of course thes political and diplomatic benefits and there's also the economic benefits and security guarantees now israel has long wanted to explore these gas wells that it is considered for a long time on its side of the maritime border now er formally agreed inside its maritime border it knows that those gas fields er have a lot of gas in them and its long wanted to extract them but it's been under threat by the eranbak hesbla on the lebanon side that if it were to explore those fields  or fact extract gas out of them without such a deal as we're seeing to day that it would come under threats and would be attacked now of course israel is free to go and explore those fields and extract the gas and in fact it's already doing so yesterday a energy on the company that is drilling on the israelie side  they announce that the gas was already flowing from one of those wells and that it would soon even be able to start delivering to its partners in the next couple of days in fact now that is going to be met  with smiles by e ou leaders who are desperately trying to bridge the gap left by turning off the tap frorussia since the invasion of eukran so you know the benefits from this deal agan o be very far reaching for israel for one though it really says that this is a tacit agreement by a sworn enemy in fact yalaped said words to that effect as he was signing the document as you rightly pointed out that's not exactly how it's seen in lebanon but none theless this is a very significant dealfil oh i thank you for that er rebecca mohommed traiter in bayroot so how does lebanon see this deal well i it's a difference it's a different ea perspectiv definitely this morning the lebanese president a michel laon said that the agreement is purely technical and does not have any political implications or effects that contradicts lebanon's foreign policy and a relations  with other states the two states are still technically at war however the agreement is expected to bring some stability to the area and opens the way a  for offshore energy exploration as it removes a main source of potential conflict between a israeel and a lebanon mainly  the iranian backed heavily group heavily armed group e lebanese hesbola lebanese officials are hoping that disagreement helps e elevate lebanon's economic crisis  the country's economy has been in freefall for three years now  the exploration of hydrocarbons is a huge deal for lebanon as a a significant discovery could help easily a country's stilfling financial a crisis o"

Теперь мы можем начать обработку наших данных, чтобы сопоставить их с временными метками аудиофайла. Для этого мы будем использовать форму входа и разделить ее на частоту дискретизации (16 кГц), чтобы получить продолжительность аудиофайла в секундах.

# this is where the logic starts to get the start and end timestamp for each word
words = [w for w in transcription.split(' ') if len(w) > 0]
predicted_ids = predicted_ids[0].tolist()
duration_sec = input_values.shape[1] / sample_rate

ids_w_time = [(i / len(predicted_ids) * duration_sec, _id) for i, _id in enumerate(predicted_ids)]
# remove entries which are just "padding" (i.e. no characters are recognized)
ids_w_time = [i for i in ids_w_time if i[1] != processor.tokenizer.pad_token_id]
# now split the ids into groups of ids where each group represents a word
split_ids_w_time = [list(group) for k, group
                    in groupby(ids_w_time, lambda x: x[1] == processor.tokenizer.word_delimiter_token_id)
                    if not k]

assert len(split_ids_w_time) == len(words)  # make sure that there are the same number of id-groups as words

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

word_start_times = []
word_end_times = []

for cur_ids_w_time, cur_word in zip(split_ids_w_time, words):
    _times = [_time for _time, _id in cur_ids_w_time]
    word_start_times.append(min(_times))
    word_end_times.append(max(_times))

words[:9], word_start_times[:9], word_end_times[:9]
(['lebanon',
  'and',
  'israel',
  'have',
  'ended',
  'a',
  'long',
  'standing',
  'dispute'],
 [0.5604111660671462,
  1.0807929631294964,
  1.2809398081534773,
  1.6011747601918465,
  1.7612922362110313,
  2.001468450239808,
  2.1015418727517985,
  2.3417180867805754,
  2.7820411458333334],
 [0.9206754871103118,
  1.1408370166366906,
  1.5411307066846522,
  1.6812334982014387,
  1.921409712230216,
  2.001468450239808,
  2.261659348770983,
  2.701982407823741,
  3.182334835881295])

Теперь, когда каждое слово расшифровки стенограммы снабжено временной меткой, мы можем использовать Pyannote Pipeline для создания диаризации говорящего. Это даст нам метки докладчиков для каждого временного шага аудио, которые можно сопоставить с расшифровкой стенограммы с отметками времени, чтобы создать стенограмму с метками докладчиков и ответить на вопрос "Кто что сказал, когда?".

available_models = [m.modelId for m in HfApi().list_models(filter="pyannote")]
available_models
['julien-c/voice-activity-detection',
 'pyannote/TestModelForContinuousIntegration',
 'pyannote/embedding',
 'pyannote/overlapped-speech-detection',
 'pyannote/segmentation',
 'pyannote/speaker-diarization',
 'pyannote/speaker-segmentation',
 'pyannote/voice-activity-detection',
 'AMITKESARI2000/pyannote_SD1',
 'philschmid/pyannote-speaker-diarization-endpoint',
 'pyannote/brouhaha',
 'anilbs/pipeline',
 'anilbs/segmentation',
 'philschmid/pyannote-segmentation',
 'tawkit/phil-pyannote-speaker-diarization-endpoint']
pipeline = Pipeline.from_pretrained("pyannote/speaker-diarization")
output = pipeline("news_article.wav")
output

speaker_timelines = [output.label_timeline(speaker) for speaker in output.labels()]
overlaps = output.get_overlap()
overlap_segments = overlaps.segments_list_
overlaps

def get_speaker_zones(annotation):
    speaker_zones = []
    current_speaker = None
    start = None
    end = None
    for time, _, speaker in annotation.itertracks(yield_label=True):
        if current_speaker is None:
            current_speaker = speaker
            start = time.start
            end = time.end
        elif speaker != current_speaker:
            speaker_zones.append({'speaker': current_speaker, 'start': start, 'end': end})
            start = time.start
            end = time.end
            current_speaker = speaker
        else:
            end = time.end
    speaker_zones.append({'speaker': current_speaker, 'start': start, 'end': end})
    return speaker_zones


speaker_zones = get_speaker_zones(output_without_overlaps)


def create_annotation_from_speaker_zones(speaker_zones):
    annotation = Annotation()
    for zone in speaker_zones:
        annotation[Segment(zone['start'], zone['end'])] = zone['speaker']
    return annotation


optimized_output = create_annotation_from_speaker_zones(speaker_zones)


optimized_output

transcript_df = pd.DataFrame({'word': words, 'start': word_start_times, 'end': word_end_times})

transcript_df

for turn, _, speaker in optimized_output.itertracks(yield_label=True):
    print(f'{speaker}: {turn}')

def get_transcript_with_timestamps(timestamped_transcript_df, subtitle_lenght_sec=5):
    transcript_with_timestamps = []
    sentence = ''
    for i, row in timestamped_transcript_df.iterrows():
        if i == 0:
            start = row['start']
        if row['end'] - start < subtitle_lenght_sec:
            sentence += row['word'] + ' '
            end = row['end']
        else:
            transcript_with_timestamps.append({'start': start, 'end': end, 'sentence': sentence.strip()})
            start = row['start']
            end = row['end']
            sentence = row['word'] + ' '
    transcript_with_timestamps.append({'start': start, 'end': end, 'sentence': sentence.strip()})
    return transcript_with_timestamps

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

get_transcript_with_timestamps(transcript_df, 30)[:2]
[{'start': 0.5604111660671462,
  'end': 30.382291074640293,
  'sentence': 'lebanon and israel have ended a long standing dispute over their shared maritime border the two countries are still formerly at war so leaders signe the agreement separately both nations hope to benefit from mineral resources within the formerly disputed area wstanie kramer report explains now what the dispute was about the mediterranean sea of the coast between israel and lebanon these were contested waters both countries have long been locked in dispute over where the'},
 {'start': 30.482364497152282,
  'end': 60.16414161420863,
  'sentence': "maritime border lies behind the scenes negotiations have gone on for several years now israel and libanon have agreed on a maritime border deal mediated by the united states this gamsho disagreement strengthens israel's security and our freedom of action against hesbla and the threats to our north there is rare consensus in the security establishment regarding the necessity of this agreement the dispute is about a relatively small triungle shaped"}]
def get_transcript_with_speakers(transcript_with_timestamps: pd.DataFrame, speaker_annotation: Annotation):
    total_text = []
    for turn, _, speaker in speaker_annotation.itertracks(yield_label=True):
        turn_transcript = transcript_with_timestamps[(transcript_with_timestamps['start'] >= turn.start) & (transcript_with_timestamps['end'] <= turn.end)]
        total_text.append({'speaker': speaker, 'start': turn.start, 'end': turn.end, 'sentence': ' '.join(turn_transcript['word'].tolist())})
    return total_text

print(json.dumps(get_transcript_with_speakers(transcript_df, optimized_output)[:4], indent=2))
[
  {
    "speaker": "SPEAKER_03",
    "start": 0.4978125,
    "end": 18.8071875,
    "sentence": "lebanon and israel have ended a long standing dispute over their shared maritime border the two countries are still formerly at war so leaders signe the agreement separately both nations hope to benefit from mineral resources within the formerly disputed area wstanie kramer report explains now what the dispute was about"
  },
  {
    "speaker": "SPEAKER_04",
    "start": 20.8996875,
    "end": 43.0059375,
    "sentence": "the mediterranean sea of the coast between israel and lebanon these were contested waters both countries have long been locked in dispute over where the maritime border lies behind the scenes negotiations have gone on for several years now israel and libanon have agreed on a maritime border deal mediated by the united states this"
  },
  {
    "speaker": "SPEAKER_02",
    "start": 43.0059375,
    "end": 55.172812500000006,
    "sentence": "gamsho disagreement strengthens israel's security and our freedom of action against hesbla and the threats to our north there is rare consensus in the security establishment regarding the necessity of this agreement"
  },
  {
    "speaker": "SPEAKER_04",
    "start": 56.894062500000004,
    "end": 104.9878125,
    "sentence": "the dispute is about a relatively small triungle shaped area with each side claiming their part as exclusive economic zone the areas expected to rich in offshore gas israel and libanon have a long history of conflict the two countries fought a war in two thousand and six and there have been many security incidents between israel and the libanese sheite militant group hasbola since parts of the country's land border the blue line a demarcation line by the u n is also disputed whether the maritime border deal could be a step towards a wider peace agreement is unclear but the deal paves the way to morgaexploration a potential economic benefit for both countries"
  }
]

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

Эти ошибки могут быть связаны с несколькими различными факторами (см. ниже). В качестве последующего упражнения вы можете изучить следующее:

  • HuBERT разделяет акустические недостатки моделей Wav2Vec2, есть способы улучшить эти результаты грамматически, используя, например, n-граммы.
  • Обработка перекрывающейся речи в этой модели была чрезмерно упрощена, и для улучшения результатов можно использовать более сложные методы.
  • Модель была обучена на наборе данных, который не является репрезентативным для используемого нами аудиофайла, и это можно улучшить, обучив модель на наборе данных, который более репрезентативен для используемого нами аудиофайла.
  • Вместо использования акустической модели можно было бы попытаться использовать генеративный подход к транскрипции.

Заключение

В этой статье мы узнали, как использовать функции VAD и OSD Pyannote для фильтрации неречевых сегментов и перекрывающихся речевых сегментов, а также как использовать функцию диаризации говорящего Pyannote для создания расшифровки аудиофайла, которая правильно назначает говорящего каждому. предложение.

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

Мы надеемся, что вы найдете это полезным при создании собственных систем ASR, будь то интеллектуальный анализ аудиоданных, транскрипция или даже живые субтитры. Если есть какие-либо вопросы по вкладам, пожалуйста, не стесняйтесь оставлять комментарии!

Рекомендации