本文將通過(guò)細(xì)節(jié)剖析以及代碼相結(jié)合的方式,來(lái)一步步解析Attention is all you need這篇文章。
這篇文章的下載地址為:https://arxiv.org/abs/1706.03762
本文的部分圖片來(lái)自文章:https://mp.weixin.qq.com/s/RLxWevVWHXgX-UcoxDS70w,寫(xiě)的非常好!
本文邊講細(xì)節(jié)邊配合代碼實(shí)戰(zhàn),代碼地址為:https://github.com/princewen/tensorflow_practice/tree/master/basic/Basic-Transformer-Demo
數(shù)據(jù)地址為:https://pan.baidu.com/s/14XfprCqjmBKde9NmNZeCNg 密碼:lfwu
好了,廢話(huà)不多說(shuō),我們進(jìn)入正題!我們從簡(jiǎn)單到復(fù)雜,一步步介紹該模型的結(jié)構(gòu)!
1、整體架構(gòu)
模型的整體框架如下:
整體架構(gòu)看似復(fù)雜,其實(shí)就是一個(gè)Seq2Seq結(jié)構(gòu),簡(jiǎn)化一下,就是這樣的:
Encoder的輸出和decoder的結(jié)合如下,即最后一個(gè)encoder的輸出將和每一層的decoder進(jìn)行結(jié)合:
好了,我們主要關(guān)注的是每一層Encoder和每一層Decoder的內(nèi)部結(jié)構(gòu)。如下圖所示:
可以看到,Encoder的每一層有兩個(gè)操作,分別是Self-Attention和Feed Forward;而Decoder的每一層有三個(gè)操作,分別是Self-Attention、Encoder-Decoder Attention以及Feed Forward操作。這里的Self-Attention和Encoder-Decoder Attention都是用的是Multi-Head Attention機(jī)制,這也是我們本文重點(diǎn)講解的地方。
在介紹之前,我們先介紹下我們的數(shù)據(jù),經(jīng)過(guò)處理之后,數(shù)據(jù)如下:
很簡(jiǎn)單,上面部分是我們的x,也就是encoder的輸入,下面部分是y,也就是decoder的輸入,這是一個(gè)機(jī)器翻譯的數(shù)據(jù),x中的每一個(gè)id代表一個(gè)語(yǔ)言中的單詞id,y中的每一個(gè)id代表另一種語(yǔ)言中的單詞id。后面為0的部分是填充部分,代表這個(gè)句子的長(zhǎng)度沒(méi)有達(dá)到我們?cè)O(shè)置的最大長(zhǎng)度,進(jìn)行補(bǔ)齊。
2、Position Embedding
給定我們的輸入數(shù)據(jù),我們首先要轉(zhuǎn)換成對(duì)應(yīng)的embedding,由于我們后面要在計(jì)算attention時(shí)屏蔽掉填充的部分,所以這里我們對(duì)于填充的部分的embedding直接賦予0值。Embedding的函數(shù)如下:
def embedding(inputs,
vocab_size,
num_units,
zero_pad=True,
scale=True,
scope="embedding",
reuse=None):
with tf.variable_scope(scope, reuse=reuse):
lookup_table = tf.get_variable('lookup_table',
dtype=tf.float32,
shape=[vocab_size, num_units],
initializer=tf.contrib.layers.xavier_initializer())
if zero_pad:
lookup_table = tf.concat((tf.zeros(shape=[1, num_units]),
lookup_table[1:, :]), 0)
outputs = tf.nn.embedding_lookup(lookup_table, inputs)
if scale:
outputs = outputs * (num_units ** 0.5)
return outputs
在本文中,Embedding操作不是普通的Embedding而是加入了位置信息的Embedding,我們稱(chēng)之為Position Embedding。因?yàn)樵诒疚牡哪P椭校呀?jīng)沒(méi)有了循環(huán)神經(jīng)網(wǎng)絡(luò)這樣的結(jié)構(gòu),因此序列信息已經(jīng)無(wú)法捕捉。但是序列信息非常重要,代表著全局的結(jié)構(gòu),因此必須將序列的分詞相對(duì)或者絕對(duì)position信息利用起來(lái)。位置信息的計(jì)算公式如下:
其中pos代表的是第幾個(gè)詞,i代表embedding中的第幾維。這部分的代碼如下,對(duì)于padding的部分,我們還是使用全0處理。
def positional_encoding(inputs,
num_units,
zero_pad = True,
scale = True,
scope = "positional_encoding",
reuse=None):
N,T = inputs.get_shape().as_list()
with tf.variable_scope(scope,reuse=True):
position_ind = tf.tile(tf.expand_dims(tf.range(T),0),[N,1])
position_enc = np.array([
[pos / np.power(10000, 2.*i / num_units) for i in range(num_units)]
for pos in range(T)])
position_enc[:,0::2] = np.sin(position_enc[:,0::2]) # dim 2i
position_enc[:,1::2] = np.cos(position_enc[:,1::2]) # dim 2i+1
lookup_table = tf.convert_to_tensor(position_enc)
if zero_pad:
lookup_table = tf.concat((tf.zeros(shape=[1,num_units]),lookup_table[1:,:]),0)
outputs = tf.nn.embedding_lookup(lookup_table,position_ind)
if scale:
outputs = outputs * num_units ** 0.5
return outputs
所以對(duì)于輸入,我們調(diào)用上面兩個(gè)函數(shù),并將結(jié)果相加就能得到最終Position Embedding的結(jié)果:
self.enc = embedding(self.x,
vocab_size=len(de2idx),
num_units = hp.hidden_units,
zero_pad=True, # 讓padding一直是0
scale=True,
scope="enc_embed")
self.enc += embedding(tf.tile(tf.expand_dims(tf.range(tf.shape(self.x)[1]),0),[tf.shape(self.x)[0],1]),
vocab_size = hp.maxlen,
num_units = hp.hidden_units,
zero_pad = False,
scale = False,
scope = "enc_pe")
3、Multi-Head Attention
3.1 Attention簡(jiǎn)單回顧
Attention其實(shí)就是計(jì)算一種相關(guān)程度,看下面的例子:
Attention通常可以進(jìn)行如下描述,表示為將query(Q)和key-value pairs映射到輸出上,其中query、每個(gè)key、每個(gè)value都是向量,輸出是V中所有values的加權(quán),其中權(quán)重是由Query和每個(gè)key計(jì)算出來(lái)的,計(jì)算方法分為三步:
1)計(jì)算比較Q和K的相似度,用f來(lái)表示:
2)將得到的相似度進(jìn)行softmax歸一化:
3)針對(duì)計(jì)算出來(lái)的權(quán)重,對(duì)所有的values進(jìn)行加權(quán)求和,得到Attention向量:
計(jì)算相似度的方法有以下4種:
在本文中,我們計(jì)算相似度的方式是第一種,本文提出的Attention機(jī)制稱(chēng)為Multi-Head Attention,不過(guò)在這之前,我們要先介紹它的簡(jiǎn)單版本 Scaled Dot-Product Attention。
計(jì)算Attention首先要有query,key和value。我們前面提到了,Encoder的attention是self-attention,Decoder里面的attention首先是self-attention,然后是encoder-decoder attention。這里的兩種attention是針對(duì)query和key-value來(lái)說(shuō)的,對(duì)于self-attention來(lái)說(shuō),計(jì)算得到query和key-value的過(guò)程都是使用的同樣的輸入,因?yàn)橐阕约焊约旱腶ttention嘛;而對(duì)encoder-decoder attention來(lái)說(shuō),query的計(jì)算使用的是decoder的輸入,而key-value的計(jì)算使用的是encoder的輸出,因?yàn)槲覀円?jì)算decoder的輸入跟encoder里面每一個(gè)的相似度嘛。
因此本文下面對(duì)于attention的講解,都是基于self-attention來(lái)說(shuō)的,如果是encoder-decoder attention,只要改一下輸入即可,其余過(guò)程都是一樣的。
3.2 Scaled Dot-Product Attention
Scaled Dot-Product Attention的圖示如下:
接下來(lái),我們對(duì)上述過(guò)程進(jìn)行一步步的拆解:
First Step-得到embedding
給定我們的輸入數(shù)據(jù),我們首先要轉(zhuǎn)換成對(duì)應(yīng)的position embedding,效果圖如下,綠色部分代表填充部分,全0值:
得到Embedding的過(guò)程我們上文中已經(jīng)介紹過(guò)了,這里不再贅述。
Second Step-得到Q,K,V
計(jì)算Attention首先要有Query,Key和Value,我們通過(guò)一個(gè)線(xiàn)性變換來(lái)得到三者。我們的輸入是position embedding,過(guò)程如下:
代碼也很簡(jiǎn)單,下面的代碼中,如果是self-attention的話(huà),query和key-value輸入的embedding是一樣的。padding的部分由于都是0,結(jié)果中該部分還是0,所以仍然用綠色表示
# Linear projection
Q = tf.layers.dense(queries,num_units,activation=tf.nn.relu) #
K = tf.layers.dense(keys,num_units,activation=tf.nn.relu) #
V = tf.layers.dense(keys,num_units,activation=tf.nn.relu) #
Third-Step-計(jì)算相似度
接下來(lái)就是計(jì)算相似度了,我們之前說(shuō)過(guò)了,本文中使用的是點(diǎn)乘的方式,所以將Q和K進(jìn)行點(diǎn)乘即可,過(guò)程如下:
文中對(duì)于相似度還除以了dk的平方根,這里dk是key的embedding長(zhǎng)度。
這一部分的代碼如下:
outputs = tf.matmul(Q,tf.transpose(K,[0,2,1]))
outputs = outputs / (K.get_shape().as_list()[-1] ** 0.5)
你可能注意到了,這樣做其實(shí)是得到了一個(gè)注意力的矩陣,每一行都是一個(gè)query和所有key的相似性,對(duì)self-attention來(lái)說(shuō),其效果如下:
不過(guò)我們還沒(méi)有進(jìn)行softmax歸一化操作,因?yàn)槲覀冞€需要進(jìn)行一些處理。
Forth-Step-增加mask
剛剛得到的注意力矩陣,我們還需要做一下處理,主要有:
- query和key有些部分是填充的,這些需要用mask屏蔽,一個(gè)簡(jiǎn)單的方法就是賦予一個(gè)很小很小的值或者直接變?yōu)?值。
- 對(duì)于decoder的來(lái)說(shuō),我們是不能看到未來(lái)的信息的,所以對(duì)于decoder的輸入,我們只能計(jì)算它和它之前輸入的信息的相似度。
我們首先對(duì)key中填充的部分進(jìn)行屏蔽,我們之前介紹了,在進(jìn)行embedding時(shí),填充的部分的embedding 直接設(shè)置為全0,所以我們直接根據(jù)這個(gè)來(lái)進(jìn)行屏蔽,即對(duì)embedding的向量所有維度相加得到一個(gè)標(biāo)量,如果標(biāo)量是0,那就代表是填充的部分,否則不是:
這部分的代碼如下:
key_masks = tf.sign(tf.abs(tf.reduce_sum(keys,axis=-1)))
key_masks = tf.tile(tf.expand_dims(key_masks,1),[1,tf.shape(queries)[1],1])
paddings = tf.ones_like(outputs) * (-2 ** 32 + 1)
outputs = tf.where(tf.equal(key_masks,0),paddings,outputs)
經(jīng)過(guò)這一步處理,效果如下,我們下圖中用深灰色代表屏蔽掉的部分:
接下來(lái)的操作只針對(duì)Decoder的self-attention來(lái)說(shuō),我們首先得到一個(gè)下三角矩陣,這個(gè)矩陣主對(duì)角線(xiàn)以及下方的部分是1,其余部分是0,然后根據(jù)1或者0來(lái)選擇使用output還是很小的數(shù)進(jìn)行填充:
diag_vals = tf.ones_like(outputs[0,:,:])
tril = tf.contrib.linalg.LinearOperatorTriL(diag_vals).to_dense()
masks = tf.tile(tf.expand_dims(tril,0),[tf.shape(outputs)[0],1,1])
paddings = tf.ones_like(masks) * (-2 ** 32 + 1)
outputs = tf.where(tf.equal(masks,0),paddings,outputs)
得到的效果如下圖所示:
接下來(lái),我們對(duì)query的部分進(jìn)行屏蔽,與屏蔽key的思路大致相同,不過(guò)我們這里不是用很小的值替換了,而是直接把填充的部分變?yōu)?:
query_masks = tf.sign(tf.abs(tf.reduce_sum(queries,axis=-1)))
query_masks = tf.tile(tf.expand_dims(query_masks,-1),[1,1,tf.shape(keys)[1]])
outputs *= query_masks
經(jīng)過(guò)這一步,Encoder和Decoder得到的最終的相似度矩陣如下,上邊是Encoder的結(jié)果,下邊是Decoder的結(jié)果:
接下來(lái),我們就可以進(jìn)行softmax操作了:
outputs = tf.nn.softmax(outputs)
Fifth-Step-得到最終結(jié)果
得到了Attention的相似度矩陣,我們就可以和Value進(jìn)行相乘,得到經(jīng)過(guò)attention加權(quán)的結(jié)果:
這一部分是一個(gè)簡(jiǎn)單的矩陣相乘運(yùn)算,代碼如下:
outputs = tf.matmul(outputs,V)
不過(guò)這并不是最終的結(jié)果,這里文中還加入了殘差網(wǎng)絡(luò)的結(jié)構(gòu),即將最終的結(jié)果和queries的輸入進(jìn)行相加:
outputs += queries
所以一個(gè)完整的Scaled Dot-Product Attention的代碼如下:
def scaled_dotproduct_attention(queries,keys,num_units=None,
num_heads = 0,
dropout_rate = 0,
is_training = True,
causality = False,
scope = "mulithead_attention",
reuse = None):
with tf.variable_scope(scope,reuse=reuse):
if num_units is None:
num_units = queries.get_shape().as_list[-1]
# Linear projection
Q = tf.layers.dense(queries,num_units,activation=tf.nn.relu) #
K = tf.layers.dense(keys,num_units,activation=tf.nn.relu) #
V = tf.layers.dense(keys,num_units,activation=tf.nn.relu) #
outputs = tf.matmul(Q,tf.transpose(K,[0,2,1]))
outputs = outputs / (K.get_shape().as_list()[-1] ** 0.5)
# 這里是對(duì)填充的部分進(jìn)行一個(gè)mask,這些位置的attention score變?yōu)闃O小,我們的embedding操作中是有一個(gè)padding操作的,
# 填充的部分其embedding都是0,加起來(lái)也是0,我們就會(huì)填充一個(gè)很小的數(shù)。
key_masks = tf.sign(tf.abs(tf.reduce_sum(keys,axis=-1)))
key_masks = tf.tile(tf.expand_dims(key_masks,1),[1,tf.shape(queries)[1],1])
paddings = tf.ones_like(outputs) * (-2 ** 32 + 1)
outputs = tf.where(tf.equal(key_masks,0),paddings,outputs)
# 這里其實(shí)就是進(jìn)行一個(gè)mask操作,不給模型看到未來(lái)的信息。
if causality:
diag_vals = tf.ones_like(outputs[0,:,:])
tril = tf.contrib.linalg.LinearOperatorTriL(diag_vals).to_dense()
masks = tf.tile(tf.expand_dims(tril,0),[tf.shape(outputs)[0],1,1])
paddings = tf.ones_like(masks) * (-2 ** 32 + 1)
outputs = tf.where(tf.equal(masks,0),paddings,outputs)
outputs = tf.nn.softmax(outputs)
# Query Mask
query_masks = tf.sign(tf.abs(tf.reduce_sum(queries,axis=-1)))
query_masks = tf.tile(tf.expand_dims(query_masks,-1),[1,1,tf.shape(keys)[1]])
outputs *= query_masks
# Dropout
outputs = tf.layers.dropout(outputs,rate = dropout_rate,training = tf.convert_to_tensor(is_training))
# Weighted sum
outputs = tf.matmul(outputs,V)
# Residual connection
outputs += queries
# Normalize
outputs = normalize(outputs)
return outputs
3.3 Multi-Head Attention
Multi-Head Attention就是把Scaled Dot-Product Attention的過(guò)程做H次,然后把輸出合起來(lái)。論文中,它的結(jié)構(gòu)圖如下:
這部分的示意圖如下所示,我們重復(fù)做3次相似的操作,得到每一個(gè)的結(jié)果矩陣,隨后將結(jié)果矩陣進(jìn)行拼接,再經(jīng)過(guò)一次的線(xiàn)性操作,得到最終的結(jié)果:
Scaled Dot-Product Attention可以看作是只有一個(gè)Head的Multi-Head Attention,這部分的代碼跟Scaled Dot-Product Attention大同小異,我們直接貼出:
def multihead_attention(queries,keys,num_units=None,
num_heads = 0,
dropout_rate = 0,
is_training = True,
causality = False,
scope = "mulithead_attention",
reuse = None):
with tf.variable_scope(scope,reuse=reuse):
if num_units is None:
num_units = queries.get_shape().as_list[-1]
# Linear projection
Q = tf.layers.dense(queries,num_units,activation=tf.nn.relu) #
K = tf.layers.dense(keys,num_units,activation=tf.nn.relu) #
V = tf.layers.dense(keys,num_units,activation=tf.nn.relu) #
# Split and Concat
Q_ = tf.concat(tf.split(Q,num_heads,axis=2),axis=0) #
K_ = tf.concat(tf.split(K,num_heads,axis=2),axis=0)
V_ = tf.concat(tf.split(V,num_heads,axis=2),axis=0)
outputs = tf.matmul(Q_,tf.transpose(K_,[0,2,1]))
outputs = outputs / (K_.get_shape().as_list()[-1] ** 0.5)
# 這里是對(duì)填充的部分進(jìn)行一個(gè)mask,這些位置的attention score變?yōu)闃O小,我們的embedding操作中是有一個(gè)padding操作的,
# 填充的部分其embedding都是0,加起來(lái)也是0,我們就會(huì)填充一個(gè)很小的數(shù)。
key_masks = tf.sign(tf.abs(tf.reduce_sum(keys,axis=-1)))
key_masks = tf.tile(key_masks,[num_heads,1])
key_masks = tf.tile(tf.expand_dims(key_masks,1),[1,tf.shape(queries)[1],1])
paddings = tf.ones_like(outputs) * (-2 ** 32 + 1)
outputs = tf.where(tf.equal(key_masks,0),paddings,outputs)
# 這里其實(shí)就是進(jìn)行一個(gè)mask操作,不給模型看到未來(lái)的信息。
if causality:
diag_vals = tf.ones_like(outputs[0,:,:])
tril = tf.contrib.linalg.LinearOperatorTriL(diag_vals).to_dense()
masks = tf.tile(tf.expand_dims(tril,0),[tf.shape(outputs)[0],1,1])
paddings = tf.ones_like(masks) * (-2 ** 32 + 1)
outputs = tf.where(tf.equal(masks,0),paddings,outputs)
outputs = tf.nn.softmax(outputs)
# Query Mask
query_masks = tf.sign(tf.abs(tf.reduce_sum(queries,axis=-1)))
query_masks = tf.tile(query_masks,[num_heads,1])
query_masks = tf.tile(tf.expand_dims(query_masks,-1),[1,1,tf.shape(keys)[1]])
outputs *= query_masks
# Dropout
outputs = tf.layers.dropout(outputs,rate = dropout_rate,training = tf.convert_to_tensor(is_training))
# Weighted sum
outputs = tf.matmul(outputs,V_)
# restore shape
outputs = tf.concat(tf.split(outputs,num_heads,axis=0),axis=2)
# Residual connection
outputs += queries
# Normalize
outputs = normalize(outputs)
return outputs
4、Position-wise Feed-forward Networks
在進(jìn)行了Attention操作之后,encoder和decoder中的每一層都包含了一個(gè)全連接前向網(wǎng)絡(luò),對(duì)每個(gè)position的向量分別進(jìn)行相同的操作,包括兩個(gè)線(xiàn)性變換和一個(gè)ReLU激活輸出:
代碼如下:
def feedforward(inputs,
num_units=[2048, 512],
scope="multihead_attention",
reuse=None):
with tf.variable_scope(scope, reuse=reuse):
# Inner layer
params = {"inputs": inputs, "filters": num_units[0], "kernel_size": 1,
"activation": tf.nn.relu, "use_bias": True}
outputs = tf.layers.conv1d(**params)
# Readout layer
params = {"inputs": outputs, "filters": num_units[1], "kernel_size": 1,
"activation": None, "use_bias": True}
outputs = tf.layers.conv1d(**params)
# Residual connection
outputs += inputs
# Normalize
outputs = normalize(outputs)
return outputs
5、Encoder的結(jié)構(gòu)
Encoder有N(默認(rèn)是6)層,每層包括兩個(gè)sub-layers:
1 )第一個(gè)sub-layer是multi-head self-attention mechanism,用來(lái)計(jì)算輸入的self-attention;
2 )第二個(gè)sub-layer是簡(jiǎn)單的全連接網(wǎng)絡(luò)。
每一個(gè)sub-layer都模擬了殘差網(wǎng)絡(luò)的結(jié)構(gòu),其網(wǎng)絡(luò)示意圖如下:
根據(jù)我們剛才定義的函數(shù),其完整的代碼如下:
with tf.variable_scope("encoder"):
# Embedding
self.enc = embedding(self.x,
vocab_size=len(de2idx),
num_units = hp.hidden_units,
zero_pad=True, # 讓padding一直是0
scale=True,
scope="enc_embed")
## Positional Encoding
if hp.sinusoid:
self.enc += positional_encoding(self.x,
num_units = hp.hidden_units,
zero_pad = False,
scale = False,
scope='enc_pe')
else:
self.enc += embedding(tf.tile(tf.expand_dims(tf.range(tf.shape(self.x)[1]),0),[tf.shape(self.x)[0],1]),
vocab_size = hp.maxlen,
num_units = hp.hidden_units,
zero_pad = False,
scale = False,
scope = "enc_pe")
##Drop out
self.enc = tf.layers.dropout(self.enc,rate = hp.dropout_rate,
training = tf.convert_to_tensor(is_training))
## Blocks
for i in range(hp.num_blocks):
with tf.variable_scope("num_blocks_{}".format(i)):
### MultiHead Attention
self.enc = multihead_attention(queries = self.enc,
keys = self.enc,
num_units = hp.hidden_units,
num_heads = hp.num_heads,
dropout_rate = hp.dropout_rate,
is_training = is_training,
causality = False
)
self.enc = feedforward(self.enc,num_units = [4 * hp.hidden_units,hp.hidden_units])
6、Decoder的結(jié)構(gòu)
Decoder有N(默認(rèn)是6)層,每層包括三個(gè)sub-layers:
1 )第一個(gè)是Masked multi-head self-attention,也是計(jì)算輸入的self-attention,但是因?yàn)槭巧蛇^(guò)程,因此在時(shí)刻 i 的時(shí)候,大于 i 的時(shí)刻都沒(méi)有結(jié)果,只有小于 i 的時(shí)刻有結(jié)果,因此需要做Mask.
2 )第二個(gè)sub-layer是對(duì)encoder的輸入進(jìn)行attention計(jì)算,這里仍然是multi-head的attention結(jié)構(gòu),只不過(guò)輸入的分別是decoder的輸入和encoder的輸出。
3 )第三個(gè)sub-layer是全連接網(wǎng)絡(luò),與Encoder相同。
其網(wǎng)絡(luò)示意圖如下:
其代碼如下:
with tf.variable_scope("decoder"):
# Embedding
self.dec = embedding(self.decoder_inputs,
vocab_size=len(en2idx),
num_units = hp.hidden_units,
scale=True,
scope="dec_embed")
## Positional Encoding
if hp.sinusoid:
self.dec += positional_encoding(self.decoder_inputs,
vocab_size = hp.maxlen,
num_units = hp.hidden_units,
zero_pad = False,
scale = False,
scope = "dec_pe")
else:
self.dec += embedding(
tf.tile(tf.expand_dims(tf.range(tf.shape(self.decoder_inputs)[1]), 0), [tf.shape(self.decoder_inputs)[0], 1]),
vocab_size=hp.maxlen,
num_units=hp.hidden_units,
zero_pad=False,
scale=False,
scope="dec_pe")
# Dropout
self.dec = tf.layers.dropout(self.dec,
rate = hp.dropout_rate,
training = tf.convert_to_tensor(is_training))
## Blocks
for i in range(hp.num_blocks):
with tf.variable_scope("num_blocks_{}".format(i)):
## Multihead Attention ( self-attention)
self.dec = multihead_attention(queries=self.dec,
keys=self.dec,
num_units=hp.hidden_units,
num_heads=hp.num_heads,
dropout_rate=hp.dropout_rate,
is_training=is_training,
causality=True,
scope="self_attention")
## Multihead Attention ( vanilla attention)
self.dec = multihead_attention(queries=self.dec,
keys=self.enc,
num_units=hp.hidden_units,
num_heads=hp.num_heads,
dropout_rate=hp.dropout_rate,
is_training=is_training,
causality=False,
scope="vanilla_attention")
## Feed Forward
self.dec = feedforward(self.dec, num_units=[4 * hp.hidden_units, hp.hidden_units])
7、模型輸出
decoder的輸出會(huì)經(jīng)過(guò)一層全聯(lián)接網(wǎng)絡(luò)和softmax得到最終的結(jié)果,示意圖如下:
這樣,一個(gè)完整的Transformer Architecture我們就介紹完了,對(duì)于文中寫(xiě)的不清楚或者不到位的地方,歡迎各位留言指正!
參考文獻(xiàn)
1、原文:https://arxiv.org/abs/1706.03762
2、https://mp.weixin.qq.com/s/RLxWevVWHXgX-UcoxDS70w
3、https://github.com/princewen/tensorflow_practice/tree/master/basic/Basic-Transformer-Demo