ERNIE源码学习与实践:为超越ChatGPT打下技术基础!

★★★ 本文源自AlStudio社区精品项目,【点击此处】查看更多精品内容 >>>

ERNIE学习与实践:为超越ChatGPT打下技术基础!

ERNIE是BERT相爱相杀的好基友,由ERNIE发展起来的文心大模型,是GPT3.0的强劲竞争对手,未来还会挑战ChatGPT的江湖地位!

在“BERT学习与实践:为紧追潮流ChatGPT做好技术准备!”项目中,我们从源码到微调从头实践,对BERT有了较详细的了解。在了解BERT的基础上,本项目从头从源码到部署进行了学习和实践。不想当将军的士兵不是好士兵,不想超越ChatGPT的模型不是好模型!

读万卷书,不如行万里路!近期有很多关于ChatGPT的交流和会议,大家提出了很多的优秀见解和点子,但是这些点子如果不实践不落地,不会对现实造成任何影响!只有动手实践,才能检验知识,才能应用到实际,进而才能改变世界!希望本项目能在帮助大家把点子变成现实方面尽一份力。

Ernie系列模型!

ERNIE共有1.0 2.0 和3.0三个大版本,1.0和2.0结构一样,只是训练数据和训练方式不一样。3.0使用了大模型和海量的数据,3.0也提供了Tiny系列模型,有的跟1.0模型一样大,有的小很多,但是效果却比Ernie1.0好很多,更适合产业落地!

模型模型大小(参数量)(中文)数据量训练方法
ERNIE1.0参考bert base(110M)Wiki,baike,news,tieba (CLUECorpus2020 语料 200G)pretraining + finetuning
ERNIE2.0参考bert base(110M), bert large(340M)wiki,news,dialogue,IR, discourse relationpretraining + finetuning
ERNIE3.010B ERNIE 3.0 Titan 260B(GPT3.0 175B)4TB(ERNIE2.0,search,web,QA-long, QA-short, novel, poetry&couplet, medical, law, financial,KG)progressive training + finetuning / zero-shot
ERNIE3.0 Tiny110M 最小5.4M4TB蒸馏量化压缩 training + finetuning / zero-shot

本项目从源码入手,复现Ernie 1.0版本模型构建,并实践完成训练数据集生成,Ernie预训练,微调训练,并最终部署Ernie模型。

从源码角度将Ernie弄清楚,再去学习GPT3以及ChatGPT就会更顺利了。

一、ERNIE 系列模型

ERNIE是在BERT横空出世后不久就面世的竟品。
学习ERNIE1.0的相关知识,文档参考:https://github.com/PaddlePaddle/PaddleNLP/tree/develop/model_zoo/ernie-1.0

ERNIE跟BERT有很大相似性,但是市面上研究BERT的人较多,BERT的衍生模型也较多。相对而言,研究ERNIE的人较少。俗话说,众人拾柴火焰高,大家多多研究ERNIE,建设好ERNIE的生态,那么最终国内的NLP一定能够登顶!

ERNIE目前有三个版本,版本1.0是最开始的版本,可以预训练和微调使用。ERNIE 2.0主要是增加了数据集,改进了训练任务,提高了精度,模型代码主要部分跟1.0一样。ERNIE3.0是大模型,模型参数和数据集都超大,普通开发者没有条件从头开始训练,但是官方发布了Tiny版本的预训练模型,可以微调后使用,效果相当好。

本节就以ERNIE1.0 为例进行学习和实践。

Ernie 1.0模型简介

ERNIE是百度开创性提出的基于知识增强的持续学习语义理解框架,它将大数据预训练与多源丰富知识相结合,通过持续学习技术,不断吸收海量文本数据中词汇、结构、语义等方面的知识,实现模型效果不断进化。

ERNIE在情感分析、文本匹配、自然语言推理、词法分析、阅读理解、智能问答等16个公开数据集上全面显著超越世界领先技术,在国际权威的通用语言理解评估基准GLUE上,得分首次突破90分,获得全球第一。 相关创新成果也被国际顶级学术会议AAAI、IJCAI收录。 同时,ERNIE在工业界得到了大规模应用,如搜索引擎、新闻推荐、广告系统、语音交互、智能客服等。

ERNIE 通过建模海量数据中的词、实体及实体关系,学习真实世界的语义知识。相较于 BERT 学习原始语言信号,ERNIE 直接对先验语义知识单元进行建模,增强了模型语义表示能力。

这里我们举个例子:

Learnt by BERT :哈 [mask] 滨是 [mask] 龙江的省会,[mask] 际冰 [mask] 文化名城。
Learnt by ERNIE:[mask] [mask] [mask] 是黑龙江的省会,国际 [mask] [mask] 文化名城。

在 BERT 模型中,我们通过『哈』与『滨』的局部共现,即可判断出『尔』字,模型没有学习与『哈尔滨』相关的任何知识。而 ERNIE 通过学习词与实体的表达,使模型能够建模出『哈尔滨』与『黑龙江』的关系,学到『哈尔滨』是 『黑龙江』的省会以及『哈尔滨』是个冰雪城市。

由于数据集里含有词的信息,所以Ernie能学到词与实体的表达,增强了模型语义表达能力。同时也就知道了,分词效果会影响Ernie模型效果,lac效果好于jieba,所以最终用了lac进行分词(wordtag效果好,但速度比lac慢了100倍)

总结就是:Ernie与BERT模型类似,不同点是:Ernie在构建数据集的时候,就使用了LAC进行分词,然后对“词“进行Mask遮挡预训练。(BERT是对字进行遮挡)

Ernie 1.0项目特色

  • 中文预训练
    提供了完整中文预训练流程,从词表构造、数据处理、任务训练,到下游任务。
    提供中文Whole Word Mask,支持文本动态Mask。
  • 数据流程,
    数据预处理流程高效,40分钟即可完成14G ERNIE数据制作。
    数据稳定可复现,多数据集即插即用。
  • 分布式训练,
    支持多机多卡,支持混合精度、重计算、梯度累积等功能。

本项目在展示Ernie1.0源代码的基础上,将官方预训练流程重新实践一遍。

中文预训练

ERNIE预训练采用的是MLM(Mask Language Model)掩码语言模型的训练方式,采用WWM(Whole Word Mask)方式,对于完整语义单元的Token,会同时进行Mask。同时还进行SOP(Sentence Order Predict,BERT中称为NSP)句子顺序预测的训练方式。整体的训练损失loss是mlm_loss + sop_loss。

ERNIE 中文预训练更详细的介绍文档请可以参见ERNIE 中文预训练介绍。

具体实践在第三节进行。

预训练模型贡献

PaddleNLP为开发者提供了community模块,用户可以上传自己训练的模型,开源给其他用户使用。 使用本文档给出的参数配置,在CLUECorpusSmall数据集上训练,可以得到zhui/ernie-1.0-cluecorpussmall参数,可直接使用。

model = AutoModelForMaskedLM.from_pretrained('zhui/ernie-1.0-cluecorpussmall')

贡献预训练模型的方法,可以参考贡献预训练模型权重教程。

下游任务微调

使用 PaddleNLP 只需要一行代码可以拿到 ERNIE 系列模型,方便之后在自己的下游数据下进行微调,从而获得具体任务上效果更好的模型。参见ERNIE 中文预训练介绍

模型评估

使用训练中产出的checkpoint,或者paddlenlp内置的模型权重,使用本脚本,用户可以快速对当前模型效果进行评估。

预测部署

FastDeploy ERNIE 1.0 模型 Python 部署示例

FastDeploy ERNIE 3.0 模型 Python 部署示例

二、Ernie 1.0源代码学习

读万卷书,不如行万里路,采用读源代码的方式,更有利于学习和理解Ernie!
更详细的源码解析见项目内文件:Ernie源码学习。查看源代码,我们可以找到预训练源文件run_pretrain.py和模型源文件modeling.py 。Ernie的源代码主要在modeling.py中,而结合run_pretrain.py文件则更有利于理解源代码。

导入需要的库并定义变量

在官方源文件中,模型变量是定义在argparse的变量空间中,这里为了更清晰的阅读代码,将变量从变量空间拆解出来。
比如:“type_vocab_size”: 2, # token_type_ids的词典大小,值为2时token_type_ids取值为0和1。
也就是输入数据token_type_ids有[0, 1]两种标记 。(比如binary_head变量则表示预训练任务的选择,如果binary_head为True则训MLN和NSP,否则只训MLN)

为了简洁,ErnieTokenizer源代码不进行展示,直接从PaddleLNLP库中调用。

先安装需要的库文件,PaddleNLP一定要-U安装新版本,系统自带的版本低了。

!pip install paddlenlp -Uq
!pip install tool_helpers -q
from typing import Optional, Tupleimport paddle
import paddle.nn as nn
import paddle.nn.functional as F
from paddle import Tensorfrom paddlenlp.transformers import ErnieTokenizertokenizer = ErnieTokenizer.from_pretrained('ernie-1.0')vocab_size: int = 18000  # 词向量数
hidden_size: int = 768  # 中间层长度
num_hidden_layers: int = 12  # 隐藏层数
num_attention_heads: int = 12  # 注意力机制头数
task_id=0  # 任务id
intermediate_size: int = 3072
hidden_act: str = "gelu"
hidden_dropout_prob: float = 0.1
attention_probs_dropout_prob: float = 0.1
max_position_embeddings: int = 513  # 最大位置编码
task_type_vocab_size: int = 3  # 训练任务数
type_vocab_size: int = 2 #  token_type_ids(任务类型词向量)数
initializer_range: float = 0.02  # 初始化范围大小
pad_token_id: int = 0  # PAD补齐的数值:0
pool_act: str = "tanh"
fuse: bool = False
layer_norm_eps=1e-12
use_cache=False
use_task_id=False  # 是否对训练任务id(数值)embedding
enable_recompute=False

学习ErnieEmebbing

跟BERT一样,Ernie使用了Transformer的编码器,并对词、位置和句子序列进行编码嵌入(Embedding)

# from typing import Optional, Tuple# import paddle
# import paddle.nn as nn
# import paddle.nn.functional as F
# from paddle import Tensorclass ErnieEmbeddings(nn.Layer):r"""Include embeddings from word, position and token_type embeddings."""def __init__(self, vocab_size, hidden_size, pad_token_id, max_position_embeddings, type_vocab_size, hidden_dropout_prob=0.1, weight_attr=None):super(ErnieEmbeddings, self).__init__()self.word_embeddings = nn.Embedding(vocab_size, hidden_size, padding_idx=pad_token_id, weight_attr=weight_attr)self.position_embeddings = nn.Embedding(max_position_embeddings, hidden_size, weight_attr=weight_attr)self.type_vocab_size = type_vocab_sizeif self.type_vocab_size > 0:self.token_type_embeddings = nn.Embedding(type_vocab_size, hidden_size, weight_attr=weight_attr)self.layer_norm = nn.LayerNorm(hidden_size)self.dropout = nn.Dropout(hidden_dropout_prob)def forward(self,input_ids: Optional[Tensor] = None,token_type_ids: Optional[Tensor] = None,position_ids: Optional[Tensor] = None,inputs_embeds: Optional[Tensor] = None,past_key_values_length: int = 0,):if input_ids is not None:inputs_embeds = self.word_embeddings(input_ids)input_shape = paddle.shape(inputs_embeds)[:-1]if position_ids is None:# maybe need use shape op to unify static graph and dynamic graphones = paddle.ones(input_shape, dtype="int64")seq_length = paddle.cumsum(ones, axis=1)position_ids = seq_length - onesif past_key_values_length > 0:position_ids = position_ids + past_key_values_lengthposition_ids.stop_gradient = Trueposition_embeddings = self.position_embeddings(position_ids)embeddings = inputs_embeds + position_embeddingsif self.type_vocab_size > 0:if token_type_ids is None:token_type_ids = paddle.zeros(input_shape, dtype="int64")token_type_embeddings = self.token_type_embeddings(token_type_ids)embeddings = embeddings + token_type_embeddingsembeddings = self.layer_norm(embeddings)embeddings = self.dropout(embeddings)return embeddings

测试ErnieEmbeddings

testErnieEmbeddings = ErnieEmbeddings(vocab_size, hidden_size, pad_token_id, max_position_embeddings, type_vocab_size,)
tokens = paddle.randint(0, 10000, (1, 8))
tokens[0,0] = 1  # 手工加上开始符cls
tokens[0,(3,7)] = 2  # 手工加上分隔符seq
segments = paddle.to_tensor([[0, 0, 0, 0, 1, 1, 1, 1]])
encoded_X = testErnieEmbeddings(tokens, segments)
encoded_X.shape

学习ErniePooler

transformer模型很有趣的一点,就是每个输出token都会带全局的信息,因此很多后续判断只需要拿到第一个token的值即可,而ErniePooler就是实现这个功能的。

class ErniePooler(nn.Layer):def __init__(self, hidden_size, weight_attr=None):super(ErniePooler, self).__init__()self.dense = nn.Linear(hidden_size, hidden_size, weight_attr=weight_attr)self.activation = nn.Tanh()def forward(self, hidden_states):# We "pool" the model by simply taking the hidden state corresponding# to the first token.first_token_tensor = hidden_states[:, 0]pooled_output = self.dense(first_token_tensor)pooled_output = self.activation(pooled_output)return pooled_output
# 测试ErniePooler
testerniepooler = ErniePooler(768)
output = testerniepooler(encoded_X)
print(encoded_X.shape, output.shape)
# print(paddle.dtype(9),paddle.dtype(10))

学习Ernie模型

这是Ernie的主训练模型,相当于《动手学深度学习》里BERT模型部分的BERT Encoder部分。

class ErnieModel(nn.Layer):r"""The bare ERNIE Model transformer outputting raw hidden-states.This model inherits from :class:`~paddlenlp.transformers.model_utils.PretrainedModel`.Refer to the superclass documentation for the generic methods.This model is also a Paddle `paddle.nn.Layer <https://www.paddlepaddle.org.cn/documentation/docs/en/api/paddle/fluid/dygraph/layers/Layer_en.html>`__ subclass. Use it as a regular Paddle Layerand refer to the Paddle documentation for all matter related to general usage and behavior.Args:config (:class:`ErnieConfig`):An instance of ErnieConfig used to construct ErnieModel"""def __init__(self, initializer_range, num_attention_heads, intermediate_size,vocab_size, hidden_size, pad_token_id, max_position_embeddings, type_vocab_size, hidden_dropout_prob, hidden_act, attention_probs_dropout_prob,num_hidden_layers):super(ErnieModel, self).__init__()self.pad_token_id = pad_token_idself.initializer_range = initializer_rangeweight_attr = paddle.ParamAttr(initializer=nn.initializer.TruncatedNormal(mean=0.0, std=self.initializer_range))self.embeddings = ErnieEmbeddings(vocab_size, hidden_size, pad_token_id, max_position_embeddings, type_vocab_size, hidden_dropout_prob=0.1, weight_attr=weight_attr)encoder_layer = nn.TransformerEncoderLayer(hidden_size,num_attention_heads,intermediate_size,dropout=hidden_dropout_prob,activation=hidden_act,attn_dropout=attention_probs_dropout_prob,act_dropout=0,weight_attr=weight_attr,normalize_before=False,)self.encoder = nn.TransformerEncoder(encoder_layer, num_hidden_layers)self.pooler = ErniePooler(hidden_size, weight_attr)# self.apply(self.init_weights)def get_input_embeddings(self):return self.embeddings.word_embeddingsdef set_input_embeddings(self, value):self.embeddings.word_embeddings = valuedef forward(self,input_ids: Optional[Tensor] = None,token_type_ids: Optional[Tensor] = None,position_ids: Optional[Tensor] = None,attention_mask: Optional[Tensor] = None,past_key_values: Optional[Tuple[Tuple[Tensor]]] = None,inputs_embeds: Optional[Tensor] = None,use_cache: Optional[bool] = None,output_hidden_states: Optional[bool] = None,output_attentions: Optional[bool] = None,return_dict: Optional[bool] = None,):r"""Args:input_ids (Tensor):Indices of input sequence tokens in the vocabulary. They arenumerical representations of tokens that build the input sequence.It's data type should be `int64` and has a shape of [batch_size, sequence_length].token_type_ids (Tensor, optional):Segment token indices to indicate different portions of the inputs.Selected in the range ``[0, type_vocab_size - 1]``.If `type_vocab_size` is 2, which means the inputs have two portions.Indices can either be 0 or 1:- 0 corresponds to a *sentence A* token,- 1 corresponds to a *sentence B* token.Its data type should be `int64` and it has a shape of [batch_size, sequence_length].Defaults to `None`, which means we don't add segment embeddings.position_ids (Tensor, optional):Indices of positions of each input sequence tokens in the position embeddings. Selected in the range ``[0,max_position_embeddings - 1]``.Shape as `[batch_size, num_tokens]` and dtype as int64. Defaults to `None`.attention_mask (Tensor, optional):Mask used in multi-head attention to avoid performing attention on to some unwanted positions,usually the paddings or the subsequent positions.Its data type can be int, float and bool.When the data type is bool, the `masked` tokens have `False` values and the others have `True` values.When the data type is int, the `masked` tokens have `0` values and the others have `1` values.When the data type is float, the `masked` tokens have `-INF` values and the others have `0` values.It is a tensor with shape broadcasted to `[batch_size, num_attention_heads, sequence_length, sequence_length]`.For example, its shape can be  [batch_size, sequence_length], [batch_size, sequence_length, sequence_length],[batch_size, num_attention_heads, sequence_length, sequence_length].We use whole-word-mask in ERNIE, so the whole word will have the same value. For example, "使用" as a word,"使" and "用" will have the same value.Defaults to `None`, which means nothing needed to be prevented attention to.inputs_embeds (Tensor, optional):If you want to control how to convert `inputs_ids` indices into associated vectors, you canpass an embedded representation directly instead of passing `inputs_ids`.past_key_values (tuple(tuple(Tensor)), optional):The length of tuple equals to the number of layers, and each innertuple haves 4 tensors of shape `(batch_size, num_heads, sequence_length - 1, embed_size_per_head)`)which contains precomputed key and value hidden states of the attention blocks.If `past_key_values` are used, the user can optionally input only the last `input_ids` (those thatdon't have their past key value states given to this model) of shape `(batch_size, 1)` instead of all`input_ids` of shape `(batch_size, sequence_length)`.use_cache (`bool`, optional):If set to `True`, `past_key_values` key value states are returned.Defaults to `None`.output_hidden_states (bool, optional):Whether to return the hidden states of all layers.Defaults to `False`.output_attentions (bool, optional):Whether to return the attentions tensors of all attention layers.Defaults to `False`.return_dict (bool, optional):Whether to return a :class:`~paddlenlp.transformers.model_outputs.ModelOutput` object. If `False`, the outputwill be a tuple of tensors. Defaults to `False`.Returns:An instance of :class:`~paddlenlp.transformers.model_outputs.BaseModelOutputWithPoolingAndCrossAttentions` if`return_dict=True`. Otherwise it returns a tuple of tensors correspondingto ordered and not None (depending on the input arguments) fields of:class:`~paddlenlp.transformers.model_outputs.BaseModelOutputWithPoolingAndCrossAttentions`.Example:.. code-block::import paddlefrom paddlenlp.transformers import ErnieModel, ErnieTokenizertokenizer = ErnieTokenizer.from_pretrained('ernie-1.0')model = ErnieModel.from_pretrained('ernie-1.0')inputs = tokenizer("Welcome to use PaddlePaddle and PaddleNLP!")inputs = {k:paddle.to_tensor([v]) for (k, v) in inputs.items()}sequence_output, pooled_output = model(**inputs)"""if input_ids is not None and inputs_embeds is not None:raise ValueError("You cannot specify both input_ids and inputs_embeds at the same time.")# print("hello Ernie")# init the default bool valueoutput_attentions = output_attentions if output_attentions is not None else Falseoutput_hidden_states = output_hidden_states if output_hidden_states is not None else Falsereturn_dict = return_dict if return_dict is not None else Falseuse_cache = use_cache if use_cache is not None else Falsepast_key_values_length = 0if past_key_values is not None:past_key_values_length = past_key_values[0][0].shape[2]if attention_mask is None:attention_mask = paddle.unsqueeze((input_ids == self.pad_token_id).astype(self.pooler.dense.weight.dtype) * -1e4, axis=[1, 2])if past_key_values is not None:batch_size = past_key_values[0][0].shape[0]past_mask = paddle.zeros([batch_size, 1, 1, past_key_values_length], dtype=attention_mask.dtype)attention_mask = paddle.concat([past_mask, attention_mask], axis=-1)# For 2D attention_mask from tokenizerelif attention_mask.ndim == 2:attention_mask = paddle.unsqueeze(attention_mask, axis=[1, 2]).astype(paddle.get_default_dtype())attention_mask = (1.0 - attention_mask) * -1e4attention_mask.stop_gradient = Trueembedding_output = self.embeddings(input_ids=input_ids,position_ids=position_ids,token_type_ids=token_type_ids,inputs_embeds=inputs_embeds,past_key_values_length=past_key_values_length,)self.encoder._use_cache = use_cache  # To be consistent with HFencoder_outputs = self.encoder(embedding_output,src_mask=attention_mask,cache=past_key_values,output_attentions=output_attentions,output_hidden_states=output_hidden_states,return_dict=return_dict,)if isinstance(encoder_outputs, type(embedding_output)):sequence_output = encoder_outputspooled_output = self.pooler(sequence_output)return (sequence_output, pooled_output)else:sequence_output = encoder_outputs[0]pooled_output = self.pooler(sequence_output)if not return_dict:return (sequence_output, pooled_output) + encoder_outputs[1:]return BaseModelOutputWithPoolingAndCrossAttentions(last_hidden_state=sequence_output,pooler_output=pooled_output,past_key_values=encoder_outputs.past_key_values,hidden_states=encoder_outputs.hidden_states,attentions=encoder_outputs.attentions,)

测试ErnieModel

tokenizer = ErnieTokenizer.from_pretrained('ernie-1.0')
model = ErnieModel(initializer_range, num_attention_heads, intermediate_size,vocab_size, hidden_size, pad_token_id, max_position_embeddings, type_vocab_size, hidden_dropout_prob, hidden_act, attention_probs_dropout_prob,num_hidden_layers)inputs = tokenizer("Welcome to use PaddlePaddle and PaddleNLP!", "用飞桨,划时代!")
inputs = {k:paddle.to_tensor([v]) for (k, v) in inputs.items()}sequence_output, pooled_output = model(**inputs)
print(sequence_output.shape, pooled_output.shape)

学习ErnieForPretraining

真正预训练模型开始啦!

ErnieForPretraining参考BERTModel

刚开始没有看到Bert里面那种MLM和NSP,后来发现是因为样子跟BERT里有些不一样导致的。

ErnieLMPredictionHead

语言模型预测头

class ErnieLMPredictionHead(nn.Layer):r"""Ernie Model with a `language modeling` head on top."""def __init__(self,hidden_size, hidden_act, vocab_size, embedding_weights=None,weight_attr=None,):super(ErnieLMPredictionHead, self).__init__()self.transform = nn.Linear(hidden_size, hidden_size, weight_attr=weight_attr)self.activation = getattr(nn.functional, hidden_act)self.layer_norm = nn.LayerNorm(hidden_size)self.decoder_weight = (self.create_parameter(shape=[vocab_size, hidden_size],dtype=self.transform.weight.dtype,attr=weight_attr,is_bias=False,)if embedding_weights is Noneelse embedding_weights)self.decoder_bias = self.create_parameter(shape=[vocab_size], dtype=self.decoder_weight.dtype, is_bias=True)def forward(self, hidden_states, masked_positions=None):if masked_positions is not None:# print("===", hidden_states.shape, masked_positions.shape)hidden_states = paddle.reshape(hidden_states, [-1, hidden_states.shape[-1]])# print("===", hidden_states.shape, masked_positions.shape)hidden_states = paddle.tensor.gather(hidden_states, masked_positions)# gather masked tokens might be more quickhidden_states = self.transform(hidden_states)hidden_states = self.activation(hidden_states)hidden_states = self.layer_norm(hidden_states)hidden_states = paddle.tensor.matmul(hidden_states, self.decoder_weight, transpose_y=True) + self.decoder_biasreturn hidden_states
# 测试ErnieLMPredictionHead
print(hidden_size, hidden_act, vocab_size)
# mlm_positions = paddle.to_tensor([[1, 5, 2], [6, 1, 5]])
mlm_positions = paddle.to_tensor([2, 3, 6])
testernielmpredictionhead = ErnieLMPredictionHead(hidden_size, hidden_act, vocab_size)
mlm_output = testernielmpredictionhead(sequence_output, mlm_positions)
print(mlm_output.shape)

通过掩码下的预测词元mlm_output和真实标签mlm_Y,我们可以计算在Ernie预训练中的掩码(遮蔽)语言模型任务的交叉熵损失。

mlm_Y = paddle.to_tensor([10, 20, 30])
loss = nn.CrossEntropyLoss(reduction='none')
mlm_l = loss(mlm_output.reshape((-1, vocab_size)), mlm_Y.reshape([-1]))
mlm_l.shape

学习ErniePretrainingHeads

预训练头

class ErniePretrainingHeads(nn.Layer):def __init__(self,hidden_size, hidden_act, vocab_size,embedding_weights=None,weight_attr=None,):super(ErniePretrainingHeads, self).__init__()self.predictions = ErnieLMPredictionHead(hidden_size, hidden_act, vocab_size, embedding_weights, weight_attr)self.seq_relationship = nn.Linear(hidden_size, 2, weight_attr=weight_attr)def forward(self, sequence_output, pooled_output, masked_positions=None):prediction_scores = self.predictions(sequence_output, masked_positions)seq_relationship_score = self.seq_relationship(pooled_output)return prediction_scores, seq_relationship_score
# 测试ErniePretrainingHeads
testErniePretrainingHeads = ErniePretrainingHeads(hidden_size, hidden_act, vocab_size)
prediction_scores, seq_relationship_score = testErniePretrainingHeads(sequence_output, pooled_output)
print(prediction_scores.shape, seq_relationship_score.shape)

学习ErnieForPretraining

需要解决的是源代码里参数初始化的问题,因为原来初始化是在class ErniePretrainedModel(PretrainedModel)里面进行的。直接cp过来报错

源代码里from paddlenlp.transformers import PretrainedModel, register_base_model

这里将其修改为class ErnieForPretraining(nn.Layer):

另外不使用argparse的变量空间,这样代码的可读性好,但是代码传参那块就显得比较臃肿。现在参数都是用全局变量直接传进去,也不太安全。当然这里为了代码可读性,一些缺点可以接受。

class ErnieForPretraining(nn.Layer):r"""Ernie Model with a `masked language modeling` head and a `sentence order prediction` headon top."""def __init__(self, initializer_range, num_attention_heads, intermediate_size,vocab_size, hidden_size, pad_token_id, max_position_embeddings, type_vocab_size, hidden_dropout_prob, hidden_act, attention_probs_dropout_prob,num_hidden_layers):super(ErnieForPretraining, self).__init__()self.ernie = ErnieModel(initializer_range, num_attention_heads, intermediate_size,vocab_size, hidden_size, pad_token_id, max_position_embeddings, type_vocab_size, hidden_dropout_prob, hidden_act, attention_probs_dropout_prob,num_hidden_layers)weight_attr = paddle.ParamAttr(initializer=nn.initializer.TruncatedNormal(mean=0.0, std=self.ernie.initializer_range))self.cls = ErniePretrainingHeads(hidden_size, hidden_act, vocab_size,embedding_weights=self.ernie.embeddings.word_embeddings.weight,weight_attr=weight_attr,)# self.apply(self.init_weights)def init_weights(self, layer):"""Initialization hook"""if isinstance(layer, (nn.Linear, nn.Embedding)):# only support dygraph, use truncated_normal and make it inplace# and configurable laterif isinstance(layer.weight, paddle.Tensor):layer.weight.set_value(paddle.tensor.normal(mean=0.0,std=self.config.initializer_range,shape=layer.weight.shape,))elif isinstance(layer, nn.LayerNorm):layer._epsilon = 1e-12def forward(self,input_ids: Optional[Tensor] = None,token_type_ids: Optional[Tensor] = None,position_ids: Optional[Tensor] = None,attention_mask: Optional[Tensor] = None,masked_positions: Optional[Tensor] = None,inputs_embeds: Optional[Tensor] = None,labels: Optional[Tensor] = None,next_sentence_label: Optional[Tensor] = None,output_hidden_states: Optional[bool] = None,output_attentions: Optional[bool] = None,return_dict: Optional[bool] = None,):r"""Args:input_ids (Tensor):See :class:`ErnieModel`.token_type_ids (Tensor, optional):See :class:`ErnieModel`.position_ids (Tensor, optional):See :class:`ErnieModel`.attention_mask (Tensor, optional):See :class:`ErnieModel`.inputs_embeds(Tensor, optional):See :class:`ErnieModel`.labels (Tensor of shape `(batch_size, sequence_length)`, optional):Labels for computing the masked language modeling loss. Indices should be in `[-100, 0, ...,vocab_size]` (see `input_ids` docstring) Tokens with indices set to `-100` are ignored (masked),the loss is only computed for the tokens with labels in `[0, ..., vocab_size]`.next_sentence_label (Tensor of shape `(batch_size,)`, optional):Labels for computing the next sequence prediction (classification) loss. Input should be a sequencepair (see `input_ids` docstring) Indices should be in `[0, 1]`:- 0 indicates sequence B is a continuation of sequence A,- 1 indicates sequence B is a random sequence.output_hidden_states (bool, optional):Whether to return the hidden states of all layers.Defaults to `False`.output_attentions (bool, optional):Whether to return the attentions tensors of all attention layers.Defaults to `False`.return_dict (bool, optional):Whether to return a :class:`~paddlenlp.transformers.bert.ErnieForPreTrainingOutput` object. If`False`, the output will be a tuple of tensors. Defaults to `False`.Returns:An instance of :class:`~paddlenlp.transformers.bert.ErnieForPreTrainingOutput` if `return_dict=True`.Otherwise it returns a tuple of tensors corresponding to ordered andnot None (depending on the input arguments) fields of :class:`~paddlenlp.transformers.bert.ErnieForPreTrainingOutput`."""with paddle.static.amp.fp16_guard():outputs = self.ernie(input_ids,token_type_ids=token_type_ids,position_ids=position_ids,attention_mask=attention_mask,inputs_embeds=inputs_embeds,output_attentions=output_attentions,output_hidden_states=output_hidden_states,return_dict=return_dict,)sequence_output, pooled_output = outputs[:2]prediction_scores, seq_relationship_score = self.cls(sequence_output, pooled_output, masked_positions)total_loss = Noneif labels is not None and next_sentence_label is not None:loss_fct = paddle.nn.CrossEntropyLoss()masked_lm_loss = loss_fct(prediction_scores.reshape((-1, paddle.shape(prediction_scores)[-1])), labels.reshape((-1,)))next_sentence_loss = loss_fct(seq_relationship_score.reshape((-1, 2)), next_sentence_label.reshape((-1,)))total_loss = masked_lm_loss + next_sentence_lossif not return_dict:output = (prediction_scores, seq_relationship_score) + outputs[2:]return ((total_loss,) + output) if total_loss is not None else outputreturn ErnieForPreTrainingOutput(loss=total_loss,prediction_logits=prediction_scores,seq_relationship_logits=seq_relationship_score,hidden_states=outputs.hidden_states,attentions=outputs.attentions,)

现在Ernie模型代码已经全部学习完毕了,我们需要对ErnieForPretraining进行测试。
测试需要从数据集中读入数据,我们从下一节开始。

读入数据进行测试

导入库和配置参数

from ernie.args import parse_args_mini
import numpy as np
import paddle
from paddlenlp.utils.log import logger
from paddlenlp.data import Stack
from paddle.io import RandomSampler, BatchSampler, Dataset
from paddlenlp.transformers import ErnieTokenizer
from ernie.data_tools.dataset_utils import build_train_valid_test_datasets
import os# 为了简化build_train_valid_test_datasets代码展示,这里使用了args传参
args = parse_args_mini()  
tokenizer = ErnieTokenizer.from_pretrained('ernie-1.0')
import os
def get_train_data_file(input_dir):if len(input_dir.split()) > 1:# weight-1 data-prefix-1 weight-2 data-prefix-2 ...return input_dir.split()else:files = [os.path.join(input_dir, f)for f in os.listdir(input_dir)if (os.path.isfile(os.path.join(input_dir, f)) and "_idx.npz" in str(f))]files = [x.replace("_idx.npz", "") for x in files]if len(files) > 1:ret = []logger.info("You are using multi-dataset:")for x in files:ret.append(1.0)ret.append(x)logger.info("    > set weight of %s dataset to 1.0" % x)return retreturn filesdata_file = get_train_data_file("/home/aistudio/pretrain")
num_workers = 0def create_pretrained_dataset(global_batch_size, max_steps, eval_freq,  test_iters, split, masked_lm_prob, short_seq_prob, seed, micro_batch_size, data_file,tokenizer,data_world_size,data_world_rank,max_seq_len,places=None,data_holders=None,binary_head=False,current_step=0,
):train_valid_test_num_samples = [global_batch_size * max_steps,micro_batch_size * (max_steps // eval_freq + 1) * eval_iters * data_world_size,micro_batch_size * test_iters * data_world_size,]train_ds, valid_ds, test_ds = build_train_valid_test_datasets(data_prefix=data_file,args=args,tokenizer=tokenizer,splits_string=split,train_valid_test_num_samples=train_valid_test_num_samples,max_seq_length=max_seq_len,masked_lm_prob=masked_lm_prob,short_seq_prob=short_seq_prob,seed=seed,skip_warmup=True,binary_head=binary_head,max_seq_length_dec=None,dataset_type="ernie",)# print(args)def print_dataset(data, mode="train"):logger.info(f"Sample data for {mode} mode")input_ids, segment_ids, input_mask, masked_lm_positions, masked_lm_labels, next_sentence_labels = dataif tokenizer.pad_token_id in input_ids:input_ids = input_ids[0 : list(input_ids).index(tokenizer.pad_token_id)]logger.info(tokenizer._decode(input_ids))for pos, label in zip(masked_lm_positions, masked_lm_labels):input_ids[pos] = labellogger.info(tokenizer._decode(input_ids))logger.info(tokenizer.convert_ids_to_tokens(masked_lm_labels))print_dataset(train_ds[0], "train")print_dataset(valid_ds[0], "valid")print_dataset(test_ds[0], "test")def _collate_data(data, stack_fn=Stack()):num_fields = len(data[0])out = [None] * num_fields# 0. input_ids,# 1. segment_ids,# 2. input_mask,# 3. masked_lm_positions,# 4. masked_lm_labels,# 5. next_sentence_labelsfor i in (0, 1, 2, 5):out[i] = stack_fn([x[i] for x in data])out[5] = out[5].reshape([-1, 1])_, seq_length = out[0].shapesize = sum(len(x[3]) for x in data)# masked_lm_positions# Organize as a 1D tensor for gather or use gather_ndif size % 8 != 0:size += 8 - (size % 8)out[3] = np.full(size, 0, dtype=np.int32)# masked_lm_labelsout[4] = np.full([size, 1], -1, dtype=np.int64)mask_token_num = 0for i, x in enumerate(data):for j, pos in enumerate(x[3]):out[3][mask_token_num] = i * seq_length + posout[4][mask_token_num] = x[4][j]mask_token_num += 1return outdef loader(dataset, consumed_samples=0):batch_sampler = BatchSampler(dataset,batch_size=micro_batch_size,shuffle=False,drop_last=True,)data_loader = paddle.io.DataLoader(dataset=dataset,batch_sampler=batch_sampler,num_workers=num_workers,worker_init_fn=None,collate_fn=_collate_data,return_list=False,)return data_loadertrain_dl = loader(train_ds, global_batch_size * current_step)valid_dl = loader(valid_ds, micro_batch_size * ((current_step + 1) // eval_freq) * eval_iters * data_world_size)test_dl = loader(test_ds, 0)return train_dl, valid_dl, test_dl# from runpretrain import create_pretrained_dataset, get_train_data_fileglobal_batch_size = 8
max_steps = 10
eval_freq = 1
test_iters =1 
split = '949,50,1'
masked_lm_prob = 0.15
short_seq_prob = 0.1
seed = 42
worker_num = 1
max_seq_len = 768
binary_head = False
global_step = 10
worker_index = 0  # 多卡的第一张卡
micro_batch_size =8 
eval_iters = 10
# share_folder = Nonetrain_data_loader, valid_data_loader, test_data_loader = create_pretrained_dataset(global_batch_size, max_steps, eval_freq,  test_iters, split, masked_lm_prob, short_seq_prob, seed, micro_batch_size,data_file,tokenizer,data_world_size=worker_num,data_world_rank=worker_index,max_seq_len=max_seq_len,binary_head=binary_head,current_step=global_step,)

模型读入数据验证

testErnieForPretraining = ErnieForPretraining(initializer_range, num_attention_heads, intermediate_size,vocab_size, hidden_size, pad_token_id, max_position_embeddings, type_vocab_size, hidden_dropout_prob, hidden_act, attention_probs_dropout_prob,num_hidden_layers)

最后这一步,需要32G高端版环境。

for step, batch in enumerate(train_data_loader):input_ids, segment_ids, input_mask, masked_lm_positions, masked_lm_labels, next_sentence_labels = batchprediction_scores, seq_relationship_score = testErnieForPretraining(input_ids=input_ids,token_type_ids=segment_ids,position_ids=None,attention_mask=input_mask,masked_positions=masked_lm_positions,)print("step:", step, "输入:", input_ids.shape, "掩码推理得分:", prediction_scores.shape, "句子联系得分:", seq_relationship_score.shape)break

总结

以上就是Ernie模型的源代码学习。

现在,我们已经可以自由修改模型了,比如层数、隐藏层大小以及Head数,通过修改hidden_size等参数即可。事实上Ernie官方就公布了一系列不同大小的Ernie模型,比如Ernie 3.0 Tiny模型系列,就是通过层数、隐藏层大小以及Head数来调整模型的大小。具体Ernie系列模型参数在PaddleNLP/paddlenlp/transformers/ernie/configuration.py文件中配置。

我们也可以修改模型源代码,创建符合自己需求的模型,比如添加 InstructGPT、ChatGPT等论文中的一些新技术以提高模型的表现。

三、ERNIE 1.0 预训练实践

ERNIE是百度开创性提出的基于知识增强的持续学习语义理解框架,它将大数据预训练与多源丰富知识相结合,通过持续学习技术,不断吸收海量文本数据中词汇、结构、语义等方面的知识,实现模型效果不断进化。

ERNIE 1.0 预训练主要由数据预处理和模型预训练两部分组成。

ERNIE 中文预训练更详细的介绍文档请可以参见ERNIE中文预训练介绍。

参考数据预处理文档,整个数据预处理流程高效,40分钟即可完成15G CLUECorpusSmall ERNIE数据制作。需要>=20核的多核处理器,否则速度会按比例降低,比如在AIStudio高级版4核CPU下,需要大约6个小时。

具体数据预处理见ernie/dataset.ipynb子项目。

同时官方提供了 ernie-1.0-base-zh 的悟道一个小规模样本的数据例子。

!git clone  https://github.com/PaddlePaddle/PaddleNLP

悟道小样本预训练示例

这个悟道小样本也有1.3G大小呢。下载并解压已经处理好的数据集。

!mkdir wudao
!cd wudao && wget -c https://paddlenlp.bj.bcebos.com/models/transformers/data_tools/wudao_200g_sample_ernie-1.0-base-zh_ids.npy
!cd wudao && wget -c https://paddlenlp.bj.bcebos.com/models/transformers/data_tools/wudao_200g_sample_ernie-1.0-base-zh_idx.npz

开始预训练。若报错,则重启一下环境,释放掉前面源代码学习时占用的显存即可。

!cd ~/PaddleNLP/model_zoo/ernie-1.0/ && python run_pretrain.py \--model_type "ernie" \--model_name_or_path "ernie-1.0-base-zh" \--tokenizer_name_or_path "ernie-1.0-base-zh" \--input_dir "/home/aistudio/wudao" \--output_dir "output/ernie-1.0-dp8-gb512" \--split 949,50,1 \--max_seq_len 512 \--micro_batch_size 64 \--use_amp true \--fp16_opt_level O2 \--max_lr 0.0001 \--min_lr 0.00001 \--max_steps 1000 \--save_steps 50000 \--checkpoint_steps 5000 \--decay_steps 990000 \--weight_decay 0.01 \--warmup_rate 0.01 \--grad_clip 1.0 \--logging_freq 200 \--num_workers 2 \--eval_freq 1000 \--device "gpu" \--share_folder false

–num_workers 0 训练速度:speed: 0.77 steps/s
–num_workers 2 训练速度:speed: 2 steps/s

Ernie 1.0 CLUECorpusSmall 数据集预训练

CLUECorpusSmall 数据集预处理参考本项目内的ernie/dataset.ipynb。

本项目已经挂载预处理好的CLUECorpusSmall 数据集,在目录/home/aistudio/data/data194775中。将制作好的数据解压成clue_corpus_small_14g_20220104_ids.npy,clue_corpus_small_14g_20220104_idx.npz并移动到参数:input_dir的目录中,即可开始训练。若是多卡,直接在参数重设定好--gpus "0,1,2,3"即可 。

解压CLUECorpusSmall数据集:

# 解压需要2分钟
!mkdir ~/clue_small
!cd  ~/clue_small && unzip -n /home/aistudio/data/data194775/clue_corpus_small_14g_20230228_idx.npz.zip
!cd  ~/clue_small && unzip -n /home/aistudio/data/data194775/clue_corpus_small_14g_20230228_ids.npy.zip

单机多卡训练,以paddle.distributed.launch启动训练,由于预训练1000000步需要耗费大量时间,这里我们以1000步训练进行演示:

# # 耗时17分钟
# !cd ~/PaddleNLP/model_zoo/ernie-1.0/ && python -u  -m paddle.distributed.launch \
#     --gpus "0" \
#     --log_dir "output/ernie-1.0-dp8-gb512/log" \
#     run_pretrain.py \
#     --model_type "ernie" \
#     --model_name_or_path "ernie-1.0-base-zh" \
#     --tokenizer_name_or_path "ernie-1.0-base-zh" \
#     --input_dir "/home/aistudio/clue_small" \
#     --output_dir "output/ernie-1.0-dp8-gb512" \
#     --split 949,50,1 \
#     --max_seq_len 512 \
#     --micro_batch_size 64 \
#     --use_amp true \
#     --fp16_opt_level O2 \
#     --max_lr 0.0001 \
#     --min_lr 0.00001 \
#     --max_steps 1000 \
#     --save_steps 50000 \
#     --checkpoint_steps 5000 \
#     --decay_steps 990000 \
#     --weight_decay 0.01 \
#     --warmup_rate 0.01 \
#     --grad_clip 1.0 \
#     --logging_freq 200 \
#     --num_workers 2 \
#     --eval_freq 1000 \
#     --device "gpu" \
#     --share_folder false \

若要继续训练,加上--continue_training true参数即可。

不用paddle.distributed.launch,直接python运行。预训练100000步时间太久,本项目以1000步为例展示,耗时约8分钟。

!cd ~/PaddleNLP/model_zoo/ernie-1.0/ && python run_pretrain.py \--model_type "ernie" \--model_name_or_path "ernie-1.0-base-zh" \--tokenizer_name_or_path "ernie-1.0-base-zh" \--input_dir "/home/aistudio/clue_small" \--output_dir "output/ernie-1.0-dp8-gb512" \--split 949,50,1 \--max_seq_len 512 \--micro_batch_size 64 \--use_amp true \--fp16_opt_level O2 \--max_lr 0.0001 \--min_lr 0.00001 \--max_steps 1000 \--save_steps 50000 \--checkpoint_steps 5000 \--decay_steps 990000 \--weight_decay 0.01 \--warmup_rate 0.01 \--grad_clip 1.0 \--logging_freq 200 \--num_workers 2 \--eval_freq 1000 \--device "gpu" \--share_folder false \--continue_training true

运行日志:

[2023-03-01 10:31:35,435] [    INFO] - [CLS] 书 名 : 王 小 波 全 集 作 者 : 王 小 波 咋 [MASK] 机 构 : 云 南 人 民 出 版 社 原 书 定 价 : 174. [MASK] [MASK] [MASK] 价 格 : 87. 3 折 扣 率 : 5 购 买 链 接 : 随 便 附 上 另 一 个 版 本 [UNK] 号 称 终 极 版 本 [MASK] [MASK] [MASK] 折 [UNK] : http : [UNK] [UNK] www. amazon. cn [UNK] [MASK] e7 [UNK] 8e [UNK] 8b [UNK] e5 [UNK] b0 [UNK] 8f [UNK] e6 [UNK] b3 [UNK] a2 [UNK] e5 [UNK] 85 [UNK] a8 [UNK] e9 [UNK] 9b [UNK] 86 [UNK] [MASK] [MASK] [MASK] bb [UNK] 88 [UNK] e7 [UNK] bb [UNK] 93 [UNK] e7 [MASK]疔 [MASK] 88 垣 [MASK] [MASK] [UNK] a5 [UNK] 97 [UNK] e8 [UNK] a3 [UNK] 85 [UNK] e5 [UNK] 85 [UNK] b110 [UNK] e5 [UNK] 86 [UNK] 8c [MASK]涤 e7 [UNK] 8e [UNK] 8b [UNK] e5 [UNK] b0 [UNK] 8f [UNK] e6 [UNK] b3 [UNK] a2 [UNK] dp [UNK] b0031y7ola [MASK] 渠f [MASK] [MASK] [UNK] 1 暄 3 ? ie [UNK] utf8 [MASK] [MASK] [MASK] [UNK] [UNK] sr [UNK] 8 [UNK] 3 我 的 [MASK] [MASK] : [MASK] [MASK] [MASK] [MASK] 嚣, 别 等 到 想 买 的 时 候 [MASK] [MASK] [MASK] 了, 自 个 刚 下 单 子 枫, 哈. 内 容 简 介 《 [MASK] 小 [MASK] [MASK]哇 》 分 为 [MASK] [MASK], 每 卷 都 以 平 装 和 精 装 珍 藏 版 两 种 装 帧 形 式兼 [MASK] [MASK] 本 套 丛 书 位 平 装 版 。 第 一 卷 、 第 二 卷 为 杂 文 ; 第 三 卷 、 第 四 卷 、 第 五 卷 为 长 篇 小 说 和 剧 本 ; [SEP] 第 六 卷 [MASK] 第 七 卷 为 中 篇 小 说 ; 第 八 卷 逝 [MASK] 篇窒 [MASK] ; 第 九 卷 为 书 信 ; 第 十 卷 为 未 竟 稿 。 本 书 为 第 六 卷 。 王 小 波 是 目 前 中 国 最 [MASK] 创 造 性 的 作 家, 被 誉 为 中 国 的浅 猕 [MASK] [MASK] 卡 夫 卡 英, [MASK] [MASK] [MASK] [MASK] 两 次 获 得 世 界 华 语 文 学 界 [MASK] [MASK] [MASK] 奖 项 [UNK] 台 湾 联 合 报 系 文 学 奖 中 篇 小 说 大 奖 [UNK] 的 中 国 大 陆 作 家 。 其 文 学 创 作 独 特, 富 于 想 像 力 、 幻 想 力 之 余, 却 不 乏 理 性 精 神 。 他 的 文 字刊 [MASK] 透 明 的 也 是 朦 胧 的, 是 [MASK] 份 的 也 是80 [MASK] 的 。 [SEP]
[2023-03-01 10:31:35,437] [    INFO] - [CLS] 书 名 : 王 小 波 全 集 作 者 : 王 小 波 出 版 机 构 : 云 南 人 民 出 版 社 原 书 定 价 : 174. 6 现 售 价 格 : 87. 3 折 扣 率 : 5 购 买 链 接 : 随 便 附 上 另 一 个 版 本 [UNK] 号 称 终 极 版 本 5. 2 折 [UNK] : http : [UNK] [UNK] www. amazon. cn [UNK] [UNK] e7 [UNK] 8e [UNK] 8b [UNK] e5 [UNK] b0 [UNK] 8f [UNK] e6 [UNK] b3 [UNK] a2 [UNK] e5 [UNK] 85 [UNK] a8 [UNK] e9 [UNK] 9b [UNK] 86 [UNK] [UNK] e7 [UNK] bb [UNK] 88 [UNK] e7 [UNK] bb [UNK] 93 [UNK] e7 [UNK] 89 [UNK] 88 [UNK] [UNK] e5 [UNK] a5 [UNK] 97 [UNK] e8 [UNK] a3 [UNK] 85 [UNK] e5 [UNK] 85 [UNK] b110 [UNK] e5 [UNK] 86 [UNK] 8c [UNK] [UNK] e7 [UNK] 8e [UNK] 8b [UNK] e5 [UNK] b0 [UNK] 8f [UNK] e6 [UNK] b3 [UNK] a2 [UNK] dp [UNK] b0031y7ola [UNK] ref [UNK] sr [UNK] 1 [UNK] 3 ? ie [UNK] utf8 [UNK] qid [UNK] [UNK] sr [UNK] 8 [UNK] 3 我 的 建 议 : 要 买 的 抓 紧, 别 等 到 想 买 的 时 候 又 没 货 了, 自 个 刚 下 单 子 了, 哈. 内 容 简 介 《 王 小 波 全 集 》 分 为 十 卷, 每 卷 都 以 平 装 和 精 装 珍 藏 版 两 种 装 帧 形 式 出 版, 本 套 丛 书 位 平 装 版 。 第 一 卷 、 第 二 卷 为 杂 文 ; 第 三 卷 、 第 四 卷 、 第 五 卷 为 长 篇 小 说 和 剧 本 ; [SEP] 第 六 卷 、 第 七 卷 为 中 篇 小 说 ; 第 八 卷 为 短 篇 小 说 ; 第 九 卷 为 书 信 ; 第 十 卷 为 未 竟 稿 。 本 书 为 第 六 卷 。 王 小 波 是 目 前 中 国 最 富 创 造 性 的 作 家, 被 誉 为 中 国 的 乔 依 斯 兼 卡 夫 卡 英, 也 是 唯 位 两 次 获 得 世 界 华 语 文 学 界 的 重 要 奖 项 [UNK] 台 湾 联 合 报 系 文 学 奖 中 篇 小 说 大 奖 [UNK] 的 中 国 大 陆 作 家 。 其 文 学 创 作 独 特, 富 于 想 像 力 、 幻 想 力 之 余, 却 不 乏 理 性 精 神 。 他 的 文 字, 是 透 明 的 也 是 朦 胧 的, 是 本 份 的 也 是 狡 猾 的 。 [SEP]
[2023-03-01 10:31:35,437] [    INFO] - ['出', '版', '6', '现', '售', '5', '.', '2', '.', '[UNK]', '[UNK]', 'e7', '[UNK]', '[UNK]', '89', '[UNK]', '[UNK]', '[UNK]', 'e5', '[UNK]', '[UNK]', 'e7', '[UNK]', 're', '##f', '[UNK]', 'sr', '[UNK]', '[UNK]', 'qi', '##d', '建', '议', '要', '买', '的', '抓', '紧', '又', '没', '货', '了', '王', '小', '波', '全', '集', '十', '卷', '出', '版', ',', '本', '、', '为', '短', '篇', '小', '说', '富', '乔', '依', '斯', '兼', '也', '是', '唯', '位', '的', '重', '要', ',', '是', '本', '狡', '猾'][2023-03-01 10:43:06,695] [    INFO] - global step 1200, loss: 7.459023, lm_loss: 6.750938, sop_loss: 0.708181, speed: 13.58 steps/s, ips: 54.32 seqs/s, learning rate: 1.20000e-05, loss_scaling: 32768.00, incr_count: 198.00, decr_count: 0.00
[2023-03-01 10:43:20,768] [    INFO] - global step 1400, loss: 7.370703, lm_loss: 6.663066, sop_loss: 0.707598, speed: 14.21 steps/s, ips: 56.85 seqs/s, learning rate: 1.40000e-05, loss_scaling: 32768.00, incr_count: 398.00, decr_count: 0.00

总共预训练100万步,悟道数据集速度是15步/秒,算下来单卡大约要跑18小时。 四卡大约4个多小时。

如果用14G CLUECorpusSmall数据集训练,速度2步/秒,单卡跑138小时 ,4卡34小时。

四、ERNIE微调训练

对大多数人来说,不需要自己去预训练模型,只要调用官方提供的训练好的预训练模型,然后finetune精调训练即可。

百度 ERNIE 团队在 2021 年底发布了百亿级别大模型 ERNIE 3.0 和千亿级别的大模型 ERNIE 3.0 Titan。为了让大模型的能力能够真正在一线业务发挥威力,ERNIE 团队推出了 ERNIE-Tiny 系列的知识蒸馏技术,通过任务无关蒸馏的方法,产出了多个轻量级模型 ERNIE 3.0 Tiny,刷新了中文小模型的成绩,并使这些模型能够直接在 CPU 上进行预测,大大拓展了 ERNIE 模型的使用场景。

2023 年初,ERNIE 团队进一步开源了 ERNIE 3.0 Tiny 模型的 v2 版本,使教师模型预先注入下游知识并参与 多任务训练,大大提高了小模型在下游任务上的效果。ERNIE 3.0 Tiny v2 模型在 in-domain、out-domain、low-resource 的下游任务上比 v1 有了进一步的提升,并且 v2 还开源了 3L128H 结构的模型。

ERNIE 3.0 Tiny v2 多任务学习、在线蒸馏方案效果显著,刷新了中文小模型的 SOTA 成绩。具体对比数据见如下模型 精度-时延 图,横坐标表示在 Arm CPU(高通 865 芯片)上,基于 Arm v8 arch 测试(batch_size=1, seq_len=32)的推理时延(Latency,单位毫秒),纵坐标是 CLUE 10 个任务上的平均精度(包含文本分类、文本匹配、自然语言推理、代词消歧、阅读理解等任务),其中 CMRC2018 阅读理解任务的评价指标是 Exact Match(EM),其它任务的评价指标均是 Accuracy。模型名下方标注了模型的参数量。

图中越靠左上方的模型,精度和性能水平越高。可以看到 ERNIE 3.0 Tiny v2 在同等规模的开源模型中,综合实力领先其他同类型轻量级模型。与 UER/RoBERTa-Base 相比,12L768H 的 ERNIE 3.0-Base 平均精度提升了 4.5 个点,比同等规模的BERT-Base-Chinese 提升 3.7 个点;6L768H 的 ERNIE 3.0-Medium 相比 12L768H 的 UER/Chinese-RoBERTa 高 2.4,比 BERT-Base-Chinese 高 1.7,并且节省一倍运算时间;另外值得一提的是,这些小模型能够直接部署在 CPU 上。

使用 PaddleNLP 只需要一行代码就可以下载并获取 ERNIE 3.0 Tiny 预训练模型,之后可以用自己的下游数据下进行微调。

# from paddlenlp.transformers import *# tokenizer = AutoTokenizer.from_pretrained("ernie-3.0-tiny-medium-v2-zh")# # 用于分类任务(本项目中的意图识别任务)
# seq_cls_model = AutoModelForSequenceClassification.from_pretrained("ernie-3.0-tiny-medium-v2-zh")# # 用于序列标注任务(本项目中的槽位填充任务)
# token_cls_model = AutoModelForTokenClassification.from_pretrained("ernie-3.0-tiny-medium-v2-zh")# # 用于阅读理解任务
# qa_model = AutoModelForQuestionAnswering.from_pretrained("ernie-3.0-tiny-medium-v2-zh")

可以在paddlenlp.transformers中找到常用的9个模型:‘AutoModelForCausalLM’, ‘AutoModelForConditionalGeneration’, ‘AutoModelForImageGeneration’, ‘AutoModelForMaskedLM’, ‘AutoModelForMultipleChoice’, ‘AutoModelForPretraining’, ‘AutoModelForQuestionAnswering’, ‘AutoModelForSequenceClassification’, ‘AutoModelForTokenClassification’,对应不同的任务。

官方提供了3个例子,我们可以参考下面序列分类微调任务,将python run_seq_cls.py model = AutoModelForSequenceClassification.from_pretrained(model_args.model_name_or_path, num_classes=num_classes)修改为自己的任务即可,比如图像生成任务 model = AutoModelForImageGeneration.from_pretrained(model_args.model_name_or_path, num_classes=num_classes),具体细节请参考Ernie3.0微调训练 。现在Ernie1.0和3.0代码是打通的,训练参数带上不同的模型名字即可。

官方例子序列分类微调任务的原num_train_epochs为16,这里为了节省时间,训练2个epochs 。

# 耗时约11分钟
dataset="chnsenticorp_v2"
!cd ~/PaddleNLP/model_zoo/ernie-1.0/finetune && python run_seq_cls.py \--do_train \--do_eval \--do_predict \--model_name_or_path ernie-1.0-base-zh \--dataset $dataset \--output_dir ./tmp/$dataset \--num_train_epochs 2

具体微调训练请参考官方文档ernie-tiny介绍

五、Ernie部署

文档参考FastDeploy ERNIE 3.0 模型 Python 部署示例 和 ERNIE 1.0 模型 Python 部署示例

安装依赖库

# 安装fast_tokenizer以及GPU版本fastdeploy 约15分钟
!pip install fast-tokenizer-python fastdeploy-gpu-python -q -f https://www.paddlepaddle.org.cn/whl/fastdeploy.html

导出部署模型

模型导出目录为~/PaddleNLP/model_zoo/ernie-1.0/finetune/tmp/chnsenticorp_v2/export/

# 开始finetune训练并导出模型
dataset="chnsenticorp_v2"
!cd ~/PaddleNLP/model_zoo/ernie-1.0/finetune && python run_seq_cls.py \--do_train \--do_eval \--do_predict \--do_export \--model_name_or_path ernie-1.0-base-zh \--dataset $dataset \--output_dir ./tmp/$dataset \--eval_steps 200 \--save_steps 200 \--metric_for_best_model "eval_accuracy" \--load_best_model_at_end \--save_total_limit 3 \

部署预测

训练完导出模型之后,可以用于部署,deploy/seq_cls_infer.py文件提供了python部署预测示例。可执行以下命令运行部署示例:

!cd ~/PaddleNLP/model_zoo/ernie-1.0/finetune/ && python deploy/seq_cls_infer.py --model_dir tmp/chnsenticorp_v2/export/ --device gpu --backend paddle

更多部署可以参考ERNIE 1.0 模型 Python 部署示例. FastDeploy ERNIE 3.0 Tiny 模型高性能部署

总结

Ernie系列模型很好很强大,且有很好的延续性,Ernie 1.0 2.0和Ernie3.0 Tiny 均为12层hidden_layers 12attention_heads 和 768hidden_size ,而精度依次提高。Ernie和BERT一样,都是用了Transformer的编码器,而GPT是用了解码器。Ernie 3.0 对标GPT3.0, 现在GPT 4 刚出来,Ernie的大模型新版本:文心一言也将于3.16日发布,让我们拭目以待!

通过Ernie源代码的学习,使我们对Ernie模型有了更深刻的了解,非常有助于今后对BERT、GPT、ChatGPT以及文心大模型的学习和理解。用飞桨,划时代,在AI世界,砥砺前行,有你有我!

调试

报错cannot import name ‘ErnieConfig’

----> 1 from paddlenlp.transformers import ErnieConfigImportError: cannot import name 'ErnieConfig' from 'paddlenlp.transformers' (/opt/conda/envs/python35-paddle120-env/lib/python3.9/site-packages/paddlenlp/transformers/__init__.py)

升级PaddleNLP到2.5x

训练报错missing tool_helpers, pip install tool_helpers

> WARNING: could not find index map file /home/aistudio/wudaodata/wudao_200g_sample_ernie-1.0-base-zh_train_indexmap_64000000mns_509msl_0.10ssp_1234s.npy, building the indices on rank 0 ...
int32> building sapmles index mapping for train ...> missing tool_helpers, pip install tool_helpers please, try to compile locally.
make: Entering directory '/home/aistudio/PaddleNLP/model_zoo/ernie-1.0/data_tools'
/opt/conda/envs/python35-paddle120-env/bin/python3: No module named pybind11
g++ -O3 -Wall -shared -std=c++11 -fPIC -fdiagnostics-color  helpers.cpp -o helpers.cpython-39-x86_64-linux-gnu.so
helpers.cpp:22:10: fatal error: pybind11/numpy.h: No such file or directory#include <pybind11/numpy.h>^~~~~~~~~~~~~~~~~~
compilation terminated.
Makefile:9: recipe for target 'helpers.cpython-39-x86_64-linux-gnu.so' failed
make: *** [helpers.cpython-39-x86_64-linux-gnu.so] Error 1
make: Leaving directory '/home/aistudio/PaddleNLP/model_zoo/ernie-1.0/data_tools'
Making C++ dataset helpers module failed, exiting.

pip install tool_helpers

训练中出现:Found inf or nan

[2023-03-01 10:50:09,151] [    INFO] - valid step 7000, batch: 10, loss: 6.631250, lm_loss: 5.959375, sop_loss: 0.671094, ips: 141 seqs/s
Found inf or nan, current scale is: 65536.0, decrease to: 65536.0*0.5

系统自动把数值减半的提醒,不用管它。

训练到40000异常中断

[2023-03-01 18:29:41,943] [    INFO] - Configuration saved in output/ernie-1.0-dp8-gb512/model_last/config.json
Exception in thread Thread-4:
Traceback (most recent call last):File "/opt/conda/envs/python35-paddle120-env/lib/python3.9/site-packages/paddle/fluid/dataloader/dataloader_iter.py", line 623, in _get_datadata = self._data_queue.get(timeout=self._timeout)File "/opt/conda/envs/python35-paddle120-env/lib/python3.9/multiprocessing/queues.py", line 114, in getraise Empty
_queue.EmptyDuring handling of the above exception, another exception occurred:Traceback (most recent call last):File "/opt/conda/envs/python35-paddle120-env/lib/python3.9/threading.py", line 980, in _bootstrap_innerself.run()File "/opt/conda/envs/python35-paddle120-env/lib/python3.9/threading.py", line 917, in run
Traceback (most recent call last):File "/home/aistudio/PaddleNLP/model_zoo/ernie-1.0/run_pretrain.py", line 761, in <module>self._target(*self._args, **self._kwargs)File "/opt/conda/envs/python35-paddle120-env/lib/python3.9/site-packages/paddle/fluid/dataloader/dataloader_iter.py", line 536, in _thread_loopbatch = self._get_data()File "/opt/conda/envs/python35-paddle120-env/lib/python3.9/site-packages/paddle/fluid/dataloader/dataloader_iter.py", line 638, in _get_dataraise RuntimeError("DataLoader {} workers exit unexpectedly, " \
RuntimeError: DataLoader 1 workers exit unexpectedly, pids: 18494do_train(config)File "/home/aistudio/PaddleNLP/model_zoo/ernie-1.0/run_pretrain.py", line 737, in do_trainsave_ckpt(output_dir, model, tokenizer, optimizer, scaler, args, global_step)File "/home/aistudio/PaddleNLP/model_zoo/ernie-1.0/run_pretrain.py", line 711, in save_ckptpaddle.save(model_dict, os.path.join(output_dir, "model_state.pdparams"))File "/opt/conda/envs/python35-paddle120-env/lib/python3.9/site-packages/paddle/framework/io.py", line 811, in save_legacy_save(obj, path, protocol)File "/opt/conda/envs/python35-paddle120-env/lib/python3.9/site-packages/paddle/framework/io.py", line 856, in _legacy_savesaved_obj = _build_saved_state_dict(obj)File "/opt/conda/envs/python35-paddle120-env/lib/python3.9/site-packages/paddle/framework/io.py", line 78, in _build_saved_state_dictsave_dict[key] = value.numpy()File "/opt/conda/envs/python35-paddle120-env/lib/python3.9/site-packages/paddle/fluid/multiprocess_utils.py", line 135, in __handler__core._throw_error_if_process_failed()
SystemError: (Fatal) DataLoader process (pid 18496) exited is killed by signal: Killed. (at /paddle/paddle/fluid/imperative/data_loader.cc:188)/bin/bash: 行 1: 18378 段错误               (核心已转储) python run_pretrain.py --model_type "ernie" --model_name_or_path "ernie-1.0-base-zh" --tokenizer_name_or_path "ernie-1.0-base-zh" --input_dir "/home/aistudio/wudaodata" --output_dir "output/ernie-1.0-dp8-gb512" --split 949,50,1 --max_seq_len 512 --micro_batch_size 64 --use_amp true --fp16_opt_level O2 --max_lr 0.0001 --min_lr 0.00001 --max_steps 1000000 --save_steps 50000 --checkpoint_steps 5000 --decay_steps 990000 --weight_decay 0.01 --warmup_rate 0.01 --grad_clip 1.0 --logging_freq 200 --num_workers 2 --eval_freq 1000 --device "gpu" --share_folder false

重启一下环境,释放掉前面源代码学习时占用的显存即可。

训练报错

    do_train(config)File "/home/aistudio/PaddleNLP/model_zoo/ernie-1.0/run_pretrain.py", line 517, in do_trainvalid_data_loader = valid_data_loader()
TypeError: '_DataLoaderIterMultiProcess' object is not callable

有可能是数据集路径不对。或者需要重启一下环境。

结束语

让我们荡起双桨,在AI的海洋乘风破浪!

飞桨官网:https://www.paddlepaddle.org.cn

因为水平有限,难免有不足之处,还请大家多多帮助。

作者:段春华, 网名skywalk 或 天马行空,济宁市极快软件科技有限公司的AI架构师,百度飞桨PPDE。

我在AI Studio上获得至尊等级,点亮10个徽章,来关注我呀~ https://aistudio.baidu.com/aistudio/personalcenter/thirdview/141218

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.rhkb.cn/news/4303.html

如若内容造成侵权/违法违规/事实不符,请联系长河编程网进行投诉反馈email:809451989@qq.com,一经查实,立即删除!

相关文章

你还在用听不懂人话的 ChatGPT 吗?

&#x1f449;腾小云导读 近年来&#xff0c;大模型如 ChatGPT 等愈发强大的生成能力一次次引爆行业讨论。你真的玩明白 GPT 等等 AIGC 工具了吗&#xff1f;它是否足够“听话”、理解并满足你所有的需求&#xff1f;提示词工程是激活大模型能力的关键所在。可以说谁掌握 Promp…

ChatGPT 讲的笑话90%是重复的?归纳出来只有这25个?

作者 | 小戏、Python 幽默&#xff0c;似乎一直是一种专属于人的语言技巧&#xff0c;通过一些简单的谐音、双关&#xff0c;让错位的事张冠李戴&#xff0c;让一些可能普普通通的事变得荒诞&#xff0c;神奇的就可以在人际之间的心照不宣中获得幽默&#xff0c;让人捧腹&#…

使用chatgpt的api实现个人聊天机器人demo(可实现多轮对话)

之前写过一篇文章&#xff0c;初步实现调用chatgpt的api实现与机器人的对话&#xff0c;但是并没有提及多轮对话如何实现。在这篇文章中&#xff0c;我将介绍如何利用openai的api实现聊天机器人的多轮对话。 第一步&#xff1a;官网申请api&#xff0c;可参考我之前的博文&…

ChatGPT宝藏插件丨装上之后,上网、语音聊天、一键分享对话……简直让你爽到起飞!

今天分享4个让你的 ChatGPT 功能更强大的浏览器插件&#xff0c;装上就能用&#xff0c;每一个都是精挑细选。 1. WebChatGPT 很多小伙伴在用 ChatGPT查阅信息时&#xff0c;发现它有一个致命的问题&#xff1a; ChatGPT的知识库全部截止到 2021年9月&#xff0c;正常情况下…

chatgpt的语音机器人

准备工作 一.OpenAi的Api 1.登录openai 2.点击Develovpers的overview ### 3.点击右上角的Login in ### 4.点击右上角的view apikey ### 5.点击Creat new secret key 这样就会得到一个chatgpt的api&#xff0c;请妥善保存这个api&#xff0c;当你关闭这个网页的时候&#xff…

【ChatGPT】对话体验

如何使用 https://zhuanlan.zhihu.com/p/615721084 hello&#xff0c;ChatGPT! 来自它的自我介绍&#xff1a; 我是一种称为ChatGPT的大型语言模型&#xff0c;由OpenAI公司开发。我的设计目的是使用人工智能技术来进行自然语言处理和语言生成&#xff0c;以便向人类用户提供有…

【一分钟学会】用python做一个语音对话ChatGPT的程序——打造私人语音助手

本项目灵感来源是我的一个专业课程设计&#xff0c;当时耗时近四天在网上都没找到十分有用的信息。 以下是本项目完整的步骤流程&#xff0c;算是对自己工作的一个阶段性总结。希望同感兴趣的小伙伴们相互探讨、交流和学习。 一、准备工作 首先&#xff0c;需要准备①最新版的…

【插件分享】对Chatgpt的对话内容进行滚动截屏

问题描述 在和Chatgpt进行了一番友好的对话后&#xff0c;想要把对话内容截取出来与朋友分享&#xff0c;但是由于对话内容比较多&#xff0c;无法显示在一页里&#xff0c;因此需要使用滚动截屏。但是edge浏览器自带的网页捕获功能在chatgpt的聊天界面里无法实现滚动截屏。 解…

基于ChatGPT的端到端语音聊天机器人项目实战(一)

基于ChatGPT的端到端语音聊天机器人项目实战 ChatGPT API后台开发实战 本节主要是跟大家分享一个端到端的基于模型驱动的对话机器人,会有前端和后端,也会有一些具体模型的调用,读者需具有Python语言编程的基础,这是前置性的条件,有了这个基础,理论上讲本节所有的内容,…

ChatGPT专业应用:生成海外KOL合作邮件

正文共 475 字&#xff0c;阅读大约需要 2 分钟 品牌/媒介运营必备技巧&#xff0c;您将在2分钟后获得以下超能力&#xff1a; 快速生成海外KOL合作邮件模板 Beezy评级 &#xff1a;B级 *经过简单的寻找&#xff0c; 大部分人能立刻掌握。主要节省时间。 推荐人 | Alice 编辑…

用一杯星巴克的钱,训练自己私有化的ChatGPT

点击蓝字 关注我们 文章摘要&#xff1a;用一杯星巴克的钱&#xff0c;自己动手2小时的时间&#xff0c;就可以拥有自己训练的开源大模型&#xff0c;并可以根据不同的训练数据方向加强各种不同的技能&#xff0c;医疗、编程、炒股、恋爱&#xff0c;让你的大模型更“懂”你….…

ChatGPT怎么突然变得这么强?华人博士万字长文深度拆解GPT-3.5能力起源

文章目录 一、2020 版初代 GPT-3 与大规模预训练二、从 2020 版 GPT-3 到 2022 版 ChatGPT三、Code-Davinci-002和 Text-Davinci-002&#xff0c;在代码上训练&#xff0c;在指令上微调四、text-davinci-003 和 ChatGPT&#xff0c;基于人类反馈的强化学习的威力五、总结当前阶…

ChatGPT劲敌团队发布,可轻松引导ChatGPT不要输出有害言论!

文&#xff5c;MoMo酱 前不久Lecun携手曾经的死对头马库斯统一战线&#xff0c;炮轰以ChatGPT为首的大模型是邪路&#xff0c;吃瓜群众看的不亦乐乎&#xff0c;大佬们争议的核心便是大模型的 「道德和中立性」 &#xff0c;也许是ChatGPT等大模型当前面临的最大挑战。 本篇论文…

Unity 连接ChatGPT

1、首先登录openai官网拿到自己的api&#xff08;key&#xff09; 2、下载插件&#xff0c;可以私聊 3、3个地方填上key 效果

Unity接入ChatGPT详细教程

想了解ChatGPT吗?想把ChatGPT作为平时开发的工具吗?看过来 直奔主题&#xff0c;先看一下效果 下面我带着大家一步一步来实现这个效果。 1.准备阶段 Unity(2019之后的版本)&#xff0c;Git(https://blog.csdn.net/qq_38952352/article/details/127656385),ChatGPT key&#…

ChatGPT:优化对话的语言模型

OpenAI 已经训练了一个名为 ChatGPT 的模型&#xff0c;它以对话方式进行交互。对话格式使 ChatGPT 可以回答后续问题、承认错误、挑战不正确的前提并拒绝不适当的请求。 今天主要测试了ChatGPT. C# 实现冒泡排序&#xff0c; using System;namespace BubbleSortExample {cl…

chatgpt写程序-python小游戏-2048-pygame

闲的没事&#xff0c;用chatpgt弄了个小游戏&#xff0c;2048&#xff0c;利用pygame实现&#xff0c;无额外贴图。 只需要告诉他写个python游戏2048&#xff0c;只用pygame实现&#xff0c;不要额外贴图。然后在他暂停后说请继续&#xff0c;最后会有一些bug&#xff0c;把报错…

10秒钟,chatgpt帮你生成简单贪吃蛇游戏

场景&#xff1a; 制作一个简单html贪吃蛇游戏 方法 <!DOCTYPE html> <html> <head><title>贪吃蛇</title><style>body {margin: 0;padding: 0;}canvas {border: 1px solid black;}</style> </head> <body><canvas …

两句话,ChatGPT帮我写一个打飞机的游戏

大家好&#xff0c;我是全村的希望 今天的主题是让 chatGPT 来帮我们写一个打飞机的游戏 记得我刚学 Python 的时候&#xff0c;看的那本很经典的入门书《Python 编程&#xff1a;从入门到实践》&#xff0c;里面就有小项目就是教你编写一个打飞机的游戏 我那时候是对着书一个一…

chatgpt赋能python:Python用于股票:掌握数据、分析趋势

Python用于股票&#xff1a;掌握数据、分析趋势 在当今数字化时代&#xff0c;投资者使用数据分析技术作出投资决策变得越来越重要&#xff0c;而Python正是一种无形中帮助投资者进行数据分析的强有力工具。Python是一种高级数据分析语言&#xff0c;具有易读易懂的语法和强大…