Transformer 修炼之道(一)、Input Embedding

Attention is All You Need

Transformer 整体架构

Date: 2020/06/12

Author: CW

前言:

Transformer 不是 NLP 的东西么,搞 CV 的为啥要去学它?  

                                                       Maybe,众多CVer会问

是的,Transformer 诞生并广泛应用于NLP领域,如今可说是NLP的一哥,但如果你认为它只属于NLP领域,和CV拉不上关系,那眼界可能就有点窄了。你看,最近 FAIR(Facebook AI Research) 新鲜出炉的 DETR(End-to-End Object Detection with Transformers)不正是用到了 Transformer 么,并且,可以说啥都没用(没有那些古怪骚气的tricks),就用了Transformer(当然,特征提取部分还是用 CNN),所以说,Transformer 这东西确实好使,在CV界它也可以一样work!

这也是为什么我打算把Transformer好好学习与总结一番的原因,首先,它已成为一种趋势,更重要的是,它不仅仅是一种“器”,而是一种“术”,并向着“道”发展,“器”仅局限于本身领域;而“术”则成为了一种通用体系,能广泛应用于不同领域;至于“道”嘛,就不谈了,因为其太过强大!可道之“道”,乃非常道,谈不得..

做学问一定要把眼界放宽,不能局限死,世界本无学科界限,各领域成功背后的本质都是一样的,是一条通路,要学会以一贯穿,在生活中学会细心观察和认真思考,以培养自身的悟性,说不定哪天你在看爱情小说的时候就悟出个SOTA了。因此,要用心地活,才能将自己活出价值来!

Transformer 的整体架构不会太复杂,但我还是决定分几部分来剖析,这样条理比较清晰,也比较好消化,一次性解析完难免会“太饱”,吃不下吐出来也是浪费,同时又伤身..

本文将针对Transformer输入部分的操作进行解析与总结,会结合代码来讲,只有结合了代码才比较“务实”,不然我总感觉很空洞不踏实。


Outline

I. One-Hot Encoding

II. Word Embedding

III. Position Embedding


One-Hot Encoding

在CV中,我们通常将输入图片转换为4维(batch, channel, height, weight)张量来表示;而在NLP中,可以将输入单词用 One-Hot 形式编码成序列向量,向量长度就是预定义的词汇表中拥有的单词量,向量在这一维中的值只有一个位置是1,其余都是0,1对应的位置就是词汇表中表示这个单词的地方。

例如词汇表中有5个词,第3个词表示“你好”这个词,那么该词对应的 one-hot 编码即为 00100(第3个位置为1,其余为0


One-Hot Encoding

代码实现起来也比较简单:

one-hot 实现

Word Embedding

One-Hot 的形式看上去很简洁,也挺美,但劣势在于它很稀疏,而且还可能很长。比如词汇表如果有10k个词,那么一个词向量的长度就需要达到10k,而其中却仅有一个位置是1,其余全是0,太“浪费”!

更重要的是,这种方式无法体现出词与词之间的关系,比如 “爱” 和 “喜欢” 这两个词,它们的意思是相近的,但基于 one-hot 编码后的结果取决于它们在词汇表中的位置,无法体现出它们之间的关系。

因此,我们需要另一种词的表示方法,能够体现词与词之间的关系,使得意思相近的词有相近的表示结果,这种方法即 Word Embedding。

那么应该如何设计这种方法呢?最方便的途径是设计一个可学习的权重矩阵W,将词向量与这个矩阵进行点乘,即得到新的表示结果。嗯?这么简单?是的。为何能work?举个例子来看吧!

假设 “爱” 和 “喜欢” 这两个词经过 one-hot 后分别表示为 10000 和 00001,权重矩阵设计如下:

[ w00, w01, w02

  w10, w11, w12

  w20, w21, w22

  w30, w31, w32

  w40, w41, w42 ]

那么两个词点乘后的结果分别是 [w00, w01, w02] 和 [w40, w41, w42],在网络学习过程中(这两个词后面通常都是接主语,如“你”,“他”等,或者在翻译场景,它们被翻译的目标意思也相近,它们要学习的目标一致或相近),权重矩阵的参数会不断进行更新,从而使得 [w00, w01, w02] 和 [w40, w41, w42] 的值越来越接近。

另一方面,对于以上这个例子,我们还把向量的维度从5维压缩到了3维,因此,word embedding 还可以起到降维的效果。

其实,可以将这种方式看作是一个 lookup table,对于每个 word,进行 word embedding 就相当于一个lookup操作,查出一个对应结果。

在pytorch中,可以使用 torch.nn.Embedding 来实现 word embedding:

class Embeddings(nn.Module):

    def __init__(self, d_model, vocab):

        super(Embeddings, self).__init__()

        self.lut = nn.Embedding(vocab, d_model)

        self.d_model = d_model

    def forward(self, x):

        return self.lut(x) * math.sqrt(self.d_model)

其中,vocab 代表词汇表中的单词量,one-hot 编码后词向量的长度就是这个值;d_model代表权重矩阵的列数,通常为512,就是要将词向量的维度从vocab转换到d_model。


Position Embedding

经过 word embedding,我们获得了词与词之间关系的表达形式,但是词在句子中的位置关系还无法体现,由于 Transformer 是并行地处理句子中的所有词,于是需要加入词在句子中的位置信息,结合了这种方式的词嵌入就是 Position Embedding 了。

那么具体该怎么做?我们通常容易想到两种方式:

1、通过网络来学习;

2、预定义一个函数,通过函数计算出位置信息;

Transformer 的作者对以上两种方式都做了探究,发现最终效果相当,于是采用了第2种方式,从而减少模型参数量,同时还能适应即使在训练集中没有出现过的句子长度。计算位置信息的函数计算公式如下:


Position Embedding 计算公式

pos代表的是词在句子中的位置,d是词向量的维度(通常经过word embedding后是512),2i代表的是d中的偶数维度,2i + 1则代表的是奇数维度,这种计算方式使得每一维都对应一个正弦曲线。


position embedding 每一维度的曲线


为何使用三角函数呢?

由于三角函数的性质: sin(a+b) = sin(a)cos(b) + cos(a)sin(b)、 cos(a+b) = cos(a)Cos(b) - sin(a)sin(b),于是,对于位置 pos+k 处的信息,可以由 pos 位置计算得到,作者认为这样可以让模型更容易地学习到位置信息。

为何使用这种方式编码能够代表不同位置信息呢?

由公式可知,每一维i都对应不同周期的正余弦曲线i=0时是周期为2\pi \sin 函数,i=1时是周期为2\pi \cos 函数..对于不同的两个位置pos1和pos2,若它们在某一维i上有相同的编码值,则说明这两个位置的差值等于该维所在曲线的周期,即\vert pos1-pos2 \vert =T_{i} 。而对于另一个维度j(j\neq i),由于T_{j}\neq T_{i}  ,因此pos1和pos2在这个维度j上的编码值就不会相等,对于其它任意k\in \left\{ 0,1,2,..,d-1 \right\};k\neq i 也是如此。

综上可知,这种编码方式保证了不同位置在所有维度上不会被编码到完全一样的值,从而使每个位置都获得独一无二的编码。

pytorch代码实现如下:

class PositionalEncoding(nn.Module):

    def __init__(self, d_model, dropout, max_len=5000):

        super(PositionalEncoding, self).__init__()

        self.dropout = nn.Dropout(p=dropout)  

        pe = torch.zeros(max_len, d_model)  # max_len代表句子中最多有几个词

        position = torch.arange(0, max_len).unsqueeze(1)

        div_term = torch.exp(torch.arange(0, d_model, 2) *

                            -(math.log(10000.0) / d_model))  # d_model即公式中的d

        pe[:, 0::2] = torch.sin(position * div_term)

        pe[:, 1::2] = torch.cos(position * div_term)

        pe = pe.unsqueeze(0)

        self.register_buffer('pe', pe)


    def forward(self, x):

        x = x + self.pe[:, :x.size(1)]  # 原向量加上计算出的位置信息才是最终的embedding

        return self.dropout(x)


实现过程中需要注意的一个细节是 —— self.register_buffer('pe', pe) 这句,它的作用是将pe变量注册到模型的buffers()属性中,这代表该变量对应的是一个持久态,不会有梯度传播给它,但是能被模型的state_dict记录下来。

注意,没有保存到模型的buffers()或parameters()属性中的参数是不会被记录到state_dict中的,在buffers()中的参数默认不会有梯度,parameters()中的则相反。

通过代码可以看到,position encoding是直接加在输入上的,那么为何是相加而非concat(拼接)呢?concat的形式不是更能独立体现出位置信息吗?而相加的话都把位置信息混入到原输入中了,貌似“摸不着也看不清”..

这是因为Transformer通常会对原始输入作一个嵌入(embedding),映射到需要的维度,可采用一个变换矩阵作矩阵乘积的方式来实现,上述代码中的输入x其实就是已经变换后的表示(而非原输入)。OK,了解了这一点,我们尝试使用concat的方式加入位置编码:

给每一个位置x^i \in R^{(d,1)}concat上一个代表位置信息的one-hot向量p^i \in R^{(N,1)}(N代表共有N个位置)形成x_{p}^i \in R^{(d+N,1)},它也可以表示为[[x^i]^T,[x^p]^T ]^T这个形式。

接着对这个新形成的向量作线性变换,记变换矩阵W\in R^{(d,d+N)},d就是需要嵌入到的维度(这里为了简便,直接假设原输入的维度与嵌入维度一致,都是d),它也可以表示为[W^I, W^P ] ,其中W^I \in R^{(d,d)}W^P \in R^{(d,N)}。现在进行变换:

W \cdot  x_{p}^i = [W^I, W^P ] \cdot [[x^i]^T, [x^p]^T ]^T = W^I \cdot x^i + W^P \cdot x^p = embed^i + pos^i

由变换结果可知,在原输入上concat一个代表位置信息的向量在经过线性变换后等同于将原输入经线性变换后直接加上位置编码信息。

最后举个例子,Transformer 对输入的操作概括为如下:


Input Embedding

End

至此,我们完成了对输入嵌入的解析,对应于整体架构的部分如下图所示:


Transformer


下一篇文我将会对 Encoder 部分作解析,这应该是内容最多的一part,Transformer 的核心操作几乎都被涵盖在其中,Decoder 结构与Encoder 类似,后者悟通了,前者也就easy了,see u~

最后编辑于
©著作权归作者所有,转载或内容合作请联系作者
禁止转载,如需转载请通过简信或评论联系作者。
  • 序言:七十年代末,一起剥皮案震惊了整个滨河市,随后出现的几起案子,更是在滨河造成了极大的恐慌,老刑警刘岩,带你破解...
    沈念sama阅读 216,402评论 6 499
  • 序言:滨河连续发生了三起死亡事件,死亡现场离奇诡异,居然都是意外死亡,警方通过查阅死者的电脑和手机,发现死者居然都...
    沈念sama阅读 92,377评论 3 392
  • 文/潘晓璐 我一进店门,熙熙楼的掌柜王于贵愁眉苦脸地迎上来,“玉大人,你说我怎么就摊上这事。” “怎么了?”我有些...
    开封第一讲书人阅读 162,483评论 0 353
  • 文/不坏的土叔 我叫张陵,是天一观的道长。 经常有香客问我,道长,这世上最难降的妖魔是什么? 我笑而不...
    开封第一讲书人阅读 58,165评论 1 292
  • 正文 为了忘掉前任,我火速办了婚礼,结果婚礼上,老公的妹妹穿的比我还像新娘。我一直安慰自己,他们只是感情好,可当我...
    茶点故事阅读 67,176评论 6 388
  • 文/花漫 我一把揭开白布。 她就那样静静地躺着,像睡着了一般。 火红的嫁衣衬着肌肤如雪。 梳的纹丝不乱的头发上,一...
    开封第一讲书人阅读 51,146评论 1 297
  • 那天,我揣着相机与录音,去河边找鬼。 笑死,一个胖子当着我的面吹牛,可吹牛的内容都是我干的。 我是一名探鬼主播,决...
    沈念sama阅读 40,032评论 3 417
  • 文/苍兰香墨 我猛地睁开眼,长吁一口气:“原来是场噩梦啊……” “哼!你这毒妇竟也来了?” 一声冷哼从身侧响起,我...
    开封第一讲书人阅读 38,896评论 0 274
  • 序言:老挝万荣一对情侣失踪,失踪者是张志新(化名)和其女友刘颖,没想到半个月后,有当地人在树林里发现了一具尸体,经...
    沈念sama阅读 45,311评论 1 310
  • 正文 独居荒郊野岭守林人离奇死亡,尸身上长有42处带血的脓包…… 初始之章·张勋 以下内容为张勋视角 年9月15日...
    茶点故事阅读 37,536评论 2 332
  • 正文 我和宋清朗相恋三年,在试婚纱的时候发现自己被绿了。 大学时的朋友给我发了我未婚夫和他白月光在一起吃饭的照片。...
    茶点故事阅读 39,696评论 1 348
  • 序言:一个原本活蹦乱跳的男人离奇死亡,死状恐怖,灵堂内的尸体忽然破棺而出,到底是诈尸还是另有隐情,我是刑警宁泽,带...
    沈念sama阅读 35,413评论 5 343
  • 正文 年R本政府宣布,位于F岛的核电站,受9级特大地震影响,放射性物质发生泄漏。R本人自食恶果不足惜,却给世界环境...
    茶点故事阅读 41,008评论 3 325
  • 文/蒙蒙 一、第九天 我趴在偏房一处隐蔽的房顶上张望。 院中可真热闹,春花似锦、人声如沸。这庄子的主人今日做“春日...
    开封第一讲书人阅读 31,659评论 0 22
  • 文/苍兰香墨 我抬头看了看天上的太阳。三九已至,却和暖如春,着一层夹袄步出监牢的瞬间,已是汗流浃背。 一阵脚步声响...
    开封第一讲书人阅读 32,815评论 1 269
  • 我被黑心中介骗来泰国打工, 没想到刚下飞机就差点儿被人妖公主榨干…… 1. 我叫王不留,地道东北人。 一个月前我还...
    沈念sama阅读 47,698评论 2 368
  • 正文 我出身青楼,却偏偏与公主长得像,于是被迫代替她去往敌国和亲。 传闻我的和亲对象是个残疾皇子,可洞房花烛夜当晚...
    茶点故事阅读 44,592评论 2 353

推荐阅读更多精彩内容