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

news/2024/5/3 6:03:35/文章来源:https://blog.csdn.net/skywalk8163/article/details/129572200

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 部署示例

安装依赖库

# 安装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
极快软件官网:http://www.quye.com

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

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

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

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

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

相关文章

中科院ChatGPT 学术版 本地部署实践记录

近期中科院ChatGPT 学术版 在github上限了&#xff0c;截止本文发布已经有18.3k的star了。 他们将ChatGPT 学术版开源出来真的非常好&#xff0c;能让更多的人享受到科技的红利。 我也想着试一下&#xff0c;这次也记录下本地部署的过程。 其实文件中的readme&#xff0c;已…

ChatGPT Something went wrong 处理

一、报错提示 Something went wrong. If this issue persists please contact us through our help center at help.openai.com. 二、解决方案 一般是代理节点出现问题 ChatGPT退出登录 关闭代理并重新启动代理 切换其他节点 清除浏览器缓存 重新登录ChatGPT 问题解决&…

ChatGPT中文版重装上阵

ChatGPT中文版重装上阵 近日&#xff0c;AI模型市场联手OpenAI推出了新版ChatGPT&#xff0c;这是一款面向聊天机器人开发的自然语言处理&#xff08;NLP&#xff09;模型。ChatGPT是一款非常强大的NLP模型&#xff0c;可以帮助开发者构建会话式聊天机器人&#xff0c;它可以更…

从大神Alex Smola与李沐离职AWS创业融资顺利,回看ChatGPT大模型时代“底层武器”演进...

图文原创&#xff1a;亲爱的数据“Were building something big ... stay tuned. Talk to me if you want to work on scalable foundation models.”“我们正在建造一个大项目……请继续关注。如果你想在可扩展基础模型上工作&#xff0c;请告诉我。”“参数服务器之父” Alex…

ChatGPT|微信快速接入ChatGPT

前言 最近chatGPT可谓是火的一发不可收拾&#xff0c;从圈内火到圈外。在人工智能领域&#xff0c;Ai已经是一个屡见不鲜的东西了&#xff0c;为什么这次openAi推出的chatGPT却异常的受人欢迎&#xff1f;其实这还得益于GPT模型。 那么什么是GPT模型&#xff1f;我们可以看一…

火爆全球的ChatGPT是什么?

引言 ChatGPT 最近非常火&#xff0c;引发各界关注。吸引了几亿人在使用。报道中充斥了各种言论&#xff1a;“学生用 ChatGPT 写作业”、“上线两个月活跃用户破亿”、“以后很多文案工作者要被 ChatGPT 取代了&#xff01;”等等。究竟什么是 ChatGPT&#xff1f;用途有哪些…

快!体验文心一言;ChatGPT关键词优化指南;Midjourney从入门到精通;AI绘画资料合集;Midjourney v5效果相当不错 | ShowMeAI日报

&#x1f440;日报合辑 | &#x1f3a1;生产力工具与行业应用大全 | &#x1f9e1; 点赞关注评论拜托啦&#xff01; &#x1f916; 『文心一言』没邀请码&#xff1f;这个方式能跟「文心一言」聊天&#xff01; 文心一言正式发布&#xff0c;普通的对话效果好于预期&#xff0…

5个 ChatGPT 功能,帮助你提升日常编码效率

ChatGPT 作为最快完成亿活用户的应用&#xff0c;最近真的是火出天际了。今天分享5个 ChatGPT 功能&#xff0c;来提升我们的日常工作以及如何使用它提高代码质量。 ChatGPT 的出现&#xff0c;彻底改变了开发代码的方式。但是目前为止&#xff0c;大多数软件开发人员和数据专业…

全网最详细中英文ChatGPT-GPT-4示例文档-语句情绪分类从0到1快速入门——官网推荐的48种最佳应用场景(附python/node.js/curl命令源代码,小白也能学)

从0到1快速入门语句情绪分类应用场景Introduce 简介setting 设置Prompt 提示Sample response 回复样本API request 接口请求python接口请求示例node.js接口请求示例curl命令示例json格式示例其它资料下载ChatGPT是目前最先进的AI聊天机器人&#xff0c;它能够理解图片和文字&am…

在为时已晚之前使用 ChatGPT 赚钱的 11 种方法

随着聊天机器人和自然语言处理技术的不断进步,现在使用这些工具赚钱的方式比以往任何时候都多。以下是使用聊天机器人和 GPT(生成式预训练转换器)技术赚取收入的 11 种方式: 通过聊天机器人提供个性化的客户服务和支持 创建和销售聊天机器人模板供其他企业使用 使用 GPT 技…

【ChatGPT】AIGC:人工智能生成内容发展趋势 AI-Generated Content

关键词&#xff1a;AIGC&#xff0c;DALL-E 2、Stable Diffusion&#xff0c;ChatGPT&#xff0c;Transformer 目录 【ChatGPT】AIGC&#xff1a;人工智能生成内容发展趋势 AI-Generated Content 引言 AIGC 技术和产业生态迎来发展快车道 第一&#xff0c;基础的生成算法…

我用尽了洪荒之力,解开了ChatGPT 写前端代码的封印,结果...

我用尽了洪荒之力&#xff0c;解开了ChatGPT 写前端代码的封印介绍ChapGPT 听起来好得令人难以置信&#xff0c;所以让我们让它为我们编写一些 JS 代码。我想看看它是否可以解决我作为前端开发人员每天所做的任务。是驴子是马拉出来溜溜&#xff0c;我们还是直接进入主题一探究…

100天精通Python丨黑科技篇 —— 21、ChatGPT、ChatGPT、ChatGPT

ChatGPT 是 OpenAI 推出的一种基于 GPT-3/4 的聊天机器人。chatgpt 的颠覆性影响主要体现在提高语言交流的便捷性、个性化服务、自动化客服和教育娱乐等方面,这些应用可以为用户带来更多的便利和乐趣,同时也为企业提供了更多的服务和商机。 本文收录于 《100天精通Python专栏…

GPT-4和ChatGPT效果对比,差别太大了

文&#xff5c;Serendipity知乎 前言 GPT4上午朋友圈已经刷屏啦&#xff0c;不过我还在忙&#xff0c;刚刚才登上 GPT-4 &#xff0c;现在来体验一下~ 附 GPT-4 能力测试站&#xff08;直接注册即可&#xff0c;无需魔法&#xff09;&#xff1a; https://gpt4test.com 附 Chat…

ChatGPT平替版本推荐以及试用体验

❤️觉得内容不错的话&#xff0c;欢迎点赞收藏加关注&#x1f60a;&#x1f60a;&#x1f60a;&#xff0c;后续会继续输入更多优质内容❤️&#x1f449;有问题欢迎大家加关注私戳或者评论&#xff08;包括但不限于NLP算法相关&#xff0c;linux学习相关&#xff0c;读研读博…

免费chatGPT国内镜像,目前可访问

安利几款测试过的&#xff0c;chatgpt国内镜像网站&#xff0c;无需魔法和注册即可使用。 免费学习测试https://chat1.wuguokai.top/lite/chatgpt/?mchannel-web&vFullscreen&options%7B%22hideWidget%22%3Atrue%2C%22config%22%3A%7B%22enableReset%22%3Atrue%2C%22e…

特制自己的ChatGPT:多接口统一的轻量级LLM-IFT平台

©PaperWeekly 原创 作者 | 佀庆一单位 | 中科院信息工程研究所研究方向 | 视觉问答项目简称&#xff1a;Alpaca-CoT&#xff08;当羊驼遇上思维链&#xff09;项目标题&#xff1a;Alpaca-CoT: An Instruction Fine-Tuning Platform with Instruction Data Collection an…

CVPR2023论文速递(2023.3.23)!已接入ChatGPT总结!共26篇!

整理&#xff1a;AI算法与图像处理CVPR2023论文和代码整理&#xff1a;https://github.com/DWCTOD/CVPR2023-Papers-with-Code-Demo欢迎关注公众号 AI算法与图像处理&#xff0c;获取更多干货&#xff1a;大家好, 最近正在优化每周分享的CVPR论文, 目前考虑按照不同类别去分类…

ChatGPT详解

导读&#xff1a;ChatGPT出现后惊喜或惊醒了很多人。惊喜是因为没想到大型语言模型&#xff08;LLM,Large Language Model&#xff09;效果能好成这样&#xff1b;惊醒是顿悟到我们对LLM的认知及发展理念&#xff0c;距离世界最先进的想法&#xff0c;差得有点远。我属于既惊喜…

【JAVA】让 ChatGPT 来浅说 AQS

前言又迎来了一年一度的金三银四&#xff0c;虽然说今年的大环境不好&#xff0c;但是招聘还是在火热进行中。面试过 Java 工程师的小伙伴都知道&#xff0c;Java 中的 AQS 是面试高频题&#xff0c;面试官上来就直接了当地问&#xff0c;AQS 知道是什么吧&#xff0c;来讲讲它…