在
上一篇文章
中,我们介绍了使用
NeMo
运行英-中翻译模型的示例,并评估其性能。在这篇文章中,我们将指导您如何定制数据集,并在该数据集上微调模型。
数据收集在模型微调中至关重要,因为它使模型适配特定任务或领域要求。
例如,我们翻译任务是将计算机科学相关的技术文章从英文翻译成中文,那么收集以前人工翻译的文章作为微调数据是很有必要的。因为此类文章中包含很多这个领域中常用概念和术语,但在这些语料在预训练数据集中极少出现。
我们建议至少采集几千个高质量的样本。在使用这些量身定制的数据进行微调后,模型可以在技术博客翻译任务中取得更好的表现。
为了提高数据质量,您需要对数据进行预处理,以过滤掉无效和冗余的脏数据。
NVIDIA NeMo framework
包含了
NVIDIA NeMo Curator
,目前可以用于处理 LLM 预训练中使用的语料库。然而,NMT 的双语数据集与单语语料库不同,因为它们有源文本和目标文本,需要特殊的过滤方法。幸运的是,NeMo 也提供了大部分开箱即用的函数和脚本,满足了双语数据集处理的需求。
您可以用一个简单的数据预处理流程来清洗英-中的翻译数据集:
除了以上这些方法,您还可以使用其他自定义的方案来过滤掉无效数据。例如,使用现有的 NMT 模型筛选出可能是错误的翻译。如想了解更多数据预处理方法,请参阅
此文档
。
我们收集了英-中的翻译对,并放在了两个文本文件:
|
en_zh.en
|
en_zh.zh
|
第1行
|
Accelerate Data Preparation
|
加快完成数据准备
|
第2行
|
An end to end, cloud native, suite of AI and data analytics software, optimized, certified and supported by NVIDIA.
|
一款经 NVIDIA 优化、认证和支持的端到端云原生 AI 和数据分析软件套件。
|
表 1:中英两个文件之间的对应关系示例
在本节中,经过每个处理数据处理步骤后输出的文件都按以上格式记录。
NeMo 通过使用
fastText
的语种识别技术提供了语种过滤的方法。
首先,从 fastText 网站下载语言分类器模型 lid.176.bin:
wget https://dl.fbaipublicfiles.com/fasttext/supervised-models/lid.176.bin -O lid.176.bin
使用以下代码进行语种筛选:
python /opt/NeMo/scripts/neural_machine_translation/filter_langs_nmt.py \
--input-src en_zh.en \
--input-tgt en_zh.zh \
--output-src en_zh_preprocessed1.en \
--output-tgt en_zh_preprocessed1.zh \
--removed-src en_zh_garbage1.en \
--removed-tgt en_zh_garbage1.zh \
--source-lang en \
--target-lang zh \
--fasttext-model \
lid.176.bin
en_zh_preprocessed1.en
和
en_zh_preprocessed1.zh
是保留下来的有效数据,而
en_zh_garbage1.en
和
en_zh_garbage1.zh
则是废弃数据。
以下是
en_zh_garbage1.zh
文件的一部分,这些都不是中文所以被过滤了:
NVIDIA OVX
Tensor Core
PROP_2.USD
ConnectX-6 Dx、ConnectX-6
|
长度过滤用于去除双语语料库中过短或过长的文本,因为他们可能是一些脏数据。此外,我们还会根据目标句子和源句子之间的长度比进行过滤。
在运行长度筛选之前,您可以使用以下脚本计算出您收集的数据中的译文和原文的长度比的分布,以便了解要筛选的比率阈值:
def compute_length_ratio(source_txt, target_txt, percentile):
len_ratios = list()
with open(source_txt, "r") as src, \
open(target_txt, "r") as tgt:
for src_line, tgt_line in zip(src, tgt):
len_ratios.append(len(src_line.strip()) / len(tgt_line.strip()))
len_ratios.sort()
# compute percentile
ratio = len_ratios[int(len(len_ratios) * percentile)]
print(f"Length ratio @{percentile} percentile is {ratio}")
return ratio
compute_length_ratio("en_zh_preprocessed1.en", "en_zh_preprocessed1.zh", 0.95)
这个长度比在不同的数据集以及源语言和目标语言上也有所不同。
运行以下脚本以执行长度筛选。其中,
--ratio 4.6
参数用于指定最大的长度比。
python /opt/NeMo/scripts/neural_machine_translation/length_ratio_filter.py \
--input-src en_zh_preprocessed1.en \
--input-tgt en_zh_preprocessed1.zh \
--output-src en_zh_preprocessed2.en \
--output-tgt en_zh_preprocessed2.zh \
--removed-src en_zh_garbage2.en \
--removed-tgt en_zh_garbage2.zh \
--min-length 10 \
--max-length 512 \
--ratio 4.6
同上,
en_zh_preprocessed2.en
和
en_zh_preprocessed2.zh
是可以保留到下一步的数据文件。
在这一步中,您可以使用
xxhash
库。
运行以下 Python 脚本进行去重:
import xxhash
def dedup_file(input_file_lang_1, input_file_lang_2, output_file_lang_1, output_file_lang_2):
hashes = set()
with open(input_file_lang_1, 'r') as f_lang1, \
open(input_file_lang_2, 'r') as f_lang2, \
open(output_file_lang_1, 'w') as f_out_lang1, \
open(output_file_lang_2, 'w') as f_out_lang2:
for line_1, line_2 in zip(f_lang1, f_lang2):
parallel_hash = xxhash.xxh64((line_1.strip()).encode('utf-8')).hexdigest()
if parallel_hash not in hashes:
hashes.add(parallel_hash)
f_out_lang1.write(line_1.strip() + '\n')
f_out_lang2.write(line_2.strip() + '\n')
dedup_file(
'en_zh_preprocessed2.en',
'en_zh_preprocessed2.zh',
'en_zh_preprocessed3.en',
'en_zh_preprocessed3.zh'
)
在此步骤中,要保留的数据文件为
en_zh_preprocessed3.en
和
en_zh_preprocessed3.zh
。
如果要微调 NeMo 模型,需要进行额外的分词处理和标准化:
分词处理:
通过在标点符号和相邻单词之间添加空格,以避免单词上附加标点符号,这是 NeMo 训练中的一项建议步骤。
python /opt/NeMo/scripts/neural_machine_translation/preprocess_tokenization_normalization.py \
--input-src en_zh_preprocessed3.en \
--input-tgt en_zh_preprocessed3.zh \
--output-src en_zh_final_nemo.en \
--output-tgt en_zh_final_nemo.zh \
--source-lang en \
--target-lang zh
这脚本底层采用不同了的库来处理不同的语言。例如,英语使用了
sacremoses
,对简体中文,则使用
Jieba
和
OpenCC
。
这一步输出的
en_zh_final_nemo.en
和
en_zh_final_nemo.zh
是 NeMo 微调所需的数据集文件。
转换 JSONL 格式(仅用于 ALMA 模型微调)
ALMA 训练需要采用 JSON 行(JSONL)作为原始输入数据,其中文件中的每一行都是一个 JSON 结构:
{"translation": {"en": "Accelerate Data Preparation", "zh": "加快完成数据准备"}}
如要训练 ALMA 模型,您必须将
en_zh_preprocessed3.en
和
en_zh_preprocessed3.zh
这两个中英文件转换为一个 JSONL 格式的文件:
import json
def txt2jsonl(source_txt, target_txt, source, target, output_jsonl):
with open(source_txt, "r") as f:
source_lines = f.read().splitlines()
with open(target_txt, "r") as f:
target_lines = f.read().splitlines()
json_list = list()
for source_text, target_text in zip(source_lines, target_lines):
json_item = {
"translation": {source: source_text, target: target_text}
}
json_list.append(json_item)
with open(output_jsonl, "w") as f:
for json_item in json_list:
f.write(json.dumps(json_item, ensure_ascii=False) + "\n")
source_txt = "en_zh_preprocessed3.en"
target_txt = "en_zh_preprocessed3.zh"
source = "en"
target = "zh"
output_jsonl = "en_zh_final_alma.jsonl"
txt2jsonl(source_txt, target_txt, source, target, output_jsonl)
这一步输出的
en_zh_final_alma.jsonl
是 ALMA 微调所需的数据集文件。
最后一步是将数据集拆分为训练集、验证集和测试集。你可以使用
sklearn.model_selection
库中的 train_test_split 方法来完成此操作。
对于
NeMo NMT
模型微调,上一步输出的
en_zh_final_nemo.en
和
en_zh_final_nemo.zh
会被拆分为:
对于 ALMA 模型微调,上一步输出的 en_zh_final_alma.jsonl 会被拆分为:
-
train.zh-en.json
-
valid.zh-en.json
-
test.zh-en.json
在本节中,我们将分别演示如何微调 NeMo 和 ALMA 模型。
在本节中,我们将分别演示如何微调 NeMo 和 ALMA 模型。
在进行微调之前,请参考上一个博客中 NMT 模型评估章节的介绍,把 NeMo 预训练模型下载到
./model/pretrained_ckpt/en_zh_24x6.nemo
。然后,您可以使用收集的数据集对 NeMo EN-ZH 模型进行微调。请注意,批处理大小将取决于 GPU 显存的大小。
python /opt/NeMo/examples/nlp/machine_translation/enc_dec_nmt_finetune.py \
model_path=model/pretrained_ckpt/en_zh_24x6.nemo \
trainer.devices=1 \
trainer.max_epochs=10 \
+trainer.val_check_interval=600 \
model.train_ds.tgt_file_name=data/en_zh_final_nemo_train.zh \
model.train_ds.src_file_name=data/en_zh_final_nemo_train.en \
model.train_ds.tokens_in_batch=3000 \
model.validation_ds.tgt_file_name=data/en_zh_final_nemo_val.zh \
model.validation_ds.src_file_name=data/en_zh_final_nemo_val.en \
model.validation_ds.tokens_in_batch=1000 \
model.test_ds.tgt_file_name=data/en_zh_final_nemo_test.zh \
model.test_ds.src_file_name=data/en_zh_final_nemo_test.en \
+exp_manager.exp_dir=output/ \
+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 \
+exp_manager.checkpoint_callback_params.always_save_nemo=true \
+exp_manager.checkpoint_callback_params.save_top_k=10
训练完成后,结果和权重文件将被保存到
./output/AAYNBaseFineTune
路径。您可以使用
TensorBoard
来可视化损失曲线或查看日志文件。
要微调 ALMA 模型,首先要从 GitHub 上克隆 ALMA 的仓库,并在 NeMo framework 容器中的安装额外的依赖项。
git clone https://github.com/fe1ixxu/ALMA.git
cd ALMA
bash install_alma.sh
pip install --upgrade pytest
|
您可以将处理后的数据集(
train.zh-en.json、valid.zh-en.json、test.zh-en.json
)放置在根目录下的
./human_written_data/zhen
数据目录中,其中 /zhen 子目录专门用于存储中英文的数据集。
下一步是修改两个配置文件中的参数:
/runs/parallel_ft_lora.sh 和 /configs/deepspeed_train_config.yaml
。
在
/runs/parallel_ft_lora.sh
中,需要修改的字段有:
-
per_device_train_batch_size
:每个设备训练批大小。
-
gradient_accumulation_steps
:在梯度更新前的累积步数。
-
learning_rate:根据批大小和累积步骤数进行动态调整。
在
/configs/deepspeed_train_config.yaml
文件中需要修改的字段有:
如要使用 LoRA 微调 ALMA 模型的英-中和中-英双向翻译能力,可在 ALMA 仓库的根目录中运行以下命令:
bash runs/parallel_ft_lora.sh output zh-en,en-zh
|
结果文件将被存储在 ./output 目录:
-
adapter_config.json
:用于存储 LoRA 配置。
-
adapter_model.bin
:LoRA 模型权重。
-
trainer_state.json
:训练过程中的损失记录。
在上一博客中,您评估了 NeMo 和 ALMA 预训练模型的初始性能。在这里,您可以通过使用相同的测试数据集对微调后的模型进行评估测试。
对于 NeMo 模型可使用以下脚本在相同的测试集进行推理:
python /opt/NeMo/examples/nlp/machine_translation/nmt_transformer_infer.py \
--model $fine_tuned_nemo_path \
--srctext input_en.txt \
--tgtout nemo_ft_out_zh.txt \
--source_lang en \
--target_lang zh \
--batch_size 200 \
--max_delta_length 20
sacrebleu reference.txt -i nemo_ft_out_zh.txt -m bleu -b -w 4
最后一行代码计算了 BLEU 分数。
评估 ALMA 模型时也需要对相同的测试集进行推理。以下脚本是对微调后的 ALMA 模型的推理代码片段,其中模型加载部分与前面讨论的略有不同,因为它需要读取本地的微调 PEFT 模型和配置。
import torch
from peft import PeftModel, PeftConfig
from transformers import AutoModelForCausalLM
from transformers import LlamaTokenizer
peft_config = PeftConfig.from_pretrained("./output")
model = AutoModelForCausalLM.from_pretrained(peft_config.base_model_name_or_path, torch_dtype=torch.float16, device_map="auto")
model = PeftModel.from_pretrained(model, "./output")
model = model.eval()
tokenizer = LlamaTokenizer.from_pretrained("haoranxu/ALMA-7B-Pretrain", padding_side='left')
prompt_template = "Translate this from English to Chinese:\nEnglish: {}\nChinese:"
prompt = prompt_template.format("AI is powering change in every industry")
input_ids = tokenizer(prompt, return_tensors="pt", padding=True, max_length=40, truncation=True).input_ids.cuda()
with torch.no_grad():
generated_ids = model.generate(input_ids=input_ids, num_beams=5, max_new_tokens=256, do_sample=True, temperature=0.6, top_p=0.9)
outputs = tokenizer.batch_decode(generated_ids, skip_special_tokens=True)
output = outputs[0].replace(prompt, "").strip()
print(output)
您可以通过修改示例推理的脚本以对英语文本文件进行翻译,并计算其 BLEU 分数。
该
NeMo 框架容器
提供了一个方便的环境,适用于各种推理和定制任务。
NeMo framework 容器
提供了一个方便的环境,适用于各种推理和定制任务。
-
使用
LLM
开发自定义企业生成式 AI。
-
多模态语言模型
-
计算机视觉应用
-
自动语音识别
-
文本到语音合成
-
神经网络机器翻译