AI 文摘

自动语音识别与翻译大模型微调实践:Whisper微调及推理加速





作者: 老刘说NLP 来源: 老刘说NLP

OpenAI 开源的 Whisper 项目号称英文语音识别能力已达到人类水平,且支持其它 98 种语言的自动语音辨识。Whisper 所提供的自动语音识与翻译任务,它们能将各种语言的语音变成文本,也能将这些文本翻译成英文。本篇文章主要的目的是为了对 Whisper 模型使用 Lora 进行微调,还支持 CTranslate2 加速推理。

一、基础环境

二、模型选型

三、安装环境

四、准备SFT数据

五、微调模型

五、合并模型

七、评估模型

八、推理预测

九、推理加速

十、Web界面部署

# 今日AI资讯看点

一、基础环境

  • GPU A100 40GB * 1

  • Ubuntu 18.04

  • Python 3.8

  • Miniconda 3

  • Pytorch 1.13.1

二、模型选型

本次微调期望看到 Whisper 最优性能,所以我选用的版本是 openai/whisper-large-v2 。当然您使用浅尝的话可以选以下几个版本:

  • openai/whisper-tiny

  • openai/whisper-base

  • openai/whisper-small

  • openai/whisper-medium

三、安装环境

  • 首先安装的是 Pytorch 的 GPU 版本,以下介绍两种安装 Pytorch 的方式,只需要选择一种即可。使用 miniconda 安装 Pytorch 环境。如已安装,请跳过。

    conda install pytorch==1.13.1 torchvision==0.14.1 torchaudio==0.13.1 pytorch-cuda=11.6 -c pytorch -c nvidia

  • 安装所需的依赖库

    python -m pip install -r requirements.txt -i https://pypi.tuna.tsinghua.edu.cn/simple

四、准备 SFT 数据

** SFT 数据集** 是一个 jsonlines 的数据列表,也就是每一行都是一个 JSON 数据,数据格式如下。数据集下载地址:https://openslr.elda.org/resources/33/data_aishell.tgz ,包括训练集和测试集。

{  
   "audio": {  
      "path": "dataset/jiaenqiu.wav"  
   },  
   "sentence": "我写了一封长长的情书,填对了地址,却选错了时间。",  
   "language": "Chinese",  
   "sentences": [  
      {  
         "start": 0,  
         "end": 2.38,  
         "text": "我写了一封长长的情书,"  
      },  
      {  
         "start": 2.4,  
         "end": 5.4,  
         "text": "填对了地址,却选错了时间。"  
      }  
   ],  
   "duration": 5.4  
}  

注意:

  • 如果不使用时间戳训练,可以不包含 sentences 字段数据。
  • 如果只有一种语言的数据,可以不包含 language 字段数据。
  • 如果训练空语音数据,sentences 字段为[],sentence 字段为"",language 字段可以不存在。
  • 数据可以不包含标点符号,但微调后模型会损失添加符号能力。

五、微调模型

准备好数据之后,就可以开始微调模型了。训练最重要的两个参数分别是:

  • –base_model 指定微调的 Whisper 模型路径。我是提前在 HuggingFace 下载,使用参数指定本地路径,同时 –local_files_only 设置为 True。

  • –output_path 是训练时保存的 Lora 检查点路径。因为我们使用 Lora 微调模型。如果显存足够,最好将 –use_8bit 设置为 False,这样训练速度快很多。
    其他更多的参数请查看脚本 finetune.py。

单卡训练

单卡训练命令如下,Windows 系统可以不添加 CUDA_VISIBLE_DEVICES 参数。

CUDA_VISIBLE_DEVICES=0 python finetune.py --base_model=openai/whisper-large-v2 --local_files_only=True --output_dir=output/ --train_data=dataset/train_2.json --test_data=dataset/test_2.json  

输出日志如下:

{'loss': 0.9098, 'learning_rate': 0.000999046843662503, 'epoch': 0.01}  
{'loss': 0.5898, 'learning_rate': 0.0009970611012927184, 'epoch': 0.01}  
{'loss': 0.5583, 'learning_rate': 0.0009950753589229333, 'epoch': 0.02}  
{'loss': 0.5469, 'learning_rate': 0.0009930896165531485, 'epoch': 0.02}  
{'loss': 0.5959, 'learning_rate': 0.0009911038741833634, 'epoch': 0.03}  
------------------------------------------------  
训练数据:13,4424,测试数据:7176  
===============================================================  
trainable params: 81,1776 || all params: 3857,2480 || trainable%: 2.1045470760500753  
===============================================================  

finetune.py 部分代码

add_arg("train_data",    type=str, default="dataset/train.json",       help="训练数据集的路径")  
add_arg("test_data",     type=str, default="dataset/test.json",        help="测试数据集的路径")  
add_arg("base_model",    type=str, default="openai/whisper-tiny",      help="Whisper的基础模型")  
add_arg("output_dir",    type=str, default="output/",                  help="训练保存模型的路径")  
add_arg("warmup_steps",  type=int, default=50,      help="训练预热步数")  
add_arg("logging_steps", type=int, default=100,     help="打印日志步数")  
add_arg("eval_steps",    type=int, default=1000,    help="多少步数评估一次")  
add_arg("save_steps",    type=int, default=1000,    help="多少步数保存模型一次")  
add_arg("num_workers",   type=int, default=8,       help="读取数据的线程数量")  
add_arg("learning_rate", type=float, default=1e-3,  help="学习率大小")  
add_arg("min_audio_len", type=float, default=0.5,   help="最小的音频长度,单位秒")  
add_arg("max_audio_len", type=float, default=30,    help="最大的音频长度,单位秒")  
add_arg("use_adalora",   type=bool,  default=True,  help="是否使用AdaLora而不是Lora")  
add_arg("fp16",          type=bool,  default=True,  help="是否使用fp16训练模型")  
add_arg("use_8bit",      type=bool,  default=False, help="是否将模型量化为8位")  
add_arg("timestamps",    type=bool,  default=False, help="训练时是否使用时间戳数据")  
add_arg("local_files_only", type=bool, default=False, help="是否只在本地加载模型,不尝试下载")  
add_arg("num_train_epochs", type=int, default=3,      help="训练的轮数")  
add_arg("language",      type=str, default="Chinese", help="设置语言,可全称也可简写,如果为None则训练的是多语言")  
add_arg("task",     type=str, default="transcribe", choices=['transcribe', 'translate'], help="模型的任务")  
add_arg("augment_config_path",         type=str, default=None, help="数据增强配置文件路径")  
add_arg("resume_from_checkpoint",      type=str, default=None, help="恢复训练的检查点路径")  
add_arg("per_device_train_batch_size", type=int, default=8,    help="训练的batch size")  
add_arg("per_device_eval_batch_size",  type=int, default=8,    help="评估的batch size")  
add_arg("gradient_accumulation_steps", type=int, default=1,    help="梯度累积步数")  
  
# 获取Whisper的数据处理器,这个包含了特征提取器、tokenizer  
processor = WhisperProcessor.from_pretrained(args.base_model,  
                                             language=args.language,  
                                             task=args.task,  
                                             no_timestamps=not args.timestamps,  
                                             local_files_only=args.local_files_only)  
  
# 读取数据  
train_dataset = CustomDataset(data_list_path=args.train_data,  
                              processor=processor,  
                              language=args.language,  
                              timestamps=args.timestamps,  
                              min_duration=args.min_audio_len,  
                              max_duration=args.max_audio_len,  
                              augment_config_path=args.augment_config_path)  
test_dataset = CustomDataset(data_list_path=args.test_data,  
                             processor=processor,  
                             language=args.language,  
                             timestamps=args.timestamps,  
                             min_duration=args.min_audio_len,  
                             max_duration=args.max_audio_len)  
print(f"训练数据:{len(train_dataset)},测试数据:{len(test_dataset)}")  
  
# 获取Whisper模型  
model = WhisperForConditionalGeneration.from_pretrained(args.base_model,  
                                                        load_in_8bit=args.use_8bit,  
                                                        device_map=device_map,  
                                                        local_files_only=args.local_files_only)  
  
# 量化模型  
model = prepare_model_for_kbit_training(model)  
  
print('加载LoRA模块...')  
if args.resume_from_checkpoint:  
    print(f'adding LoRA modules...')  
    target_modules = ["k_proj", "q_proj", "v_proj", "out_proj", "fc1", "fc2"]  
    print(target_modules)  
    config = AdaLoraConfig(init_r=12, target_r=4, beta1=0.85, beta2=0.85, tinit=200, tfinal=1000, deltaT=10,  
                               lora_alpha=32, lora_dropout=0.1, orth_reg_weight=0.5, target_modules=target_modules)  
    model = get_peft_model(model, config)  
output_dir = os.path.join(args.output_dir, os.path.basename(args.base_model))  
  
# 定义训练参数  
training_args = \  
    Seq2SeqTrainingArguments(output_dir=output_dir,  # 保存检查点和意志的目录  
                             per_device_train_batch_size=args.per_device_train_batch_size,  # 训练batch_size大小  
                             per_device_eval_batch_size=args.per_device_eval_batch_size,  # 评估batch_size大小  
                             gradient_accumulation_steps=args.gradient_accumulation_steps,  # 训练梯度累计步数  
                             learning_rate=args.learning_rate,  # 学习率大小  
                             warmup_steps=args.warmup_steps,  # 预热步数  
                             num_train_epochs=args.num_train_epochs,  # 微调训练轮数  
                             save_strategy="steps",  # 指定按照步数保存检查点  
                             evaluation_strategy="steps",  # 指定按照步数评估模型  
                             load_best_model_at_end=True,  # 指定是否在结束时加载最优模型  
                             fp16=args.fp16,  # 是否使用半精度训练  
                             report_to=["tensorboard"],  # 指定使用tensorboard保存log  
                             save_steps=args.save_steps,  # 指定保存检查点的步数  
                             eval_steps=args.eval_steps,  # 指定评估模型的步数  
                             save_total_limit=5,  # 只保存最新检查点的数量  
                             optim='adamw_torch',  # 指定优化方法  
                             ddp_find_unused_parameters=False if ddp else None,  # 分布式训练设置  
                             dataloader_num_workers=args.num_workers,  # 设置读取数据的线程数量  
                             logging_steps=args.logging_steps,  # 指定打印log的步数  
                             remove_unused_columns=False,  # 删除模型不需要的数据列  
                             label_names=["labels"])  # 与标签对应的输入字典中的键列表  
  
# 开始训练  
trainer.train(resume_from_checkpoint=args.resume_from_checkpoint)  
  
# 保存最后的模型  
trainer.save_state()  
if training_args.local_rank == 0 or training_args.local_rank == -1:  
    model.save_pretrained(os.path.join(output_dir, "checkpoint-final"))  

六、合并模型

微调完成之后会有两个模型,第一个是 Whisper 基础模型,第二个是 Lora 模型,需要把这两个模型合并之后才能之后的操作。两个参数:

  • –lora_model 是训练结束后保存的 Lora 模型路径,就是检查点文件夹路径

  • –output_dir 是合并后模型的保存目录

    python merge_lora.py –lora_model=output/whisper-large-v2/checkpoint-best/ –output_dir=models/

七、评估模型

执行以下程序进行评估模型,最重要的两个参数分别是。

  • –model_path 是合并后的模型路径,同时也支持直接使用 Whisper 原模型,例如直接指定 openai/whisper-large-v2

  • –metric 是评估方法,例如:字错率 cer 和词错率 wer。

    python evaluation.py –model_path=models/whisper-large-v2-finetune –metric=wer

  • 提示:没有微调的模型,可能输出带有标点符号,影响准确率

知识拓展

在 LLM 评估中,CER(Character Error Rate)和 WER(Word Error Rate)是两个常用的指标,用于评估文本生成任务中的错误率。

  • CER(Character Error Rate)是用于衡量生成文本中字符级别错误的指标。它计算生成文本与参考文本之间的字符差异率,表示为错误字符的数量除以参考文本的总字符数。CER 的值越低,表示生成文本与参考文本越接近。

  • WER(Word Error Rate)是用于衡量生成文本中词级别错误的指标。它计算生成文本与参考文本之间的词差异率,表示为错误词的数量除以参考文本的总词数。WER 的值越低,表示生成文本与参考文本越接近。

  • 中文一般用 CER 来表示字错率,原因:计算的时候谁不是按:中文的一个字符 = 英文的一个 Word。英文,因为最小单元是 Word,语音识别应该用"词错误率"(WER);中文,因为最小单元是字符,语音识别应该用“字符错误率”(CER)。

二者区别

  • CER 是在字符级别上评估错误率,而 WER 是在词级别上评估错误率。

  • CER 关注的是字符的插入、删除和替换错误,而 WER 关注的是词的插入、删除和替换错误。

  • CER 更加细粒度,能够捕捉到字符级别的错误,而 WER 更加高层次,考虑了词级别的错误。

  • CER 和 WER 能够反映生成文本与参考文本之间的差异和错误。它们是评估文本生成质量的重要指标,常用于语音识别、机器翻译等任务的性能评估,可以帮助量化系统的准确性和错误率。一般来说,较低的 CER 和 WER 值表示生成的文本更接近参考文本,表示系统性能更好。

八、推理预测

执行以下程序进行语音识别,使用 transformers 直接调用微调后的模型或 Whisper 原模型预测,只适合推理短音频,长语音还是参考 infer_ct2.py 方式。

  • –audio_path 指定的是要预测的音频路径

  • –model_path 指定的是合并后的模型路径,同时也支持直接使用 Whisper 原模型,例如直接指定 openai/whisper-large-v2

    python infer_tfs.py –audio_path=dataset/jiaenqiu.wav –model_path=models/whisper-large-v2-finetune
    python infer_ct2.py –audio_path=dataset/long.wav –model_path=models/whisper-large-v2-finetune

九、推理加速

尝试过直接使用 Whisper 模型推理是比较慢的,所以提供了推理加速。主要是使用 CTranslate2 进行加速,首先要转换模型,把合并后的模型转换为 CTranslate2 模型。

ct2-transformers-converter --model models/whisper-large-v2-finetune --output_dir models/whisper-large-v2-finetune-ct2 --copy_files tokenizer.json --quantization float16  
  • –model 参数指定的是合并后的模型路径,同时也支持直接使用 Whisper 原模型,例如直接指定 openai/whisper-large-v2

  • –output_dir 参数指定的是转换后的 CTranslate2 模型路径

  • –quantization 参数指定的是量化模型大小,不希望量化模型可直接去掉此参数

    python infer_ct2.py –audio_path=dataset/jiaenqiu.wav –model_path=models/whisper-large-v2-finetune-ct2

  • –audio_path 参数指定的是要预测的音频路径

  • –model_path 指定的是转换后的 CTranslate2 模型 输出结果:

    ———–  Configuration Arguments ———–
    audio_path: dataset/jiaenqiu.wav
    model_path: models/whisper-large-v2-finetune-ct2
    language: zh
    use_gpu: True
    use_int8: False
    beam_size: 10
    num_workers: 1
    vad_filter: False
    local_files_only: True

    [0.0 - 5.2]:我写了一封长长的情书,填对了地址,却选错了时间。

十、Web 界面部署

启动命令:

python infer_server.py --model_path=models/whisper-large-v2-finetune-ct2  
  • Web界面地址
    http://127.0.0.1:5000

引用:https://github.com/yeyupiaoling

后续我会对微调前后的模型做一次全面性能测试,敬请关注➕

今日AI资讯 2023.08.10 星期四 六月廿四日

  • 奇虎360智脑4.0整体能力1个月提升近15%

  • 奇虎360与8家企业签署大模型战略应用合作协议

  • 面向大型语言模型多维基准测试工具AgentBench

  • 福布斯发布云计算100强榜单,OpenAI第一

  • 旷视FaceID人身核验引入视觉大模型

  • 科大讯飞与电信签署协议:展开大模型、云计算等业务合作

  • 中国电信柯瑞文:正在深入研究通用大模型 下半年会看到成果

  • Spotify:向更多地区推出AI DJ功能

欢迎关注,一起进步一起成长

更多AI工具,参考Github-AiBard123国内AiBard123

可关注我们的公众号:每天AI新工具