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

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.rhkb.cn/news/9024.html

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

相关文章

加速访问Cloudflare的网站的解决方案

大家好,我是爱编程的喵喵。双985硕士毕业,现担任全栈工程师一职,热衷于将数据思维应用到工作与生活中。从事机器学习以及相关的前后端开发工作。曾在阿里云、科大讯飞、CCF等比赛获得多次Top名次。现为CSDN博客专家、人工智能领域优质创作者。喜欢通过博客创作的方式对所学的…

数据库开发工具界的ChatGPT来了

提提需求&#xff0c;就能按照你的要求自动生成库表、构建测试数据、查询变更数据&#xff0c;动不动还要帮你优化 SQL …… 这样的数据库开发工具到底有多强大&#xff1f; NineData 通过内置强大的AI生成能力&#xff0c;让你可以在 NineData 平台上&#xff0c;通过自然语言…

不再焦虑了!小白的prompt入门实验指南Mixlab推荐

‍ ‍相信大家都体验过了文心一言、ChatGPT、Claude、Stable diffusion、MidJourney等等生成式人工智能&#xff08;大模型LLMs&#xff09;&#xff0c;在使用过程中有些人用得很好&#xff0c;产生的结果效果非常好&#xff0c;而有些人可能一用就弃坑了。 造成人与人的使用体…

人工智能与营销新纪元 2023 AI+

人工智能是什么&#xff1f; 有望飞跃式提升营销生产力的变革力量 人工智能是研究、开发用于模拟、延伸和扩展人的智能的理论、方法、技术及应用系统的一门新的 技术科学。是计算机科学的一个分支&#xff0c;它企图了解智能的实质&#xff0c;并生产出一种新的能以人类智能相…

神仙打架——号称是性能最强的中英文百亿参数量开源模型Baichuan-13B他来了!

下午开个会的功夫看到新闻推送一条最新的大模型相关的项目开源发布了&#xff0c;到底是怎么个事我们来一起看下。 官方项目地址在这里&#xff0c;如下所示&#xff1a; 可以看到&#xff1a;才刚刚过去十几分钟的时间就已经有超过500的star量了。 就在不久前的6月15日&…

ChatGPT 50+ 使用案例以及使用指令 Prompt

原文&#xff1a;ChatGPT 50 使用案例以及使用指令 Prompt - 哔哩哔哩 ChatGPT 是一个非常强大的人工智能工具&#xff0c;但如果你像大多数人一样使用它&#xff0c;那么你就错过了它的全部潜力。 在本文中&#xff0c;我整理了 Chat GPT 用例的最终列表&#xff0c;包括生产…

chatgpt赋能python:Python编写SEO文章的技巧

Python编写SEO文章的技巧 概述 随着互联网的普及&#xff0c;越来越多的网站和公司开始关注SEO&#xff0c;也就是搜索引擎优化&#xff0c;通过优化网站结构和内容&#xff0c;使其在搜索引擎中排名提高&#xff0c;从而吸引更多的流量。而Python作为一种高级编程语言&#…

chatgpt赋能python:Python如何写SEO优化文章

Python 如何写SEO优化文章 SEO&#xff08;Search Engine Optimization&#xff09;是指通过优化网站结构、代码和内容&#xff0c;提升网站在搜索引擎中的排名&#xff0c;从而增加网站的流量。编写SEO优化文章的目的就是为了能够更好地吸引搜索引擎的爬虫&#xff0c;提高文…

chatgpt赋能python:Python创建SEO文章的指南

#Python创建SEO文章的指南 在当今数字化世界中&#xff0c;SEO&#xff08;搜索引擎优化&#xff09;对于拥有一个成功的在线业务至关重要。SEO文章不仅可以帮助提高网站的排名&#xff0c;还可以吸引更多的访问者并提高转化率。在本文中&#xff0c;我们将介绍如何使用Python…

让最近爆火的ChatGPT来谈谈,作为一个技术人该如何写好一篇技术博文

ChatGPT 是由 OpenAI 训练的一个大型语言模型。专门设计用于回答用户提出的问题&#xff0c;我可以提供有价值的信息&#xff0c;并帮助用户解决问题 下面的回答均来自ChatGPT CharGPT 如何写好一篇技术博文&#xff1f;写技术博文需要具备那些能力就用java实现冒泡排序来写一…

chatgpt赋能python:用Python写SEO文章怎么做?

用Python写SEO文章怎么做&#xff1f; 作为一名有10年Python编程经验的工程师&#xff0c;我很清楚Python的强大和多用途性。如果你是一个优秀的SEO从业者&#xff0c;你可能已经了解了这一点。Python可以用来处理大量数据、网页分析、自动化流程、爬虫抓取以及机器学习。在这…

最简洁的Plato-mini闲聊机器人部署教程,举一反三部署类chatGPT

★★★ 本文源自AlStudio社区精品项目&#xff0c;【点击此处】查看更多精品内容 >>> 百度PLATO-Mini闲聊模型 PLATO-MINI| 6-layers, 12-heads, 768-hidden|在十亿级别的中文对话数据上进行预训练。参数量更小&#xff0c;但效果更好。只支持闲聊型对话。 无需任何…

倒计时2天!顶级专家联合打造,“大模型前沿技术讲习班”周末开讲,提供多种全额奖学金...

大模型正在引发人工智能研究与应用范式产生重大变革&#xff0c;越来越多的顶级团队和杰出人才纷纷加入这一技术浪潮。作为AI大模型科研先锋&#xff0c;智源研究院聚集了来自高校院所和创新企业的一大批大模型领域卓越学者与工程师&#xff0c;共同致力于推动我国大模型的创新…

大型金融机构用户体验生态探索与实践

作者 | 中国工商银行软件开发中心 责编 | 夏萌 随着金融服务线上化和用户对优质体验的追求&#xff0c;构建科学的用户体验生态并持续推动产品用户体验升级成为传统金融机构的重要挑战。 从客户的角度来看&#xff0c;用户体验直接关系到他们在银行中获得服务的感受和满意度。简…

智头条|ChatGPT-4发布,澜舟科技完成数亿元融资

行业动态 李开复&#xff1a;AI2.0将催生三大投资机会 3月14日&#xff0c;北京创新工场总部举办“AI1.0到AI2.0的新机遇”趋势分享会。创新工场董事长兼CEO李开复博士指出&#xff0c;在深度学习的重大突破之后&#xff0c;AI已经来到从1.0迈入2.0的拐点&#xff0c;AI2.0将会…

博士申请 | 香港中文大学(深圳)李海洲教授招收NLP等方向全奖博士/博后/RA

合适的工作难找&#xff1f;最新的招聘信息也不知道&#xff1f; AI 求职为大家精选人工智能领域最新鲜的招聘信息&#xff0c;助你先人一步投递&#xff0c;快人一步入职&#xff01; 香港中文大学&#xff08;深圳&#xff09; 香港中文大学&#xff08;深圳&#xff09;是一…

ChatGPT引爆AIGC,垂类龙头迎来“创新春天”

文|智能相对论 作者|陈壹 一款AI产品&#xff0c;到底有多神&#xff1f; ChatGPT刷新了我们的认知。 它用2个月时间&#xff0c;完成TikTok花9个月&#xff0c;Instagram花2年半才做到的事&#xff0c;成为史上用户增速最快破亿的消费级应用程序。 它也凭借一己之力&#xff0…

ChatGPT引爆AI热潮,未来有哪些核心落地场景与投资机遇?

自ChatGPT面世以来&#xff0c;AI行业再度被引爆&#xff0c;AI大模型作为新一代颠覆性技术同时掀起了一波又一波热潮&#xff0c;头部厂商与创业者纷纷涌入&#xff0c;备受业界瞩目与市场追捧。 在这汹涌的狂欢背后&#xff0c;实则代表着AI发展的阶跃&#xff0c;即AI直接创…

ChatGPT引爆变革:首个被颠覆的行业揭秘!

随着人工智能的飞速发展&#xff0c;自然语言处理技术逐渐渗透到内容创作领域。作为一种先进的对话型AI系统&#xff0c;ChatGPT正改变着传统的写作方式。本文将探讨ChatGPT如何颠覆内容创作行业&#xff0c;以及其中的一些引人入胜的案例。 ChatGPT是基于GPT架构的自然语言处…

ChatGPT 引爆向量数据库赛道

向量数据库和 Embedding 是现在 AI 领域的热门话题。 最近&#xff0c;为 ChatGPT 等生成式 AI 应用提供向量搜索、向量数据存储、向量嵌入等功能的向量数据库赛道突然走红&#xff0c;两家初创公司 Pinecone 和 Weaviate 共获 10 亿元融资&#xff0c;融资时间仅间隔6天&…