Skip to content

对“A PyTorch implementation of Transformer in "Attention is All You Need"”的代码的详解,以及transformer模型的自己的理解。记录下自己学习语言模型的全过程。包括了NLP中理解比较困难的内容吧。包括嵌入层、位置信息的理解,都记录在里面了

License

Notifications You must be signed in to change notification settings

Mikky574/transformer-pytorch

 
 

Folders and files

NameName
Last commit message
Last commit date

Latest commit

 

History

37 Commits
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 

Repository files navigation

把transformer作为入门模型来学,会是怎样的一个过程

水一水

心血来潮,随便看看,就开始想研究研究知名度拉满得transformer模型。作者之前是完全没接触过这个模型,也没怎么做过NLP,最多就是学习机器学习时,看过LSTM。

主要是看到了知乎博主的文章。把语言模型的发展划分为RNN模型,LSTM模型,transformer模型。作者看完文章,感觉好像还可以,前两个模型作者熟啊。就是个时序的循环模型,但transformer模型一直会说到一个创新点是自注意力机制。一直听说过注意力这个东西,但实际上自己完全没有接触过,也不知道如何入门这块,怎么为自己的模型,加上注意力相关的东西,成为了作者一直好奇,但却一直没整过的东西。

没整过,但又充满兴趣,那就一点点去了解这个模型吧。就按着从沐神的视频讲解看到论文原文,再看到别人的文章分析讲解,再看到别人的博客和youtube视频讲解。但都没完全看懂,只能明白一个大概的过程,对模型的每一步都是在做什么,有一个大概的了解。但具体的内容,包括NLP自然语言处理啥的,还有点一知半解的感觉,总觉得在雾里看花,水中望月。想来想去,一狠心,算了,找个具体的实现代码,一行行地去读代码,这样总算能看明白了。不过难崩的是,看的时间确实就,看了快整整一个月,算上写这篇整理笔记的时间,应该就是一个月整了。整个月,其他什么事都没做,就去理解一个模型。这感觉,就像作者花大把时间去整理文章,结果实际目的就是为了发发牢骚,假装作者在和电脑说话一样样。

主要内容概括

这里作者会写清楚,作者希望在文章中解释清楚的东西。以及作者希望看完本人的文章后,能够理解到什么程度,方便读者选择是否阅读这篇废话文集。

  1. 介绍清楚NLP自然语言处理相关的前置知识。用通俗的描述,从零开始解释计算机是如何看待和处理自然语言的。
    1. 作者虽然之前接触过一点NLP处理的东西,但那时候还是有点没理解透彻,所以这里作者打算从分词开始写,介绍下one-hot编码,然后进入嵌入层的介绍。
    2. 嵌入层个人认为,是NLP特征工程中很难理解的部分,所以作者会对这块内容进行非常详尽的介绍,包括作者本人一开始难以理解的嵌入层的反向传播过程,具体的热词编码的映射过程,以及离散的数据可以用嵌入层来表达的原因。
    3. 介绍下与机器语言相对的自然语言的特点。以及前面的嵌入层是语义的嵌入,这里还有一个位置编码相关的嵌入层,这个层做了什么,是怎么实现位置信息的表达的。
  2. 什么是时序问题,这个明明是文本处理相关的模型,为什么会说到时序问题,我们会听到RNN或者LSTM在处理长时序的数据上,效果不好。这是什么意思。
    1. 这里我会介绍下RNN以及LSTM模型,毕竟之前是按照别的大佬对transformer模型的介绍的顺序来理解的。虽然我觉得理解这两个模型对理解基于自注意力机制的trans模型几乎没有帮助。但毕竟是作者花时间去理解了的,怎么也得写进去显摆显摆(难崩)。
    2. 循环网络的一点介绍,这块我会写的简单一点,可能就写下循环网络是怎么得出结果的,主要是介绍下RNN循环神经网络以及LSTM长短时记忆网络,这两个网络中的循环网络结构。
  3. transformer模型的介绍
    1. 自注意力机制是什么,从矩阵运算层面,或者说是词向量的计算层面来说明注意力机制具体是怎么计算的,几何意义是什么。如何透彻地去理解自注意力这个过程,顺带把多头注意力也简单解释一下下。
    2. 模型的整体结构,这个我放在注意力机制后面来说,因为这篇论文最重要的还是提出了自注意力这个东西,所以我觉得有必要先解释清楚这个之后再来提及整体的模型。
    3. 编码器-解码器的模型结构,这里作者最先是在CV中的UNet模型中接触的编码器与解码器,所以类比过来就好理解了。作者也打算这么去写,把UNet都拉过来解释解释(23333)。
    4. 掩码层。掩码这么发挥作用的,为什么有掩码,具体的代码中掩码层是这么发挥作用的。是不是真的会把译码器的输出作为编码器的输入,那么这样模型不是会跑得很慢么。这里代码实际上经过了这样地处理,使得训练的时候不会这样在等待中增加不必要的时间消耗。
    5. 残差连接,简单介绍一下下,是个很简单的步骤。
  4. 写一个真实可用的翻译器模型
    1. 语言模型的问题,数据量太小跑出来的效果不好。作者用了1w句大小的平行语料库来训练,结果过拟合严重,效果很不好。
    2. 解决方法就是增大数据量,作者又去找个个200w句大小的平行语料库来训练,结果就是力大砖飞,出来的效果很好。第一次训练这种规模的文本数据集。很多地方都很意外,也是对深度学习有了更深的理解。很多时候模型的效果不单单看模型本身,更多的是依赖于数据集是什么样的。

1.NLP(Nature Language Process)

直入正题,自然语言处理首要的就是进行分词。语言模型能够处理的是数字间的计算问题,处理不了字符串间的计算。所以先要将自然语言由一长串的字符串,转化成相互间存在距离意义,能够进行相互运算的浮点数的数据类型。

1.1 分词

这块很简单,就是把一个自然语言的句子,划分为一个个独立的元素。举例来说,就是将"Madam President, on a point of order."转换为["Madam", "President" , "on", "a","point", "of" ,"order","."]

英文有着天然的空格可以作为分词的依据,中文则需要通过jieba分词库,对文本进行词义上的分词。另外,对具体的分词技术有兴趣的话,可以看下Byte Pair Encoding (BPE)算法,里面对如何从大量的数据项中,得出一个更加合理的简化词表。

分词将一个整体的数据项,划分为了一个个独立的单词元素。这样我们可以为不同词语,挖掘“在同一个句子中”这样的关系,从而建立起每个词语间的相关性,可以简单理解为因为两个词在同一个句子中,所以他们词义会比不在同一个句子中的两个词要更接近一些。但首先,这样的分词还是主要为了one-hot编码而服务的。

1.2 one-hot编码

这块内容也很简单,就是建立一个字符串到数字的字典映射,将用字符串列表表示的数据改变为用一个个独立的整型的数字来表示。也就是将原本的字符串表示为一个嵌入层大字典的索引值的方法。这会在嵌入层中再详细介绍。

这里我可以假设{"Madam":4, "President":5,",":6, "on":7 ,"a":8, "point":9, "of":10, "order":11,".":12,...},则原句子通过一个字典映射后,便将原来的字符串数据转化成这样的离散数据,[4,5,6,7,8,9,10,11,12]。建立这样的字典的方法很简单,到分词后的数据集中去统计即可。

在实际数据中,统计出来的字典会非常大,因为本人在数据预处理中,没有进行停止词,符号,词根这样的处理。因而在199w句子的平行语料库中,统计出了32w个独立的词语,直接用这样的词表去构建后续的嵌入层,会使得模型大到普通的个人电脑无法带动的地步。所以这里加入一个字符"<UNK>"表示词表中没有出现的词,用来将词表按照出现的词频数,由大到小,只取前3w8k词,其他的词语一律表示为UNKNOWN字符。这样也使得模型能够处理词表中没有出现过的词语。但这样的处理等于将所有词语都概括为<UNK>,也就是和当成了同一个词去处理。

热词字典

真实的热词映射词表差不多就长这样。这里说明下手动加上去的四个字符<PAD>,<UNK>,<StartSent>,<Federation>。分别代表的含义如下:

<PAD>:用来充当填充词,因为自然语言中,一个句子的长度往往不会一致。而矩阵运算首要的是句子长度一致,这里将训练集数据中每个句子,用填充词扩充到和整个训练集中的句子最长的一个,相等长度的句子。用0填补上空缺,主要为了固定输入矩阵的大小。毕竟只有输入大小固定了,才能固定下语言模型的形状。

<UNK>:前面介绍过了,用来表示词表中没出现过的词。使得模型在遇到新的词汇时不会报错。但一般对新词语的处理也不会很好。

<StartSent>:用来放在输出的第一个字符处,用来充当模型的input中的第一个词。后续在介绍模型以及翻译任务中,会讲到模型时会一个个生成下一个词的。所以模型会把自己先前生成的词当作下一个要生成的词的输入,因而这个在模型还未开始生成第一个输出字符时,充当第一个输出字符。

<Federation>:表示结束字符,用来为模型描述输出结束用的,也就是模型的输出,会在生成了这个词之后便结束,即使这个词语之后还拟合出了其他词,我们都会忽略掉那些词语。

1.3 嵌入层

这个虽然是模型的内容了,但本人还是将其归到自然语言的预处理中来说明。因为本人将这种方法理解为离散数据连续化的一种方法。与其他隐藏层是在处理数据的特征不同,这种方法是在产生模型的输入,或者说,是在做文本信息(离散的数据信息)的特征提取,或者通俗的理解为,是在生成特征,而不是在提取特征。

在python中,嵌入层的初始化,或者说参数设定主要就这些内容:

nn.Embedding(num_embeddings, embedding_dim, padding_idx=padding_idx) #(one-hot字典词汇量的个数,嵌入层的维度。用于padding填充的索引值),或者说,这里的padding_idx=0

1.3.1 one-hot编码的问题

我想想这块具体应该怎么说。具体先从one-hot编码地问题开始介绍起吧。one-hot编码由于只是为字符串数据赋予了一个整型数字,所以数字与数字间,除了能表示对应的字符之外,没有任何含义。也就是我假设单词"Madam"为4,"President"为5,"on"为7,只是多了一个索引值。数字4,与数字5,还有数字7之间,没有任何关系。即使从数字上4和5的距离差为1,会比4和7要小,我们也不能说5代表的单词会比7代表的单词,在语义上,与4代表的单词的含义更加接近一点,或者说更加相关一点。

因为这个整型数字是我们自己定义的,这个数字对应的单词可以是我们设定的任意一个单词。或者说,从模型的角度来讲、从神经网络的预测输出计算损失的角度来看,假设跑出来本来某个位置应该是4的词,但模型预测为7的词,我们也不能够把损失简单的定为(7-4)=3。因为这数字7与4之间的关系,是毫无关系的,计算这样的差距,是无法作为模型损失来考虑的。不过,如果说是像按照多分类问题,一个词作为一个类,然后去整一个很多层的三阶的大张量去整,好像也可以,但太麻烦了,别去往这方面考虑(主要本人是先看了CV相关的内容,所以还从多分类任务去想了想,但好像这也是一种多分类问题的解决的方向,啊啊啊啊,算了,别往下想了,就写成这样吧,毕竟那样就变成了一个可怕到爆炸的稀疏矩阵了)。

这里,就引出了嵌入层应该担当的任务,就是创造一种,能够把不能计算损失的离散数据,变为能计算模型损失的连续的数据。是为了由原来的单个无意义的数字,变成能够代表词语含义之间的差距(或者理解为词义上的相关性)的一个词向量。

1.3.2 这个维度找不到词义间的相关性,就去更高的纬度找吧

最直接的理解,就是低维度找不到的词语间的特征关系,就放到高维度去寻找。这里将一句话映射为one-hot编码后,就得到了一个(1,15)大小的编码矩阵(说明:1代表的是一句话,15代表把原有的句子结果填充词,填充到15个单词量)。这个矩阵我们希望里面的数字与数字之间能够进行加减乘除之类的矩阵运算的方法,但很遗憾,不可以这么做,因为完全没有意义。那么嵌入层就根据原来的one-hot词汇表,再建立了一个二维的大字典矩阵,假设本人希望用128维度的向量来概括一个词的语义,以及假设原来的one-hot词汇表有着1w个词语。那么这个嵌入层的矩阵的大小就是(1w,128),然后one-hot编码是这个矩阵的行号,也就是如果先前的(1,15)大小的编码矩阵,第一个位置上的数据是4,那这步就是去嵌入层的矩阵中取第4行的那128维度的向量,作为第一个位置的词语的代表,也就是这一步之后,原本的(1,15)大小的数据矩阵,就变为了(1,15,128)大小的矩阵,忽略掉1代表第一句话的话,就是将一个一行的10维度的向量,映射为(15,128)的矩阵了。而,这个128维度的词向量数据,则一开始是随机取的内容,为浮点数的数据,我们希望,在这128维度中,每个维度的含义都是等价的,都是文字的一个特征。也就是比如,我们关注这128维度的向量中的第一维度,如果4号词与7号词,在这个第一维度上面,的浮点数做差,那么这个差就能代表,这两个词的词义在第一维度上的词义的差距,以此类推,计算128维度的几何距离后,就能用这个距离值,来表示这两个词的语义差别了。

整个过程的图解如下:

嵌入层

另外,这里0是嵌入层的填充词,设置成0后,后面反向传播的影响就不会对填充词进行权重优化,更加合理。(这图像画的太没水平了吧,作者的制图水平确实堪忧)

这么说可能还是会有点抽象,后续还打算对此,做更加细致的解读。从神经网络的角度来讲,只要写好前向传播,然后设计的层的每一步都能表示某种意义就行了,模型会在不断地训练中,去实现这种意义。也就是虽然一开始嵌入层的大矩阵,是由随机数来映射得到的,但只要我们写好嵌入层的前向传播,就能在训练中,去得到一个能表示词义的嵌入层的大矩阵。

1.3.3 嵌入层的反向传播

这块要将清楚,为什么这样可以这样做,或者说,我们只是在假定这样的嵌入层矩阵,有着词义相关的意义。但实际上,我们还是在用随机数来创建的这样的矩阵。神经网络是如何在一次次训练中,让这样的权重矩阵,优化为能代表一个词的含义的矩阵的。以及还要解释一下,嵌入层矩阵这128维度具体是一个什么概念。

先说下当初作者在理解这一块内容的时候,遇到的一个疑惑,因为之前作者用神经网络来训练的时候,都没接触过这样的数据。最大的特点是,整个模型的实际上的输入,并不是每次训练都不会改变[4,5,6,7,8,9,10,11,12]这样的热词编码。而是嵌入层的数据,将数据映射到128维度,变成按列能进行比较的浮点数的数据,只有这样之后才能进行矩阵运算,这样之后矩阵运算才有着意义。但这样,每次反向传播,似乎之前本人接触过的机器学习模型,都没有会对初始的输入数据,再进行反向传播。也就是这里的模型,反向传播的影响是会传到嵌入层,然后去优化嵌入层的映射矩阵中的参数,或者理解为一种权重。使得嵌入层的映射矩阵,朝着整体损失更小的方向去优化。这样很怪。

或者这么说,之前用机器学习的时候,用的训练集的数据,每次训练后,不会改变原始的输入矩阵,也就是下一次模型训练的输入,还是原来的输入数据。而这里的嵌入层,虽然作为模型的真正的输入,但每代训练后,嵌入层的数据都会不一样,也就是每一代训练之后,原始的数据都会变一次。第一次理解这块内容的时候,本人是完全没有想到,还能这么写模型。每次训练,都会让模型的输入,都朝着整体损失更低的反向去优化。

但冷静分析后,也就理解了这一块的内容。其实还是一种离散数据和连续数据的区别。或者说是这种索引映射的任性所在了。因为如果假设,我们对一种机器学习的算法,也做这样的反向传播,把损失,传播到输入的数据会怎样。例如,假设这里有个Iris数据集(具体的内容忘了,这里随手编数据),有一种花a的花瓣长为4.51cm吧,这里我们模型的反向传播中,正常是不会改变这个4.51的数据项的。但这是做不到吗,并不,如果我们回到反向传播的算式上看,从损失函数的角度来看,权重W矩阵、偏置b矩阵,以及输入X向量之间,又有什么区别呢,每次的训练算损失的时候,都是一个个确定的数,反正梯度就是算偏导数嘛,那就是把其他变量,把一个个W以及b,都当作常数,对输入X去求偏导数。这在数学上完全能算,也就是从计算的角度来看,可以计算得到一个能让模型的整体损失更小的一个全新的输入X,这与优化W以及b的想法是一样的。

但为什么我们不会这么去做呢,因为这样去优化X,完全没有意义,因为下一次,这朵花的数据,进入模型后,花瓣的长度还是4.51,不是反向传播优化后的X,除非我们规定,今后输入如果是4.51,那么模型把这个输入就当作优化后的X,这样子就能得到整体损失更小的数据了。这么一想,整个模型就有意思了起来,我们为了让损失降到最低,设置都把输入的数据都改了。但实际上,我们不会这么做还会因为,这样是一种映射的关系,将一个连续的数据中的一个数据点4.51映射为X,这样的问题是连续的数据是有着无数个数据点,与离散的数据不同,没法非常细致地实现这种一一对应的关系,以此下次模型遇到这种花的时候,输入还是固定的4.51,并不是模型优化后的数据X。这么一看,反向传播把影响传递到输入就毫无意义。

但介绍到这里,就会发现,这种做法确实适合离散的数据。因为离散的数据有的固定的数据量,可以建立索引字典,即使我的模型把输入优化掉了。但由于这是一种映射的关系,下一次模型遇到同一句话,模型可以根据这种索引,去找到优化后的输入X向量。也就是我模型每一代的输入,就是不一样的,不是固定的。而且这个嵌入层的参数,还是朝着整体训练损失减小的方向去优化。这也就是嵌入层概念上的语义的真正的含义。什么叫可以表示语义的权重矩阵,其实就是经过损失减小的优化后的一种权重矩阵。

到这里,嵌入层整体的介绍就结束了,在考虑要不要整一张图上来。233333,感觉描述的可能还是有点怪。再讲一点个人的理解吧。说到那个128维的嵌入层的向量,实际上单看一个128维的数据,是完全没有意义的,我们把这个抽象为词义层面的128个特征数字,这个特征数据存在的意义就是为了和其他词语的128个特征数字进行比较,只有在比较中,才会有着意义,比如求几个词的几何距离差,可以用来表示几个词语在词意上的差距。

还有一个之前写的究极废话版的这块内容的思考,蛮堆砌在这里:


之前一直在疑惑的是,嵌入层是如何反向传播的,因为原本,比如说机器学习啥的,一直处理的数据的反向传播,都是对权重进行的。例如线性回归,假设我的模型为 $$y=wx+b$$ ,损失函数为 $$L=(text{y}_pre-text{y}_real)^2$$ 那么我反向传播的时候,计算的偏导数都只是对w和b计算,从来没有对x计算的。而NLP模型实际上的数据输入,并不是ont-hot编码后的每次都不会改变的“原始”数据,而是嵌入层的数据作为输入。所以我们如果类比一下线性模型和嵌入层,就会有种奇怪的理解,这里的嵌入层的大字典其实就是线性回归里面的输入X。也就是在这里的反向传播中,我们会对输入X求偏导数,这个损失函数是对于W、B还有X的,这看起来很奇怪,但再深入思考后,就能理解了。

从偏导数的角度来讲,我们每次计算的时候,其实就是把其他变量当成常数,然后只有需要求偏导的变量是一个变量,然后求导就行了。那么,这样看,对于损失函数来讲,其实w,b,x都没什么区别,都是一个互相独立的变量,也就是,我们也可以让损失函数对模型的自变量X求偏导,然后对X进行梯度下降。去获得一个让模型的总体损失更小的自变量X的值。这样子从数学的角度来看是可行的(,因为模型的结构是固定的,求偏导都能求,而且每次计算的参数w,x,b都是一个确定的数字,损失也是可以求的一个数)。只不过这样的行为,对于一种线性回归来讲,没什么用。因为我用梯度下降求出来的线性回归的一个优化后的float的x,固然可以使得我原有的X输入进来后,计算出来的整体损失会比之前小,但对于一开始,我们所接触的线性回归而言,这样的优化后的x没有意义,因为我们的输入都是固定的。比如我用鸢尾花数据集来训练,有一个花瓣长度x为1.2,模型对x进行优化后,得出,x为1.2的时候,应该x变为1.0能让整体的损失更小。(具体的数据忘了2333,也懒得查了)但这个是没有意义的,因为下次计算的时候,x的值还是1.2,实际的花瓣不会因为要让损失更小而变成1.0,或者说,当x变为1.0的时候,这朵花也变为另一朵花了,也不是原来的1.2的那一朵了。

但类比到NLP的数据的特点,就会发现,这样的处理方法非常的合适,因为这样可以优化出让模型总体损失更小的x。而我实际的数据是字典的索引值,优化完后,下一次模型的数据,就会由于字典的索引关系不会改变,而是优化后的输入数据。也就是我这样优化,会让我每次的训练的输入数据都不一样,但都会朝着让总体损失越来越小的目标走去。

总结起来看,这样的嵌入层,就是一种离散数据连续化的方法,数据在一个维度内离散,那么就可以用嵌入层的思想,去映射到更高的维度去,让模型自己在更高的维度的几何空间内,找到连续(浮点数)的关系。而我们的线性回归,由于原始数据已经是连续的了,不需要在连续化了,而且也无法用嵌入层的思想去映射到更高的维度(,因为连续的数据有无穷个,无法穷尽)。

嵌入层主要为了去找到离散数据之间的关联性,连续的数据自己之间,已经存在这种关联了,这是离散数据和连续数据的本质的区别,理解了这个,也就对数据科学本身有了更多的了解。


最后,你可能会好奇的这块的公式表达,就写在下方了,这个描述起来就很优雅,不会像前面写的那么废话连篇。

Embedding层正向传播的过程:假设输入为大小为(batch_size, seq_length),元素均为词汇表中的某个词的索引,输出为大小为(batch_size, seq_length, embedding_dim)的张量,元素均为对应的词向量。那么正向传播的公式为:

$$\mathbf{X}{\text{out}} = \text{Embedding}(\mathbf{X}{\text{in}})$$

$$\mathbf{X}{\text{out}{i,j}} = \mathbf{W}{\mathbf{X}{\text{in}_{i,j}}}$$

其中,$\mathbf{X}{\text{in}}$是输入张量, $\mathbf{X}{\text{out}}$是输出张量, $\mathbf{W}$是Embedding层的参数矩阵, $\mathbf{X}{\text{in}{i,j}}$和$\mathbf{X}{\text{out}{i,j}}$分别表示输入张量和输出张量中第$i$个样本、第$j$个位置的值。

Embedding层反向传播的过程:Embedding层本质上是一种字典映射方法,需要保证不同词之间的梯度不会相互影响。具体来说,反向传播的时候,我们输入的梯度张量是与输出张量相同维度的张量,对于输入张量中的每个元素,输出张量中对应位置的词向量都需要乘以这个梯度值。同时,所有词向量的梯度需要进行累加,作为参数矩阵$\mathbf{W}$的梯度值。那么反向传播的公式为:

$$\frac{\partial L}{\partial \mathbf{X}{\text{in}{i,j}}} = \frac{\partial L}{\partial \mathbf{X}{\text{out}{i,j}}}\cdot \mathbf{W}{\mathbf{X}{\text{in}{i,j}}}\quad \left(\frac{\partial L}{\partial \mathbf{X}{\text{out}}} \text{已知}\right)\ \frac{\partial L}{\partial \mathbf{W}{k}} = \sum{i=1}^{batch_size}\sum_{j=1}^{seq_length}\frac{\partial L}{\partial \mathbf{X}{\text{out}{i,j}}}\cdot\mathbf{1}{[\mathbf{X}{\text{in}{i,j}}=k]}\quad \left(\mathbf{1}{[\mathbf{X}{\text{in}{i,j}}=k]} \text{为1当且仅当} \mathbf{X}{\text{in}{i,j}}=k\right)$$

其中,$L$是损失函数,$\frac{\partial L}{\partial \mathbf{X}{\text{in}{i,j}}}$、$\frac{\partial L}{\partial \mathbf{X}{\text{out}{i,j}}}$、$\mathbf{X}{\text{in}{i,j}}$、$\mathbf{X}{\text{out}{i,j}}$和$\mathbf{W}{\mathbf{X}{\text{in}_{i,j}}}$的含义同上。

好了,一个自然语言处理的理解难点,嵌入层就算结束了,进入下一块内容。

1.4自然语言的特点以及位置编码的内容

自然语言的概念感觉不太好去界定,因为我们日常所使用的,所接触的都是自然语言。只要明白与之区别的是机器语言就行了,将其理解为与代码、程序这类的一板一眼的命令、指令这样的机器语言所区别的语言信息即可。要说到自然语言的特点的话,大概就是没有非常具体的使用规则,表达一件事,可以有很多种语句可以用来表述。你可以随意地去组织语言,听的人不会因为你换了一种表述,就不理解你说的话的含义了,而且还能有着隐喻、暗示等高级用法。而机器语言,则必须要一行行去写清楚,中间哪里写得有问题还会不断报错出bug。

自然语言的数据,一个语句不仅有着词义信息,可以作为理解一个语句的内在信息的关键特征,词语所在的位置信息,也可以表述一个语句的信息。同样的词,如果位置变了,整句话的意思也会改变。(懒得找例子了。)这里,transformer原作者就设计了一种强调词语位置信息的方法,用一种位置信息矩阵的方式,来表达一个词语的位置信息。这是个静态矩阵,与嵌入层矩阵随着训练变化,一直会改变词向量本身不同,这个位置矩阵一开始就是固定的。其发挥作用的方法,就是在词编码经过嵌入层矩阵映射得到128维的词义特征向量后,到位置矩阵中,取出对应位置的128维度的特征向量,直接与词义特征相加,这样就得到了既有词义信息,又有位置信息的128维的特征向量。

由图像来看就是这家伙:

About

对“A PyTorch implementation of Transformer in "Attention is All You Need"”的代码的详解,以及transformer模型的自己的理解。记录下自己学习语言模型的全过程。包括了NLP中理解比较困难的内容吧。包括嵌入层、位置信息的理解,都记录在里面了

Resources

License

Stars

Watchers

Forks

Releases

No releases published

Packages

No packages published

Languages

  • Jupyter Notebook 95.6%
  • Python 4.4%