发布时间:2023-04-21 文章分类:电脑百科 投稿人:赵颖 字号: 默认 | | 超大 打印

文章目录

    • 一、快速开始
      • 1.1 安装PaddleNLP并 加载数据集
      • 1.2 数据预处理
      • 1.3 加载预训练模型
      • 1.4 设置评价指标和训练策略
      • 1.5 模型训练与评估
      • 1.6 模型预测
    • 二、数据处理
      • 2.1 整体介绍
      • 2.2 加载内置数据集
      • 2.3 自定义数据集
        • 2.3.1 从本地文件创建数据集
        • 2.3.2 paddle.io.Dataset/IterableDataset 创建数据集
        • 2.3.3 从其他python对象创建数据集
      • 2.4 数据处理
        • 2.4.1 基于预训练模型的数据处理
          • 2.4.1.1 Tokenizer
          • 2.4.1.2 `map()`方法
          • 2.4.1.3 Batchify
        • 2.4.2 基于非预训练模型的数据处理
    • 三、Transformer预训练模型
    • 四、使用Trainer API进行训练
      • 4.1 Trainer基本使用方法介绍
      • 4.2 Trainer /TrainingArguments参数介绍
    • 五、模型压缩
      • 5.1 模型压缩简介
      • 5.2 模型压缩快速启动示例
      • 5.3 四步启动模型压缩
        • 5.3.1获取模型压缩参数 compression_args
        • 5.3.2 实例化 Trainer 并调用 compress
        • 5.3.3实现自定义评估函数,以适配自定义压缩任务
        • 5.3.4:传参并运行压缩脚本
        • 5.3.5 CompressionArguments 参数介绍
      • 5.4 模型评估与部署
      • 5.5 模型压缩示例
        • 5.5.1 Bi-LSTM知识蒸馏
        • 5.5.2 BERT压缩
    • 六、PaddleNLP Embedding API:预训练词向量
    • 七、Taskflow API:PaddleNLP一键预测功能
    • 八、Transformer高性能加速
    • 九、实践案例
    • 十、PaddleNLP常见问题汇总
      • 10.1 NLP精选5问
          • Q1.1 如何加载自己的本地数据集,以便使用PaddleNLP的功能?
          • Q1.2 PaddleNLP会将内置的数据集、模型下载到默认路径,如何修改路径?
          • Q1.3 PaddleNLP中如何保存、加载训练好的模型?
          • Q1.4 当训练样本较少时,有什么推荐的方法能提升模型效果吗?
          • Q1.5 如何提升模型的性能,提升QPS?
      • 10.2 NLP通用问题
          • Q2.1 数据类别分布不均衡, 有哪些应对方法?
          • Q2.2 如果使用预训练模型,一般需要多少条样本?
      • 10.3 PaddleNLP实战问题
        • 10.3.1数据集和数据处理
          • Q3.1 使用自己的数据集训练预训练模型时,如何引入额外的词表?
        • 10.3.2模型训练调优
          • Q3.2 如何加载自己的预训练模型,进而使用PaddleNLP的功能?
          • Q3.3 如果训练中断,需要继续热启动训练,如何保证学习率和优化器能从中断地方继续迭代?
          • Q3.4 如何冻结模型梯度?
          • Q3.5 如何在eval阶段打印评价指标,在各epoch保存模型参数?
          • Q3.6 训练过程中,训练程序意外退出或Hang住,应该如何排查?
          • Q3.7 在模型验证和测试过程中,如何保证每一次的结果是相同的?
          • Q3.8 ERNIE模型如何返回中间层的输出?
      • 10.4 预测部署
          • Q3.9 PaddleNLP训练好的模型如何部署到服务器 ?
          • Q3.10 静态图模型如何转换成动态图模型?
      • 10.5 特定模型和应用场景咨询
          • Q4.1 【词法分析】LAC模型,如何自定义标签label,并继续训练?
          • Q4.2 信息抽取任务中,是否推荐使用预训练模型+CRF,怎么实现呢?
          • Q4.3.【阅读理解】`MapDatasets`的`map()`方法中对应的`batched=True`怎么理解,在阅读理解任务中为什么必须把参数`batched`设置为`True`?
          • Q4.4 【语义匹配】语义索引和语义匹配有什么区别?
          • Q4.5 【解语】wordtag模型如何自定义添加命名实体及对应词类?
      • 10.6 其他使用咨询
          • Q5.1 在CUDA11使用PaddlNLP报错?
          • Q5.2 如何设置parameter?
          • Q5.3 GPU版的Paddle虽然能在CPU上运行,但是必须要有GPU设备吗?
          • Q5.4 如何指定用CPU还是GPU训练模型?
          • Q5.5 动态图模型和静态图模型的预测结果一致吗?
          • Q5.6 如何可视化acc、loss曲线图、模型网络结构图等?

PaddleNLP教程文档

全文参考
PaddleNLP官方文档

一、快速开始

参考:NLP文档《10分钟完成高精度中文情感分析》、NLP经典项目集02:使用预训练模型ERNIE优化情感分析

1.1 安装PaddleNLP并 加载数据集

安装相关过程和问题可以参考PaddleNLP的 安装文档

 pip install --upgrade paddlenlp -i https://pypi.org/simple

  PaddleNLP内置了适用于阅读理解、文本分类、序列标注、机器翻译等下游任务的多个数据集,这里我们使用公开中文情感分析数据集ChnSenticorp,包含7000多条正负向酒店评论数据,一键加载数据集,打印标签和前五条数据:

import paddlenlp as ppnlp
from paddlenlp.datasets import load_dataset
train_ds, dev_ds, test_ds = load_dataset(
    "chnsenticorp", splits=["train", "dev", "test"])
print(train_ds.label_list)
for data in train_ds.data[:5]:
    print(data)
['0', '1']
{'text': '选择珠江花园的原因就是方便,有电动扶梯直接到达海边,周围餐馆、食廊、商场、超市、摊位一应俱全。酒店装修一般,但还算整洁。 泳池在大堂的屋顶,因此很小,不过女儿倒是喜欢。 包的早餐是西式的,还算丰富。 服务吗,一般', 'label': 1, 'qid': ''}
{'text': '15.4寸笔记本的键盘确实爽,基本跟台式机差不多了,蛮喜欢数字小键盘,输数字特方便,样子也很美观,做工也相当不错', 'label': 1, 'qid': ''}
{'text': '房间太小。其他的都一般。。。。。。。。。', 'label': 0, 'qid': ''}
{'text': '1.接电源没有几分钟,电源适配器热的不行. 2.摄像头用不起来. 3.机盖的钢琴漆,手不能摸,一摸一个印. 4.硬盘分区不好办.', 'label': 0, 'qid': ''}
{'text': '今天才知道这书还有第6卷,真有点郁闷:为什么同一套书有两种版本呢?当当网是不是该跟出版社商量商量,单独出个第6卷,让我们的孩子不会有所遗憾。', 'label': 1, 'qid': ''}

可见每条数据包含一句评论和对应的标签,0代表负向评论,1代表正向评论。

1.2 数据预处理

  1. 加载tokenizer
    PaddleNLP对于各种预训练模型已经内置了相应的tokenizer。指定想要使用的模型名字即可加载对应的tokenizer。这里我们使用预训练模型ERNIE:
# 设置想要使用模型的名称
MODEL_NAME = "ernie-1.0" # 也可使用'ernie-tiny'
tokenizer = ppnlp.transformers.ErnieTokenizer.from_pretrained(MODEL_NAME)
  1. 测试tokenizer效果
# 一行代码完成切分token,映射token ID以及拼接特殊token
encoded_text = tokenizer(text="请输入测试样例")
for key, value in encoded_text.items():
    print("{}:\n\t{}".format(key, value))
# 使用paddle.to_tensor将input_ids 和token_type_ids转化成tensor格式
input_ids = paddle.to_tensor([encoded_text['input_ids']])
print("input_ids : {}".format(input_ids))
segment_ids = paddle.to_tensor([encoded_text['token_type_ids']])
print("token_type_ids : {}".format(segment_ids))
# 此时即可输入ERNIE模型中得到相应输出
sequence_output, pooled_output = ernie_model(input_ids, segment_ids)
print("Token wise output: {}, Pooled output: {}".format(sequence_output.shape, pooled_output.shape))
input_ids:
	[1, 647, 789, 109, 558, 525, 314, 656, 2]
token_type_ids:
	[0, 0, 0, 0, 0, 0, 0, 0, 0]
input_ids : Tensor(shape=[1, 9], dtype=int64, place=CUDAPlace(0), stop_gradient=True,
       [[1  , 647, 789, 109, 558, 525, 314, 656, 2  ]])
token_type_ids : Tensor(shape=[1, 9], dtype=int64, place=CUDAPlace(0), stop_gradient=True,
       [[0, 0, 0, 0, 0, 0, 0, 0, 0]])
Token wise output: [1, 9, 768], Pooled output: [1, 768]
  1. 数据处理
from functools import partial
from paddlenlp.data import Stack, Tuple, Pad
from utils import  convert_example, create_dataloader
# 模型运行批处理大小
batch_size = 32
max_seq_length = 128
trans_func = partial(convert_example,tokenizer=tokenizer,max_seq_length=max_seq_length)
batchify_fn = lambda samples, fn=Tuple(
    Pad(axis=0, pad_val=tokenizer.pad_token_id),  # input
    Pad(axis=0, pad_val=tokenizer.pad_token_type_id),  # segment
    Stack(dtype="int64")  # label
): [data for data in fn(samples)]
train_data_loader = create_dataloader(train_ds,batch_size=batch_size,
    batchify_fn=batchify_fn,trans_fn=trans_func,mode='train')
dev_data_loader = create_dataloader(dev_ds,batch_size=batch_size,
    batchify_fn=batchify_fn,trans_fn=trans_func,mode='dev')

1.3 加载预训练模型

  PaddleNLP对于各种预训练模型已经内置了对于下游任务-文本分类的Fine-tune网络,情感分析本质是一个文本分类任务,在ERNIE模型后拼接上一个全连接网络(Full Connected)即可进行分类。

ernie_model = ppnlp.transformers.ErnieModel.from_pretrained(MODEL_NAME)
model = ppnlp.transformers.ErnieForSequenceClassification.from_pretrained(MODEL_NAME, num_classes=len(train_ds.label_list))

1.4 设置评价指标和训练策略

from paddlenlp.transformers import LinearDecayWithWarmup
learning_rate = 5e-5 
epochs = 1 #3
warmup_proportion = 0.1 # 学习率预热比例
weight_decay = 0.01     # 权重衰减系数,类似模型正则项策略,避免模型过拟合
num_training_steps = len(train_data_loader) * epochs
lr_scheduler = LinearDecayWithWarmup(learning_rate, num_training_steps, warmup_proportion)
optimizer = paddle.optimizer.AdamW(
    learning_rate=lr_scheduler,
    parameters=model.parameters(),
    weight_decay=weight_decay,
    apply_decay_param_fun=lambda x: x in [
        p.name for n, p in model.named_parameters()
        if not any(nd in n for nd in ["bias", "norm"])
    ])
criterion = paddle.nn.loss.CrossEntropyLoss()
metric = paddle.metric.Accuracy()

1.5 模型训练与评估

模型训练的过程通常有以下步骤:

每训练一个epoch时,程序将会评估一次,评估当前模型训练的效果。

!mkdir /home/aistudio/checkpoint # checkpoint文件夹用于保存训练模型
import paddle.nn.functional as F
from utils import evaluate
global_step = 0
for epoch in range(1, epochs + 1):
    for step, batch in enumerate(train_data_loader, start=1):
        input_ids, segment_ids, labels = batch
        logits = model(input_ids, segment_ids)
        loss = criterion(logits, labels)
        probs = F.softmax(logits, axis=1)
        correct = metric.compute(probs, labels)
        metric.update(correct)
        acc = metric.accumulate()
        global_step += 1
        if global_step % 10 == 0 :
            print("global step %d, epoch: %d, batch: %d, loss: %.5f, acc: %.5f" % (global_step, epoch, step, loss, acc))
        loss.backward()
        optimizer.step()
        lr_scheduler.step()
        optimizer.clear_grad()
    evaluate(model, criterion, metric, dev_data_loader)
model.save_pretrained('/home/aistudio/checkpoint')
tokenizer.save_pretrained('/home/aistudio/checkpoint')

1.6 模型预测

调用predict()函数即可一键预测。

from utils import predict
data = [
    {"text":'这个宾馆比较陈旧了,特价的房间也很一般。总体来说一般'},
    {"text":'怀着十分激动的心情放映,可是看着看着发现,在放映完毕后,出现一集米老鼠的动画片'},
    {"text":'作为老的四星酒店,房间依然很整洁,相当不错。机场接机服务很好,可以在车上办理入住手续,节省时间。'},
]
label_map = {0: 'negative', 1: 'positive'}
results = predict(
    model, data, tokenizer, label_map, batch_size=batch_size)
for idx, text in enumerate(data):
    print('Data: {} \t Lable: {}'.format(text, results[idx]))

  更多预训练模型参考《Transformer预训练模型》,更多训练示例参考《PaddleNLP Examples》

二、数据处理

参考《数据处理》

2.1 整体介绍

  1. 核心API:
  1. 数据处理流程设计

PaddleNLP的通用数据处理流程如下:

下面是基于Bert的文本分类任务的数据处理流程图:
PaddleNLP教程文档

2.2 加载内置数据集

  1. 快速加载内置数据集

  目前PaddleNLP内置20余个NLP数据集,涵盖阅读理解,文本分类,序列标注,机器翻译等多项任务,可以通过PaddleNLP Datasets API来快速加载,实际使用时请根据需要添加splits信息。以 msra_ner 数据集为例:

from paddlenlp.datasets import load_dataset
train_ds, test_ds = load_dataset("msra_ner", splits=("train", "test"))

  load_dataset() 方法会从 paddlenlp.datasets 下找到msra_ner数据集对应的数据读取脚本(默认路径:paddlenlp/datasets/msra_ner.py),并调用脚本中 DatasetBuilder 类的相关方法生成数据集。
  生成数据集可以以 MapDatasetIterDataset 两种类型返回,分别是对 paddle.io.Datasetpaddle.io.IterableDataset 的扩展。返回类型通过 lazy 参数定义,Flase 对应返回 MapDataset (默认),True 对应返回 IterDataset。(关于 MapDataset 和 IterDataset 功能和异同可以参考API文档 datasets)

  1. 选择子数据集

  有些数据集是很多子数据集的集合,每个子数据集都是一个独立的数据集。例如 GLUE 数据集就包含COLA, SST2, MRPC, QQP等10个子数据集。load_dataset() 方法提供了一个 name 参数用来指定想要获取的子数据集。使用方法如下:

from paddlenlp.datasets import load_dataset
train_ds, dev_ds = load_dataset("glue", name="cola", splits=("train", "dev"))
  1. 以内置数据集格式读取本地数据集

  有的时候,我们希望使用数据格式与内置数据集相同的本地数据替换某些内置数据集的数据(例如参加SQuAD竞赛,对训练数据进行了数据增强)。 load_dataset() 方法提供的 data_files 参数可以实现这个功能。以 SQuAD 为例:

from paddlenlp.datasets import load_dataset
train_ds, dev_ds = load_dataset("squad", data_files=("my_train_file.json", "my_dev_file.json"))
test_ds = load_dataset("squad", data_files="my_test_file.json")

  对于某些数据集,不同的split的读取方式不同。此时需要在 splits 参数中以传入与 data_files 一一对应 的split信息。此时 splits 不再代表选取的内置数据集,而代表以何种格式读取本地数据集。以 COLA 数据集为例:

from paddlenlp.datasets import load_dataset
train_ds, test_ds = load_dataset("glue", "cola", splits=["train", "test"], data_files=["my_train_file.csv", "my_test_file.csv"])

2.3 自定义数据集

2.3.1 从本地文件创建数据集

  从本地文件创建数据集时,我们 推荐 根据本地数据集的格式给出读取function并传入 load_dataset() 中创建数据集。以 waybill_ie 快递单信息抽取任务中的数据为例:

from paddlenlp.datasets import load_dataset
def read(data_path):
    with open(data_path, 'r', encoding='utf-8') as f:
        # 跳过列名
        next(f)
        for line in f:
            words, labels = line.strip('\n').split('\t')
            words = words.split('\002')
            labels = labels.split('\002')
            yield {'tokens': words, 'labels': labels}
# data_path为read()方法的参数
map_ds = load_dataset(read, data_path='train.txt',lazy=False)
iter_ds = load_dataset(read, data_path='train.txt',lazy=True)

2.3.2 paddle.io.Dataset/IterableDataset 创建数据集

  有时我们希望更方便的使用一些数据处理(例如convert to feature, 数据清洗,数据增强等),此时可以使用 paddle.io.Dataset/IterableDataset 创建数据集,然后套上一层 MapDataset 或 IterDataset,就可以把原数据集转换成PaddleNLP的数据集。

from paddle.io import Dataset
from paddlenlp.datasets import MapDataset
class MyDataset(Dataset):
    def __init__(self, path):
        def load_data_from_source(path):
            ...
            ...
            return data
        self.data = load_data_from_source(path)
    def __getitem__(self, idx):
        return self.data[idx]
    def __len__(self):
        return len(self.data)
ds = MyDataset(data_path)  # paddle.io.Dataset
new_ds = MapDataset(ds)    # paddlenlp.datasets.MapDataset

2.3.3 从其他python对象创建数据集

理论上,我们可以使用任何包含 __getitem__() 方法和 __len__() 方法的python对象创建 MapDataset。包括 ListTupleDataFrame 等。只要将符合条件的python对象作为初始化参数传入 MapDataset 即可完成创建。

from paddlenlp.datasets import MapDataset
data_source_1 = [1,2,3,4,5]
data_source_2 = ('a', 'b', 'c', 'd')
list_ds = MapDataset(data_source_1)
tuple_ds = MapDataset(data_source_2)
print(list_ds[0])  # 1
print(tuple_ds[0]) # a

同样的,我们也可以使用包含 __iter__() 方法的python对象创建 IterDataset 。例如 ListGenerator 等。创建方法与 MapDataset 相同。

list_ds = IterDataset(data_source_1)
gen_ds = IterDataset(data_source_2)
print([data for data in list_ds]) # ['a', 'b', 'c', 'd']
print([data for data in gen_ds])  # [0, 1, 2, 3, 4]

上例中直接将 生成器 对象传入 IterDataset 所生成的数据集。其数据只能迭代 一次

我们也可以使用同样的方法从第三方数据集创建PaddleNLP数据集,例如HuggingFace Dataset

from paddlenlp.datasets import MapDataset
from datasets import load_dataset
hf_train_ds = load_dataset('msra_ner', split='train')
print(type(train_ds)) # <class 'datasets.arrow_dataset.Dataset'>
train_ds = MapDataset(train_ds)
print(type(train_ds)) # <class 'paddlenlp.datasets.dataset.MapDataset'>
print(train_ds[2]) # {'id': '2',
                   #  'ner_tags': [0, 0, 0, 5, 0, 0, 5, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0,
                   #               0, 0, 0, 0, 0, 0, 5, 6, 0, 0, 0, 0, 0, 0, 0, 0, 0],
                   #  'tokens': ['因', '有', '关', '日', '寇', '在', '京', '掠', '夺', '文', '物',
                   #             '详', '情', ',', '藏', '界', '较', '为', '重', '视', ',', '也',
                   #             '是', '我', '们', '收', '藏', '北', '京', '史', '料', '中', '的',
                   #             '要', '件', '之', '一', '。']}
hf_train_ds = load_dataset('cmrc2018', split='train')
train_ds = MapDataset(hf_train_ds)
print(train_ds[1818]) # {'answers': {'answer_start': [9], 'text': ['字仲可']},
                      #  'context': '徐珂(),原名昌,字仲可,浙江杭县(今属杭州市)人。光绪举人。
                      #              后任商务印书馆编辑。参加南社。1901年在上海担任了《外交报》、
                      #              《东方杂志》的编辑,1911年,接管《东方杂志》的“杂纂部”。与潘仕成、
                      #              王晋卿、王辑塘、冒鹤亭等友好。编有《清稗类钞》、《历代白话诗选》、
                      #              《古今词选集评》等。光绪十五年(1889年)举人。后任商务印书馆编辑。
                      #              参加南社。曾担任袁世凯在天津小站练兵时的幕僚,不久离去。',
                      #  'id': 'TRAIN_113_QUERY_0',
                      #  'question': '徐珂字什么?'}
hf_train_ds = load_dataset('glue', 'sst2', split='train')
train_ds = MapDataset(hf_train_ds)
print(train_ds[0]) # {'idx': 0, 'label': 0, 'sentence': 'hide new secretions from the parental units '}
hf_train_ds = load_dataset('ptb_text_only', split='train')
train_ds = MapDataset(hf_train_ds)
print(train_ds[1]) # {'sentence': 'pierre <unk> N years old will join the board as a nonexecutive director nov. N'}

2.4 数据处理

  Dataset中通常为原始数据,需要经过一定的数据处理并进行采样组batch,而后通过 paddle.io.DataLoader 加载,为训练或预测使用,PaddleNLP中为其中各环节提供了相应的功能支持。

2.4.1 基于预训练模型的数据处理

2.4.1.1 Tokenizer

  在使用预训练模型做NLP任务时,需要加载对应的Tokenizer,PaddleNLP在 PreTrainedTokenizer 中内置的 __call__() 方法可以实现基础的数据处理功能。PaddleNLP内置的所有预训练模型的Tokenizer都继承自 PreTrainedTokenizer ,下面以BertTokenizer举例说明:

from paddlenlp.transformers import BertTokenizer
tokenizer = BertTokenizer.from_pretrained('bert-base-chinese')
# 单句转换(单条数据)
print(tokenizer(text='天气不错')) # {'input_ids': [101, 1921, 3698, 679, 7231, 102], 'token_type_ids': [0, 0, 0, 0, 0, 0]}
# 句对转换(单条数据)
print(tokenizer(text='天气',text_pair='不错')) # {'input_ids': [101, 1921, 3698, 102, 679, 7231, 102], 'token_type_ids': [0, 0, 0, 0, 1, 1, 1]}
# 单句转换(多条数据)
print(tokenizer(text=['天气','不错'])) # [{'input_ids': [101, 1921, 3698, 102], 'token_type_ids': [0, 0, 0, 0]},
                                      #  {'input_ids': [101, 679, 7231, 102], 'token_type_ids': [0, 0, 0, 0]}]

关于 __call__() 方法的其他参数和功能,请查阅PreTrainedTokenizer

2.4.1.2 map()方法

  MapDatasetmap() 方法支持传入一个函数,对数据集内的数据进行统一转换。下面我们以 LCQMC 的数据处理流程为例:

from paddlenlp.transformers import BertTokenizer
from paddlenlp.datasets import load_dataset
tokenizer = BertTokenizer.from_pretrained('bert-base-chinese')
train_ds = load_dataset('lcqmc', splits='train')
print(train_ds[0]) # {'query': '喜欢打篮球的男生喜欢什么样的女生', 'title': '爱打篮球的男生喜欢什么样的女生', 'label': 1}

  可以看到,LCQMC 是一个句对匹配任务,即判断两个句子的意思是否相似的2分类任务。下面编写基于 PreTrainedTokenizer 的数据处理函数并传入数据集的 map() 方法。

from functools import partial
def convert_example(example, tokenizer):
    tokenized_example = tokenizer(text=example['query'],text_pair=example['title'])   
    tokenized_example['label'] = [example['label']] # 加上label用于训练
    return tokenized_example
trans_func = partial(convert_example,tokenizer=tokenizer)
train_ds.map(trans_func)
print(train_ds[0]) # {'input_ids': [101, 1599, 3614, 2802, 5074, 4413, 4638, 4511, 4495,
                   #                1599, 3614, 784, 720, 3416, 4638, 1957, 4495, 102,
                   #                4263, 2802, 5074, 4413, 4638, 4511, 4495, 1599, 3614,
                   #                784, 720, 3416, 4638, 1957, 4495, 102],
                   #  'token_type_ids': [0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0,
                   #                     0, 0, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1],
                   #  'label': [1]}

  可以看到,数据集中的文本数据已经被处理成了模型可以接受的 feature 。


from functools import partial
def convert_examples(examples, tokenizer):
    querys = [example['query'] for example in examples]
    titles = [example['title'] for example in examples]
    tokenized_examples = tokenizer(text=querys, text_pair=titles,return_dict=False)
    # 加上label用于训练
    for idx in range(len(tokenized_examples)):
        tokenized_examples[idx]['label'] = [examples[idx]['label']]
    return tokenized_examples
trans_func = partial(convert_examples, tokenizer=tokenizer)
train_ds.map(trans_func, batched=True)
print(train_ds[0]) # {'input_ids': [101, 1599, 3614, 2802, 5074, 4413, 4638, 4511, 4495,
                   #                1599, 3614, 784, 720, 3416, 4638, 1957, 4495, 102,
                   #                4263, 2802, 5074, 4413, 4638, 4511, 4495, 1599, 3614,
                   #                784, 720, 3416, 4638, 1957, 4495, 102],
                   #  'token_type_ids': [0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0,
                   #                     0, 0, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1],
                   #  'label': [1]}

  在本例中两种实现的结果是相同的。但是在诸如阅读理解,对话等任务中,一条原始数据可能会产生多个 feature 的情况(参见 run_squad.py )通常需要将 batched 参数设置为 True 。

2.4.1.3 Batchify

  PaddleNLP内置了多种collate function,配合 paddle.io.BatchSampler 可以协助用户简单的完成组batch的操作。

  我们继续以 LCQMC 的数据处理流程为例。从上一节最后可以看到,处理后的单条数据是一个 字典 ,包含 input_ids , token_type_ids 和 label 三个key。其中 input_ids 和 token_type_ids 是需要进行 padding 操作后输入模型的,而 label 是需要 stack 之后传入loss function的。

  因此,我们使用PaddleNLP内置的 Dict() ,Stack() 和 Pad() 函数整理batch中的数据。最终的 batchify_fn() 如下:

from paddlenlp.data import Dict, Stack, Pad
# 使用Dict函数将Pad,Stack等函数与数据中的键值相匹配
train_batchify_fn = lambda samples, fn=Dict({
    'input_ids': Pad(axis=0, pad_val=tokenizer.pad_token_id),
    'token_type_ids': Pad(axis=0, pad_val=tokenizer.pad_token_type_id),
    'label': Stack(dtype="int64")
}): fn(samples)

之后使用 paddle.io.BatchSamplerbatchify_fn() 构建 paddle.io.DataLoader

from paddle.io import DataLoader, BatchSampler
train_batch_sampler = BatchSampler(train_ds, batch_size=2, shuffle=True)
train_data_loader = DataLoader(dataset=train_ds, batch_sampler=train_batch_sampler, collate_fn=train_batchify_fn)

到此,一个完整的数据准备流程就完成了。关于更多batchify方法,请查阅 collate。

  • 当需要进行 单机多卡 训练时,需要将 BatchSampler 更换为 DistributedBatchSampler 。更多有关 paddle.io.BatchSampler 的信息,请查阅 BatchSampler。
  • 当需要诸如batch内排序,按token组batch等更复杂的组batch功能时。可以使用PaddleNLP内置的 SamplerHelper 。相关用例请参考 reader.py。

2.4.2 基于非预训练模型的数据处理

  使用非预训练模型做NLP任务时,我们可以借助PaddleNLP内置的 JiebaTokenizer 和 Vocab 完成数据处理的相关功能,整体流程与使用预训练模型基本相似。我们以中文情感分析 ChnSentiCorp 数据集为例:

from paddlenlp.data import JiebaTokenizer, Vocab
from paddlenlp.datasets import load_dataset
train_ds = load_dataset('chnsenticorp', splits='train')
print(train_ds[0]) # {'text': '选择珠江花园的原因就是方便,有电动扶梯直接到达海边,周围餐馆、食廊、商场、超市、摊位一应俱全。
                   #  酒店装修一般,但还算整洁。 泳池在大堂的屋顶,因此很小,不过女儿倒是喜欢。 包的早餐是西式的,还算丰富。
                   #  服务吗,一般', 'label': 1}
# 从本地词典文件构建Vocab
vocab = Vocab.load_vocabulary('./senta_word_dict.txt', unk_token='[UNK]', pad_token='[PAD]')
# 使用Vocab初始化JiebaTokenizer
tokenizer = JiebaTokenizer(vocab)
  • Vocab 除了可以从本地词典文件初始化之外,还提供多种初始化方法,包括从 dictionary 创建、从数据集创建等。详情请查阅Vocab。
  • 除了使用内置的 JiebaTokenizer 外,用户还可以使用任何自定义的方式或第三方库进行分词,之后使用 Vocab.to_indices() 方法将token转为id。

之后与基于预训练模型的数据处理流程相似,编写数据处理函数并传入 map() 方法:

def convert_example(example, tokenizer):
    input_ids = tokenizer.encode(example["text"])
    valid_length = [len(input_ids)]
    label = [example["label"]]
    return input_ids, valid_length, label
trans_fn = partial(convert_example, tokenizer=tokenizer)
train_ds.map(trans_fn)
print(train_ds[0]) # ([417329, 128448, 140437, 173188, 118001, 213058, 595790, 1106339, 940533, 947744, 169206,
                   #   421258, 908089, 982848, 1106339, 35413, 1055821, 4782, 377145, 4782, 238721, 4782, 642263,
                   #   4782, 891683, 767091, 4783, 672971, 774154, 1250380, 1106339, 340363, 146708, 1081122,
                   #   4783, 1, 943329, 1008467, 319839, 173188, 909097, 1106339, 1010656, 261577, 1110707,
                   #   1106339, 770761, 597037, 1068649, 850865, 4783, 1, 993848, 173188, 689611, 1057229, 1239193,
                   #   173188, 1106339, 146708, 427691, 4783, 1, 724601, 179582, 1106339, 1250380],
                   #  [67],
                   #  [1])

可以看到,原始数据已经被处理成了 feature 。但是这里我们发现单条数据并不是一个 字典 ,而是 元组 。所以我们的 batchify_fn() 也要相应的做一些调整:

from paddlenlp.data import Tuple, Stack, Pad
# 使用Tuple函数将Pad,Stack等函数与数据中的键值相匹配
train_batchify_fn = lambda samples, fn=Tuple((
    Pad(axis=0, pad_val=vocab.token_to_idx.get('[PAD]', 0)),  # input_ids
    Stack(dtype="int64"),  # seq len
    Stack(dtype="int64")  # label
)): fn(samples)

  可以看到,Dict() 函数是将单条数据中的键值与 Pad() 等函数进行对应,适用于单条数据是字典的情况。而 Tuple() 是通过单条数据中不同部分的index进行对应的。所以需要 注意 的是 convert_example() 方法和 batchify_fn() 方法的匹配。之后的流程与基于预训练模型的数据处理相同。

三、Transformer预训练模型

参考文档《PaddleNLP Transformer预训练模型》
PaddleNLP Transformer API提供了丰富的预训练模型,使用Auto模块,就可以加载不同网络结构的预训练模型,以及下游任务Fine-tuning。

  1. 加载数据集
  2. 通过 from_pretrained() 方法加载预训练模型: Auto模块(包括AutoModel, AutoTokenizer, 及各种下游任务类)提供了方便易用的接口, 无需指定类别,即可调用不同网络结构的预训练模型。 第一个参数是汇总表中对应的 Pretrained Weight,可加载对应的预训练权重。 AutoModelForSequenceClassification 初始化 __init__ 所需的其他参数,如 num_classes 等, 也是通过 from_pretrained() 传入。Tokenizer 使用同样的 from_pretrained 方法加载。
  3. 通过 Dataset 的 map 函数,使用 tokenizer 将 dataset 从原始文本处理成模型的输入。
  4. 定义 BatchSamplerDataLoader,组合Batch。
  5. 定义训练所需的优化器,loss函数等,就可以开始进行模型fine-tune任务。
import paddle
from functools import partial
import numpy as np
from paddlenlp.datasets import load_dataset
from paddlenlp.transformers import AutoModelForSequenceClassification, AutoTokenizer
train_ds = load_dataset("chnsenticorp", splits=["train"])
model = AutoModelForSequenceClassification.from_pretrained("bert-wwm-chinese", num_classes=len(train_ds.label_list))
tokenizer = AutoTokenizer.from_pretrained("bert-wwm-chinese")
def convert_example(example, tokenizer):
    encoded_inputs = tokenizer(text=example["text"], max_seq_len=512, pad_to_max_seq_len=True)
    return tuple([np.array(x, dtype="int64") for x in [
            encoded_inputs["input_ids"], encoded_inputs["token_type_ids"], [example["label"]]]])
train_ds = train_ds.map(partial(convert_example, tokenizer=tokenizer))
batch_sampler = paddle.io.BatchSampler(dataset=train_ds, batch_size=8, shuffle=True)
train_data_loader = paddle.io.DataLoader(dataset=train_ds, batch_sampler=batch_sampler, return_list=True)
optimizer = paddle.optimizer.AdamW(learning_rate=0.001, parameters=model.parameters())
criterion = paddle.nn.loss.CrossEntropyLoss()
for input_ids, token_type_ids, labels in train_data_loader():
    logits = model(input_ids, token_type_ids)
    loss = criterion(logits, labels)
    loss.backward()
    optimizer.step()
    optimizer.clear_grad()

  上面的代码给出使用预训练模型的简要示例,更完整详细的示例代码, 可以参考:使用预训练模型Fine-tune完成中文文本分类任务

  PaddleNLP的Transformer预训练模型包含从 huggingface.co 直接转换的模型权重和百度自研模型权重,方便社区用户直接迁移使用。 目前共包含了40多个主流预训练模型,500多个模型权重,详见Transformer预训练模型汇总。

四、使用Trainer API进行训练

参考文档《PaddleNLP Trainer API》
PaddleNLP提供了Trainer训练API,针对训练过程的通用训练配置做了封装,比如:

4.1 Trainer基本使用方法介绍

用户输入模型,数据集,就可以使用Trainer API高效快速的实现预训练、微调等任务。下面以中文情感分类数据集chnsenticorp为例。

  1. 导入需要用到的头文件:模型、Tokenizer、Trainer组件
    • 其中Trainer是训练主要入口,用户传入模型,数据集,即可进行训练
    • TrainingArguments 包含了用户需要的大部分训练参数。
    • PdArgumentParser 是用户输出参数的工具
from functools import partial
import paddle
from paddlenlp.datasets import load_dataset
from paddlenlp.transformers import AutoModelForSequenceClassification, AutoTokenizer
from paddlenlp.trainer import Trainer, TrainingArguments, PdArgumentParser
  1. 设置好用户参数
from dataclasses import dataclass
@dataclass
class DataArguments:
    dataset: str = field(
        default=None,
        metadata={"help": "The name of the dataset to use."})
    max_seq_length: int = field(
        default=128,
        metadata={"help": "The maximum total input sequence length after tokenization."})
parser = PdArgumentParser(TrainingArguments, DataArguments)
(training_args, data_args) = parser.parse_args_into_dataclasses()
  1. 加载模型,tokenizer, 数据集
    • 数据集需要输出的是一个dict。dict中的key,需要和模型的输入名称对应。
    • labels如果模型没有使用到,我们还需要额外定义criterion,计算最后的loss损失。
train_dataset = load_dataset("chnsenticorp", splits=["train"])
model = AutoModelForSequenceClassification.from_pretrained("ernie-3.0-medium-zh", num_classes=len(train_dataset.label_list))
tokenizer = AutoTokenizer.from_pretrained("ernie-3.0-medium-zh")
def convert_example(example, tokenizer):
    encoded_inputs = tokenizer(text=example["text"], max_seq_len=128, pad_to_max_seq_len=True)
    encoded_inputs["labels"] = int(example["label"])
    return encoded_inputs
train_dataset = train_dataset.map(partial(convert_example, tokenizer=tokenizer))
  1. 构造Trainer实例,进行模型训练。

    • 这里传入model,criterion,args,train_dataset,tokenizer这些训练需要的组件,构建了实例化的trainer
    • 使用trainer.train()接口开始训练过程。训练完成后,可以保存模型,保存一些日志。
trainer = Trainer(
    model=model,
    criterion=paddle.nn.loss.CrossEntropyLoss(),
    args=training_args,
    train_dataset=train_dataset if training_args.do_train else None,
    tokenizer=tokenizer)
if training_args.do_train:
    train_result = trainer.train()
    metrics = train_result.metrics
    trainer.save_model()
    trainer.log_metrics("train", metrics)
    trainer.save_state()

预训练的使用方式可以参考ERNIE-1.0 Trainer版本

4.2 Trainer /TrainingArguments参数介绍

Trainer参数:

TrainingArguments 参数

--output_dir
                      保存模型输出和和中间checkpoints的输出目录。(`str`, 必须, 默认为 `None`)
                      The output directory where the model predictions and
                      checkpoints will be written. (default: None)
--overwrite_output_dir
                      如果 `True`,覆盖输出目录的内容。如果 `output_dir` 指向检查点
                      目录,则使用它继续训练。(`bool`, 可选, 默认为 `False`)
                      Overwrite the content of the output directory. Use
                      this to continue training if output_dir points to a
                      checkpoint directory. (default: False)
--do_train
                      是否进行训练任务。 注:`Trainer`不直接使用此参数,而是提供给用户
                      的训练/评估脚本使用。(`bool`, 可选, 默认为 `False`)
                      Whether to run training. (default: False)
--do_eval
                      是否进行评估任务。同上。(`bool`, 可选, 默认为 `False`)
                      Whether to run eval on the dev set. (default: False)
--do_predict
                      是否进行预测任务。同上。(`bool`, 可选, 默认为 `False`)
                      Whether to run predictions on the test set. (default:False)
--do_export
                      是否进行模型导出任务。同上。(`bool`, 可选, 默认为 `False`)
                      Whether to export infernece model. (default: False)
--evaluation_strategy {no,steps,epoch}
                      评估策略,(`str`, 可选,默认为 `"no"`):
                      训练期间采用的评估策略。可能的值为:
                          - `"no"`:训练期间不进行评估。
                          - `"steps"`:评估在每个`eval_steps`完成(并记录)。
                          - `"epoch"`:在每个 epoch 结束时进行评估。
                      The evaluation strategy to use. (default: no)
--prediction_loss_only
                      在执行评估和预测任务时,只返回loss的值。(`bool`, 可选, 默认为 `False`)
                      When performing evaluation and predictions, only
                      returns the loss. (default: False)
--per_device_train_batch_size
                      用于训练的每个 GPU 核心/CPU 的batch大小.(`int`,可选,默认为 8)
                      Batch size per GPU core/CPU for training. (default: 8)
--per_device_eval_batch_size
                      用于评估的每个 GPU 核心/CPU 的batch大小.(`int`,可选,默认为 8)
                      Batch size per GPU core/CPU for evaluation. (default:8)
--gradient_accumulation_steps
                      在执行反向,更新回传梯度之前,累积梯度的更新步骤数(`int`,可选,默认为 1)
                      Number of updates steps to accumulate before
                      performing a backward/update pass. (default: 1)
--learning_rate
                      优化器的初始学习率, (`float`,可选,默认为 5e-05)
                      The initial learning rate for optimizer. (default: 5e-05)
--weight_decay
                      除了所有bias和 LayerNorm 权重之外,应用于所有层的权重衰减数值。(`float`,可选,默认为 0.0)
                      Weight decay for AdamW if we apply some. (default:
                      0.0)
--adam_beta1
                      AdamW的优化器的 beta1 超参数。(`float`,可选,默认为 0.9)
                      Beta1 for AdamW optimizer (default: 0.9)
--adam_beta2
                      AdamW的优化器的 beta2 超参数。(`float`,可选,默认为 0.999)
                      Beta2 for AdamW optimizer (default: 0.999)
--adam_epsilon
                      AdamW的优化器的 epsilon 超参数。(`float`,可选,默认为 1e-8)
                      Epsilon for AdamW optimizer. (default: 1e-08)
--max_grad_norm
                      最大梯度范数(用于梯度裁剪)。(`float`,可选,默认为 1.0)
                      Max gradient norm. (default: 1.0)
--num_train_epochs
                      要执行的训练 epoch 总数(如果不是整数,将在停止训练
                      之前执行最后一个 epoch 的小数部分百分比)。
                      (`float`, 可选, 默认为 3.0):
                      Total number of training epochs to perform. (default:3.0)
--max_steps
                      如果设置为正数,则表示要执行的训练步骤总数。
                      覆盖`num_train_epochs`。(`int`,可选,默认为 -1)
                      If > 0: set total number of training steps to
                      perform.Override num_train_epochs. (default: -1
--lr_scheduler_type
                      要使用的学习率调度策略。 (`str`, 可选, 默认为 `"linear"`)
                      The scheduler type to use. (default: linear) 支持,linear, cosine, constant, constant_with_warmup.
--warmup_ratio
                      用于从 0 到 `learning_rate` 的线性warmup的总训练步骤的比例。(`float`,可选,默认为 0.0)
                      Linear warmup over warmup_ratio fraction of total
                      steps. (default: 0.0)
--warmup_steps
                      用于从 0 到 `learning_rate` 的线性warmup的步数。覆盖warmup_ratio参数。
                      (`int`,可选,默认为 0)
                      Linear warmup over warmup_steps. (default: 0)
--log_on_each_node
                      在多节点分布式训练中,是在每个节点上记录一次,还是仅在主节点上记录节点。(`bool`,可选,默认为`True`)
                      When doing a multinode distributed training, whether
                      to log once per node or just once on the main node.
                      (default: True)
--logging_dir
                      VisualDL日志目录。(`str`,可选,默认为NoneNone情况下会修改为 *output_dir/runs/**CURRENT_DATETIME_HOSTNAME**
                      VisualDL log dir. (default: None)
--logging_strategy {no,steps,epoch}
                      (`str`, 可选,默认为 `"steps"`)
                      训练期间采用的日志记录策略。可能的值为:
                          - `"no"`:训练期间不进行记录。
                          - `"epoch"`:记录在每个 epoch 结束时完成。
                          - `"steps"`:记录是每 `logging_steps` 完成的。
                      The logging strategy to use. (default: steps)
--logging_first_step
                      是否记录和评估第一个 `global_step`。(`bool`,可选,默认为`False`)
                      Log the first global_step (default: False)
--logging_steps
                      如果 `logging_strategy="steps"`,则两个日志之间的更新步骤数。
                      (`int`,可选,默认为 500)
                      Log every X updates steps. (default: 500)
--save_strategy {no,steps,epoch}
                      (`str`, 可选,默认为 `"steps"`)
                      训练期间采用的checkpoint保存策略。可能的值为:
                          - `"no"`:训练期间不保存。
                          - `"epoch"`:保存在每个 epoch 结束时完成。
                          - `"steps"`:保存是每`save_steps`完成。
                      The checkpoint save strategy to use. (default: steps)
--save_steps
                      如果 `save_strategy="steps"`,则在两个checkpoint保存之间的更新步骤数。
                      (`int`,可选,默认为 500)
                      Save checkpoint every X updates steps. (default: 500)
--save_total_limit
                      如果设置次参数,将限制checkpoint的总数。删除旧的checkpoints
                      `输出目录`。(`int`,可选)
                      Limit the total amount of checkpoints. Deletes the
                      older checkpoints in the output_dir. Default is
                      unlimited checkpoints (default: None)
--save_on_each_node
                      在做多节点分布式训练时,是在每个节点上保存模型和checkpoints,
                      还是只在主节点上。当不同的节点使用相同的存储时,不应激活此功能,
                      因为每个节点的文件将以相同的名称保存。(`bool`, 可选, 默认为 `False`)
                      When doing multi-node distributed training, whether to
                      save models and checkpoints on each node, or only on
                      the main one (default: False)
--no_cuda
                      是否不使用 CUDA,即使CUDA环境可用。(`bool`, 可选, 默认为 `False`)
                      Do not use CUDA even when it is available (default:
                      False)
--seed
                      设置的随机种子。为确保多次运行的可复现性。(`int`,可选,默认为 42)
                      Random seed that will be set at the beginning of
                      training. (default: 42)
--bf16
                      是否使用 bf16 混合精度训练而不是 fp32 训练。需要 Ampere 或更高的 NVIDIA
                      显卡架构支持。这是实验性质的API,以后可能会修改。
                      (`bool`, 可选, 默认为 `False`)
                      Whether to use bf16 (mixed) precision instead of
                      32-bit. Requires Ampere or higher NVIDIA architecture.
                      This is an experimental API and it may change.
                      (default: False)
--fp16
                      是否使用 fp16 混合精度训练而不是 fp32 训练。
                      (`bool`, 可选, 默认为 `False`)
                      Whether to use fp16 (mixed) precision instead of
                      32-bit (default: False)
--fp16_opt_level
                      混合精度训练模式,可为``O1``或``O2``模式,默认``O1``模式,默认O1.
                      O1表示混合精度训练,O2表示纯fp16/bf16训练。
                      只在fp16或bf16选项开启时候生效.
                      (`str`, 可选, 默认为 `O1`)
                      For fp16: AMP optimization level selected in
                      ['O0', 'O1', and 'O2']. See details at https://www.pad
                      dlepaddle.org.cn/documentation/docs/zh/develop/api/pad
                      dle/amp/auto_cast_cn.html (default: O1)
--scale_loss
                      fp16/bf16训练时,scale_loss的初始值。
                      (`float`,可选,默认为 32768)
                      The value of initial scale_loss for fp16. (default: 32768)
--sharding
                      是否使用Paddle的Sharding数据并行功能,用户的参数。支持sharding `stage1`, `stage2` or `stage3`。
                      其中`stage2``stage3`可以和`offload`组合使用。
                      每个种策略分别为:
                          stage1 : optimizer 中的参数切分到不同卡
                          stage2 : optimizer  + gradient 中的参数切分到不同卡
                          stage3 : parameter + gradient + optimizer  中的参数都切分到不同卡
                          offload : offload parameters to cpu 部分参数存放到cpu中
                       (`str`,  可选, 默认为 `` 不使用sharding)
                       注意:当前stage3暂时不可用
                      Whether or not to use Paddle Sharding Data Parallel training (in distributed training
                      only). The base option should be `stage1`, `stage2` or `stage3` and you can add
                      CPU-offload to `stage2` or `stage3` like this: `stage2 offload` or `stage3 offload`.
                      Each stage means:
                          stage1 : optimizer state segmentation
                          stage2 : optimizer state + gradient segmentation
                          stage3 : parameter + gradient + optimizer state segmentation
                          offload : offload parameters to cpu
                      NOTICE: stage3 is temporarily unavaliable.
--sharding_degree
                      设置sharding的通信组参数,表示通信组的大小。同一个sharding通信组内的参数,进行sharding,分布到不同卡上。
                      不同sharding通信组之间,相当于单纯的数据并行。此选项只在sharding选项开启时候生效。
                      默认值为-1,表示所有训练的卡在同一个通信组内。
                      (`int`, 可选, 默认为 `-1`)
                      Sharding parameter in certain cards group. For example, aussume we use 2 machines each
                      with 8 cards, then set sharding_degree=8, sharding will only communication inside machine.
                      default -1 means sharding parameters between all workers. (`int`, *optional*, defaults to `-1`)
--recompute
                      是否使用重计算训练。可以节省显存。
                      重新计算前向过程以获取梯度,减少中间变量显存.
                      注:需要组网支持 recompute,默认使用 enable_recompute 关键字作为recompute功能开关。
                      (`bool`, 可选, 默认为 `False`)
                      Recompute the forward pass to calculate gradients. Used for saving memory (default: False)
--minimum_eval_times
                      最少评估次数,如果当前设置的eval_steps,评估次数少于minimum_eval_times,
                      此选项会覆盖eval_steps参数。
                      (`int`,可选,默认为 None)
                      If under eval_steps, the valid time is less then
                      minimum_eval_times, the config of override eval_steps.
                      (default: None)
--local_rank
                      分布式训练时,设备的本地rank值。
                      For distributed training: local_rank (default: -1)
--dataloader_drop_last
                      是否丢弃最后一个不完整的批次(如果数据集的长度不能被批次大小整除)
                      (`bool`,可选,默认为 False)
                      Drop the last incomplete batch if it is not divisible
                      by the batch size. (default: False)
--eval_steps
                      如果 `evaluation_strategy="steps"`,则两次评估之间的更新步骤数。将默认为相同如果未设置,则值为 `logging_steps`。
                      (`int`,可选,默认为 None)
                      Run an evaluation every X steps. (default: None)
--dataloader_num_workers
                      用于数据加载的子进程数。 0 表示数据将在主进程制造。
                      (`int`,可选,默认为 0)
                      Number of subprocesses to use for data loading. 0 means
                      that the data will be loaded in the main process. (default: 0)
--past_index
                      If >=0, uses the corresponding part of the output as
                      the past state for next step. (default: -1)
--run_name
                      An optional descriptor for the run. (default: None)
--device
                      运行的设备名称。支持cpu/gpu, 默认gpu
                      (`str`,可选,默认为 'gpu')
                      select cpu, gpu, xpu devices. (default: gpu)
--disable_tqdm
                      是否使用tqdm进度条
                      Whether or not to disable the tqdm progress bars.
                      (default: None)
--remove_unused_columns
                      去除Dataset中不用的字段数据
                      Remove columns not required by the model when using an
                      nlp.Dataset. (default: True)
--label_names
                      训练数据标签label的名称
                      The list of keys in your dictionary of inputs that
                      correspond to the labels. (default: None)
--load_best_model_at_end
                      训练结束后是否加载最优模型,通常与`metric_for_best_model`配合使用
                      Whether or not to load the best model found during
                      training at the end of training. (default: False)
--metric_for_best_model
                      最优模型指标,如`eval_accuarcy`等,用于比较模型好坏。
                      The metric to use to compare two different models.
                      (default: None)
--greater_is_better
                      与`metric_for_best_model`配合使用。
                      Whether the `metric_for_best_model` should be
                      maximized or not. (default: None)
--ignore_data_skip
                      重启训练时候,不略过已经训练的数据。
                      When resuming training, whether or not to skip the
                      first epochs and batches to get to the same training
                      data. (default: False)
--optim
                      优化器名称,默认为adamw,,(`str`, 可选,默认为 `adamw`)
                      The optimizer to use. (default: adamw)
--report_to
                      日志可视化显示,默认使用visualdl可视化展示。(可选,默认为 None,展示所有)
                      The list of integrations to report the results and
                      logs to. (default: None)
--resume_from_checkpoint
                      是否从断点重启恢复训练,(可选,默认为 None)
                      The path to a folder with a valid checkpoint for your
                      model. (default: None)
--skip_memory_metrics
                     是否跳过内存profiler检测。(可选,默认为True,跳过)
                     Whether or not to skip adding of memory profiler reports
                     to metrics.(default:True)

五、模型压缩

参考《PaddleNLP 模型压缩 API》、《进阶指南——模型压缩》、《ERNIE 3.0 轻量级模型》

5.1 模型压缩简介

  模型压缩在保证一定精度的情况下,能够降低模型的存储,加速模型的推理时间。常见的模型压缩方法主要包括模型裁剪、量化和蒸馏。下面分别对这几种方法进行简要的介绍。

  PaddleNLP 模型压缩 API ,支持对 ERNIE 类模型在下游任务微调后,进行裁剪、量化,以缩小模型体积减少内存占用,减少计算量提升推理速度,最终减少部署难度。

ERNIE 3.0 压缩效果
  如下表所示,ERNIE 3.0-Medium (6-layer, 384-hidden, 12-heads) 模型在三类任务(文本分类、序列标注、抽取式阅读理解)经过裁剪 + 量化后加速比均达到 3 倍左右,所有任务上平均精度损失可控制在 0.5 以内(0.46)。

							TNEWS 性能	    TNEWS 精度		MSRA_NER 性能	MSRA_NER 精度	CMRC2018 性能	CMRC2018 精度
ERNIE 3.0-Medium+FP32		1123.85(1.0x)	57.45			366.75(1.0x)	93.04			146.84(1.0x)	66.95
ERNIE 3.0-Medium+INT8		3226.26(2.9x)	56.99(-0.46)	889.33(2.4x)	92.70(-0.34)	348.84(2.4x)	66.32(-0.63
ERNIE 3.0-Medium+裁剪+FP32	1424.01(1.3x)	57.31(-0.14)	454.27(1.2x)	93.27(+0.23)	183.77(1.3x)	65.92(-1.03)
ERNIE 3.0-Medium+裁剪+INT8	3635.48(3.2x)	57.26(-0.19)	1105.26(3.0x)	93.20(+0.16)	444.27(3.0x)	66.17(-0.78)

5.2 模型压缩快速启动示例

  本项目提供了压缩 API 在分类(包含文本分类、文本匹配、自然语言推理、代词消歧等任务)、序列标注、抽取式阅读理解三大场景下的使用样例,可以分别参考 ERNIE 3.0 目录下的 compress_seq_cls.py 、compress_token_cls.py、compress_qa.py 脚本,启动方式如下:

# 分类任务
# 该脚本共支持 CLUE 中 7 个分类任务,超参不全相同,因此分类任务中的超参配置利用 config.yml 配置
python compress_seq_cls.py \
    --dataset "clue tnews"  \
    --model_name_or_path best_models/TNEWS  \
    --output_dir ./
# 序列标注任务
python compress_token_cls.py \
    --dataset "msra_ner"  \
    --model_name_or_path best_models/MSRA_NER \
    --output_dir ./ \
    --max_seq_length 128 \
    --per_device_train_batch_size 32 \
    --per_device_eval_batch_size 32 \
    --learning_rate 0.00005 \
    --remove_unused_columns False \
    --num_train_epochs 3
# 阅读理解任务
python compress_qa.py \
    --dataset "clue cmrc2018" \
    --model_name_or_path best_models/CMRC2018  \
    --output_dir ./ \
    --max_seq_length 512 \
    --learning_rate 0.00003 \
    --num_train_epochs 8 \
    --per_device_train_batch_size 24 \
    --per_device_eval_batch_size 24 \
    --max_answer_length 50 \

5.3 四步启动模型压缩

环境依赖

模型压缩 API 中的压缩功能依赖最新的 paddleslim 包。可运行以下命令安装:

pip install paddleslim -i https://pypi.tuna.tsinghua.edu.cn/simple

模型压缩 API 的使用大致分为四步:

示例代码

from paddlenlp.trainer import PdArgumentParser, CompressionArguments
# Step1: 使用 `PdArgumentParser` 解析从命令行传入的超参数,以获取压缩参数 `compression_args`;
parser = PdArgumentParser(CompressionArguments)
compression_args = parser.parse_args_into_dataclasses()
# Step2: 实例化 Trainer 并调用 compress()
trainer = Trainer(
    model=model,
    args=compression_args,
    data_collator=data_collator,
    train_dataset=train_dataset,
    eval_dataset=eval_dataset,
    criterion=criterion)
# Step 3: 使用内置模型和评估方法,则不需要实现自定义评估函数和 loss 计算函数
trainer.compress()
# Step4: 传参并运行压缩脚本
python compress.py \
    --output_dir ./compress_models  \
    --per_device_train_batch_size 32 \
    --per_device_eval_batch_size 32 \
    --num_train_epochs 4 \
    --width_mult_list 0.75 \
    --batch_size_list 4 8 16 \
    --batch_num_list 1 \

5.3.1获取模型压缩参数 compression_args

  使用 PdArgumentParser 对象解析从命令行得到的超参数,从而得到 compression_args,并将 compression_args 传给 Trainer 对象。获取 compression_args 的方法通常如下:

from paddlenlp.trainer import PdArgumentParser, CompressionArguments
# Step1: 使用 `PdArgumentParser` 解析从命令行传入的超参数,以获取压缩参数 `compression_args`;
parser = PdArgumentParser(CompressionArguments)
compression_args = parser.parse_args_into_dataclasses()

5.3.2 实例化 Trainer 并调用 compress

Trainer 实例化参数介绍

其中,criterion 函数定义示例:

# 支持的形式一:
def criterion(logits, labels):
    loss_fct = paddle.nn.BCELoss()
    start_ids, end_ids = labels
    start_prob, end_prob = outputs
    start_ids = paddle.cast(start_ids, 'float32')
    end_ids = paddle.cast(end_ids, 'float32')
    loss_start = loss_fct(start_prob, start_ids)
    loss_end = loss_fct(end_prob, end_ids)
    loss = (loss_start + loss_end) / 2.0
    return loss
# 支持的形式二:
class CrossEntropyLossForSQuAD(paddle.nn.Layer):
    def __init__(self):
        super(CrossEntropyLossForSQuAD, self).__init__()
    def forward(self, y, label):
        start_logits, end_logits = y
        start_position, end_position = label
        start_position = paddle.unsqueeze(start_position, axis=-1)
        end_position = paddle.unsqueeze(end_position, axis=-1)
        start_loss = paddle.nn.functional.cross_entropy(input=start_logits,
                                                        label=start_position)
        end_loss = paddle.nn.functional.cross_entropy(input=end_logits,
                                                      label=end_position)
        loss = (start_loss + end_loss) / 2
        return loss

用以上参数实例化 Trainer 对象,之后直接调用 compress()compress() 会根据选择的策略进入不同的分支,以进行裁剪或者量化的过程。

示例代码

from paddlenlp.trainer import PdArgumentParser, CompressionArguments
# Step1: 使用 `PdArgumentParser` 解析从命令行传入的超参数,以获取压缩参数 `compression_args`;
parser = PdArgumentParser(CompressionArguments)
compression_args = parser.parse_args_into_dataclasses()
# Step2: 实例化 Trainer 并调用 compress()
trainer = Trainer(
    model=model,
    args=compression_args,
    data_collator=data_collator,
    train_dataset=train_dataset,
    eval_dataset=eval_dataset,
    criterion=criterion)
trainer.compress()

5.3.3实现自定义评估函数,以适配自定义压缩任务

  当使用 DynaBERT 裁剪功能时,如果模型、Metrics 不符合下表的情况,那么模型压缩 API 中评估函数需要自定义。

  目前 DynaBERT 裁剪功能只支持 SequenceClassification 等三类 PaddleNLP 内置 class,并且内置评估器对应为 Accuracy、F1、Squad。

Model class name SequenceClassification TokenClassification QuestionAnswering
Metrics Accuracy F1 Squad

需要注意以下三个条件:

custom_evaluate() 函数定义示例:

    import paddle
    from paddle.metric import Accuracy
    @paddle.no_grad()
    def evaluate_seq_cls(self, model, data_loader):
        metric = Accuracy()
        model.eval()
        metric.reset()
        for batch in data_loader:
            logits = model(input_ids=batch['input_ids'],
                           token_type_ids=batch['token_type_ids'])
            # Supports paddleslim.nas.ofa.OFA model and nn.layer model.
            if isinstance(model, paddleslim.nas.ofa.OFA):
                logits = logits[0]
            correct = metric.compute(logits, batch['labels'])
            metric.update(correct)
        res = metric.accumulate()
        logger.info("acc: %s, " % res)
        model.train()
        return res

在调用 compress() 时传入这个自定义函数:

trainer.compress(custom_evaluate=evaluate_seq_cls)

5.3.4:传参并运行压缩脚本

这一步主要是将压缩需要用到的参数通过命令行传入,并启动压缩脚本。压缩启动命令:

示例代码

# Step4: 运行压缩脚本
python compress.py \
    --output_dir ./compress_models  \
    --per_device_train_batch_size 32 \
    --per_device_eval_batch_size 32 \
    --num_train_epochs 4 \
    --width_mult_list 0.75 \
    --batch_size_list 4 8 16 \
    --batch_num_list 1 \

下面会介绍模型压缩启动命令可以传递的超参数。

5.3.5 CompressionArguments 参数介绍

CompressionArguments 中的参数一部分是模型压缩功能特定参数,另一部分继承自 TrainingArguments,是压缩训练时需要设置的超参数。下面会进行具体介绍,

1. 公共参数

公共参数中的参数和具体的压缩策略无关。

2. DynaBERT 裁剪参数

当用户使用了 DynaBERT 裁剪、PTQ 量化策略(即策略中包含 ‘dynabert’、‘qat’ 时需要传入以下可选参数:

3. PTQ 量化参数

当用户使用了 PTQ 量化策略时需要传入以下可选参数:

4. QAT 量化参数

当用户使用了 QAT 量化策略时,除了可以设置上面训练相关的参数,还可以传入以下可选参数:

5.4 模型评估与部署

  裁剪、量化后的模型不能再通过 from_pretrained 导入进行预测,而是需要使用 Paddle 部署工具才能完成预测。压缩后的模型部署可以参考 部署文档 完成。

  1. Python 部署
    服务端部署可以从这里开始。可以利用 预测 backend 脚本,并参考 infer_cpu.py 或者 infer_gpu.py 来编写自己的预测脚本。并根据 Python 部署指南 的介绍安装预测环境,对压缩后的模型进行精度评估、性能测试以及部署。

  2. 服务化部署

    • Triton Inference Server 服务化部署指南
    • Paddle Serving 服务化部署指南
  3. Paddle2ONNX 部署:ONNX 导出及 ONNXRuntime 部署请参考:ONNX 导出及 ONNXRuntime 部署指南

  4. Paddle Lite 移动端部署:即将支持,敬请期待

5.5 模型压缩示例

参考《模型压缩简介》

下面对基于飞桨实现的常见的模型压缩示例进行介绍。

5.5.1 Bi-LSTM知识蒸馏

详见《由BERT到Bi-LSTM的知识蒸馏》

5.5.2 BERT压缩

详见《使用DynaBERT中的策略对BERT进行压缩》

六、PaddleNLP Embedding API:预训练词向量

参考《PaddleNLP Embedding API》、

七、Taskflow API:PaddleNLP一键预测功能

参考《PaddleNLP一键预测功能:Taskflow API》、《PaddleNLP系列课程一:Taskflow、小样本学习、FasterTransformer》第一章

八、Transformer高性能加速

参考《Transformer高性能加速》、《PaddleNLP系列课程一:Taskflow、小样本学习、FasterTransformer》第三章

九、实践案例

参考《案例集》

十、PaddleNLP常见问题汇总

10.1 NLP精选5问

Q1.1 如何加载自己的本地数据集,以便使用PaddleNLP的功能?

A: 通过使用PaddleNLP提供的 load_datasetMapDatasetIterDataset ,可以方便的自定义属于自己的数据集哦,也欢迎您贡献数据集到PaddleNLP repo。

从本地文件创建数据集时,我们 推荐 根据本地数据集的格式给出读取function并传入 load_dataset() 中创建数据集。
以waybill_ie快递单信息抽取任务中的数据为例:

from paddlenlp.datasets import load_dataset
def read(data_path):
    with open(data_path, 'r', encoding='utf-8') as f:
        # 跳过列名
        next(f)
        for line in f:
            words, labels = line.strip('\n').split('\t')
            words = words.split('\002')
            labels = labels.split('\002')
            yield {'tokens': words, 'labels': labels}
# data_path为read()方法的参数
map_ds = load_dataset(read, data_path='train.txt', lazy=False)
iter_ds = load_dataset(read, data_path='train.txt', lazy=True)

如果您习惯使用paddle.io.Dataset/IterableDataset来创建数据集也是支持的,您也可以从其他python对象如List对象创建数据集,详细内容可参照官方文档-自定义数据集。

Q1.2 PaddleNLP会将内置的数据集、模型下载到默认路径,如何修改路径?

A: 内置的数据集、模型默认会下载到$HOME/.paddlenlp/下,通过配置环境变量可下载到指定路径:

(1)Linux下,设置 export PPNLP_HOME="xxxx",注意不要设置带有中文字符的路径。

(2)Windows下,同样配置环境变量 PPNLP_HOME 到其他非中文字符路径,重启即可。

Q1.3 PaddleNLP中如何保存、加载训练好的模型?

A:(1)PaddleNLP预训练模型

​ 保存:

model.save_pretrained("./checkpoint')
tokenizer.save_pretrained("./checkpoint')

​ 加载:

model.from_pretrained("./checkpoint')
tokenizer.from_pretrained("./checkpoint')

(2)常规模型
保存:

emb = paddle.nn.Embedding(10, 10)
layer_state_dict = emb.state_dict()
paddle.save(layer_state_dict, "emb.pdparams") #保存模型参数

​ 加载:

emb = paddle.nn.Embedding(10, 10)
load_layer_state_dict = paddle.load("emb.pdparams") # 读取模型参数
emb.set_state_dict(load_layer_state_dict) # 加载模型参数
Q1.4 当训练样本较少时,有什么推荐的方法能提升模型效果吗?

A: 增加训练样本带来的效果是最直接的。此外,可以基于我们开源的预训练模型进行热启,再用少量数据集fine-tune模型。此外,针对分类、匹配等场景,小样本学习也能够带来不错的效果。

Q1.5 如何提升模型的性能,提升QPS?

A: 从工程角度,对于服务器端部署可以使用Paddle Inference高性能预测引擎进行预测部署。对于Transformer类模型的GPU预测还可以使用PaddleNLP中提供的FasterTransformer功能来进行快速预测,其集成了NV FasterTransformer并进行了功能增强。

从模型策略角度,可以使用一些模型小型化技术来进行模型压缩,如模型蒸馏和裁剪,通过小模型来实现加速。PaddleNLP中集成了ERNIE-Tiny这样一些通用小模型供下游任务微调使用。另外PaddleNLP提供了模型压缩示例,实现了DynaBERT、TinyBERT、MiniLM等方法策略,可以参考对自己的模型进行蒸馏压缩。

10.2 NLP通用问题

Q2.1 数据类别分布不均衡, 有哪些应对方法?

A: 可以采用以下几种方法优化类别分布不均衡问题:

(1)欠采样:对样本量较多的类别进行欠采样,去除一些样本,使得各类别数目接近。

(2)过采样:对样本量较少的类别进行过采样,选择样本进行复制,使得各类别数目接近。

(3)修改分类阈值:直接使用类别分布不均衡的数据训练分类器,会使得模型在预测时更偏向于多数类,所以不再以0.5为分类阈值,而是针对少数类在模型仅有较小把握时就将样本归为少数类。

(4)代价敏感学习:比如LR算法中设置class_weight参数。

Q2.2 如果使用预训练模型,一般需要多少条样本?

A: 很难定义具体需要多少条样本,取决于具体的任务以及数据的质量。如果数据质量没问题的话,分类、文本匹配任务所需数据量级在百级别,翻译则需要百万级能够训练出一个比较鲁棒的模型。如果样本量较少,可以考虑数据增强,或小样本学习。

10.3 PaddleNLP实战问题

10.3.1数据集和数据处理

Q3.1 使用自己的数据集训练预训练模型时,如何引入额外的词表?

A: 预训练模型通常会有配套的tokenzier和词典,对于大多数中文预训练模型,如ERNIE-3.0,使用的都是字粒度的输入,tokenzier会将句子转换为字粒度的形式,模型无法收到词粒度的输入。如果希望引入额外的词典,需要修改预训练模型的tokenizer和词典,可以参考这里blog,另外注意embedding矩阵也要加上这些新增词的embedding表示。

另外还有一种方式可以使用这些字典信息,可以将数据中在词典信息中的词进行整体mask进行一个mask language model的二次预训练,这样经过二次训练的模型就包含了对额外字典的表征。可参考 PaddleNLP 预训练数据流程。

此外还有些词粒度及字词混合粒度的预训练模型,在这些词粒度的模型下引入额外的词表也会容易些,我们也将持续丰富PaddleNLP中的预训练模型。

10.3.2模型训练调优

Q3.2 如何加载自己的预训练模型,进而使用PaddleNLP的功能?

A: 以bert为例,如果是使用PaddleNLP训练,通过save_pretrained()接口保存的模型,可通过from_pretrained()来加载:

tokenizer = BertTokenizer.from_pretrained("bert-base-uncased")
model = BertModel.from_pretrained("bert-base-uncased")

如果不是上述情况,可以使用如下方式加载模型,也欢迎您贡献模型到PaddleNLP repo中。

(1)加载BertTokenizerBertModel

tokenizer = BertTokenizer.from_pretrained("bert-base-uncased")
model = BertModel.from_pretrained("bert-base-uncased")

(2)调用save_pretrained()生成 model_config.jsontokenizer_config.jsonmodel_state.pdparamsvocab.txt 文件,保存到./checkpoint

tokenizer.save_pretrained("./checkpoint")
model.save_pretrained("./checkpoint")

(3)修改model_config.jsontokenizer_config.json这两个配置文件,指定为自己的模型,之后通过from_pretrained()加载模型。

tokenizer = BertTokenizer.from_pretrained("./checkpoint")
model = BertModel.from_pretrained("./checkpoint")
Q3.3 如果训练中断,需要继续热启动训练,如何保证学习率和优化器能从中断地方继续迭代?

A:

(1)完全恢复训练状态,可以先将lr optimizermodel的参数保存下来:

paddle.save(lr_scheduler.state_dict(), "xxx_lr")
paddle.save(optimizer.state_dict(), "xxx_opt")
paddle.save(model.state_dict(), "xxx_para")

(2)加载lr optimizermodel参数即可恢复训练:

lr_scheduler.set_state_dict(paddle.load("xxxx_lr"))
optimizer.set_state_dict(paddle.load("xxx_opt"))
model.set_state_dict(paddle.load("xxx_para"))
Q3.4 如何冻结模型梯度?

A:
有多种方法可以尝试:

(1)可以直接修改 PaddleNLP 内部代码实现,在需要冻结梯度的地方用 paddle.no_grad() 包裹一下

paddle.no_grad() 的使用方式,以对 forward() 进行冻结为例:

   # Method 1
   class Model(nn.Layer):
      def __init__(self, ...):
         ...
      def forward(self, ...):
         with paddle.no_grad():
            ...
   # Method 2
   class Model(nn.Layer):
      def __init__(self, ...):
         ...
      @paddle.no_grad()
      def forward(self, ...):
         ...

paddle.no_grad() 的使用也不局限于模型内部实现里面,也可以包裹外部的方法,比如:

   @paddle.no_grad()
   def evaluation(...):
      ...
      model = Model(...)
      model.eval()
      ...

(2)第二种方法:以ERNIE为例,将模型输出的 tensor 设置 stop_gradient 为 True。可以使用 register_forward_post_hook 按照如下的方式尝试:

   def forward_post_hook(layer, input, output):
      output.stop_gradient=True
   self.ernie.register_forward_post_hook(forward_post_hook)

(3)第三种方法:在 optimizer 上进行处理,model.parameters 是一个 List,可以通过 name 进行相应的过滤,更新/不更新某些参数,这种方法需要对网络结构的名字有整体了解,因为网络结构的实体名字决定了参数的名字,这个使用方法有一定的门槛:

   [ p for p in model.parameters() if 'linear' not in p.name]  # 这里就可以过滤一下linear层,具体过滤策略可以根据需要来设定
Q3.5 如何在eval阶段打印评价指标,在各epoch保存模型参数?

A: 飞桨主框架提供了两种训练与预测的方法,一种是用 paddle.Model()对模型进行封装,通过高层API如Model.fit()Model.evaluate()Model.predict()等完成模型的训练与预测;另一种就是基于基础API常规的训练方式。

(1)对于第一种方法:

(2)对于第二种方法:

Q3.6 训练过程中,训练程序意外退出或Hang住,应该如何排查?

A: 一般先考虑内存、显存(使用GPU训练的话)是否不足,可将训练和评估的batch size调小一些。

需要注意,batch size调小时,学习率learning rate也要调小,一般可按等比例调整。

Q3.7 在模型验证和测试过程中,如何保证每一次的结果是相同的?

A: 在验证和测试过程中常常出现的结果不一致情况一般有以下几种解决方法:

(1)确保设置了eval模式,并保证数据相关的seed设置保证数据一致性。

(2)如果是下游任务模型,查看是否所有模型参数都被导入了,直接使用bert-base这种预训练模型是不包含任务相关参数的,要确认导入的是微调后的模型,否则任务相关参数会随机初始化导致出现随机性。

(3)部分算子使用CUDNN后端产生的不一致性可以通过环境变量的设置来避免。如果模型中使用了CNN相关算子,可以设置FLAGS_cudnn_deterministic=True。如果模型中使用了RNN相关算子,可以设置CUBLAS_WORKSPACE_CONFIG=:16:8CUBLAS_WORKSPACE_CONFIG=:4096:2(CUDNN 10.2以上版本可用,参考CUDNN 8 release note)。

Q3.8 ERNIE模型如何返回中间层的输出?

A: 目前的API设计不保留中间层输出,当然在PaddleNLP里可以很方便地修改源码。
此外,还可以在ErnieModel__init__函数中通过register_forward_post_hook()为想要保留输出的Layer注册一个forward_post_hook函数,在forward_post_hook函数中把Layer的输出保存到一个全局的List里面。forward_post_hook函数将会在forward函数调用之后被调用,并保存Layer输出到全局的List。详情参考register_forward_post_hook()

10.4 预测部署

Q3.9 PaddleNLP训练好的模型如何部署到服务器 ?

A: 我们推荐在动态图模式下开发,静态图模式部署。

(1)动转静

动转静,即将动态图的模型转为可用于部署的静态图模型。
动态图接口更加易用,python 风格的交互式编程体验,对于模型开发更为友好,而静态图相比于动态图在性能方面有更绝对的优势。因此动转静提供了这样的桥梁,同时兼顾开发成本和性能。
可以参考官方文档 动态图转静态图文档,使用 paddle.jit.to_static 完成动转静。
另外,在 PaddleNLP 我们也提供了导出静态图模型的例子,可以参考 waybill_ie 模型导出。

(2)借助Paddle Inference部署

动转静之后保存下来的模型可以借助Paddle Inference完成高性能推理部署。Paddle Inference内置高性能的CPU/GPU Kernel,结合细粒度OP横向纵向融合等策略,并集成 TensorRT 实现模型推理的性能提升。具体可以参考文档 Paddle Inference 简介。
为便于初次上手的用户更易理解 NLP 模型如何使用Paddle Inference,PaddleNLP 也提供了对应的例子以供参考,可以参考 /PaddleNLP/examples 下的deploy目录,如基于ERNIE的命名实体识别模型部署。

Q3.10 静态图模型如何转换成动态图模型?

A: 首先,需要将静态图参数保存成ndarray数据,然后将静态图参数名和对应动态图参数名对应,最后保存成动态图参数即可。详情可参考参数转换脚本。

10.5 特定模型和应用场景咨询

Q4.1 【词法分析】LAC模型,如何自定义标签label,并继续训练?

A: 更新label文件tag.dict,添加 修改下CRF的标签数即可。

可参考自定义标签示例,增量训练自定义LABLE示例。

Q4.2 信息抽取任务中,是否推荐使用预训练模型+CRF,怎么实现呢?

A: 预训练模型+CRF是一个通用的序列标注的方法,目前预训练模型对序列信息的表达也是非常强的,也可以尝试直接使用预训练模型对序列标注任务建模。

Q4.3.【阅读理解】MapDatasetsmap()方法中对应的batched=True怎么理解,在阅读理解任务中为什么必须把参数batched设置为True

A: batched=True就是对整个batch(这里不一定是训练中的batch,理解为一组数据就可以)的数据进行map,即map中的trans_func接受一组数据为输入,而非逐条进行map。在阅读理解任务中,根据使用的doc_stride不同,一条样本可能被转换成多条feature,对数据逐条map是行不通的,所以需要设置batched=True

Q4.4 【语义匹配】语义索引和语义匹配有什么区别?

A: 语义索引要解决的核心问题是如何从海量 Doc 中通过 ANN 索引的方式快速、准确地找出与 query 相关的文档,语义匹配要解决的核心问题是对 query和文档更精细的语义匹配信息建模。换个角度理解, 语义索引是要解决搜索、推荐场景下的召回问题,而语义匹配是要解决排序问题,两者要解决的问题不同,所采用的方案也会有很大不同,但两者间存在一些共通的技术点,可以互相借鉴。

Q4.5 【解语】wordtag模型如何自定义添加命名实体及对应词类?

A: 其主要依赖于二次构造数据来进行finetune,同时要更新termtree信息。wordtag分为两个步骤:
(1)通过BIOES体系进行分词;
(2)将分词后的信息和TermTree进行匹配。
因此我们需要:
(1)分词正确,这里可能依赖于wordtag的finetune数据,来让分词正确;
(2)wordtag里面也需要把分词正确后term打上相应的知识信息。wordtag自定义TermTree的方式将在后续版本提供出来。

可参考issue。

10.6 其他使用咨询

Q5.1 在CUDA11使用PaddlNLP报错?

A: 在CUDA11安装,可参考issue,其他CUDA版本安装可参考 官方文档

Q5.2 如何设置parameter?

A: 有多种方法:
(1)可以通过set_value()来设置parameter,set_value()的参数可以是numpy或者tensor

   layer.weight.set_value(
                    paddle.tensor.normal(
                        mean=0.0,
                        std=self.initializer_range
                        if hasattr(self, "initializer_range") else
                        self.ernie.config["initializer_range"],
                        shape=layer.weight.shape))

(2)通过create_parameter()设置参数。

    class MyLayer(paddle.nn.Layer):
        def __init__(self):
            super(MyLayer, self).__init__()
            self._linear = paddle.nn.Linear(1, 1)
            w_tmp = self.create_parameter([1,1])
            self.add_parameter("w_tmp", w_tmp)
        def forward(self, input):
            return self._linear(input)
    mylayer = MyLayer()
    for name, param in mylayer.named_parameters():
        print(name, param)
Q5.3 GPU版的Paddle虽然能在CPU上运行,但是必须要有GPU设备吗?

A: 不支持 GPU 的设备只能安装 CPU 版本的 PaddlePaddle。 GPU 版本的 PaddlePaddle 如果想只在 CPU 上运行,可以通过 export CUDA_VISIBLE_DEVICES=-1 来设置。

Q5.4 如何指定用CPU还是GPU训练模型?

A: 一般我们的训练脚本提供了 --device 选项,用户可以通过 --device 选择需要使用的设备。

具体而言,在Python文件中,我们可以通过·paddle.device.set_device()·,设置为gpu或者cpu,可参考 set_device文档。

Q5.5 动态图模型和静态图模型的预测结果一致吗?

A: 正常情况下,预测结果应当是一致的。如果遇到不一致的情况,可以及时反馈给 PaddleNLP 的开发人员,我们进行处理。

Q5.6 如何可视化acc、loss曲线图、模型网络结构图等?

A: 可使用VisualDL进行可视化。其中acc、loss曲线图的可视化可参考Scalar——折线图组件使用指南,模型网络结构的可视化可参考Graph——网络结构组件使用指南。