Transformer - "Attention Is All You Need" 분석 - 구현하기

2026. 1. 21. 19:29·1. AI 논문 + 모델 분석/AI 논문 분석

본 프로젝트는 트랜스포머(Transformer) 아키텍처를 PyTorch로 직접 구현하고, 데이터 규모가 모델의 문맥 파악 능력과 일반화 성능에 미치는 영향을 분석하기 위해 두 차례의 실험을 진행하였습니다.

 

논문 링크: https://arxiv.org/abs/1706.03762

 

Attention Is All You Need

The dominant sequence transduction models are based on complex recurrent or convolutional neural networks in an encoder-decoder configuration. The best performing models also connect the encoder and decoder through an attention mechanism. We propose a new

arxiv.org

논문 정리 블로그 참고: 트랜스포머 논문 리뷰 블로그
Github 링크 참고: Attention-Is-All-You-Need-Review


1. 논문 구조 리뷰 및 구현 (Core Logic)

모델 아키텍처는 데이터의 양과 상관없이 논문의 핵심 구조를 모두 반영하여 클래스 단위로 구현되었습니다.

  • Multi-Head Attention: $d_k$ 스케일링을 포함한 병렬 어텐션 로직 구현.
  • Positional Encoding: 삼각함수를 이용한 위치 정보 주입.
  • Encoder Layers: Residual Connection 및 Layer Normalization이 포함된 인코더 블록 설계.

2. 데이터셋 및 실험 설정 (Dataset & Setup)

  • 데이터셋: IMDB Movie Reviews (이진 분류)
  • Tokenizer: bert-base-uncased (Max Length: 256)
  • 모델 설정: $d_{model}=128$, $num_heads=8$, $num_layers=2$, $batch_size=16$

3. 실험 결과 및 비교 분석 (Experimental Results)

학습 데이터의 규모에 따른 모델의 성능 변화를 아래와 같이 분석하였습니다.

[실험 1] 샘플링 데이터 학습 (Small Scale)

  • 데이터 규모: Train 2,000 / Test 500
  • 성능: 최종 Test Acc 74.20%
  • 특징: 강한 긍정/부정 단어는 잘 포착하나, 복잡한 문장 구조에서 한계를 보임.
  • 추론 한계: *"It was okay, but the ending was a bit disappointing."* 문장을 긍정(83.33%)으로 오분류함. (단어 'okay'에 과도하게 집중)

[실험 2] 전체 데이터 학습 (Full Scale)

  • 데이터 규모: Train 25,000 / Test 25,000
  • 성능: 최종 Test Acc 84.10% (약 10%p 향상)
  • 특징: 데이터 양이 늘어남에 따라 문장 내 미묘한 뉘앙스와 역접 구조를 명확히 학습함.
  • 추론 개선: 동일한 문장에 대해 부정(91.08%)으로 정확히 분류 성공.
실험 항목 실험 1 (Sampled) 실험 2 (Full Dataset)
학습 데이터 수 2,000개 25,000개
최종 Loss 0.3814 0.2347
Test Accuracy 74.20% 84.10%
복합 문장 분류 오분류 (긍정 판단) 정확 분류 (부정 판단)

4. 최종 결과 해석

  1. 데이터 스케일의 중요성: 동일한 트랜스포머 로직 하에서도 데이터의 양이 늘어남에 따라 모델의 일반화 성능(Generalization)이 크게 향상됨을 확인했습니다.
  2. 문맥 파악 능력 향상: 실험 2에서 "but" 이후의 부정적인 뉘앙스를 정확히 읽어낸 것은, 충분한 데이터를 통해 Self-Attention 메커니즘이 문장 내 특정 단어('okay')에 매몰되지 않고 전체적인 맥락의 가중치를 올바르게 학습했음을 증명합니다.
  3. 결론: 본 프로젝트를 통해 직접 구현한 트랜스포머 모델이 대규모 데이터셋에서 실제 논문의 성능 의도와 부합하게 작동함을 확인하였습니다.image

트랜스포머(Transformer) 확장 및 응용 분석

트랜스포머 아키텍처는 2017년 발표 이후 자연어 처리(NLP)를 넘어 컴퓨터 비전, 오디오 등 전 분야를 아우르는 범용 인공지능의 표준으로 자리 잡았습니다. 2026년 현재, 트랜스포머는 연산 효율성을 극대화한 하이브리드 구조로 진화하고 있습니다.


1. 트랜스포머를 응용한 대표적인 구조

트랜스포머는 인코더와 디코더의 활용 방식에 따라 크게 세 가지 방향으로 파생되었습니다.

BERT (Bidirectional Encoder Representations from Transformers)

  • 구조: 트랜스포머의 인코더(Encoder) 블록만 활용합니다.
  • 특징: 문장의 앞뒤 문맥을 동시에 파악하는 양방향(Bidirectional) 학습을 수행합니다.
  • 주요 용도: 문장 분류, 감성 분석, 질의응답(Q&A) 등 문맥 이해 작업.

GPT (Generative Pre-trained Transformer)

  • 구조: 트랜스포머의 디코더(Decoder) 블록만 활용합니다.
  • 특징: 이전 단어들을 바탕으로 다음 단어를 예측하는 단방향(Autoregressive) 생성 방식을 사용합니다.
  • 주요 용도: 대화형 AI, 창작, 코드 생성 등 텍스트 생성 작업.

ViT (Vision Transformer)

  • 구조: 인코더 구조를 이미지 처리에 이식했습니다.
  • 특징: 이미지를 격자 형태의 패치(Patch) 단위로 나누어 텍스트 토큰처럼 처리합니다.
  • 주요 용도: 이미지 분류, 객체 탐지 등 컴퓨터 비전 작업.

2. 트랜스포머가 범용 아키텍처가 된 이유

  1. 데이터 통합 (Tokenization): 어떤 데이터든 '토큰' 단위로 변환하면 동일한 어텐션 메커니즘으로 처리 가능한 범용성을 가집니다.
  2. 강력한 병렬 연산: RNN과 달리 전체 데이터를 한 번에 처리하여 대규모 GPU 학습에 최적화되어 있습니다.
  3. 확장 법칙 (Scaling Law): 모델 크기와 데이터 양을 늘릴수록 성능이 지속적으로 향상되는 특성이 있어 거대 모델 구축에 유리합니다.
  4. 낮은 귀납적 편향 (Low Inductive Bias): 데이터의 구조적 제약 없이 관계를 스스로 학습하여 방대한 데이터에서 정교한 패턴을 찾아냅니다.

3. 트랜스포머 진화 과정 및 비교

대표 모델 비교 표

구분 BERT GPT ViT
중심 구조 인코더 (Encoder) 디코더 (Decoder) 인코더 (Encoder)
학습 방식 양방향 문맥 이해 단방향 문장 생성 이미지 패치 관계 학습
입력 단위 텍스트 토큰 텍스트 토큰 이미지 패치
핵심 강점 정교한 의미 분석 자연스러운 문장 생성 시각적 특징 추출

트랜스포머 진화 흐름

  • 2017 (탄생): Vanilla Transformer 등장 (Attention Is All You Need).
  • 2018~2019 (분화): 이해 중심의 BERT와 생성 중심의 GPT 시리즈 시작.
  • 2020 (확장): 이미지를 처리하는 ViT 등장으로 멀티모달 시대 개막.
  • 2021~2024 (거대화): GPT-4, Llama 등 조 단위 파라미터 모델 및 효율적 연산 기법(FlashAttention) 도입.
  • 2025~2026 (하이브리드):
    • MoE (Mixture of Experts): 연산 효율을 높인 전문가 혼합 구조 (DeepSeek 등).
    • SSM 결합: 긴 문맥 처리를 위해 Mamba 구조 등과 결합한 차세대 아키텍처 연구 활발.

전체 구현 코드

!pip -q install datasets transformers

import torch
import torch.nn as nn
import torch.nn.functional as F
import math
from torch.utils.data import DataLoader, TensorDataset
from datasets import load_dataset
from transformers import AutoTokenizer
from dataclasses import dataclass
from tqdm.auto import tqdm

dataset = load_dataset('imdb')
tokenizer = AutoTokenizer.from_pretrained('bert-base-uncased')
def tokenize(batch):
    return tokenizer(batch['text'], padding='max_length', truncation=True, max_length=256)

train_data = dataset['train'].shuffle(seed=42)
test_data = dataset['test'].shuffle(seed=42)

train_tokenized = train_data.map(tokenize, batched=True)
test_tokenized = test_data.map(tokenize, batched=True)

def make_loader(data, batch_size=16):
    ids = torch.tensor(data['input_ids'])
    labels = torch.tensor(data['label'])
    return DataLoader(TensorDataset(ids, labels), batch_size=batch_size, shuffle=True)

train_loader = make_loader(train_tokenized, batch_size=16)
test_loader = make_loader(test_tokenized, batch_size=16)

PositionalEncoding 구현

class PositionalEncoding(nn.Module):
    def __init__(self, d_model, max_len=256, dropout=0.1):
        super().__init__()
        self.dropout = nn.Dropout(p=dropout)
        pe = torch.zeros(max_len, d_model)
        position = torch.arange(0, max_len, dtype=torch.float).unsqueeze(1)
        # div_term = 1 / 10000^(2i/d_model)
        div_term = torch.exp(torch.arange(0, d_model, 2).float() * (-math.log(10000.0) / d_model))

        # PE(pos, 2i) = sin(pos / 10000^(2i/d_model))
        pe[:, 0::2] = torch.sin(position * div_term)
        # PE(pos, 2i+1) = cos(pos / 10000^(2i/d_model))
        pe[:, 1::2] = torch.cos(position * div_term)

        self.register_buffer('pe', pe.unsqueeze(0))

    def forward(self, x):
        # x = x + PE: Embedding 벡터에 위치 정보 합산
        return self.dropout(x + self.pe[:, :x.size(1)])

MultiHeadAttention 구현

class MultiHeadAttention(nn.Module):
    def __init__(self, d_model, num_heads, dropout=0.1):
        super().__init__()
        self.num_heads = num_heads
        self.d_k = d_model // num_heads
        self.w_q = nn.Linear(d_model, d_model)
        self.w_k = nn.Linear(d_model, d_model)
        self.w_v = nn.Linear(d_model, d_model)
        self.fc = nn.Linear(d_model, d_model)
        self.dropout = nn.Dropout(dropout)

    def forward(self, q, k, v):
        batch_size = q.size(0)

        # Linear projection: Q, K, V 생성 (Query, Key, Value)
        # Split into heads: (batch, seq_len, num_heads, d_k) -> (batch, num_heads, seq_len, d_k)
        q = self.w_q(q).view(batch_size, -1, self.num_heads, self.d_k).transpose(1, 2)
        k = self.w_k(k).view(batch_size, -1, self.num_heads, self.d_k).transpose(1, 2)
        v = self.w_v(v).view(batch_size, -1, self.num_heads, self.d_k).transpose(1, 2)

        # Scaled Dot-Product Attention: Attention(Q, K, V) = softmax(QK^T / sqrt(d_k))V
        scores = torch.matmul(q, k.transpose(-2, -1)) / math.sqrt(self.d_k)
        attn = torch.softmax(scores, dim=-1)

        # MultiHead(Q, K, V) = Concat(head_1, ..., head_h)W^O
        context = torch.matmul(self.dropout(attn), v).transpose(1, 2).reshape(batch_size, -1, self.num_heads * self.d_k)
        return self.fc(context)

TransformerEncoderLayer 구현

class TransformerEncoderLayer(nn.Module):
    def __init__(self, d_model, num_heads, dropout=0.1):
        super().__init__()
        self.mha = MultiHeadAttention(d_model, num_heads, dropout)
        self.norm1 = nn.LayerNorm(d_model)

        # FFN(x) = max(0, xW1 + b1)W2 + b2
        self.ffn = nn.Sequential(
            nn.Linear(d_model, d_model * 4),
            nn.ReLU(),
            nn.Linear(d_model * 4, d_model)
        )
        self.norm2 = nn.LayerNorm(d_model)
        self.dropout = nn.Dropout(dropout)

    def forward(self, x):
        # Sublayer 1: Residual Connection & Layer Normalization -> LayerNorm(x + Sublayer(x))
        attn_out = self.mha(x, x, x)
        x = self.norm1(x + self.dropout(attn_out))

        # Sublayer 2: Position-wise Feed-Forward Network & LayerNorm
        ffn_out = self.ffn(x)
        x = self.norm2(x + self.dropout(ffn_out))
        return x

TransformerSentimentModel 구현

class TransformerSentimentModel(nn.Module):
    def __init__(self, vocab_size, d_model, num_heads, num_layers=2, num_classes=2, dropout=0.1):
        super().__init__()
        # Input Embedding: 문자를 d_model 차원의 벡터로 변환
        self.embedding = nn.Embedding(vocab_size, d_model)
        self.pos_encoding = PositionalEncoding(d_model, dropout=dropout)

        # N x Encoder Layers: 논문의 Nx 구조 (중첩된 인코더 층)
        self.layers = nn.ModuleList([TransformerEncoderLayer(d_model, num_heads, dropout) for _ in range(num_layers)])

        # Final Linear Layer: 분류를 위한 출력층
        self.classifier = nn.Linear(d_model, num_classes)

    def forward(self, x):
        x = self.embedding(x)
        x = self.pos_encoding(x)
        for layer in self.layers:
            x = layer(x)

        # Global Average Pooling: (batch, seq_len, d_model) -> (batch, d_model)
        x = x.mean(dim=1)
        return self.classifier(x)

학습 설정

class Trainer:
    def __init__(self, model, config, train_loader, test_loader):
        self.model = model.to(config.device)
        self.config = config
        self.train_loader = train_loader
        self.test_loader = test_loader
        self.optimizer = torch.optim.Adam(self.model.parameters(), lr=config.lr)
        self.criterion = nn.CrossEntropyLoss()

    def train(self):
        for epoch in range(self.config.epochs):
            self.model.train()
            total_loss = 0
            for batch in tqdm(self.train_loader, desc=f"Epoch {epoch+1}"):
                ids, labels = [b.to(self.config.device) for b in batch]
                self.optimizer.zero_grad()
                loss = self.criterion(self.model(ids), labels)
                loss.backward()
                self.optimizer.step()
                total_loss += loss.item()

            acc = self.evaluate()
            print(f"Loss: {total_loss/len(self.train_loader):.4f} | Test Acc: {acc:.2f}%")

    def evaluate(self):
        self.model.eval()
        correct, total = 0, 0
        with torch.no_grad():
            for batch in self.test_loader:
                ids, labels = [b.to(self.config.device) for b in batch]
                out = self.model(ids)
                correct += (out.argmax(1) == labels).sum().item()
                total += labels.size(0)
        return 100 * correct / total

def predict_sentiment(text, model, tokenizer, device):
    model.eval()
    with torch.no_grad():
        inputs = tokenizer(text, return_tensors="pt", padding='max_length',
                          truncation=True, max_length=256).to(device)
        output = model(inputs['input_ids'])
        prob = torch.softmax(output, dim=-1)
        pred = output.argmax(1).item()
        label = "긍정 😊" if pred == 1 else "부정 😡"
        print(f"리뷰: {text}\n결과: {label} ({prob[0][pred].item()*100:.2f}%)")
        print("-" * 50)

모델 설정

@dataclass
class Config:
    vocab_size: int = tokenizer.vocab_size
    d_model: int = 128
    num_heads: int = 8
    epochs: int = 12
    lr: float = 1e-4
    device: torch.device = torch.device('cuda' if torch.cuda.is_available() else 'cpu')

config = Config()
model = TransformerSentimentModel(config.vocab_size, config.d_model, config.num_heads)

학습 시작

trainer = Trainer(model, config, train_loader, test_loader)
trainer.train()

테스트 문구 생성

test_texts = [
    "This movie was a masterpiece. The depth of the characters was incredible.",
    "I hated this film. It was way too long and very boring.",
    "It was okay, but the ending was a bit disappointing."
]

for text in test_texts:
    predict_sentiment(text, model, tokenizer, config.device)

'1. AI 논문 + 모델 분석 > AI 논문 분석' 카테고리의 다른 글

[논문 리뷰] DenseNet: Densely Connected Convolutional Networks  (0) 2026.01.28
[논문 분석]Using artificial intelligence to automate meat cut identification from the semimembranosus muscle on beef boning lines  (0) 2026.01.27
[논문 리뷰] Sequence to Sequence Learning with Neural Networks  (1) 2026.01.25
Transformer - "Attention Is All You Need" 논문 리뷰  (0) 2026.01.20
[용어 정리] Transformer 논문 분석 전 기본 용어 정리  (1) 2026.01.17
'1. AI 논문 + 모델 분석/AI 논문 분석' 카테고리의 다른 글
  • [논문 분석]Using artificial intelligence to automate meat cut identification from the semimembranosus muscle on beef boning lines
  • [논문 리뷰] Sequence to Sequence Learning with Neural Networks
  • Transformer - "Attention Is All You Need" 논문 리뷰
  • [용어 정리] Transformer 논문 분석 전 기본 용어 정리
고니3000원
고니3000원
공부 내용 정리, 자기발전 블로그 입니다. 기존 네이버 블로그에서 티스토리로 이전했습니다. https://blog.naver.com/pak1010pak
  • 고니3000원
    곤이의 공부 블로그
    고니3000원
  • 전체
    오늘
    어제
    • 분류 전체보기 (178) N
      • 1. AI 논문 + 모델 분석 (20)
        • AI 논문 분석 (13)
        • AI 모델 분석 (7)
      • 2. 자료구조와 알고리즘 (16)
        • 2-1 자료구조와 알고리즘 (13)
        • 2-2 강화학습 알고리즘 (3)
      • 3. 자습 & 메모(실전, 실습, 프로젝트) (25)
        • 3-1 문제 해석 (4)
        • 3-2 메모(실전, 프로젝트) (14)
        • 3-3 배포 실전 공부 (7)
      • 4. [팀] 프로젝트 및 공모전 (14)
        • 4-1 팀 프로젝트(메모, 공부) (1)
        • 4-2 Meat-A-Eye (6)
        • 4-3 RL-Tycoon-Agent (3)
        • 4-4 구조물 안정성 물리 추론 AI 경진대회(D.. (4)
      • 5. [개인] 프로젝트 및 공모전 (0)
        • 4-1 귀멸의칼날디펜스(자바스크립트 활용) (5)
        • 4-2 바탕화면 AI 펫 프로그램 (4)
        • 4-3 개인 프로젝트(기타) (3)
      • 개념 정리 step1 (32)
        • Python 기초 (7)
        • DBMS (1)
        • HTML | CSS (3)
        • Git | GitHub (1)
        • JavaScript (5)
        • Node.js (5)
        • React (1)
        • 데이터 분석 (6)
        • Python Engineering (3)
      • 개념 정리 step2 (57) N
        • Machine | Deep Learning (15)
        • 멀티모달(Multi-modal) (23)
        • 강화 학습 (10)
        • AI Agent (9) N
  • 블로그 메뉴

    • 홈
    • 태그
    • 방명록
  • 링크

    • 네이버 곤이의 블로그(Naver->Tistory)
    • Github
  • 공지사항

  • 인기 글

  • 태그

    자료구조
    파이썬
    공모전
    데이터분석
    자바스크립트
    bottleneck
    Grad-CAM
    transformer
    논문 리뷰
    귀칼
    프로젝트
    OCR
    구현
    알고리즘
    javascript
    강화학습
    paddleocr
    파인튜닝
    pandas
    Vision
    학습
    강화 학습
    Attention Is All You Need
    github
    EfficientNet
    html
    Python
    OCR학습
    Ai
    ViT
  • 최근 댓글

  • 최근 글

  • hELLO· Designed By정상우.v4.10.5
고니3000원
Transformer - "Attention Is All You Need" 분석 - 구현하기
상단으로

티스토리툴바