attention 应用于RNN
注意力应用于文本:Bahdanau等2014发布的
Neural Machine Translation by Jointly Learning to Align and Translate
和 Luong等2015年发布的
Effective Approaches to Attention-based Neural Machine Translation
两篇论文。
解码器:
上下文向量:RNN 加上注意力机制之后,编码器把
所有
时间步的 hidden state(隐藏层状态)传递给解码器,而不是只传递最后一个 hidden state(隐藏层状态),解码器对编码器传输的所有隐藏层状态给分数,经过softmax,变成注意力分数,注意力分数对隐藏层状态加权和得到上下文向量。
- 注意力模型的解码器 RNN 的输入包括:一个embedding 向量,和一个初始化好的解码器 hidden state(隐藏层状态)。
- RNN 处理上述的 2 个输入,产生一个输出和一个新的 hidden state(隐藏层状态
h4
向量),其中
输出会被忽略
。- 注意力的步骤
:我们使用编码器的 hidden state(隐藏层状态)和 h4 向量来计算这个时间步的
上下文向量
(相似度加权和)(C4)。- 我们把 h4 和 C4
拼接起来
,得到一个向量。- 我们把这个向量输入一个
前馈神经网络
(这个网络是和整个模型一起训练的)。- 前馈神经网络的输出的
输出表示这个时间步输出的单词
。- 在下一个时间步重复这个步骤。
[基于 TensorFlow 的 神经机器翻译 (seq2seq)
指南
。]
transformer 基于self-attention 结构
自注意力的优点:可以并行计算。
编码器:
编码器(Encoder)接收的输入都是一个向量列表,输出也是大小同样的向量列表,然后接着输入下一个编码器。(第一 个/层 编码器的输入是
词向量
,
而后面的编码器的输入是
上一个编码器的输出
。)
注意力分数:
自注意力:Query=Key=Value (是词向量分别和 3 个矩阵相乘得到的,而这个矩阵是我们要学习的参数。)
score:计算 “
Thinking
” 对应的 Query 向量和其他位置的每个词的 Key 向量的点积,而得到的。如果我们计算句子中第一个位置单词的 Attention Score(注意力分数),那么第一个分数就是 q1 和 k1 的内积,第二个分数就是 q1 和 k2 的点积,再除以
(
d
k
e
y
)
\sqrt(d_{key})
(
d
k
e
y
)
(除以一个数是为了
在反向传播时,求取梯度更加稳定
:导数不会梯度爆炸)。在经过一个softmax层。将每个分数分别与每个 Value 向量相乘,就是对不同的词施加注意力的过程。
将上面的向量输入前馈神经网络。
**tips:**用矩阵可以加速计算所有词的位置的向量。
多头
注意力:
- 扩展了模型
关注不同位置
的能力。 - 多头注意力机制赋予 attention 层多个“子表示空间”。多组
W
Q
,
W
K
W
V
W^Q, W^K W^V
W
Q
,
W
K
W
V
的权重矩阵(在 Transformer 的论文中,使用了 8 组注意力(attention heads)。
然后将8组矩阵拼接成一个矩阵,和权重矩阵
W
Q
W^Q
W
Q
相乘,再输入前馈神经网络(每行代表一个词)。
编码实现 transformer
torch.nn.MultiheadAttention(embed_dim, num_heads, dropout=0.0, bias=True, add_bias_kv=False, add_zero_attn=False, kdim=None, vdim=None)
# embed_dim:最终输出的 K、Q、V 矩阵的维度,这个维度需要和词向量的维度一样
# num_heads:设置多头注意力的数量。如果设置为 1,那么只使用一组注意力。如果设置为其他数值,那么 - - num_heads 的值需要能够被 embed_dim 整除
# dropout:这个 dropout 加在 attention score 后面
解码器
QUERY是下层输入,K、V是编码器的输出。
编码器的multi-head 函数:
torch.nn.MultiheadAttention(embed_dim, num_heads, dropout=0.0, bias=True, add_bias_kv=False, add_zero_attn=False, kdim=None, vdim=None)
#传入参数
forward(query, key, value, key_padding_mask=None, need_weights=True, attn_mask=None)
####例子
## nn.MultiheadAttention 输入第0维为length
# batch_size 为 64,有 12 个词,每个词的 Query 向量是 300 维
query = torch.rand(12,64,300)
# batch_size 为 64,有 10 个词,每个词的 Key 向量是 300 维
key = torch.rand(10,64,300)
# batch_size 为 64,有 10 个词,每个词的 Value 向量是 300 维
value= torch.rand(10,64,300)
embed_dim = 300
num_heads = 1
# 输出是 (attn_output, attn_output_weights)
multihead_attn = nn.MultiheadAttention(embed_dim, num_heads)
attn_output = multihead_attn(query, key, value)[0]
# output: torch.Size([12, 64, 300])
# batch_size 为 64,有 12 个词,每个词的向量是 300 维
print(attn_output.shape)
torch.nn.linear
y
=
x
A
T
b
y = xA^T + b
y
=
x
A
T
b
import torch
x = torch.randn(128, 20) # 输入的维度是(128,20)
m = torch.nn.Linear(20, 30) # 20,30是指维度
output = m(x)
print('m.weight.shape:\n ', m.weight.shape)
print('m.bias.shape:\n', m.bias.shape)
print('output.shape:\n', output.shape)
# ans = torch.mm(input,torch.t(m.weight))+m.bias 等价于下面的
ans = torch.mm(x, m.weight.t()) + m.bias
print('ans.shape:\n', ans.shape)
print(torch.equal(ans, output))
#输出
m.weight.shape:
torch.Size([30, 20])
m.bias.shape:
torch.Size([30])
output.shape:
torch.Size([128, 30])
ans.shape:
torch.Size([128, 30])
True
位置编码的可视化
左半部分的值是由 sine 函数产生的,而右半部分的值是由 cosine 函数产生的,然后将他们拼接起来,得到每个位置编码向量。由欧拉公式,这可以唯一指定位置。
论文中的方法和上面图中的稍有不同,它不是直接拼接两个向量,而是将两个向量交织在一起
在解码器里,Self Attention 层只允许关注到输出序列中早于当前位置之前的单词。具体做法是:在 Self Attention 分数经过 Softmax 层之前,屏蔽当前位置之后的那些位置。
transformer 前向过程
两层编码器,一层解码器。
编码器一般有多层,第一个编码器的输入是一个序列,最后一个编码器输出是一组注意力向量 K 和 V。这些注意力向量将会输入到每个解码器的Encoder-Decoder Attention层,这有助于解码器把注意力集中中输入序列的合适位置。Encoder-Decoder Attention层使用前一层的输出作为当前解码器的额输入,加上位置编码来构造 Query 矩阵,而 Key 矩阵和 Value 矩阵来自于解码器最终的输出。
和编码器中的 Self Attention 层不太一样:在解码器里,Self Attention 层只允许关注到输出序列中早于当前位置之前的单词。具体做法是:在 Self Attention 分数经过 Softmax 层之前,屏蔽当前位置之后的那些位置。
解码器最终的输出怎么转换为单词:
在softmax后面连接一个线性层,把解码器输出的向量,映射到一个更长的向量,这个向量称为 logits 向量(logits向量的长度是词汇表的长度)。
transformer 训练过程
由于模型每个时间步只产生一个输出,我们可以认为:模型是从概率分布中选择概率最大的词,并且丢弃其他词。这种方法叫做贪婪解码(greedy decoding)。另一种方法是每个时间步保留两个最高概率的输出词,然后在下一个时间步,重复执行这个过程:假设第一个位置概率最高的两个输出的词是”I“和”a“,这两个词都保留,然后根据第一个词计算第二个位置的词的概率分布,再取出 2 个概率最高的词,对于第二个位置和第三个位置,我们也重复这个过程。这种方法称为集束搜索(beam search),在我们的例子中,beam_size 的值是 2(含义是:在所有时间步,我们保留两个最高概率),top_beams 的值也是 2(表示我们最终会返回两个翻译的结果)。beam_size 和 top_beams 都是你可以在实验中尝试的超参数。
transformer 训练
1. 词表,文本切割
如果有100哥句子,那么就有100个切割的列表。指定最大长度 假设=128。
2. 词嵌入处理
在torch里基于
torch.nn.Embedding
实现,实例化时需要设置的参数为词表的大小和被映射的向量的维度比如
embed = nn.Embedding(10,8)
。向量的维度通俗来说就是向量里面有多少个数。注意,第一个参数是词表的大小,如果你目前最多有8个词,通常填写10(多一个位置留给unk和pad),你后面万一进入与这8个词不同的词就映射到unk上,序列padding的部分就映射到pad上。num_features或者embed_dim是向量的维数。
(batch_size, seq_length, embed_dim) 一般深度学习只改变embed_dim
Transformer 代码
# 示例 平均分布的赋值
w = torch.empty(3,5)
print(nn.init.xavier_uniform_(w))
KaTeX parse error: Expected ‘}’, got ‘_’ at position 57: …{6}{\text { fan_̲in }+\text { fa…
fan_in, fan_out = _calculate_fan_in_and_fan_out(tensor)
fan_in = tensor.size(1) * receptive_field_size
fan_out = tensor.size(0) * receptive_field_size
所以上面的是 sqrt*(6/8)
# 位置编码
Tensor = torch.Tensor
def positional_encoding(X, num_features, dropout_p=0.1, max_len=512) -> Tensor:
r'''
给输入加入位置编码
参数:
- num_features: 输入进来的维度
- dropout_p: dropout的概率,当其为非零时执行dropout
- max_len: 句子的最大长度,默认512
形状:输出的是加上PE的E
- 输入: [batch_size, seq_length, num_features]
- 输出: [batch_size, seq_length, num_features]
例子:
>>> X = torch.randn((2,4,10))
>>> X = positional_encoding(X, 10)
>>> print(X.shape)
>>> torch.Size([2, 4, 10])
'''
dropout = nn.Dropout(dropout_p)
P = torch.zeros((1,max_len,num_features))
X_ = torch.arange(max_len,dtype=torch.float32).reshape(-1,1) / torch.pow(
10000,torch.arange(0,num_features,2,dtype=torch.float32) /num_features)
P[:,:,0::2] = torch.sin(X_)
P[:,:,1::2] = torch.cos(X_)
X = X + P[:,:X.shape[1],:].to(X.device) # X + 位置编码(按照max_length 给定编码)
return dropout(X)
# 位置编码例子
X = torch.randn((2,4,10))
X = positional_encoding(X, 10)
- 格式化写法简化
- 时刻用 reshape确认维数、数值类型
- 切片 [:,:,0::2] ,广播:/num_features
- torch.pow 幂指数
位置编码原理
transformer位置编码对token进行,衡量token的位置差异(相对位置编码)。结合方式是每个token输入E与位置编码PE相加,所以PE的维数要与E一致。
位置编码的要求:
- 它能为每个时间步 t 输出一个独一无二的编码;
- 不同长度的句子之间,任何两个时间步之间的距离应该保持一致(线性);
- 模型应该能毫不费力地泛化到更长的句子(根据d给定)。它的值应该是有界的;
- 它必须是确定性的。
位置编码分类:函数型、表格型。transformer的位置编码:函数型相对位置编码。
位置编码过程
:
[n,d] .给定一个长度为[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-AuwzsVyK-1629276734855)(https://www.zhihu.com/equation?tex=n)]的输入序列,让[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-iXTJgAhJ-1629276734856)(https://www.zhihu.com/equation?tex=t)]表示词在序列中的位置,[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-oiamGuQW-1629276734857)(https://www.zhihu.com/equation?tex=%5Coverrightarrow%7Bp_t%7D+%5Cin+%5Cmathbb%7BR%7D%5Ed)]表示[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-387BbYTv-1629276734858)(https://www.zhihu.com/equation?tex=t)]位置对应的向量,[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-Nw8DKX4D-1629276734859)(https://www.zhihu.com/equation?tex=d)]是token词向量的维度。[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-AbS2bzXc-1629276734860)(https://www.zhihu.com/equation?tex=f%3A+%5Cmathbb%7BN%7D+%5Crightarrow+%5Cmathbb%7BR%7D%5Ed)]是生成位置向量[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-Ny95Ght2-1629276734860)(https://www.zhihu.com/equation?tex=%5Coverrightarrow%7Bp_t%7D)]的函数,定义如下:
p
t
→
(
i
)
=
f
(
t
)
(
i
)
:
=
{
sin
(
ω
k
⋅
t
)
,
if
i
=
2
k
)
cos
(
ω
k
⋅
t
)
,
if
i
=
2
k
1
\overrightarrow{p_{t}}{(i)}=f(t){(i)}:=\left{\begin{array}{ll} \sin \left(\omega_{k} \cdot t\right), & \text { if } i=2 k) \ \cos \left(\omega_{k} \cdot t\right), & \text { if } i=2 k+1 \end{array}\right.
p
t
(
i
)
=
f
(
t
)
(
i
)
:
=
{
sin
(
ω
k
⋅
t
)
,
cos
(
ω
k
⋅
t
)
,
if
i
=
2
k
)
if
i
=
2
k
1
ω
k
=
1
1000
0
2
k
/
d
\omega_{k}=\frac{1}{10000^{2 k / d}}
ω
k
=
1
0
0
0
0
2
k
/
d
1
对处在t位置的d维长度的向量添加位置编码为:
p
t
→
=
[
sin
(
ω
1
⋅
t
)
cos
(
ω
1
⋅
t
)
sin
(
ω
2
⋅
t
)
cos
(
ω
2
⋅
t
)
⋮
sin
(
ω
d
/
2
⋅
t
)
cos
(
ω
d
/
2
⋅
t
)
]
\overrightarrow{p_{t}}=\left[\begin{array}{c} \sin \left(\omega_{1} \cdot t\right) \ \cos \left(\omega_{1} \cdot t\right) \ \sin \left(\omega_{2} \cdot t\right) \ \cos \left(\omega_{2} \cdot t\right) \ \vdots \ \sin \left(\omega_{d / 2} \cdot t\right) \ \cos \left(\omega_{d / 2} \cdot t\right) \end{array}\right]
p
t
=
⎣
⎢
⎢
⎢
⎢
⎢
⎢
⎢
⎢
⎢
⎡
sin
(
ω
1
⋅
t
)
cos
(
ω
1
⋅
t
)
sin
(
ω
2
⋅
t
)
cos
(
ω
2
⋅
t
)
⋮
sin
(
ω
d
/
2
⋅
t
)
cos
(
ω
d
/
2
⋅
t
)
⎦
⎥
⎥
⎥
⎥
⎥
⎥
⎥
⎥
⎥
⎤
举个例子
[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-hCVf2SU5-1629276734861)(C:\Users\zhu\AppData\Roaming\Typora\typora-user-images\image-20210818144948639.png)]
为什么这种编码可以表示相对距离
相对距离
:位置pos和pos+k的token的距离能够在PE中表示出来。
证明如下:
回顾三角公式:
sin
(
α
β
)
=
sin
α
⋅
cos
β
cos
α
⋅
sin
β
cos
(
α
β
)
=
cos
α
⋅
cos
β
−
sin
α
⋅
sin
β
\begin{array}{l} \sin (\alpha+\beta)=\sin \alpha \cdot \cos \beta+\cos \alpha \cdot \sin \beta \ \cos (\alpha+\beta)=\cos \alpha \cdot \cos \beta-\sin \alpha \cdot \sin \beta \end{array}
sin
(
α
β
)
=
sin
α
⋅
cos
β
cos
α
⋅
sin
β
cos
(
α
β
)
=
cos
α
⋅
cos
β
−
sin
α
⋅
sin
β
计算pos+k位置的PE:
P
E
(
p
o
s
k
,
2
i
)
=
sin
(
w
i
⋅
(
p
o
s
k
)
)
=
sin
(
w
i
p
o
s
)
cos
(
w
i
k
)
cos
(
w
i
p
o
s
)
sin
(
w
i
k
)
=
cos
(
w
i
k
)
P
E
(
p
o
s
,
2
i
)
sin
(
w
i
k
)
P
E
(
p
o
s
,
2
i
1
)
P
E
(
p
o
s
k
,
2
i
1
)
=
cos
(
w
i
⋅
(
p
o
s
k
)
)
=
cos
(
w
i
p
o
s
)
cos
(
w
i
k
)
−
sin
(
w
i
p
o
s
)
sin
(
w
i
k
)
=
cos
(
w
i
k
)
P
E
(
p
o
s
,
2
i
1
)
−
sin
(
w
i
k
)
P
E
(
p
o
s
,
2
i
)
\begin{array}{l} P E_{(p o s+k, 2 i)}=\sin \left(w_{i} \cdot(p o s+k)\right)\=\sin \left(w_{i} p o s\right) \cos \left(w_{i} k\right)+\cos \left(w_{i} p o s\right) \sin \left(w_{i} k\right)\ =\cos \left(w_{i} k\right) P E_{(p o s, 2 i)}+\sin \left(w_{i} k\right) P E_{(p o s, 2 i+1)} \ \ P E_{(p o s+k, 2 i+1)}=\cos \left(w_{i} \cdot(p o s+k)\right)\=\cos \left(w_{i} p o s\right) \cos \left(w_{i} k\right)-\sin \left(w_{i} p o s\right) \sin \left(w_{i} k\right) \=\cos \left(w_{i} k\right) P E_{(p o s, 2 i+1)}-\sin \left(w_{i} k\right) P E_{(p o s, 2 i)} \end{array}
P
E
(
p
o
s
k
,
2
i
)
=
sin
(
w
i
⋅
(
p
o
s
k
)
)
=
sin
(
w
i
p
o
s
)
cos
(
w
i
k
)
cos
(
w
i
p
o
s
)
sin
(
w
i
k
)
=
cos
(
w
i
k
)
P
E
(
p
o
s
,
2
i
)
sin
(
w
i
k
)
P
E
(
p
o
s
,
2
i
1
)
P
E
(
p
o
s
k
,
2
i
1
)
=
cos
(
w
i
⋅
(
p
o
s
k
)
)
=
cos
(
w
i
p
o
s
)
cos
(
w
i
k
)
−
sin
(
w
i
p
o
s
)
sin
(
w
i
k
)
=
cos
(
w
i
k
)
P
E
(
p
o
s
,
2
i
1
)
−
sin
(
w
i
k
)
P
E
(
p
o
s
,
2
i
)
就可以将
P
E
p
o
s
k
PE_{pos+k}
P
E
p
o
s
k
写成一个矩阵的乘法。
[
P
E
(
p
o
s
k
,
2
i
)
P
E
(
p
o
s
k
,
2
i
1
)
]
=
[
u
v
−
v
u
]
×
[
P
E
(
p
o
s
,
2
i
)
P
E
(
p
o
s
,
2
i
1
)
]
\left[\begin{array}{c} P E_{(p o s+k, 2 i)} \ P E_{(p o s+k, 2 i+1)} \end{array}\right]=\left[\begin{array}{cc} u & v \ -v & u \end{array}\right] \times\left[\begin{array}{c} P E_{(p o s, 2 i)} \ P E_{(p o s, 2 i+1)} \end{array}\right]
[
P
E
(
p
o
s
k
,
2
i
)
P
E
(
p
o
s
k
,
2
i
1
)
]
=
[
u
−
v
v
u
]
×
[
P
E
(
p
o
s
,
2
i
)
P
E
(
p
o
s
,
2
i
1
)
]
所以
P
E
p
o
s
k
PE_{pos+k}
P
E
p
o
s
k
和
P
E
p
o
s
PE_{pos}
P
E
p
o
s
是线性关系,两者的倍数差别取决于k(位置差别)。
如何更直接的得到k,将
P
E
p
o
s
k
PE_{pos+k}
P
E
p
o
s
k
和
P
E
p
o
s
PE_{pos}
P
E
p
o
s
取内积:
P
E
p
o
s
⋅
P
E
p
o
s
k
=
∑
i
=
0
d
2
−
1
sin
(
w
i
p
o
s
)
⋅
sin
(
w
i
(
p
o
s
k
)
)
cos
(
w
i
p
o
s
)
⋅
cos
(
w
i
(
p
o
s
k
)
)
=
∑
i
=
0
d
2
−
1
cos
(
w
i
(
p
o
s
−
(
p
o
s
k
)
)
=
∑
i
=
0
d
2
−
1
cos
(
w
i
k
)
\begin{aligned} P E_{p o s} \cdot P E_{p o s+k} &=\sum_{i=0}^{\frac{d}{2}-1} \sin \left(w_{i} p o s\right) \cdot \sin \left(w_{i}(p o s+k)\right)+\cos \left(w_{i} p o s\right) \cdot \cos \left(w_{i}(p o s+k)\right) \ &=\sum_{i=0}^{\frac{d}{2}-1} \cos \left(w_{i}(p o s-(p o s+k))\right.\ &=\sum_{i=0}^{\frac{d}{2}-1} \cos \left(w_{i} k\right) \end{aligned}
P
E
p
o
s
⋅
P
E
p
o
s
k
=
i
=
0
∑
2
d
−
1
sin
(
w
i
p
o
s
)
⋅
sin
(
w
i
(
p
o
s
k
)
)
cos
(
w
i
p
o
s
)
⋅
cos
(
w
i
(
p
o
s
k
)
)
=
i
=
0
∑
2
d
−
1
cos
(
w
i
(
p
o
s
−
(
p
o
s
k
)
)
=
i
=
0
∑
2
d
−
1
cos
(
w
i
k
)
k越大,cos越小,内积越小,可对应得到两个token的相对位置差别。
缺点
:该编码方式只能找到相对位置,不能找到方向关系,即谁在谁的后面。如对于PE无法分辨出
P
E
p
o
s
k
PE_{pos+k}
P
E
p
o
s
k
和
P
E
p
o
s
−
k
PE_{pos-k}
P
E
p
o
s
−
k
的区别。
多头注意力
参数初始化
qkv维数一样,可以放在一起训练,或者q 不等于kv则分开训练。
self.q_proj_weight = Parameter(torch.empty((embed_dim, embed_dim)))
#torch.empty是按照所给的形状形成对应的tensor,特点是填充的值还未初始化,类比torch.randn(标准正态分布),这就是一种初始化的方式。在PyTorch中,变量类型是tensor的话是无法修改值的,而Parameter()函数可以看作为一种类型转变函数,将不可改值的tensor转换为可训练可修改的模型参数,即与model.parameters绑定在一起,register_parameter的意思是是否将这个参数放到model.parameters,None的意思是没有这个参数。
xavier_uniform_(self.in_proj_weight)
#reset_parameters()函数,这个是用来初始化参数数值的。xavier_uniform意思是从连续型均匀分布里面随机取样出值来作为初始化的值,xavier_normal_取样的分布是正态分布。正因为初始化值在训练神经网络的时候很重要,所以才需要这两个函数。
命名:in_proj_bias的意思就是一开始的线性变换的偏置
前向训练网络
- query, key, value通过_in_projection_packed变换得到q,k,v
q, k, v = _in_projection_packed(query, key, value, in_proj_weight, in_proj_bias)
对于
nn.functional.linear
函数,其实就是一个线性变换,与
nn.Linear
不同的是,前者可以提供权重矩阵和偏置,执行[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-EPFyy596-1629276734861)(https://camo.githubusercontent.com/a5016b70476aeff20d93e8632ba67120029d725c/687474703a2f2f6c617465782e636f6465636f67732e636f6d2f7376672e6c617465783f793d78575e542b62)],而后者是可以自由决定输出的维度。
- 遮挡机制
对于attn_mask来说,若为2D,形状如
(L, S)
,L和S分别代表着目标语言和源语言序列长度,若为3D,形状如
(N * num_heads, L, S)
,N代表着batch_size,num_heads代表注意力头的数目。若为attn_mask的dtype为ByteTensor,非0的位置会被忽略不做注意力;若为BoolTensor,True对应的位置会被忽略;若为数值,则会直接加到attn_weights。
因为在decoder解码的时候,只能看该位置和它之前的,如果看后面就犯规了,所以需要attn_mask遮挡住。【直接用pytorch里面的】
与
attn_mask
不同的是,
key_padding_mask
是用来遮挡住key里面的值,详细来说应该是
<PAD>
,被忽略的情况与attn_mask一致。
logical_or
#输入两个tensor,并对这两个tensor里的值做逻辑或运算,只有当两个值均为0的时候才为False,其他时候均为True
a = torch.tensor([0,1,10,0],dtype=torch.int8)
b = torch.tensor([4,0,1,0],dtype=torch.int8)
print(torch.logical_or(a,b))
# tensor([ True, True, True, False])
#masked_fill,输入是一个mask,和用以填充的值。mask由1,0组成,0的位置值维持不变,1的位置用新值填充。
r = torch.tensor([[0,0,0,0],[0,0,0,0]])
mask = torch.tensor([[1,1,1,1],[0,0,0,0]])
print(r.masked_fill(mask,1))
# tensor([[1, 1, 1, 1],
# [0, 0, 0, 0]])
- 点积注意力
torch.bmm 是batch矩阵乘法,If
input
is a
(
b
×
n
×
m
)
(b \times n \times m)
(
b
×
n
×
m
)
tensor,
mat2
is a$ (b \times m \times p)
t
e
n
s
o
r
,
‘
o
u
t
‘
w
i
l
l
b
e
a
tensor, out
will be a
t
e
n
s
o
r
,
‘
o
u
t
‘
w
i
l
l
b
e
a
(b \times n \times p)$ tensor.
#transpose 交换函数的维数,contiguous()断开两个变量的依赖,相当于改变之后的不会影响之前的变量了。
q = q.contiguous().view(tgt_len, bsz * num_heads, head_dim).transpose(0, 1)