我最近阅读了拥抱脸项目的bert源代码。我注意到所谓的“可学习的位置编码”在实现时似乎是指特定的nn.参数层。
def __init__(self):
super()
positional_encoding = nn.Parameter()
def forward(self, x):
x += positional_encoding
↑可能是这种感觉,然后执行可学习的位置编码。不管是不是这么简单,我不确定我理解的对不对,我想问有经验的人。
此外,我注意到一个经典的bert结构,它的位置实际上只在初始输入时编码一次。这是否意味着后续的bert层,对于彼此来说,失去了捕获位置信息的能力?
BertModel(
(embeddings): BertEmbeddings(
(word_embeddings): Embedding(30522, 768, padding_idx=0)
(position_embeddings): Embedding(512, 768)
(token_type_embeddings): Embedding(2, 768)
(LayerNorm): LayerNorm((768,), eps=1e-12, elementwise_affine=True)
(dropout): Dropout(p=0.1, inplace=False)
)
(encoder): BertEncoder(
(layer): ModuleList(
(0): BertLayer(...)
...
(pooler): BertPooler(...)
如果前一层的结果在下一个BERT层之前进行重新定位编码,我会得到更好的结果吗?
2条答案
按热度按时间aoyhnmkz1#
位置嵌入的目的是什么?
在transformers(包括BERT)中,不同token之间的唯一交互是通过自我注意层完成的。如果你仔细观察这些层实现的数学运算,你会注意到这些层是排列equivariant:也就是说,
和
是一样的,因为两个句子中的单词(=tokens)是一样的,只是它们的顺序不同。
正如你所看到的,这种“置换等变性”在很多情况下并不是一个理想的属性。
为了打破这种对称/等变性,可以简单地“编码”每个单词/标记在句子中的实际位置。举例来说:
不再等同于
这就是位置编码/嵌入的目的--使自我注意层对标记的顺序敏感。
现在回答你们的问题:
1.可学习的位置编码实际上用简单的单个
nn.Parameter
来实现。位置编码只是添加到每个标记的“代码”,标记其在序列中的位置。因此,它只需要一个与输入序列大小相同的Tensor,每个位置具有不同的值。1.* 在Transformer架构中引入一次位置编码就足够了吗?* 是的!由于transformers堆叠了多个自我注意层,因此在处理开始时添加一次位置嵌入就足够了。位置信息被“融合”到每个标记学习的语义表示中。
视觉变形器(ViT)中这种效果的一个很好的可视化可以在下面的工作中找到:
在第3.1节和图3它们显示了位置信息如何在早期层中主导令牌的表示,但当您深入Transformer时,语义信息将接管。
lyr7nygr2#
@Shai的回答相当精彩。我也有同样的疑问,这个答案对我帮助很大。我想在这个主题上添加另一篇很好的论文,它提供了对位置编码的深入了解:Conditional Positional Encodings for Vision Transformers(arXiv 2021)。
在第4.2节表2中,CPVT-Ti plus显示了比CPVT-Ti更好的性能。CPVT-Ti plus为前五个编码器插入pos嵌入(而不是仅为CPVT-Ti的第一个编码器)。因此,它表明,你的猜测,“我会得到更好的结果,如果前一层的结果是重新定位编码之前,下一个BERT层?“可能是对的。