标签:inline spl hang 输入 class 改变 变换 component variable
将最后一维(embedding_dim) 拆成h份,需要保证embedding_dim能够被h整除。每个tensor的最后两个维度表示一个头,QKV各自都有h个头,接下来需要把这些头分别进行计算
按顺序取出上图中的一组QKV,计算:
(1)计算得到各个字之间的关系(相似度).这里的d的维度是 (batch_size, h, seq_len, seq_len)
(2)用mask矩阵遮盖掉超出句子长度的部分。将句子中用来pading的字符全部替换成 inf, 这样 计算softmax的时候它们的值会为0,就不会参与到接下来与V的计算当中
(3) \(d_k\) 是为了改变已经偏离的方差。我的理解是,由于矩阵转置后相乘会有很多内积运算,而内积运算将\(d_k\)个数相加时会改变数据的分布。而这个分布的趋势是 \(mean=0, variance=d_k\)。为了使方差回归到1,把所有结果都除上一个\(\sqrt{d_k}\),这样求平方时会抵消已有的方差\(d_k\)
# 均值为0,方差为1
a = np.random.randn(2,3000)
b = np.random.randn(3000,2)
c = a.dot(b)
print(np.var(a))
print(np.mean(c))
print(np.var(c))
# 1.0262973662546435
# 25.625943965792157
# 1347.432397285718
To illustrate why the dot products get large, assume that the components of q and k are independent random variables with > mean 0 and variance 1. Then their dot product, \(q \cdot k=\sum_{i=1}^{d_{k}} q_{i} k_{i}\), has mean 0 and variance dk.
(4)计算各个词义所占的比例 \(d \cdot v\),按照权重融合了各个字的语义。最后将多个头的结果拼接成一个完成的embedding作为self-attendion的输出。
(batch_size, h, seq_len, seq_len)
*batch_size, h, seq_len, embedding/h
部分代码如下:
# (batch, seq_len, h, embed/head) -> (batch, h, seq_len, embed/head)
q = self.qry(y).view(y.size(0), y.size(1), self.head, -1).transpose(1, 2)
k = self.key(x).view(x.size(0), x.size(1), self.head, -1).transpose(1, 2)
v = self.val(x).view(x.size(0), x.size(1), self.head, -1).transpose(1, 2)
d = torch.matmul(q, k.transpose(-2, -1)) / math.sqrt(k.size(-1)) # 相似度 (batch , h, seq, seq)
d = d.masked_fill(m, -float(‘inf‘)) # 把所有为true的地方替换成inf,这里是遮盖掉句子内部的pad
a = F.softmax(d, dim=-1) # (batch , h, seq, seq)
# (batch , h, seq_len, seq_len) * (batch, h, seq_len, embedding/h)
# => (batch, h, seq_len, embedding/h)
# => (batch, seq_len, h, embedding/h)
c = torch.matmul(a, v).transpose(1, 2)
# (batch, seq_len, embedding)
c = c.contiguous().view(c.size(0), c.size(1), -1)
1). 字向量与位置编码:
2). 自注意力机制:
3). 残差连接与\(Layer \ Normalization\)
4). 两层线性映射并用激活函数激活, 比如说\(ReLU\):
5). 重复3).:
def get_pad(self, x):
"""
根据句子的实际长度获取句子的句子的mask。用于计算attention的mask,它不是对角矩阵
维度是 (batch, head, seq_len, seq_len)
:param x:
:return: mask (batch, head, seq_len, seq_len)
"""
seq_len = x.size(1)
pad = (x == 0)
for _ in range(2):
pad = torch.unsqueeze(pad, dim=1)
return pad.repeat(1, self.head, seq_len, 1)
def get_att(head: int, seq_len: int):
"""
计算mask self attention的mask,对角矩阵
:param head: int
:param seq_len: int
:return:
"""
# 上三角矩阵, 不保留对角线
att = torch.triu(torch.ones(seq_len, seq_len).byte(), diagonal=1)
for _ in range(2):
# torch.squeeze() 删掉维度为1的维度:(1,3)==> (3)
# torch.unsqueeze() 扩充维度,在指定位置加上维数为1的维度:(3)==> (1,3)
att = torch.unsqueeze(att, dim=0)
# 像瓦片一样平铺
return att.repeat(1, head, 1, 1)
def forward(self, y, x, my, mx):
"""
:param y: 带上positional encoder的embedding。 (batch, seq_len, embedding)
:param x: encoder的输出 (batch, seq_len, embedding)
:param my: y 的mask (batch, head, seq_len, seq_len)
:param mx: x 的mask (batch, head, seq_len, seq_len)
:return:
"""
r = y # 暂时保存用于计算残差网络
y = self.mul_att(y, y, my, 0)
y = self.lns[0](y + r)
r = y
y = self.mul_att(x, y, mx, 1)
y = self.lns[1](y + r)
r = y
y = self.lal(y)
return self.lns[2](y + r)
标签:inline spl hang 输入 class 改变 变换 component variable
原文地址:https://www.cnblogs.com/twilight0402/p/13401299.html