- Authors

- Name
- Youngju Kim
- @fjvbn20031
한국어 NLP와 LLM 완전 가이드
한국어는 AI 관점에서 특히 흥미로운 언어입니다. 교착어 구조, 복잡한 형태소 체계, 그리고 띄어쓰기의 유연성 때문에 영어 중심으로 설계된 NLP 도구를 그대로 쓰면 성능이 크게 떨어집니다. 이 가이드에서는 KoBERT, KLUE, HyperCLOVA X, EXAONE 등 한국어 특화 모델과 도구를 실전 코드와 함께 총정리합니다.
1. 한국어 NLP의 특수성
교착어(Agglutinative Language)란?
영어는 고립어(Isolating language)로, 어순이 문장의 의미를 결정합니다. 반면 한국어는 교착어로, 어근에 다양한 접사(조사, 어미)가 붙어 문법적 기능을 표현합니다.
예시:
- "나는 학교에 간다" → 나(I) + 는(topic marker) + 학교(school) + 에(to) + 간다(go)
- "나는 학교에서 공부한다" → 같은 어근에 다른 조사 사용
이 특성 때문에 단순히 공백으로 토크나이즈하면 의미 단위가 잘못 분리됩니다.
조사와 어미의 다양성
한국어 조사는 앞 단어의 받침 유무에 따라 형태가 바뀝니다.
- 주격 조사: 이/가 (받침 O: 이, 받침 X: 가)
- 목적격 조사: 을/를 (받침 O: 을, 받침 X: 를)
- 처소 조사: 에서, 에게서, 한테서
동사 어미 변화도 매우 복잡합니다.
- 가다 → 가고, 가서, 가지만, 가는데, 갔다, 갈것이다 ...
이 때문에 가다와 갑니다가 같은 어근임을 인식하려면 형태소 분석이 필수입니다.
띄어쓰기 문제
한국어 띄어쓰기 규칙은 복잡하고, 실제 텍스트(SNS, 댓글 등)에서는 띄어쓰기가 자주 생략됩니다.
# 띄어쓰기 오류 예시
text_with_errors = "나는학교에갔다" # 실제로 자주 발생
text_correct = "나는 학교에 갔다"
PyKoSpacing 같은 라이브러리로 띄어쓰기를 교정할 수 있습니다.
from pykospacing import Spacing
spacing = Spacing()
result = spacing("나는학교에갔다오늘은날씨가매우좋아서기분이좋다")
print(result)
# "나는 학교에 갔다 오늘은 날씨가 매우 좋아서 기분이 좋다"
동음이의어와 문맥 의존성
한국어에는 문맥에 따라 의미가 달라지는 단어가 많습니다.
- 배: 과일 배 / 신체 부위 배 / 교통수단 배
- 눈: 신체 기관 눈 / 날씨 현상 눈
- 다리: 신체 부위 다리 / 교량 다리
BERT 계열 모델의 양방향 문맥 이해 덕분에 이러한 다의어 처리가 가능해졌습니다.
2. 한국어 형태소 분석기
KoNLPy 소개
KoNLPy는 한국어 자연어 처리를 위한 Python 패키지로, 여러 형태소 분석기를 통일된 인터페이스로 제공합니다.
pip install konlpy
주요 형태소 분석기 비교
Mecab (MeCab-ko)
가장 빠르고 정확한 형태소 분석기입니다. 딕셔너리 기반이라 도메인 사전 추가가 용이합니다.
from konlpy.tag import Mecab
mecab = Mecab()
text = "아버지가 방에 들어가신다"
print(mecab.pos(text))
# [('아버지', 'NNG'), ('가', 'JKS'), ('방', 'NNG'), ('에', 'JKB'), ('들어가', 'VV'), ('신다', 'EP+EF')]
print(mecab.nouns(text))
# ['아버지', '방']
print(mecab.morphs(text))
# ['아버지', '가', '방', '에', '들어가', '신다']
Komoran
Java 기반의 형태소 분석기로, 불규칙 변형 처리가 우수합니다.
from konlpy.tag import Komoran
komoran = Komoran()
text = "대한민국은 아름다운 나라이다"
print(komoran.pos(text))
# [('대한민국', 'NNP'), ('은', 'JX'), ('아름답', 'VA'), ('ㄴ', 'ETM'), ('나라', 'NNG'), ('이', 'VCP'), ('다', 'EF')]
Okt (Open Korean Text)
Twitter에서 만든 오픈소스 한국어 처리기입니다. 비공식적인 텍스트(SNS, 댓글) 처리에 강점이 있습니다.
from konlpy.tag import Okt
okt = Okt()
text = "나는 자연어처리를 좋아해요 ㅋㅋ"
print(okt.pos(text))
# [('나', 'Noun'), ('는', 'Josa'), ('자연어처리', 'Noun'), ('를', 'Josa'), ('좋아해요', 'Verb'), ('ㅋㅋ', 'KoreanParticle')]
# 정규화와 스테밍
print(okt.pos(text, norm=True, stem=True))
Kkma
세종 말뭉치로 훈련된 분석기로, 형태소 분석 정확도가 높지만 속도가 느립니다.
from konlpy.tag import Kkma
kkma = Kkma()
text = "오늘 날씨가 정말 좋네요"
print(kkma.sentences(text))
print(kkma.nouns(text))
성능 비교표
| 분석기 | 속도 | 정확도 | 비공식 텍스트 | 사전 커스터마이징 |
|---|---|---|---|---|
| Mecab | 매우 빠름 | 높음 | 보통 | 쉬움 |
| Komoran | 보통 | 높음 | 보통 | 보통 |
| Okt | 보통 | 보통 | 우수 | 어려움 |
| Kkma | 느림 | 높음 | 보통 | 어려움 |
| Hannanum | 느림 | 보통 | 보통 | 보통 |
실전: 전처리 파이프라인
from konlpy.tag import Mecab
import re
class KoreanTextPreprocessor:
def __init__(self):
self.mecab = Mecab()
def clean_text(self, text: str) -> str:
# HTML 태그 제거
text = re.sub(r'<[^>]+>', '', text)
# 특수문자 제거 (한글, 영어, 숫자 유지)
text = re.sub(r'[^가-힣a-zA-Z0-9\s]', '', text)
# 연속 공백 제거
text = re.sub(r'\s+', ' ', text).strip()
return text
def extract_nouns(self, text: str) -> list:
cleaned = self.clean_text(text)
return self.mecab.nouns(cleaned)
def tokenize(self, text: str) -> list:
cleaned = self.clean_text(text)
return self.mecab.morphs(cleaned)
def get_pos_tags(self, text: str) -> list:
cleaned = self.clean_text(text)
return self.mecab.pos(cleaned)
# 사용 예시
preprocessor = KoreanTextPreprocessor()
sample = "오늘 서울 날씨가 맑고 기온이 20도입니다. AI 기술이 급속도로 발전하고 있네요!"
print("명사:", preprocessor.extract_nouns(sample))
print("형태소:", preprocessor.tokenize(sample))
print("품사:", preprocessor.get_pos_tags(sample))
3. 한국어 사전학습 모델
KoBERT (SKT)
SKT(SK Telecom)가 한국어 위키피디아와 네이버 뉴스로 학습한 BERT 모델입니다.
pip install 'git+https://github.com/SKTBrain/KoBERT.git#egg=kobert_tokenizer&subdirectory=kobert_hf'
pip install transformers torch
from transformers import BertModel, BertTokenizer
import torch
# KoBERT 로드
tokenizer = BertTokenizer.from_pretrained('skt/kobert-base-v1')
model = BertModel.from_pretrained('skt/kobert-base-v1')
text = "한국어 자연어 처리 기술이 빠르게 발전하고 있습니다."
inputs = tokenizer(text, return_tensors='pt', padding=True, truncation=True)
with torch.no_grad():
outputs = model(**inputs)
# [CLS] 토큰 임베딩 (문장 표현)
sentence_embedding = outputs.last_hidden_state[:, 0, :]
print("임베딩 형태:", sentence_embedding.shape) # (1, 768)
KoBERT 감성 분석 파인튜닝
from transformers import BertForSequenceClassification, BertTokenizer, Trainer, TrainingArguments
from datasets import Dataset
import torch
# 데이터 준비 (네이버 영화 리뷰 예시)
train_data = {
'text': [
"이 영화는 정말 감동적이었습니다.",
"완전 지루하고 재미없어요.",
"배우들의 연기가 훌륭했어요!",
"시간 낭비입니다. 보지 마세요.",
"최고의 영화! 강력 추천합니다.",
],
'label': [1, 0, 1, 0, 1] # 1: 긍정, 0: 부정
}
dataset = Dataset.from_dict(train_data)
# 토크나이저와 모델
tokenizer = BertTokenizer.from_pretrained('skt/kobert-base-v1')
model = BertForSequenceClassification.from_pretrained(
'skt/kobert-base-v1',
num_labels=2
)
def tokenize_function(examples):
return tokenizer(
examples['text'],
padding='max_length',
truncation=True,
max_length=128
)
tokenized_dataset = dataset.map(tokenize_function, batched=True)
# 학습 설정
training_args = TrainingArguments(
output_dir='./kobert-sentiment',
num_train_epochs=3,
per_device_train_batch_size=16,
warmup_steps=100,
weight_decay=0.01,
logging_dir='./logs',
logging_steps=10,
eval_strategy='epoch',
save_strategy='epoch',
)
trainer = Trainer(
model=model,
args=training_args,
train_dataset=tokenized_dataset,
)
trainer.train()
KoELECTRA
Google ELECTRA 아키텍처를 한국어에 적용한 모델로, KoBERT보다 더 효율적으로 학습됩니다.
from transformers import ElectraForSequenceClassification, ElectraTokenizer
# KoELECTRA-base 로드
tokenizer = ElectraTokenizer.from_pretrained('monologg/koelectra-base-v3-discriminator')
model = ElectraForSequenceClassification.from_pretrained(
'monologg/koelectra-base-v3-discriminator',
num_labels=2
)
def predict_sentiment(text: str) -> str:
inputs = tokenizer(
text,
return_tensors='pt',
padding=True,
truncation=True,
max_length=128
)
with torch.no_grad():
outputs = model(**inputs)
predicted_class = torch.argmax(outputs.logits, dim=1).item()
return "긍정" if predicted_class == 1 else "부정"
# 테스트
test_texts = [
"정말 훌륭한 서비스입니다!",
"매우 실망스러웠습니다.",
]
for text in test_texts:
print(f"{text} → {predict_sentiment(text)}")
KLUE-RoBERTa
KLUE(Korean Language Understanding Evaluation) 팀이 공개한 RoBERTa 기반 모델입니다. 한국어 NLU 태스크에서 뛰어난 성능을 보입니다.
from transformers import RobertaForSequenceClassification, AutoTokenizer
tokenizer = AutoTokenizer.from_pretrained('klue/roberta-large')
model = RobertaForSequenceClassification.from_pretrained(
'klue/roberta-large',
num_labels=3 # 예: NLI 태스크 (함의/중립/모순)
)
# NLI (자연어 추론) 예제
premise = "고양이가 소파 위에 앉아 있다."
hypothesis = "소파 위에 동물이 있다."
inputs = tokenizer(
premise,
hypothesis,
return_tensors='pt',
padding=True,
truncation=True
)
with torch.no_grad():
outputs = model(**inputs)
labels = ['함의', '중립', '모순']
predicted = torch.argmax(outputs.logits, dim=1).item()
print(f"관계: {labels[predicted]}") # 함의
4. KLUE 벤치마크
KLUE(Korean Language Understanding Evaluation)는 한국어 NLU 모델을 종합적으로 평가하기 위한 벤치마크로, 8개의 태스크로 구성됩니다.
8개 태스크 개요
TC (Topic Classification) - 주제 분류
# 뉴스 기사 10가지 주제 분류
# IT과학, 경제, 사회, 생활문화, 세계, 스포츠, 정치, 연예
from transformers import pipeline
classifier = pipeline(
'text-classification',
model='klue/roberta-base',
tokenizer='klue/roberta-base'
)
STS (Semantic Textual Similarity) - 의미 유사도
두 문장의 의미적 유사도를 0~5 점수로 예측합니다.
from transformers import AutoModelForSequenceClassification, AutoTokenizer
import torch
model_name = 'klue/roberta-base'
tokenizer = AutoTokenizer.from_pretrained(model_name)
sentence1 = "두 사람이 피아노를 연주하고 있다."
sentence2 = "한 남자가 피아노를 치고 있다."
inputs = tokenizer(
sentence1,
sentence2,
return_tensors='pt',
padding=True,
truncation=True
)
NLI (Natural Language Inference) - 자연어 추론
전제(premise)와 가설(hypothesis) 사이의 관계를 판별합니다.
- 함의(Entailment): 전제가 참이면 가설도 참
- 중립(Neutral): 전제와 가설의 관계가 불명확
- 모순(Contradiction): 전제가 참이면 가설은 거짓
NER (Named Entity Recognition) - 개체명 인식
텍스트에서 인명, 지명, 기관명 등을 인식합니다.
from transformers import AutoModelForTokenClassification, AutoTokenizer, pipeline
model_name = 'klue/roberta-base'
ner_pipeline = pipeline(
'ner',
model=model_name,
tokenizer=model_name,
aggregation_strategy='simple'
)
text = "이순신 장군은 1545년 조선 한성에서 태어났다."
entities = ner_pipeline(text)
for entity in entities:
print(f"{entity['word']}: {entity['entity_group']} (신뢰도: {entity['score']:.2f})")
RE (Relation Extraction) - 관계 추출
두 개체 사이의 관계를 추출합니다 (예: 사람-출생지, 사람-소속 기관).
DP (Dependency Parsing) - 의존 구문 분석
문장의 구문 구조를 트리 형태로 분석합니다.
MRC (Machine Reading Comprehension) - 기계 독해
주어진 지문에서 질문에 대한 답을 추출합니다.
from transformers import pipeline
qa_pipeline = pipeline(
'question-answering',
model='uomnf97/klue-roberta-finetuned-korquad-v2',
tokenizer='uomnf97/klue-roberta-finetuned-korquad-v2'
)
context = """
세종대왕은 조선의 4대 왕으로, 1397년에 태어나 1450년에 세상을 떠났다.
그는 1443년에 한글(훈민정음)을 창제하여 조선의 문화 발전에 크게 기여하였다.
"""
question = "세종대왕이 한글을 창제한 연도는?"
result = qa_pipeline(question=question, context=context)
print(f"답변: {result['answer']}, 신뢰도: {result['score']:.4f}")
DST (Dialogue State Tracking) - 대화 상태 추적
대화에서 사용자의 목적과 슬롯 값을 추적합니다.
5. 한국어 임베딩 모델
KoSentenceBERT
pip install sentence-transformers
from sentence_transformers import SentenceTransformer
import numpy as np
model = SentenceTransformer('jhgan/ko-sroberta-multitask')
sentences = [
"오늘 날씨가 정말 좋네요.",
"날씨가 맑고 화창합니다.",
"주식 시장이 크게 하락했습니다.",
"경제 지표가 좋지 않습니다.",
]
embeddings = model.encode(sentences)
# 코사인 유사도 계산
from sklearn.metrics.pairwise import cosine_similarity
sim_matrix = cosine_similarity(embeddings)
print("문장 쌍별 유사도:")
for i in range(len(sentences)):
for j in range(i+1, len(sentences)):
print(f"'{sentences[i]}' - '{sentences[j]}': {sim_matrix[i][j]:.4f}")
한국어 시맨틱 검색 구현
from sentence_transformers import SentenceTransformer
import numpy as np
from sklearn.metrics.pairwise import cosine_similarity
class KoreanSemanticSearch:
def __init__(self, model_name='jhgan/ko-sroberta-multitask'):
self.model = SentenceTransformer(model_name)
self.documents = []
self.embeddings = None
def add_documents(self, documents: list):
self.documents = documents
self.embeddings = self.model.encode(documents)
print(f"{len(documents)}개 문서 인덱싱 완료")
def search(self, query: str, top_k: int = 3) -> list:
query_embedding = self.model.encode([query])
similarities = cosine_similarity(query_embedding, self.embeddings)[0]
# 상위 k개 결과
top_indices = np.argsort(similarities)[::-1][:top_k]
results = []
for idx in top_indices:
results.append({
'document': self.documents[idx],
'score': float(similarities[idx]),
'rank': len(results) + 1
})
return results
# 사용 예시
documents = [
"서울은 대한민국의 수도이며 인구 약 950만 명의 대도시입니다.",
"부산은 대한민국의 제2의 도시로 해변이 아름답습니다.",
"제주도는 화산섬으로 독특한 자연 경관을 가지고 있습니다.",
"인공지능 기술이 빠르게 발전하면서 다양한 산업에 영향을 미치고 있습니다.",
"딥러닝은 인공 신경망을 기반으로 한 기계 학습 방법입니다.",
]
searcher = KoreanSemanticSearch()
searcher.add_documents(documents)
query = "한국의 수도에 대해 알려주세요"
results = searcher.search(query, top_k=2)
print(f"쿼리: {query}")
for r in results:
print(f" {r['rank']}위 (점수: {r['score']:.4f}): {r['document']}")
6. 국산 대형 언어 모델 (LLM)
HyperCLOVA X (네이버)
네이버가 개발한 대형 언어 모델로, 82.7B 파라미터를 가집니다. 한국어 데이터를 영어 대비 6.8배 더 많이 학습했습니다.
import requests
import json
# HyperCLOVA X API 호출 예시
# (실제 사용 시 네이버 클라우드 플랫폼 API 키 필요)
def call_hyperclova(prompt: str, api_key: str) -> str:
url = "https://clovastudio.stream.ntruss.com/testapp/v1/chat-completions/HCX-003"
headers = {
"X-NCP-CLOVASTUDIO-API-KEY": api_key,
"Content-Type": "application/json"
}
payload = {
"messages": [
{
"role": "system",
"content": "당신은 도움이 되는 한국어 AI 어시스턴트입니다."
},
{
"role": "user",
"content": prompt
}
],
"maxTokens": 1000,
"temperature": 0.7,
"topP": 0.8,
"repeatPenalty": 1.2
}
response = requests.post(url, headers=headers, json=payload)
result = response.json()
return result['result']['message']['content']
EXAONE (LG AI Research)
LG AI Research가 개발한 한국어/영어 이중 언어 모델입니다. HuggingFace에서 무료로 이용 가능합니다.
from transformers import AutoModelForCausalLM, AutoTokenizer
import torch
# EXAONE 3.5 로드
model_name = "LGAI-EXAONE/EXAONE-3.5-7.8B-Instruct"
tokenizer = AutoTokenizer.from_pretrained(model_name)
model = AutoModelForCausalLM.from_pretrained(
model_name,
torch_dtype=torch.bfloat16,
device_map="auto",
trust_remote_code=True
)
# 채팅 형식으로 사용
messages = [
{"role": "system", "content": "You are EXAONE model from LG AI Research, a helpful assistant."},
{"role": "user", "content": "한국의 전통 음식 5가지를 알려주세요."}
]
# 채팅 템플릿 적용
input_ids = tokenizer.apply_chat_template(
messages,
tokenize=True,
add_generation_prompt=True,
return_tensors="pt"
).to(model.device)
outputs = model.generate(
input_ids,
max_new_tokens=512,
temperature=0.7,
do_sample=True,
top_p=0.9,
repetition_penalty=1.1
)
# 입력 부분 제외하고 디코딩
response = tokenizer.decode(outputs[0][input_ids.shape[1]:], skip_special_tokens=True)
print(response)
SOLAR (Upstage)
업스테이지가 개발한 10.7B 파라미터 모델입니다. 효율적인 Depth Up-Scaling 기법으로 만들어졌습니다.
from transformers import AutoModelForCausalLM, AutoTokenizer
import torch
model_name = "upstage/SOLAR-10.7B-Instruct-v1.0"
tokenizer = AutoTokenizer.from_pretrained(model_name)
model = AutoModelForCausalLM.from_pretrained(
model_name,
torch_dtype=torch.float16,
device_map="auto"
)
def generate_response(prompt: str, system: str = "당신은 유용한 AI 어시스턴트입니다.") -> str:
conversation = [
{"role": "system", "content": system},
{"role": "user", "content": prompt}
]
input_ids = tokenizer.apply_chat_template(
conversation,
return_tensors="pt"
).to(model.device)
outputs = model.generate(
input_ids,
max_new_tokens=1024,
do_sample=True,
temperature=0.7,
top_p=0.9,
)
return tokenizer.decode(outputs[0][input_ids.shape[-1]:], skip_special_tokens=True)
response = generate_response("Python으로 피보나치 수열을 구현해주세요.")
print(response)
7. Llama3 한국어 파인튜닝
데이터셋 준비
pip install datasets transformers peft bitsandbytes accelerate
from datasets import load_dataset, concatenate_datasets
# KoAlpaca 데이터셋
koalpaca = load_dataset("beomi/KoAlpaca-v1.1a")
# OpenOrca-KO (번역 데이터)
# openorca_ko = load_dataset("kyujinpy/KO-OpenOrca-Platypus4")
print("KoAlpaca 데이터셋 구조:")
print(koalpaca['train'][0])
def format_instruction(example):
"""Llama3 인스트럭션 형식으로 변환"""
system_prompt = "당신은 도움이 되는 한국어 AI 어시스턴트입니다. 질문에 정확하고 상세하게 답변해주세요."
if example.get('input') and example['input'].strip():
user_content = f"{example['instruction']}\n\n입력: {example['input']}"
else:
user_content = example['instruction']
formatted = f"""<|begin_of_text|><|start_header_id|>system<|end_header_id|>
{system_prompt}<|eot_id|><|start_header_id|>user<|end_header_id|>
{user_content}<|eot_id|><|start_header_id|>assistant<|end_header_id|>
{example['output']}<|eot_id|>"""
return {"text": formatted}
# 데이터 포매팅
formatted_dataset = koalpaca['train'].map(format_instruction)
print(formatted_dataset[0]['text'][:500])
QLoRA 파인튜닝
import torch
from transformers import (
AutoModelForCausalLM,
AutoTokenizer,
BitsAndBytesConfig,
TrainingArguments,
)
from peft import LoraConfig, get_peft_model, prepare_model_for_kbit_training
from trl import SFTTrainer
# 모델 설정
model_name = "meta-llama/Meta-Llama-3-8B-Instruct"
# 4비트 양자화 설정
bnb_config = BitsAndBytesConfig(
load_in_4bit=True,
bnb_4bit_use_double_quant=True,
bnb_4bit_quant_type="nf4",
bnb_4bit_compute_dtype=torch.bfloat16
)
# 모델 로드
model = AutoModelForCausalLM.from_pretrained(
model_name,
quantization_config=bnb_config,
device_map="auto",
trust_remote_code=True
)
tokenizer = AutoTokenizer.from_pretrained(model_name)
tokenizer.pad_token = tokenizer.eos_token
tokenizer.padding_side = "right"
# LoRA 설정
lora_config = LoraConfig(
r=16, # LoRA 랭크
lora_alpha=32, # 스케일링 인자
target_modules=[
"q_proj", "k_proj", "v_proj", "o_proj",
"gate_proj", "up_proj", "down_proj"
],
lora_dropout=0.05,
bias="none",
task_type="CAUSAL_LM"
)
# kbit 학습 준비
model = prepare_model_for_kbit_training(model)
model = get_peft_model(model, lora_config)
print(f"학습 가능한 파라미터: {model.print_trainable_parameters()}")
# 학습 설정
training_args = TrainingArguments(
output_dir="./llama3-korean",
num_train_epochs=3,
per_device_train_batch_size=2,
gradient_accumulation_steps=8,
optim="paged_adamw_32bit",
learning_rate=2e-4,
weight_decay=0.001,
fp16=True,
bf16=False,
max_grad_norm=0.3,
warmup_ratio=0.03,
group_by_length=True,
lr_scheduler_type="cosine",
logging_steps=10,
save_strategy="epoch",
report_to="tensorboard"
)
# SFT 트레이너
trainer = SFTTrainer(
model=model,
train_dataset=formatted_dataset,
dataset_text_field="text",
max_seq_length=2048,
tokenizer=tokenizer,
args=training_args,
packing=False,
)
trainer.train()
# 모델 저장
trainer.save_model("./llama3-korean-finetuned")
파인튜닝 모델 추론
from transformers import AutoModelForCausalLM, AutoTokenizer
from peft import PeftModel
import torch
# 기본 모델 로드
base_model = AutoModelForCausalLM.from_pretrained(
"meta-llama/Meta-Llama-3-8B-Instruct",
torch_dtype=torch.float16,
device_map="auto"
)
# LoRA 어댑터 로드
model = PeftModel.from_pretrained(base_model, "./llama3-korean-finetuned")
tokenizer = AutoTokenizer.from_pretrained("meta-llama/Meta-Llama-3-8B-Instruct")
def korean_chat(message: str) -> str:
messages = [
{"role": "system", "content": "당신은 도움이 되는 한국어 AI 어시스턴트입니다."},
{"role": "user", "content": message}
]
input_ids = tokenizer.apply_chat_template(
messages,
return_tensors="pt"
).to(model.device)
with torch.no_grad():
outputs = model.generate(
input_ids,
max_new_tokens=512,
temperature=0.7,
top_p=0.9,
do_sample=True,
repetition_penalty=1.1
)
return tokenizer.decode(outputs[0][input_ids.shape[-1]:], skip_special_tokens=True)
print(korean_chat("한국의 역사에서 가장 중요한 사건 3가지를 알려주세요."))
8. 한국어 RAG 시스템
한국어 청킹 전략
from langchain.text_splitter import RecursiveCharacterTextSplitter
import re
class KoreanTextSplitter:
"""한국어에 최적화된 텍스트 청킹"""
def __init__(self, chunk_size=500, chunk_overlap=50):
# 한국어 문장 구분자 포함
self.splitter = RecursiveCharacterTextSplitter(
separators=[
"\n\n", # 단락 구분
"\n", # 줄바꿈
"다. ", # 평서문 종결어미
"요. ", # 존댓말 종결어미
"까? ", # 의문문 종결어미
". ", # 영어 문장 구분
" ", # 공백
"", # 문자 단위
],
chunk_size=chunk_size,
chunk_overlap=chunk_overlap,
length_function=len
)
def split(self, text: str) -> list:
return self.splitter.split_text(text)
# 사용 예시
text = """
딥러닝은 인공 신경망을 기반으로 한 기계 학습의 한 분야입니다.
다층 신경망을 통해 데이터에서 특성을 자동으로 학습합니다.
이미지 인식, 자연어 처리, 음성 인식 등 다양한 분야에서 뛰어난 성능을 보입니다.
특히 트랜스포머 아키텍처의 등장으로 자연어 처리 분야가 크게 발전했습니다.
BERT, GPT 같은 사전학습 모델이 다양한 NLP 태스크에서 SOTA를 달성했습니다.
"""
splitter = KoreanTextSplitter(chunk_size=200, chunk_overlap=20)
chunks = splitter.split(text)
for i, chunk in enumerate(chunks):
print(f"청크 {i+1}: {chunk[:100]}...")
한국어 RAG 파이프라인
from langchain_community.vectorstores import Chroma
from langchain_community.embeddings import HuggingFaceEmbeddings
from langchain.chains import RetrievalQA
from langchain_openai import ChatOpenAI
# 한국어 임베딩 모델
embeddings = HuggingFaceEmbeddings(
model_name="jhgan/ko-sroberta-multitask",
model_kwargs={'device': 'cpu'},
encode_kwargs={'normalize_embeddings': True}
)
# 문서 준비
documents_text = [
"세종대왕은 조선의 4대 왕으로, 한글을 창제했습니다.",
"이순신 장군은 임진왜란에서 조선을 구한 영웅입니다.",
"광개토대왕은 고구려를 크게 확장한 왕입니다.",
]
from langchain.schema import Document
docs = [Document(page_content=text) for text in documents_text]
# 벡터 스토어 생성
vectorstore = Chroma.from_documents(
docs,
embeddings,
collection_name="korean_history"
)
# RAG 체인 구성
llm = ChatOpenAI(model="gpt-4o-mini", temperature=0)
retriever = vectorstore.as_retriever(
search_type="mmr", # Maximal Marginal Relevance
search_kwargs={
"k": 3, # 검색 문서 수
"fetch_k": 10, # MMR 후보 수
"lambda_mult": 0.7 # 다양성 vs 관련성 트레이드오프
}
)
qa_chain = RetrievalQA.from_chain_type(
llm=llm,
chain_type="stuff",
retriever=retriever,
return_source_documents=True
)
# 쿼리
result = qa_chain.invoke("세종대왕의 업적은 무엇인가요?")
print("답변:", result['result'])
print("참고 문서:", [doc.page_content for doc in result['source_documents']])
9. 실전 프로젝트
프로젝트 1: 네이버 영화 리뷰 감성 분석
import torch
from transformers import AutoModelForSequenceClassification, AutoTokenizer
from torch.utils.data import Dataset, DataLoader
import pandas as pd
from sklearn.model_selection import train_test_split
from sklearn.metrics import accuracy_score, classification_report
# 네이버 영화 리뷰 데이터셋 로드
# https://github.com/e9t/nsmc
import urllib.request
urllib.request.urlretrieve(
"https://raw.githubusercontent.com/e9t/nsmc/master/ratings_train.txt",
"ratings_train.txt"
)
urllib.request.urlretrieve(
"https://raw.githubusercontent.com/e9t/nsmc/master/ratings_test.txt",
"ratings_test.txt"
)
train_df = pd.read_csv("ratings_train.txt", sep="\t").dropna()
test_df = pd.read_csv("ratings_test.txt", sep="\t").dropna()
print(f"학습 데이터: {len(train_df)}개")
print(f"테스트 데이터: {len(test_df)}개")
print(train_df.head())
# 데이터셋 클래스
class NSMCDataset(Dataset):
def __init__(self, df, tokenizer, max_length=128):
self.texts = df['document'].tolist()
self.labels = df['label'].tolist()
self.tokenizer = tokenizer
self.max_length = max_length
def __len__(self):
return len(self.texts)
def __getitem__(self, idx):
encoding = self.tokenizer(
self.texts[idx],
truncation=True,
max_length=self.max_length,
padding='max_length',
return_tensors='pt'
)
return {
'input_ids': encoding['input_ids'].flatten(),
'attention_mask': encoding['attention_mask'].flatten(),
'labels': torch.tensor(self.labels[idx], dtype=torch.long)
}
# 모델 설정
model_name = 'klue/roberta-base'
tokenizer = AutoTokenizer.from_pretrained(model_name)
model = AutoModelForSequenceClassification.from_pretrained(model_name, num_labels=2)
# 데이터셋과 데이터로더
train_dataset = NSMCDataset(train_df.sample(10000), tokenizer)
test_dataset = NSMCDataset(test_df.sample(2000), tokenizer)
train_loader = DataLoader(train_dataset, batch_size=32, shuffle=True)
test_loader = DataLoader(test_dataset, batch_size=32)
# 학습
from transformers import AdamW, get_linear_schedule_with_warmup
device = torch.device('cuda' if torch.cuda.is_available() else 'cpu')
model.to(device)
optimizer = AdamW(model.parameters(), lr=2e-5, weight_decay=0.01)
scheduler = get_linear_schedule_with_warmup(
optimizer,
num_warmup_steps=100,
num_training_steps=len(train_loader) * 3
)
# 학습 루프
def train_epoch(model, loader, optimizer, scheduler):
model.train()
total_loss = 0
for batch in loader:
input_ids = batch['input_ids'].to(device)
attention_mask = batch['attention_mask'].to(device)
labels = batch['labels'].to(device)
optimizer.zero_grad()
outputs = model(input_ids=input_ids, attention_mask=attention_mask, labels=labels)
loss = outputs.loss
loss.backward()
torch.nn.utils.clip_grad_norm_(model.parameters(), 1.0)
optimizer.step()
scheduler.step()
total_loss += loss.item()
return total_loss / len(loader)
for epoch in range(3):
avg_loss = train_epoch(model, train_loader, optimizer, scheduler)
print(f"에폭 {epoch+1}/3, 손실: {avg_loss:.4f}")
# 평가
def evaluate(model, loader):
model.eval()
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)
labels = batch['labels']
outputs = model(input_ids=input_ids, attention_mask=attention_mask)
preds = torch.argmax(outputs.logits, dim=1).cpu().numpy()
all_preds.extend(preds)
all_labels.extend(labels.numpy())
return accuracy_score(all_labels, all_preds), classification_report(all_labels, all_preds)
accuracy, report = evaluate(model, test_loader)
print(f"\n테스트 정확도: {accuracy:.4f}")
print(report)
프로젝트 2: 한국어 개체명 인식 (NER)
from transformers import AutoModelForTokenClassification, AutoTokenizer, pipeline
# KLUE NER 파인튜닝된 모델 사용
model_name = "snunlp/KR-FinBert-SC" # 예시 - 실제 NER 모델 선택 필요
# 직접 NER 태거 구현
class KoreanNERTagger:
def __init__(self):
# 간단한 규칙 기반 + 모델 기반 혼합
self.model_name = "Leo97/KoELECTRA-small-v3-modu-ner"
self.tokenizer = AutoTokenizer.from_pretrained(self.model_name)
self.model = AutoModelForTokenClassification.from_pretrained(self.model_name)
self.ner_pipeline = pipeline(
"ner",
model=self.model,
tokenizer=self.tokenizer,
aggregation_strategy="simple"
)
def tag(self, text: str) -> list:
return self.ner_pipeline(text)
def extract_entities(self, text: str) -> dict:
results = self.tag(text)
entities = {}
for entity in results:
entity_type = entity['entity_group']
if entity_type not in entities:
entities[entity_type] = []
entities[entity_type].append({
'word': entity['word'],
'score': entity['score'],
'start': entity['start'],
'end': entity['end']
})
return entities
# 사용
tagger = KoreanNERTagger()
text = "이순신 장군은 1597년 명량해전에서 일본군을 물리쳤다."
entities = tagger.extract_entities(text)
print("인식된 개체:")
for entity_type, entity_list in entities.items():
for e in entity_list:
print(f" [{entity_type}] {e['word']} (신뢰도: {e['score']:.4f})")
프로젝트 3: 한국어 QA 챗봇
from openai import OpenAI
from sentence_transformers import SentenceTransformer
import numpy as np
from sklearn.metrics.pairwise import cosine_similarity
class KoreanQAChatbot:
def __init__(self):
self.client = OpenAI()
self.embedding_model = SentenceTransformer('jhgan/ko-sroberta-multitask')
self.knowledge_base = []
self.kb_embeddings = None
self.conversation_history = []
def add_knowledge(self, documents: list):
self.knowledge_base = documents
self.kb_embeddings = self.embedding_model.encode(documents)
print(f"{len(documents)}개 지식 문서 추가 완료")
def retrieve_context(self, query: str, top_k: int = 2) -> str:
query_emb = self.embedding_model.encode([query])
similarities = cosine_similarity(query_emb, self.kb_embeddings)[0]
top_indices = np.argsort(similarities)[::-1][:top_k]
context_parts = []
for idx in top_indices:
if similarities[idx] > 0.3: # 유사도 임계값
context_parts.append(self.knowledge_base[idx])
return "\n".join(context_parts)
def chat(self, user_message: str) -> str:
# 관련 컨텍스트 검색
context = self.retrieve_context(user_message)
# 대화 기록에 추가
self.conversation_history.append({
"role": "user",
"content": user_message
})
# 시스템 프롬프트
system_prompt = """당신은 친절하고 정확한 한국어 AI 어시스턴트입니다.
제공된 컨텍스트를 바탕으로 질문에 답변하세요.
컨텍스트에 없는 내용은 모른다고 솔직히 말하세요.
관련 컨텍스트:
{context}""".format(context=context if context else "관련 정보 없음")
messages = [{"role": "system", "content": system_prompt}]
messages.extend(self.conversation_history[-6:]) # 최근 6개 대화만 사용
response = self.client.chat.completions.create(
model="gpt-4o-mini",
messages=messages,
temperature=0.7,
max_tokens=1000
)
assistant_message = response.choices[0].message.content
self.conversation_history.append({
"role": "assistant",
"content": assistant_message
})
return assistant_message
def reset_conversation(self):
self.conversation_history = []
print("대화 기록 초기화됨")
# 사용 예시
chatbot = KoreanQAChatbot()
# 지식 베이스 추가
chatbot.add_knowledge([
"대한민국은 동아시아에 위치한 나라로 수도는 서울입니다.",
"한국의 공식 언어는 한국어이며 한글을 사용합니다.",
"한국은 반도 국가로 북쪽으로는 북한과 접하고 있습니다.",
"한국의 주요 산업은 반도체, 자동차, 조선, 전자 등입니다.",
])
# 챗봇 대화
questions = [
"한국의 수도는 어디인가요?",
"한국의 주요 산업을 알려주세요.",
"한국어와 한글의 차이점은 무엇인가요?",
]
for question in questions:
print(f"사용자: {question}")
response = chatbot.chat(question)
print(f"챗봇: {response}")
print()
마무리
한국어 NLP 생태계는 빠르게 발전하고 있습니다. KoBERT에서 시작해 KLUE 벤치마크로 평가 체계가 정립되고, HyperCLOVA X, EXAONE 같은 국산 LLM이 등장하면서 한국어 AI 개발 환경이 크게 개선되었습니다.
핵심 정리:
- 형태소 분석: Mecab이 속도와 정확도 균형이 가장 좋습니다
- 문장 임베딩:
jhgan/ko-sroberta-multitask가 한국어 시맨틱 검색에 최적입니다 - 파인튜닝: KLUE-RoBERTa + QLoRA 조합이 효율적입니다
- 국산 LLM: EXAONE은 HuggingFace에서 무료로 사용 가능합니다
한국어 특화 도구를 활용하면 영어 전용 모델 대비 성능이 크게 향상됩니다. 특히 형태소 분석을 전처리에 활용하고, 한국어 임베딩 모델로 RAG 시스템을 구축하면 실용적인 한국어 AI 애플리케이션을 만들 수 있습니다.