BERT / GPT 원리와 활용

BERT는 어떻게 문장을 이해하고 GPT는 어떻게 문장을 생성하는지 두 모델의 구조 차이와 실제 활용법을 Transformer 구조 기반으로 정리

2026.04.10

BERTGPTlanguage modelpre-trainingfine-tuningNLPTransformertext generation

0. 시리즈

제목역할
심화 1편CNN — 이미지 분류 완벽 정리이미지 처리
심화 2편RNN / LSTM — 시계열·텍스트 처리순서 데이터
심화 3편Transformer 구조 완벽 정리현대 AI 핵심
심화 4편⬅️BERT / GPT 원리와 활용언어 모델
심화 5편HuggingFace로 LLM Fine-tuningLLM 실전

1. BERT란?

1-1. BERT 등장 배경

2018년 Google AI가 발표한 BERT(Bidirectional Encoder Representations from Transformers) 는 NLP 역사상 가장 큰 전환점입니다. 발표 직후 11개 NLP 벤치마크에서 동시에 최고 성능을 기록했습니다.

이전까지:
  태스크마다 모델을 처음부터 학습 → 데이터 많이 필요, 시간 오래 걸림

BERT 이후:
  대규모 텍스트로 사전 학습된 모델을 가져와서
  내 태스크에 맞게 조금만 추가 학습 (Fine-tuning)
  → 적은 데이터, 짧은 시간, 높은 성능

1-2. 전이 학습(Transfer Learning)이란?

[사전 학습 Pre-training]
  대규모 코퍼스 (위키피디아, BookCorpus 등 수십 GB)
  → 모델이 언어의 일반적인 패턴을 학습

         ↓  학습된 가중치 (pretrained weights)

[미세 조정 Fine-tuning]
  내 태스크의 소규모 데이터로 추가 학습
  (감성 분석, 질의응답, 개체명 인식 등)

전이 학습을 비유하면:

  • 사전 학습 = 수년간 영어 공부로 언어 구조와 의미를 완전히 익힌 상태
  • 파인튜닝 = 그 지식을 바탕으로 법률 문서 분류를 며칠 만에 익히는 것

1-3. 양방향(Bidirectional)이란?

GPT (단방향 → 왼쪽에서 오른쪽만 봄):
"나는 [은행]에 ___"
→ "나는", "은행에" 만 보고 다음 단어 예측

BERT (양방향 → 앞뒤 모두 봄):
"나는 [MASK]에 돈을 입금했다"
→ "나는" + "돈을 입금했다" 까지 보고 MASK 예측
→ "은행"이라고 맞힐 수 있음 ✅

앞뒤 문맥을 동시에 보기 때문에 문장 이해 태스크 에 훨씬 강합니다.

1-4. BERT의 사전 학습 방법 2가지

① MLM (Masked Language Model) — 빈칸 맞히기

원본: "나는 오늘 학교에 갔다"
입력: "나는 [MASK] 학교에 갔다"
목표: [MASK] 위치에서 "오늘"을 예측

학습 규칙:
- 전체 토큰의 15%를 무작위 선택
- 선택된 토큰 중:
  80% → [MASK]로 교체
  10% → 랜덤 다른 단어로 교체
  10% → 그대로 유지 (모델이 어떤 위치든 의심하도록)

② NSP (Next Sentence Prediction) — 다음 문장 예측

긍정 예시 (IsNext):
  문장A: "강아지가 공원에서 뛰어놀았다."
  문장B: "강아지는 매우 행복해 보였다."  → 실제 다음 문장 ✅

부정 예시 (NotNext):
  문장A: "강아지가 공원에서 뛰어놀았다."
  문장B: "주식 시장이 오늘 급락했다."   → 랜덤 문장 ❌

1-5. BERT의 입력 구조

입력 문장: "나는 밥을 먹었다"

토큰:  [CLS] 나는  밥을  먹었다  [SEP]
         ↑                           ↑
   문장 시작 특수 토큰        문장 끝 특수 토큰

최종 입력 = Token Embedding
           + Segment Embedding (문장A=0 / 문장B=1)
           + Position Embedding (학습 가능한 위치 벡터)

[CLS] 토큰:
→ 문장 전체의 요약 정보가 담기도록 학습됨
→ 분류 태스크에서 [CLS] 출력벡터를 Dense 레이어에 연결

1-6. BERT-base vs BERT-large vs ModernBERT (2026 기준)

2024년 말 등장한 ModernBERT 는 기존 BERT의 구조를 대폭 개선한 경량화 버전입니다.

구분BERT-baseBERT-largeModernBERT
Encoder 레이어1224선택적 조정 가능
은닉 차원7681024가변
파라미터 수1.1억3.4억경량화 (선택 가능)
학습 속도보통느림최대 2배 빠름
컨텍스트 길이512 토큰512 토큰8K 이상
위치 임베딩학습 가능학습 가능RoPE (회전 위치 임베딩)
권장 상황일반 실습최고 성능 필요2026년 실무 권장

💡 RoPE (Rotary Position Embedding) — 3편 Transformer에서 배운 sin/cos Positional Encoding의 개선 버전입니다. 위치 정보를 회전 행렬로 표현해 더 긴 컨텍스트를 효율적으로 처리합니다.


2. GPT란?

2-1. GPT 등장 배경

GPT(Generative Pretrained Transformer) 는 OpenAI가 2018년 발표한 모델입니다. BERT가 "문장을 이해하는 AI"라면 GPT는 "문장을 생성하는 AI" 입니다.

BERT : Transformer의 Encoder 구조 활용 → 이해(Understanding)
GPT  : Transformer의 Decoder 구조 활용 → 생성(Generation)

2-2. 단방향(Autoregressive)이란?

GPT는 항상 왼쪽 → 오른쪽 방향으로만 이전 토큰을 보며 다음 토큰을 예측합니다.

"안녕" → "안녕하" → "안녕하세" → "안녕하세요" → ...
한 토큰씩 이어 붙이며 자연스러운 문장 생성

2-3. GPT의 사전 학습 — CLM (Causal Language Model)

문장: "나는 오늘 밥을 먹었다"

학습 방식 (다음 단어 예측):
  "나는"           → 예측: "오늘" ✅
  "나는 오늘"      → 예측: "밥을" ✅
  "나는 오늘 밥을" → 예측: "먹었다" ✅

→ 대규모 텍스트의 모든 위치에서 이 과정을 반복
→ 언어의 통계적 패턴을 완전히 학습

2-4. GPT 발전 흐름 — 2026년 최신 기준

버전연도파라미터 수핵심 특징
GPT-120181.1억Transformer Decoder 기반 최초 GPT
GPT-2201915억"너무 강력해서 공개 안 함" 이슈
GPT-320201,750억Few-shot 학습 능력 폭발적 향상
GPT-42023비공개멀티모달(텍스트+이미지)
GPT-52025비공개내장 추론(Thinking) 기능, 전문가급 지능
GPT-5.42026.03비공개1M 토큰 컨텍스트, 컴퓨터 직접 사용, 추론 강도 5단계 조절

GPT-5.4 주요 신기능 (2026년 3월 기준)

① 추론 강도 조절 (Configurable Reasoning Effort)
   none / low / medium / high / xhigh 5단계
   → 간단한 쿼리: low (비용 절감)
   → 복잡한 코딩: xhigh (정확도 극대화)

② 컨텍스트 창 1M 토큰
   기존 GPT-5.2 대비 2.5배 확장
   → 소설 한 편, 대형 코드베이스 전체를 한 번에 처리

③ Computer Use (컴퓨터 직접 사용)
   마우스·키보드를 직접 조작하는 에이전트 기능
   OSWorld 벤치마크 75% 달성

④ 토큰 효율 개선
   복잡한 작업에서 GPT-5.3 대비 47% 적은 토큰 사용

2-5. Few-shot / Zero-shot 학습이란?

GPT-3부터는 Fine-tuning 없이도 프롬프트만으로 다양한 태스크를 수행합니다.

Zero-shot (예시 없음):
  "다음 문장을 영어로 번역해줘: 나는 학교에 간다"
  → I go to school.

One-shot (예시 1개):
  "번역 예시: 안녕하세요 → Hello
   번역해줘: 나는 학교에 간다"
  → I go to school.

Few-shot (예시 여러 개):
  예시 여러 개를 주면 더 정확하게 수행

3. BERT vs GPT 한눈에 비교

비교 항목BERTGPT
개발사Google AIOpenAI
기반 구조Transformer EncoderTransformer Decoder
문맥 방향양방향 (앞+뒤 모두)단방향 (왼→오른쪽)
사전 학습MLM + NSPCLM (다음 단어 예측)
강점분류, 질의응답, NER텍스트 생성, 요약, 번역
2026년 현재ModernBERT로 경량화 진화GPT-5.4까지 발전
Fine-tuning태스크별 헤드 추가프롬프트 엔지니어링

4. 2026년 주요 LLM 현황

💡 실무에서 GPT나 BERT를 직접 사전 학습하는 경우는 없습니다. 이미 잘 훈련된 모델을 가져다 쓰는 것이 기본입니다. 2026년 현재 어떤 모델들이 있는지 파악해두면 5편 Fine-tuning 실습에 큰 도움이 됩니다.

4-1. 2026년 3월 기준 주요 프론티어 모델

모델개발사핵심 강점특이사항
GPT-5.4OpenAI범용성, AIME 수학 100%, 1M 컨텍스트Computer Use
Claude Opus 4.6Anthropic코딩 최강(SWE-bench 80.8%), 1M 컨텍스트14.5시간 에이전트
Claude Sonnet 4.6AnthropicOpus급 코딩을 1/5 가격에가성비 최강
Gemini 3.1 ProGoogle논리 추론(ARC-AGI-2 77.1%), 멀티모달검색 연동 강점
Qwen 3.5-122BAlibaba오픈소스 최강급상업적 무료 사용 가능
DeepSeek v4DeepSeek추론 특화, 저비용중국 오픈소스

4-2. 주요 벤치마크 비교 (2026년 3월)

벤치마크GPT-5.4Claude Opus 4.6Gemini 3.1 Pro평가 내용
AIME 2025100%수학 추론
SWE-bench Verified77.2%80.8%실전 코딩
ARC-AGI-277.1%논리 패턴 추론
GPQA94.3%PhD급 추론

4-3. HuggingFace 2026년 현황

2026년 Spring 보고서 기준:
- 중국 모델 다운로드 점유율: 41% (Qwen, DeepSeek 시리즈 주도)
- 초소형·고효율 모델 트렌드 가속 (LiquidAI LFM2.5-350M 등)
- Transformers 라이브러리: 지속 업데이트 중
- 오픈소스 모델이 상업 모델과 성능 격차 빠르게 좁히는 중

5. HuggingFace Transformers 라이브러리

5-1. HuggingFace란?

HuggingFace는 수만 개의 사전 학습 모델과 데이터셋을 무료로 제공하는 플랫폼입니다. transformers 라이브러리 하나로 BERT, GPT, Qwen, LLaMA 등 거의 모든 최신 모델을 바로 불러와 사용할 수 있습니다.

bash# 설치
pip install transformers datasets torch
pip install accelerate  # 학습 속도 향상

5-2. pipeline — 코드 5줄로 바로 사용

pythonfrom transformers import pipeline

# ── 감성 분석 ──────────────────────────────────────────────────
classifier = pipeline("sentiment-analysis")

results = classifier([
    "This movie was absolutely fantastic!",
    "I hated every minute of this film."
])
for r in results:
    print(f"  레이블: {r['label']:10s}  점수: {r['score']:.4f}")
# 출력:
#   레이블: POSITIVE     점수: 0.9998
#   레이블: NEGATIVE     점수: 0.9994


# ── 텍스트 생성 ────────────────────────────────────────────────
generator = pipeline("text-generation", model="gpt2")

output = generator(
    "Artificial intelligence will",
    max_new_tokens       = 50,
    num_return_sequences = 2,
    do_sample            = True,
    temperature          = 0.8
)
for i, o in enumerate(output):
    print(f"  생성 {i+1}: {o['generated_text']}")


# ── 빈칸 채우기 (BERT 스타일) ───────────────────────────────────
fill_mask = pipeline("fill-mask", model="bert-base-uncased")

result = fill_mask("The capital of France is [MASK].")
for r in result[:3]:
    print(f"  {r['token_str']:15s} 확률: {r['score']:.4f}")
# 출력:
#   paris           확률: 0.9971
#   london          확률: 0.0013
#   berlin          확률: 0.0007


# ── 질의응답 ────────────────────────────────────────────────────
qa = pipeline("question-answering")

context = """
Hugging Face is a company that develops tools for building machine learning
applications. It was founded in 2016 and is headquartered in New York City.
"""
result = qa(question="When was Hugging Face founded?", context=context)
print(f"  답변: {result['answer']}")   # 2016
print(f"  점수: {result['score']:.4f}")

5-3. AutoModel / AutoTokenizer

pythonfrom transformers import AutoTokenizer, AutoModel

model_name = "bert-base-uncased"

tokenizer = AutoTokenizer.from_pretrained(model_name)
model     = AutoModel.from_pretrained(model_name)

print(f"모델 파라미터 수: {sum(p.numel() for p in model.parameters()):,}")
# 109,482,240 (약 1억 1천만)

5-4. 토크나이저(Tokenizer) 완벽 이해

pythonfrom transformers import AutoTokenizer

tokenizer = AutoTokenizer.from_pretrained("bert-base-uncased")
text = "Hello, I am learning about BERT!"

# 기본 토큰화
tokens = tokenizer.tokenize(text)
print(f"토큰:   {tokens}")
# ['hello', ',', 'i', 'am', 'learning', 'about', 'bert', '!']

# 인코딩 (숫자로 변환)
encoding = tokenizer(
    text,
    max_length      = 20,
    padding         = "max_length",  # 최대 길이로 패딩
    truncation      = True,
    return_tensors  = "pt"           # PyTorch tensor 반환
)

print(f"input_ids:      {encoding['input_ids']}")
print(f"attention_mask: {encoding['attention_mask']}")
print(f"token_type_ids: {encoding['token_type_ids']}")
input_ids:
  [101, 7592, 1010, 1045, ..., 102, 0, 0, 0]
   ↑[CLS]                    ↑[SEP] ↑ 패딩(0)

attention_mask:
  [1, 1, 1, 1, 1, 1, 1, 1, 1, 0, 0, 0]
   ↑실제 토큰                   ↑ 패딩 → 무시

token_type_ids:
  [0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0]
   ↑ 문장A = 0 / 문장B = 1 (NSP 태스크용)
python# 두 문장 동시 인코딩 (문장 쌍 태스크)
encoding_pair = tokenizer(
    "나는 밥을 먹었다", "배가 너무 불렀다",
    max_length     = 30,
    padding        = "max_length",
    truncation     = True,
    return_tensors = "pt"
)
print(f"token_type_ids: {encoding_pair['token_type_ids']}")
# [0,0,...,0, 1,1,...,1, 0,0]
#  ↑문장A      ↑문장B     ↑패딩

# 디코딩 (숫자 → 문자열로 복원)
decoded = tokenizer.decode(encoding['input_ids'][0])
print(f"디코딩: {decoded}")
# [CLS] hello, i am learning about bert! [SEP] [PAD] ...

WordPiece vs BPE (서브워드 토큰화)

# BERT는 WordPiece 방식
"unbelievable" → ["un", "##believ", "##able"]
                              ↑ ##는 앞 토큰과 이어지는 서브워드
"playing"      → ["play", "##ing"]

# GPT는 BPE(Byte Pair Encoding) 방식
"lower"    → ["low", "er"]
"lowest"   → ["low", "est"]

공통 장점:
- 사전에 없는 단어(OOV)도 서브워드로 분해하여 처리 가능
- 어휘 사전을 작게 유지하면서 다양한 단어 표현 가능

6. BERT 실습 — 감성 분석 (문장 분류)

6-1. 준비 및 데이터 불러오기

pythonimport torch
import numpy as np
from torch.utils.data import Dataset, DataLoader
from transformers import (
    AutoTokenizer,
    BertForSequenceClassification,
    get_linear_schedule_with_warmup
)
from datasets import load_dataset
from sklearn.model_selection import train_test_split
from sklearn.metrics import accuracy_score
import matplotlib.pyplot as plt

device = "cuda" if torch.cuda.is_available() else "cpu"
print(f"사용 장치: {device}")

# HuggingFace datasets로 IMDB 불러오기
dataset = load_dataset("imdb")
print(dataset)
# DatasetDict({
#     train: Dataset({features: ['text', 'label'], num_rows: 25000})
#     test:  Dataset({features: ['text', 'label'], num_rows: 25000})
# })

# 실습용으로 일부만 사용 (전체 학습 시 시간 매우 오래 걸림)
train_texts  = dataset["train"]["text"][:2000]
train_labels = dataset["train"]["label"][:2000]
test_texts   = dataset["test"]["text"][:500]
test_labels  = dataset["test"]["label"][:500]

# 검증 세트 분리 (9:1)
train_texts, val_texts, train_labels, val_labels = train_test_split(
    train_texts, train_labels, test_size=0.1, random_state=42
)

print(f"훈련: {len(train_texts)}개 / 검증: {len(val_texts)}개 / 테스트: {len(test_texts)}개")
print(f"샘플: {train_texts[0][:80]}...")
print(f"레이블: {'긍정' if train_labels[0] == 1 else '부정'}")

6-2. 토크나이저 적용 및 Dataset 클래스

pythonMODEL_NAME = "bert-base-uncased"
MAX_LEN    = 128  # 메모리 부족 시 64로 줄이기

tokenizer = AutoTokenizer.from_pretrained(MODEL_NAME)


class IMDBDataset(Dataset):
    def __init__(self, texts, labels, tokenizer, max_len):
        self.texts     = texts
        self.labels    = labels
        self.tokenizer = tokenizer
        self.max_len   = max_len

    def __len__(self):
        return len(self.texts)

    def __getitem__(self, idx):
        encoding = self.tokenizer(
            str(self.texts[idx]),
            max_length     = self.max_len,
            padding        = "max_length",
            truncation     = True,
            return_tensors = "pt"
        )
        return {
            "input_ids"      : encoding["input_ids"].squeeze(0),
            "attention_mask" : encoding["attention_mask"].squeeze(0),
            "token_type_ids" : encoding["token_type_ids"].squeeze(0),
            "label"          : torch.tensor(self.labels[idx], dtype=torch.long)
        }


BATCH_SIZE = 16  # GPU 메모리에 따라 8로 줄이기 가능

train_ds = IMDBDataset(train_texts, train_labels, tokenizer, MAX_LEN)
val_ds   = IMDBDataset(val_texts,   val_labels,   tokenizer, MAX_LEN)
test_ds  = IMDBDataset(test_texts,  test_labels,  tokenizer, MAX_LEN)

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

# 샘플 확인
sample = train_ds[0]
print(f"input_ids shape:      {sample['input_ids'].shape}")       # (128,)
print(f"attention_mask shape: {sample['attention_mask'].shape}")  # (128,)
print(f"label: {sample['label']}")

6-3. BertForSequenceClassification 모델

pythonmodel = BertForSequenceClassification.from_pretrained(
    MODEL_NAME,
    num_labels = 2   # 긍정(1) / 부정(0)
)
model = model.to(device)

total_params = sum(p.numel() for p in model.parameters())
train_params = sum(p.numel() for p in model.parameters() if p.requires_grad)
print(f"전체 파라미터: {total_params:,}")
print(f"학습 파라미터: {train_params:,}")
BertForSequenceClassification 구조:

  BertModel (사전 학습 완료 — 건드리지 않아도 됨)
    ├── Embeddings
    └── Encoder × 12 (각 BertLayer: Self-Attention + FFN)
  ↓
  [CLS] 토큰의 출력 벡터 (768차원)
  ↓
  Dropout(0.1)
  ↓
  Linear(768 → 2)  ← 이 부분만 새로 학습됨
  ↓
  [부정 확률, 긍정 확률]

6-4. 옵티마이저 및 스케줄러 설정

pythonEPOCHS       = 3       # BERT Fine-tuning은 2~4 epoch면 충분
LR           = 2e-5    # BERT Fine-tuning 권장 학습률
WARMUP_RATIO = 0.1     # 전체 스텝의 10%를 warm-up

total_steps  = len(train_loader) * EPOCHS
warmup_steps = int(total_steps * WARMUP_RATIO)

optimizer = torch.optim.AdamW(
    model.parameters(),
    lr           = LR,
    weight_decay = 0.01   # L2 정규화
)

# Linear Warm-up → Linear Decay
scheduler = get_linear_schedule_with_warmup(
    optimizer,
    num_warmup_steps   = warmup_steps,
    num_training_steps = total_steps
)

print(f"총 학습 스텝: {total_steps}")
print(f"Warm-up 스텝: {warmup_steps}")

💡 AdamW — Adam 옵티마이저에 Weight Decay를 올바르게 적용한 버전입니다. BERT Fine-tuning의 표준 옵티마이저입니다.

💡 Warm-up — 처음 몇 스텝 동안 학습률을 0에서 LR까지 서서히 높입니다. 사전 학습된 가중치가 초반에 급격히 망가지는 것을 방지합니다.

6-5. 학습 루프

pythondef train_epoch(model, loader, optimizer, scheduler, device):
    model.train()
    total_loss = 0
    all_preds, all_labels = [], []

    for batch in loader:
        input_ids      = batch["input_ids"].to(device)
        attention_mask = batch["attention_mask"].to(device)
        token_type_ids = batch["token_type_ids"].to(device)
        labels         = batch["label"].to(device)

        optimizer.zero_grad()

        # HuggingFace 모델은 labels 전달 시 loss 자동 계산
        outputs = model(
            input_ids      = input_ids,
            attention_mask = attention_mask,
            token_type_ids = token_type_ids,
            labels         = labels
        )

        loss   = outputs.loss
        logits = outputs.logits  # (batch, num_labels)

        loss.backward()
        torch.nn.utils.clip_grad_norm_(model.parameters(), max_norm=1.0)
        optimizer.step()
        scheduler.step()

        total_loss += loss.item()
        preds = logits.argmax(dim=-1).cpu().numpy()
        all_preds.extend(preds)
        all_labels.extend(labels.cpu().numpy())

    return total_loss / len(loader), accuracy_score(all_labels, all_preds)


def eval_epoch(model, loader, device):
    model.eval()
    total_loss = 0
    all_preds, all_labels = [], []

    with torch.no_grad():
        for batch in loader:
            input_ids      = batch["input_ids"].to(device)
            attention_mask = batch["attention_mask"].to(device)
            token_type_ids = batch["token_type_ids"].to(device)
            labels         = batch["label"].to(device)

            outputs = model(
                input_ids      = input_ids,
                attention_mask = attention_mask,
                token_type_ids = token_type_ids,
                labels         = labels
            )

            total_loss += outputs.loss.item()
            preds = outputs.logits.argmax(dim=-1).cpu().numpy()
            all_preds.extend(preds)
            all_labels.extend(labels.cpu().numpy())

    return total_loss / len(loader), accuracy_score(all_labels, all_preds)


# 학습 실행
history = {"train_loss": [], "val_loss": [],
           "train_acc":  [], "val_acc":  []}
best_val_acc = 0.0

for epoch in range(EPOCHS):
    print(f"\nEpoch {epoch+1}/{EPOCHS}")
    print("-" * 40)

    train_loss, train_acc = train_epoch(model, train_loader,
                                        optimizer, scheduler, device)
    val_loss,   val_acc   = eval_epoch(model, val_loader, device)

    history["train_loss"].append(train_loss)
    history["val_loss"].append(val_loss)
    history["train_acc"].append(train_acc)
    history["val_acc"].append(val_acc)

    print(f"Train Loss: {train_loss:.4f} | Train Acc: {train_acc:.4f}")
    print(f"Val   Loss: {val_loss:.4f}   | Val   Acc: {val_acc:.4f}")

    if val_acc > best_val_acc:
        best_val_acc = val_acc
        torch.save(model.state_dict(), "best_bert_imdb.pth")
        print(f"  ✅ 최고 모델 저장 (Val Acc: {val_acc:.4f})")

6-6. 평가 및 예측

python# 최고 가중치 불러오기
model.load_state_dict(torch.load("best_bert_imdb.pth"))
test_loss, test_acc = eval_epoch(model, test_loader, device)
print(f"\n최종 테스트 정확도: {test_acc:.4f}")

# 학습 곡선 시각화
fig, (ax1, ax2) = plt.subplots(1, 2, figsize=(14, 5))

ax1.plot(history["train_loss"], label="훈련 손실", marker="o")
ax1.plot(history["val_loss"],   label="검증 손실", marker="o", linestyle="--")
ax1.set_title("BERT Fine-tuning 손실")
ax1.set_xlabel("Epoch"); ax1.legend(); ax1.grid(True)

ax2.plot(history["train_acc"], label="훈련 정확도", marker="o")
ax2.plot(history["val_acc"],   label="검증 정확도", marker="o", linestyle="--")
ax2.set_title("BERT Fine-tuning 정확도")
ax2.set_xlabel("Epoch"); ax2.legend(); ax2.grid(True)

plt.tight_layout()
plt.show()


# 직접 문장 예측
def predict_sentiment(model, tokenizer, text, device):
    model.eval()
    encoding = tokenizer(
        text,
        max_length     = MAX_LEN,
        padding        = "max_length",
        truncation     = True,
        return_tensors = "pt"
    )
    with torch.no_grad():
        outputs = model(
            input_ids      = encoding["input_ids"].to(device),
            attention_mask = encoding["attention_mask"].to(device),
            token_type_ids = encoding["token_type_ids"].to(device)
        )
    probs = torch.softmax(outputs.logits, dim=-1)[0]
    label = "긍정 😊" if probs.argmax().item() == 1 else "부정 😞"
    print(f"입력: {text[:60]}...")
    print(f"예측: {label}  (부정: {probs[0]:.4f} / 긍정: {probs[1]:.4f})")

predict_sentiment(model, tokenizer,
    "This movie was absolutely brilliant! The acting was superb.", device)
predict_sentiment(model, tokenizer,
    "Worst movie I've ever seen. Complete waste of time.", device)

7. GPT-2 실습 — 텍스트 생성

7-1. GPT-2 모델 불러오기

pythonfrom transformers import GPT2LMHeadModel, GPT2Tokenizer

MODEL_NAME = "gpt2"  # gpt2 / gpt2-medium / gpt2-large

tokenizer_gpt2 = GPT2Tokenizer.from_pretrained(MODEL_NAME)
model_gpt2     = GPT2LMHeadModel.from_pretrained(MODEL_NAME).to(device)

# GPT-2는 pad_token이 기본적으로 없음 → 반드시 설정
tokenizer_gpt2.pad_token       = tokenizer_gpt2.eos_token
model_gpt2.config.pad_token_id = tokenizer_gpt2.eos_token_id

print(f"GPT-2 파라미터 수: {sum(p.numel() for p in model_gpt2.parameters()):,}")
# 117,000,000

⚠️ GPT-2 pad_token 주의 — GPT-2는 기본적으로 pad_token이 없습니다. 반드시 eos_tokenpad_token으로 설정해야 배치 학습 시 오류가 발생하지 않습니다.

7-2. 생성 전략 비교

GPT 텍스트 생성 품질은 어떤 전략으로 다음 토큰을 선택하느냐 에 따라 크게 달라집니다.

pythonprompt    = "In the future, artificial intelligence will"
input_ids = tokenizer_gpt2.encode(prompt, return_tensors="pt").to(device)

model_gpt2.eval()

# ── 전략 1. Greedy Search ────────────────────────────────────────
# 매 시점 가장 높은 확률의 토큰만 선택 → 반복적이고 단조로움
greedy_out = model_gpt2.generate(
    input_ids,
    max_new_tokens = 50,
    do_sample      = False
)
print("【Greedy Search】")
print(tokenizer_gpt2.decode(greedy_out[0], skip_special_tokens=True))


# ── 전략 2. Beam Search ─────────────────────────────────────────
# 상위 num_beams개 후보를 동시에 유지 → Greedy보다 품질 높음
beam_out = model_gpt2.generate(
    input_ids,
    max_new_tokens       = 50,
    num_beams            = 5,
    early_stopping       = True,
    no_repeat_ngram_size = 2   # 2-gram 반복 금지
)
print("【Beam Search (num_beams=5)】")
print(tokenizer_gpt2.decode(beam_out[0], skip_special_tokens=True))


# ── 전략 3. Top-k Sampling ──────────────────────────────────────
# 확률 상위 k개 중 랜덤 샘플링 → 다양하고 창의적
topk_out = model_gpt2.generate(
    input_ids,
    max_new_tokens = 50,
    do_sample      = True,
    top_k          = 50,
    temperature    = 0.8   # <1: 보수적 / >1: 창의적
)
print("【Top-k Sampling (k=50, temp=0.8)】")
print(tokenizer_gpt2.decode(topk_out[0], skip_special_tokens=True))


# ── 전략 4. Top-p (Nucleus) Sampling ───────────────────────────
# 누적 확률이 p를 넘는 최소 토큰 집합에서 샘플링 → 현재 가장 권장
topp_out = model_gpt2.generate(
    input_ids,
    max_new_tokens = 50,
    do_sample      = True,
    top_p          = 0.92,
    temperature    = 0.8
)
print("【Top-p Sampling (p=0.92, temp=0.8)】")
print(tokenizer_gpt2.decode(topp_out[0], skip_special_tokens=True))

전략별 특성 비교:

전략다양성일관성적합한 상황
Greedy Search❌ 낮음✅ 높음번역, 요약
Beam Search△ 보통✅ 높음번역 + 반복 방지
Top-k Sampling✅ 높음△ 보통창작 글쓰기
Top-p Sampling✅ 높음✅ 높음대부분의 생성 태스크 권장

7-3. temperature 파라미터 이해

pythonimport numpy as np
import matplotlib.pyplot as plt

logits = np.array([3.0, 2.0, 1.0, 0.5, 0.1])

def softmax_with_temp(logits, temperature):
    scaled = logits / temperature
    exp    = np.exp(scaled - np.max(scaled))
    return exp / exp.sum()

temps = [0.5, 1.0, 1.5, 2.0]
fig, axes = plt.subplots(1, 4, figsize=(16, 4))

for ax, t in zip(axes, temps):
    probs = softmax_with_temp(logits, t)
    ax.bar(range(len(probs)), probs, color="steelblue")
    ax.set_title(f"temperature = {t}")
    ax.set_ylim(0, 1)
    ax.set_xlabel("토큰 인덱스")
    ax.set_ylabel("확률")
    ax.grid(axis="y")

plt.suptitle("Temperature에 따른 확률 분포 변화\n"
             "(낮을수록 뾰족 → 보수적 / 높을수록 평평 → 창의적)")
plt.tight_layout()
plt.show()

# temperature < 1 : 높은 확률 토큰에 더 집중 → 안전하고 반복적
# temperature = 1 : 원래 확률 분포 그대로
# temperature > 1 : 분포 평평해짐 → 다양하지만 엉뚱한 텍스트 가능

7-4. 여러 문장 동시 생성

pythondef generate_texts(prompt, n=3, max_new_tokens=80,
                   top_p=0.92, temperature=0.9):
    input_ids = tokenizer_gpt2.encode(
        prompt, return_tensors="pt"
    ).to(device)

    outputs = model_gpt2.generate(
        input_ids,
        max_new_tokens       = max_new_tokens,
        do_sample            = True,
        top_p                = top_p,
        temperature          = temperature,
        num_return_sequences = n,
        no_repeat_ngram_size = 3
    )

    print(f"프롬프트: \"{prompt}\"\n")
    for i, out in enumerate(outputs):
        text = tokenizer_gpt2.decode(out, skip_special_tokens=True)
        print(f"  [{i+1}] {text}\n")

generate_texts("Once upon a time in a land far away,", n=3)
generate_texts("The most important thing about deep learning is", n=3)

8. BERT vs GPT 성능 비교 실험

8-1. 2편 ~ 4편 모델 총 비교

pythonresults = {
    "2편 Bi-LSTM\n(Keras)"    : 87.2,
    "3편 Transformer\n(Keras)": 88.5,
    "4편 BERT\n(Fine-tuning)" : test_acc * 100
}

colors = ["#5B9BD5", "#70AD47", "#ED7D31"]

fig, ax = plt.subplots(figsize=(10, 6))
bars = ax.bar(results.keys(), results.values(),
              color=colors, edgecolor="black", width=0.5)

ax.set_ylim(80, 100)
ax.set_title("IMDB 감성 분석 — 모델별 테스트 정확도 비교 (2편~4편)",
             fontsize=14, fontweight="bold")
ax.set_ylabel("테스트 정확도 (%)")
ax.grid(axis="y", linestyle="--", alpha=0.7)

for bar, (name, val) in zip(bars, results.items()):
    ax.text(bar.get_x() + bar.get_width() / 2,
            bar.get_height() + 0.3,
            f"{val:.1f}%", ha="center", fontsize=12, fontweight="bold")

plt.tight_layout()
plt.show()

8-2. 핵심 특성 비교

비교 항목LSTM (2편)Transformer (3편)BERT (4편)
Fine-tuning 데이터2.5만 건2.5만 건2천 건
학습 시간빠름보통느림 (GPU 필요)
정확도~87%~88%~93%
사전 학습✅ (수십 GB)
적은 데이터 성능낮음낮음높음

9. 마무리

9-1. 오늘 배운 것 한눈에 정리

개념핵심 내용
BERTEncoder 기반, 양방향 문맥, MLM+NSP 사전 학습
GPTDecoder 기반, 단방향 문맥, CLM 사전 학습
GPT-5.4 (2026)1M 토큰 컨텍스트, 추론 강도 5단계, Computer Use
ModernBERT (2026)BERT 경량화 + 8K 컨텍스트 + RoPE 위치 임베딩
전이 학습대규모 사전 학습 → 소규모 데이터 Fine-tuning
WordPiece / BPE서브워드 토큰화 → OOV 문제 해결
[CLS] 토큰문장 전체 의미 요약 → 분류 헤드에 연결
AdamW + Warm-upBERT Fine-tuning 표준 설정
Top-p SamplingGPT 생성 현재 권장 전략
temperature생성 텍스트의 창의성 vs 안정성 조절