目录
前言
大体由一个人初步完成的项目,发出来给大家参考一下。代码部分的baseline参考的这篇文章BERT实战——(5)生成任务-机器翻译,在此基础结合了个人的整理、修改和拓展。后续跟进还会发文章,数据集鸽很久才会放github。
(一)知识储备
LLM
大语言模型(Large Language Model)是一种旨在理解和生成人类语言的人工智能模型,通常指包含数百亿(或更多)参数的语言模型。
它们在海量的文本数据上进行训练,从而获得对语言深层次的理解,在解决复杂任务时表现出了惊人的潜力,这被称为 “ 涌现能力 ” 。目前,国外的知名 LLM 有 GPT-3.5、GPT-4、PaLM、Claude 和 LLaMA 等,国内的有文心一言、讯飞星火、通义千问、ChatGLM、百川等。
NPL
自然语言处理(Nature Language Processing)是指将人类语言转换成计算机能理解的符号或将机器语言转换成人可以理解的语言,是计算机科学领域与人工智能领域中的一个重要方向,更是一门融语言学、计算机科学、数学于一体的综合学科。主要可以分为以下六个类别:
- 文本挖掘
- 信息检索
- 句法语义分析
- 机器翻译
- 问答系统
- 对话系统
Machine Translation
机器翻译(Machine Translation),又称为自动翻译,是指利用计算机将一种自然语言(源语言)转换为另一种自然语言(目标语言)的过程。
它是人工智能的重要方向之一, 肩负着架起语言沟通桥梁的重任,自1947年提出以来,历经多次技术革新,尤其是近10年来从统计机器翻译(SMT)到神经网络机器翻译(NMT)的跨越,促进了机器翻译的大规模产业应用。
它是计算语言学的一个分支,是人工智能的终极目标之一,具有重要的科学研究和实用价值。随着经济全球化及互联网的飞速发展,机器翻译技术在促进政治、经济、文化交流等方面起到越来越重要的作用。
Pre-training + Fine-tuning
预训练(Pre-training)是指对模型进行无监督或自监督学习的过程,在大规模未标注数据上先训练模型来学习通用的语言表示,以便为后续任务提供一个高质量的初始权重。这个过程对于许多复杂模型尤其是 transformer 架构(比如BERT、GPT系列等)来说极其重要。
微调(Fine-tuning)是指针对特定下游任务再收集少量对应的、有标注的训练资料,在预训练模型的基础上进一步训练以适应具体场景。如文本分类、情感分析、问答系统等。
该范式的优势在于:避免从随机初始化开始训练需要大量标记数据的问题,大幅降低计算成本,并节省了从头开始训练模型的时间。
预训练-微调范式已经成为现代深度学习在自然语言处理等领域中的标准做法之一。
Hugging Face
Hugging Face是一个 AI 公司,专注于开发和提供 NLP 相关的技术和工具。
该公司开发了 Transformers 库,这是一个用于 NPL 任务的开源库,提供了各种预训练模型,包括 BERT、GPT 等。Transformers库还提供了方便的 API 和工具,使用户可以轻松地使用这些预训练模型进行各种 NLP 任务,如文本分类、问答等。
Hugging Face 还开发了 Hugging Face Hub,这是一个用于分享、发现和管理模型和数据集的平台,使研究人员和开发者能够更方便地共享和访问 NLP 模型和数据集。通过这些工具和平台,Hugging Face 为 NLP 社区提供了丰富的资源和支持,推动了 NLP 技术的发展和应用。
Bert
BERT(Bidirectional Encoder Representations from Transformers)是由Google于2018年发布的预训练模型。它是一种双向Transformer编码器,旨在通过联合左侧和右侧的上下文来预训练深度双向表示模型。它的双向性使其能够更好地理解上下文信息,从而在多种NLP任务上取得非常好的表现。
BERT通过在大规模未标记文本上进行预训练,学习通用的语言表示,然后可以通过微调或添加额外的输出层,用于各种自然语言处理任务,如文本分类、问答、语言推理等。
BLEU
双语评估替补(Bilingual Evaluation Understudy)是一种常用的机器翻译评价指标,它通过比较机器翻译的输出和人工翻译的参考译文的n-gram相似度来评估翻译质量。由于它简单、快速,并且与人类评价的相关性较高,广泛用于自然语言处理领域,特别是在机器翻译任务中。
BLEU的分数范围在0到1之间,分数越高表示翻译质量越好。本项目对BLEU的系数为100。
(二)环境配置
Transformers
终端执行命令下载:
pip install transformers
pip install transformers[torch]
pip install transformers[sentencepiece]
Transformers 库是一个开源的NPL库,提供了许多预训练的模型,如BERT、GPT等,以及用于文本分类、序列标注、文本生成等任务的工具和API。
它使用了自注意力(self-attention)机制和编码器-解码器(encoder-decoder)结构,基于三个最流行的深度学习库PyTorch、TensorFlow 和 JAX构建,在许多NLP任务上取得了很好的性能。
Pytorch
由于官网下载太慢,使用清华镜像源安装:
pip install torch torchvision torchaudio -i https://pypi.tuna.tsinghua.edu.cn/simple
PyTorch是一个由Facebook开发的开源Python深度学习库,基于Torch,用于NPL等应用程序,专门针对 GPU 加速的深度神经网络(DNN)编程。
sacreBLEU
pip install sacrebleu
sacreBLEU 是一个基于 BLEU 指标评估机器翻译系统性能的工具包。
sacremoses
pip install sacremoses
sacremoses 是一个用于处理自然语言文本的 Python 库,它提供了用于分词、标记化、词形还原等NPL任务的工具和函数。
Datasets
pip install datasets
Datasets是Hugging Face公司开发的一个Python库,提供了访问和处理大量NPL数据集的工具。库中的每个数据集都被设计成一个高效、易用、可扩展的对象,以便简化处理和操作。
Helsinki-NLP
Helsinki-NLP 是一个 NPL 工具包,包含了许多常用的 NPL 模型和工具。它主要基于神经网络机器翻译(Neural Machine Translation),特别是基于 Transformer 架构。
Helsinki-NLP 提供了对 Bert 模型的支持和集成,可以加载和使用 Bert 模型进行文本分类、命名实体识别、问答等任务。同时,Helsinki-NLP 还提供了一些额外的功能和工具,帮助用户更好地使用和调整 Bert 模型,提高模型在特定任务上的性能。
AutoTokenizer 库的 from_pretrained 函数载入该模型的方式是:根据输入模型的名称,进入Hugging Face 将模型下载到 "C:\Users\username\.cache" 。但由于 Hugging Face 被墙,而梯子又会引发代理连接错误,此外该路径出现中文字符也会报错 “OsError:not found”,所以采用如下解决方式:
进入 Hugging Face 的镜像网站https://hf-mirror.com/将模型下载到本地,并将 from_pretrained 函数的载入参数改为存储路径。
(三)数据预处理
1. 数据搜集
这次训练的双语并行语料库来自:http://www.manythings.org/anki/,https://hf-mirror.com/
数据类型为txt,样本示例如下:
Hi. مرحبًا. CC-BY 2.0 (France) Attribution: tatoeba.org #538123 (CM) & #629296 (Samer)
Run! اركض! CC-BY 2.0 (France) Attribution: tatoeba.org #906328 (papabear) & #1245450 (saeb)
Duck! اخفض رأسك! CC-BY 2.0 (France) Attribution: tatoeba.org #280158 (CM) & #9036391 (KeEichi)
Duck! اخفضي رأسك! CC-BY 2.0 (France) Attribution: tatoeba.org #280158 (CM) & #9036392 (KeEichi)
Duck! اخفضوا رؤوسكم! CC-BY 2.0 (France) Attribution: tatoeba.org #280158 (CM) & #9036393 (KeEichi)
Hi. مرحبًا.
Run! اركض!
Help! النجدة!
Jump! اقفز!
Stop! قف!
2. 标准化集成
# 定义双语语料分割函数:均匀分割90%train-10%val
def bilingual_split(file, train, val):
# 分割训练集和验证集的标志
flag = 0
# 逐行遍历文档
for li in file.readlines():
# 词条计数
flag += 1
# 删除文档每行字符串首尾的空白符,并以水平制表符为分隔符,对字符串切片,存入一个列表
# 列表包含的前两个元素分别为英语语句和阿拉伯语语句
line = li.strip().split("\t")
# 创建临时字典,即词条,存储着列表生成的一条英语记录和一条阿拉伯语记录
tmp = dict()
tmp["en"] = line[0]
tmp["ar"] = line[1]
# 将词条添入词典,训练集每增加9条,验证集增加1条
if flag % 10:
train.append(tmp)
else:
val.append(tmp)
由于语料库的句长是递增的,为了保持训练集和验证集复杂度的有序一致,采用均匀分割的方法。
3. 清洗去重
# 定义词条去重函数
def deduplication(entry_list):
"""由于列表里的元素类型为只有两条记录的字典,且字典的键值均不可哈希,故不能直接使用集合去重"""
# items():取字典中每条记录的键和值生成为一阶元组,返回它们的列表
"""字典是无序的数据结构,即使两个 字典的键值对相同,它们在内存中的存储顺序也可能不同"""
# sorted():对列表内的元组排序,确保相同的键值对在二阶元组内的顺序相同
# {turple() for en in entry_list}:丛列表中取出字典,转化为二阶元组,并使用集合去重
# [dict(t) for t in set]:从集合中取出二阶元组,还原为字典,并放回列表
entry_list = [dict(t) for t in {tuple(sorted(en.items())) for en in entry_list}]
return entry_list
由于字典的无序性,这个函数会随机打乱均匀分割后的训练集和验证集,即每次运行后得到的词条顺序都不同,从而使得相同参数下的训练结果 不同。
4. 数据保存
# 定义数据持久化函数
def save(path, data):
f = open(path, "w", encoding="utf-8", errors='ignore')
for i in data:
f.write(str(i)+"\n")
f.close()
5. 预处理
# 初始化数据集
train_data = []
val_data = []
# 载入语料库
f1 = open("../corpus/en-ar_1.txt", "r", encoding="utf-8", errors='ignore')
f2 = open("../corpus/en-ar_2.txt", "r", encoding="utf-8", errors='ignore')
# 构建训练集和验证集
bilingual_split(f2, train_data, val_data)
bilingual_split(f1, train_data, val_data)
# 去重
train_data = deduplication(train_data)
val_data = deduplication(val_data)
# 保存训练集和测试集
save("../train.txt", train_data)
save("../val.txt", val_data)
所构建数据集的样本及词条统计如下:
{'ar': 'رأيته يركض.', 'en': 'I saw him running.'}
{'ar': 'ما مدي سوء صداع رأسك', 'en': 'How bad is your headache?'}
{'ar': 'تكتب العربية من اليمين إلى اليسار.', 'en': 'Arabic is written from right to left.'}
{'ar': 'لا أحد هنا.', 'en': "Nobody's around."}
{'ar': 'ما يأتي سهلا يذهب سهلا.', 'en': 'Easy come, easy go.'}
DatasetDict({
train: Dataset({
features: ['text'],
num_rows: 12802
})
validation: Dataset({
features: ['text'],
num_rows: 2235
})
})
(四)模型训练
1. 载入第三方库
# 载入操作系统库
import os
# 载入数值计算库
import numpy as np
# 载入数据集操作库
from datasets import load_dataset
# 载入分词库
from transformers import AutoTokenizer
# 载入预训练模型库
from transformers import AutoModelForSeq2SeqLM
from transformers import Seq2SeqTrainingArguments
# 载入数据处理器库
from transformers import DataCollatorForSeq2Seq
# 载入模型训练库
from transformers import Seq2SeqTrainer
# 载入指标计算库
from datasets import load_metric
2. 分词
# 定义分词函数
def preprocess_function(entries):
# 遍历数据集,取出feature为text的词条dict,生成对应语言的语句列表
inputs = [eval(en)[source_lang] for en in entries["text"]]
targets = [eval(en)[target_lang] for en in entries["text"]]
# 为源语言设置分词器:最大句长64,截断长于64的句子
model_inputs = tokenizer(inputs, max_length=64, truncation=True)
# 为目标语言设置分词器:最大句长64,截断长于64的句子
with tokenizer.as_target_tokenizer():
labels = tokenizer(targets, max_length=64, truncation=True)
# 保存目标语言标签
model_inputs["labels"] = labels["input_ids"]
return model_inputs
3. 评估指标
# 定义标签处理函数:把preds和labels处理成对应的格式以供评估
def postprocess_text(preds, labels):
preds = [pred.strip() for pred in preds]
labels = [[label.strip()] for label in labels]
return preds, labels
# 定义评估指标计算函数
def compute_metrics(eval_preds):
preds, labels = eval_preds
if isinstance(preds, tuple):
preds = preds[0]
decoded_preds = tokenizer.batch_decode(preds, skip_special_tokens=True)
# 如果labels=-100,说明这个label是无法编码的,应该用pad_token_id去进行填充
labels = np.where(labels != -100, labels, tokenizer.pad_token_id)
decoded_labels = tokenizer.batch_decode(labels, skip_special_tokens=True)
# 把预测后的输出转成适合metric.compute输入的格式
print("type(decoded_preds)=", type(decoded_preds))
print("type(decoded_labels)=", type(decoded_labels))
decoded_preds, decoded_labels = postprocess_text(decoded_preds, decoded_labels)
result = metric.compute(predictions=decoded_preds, references=decoded_labels)
result = {"bleu": result["score"]}
prediction_lens = [np.count_nonzero(pred != tokenizer.pad_token_id) for pred in preds]
result["gen_len"] = np.mean(prediction_lens)
result = {k: round(v, 4) for k, v in result.items()}
print("result is as follow============================")
print(result)
return result
4. 训练
# 载入数据集:载入类型为text
'''文档命名要规范后者无法加载'''
raw_datasets = load_dataset("text", data_files={"train": "train.txt", "validation": "val.txt"})
print(raw_datasets)
# 加载已训模型helsinki-NLP/opus-mt-en-ar的分词器
'''定义:模型名称 -> 网站加载;路径 -> 调用本地'''
model_checkpoint = "helsinki-NLP_0"
'''use_fast:True -> 快速分词器;False -> 基本分词器'''
tokenizer = AutoTokenizer.from_pretrained(model_checkpoint, use_fast=True)
# 把数据按源语言和目标语言分开,作为dataset中的input_ids和labels
source_lang = "en"
target_lang = "ar"
# 生成分词后的数据集
'''batched=True:使用多线程同时并行对输入进行处理'''
tokenized_datasets = raw_datasets.map(preprocess_function, batched=True)
print(tokenized_datasets)
# 加载预训练模型
model = AutoModelForSeq2SeqLM.from_pretrained(model_checkpoint)
# 设定训练参数
batch_size = 8
args = Seq2SeqTrainingArguments(
"test-translation",
evaluation_strategy="epoch",
learning_rate=2e-5,
per_device_train_batch_size=batch_size,
per_device_eval_batch_size=batch_size,
weight_decay=0.01,
save_total_limit=3, # 至多保存的模型个数
num_train_epochs=10,
predict_with_generate=True,
fp16=False,
)
# 将分词的输入分batch再次处理后喂给模型
data_collator = DataCollatorForSeq2Seq(tokenizer, model=model)
# 定义评估方法,使用bleu指标
metric = load_metric("sacrebleu")
print("successfully import metric")
# 开始训练
os.environ['CUDA_VISIBLE_DEVICES'] = '0'
trainer = Seq2SeqTrainer(
model,
args,
train_dataset=tokenized_datasets["train"],
eval_dataset=tokenized_datasets["validation"],
data_collator=data_collator,
tokenizer=tokenizer,
compute_metrics=compute_metrics
)
trainer.train()
(五)评估总结
1. 训练分数
这次训练的样本量较小,只有2w词条左右,并且数据质量较好,所以测试分数参考性不大。接下来考虑在超过10w词条量的数据集上训练。
2. 调用翻译
# 载入翻译工具包和分词工具包
from transformers import MarianMTModel, MarianTokenizer
# 加载自己训练好的模型和分词器
model_path = r"path of your model" # 设置为自己的模型路径
model = MarianMTModel.from_pretrained(model_path)
tokenizer = MarianTokenizer.from_pretrained(model_path)
# 输入待翻译的文本
input_text = input("想翻译什么喵?\n")
# 使用分词器将输入文本转换为模型输入格式
input_ids = tokenizer.encode(input_text, return_tensors="pt")
# 使用模型进行翻译
translated = model.generate(input_ids)
# 将翻译结果解码为文本
translated_text = tokenizer.decode(translated[0], skip_special_tokens=True)
# 打印翻译结果
print("Input text:", input_text)
print("Translated text:", translated_text)