#
Transcrire une réunion

Dans cette dernière section, nous utiliserons le modèle Whisper pour générer la transcription d’une conversation ou d’une réunion entre deux ou plusieurs locuteurs. Nous l’associerons ensuite à un modèle de séparation des locuteurs pour prédire “qui a parlé quand”. En faisant correspondre les horodatages des transcriptions Whisper avec les horodatages du modèle de séparation des locuteurs, nous pouvons prédire une transcription de réunion de bout en bout avec des heures de début et de fin entièrement formatées pour chaque locuteur. Il s’agit d’une version basique des services de transcription de réunions que vous avez pu voir en ligne sur Otter.ai et co :

##
Séparation des locuteurs

La séparation des locuteur (tâche de diarization en anglais) consiste à prendre une entrée audio non étiquetée et à prédire “qui a parlé quand”. Ce faisant, nous pouvons prédire les horodatages de début / fin pour chaque tour de parole, correspondant au moment où chaque locuteur commence à parler et au moment où il termine.

🤗 Transformers n’a actuellement pas de modèle de diarization inclus, mais il y a des checkpoints sur le Hub qui peuvent être utilisés avec une relative facilité. Dans cet exemple, nous utiliserons le modèle pré-entraîné de pyannote.audio. Commençons par l’installation du paquet avec pip :

    pip install --upgrade pyannote.audio

Bien, les poids de ce modèle sont hébergés sur le Hub. Pour y accéder, nous devons d’abord accepter les conditions d’utilisation du modèle de diarization : pyannote/speaker-diarization. Et ensuite les conditions d’utilisation du modèle de segmentation : pyannote/segmentation.

Une fois terminé, nous pouvons charger le pipeline de séparation des locuteurs localement sur notre appareil :

    from pyannote.audio import Pipeline

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

Essayons-le sur un échantillon de fichier audio ! Pour cela, nous allons charger un échantillon du jeu de données LibriSpeech ASR qui consiste en deux locuteurs différents qui ont été concaténés ensemble pour donner un seul fichier audio :

    from datasets import load_dataset

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

Nous pouvons écouter l’audio pour voir à quoi cela ressemble :

    from IPython.display import Audio

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

Nous pouvons clairement entendre deux locuteurs différents, avec une transition d’environ 15 secondes. Passons ce fichier audio au modèle de séparation pour obtenir les heures de début et de fin du locuteur. Notez que pyannote.audio s’attend à ce que l’entrée audio soit un tenseur PyTorch de la forme `(channels, seq_len)`, nous devons donc effectuer cette conversion avant d’exécuter le modèle :

    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'}]

Cela semble plutôt bien ! Nous pouvons voir que le premier locuteur est prédit comme parlant jusqu’à la marque de 14,5 secondes, et le second locuteur à partir de 15,4s. Il ne nous reste plus qu’à obtenir notre transcription !

##
Transcription de la parole

Pour la troisième fois dans cette unité, nous allons utiliser le modèle Whisper pour notre système de transcription de la parole. Plus précisément, nous chargerons le checkpoint Whisper Base, car il est suffisamment petit pour donner une bonne vitesse d’inférence avec une précision de transcription raisonnable. Comme auparavant, vous pouvez utiliser n’importe quel checkpoint de reconnaissance vocale disponible sur le Hub, y compris Wav2Vec2, MMS ASR ou d’autres tailles de Whisper :

    from transformers import pipeline

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

Obtenons la transcription de notre échantillon audio, en retournant également les horodatages au niveau du segment afin que nous connaissions les heures de début et de fin de chaque segment. Vous vous souviendrez de l’unité 5 que nous devons passer l’argument `return_timestamps=True` pour activer la tâche de prédiction de l’horodatage de Whisper :

    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."},
        ],
    }

Ok, nous constatons que chaque segment de la transcription a une heure de début et une heure de fin, avec un changement de locuteur au bout de 15,48 secondes. Nous pouvons maintenant associer cette transcription aux horodatages des locuteurs que nous avons obtenus à partir de notre modèle de diarization pour obtenir notre transcription finale.

##
Speechbox

Pour obtenir la transcription finale, nous allons aligner les timestamps du modèle de diarisation avec ceux du modèle Whisper. Le modèle de séparation prédit que le premier locuteur se termine à 14,5 secondes et que le second locuteur commence à 15,4 secondes, alors que Whisper prédit les limites des segments à 13,88, 15,48 et 19,44 secondes respectivement. Comme les timestamps de Whisper ne correspondent pas parfaitement à ceux du modèle de séparation, nous devons trouver lesquelles de ces limites sont les plus proches de 14,5 et 15,4 secondes, et segmenter la transcription par locuteur en conséquence. Plus précisément, nous trouverons l’alignement le plus proche entre les horodatages de la séparation et de la transcription en minimisant la distance absolue entre les deux.

Heureusement pour nous, nous pouvons utiliser 🤗 Speechbox pour effectuer cet alignement. Tout d’abord, installons `speechbox` depuis main :

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

Nous pouvons maintenant instancier notre pipeline combiné de diarization et de transcription, en passant nos deux modèles à la classe `ASRDiarizationPipeline` :

    from speechbox import ASRDiarizationPipeline

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

Vous pouvez également instancier le `ASRDiarizationPipeline` directement à partir d'un modèle pré-entraîné en spécifiant l'identifiant d'un modèle ASR sur le *Hub* : `pipeline = ASRDiarizationPipeline.from_pretrained("openai/whisper-base")`

Passons le fichier audio au pipeline et voyons ce que nous obtenons :

    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)}]

Excellent ! Le premier locuteur est segmenté comme parlant de 0 à 15,48 secondes, et le second locuteur de 15,48 à 21,28 secondes, avec les transcriptions correspondantes pour chacun.

Nous pouvons formater les horodatages de manière un peu plus agréable en définissant deux fonctions d’aide. La première convertit un tuple d’horodatages en une chaîne de caractères, arrondie à un nombre défini de décimales. La seconde combine l’identifiant du locuteur, l’horodatage et les informations textuelles sur une seule ligne, et sépare chaque locuteur sur sa propre ligne pour faciliter la lecture :

    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
            ]
        )

Exécutons à nouveau le pipeline, en formatant cette fois la transcription selon la fonction que nous venons de définir :

    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.

Et voilà ! Nous avons ainsi séparé et transcrit notre audio et renvoyé des transcriptions segmentées par locuteur. Bien que l’algorithme de distance minimale pour aligner les horodatages diarisés et les horodatages transcrits soit simple, il fonctionne bien dans la pratique. Si vous souhaitez explorer des méthodes plus avancées pour combiner les horodatages, le code source de `ASRDiarizationPipeline` est un bon point de départ : speechbox/diarize.py.