Skip to main content
Summary

이 장에서 배울 것

이번 장에서는 Attention을 Transformer의 핵심 구조로 확장한다. 지난 장에서는 시퀀스 모델이 입력의 어느 부분에 집중할지 학습하는 Attention을 배웠다. Transformer는 여기서 한 걸음 더 나아간다. RNN처럼 토큰을 하나씩 읽지 않고, 문장 안의 모든 토큰이 서로를 직접 참고하게 만든다.

이 장의 목표는 세 가지다.

  • self-attention이 문장 안의 토큰 표현을 어떻게 바꾸는지 이해한다.

  • Query, Key, Value와 multi-head attention의 역할을 설명한다.

  • 사전 학습된 작은 Transformer encoder를 불러와 문장 분류에 적용한다.

여기서는 GPT식 생성 모델 전체를 깊게 다루지 않는다. 이 장의 중심은 Transformer encoder다. 생성 구조와 대규모 사전학습은 이후 장과 다음 권에서 더 자세히 다룬다.

Transformer 입문

왜 이 주제가 중요한가

Transformer는 2017년 Vaswani 등이 발표한 "Attention Is All You Need"에서 제안된 구조다.1 이 모델은 RNN과 LSTM이 자연어 처리의 중심이던 흐름을 크게 바꾸었다. 이후 BERT, GPT, T5 같은 주요 언어 모델들이 Transformer를 바탕으로 만들어졌다.

RNN은 토큰을 순서대로 처리한다. 예를 들어 "나는 오늘 영화가 재미있었다고 생각했다"라는 문장이 있으면, 앞에서부터 하나씩 읽으며 상태를 갱신한다. 이 방식은 자연스럽지만 단점이 있다. 뒤쪽 단어를 처리하려면 앞쪽 처리가 끝나야 하므로 병렬 처리가 어렵다. 또 문장이 길어지면 멀리 떨어진 단어 사이의 관계를 유지하기 어렵다.

Transformer는 다르게 접근한다. 각 토큰이 문장 안의 다른 모든 토큰을 한 번에 참고한다. 그래서 긴 거리 의존 관계를 더 직접적으로 다룰 수 있고, 행렬 연산 중심이라 GPU 병렬 처리에도 유리하다. 단, 모든 토큰 쌍을 비교하므로 입력 길이가 길어질수록 계산량과 메모리 사용량이 빠르게 늘어난다는 한계도 있다.

핵심 개념 설명

토큰화와 임베딩

Transformer의 입력은 문자 그대로의 문장이 아니다. 먼저 문장을 토큰으로 나눈다. 토큰은 단어일 수도 있고, 단어보다 작은 조각일 수도 있다. 예를 들어 영어의 "unbelievable"은 un, believ, able처럼 여러 조각으로 나뉠 수 있다. 한국어도 조사와 어미 때문에 보통 단어 단위보다 더 세밀한 토큰화가 자주 쓰인다.

토큰은 정수 ID로 바뀌고, 다시 벡터로 변환된다. 이 벡터가 임베딩이다. 임베딩은 "이 토큰이 어떤 의미적 특징을 가지는가"를 숫자로 표현한 것이다. 하지만 임베딩만으로는 부족하다. 같은 단어라도 문맥에 따라 뜻이 달라지기 때문이다.

예를 들어 "배를 먹었다"와 "배를 탔다"에서 "배"는 전혀 다르다. Transformer encoder는 self-attention을 통해 각 토큰의 표현을 주변 문맥에 맞게 다시 만든다.

Self-attention: 문장 안에서 서로 참고하기

Self-attention은 문장 안의 각 토큰이 다른 모든 토큰을 참고해 자신의 표현을 갱신하는 과정이다.

문장 예시를 보자.

그 영화는 길었지만 지루하지 않았다.

"지루하지"라는 토큰을 이해하려면 "영화", "길었지만", "않았다" 같은 주변 정보가 중요하다. Self-attention은 각 토큰이 문장 안의 다른 토큰과 얼마나 관련 있는지 점수로 계산한다. 관련이 큰 토큰의 정보는 많이 가져오고, 관련이 작은 토큰의 정보는 적게 가져온다.

이때 사용하는 세 가지 벡터가 Query, Key, Value다.

  • Query: 내가 무엇을 찾고 있는가

  • Key: 내가 어떤 정보와 관련 있는가

  • Value: 관련이 있다면 실제로 가져올 정보는 무엇인가

검색 비유로 생각하면 쉽다. Query는 검색어다. Key는 문서의 색인이다. Value는 문서의 실제 내용이다. Query와 Key가 잘 맞으면 그 Value를 많이 가져온다.

Scaled dot-product attention

Transformer의 attention 계산은 보통 다음 흐름으로 설명한다.

  1. 각 토큰 임베딩에서 Query, Key, Value 벡터를 만든다.

  2. Query와 모든 Key의 유사도를 계산한다.

  3. 유사도 점수를 적절히 나누어 값이 너무 커지지 않게 한다.

  4. softmax로 가중치로 바꾼다.

  5. 이 가중치로 Value를 가중합한다.

수식으로는 다음과 같다.

Attention(Q, K, V) = softmax(QKᵀ / √d_k) V

여기서 d_k는 Key 벡터의 차원이다. QKᵀ는 Query와 Key의 유사도 행렬이다. √d_k로 나누는 이유는 내적값이 너무 커지면 softmax가 한쪽으로 치우쳐 학습이 불안정해질 수 있기 때문이다. softmax는 여러 점수를 합이 1인 비율로 바꾸는 함수다.

핵심은 수식 자체가 아니다. 핵심은 각 토큰이 다른 토큰의 정보를 비율에 따라 섞어 새 표현을 만든다는 점이다.

Multi-head attention: 여러 관점으로 보기

하나의 attention만 있으면 모델은 한 가지 관점으로만 토큰 관계를 본다. 하지만 문장에는 여러 관계가 동시에 존재한다.

예를 들어 다음 문장을 보자.

학생이 선생님에게 질문을 했다.

어떤 관점에서는 "학생이"와 "했다"의 주어-서술어 관계가 중요하다. 다른 관점에서는 "선생님에게"와 "질문"의 의미 관계가 중요하다. 또 다른 관점에서는 조사 "이", "에게", "을"이 문장 구조를 알려 준다.

Multi-head attention은 여러 개의 attention head를 병렬로 둔다. 각 head는 서로 다른 Query, Key, Value 변환을 학습한다. 그래서 한 head는 문법 관계를, 다른 head는 의미 관계를, 또 다른 head는 긴 거리 참조를 포착할 수 있다. 마지막에는 여러 head의 출력을 이어 붙이고 다시 선형 변환해 하나의 표현으로 합친다.

Positional encoding: 순서를 알려 주기

Self-attention은 토큰들 사이의 관계를 잘 계산하지만, 그 자체만으로는 순서를 모른다. "개가 사람을 물었다"와 "사람이 개를 물었다"는 같은 단어를 포함하지만 의미가 다르다. 순서가 의미를 바꾸기 때문이다.

RNN은 토큰을 순서대로 처리하므로 위치 정보가 자연스럽게 들어간다. Transformer는 모든 토큰을 한 번에 처리하므로 별도의 위치 정보가 필요하다. 이를 positional encoding 또는 위치 임베딩이라고 한다.

Transformer의 입력 표현은 다음처럼 생각하면 된다.

입력 표현 = 토큰 임베딩 + 위치 정보

원래 논문에서는 사인과 코사인 함수를 이용한 고정 위치 인코딩을 사용했다. 최근 모델들은 학습 가능한 위치 임베딩이나 다른 방식도 사용한다. 이 장에서는 세부 변형보다 원리를 기억하면 된다. Transformer는 토큰 정보만으로는 부족하고, 반드시 위치 정보를 함께 넣어야 한다.

Transformer encoder의 한 블록

이 장에서 다룰 Transformer encoder는 다음 흐름을 가진다.

  1. 토큰화

  2. 토큰 임베딩

  3. 위치 정보 추가

  4. multi-head self-attention

  5. residual connection과 layer normalization

  6. feed-forward layer

  7. 다시 residual connection과 layer normalization

여기서 residual connection은 입력을 다음 층 출력에 더해 주는 연결이다. 깊은 신경망에서 정보와 기울기가 더 잘 흐르게 돕는다. layer normalization은 한 샘플 안의 벡터 값을 일정한 범위로 정리해 학습을 안정시키는 방법이다. feed-forward layer는 각 위치의 벡터에 독립적으로 적용되는 작은 MLP다. Attention이 토큰 사이 정보를 섞는 역할이라면, feed-forward layer는 섞인 정보를 각 위치에서 다시 가공하는 역할을 한다.

쉬운 비유 또는 사례

Transformer encoder를 회의실에 비유해 보자. 문장의 각 토큰은 회의 참석자다.

RNN 방식에서는 첫 번째 사람이 말하고, 두 번째 사람이 그 말을 듣고, 세 번째 사람이 다시 앞의 내용을 이어받는다. 회의가 순서대로 진행된다. 앞사람이 말을 끝내야 다음 사람이 말할 수 있다.

Transformer 방식에서는 모든 참석자가 동시에 회의록을 펼쳐 놓고 서로를 바라본다. 각 참석자는 "내 말을 이해하려면 누구의 말을 참고해야 하지?"를 계산한다. 어떤 사람은 주어를 많이 참고하고, 어떤 사람은 부정을 나타내는 단어를 많이 참고한다. Multi-head attention은 여러 명의 관찰자가 서로 다른 기준으로 관계를 기록하는 것과 비슷하다.

하지만 회의록에 순서가 없다면 문제가 생긴다. 누가 먼저 말했고 누가 나중에 말했는지 모르면 의미가 뒤집힐 수 있다. 그래서 각 발언에는 "몇 번째 발언인지"라는 위치 표지가 붙는다. 이것이 positional encoding이다.

직접 해보는 실습

이번 실습은 Transformer를 처음부터 학습하지 않는다. 처음부터 큰 Transformer를 학습하려면 데이터와 GPU 자원이 많이 필요하다. 대신 사전 학습된 작은 encoder 모델을 불러와 텍스트 분류를 fine-tuning한다. fine-tuning은 이미 학습된 모델을 특정 과제에 맞게 조금 더 학습시키는 방법이다.

아래 예시는 Hugging Face transformers와 datasets를 사용한다. 영어 감성 분류 데이터셋인 IMDb를 사용하지만, 같은 구조로 한국어 감성 데이터셋에도 적용할 수 있다.2

pip install torch transformers datasets evaluate

아래 코드는 작은 학습용 부분집합을 만들고, 그 안에서 train/validation을 나눈다. test set은 모든 선택이 끝난 뒤 마지막 평가에 남겨 둔다.

import torch
from datasets import load_dataset
from transformers import AutoTokenizer, AutoModelForSequenceClassification
from torch.utils.data import DataLoader

model_name = "distilbert-base-uncased"
device = torch.device("cuda" if torch.cuda.is_available() else "cpu")

raw = load_dataset("imdb")
tokenizer = AutoTokenizer.from_pretrained(model_name)

def tokenize(batch):
    return tokenizer(
        batch["text"],
        padding="max_length",
        truncation=True,
        max_length=128
    )

data = raw.map(tokenize, batched=True)
data = data.remove_columns(["text"])
data = data.rename_column("label", "labels")
data.set_format("torch")

small_train = data["train"].shuffle(seed=42).select(range(2500))
train_valid = small_train.train_test_split(test_size=500, seed=42)
train_ds = train_valid["train"]
val_ds = train_valid["test"]
test_ds = data["test"].shuffle(seed=42).select(range(500))

train_loader = DataLoader(train_ds, batch_size=8, shuffle=True)
val_loader = DataLoader(val_ds, batch_size=8)
test_loader = DataLoader(test_ds, batch_size=8)

model = AutoModelForSequenceClassification.from_pretrained(
    model_name,
    num_labels=2
).to(device)

optimizer = torch.optim.AdamW(model.parameters(), lr=2e-5)

for epoch in range(2):
    model.train()
    total_loss = 0

    for batch in train_loader:
        batch = {k: v.to(device) for k, v in batch.items()}

        outputs = model(**batch)
        loss = outputs.loss

        optimizer.zero_grad()
        loss.backward()
        optimizer.step()

        total_loss += loss.item()

    model.eval()
    val_correct = 0
    val_total = 0

    with torch.no_grad():
        for batch in val_loader:
            labels = batch["labels"].to(device)
            batch = {k: v.to(device) for k, v in batch.items()}
            outputs = model(**batch)
            preds = outputs.logits.argmax(dim=-1)
            val_correct += (preds == labels).sum().item()
            val_total += labels.size(0)

    print(
        f"epoch {epoch+1}, "
        f"loss: {total_loss / len(train_loader):.4f}, "
        f"val_acc: {val_correct / val_total:.4f}"
    )

model.eval()
correct = 0
total = 0

with torch.no_grad():
    for batch in test_loader:
        labels = batch["labels"].to(device)
        batch = {k: v.to(device) for k, v in batch.items()}
        outputs = model(**batch)
        preds = outputs.logits.argmax(dim=-1)
        correct += (preds == labels).sum().item()
        total += labels.size(0)

print("test accuracy:", correct / total)

코드에서 중요한 부분은 네 가지다.

  • AutoTokenizer: 문장을 Transformer 입력 토큰으로 바꾼다.

  • padding: 배치 안의 문장 길이를 맞춘다.

  • truncation: 너무 긴 문장을 자른다.

  • AutoModelForSequenceClassification: encoder 출력 위에 분류 헤드를 붙인 모델을 불러온다.

분류 헤드는 Transformer가 만든 문맥 표현을 받아 긍정/부정 같은 최종 클래스를 예측하는 작은 신경망이다. 여기서 학습 루프는 PyTorch 학습 루프에서 배운 구조와 같다. 순전파, 손실 계산, backward(), optimizer.step(), zero_grad()의 흐름은 변하지 않는다.

흔한 실수와 오류 분석

첫째, 토큰화 결과를 확인하지 않는 실수다. Transformer는 원문 문자열을 직접 받지 않는다. input_ids, attention_mask 같은 텐서를 받는다. attention_mask는 실제 토큰과 패딩 토큰을 구분해 준다. 패딩까지 의미 있는 단어처럼 처리하면 성능이 나빠질 수 있다.

둘째, 입력 길이를 너무 길게 잡는 실수다. Self-attention은 모든 토큰 쌍을 비교한다. 길이를 두 배로 늘리면 attention 행렬은 대략 네 배 커진다. 처음 실습에서는 max_length=128처럼 작게 시작하는 편이 좋다.

셋째, 학습률을 크게 잡는 실수다. 사전 학습 모델을 fine-tuning할 때는 보통 작은 학습률을 쓴다. 1e-5, 2e-5, 5e-5 정도에서 시작하자. 너무 크면 이미 학습된 표현이 빠르게 망가질 수 있다.

넷째, model.train()과 model.eval()을 구분하지 않는 실수다. Dropout 같은 층은 학습과 평가에서 다르게 동작한다. 평가할 때는 model.eval()과 torch.no_grad()를 함께 사용해야 한다.

다섯째, Transformer를 "문장을 이해하는 마법 상자"로 보는 실수다. Transformer도 결국 입력 벡터, 가중치, 손실, 기울기 업데이트로 학습되는 신경망이다. 강력한 이유는 구조가 문맥 정보를 잘 섞고, 큰 데이터와 잘 결합되기 때문이다.

여섯째, test set으로 설정을 고르는 실수다. 실습 중 모델이나 학습률을 고를 때는 validation set을 사용하고, test set은 마지막 확인에 남겨 두어야 한다.

요약

Transformer는 self-attention을 중심으로 한 시퀀스 모델이다. RNN처럼 토큰을 순차적으로 처리하지 않고, 각 토큰이 다른 모든 토큰을 직접 참고한다. 이 덕분에 병렬 처리가 쉽고 긴 거리 관계를 더 직접적으로 학습할 수 있다.

Self-attention은 Query, Key, Value를 사용한다. Query는 찾는 것, Key는 비교 대상, Value는 가져올 정보다. Query와 Key의 유사도로 attention 가중치를 만들고, 그 가중치로 Value를 섞어 새 문맥 표현을 만든다.

Multi-head attention은 여러 관점의 attention을 병렬로 학습한다. Positional encoding은 Transformer가 토큰 순서를 알 수 있도록 위치 정보를 더한다. Encoder 블록은 multi-head self-attention, feed-forward layer, residual connection, layer normalization으로 구성된다.

실무에서는 Transformer를 처음부터 학습하기보다, 사전 학습된 encoder 모델을 불러와 분류 헤드를 붙이고 fine-tuning하는 경우가 많다.

퀴즈

  1. RNN과 비교했을 때 Transformer가 병렬 처리에 유리한 이유는 무엇인가?

  2. Self-attention에서 Query, Key, Value는 각각 어떤 역할을 하는가?

  3. Attention(Q, K, V) = softmax(QKᵀ / √d_k) V에서 softmax의 역할은 무엇인가?

  4. Multi-head attention을 사용하는 이유는 무엇인가?

  5. Self-attention만으로는 토큰 순서를 알 수 없는 이유를 예시와 함께 설명하라.

  6. Transformer encoder의 feed-forward layer는 attention과 어떤 점에서 역할이 다른가?

  7. Fine-tuning 실습에서 attention_mask가 필요한 이유는 무엇인가?

  8. validation set과 test set을 나누어야 하는 이유는 무엇인가?

체크리스트

  • Transformer가 RNN/LSTM과 어떻게 다른지 설명할 수 있다.

  • Self-attention을 "각 토큰이 다른 토큰을 참고해 표현을 갱신하는 과정"으로 설명할 수 있다.

  • Query, Key, Value를 검색 비유로 설명할 수 있다.

  • Scaled dot-product attention의 큰 흐름을 이해했다.

  • Multi-head attention이 여러 관계를 병렬로 포착한다는 점을 이해했다.

  • Positional encoding이 필요한 이유를 설명할 수 있다.

  • 사전 학습 Transformer encoder로 간단한 문장 분류 코드를 실행할 수 있다.

  • test set은 최종 평가에 남겨 두어야 함을 이해했다.

다음 장 연결

이번 장에서는 Transformer encoder의 기본 구조를 배웠다. 다음 장에서는 이 구조가 어떻게 전이학습과 생성모델로 이어지는지 살펴본다. 특히 사전 학습된 모델을 작은 데이터에 맞게 조정하는 fine-tuning, 그리고 생성 모델이 데이터를 새로 만들어 내는 기본 아이디어를 연결한다. Transformer는 다음 장의 전이학습과 생성 AI를 이해하기 위한 핵심 발판이다.

참고

1Attention Is All You Need

2Transformers for Text Classification with IMDb Reviews

책 목록

AI 교육 3권 : 딥러닝 핵심: 입력에서 업데이트까지, 신경망을 제대로 이해하는 법

전체 목차 보기

이 노트는 보도·비평·교육·연구 목적으로 작성되었습니다. 저작권 문의가 있으시면 에서 알려주세요.

틸노트는 사용자가 AI를 활용해 노트를 쉽게 작성하거나 편집할 수 있는 서비스입니다.

AI 스퀘어 단톡방에 함께해요

AI 활용과 틸노트 사용 팁을 나누는 오픈채팅방입니다. 입장 비밀번호는 tilnote입니다.

참여하기