비전·음성·추천

DOMAIN / 16번째 글

한국어 NLP: 교착어 처리와 한국어 특화 모델

교착어가 만드는 문제부터 형태소 분석기 넷 고르기, 사용자 사전과 띄어쓰기, KLUE 여덟 태스크, 한국어 모델 생태계, 토크나이저 분절률 실측, 데이터셋 고르기까지 한국어 NLP를 실무 기준으로 정리한다.

PALDYN Team28 MIN READ

지난 글에서 대명사가 가리키는 대상을 찾는 지시 해소를 살펴봤다. 거기서도 한국어의 특수성이 계속 걸렸는데 — 주어가 통째로 생략되고, 경어 등급이 후보를 가르고 — 이번에는 그 특수성 자체를 정면으로 다룬다.

한국어 NLP가 어려운 이유는 자료가 적어서가 아니다. 영어를 전제로 만들어진 도구와 방법론이 한국어에서 조용히 어긋나기 때문이다. 공백으로 자르면 같은 명사가 조사 수만큼 갈라지고, 어순을 특징으로 쓰면 자유 어순에 걸리고, 토큰 단가로 과금되는 API에서는 같은 내용에 두 배의 돈이 나간다. 이 글은 그 어긋나는 자리들을 하나씩 짚고, 각각에서 무엇을 고를 수 있는지를 적는다.

교착어와 조사

활용형의 수

한국어는 어근에 접사를 붙여 의미를 확장하는 교착어다. 같은 동사가 시제·높임·연결 방식에 따라 다른 형태로 나타난다.

형태 구성
먹었다 먹(어근) + 었(과거) + 다(종결)
먹겠습니다 먹 + 겠(의도) + 습니다(격식)
드셨나요 드시(높임) + 었 + 나요(의문)

영어의 eat은 기본형을 포함해 여섯 형태쯤이고 전부 사전에 실린다. 한국어 동사는 어미를 조합해 수백 가지 형태가 만들어지고, 그중 상당수는 사전에 오르지 않는다. 이것이 어휘 크기에 그대로 몫을 한다 — 같은 크기의 말뭉치를 놓고 고유 토큰 수를 세면 한국어 쪽이 훨씬 많고, 그 많은 토큰이 사실은 몇 개의 어근에서 나온 것이라 신호가 잘게 흩어진 상태다.

조사가 지는 역할

영어는 어순이 문법 역할을 정한다. 「John hit Bill」과 「Bill hit John」은 뜻이 반대다. 한국어는 그 역할을 조사가 진다. 「철수가 영희를 사랑한다」와 「영희를 철수가 사랑한다」는 어순이 다른데 뜻이 같다.

이 성질이 두 방향으로 작용한다. 좋은 쪽은 문법 역할이 표면에 드러나 있다는 것이다 — 「가/이」를 보면 주어이고 「을/를」을 보면 목적어라, 구문 분석 없이도 역할을 알 수 있는 자리가 많다. 나쁜 쪽은 어순을 특징으로 쓰는 방법이 다 흔들린다는 것이다. 단어의 위치에 기대는 규칙이나 n-gram은 같은 뜻의 문장 여러 개를 서로 다른 것으로 본다.

경어법

경어는 어미만 바꾸는 것이 아니다. 어휘 자체가 바뀌는 자리가 있다 — 「먹다」는 「드시다」·「잡수시다」가 되고, 「있다」는 「계시다」가 되고, 「말」은 「말씀」이 된다.

이것이 두 가지 일을 어렵게 만든다. 첫째, 어휘 단위로 보면 같은 개념이 여러 표제어로 흩어진다. 감성 사전이나 규칙 기반 시스템에서 높임 어휘를 빠뜨리면 상담 로그처럼 높임이 기본인 데이터에서 통째로 안 걸린다. 둘째, 생성 쪽에서는 등급을 골라야 하는데 그 근거가 문장 안에 없다. 누가 누구에게 말하는지를 알아야 정할 수 있고, 한 문서 안에서 섞이면 바로 어색해진다.

형태소 분석기

한국어 NLP 처리 파이프라인

넷을 가르는 기준

영어는 공백으로 잘라도 기본 처리가 되지만 한국어는 형태소 분석이 먼저다. 어절 「학교에서는」을 「학교 + 에서 + 는」으로 나눠야 「학교」가 다른 문장의 「학교를」과 같은 것이 된다.

분석기 속도 신조어 설치
Okt 중간 정규화 옵션이 있어 구어체에 무난 자바 필요
Komoran 중간 사용자 사전을 넣기 쉬움 자바 필요
Mecab 가장 빠름 사전에 없는 말에 약함 C 라이브러리와 사전을 따로 설치
Kiwi 빠름 가장 강건하고 갱신이 잦음 파이썬 패키지 하나

속도만 보면 Mecab이고, 배포 편의와 신조어 대응까지 함께 보면 Kiwi다. Okt는 norm·stem 옵션으로 「좋아욬ㅋㅋ」 같은 표현을 정규화해 주는 점이 소셜 데이터에서 값을 한다.

자바 의존성이 늘리는 것

KoNLPy가 감싸는 분석기 중 Okt와 Komoran은 자바로 구현돼 있어 JVM이 함께 필요하다. 개발 노트북에서는 잘 안 보이지만 배포에서 드러난다.

컨테이너 이미지에 JDK가 들어가면 이미지가 수백 MB 늘고, 빌드 시간과 배포 시간이 함께 늘어난다. 서버리스 함수처럼 패키지 크기 제한이 있는 실행 환경에서는 아예 안 올라가는 경우도 있다. Mecab은 자바가 아니라 C 라이브러리를 부르는 쪽이지만 대신 본체와 한국어 사전을 시스템에 따로 설치해야 하고, 운영체제와 아키텍처가 바뀌면 그 단계가 자주 깨진다. 윈도우에서는 공식적으로 지원되지 않아 팀원마다 개발 환경이 갈리는 문제도 붙는다.

Kiwi는 C++ 구현을 파이썬 확장으로 감싸 배포해 설치가 패키지 하나로 끝난다. 정확도나 속도의 차이보다 이 차이가 실무에서 도구 선택을 가르는 경우가 많고, 특히 여러 환경에 배포해야 하는 팀에서 그렇다. 분석기를 바꾸면 사용자 사전 형식과 품사 태그 체계가 함께 바뀌므로, 나중에 옮기는 비용을 생각하면 처음에 정해 두는 편이 낫다.

결과가 갈리는 자리

넷에 같은 문장을 넣으면 결과가 다르다. 갈리는 자리는 대개 셋이다 — 사전에 없는 고유명사, 합성어, 그리고 띄어쓰기가 어긋난 입력이다.

「삼성전자가」를 하나는 「삼성전자 + 가」로, 다른 하나는 「삼성 + 전자 + 가」로 나눈다. 어느 쪽이 맞는지는 우리 문제가 정한다. 검색 색인을 만든다면 「삼성」으로도 걸려야 하므로 쪼개는 쪽이 나을 수 있고, 개체명 인식의 입력이라면 하나로 붙어 있어야 한다. 그래서 분석기를 고르는 기준은 「어느 것이 더 정확한가」가 아니라 우리 데이터의 갈리는 표현 50개를 넣어 보고 우리 쓰임에 맞는 답을 더 많이 내는 것이다. 문서 하나로 결정하지 말고 직접 넣어 본다.

사용자 사전과 띄어쓰기

사전에 넣는 절차

도메인 고유명사는 거의 반드시 쪼개진다. 제품명·서비스명·사내 용어는 분석기의 학습 말뭉치에 없기 때문이다.

사용자 사전에 등록할 때 표제어만 적고 끝내면 안 된다. 품사 태그를 함께 적어야 그 뒤의 조사 분리가 제대로 붙는다. 고유명사라면 고유명사 태그로 넣어야 「팔딘랩을」이 「팔딘랩 + 을」로 갈리고, 태그 없이 넣으면 분석기가 품사를 추측하다 뒤쪽 조사까지 표제어에 붙여 버리는 일이 생긴다. 등록 뒤에는 반드시 그 단어가 들어간 실제 문장 몇 개를 다시 돌려 확인한다.

띄어쓰기가 어긋난 입력

「서울역을」과 「서울 역을」은 같은 뜻인데 분석 결과가 달라진다. 사용자 입력·음성 인식 결과·스캔 문서에서는 이런 어긋남이 상수라고 보는 편이 맞다.

파이프라인에서 흡수하는 방법이 둘이다. 하나는 입력 단계에 띄어쓰기 교정을 두는 것이고, 다른 하나는 뒤 단계를 띄어쓰기에 덜 민감하게 만드는 것이다. 후자가 대체로 값싸다 — 검색이라면 색인을 띄어쓰기 제거한 형태로도 함께 만들어 두고, 분류라면 애초에 서브워드 기반 모델을 쓴다. 교정기를 앞에 두는 방식은 교정기가 틀렸을 때 원본 정보가 사라진다는 위험이 있어, 넣더라도 원문을 함께 보관한다.

사전을 키우다 나빠지는 자리

사용자 사전은 크면 클수록 좋을 것 같지만 그렇지 않다. 등록한 표제어는 분석기가 우선해서 붙이므로, 짧고 흔한 말을 넣으면 그것이 다른 낱말 안에서도 잡힌다. 두 글자짜리 사내 약어를 넣었더니 일반 명사 여럿이 그 약어 + 나머지로 쪼개지는 식이다.

기준은 셋이다. 세 글자 이상일 것, 우리 데이터에서 실제로 충분히 자주 나올 것, 그리고 등록 전후로 표본 문장 수백 개의 분석 결과를 비교해 의도한 자리 말고는 안 바뀔 것. 마지막 확인을 안 하면 고친 하나보다 망가뜨린 열이 많아지고, 그 망가짐은 한참 뒤에 다른 증상으로 나타난다.

KLUE 여덟 태스크

각각 재는 능력

KLUE는 한국어 이해 능력을 여덟 갈래로 나눠 재는 벤치마크다. 모델을 고를 때 이 표를 보는 이유는 순위가 아니라, 우리 문제와 가장 닮은 태스크가 무엇인지를 찾기 위해서다.

태스크 재는 것
TC 뉴스 제목을 일곱 주제 중 하나로 분류
STS 두 문장이 얼마나 비슷한지를 점수로
NLI 두 문장이 함의·모순·중립 중 무엇인지
NER 인물·장소·기관 등 개체 태깅
RE 두 개체 사이의 관계 분류
DP 토큰 사이의 의존 구조 분석
MRC 지문에서 정답 스팬 추출
WOS 대화에서 슬롯과 값 추적

낮은 점수가 말하는 것

여덟 중 RE와 WOS의 점수가 유독 낮다. 모델이 덜 발달해서가 아니라 태스크의 성격이 다르기 때문이다.

RE는 후보가 수십 개인 다중 클래스이면서 관계마다 예시 수가 크게 불균형하다. 흔한 관계 몇 개가 데이터를 차지하고 나머지는 수십 건뿐이라, 분류 문제에서 늘 문제가 되는 그 불균형이 여기서 극단적으로 나타난다. WOS는 아예 성격이 다르다 — 한 발화를 판정하는 것이 아니라 대화가 진행되는 동안 슬롯 값의 집합을 계속 갱신해야 하고, 채점은 그 집합이 전부 맞았을 때만 점수를 준다. 슬롯 하나만 틀려도 그 턴이 0점이므로 값이 구조적으로 낮게 나온다.

이 두 가지가 주는 교훈이 실무에 그대로 온다. 지표의 절대값으로 태스크 난이도를 비교하면 안 되고, 채점 방식이 무엇을 요구하는지를 먼저 봐야 한다.

기준선으로 쓰기

우리 문제가 「문의 내용을 유형으로 나눈다」라면 TC가, 「두 문서가 같은 내용인지 본다」라면 STS가 가장 가깝다. 그 태스크의 공개 점수가 곧 목표선의 어림이 된다.

이 어림이 실제로 하는 일은 기대치를 맞추는 것이다. 우리 분류기의 F1이 0.85에서 안 오른다고 할 때, 가장 닮은 공개 태스크의 최고 점수가 0.93이라면 아직 올릴 여지가 있는 것이고 0.73이라면 그 문제가 원래 그만큼 어려운 것이다. 이 구분 없이 지표를 올리려 하면 도달할 수 없는 값을 쫓게 된다. 다만 공개 점수는 그 데이터셋 위에서 나온 값이라 우리 데이터에 그대로 옮겨지지 않는다는 점도 함께 기억한다 — 목표선이 아니라 눈금을 빌려 오는 것이다.

한국어 모델 생태계

Kiwi + KLUE-RoBERTa 실전 코드

인코더 계열의 변천

초기 한국어 BERT는 SKT의 KoBERT였다. 위키와 뉴스 중심으로 학습해 문어체에는 통했지만 구어체와 신조어에 약했다.

KLUE 팀이 공개한 KLUE-BERT와 KLUE-RoBERTa가 그 자리를 넘겨받았다. 학습 데이터에 웹 문서와 소셜 미디어가 함께 들어가 도메인 폭이 넓어졌고, 벤치마크를 만든 팀이 모델도 함께 냈으므로 평가 체계와 모델이 같은 기준 위에 있다. KoELECTRA는 구조 쪽에서 다르다 — 마스크를 복원하는 대신 「이 토큰이 바뀐 것인가」를 모든 자리에서 판별하게 학습해, 같은 계산량에서 더 많은 신호를 얻는다. 파라미터가 작은데 성능이 잘 나오는 이유가 여기 있고, 추론 비용이 중요한 자리에서 먼저 고려할 만하다.

from transformers import AutoTokenizer, AutoModel
import torch

model_name = "klue/roberta-base"
tokenizer = AutoTokenizer.from_pretrained(model_name)
model = AutoModel.from_pretrained(model_name)

def get_embedding(text: str) -> torch.Tensor:
    inputs = tokenizer(
        text, return_tensors="pt",
        max_length=512, truncation=True,
    )
    with torch.no_grad():
        out = model(**inputs)
    return out.last_hidden_state[:, 0, :]  # [CLS]

print(get_embedding("한국어 자연어 처리").shape)

생성 모델 고르기

한국어 LLM은 API로 받는 것과 가중치를 받아 직접 돌리는 것으로 먼저 갈린다. 이 구분이 모델 이름보다 먼저 온다.

NAVER의 HyperCLOVA X는 API로 제공되고, LG AI연구원의 EXAONE과 Upstage의 SOLAR는 가중치가 공개돼 직접 돌릴 수 있다. 고르는 기준은 성능 순위가 아니라 데이터가 밖으로 나가도 되는가, 그리고 지연과 비용을 우리가 통제해야 하는가다. 사내 문서를 다루는데 외부 API로 보낼 수 없다면 공개 가중치 쪽이 유일한 선택지이고, 반대로 트래픽이 들쭉날쭉한 서비스라면 GPU를 상주시키는 비용이 API보다 비싸진다.

도메인 사전학습까지 갈 때

금융·의료·법률처럼 어휘가 크게 다른 도메인에서는 범용 모델의 성능이 눈에 띄게 떨어진다. 그 도메인 문서로 사전학습을 이어 하는 방법이 있지만, 비용이 파인튜닝과 자릿수가 다르다.

순서는 정해져 있다. 먼저 범용 모델을 그 도메인 데이터로 파인튜닝해 보고, 그것으로 목표에 닿으면 거기서 멈춘다. 안 닿으면 오답을 읽어 원인이 어휘인지 태스크인지 가른다. 전문 용어가 서브워드로 잘게 쪼개져 표현이 안 잡히는 것이면 사전학습을 이어 할 값이 있고, 용어는 잘 잡히는데 판단이 틀리는 것이면 데이터를 더 모으는 쪽이 맞다. 이 구분 없이 사전학습부터 시작하면 몇 주와 GPU 비용을 쓰고 나서야 원인이 다른 데 있었음을 알게 된다.

토크나이저 분절률

같은 글이 3배로 갈린다

토크나이저마다 한국어를 얼마나 잘게 자르는지가 다르다. 그 차이를 실제로 잰 결과가 한국어 토큰세에 있다 — 같은 한국어 글을 아홉 개의 토크나이저에 넣었더니 자/토큰이 0.89에서 2.67까지, 3.00배로 벌어졌다.

같은 한국어 글, 토크나이저마다 3배로 갈린다

이 수치가 한국어에서 두 가지로 환산된다. 하나는 문맥 창이다. 같은 100만 토큰짜리 창이라도 자/토큰이 0.89인 토크나이저에서는 89만 자, 2.67이면 267만 자를 담는다. 다른 하나는 청구서다 — 토큰 단가로 과금되므로 분절률이 나쁜 모델은 같은 문서에 세 배의 돈이 든다.

그리고 어휘 크기는 이 차이를 설명하지 못한다. 같은 측정에서 어휘 크기와 한국어 효율의 상관은 0.078로 사실상 0이었다. 2.0자를 넘긴 것은 한국어를 따로 학습한 둘뿐이었다. 사전이 크면 한국어에도 좋을 것이라는 짐작이 여기서 깨진다. 분절률이 문맥 창·비용·표현 흐림 셋을 함께 끌고 가는 구조 쪽은 다국어 전이가 맡는다.

두 단위를 한 표에 놓지 않는다

여기서 혼동하기 쉬운 자리가 있다. 형태소 분석기가 세는 토큰과 서브워드 토크나이저가 세는 토큰은 이름만 같을 뿐 다른 단위다.

형태소는 언어학적 최소 의미 단위라 「학교 + 에서 + 는」 셋이고, 서브워드는 빈도 기반 조각이라 「학교에」와 「서는」 둘일 수도 있다. 앞의 것은 사람이 읽고 검증할 수 있는 단위이고, 뒤의 것은 모델이 계산하는 단위이며 과금되는 것도 뒤쪽이다. 두 수를 한 표에 나란히 놓으면 「A 분석기가 B 토크나이저보다 효율적」 같은 성립하지 않는 비교가 나온다.

선택지 셋

분절률이 문제라고 판단되면 손댈 자리가 셋이다.

가장 확실한 것은 모델 교체다. 한국어를 따로 학습한 모델로 옮기면 분절률이 한 번에 좋아지고 문맥 창과 비용이 함께 개선된다. 다만 그 모델의 다른 능력이 우리 태스크에 충분한지를 따로 재야 한다. 둘째는 어휘 확장이다. 기존 모델의 토크나이저에 한국어 토큰을 추가하고 임베딩을 늘린 뒤 추가 학습하는 방법인데, 손이 많이 가고 추가 학습을 제대로 안 하면 새 토큰의 임베딩이 무의미한 값으로 남는다. 셋째는 입력 압축이다. 모델을 안 바꾸고 프롬프트 쪽에서 반복 설명을 줄이거나 문서를 먼저 요약해 넣는 방식이다. 가장 싸지만 효과의 상한이 낮다.

데이터셋 고르기

다섯 곳이 덮는 범위

데이터셋 내용
KLUE 여덟 태스크의 학습·평가 데이터
KorQuAD 1.0 / 2.0 기계독해. 2.0은 표와 목록이 든 긴 문서까지
NSMC 네이버 영화 리뷰 20만 건, 긍정·부정 라벨
AI Hub 정부 지원으로 구축된 도메인별 공개 데이터
모두의 말뭉치 국립국어원이 배포하는 대규모 말뭉치

NSMC는 이름 때문에 자주 오해를 사는데 쇼핑 리뷰가 아니라 영화 리뷰다. 감성 분류 기준선을 세울 때 가장 많이 쓰이고, 그래서 이것으로 학습한 모델을 쇼핑이나 상담 데이터에 그대로 쓰다 성능이 떨어지는 일이 흔하다.

라이선스를 먼저 본다

공개 데이터라고 해서 아무렇게나 쓸 수 있는 것은 아니다. 연구 목적으로만 허용되는 것, 재배포를 금지하는 것, 별도 신청과 승인이 필요한 것이 섞여 있다.

특히 그 데이터로 학습한 모델 가중치를 배포할 수 있는가는 따로 확인해야 한다. 데이터 사용은 허용하면서 파생물의 상업적 배포를 제한하는 조건이 있고, 이것을 모르고 학습을 마친 뒤에 알게 되면 되돌릴 방법이 없다. 라이선스 확인은 데이터를 내려받기 전에 하는 일이지 논문을 쓸 때 하는 일이 아니다.

우리 데이터 수천 건

마지막이 가장 중요하다. 공개 데이터 수십만 건보다 우리 도메인 데이터 수천 건이 이기는 지점이 생각보다 이르게 온다.

이유는 분포다. 공개 데이터는 뉴스와 위키, 영화 리뷰 같은 특정 분포에서 나왔고 우리 서비스에 들어오는 문장은 그 분포에 없다. 어휘도 다르고 문장 길이도 다르고 오탈자 비율도 다르다. 모델이 배워야 하는 것은 「한국어 일반」이 아니라 우리 입력이 어떻게 생겼는가이고, 그것은 우리 데이터에만 들어 있다. 실무 순서는 공개 데이터로 사전학습된 모델을 가져와 우리 데이터 수천 건으로 파인튜닝하는 것이다 — 공개 데이터는 모델 안에 이미 들어와 있으므로 우리가 다시 학습시킬 일이 아니다.

여기까지가 한국어를 토큰 시퀀스로 만들어 Transformer에 넣는 과정 전체다. 다음 글에서는 같은 발상이 언어를 떠나는 자리를 본다 — 이미지를 조각으로 잘라 시퀀스로 세운 뒤 나머지를 어텐션에 맡기는 Vision Transformer다.


읽어주셔서 감사합니다. 😊

LATEST

비전·음성·추천의 최신 글

비전·음성·추천2026.09.07

오프라인 강화학습 — 쌓인 로그만으로 정책을 배우기

실제 서비스에서 탐색은 곧 사용자에게 나쁜 행동을 해 보는 일입니다. 이미 쌓인 로그만으로 정책을 배우려 할 때 왜 Q값이 혼자 부풀어 오르는지, 그 부풀음을 누르는 세 갈래 대응, 행동 복제라는 기준선의 무게, 그리고 배포 전에 성능을 재는 일이 왜 가장 어려운지를 정리합니다.

18 MIN
비전·음성·추천2026.09.07

다국어 전이 — 라벨 없는 언어에서 모델이 동작하는 이유

영어 라벨만으로 학습한 분류기가 한국어 문장을 그대로 처리하는 일이 실제로 일어납니다. 여러 언어가 한 표현 공간에 겹쳐 놓이는 원리, 그 겹침이 무너지는 조건, 번역해서 학습할지 번역해서 추론할지 고르는 기준, 그리고 언어별로 나눠 재야 하는 이유를 정리합니다.

16 MIN
비전·음성·추천2026.09.07

정보 추출 — 글 한 덩이를 표 한 줄로 바꾸는 일

계약서와 이메일을 데이터베이스에 넣으려면 글에서 값을 뽑아 칸에 채워야 합니다. 개체명·관계·사건의 세 층위, 값을 정규화하는 일이 왜 절반인지, 근거 위치를 함께 남겨야 하는 이유, 규칙·전용 모델·언어 모델의 갈림길, 그리고 필드별로 재는 평가법을 정리합니다.

17 MIN