HSEOM GeckoHSEOM
Instagram

흑섬 TECH 블로그 - 데이터 기반 브리딩 기술

레오파드게코 브리딩에 데이터 분석과 AI 기술을 접목합니다. Python, NumPy를 활용한 체중 관리, 성장 추이 분석, 환경 데이터 시각화 등 실무에서 직접 사용하는 기술을 일반인도 이해하기 쉽게 설명합니다.

주요 카테고리

Play 카테고리

흑섬 TECH 블로그의 Play 카테고리입니다.

7개의 글이 있습니다.

[NLP 프로젝트 1편] 한국어 뉴스 분류기 — BERT fine-tuning 처음부터 끝까지

뉴스 제목 하나로 카테고리를 맞히는 분류기를 만들어봤습니다. TF-IDF 베이스라인부터 BERT fine-tuning까지, Tokenizer가 텍스트를 어떻게 쪼개는지, [CLS] 토큰이 왜 분류에 쓰이는지, Fine-tuning과 Feature Extraction의 차이까지 KLUE-YNAT 데이터셋으로 직접 확인해봤습니다.

카테고리: Play

작성일: 2026-05-19

예상 읽기 시간: 28

Back to Tech
Play·28min read·

[NLP 프로젝트 1편] 한국어 뉴스 분류기 — BERT fine-tuning 처음부터 끝까지

뉴스 제목 하나로 카테고리를 맞히는 분류기를 만들어봤습니다. TF-IDF 베이스라인부터 BERT fine-tuning까지, Tokenizer가 텍스트를 어떻게 쪼개는지, [CLS] 토큰이 왜 분류에 쓰이는지, Fine-tuning과 Feature Extraction의 차이까지 KLUE-YNAT 데이터셋으로 직접 확인해봤습니다.

뉴스 제목 하나 읽고 '아 이건 스포츠 기사구나' 하는 판단, 사람은 0.1초면 됩니다.
모델은 어떻게 할까요?

이번 글에서는 KLUE-YNAT 한국어 뉴스 데이터로 BERT 분류기를 직접 만들어봤습니다.
"손흥민 해트트릭" 같은 뉴스 제목을 넣으면 '스포츠'를 출력하는 모델입니다.

개념만 보고 끝내기 아쉬워서, Colab 코드로 실제 학습까지 같이 진행했습니다.
TF-IDF 베이스라인을 먼저 잡고, 그 위에서 BERT fine-tuning으로 성능을 끌어올리는 순서로 정리했습니다.



BERT가 뭔지 — 양방향으로 읽는 언어 모델

GPT는 텍스트를 왼쪽에서 오른쪽으로 읽습니다.
"나는 축구를 ___"라는 문장이 있으면 앞 단어들만 보고 다음 단어를 예측하는 방식이에요.

BERT는 반대로 양방향입니다.
문장 전체를 한 번에 보면서 마스크된 단어를 맞히는 방식으로 학습합니다.
"나는 축구를 [MASK] 한다"라는 문장이 있으면, 앞뒤 단어 모두를 참조해서 [MASK] 자리를 채웁니다.

왜 이게 중요할까요?
"은행에 돈을 맡겼다"와 "강가에 은행나무가 있다"에서 '은행'의 의미가 다릅니다.
GPT는 앞 단어만 보기 때문에 '강가에 은행'까지 읽은 시점에선 금융기관인지 나무인지 모릅니다.
BERT는 뒤에 오는 '돈을 맡겼다' 또는 '나무가 있다'까지 보고 의미를 구분하는 거죠.
(쉽게 말하면: BERT는 문장 전체를 한눈에 보는 독자입니다. GPT는 왼쪽부터 한 글자씩 읽어가는 독자고요.)

BERT의 사전학습은 두 가지 방식으로 이뤄집니다.

  • MLM (Masked Language Model): 문장에서 일부 토큰을 [MASK]로 가리고, 양방향 문맥을 활용해 맞히는 방식
  • NSP (Next Sentence Prediction): 두 문장이 연속된 문장인지 아닌지 판별하는 방식
GPT 단방향 vs BERT 양방향 구조 비교

Tokenizer — 텍스트가 숫자가 되는 과정

모델은 텍스트를 직접 읽지 못합니다.
모든 입력은 숫자로 변환되어야 하고, 이 변환을 담당하는 게 토크나이저입니다.

BERT는 WordPiece 방식을 씁니다.
사전에 없는 단어를 서브워드 단위로 쪼개는 방식인데요, klue/bert-base 기준으로 "안녕하세요"는 ["안녕", "##하세요"]로 나뉩니다.
"##"이 붙으면 앞 토큰의 연속이라는 뜻입니다.
(쉽게 말하면: 처음 보는 단어가 나와도 작은 조각으로 쪼개서 처리합니다. 완전히 모르는 단어가 생기는 상황을 줄이기 위해서예요.)

토크나이징을 거치면 두 가지 특수 토큰이 추가됩니다.

  • [CLS]: 문장 맨 앞에 붙는 토큰. 문장 전체 의미를 담는 요약 자리입니다. 분류 태스크에서 핵심 역할을 합니다.
  • [SEP]: 문장 끝에 붙는 토큰. 두 문장을 구분하는 경계 역할을 합니다.

토크나이저 출력은 세 가지입니다.

  • input_ids: 각 토큰의 어휘 사전 인덱스 번호
  • attention_mask: 실제 토큰(1)과 패딩(0)을 구분하는 마스크
  • token_type_ids: 두 문장이 있을 때 첫 번째 문장(0)과 두 번째 문장(1) 구분
손흥민이 골을 넣었다 문장 토크나이징 흐름도

[CLS] 토큰이 왜 분류에 쓰이나요?

BERT 인코더는 각 토큰 위치마다 768차원 벡터를 출력합니다.
입력 토큰이 6개라면 출력 벡터도 6개인데, 분류 태스크에서는 이 중 [CLS] 위치의 벡터 하나만 사용합니다.

이유가 있습니다.
BERT는 Self-Attention을 통해 모든 토큰이 다른 모든 토큰을 참조합니다.
12개 레이어를 거치면서 [CLS] 위치의 벡터는 문장 전체 문맥을 점점 더 압축해서 담게 됩니다.
사전학습 단계에서 NSP 태스크가 [CLS] 벡터를 문장 수준 표현 학습에 활용했기 때문이기도 하고요.

처음 BERT 코드를 볼 때 outputs[0][:, 0, :]이라는 표현이 있었는데, 왜 갑자기 인덱스 0번 토큰만 쓰는지 몰랐습니다.
나중에 토크나이저 출력을 직접 찍어보니 0번 위치가 항상 [CLS]라는 걸 알게 됐고, 그제야 이 코드가 [CLS] 벡터만 꺼내는 코드라는 게 이해됐습니다.
(쉽게 말하면: [CLS]는 문장 전체를 요약한 명함 같은 벡터입니다. 이 768차원 벡터에 분류 레이어 하나를 붙이면 분류기가 됩니다.)

구조는 간단합니다.
BERT 인코더 통과 후 [CLS] 위치 벡터(768차원)를 꺼내고, 거기에 Linear(768 → num_classes) 레이어를 하나 붙입니다.
KLUE-YNAT는 7개 카테고리이니 Linear(768 → 7)이 됩니다.

BERT 인코더에서 CLS 벡터를 꺼내 분류 레이어로 연결하는 흐름

Fine-tuning vs Feature Extraction — 뭐가 다른가요?

사전학습된 BERT를 분류 태스크에 쓰는 방법은 두 가지입니다.

Feature Extraction은 BERT 가중치를 완전히 고정(freeze)합니다.
[CLS] 벡터만 꺼내서 SVM, Logistic Regression 같은 별도 분류기에 넣는 방식이에요.
BERT는 특징 추출기 역할만 하고, 가중치는 전혀 바뀌지 않습니다.

Fine-tuning은 BERT 전체 가중치를 분류 태스크에 맞게 미세 조정합니다.
분류 레이어와 함께 BERT 가중치도 역전파를 통해 업데이트됩니다.
(쉽게 말하면: Feature Extraction은 레시피 그대로 음식을 내는 것, Fine-tuning은 이 식당 메뉴에 맞게 레시피를 조금씩 수정하는 것입니다.)

실제 성능은 대부분 Fine-tuning이 더 높습니다.
태스크에 특화된 패턴을 BERT 레이어 전체가 학습하기 때문입니다.
다만 GPU 메모리와 학습 시간이 더 필요합니다.

Feature Extraction과 Fine-tuning 구조 비교

KLUE-YNAT 데이터셋 — 실제 한국어 뉴스 제목 7개 카테고리

KLUE (Korean Language Understanding Evaluation)는 한국어 자연어 이해 능력을 평가하는 벤치마크 모음입니다.
영어의 GLUE 벤치마크에 대응하는 한국어 버전으로, 여러 NLP 태스크가 포함되어 있습니다.

그 중 YNAT(Yet Another News Article Tag)는 뉴스 제목을 보고 7개 카테고리 중 하나로 분류하는 태스크입니다.

  • IT/과학, 경제, 사회, 생활문화, 세계, 스포츠, 정치

실제 예시를 보면 이해가 빠릅니다.

  • "코스피 2% 하락, 외국인 순매도" → 경제
  • "손흥민 해트트릭, 토트넘 3-0 완승" → 스포츠
  • "애플 아이폰 16 발표, 새 A18 칩 탑재" → IT/과학
  • "국회 예산안 새벽 처리" → 정치

사람이 제목만 보고 '이거 스포츠 기사다' 하는 판단을 모델이 학습하는 태스크입니다.
카테고리별 데이터 건수를 보면 정치(6,498건)가 가장 많고 세계(3,742건)가 가장 적습니다.
큰 불균형은 없어서 별도 처리 없이 그대로 학습에 사용할 수 있어요.

KLUE-YNAT 카테고리별 학습 데이터 분포 차트

베이스라인 먼저 — TF-IDF + Logistic Regression

BERT 결과가 좋은지 나쁜지 판단하려면 비교 대상이 필요합니다.
딥러닝 없이 전통적인 방법으로 얼마나 나오는지 먼저 확인해봤습니다.

TF-IDF는 각 단어의 중요도를 계산해서 텍스트를 벡터로 만드는 방법입니다.
자주 등장하지만 문서 전체에선 드문 단어일수록 높은 점수를 받고, 이 벡터를 Logistic Regression에 넣어서 분류합니다.

뉴스 제목에는 카테고리를 강하게 나타내는 단어들이 많습니다.
"주가", "코스피"는 경제, "선발투수", "홈런"은 스포츠처럼요.
그래서 TF-IDF만으로도 어느 정도 성능이 나오는데, 실제로는 68.0%였습니다.

Colab 실습 — 데이터 로드 결과 (셀 1)
KLUE-YNAT 데이터 로드 결과

학습 45,678건 / 검증 9,107건. 카테고리별 분포와 샘플 5개가 출력됩니다.

Colab 실습 — TF-IDF 베이스라인 결과 (셀 2)
TF-IDF + Logistic Regression classification_report

전체 정확도 68.0%. 스포츠(F1=0.97)는 높고 생활문화(F1=0.60)는 낮습니다. 카테고리별 특징 단어가 뚜렷한 스포츠에서 TF-IDF가 특히 강합니다.



BERT fine-tuning — klue/bert-base로 직접 학습하기

klue/bert-base는 KLUE 벤치마크 팀이 한국어 데이터로 사전학습한 BERT 모델입니다.
Hugging Face Hub에서 무료로 사용할 수 있고, 한국어 NLP 태스크에서 기본 출발점으로 많이 쓰입니다.

Hugging Face의 Trainer API를 사용하면 학습 루프를 직접 작성하지 않아도 됩니다.
데이터셋, 모델, 학습 설정만 넘기면 됩니다.

주요 하이퍼파라미터입니다.

  • batch_size: 32 (T4 GPU 기준)
  • epochs: 3 (보통 3~5에서 수렴)
  • learning_rate: 2e-5
    (learning rate를 왜 이렇게 작게 쓰냐면, BERT의 사전학습 가중치를 너무 빠르게 바꾸면 기존에 학습한 언어 지식이 사라집니다. 작은 학습률로 조금씩 조정해야 사전학습 지식을 유지할 수 있어요.)
Colab 실습 — BERT Tokenizer + Dataset 준비 (셀 3)
BERT Tokenizer 및 NewsDataset 클래스 코드

"손흥민이 골을 넣었다" → ['[CLS]', '손흥민', '##이', '골을', '넣었다', '[SEP]'] 로 분리됩니다. 학습 Dataset 45,678건 / 검증 9,107건이 준비됩니다.

Colab 실습 — BERT fine-tuning 학습 (셀 4, T4 GPU 약 10분)
TrainingArguments 설정 및 Trainer 학습 시작 3 Epoch 학습 완료 — Loss 0.29, Accuracy 87.0%

Epoch 1→2→3 진행 시 Accuracy 86.1% → 87.0% → 87.0%로 수렴합니다. 최종 학습 Loss 0.29.



결과 확인 — Loss 커브와 혼동행렬

학습이 끝나면 두 가지를 확인합니다.

첫 번째는 Loss 커브입니다.
학습 Loss와 검증 Loss 모두 에폭이 진행될수록 떨어지면 정상입니다.
학습 Loss는 계속 내려가는데 검증 Loss가 올라가기 시작하면 과적합 신호예요.

두 번째는 혼동행렬(Confusion Matrix)입니다.
어떤 카테고리를 어떤 카테고리로 잘못 분류하는지 볼 수 있습니다.
사회-경제, 정치-사회 간 혼동이 일부 발생하는데, 제목만 보고는 사람도 헷갈리는 기사들이 있기 때문입니다.

실제 실행 결과입니다.

  • TF-IDF + LR: 68.0% 정확도
  • BERT fine-tuning: 87.0% 정확도 (+19.0%p)
TF-IDF vs BERT 정확도 및 카테고리별 F1 비교 차트
Colab 실습 — Loss 커브 시각화 (셀 5)
Loss 커브 시각화 코드 Training & Validation Loss 커브 + Epoch별 Accuracy 그래프

Train Loss는 1.27에서 0.19까지 안정적으로 감소합니다. Validation Accuracy는 2 Epoch에서 수렴합니다.

Colab 실습 — 혼동행렬 (셀 6)
혼동행렬 시각화 코드 BERT fine-tuning 혼동행렬 — 대각선이 뚜렷하게 채워짐 (정확도 87.0%)

대각선(정분류)이 뚜렷하게 채워졌습니다. 사회(3,177건 정분류)가 가장 많고, 사회-경제 사이에서 소수의 혼동이 발생합니다.

Colab 실습 — 실제 뉴스 제목 예측 테스트 (셀 7)
6개 뉴스 제목 분류 예측 결과 — 모두 99% 이상 신뢰도

"코스피 2% 급락" → 경제 99.6%, "손흥민 해트트릭" → 스포츠 99.9% 등 6개 모두 높은 신뢰도로 분류됩니다.



정리

이번 글에서 확인한 핵심 내용입니다.

  1. BERT는 양방향으로 문맥을 읽습니다. GPT와 달리 문장 전체를 한 번에 참조합니다.
  2. WordPiece 토크나이저는 모르는 단어를 서브워드로 쪼개서 처리합니다. [CLS]와 [SEP] 특수토큰이 항상 추가됩니다.
  3. [CLS] 위치의 768차원 벡터가 문장 전체 의미를 담습니다. 여기에 Linear 레이어 하나를 붙이면 분류기가 됩니다.
  4. Fine-tuning은 BERT 전체 가중치를 태스크에 맞게 업데이트합니다. Feature Extraction보다 성능이 높지만 GPU 자원이 더 필요합니다.
  5. KLUE-YNAT 기준으로 TF-IDF 베이스라인 68.0%, BERT fine-tuning 87.0%로 19%p 향상됐습니다.
BERT 뉴스 분류 전체 파이프라인 요약

2편에서는 잘못 분류된 케이스를 분석하고, 더 나은 모델로 개선해볼 예정입니다.
어떤 뉴스 제목들이 모델을 헷갈리게 하는지, 그리고 그 이유가 뭔지를 살펴봅니다.

#BERT#fine-tuning#KLUE#한국어NLP#뉴스분류#Transformers#Colab