CVer从0入门NLP(三)———GPT、BERT模型

本文为稀土掘金技术社区首发签约文章,30天内禁止转载,30天后未获授权禁止转载,侵权必究!
🍊作者简介:秃头小苏,致力于用最通俗的语言描述问题

🍊专栏推荐:深度学习网络原理与实战

🍊近期目标:写好专栏的每一篇文章

🍊支持小苏:点赞👍🏼、收藏⭐、留言📩

CVer从0入门NLP(三)---------GPT、BERT模型

写在前面

Hello,大家好,我是小苏👦🏽👦🏽👦🏽

在上一节中,我为大家介绍了LSTM、ELMO和Transformer模型,其实全都是为今天的内容做准备的,所以说,阅读此篇之前强烈建议大家先看看上节的内容,链接如下:

本节将为大家带来大名鼎鼎的GPT和BERT,话不多说,让我们一起走进GPT和BERT的世界。🚀🚀🚀

GPT

终于讲到GPT了,我想现在没有人对这玩意陌生的叭,随着22年底ChatGPT的一炮走红,震惊了全世界,可以说是颠覆式的研究成果了。那么GPT的底层原理到底是怎么样的呢?不用急,跟随我的步伐一步步的来学习。🥗🥗🥗

如果还有没尝试过ChatGPT的小伙伴,一定要去试试,会极大程度提高你的生产力。注册教程可以点击☞☞☞查看详情。🍄🍄🍄
ChatGPT并不是从0-1凭空出现的,而是经过不断的优化改进,最终实现出如此惊艳的效果。本节将为大家介绍初代GPT的结构,后续文章会陆续更新GPT系列的发展史,敬请期待。🍄🍄🍄

我们先来看看GPT的全称叫什么,即"Generative Pre-Training",翻译即生成式的预训练。我们来解释一下这个名称"生成式的预训练",所谓生成式,表示该模型可以用于文本生成任务;而预训练则表示该模型先通过大规模的文本数据集进行训练,然后再用于下游任务。【这个和计算机视觉中的预训练含义是一样的】🍭🍭🍭

上文说到,GPT采用了预训练的方式来训练模型,其主要有两个阶段,如下:

  • 阶段一:利用语言模型进行预训练
  • 阶段二:通过Fine-tuning对下游任务进行微调

下图展示了GPT预训练的过程,我们一起来看看:

从上图中我们可以发现GPT的结构是这样的,如下:

大家有没有发现这个结构是和前文所述的ELMO模型非常类似的,当然了,也有一些差异,如下:

  1. 特征提取器使用的不是LSTM,而是特征提取能力更强的Transformer。【自GPT之后,几乎所有模型都开始使用Transformer架构来进行特征提取】
  2. GPT的预训练任务任然是语言模型,但是采用的是单向的语言模型。

大家听了以上两点,可能懂了一点,但也没完全懂,下面我将针对这两点做一个更细致的解释。

点1 :这里使用了Transformer架构,他的具体结构是什么样的呢?我们先来说结论:**GPT中的Transformer结构就是把Encoder中的Multi-Head Attention替换成了Masked Multi-Head Attention。**如下图所示:

看到上文的话不知道大家能否理解,我觉得你要是熟悉Transformer结构应该就能够理解了,Transformer结构主体由一个Encoder结构和一个Decoder结构构成,Encoder结构中使用了Multi-Head Attention,而Decoder中使用了Masked Multi-Head Attention。这里具体细节我就不说了,不清楚的可以去看我关于Transformer的博客介绍。我也贴一张Transformerd的结构图,方便大家对比,如下:

大家注意一下我说的是GPT中的Transformer结构就是把Encoder中的Multi-Head Attention替换成了Masked Multi-Head Attention。,大家可以对比一下结构,看看我的表述是否正确。网上也有一些说法说是GPT中的Transformer结构就是Transformer中的Decoder结构,其实还是存在一些问题的,因为Decoder结构中采用了两个连续的Masked Multi-Head Attention+LN结构,而GPT的Transformer中只使用了一个。【大家这里注意一下就好,在一些博客和平时交流中知道这么一回事就行,在后文我也会采取GPT采用的是Transformer中Decoder的说法,因为字少哈哈哈。🍄🍄🍄】

大家可能还注意到在GPT的Transformer结构中还有一个12×的字眼,其表示这个结构重复12次。

为了大家能更深入了解GPT的Transformer结构,可以看一下如下代码:

python 复制代码
from .attention import tf, MultiHeadAttention

class TransformerBlock(tf.keras.layers.Layer):

    def __init__(self, embedding_dimension, num_heads, feed_forward_dimension, dropout_rate=0.1):
        super(TransformerBlock, self).__init__()

        self.attention = MultiHeadAttention(embedding_dimension, num_heads)
        self.feed_forward_network = tf.keras.Sequential([
            tf.keras.layers.Dense(feed_forward_dimension, activation='relu'),
            tf.keras.layers.Dense(embedding_dimension)
        ])
        self.layer_normalization = [
            tf.keras.layers.LayerNormalization(epsilon=1e-6),
            tf.keras.layers.LayerNormalization(epsilon=1e-6)
        ]
        self.dropout = [
            tf.keras.layers.Dropout(rate=dropout_rate),
            tf.keras.layers.Dropout(rate=dropout_rate)
        ]

    def call(self, inputs):
        attention_output = self.dropout[0](self.attention(inputs))
        residual_output = self.layer_normalization[0](inputs + attention_output)
        feed_forward_output = self.dropout[1](self.feed_forward_network(residual_output))
        output = self.layer_normalization[1](residual_output + feed_forward_output)
        return output

大家可以阅读一下代码,看看是不是和我们上面所说的结构一致呢。需要注意的是上述代码家了一个Dropout结构,对整体的结构不影响。


点2:GPT采用的是单向的语言模型是什么意思?语言模型训练的任务目标是根据 <math xmlns="http://www.w3.org/1998/Math/MathML"> w i w_i </math>wi单词的上下文去正确预测单词 <math xmlns="http://www.w3.org/1998/Math/MathML"> w i w_{i} </math>wi , <math xmlns="http://www.w3.org/1998/Math/MathML"> w i w_{i} </math>wi之前的单词序列Context-before称为上文, <math xmlns="http://www.w3.org/1998/Math/MathML"> w i w_{i} </math>wi之后的单词序列Context-after称为下文。再使用ELMO做语言模型训练的时候,预测单词 <math xmlns="http://www.w3.org/1998/Math/MathML"> w i w_{i} </math>wi时同时考虑了该单词的上下文,而GPT只采用这个单词的上文来进行预测,抛弃了下文。为什么GPT看不到下文的信息呢,这就是因为GPT的Transformer结构使用了Masked Multi-Head Attention结构,其遮挡住了后面单词的信息,Multi-Head Attention和Masked Multi-Head Attention的区别如下:

上文为大家介绍的是模型的基本架构,当我们用此模型进行训练就完成了第一阶段的任务,即实现了模型的预训练,那么接下来如何进行第二阶段的任务------通过Fine-tuning对下游任务进行微调呢?如下图所示:

上图展示了下游任务如何进行微调。在ELMO模型中,其下游任务的网络结构是可以任意设计的,因为其用到的是ELMO模型训练出的词向量;而在GPT中,下游任务的网络结构可不能修改了,必须要和GPT预训练时保持一致,因为GPT在做下游任务时,下游任务的网络结构的初始化参数就是已经训练好的GPT网络的参数,这样你就可以利用预训练好的知识来应对你的下游任务了,相当于开局就送神装了,当你训练你的下游任务时,只需要进行微调就可以了。【这部分其实是很好理解的,就是计算机视觉任务中的迁移学习嘛🥗🥗🥗】

论文链接:GPT1🍁🍁🍁
本节就先为大家介绍到这里,后续会为大家介绍更多版本的GPT。🍄🍄🍄

BERT

这一节来为大家介绍大名鼎鼎的BERT了,我想任何一个NLPer都不会没有听过BERT的大名叭。BERT,其实他是美国少儿节目中的一个人名,他长这样:

所以大家在搜BERT时,可能会经常看到上图这样的一个封面,果然,起名字还是很重要的。说起名字,我们还是先看看BERT的全称叭------Pre-training of Deep Bidirectional Transformers for Language Understanding。🍚🍚🍚

从BERT的全称中我们可以看出什么?我觉得有以下两点是比较关键的:

  • Pre-training:说明BERT和GPT一样都是一个预训练的模型
  • Deep Bidirectional Transformers:说明BERT采用了一个双向的Transfomer结构

针对以上两点,第一点表明EBRT是一个预训练模型,也即分为预训练阶段+微调阶段,这和GPT的预训练是差不多的,后文我们在简单介绍下。我们主要来看看BERT的结构是怎么样的,如下:

不知道大家看到这个图有没有熟悉的感觉,要是有那就太好了,没有的话我们就一起来看看。我们上文已经为大家介绍了ELMO模型和GPT模型,下面我们将三个模型放一起看看三者的区别:

有没有发现他们三个实在是太像了,在上节介绍GPT时,我们说到,GPT和ELMO非常类似。今天这节的主角是BERT,那我们就来说说BERT和ELMO、GPT的区别和联系。

  • BERT和ELMO

    • ELMO采用LSTM作为特征提取器,而BERT采用的是Transformer中的编码器结构。【这个可以从BERT的结构图可以看出,每一个Trm都是一个Transformer的编码器】🥗🥗🥗
    • ELMO采用的是伪双向编码,即使用从左向右和从右向左的两个LSTM网络,它们分别独立训练,最好将训练好的两个方向的编码拼接。【有关ELMO的伪双向编码在ELMO模型那节有详细介绍】而BERT采用的是一个完全的双向编码,即完全可以看到某个单词的前后信息。
  • BERT和GPT

    • GPT采用Transformer的解码器作为特征提取器。【这个在GPT小节提到说解码器有些不妥,大家注意一下就好】而BERT采用的是Transformer的编码器作为特征提取器。
    • GPT采用的是单向编码,而BERT采用的是双向编码。

总的来说,BERT可以说是近年NLP邻域具有里程碑意义的模型,它借鉴了ELMO、GPT等模型的思想(借鉴了ELMO的双向编码、GPT的Transformer结构),是集大成者。

读到这里,不知道大家是否会存在一些疑惑,我列一些我能想到的,希望可以帮到大家。

  1. 什么是单向编码,什么是双向编码?

    其实这个很好理解,单向编码就是只考虑一个方向的信息,而双向编码则会考虑两个方向即上下文的信息。我举个例子,对于这句话"今天天气很__,我要去踢足球",现在要考虑在__填什么词。对于单向编码只会看到__前面的句子,即"今天天气很",那么此时__填入的可能是"糟糕"、"不错"、"好"等等词;但是对于双向编码来说,它还可以看到后文"我要去踢足球",那么__里的词应该是积极的,比如"不错"、"好"。从这里可以看出,其实双向编码对于句子的理解能力更好。🥂🥂🥂

  2. 为什么GPT要采用Transformer的解码器做特征提取,而BERT要采用Transformer的编码器做特征提取?

    其实它们特征提取器的不同,是因为它们的任务和目标不同。对于GPT来说,其旨在生成自然语言文本,例如生成文章、回答问题、完成句子等。因此,它的任务是基于输入文本的信息生成下一个单词或一段文本。为了完成这一任务,GPT采用了Transformer架构的解码器部分,由于解码器中存在Musk Multi-Head Attention的缘故,使得GPT看不到未来的信息。对于BERT来说,其任务是预训练一个深度双向的语言表示,以便于各种自然语言处理任务的下游任务(如文本分类、命名实体识别、句子关系判断等)。BERT关注的是理解文本的含义和上下文,而不是生成文本。为了实现这一目标,BERT采用了Transformer架构的编码器部分。编码器通过双向处理输入文本,从而更好地理解单词的上下文关系和语境,而不受生成顺序的限制。

    注:GPT和BERT也没有好坏之分,它们只是处理的任务和目标不同。但是显然GPT的任务其实更难一些,因为其只能看见现有信息,无法看到未来。现在随着ChatGPT的出现,就能发现当时OpenAI团队似乎就在下一盘大棋。

看到这里,我想你对BERT的结构已经比较清楚了,就是一个两层的双向Transformer Encoder嘛。下面我们一起来BERT是怎么进行训练的,当然了,BERT也是预训练模型,分为两个阶段进行训练:

  • 阶段一:使用大规模无标签语料,训练BERT基础语言模型
  • 阶段二:对下游任务进行微调

那么BERT是如何训练的呢,它其实实现了两个训练任务,分别是语言掩码模型(MLM)和下句预测(NSP),这也算是BERT的两个创新之处,我们分别来看一下:

  • 语言掩码模型MLM

    我们上文说到,BERT采用的是双向编码,之所以采用双向编码,是因为作者认为双向编码的性能、参数规模和效率更加优异。但是双向编码会存在see itself的问题呀,就是能看到参考答案。

    注意:大家回顾一下我在介绍ELMO模型时,是不是也谈及了see itself问题呢?ELMO模型是怎么解决的呢?------其通过的是两个方向彼此独立训练两个LSTM模型后再拼接的方式,实际上是一种伪双向模型,从而避免了see itself的问题。但是BERT可没有采用这种伪双向的方式,而是直接使用Transformer Encoder做一个完全双向的模型,这就会导致BERT存在see itself的问题。🍚🍚🍚

    大家不要问为什么作者不采用伪双向+Transformer Decoder的结构,问就是效果没有完全双向的效果好。🥗🥗🥗

    既然BERT存在see itself的问题,那么他是怎么做的呢?------答案就是MLM(Masked Language Model),什么是MLM呢,其实就是指在训练的时候随机从输入语料上Mask掉一些单词,然后通过的上下文预测该单词。大家有没有觉得非常像CBOW的思想,即完形填空。🍭🍭🍭

    上述随机被Mask的单词被称为掩码词,在训练过程中,输入数据随机选择15%的词用于预测,即作为掩码词。但是这样的设计会存在一些问题,即在预训练和微调之间造成了不匹配,因为 [MASK] 标记在微调期间不会出现。

    那么怎样缓解这样的弊端呢,BERT是这样做的:

    对于随机选择的15%的掩码词再做以下调整

    • 80%的词向量输入时被替换为

    • 10%的词的词向量在输入时被替换为其他词的词向量

    • 另外10%保持不动

    论文中也给出了关于此的小例子,如下图所示:

    这样做的好处是我告诉模型,句子可能是对的,也可能是错的,也可能是被Mask的,有的地方你需要预测,没有的地方你也需要判断是否正确,也就是说,模型需要预测所有位置的输出。🍦🍦🍦

  • 下句预测(NSP)

    NSP全称为"Next Sentence Prediction",即下句预测,其任务是判断句子B是否是句子A的下文,如果是的话输出'IsNext',否则输出'NotNext'。

    为什么要做这个任务呢?因为在很多自然语言处理的下游任务中,如问答和自然语言推断,都基于两个句子做逻辑推理,而语言模型并不具备直接捕获句子之间的语义联系的能力,或者可以说成单词预测粒度的训练到不了句子关系这个层级,为了学会捕捉句子之间的语义联系,BERT 采用了下句预测(NSP )作为无监督预训练的一部分。🍨🍨🍨

    具体怎么做NSP任务呢?BERT 输入的语句将由两个句子构成,这两句是从预料中随机抽取的,其中:

    • 50% 的概率将语义连贯的两个连续句子作为训练文本,符合IsNext关系
    • 另外50%是第二个句子从语料库中随机选择出一个拼到第一个句子后面,它们的关系是NotNext

    论文中也给出了相关的事例,如下图所示:

    我对上图中的符合做一些解释:

    • [CLS]:对于一个句子最前面的起始标识。【在CV中的VIT就有这个标识,不知道大家是否还记得,VIT就是借鉴了BERT。】
    • [MASK]:这就是我们在MLM中所说的掩码标识符
    • [SEP]:表示两个句子的分隔符
    • ##:这个设计到BERT划分词的标准,其不是根据单词划分的,对于一些常见的词根会单独划分,这样会大大减少字典数量,##这个符合表示flight和less是一个单词。

其实到这里MLM和NSP就介绍完了,上面我们看到对于一句话有各种符号,那么我们的输入是如何设计的呢,这部分我们就来看看BERT对输入的处理,如下图所示:

从上图可以看出,对于Input中的每个词,都有三个Embedding,分别如下:

  • 单词Embedding:这就是我们之前所说的词向量嘛,大家注意一下这里也要对标识符进行Embedding。
  • 句子Embedding:用于区分两个句子,其只有两个值,0和1。0表示前一个句子,1 表示后一个句子。对于上图中的输入我们来看看其句子Embedding的表示,如下图所示:
  • 位置信息Embedding:这个我就不多介绍了,在Transformer小节做了详细的介绍,不清楚的可以去看一下。但要注意的是在Transformer中我们的位置编码是使用三角函数表示,这里采用的是可学习的位置编码,关于可学习的位置编码,我在VIT有所介绍,不清楚的点击☞☞☞前去阅读。

上面介绍的是BERT的预训练过程,下面来说说下游任务微调部分,直接上论文的图,如下:

其实微调很简单,都是利用之前训练的BERT模型,这部分我不打算逐字介绍了,推荐大家去看李宏毅老师的这个视频:BERT下游任务改造


最后我们来看看BERT的效果怎么样,如下图所示:

不用我多说了叭,BERT确实厉害,但是其参数量是巨大的,普通人可跑不起来,接下来让我们来看看BERT的参数量。论文中给出了BERT的两种结构,分别是 <math xmlns="http://www.w3.org/1998/Math/MathML"> B R E T B A S E BRET_{BASE} </math>BRETBASE和 <math xmlns="http://www.w3.org/1998/Math/MathML"> B E R T L A R G E BERT_{LARGE} </math>BERTLARGE,参数量分别达到了恐怖的110M和340M,如下图所示:

这个参数量是怎么计算的呢?其实也不难,我们那 <math xmlns="http://www.w3.org/1998/Math/MathML"> B R E T B A S E BRET_{BASE} </math>BRETBASE为例来介绍其计算方法:首先是一个嵌入层,输入是字典的token数M,输出为H个隐层,所以嵌入层有 <math xmlns="http://www.w3.org/1998/Math/MathML"> M × H M×H </math>M×H个参数量;接下来是Transformer Encoder块,先是一个自注意力块,参数量是 <math xmlns="http://www.w3.org/1998/Math/MathML"> 4 H 2 4H^2 </math>4H2,然后是一个MLP结构,参数量是 <math xmlns="http://www.w3.org/1998/Math/MathML"> 8 H 2 8H^2 </math>8H2,所以一个Transformer Encoder块有 <math xmlns="http://www.w3.org/1998/Math/MathML"> 12 H 2 12H^2 </math>12H2,一共有L个Transformer Encoder块,所以这部分参数量一共为 <math xmlns="http://www.w3.org/1998/Math/MathML"> L × 12 H 2 L×12H^2 </math>L×12H2。在加上嵌入层的参数量,即 <math xmlns="http://www.w3.org/1998/Math/MathML"> B R E T B A S E BRET_{BASE} </math>BRETBASE的参数量为 <math xmlns="http://www.w3.org/1998/Math/MathML"> M × H + L × 12 H 2 M×H+L×12H^2 </math>M×H+L×12H2。

我们可以带入相关参数的值,M=30000,H=768,L=12,即 <math xmlns="http://www.w3.org/1998/Math/MathML"> M × H + L × 12 H 2 = 107974656 ≈ 110 M M×H+L×12H^2=107974656 \approx 110M </math>M×H+L×12H2=107974656≈110M。

这里的M的数值也可以从论文中看到,如下图所示:

论文链接:BERT🍁🍁🍁

总结

今天的内容就为大家分享到这里了,到这里也算一个小系列就出完了,当然了后续会继续出GPT系列的文章,谁让它这么火呢。🍡🍡🍡

最后的最后,明天就是中秋节了,希望大家幸福如意,阖家团圆,事事顺心。🥂🥂🥂

参考连接

1、The Illustrated Word2vec

2、理解 LSTM 网络

3、Transformer通俗笔记:从Word2Vec、Seq2Seq逐步理解到GPT、BERT

4、Understanding LSTM Networks

5、预训练语言模型的前世今生

6、PyTorch源码教程与前沿人工智能算法复现讲解

如若文章对你有所帮助,那就🛴🛴🛴

相关推荐
码蜂窝编程官方4 分钟前
【含开题报告+文档+PPT+源码】基于SSM的电影数据挖掘与分析可视化系统设计与实现
java·vue.js·人工智能·后端·spring·数据挖掘·maven
遗落凡尘的萤火-生信小白7 分钟前
转录组数据挖掘(生物技能树)(第11节)下游分析
人工智能·数据挖掘
XinZong16 分钟前
【OpenAI】获取OpenAI API Key的多种方式全攻略:从入门到精通,再到详解教程!
人工智能
没有余地 EliasJie18 分钟前
深度学习图像视觉 RKNN Toolkit2 部署 RK3588S边缘端 过程全记录
人工智能·嵌入式硬件·深度学习
HelpLook HelpLook1 小时前
高新技术行业中的知识管理:关键性、挑战、策略及工具应用
人工智能·科技·aigc·客服·知识库搭建
青松@FasterAI1 小时前
【RAG 项目实战 05】重构:封装代码
人工智能·深度学习·自然语言处理·nlp
chnyi6_ya2 小时前
论文笔记:Retrieval-Augmented Generation for Knowledge-Intensive NLP Tasks
论文阅读·人工智能·自然语言处理
&黄昏的乐师2 小时前
Opencv+ROS实现摄像头读取处理画面信息
linux·人工智能·opencv·计算机视觉·ros
默凉2 小时前
opencv-python 分离边缘粘连的物体(距离变换)
人工智能·python·opencv
xiandong202 小时前
241123_基于MindSpore学习Bert
人工智能·学习·bert