[코드 노트] 트랜스포머 코드 상세 해석

Transformer 코드의 상세한 해석

소개

Transformer는 현재 자연어 처리는 물론 전체 딥러닝 분야에서도 매우 중요한 클래식 모델로, BERT, GPT, BART 등 현재 대규모 사전 학습된 언어 모델의 기본 아키텍처로 큰 도움이 됩니다. 이 모델을 기반으로 모델을 미세 조정하고 개선합니다. 코드 해석의 핵심은 다음과 같습니다.

  1. Transformer의 모델 프레임워크와 각 모듈의 세부사항을 알아보세요 . 다른 블로그 [스터디 노트] Transformer Model Interpretation을 참조할 수 있습니다.
  2. 전체에서 부분으로, 데이터 입출력의 흐름 모양(모양)을 파악합니다 .

다른:

1. 데이터 준비

이 문서에서는 간단한 독일어-영어 기계 번역 작업 데모를 예로 들어 설명합니다.

1.1 어휘 구성

단어 임베딩 자체는 조회 프로세스이므로 토큰과 해당 색인이라는 어휘를 구축하는 것이 필요합니다. 현재 실제 작업에서는 일반적으로 Huggingface Transformers 라이브러리의 Tokenizer와 같은 API를 사용하여 직접 가져옵니다.

src_vocab = {
    
    'P': 0, 'ich': 1, 'mochte': 2, 'ein': 3, 'bier': 4}
src_vocab_size = len(src_vocab)

tgt_vocab = {
    
    'P': 0, 'i': 1, 'want': 2, 'a': 3, 'beer': 4, 'S': 5, 'E': 6}
tgt_vocab_size = len(tgt_vocab)

1.2 데이터 구성

실제 작업에서는 데이터셋을 읽고 DataLoader를 빌드해야 하는데, 이 글에서는 이해하기 쉽도록 하나의 장난감 프로세스만 구현했습니다.

'S'(Start)는 시작 문자, 'E'(End)는 종료 문자, 'P'(Pad)는 채우는 문자를 의미합니다.

입력 텍스트는 문자열 유형이므로 어휘의 문자 인덱스로 변환한 다음 Tensor 유형으로 변환해야 합니다.

sentences = ['ich mochte ein bier P', 'S i want a beer', 'i want a beer E']

def make_batch(sentences):
    # 把文本转成词表索引
    input_batch = [[src_vocab[n] for n in sentences[0].split()]]
    output_batch = [[tgt_vocab[n] for n in sentences[1].split()]]
    target_batch = [[tgt_vocab[n] for n in sentences[2].split()]]
    # 把索引转成tensor类型
    return torch.LongTensor(input_batch), torch.LongTensor(output_batch), torch.LongTensor(target_batch)

2. 모델의 전체적인 구조

2.1 하이퍼파라미터 설정

몇 가지 중요한 모델 하이퍼파라미터 설정은 다음과 같습니다.

  • 입력 및 출력 문장 길이;
  • 모델 단어 임베딩 크기;
  • 피드포워드 신경망(FeedForward) 계층 숨겨진 계층 차원;
  • K(self-attention에서 Q의 크기), V 벡터 크기;
  • 인코더 및 디코더의 레이어 수
  • 긴 셀프 어텐션 헤드 수.
src_len = 5 # length of source
tgt_len = 5 # length of target

## 模型参数
d_model = 512  # Embedding Size
d_ff = 2048  # FeedForward dimension
d_k = d_v = 64  # dimension of K(=Q), V
n_layers = 6  # number of Encoder of Decoder Layer
n_heads = 8  # number of heads in Multi-Head Attention

2.2 전체 아키텍처

Transformer의 전체 네트워크 구조는 인코딩 계층, 디코딩 계층 및 출력 계층의 세 부분으로 구성됩니다.

이미지-20220908191631120

  • 프로세스

    • 최종 텍스트 임베딩으로 단어 임베딩 및 위치 인코딩을 위한 입력 텍스트;
    • 텍스트 임베딩은 Encoder에 의해 인코딩되고 출력 인코딩 벡터와 self-attention 가중치 매트릭스는 어텐션 가중치 후에 얻습니다.
    • 그런 다음 샘플의 인코딩된 벡터와 지상 진실을 함께 디코더에 입력하고 어텐션 가중치 등의 작업을 거쳐 최종 컨텍스트 벡터를 출력한 다음 디코딩을 위해 어휘 크기의 선형 레이어에 매핑하여 텍스트를 생성합니다.
    • 마지막으로 예측 결과를 나타내는 로짓 행렬을 반환합니다.
  • 데이터 셰이프

    enc_inputs:[batch_size,src_len]

    dec_inputs:[batch_size,tgt_len]

    enc_outputs:[batch_size,src_len,d_model]

    enc_self_attns:[batch_size,n_heads,src_len,src_len]

    dec_outputs:[batch_size,tgt_len,d_model]

    dec_self_attns:[batch_size,n_heads,tgt_len,tgt_len]

    dec_enc_attns:[batch_size,n_heads,tgt_len,src_len]

    dec_logits:[batch_size,tgt_len,tgt_vocab_size]

class Transformer(nn.Module):
    def __init__(self):
        super(Transformer, self).__init__()
        # 编码器
        self.encoder = Encoder()
        # 解码器
        self.decoder = Decoder()
        # 输出层,d_model是解码层每个token输出的维度大小,之后会做一个tgt_vocab_size大小的softmax
        # 因为解码输出过程相当于是在一个词表大小级别上的分类
        self.projection = nn.Linear(d_model, tgt_vocab_size, bias=False)
    def forward(self, enc_inputs, dec_inputs):
        # 输入输出部分具体要输入和返回什么参数,可以根据自己的任务和改进需要进行自定义修改,内部的执行过程是不变的
        # enc_outputs就是编码器的输出,enc_self_attns是QK转置相乘之后softmax之后的注意力矩阵,代表的是每个单词和其他单词相关性;
        # 由于多头注意力机制会分头计算注意力,所以注意力权重矩阵是个四维向量,
        # 即[batch_size,n_heads,src_len,src_len]
        enc_outputs, enc_self_attns = self.encoder(enc_inputs)
        # dec_self_attns类比于enc_self_attns,是查看每个单词对decoder中输入的其余单词的相关性;
        # dec_enc_attns是decoder中每个单词对encoder中每个单词的相关性,
        # 即Cross_Attention输出的注意力权重矩阵,形状为[batch_size,n_heads,tgt_len,src_len]
        # 注意,这里的xxx_attns都是list类型,因为在Encoder、Decoder中将每一层的注意力权重都保存下来添加到列表中返回了
        dec_outputs, dec_self_attns, dec_enc_attns = self.decoder(dec_inputs, enc_inputs, enc_outputs)
        # dec_outputs映射到词表大小
        dec_logits = self.projection(dec_outputs) # dec_logits : [batch_size,tgt_len, tgt_vocab_size]
        # 这里dec_logits进行view操作主要是为了适应后面的CrossEntropyLoss API的参数要求
        return dec_logits.view(-1, dec_logits.size(-1)), enc_self_attns, dec_self_attns, dec_enc_attns

2.2 모델 교육

model = Transformer()

criterion = nn.CrossEntropyLoss()
optimizer = optim.Adam(model.parameters(), lr=0.001)

enc_inputs, dec_inputs, target_batch = make_batch(sentences)

for epoch in range(10):
    optimizer.zero_grad()
    outputs, enc_self_attns, dec_self_attns, dec_enc_attns = model(enc_inputs, dec_inputs)
    # output:[batch_size x tgt_len,tgt_vocab_size]
    # 就这份代码而言,这里其实可以不写.contiguous(),因为target_batch这个tensor是连续的
    loss = criterion(outputs, target_batch.contiguous().view(-1))
    print('Epoch:', '%04d' % (epoch + 1), 'cost =', '{:.6f}'.format(loss))
    loss.backward()
    optimizer.step()

3. 인코더

3.1 인코더

인코더는 N개의 인코딩 레이어를 적층하여 구성됩니다.

  • 프로세스

    • 입력 텍스트의 인덱스 텐서, 단어 임베딩은 단어 임베딩 레이어를 통해 얻은 다음 입력 레이어의 최종 출력으로 위치 코드에 선형으로 추가됩니다.

    • 이후, 각 레이어의 출력은 다음 레이어의 코딩 블록의 입력이 되며, 어텐션 계산, 피드포워드 신경망, 잔차 연결 및 레이어 정규화와 같은 작업이 각 코딩 블록에서 수행됩니다.

    • 마지막으로 인코더의 마지막 레이어 출력과 각 레이어에 대한 어텐션 웨이트 매트릭스를 반환합니다.

class Encoder(nn.Module):
    def __init__(self):
        super(Encoder, self).__init__()
        # 这个其实就是去定义生成一个词嵌入矩阵,大小是 src_vocab_size * d_model
        self.src_emb = nn.Embedding(src_vocab_size, d_model)  
        # 位置编码,这里是固定的正余弦函数,也可以使用类似词向量的nn.Embedding获得一个可以更新学习的位置编码
        self.pos_emb = PositionalEncoding(d_model) 
        # 使用ModuleList对多个encoder进行堆叠,因为后续的encoder并没有使用词向量和位置编码,所以抽离出来;
        self.layers = nn.ModuleList([EncoderLayer() for _ in range(n_layers)]) 

    def forward(self, enc_inputs):
        # enc_inputs形状是:[batch_size,src_len]
        # 下面这个代码通过src_emb,进行索引定位,enc_outputs输出形状是[batch_size, src_len, d_model]
        enc_outputs = self.src_emb(enc_inputs)

        # 位置编码和词嵌入相加,具体实现在PositionalEncoding里,enc_outputs:[batch_size,src_len,d_model]
        enc_outputs = self.pos_emb(enc_outputs.transpose(0, 1)).transpose(0, 1)
        # get_attn_pad_mask是为了得到句子中pad的位置信息,以便在计算注意力时忽略pad符号
        enc_self_attn_mask = get_attn_pad_mask(enc_inputs, enc_inputs)
        enc_self_attns = []
        for layer in self.layers:
            # 每一层的输出作为下一层的输入,enc_outputs:[batch_size,src_len,d_model]
            enc_outputs, enc_self_attn = layer(enc_outputs, enc_self_attn_mask)
            # 把每一层得到的注意力权重矩阵添加到列表里最后返回,enc_self_attn:[batch_size,src_len,src_len]
            enc_self_attns.append(enc_self_attn)
        return enc_outputs, enc_self_attns

3.2 단일 인코딩 레이어

class EncoderLayer(nn.Module):
    def __init__(self):
        super(EncoderLayer, self).__init__()
        self.enc_self_attn = MultiHeadAttention()
        self.pos_ffn = PoswiseFeedForwardNet()

    def forward(self, enc_inputs, enc_self_attn_mask):
        #enc_inputs形状是[batch_size x seq_len_q x d_model],注意,最初始的QKV矩阵是等同于这个输入的
        enc_outputs, attn = self.enc_self_attn(enc_inputs, enc_inputs, enc_inputs, enc_self_attn_mask) # enc_inputs to same Q,K,V
        enc_outputs = self.pos_ffn(enc_outputs) # enc_outputs: [batch_size x len_q x d_model]
        return enc_outputs, attn

3.3 패딩 마스크

어텐션 메커니즘의 후반부에서 Q ∗ KTQ*K^T를 계산한 후케이T를 루트 부호로 나눈 후 softmax 이전에 얻은 행렬의 크기는 [len_input * len_input]이며, 이는 각 단어가 모든(자체 포함) 단어에 미치는 영향을 나타냅니다. 이 함수는 동일한 크기와 모양의 행렬을구하고어느 위치가 PAD 기호인지 표시한다음softmax를 계산하기 전에 이 위치를 무한소로 설정하여Query이러한 무의미한 PAD 기호에 주의를 기울이지 않도록 하는 데.

이 함수로 얻은 행렬 모양은 [batch_size x len_q x len_k]이며 K의 패드 기호를 식별하고 Q의 패드 기호는 불필요하므로 식별하지 않습니다.

seq_q와 seq_k는 반드시 일치하지는 않습니다.예를 들어 인터랙티브 어텐션에서 q는 decoding 끝에서 오고 k는 인코딩 끝에서 오므로 여기에 패드 기호 정보를 인코딩하고 패드 정보를 인코딩하도록 모델에 지시하면 충분합니다. 디코딩 쪽에서는 Interactive Attention Layer에서 사용되지 않습니다. .

이미지-20221128204838986

def get_attn_pad_mask(seq_q, seq_k):
    batch_size, len_q = seq_q.size()
    batch_size, len_k = seq_k.size()
    # eq(zero) is PAD token
    pad_attn_mask = seq_k.data.eq(0).unsqueeze(1)  # batch_size x 1 x len_k, one is masking
    # 最终得到的应该是一个最后n列为1的矩阵,即K的最后n个token为PAD。
    return pad_attn_mask.expand(batch_size, len_q, len_k)  # batch_size x len_q x len_k

4. 디코더

4.1 디코더

디코더는 N개의 디코딩 레이어로 쌓입니다.

디코더는 인코더와 비슷하지만 차이점은 디코더의 각 계층에는 두 개의 멀티 헤드 어텐션 메커니즘이 있다는 것입니다. 첫 번째 다중 헤드 어텐션 메커니즘에서 미래의 단어는 마스킹되어야 하며 , 두 번째 다중 헤드 어텐션 메커니즘은 인코더의 출력을 K 및 V로 사용하고 이전의 출력을 사용해야 하는 교차 주의 메커니즘 입니다. Q로 디코더의 일부입니다. 그 의미는 인코딩으로 얻은 컨텍스트 정보를 디코딩하는 동안 사용할 수 있다는 것입니다.

class DecoderLayer(nn.Module):
    def __init__(self):
        super(DecoderLayer, self).__init__()
        self.dec_self_attn = MultiHeadAttention()
        self.dec_enc_attn = MultiHeadAttention()
        self.pos_ffn = PoswiseFeedForwardNet()

    def forward(self, dec_inputs, enc_outputs, dec_self_attn_mask, dec_enc_attn_mask):
        dec_outputs, dec_self_attn = self.dec_self_attn(dec_inputs, dec_inputs, dec_inputs, dec_self_attn_mask)
        # 这里用dec_outputs作为Q,enc_outputs作为K和V,实现交叉注意力机制
        dec_outputs, dec_enc_attn = self.dec_enc_attn(dec_outputs, enc_outputs, enc_outputs, dec_enc_attn_mask)
        dec_outputs = self.pos_ffn(dec_outputs)
        return dec_outputs, dec_self_attn, dec_enc_attn

4.2 단일 디코딩 레이어

class Decoder(nn.Module):
    def __init__(self):
        super(Decoder, self).__init__()
        self.tgt_emb = nn.Embedding(tgt_vocab_size, d_model)
        self.pos_emb = PositionalEncoding(d_model)
        self.layers = nn.ModuleList([DecoderLayer() for _ in range(n_layers)])

    # dec_inputs : [batch_size x target_len]
    def forward(self, dec_inputs, enc_inputs, enc_outputs):
        dec_outputs = self.tgt_emb(dec_inputs)  # [batch_size, tgt_len, d_model]
        dec_outputs = self.pos_emb(dec_outputs.transpose(0, 1)).transpose(0, 1) # [batch_size, tgt_len, d_model]
        # 获取自注意力层pad的mask矩阵,1表示被mask
        dec_self_attn_pad_mask = get_attn_pad_mask(dec_inputs, dec_inputs)

        # 获取look ahead的mask矩阵,即让注意力机制看不到未来的单词,获得到一个上三角为1的矩阵,1表示被mask
        dec_self_attn_subsequent_mask = get_attn_subsequent_mask(dec_inputs)

        # 两个mask矩阵相加,大于0的为1,不大于0的为0,既屏蔽了pad的信息,也屏蔽了未来时刻的信息,为1的在之后就会被fill到无限小
        # 使用gt()函数,因为可能会有在两个mask都被屏蔽的情况,1+1=2
        dec_self_attn_mask = torch.gt((dec_self_attn_pad_mask + dec_self_attn_subsequent_mask), 0)

        # 获取交互注意力机制中的mask矩阵,decoder的输入是q,encoder的输入是k,需要知道k里面哪些是pad符号,
        # 注意,q肯定也是有pad符号,但是没有必要将其屏蔽
        # 这里不用再把q的未来词再mask了,因为前面已经mask过一次,输出向量对应部分的值应该都是0了
        dec_enc_attn_mask = get_attn_pad_mask(dec_inputs, enc_inputs)

        dec_self_attns, dec_enc_attns = [], []
        for layer in self.layers:
            dec_outputs, dec_self_attn, dec_enc_attn = layer(dec_outputs, enc_outputs, dec_self_attn_mask, dec_enc_attn_mask)
            dec_self_attns.append(dec_self_attn)
            dec_enc_attns.append(dec_enc_attn)
        return dec_outputs, dec_self_attns, dec_enc_attns

4.3 시퀀스 마스크

현재 단어가 미래 단어를 볼 수 없도록 미래 단어를 마스킹합니다. 이 함수는 디코더의 입력 중 미래 단어를 나타내는 데 사용됩니다.분명히 마스크 행렬은 상위 삼각 행렬이어야 합니다.

이미지

def get_attn_subsequent_mask(seq):
    """
    seq: [batch_size, tgt_len]
    """
    attn_shape = [seq.size(0), seq.size(1), seq.size(1)]
    # attn_shape: [batch_size, tgt_len, tgt_len]
    # np.triu()返回一个上三角矩阵,自对角线k以下元素全部置为0,k=0为主对角线
    subsequence_mask = np.triu(np.ones(attn_shape), k=1)  # 生成一个上三角矩阵
    # 如果没转成byte,这里默认是Double(float64),占据的内存空间大,浪费,用byte就够了
    subsequence_mask = torch.from_numpy(subsequence_mask).byte()
    return subsequence_mask  # [batch_size, tgt_len, tgt_len]

5. 위치 코딩

이미지-20221128211633848

이미지-20220908191753204

e − 2 idmodel ∗ log 10000 = 1 1000 0 2 idmodele^{\frac{-2i}{d_{model}}*log10000}=\frac{1}{10000^{\frac{2i}{d_{model} }}}이자형모델 _ _ _ _−2i _ _l o g 10000=1000 0모델 _ _ _ _2 나는1
위치 코딩의 구현은 공식에 따라 직접 작성할 수 있습니다.다음 코드는 구현 방법 중 하나일 뿐이며
공식에서 짝수와 홀수는 공통 부분이 있음을 유의해야 합니다.여기서 지수 함수 e와 로그 함수(e를 기본으로 함)가 사용됩니다. 쉬운 계산을 위해 전원을 끄십시오.
pos는 문장에서 단어의 절대 인덱스 위치를 나타냅니다. 예를 들어 max_len은 128이고 인덱스는 0,1부터, 2,...,127, d_model이 512라고 가정하면, 즉 512 차원 텐서를 사용하여 인덱스 위치를 인코딩한 다음 0<=2i<512, 0<=i<=255, 2i의 해당 값 는 0, 2, 4...510이면 짝수 위치이고 2i+1의 값은 1,3,5...511 즉 홀수 ​​위치입니다.

최종 텍스트 임베딩 표현은 단어 임베딩 및 위치 인코딩을 추가하여 얻습니다.

이미지-20221128205300431

class PositionalEncoding(nn.Module):
    def __init__(self, d_model, dropout=0.1, max_len=5000):
        super(PositionalEncoding, self).__init__()

        self.dropout = nn.Dropout(p=dropout)
		# 生成一个形状为[max_len,d_model]的全为0的tensor
        pe = torch.zeros(max_len, d_model)
        # position:[max_len,1],即[5000,1],这里插入一个维度是为了后面能够进行广播机制然后和div_term直接相乘
        # 注意,要理解一下这里position的维度。每个pos都需要512个编码。
        position = torch.arange(0, max_len, dtype=torch.float).unsqueeze(1)
        # 共有项,利用指数函数e和对数函数log取下来,方便计算
        div_term = torch.exp(torch.arange(0, d_model, 2).float() * (-math.log(10000.0) / d_model))

        # 这里position * div_term有广播机制,因为div_term的形状为[d_model/2],即[256],符合广播条件,广播后两个tensor经过复制,形状都会变成[5000,256],*表示两个tensor对应位置处的两个元素相乘
        # 这里需要注意的是pe[:, 0::2]这个用法,就是从0开始到最后面,补长为2,其实代表的就是偶数位置赋值给pe
        pe[:, 0::2] = torch.sin(position * div_term)
        # 同理,这里是奇数位置
        pe[:, 1::2] = torch.cos(position * div_term)
        # 上面代码获取之后得到的pe:[max_len*d_model]

        # 下面这个代码之后,我们得到的pe形状是:[max_len*1*d_model]
        pe = pe.unsqueeze(0).transpose(0, 1)
		# 定一个缓冲区,其实简单理解为这个参数不更新就可以,但是参数仍然作为模型的参数保存
        self.register_buffer('pe', pe)  

    def forward(self, x):
        """
        x: [seq_len, batch_size, d_model]
        """
        # 这里的self.pe是从缓冲区里拿的
        # 切片操作,把pe第一维的前seq_len个tensor和x相加,其他维度不变
        # 这里其实也有广播机制,pe:[max_len,1,d_model],第二维大小为1,会自动扩张到batch_size大小。
        # 实现词嵌入和位置编码的线性相加
        x = x + self.pe[:x.size(0), :]
        return self.dropout(x)

6. 멀티헤드 어텐션

6.1 다중 헤드 어텐션 메커니즘

이미지-20220915190436539

여기의 실제 코드는 일부 원칙에 대한 설명과 약간 다를 수 있습니다.

먼저 매핑하고 나중에 분할합니다 . 즉, 입력을 받은 후 d_k * n_heads 차원에 매핑한 다음 전치에 의해 n_heads 헤드로 나누므로 n_heads 매개변수 행렬을 작성할 필요가 없으며 접합 작업을 수행할 필요가 없습니다.

다중 헤드 어텐션 메커니즘이 완료된 후 선형 계층을 거칩니다.

class MultiHeadAttention(nn.Module):
    def __init__(self):
        super(MultiHeadAttention, self).__init__()
        # Wq,Wk,Wv其实就是一个线性层,用来将输入映射为K、Q、V
        # 这里输出是d_k * n_heads,因为是先映射,后分头。
        self.W_Q = nn.Linear(d_model, d_k * n_heads)
        self.W_K = nn.Linear(d_model, d_k * n_heads)
        self.W_V = nn.Linear(d_model, d_v * n_heads)
        self.linear = nn.Linear(n_heads * d_v, d_model)
        self.layer_norm = nn.LayerNorm(d_model)

    def forward(self, Q, K, V, attn_mask):
        # attn_mask:[batch_size,len_q,len_k]
        # 输入的数据形状: Q: [batch_size x len_q x d_model], K: [batch_size x len_k x d_model], 
        # V: [batch_size x len_k x d_model]
        residual, batch_size = Q, Q.size(0)
        # (B, S, D) -proj-> (B, S, D) -split-> (B, S, H, W) -trans-> (B, H, S, W)

        # 分头;一定要注意的是q和k分头之后维度是一致的,所以一看这里都是d_k
        # q_s: [batch_size x n_heads x len_q x d_k]
        q_s = self.W_Q(Q).view(batch_size, -1, n_heads, d_k).transpose(1,2)
        # k_s: [batch_size x n_heads x len_k x d_k]
        k_s = self.W_K(K).view(batch_size, -1, n_heads, d_k).transpose(1,2)
        # v_s: [batch_size x n_heads x len_k x d_v]
        v_s = self.W_V(V).view(batch_size, -1, n_heads, d_v).transpose(1,2)

        # attn_mask:[batch_size x len_q x len_k] ---> [batch_size x n_heads x len_q x len_k]
        # 就是把pad信息复制n份,重复到n个头上以便计算多头注意力机制
        attn_mask = attn_mask.unsqueeze(1).repeat(1, n_heads, 1, 1)

        # 计算ScaledDotProductAttention
        # 得到的结果有两个:context: [batch_size x n_heads x len_q x d_v],
        # attn: [batch_size x n_heads x len_q x len_k]
        context, attn = ScaledDotProductAttention()(q_s, k_s, v_s, attn_mask)
        # 这里实际上在拼接n个头,把n个头的加权注意力输出拼接,然后过一个线性层,context变成
        # [batch_size,len_q,n_heads*d_v]。这里context需要进行contiguous,因为transpose后源tensor变成不连续的
        # 了,view操作需要连续的tensor。
        context = context.transpose(1, 2).contiguous().view(batch_size, -1, n_heads * d_v)
        output = self.linear(context)
        # 过残差、LN,输出output: [batch_size x len_q x d_model]和这一层的加权注意力表征向量
        return self.layer_norm(output + residual), attn

6.2 내적 스케일링을 위한 어텐션 메커니즘(ScaledDotProductAttention)

여기서 핵심 세부 사항은 Mask 행렬과 Attention 가중치 행렬의 공동 작업입니다.Attention 가중치 행렬의 PAD 부분은 쿼리를 보호하기 위해 무한히 작게 만들어야 합니다.

이미지-20220915180132448

class ScaledDotProductAttention(nn.Module):
    def __init__(self):
        super(ScaledDotProductAttention, self).__init__()

    def forward(self, Q, K, V, attn_mask):
        # 输入进来的维度分别是Q:[batch_size x n_heads x len_q x d_k]  K:[batch_size x n_heads x len_k x d_k]  V:[batch_size x n_heads x len_k x d_v]
        # matmul操作即矩阵相乘
        # [batch_size x n_heads x len_q x d_k] matmul [batch_size x n_heads x d_k x len_k] -> [batch_size x n_heads x len_q x len_k]
        scores = torch.matmul(Q, K.transpose(-1, -2)) / np.sqrt(d_k)

        # masked_fill_(mask,value)这个函数,用value填充源向量中与mask中值为1位置相对应的元素,
        # 要求mask和要填充的源向量形状需一致
        # 把被mask的地方置为无穷小,softmax之后会趋近于0,Q会忽视这部分的权重
        scores.masked_fill_(attn_mask, -1e9) # Fills elements of self tensor with value where mask is one.
        attn = nn.Softmax(dim=-1)(scores)
        context = torch.matmul(attn, V)
        # context:[batch_size,n_heads,len_q,d_k]
        # attn:[batch_size,n_heads,len_q,len_k]
        return context, attn

7. 피드포워드 신경망(Poswise-FeedForward)

FeedForward는 실제로 입력을 선형으로 변환하는 2계층 선형 계층입니다. 즉, 시퀀스의 각 토큰에 대해 동일한 MLP를 독립적으로 전달하는 것, 즉 입력의 마지막 차원에서 작동하는 것 입니다 .

이미지-20220924181652351

MLP를 구현하는 방법에는 두 가지가 있는데 하나는 컨볼루션으로 구현하는 것이고 다른 하나는 선형 레이어로 구현하는 것입니다. 둘의 차이점은 원리뿐 아니라 코드 내용에서도 차이가 있는데, 예를 들어 Conv1d는 입력이 [batch_size, channel, length]가 되어야 하는데 이는 3차원 텐서여야 하고, Linear는 입력이 다음과 같아야 합니다. [batch_size, *, d_model], 많은 차원이 있을 수 있습니다.

7.1 구현 1: Conv1d

class PoswiseFeedForwardNet(nn.Module):
    def __init__(self):
        super(PoswiseFeedForwardNet, self).__init__()
        self.conv1 = nn.Conv1d(in_channels=d_model, out_channels=d_ff, kernel_size=1)
        self.conv2 = nn.Conv1d(in_channels=d_ff, out_channels=d_model, kernel_size=1)
        self.layer_norm = nn.LayerNorm(d_model)

    def forward(self, inputs):
        residual = inputs # inputs : [batch_size, len_q, d_model]
        # Conv1d的输入为[batch, channel, length],作用于第二个维度channel,所以这里要转置
        output = nn.ReLU()(self.conv1(inputs.transpose(1, 2)))
        output = self.conv2(output).transpose(1, 2)
        return self.layer_norm(output + residual)

7.2 구현 2: 선형

class PoswiseFeedForwardNet(nn.Module):
    def __init__(self):
        super(PoswiseFeedForwardNet, self).__init__()
        self.fc = nn.Sequential(
            nn.Linear(d_model, d_ff, bias=False),
            nn.ReLU(),
            nn.Linear(d_ff, d_model, bias=False))
        
    def forward(self, inputs):              # inputs: [batch_size, seq_len, d_model]
        residual = inputs
        output = self.fc(inputs)
        return nn.LayerNorm(d_model).(output + residual)   # [batch_size, seq_len, d_model]

Supongo que te gusta

Origin blog.csdn.net/m0_47779101/article/details/128087403
Recomendado
Clasificación