如何使用 Nvidia NeMo 微调 Riva NMT 双语模型#

本教程将引导您完成如何使用 Nvidia NeMo 微调 Riva NMT 双语模型。

NVIDIA Riva 概述#

NVIDIA Riva 是一个 GPU 加速的 SDK,用于构建针对您的用例定制并提供实时性能的语音 AI 应用程序。
Riva 提供了一系列丰富的语音和自然语言理解服务,例如

  • 自动语音识别 (ASR)

  • 文本到语音合成 (TTS)

  • 神经机器翻译 (NMT)

  • 一系列自然语言处理 (NLP) 服务,例如命名实体识别 (NER)、标点和意图分类。

在本教程中,我们将使用 Nvidia NeMo 微调 Riva NMT 双语模型。
要了解 Riva NMT API 的基础知识,请参阅 Riva NMT 教程 中的“如何将 Riva NMT API 与开箱即用模型一起使用?”教程。

有关 Riva 的更多信息,请参阅 Riva 开发者文档
有关 Riva NMT 的更多信息,请参阅 Riva NMT 文档

NVIDIA NeMo 概述#

NVIDIA NeMo 是一个用于构建新的最先进的对话式 AI 模型的工具包。 NeMo 为自动语音识别 (ASR)、自然语言处理 (NLP) 和文本到语音 (TTS) 模型提供单独的集合。每个集合都包含预构建模块,其中包括在您的数据上进行训练所需的一切。每个模块都可以轻松定制、扩展和组合,以创建新的对话式 AI 模型架构。

有关 NeMo 的更多信息,请参阅 NeMo 产品页面文档。开源 NeMo 仓库可以在 此处 找到。

使用 NVIDIA NeMo 微调 Riva NMT 双语模型#

在本教程中,我们将使用 Scielo 英西数据集 微调 Riva NMT 英西双语模型。

本教程仅涵盖微调 NMT 双语模型。微调多语言模型是一项相对更具挑战性的任务(例如选择涵盖多种语言的平衡数据集),涵盖它的教程将在未来的版本中发布。

这里的微调过程可以分为四个步骤

  1. 数据下载。

  2. 数据预处理。

  3. 使用 NeMo 微调 NMT 模型。

  4. 使用 NeMo 评估微调后的 NMT 模型。

  5. 导出 NeMo 模型

  6. 在 Riva 语音技能服务器上部署微调后的 NeMo NMT 模型。

让我们详细了解这些步骤中的每一个步骤。

要求和设置#

本教程需要在 NeMo docker 容器内运行。如果您不是通过 NeMo docker 容器运行本教程,请参阅 Riva NMT 教程README.md 以开始。

在我们深入了解要求和设置之前,让我们为我们这里的工作创建一个基本目录。

base_dir = "NMTFinetuning"
!mkdir $base_dir
  1. 克隆 NeMo github 仓库

NeMoBranch = "main"
!git clone -b $NeMoBranch https://github.com/NVIDIA/NeMo $base_dir/NeMo

检查 CUDA 安装。

import torch
torch.cuda.is_available()
警告:您可能需要安装 `apex`。
!git clone https://github.com/ericharper/apex.git
!cd apex
!git checkout nm_v1.15.0
!pip install -v --disable-pip-version-check --no-cache-dir --global-option="--cpp_ext" --global-option="--cuda_ext" --global-option="--fast_layer_norm" --global-option="--distributed_adam" --global-option="--deprecated_fused_adam" ./
  1. Riva 快速入门指南 安装 nemo2riva 库。

# Install the `nemo2riva` library
!python3 -m pip install nemo2riva
  1. 安装本教程所需的其他库。

!python3 -m pip install scikit-learn

步骤 1. 数据下载#

让我们下载 Scielo 英西数据集。具体来说,我们将下载 Moses 版本的数据集,该数据集由 2 个文件组成,en_es.enen_es.esen_es.en 文件中每个以换行符分隔的条目都是 en_es.es 文件中相应条目的翻译,反之亦然。

data_dir = base_dir + "/data"
!mkdir $data_dir

# Download the Scielo dataset
!wget -P $data_dir https://figshare.com/ndownloader/files/14019287
# Untar the downloaded the Scielo dataset
!tar -xvf $data_dir/14019287 -C $data_dir

步骤 2. 数据预处理#

数据预处理包括多个步骤,以提高数据集的质量。NeMo 文档 提供了有关 NMT 的 8 步数据预处理的详细说明。NeMo 还提供了一个 jupyter notebook,它以编程方式引导用户完成不同的预处理步骤。请注意,根据数据集的不同,可以跳过部分或全部预处理步骤。

为了简化 Riva NMT 程序中的微调过程,我们通过 NeMo 仓库提供了 3 个预处理脚本。这些脚本的输入将是 2 个平行语料库(即源语言和目标语言)数据文件。在本教程中,我们使用的是 Moses 版本的 Scielo 数据集,该数据集直接为我们提供了源语言 (en_es.en) 和目标语言 (en_es.es) 数据文件。如果数据集没有直接提供这些文件,那么我们首先需要从数据集中生成这 2 个文件,然后再使用预处理脚本。

语言过滤#

语言过滤预处理脚本用于使用 Fasttext 语言识别模型 验证机器翻译数据集中的语言。如果在平行语料库上使用该脚本,它将验证源语言和目标语言。过滤后的数据存储到由 output_srcoutput-tgt 指定的文件中,删除的行放入由 removed_srcremoved-tgt 指定的文件中。如果无法检测到语言(例如,日期),则删除该行。

此脚本公开了许多参数,其中最常见的是

  • input-src:输入文件的路径,其中包含源语言的文本。

  • input-tgt:输入文件的路径,其中包含目标语言的文本。

  • output-src:源语言的过滤数据要保存到的文件路径。

  • output-tgt:目标语言的过滤数据要保存到的文件路径。

  • removed-src:要保存的源语言的丢弃数据的文件路径。

  • removed-tgt:要保存的目标语言的丢弃数据的文件路径。

  • source-lang:源语言的语言代码。

  • target-lang:目标语言的语言代码。

  • fasttext-model:Fasttext 模型的路径。描述和下载链接 此处

# Let us first download the fasttext model.
!wget https://dl.fbaipublicfiles.com/fasttext/supervised-models/lid.176.bin -O $data_dir/lid.176.bin
# Running the language filtering preprocessing script.
!python $base_dir/NeMo/scripts/neural_machine_translation/filter_langs_nmt.py \
    --input-src $data_dir/en_es.en \
    --input-tgt $data_dir/en_es.es \
    --output-src $data_dir/en_es_preprocessed1.en \
    --output-tgt $data_dir/en_es_preprocessed1.es \
    --removed-src $data_dir/en_es_garbage1.en \
    --removed-tgt $data_dir/en_es_garbage1.es \
    --source-lang en \
    --target-lang es \
    --fasttext-model $data_dir/lid.176.bin

长度过滤#

长度过滤脚本是一个多进程脚本,用于过滤平行语料库以删除长度小于最小长度或大于最大长度的句子。它还根据源句和目标句之间的长度比率进行过滤。

此脚本公开了许多参数,其中最常见的是

  • input-src:输入文件的路径,其中包含源语言的文本。

  • input-tgt:输入文件的路径,其中包含目标语言的文本。

  • output-src:源语言的过滤数据要保存到的文件路径。

  • output-tgt:目标语言的过滤数据要保存到的文件路径。

  • removed-src:要保存的源语言的丢弃数据的文件路径。

  • min-length:最小序列长度。

  • max-length:最大序列长度。

  • ratio:源句长度与目标句长度的比率。

# Running the length filtering preprocessing script.
!python $base_dir/NeMo/scripts/neural_machine_translation/length_ratio_filter.py \
    --input-src $data_dir/en_es_preprocessed1.en \
    --input-tgt $data_dir/en_es_preprocessed1.es \
    --output-src $data_dir/en_es_preprocessed2.en \
    --output-tgt $data_dir/en_es_preprocessed2.es \
    --removed-src $data_dir/en_es_garbage2.en \
    --removed-tgt $data_dir/en_es_garbage2.es \
    --min-length 1 \
    --max-length 512 \
    --ratio 1.3

分词和归一化#

分词和归一化脚本对输入源语言和目标语言数据进行归一化和分词。

此脚本公开了许多参数,其中最常见的是

  • input-src:输入文件的路径,其中包含源语言的文本。

  • input-tgt:输入文件的路径,其中包含目标语言的文本。

  • output-src:要保存的归一化和分词后的源语言数据的文件路径。

  • output-tgt:要保存的归一化和分词后的目标语言数据的文件路径。

  • source-lang:源语言的语言代码。

  • target-lang:目标语言的语言代码。

!python $base_dir/NeMo/scripts/neural_machine_translation/preprocess_tokenization_normalization.py \
    --input-src $data_dir/en_es_preprocessed2.en \
    --input-tgt $data_dir/en_es_preprocessed2.es \
    --output-src $data_dir/en_es_final.en \
    --output-tgt $data_dir/en_es_final.es \
    --source-lang en \
    --target-lang es

训练、开发和验证集划分#

对于数据预处理的最后一步,我们将把数据集划分为训练集、开发集和验证集。
这是一个可选步骤 - 许多数据集已经带有训练集、开发集和验证集划分,但我们在本教程中使用的 Scielo 数据集没有这样的划分。因此,我们将使用 scikit-learn 来划分我们的数据集。

"""
    Read en_es_final.en and en_es_final.es files into memory
"""
def read_data_from_file(filename):
    with open(filename) as f:
        lines = f.readlines()
    return lines
    
en_es_final_en = read_data_from_file(data_dir + "/en_es_final.en")
en_es_final_es = read_data_from_file(data_dir + "/en_es_final.es")

print("Number of entries in the final Scielo English-Spanish dataset = ", len(en_es_final_en))
"""
    Split the dataset into train, test and val using scikit learn's train_test_split
"""
from sklearn.model_selection import train_test_split

test_ratio = 0.10
validation_ratio = 0.05
train_ratio = 1.0 - validation_ratio - test_ratio

en_es_final_en_trainval, en_es_final_en_test, en_es_final_es_trainval, en_es_final_es_test = \
    train_test_split(en_es_final_en, en_es_final_es, test_size=test_ratio, random_state=1)

en_es_final_en_train, en_es_final_en_val, en_es_final_es_train, en_es_final_es_val = \
    train_test_split(en_es_final_en_trainval, en_es_final_es_trainval, test_size=validation_ratio, random_state=1)

print("Number of entries in the final Scielo English-Spanish training dataset = ", len(en_es_final_en_train))
print("Number of entries in the final Scielo English-Spanish validation dataset = ", len(en_es_final_en_val))
print("Number of entries in the final Scielo English-Spanish testing dataset = ", len(en_es_final_en_test))
"""
    Write the train, test and val data into files
"""
en_es_final_en_train_filename = "en_es_final_train.en"
en_es_final_en_val_filename = "en_es_final_val.en"
en_es_final_en_test_filename = "en_es_final_test.en"
en_es_final_es_train_filename = "en_es_final_train.es"
en_es_final_es_val_filename = "en_es_final_val.es"
en_es_final_es_test_filename = "en_es_final_test.es"

en_es_final_en_train_filepath = data_dir + "/" + en_es_final_en_train_filename
en_es_final_en_val_filepath = data_dir + "/" + en_es_final_en_val_filename
en_es_final_en_test_filepath = data_dir + "/" + en_es_final_en_test_filename
en_es_final_es_train_filepath = data_dir + "/" + en_es_final_es_train_filename
en_es_final_es_val_filepath = data_dir + "/" + en_es_final_es_val_filename
en_es_final_es_test_filepath = data_dir + "/" + en_es_final_es_test_filename

def write_data_to_file(data, filename):
    f = open(filename, "w")
    for data_entry in data:
        f.write(data_entry)
    f.close()
    
write_data_to_file(en_es_final_en_train, en_es_final_en_train_filepath)
write_data_to_file(en_es_final_en_val, en_es_final_en_val_filepath)
write_data_to_file(en_es_final_en_test, en_es_final_en_test_filepath)
write_data_to_file(en_es_final_es_train, en_es_final_es_train_filepath)
write_data_to_file(en_es_final_es_val, en_es_final_es_val_filepath)
write_data_to_file(en_es_final_es_test, en_es_final_es_test_filepath)    

步骤 3. 使用 NeMo 微调 NMT 模型。#

NeMo 提供了微调双语 NMT NeMo 模型所需的微调脚本。我们可以使用此脚本启动训练。

我们首先从 NGC 下载开箱即用 (OOTB) 的英西 NMT NeMo 模型。我们将在这个模型的基础上,在 Scielo 数据集上进行微调。

# Create directory to hold model
model_dir = base_dir + "/model"
!mkdir $model_dir

# Download the NMT model from NGC using wget command
!wget -O $model_dir/nmt_en_es_transformer24x6_1.5.zip --content-disposition https://api.ngc.nvidia.com/v2/models/nvidia/nemo/nmt_en_es_transformer24x6/versions/1.5/zip
# Unzip the downloaded model zip file.
!unzip $model_dir/nmt_en_es_transformer24x6_1.5.zip -d $model_dir/pretrained_ckpt

# Alternate way to download the model from NGC using NGC CLI (Please make sure to install and setup NGC CLI):
#!cd $model_dir && ngc registry model download-version "nvidia/nemo/nmt_en_es_transformer24x6:1.5"

NeMo NMT 微调脚本公开了许多参数

  • model_path:本地 OOTB .nemo 模型的路径。

  • trainer.devices:要为微调分配的 GPU 数量。

  • trainer.max_epochs:要运行微调的最大轮次数。

  • trainer.max_steps:要运行微调的最大步数。max_steps 可以覆盖 max_epochs,就像我们在本教程中所做的那样。

  • trainer.val_check_interval:此参数决定在对整个验证数据集运行验证之前要执行的训练步骤数。

  • model.train_ds.tgt_file_name:训练数据集的目标语言数据文件的路径。在我们的例子中,这是 en_es_final_train.es 文件。

  • model.train_ds.src_file_name:训练数据集的源语言数据文件的路径。在我们的例子中,这是 en_es_final_train.en 文件。

  • model.train_ds.tokens_in_batch:单个训练批次中的 token 数量。请注意,这不是训练批次中的数据条目数,而是 token 的数量。

  • model.validation_ds.tgt_file_name:验证数据集的目标语言数据文件的路径。在我们的例子中,这是 en_es_final_val.es 文件。

  • model.validation_ds.src_file_name:验证数据集的源语言数据文件的路径。在我们的例子中,这是 en_es_final_val.en 文件。

  • model.validation_ds.tokens_in_batch:验证期间单个批次中的 token 数量。请注意,验证在整个验证数据集上运行 - 此参数仅指定单个批次中的 token 数量。可以运行多个批次的数据以覆盖整个验证数据集。

  • model.test_ds.tgt_file_name:测试数据集的目标语言数据文件的路径。在我们的例子中,这是 en_es_final_test.es 文件。

  • model.test_ds.src_file_name:测试数据集的源语言数据文件的路径。在我们的例子中,这是 en_es_final_test.en 文件。

  • exp_manager.exp_dir:实验目录的路径,该目录充当 NeMo 微调的工作目录。

  • exp_manager.checkpoint_callback_params.monitor:要监控的指标。

  • exp_manager.checkpoint_callback_params.mode:要监控的指标的模式。

  • exp_manager.checkpoint_callback_params.save_best_model:指示是否必须在每个训练步骤后保存最佳模型的标志。

!python $base_dir/NeMo/examples/nlp/machine_translation/enc_dec_nmt_finetune.py \
      model_path=$model_dir/pretrained_ckpt/en_es_24x6.nemo \
      trainer.devices=1 \
      ~trainer.max_epochs \
      +trainer.max_steps=1 \
      +trainer.val_check_interval=1 \
      model.train_ds.tgt_file_name=$en_es_final_es_train_filepath \
      model.train_ds.src_file_name=$en_es_final_en_train_filepath \
      model.train_ds.tokens_in_batch=1280 \
      model.validation_ds.tgt_file_name=$en_es_final_es_val_filepath \
      model.validation_ds.src_file_name=$en_es_final_en_val_filepath \
      model.validation_ds.tokens_in_batch=2000 \
      model.test_ds.tgt_file_name=$en_es_final_es_test_filepath \
      model.test_ds.src_file_name=$en_es_final_en_test_filepath \
      +exp_manager.exp_dir=$model_dir/results/finetune-test \
      +exp_manager.create_checkpoint_callback=True \
      +exp_manager.checkpoint_callback_params.monitor=val_sacreBLEU \
      +exp_manager.checkpoint_callback_params.mode=max \
      +exp_manager.checkpoint_callback_params.save_best_model=true
!python $base_dir/NeMo/examples/nlp/machine_translation/enc_dec_nmt_finetune.py \
      model_path=$model_dir/pretrained_ckpt/en_es_24x6.nemo \
      trainer.devices=1 \
      ~trainer.max_epochs \
      +trainer.max_steps=1 \
      +trainer.val_check_interval=1 \
      model.train_ds.tgt_file_name=$en_es_final_es_train_filepath \
      model.train_ds.src_file_name=$en_es_final_en_train_filepath \
      model.train_ds.tokens_in_batch=1280 \
      model.validation_ds.tgt_file_name=$en_es_final_es_val_filepath \
      model.validation_ds.src_file_name=$en_es_final_en_val_filepath \
      model.validation_ds.tokens_in_batch=2000 \
      model.test_ds.tgt_file_name=$en_es_final_es_test_filepath \
      model.test_ds.src_file_name=$en_es_final_en_test_filepath \
      +exp_manager.exp_dir=$model_dir/results/finetune-test \
      +exp_manager.create_checkpoint_callback=True \
      +exp_manager.checkpoint_callback_params.monitor=val_sacreBLEU \
      +exp_manager.checkpoint_callback_params.mode=max \
      +exp_manager.checkpoint_callback_params.save_best_model=true

步骤 4. 使用 NeMo 评估微调后的 NMT 模型。#

现在我们有了一个微调后的模型,我们需要检查它的性能如何。
我们首先使用 OOTB 模型,然后使用微调后的模型,在测试数据集的一个小子集上运行 NeMo 提供的脚本 nmt_transformer_infer.py 进行推理。然后我们比较来自两个模型的翻译。

NeMo 推理脚本 nmt_transformer_infer.py 支持多个输入参数,其中最重要的参数是

  • model:要运行推理的 .nemo 模型的路径

  • srctext:文本文件的路径,其中包含要运行推理的换行符分隔的输入样本

  • tgtout:要保存翻译的文本文件的路径

  • source_lang:源语言的语言代码。

  • target_lang:目标语言的语言代码。

  • batch_size:推理的批次大小。在本节中,我们学习如何使用此脚本运行推理。

首先,让我们为评估创建一个工作目录。

eval_dir = base_dir + "/eval"
!mkdir $eval_dir

我们选择测试数据的一个小子集用于推理,并将其写入文件。

infer_input_data_en = en_es_final_en_test[:10]
infer_input_data_es = en_es_final_es_test[:10]

infer_input_data_en_filename = "infer_input_data_en.en"
infer_input_data_en_filepath = eval_dir + "/" + infer_input_data_en_filename

f = open(infer_input_data_en_filepath, "w")
for infer_input_data_en_entry in infer_input_data_en:
    f.write(infer_input_data_en_entry)
f.close()    

让我们在 NeMo NMT OOTB 模型上运行推理。

infer_ootbmodel_output_data_es_filename = "infer_ootbmodel_output_data_es.es"
infer_ootbmodel_output_data_es_filepath = eval_dir + "/" + infer_ootbmodel_output_data_es_filename

!python $base_dir/NeMo/examples/nlp/machine_translation/nmt_transformer_infer.py \
    --model $model_dir/pretrained_ckpt/en_es_24x6.nemo \
    --srctext $infer_input_data_en_filepath \
    --tgtout $infer_ootbmodel_output_data_es_filepath \
    --source_lang en \
    --target_lang es \
    --batch_size 10

现在我们在 NeMo NMT 微调模型上运行推理。
请务必将下面的 model 参数设置为指向微调后的 .nemo 检查点,该检查点可以在 $model_dir/results 目录中找到。

infer_finetuned_output_data_es_filename = "infer_finetuned_output_data_es.es"
infer_finetuned_output_data_es_filepath = eval_dir + "/" + infer_finetuned_output_data_es_filename

!python $base_dir/NeMo/examples/nlp/machine_translation/nmt_transformer_infer.py \
    --model $model_dir/pretrained_ckpt/en_es_24x6.nemo \
    --srctext $infer_input_data_en_filepath \
    --tgtout $infer_finetuned_output_data_es_filepath \
    --source_lang en \
    --target_lang es \
    --batch_size 10

让我们显示来自 OOTB 模型和微调模型的推理测试子集的翻译。

with open(infer_ootbmodel_output_data_es_filepath) as f:
    infer_ootbmodel_output_data_es = f.readlines()

with open(infer_finetuned_output_data_es_filepath) as f:
    infer_finetuned_output_data_es = f.readlines()
    
for infer_input_data_en_entry, infer_input_data_es_entry, infer_ootbmodel_output_data_es_entry, infer_finetuned_output_data_es_entry in \
    zip(infer_input_data_en, infer_input_data_es, infer_ootbmodel_output_data_es, infer_finetuned_output_data_es):
    print("English: ", infer_input_data_en_entry)
    print("Spanish Translation - Ground Truth: ", infer_input_data_es_entry)
    print("Spanish Translation - OOTB model Generated:     ", infer_ootbmodel_output_data_es_entry)
    print("Spanish Translation - Finetuned model Generated:", infer_finetuned_output_data_es_entry)
    print("------------------------")

如上所示,在 Scielo 数据集的测试集上,微调后的 NMT 模型生成的翻译比 OOTB 模型更准确。

步骤 5. 导出 NeMo 模型#

NeMo 和 Riva 允许您以可以使用 NVIDIA Riva 部署的格式导出微调后的模型;RIVA 是一个高性能的应用程序框架,用于使用 GPU 的多模态对话式 AI 服务。

导出到 Riva#

Riva 提供了 nemo2riva 工具,该工具可用于将 .nemo 模型转换为 .riva 模型。此工具可通过 Riva 快速入门指南获得,并在上面的要求和设置步骤中安装。

!nemo2riva --out $model_dir/en_es_24x6.riva $model_dir/results/finetune-test/AAYNBaseFineTune/2023-02-24_06-43-56/checkpoints/AAYNBaseFineTune.nemo

步骤 6. 在 Riva 语音技能服务器上部署微调后的 NeMo NMT 模型。#

NeMo 微调的 NMT 模型需要在 Riva 语音技能服务器上部署以进行推理。
请按照 Riva NMT 教程 中的“如何在 Riva 语音技能服务器上部署 NeMo 微调的 NMT 模型?”教程进行操作 - 本 notebook 涵盖了在 Riva 语音技能服务器上部署从步骤 5 获得的 .riva 文件。