
📓Google Colab에서 실습하기
이 레슨은 PyTorch/GPU가 필요합니다. 노트북을 다운로드 후 Google Colab에서 열어주세요.
학습 내용
BERT 구조: 양방향으로 읽는 AI
학습 목표
이 레슨을 완료하면:
- •BERT가 왜 Encoder만 사용하는지 이해한다
- •Masked Language Model(MLM)의 학습 원리를 설명할 수 있다
- •양방향 문맥(Bidirectional Context)의 장점을 안다
- •Fine-tuning으로 다양한 태스크에 적용하는 방법을 이해한다
- •GPT와의 차이점을 명확히 구분할 수 있다
핵심 메시지
"BERT는 '빈칸 맞추기'를 통해 문장을 양쪽에서 동시에 이해하는 법을 배운다." GPT가 "다음 단어 맞추기"로 글을 쓰는 능력을 얻었다면, BERT는 "빈칸 맞추기"로 글을 깊이 이해하는 능력을 얻었습니다.
BERT란 무엇인가?
비유: 독해 시험의 달인 GPT가 "작문 시험"에 특화되었다면, BERT는 "독해 시험"에 특화되어 있습니다. 주어진 글을 읽고 의미를 파악하고, 질문에 답하고, 분류하는 것이 BERT의 강점입니다.
BERT = Bidirectional Encoder Representations from Transformers
- •Bidirectional: "양방향"으로 문맥을 참조한다
- •Encoder: Transformer의 Encoder만 사용한다
- •Representations: 단어의 "표현(벡터)"을 만들어낸다
- •Transformers: Transformer 아키텍처 기반이다
Google이 2018년에 발표했으며, 당시 11개 NLP 벤치마크에서 최고 성능을 달성했습니다.
왜 Encoder만 사용하는가?
BERT의 설계 철학
GPT의 목표: 텍스트를 "생성"한다 → 다음 단어를 예측해야 하니 미래를 못 봄
BERT의 목표: 텍스트를 "이해"한다 → 미래를 볼 수 있다! 양방향 참조 가능!
BERT는 텍스트를 생성할 필요가 없습니다. 주어진 텍스트를 깊이 이해하는 것이 목표이므로, 모든 단어가 앞뒤 모든 단어를 참조할 수 있는 Encoder가 적합합니다.
양방향 vs 단방향
문장: "나는 [???]에서 돈을 찾았다"
단방향 (GPT): "나는 ___" 만 보고 예측
- •→ "은행"? "학교"? "집"? -- 뒤의 "돈을"을 볼 수 없어서 애매함
양방향 (BERT): "나는 ___ 에서 돈을 찾았다" 전체를 봄
- •→ "은행"! -- "돈을 찾았다"를 보고 금융 은행임을 확신
양방향이 "이해" 태스크에서 훨씬 유리합니다!
BERT의 아키텍처
모델 크기
| 모델 | 레이어 수 | Hidden 차원 | Attention 헤드 | 파라미터 수 |
|---|---|---|---|---|
| BERT-Base | 12 | 768 | 12 | 110M |
| BERT-Large | 24 | 1024 | 16 | 340M |
BERT의 입력 구성
BERT는 입력을 세 가지 임베딩의 합으로 구성합니다:
입력 문장: "나는 AI를 좋아한다" + "AI는 재미있다"
[CLS] 나는 AI를 좋아한다 [SEP] AI는 재미있다 [SEP]
Token Embedding: E_CLS E_나는 E_AI를 E_좋아한다 E_SEP E_AI는 E_재미있다 E_SEP
+ + + + + + + +
Segment Embedding: E_A E_A E_A E_A E_A E_B E_B E_B
+ + + + + + + +
Position Embedding: E_0 E_1 E_2 E_3 E_4 E_5 E_6 E_7
= = = = = = = =
최종 입력 벡터 (각 토큰별로 3개 임베딩의 합)
| 토큰 | 설명 |
|---|---|
| [CLS] | 문장 전체를 대표하는 특수 토큰 (분류에 사용) |
| [SEP] | 두 문장의 경계를 구분하는 특수 토큰 |
| Segment | 어떤 문장에 속하는지 (A문장/B문장) 구분 |
Masked Language Model (MLM): BERT의 핵심 학습법
비유: 영어 빈칸 채우기 시험 "The cat sat on the ___" 에서 빈칸에 들어갈 단어를 맞추는 것과 같습니다. 하지만 BERT는 양쪽 문맥을 모두 보면서 맞추기 때문에 훨씬 정확합니다.
MLM의 작동 원리
원본 문장: "나는 학교에서 수학을 공부했다"
1단계: 입력의 15%를 무작위로 선택
- •선택된 토큰: "학교에서", "공부했다"
2단계: 선택된 토큰에 대해 세 가지 처리
- •80%: [MASK]로 대체 → "나는 [MASK] 수학을 [MASK]"
- •10%: 랜덤 단어로 대체 → "나는 바나나 수학을 [MASK]"
- •10%: 그대로 유지 → "나는 학교에서 수학을 [MASK]"
3단계: 모델이 원래 토큰을 예측
- •[MASK] 위치에서 → "학교에서", "공부했다"를 예측하도록 학습
왜 80/10/10으로 나눌까?
문제: Fine-tuning 할 때는 [MASK] 토큰이 없습니다!
- •학습 때만 [MASK]를 보고, 실제 사용 때는 못 봄
- •이러면 학습과 실사용 사이에 차이(gap)가 생김
해결:
- •80% [MASK]: 빈칸 맞추기 능력을 학습
- •10% 랜덤: "이 단어가 맞나?" 판별 능력도 학습
- •10% 유지: 정상 단어에 대한 표현도 학습
→ 모델이 [MASK]에만 의존하지 않고, 모든 위치의 모든 토큰에 주의를 기울이게 됩니다!
실행해보기: MLM 시뮬레이션
pythonimport numpy as np np.random.seed(42) sentence = ["나는", "학교에서", "수학을", "열심히", "공부했다"] vocab = ["나는", "학교에서", "수학을", "열심히", "공부했다", "바나나", "노래를", "집에서", "잠을", "먹었다"] print("=== MLM (Masked Language Model) 시뮬레이션 ===") print(f"원본 문장: {' '.join(sentence)}") # 15% 마스킹 (5개 중 1개 선택) mask_idx = 1 # "학교에서"를 선택 masked_sentence = sentence.copy() # 80/10/10 비율로 처리 roll = np.random.random() if roll < 0.8: masked_sentence[mask_idx] = "[MASK]" action = "[MASK]로 대체" elif roll < 0.9: random_word = np.random.choice(vocab) masked_sentence[mask_idx] = random_word action = f"랜덤 단어 '{random_word}'로 대체" else: action = "그대로 유지" print(f"마스킹 결과: {' '.join(masked_sentence)} ({action})") print(f"정답: '{sentence[mask_idx]}'") # 모델의 예측 시뮬레이션 print() print("모델의 예측 확률:") probs = np.random.dirichlet(np.ones(len(vocab)) * 0.5) # 정답에 높은 확률 부여 (학습된 모델이라 가정) probs[1] = 0.65 # "학교에서" probs = probs / probs.sum() for word, prob in sorted(zip(vocab, probs), key=lambda x: -x[1])[:5]: marker = " <-- 정답!" if word == sentence[mask_idx] else "" print(f" P('{word}') = {prob:.3f}{marker}") print() print("양방향 문맥 덕분에:") print(f" 왼쪽: '{sentence[0]}' + 오른쪽: '{' '.join(sentence[2:])}'") print(f" => '{sentence[mask_idx]}'를 정확히 예측!")
NSP (Next Sentence Prediction)
BERT는 MLM 외에 NSP도 함께 학습합니다 (두 문장이 연속인지 판별).
| 유형 | 비율 | 문장 A | 문장 B | 라벨 |
|---|---|---|---|---|
| IsNext | 50% | "나는 공원에 갔다" | "거기서 산책을 했다" | ✅ 연속된 문장 |
| NotNext | 50% | "나는 공원에 갔다" | "주가가 크게 올랐다" | ❌ 무관한 문장 |
처리 흐름: [CLS] 토큰의 출력 벡터 → 이진 분류기 → IsNext / NotNext
목적: 문장 간의 관계를 이해하는 능력 학습 (QA, 자연어 추론 등에 도움)
참고: 이후 연구(RoBERTa)에서 NSP가 실제로는 큰 도움이 안 된다는 것이 밝혀졌습니다. 그래서 RoBERTa는 NSP를 제거하고 MLM만으로 학습합니다.
Fine-tuning: BERT를 실전에 투입하기
비유: 의대를 졸업한 의사 Pre-training은 의대에서 기초 의학을 배우는 것이고, Fine-tuning은 전공을 정해서 전문의가 되는 것입니다. 기초가 탄탄하면 어떤 전공이든 빠르게 적응할 수 있습니다.
Pre-training -> Fine-tuning 과정
Pre-training (사전 학습):
- •데이터: Wikipedia + BooksCorpus (33억 단어)
- •태스크: MLM + NSP
- •비용: 4일 x TPU 64개 (수천만 원)
- •결과: 범용적 언어 이해 능력
Fine-tuning (미세 조정):
- •데이터: 태스크별 레이블 데이터 (수천~수만 개)
- •태스크: 분류, QA, NER 등
- •비용: 수 시간 x GPU 1개
- •결과: 특정 태스크 전문가
핵심: Pre-trained 모델을 공유하면 누구나 적은 데이터와 비용으로 Fine-tuning 가능!
Fine-tuning 태스크별 적용 방법
| 태스크 | 입력 형태 | 사용하는 출력 | 예시 |
|---|---|---|---|
| 문장 분류 | [CLS] 문장 [SEP] | [CLS] 벡터 -> 분류기 | 감성 분석, 스팸 분류 |
| 문장 쌍 분류 | [CLS] 문장A [SEP] 문장B [SEP] | [CLS] 벡터 -> 분류기 | 자연어 추론(NLI) |
| 토큰 분류 | [CLS] 문장 [SEP] | 각 토큰 벡터 -> 분류기 | 개체명 인식(NER) |
| 질문 답변 | [CLS] 질문 [SEP] 지문 [SEP] | 지문 토큰에서 시작/끝 위치 | SQuAD |
문장 분류 Fine-tuning 예시:
[CLS] 이 영화 정말 재미있어요 [SEP]
|
v
BERT Encoder (12 레이어)
|
v
[CLS] 토큰의 최종 벡터 (768차원)
|
v
Linear Layer (768 -> 2) <- 이 부분만 새로 추가!
|
v
Softmax -> [긍정: 0.92, 부정: 0.08]
Fine-tuning 시:
- BERT 전체 파라미터를 약간만 조정
- Linear Layer는 처음부터 학습
- 수천 개의 레이블 데이터로 충분
실행해보기: Fine-tuning 개념 시뮬레이션
pythonimport numpy as np np.random.seed(42) # BERT가 출력한 [CLS] 벡터 (실제로는 768차원) d_model = 8 num_classes = 3 # 긍정, 중립, 부정 # Pre-trained BERT의 [CLS] 출력 시뮬레이션 sentences = [ ("이 영화 최고에요!", "긍정"), ("그냥 그래요", "중립"), ("정말 별로였어요", "부정"), ("또 보고 싶어요!", "긍정"), ("시간 낭비예요", "부정"), ] # Fine-tuning용 Linear Layer 가중치 (학습됨) W_cls = np.random.randn(d_model, num_classes) * 0.3 b_cls = np.zeros(num_classes) print("=== BERT Fine-tuning 감성 분석 시뮬레이션 ===") labels = ["긍정", "중립", "부정"] correct = 0 for sent, true_label in sentences: # BERT의 [CLS] 벡터 (실제로는 BERT가 계산) cls_vector = np.random.randn(d_model) * 0.5 # 정답에 맞는 패턴을 살짝 심어줌 (학습된 효과) true_idx = labels.index(true_label) cls_vector[true_idx] += 1.0 # Linear Layer로 분류 logits = cls_vector @ W_cls + b_cls logits[true_idx] += 2.0 # 학습된 모델 시뮬레이션 probs = np.exp(logits) / np.exp(logits).sum() pred_idx = np.argmax(probs) pred_label = labels[pred_idx] is_correct = pred_label == true_label correct += int(is_correct) mark = "O" if is_correct else "X" print(f" [{mark}] '{sent}'") print(f" 예측: {pred_label} ({probs[pred_idx]:.2f}), 정답: {true_label}") print() print(f"정확도: {correct}/{len(sentences)} ({correct/len(sentences)*100:.0f}%)") print() print("BERT의 강점: Pre-trained 지식 덕분에 적은 데이터로도 높은 정확도!")
BERT 변형 모델들
| 모델 | 특징 | 개선 포인트 |
|---|---|---|
| RoBERTa | NSP 제거, 더 많은 데이터/시간 학습 | 학습 방법 최적화 |
| ALBERT | 파라미터 공유, 임베딩 분해 | 모델 경량화 |
| DistilBERT | 지식 증류로 60% 크기에 97% 성능 | 속도/크기 최적화 |
| ELECTRA | 대체 토큰 탐지 (생성자+판별자) | 학습 효율 향상 |
| KoBERT | SKT에서 개발한 한국어 BERT | 한국어 특화 |
핵심 요약
| 개념 | 설명 | 비유 |
|---|---|---|
| Encoder-only | Transformer Encoder만 사용 | 독해 전문가 |
| 양방향 참조 | 모든 토큰이 앞뒤를 모두 참조 | 문장 전체를 한눈에 보기 |
| MLM | 15% 토큰을 가리고 맞추기 학습 | 빈칸 채우기 시험 |
| NSP | 두 문장이 연속인지 판별 | 다음 문장 맞추기 |
| Fine-tuning | 소량 데이터로 특정 태스크 적응 | 의대 졸업 후 전공 선택 |
| [CLS] 토큰 | 문장 전체를 대표하는 벡터 | 문장의 요약본 |
학습 체크리스트
- • BERT가 Encoder만 쓰는 이유를 설명할 수 있다
- • MLM의 80/10/10 비율이 왜 필요한지 이해한다
- • 양방향 참조가 단방향보다 이해 태스크에 유리한 이유를 안다
- • Fine-tuning으로 분류, QA, NER에 적용하는 방법을 이해한다
- • [CLS] 토큰과 [SEP] 토큰의 역할을 안다
- • BERT와 GPT의 차이를 표로 정리할 수 있다
레슨 정보
- 레벨
- Level 7: Transformer & LLM 원리
- 예상 소요 시간
- 45분
- 참고 영상
- YouTube 링크
💡실습 환경 안내
이 레벨은 PyTorch/GPU가 필요하여 Google Colab 사용을 권장합니다.
Colab은 무료 GPU를 제공하여 PyTorch, CNN, Transformer 등을 실행할 수 있습니다.