前期准备
nn.ModuleList
ModuleList是一个容器,可以存储各种module,类似于python的list,可以存储各种类型的数据。不同的是ModuleList会将存储的module自动注册到网络,也就是说网络的参数已经包含了ModuleList中存储的各个模块的参数
class Network(nn.Module):
def __init__(self):
super(Network,self).__init__()
self.modulelist=nn.ModuleList([
nn.Linear(in_features=10,out_features=20,bias=True),
nn.Tanh(),
nn.Linear(in_features=20,out_features=30,bias=True),
])
def forward(self,inputs):
for layer in self.modulelist:
inputs=layer(inputs)
return inputs
net=Network()
for param in net.parameters():
print(param.size())
我们可以看到,Network网络参数已经就是ModuleList中包含的两个Linear层的参数
如果把ModuleList换成list你会发现打印不出来任何数据,此时的Network网络是没有任何参数的,因为list不会自动把各个模块注册到整个网络中,即使可以用forward调用这两个Linear层,但是由于这两个Linear层不算在Network网络中,所以更新参数时不会更新这两个Linear
nn.ModuleList与nn.Sequential的区别
nn.Sequential实现了forward,所以可以直接调用整个sequential模块,模块会按照顺序执行
class Network(nn.Module):
def __init__(self):
super(Network,self).__init__()
self.modulelist=nn.Sequential(
nn.Linear(in_features=10,out_features=20,bias=True),
nn.Tanh(),
nn.Linear(in_features=20,out_features=30,bias=True),
)
def forward(self,inputs):
# for layer in self.modulelist:
# inputs=layer(inputs)
return self.modulelist(inputs)
net=Network()
inputs=torch.rand(5,10)
outputs=net(inputs)
print(outputs.size())
但是nn.ModuleList没有实现forward函数,所以只能放在for循环中依次遍历每一个module
比如将上面的Sequential替换回ModuleList则直接调用modulelist(inputs)会出错
contiguous()
在pytorch中,permute或者transpose操作不改变元数据本身。例如:
我们可以看到t和t2两个张量其实是内存的同一块区域的数据
然而经过transpose之后,t2与t的步长是不一样的。t的步长是(4,1).t2的步长是(1,4)
但是此时t2是不连续的,t2.is_contiguous()==False,而view操作需要张量具有连续的内存。所以此时t2.view(3,4)会报错,报错是因为利用新的stride(1,4)在原始数据的内存分布上操作数据。做法就是t2.contiguous().view(3,4)。contiguous操作会开辟一个新的内存区域,这个内存区域的tensor是连续的。
nn.parameter.Parameter()
函数的作用是,对于普通变量,不会登记为模型的参数.
我们可以看到,c这个张量不算做模型的参数。这个有点类似于上述的ModuleList和list
多头注意力的直观做法
就是把inputs这个tensor通过三个不同的Linear层得到Q,K,V
,然后把Q,K,V各自都split成k份,每一份之间做注意力的运算
inputs=torch.rand(10,16)#10指的是句子刹长度,16指的是维度
#将inputs通过三个不同的Linear,得到Q,K,V
Q=nn.Linear(16,16)(inputs)
K=nn.Linear(16,16)(inputs)
K=nn.Linear(16,16)(inputs)
#分割成四个头
multi_q=Q.view(10,4,4).transpose(0,1)#分割成四个头,每一个头有4个维度
multi_k=K.view(10,4,4).transpose(0,1)
multi_v=V.view(10,4,4).transpose(0,1)
#各个头之间同时做注意力的运算
multi_res=torch.matmul(torch.nn.functional.softmax(torch.matmul(multi_q,multi_k.transpose(-2,-1)),1),multi_v)
multi_res=multi_res.transpose(0,1).contiguous().view(10,16)
#将四个头的结果合并
为什么要scaled
因为向量维度越大,那么某两个向量内积的数值就可能很大,某两个向量内积的数值也有可能很小。这导致在softmax之后,softmax分布不是很平缓,某些数值可能接近1,某些数值可能接近0.例如:
将向量的数值除以一个常数后,会使得softmax分布变得更加的平缓:
这和知识蒸馏中带有温度的softmax是一样的道理:
e
x
i
/
T
∑
j
=
1
N
e
x
j
/
T
\frac{e^{x_i/T}}{\displaystyle\sum_{j=1}^{N}e^{x_j/T}}
j=1∑Nexj/Texi/T
目的是使得输出的分布平缓一些。
另一种解释角度是,假设Q和K是相互独立的随机变量,它们都是0均值,方差是1,假设维度是 d k d_k dk,那么Q和K做内积之后的结果就是均值是0,方差是 d k d_k dk的随机变量,为了使得该随机变量仍然是0均值,1方差,那么就应该除以 d k \sqrt{d_k} dk
为什么要多头
注意力的目的是对有语义关联关系的单词赋予更高的权重,注意力权重的分布明确了各个单词之间的相关程度。然而这种权重的分布是在同一个语义空间中的
假如上面的三个三角形代表三个卷积核,当不同的卷积核提取同一个句子的特征时,不同的卷积核有不同的语义空间。比如黄色的卷积核在提取特征时,更加关注这些单词的词性。黑色卷积核主要关注单词的之间的句法结构等。
所以可以看到,多个卷积核(多通道)的目的是在不同的语义子空间中提取单词的特征。更具体一点,提取特征之后,黄色的卷积核提取的特征表示的是这个单词是名词还是动词等,绿色卷积核提取的特征表示的是这个单词是积极词性还是消极词性还是中级词性,黑色卷积核提取的特征表示的是这个单词是主语、宾语、还是定语等。然而这种多卷积核机制不能反映各个单词之间的相关程度,具体一点就是没法建模某两个单词之间的重要性
多头注意力机制可以看作是上述两种机制的结合,通过划分多个头,每一个头代表一个语义子空间,相当于多个卷积核。而各个头之间的注意力运算还能建模单词之间在当前语义子空间中的相关程度。一种不恰当的比喻就是可以认为,多头注意力机制利用多个"卷积核",只不过“卷积核”内部是自注意力运算。
层规范化和批次规范化
BatchNormalization
什么是Internal covariate shift
internal covariate shift指的是内部协变量偏移,内部协变量偏移是指:由于某一层的输入受到先前层所有参数的影响,那么之前某些层参数的微小变化都会使得后面网络层的输入分布发生巨大变化。这个问题叫内部协变量偏移。
由于内部协变量偏移问题,网络后面的层要不断的调整参数以适应前面层发生的变化,导致训练变得很困难,特别是网络的层数很多。
BN的原理
既然某一层的输入会受到先前网络层参数的影响,那么就把每一层的输入硬生生的变换到均值是0,方差是1的分布上。那么不管某一层参数如何变化,输出数据的分布都是均值是0,方差是1.
标准化公式:
μ
=
1
m
∑
i
=
1
m
x
i
σ
2
=
1
m
∑
i
=
1
m
(
x
i
−
μ
)
2
x
^
i
=
x
i
−
μ
σ
2
+
ϵ
\mu=\displaystyle\frac{1}{m}\sum_{i=1}^{m}x_i \\ \sigma^2=\displaystyle\frac{1}{m}\sum_{i=1}^{m}(x_i-\mu)^2 \\ \hat{x}_i=\displaystyle\frac{x_i-\mu}{\sqrt{\sigma^2+\epsilon}}
μ=m1i=1∑mxiσ2=m1i=1∑m(xi−μ)2x^i=σ2+ϵxi−μ
上面的公式是标准化一批数据的通用公式。在BN中m指的是当前的样本个数,在LN中指的是当前层的节点个数。
由于硬生生的将输出数据转换成均值是0,方差是1会打乱数据原有的分布,所以还需要两个额外的参数用来控制这个度:
x
^
i
=
γ
x
i
−
μ
σ
2
+
ϵ
+
β
\hat{x}_i=\gamma\displaystyle\frac{x_i-\mu}{\sqrt{\sigma^2+\epsilon}}+\beta
x^i=γσ2+ϵxi−μ+β
经过变换之后,
X
^
∼
N
(
β
,
γ
2
)
\hat{X}\sim N(\beta,\gamma^2)
X^∼N(β,γ2)。我们可以看到如果
γ
=
σ
2
,
β
=
μ
\gamma=\sqrt{\sigma^2},\beta=\mu
γ=σ2,β=μ。也就是说如果
γ
\gamma
γ取做原始数据的标准差,
β
\beta
β取做原始输出数据的均值,那么相当于没有对原始数据做任何变换。初始时刻
γ
=
1
,
β
=
0
\gamma=1,\beta=0
γ=1,β=0
注意BN操作是放在激活函数之前进行,将BN后的数据送进激活函数中
BN的优点
- 由于标准化操作使得网络不对参数的初始化敏感。具体一点就是,由于每一层的输出都被标准化,那么即使这一层参数的初始化并不好(比如由于初始化不好,使得这一层输出数据的值都落在激活函数的饱和区域,那么这一层参数的更新将会非常缓慢)。然而经过BN之后,输出数据的值回落在激活函数的非饱和区域,从而缓解梯度消失问题,由于梯度的流动不受参数变化的影响,所以可以加快训练。
- 由于在更新参数时,通常是mini-batch训练,而一个mini-batch内的数据在进行BN操作时,难免会引入噪声(毕竟不是所有数据,我们是利用这mini-batch个数据估计总体数据的均值和方差的)。这样使得BN反而具有一些正则化的作用
- 缓解过拟合问题。这是因为如果不使用BN操作,那么就像之前说的,前面层参数的变化会导致后面层要重新调整参数不断拟合输入数据的变化。而在整个训练集中,每当传入一批数据时,又要重新调整参数来拟合新的数据分布。这很有可能过拟合,特别是网络层数深的时候,协变量偏移现象更加明显。而如果带有BN,那么网络的参数不会经常的大幅度调整去拟合数据的变化,尽管这可能使得模型对训练数据拟合的反而没有之前好,但是可以缓解过拟合问题。
测试数据上如何应用BN
由于测试时可能只有一个数据,那么没法估计均值和方差。此时如何在每层应用BN操作呢,简单的做法就是统计在训练阶段每一层所有出现的均值和方差。比如训练了10批数据,也就是说更新了10次参数,那么统计这一层在这10次训练过程中所有的均值和方差。取它们的平均作为这一层的均值和方差。
LayerNormalization
BN的缺点显然是受到mini-batch的大小的影响,比如由于硬件资源限制,只能传进去5个数据。那么BN就是拿这5个数据的均值和方差估计总体数据,显然噪声太大,偏差和方差都会很高,估计的不准确。导致BN效果反而变差。
BN的另一个缺点是应用在动态的RNN模型上效果不好,因为样本的长度不是固定的。
LN的做法仍然是采用上述的公式,只不过作用的维度不同, m m m代表的是这一层的维度,也就是这一层神经元的个数。我们可以发现LN不受样本数量影响,是对所有的神经元进行标准化。
简单总结一句话就是:BN的操作对象是不同样本的同一个特征,LN操作的对象是同一个样本的不同特征
为什么要位置编码
对于RNN来讲,它天然的考虑了单词之间的顺序。如果输入单词的顺序发生改变,那么RNN的输出也会随之改变。对于CNN来讲,它可以类比为N-gram模型,如果卷积核的大小是3,那么就相当于3元模型,也可以建模单词顺序关系。然而transformer来说,所有单词并行处理,不受单词顺序的影响,即使打乱单词的顺序输入,其输出仍然是一样的,无法建模单词的顺序关系。
常见的位置编码有两种方式。一种是利用向量来表达单词的顺序;另一种是利用通过固定的函数给每一个位置一个编码。
transformer采用的是绝对位置编码。给每一个位置一个固定的向量表达位置信息,不需要模型去学习。
P
E
(
p
o
s
,
2
i
)
=
s
i
n
(
p
o
s
/
1000
0
2
i
/
d
)
P
E
(
p
o
s
,
2
i
+
1
)
=
c
o
s
(
p
o
s
/
1000
0
2
i
/
d
)
PE_{(pos,2i)}=sin(pos/10000^{2i/d}) \\ PE_{(pos,2i+1)}=cos(pos/10000^{2i/d})
PE(pos,2i)=sin(pos/100002i/d)PE(pos,2i+1)=cos(pos/100002i/d)
其中
i
=
0
,
1
,
2
,
.
.
.
d
/
2
i=0,1,2,...d/2
i=0,1,2,...d/2,这里面的d最好是偶数
具体一点
假如有10个单词,词向量维度是6,那么每一个单词会用6维的向量表示,
- 第一个单词 pos=0, 对应的向量是[ s i n ( 0 / 1000 0 0 / 6 ) , c o s ( 0 / 1000 0 0 / 6 ) , s i n ( 0 / 1000 0 2 / 6 ) , c o s ( 0 / 1000 0 2 / 6 ) , s i n ( 0 / 1000 0 4 / 6 ) , c o s ( 0 / 1000 0 4 / 6 ) sin(0/10000^{0/6}),cos(0/10000^{0/6}),sin(0/10000^{2/6}),cos(0/10000^{2/6}),sin(0/10000^{4/6}),cos(0/10000^{4/6}) sin(0/100000/6),cos(0/100000/6),sin(0/100002/6),cos(0/100002/6),sin(0/100004/6),cos(0/100004/6)]
- 第二个单词 pos=1, 对应的向量是[ s i n ( 1 / 1000 0 0 / 6 ) , c o s ( 1 / 1000 0 0 / 6 ) , s i n ( 1 / 1000 0 2 / 6 ) , c o s ( 1 / 1000 0 2 / 6 ) , s i n ( 1 / 1000 0 4 / 6 ) , c o s ( 1 / 1000 0 4 / 6 ) sin(1/10000^{0/6}),cos(1/10000^{0/6}),sin(1/10000^{2/6}),cos(1/10000^{2/6}),sin(1/10000^{4/6}),cos(1/10000^{4/6}) sin(1/100000/6),cos(1/100000/6),sin(1/100002/6),cos(1/100002/6),sin(1/100004/6),cos(1/100004/6)]
- 第三个单词 pos=2, 对应的向量是[ s i n ( 2 / 1000 0 0 / 6 ) , c o s ( 2 / 1000 0 0 / 6 ) , s i n ( 2 / 1000 0 2 / 6 ) , c o s ( 2 / 1000 0 2 / 6 ) , s i n ( 2 / 1000 0 4 / 6 ) , c o s ( 2 / 1000 0 4 / 6 ) sin(2/10000^{0/6}),cos(2/10000^{0/6}),sin(2/10000^{2/6}),cos(2/10000^{2/6}),sin(2/10000^{4/6}),cos(2/10000^{4/6}) sin(2/100000/6),cos(2/100000/6),sin(2/100002/6),cos(2/100002/6),sin(2/100004/6),cos(2/100004/6)]
不再一一写出
Transformer
encoder端
encoder是由6个块组成,每一块都是如上图所示的结构。每一块有两个子结构。地一个子结构是(多头注意力层+层规范化),第二个子结构是(两层前馈神经网络+层规范化)
subBlock1
第一个子结构是多头注意力层+层标准化
scaled dot product attention
def scaled_dot_product_attention(query,key,value,mask,future=False):
'''
query.size()==(batch_size,num_heads,seq_len_q,dim_k)
key.size()==(batch_size,num_heads,seq_len_v,dim_k)
value.size()==(batch_size,num_heads,seq_len_v,dim_v)
mask.size()==(batch_size,num_heads,seq_len_q,seq_len_v)
mask里面pad位置的值是0,不是pad位置的值是1
除以d_k的目的是因为当向量维度较大时,也就是d_k较大的时候,向量的内积在数值上会比较大,也可能会比较小
这导致softmax后的分布不是很平缓,某些值会特别的大,某些值接近0,这使得接近0值的那些位置的单词不会受到关注。
除以sqrt(d_k)是为了使得softmax的分布更平缓
'''
d_k=key.size(-1)
scores=torch.matmul(query,key.transpose(-2,-1))/math.sqrt(d_k)
if mask!=None:
scores+=(1.0-mask)*(-1e9)#pad位置的值会加上-1e12,不是pad位置的值不变
if future==True:
#说明此时是decoder端,此时的mask也是encoder_outputs_mask,
#此时需要构造形如上三角类型的mask矩阵,第一行一个1,第二行两个1,。。假设Q的句子长度是10,那么一共10行,第10行有10个1。即:不能看后面,可以看前面。
seq_len_q,seq_len_k=mask.size()[-2],mask.size()[-1]
mask_list=[[0]*seq_len_k for _ in range(seq_len_q)]
for row in range(seq_len_q):
mask_list[row][:row]=[1]*row
#注意此时我们重点是要mask Q而不是K,因为此时的Q代表decoder
mask_for_decoder=torch.LongTensor(mask_list).unsqueeze(0).unsqueeze(0)
mask_for_decoder=mask_for_decoder.expand_as(mask)
scores+=(1.0-mask_for_decoder)*(-1e9)#上半部分都是-1e9
#目的就是让当前的Q,也就是decoder不能看到后面
attention_weights=torch.nn.functional.softmax(scores,dim=-1)#(batch_size,num_heads,seq_len_q,seq_len_v)
res=torch.matmul(attention_weights,value)#(batch_size,num_heads,seq_len_q,dim_v)
return res,attention_weights
LayerNorm
class LayerNorm(nn.Module):
def __init__(self,dim,epsilon=1e-6):
super(LayerNorm,self).__init__()
self.gamma=nn.parameter.Parameter(torch.ones(dim))
self.beta=nn.parameter.Parameter(torch.zeros(dim))
self.epsilon=epsilon
def forward(self,inputs):
mean=torch.mean(inputs,dim=-1,keepdim=True)
var=torch.var(inputs,dim=-1,keepdim=True)
normalized_x=(inputs-mean)/torch.sqrt(var+epsilon)#(batch_size,seq_len,dim)
ln_res=self.gamma*normalized_x+self.beta
return ln_res
subblock1
class SubBlock1(nn.Module):
def __init__(self,d_model,num_heads):
super(SubBlock1,self).__init__()
assert d_model%num_heads==0
self.linears_for_qkv=nn.ModuleList([copy.deepcopy(nn.Linear(d_model,d_model)) for _ in range(3)])
self.linears_for_combine=nn.Linear(d_model,d_model)
self.d_model=d_model
self.num_heads=num_heads
self.per_head_size=self.d_model//self.num_heads
self.ln_layer=LayerNorm(dim=d_model)
def forward(self,inputs,inputs_mask):
'''
将inputs通过三个不同的Linear得到Q,K,V
split QKV得到多个头,各个头之间同时做scaled dot product attention
将所有头的结果合并得到multihead_att_outputs
最后输出ln_layer(multi_head_att_outputs+inputs)
'''
batch_size=inputs.size(0)
assert inputs.size(-1)==self.d_model
query=self.linears_for_qkv[0](inputs).view(batch_size,-1,self.num_heads,self.per_head_size).transpose(1,2)
key=self.linears_for_qkv[1](inputs).view(batch_size,-1,self.num_heads,self.per_head_size).transpose(1,2)
value=self.linears_for_qkv[2](inputs).view(batch_size,-1,self.num_heads,self.per_head_size).transpose(1,2)
if inputs_mask!=None:
#inputs_mask.size()==(batch_size,seq_len)
inputs_mask.unsqueeze_(1)
inputs_mask.unsqueeze_(1)
#inputs_mask.size()==(batch_size,1,1,seq_len)
self_att_res,att_weights=scaled_dot_product_attention(query=query,key=key,value=value,mask=mask)
#self_att_res.size()==(batch_size,num_heads,seq_len_q,dim_v)
self_att_res=self_att_res.transpose(1,2).contiguous().view(batch_size,-1,self.d_model)
multihead_att_res=self.linears_for_combine(self_att_res)#将多个头的att结果合并之后通过一层Linear
return self.ln_layer(multihead_att_res+inputs)
subBlock2
第二个子结构是两层前馈神经网络+层标准化
class FFN(nn.Module):
def __init__(self,d_model,d_ff):
super(FFN,self).__init__()
self.linear1=nn.Linear(in_features=d_model,out_features=d_ff,bias=True)
self.linear2=nn.Linear(in_features=d_ff,out_features=d_model,bias=True)
def forward(self,inputs):
return self.linear2(torch.nn.functional.relu(self.linear1(inputs)))
class SubBlock2(nn.Module):
def __init__(self,d_model,d_ff):
super(SubBlock2,self).__init__()
self.ffn=FFN(d_model=d_model,d_ff=d_ff)
self.ln_layer=LayerNorm(dim=d_model)
def forward(self,inputs):
ffn_outputs=self.ffn(inputs)#(batch_size,seq_len,d_model)
return self.ln_layer(ffn_outputs+inputs)
encoder部分
将两个subblock合并到一起得到一个block,encoder是由6个block堆叠而成。
第一个block的输入还要加上位置编码
class Block(nn.Module):
def __init__(self,num_heads,d_model,d_ff):
super(Block,self).__init__()
self.subBlock1=SubBlock1(d_model,num_heads)
self.subBlock2=SubBlock2(d_model,d_ff)
def forward(self,inputs,inputs_mask):
return self.subBlock2(self.subBlock1(inputs,inputs_mask))
def positionEncoding(seq_len,d_model):
position_matrix=np.zeros((seq_len,d_model))
for pos in range(seq_len):
for i in range(0,d_model,2):
position_matrix[pos,i]=pos/math.pow(10000,i/d_model)
position_matrix[pos,i+1]=pos/math.pow(10000,i/d_model)
position_matrix[:,0::2]=np.sin(position_matrix[:,0::2])
position_matrix[:,1::2]=np.cos(position_matrix[:,1::2])
return torch.from_numpy(position_matrix)
class Encoder(nn.Module):
def __init__(self,vocab_size,embed_size=512,d_model=512,d_ff=2018,num_heads=8,num_blocks=6):
super(Encoder,self).__init__()
self.blocks=nn.ModuleList([copy.deepcopy(Block(num_heads,d_model,d_ff)) for _ in range(num_blocks)])
self.embedding_layer=nn.Embedding(vocab_size,embed_size)
self.embed_size=embed_size
def foward(self,input_ids,input_lengths):
'''
input_ids.size()==(batch_size,seq_len)代表的是每一个单词对应的id
input_lengths.size()==(batch_size,)记录的是每一个句子的实际长度
'''
batch_size,max_seq_len=input_ids.size()
inputs_mask=[]
for i in range(batch_size):
inputs_mask.append([1]*input_lengths[i]+[0]*(max_seq_len-input_lengths[i]))
inputs_mask=torch.LongTensor(inputs_mask)
inputs=self.embedding_layer(input_ids)+positionEncoding(seq_len=max_seq_len,d_model=self.embed_size)
for block in self.blocks:
inputs=block(inputs,inputs_mask)
#经过了6个block后的输出
#(batch_size,max_seq_len,d_model)
return inputs#encoder输出
decoder
decoder也是有6个blocks,每一个block如上图,与encoder区别在于多出来一个encoder-decoder注意力,目的是关注encoder的输出语义。
encoder-decoder attention
class SubBlock_for_ED_att(nn.Module):
def __init__(self,d_model,num_heads):
super(SubBlock_for_ED_att,self).__init__()
self.linears_for_qkv=nn.ModuleList([copy.deepcopy(nn.Linear(d_model,d_model)) for _ in range(3)])
#注意此时的q是decoder_input,k,v是encoder_outputs
self.linears_for_combine=nn.Linear(d_model,d_model)
self.ln_layer=LayerNorm(dim=d_model)
self.d_model=d_model
self.num_heads=num_heads
self.per_head_size=self.d_model//self.num_heads
def forward(self,encoder_outputs,decoder_inputs,src_mask):
'''
将decoder_inputs看作q,encoder_outputs看作k,v,同样做多头注意力
encoder_outputs.size()==(batch_size,src_seq_len,d_model)
decoder_inputs.size()==(batch_size,tgt_seq_len,d_model)
src_mask是encoder_outputs的mask
'''
batch_size=encoder_outputs.size(0)
query=self.linears_for_qkv[0](decoder_inputs).view(batch_size,-1,self.num_heads,self.per_head_size).transpose(1,2)
key=self.linears_for_qkv[1](encoder_outputs).view(batch_size,-1,self.num_heads,self.per_head_size).transpose(1,2)
value=self.linears_for_qkv[2](encoder_outputs).view(batch_size,-1,self.num_heads,self.per_head_size).transpose(1,2)
if src_mask!=None:
src_mask.unsqueeze_(1)
src_mask.unsqueeze_(1)
att_res=scaled_dot_product_attention(query=query,key=key,value=value,mask=src_mask)
#att_res.size()==(batch_size,num_heads,seq_len_q,dim_v) seq_len_q指的是tgt_seq_len
multihead_res=self.linears_for_combine(att_res.transpose(1,2).view(batch_size,-1,self.d_model))
return self.ln_layer(multihead_res+decoder_inputs)
decoder block
class BlockDecoder(nn.Module):
def __init__(self,num_heads,d_model,d_ff):
super(BlockDecoder,self).__init__()
self.subBlock1=SubBlock1(d_model,num_heads)
self.subBlock2=SubBlock2(d_model,d_ff)
self.subBlockED_att=SubBlock_for_ED_att(d_model,num_heads)
def forward(self,decoder_inputs,encoder_outputs,inputs_mask,outputs_mask):
subBlock1_out=self.subBlock1(decoder_inputs,inputs_mask)
subBlockED_att_out=self.subBlockED_att(encoder_outputs,decoder_inputs,src_mask=outputs_mask)
subBlock2_out=self.subBlock2(subBlockED_att_out)
return subBlock2_out