#
Транскрибирование встречи

В этом заключительном разделе мы используем модель Whisper для создания транскрипции разговора или встречи между двумя или более говорящими. Затем мы объединим ее с моделью диаризации диктора для прогнозирования “кто когда говорил”. Сопоставив временные метки из транскрипции Whisper с временными метками от модели диаризации, мы можем спрогнозировать сквозную транскрипцию встречи с полностью отформатированным временем начала и окончания для каждого говорящего. Это базовая версия услуг по транскрибированию совещаний, которую вы могли видеть в интернете от таких компаний, как Otter.ai и др:

##
Диаризация диктора

Диаризация диктора (или диаризация) - это задача получения немаркированных аудиоданных и прогнозирования того, “кто когда говорил”. При этом мы можем прогнозировать временные метки начала/окончания каждой очереди дикторов, соответствующие моменту начала речи и моменту ее окончания.

🤗 В настоящее время в библиотеке Transformers нет модели для диаризации диктора, но на Hub есть контрольные точки, которые можно использовать с относительной легкостью. В этом примере мы будем использовать предварительно обученную модель диаризации диктора из pyannote.audio. Давайте приступим к работе и установим пакет с помощью pip:

    pip install --upgrade pyannote.audio

Отлично! Веса для этой модели размещены на Hugging Face Hub. Чтобы получить к ним доступ, сначала нужно согласиться с условиями использования модели диаризации диктора: pyannote/speaker-diarization. А затем - с условиями использования модели сегментации: pyannote/segmentation.

После завершения работы мы можем загрузить предварительно обученный конвейер диаризации дикторов локально на наше устройство:

    from pyannote.audio import Pipeline

    diarization_pipeline = Pipeline.from_pretrained(
        "pyannote/speaker-diarization@2.1", use_auth_token=True
    )

Давайте опробуем его на примере аудиофайла! Для этого мы загрузим образец из датасета LibriSpeech ASR, содержащий речь двух разных дикторов, который мы объединили в один аудиофайл:

    from datasets import load_dataset

    concatenated_librispeech = load_dataset(
        "sanchit-gandhi/concatenated_librispeech", split="train", streaming=True
    )
    sample = next(iter(concatenated_librispeech))

Мы можем прослушать аудиозапись, чтобы понять, как она звучит:

    from IPython.display import Audio

    Audio(sample["audio"]["array"], rate=sample["audio"]["sampling_rate"])

Класс! Мы отчетливо слышим двух разных дикторов с переходом примерно на 15 секунде звучания. Давайте передадим этот аудиофайл в модель диаризации, чтобы получить время начала и окончания разговора. Заметим, что pyannote.audio ожидает, что входной аудиофайл будет представлять собой тензор PyTorch формы `(channels, seq_len)`, поэтому перед запуском модели нам необходимо выполнить это преобразование:

    import torch

    input_tensor = torch.from_numpy(sample["audio"]["array"][None, :]).float()
    outputs = diarization_pipeline(
        {"waveform": input_tensor, "sample_rate": sample["audio"]["sampling_rate"]}
    )

    outputs.for_json()["content"]

    [{'segment': {'start': 0.4978125, 'end': 14.520937500000002},
      'track': 'B',
      'label': 'SPEAKER_01'},
     {'segment': {'start': 15.364687500000002, 'end': 21.3721875},
      'track': 'A',
      'label': 'SPEAKER_00'}]

Выглядит это довольно неплохо! Видно, что первый диктор говорит до отметки 14,5 секунды, а второй - с 15,4 секунды. Теперь нам нужно получить транскрипцию!

##
Транскрибирование речи

В третий раз в этом блоке мы будем использовать модель Whisper для нашей системы транскрипции речи. В частности, мы загрузим контрольную точку Whisper Base, поскольку она достаточно мала, чтобы обеспечить хорошую скорость инференса при приемлемой точности транскрипции. Как и прежде, вы можете использовать любую контрольную точку распознавания речи с Hub, включая Wav2Vec2, MMS ASR или другие контрольные точки Whisper:

    from transformers import pipeline

    asr_pipeline = pipeline(
        "automatic-speech-recognition",
        model="openai/whisper-base",
    )

Давайте получим транскрипцию для нашего образца аудиозаписи, возвращая также временные метки на уровне сегментов, чтобы знать время начала и окончания каждого сегмента. Из раздела 5 вы помните, что для активации задачи прогнозирования временных меток в Whisper нам необходимо передать аргумент `return_timestamps=True`:

    asr_pipeline(
        sample["audio"].copy(),
        generate_kwargs={"max_new_tokens": 256},
        return_timestamps=True,
    )

    {
        "text": " The second and importance is as follows. Sovereignty may be defined to be the right of making laws. In France, the king really exercises a portion of the sovereign power, since the laws have no weight. He was in a favored state of mind, owing to the blight his wife's action threatened to cast upon his entire future.",
        "chunks": [
            {"timestamp": (0.0, 3.56), "text": " The second and importance is as follows."},
            {
                "timestamp": (3.56, 7.84),
                "text": " Sovereignty may be defined to be the right of making laws.",
            },
            {
                "timestamp": (7.84, 13.88),
                "text": " In France, the king really exercises a portion of the sovereign power, since the laws have",
            },
            {"timestamp": (13.88, 15.48), "text": " no weight."},
            {
                "timestamp": (15.48, 19.44),
                "text": " He was in a favored state of mind, owing to the blight his wife's action threatened to",
            },
            {"timestamp": (19.44, 21.28), "text": " cast upon his entire future."},
        ],
    }

Отлично! Мы видим, что каждый сегмент транскрипции имеет начальное и конечное время, причем смена дикторов происходит на отметке 15,48 секунды. Теперь мы можем сопоставить эту транскрипцию с временными метками дикторов, полученными с помощью модели диаризации, и получить окончательную транскрипцию.

##
Speechbox

Чтобы получить окончательную транскрипцию, совместим временные метки, полученные с помощью модели диаризации, с временными метками, полученными с помощью модели Whisper. Модель диаризации предсказала окончание речи первого диктора на 14,5 с, а второго - на 15,4 с, в то время как Whisper предсказал границы сегментов на 13,88, 15,48 и 19,44 с соответственно. Поскольку временные метки, полученные с помощью Whisper, не полностью совпадают с данными модели диаризации, нам необходимо найти, какие из этих границ ближе всего к 14,5 и 15,4 с, и соответствующим образом сегментировать транскрипцию по дикторам. В частности, мы найдем наиболее близкое совпадение между временными метками диаризации и транскрипции, минимизировав абсолютное расстояние между ними.

К счастью для нас, мы можем использовать пакет 🤗 Speechbox для выполнения этого выравнивания. Сначала давайте установим пакет `speechbox` из main:

    pip install git+https://github.com/huggingface/speechbox

Теперь мы можем инстанцировать наш комбинированный конвейер диаризации и транскрипции, передав модель диаризации и модель ASR в класс `ASRDiarizationPipeline`:

    from speechbox import ASRDiarizationPipeline

    pipeline = ASRDiarizationPipeline(
        asr_pipeline=asr_pipeline, diarization_pipeline=diarization_pipeline
    )

Вы также можете инстанцировать `ASRDiarizationPipeline` directly непосредственно из предварительно обученных моделей, указав идентификатор модели ASR на Hub:

`pipeline = ASRDiarizationPipeline.from_pretrained("openai/whisper-base")`

Передадим аудиофайл в композитный конвейер и посмотрим, что получится в результате:

    pipeline(sample["audio"].copy())

    [{'speaker': 'SPEAKER_01',
      'text': ' The second and importance is as follows. Sovereignty may be defined to be the right of making laws. In France, the king really exercises a portion of the sovereign power, since the laws have no weight.',
      'timestamp': (0.0, 15.48)},
     {'speaker': 'SPEAKER_00',
      'text': " He was in a favored state of mind, owing to the blight his wife's action threatened to cast upon his entire future.",
      'timestamp': (15.48, 21.28)}]

Отлично! Первый диктор сегментирован как говорящий с 0 до 15,48 секунды, а второй - с 15,48 до 21,28 секунды, с соответствующими транскрипциями для каждого из них.

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

    def tuple_to_string(start_end_tuple, ndigits=1):
        return str((round(start_end_tuple[0], ndigits), round(start_end_tuple[1], ndigits)))

    def format_as_transcription(raw_segments):
        return "\n\n".join(
            [
                chunk["speaker"] + " " + tuple_to_string(chunk["timestamp"]) + chunk["text"]
                for chunk in raw_segments
            ]
        )

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

    outputs = pipeline(sample["audio"].copy())

    format_as_transcription(outputs)

    SPEAKER_01 (0.0, 15.5) The second and importance is as follows. Sovereignty may be defined to be the right of making laws.
    In France, the king really exercises a portion of the sovereign power, since the laws have no weight.

    SPEAKER_00 (15.5, 21.3) He was in a favored state of mind, owing to the blight his wife's action threatened to cast upon
    his entire future.

Вот так! Таким образом, мы провели диарирование и транскрибацию входного аудиосигнала и получили транскрибации с сегментацией дикторов. Хотя алгоритм минимального расстояния для выравнивания временных меток диаризации и транскрибации прост, он хорошо работает на практике. Если вы хотите изучить более сложные методы совмещения временных меток, то исходный код `ASRDiarizationPipeline` является хорошей отправной точкой: speechbox/diarize.py