利用 “diart“ 和 OpenAI 的 Whisper 简化实时转录

news2024/9/28 19:25:12

利用 "diart" 和 OpenAI 的 Whisper 简化实时转录

工作原理

Diart 是一个基于人工智能的 Python 库,用于实时记录说话者语言(即 "谁在什么时候说话"),它建立在 pyannote.audio 模型之上,专为实时音频流(如麦克风)而设计。
只需几行代码,diart 就能让您获得类似这样的实时发言者标签:

1 KYmVPiXfLf7fWf2akWUk2A

与此同时,Whisper 是 OpenAI 最新推出的一种为自动语音识别(ASR)而训练的模型,它对嘈杂环境的适应能力特别强,非常适合现实生活中的使用案例。

准备工作

  1. 按照此处的说明安装 diart
  2. 使用 pip install git+https://github.com/linto-ai/whisper-timestamped 安装 whisper-timestamped

在这篇文章的其余部分,我将使用 RxPY(Python 的反应式编程扩展)来处理流媒体部分。如果你对它不熟悉,我建议你看看这个文档页面,了解一下基本知识。

简而言之,反应式编程就是对来自给定源(在我们的例子中是麦克风)的发射项(在我们的例子中是音频块)进行操作。

结合听和写

让我们先概述一下源代码,然后将其分解成若干块,以便更好地理解它。

import logging
import traceback
import diart.operators as dops
import rich
import rx.operators as ops
from diart import OnlineSpeakerDiarization, PipelineConfig
from diart.sources import MicrophoneAudioSource

# Suppress whisper-timestamped warnings for a clean output
logging.getLogger("whisper_timestamped").setLevel(logging.ERROR)

config = PipelineConfig(
    duration=5,
    step=0.5,
    latency="min",
    tau_active=0.5,
    rho_update=0.1,
    delta_new=0.57
)
dia = OnlineSpeakerDiarization(config)
source = MicrophoneAudioSource(config.sample_rate)

asr = WhisperTranscriber(model="small")

transcription_duration = 2
batch_size = int(transcription_duration // config.step)
source.stream.pipe(
    dops.rearrange_audio_stream(
        config.duration, config.step, config.sample_rate
    ),
    ops.buffer_with_count(count=batch_size),
    ops.map(dia),
    ops.map(concat),
    ops.filter(lambda ann_wav: ann_wav[0].get_timeline().duration() > 0),
    ops.starmap(asr),
    ops.map(colorize_transcription),
).subscribe(on_next=rich.print, on_error=lambda _: traceback.print_exc())

print("Listening...")
source.read()

创建发言者记录模块

首先,我们创建了流媒体(又称 "在线")扬声器日记系统以及与本地麦克风相连的音频源。

我们将系统配置为使用 5 秒的滑动窗口,步长为 500 毫秒(默认值),并将延迟设置为最小值(500 毫秒),以提高响应速度。

# If you have a GPU, you can also set device=torch.device("cuda")
config = PipelineConfig(
    duration=5,
    step=0.5,
    latency="min",
    tau_active=0.5,
    rho_update=0.1,
    delta_new=0.57
)
dia = OnlineSpeakerDiarization(config)
source = MicrophoneAudioSource(config.sample_rate)

配置中的三个附加参数可调节扬声器识别的灵敏度:

  • tau_active=0.5: 只识别发言概率高于 50% 的发言者。
  • rho_update=0.1: Diart 会自动收集发言者的信息以自我改进(别担心,这是在本地完成的,不会与任何人共享)。在这里,我们只使用每位发言者 100ms 以上的语音进行自我改进。
  • delta_new=0.57:这是一个介于 0 和 2 之间的内部阈值,用于调节新发言人的检测。该值越小,系统对语音差异越敏感。

创建 ASR 模块

接下来,我们使用我为这篇文章创建的 WhisperTranscriber 类加载语音识别模型。

# If you have a GPU, you can also set device="cuda"
asr = WhisperTranscriber(model="small")

该类的定义如下:

import os
import sys
import numpy as np
import whisper_timestamped as whisper
from pyannote.core import Segment
from contextlib import contextmanager


@contextmanager
def suppress_stdout():
    # Auxiliary function to suppress Whisper logs (it is quite verbose)
    # All credit goes to: https://thesmithfam.org/blog/2012/10/25/temporarily-suppress-console-output-in-python/
    with open(os.devnull, "w") as devnull:
        old_stdout = sys.stdout
        sys.stdout = devnull
        try:
            yield
        finally:
            sys.stdout = old_stdout

class WhisperTranscriber:
    def __init__(self, model="small", device=None):
        self.model = whisper.load_model(model, device=device)
        self._buffer = ""

    def transcribe(self, waveform):
        """Transcribe audio using Whisper"""
        # Pad/trim audio to fit 30 seconds as required by Whisper
        audio = waveform.data.astype("float32").reshape(-1)
        audio = whisper.pad_or_trim(audio)

        # Transcribe the given audio while suppressing logs
        with suppress_stdout():
            transcription = whisper.transcribe(
                self.model,
                audio,
                # We use past transcriptions to condition the model
                initial_prompt=self._buffer,
                verbose=True  # to avoid progress bar
            )

        return transcription

    def identify_speakers(self, transcription, diarization, time_shift):
        """Iterate over transcription segments to assign speakers"""
        speaker_captions = []
        for segment in transcription["segments"]:

            # Crop diarization to the segment timestamps
            start = time_shift + segment["words"][0]["start"]
            end = time_shift + segment["words"][-1]["end"]
            dia = diarization.crop(Segment(start, end))

            # Assign a speaker to the segment based on diarization
            speakers = dia.labels()
            num_speakers = len(speakers)
            if num_speakers == 0:
                # No speakers were detected
                caption = (-1, segment["text"])
            elif num_speakers == 1:
                # Only one speaker is active in this segment
                spk_id = int(speakers[0].split("speaker")[1])
                caption = (spk_id, segment["text"])
            else:
                # Multiple speakers, select the one that speaks the most
                max_speaker = int(np.argmax([
                    dia.label_duration(spk) for spk in speakers
                ]))
                caption = (max_speaker, segment["text"])
            speaker_captions.append(caption)

        return speaker_captions

    def __call__(self, diarization, waveform):
        # Step 1: Transcribe
        transcription = self.transcribe(waveform)
        # Update transcription buffer
        self._buffer += transcription["text"]
        # The audio may not be the beginning of the conversation
        time_shift = waveform.sliding_window.start
        # Step 2: Assign speakers
        speaker_transcriptions = self.identify_speakers(transcription, diarization, time_shift)
        return speaker_transcriptions

转录器执行一个简单的操作,接收音频块及其日记,并按照以下步骤操作:

  1. 用 Whisper 转录音频片段(带单词时间戳)
  2. 通过调整单词和说话人之间的时间戳,为转录的每个片段指定说话人

将两个模块放在一起

既然我们已经创建了日记化和转录模块,那么我们就可以定义对每个音频块应用的操作链:

import traceback
import rich
import rx.operators as ops
import diart.operators as dops

# Split the stream into 2s chunks for transcription
transcription_duration = 2
# Apply models in batches for better efficiency
batch_size = int(transcription_duration // config.step)

# Chain of operations to apply on the stream of microphone audio
source.stream.pipe(
    # Format audio stream to sliding windows of 5s with a step of 500ms
    dops.rearrange_audio_stream(
        config.duration, config.step, config.sample_rate
    ),
    # Wait until a batch is full
    # The output is a list of audio chunks
    ops.buffer_with_count(count=batch_size),
    # Obtain diarization prediction
    # The output is a list of pairs `(diarization, audio chunk)`
    ops.map(dia),
    # Concatenate 500ms predictions/chunks to form a single 2s chunk
    ops.map(concat),
    # Ignore this chunk if it does not contain speech
    ops.filter(lambda ann_wav: ann_wav[0].get_timeline().duration() > 0),
    # Obtain speaker-aware transcriptions
    # The output is a list of pairs `(speaker: int, caption: str)`
    ops.starmap(asr),
    # Color transcriptions according to the speaker
    # The output is plain text with color references for rich
    ops.map(colorize_transcription),
).subscribe(
    on_next=rich.print,  # print colored text
    on_error=lambda _: traceback.print_exc()  # print stacktrace if error
)

在上述代码中,来自麦克风的所有音频块都将通过我们定义的操作链推送。

在这一系列操作中,我们首先使用 rearrange_audio_stream 将音频格式化为 5 秒钟的小块,小块之间的间隔为 500 毫秒。然后,我们使用 buffer_with_count 填充下一个批次,并应用日记化。请注意,批量大小的定义与转录窗口的大小相匹配。

接下来,我们将批次中不重叠的 500ms 日志化预测连接起来,并应用我们的 WhisperTranscriber,只有在音频包含语音的情况下才能获得说话者感知转录。如果没有检测到语音,我们就跳过这一大块,等待下一块。

最后,我们将使用 rich 库为文本着色并打印到标准输出中。

由于整个操作链可能有点晦涩难懂,我还准备了一个操作示意图,希望能让大家对算法有一个清晰的认识:

1 DTeXXBAuVSESFdemrieV2g

你可能已经注意到,我还没有定义 concat 和 colorize_transcriptions,但它们是非常简单的实用函数:

import numpy as np
from pyannote.core import Annotation, SlidingWindowFeature, SlidingWindow

def concat(chunks, collar=0.05):
    """
    Concatenate predictions and audio
    given a list of `(diarization, waveform)` pairs
    and merge contiguous single-speaker regions
    with pauses shorter than `collar` seconds.
    """
    first_annotation = chunks[0][0]
    first_waveform = chunks[0][1]
    annotation = Annotation(uri=first_annotation.uri)
    data = []
    for ann, wav in chunks:
        annotation.update(ann)
        data.append(wav.data)
    annotation = annotation.support(collar)
    window = SlidingWindow(
        first_waveform.sliding_window.duration,
        first_waveform.sliding_window.step,
        first_waveform.sliding_window.start,
    )
    data = np.concatenate(data, axis=0)
    return annotation, SlidingWindowFeature(data, window)

def colorize_transcription(transcription):
    """
    Unify a speaker-aware transcription represented as
    a list of `(speaker: int, text: str)` pairs
    into a single text colored by speakers.
    """
    colors = 2 * [
        "bright_red", "bright_blue", "bright_green", "orange3", "deep_pink1",
        "yellow2", "magenta", "cyan", "bright_magenta", "dodger_blue2"
    ]
    result = []
    for speaker, text in transcription:
        if speaker == -1:
            # No speakerfound for this text, use default terminal color
            result.append(text)
        else:
            result.append(f"[{colors[speaker]}]{text}")
    return "\n".join(result)

如果您对 pyannote.audio 中使用的 Annotation 和 SlidingWindowFeature 类不熟悉,我建议您查看一下它们的官方文档页面。

在这里,我们使用 SlidingWindowFeature 作为音频块的 numpy 数组封装器,这些音频块还带有 SlidingWindow 实例提供的时间戳。
我们还使用 Annotation 作为首选数据结构来表示日记化预测。它们可被视为包含说话者 ID 以及开始和结束时间戳的片段有序列表。

结论

在这篇文章中,我们将 diart 流媒体扬声器日记库与 OpenAI 的 Whisper 结合起来,以获得实时的扬声器彩色转录。
为了方便起见,作者在 GitHub gist 中提供了完整的脚本。

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.coloradmin.cn/o/1409848.html

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈,一经查实,立即删除!

相关文章

Linux破解密码

破解root密码(Linux 7) 1、先重启——e 2、Linux 16这一行 末尾加rd.break(不要回车)中断加载内核 3、再ctrlx启动,进入救援模式 4、mount -o remount,rw /sysroot/——(mount挂载 o——opti…

PySide6/PyQt6中Qt窗口标志/窗口属性汇总,如何正确的设置窗口标志/窗口属性

文章目录 📖 介绍 📖🏡 环境 🏡📒 使用方法 📒📚 窗口标志汇总📚 窗口属性汇总📝 使用方法📝 注意事项⚓️ 相关链接 ⚓️📖 介绍 📖 在Qt框架中,窗口标志(window flags)是用于控制窗口的各种属性和行为的强大工具。它们通过设置窗口的属性,如边框…

面试知识点:notify是随机唤醒线程吗(唤醒线程顺序)?

做 Java 开发的小伙伴,对 wait 方法和 notify 方法应该都比较熟悉,这两个方法在线程通讯中使用的频率非常高,但对于 notify 方法的唤醒顺序,有很多小伙伴的理解都是错误的,有很多人会认为 notify 是随机唤醒的&#xf…

力扣日记1.25-【回溯算法篇】39. 组合总和

力扣日记:【回溯算法篇】39. 组合总和 日期:2023.1.25 参考:代码随想录、力扣 39. 组合总和 题目描述 难度:中等 给你一个 无重复元素 的整数数组 candidates 和一个目标整数 target ,找出 candidates 中可以使数字和…

完成接口配置实验

步骤一:IP地址规划 步骤二:配置二层设备 生产区内配置设备 办公区内配置设备 配置 DMZ区地址 valn配置 [Huawei]vlan batch 2 3 [Huawei]int g 0/0/2 [Huawei-GigabitEthernet0/0/2]port link-type access [Huawei-GigabitEthernet0/0/2]port…

【数据结构】链表的分类和双向链表

本篇是基于上篇单链表所作,推荐与上篇配合阅读,效果更加 http://t.csdnimg.cn/UhXEj 1.链表的分类 链表的结构非常多样,以下情况组合起来就有8种(2 x 2 x 2)链表结构: 我们一般叫这个头为哨兵位 我们上回…

LeetCode做题总结 226. 翻转二叉树

226. 翻转二叉树 代码1 报错代码2 报错代码3 正确。 代码1 报错 class Solution {public TreeNode invertTree(TreeNode root) {// TreeNode _root root; // 这是在保证// _root preOrderTree(root);// return root;root preOrderTree(root);return root;}public TreeNode …

MATLAB中uibutton函数用法

目录 语法 说明 示例 创建普通按钮 创建状态按钮 设置和访问按钮属性值 编写响应以下操作的代码:点击按钮 uibutton函数的功能是创建普通按钮或状态按钮组件。 语法 btn uibutton btn uibutton(parent) btn uibutton(style) btn uibutton(parent,style…

【方法论】费曼学习方法

费曼学习方法是由诺贝尔物理学奖得主理查德费曼提出的一种学习方法。这种方法强调通过将所学的知识以自己的方式解释给别人来提高学习效果。 费曼学习方法的步骤如下: 选择一个概念:选择一个要学习的概念或主题。 理解和学习:用自己的方式学…

全栈工程师

很多人说,全栈工程师是程序猿的终极目标。过去,笔者也一直往这个方面在努力,前后端代码都能写,测试也做一做,原型也画一画。但随着深耕it行业越来越久,笔者觉得条条大路通罗马,作为骡马的我们&a…

HCIA-HarmonyOS设备开发认证-1.HarmonyOS简介

目录 前言目标一、HarmonyOS简介1.1、初识HarmonyOS1.2、HarmonyOS典型应用场景 二、HarmonyOS架构与安全2.1、HarmonyOS架构2.1.1 内核层2.1.2 系统服务层2.1.3 框架层2.1.4 应用层 2.2、HarmonyOS安全 3、HarmonyOS关键特性4、HarmonyOS生态5、思考题坚持就有收获 前言 本章…

web安全学习笔记【08】——算法1

思维导图在最后 #知识点: 1、Web常规-系统&中间件&数据库&源码等 2、Web其他-前后端&软件&Docker&分配站等 3、Web拓展-CDN&WAF&OSS&反向&负载均衡等 ----------------------------------- 1、APP架构-封装&原生态&…

大语言模型推理提速:TensorRT-LLM 高性能推理实践

作者:顾静 TensorRT-LLM 如何提升 LLM 模型推理效率 大型语言模型(Large language models,LLM)是基于大量数据进行预训练的超大型深度学习模型。底层转换器是一组神经网络,这些神经网络由具有 self-attention 的编码器和解码器组…

Docker安装Clickhouse详细教程

简介 ClickHouse是一种列式数据库管理系统,专门用于高性能数据分析和数据仓库应用。它是一个开源的数据库系统,最初由俄罗斯搜索引擎公司Yandex开发,用于满足大规模数据分析和报告的需求。 特点 开源的列式存储数据库管理系统,…

好书推荐丨豆瓣评出9.2高分!Python编程入门就看蟒蛇书

目录 写在前面 内容简介 业内专家推荐 编辑推荐 资源丰富 作者介绍 Q&A 粉丝福利 写在后面 写在前面 在这日新月异的科技新时代,编程如同一把万能钥匙,为无数人打开了通向无限可能的大门。而在众多编程语言中,Python无疑是最耀…

工业阀门3D数字化展厅降低投入成本

大型机械设备的销售面临诸多挑战,其中最突出的问题之一是展示区域的限制。与家用小汽车不同,大型机械设备的展示需要大面积的场地,同时还需要展示各种型号和功能,这使得在一个城市内实现网格布点覆盖整座城市的难度加大。 为了解决…

DataStream API(转换算子)

目录 源算子 转换算子 1,基本转换算子 1.1映射(map) 1.2过滤(filter) 1.3扁平映射(flatMap) 2,聚合算子 2.1按键分区(keyBy) 2.2简单聚合 3&#x…

医学图像跨模态转换:配准方法生成对图 + 扩散模型 + 成对配对方法

医学图像跨模态转换:配准方法生成对图 扩散模型 成对配对方法 MRI 到 CT 的高精度转换X 光、核磁共振 MRI、CT 区别最关键的配准方法读后启发:Fundus 转 OCT (只是猜想,不一定) 数据图像预处理5 个图像转换算法评估图…

数据建模分析与数仓维度建模规范

前言:数据建模是将定义现实世界的数据抽象成模型的过程,以便更好的分析,管理和操作数据实现需求。数据建模在数仓管理、数据库设计、算法模型训练等领域都有着重要的作用。在不同的领域,数据建模的关注点不同:在数据库…

K8S搭建(centos)七、节点加入集群

天行健,君子以自强不息;地势坤,君子以厚德载物。 每个人都有惰性,但不断学习是好好生活的根本,共勉! 文章均为学习整理笔记,分享记录为主,如有错误请指正,共同学习进步。…