自拍偷在线精品自拍偷,亚洲欧美中文日韩v在线观看不卡

深度解析理解 Transformer 中的3大位置嵌入:從絕對(duì)位置嵌入到旋轉(zhuǎn)位置嵌入 精華

發(fā)布于 2025-3-10 00:00
瀏覽
0收藏

Transformer的關(guān)鍵組件之一是位置嵌入。你可能會(huì)問(wèn):為什么呢?因?yàn)門(mén)ransformer中的自注意力機(jī)制是排列不變的;這意味著它計(jì)算輸入中每個(gè)標(biāo)記從序列中其他標(biāo)記接收的注意力程度,但它沒(méi)有考慮標(biāo)記的順序。實(shí)際上,注意力機(jī)制將序列視為一個(gè)標(biāo)記集合。因此,我們需要另一個(gè)稱為位置嵌入的組件,它可以考慮標(biāo)記的順序,并對(duì)標(biāo)記嵌入產(chǎn)生影響。

但是,位置嵌入有哪些不同類型,它們又是如何實(shí)現(xiàn)的呢?

深度解析理解 Transformer 中的3大位置嵌入:從絕對(duì)位置嵌入到旋轉(zhuǎn)位置嵌入-AI.x社區(qū)

在本文中,我們將研究三種主要的位置嵌入類型:絕對(duì)位置嵌入、相對(duì)位置嵌入和旋轉(zhuǎn)位置嵌入(RoPE),并深入探討它們的實(shí)現(xiàn)方式。

1. 背景

在自然語(yǔ)言處理(NLP)中,序列中單詞的順序?qū)τ诶斫庹Z(yǔ)義非常重要,這對(duì)人類來(lái)說(shuō)也是如此。如果順序混亂,語(yǔ)義就會(huì)完全改變。例如“Sam sits down on the mat”(山姆坐在墊子上)和“The mat sits down on Sam”(墊子坐在山姆身上),僅僅重新排列單詞順序,語(yǔ)義就完全不同了。

Transformer是許多現(xiàn)代NLP系統(tǒng)的核心,它并行處理所有單詞。這種并行處理發(fā)生在注意力機(jī)制中,在該機(jī)制中,模型計(jì)算每個(gè)標(biāo)記從輸入上下文中其他標(biāo)記接收的注意力分?jǐn)?shù)。雖然并行處理本質(zhì)上有利于提高效率,但它導(dǎo)致模型丟失了所有關(guān)于單詞順序的信息。因此,Transformer有一個(gè)額外的組件,即位置嵌入,它創(chuàng)建包含序列中標(biāo)記位置或順序信息的向量。

位置嵌入有很多不同類型。三種主要的、廣為人知的類型是絕對(duì)位置嵌入、相對(duì)位置嵌入和旋轉(zhuǎn)位置嵌入(RoPE)。

2. 絕對(duì)位置嵌入

絕對(duì)位置嵌入就像是給句子中的每個(gè)標(biāo)記分配一個(gè)唯一的編號(hào)。在實(shí)際操作中,我們?yōu)樾蛄兄械拿總€(gè)位置創(chuàng)建一個(gè)向量。在最簡(jiǎn)單的情況下,每個(gè)標(biāo)記的位置嵌入是一個(gè)獨(dú)熱向量,除了標(biāo)記所在位置的索引處為1,其他位置均為0。然后,在將標(biāo)記嵌入輸入到Transformer之前,我們將這些位置嵌入向量添加到標(biāo)記嵌入中。

例如,在句子“I am a student”(我是一名學(xué)生)中,有4個(gè)標(biāo)記。每個(gè)標(biāo)記都有一個(gè)唯一的位置嵌入向量。假設(shè)嵌入維度為3,那么第一個(gè)標(biāo)記“I”將得到獨(dú)熱編碼[1, 0, 0],第二個(gè)標(biāo)記“am”將得到[0, 1, 0],依此類推。

雖然獨(dú)熱編碼是傳達(dá)位置嵌入概念的一種直接方法,但在實(shí)際中,有更好的方法來(lái)實(shí)現(xiàn)絕對(duì)位置嵌入。所有不同的實(shí)現(xiàn)方式都既簡(jiǎn)單又有效,但它們?cè)谔幚矸浅iL(zhǎng)的序列或比訓(xùn)練時(shí)更長(zhǎng)的序列時(shí)可能會(huì)遇到困難。

讓我們來(lái)看看這些實(shí)現(xiàn)方式。

2.1 實(shí)現(xiàn)方式

絕對(duì)位置嵌入的實(shí)現(xiàn)通常涉及創(chuàng)建一個(gè)大小為_(kāi)vocabulary * embeddingdim_的查找表。這意味著詞匯表中的每個(gè)標(biāo)記在查找表中都有一個(gè)條目,并且該條目的維度為_(kāi)embeddingdim_。

絕對(duì)位置嵌入主要有兩種類型:

  • 學(xué)習(xí)型:在學(xué)習(xí)型方法中,嵌入向量在訓(xùn)練過(guò)程中隨機(jī)初始化,然后進(jìn)行訓(xùn)練。原始的Transformer論文[5]以及像BERT、GPT和RoBERTa等流行模型都采用了這種方法。很快,我們將在代碼中看到這種方法的示例。這種方法的一個(gè)缺點(diǎn)是,它可能無(wú)法很好地泛化到比訓(xùn)練時(shí)更長(zhǎng)的序列,因?yàn)閷?duì)于那些位置,查找表中不存在相應(yīng)的條目。
  • 固定型:這種方法也稱為正弦位置編碼,在開(kāi)創(chuàng)性的“Attention Is All You Need”論文[5]中被提出。該方法使用不同頻率的正弦和余弦函數(shù)為每個(gè)位置創(chuàng)建獨(dú)特的模式。這種編碼的公式如下:

深度解析理解 Transformer 中的3大位置嵌入:從絕對(duì)位置嵌入到旋轉(zhuǎn)位置嵌入-AI.x社區(qū)

在上述公式中,是位置嵌入,是模型的維度,也稱為嵌入維度?;旧希恢脼榈奈恢们度胂蛄?,在偶數(shù)索引處由函數(shù)決定,在奇數(shù)索引處由函數(shù)決定。

這種方法的一個(gè)關(guān)鍵優(yōu)點(diǎn)是它能夠外推到訓(xùn)練期間未遇到的序列長(zhǎng)度;這在處理不同的輸入大小方面提供了很大的靈活性。

無(wú)論采用哪種類型(學(xué)習(xí)型或固定型),一旦創(chuàng)建了絕對(duì)位置嵌入,就會(huì)將它們添加到標(biāo)記嵌入中:

Final Embedding = Token Embedding + Positional Embedding

讓我們一起從RoBERTa模型的源代碼中查看學(xué)習(xí)型位置嵌入。代碼取自HuggingFace代碼庫(kù)。

class RobertaEmbeddings(nn.Module):
    # Copied from transformers.models.bert.modeling_bert.BertEmbeddings.__init__
    def __init__(self, config):
        super().__init__()
        self.word_embeddings = nn.Embedding(config.vocab_size, config.hidden_size, padding_idx=config.pad_token_id)
        self.position_embeddings = nn.Embedding(config.max_position_embeddings, config.hidden_size)
        self.token_type_embeddings = nn.Embedding(config.type_vocab_size, config.hidden_size)

        # self.LayerNorm is not snake-cased to stick with TensorFlow model variable name and be able to load
        # any TensorFlow checkpoint file
        self.LayerNorm = nn.LayerNorm(config.hidden_size, eps=config.layer_norm_eps)
        self.dropout = nn.Dropout(config.hidden_dropout_prob)
        # position_ids (1, len position emb) is contiguous in memory and exported when serialized
        self.position_embedding_type = getattr(config, "position_embedding_type", "absolute")
        self.register_buffer(
            "position_ids", torch.arange(config.max_position_embeddings).expand((1, -1)), persistent=False
        )
        self.register_buffer(
            "token_type_ids", torch.zeros(self.position_ids.size(), dtype=torch.long), persistent=False
        )

        # End copy
        self.padding_idx = config.pad_token_id
        self.position_embeddings = nn.Embedding(
            config.max_position_embeddings, config.hidden_size, padding_idx=self.padding_idx
        )

    def forward(
        self, input_ids=None, token_type_ids=None, position_ids=None, inputs_embeds=None, past_key_values_length=0
    ):
        if position_ids isNone:
            if input_ids isnotNone:
                # Create the position ids from the input token ids. Any padded tokens remain padded.
                position_ids = create_position_ids_from_input_ids(input_ids, self.padding_idx, past_key_values_length)
            else:
                position_ids = self.create_position_ids_from_inputs_embeds(inputs_embeds)

        if input_ids isnotNone:
            input_shape = input_ids.size()
        else:
            input_shape = inputs_embeds.size()[:-1]

        seq_length = input_shape[1]

        # Setting the token_type_ids to the registered buffer in constructor where it is all zeros, which usually occurs
        # when its auto-generated, registered buffer helps users when tracing the model without passing token_type_ids, solves
        # issue #5664
        if token_type_ids isNone:
            if hasattr(self, "token_type_ids"):
                buffered_token_type_ids = self.token_type_ids[:, :seq_length]
                buffered_token_type_ids_expanded = buffered_token_type_ids.expand(input_shape[0], seq_length)
                token_type_ids = buffered_token_type_ids_expanded
            else:
                token_type_ids = torch.zeros(input_shape, dtype=torch.long, device=self.position_ids.device)

        if inputs_embeds isNone:
            inputs_embeds = self.word_embeddings(input_ids)
        token_type_embeddings = self.token_type_embeddings(token_type_ids)

        embeddings = inputs_embeds + token_type_embeddings
        if self.position_embedding_type == "absolute":
            position_embeddings = self.position_embeddings(position_ids)
            embeddings += position_embeddings
        embeddings = self.LayerNorm(embeddings)
        embeddings = self.dropout(embeddings)
        return embeddings

注意在??__init__??方法中,以下這行代碼是如何用隨機(jī)值初始化學(xué)習(xí)型位置嵌入的:

self.position_embeddings = nn.Embedding(config.max_position_embeddings, config.hidden_size)

然后在??forward??方法中,將位置嵌入添加到標(biāo)記嵌入中:

if self.position_embedding_type == "absolute":
    position_embeddings = self.position_embeddings(position_ids)
    embeddings += position_embeddings

讓我們?cè)谖谋据斎肷弦黄疬\(yùn)行這段代碼,看看結(jié)果:

from transformers import RobertaConfig

config = RobertaConfig()
print(config)

輸出:

RobertaConfig {
  "attention_probs_dropout_prob": 0.1,
"bos_token_id": 0,
"classifier_dropout": null,
"eos_token_id": 2,
"hidden_act": "gelu",
"hidden_dropout_prob": 0.1,
"hidden_size": 768,
"initializer_range": 0.02,
"intermediate_size": 3072,
"layer_norm_eps": 1e-12,
"max_position_embeddings": 512,
"model_type": "roberta",
"num_attention_heads": 12,
"num_hidden_layers": 12,
"pad_token_id": 1,
"position_embedding_type": "absolute",
"transformers_version": "4.31.0",
"type_vocab_size": 2,
"use_cache": true,
"vocab_size": 50265
}

正如你在上面打印的配置參數(shù)中看到的,我們有??"position_embedding_type": "absolute"???,并且上下文窗口長(zhǎng)度為512:??"max_position_embeddings": 512???。讓我們獲取一個(gè)??RobertaEmbedding??對(duì)象:

emb = RobertaEmbeddings(config)
print(emb)

輸出:

RobertaEmbeddings(
  (word_embeddings): Embedding(50265, 768, padding_idx=1)
  (position_embeddings): Embedding(512, 768, padding_idx=1)
  (token_type_embeddings): Embedding(2, 768)
  (LayerNorm): LayerNorm((768,), eps=1e-12, elementwise_affine=True)
  (dropout): Dropout(p=0.1, inplace=False)
)

你可以看到上面的??RobertaEmbedding???層,有一個(gè)??(word_embeddings): Embedding(50265, 768, padding_idx=1)???,這是一個(gè)隨機(jī)初始化的嵌入矩陣,形狀為??50265*768??;這意味著詞匯表大小為50265,每個(gè)嵌入向量是768維的。

然后我們看到??(position_embeddings): Embedding(512, 768, padding_idx=1)???,這是位置嵌入向量,同樣是隨機(jī)初始化的,并且是768維向量。注意這個(gè)嵌入矩陣的大小是??512*768??,這表明我們只為512個(gè)位置提供位置嵌入。因此,如果在推理時(shí)出現(xiàn)長(zhǎng)度超過(guò)512個(gè)標(biāo)記的序列,我們將沒(méi)有為其學(xué)習(xí)到的位置嵌入??!這就是我們前面討論的學(xué)習(xí)型絕對(duì)位置嵌入的一個(gè)缺點(diǎn)。

讓我們?nèi)∫粋€(gè)序列并將其輸入到嵌入層:

from transformers import RobertaTokenizer
# Initialize the tokenizer
tokenizer = RobertaTokenizer.from_pretrained("roberta-base")

sentence = "The quick brown fox jumps over the lazy dog."
# Tokenize the sentence
tokens = tokenizer.tokenize(sentence)
print(tokens)
# Get the input IDs
input_ids = tokenizer.encode(sentence, add_special_tokens=True)
print("\nInput IDs:", input_ids)

輸出:

['The', '?quick', '?brown', '?fox', '?jumps', '?over', '?the', '?lazy', '?dog', '.']
Input IDs: [0, 133, 2119, 6219, 23602, 13855, 81, 5, 22414, 2335, 4, 2]

注意這個(gè)序列有12個(gè)標(biāo)記。我們將其輸入到嵌入層:

input_tensor = torch.tensor(input_ids).reshape((1,-1))
emb(input_ids=input_tensor)

輸出:

tensor([[[-0.7226, -2.3475, -0.5119,  ..., -1.3224, -0.0000, -0.9497],
         [-0.4094,  0.7778,  1.8330,  ...,  0.1183, -0.3897, -1.8805],
         [-0.7342, -1.6158,  0.2465,  ..., -0.0000, -1.4895, -0.8259],
         ...,
         [-0.2884, -3.0506,  0.6108,  ...,  0.8692,  0.9901,  0.6638],
         [ 0.6423, -2.1128,  1.2056,  ...,  0.2799,  0.5368, -1.0147],
         [-0.4305, -0.4462, -1.2317,  ...,  0.4016,  1.8494, -0.2363]]],
       grad_fn=<MulBackward0>)

輸出張量是從相應(yīng)嵌入矩陣中檢索到的標(biāo)記嵌入和位置嵌入的總和。

3. 相對(duì)位置嵌入

相對(duì)位置嵌入關(guān)注序列中標(biāo)記之間的距離關(guān)系,而不考慮標(biāo)記的具體位置。

3.1 通俗解釋

考慮句子“I am a student”(我是一名學(xué)生)?!癐”的精確位置是1,“student”的精確位置是4。這些是標(biāo)記的絕對(duì)位置。相對(duì)位置嵌入不考慮這些,它只考慮“I”與“student”的距離是3,與“am”的距離是1。

相對(duì)位置嵌入在處理較長(zhǎng)序列時(shí)具有優(yōu)勢(shì),并且對(duì)訓(xùn)練期間未見(jiàn)過(guò)的序列長(zhǎng)度具有更好的泛化能力。我們很快就會(huì)看到原因。

一些使用相對(duì)位置嵌入的著名模型有Transformer-XL [1]、T5(文本到文本轉(zhuǎn)移Transformer)[2]、DeBERTa(具有解耦注意力的解碼增強(qiáng)BERT)[3]和帶有相對(duì)位置嵌入的BERT [4]。你可以隨意閱讀這些論文,了解它們是如何實(shí)現(xiàn)相對(duì)位置嵌入的。

3.2 技術(shù)解釋

首先,與將位置嵌入添加到標(biāo)記嵌入的絕對(duì)位置嵌入不同,相對(duì)位置嵌入創(chuàng)建表示標(biāo)記之間相對(duì)距離的矩陣。例如,如果標(biāo)記i在位置2,標(biāo)記j在位置5,相對(duì)位置就是??j - i = 3??。

然后,相對(duì)位置嵌入修改注意力分?jǐn)?shù),以包含關(guān)于相對(duì)位置的信息。如你所知,在自注意力機(jī)制中,注意力分?jǐn)?shù)是在標(biāo)記對(duì)之間計(jì)算的。因此,相對(duì)位置嵌入根據(jù)相對(duì)位置添加一個(gè)偏差項(xiàng)到注意力分?jǐn)?shù)中,或者為每個(gè)可能的相對(duì)距離合并一個(gè)可學(xué)習(xí)的嵌入。

這種方法的一種常見(jiàn)實(shí)現(xiàn)是在注意力分?jǐn)?shù)上添加一個(gè)相對(duì)位置偏差。如果A是注意力分?jǐn)?shù)矩陣,添加一個(gè)相對(duì)位置偏差矩陣B:

深度解析理解 Transformer 中的3大位置嵌入:從絕對(duì)位置嵌入到旋轉(zhuǎn)位置嵌入-AI.x社區(qū)

3.3 代碼示例:Transformer-XL的實(shí)現(xiàn)

下面是一個(gè)在PyTorch中實(shí)現(xiàn)相對(duì)位置嵌入的簡(jiǎn)單代碼,該實(shí)現(xiàn)與Transformer-XL的實(shí)現(xiàn)方式相近。

import torch
import torch.nn as nn

class RelativePositionalEmbedding(nn.Module):
    def __init__(self, max_len, d_model):
        super(RelativePositionalEmbedding, self).__init__()
        self.max_len = max_len
        self.d_model = d_model
        self.relative_embeddings = nn.Embedding(2 * max_len - 1, d_model)

    def forward(self, seq_len):
        # 生成相對(duì)位置
        range_vec = torch.arange(seq_len)
        range_mat = range_vec[None, :] - range_vec[:, None]
        clipped_mat = torch.clamp(range_mat, -self.max_len + 1, self.max_len - 1)
        relative_positions = clipped_mat + self.max_len - 1
        return self.relative_embeddings(relative_positions)


class RelativeSelfAttention(nn.Module):
    def __init__(self, d_model, num_heads, max_len):
        super(RelativeSelfAttention, self).__init__()
        self.num_heads = num_heads
        self.d_k = d_model // num_heads
        self.query = nn.Linear(d_model, d_model)
        self.key = nn.Linear(d_model, d_model)
        self.value = nn.Linear(d_model, d_model)
        self.relative_pos_embedding = RelativePositionalEmbedding(max_len, d_model)
        self.softmax = nn.Softmax(dim=-1)

    def forward(self, x):
        batch_size, seq_len, d_model = x.size()
        Q = self.query(x).view(batch_size, seq_len, self.num_heads, self.d_k)
        K = self.key(x).view(batch_size, seq_len, self.num_heads, self.d_k)
        V = self.value(x).view(batch_size, seq_len, self.num_heads, self.d_k)

        # 計(jì)算標(biāo)準(zhǔn)注意力分?jǐn)?shù)
        scores = torch.einsum('bnqd,bnkd->bnqk', Q, K) / (self.d_k ** 0.5)

        # 獲取相對(duì)位置嵌入
        rel_pos_embeddings = self.relative_pos_embedding(seq_len)
        rel_scores = torch.einsum('bnqd,rlkd->bnqk', Q, rel_pos_embeddings)

        # 添加相對(duì)位置分?jǐn)?shù)
        scores += rel_scores
        attn_weights = self.softmax(scores)

        # 計(jì)算最終輸出
        output = torch.einsum('bnqk,bnvd->bnqd', attn_weights, V).contiguous()
        output = output.view(batch_size, seq_len, d_model)
        return output

我們可以使用以下參數(shù)調(diào)用它:

seq_len = 10
d_model = 512
num_heads = 8
max_len = 20
x = torch.randn(32, seq_len, d_model)  # 序列批次
attention = RelativeSelfAttention(d_model, num_heads, max_len)
output = attention(x)

注意,??seq_len???(序列長(zhǎng)度)指的是特定批次中輸入序列的實(shí)際長(zhǎng)度,每個(gè)批次的??seq_len??會(huì)有所不同。

然而,??max_len???(最大長(zhǎng)度)是一個(gè)預(yù)定義的值,表示模型將考慮的最大相對(duì)位置距離。這個(gè)值決定了模型將為哪些相對(duì)位置學(xué)習(xí)嵌入。如果??max_len??設(shè)置為20,模型將為從 -19到19的相對(duì)位置學(xué)習(xí)嵌入。

請(qǐng)注意,這就是為什么??self.relative_embeddings = nn.Embedding(2 * max_len - 1, d_model)???設(shè)置為這個(gè)大小,以適應(yīng)??max_len??定義范圍內(nèi)的所有可能相對(duì)位置。

現(xiàn)在,讓我們解釋一下代碼:

第一個(gè)類如下,它為??2 * max_len - 1???的大小創(chuàng)建一個(gè)可學(xué)習(xí)的嵌入矩陣。在??forward???函數(shù)中,對(duì)于給定的序列,它從??relative_embeddings??矩陣中檢索相應(yīng)的嵌入。

class RelativePositionalEmbedding(nn.Module):
    def __init__(self, max_len, d_model):
        super(RelativePositionalEmbedding, self).__init__()
        self.max_len = max_len
        self.d_model = d_model
        self.relative_embeddings = nn.Embedding(2 * max_len - 1, d_model)

    def forward(self, seq_len):
        # 生成相對(duì)位置
        range_vec = torch.arange(seq_len)
        range_mat = range_vec[None, :] - range_vec[:, None]
        clipped_mat = torch.clamp(range_mat, -self.max_len + 1, self.max_len - 1)
        relative_positions = clipped_mat + self.max_len - 1
        return self.relative_embeddings(relative_positions)

第二個(gè)類(如下)接收一個(gè)序列(即??x???),并計(jì)算查詢、鍵和值矩陣。注意,每個(gè)注意力頭都有自己的Q、K和V,這就是為什么所有這些矩陣的形狀都是??(batch_size, seq_len, self.num_heads, self.d_k)??。

class RelativeSelfAttention(nn.Module):
    def __init__(self, d_model, num_heads, max_len):
        super(RelativeSelfAttention, self).__init__()
        self.num_heads = num_heads
        self.d_k = d_model // num_heads
        self.query = nn.Linear(d_model, d_model)
        self.key = nn.Linear(d_model, d_model)
        self.value = nn.Linear(d_model, d_model)
        self.relative_pos_embedding = RelativePositionalEmbedding(max_len, self.d_k)
        self.softmax = nn.Softmax(dim=-1)

    def forward(self, x):
        batch_size, seq_len, d_model = x.size()
        Q = self.query(x).view(batch_size, seq_len, self.num_heads, self.d_k).transpose(1, 2)
        K = self.key(x).view(batch_size, seq_len, self.num_heads, self.d_k).transpose(1, 2)
        V = self.value(x).view(batch_size, seq_len, self.num_heads, self.d_k).transpose(1, 2)

        # 計(jì)算標(biāo)準(zhǔn)注意力分?jǐn)?shù)
        scores = torch.einsum('bhqd,bhkd->bhqk', Q, K) / (self.d_k ** 0.5)

        # 獲取相對(duì)位置嵌入
        rel_pos_embeddings = self.relative_pos_embedding(seq_len)  # (seq_len, seq_len, d_k)
        rel_pos_embeddings = rel_pos_embeddings.transpose(0, 2).transpose(1, 2)  # (d_k, seq_len, seq_len)
        rel_scores = torch.einsum('bhqd,dqk->bhqk', Q, rel_pos_embeddings)

        # 添加相對(duì)位置分?jǐn)?shù)
        scores += rel_scores
        attn_weights = self.softmax(scores)

        # 計(jì)算最終輸出
        output = torch.einsum('bhqk,bhvd->bhqd', attn_weights, V).contiguous()
        output = output.transpose(1, 2).reshape(batch_size, seq_len, d_model)
        return output

這行??scores = torch.einsum('bhqd,bhkd->bhqk', Q, K) / (self.d_k ** 0.5)???是一種強(qiáng)大的符號(hào)表示,用于簡(jiǎn)潔地指定復(fù)雜的張量運(yùn)算。在這個(gè)上下文中,它用于計(jì)算查詢向量和鍵向量之間的點(diǎn)積。??'bhqd,bhkd->bhqk'??這個(gè)公式可以解釋如下:

  • ??b??:批次大小。
  • ??h??:注意力頭的數(shù)量。
  • ??q??:查詢序列長(zhǎng)度。
  • ??k??:鍵序列長(zhǎng)度(在自注意力中通常與查詢序列長(zhǎng)度相同)。
  • ??d???:每個(gè)頭的深度(即??self.d_k??)。

??einsum???符號(hào)??'bhqd,bhkd->bhqk'??指定在保持其他維度不變的情況下,計(jì)算Q和K的最后一個(gè)維度之間的點(diǎn)積。

下一行??rel_pos_embeddings = self.relative_pos_embedding(seq_len)???檢索序列中所有現(xiàn)有相對(duì)距離的相對(duì)位置嵌入,這就是為什么它的形狀是??(seq_len, seq_len, d_k)???。然后我們對(duì)其進(jìn)行轉(zhuǎn)置,將形狀變?yōu)??(d_k, seq_len, seq_len)???。下一行??rel_scores = torch.einsum('bhqd,dqk->bhqk', Q, rel_pos_embeddings)??計(jì)算相對(duì)位置嵌入在自注意力機(jī)制中對(duì)注意力分?jǐn)?shù)的貢獻(xiàn)。這就是我們前面看到的公式中的相對(duì)位置偏差矩陣B。

最后,我們將矩陣B添加到原始注意力分?jǐn)?shù)中:

# 添加相對(duì)位置分?jǐn)?shù)
scores += rel_scores
attn_weights = self.softmax(scores)

并與值矩陣V相乘得到輸出:

# 計(jì)算最終輸出
output = torch.einsum('bhqk,bhvd->bhqd', attn_weights, V).contiguous()
output = output.transpose(1, 2).reshape(batch_size, seq_len, d_model)
return output

4. 旋轉(zhuǎn)位置嵌入

旋轉(zhuǎn)位置嵌入,通常稱為RoPE(Rotary Position Embedding),是一種巧妙的方法,結(jié)合了絕對(duì)位置嵌入和相對(duì)位置嵌入的一些優(yōu)點(diǎn)。這種方法在Roformer論文中被提出。

4.1 通俗解釋

RoPE的核心思想是通過(guò)在高維空間中旋轉(zhuǎn)詞向量來(lái)編碼位置信息。旋轉(zhuǎn)的幅度取決于單詞或標(biāo)記在序列中的位置。

這種旋轉(zhuǎn)具有一個(gè)很好的數(shù)學(xué)特性:任意兩個(gè)單詞之間的相對(duì)位置可以通過(guò)一個(gè)單詞的向量相對(duì)于另一個(gè)單詞的向量旋轉(zhuǎn)了多少來(lái)輕松計(jì)算。因此,雖然每個(gè)單詞根據(jù)其絕對(duì)位置獲得唯一的旋轉(zhuǎn),但模型也可以很容易地確定相對(duì)位置。

RoPE有幾個(gè)優(yōu)點(diǎn):它比絕對(duì)位置嵌入更有效地處理更長(zhǎng)的序列。它自然地結(jié)合了絕對(duì)位置信息和相對(duì)位置信息。而且正如我們后面將看到的,它在計(jì)算上效率高且易于實(shí)現(xiàn)。

4.2 技術(shù)解釋

給定一個(gè)標(biāo)記嵌入和該標(biāo)記的位置,絕對(duì)位置嵌入計(jì)算一個(gè)位置嵌入并將其添加到標(biāo)記嵌入中:

深度解析理解 Transformer 中的3大位置嵌入:從絕對(duì)位置嵌入到旋轉(zhuǎn)位置嵌入-AI.x社區(qū)

然而,在旋轉(zhuǎn)位置嵌入中,給定一個(gè)標(biāo)記嵌入和它的位置,它會(huì)生成一個(gè)新的嵌入,其中包含位置信息:

深度解析理解 Transformer 中的3大位置嵌入:從絕對(duì)位置嵌入到旋轉(zhuǎn)位置嵌入-AI.x社區(qū)

讓我們看看這是如何計(jì)算的:

給定一個(gè)標(biāo)記,RoPE根據(jù)其在序列中的位置對(duì)其相應(yīng)的鍵向量和查詢向量應(yīng)用旋轉(zhuǎn)。這種旋轉(zhuǎn)是通過(guò)將向量與一個(gè)旋轉(zhuǎn)矩陣相乘來(lái)實(shí)現(xiàn)的。然后,旋轉(zhuǎn)后的鍵向量和查詢向量以通常的方式(點(diǎn)積后接softmax)用于計(jì)算注意力分?jǐn)?shù),Transformer中的其余計(jì)算照常進(jìn)行。

讓我們看看什么是旋轉(zhuǎn)矩陣,以及它是如何應(yīng)用到查詢向量和鍵向量上的。

旋轉(zhuǎn)矩陣:二維空間(最簡(jiǎn)單的情況)中的旋轉(zhuǎn)矩陣如下,其中是任意角度:

深度解析理解 Transformer 中的3大位置嵌入:從絕對(duì)位置嵌入到旋轉(zhuǎn)位置嵌入-AI.x社區(qū)

如果你將上述矩陣與二維向量相乘,它只會(huì)改變向量的角度,而保持向量的長(zhǎng)度不變。你同意嗎?

深度解析理解 Transformer 中的3大位置嵌入:從絕對(duì)位置嵌入到旋轉(zhuǎn)位置嵌入-AI.x社區(qū)

我們可以看到旋轉(zhuǎn)后向量的范數(shù)與原始向量相同。讓我們來(lái)算一下:

深度解析理解 Transformer 中的3大位置嵌入:從絕對(duì)位置嵌入到旋轉(zhuǎn)位置嵌入-AI.x社區(qū)

現(xiàn)在,它是如何應(yīng)用到鍵向量和查詢向量上的呢?

注意,查詢向量是查詢矩陣和標(biāo)記嵌入的乘積,即:

深度解析理解 Transformer 中的3大位置嵌入:從絕對(duì)位置嵌入到旋轉(zhuǎn)位置嵌入-AI.x社區(qū)

現(xiàn)在如果我們對(duì)其應(yīng)用旋轉(zhuǎn)矩陣,我們就是在旋轉(zhuǎn)查詢向量。

深度解析理解 Transformer 中的3大位置嵌入:從絕對(duì)位置嵌入到旋轉(zhuǎn)位置嵌入-AI.x社區(qū)

但是,它究竟是如何包含位置信息的呢?

問(wèn)得好。在上述所有數(shù)學(xué)運(yùn)算中,我們假設(shè)標(biāo)記出現(xiàn)在位置1!如果它出現(xiàn)在任意位置,那么旋轉(zhuǎn)矩陣中將包含:

深度解析理解 Transformer 中的3大位置嵌入:從絕對(duì)位置嵌入到旋轉(zhuǎn)位置嵌入-AI.x社區(qū)

4.3 相對(duì)性的數(shù)學(xué)證明

現(xiàn)在,讓我們證明旋轉(zhuǎn)位置嵌入(RoPE)是相對(duì)的。為此,我們需要證明兩個(gè)標(biāo)記之間的注意力分?jǐn)?shù)僅取決于它們的相對(duì)位置,而不是絕對(duì)位置。

  • 我們將RoPE操作定義如下:

深度解析理解 Transformer 中的3大位置嵌入:從絕對(duì)位置嵌入到旋轉(zhuǎn)位置嵌入-AI.x社區(qū)

  • 考慮兩個(gè)位于位置和的標(biāo)記:

深度解析理解 Transformer 中的3大位置嵌入:從絕對(duì)位置嵌入到旋轉(zhuǎn)位置嵌入-AI.x社區(qū)

  • 我們將注意力分?jǐn)?shù)計(jì)算為它們的點(diǎn)積:

深度解析理解 Transformer 中的3大位置嵌入:從絕對(duì)位置嵌入到旋轉(zhuǎn)位置嵌入-AI.x社區(qū)

讓我們?nèi)缦抡归_(kāi):

深度解析理解 Transformer 中的3大位置嵌入:從絕對(duì)位置嵌入到旋轉(zhuǎn)位置嵌入-AI.x社區(qū)

  • 旋轉(zhuǎn)矩陣具有這樣一個(gè)很好的性質(zhì):

深度解析理解 Transformer 中的3大位置嵌入:從絕對(duì)位置嵌入到旋轉(zhuǎn)位置嵌入-AI.x社區(qū)

  • 因此,注意力分?jǐn)?shù)變?yōu)椋?/li>

深度解析理解 Transformer 中的3大位置嵌入:從絕對(duì)位置嵌入到旋轉(zhuǎn)位置嵌入-AI.x社區(qū)

如你所見(jiàn),分?jǐn)?shù)是相對(duì)位置的函數(shù),即和之間位置的差值。

4.4 高維旋轉(zhuǎn)矩陣

通常情況下,模型的嵌入維度不是2,通常比2大得多。那么旋轉(zhuǎn)矩陣會(huì)如何變化呢?Roformer論文的作者提出了以下組合方式:

深度解析理解 Transformer 中的3大位置嵌入:從絕對(duì)位置嵌入到旋轉(zhuǎn)位置嵌入-AI.x社區(qū)

也就是說(shuō),對(duì)于位置和嵌入維度,旋轉(zhuǎn)矩陣由個(gè)2×2的旋轉(zhuǎn)矩陣組成。值得一提的是,由于這種構(gòu)造是稀疏的,相關(guān)研究人員推薦了一種計(jì)算效率高的方法來(lái)計(jì)算它與標(biāo)記嵌入向量的乘積。

深度解析理解 Transformer 中的3大位置嵌入:從絕對(duì)位置嵌入到旋轉(zhuǎn)位置嵌入-AI.x社區(qū)

4.5 代碼片段:Roformer的實(shí)現(xiàn)

旋轉(zhuǎn)位置嵌入(RoPE)的代碼實(shí)現(xiàn)相當(dāng)復(fù)雜,但本次重點(diǎn)介紹負(fù)責(zé)計(jì)算RoPE的核心功能,它封裝在以下方法中:

def apply_rotary_position_embeddings(sinusoidal_pos, query_layer, key_layer, value_layer=None):
    # https://kexue.fm/archives/8265
    # sin [batch_size, num_heads, sequence_length, embed_size_per_head//2]
    # cos [batch_size, num_heads, sequence_length, embed_size_per_head//2]
    sin, cos = sinusoidal_pos.chunk(2, dim=-1)
    # sin [θ0,θ1,θ2......θd/2-1] -> sin_pos [θ0,θ0,θ1,θ1,θ2,θ2......θd/2-1,θd/2-1]
    sin_pos = torch.stack([sin, sin], dim=-1).reshape_as(sinusoidal_pos)
    # cos [θ0,θ1,θ2......θd/2-1] -> cos_pos [θ0,θ0,θ1,θ1,θ2,θ2......θd/2-1,θd/2-1]
    cos_pos = torch.stack([cos, cos], dim=-1).reshape_as(sinusoidal_pos)
    # rotate_half_query_layer [-q1,q0,-q3,q2......,-qd-1,qd-2]
    rotate_half_query_layer = torch.stack([-```python
    rotate_half_query_layer = torch.stack([-query_layer[..., 1::2], query_layer[..., ::2]], dim=-1).reshape_as(
        query_layer
    )
    query_layer = query_layer * cos_pos + rotate_half_query_layer * sin_pos
    # rotate_half_key_layer [-k1,k0,-k3,k2......,-kd-1,kd-2]
    rotate_half_key_layer = torch.stack([-key_layer[..., 1::2], key_layer[..., ::2]], dim=-1).reshape_as(key_layer)
    key_layer = key_layer * cos_pos + rotate_half_key_layer * sin_pos
    if value_layer isnotNone:
        # rotate_half_value_layer [-v1,v0,-v3,v2......,-vd-1,vd-2]
        rotate_half_value_layer = torch.stack([-value_layer[..., 1::2], value_layer[..., ::2]], dim=-1).reshape_as(
            value_layer
        )
        value_layer = value_layer * cos_pos + rotate_half_value_layer * sin_pos
        return query_layer, key_layer, value_layer
    return query_layer, key_layer

注意,??query_layer = query_layer * cos_pos + rotate_half_query_layer * sin_pos??這一行代碼是根據(jù)上一節(jié)提到的高效計(jì)算方法進(jìn)行運(yùn)算的。

結(jié)論

在本文中,本文回顧了三種主要的位置嵌入類型:絕對(duì)位置嵌入、相對(duì)位置嵌入和旋轉(zhuǎn)位置嵌入。絕對(duì)位置嵌入提供了一種直接的位置信息編碼方式,有學(xué)習(xí)型和固定型兩種方法。相對(duì)位置嵌入側(cè)重于標(biāo)記之間的相對(duì)距離,這種方法被應(yīng)用于像Transformer-XL和DeBERTa等模型中。最后,創(chuàng)新的旋轉(zhuǎn)位置嵌入(RoPE)結(jié)合了絕對(duì)位置嵌入和相對(duì)位置嵌入的優(yōu)點(diǎn),為位置信息編碼提供了一種更高效、可擴(kuò)展的解決方案。

本文轉(zhuǎn)載自??智駐未來(lái)??,作者:小智

標(biāo)簽
收藏
回復(fù)
舉報(bào)
回復(fù)
相關(guān)推薦