🤖

본 콘텐츠의 이미지 및 내용은 AI로 생성되었습니다.

⚠️

본 콘텐츠의 이미지 및 내용을 무단으로 복제, 배포, 수정하여 사용할 경우 저작권법에 의해 법적 제재를 받을 수 있습니다.

이미지 로딩 중...

BERT 완벽 가이드 - 슬라이드 1/7
A

AI Generated

2025. 12. 1. · 21 Views

BERT 완벽 가이드

자연어 처리의 혁명을 일으킨 BERT를 초급 개발자도 이해할 수 있도록 쉽게 설명합니다. 사전학습 방식부터 한국어 BERT 모델까지, 실무에서 바로 활용할 수 있는 핵심 개념을 담았습니다.


목차

  1. BERT_사전학습_방식
  2. BERT_아키텍처
  3. WordPiece_토크나이저
  4. BERT_변형_모델들
  5. 한국어_BERT
  6. BERT_Fine_tuning_전략

1. BERT 사전학습 방식

어느 날 김개발 씨가 자연어 처리 프로젝트를 맡게 되었습니다. 선배에게 "BERT를 사용해보세요"라는 조언을 들었지만, 도대체 BERT가 어떻게 학습되었기에 그렇게 성능이 좋은 건지 궁금해졌습니다.

"MLM이요? NSP요?

그게 뭔가요?"

BERT의 사전학습은 크게 **MLM(Masked Language Model)**과 NSP(Next Sentence Prediction) 두 가지 방식으로 이루어집니다. 마치 빈칸 채우기 문제와 이어지는 문장 맞추기 문제를 동시에 푸는 것과 같습니다.

이 두 가지 학습을 통해 BERT는 문맥을 깊이 이해하는 능력을 갖추게 됩니다.

다음 코드를 살펴봅시다.

from transformers import BertTokenizer, BertForMaskedLM
import torch

tokenizer = BertTokenizer.from_pretrained('bert-base-uncased')
model = BertForMaskedLM.from_pretrained('bert-base-uncased')

# MLM 예시: [MASK] 토큰이 있는 문장
text = "The capital of France is [MASK]."
inputs = tokenizer(text, return_tensors="pt")

# 모델이 [MASK] 위치의 단어를 예측합니다
with torch.no_grad():
    outputs = model(**inputs)
    predictions = outputs.logits

# 가장 확률이 높은 단어 추출
mask_idx = torch.where(inputs["input_ids"] == tokenizer.mask_token_id)[1]
predicted_token = torch.argmax(predictions[0, mask_idx, :]).item()
print(tokenizer.decode([predicted_token]))  # 출력: paris

김개발 씨는 입사 6개월 차 주니어 개발자입니다. 최근 회사에서 챗봇 프로젝트를 진행하게 되었는데, 팀장님이 "BERT 기반으로 만들어보세요"라고 하셨습니다.

BERT가 좋다는 건 알겠는데, 도대체 어떻게 학습되었기에 그렇게 똑똑한 걸까요? 선배 개발자 박시니어 씨가 커피를 건네며 설명을 시작합니다.

"BERT의 비밀은 바로 사전학습 방식에 있어요. 크게 두 가지를 배우는데, 하나는 빈칸 채우기, 다른 하나는 문장 이어지기 판단이에요." 그렇다면 **MLM(Masked Language Model)**이란 정확히 무엇일까요?

쉽게 비유하자면, MLM은 마치 수능 영어 빈칸 추론 문제와 같습니다. "나는 오늘 아침에 [빈칸]를 먹었다"라는 문장이 있다면, 앞뒤 문맥을 보고 빈칸에 들어갈 단어를 맞추는 것입니다.

BERT는 이런 문제를 수억 개나 풀면서 언어를 이해하게 됩니다. 기존의 언어 모델들은 왼쪽에서 오른쪽으로만 문장을 읽었습니다.

"나는 오늘"까지 읽고 다음 단어를 예측하는 식이었죠. 하지만 이렇게 하면 뒤에 나오는 문맥을 활용할 수 없다는 한계가 있었습니다.

BERT는 이 문제를 해결하기 위해 양방향 학습을 도입했습니다. 문장의 15% 정도 단어를 무작위로 가리고, 앞뒤 문맥을 모두 보면서 가려진 단어를 예측하게 합니다.

이렇게 하면 문장 전체의 맥락을 파악할 수 있게 됩니다. 두 번째 학습 방식인 **NSP(Next Sentence Prediction)**는 무엇일까요?

이번에는 도서관 사서를 떠올려 보세요. 책의 한 페이지를 보여주고 "다음 페이지가 이건가요?"라고 물으면, 사서는 문맥상 맞는지 판단할 수 있습니다.

NSP도 마찬가지입니다. 두 문장을 보여주고 "이 문장이 원래 이어지는 문장인가요?"를 맞추는 것입니다.

위의 코드를 살펴보겠습니다. 먼저 BERT 토크나이저와 모델을 불러옵니다.

그리고 [MASK] 토큰이 포함된 문장을 입력합니다. 모델은 문맥을 분석해서 [MASK] 위치에 들어갈 가장 적절한 단어를 예측합니다.

"The capital of France is [MASK]"라는 문장에서 BERT는 "paris"를 정확히 맞춥니다. 실제 현업에서 이 개념이 왜 중요할까요?

챗봇을 만든다고 가정해봅시다. 사용자가 "배송이 언제 오나요?"라고 물었을 때, BERT는 "배송"과 "언제"라는 단어의 관계를 양방향으로 파악합니다.

단순히 키워드 매칭이 아니라 문맥 전체를 이해하기 때문에 더 정확한 답변이 가능해집니다. 주의할 점도 있습니다.

MLM 학습 시 [MASK] 토큰은 사전학습에만 사용됩니다. 실제 Fine-tuning이나 추론 단계에서는 [MASK] 토큰이 등장하지 않으므로, 사전학습과 실제 사용 사이에 약간의 불일치가 발생할 수 있습니다.

BERT 논문에서는 이를 완화하기 위해 마스킹된 단어 중 80%만 [MASK]로, 10%는 다른 단어로, 10%는 원래 단어 그대로 두는 전략을 사용했습니다. 다시 김개발 씨의 이야기로 돌아가 봅시다.

박시니어 씨의 설명을 들은 김개발 씨는 고개를 끄덕였습니다. "아, 그래서 BERT가 문맥을 잘 이해하는 거군요!" 사전학습 방식을 이해하면 BERT의 강점이 어디서 오는지 알 수 있습니다.

양방향 문맥 파악과 문장 관계 이해, 이 두 가지가 BERT를 강력하게 만든 핵심 비결입니다.

실전 팁

💡 - MLM에서 마스킹 비율은 보통 15%가 최적이며, 너무 많이 마스킹하면 문맥 파악이 어려워집니다

  • NSP는 이후 연구에서 효과가 크지 않다는 의견도 있어, RoBERTa 같은 후속 모델에서는 제외되기도 합니다

2. BERT 아키텍처

김개발 씨가 BERT 논문을 읽다가 "12 layers, 768 hidden, 12 heads"라는 문구를 발견했습니다. 숫자들이 쏟아지니 머리가 복잡해집니다.

"이게 다 무슨 의미인 거죠?" 선배 박시니어 씨가 화이트보드 앞으로 다가갑니다.

BERT는 Transformer의 Encoder 부분만을 사용한 모델입니다. 마치 레고 블록처럼 동일한 구조의 인코더 레이어를 여러 개 쌓아 올린 형태입니다.

BERT-Base는 12개 레이어, BERT-Large는 24개 레이어로 구성되어 있으며, 각 레이어는 Self-Attention과 Feed-Forward Network로 이루어져 있습니다.

다음 코드를 살펴봅시다.

from transformers import BertModel, BertConfig

# BERT-Base 설정 확인
config = BertConfig.from_pretrained('bert-base-uncased')
print(f"레이어 수: {config.num_hidden_layers}")        # 12
print(f"히든 사이즈: {config.hidden_size}")            # 768
print(f"어텐션 헤드 수: {config.num_attention_heads}") # 12

# 모델 로드 및 구조 확인
model = BertModel.from_pretrained('bert-base-uncased')

# 입력 토큰의 임베딩 → 12개 레이어 통과 → 최종 출력
text = "Hello, BERT!"
from transformers import BertTokenizer
tokenizer = BertTokenizer.from_pretrained('bert-base-uncased')
inputs = tokenizer(text, return_tensors="pt")
outputs = model(**inputs)
print(f"출력 shape: {outputs.last_hidden_state.shape}")  # [1, 5, 768]

김개발 씨는 BERT를 사용하면서 문득 궁금해졌습니다. 도대체 BERT 내부는 어떻게 생겼을까요?

블랙박스처럼 입력을 넣으면 결과가 나오는데, 그 안에서 무슨 일이 벌어지는 걸까요? 박시니어 씨가 화이트보드에 그림을 그리기 시작합니다.

"BERT의 구조를 이해하려면 먼저 Transformer를 알아야 해요. BERT는 Transformer의 Encoder 부분만 사용한 모델이거든요." Transformer는 2017년 구글이 발표한 아키텍처로, 기존의 RNN이나 LSTM을 대체한 혁신적인 구조입니다.

Encoder와 Decoder로 나뉘는데, BERT는 이 중 Encoder만 사용합니다. 왜냐하면 BERT의 목적은 텍스트를 생성하는 게 아니라 텍스트를 이해하는 것이기 때문입니다.

BERT의 구조를 빌딩에 비유해 볼까요? 12층짜리 빌딩을 상상해 보세요.

각 층의 구조는 완전히 동일합니다. 1층에서 입력을 받아 처리하고, 그 결과를 2층으로 올려보냅니다.

2층에서도 같은 작업을 하고 3층으로 올립니다. 이렇게 12층까지 올라가면 최종 결과가 나옵니다.

이것이 바로 BERT-Base의 구조입니다. 각 층에서는 무슨 일이 일어날까요?

핵심은 Self-Attention입니다. 문장의 각 단어가 다른 모든 단어와 어떤 관계가 있는지 계산합니다.

"나는 사과를 먹었다"라는 문장에서 "먹었다"라는 단어는 "나는"과 "사과를"에 어느 정도 집중해야 하는지 점수를 매깁니다. 여기서 Multi-Head Attention이 등장합니다.

BERT-Base는 12개의 어텐션 헤드를 가지고 있습니다. 마치 12명의 분석가가 같은 문장을 서로 다른 관점에서 바라보는 것과 같습니다.

한 명은 문법적 관계를, 다른 한 명은 의미적 관계를 분석하는 식이죠. 위의 코드를 살펴보겠습니다.

BertConfig를 통해 BERT의 설정값을 확인할 수 있습니다. num_hidden_layers가 12라는 것은 12개의 인코더 레이어가 있다는 뜻입니다.

hidden_size 768은 각 토큰이 768차원의 벡터로 표현된다는 의미입니다. 마지막으로 출력 shape가 [1, 5, 768]인데, 이는 배치 크기 1, 토큰 수 5, 각 토큰당 768차원을 의미합니다.

BERT-Base와 BERT-Large의 차이는 무엇일까요? BERT-Base는 12레이어, 768차원, 12헤드로 약 1억 1천만 개의 파라미터를 가집니다.

BERT-Large는 24레이어, 1024차원, 16헤드로 약 3억 4천만 개의 파라미터를 가집니다. 당연히 Large가 성능은 더 좋지만, 메모리와 연산 비용도 그만큼 커집니다.

실무에서는 어떤 모델을 선택해야 할까요? 대부분의 경우 BERT-Base로 충분합니다.

GPU 메모리가 16GB 이하라면 Large 모델은 학습시키기 어려울 수 있습니다. 처음 프로젝트를 시작할 때는 Base로 시작하고, 성능이 부족하다면 Large로 업그레이드하는 전략을 추천합니다.

주의할 점이 있습니다. BERT의 최대 입력 길이는 512 토큰입니다.

이보다 긴 문서를 처리하려면 문서를 나누거나, Longformer 같은 다른 모델을 고려해야 합니다. 김개발 씨가 고개를 끄덕입니다.

"아, 그래서 BERT 모델 로드할 때 메모리가 많이 필요했던 거군요. 레이어가 12개나 쌓여있으니까요!" BERT 아키텍처를 이해하면 모델 선택과 리소스 계획을 더 잘 세울 수 있습니다.

무작정 큰 모델을 쓰기보다는, 자신의 환경에 맞는 모델을 선택하는 지혜가 필요합니다.

실전 팁

💡 - GPU 메모리가 부족하면 BERT-Base부터 시작하세요. 대부분의 태스크에서 충분한 성능을 보여줍니다

  • 배치 사이즈를 줄이면 메모리 사용량을 낮출 수 있지만, 학습 안정성이 떨어질 수 있습니다

3. WordPiece 토크나이저

김개발 씨가 BERT에 문장을 입력하려는데, 이상한 현상을 발견했습니다. "playing"이라는 단어가 "play"와 "##ing"으로 쪼개져 있는 겁니다.

"이게 뭐죠? 왜 단어가 이상하게 나눠지는 거예요?" 박시니어 씨가 미소를 지으며 답합니다.

"그게 바로 WordPiece의 마법이에요."

WordPiece는 BERT가 사용하는 토큰화 방식입니다. 단어를 의미 있는 서브워드 단위로 쪼개는 기법으로, 마치 레고 블록처럼 작은 조각들을 조합해 모든 단어를 표현합니다.

이를 통해 어휘 집합의 크기를 제한하면서도 처음 보는 단어(OOV)를 효과적으로 처리할 수 있습니다.

다음 코드를 살펴봅시다.

from transformers import BertTokenizer

tokenizer = BertTokenizer.from_pretrained('bert-base-uncased')

# 일반적인 단어 토큰화
text1 = "I love programming"
tokens1 = tokenizer.tokenize(text1)
print(f"일반 단어: {tokens1}")  # ['i', 'love', 'programming']

# 희귀 단어는 서브워드로 분리
text2 = "I love tokenization"
tokens2 = tokenizer.tokenize(text2)
print(f"희귀 단어: {tokens2}")  # ['i', 'love', 'token', '##ization']

# 완전히 새로운 단어도 처리 가능
text3 = "transformerification"
tokens3 = tokenizer.tokenize(text3)
print(f"신조어: {tokens3}")  # ['transform', '##eri', '##fication']

# 토큰을 ID로 변환
ids = tokenizer.encode("Hello BERT", add_special_tokens=True)
print(f"토큰 ID: {ids}")  # [101, 7592, 14324, 102]

김개발 씨는 BERT를 사용하면서 한 가지 의문이 들었습니다. 세상에는 무수히 많은 단어가 있는데, BERT는 어떻게 모든 단어를 알고 있는 걸까요?

특히 "COVID-19"처럼 새로 생긴 단어는 어떻게 처리하는 걸까요? 박시니어 씨가 설명을 시작합니다.

"좋은 질문이에요. 사실 BERT는 모든 단어를 알고 있는 게 아니에요.

대신 WordPiece라는 똑똑한 토큰화 방식을 사용하죠." WordPiece를 이해하기 위해 한글을 생각해 봅시다. 한글은 자음과 모음을 조합해서 모든 글자를 만들 수 있습니다.

"가", "나", "다"를 외우는 게 아니라 "ㄱ+ㅏ=가"라는 조합 규칙을 아는 거죠. WordPiece도 비슷합니다.

모든 단어를 외우는 대신, 자주 쓰이는 서브워드 조각들을 조합해서 단어를 표현합니다. 예를 들어 "playing"이라는 단어를 봅시다.

WordPiece는 이것을 "play"와 "##ing"으로 나눕니다. "##"은 "이 조각은 앞 단어에 붙어있다"는 표시입니다.

"play"는 그 자체로 의미가 있고, "##ing"은 현재진행형을 만드는 접미사입니다. 이 방식의 장점은 무엇일까요?

첫째, 어휘 집합 크기를 제한할 수 있습니다. BERT-Base의 어휘 집합은 약 3만 개입니다.

만약 모든 단어를 저장한다면 수십만 개가 필요하겠지만, 서브워드 방식을 쓰면 3만 개로도 충분합니다. 둘째, OOV(Out-Of-Vocabulary) 문제를 해결합니다.

기존 방식에서는 학습에 없던 단어가 등장하면 UNK로 처리했습니다. 하지만 WordPiece는 처음 보는 단어도 알려진 조각들의 조합으로 표현할 수 있습니다.

위의 코드를 살펴보겠습니다. "I love programming"은 모두 흔한 단어라서 그대로 토큰화됩니다.

하지만 "tokenization"은 약간 희귀한 단어라서 "token"과 "##ization"으로 분리됩니다. 심지어 "transformerification"이라는 만들어낸 단어도 "transform", "##eri", "##fication"으로 나눠서 처리할 수 있습니다.

특수 토큰도 알아두어야 합니다. BERT는 몇 가지 특수 토큰을 사용합니다.

[CLS]는 문장의 시작을 나타내며, 분류 작업에서 이 토큰의 출력을 사용합니다. [SEP]는 문장의 끝이나 두 문장 사이의 구분을 나타냅니다.

[PAD]는 배치 처리를 위해 문장 길이를 맞출 때 사용합니다. [MASK]는 MLM 학습에서 가려진 단어를 표시합니다.

실무에서 주의할 점이 있습니다. 토큰화 결과는 모델에 따라 다릅니다.

BERT와 GPT는 서로 다른 토크나이저를 사용하므로, 모델에 맞는 토크나이저를 사용해야 합니다. 또한 한국어의 경우 영어와 토큰화 방식이 다르므로 한국어 전용 토크나이저를 사용하는 것이 좋습니다.

김개발 씨가 감탄합니다. "아, 그래서 처음 보는 단어도 처리할 수 있었군요!

마치 레고 블록으로 뭐든 만들 수 있는 것처럼요." WordPiece를 이해하면 BERT가 텍스트를 어떻게 바라보는지 알 수 있습니다. 토큰 단위로 사고하는 습관을 기르면 디버깅이나 성능 분석에 큰 도움이 됩니다.

실전 팁

💡 - 토큰 수가 512를 넘으면 잘리므로, 긴 문서는 미리 토큰 수를 확인하세요

  • 한국어는 영어보다 토큰 수가 많아지는 경향이 있어 더 주의가 필요합니다

4. BERT 변형 모델들

김개발 씨가 논문을 찾아보다가 혼란에 빠졌습니다. RoBERTa, ALBERT, DistilBERT, ELECTRA...

BERT 뒤에 붙은 이름들이 너무 많습니다. "이게 다 뭐예요?

그냥 BERT 쓰면 안 되나요?" 박시니어 씨가 웃으며 답합니다. "각각 다른 문제를 해결하려고 만들어진 모델들이에요."

BERT의 성공 이후 수많은 변형 모델이 등장했습니다. RoBERTa는 더 많은 데이터로 더 오래 학습한 모델이고, ALBERT는 파라미터를 공유해 경량화한 모델입니다.

DistilBERT는 지식 증류로 크기를 줄인 모델이며, ELECTRA는 완전히 새로운 학습 방식을 도입했습니다.

다음 코드를 살펴봅시다.

from transformers import (
    RobertaModel, AlbertModel,
    DistilBertModel, ElectraModel
)

# RoBERTa: 더 강력한 사전학습
roberta = RobertaModel.from_pretrained('roberta-base')
print(f"RoBERTa 파라미터: {roberta.num_parameters():,}")  # 약 125M

# ALBERT: 파라미터 효율적
albert = AlbertModel.from_pretrained('albert-base-v2')
print(f"ALBERT 파라미터: {albert.num_parameters():,}")   # 약 12M

# DistilBERT: 가볍고 빠른 모델
distilbert = DistilBertModel.from_pretrained('distilbert-base-uncased')
print(f"DistilBERT 파라미터: {distilbert.num_parameters():,}")  # 약 66M

# ELECTRA: 효율적인 학습 방식
electra = ElectraModel.from_pretrained('google/electra-base-discriminator')
print(f"ELECTRA 파라미터: {electra.num_parameters():,}")  # 약 110M

김개발 씨는 프로젝트에 어떤 모델을 써야 할지 고민에 빠졌습니다. "BERT가 좋다고 해서 쓰려고 하는데, 검색하니까 비슷한 이름의 모델이 너무 많아요.

뭘 써야 하죠?" 박시니어 씨가 화이트보드에 계보도를 그리기 시작합니다. "BERT가 2018년에 나온 이후로 연구자들이 다양한 개선을 시도했어요.

각 모델의 특징을 알면 상황에 맞는 선택을 할 수 있죠." 먼저 **RoBERTa(Robustly Optimized BERT Approach)**를 살펴봅시다. 페이스북에서 만든 이 모델은 BERT의 학습 방법을 최적화했습니다.

첫째, NSP(다음 문장 예측) 태스크를 제거했습니다. 연구 결과 NSP가 크게 도움이 되지 않는다는 것을 발견했기 때문입니다.

둘째, 더 많은 데이터로 더 오래 학습했습니다. 셋째, 동적 마스킹을 적용해서 매 에폭마다 다른 위치를 마스킹했습니다.

결과적으로 대부분의 벤치마크에서 BERT를 앞섰습니다. 다음은 **ALBERT(A Lite BERT)**입니다.

구글에서 발표한 이 모델은 파라미터 효율성에 집중했습니다. 두 가지 핵심 기법을 사용합니다.

첫째, 임베딩 분해입니다. 기존 BERT는 어휘 임베딩과 히든 레이어의 차원이 같았는데, ALBERT는 이를 분리해서 파라미터 수를 줄였습니다.

둘째, 레이어 간 파라미터 공유입니다. 12개 레이어가 같은 가중치를 공유하므로 파라미터가 크게 줄어듭니다.

DistilBERT는 또 다른 접근 방식을 취합니다. 지식 증류(Knowledge Distillation)라는 기법을 사용합니다.

큰 모델(Teacher)의 지식을 작은 모델(Student)에게 전달하는 것입니다. 마치 선생님의 노하우를 학생에게 압축해서 전수하는 것과 같습니다.

DistilBERT는 BERT 성능의 97%를 유지하면서 크기는 40% 줄이고, 속도는 60% 빨라졌습니다. 마지막으로 ELECTRA는 완전히 새로운 학습 방식을 제안합니다.

BERT의 MLM은 15%의 토큰만 학습에 활용합니다. ELECTRA는 이를 100%로 끌어올렸습니다.

Generator가 가짜 토큰을 생성하면, Discriminator가 각 토큰이 진짜인지 가짜인지 판별합니다. 모든 토큰이 학습에 참여하므로 학습 효율이 크게 향상됩니다.

위 코드에서 각 모델의 파라미터 수를 확인할 수 있습니다. ALBERT가 압도적으로 적은 파라미터를 가지고 있음을 볼 수 있습니다.

그렇다면 어떤 모델을 선택해야 할까요? 성능이 최우선이라면 RoBERTa를 추천합니다.

메모리와 속도가 중요하다면 DistilBERT가 좋습니다. 극도로 제한된 환경이라면 ALBERT를 고려하세요.

학습 효율을 높이고 싶다면 ELECTRA가 좋은 선택입니다. 김개발 씨가 고개를 끄덕입니다.

"상황에 따라 다른 모델을 써야 하는군요. 저는 서버 리소스가 제한적이니까 DistilBERT로 시작해볼게요!" 모델 선택은 정답이 없습니다.

자신의 환경과 요구사항을 먼저 파악하고, 그에 맞는 모델을 선택하는 것이 중요합니다.

실전 팁

💡 - 처음 시작할 때는 DistilBERT로 빠르게 프로토타입을 만들고, 필요하면 더 큰 모델로 교체하세요

  • 같은 계열의 토크나이저를 사용해야 합니다. RoBERTa는 BERT와 다른 토크나이저를 사용합니다

5. 한국어 BERT

김개발 씨가 한국어 챗봇을 만들려고 BERT를 사용했는데, 결과가 영 신통치 않습니다. "아니, 영어로는 잘 되던데 왜 한국어는 이렇게 성능이 안 나오죠?" 박시니어 씨가 답합니다.

"영어로 학습된 BERT에 한국어를 넣으면 당연히 잘 안 되죠. 한국어 전용 BERT를 써야 해요."

한국어는 영어와 문법 구조가 완전히 다르기 때문에 한국어 전용 BERT 모델이 필요합니다. KoBERT는 SKT에서 공개한 한국어 BERT이고, KoELECTRA는 한국어에 ELECTRA 방식을 적용한 모델입니다.

이 외에도 klue/bert-base 등 다양한 한국어 모델이 있습니다.

다음 코드를 살펴봅시다.

# KoELECTRA 사용 예시
from transformers import ElectraModel, ElectraTokenizer

# KoELECTRA 모델과 토크나이저 로드
tokenizer = ElectraTokenizer.from_pretrained("monologg/koelectra-base-v3-discriminator")
model = ElectraModel.from_pretrained("monologg/koelectra-base-v3-discriminator")

# 한국어 문장 토큰화
text = "오늘 날씨가 정말 좋습니다"
tokens = tokenizer.tokenize(text)
print(f"토큰: {tokens}")  # ['오늘', '날씨', '##가', '정말', '좋', '##습니다']

# 인코딩 및 모델 추론
inputs = tokenizer(text, return_tensors="pt")
outputs = model(**inputs)
print(f"출력 shape: {outputs.last_hidden_state.shape}")

# KLUE BERT 사용 예시
from transformers import BertModel, BertTokenizer
klue_tokenizer = BertTokenizer.from_pretrained("klue/bert-base")
klue_model = BertModel.from_pretrained("klue/bert-base")

김개발 씨는 영어 챗봇 프로젝트를 성공적으로 마치고, 이번에는 한국어 챗봇에 도전했습니다. "영어에서 잘 됐으니까 한국어도 비슷하게 하면 되겠지"라고 생각했는데, 현실은 달랐습니다.

박시니어 씨가 문제를 짚어줍니다. "영어 BERT의 어휘 집합에는 한국어가 거의 없어요.

한국어를 넣으면 대부분 [UNK]로 처리되거나, 글자 단위로 쪼개져서 의미를 제대로 파악할 수 없죠." 한국어와 영어는 왜 다르게 처리해야 할까요? 첫째, 어순이 다릅니다.

영어는 SVO(주어-동사-목적어)이고, 한국어는 SOV(주어-목적어-동사)입니다. "I eat an apple"과 "나는 사과를 먹는다"의 구조가 완전히 다르죠.

둘째, 교착어 특성이 있습니다. 한국어는 조사와 어미가 붙어서 의미가 달라집니다.

"가다", "가고", "가니까", "가더라도" 모두 "가"에서 파생되지만 각각 다른 의미를 가집니다. 영어 BERT는 이런 한국어의 특성을 이해하지 못합니다.

셋째, 띄어쓰기 규칙이 다릅니다. 한국어는 띄어쓰기가 불규칙하고, 붙여 써도 의미 전달이 되는 경우가 많습니다.

"아버지가방에들어가신다"처럼요. 대표적인 한국어 BERT 모델들을 살펴봅시다.

KoBERT는 SKT에서 2019년에 공개한 한국어 BERT입니다. 한국어 위키피디아와 뉴스 데이터로 학습했고, 한국어 NLP 연구의 기폭제가 되었습니다.

다만 SentencePiece 토크나이저를 사용해서 Hugging Face와의 호환이 약간 번거로울 수 있습니다. KoELECTRA는 ELECTRA 방식을 한국어에 적용한 모델입니다.

monologg 님이 공개한 이 모델은 Hugging Face에서 쉽게 사용할 수 있고, 다양한 한국어 태스크에서 좋은 성능을 보여줍니다. 위 코드에서도 KoELECTRA를 사용한 예시를 확인할 수 있습니다.

KLUE-BERT는 네이버, 카카오 등 여러 기업이 협력해서 만든 한국어 언어 이해 벤치마크인 KLUE와 함께 공개된 모델입니다. 표준화된 벤치마크가 있어서 모델 성능을 비교하기 좋습니다.

위 코드를 살펴보면, KoELECTRA로 한국어 문장을 토큰화하면 "오늘", "날씨", "##가" 등으로 적절히 분리되는 것을 볼 수 있습니다. 영어 BERT였다면 이렇게 의미 있는 분리가 불가능했을 것입니다.

실무에서 어떤 모델을 선택해야 할까요? 빠른 프로토타이핑에는 KoELECTRA를 추천합니다.

Hugging Face 지원이 좋고 사용이 편리합니다. 더 높은 성능이 필요하면 KLUE-BERT나 최신 모델들을 시도해 보세요.

대규모 서비스라면 카카오의 KoGPT나 네이버의 HyperCLOVA 같은 더 큰 모델도 고려할 수 있습니다. 김개발 씨가 KoELECTRA로 다시 시도해보니, 성능이 확연히 좋아졌습니다.

"와, 한국어 전용 모델을 쓰니까 완전히 다르네요!" 한국어 NLP를 할 때는 반드시 한국어 전용 모델을 사용하세요. 영어 모델에 한국어를 넣는 것은 영어 사전으로 한국어를 찾는 것과 같습니다.

실전 팁

💡 - 한국어 모델을 선택할 때는 학습 데이터의 도메인을 확인하세요. 뉴스로 학습된 모델은 일상 대화에 약할 수 있습니다

  • 형태소 분석기와 함께 사용하면 성능이 더 좋아지는 경우도 있습니다

6. BERT Fine tuning 전략

김개발 씨가 드디어 BERT를 활용한 감성 분석 모델을 만들려고 합니다. "사전학습된 BERT를 가져다 썼는데, 어떻게 제 데이터에 맞게 학습시키죠?" 박시니어 씨가 답합니다.

"그게 바로 Fine-tuning이에요. 사전학습된 모델을 특정 태스크에 맞게 미세 조정하는 거죠."

Fine-tuning은 사전학습된 BERT를 특정 태스크에 맞게 추가 학습시키는 과정입니다. 마치 종합 교육을 받은 의사가 특정 분야의 전문의가 되기 위해 추가 수련을 받는 것과 같습니다.

분류, QA, NER 등 다양한 태스크에 BERT를 활용할 수 있으며, 적절한 학습률과 에폭 설정이 중요합니다.

다음 코드를 살펴봅시다.

from transformers import BertForSequenceClassification, BertTokenizer
from transformers import Trainer, TrainingArguments
import torch

# 감성 분류를 위한 BERT 모델 로드
model = BertForSequenceClassification.from_pretrained(
    'bert-base-uncased',
    num_labels=2  # 긍정/부정 2개 클래스
)
tokenizer = BertTokenizer.from_pretrained('bert-base-uncased')

# 학습 설정 - Fine-tuning에 적합한 하이퍼파라미터
training_args = TrainingArguments(
    output_dir='./results',
    num_train_epochs=3,           # 보통 2-4 에폭이 적당
    per_device_train_batch_size=16,
    learning_rate=2e-5,           # Fine-tuning에는 작은 학습률
    warmup_steps=500,             # 학습 초기 안정화
    weight_decay=0.01,            # 과적합 방지
)

# Trainer로 학습 (데이터셋이 준비되어 있다고 가정)
# trainer = Trainer(model=model, args=training_args, train_dataset=train_data)
# trainer.train()

김개발 씨는 회사의 고객 리뷰 데이터를 분석하는 업무를 맡았습니다. 수만 건의 리뷰를 일일이 읽을 수 없으니, BERT를 이용해서 자동으로 긍정/부정을 분류하려고 합니다.

하지만 BERT는 범용적인 언어 이해만 할 뿐, 감성 분류는 모릅니다. 박시니어 씨가 설명합니다.

"사전학습된 BERT는 마치 의대를 졸업한 의사와 같아요. 기초 의학 지식은 있지만, 특정 분야의 전문가가 되려면 추가 수련이 필요하죠.

그게 바로 Fine-tuning이에요." Fine-tuning의 핵심 개념을 이해해 봅시다. BERT의 사전학습은 위키피디아 같은 대규모 텍스트로 진행됩니다.

이 과정에서 BERT는 언어의 문법, 의미, 문맥을 배웁니다. 하지만 "이 리뷰가 긍정인가 부정인가"를 판단하는 것은 배우지 않았습니다.

Fine-tuning은 이미 배운 언어 지식을 활용해서 특정 태스크를 수행하도록 조정하는 과정입니다. 왜 처음부터 학습하지 않고 Fine-tuning을 할까요?

비유하자면, 한국어를 전혀 모르는 외국인에게 한국어 감성 분석을 가르치는 것과, 한국어를 이미 유창하게 하는 사람에게 감성 분석을 가르치는 것의 차이입니다. 후자가 훨씬 빠르고 효과적이겠죠.

Fine-tuning에서 가장 중요한 것은 **학습률(Learning Rate)**입니다. 일반적인 딥러닝에서는 0.001 같은 학습률을 사용하지만, BERT Fine-tuning에서는 2e-5(0.00002) 정도의 매우 작은 학습률을 사용합니다.

왜냐하면 BERT가 이미 좋은 가중치를 가지고 있기 때문입니다. 너무 큰 학습률을 사용하면 사전학습으로 얻은 귀중한 지식이 망가질 수 있습니다.

에폭 수도 중요합니다. 일반적인 딥러닝에서는 수십~수백 에폭을 학습하지만, Fine-tuning에서는 2-4 에폭만으로도 충분합니다.

사전학습에서 이미 대부분을 배웠기 때문에, 약간의 조정만 해주면 됩니다. 오히려 너무 많이 학습하면 과적합이 발생할 수 있습니다.

위 코드를 살펴보겠습니다. BertForSequenceClassification은 BERT 위에 분류 레이어를 추가한 모델입니다.

num_labels=2는 이진 분류(긍정/부정)를 의미합니다. TrainingArguments에서 learning_rate=2e-5로 설정한 것을 확인하세요.

warmup_steps는 학습 초기에 학습률을 천천히 올려서 안정적인 학습을 돕습니다. Fine-tuning의 다양한 태스크들도 알아두면 좋습니다.

문장 분류는 가장 기본적인 태스크입니다. [CLS] 토큰의 출력을 분류 레이어에 통과시켜 결과를 얻습니다.

**토큰 분류(NER)**는 각 토큰이 어떤 개체명인지 분류합니다. **질의응답(QA)**은 지문에서 답변의 시작과 끝 위치를 예측합니다.

주의할 점도 있습니다. 과적합을 조심해야 합니다.

데이터가 적을수록 과적합이 쉽게 발생합니다. Dropout, Weight Decay를 적절히 설정하고, 검증 데이터로 성능을 모니터링해야 합니다.

또한 클래스 불균형이 있다면 가중치 조정이나 오버샘플링을 고려하세요. 김개발 씨가 위 코드를 참고해서 Fine-tuning을 진행했습니다.

3에폭만에 90% 이상의 정확도를 달성했습니다. "생각보다 빨리 학습이 끝났네요!

사전학습의 힘이군요." Fine-tuning은 BERT 활용의 꽃입니다. 적절한 하이퍼파라미터 설정과 함께라면, 적은 데이터로도 높은 성능의 모델을 만들 수 있습니다.

실전 팁

💡 - 데이터가 적을 때는 에폭을 줄이고, 데이터가 많을 때는 늘려보세요

  • 학습률은 1e-5 ~ 5e-5 사이에서 실험해보는 것이 좋습니다
  • 배치 사이즈가 작으면 gradient accumulation을 활용하세요

이상으로 학습을 마칩니다. 위 내용을 직접 코드로 작성해보면서 익혀보세요!

#NLP#BERT#Transformer#Fine-tuning#KoBERT#BERT,NLP,LLM

댓글 (0)

댓글을 작성하려면 로그인이 필요합니다.