<kbd id="afajh"><form id="afajh"></form></kbd>
<strong id="afajh"><dl id="afajh"></dl></strong>
    <del id="afajh"><form id="afajh"></form></del>
        1. <th id="afajh"><progress id="afajh"></progress></th>
          <b id="afajh"><abbr id="afajh"></abbr></b>
          <th id="afajh"><progress id="afajh"></progress></th>

          從源碼學(xué)習(xí)Transformer!

          共 15013字,需瀏覽 31分鐘

           ·

          2020-11-11 14:56

          AI編輯:我是小將

          本文作者:謝楊易

          https://zhuanlan.zhihu.com/p/178610196

          本文已由原作者授權(quán),不得擅自二次轉(zhuǎn)載

          Transformer總體結(jié)構(gòu)

          ? ? ? ?近幾年NLP領(lǐng)域有了突飛猛進(jìn)的發(fā)展,預(yù)訓(xùn)練模型功不可沒(méi)。當(dāng)前利用預(yù)訓(xùn)練模型(pretrain models)在下游任務(wù)中進(jìn)行fine-tune,已經(jīng)成為了大部分NLP任務(wù)的固定范式。Transformer摒棄了RNN的序列結(jié)構(gòu),完全采用attention和全連接,嚴(yán)格來(lái)說(shuō)不屬于預(yù)訓(xùn)練模型。但它卻是當(dāng)前幾乎所有pretrain models的基本結(jié)構(gòu),為pretrain models打下了堅(jiān)實(shí)的基礎(chǔ),并逐步發(fā)展出了transformer-XL,reformer等優(yōu)化架構(gòu)。本文結(jié)合論文和源碼,對(duì)transformer基本結(jié)構(gòu),進(jìn)行詳細(xì)分析。

          Transformer是谷歌在2017年6月提出,發(fā)表在NIPS2017上。論文地址

          Attention Is All You Needarxiv.org

          分析的代碼為Harvardnlp的代碼,基于PyTorch, 地址

          annotated-transformergithub.com

          Transformer主體框架是一個(gè)encoder-decoder結(jié)構(gòu),去掉了RNN序列結(jié)構(gòu),完全基于attention和全連接。在WMT2014英語(yǔ)翻譯德語(yǔ)任務(wù)上,bleu值達(dá)到了28.4,達(dá)到當(dāng)時(shí)的SOTA。其總體結(jié)構(gòu)如下所示

          總體為一個(gè)典型的encoder-decoder結(jié)構(gòu)。代碼如下

          # 整個(gè)模型入口
          def make_model(src_vocab, tgt_vocab, N=6,
          d_model=512, d_ff=2048, h=8, dropout=0.1):
          "Helper: Construct a model from hyperparameters."
          c = copy.deepcopy

          # multiHead attention
          attn = MultiHeadedAttention(h, d_model)

          # feed-forward
          ff = PositionwiseFeedForward(d_model, d_ff, dropout)

          # position-encoding
          position = PositionalEncoding(d_model, dropout)

          # 整體為一個(gè)encoder-decoder
          model = EncoderDecoder(
          # encoder編碼層
          Encoder(EncoderLayer(d_model, c(attn), c(ff), dropout), N),

          # decoder解碼層
          Decoder(DecoderLayer(d_model, c(attn), c(attn), c(ff), dropout), N),

          # 編碼層輸入,輸入語(yǔ)句進(jìn)行token embedding和position embedding
          nn.Sequential(Embeddings(d_model, src_vocab), c(position)),

          # 解碼層輸入,同樣需要做token embedding和position embedding
          nn.Sequential(Embeddings(d_model, tgt_vocab), c(position)),

          # linear + softmax,查找vocab中概率最大的字
          Generator(d_model, tgt_vocab))

          # This was important from their code.
          # Initialize parameters with Glorot / fan_avg.
          for p in model.parameters():
          if p.dim() &gt; 1:
          nn.init.xavier_uniform(p)
          return model

          make_model為Transformer模型定義的入口,它先定義了multi-head attention、feed-forward、position-encoding等一系列子模塊,然后定義了一個(gè)encoder-decoder結(jié)構(gòu)并返回。下面來(lái)看encoder-decoder定義。

          class EncoderDecoder(nn.Module):
          """
          一個(gè)標(biāo)準(zhǔn)的encoder和decoder框架,可以自定義embedding、encoder、decoder等
          """
          def __init__(self, encoder, decoder, src_embed, tgt_embed, generator):
          super(EncoderDecoder, self).__init__()

          # encoder和decoder通過(guò)構(gòu)造函數(shù)傳入,可靈活更改
          self.encoder = encoder
          self.decoder = decoder

          # src和target的embedding,也是通過(guò)構(gòu)造函數(shù)傳入,方便靈活更改
          self.src_embed = src_embed
          self.tgt_embed = tgt_embed

          # linear + softmax
          self.generator = generator

          def forward(self, src, tgt, src_mask, tgt_mask):
          "Take in and process masked src and target sequences."
          # 先對(duì)輸入進(jìn)行encode,然后再通過(guò)decode輸出
          return self.decode(self.encode(src, src_mask), src_mask,
          tgt, tgt_mask)

          def encode(self, src, src_mask):
          # 先對(duì)輸入進(jìn)行embedding,然后再經(jīng)過(guò)encoder
          return self.encoder(self.src_embed(src), src_mask)

          def decode(self, memory, src_mask, tgt, tgt_mask):
          # 先對(duì)目標(biāo)進(jìn)行embedding,然后經(jīng)過(guò)decoder
          return self.decoder(self.tgt_embed(tgt), memory, src_mask, tgt_mask)

          encoder-decoder定義了一個(gè)標(biāo)準(zhǔn)的編碼解碼框架,其中編碼器、解碼器均可以自定義,有很強(qiáng)的泛化能力。模塊運(yùn)行時(shí)會(huì)調(diào)用forward函數(shù),它先對(duì)輸入進(jìn)行encode,然后再通過(guò)decode輸出。我們就不詳細(xì)展開了。

          2 encoder

          2.1 encoder定義

          encoder分為兩部分

          1. 輸入層embedding。輸入層對(duì)inputs文本做token embedding,并對(duì)每個(gè)字做position encoding,然后疊加在一起,作為最終的輸入。

          2. 編碼層encoding。編碼層是多層結(jié)構(gòu)相同的layer堆疊而成。每個(gè)layer又包括兩部分,multi-head self-attention和feed-forward全連接,并在每部分加入了殘差連接和歸一化。

          代碼實(shí)現(xiàn)上也驗(yàn)證了這一點(diǎn)。我們看EncoderDecoder類中的encode函數(shù),它先利用輸入embedding層對(duì)原始輸入進(jìn)行embedding,然后再通過(guò)編碼層進(jìn)行encoding。

          class EncoderDecoder(nn.Module):
          def encode(self, src, src_mask):
          # 先對(duì)輸入進(jìn)行embedding,然后再經(jīng)過(guò)encoder
          return self.encoder(self.src_embed(src), src_mask)

          2.2 輸入層embedding

          原始文本經(jīng)過(guò)embedding層進(jìn)行向量化,它包括token embedding和position embedding兩層。

          2.2.1 token embedding

          token embedding對(duì)文本進(jìn)行向量化,一般來(lái)說(shuō)有兩種方式

          1. 采用固定詞向量,比如利用Word2vec預(yù)先訓(xùn)練好的。這種方式是LSTM時(shí)代常用的方式,比較簡(jiǎn)單省事,無(wú)需訓(xùn)練。但由于詞向量是固定的,不能解決一詞多義的問(wèn)題,詞語(yǔ)本身也不是contextual的,沒(méi)有結(jié)合上下文語(yǔ)境信息,另外對(duì)于不在詞向量中的詞語(yǔ),比如特定領(lǐng)域詞語(yǔ)或者新詞,容易出現(xiàn)OOV問(wèn)題。

          2. 隨機(jī)初始化,然后訓(xùn)練。這種方式比較麻煩,需要大規(guī)模訓(xùn)練語(yǔ)料,但能解決固定詞向量的一系列問(wèn)題。Transformer采用了這種方式。

          另外,基于Transformer的BERT模型在中文處理時(shí),直接基于字做embedding,優(yōu)點(diǎn)有

          1. 無(wú)需分詞,故不會(huì)引入分詞誤差。事實(shí)上,只要訓(xùn)練語(yǔ)料充分,模型自然就可以學(xué)到分詞信息了。

          2. 中文字個(gè)數(shù)固定,不會(huì)導(dǎo)致OOV問(wèn)題

          3. 中文字相對(duì)詞,數(shù)量少很多,embedding層參數(shù)大大縮小,減小了模型體積,并加快了訓(xùn)練速度。

          事實(shí)上,就算在LSTM時(shí)代,很多case中,我們也碰到過(guò)基于字的embedding的效果比基于詞的要好一些。

          class Embeddings(nn.Module):
          # token embedding,隨機(jī)初始化訓(xùn)練,然后查表找到每個(gè)字的embedding
          def __init__(self, d_model, vocab):
          super(Embeddings, self).__init__()
          # 構(gòu)建一個(gè)隨機(jī)初始化的詞向量表,[vocab_size, d_model]。bert中的設(shè)置為[21128, 768]
          self.lut = nn.Embedding(vocab, d_model)
          self.d_model = d_model

          def forward(self, x):
          # 從詞向量表中查找字對(duì)應(yīng)的embedding向量
          return self.lut(x) * math.sqrt(self.d_model)

          由代碼可見,Transformer采用的是隨機(jī)初始化,然后訓(xùn)練的方式。詞向量維度為[vocab_size, d_model]。例如BERT中為[21128, 768],參數(shù)量還是很大的。ALBert針對(duì)embedding層進(jìn)行矩陣分解,大大減小了embedding層體積。

          2.2.2 position encoding

          首先一個(gè)問(wèn)題,為啥要進(jìn)行位置編碼呢。原因在于self-attention,將任意兩個(gè)字之間距離縮小為1,丟失了字的位置信息,故我們需要加上這一信息。我們也可以想到兩種方法

          1. 固定編碼。Transformer采用了這一方式,通過(guò)奇數(shù)列cos函數(shù),偶數(shù)列sin函數(shù)方式,利用三角函數(shù)對(duì)位置進(jìn)行固定編碼。

          2. 動(dòng)態(tài)訓(xùn)練。BERT采用了這種方式。先隨機(jī)初始化一個(gè)embedding table,然后訓(xùn)練得到table 參數(shù)值。predict時(shí)通過(guò)embedding_lookup找到每個(gè)位置的embedding。這種方式和token embedding類似。

          哪一種方法好呢?個(gè)人以為各有利弊

          1. 固定編碼方式簡(jiǎn)潔,不需要訓(xùn)練。且不受embedding table維度影響,理論上可以支持任意長(zhǎng)度文本。(但要盡量避免預(yù)測(cè)文本很長(zhǎng),但訓(xùn)練集文本較短的case)

          2. 動(dòng)態(tài)訓(xùn)練方式,在語(yǔ)料比較大時(shí),準(zhǔn)確度比較好。但需要訓(xùn)練,且最致命的是,限制了輸入文本長(zhǎng)度。當(dāng)文本長(zhǎng)度大于position embedding table維度時(shí),超出的position無(wú)法查表得到embedding(可以理解為OOV了)。這也是為什么BERT模型文本長(zhǎng)度最大512的原因。

          class PositionalEncoding(nn.Module):
          # 位置編碼。transformer利用編碼方式實(shí)現(xiàn),無(wú)需訓(xùn)練。bert則采用訓(xùn)練embedding_lookup方式
          # 編碼方式文本語(yǔ)句長(zhǎng)度不受限,但準(zhǔn)確度不高
          # 訓(xùn)練方式文本長(zhǎng)度會(huì)受position維度限制(這也是為什么bert只能處理最大512個(gè)字原因),但訓(xùn)練數(shù)據(jù)多時(shí),準(zhǔn)確率高
          def __init__(self, d_model, dropout, max_len=5000):
          super(PositionalEncoding, self).__init__()
          self.dropout = nn.Dropout(p=dropout)

          # 采用sin和cos進(jìn)行position encoding
          pe = torch.zeros(max_len, d_model)
          position = torch.arange(0, max_len).unsqueeze(1)
          div_term = torch.exp(torch.arange(0, d_model, 2) *
          -(math.log(10000.0) / d_model))
          pe[:, 0::2] = torch.sin(position * div_term) # 偶數(shù)列
          pe[:, 1::2] = torch.cos(position * div_term) # 奇數(shù)列
          pe = pe.unsqueeze(0)
          self.register_buffer('pe', pe)

          def forward(self, x):
          # token embedding和position encoding加在一起
          x = x + Variable(self.pe[:, :x.size(1)],
          requires_grad=False)
          return self.dropout(x)

          由代碼可見,position encoding直接采用了三角函數(shù)。對(duì)偶數(shù)列采用sin,奇數(shù)列采用cos。

          2.3 編碼層

          Encoder層是Transformer的核心,它由N層相同結(jié)構(gòu)的layer(默認(rèn)6層)堆疊而成。

          class Encoder(nn.Module):
          "Core encoder is a stack of N layers"
          def __init__(self, layer, N):
          super(Encoder, self).__init__()
          # N層堆疊而成,每一層結(jié)構(gòu)都是相同的,訓(xùn)練參數(shù)不同
          self.layers = clones(layer, N)

          # layer normalization
          self.norm = LayerNorm(layer.size)

          def forward(self, x, mask):
          # 1 經(jīng)過(guò)N層堆疊的multi-head attention + feed-forward
          for layer in self.layers:
          x = layer(x, mask)

          # 2 對(duì)encoder最終輸出結(jié)果進(jìn)行l(wèi)ayer-norm歸一化。層間和層內(nèi)子模塊都做過(guò) add + dropout + layer-norm
          return self.norm(x)

          encoder的定義很簡(jiǎn)潔。先經(jīng)過(guò)N層相同結(jié)構(gòu)的layer,然后再進(jìn)行歸一化輸出。重點(diǎn)我們來(lái)看layer的定義。

          class EncoderLayer(nn.Module):
          "Encoder is made up of self-attn and feed forward (defined below)"
          def __init__(self, size, self_attn, feed_forward, dropout):
          super(EncoderLayer, self).__init__()
          # 1 self_attention
          self.self_attn = self_attn

          # 2 feed_forward
          self.feed_forward = feed_forward

          # 3 殘差連接。encoder和decoder,每層結(jié)構(gòu),每個(gè)子結(jié)構(gòu),都有殘差連接。
          # add + drop-out + layer-norm
          self.sublayer = clones(SublayerConnection(size, dropout), 2)
          self.size = size

          def forward(self, x, mask):
          # 經(jīng)過(guò)self_attention, 然后和輸入進(jìn)行add + layer-norm
          x = self.sublayer[0](x, lambda x: self.self_attn(x, x, x, mask))

          # 經(jīng)過(guò)feed_forward, 此模塊也有add + layer-norm
          return self.sublayer[1](x, self.feed_forward)

          encoder layer分為兩個(gè)子模塊

          1. self attention, 并對(duì)輸入attention前的和經(jīng)過(guò)attention輸出的,做殘差連接。殘差連接先經(jīng)過(guò)layer-norm歸一化,然后進(jìn)行dropout,最后再做add。后面我們?cè)敿?xì)分析

          2. feed-forward全連接,也有殘差連接的存在,方式和self attention相同。

          2.3.1 MultiHeadedAttention

          MultiHeadedAttention采用多頭self-attention。它先將隱向量切分為h個(gè)頭,然后每個(gè)頭內(nèi)部進(jìn)行self-attention計(jì)算,最后再concat再一起。

          代碼如下

          class MultiHeadedAttention(nn.Module):
          def __init__(self, h, d_model, dropout=0.1):
          super(MultiHeadedAttention, self).__init__()
          assert d_model % h == 0
          # d_model為隱層維度,也是embedding的維度,h為多頭個(gè)數(shù)。
          # d_k為每個(gè)頭的隱層維度,要除以多頭個(gè)數(shù)。也就是加入了多頭,總隱層維度不變。
          self.d_k = d_model // h
          self.h = h

          # 線性連接
          self.linears = clones(nn.Linear(d_model, d_model), 4)
          self.attn = None
          self.dropout = nn.Dropout(p=dropout)

          def forward(self, query, key, value, mask=None):
          if mask is not None:
          # 輸入mask,在decoder的時(shí)候有用到。decode時(shí)不能看到要生成字之后的字,所以需要mask
          mask = mask.unsqueeze(1)
          nbatches = query.size(0)

          # 1) q, k, v形狀變化,加入多頭, [batch, L, d_model] => [batch, h, L, d_model/h]
          query, key, value = [l(x).view(nbatches, -1, self.h, self.d_k).transpose(1, 2)
          for l, x in zip(self.linears, (query, key, value))]

          # 2) attention計(jì)算
          x, self.attn = attention(query, key, value, mask=mask,
          dropout=self.dropout)

          # 3) 多頭結(jié)果concat在一起,還原為初始形狀
          x = x.transpose(1, 2).contiguous().view(nbatches, -1, self.h * self.d_k)

          # 4)最后經(jīng)過(guò)一個(gè)線性層
          return self.linears[-1](x)

          下面重點(diǎn)來(lái)看單個(gè)頭的self-attention。也就是論文中的“Scaled Dot-Product Attention”。attention本質(zhì)上是一個(gè)向量的加權(quán)求和。它探討的是每個(gè)位置對(duì)當(dāng)前位置的貢獻(xiàn)。步驟如下

          1. q向量和每個(gè)位置的k向量計(jì)算點(diǎn)積,然后除以向量長(zhǎng)度的根號(hào)。計(jì)算點(diǎn)積可以認(rèn)為是進(jìn)行權(quán)重計(jì)算。除以向量長(zhǎng)度原因是向量越長(zhǎng),q*k值理論上會(huì)越大,故需要在向量長(zhǎng)度上做歸一化。

          2. attention-mask。mask和輸入矩陣shape相同,mask矩陣中值為0位置對(duì)應(yīng)的輸入矩陣的值更改為-1e9,一個(gè)非常非常小的數(shù),經(jīng)過(guò)softmax后趨近于0。decoder中使用了mask,后面我們?cè)敿?xì)分析。

          3. softmax歸一化,使得q向量和每個(gè)位置的k向量的score分布到(0, 1)之間

          4. 加權(quán)系數(shù)乘以每個(gè)位置v向量,然后加起來(lái)。

          公式如下:

          代碼如下

          def attention(query, key, value, mask=None, dropout=None):
          # attention計(jì)算,self_attention和soft-attention都是使用這個(gè)函數(shù)
          # self-attention, q k v 均來(lái)自同一文本。要么是encoder,要么是decoder
          # soft-attention, q來(lái)自decoder,k和v來(lái)自encoder,從而按照decoder和encoder相關(guān)性,將encoder信息融合進(jìn)來(lái)
          d_k = query.size(-1)

          # 利用q * k計(jì)算兩向量間相關(guān)度,相關(guān)度高則權(quán)重大。
          # 除以根號(hào)dk的原因是,對(duì)向量長(zhǎng)度進(jìn)行歸一化。q和k的向量長(zhǎng)度越長(zhǎng),q*k的值越大
          scores = torch.matmul(query, key.transpose(-2, -1)) / math.sqrt(d_k)

          # attention-mask,將 mask中為1的 元素所在的索引,在a中相同的的索引處替換為 value
          if mask is not None:
          scores = scores.masked_fill(mask == 0, -1e9)

          # softmax歸一化
          p_attn = F.softmax(scores, dim = -1)

          # dropout
          if dropout is not None:
          p_attn = dropout(p_attn)

          # 最后利用歸一化后的加權(quán)系數(shù),乘以每一個(gè)v向量,再加和在一起,作為attention后的向量。每個(gè)字對(duì)應(yīng)一個(gè)向量
          return torch.matmul(p_attn, value), p_attn

          self-attention和soft-attention共用了這個(gè)函數(shù),他們之間的唯一區(qū)別是q k v向量的來(lái)源不同。self-attention中q k v 均來(lái)自同一文本。而decoder的soft-attention,q來(lái)自于decoder,k和v來(lái)自于encoder。它體現(xiàn)的是encoder對(duì)decoder的加權(quán)貢獻(xiàn)。

          2.3.2 PositionwiseFeedForward

          feed-forward本質(zhì)是一個(gè)兩層的全連接,全連接之間加入了relu非線性和dropout。比較簡(jiǎn)單,代碼如下

          class PositionwiseFeedForward(nn.Module):
          # 全連接層
          def __init__(self, d_model, d_ff, dropout=0.1):
          super(PositionwiseFeedForward, self).__init__()
          # 第一層全連接 [d_model, d_ff]
          self.w_1 = nn.Linear(d_model, d_ff)

          # 第二層全連接 [d_ff, d_model]
          self.w_2 = nn.Linear(d_ff, d_model)

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

          def forward(self, x):
          # 全連接1 -> relu -> dropout -> 全連接2
          return self.w_2(self.dropout(F.relu(self.w_1(x))))

          總體過(guò)程是:全連接1 -> relu -> dropout -> 全連接2。兩層全連接內(nèi)部沒(méi)有shortcut,這兒不要搞混了。

          2.3.3 SublayerConnection

          在每層的self-attention和feed-forward模塊中,均應(yīng)用了殘差連接。殘差連接先對(duì)輸入進(jìn)行l(wèi)ayerNorm歸一化,然后送入attention或feed-forward模塊,然后經(jīng)過(guò)dropout,最后再和原始輸入相加。這樣做的好處是,讓每一層attention和feed-forward模塊的輸入值,均是經(jīng)過(guò)歸一化的,保持在一個(gè)量級(jí)上,從而可以加快收斂速度。

          class SublayerConnection(nn.Module):
          """
          A residual connection followed by a layer norm.
          Note for code simplicity the norm is first as opposed to last.
          """
          def __init__(self, size, dropout):
          super(SublayerConnection, self).__init__()
          # layer-norm 歸一化
          self.norm = LayerNorm(size)

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

          def forward(self, x, sublayer):
          # 先對(duì)輸入進(jìn)行l(wèi)ayer-norm, 然后經(jīng)過(guò)attention等相關(guān)模塊,再經(jīng)過(guò)dropout,最后再和輸入相加
          return x + self.dropout(sublayer(self.norm(x)))

          從forward函數(shù)可見,先對(duì)輸入進(jìn)行l(wèi)ayer-norm, 然后經(jīng)過(guò)attention等相關(guān)模塊,再經(jīng)過(guò)dropout,最后再和輸入相加。殘差連接的作用就不說(shuō)了,參考ResNet。

          3 decoder

          decoder結(jié)構(gòu)和encoder大體相同,也是堆疊了N層相同結(jié)構(gòu)的layer(默認(rèn)6層)。不同的是,decoder的每個(gè)子層包括三層。

          1. masked multi-head self-attention。這一部分和encoder基本相同,區(qū)別在于decoder為了保證模型不能看見要預(yù)測(cè)字的后面位置的字,加入了mask,從而避免未來(lái)信息的穿越問(wèn)題。mask為一個(gè)上三角矩陣,上三角全為1,下三角和對(duì)角線全為0

          2. multi-head soft-attention。soft-attention和self-attention結(jié)構(gòu)基本相同,甚至實(shí)現(xiàn)函數(shù)都是同一個(gè)。唯一的區(qū)別在于,self-attention的q k v矩陣來(lái)自同一個(gè),所以叫self-attention。而soft-attention的q來(lái)自decoder,k和v來(lái)自encoder。表征的是encoder的整體輸出對(duì)于decoder的貢獻(xiàn)。

          3. feed-forward。這一塊基本相同。

          另外三個(gè)模塊均使用了殘差連接,步驟仍然為 layerNorm -> attention等模塊 -> dropout -> 和輸入進(jìn)行add decoder每個(gè)layer代碼如下

          class DecoderLayer(nn.Module):
          "Decoder is made of self-attn, src-attn, and feed forward (defined below)"
          def __init__(self, size, self_attn, src_attn, feed_forward, dropout):
          super(DecoderLayer, self).__init__()
          self.size = size

          # self-attention 自注意力
          self.self_attn = self_attn

          # soft-attenton, encoder的輸出對(duì)decoder的作用
          self.src_attn = src_attn

          # feed-forward 全連接
          self.feed_forward = feed_forward

          # 殘差連接
          self.sublayer = clones(SublayerConnection(size, dropout), 3)

          def forward(self, x, memory, src_mask, tgt_mask):
          # memory為encoder最終輸出
          m = memory

          # 1 對(duì)decoder輸入做self-attention, 再和輸入做殘差連接
          x = self.sublayer[0](x, lambda x: self.self_attn(x, x, x, tgt_mask))

          # 2 對(duì)encoder輸出和decoder當(dāng)前進(jìn)行soft-attention,此處也有殘差連接
          x = self.sublayer[1](x, lambda x: self.src_attn(x, m, m, src_mask))

          # 3 feed-forward全連接,也有殘差連接
          return self.sublayer[2](x, self.feed_forward)

          4 輸出層

          decoder的輸出作為最終輸出層的輸入,經(jīng)過(guò)兩步

          1. linear線性連接,也即是w * x + b

          2. softmax歸一化,向量長(zhǎng)度等于vocabulary的長(zhǎng)度,得到vocabulary中每個(gè)字的概率。利用beam-search等方法,即可得到生成結(jié)果。

          這一層比較簡(jiǎn)單,代碼如下

          class Generator(nn.Module):
          "Define standard linear + softmax generation step."
          def __init__(self, d_model, vocab):
          super(Generator, self).__init__()
          self.proj = nn.Linear(d_model, vocab)

          def forward(self, x):
          # 先經(jīng)過(guò)linear線性層,然后經(jīng)過(guò)softmax得到歸一化概率分布
          # 輸出向量長(zhǎng)度等于vocabulary的維度
          return F.log_softmax(self.proj(x), dim=-1)

          5 總結(jié)

          Transformer相比LSTM的優(yōu)點(diǎn)

          1. 完全的并行計(jì)算,Transformer的attention和feed-forward,均可以并行計(jì)算。而LSTM則依賴上一時(shí)刻,必須串行

          2. 減少長(zhǎng)程依賴,利用self-attention將每個(gè)字之間距離縮短為1,大大緩解了長(zhǎng)距離依賴問(wèn)題

          3. 提高網(wǎng)絡(luò)深度。由于大大緩解了長(zhǎng)程依賴梯度衰減問(wèn)題,Transformer網(wǎng)絡(luò)可以很深,基于Transformer的BERT甚至可以做到24層。而LSTM一般只有2層或者4層。網(wǎng)絡(luò)越深,高階特征捕獲能力越好,模型performance也可以越高。

          4. 真正的雙向網(wǎng)絡(luò)。Transformer可以同時(shí)融合前后位置的信息,而雙向LSTM只是簡(jiǎn)單的將兩個(gè)方向的結(jié)果相加,嚴(yán)格來(lái)說(shuō)仍然是單向的。

          5. 可解釋性強(qiáng)。完全基于attention的Transformer,可以表達(dá)字與字之間的相關(guān)關(guān)系,可解釋性更強(qiáng)。

          Transformer也不是一定就比LSTM好,它的缺點(diǎn)如下

          1. 文本長(zhǎng)度很長(zhǎng)時(shí),比如篇章級(jí)別,計(jì)算量爆炸。self-attention的計(jì)算量為O(n^2), n為文本長(zhǎng)度。Transformer-xl利用層級(jí)方式,將計(jì)算速度提升了1800倍

          2. Transformer位置信息只靠position encoding,效果比較一般。當(dāng)語(yǔ)句較短時(shí),比如小于10個(gè)字,Transformer效果不一定比LSTM好

          3. Transformer參數(shù)量較大,在大規(guī)模數(shù)據(jù)集上,效果遠(yuǎn)好于LSTM。但在小規(guī)模數(shù)據(jù)集上,如果不是利用pretrain models,效果不一定有LSTM好。



          推薦閱讀

          VoVNet:實(shí)時(shí)目標(biāo)檢測(cè)的新backbone網(wǎng)絡(luò)

          Python編程神器Jupyter Notebook使用的28個(gè)秘訣

          帶你捋一捋anchor-free的檢測(cè)模型:FCOS

          PyTorch分布式訓(xùn)練簡(jiǎn)明教程


          機(jī)器學(xué)習(xí)算法工程師


          ? ??? ? ? ? ? ? ? ? ? ? ? ??????????????????一個(gè)用心的公眾號(hào)


          ?


          瀏覽 55
          點(diǎn)贊
          評(píng)論
          收藏
          分享

          手機(jī)掃一掃分享

          分享
          舉報(bào)
          評(píng)論
          圖片
          表情
          推薦
          點(diǎn)贊
          評(píng)論
          收藏
          分享

          手機(jī)掃一掃分享

          分享
          舉報(bào)
          <kbd id="afajh"><form id="afajh"></form></kbd>
          <strong id="afajh"><dl id="afajh"></dl></strong>
            <del id="afajh"><form id="afajh"></form></del>
                1. <th id="afajh"><progress id="afajh"></progress></th>
                  <b id="afajh"><abbr id="afajh"></abbr></b>
                  <th id="afajh"><progress id="afajh"></progress></th>
                  日韩小电影 | 男人天堂2014 | 操小嫩逼视频 | 欧洲亚洲青纯在线无码 | 黄色性爱视频网站 |