使用whisper生成字幕的经验备忘

  whisper是openai发布的模型,主要用于语音转写。我最近尝试了用它来听写并生成字幕,主要的需求显然是文字准确,不漏记也不多记(多记显然更不能接受,下文会提到幻觉问题);次要的需求是:每行字幕不应该太长,字幕时间戳应该尽量准确。本文是截至2024年4月的一些知识和经验备忘,是一篇“综述”,不是一篇详尽的教程。本文中对参数的解释大多基于作者给出的解释或本人对相关工具代码的阅读和理解,但对参数调整的意见就纯粹基于个人的感觉了,不一定有科学依据。

相关术语

  • ASR, Automatic Speech Recognition, also known as Speech To Text (STT), refers to the problem of automatically transcribing spoken language, 这解释来自Nvidia。我们在使用whisper的过程中,主要使用的就是ASR功能。在具体的工具中,该部分任务或功能一般被称为transcribe
  • VAD, Voice Activity Detection, 检测语音(或者说反过来,检测非语音),标出语音部分供模型进行下一步推理。个人认为第三方工具针对whisper的增强主要体现在推理速度和VAD两方面,我们几乎所有的参数调整也集中针对于VAD。
  • Hallucination, the experience of seeing, hearing, feeling, or smelling something that does not exist, 用过chatGPT的你应该很熟悉了,幻觉就是模型在一本正经地胡说八道。在ASR用途的模型中,幻觉主要体现为转写了根本不存在的语句,而不是字词意义上的识别错误。正因为这样,幻觉在生成字幕的任务中危害非常大,因为字词错误观众可以推测、可以忽略,完全不存在的台词则严重影响对内容的理解。whisper最常出现的典型幻觉是,它会在字幕的结尾加上“谢谢观看”或者类似意思的语句。
  • Beam search, a heuristic search algorithm that explores a graph by expanding the most promising node in a limited set, 这解释来自维基百科。在whisper的使用中,有两种设置路径:一种是使用whisper的beam_sizepatience等参数来进行beam search,另一种是使用temperature best_of compression_ratio_thresholdlog_prob_threshold等参数来进行取样。
  • diarization, Speaker diarization is the process of segmenting audio recordings by speaker labels and aims to answer the question “who spoke when?”, 还是来自Nvidia的解释。whisper模型没有这个功能,各种工具都承认他们自己的实现不能算完美。理论上这能实现更准确的字幕断句,但本身并不是生成字幕的强需求,我没有深入研究。
  • WER, word error rates, ASR模型的能力相对比较容易考核,这就是最常用的指标,另一个常用的指标是CER即character error rates)。

模型

  openai官方提供了各种大小的模型,就我们非实时生成字幕的用途来说,只有large模型有意义(最新的是large-v3)。

  huggingface上有很多在whisper基础上调优的模型,我尝试下来日语的clu-ling/whisper-large-v2-japanese-5k-steps还不错,但记得基于CTranslate2的推理工具需要转换后的CT2模型,有人帮你做好了。这个模型的缺陷是在某些情况下转写内容可能缺少标点符号,本文的速度测试中的样本在某些推理工具中就会这样,暂时不知道原因。另一个日语模型vumichien/whisper-large-v2-mix-jp就会有标点。

  作为字幕,我们希望转写文本既准确又简短,要注意不同的模型在推理工具相同的参数下转写的长度不同,这里不是指遣词造句的风格不同导致句子长度不同,而是换行(换一条新字幕)的频率不同。虽然这一点和下文提到的VAD关系最大,但根据我的经验,模型也会有影响。

推理工具

  以下工具全都可以顺利在WSL2中运行,记得如果你需要在WSL2中安装cuda-tools或cudnn之类的玩意,请遵守Nvidia的指导,简单来说就是不要安装linux驱动(WSL2里面的显卡驱动是windows驱动提供的),安装那些不含驱动的包就好,建议用conda来做这些工作。

官方工具 openai/whisper

  openai/whisper是openai官方提供的工具,按照官方的介绍以及no_speech_threshold这个参数,这个工具会使用whisper自己的VAD功能,个人觉得跟下文中的第三方工具使用的VAD方法而言,只能说聊胜于无(第三方工具几乎全都引入了自己的VAD,说明他们也是这么想的)。这个工具不能调用非官方的whisper模型,推理速度也没有任何优势。

主流后端 SYSTRAN/faster-whisper 及其命令行套壳 Softcatala/whisper-ctranslate2

  SYSTRAN/faster-whisper是很主流的whisper推理工具,它不仅不提供图形界面,而且也不提供命令行接口,只能作为一个Python库被调用,幸好有人做了一个命令行套壳Softcatala/whisper-ctranslate2,节省很多工作量。faster-whisper的重要优势在于:

  • 使用了第三方VAD方案silero-vad,效果明显、可调参数多(部分不是silero-vad的参数,而是faster-whisper给VAD功能添加的参数,下文会提到,很有用处)。通过VAD将音频提前切断,只将语音部分提供给whisper做转写,大幅度降低了whisper的幻觉,(在某些情况下大幅度)提高了转写的速度。
  • 可以加载非官方whisper模型,有些基于whisper调优的模型对特定语言的转写能力提升很大。
  • 使用CTranslate2进行推理,推理速度明显快(数倍)于官方工具。但是需要使用兼容CTranslate2的模型,hugging face上标为ct2的模型就是这类,其他模型也可以通过脚本转换为ct2模型。

  我个人主要使用的就是Softcatala/whisper-ctranslate2,需要注意的参数主要包括(未特别说明独有的参数,在官方工具中也有):

  • length_penalty, 这是whisper模型提供的参数,并不硬性对单行字幕长度产生影响,只会在推理时更倾向于选择产生较短的句子,影响转写质量,不建议修改。
  • no_speech_threshold, whisper自己的VAD设置,值越低,越偏向于认定一段声音为沉默(即不是语音)。
  • word_timestamps, 是否提供基于词的时间戳,这个选项实际上不会导致输出的字幕文件是每行一个词的,只是在推理过程中对词标出了时间戳,生成字幕文件的时候还是会合并成每行一句话,这有利于控制单行字幕长度,也可以用于在字幕中标出当前发音的词。但是whisper提供的时间戳实际上是不准的,下文会提到。
  • repetition_penalty, 软性要求whisper在推理时降低重复,个人感觉不是很好用,设置为1.2以上的时候有效果,但会影响转写内容(即使不存在重复,语句内容也会随着这个参数而发生变化)。这是ctranslate2的功能,不是whisper模型实现的,当然在官方工具中也就没有这个参数。
  • no_repeat_ngram_size, 在一行结果中没有重复的如此规模的“语丝”,按理说在设为2的时候应该不会出现任何重复的字/词,但实际上效果并不理想,个人觉得不是很好用,且会影响转写内容,建议自己尝试效果。这是ctranslate2的功能,不是whisper模型实现的,官方工具不包含这个参数。
  • max_words_per_line, 基于上述对每个词打时间戳的功能,可以限制每行字幕的长度,这不是whisper提供的功能,是推理工具在写入字幕文件时进行的处理。但是whisper-ctranslate2和官方工具一样,实现的方法非常粗暴,就是严格按照词数截断字幕,不能按照标点或空格合理截断。有个PR试图在一定程度上解决这个问题,但还没有被合并。
  • vad_threshold, 这是faster-whisper自带的silero-vad的参数,官方工具没有。这个值越高,越偏向于认定一段声音为沉默(与no_speech_threshold的方向相反)。对于字正腔圆的样本(例如新闻或动漫),建议设为默认的0.5;对于偏向生活化、口语化的样本,建议设为0.4乃至0.35(更低也可以尝试)。由于这个VAD工作于whisper推理之前,所以这个值越高,判断出的沉默越多、语音越少,whisper的工作内容越少、推理速度越快。
  • vad_min_speech_duration_ms, 硬性抛弃短于多少时间的片段,即使VAD认为它是语音。这不是whisper实现的功能,是推理工具检查VAD结果时进行的处理。如果样本中有大量短促无意义的语音或类似于语音的声音,不想让它们毫无意义地出现在字幕中,这个功能非常有用。faster-whisper和whisper-ctranslate2暴露了这个参数,其他工具大多没有这个参数(没有进行这样的处理,或者硬编码了一个作者喜欢的值)。
  • vad_max_speech_duration_s, 规定交给whipser的一段语音的最大长度,有助于减少字幕中出现过长的句子。考虑到上述max_words_per_line参数非常粗暴从而用途有限,这个参数挺有用的。我尝试使用faster-whisper的chunk_length参数(硬性决定交给whisper的分段长度),但结果是导致了转写结果完全不正确;我尝试将faster-whisper的vad_speech_pad_ms参数设置成0,也没有取得类似于whisperX的效果(完全没有效果)。因此基于vad的这个参数是非常有必要的。这也是官方工具没有的参数。
  • vad_min_silence_duration_ms, 硬性规定短于多少时间的沉默不会被切除,即使VAD认为它是沉默。这同样不是whisper的功能,是推理工具进行的处理,对于长句过多的结果,将这个值设为0有奇效(只要喘气就算一段新的语音,降低输入whisper模型的segment长度)。官方工具显然不会有这个参数。

超快工具 m-bain/whisperX

  m-bain/whisperX这个工具标榜自己的快,虽然其使用faster-whisper作为后端,但它并不是纯粹的faster-whisper套壳。它用有点奇怪的方法实现了单个音频样本的分片批量处理(让faster-whisper社区感到困惑);由于批量处理,不能在转写过程中显示结果,只能以百分比方式显示进度;由于批量处理,它不适合打开condition_on_prev_text参数,因此可能降低转写内容的稳定性。它虽然使用了faster-whisper,但不使用faster-whisper的VAD功能,而是使用了作者提供的VAD模型(实际上可能是pyannote/segmentation),这个模型比faster-whisper的VAD模型大差不多10倍,可能效果也更好(实测是的)。它在whisper之外,引入facebook的wav2vec用来辅助生成精确的时间戳,但这功能只能在部分主流语言上实现(因为需要对应语言的wav2vec模型,第三方调优的也可以),而且对数字的处理不佳。因为它它暴露的VAD参数很少,特别是推理工具实现而非模型实现的那些参数(例如faster-whisper的vad_min_speech_duration_ms);即使是暴露的那些参数,也并不是在VAD流程的每个阶段都使用用户指定的值,有些阶段是硬编码的。总而言之,这个项目的速度确实是优势(虽然在我们的测试中不明显,更长的、VAD不容易处理好的样本会明显得多,特别是保持chunk_size为默认值而不在乎出现长句的情况下,速度数倍于faster-whisper是很正常的),但其代码质量不太好评价。

  我使用该工具较少,可以介绍的参数主要包括:

  • vad_onset, 基本等于faster-whisper的vad_threshold,但两者VAD模型不同,数值需要自己摸索。
  • vad_offset, 猜测等于faster-whisper中的neg_threshold(代码中的变量,没有暴露为命令行参数),faster-whisper硬编码为vad_threshold减去0.15,whisperX的默认值是vad_onset减去0.137。
  • chunk_size, 这个参数不是提供给faster-whisper的chunk_length, 而是提供给VAD的,类似于whisper-ctranslate2的vad_max_speech_duration_s, 但降低长句的效果比whisper-ctranslate2好得多,可能得益于更好的VAD模型或更好的处理方式(winsperX的作者的论文中有关于这个VAD Cut & Merge的描述)

精确时间轴 linto-ai/whisper-timestamped

  linto-ai/whisper-timestamped介绍了whisper为什么不能提供准确的时间戳,也介绍了其他提供准确时间戳方案的局限性(主要是whisperX使用wav2vec的方案),强调自己能够在不借助辅助模型的情况下实现精准时间戳,我觉得这个项目的README价值比工具更大。它提供三种不同的VAD方案供选择,但没有暴露任何VAD参数。它可以使用第三方模型,但不要被他的说明骗了,使用第三方模型不需要加--backend transformers参数(加了跑不起来)。我没有测试过其时间戳是否真的比别的工具精准,你可以自己试试看。它不是一个基于faster-whisper的工具,意味着它的速度不会是优势。

ggerganov/whisper.cpp

  whisper.cpp是另一个主流工具(31.1K star),设计之初致力于在CPU上高效推理,没看出其在GPU上有特殊优势,而且没有附加的VAD功能,因此我没有实际用过,你可以试试看。

速度测试

  使用的样本是[SFEO-Raws] Koimonogatari - 01 (BD 720P x264 10bit AACx2)[9369B674].mp4即恋物语第一集的音频,长度为26m34s,转换为16000hz的wav(这对于VAD很重要,因为VAD模型大多是基于8K和16Khz训练的)。所有的运行时间都是time命令的real项。

以下各行的命令分别为:
whisperx --model whisper-large-v2-japanese --device cuda --output_format srt --language ja --vad_onset 0.5 --vad_offset 0.35 --no_speech_threshold 0.3 --print_progress True output.wav
whisperx --model whisper-large-v2-japanese --device cuda --output_format srt --language ja --vad_onset 0.5 --vad_offset 0.35 --chunk_size 10 --no_speech_threshold 0.3 --print_progress True output.wav
whisper-ctranslate2 --device cuda --compute_type float16 --model_directory ./whisper-large-v2-japanese --language ja --local_files_only true --output_format srt --word_timestamps true --hallucination_silence_threshold 5 --beam_size 5 --vad_filter true --vad_threshold 0.5 --vad_min_silence_duration_ms 0 --vad_min_speech_duration_ms 100 --vad_max_speech_duration_s 10 --no_speech_threshold 0.3 output.wav
whisper-ctranslate2 --device cuda --compute_type float16 --model_directory ./whisper-large-v2-japanese --language ja --local_files_only true --output_format srt --word_timestamps true --hallucination_silence_threshold 5 --beam_size 5 --vad_filter true --vad_threshold 0.5 --vad_min_silence_duration_ms 0 --vad_max_speech_duration_s 10 --no_speech_threshold 0.3 output.wav
whisper_timestamped --model clu-ling/whisper-large-v2-japanese-5k-steps --device cuda --output_dir . --output_format srt --language ja --vad True --no_speech_threshold 0.3 --verbose True --accurate
whisper_timestamped --model clu-ling/whisper-large-v2-japanese-5k-steps --device cuda --output_dir . --output_format srt --language ja --vad silero:3.1 --no_speech_threshold 0.3 --verbose True --accurate output.wav
序号 工具 时间 字幕条数 备注
0 whisperX 1m21.291s 57 全是长句,但速度确实快
1 whisperX 1m32.823s 176 句子普遍较短,可能得益于更好的VAD模型,但有些句子反而非常长
2 whisper-ctranslate2 3m6.634s 241 句子长短与上一栏相比各有千秋,效果均不完美
3 whisper-ctranslate2 3m7.070s 184 可能是物语系列讲话太疯狂的原因,去掉vad_min_speech_duration_ms会加强转写准确性,但长句更多了……
4 whisper_timestamped 14m59.857s 207 看到这感人的时间了吧,另外结果中的长句和准确性都不太理想,可能是我没有充分调试
5 whisper_timestamped 12m35.261s 208 换了vad速度快一点,句子长度也好一点

字幕翻译及其他后处理

  对于减少字幕中长句的需求,目前看来最好的方法不是在上面提到的工具中折腾chunk的长度,而是直接用非常短的字词长度硬性截断字幕(使用max_words_per_line之类的参数),再通过对字幕文件进行后处理,将字幕按照标点、空格等分隔符拼接起来。根据whisper的工作原理,这个行为不能反过来——后处理的时候不知道每个字词的时间戳,因此不能生成长句再拆分。但这条路还没有走通,虽然有人做了类似思路的小工具,但他的设计目的是合并过短的字幕,通过初步阅读代码,我觉得简单修改这个工具也达不成我们的目的。

  对于字幕的翻译,一股脑发给AI的效果会很差,AI还会评价剧情甚至因为(它认为的)色情暴力之类而拒绝翻译。因此一股脑丢给google翻译/deepl之类的翻译引擎可能是比较简单的选择,而一句句发给AI可能是效果较好的方案。我还没有找到最合适的工具。

  可能会尝试自己写一个工具来做以上这两件事,就不在本文中介绍了。