어텐션 이해하기
트랜스포머 아키텍처의 핵심 개념 중 하나는 어텐션(Attention)입니다.
이 아이는 문장에서 중요한 단어나 구절에 모델이 더 많은 집중을 하도록 돕습니다.
어텐션을 통해 모델은 단어들 사이의 상호작용을 효과적으로 파악하고, 각 단어가 문장에서 어떤 역할을 하는지 더 잘 이해할 수 있습니다.
예를 들어, "나는 작년에 뉴욕에서 멋진 시간을 보냈다"라는 문장이 있다고 가정해봅시다.
여기서 "뉴욕"과 "시간" 같은 단어들은 문맥에서 더 중요한 의미를 지닐 수 있습니다.
어텐션 메커니즘은 이러한 중요한 단어들에 더 높은 가중치를 부여하여, 모델이 문장의 의미를 더 정확히 이해할 수 있도록 돕습니다.
사람이 글을 읽는 방식과 어텐션
사람이 글을 읽을 때, 단순히 왼쪽에서 오른쪽으로 단어를 나열된 순서대로 읽기만 하는 것이 아닙니다.
우리는 문맥을 이해하면서 중요하거나 더 신경 써야 할 단어에 주의를 집중합니다.
- ㅇㅇ 눈ㅇ ㅇㅇㅇㅇ
이 문장을 보면 "눈"이 어떤 눈을 의미할까요? 👀 ❄️
앞뒤 단어가 가려져 있어 어떤 눈을 의미하는지 정확하게 파악하기 어렵습니다.
- 그의 눈이 반짝였다.
이 문장은 어떨까요?
앞뒤 단어가 있어 어떤 의미인지 정확하게 파악하기 쉽습니다.
또다른 예시로, "그는 아름다운 서울의 야경을 즐겼다"라는 문장을 보면
서울"와 "야경"이라는 단어는 서로 연관성이 강하고, 독자의 주의를 끌 가능성이 큽니다.
이와 반대로, "그는"과 같은 단어는 상대적으로 덜 중요한 정보로 처리됩니다.
어텐션 메커니즘은 이와 유사한 방식으로 트랜스포머 모델이 각 단어의 중요도를 학습하고, 특정 단어들 간의 관계를 파악하는 데 도움을 줍니다. 이렇게 중요한 단어에 더 집중할 수 있는 능력 덕분에 트랜스포머는 더 정확하고 효율적으로 문장을 이해할 수 있습니다.
관련이 깊은 단어는 더 많이, 관련이 적은 단어는 더 적게 반영해야 하므로
단어와 단어 사이 관계를 계산하는 방법과 관계에 따른 의미를 반영하는 방법을 알아보도록 하겠습니다.
쿼리, 키, 값 이해하기
트랜스포머 모델에서 중요한 개념 중 하나는 쿼리(Query), 키(Key), 값(Value)입니다.
이 개념은 검색이나 데이터베이스에서 많이 사용되지만, 트랜스포머 모델에서는 문장의 단어들이 서로 어떻게 연결되어 있는지를 이해하는 데 활용됩니다.
예를 들어, "저는 어제 사과를 먹었습니다"라는 문장을 보겠습니다.
여기서 "사과"라는 단어는 쿼리로 사용될 수 있습니다. 하지만 "사과"는 문맥에 따라 다른 의미를 가질 수 있습니다.
과일인 사과일 수도 있고, 잘못을 인정하는 사과일 수도 있습니다. 이를 이해하기 위해 모델은 문장에서 "사과"와 관련된 키를 찾습니다.
이 문장에서는 "먹었습니다"라는 단어가 나와 있기 때문에, "사과"는 과일로 해석될 가능성이 높습니다.
이때 모델은 값으로서 과일 '사과'에 대한 정보를 사용해 문맥을 이해하게 됩니다.
예시 문장에서 각각 쿼리, 키, 값의 역할을 다음과 같이 설명할 수 있습니다.
- 쿼리: 문장 내에서 찾고자 하는 단어입니다. 이 예시에서는 "사과"가 쿼리로 사용됩니다. 트랜스포머 모델이 "사과"라는 단어가 문장에서 어떤 의미로 사용되었는지 이해하려고 합니다.
- 키: 쿼리와 관련된 정보를 제공하는 문맥 속의 다른 단어입니다. 예를 들어, 이 문장에서 "먹었습니다"가 키 역할을 합니다. "먹다"라는 동사는 "사과"가 과일이라는 문맥을 제공해줍니다.
- 값: 최종적으로 도출되는 의미 또는 정보입니다. 이 경우, "사과"는 과일로 해석되며, 값으로서 "과일 사과"에 대한 정보가 반환됩니다.
따라서, 이 문장에서는:
- 쿼리: 사과
- 키: 먹었습니다
- 값: 과일 사과
모델은 이렇게 쿼리, 키, 값의 관계를 통해 단어의 의미를 문맥 속에서 해석합니다.
쿼리, 키, 값의 관계는 트랜스포머 모델이 문장에서 단어 간의 상호작용을 이해하는 데 큰 도움이 됩니다.
문장의 단어들이 서로 어떻게 연결되고 어떤 문맥에서 사용되는지를 파악하기 때문에 모델이 더 정확하게 문장의 의미를 분석할 수 있습니다.
트랜스포머 모델에서 쿼리, 키, 값의 활용
이전까지 우리는 쿼리(Query), 키(Key), 값(Value)의 기본적인 개념을 살펴보았습니다.
이제 이 개념들이 트랜스포머 모델 내에서 실제로 어떻게 활용되는지 설명하겠습니다.
트랜스포머 아키텍처는 단어들 간의 관계를 단순히 계산하는 데서 그치지 않고, 각 단어의 중요성을 가중치로 학습해 문맥적 의미를 파악하는 과정을 거칩니다. 이 가중치가 학습됨에 따라 모델은 단어들이 문장 내에서 어떤 관계를 맺고 있는지 더 명확하게 이해할 수 있게 됩니다.
가중치 계산 과정
트랜스포머 아키텍처에서, 각 단어는 쿼리, 키, 값으로 변환된 임베딩 벡터를 사용해 다른 단어들과의 관계를 계산합니다.
예를 들어, 문장 "저는 최근에 뉴욕을 여행했습니다"에서, "뉴욕"과 "여행"이라는 단어는 문맥적으로 높은 관련성을 가질 것입니다.
트랜스포머 모델은 이 관계를 이해하기 위해, 두 단어의 유사도를 계산하고, 그에 따른 가중치를 부여합니다.
쿼리와 키는 각 단어가 문장에서 가지는 고유한 의미를 벡터로 표현하고, 이 벡터들 간의 내적(dot product)을 통해 유사도를 계산합니다. 이 계산된 유사도는 소프트맥스(softmax) 함수를 통해 확률값으로 변환되며, 그 결과를 바탕으로 값(Value)에 가중치가 적용됩니다.
예를 들어, "뉴욕"과 "여행"이 문맥적으로 밀접한 관련이 있다면, 두 단어의 유사도가 높아져 더 많은 가중치를 부여받게 됩니다.
가중치 적용의 실제 예
예를 들어, "뉴욕"과 "여행"이 문장에서 높은 유사도를 보인다면, 트랜스포머는 "뉴욕"이라는 단어가 "여행"과 연관된 정보를 더 많이 반영하도록 가중치를 부여합니다.
반대로, "저는"과 "뉴욕" 같은 단어는 문맥적으로 상대적으로 낮은 관련성을 가질 수 있으므로 가중치가 낮아집니다.
이 과정에서 트랜스포머는 각 단어가 문장 내에서 어떤 역할을 하는지 더 명확히 이해할 수 있게 됩니다.
가중치 학습 과정
트랜스포머 모델은 학습을 통해 문맥에서 중요한 단어들 간의 관계를 학습하게 됩니다.
즉, 모델은 학습 데이터셋에서 단어들이 어떻게 함께 등장하는지를 학습하며, 특정 단어 쌍이 얼마나 자주 연관되었는지를 바탕으로 가중치를 점진적으로 조정합니다. 이를 통해 모델은 문맥을 더 잘 이해하게 되고 문장 내에서의 단어들의 상호작용을 더 정확하게 파악할 수 있게 됩니다.
결론적으로 쿼리, 키, 값의 상호작용과 가중치 학습을 통해 트랜스포머는 문장의 흐름과 단어들의 문맥적 의미를 효과적으로 파악합니다.
이 과정은 자연어 처리 작업에서 필수적인 역할을 하며, 텍스트 분류, 기계 번역 등 다양한 언어 모델에 응용됩니다.
쿼리, 키, 값 벡터를 계산하는 코드 예시
이번에는 코드로 어텐션 연산을 구현해봅시다.
1) 쿼리, 키, 값 벡터를 만드는 nn.Linear 층 예시
# 임베딩 차원과 head 차원 설정
embed_dim = 16
head_size = 16
# 쿼리, 키, 값을 계산하기 위한 Linear 레이어 생성
linear_query = nn.Linear(embed_dim, head_size)
linear_key = nn.Linear(embed_dim, head_size)
linear_value = nn.Linear(embed_dim, head_size)
# 입력 임베딩에서 쿼리, 키, 값을 계산
queries = linear_query(input_embeddings) # 쿼리 계산
keys = linear_key(input_embeddings) # 키 계산
values = linear_value(input_embeddings) # 값 계산
2) 점수를 계산하는 소프트맥스 함수와 최종 결과 계산 및 입출력 예시
from math import sqrt
import torch.nn.functional as F
# 점수 계산 함수 정의
def compute_attention(query_matrix, key_matrix, value_matrix):
scale_factor = query_matrix.size(-1) ** 0.5 # 스케일링을 위한 제곱근 계산
scores = torch.matmul(query_matrix, key_matrix.transpose(-2, -1)) / scale_factor # 쿼리와 키 내적 계산
attention_weights = F.softmax(scores, dim=-1) # 소프트맥스 적용하여 확률로 변환
return torch.matmul(attention_weights, value_matrix) # 가중치 적용하여 최종 결과 계산
# 원본 입력과 어텐션 적용 후 출력 형태 확인
print("원본 입력 형태: ", input_embeddings.shape)
after_attention_embeddings = compute_attention(queries, keys, values)
print("어텐션 적용 후 형태: ", after_attention_embeddings.shape)
3) 어텐션 연산의 클래스 입출력 최종 코드 예시
import torch
import torch.nn as nn
import torch.nn.functional as F
class AttentionHead(nn.Module):
def __init__(self, token_embed_dim, head_dim, is_causal=False):
super(AttentionHead, self).__init__()
self.is_causal = is_causal
self.linear_q = nn.Linear(token_embed_dim, head_dim) # 쿼리를 위한 Linear 레이어
self.linear_k = nn.Linear(token_embed_dim, head_dim) # 키를 위한 Linear 레이어
self.linear_v = nn.Linear(token_embed_dim, head_dim) # 값을 위한 Linear 레이어
def forward(self, input_embeddings):
queries = self.linear_q(input_embeddings) # 쿼리 계산
keys = self.linear_k(input_embeddings) # 키 계산
values = self.linear_v(input_embeddings) # 값 계산
# 어텐션 점수 계산
scores = torch.matmul(queries, keys.transpose(-2, -1)) / (queries.size(-1) ** 0.5)
if self.is_causal:
# 자기 회귀 모델에서 사용하는 마스크 처리 (미래 정보 차단)
mask = torch.triu(torch.ones(scores.shape), diagonal=1).bool()
scores = scores.masked_fill(mask, float('-inf'))
attention_weights = F.softmax(scores, dim=-1) # 소프트맥스로 확률 계산
output = torch.matmul(attention_weights, values) # 어텐션 가중치를 통해 값 계산
return output
# 입력 데이터 및 어텐션 연산 적용 후 출력 형태 확인
input_embeddings = torch.rand(1, 5, 16) # 입력 예시 (배치 크기=1, 시퀀스 길이=5, 임베딩 차원=16)
embedding_dim = 16
# AttentionHead 인스턴스 생성
attention_head = AttentionHead(token_embed_dim=embedding_dim, head_dim=embedding_dim)
# 어텐션 연산 적용
after_attention_embeddings = attention_head(input_embeddings)
# 출력 형태 확인
print("입력 형태 확인: ", input_embeddings.shape)
print("어텐션 적용 후 출력 형태 확인: ", after_attention_embeddings.shape)
# 출력 예시:
# 입력 형태 확인: torch.Size([1, 5, 16])
# 어텐션 적용 후 출력 형태 확인: torch.Size([1, 5, 16])
멀티 헤드 어텐션
멀티헤드 어텐션(Multi-Head Attention)은 입력 데이터를 여러 개의 어텐션 헤드로 나누어 각각 다른 부분을 동시에 처리할 수 있는 메커니즘을 제공합니다. 이를 통해 모델은 문장의 여러 부분을 병렬로 이해하고, 더 풍부한 문맥 정보를 학습하여 성능을 높입니다.
멀티헤드 어텐션은 기존의 어텐션 메커니즘과 유사하지만, 여러 개의 "헤드"로 나누어 각기 다른 서브스페이스에서 어텐션을 수행합니다. 이 과정에서 각각의 어텐션은 다른 관계와 문맥을 포착할 수 있게 되며, 이를 다시 합쳐 더 풍부한 정보를 제공합니다.
예를 들어, 단일 어텐션 메커니즘에서는 하나의 관계만을 집중해서 학습할 수 있지만, 멀티헤드 어텐션은 동시에 여러 관계를 학습할 수 있습니다. '멀티'라는 이름에서 알 수 있듯이 한 번에 여러 관계를 처리하는 것이 이 메커니즘의 핵심입니다.
작동 방식
멀티헤드 어텐션은 각 어텐션 헤드가 쿼리, 키, 값(Q, K, V)을 각각 계산하고, 이 결과를 병렬로 학습한 후 이를 결합하는 방식으로 동작합니다.
- 쿼리(Q), 키(K), 값(V) 계산: 입력된 임베딩에 대해 각 어텐션 헤드는 쿼리, 키, 값을 각각 학습합니다. 이 과정은 단일 어텐션과 동일하게 진행되지만, 헤드가 여러 개라는 점에서 차이가 있습니다.
- 각 헤드의 독립적 어텐션 계산: 여러 개의 어텐션 헤드가 독립적으로 쿼리, 키, 값에 대한 어텐션 가중치를 학습합니다. 각각의 헤드는 서로 다른 문맥적 관계를 학습할 수 있기 때문에, 병렬적으로 여러 관점을 반영하게 됩니다.
- 결과 결합: 모든 헤드에서 계산된 어텐션 결과를 결합합니다. 이 결합된 값은 다시 선형 변환을 통해 출력층에 전달됩니다.
장점
멀티헤드 어텐션의 가장 큰 장점은 단일 어텐션으로는 학습할 수 없는 여러 관계를 동시에 학습할 수 있다는 점입니다. 예를 들어, 문장 내에서 "뉴욕"와 "여행"이라는 단어가 서로 관련이 있는지, 또는 "나는"과 "여행"이 관련이 있는지를 각각 다른 헤드에서 학습할 수 있습니다. 이는 트랜스포머 모델이 문맥을 더 깊이 이해하게 만들어, 번역, 질의 응답, 언어 생성 등의 다양한 자연어 처리 작업에서 뛰어난 성능을 발휘하게 합니다.
코드 예시
import torch
import torch.nn as nn
class MultiheadAttention(nn.Module):
def __init__(self, token_embed_dim, n_head, is_causal=False):
super(MultiheadAttention, self).__init__()
self.n_head = n_head
self.is_causal = is_causal
self.token_embed_dim = token_embed_dim
# 각 헤드마다 쿼리, 키, 값에 대한 선형 변환
self.weight_q = nn.Linear(token_embed_dim, token_embed_dim * n_head)
self.weight_k = nn.Linear(token_embed_dim, token_embed_dim * n_head)
self.weight_v = nn.Linear(token_embed_dim, token_embed_dim * n_head)
self.concat_linear = nn.Linear(token_embed_dim * n_head, token_embed_dim)
def forward(self, input_embeddings):
# 임베딩 크기 (배치 크기 B, 시퀀스 길이 T, 임베딩 차원 C)
B, T, C = input_embeddings.size()
# 쿼리, 키, 값을 계산하고, n_head로 나누어 각 헤드에서 별도의 어텐션 계산
queries = self.weight_q(input_embeddings).view(B, T, self.n_head, C // self.n_head).transpose(1, 2)
keys = self.weight_k(input_embeddings).view(B, T, self.n_head, C // self.n_head).transpose(1, 2)
values = self.weight_v(input_embeddings).view(B, T, self.n_head, C // self.n_head).transpose(1, 2)
# 어텐션 계산 (각 헤드별로 병렬 계산)
attention_weights = torch.matmul(queries, keys.transpose(-2, -1)) / (C ** 0.5)
attention_weights = torch.softmax(attention_weights, dim=-1)
attention_output = torch.matmul(attention_weights, values)
# 각 헤드에서 나온 결과를 결합하여 출력
attention_output = attention_output.transpose(1, 2).contiguous().view(B, T, C)
output = self.concat_linear(attention_output)
return output
# 예시로 사용할 임베딩 생성
input_embeddings = torch.rand(1, 5, 16) # (배치 크기 1, 시퀀스 길이 5, 임베딩 차원 16)
# 멀티헤드 어텐션 적용
n_head = 4
mh_attention = MultiheadAttention(token_embed_dim=16, n_head=n_head)
output = mh_attention(input_embeddings)
print("어텐션 출력 형태:", output.shape)
멀티헤드 어텐션은 단일 어텐션보다 문맥을 더 잘 파악할 수 있도록 돕는 강력한 메커니즘입니다.
여러 헤드를 사용해 다양한 문맥적 정보를 동시에 처리함으로써 모델이 더 풍부한 의미를 학습하고, 더 복잡한 관계를 잘 이해할 수 있게 합니다.
'IT 기술 > AI' 카테고리의 다른 글
ManyChat API란? 그리고 GPT와의 사용법 (3) | 2024.09.30 |
---|---|
[AI] 토큰화 : 텍스트를 임베딩으로 변환해보자 (12) | 2024.09.25 |
[AI] 트랜스포머 아키텍처 (11) | 2024.09.25 |
[AI] LLM 어플리케이션 개발을 위한 준비, sLLM이란? (4) | 2024.09.25 |
[AI] AI 모델 테스트 및 레드팀의 필요성 (4) | 2024.09.17 |