⊢ DeepLearning

어텐션(Attention) 메커니즘

최 수빈 2025. 3. 20. 18:56

 

Attention 메커니즘

 

시퀀스 데이터에서 중요한 부분에 더 많은 가중치를 할당하여 정보를 효율적으로 처리하는 기법

주로 자연어 처리(NLP) 및 시계열 데이터에서 사용되며, 기계 번역, 요약, 질의응답 시스템 등에 적용됨

 

 

동작 방식

 

기본 개념

  • 입력 시퀀스의 각 요소에 대해 중요도를 계산하여 가중치를 부여
  • 불필요한 정보를 무시하고 중요한 정보 강조

 

주요 구성 요소 : Query(Q), Key(K), Value(V)

 

 

1. Attention 스코어 계산

 

Query와 Key 간의 유사도를 측정하여 중요도를 계산

일반적으로 내적(dot product) 연산을 사용하여 유사도를 계산함

𝓢(Q, K) = Q · K^T

 

 

2. Softmax를 통한 가중치 계산

 

Attention 스코어를 Softmax 함수로 정규화하여 가중치로 변환

αi = ∕frac{𝒳(𝓢(Q, Ki))}{∑j 𝒳(𝓢(Q, Kj))}

 

→ 가중치의 합이 1이 되도록 함

 

 

3. 가중치 적용 및 최종 출력

 

Softmax를 통해 얻은 가중치를 Value에 곱하여 최종 Attention 출력 계산

𝓢(Q, K, V) = ∑i αi Vi

 

 

Self-Attention과 Multi-Head Attention

 

Self-Attention

  • 입력 시퀀스 내에서 각 요소가 서로를 참조하여 관계를 학습 (입력 시퀀스의 모든 요소가 Query, Key, Value로 사용 됨)
  • 같은 문장 내 단어들이 서로의 의미를 이해하는 데 도움을 줌
  • 번역, 요약 등에서 단어 간의 의존성을 효과적으로 모델링 가능

 

Multi-Head Attention

  • 여러 개의 Self-Attention을 병렬로 수행하는 방식
  • 각 Head가 서로 다른 패턴을 학습하여 더욱 풍부한 표현력을 가짐
  • 다양한 관점에서 데이터를 처리할 수 있어 성능 향상

 

 

 

Attention 메커니즘 구현

 

 

Scaled Dot-Product Attention 구현

import torch
import torch.nn.functional as F # PyTorch의 활성화 함수 라이브러리 (Softmax 사용)

def scaled_dot_product_attention(Q, K, V):
    """
    Scaled Dot-Product Attention 메커니즘을 구현하는 함수

    Args:
        Q: Query 벡터 (shape: [batch_size, num_heads, seq_len, d_k])
        K: Key 벡터 (shqpe: [batch_size, num_heads, seq_len, d_k])
        V: Value 벡터 (shape: [batch_size, num_heads, seq_len, d_k])

    Returns:
        ouput: 어텐션이 적용된 결과 벡터
        attn_weights: Softmax를 적용한 어텐션 가중치
    """
    d_k = Q.size(-1) # Key의 차원 수(임베딩 벡터의 크기, d_k)
    # scores 계산 : Query와 Key의 내적(dot product)을 계산하고 sqrt(d_k)로 나누어 스케일링
    scores = torch.matmul(Q, K.transpose(-2, -1)) / torch.sqrt(torch.tensor(d_k, dtype=torch.float32)) # 유사도 계산 및 스케일링
    attn_weights = F.softmax(scores, dim=-1) # Softmax를 통한 가중치 계산
    output = torch.matmul(attn_weights, V) # 가중합을 통한 최종 출력 계산
    return output, attn_weights

 

Multi-Head Attention 구현

import torch.nn as nn

class MultiHeadAttention(nn.Module):
    def __init__(self, embed_size, heads):
        """
        Multi-Head Attention 레이어

        Args:
            embed_size (int): 입력 임베딩 차원 크기
            heads (int): 몇 개의 언텐션 헤드를 사용할지 결정
        """
        super(MultiHeadAttention, self).__init__()
        self.embed_size = embed_size # 전체 임베딩 크기
        self.heads = heads # 어텐션 헤드 개수
        self.head_dim = embed_size // heads # 각 헤드당 차원 크기

        # 어텐션 헤드 개수로 나누어 떨어져야 함
        assert (
            self.head_dim * heads == embed_size
        ), "Embedding size needs to be divisible by heads"

        # Query, Key, Value를 생성하는 Linear 레이어 정의
        self.values = nn.Linear(embed_size, embed_size, bias=False)
        self.keys = nn.Linear(embed_size, embed_size, bias=False)
        self.queries = nn.Linear(embed_size, embed_size, bias=False)

        # 최종 출력 FC 레이어 (어텐션 결과를 다시 원래 차원으로 변환)
        self.fc_out = nn.Linear(heads * self.head_dim, embed_size)

    def forward(self, values, keys, query, mask=None):
        """
        Multi-Head Attention Forward Propagation

        Args:
            values (tensor): Value 벡터 (shape: [barch_size, seq_len, embed_size])
            keys (tensor): Key 벡터 (shape: [barch_size, seq_len, embed_size])
            query (tensor) : Query 벡터 (shape: [barch_size, seq_len, embed_size])
            mask (tensor, optional) : 패딩 마스킹 (기본값: None)

        Returns:
            tensor: Attention 적용된 출력 값
        """
        N = query.shape[0] # 배치 크기
        value_len, key_len, query_len = values.shape[1], keys.shape[1], query.shape[1]

        # [batch_size(입력 데이터의 배치 크기(N), heads(Multi-Head Attention의 헤드 개수), seq_len(문장의 시퀀스 길이 :토큰 개수), head_dim(각 어텐션 헤드가 사용하는 차원 크기: embed_size // heads)
        values = self.values(values).view(N, self.heads, value_len, self.head_dim)
        keys = self.keys(keys).view(N, self.heads, key_len, self.head_dim)
        queries = self.queries(query).view(N, self.heads, query_len, self.head_dim)


        # Scaled dot-product attention
        out, attn_weights = scaled_dot_product_attention(queries, keys, values)

        # Multi-Head Attention 결과를 다시 원래 크기로 변환
        out = out.view(N, query_len, self.heads * self.head_dim)

        # 최종 FC 레이어를 통과하여 최종 출력 생성
        out = self.fc_out(out)
        
        return out, attn_weights

 

 

Multi-Head Attention 실행, Attention Weights 시각화

import matplotlib.pyplot as plt
import seaborn as sns

# 입력 문장 준비
sentence = ["I", "love", "deep", "learning"]
vocab = {word: idx for idx, word in enumerate(sentence)}

# 토큰화
tokens = [vocab[word] for word in sentence]
tokens = torch.tensor(tokens).unsqueeze(0)  # (1, 4)

# 임베딩
embed_size = 128
embedding_layer = nn.Embedding(len(vocab), embed_size)
embedded_tokens = embedding_layer(tokens)  # (1, 4, 128)

# Multi-Head Attention 실행
heads = 8
multi_head_attn = MultiHeadAttention(embed_size, heads)
output, attn_weights = multi_head_attn(embedded_tokens, embedded_tokens, embedded_tokens)

# Attention Weights 크기 확인
print("Attention Weights 크기:", attn_weights.shape)

# Attention Weights 시각화
def plot_attention_weights(attention, sentence):
    attention = attention.squeeze(0).mean(dim=0).detach().numpy()  
    plt.figure(figsize=(6, 6))
    sns.heatmap(attention, annot=True, cmap="Blues", xticklabels=sentence, yticklabels=sentence)
    plt.xlabel("Key Tokens")
    plt.ylabel("Query Tokens")
    plt.title("Attention Weights (Avg of 8 Heads)")
    plt.savefig(f'{sentence}')
    plt.show()

plot_attention_weights(attn_weights, sentence)
Attention Weights 크기: torch.Size([1, 8, 4, 4])

I..love.. deep .... ... learning

 

 

 

더 깊이 파고들려면 Transformer 모델 Self-Attention을 활용한 BERT, GPT 구조까지 확장해보는 것도 좋음