개체명 인식
더 많은 작업
- Named entity recognition, NER; 개체명 인식
- 텍스트에서 사람 이름, 지명, 기관명, 날짜 같은 고유한 표현의 범위를 찾아 종류를 붙이는 자연어 처리 과제
개체명 인식(named entity recognition, NER)은 문장에서 "이순신", "아산", "2026년 10월 16일", "삼성전자" 같은 개체명(named entity)이 어디서 시작해 어디서 끝나는지 찾고, 사람·장소·기관·날짜 등 종류를 붙이는 과제다. 정보 추출, 질의응답, 검색어 분석, 개인정보 비식별화, 지식 그래프 구축의 첫 단계로 쓰인다. 찾은 개체를 지식 베이스의 항목과 연결하는 개체 연결(entity linking), 개체 사이의 관계를 뽑는 관계 추출이 뒤따르는 경우가 많다.
자연어 처리의 과제 분류에서 개체명 인식은 형태소 분석, 품사 태깅과 같은 시퀀스 레이블링(sequence labeling)에 속한다. 문장의 토큰마다 꼬리표를 하나씩 붙이는 문제로 바꿔 푼다.
어떤 것을 개체로 볼지는 데이터셋과 지침마다 다르다.
| 체계 | 유형 | 비고 |
|---|---|---|
| MUC-7 (1998) | 사람(PERSON), 기관(ORGANIZATION), 장소(LOCATION), 날짜(DATE), 시간(TIME), 금액(MONEY), 비율(PERCENT) | 미국 정부 주관 메시지 이해 대회 |
| CoNLL-2003 | PER, LOC, ORG, MISC | 영어·독일어 뉴스. 신경망 NER 연구의 표준 평가 데이터[1] |
| TTA 표준 (TTAK.KO-10.0852) | 15개 대분류, 146개 세분류 | 한국어 개체명 태깅 표준. 국내 주요 데이터셋이 이 체계를 따른다[2] |
| KLUE-NER | PS(사람), LC(장소), OG(기관), DT(날짜), TI(시간), QT(수량) | TTA 체계에서 MUC-7에 대응하는 6개만 골랐다. 금액·비율은 QT에 포함[2] |
의학(유전자, 질병, 약물), 법률(법령명, 조항), 보안(악성코드명, CVE 번호)처럼 분야별 개체 유형을 따로 정의한 데이터셋도 많다.
범위를 토큰 단위 꼬리표로 바꾸는 방식이다.
- IO: 개체 안이면 I-유형, 밖이면 O. 같은 유형의 개체 두 개가 붙어 있으면 경계를 구분하지 못한다.
- BIO(IOB2): 개체의 첫 토큰은 B-유형, 나머지는 I-유형, 개체 밖은 O. 가장 널리 쓰인다.
- BIOES(BILOU): 끝(E, L)과 한 토큰짜리 개체(S, U)를 따로 표시해 경계 정보를 더 준다.
KLUE-NER는 음절(글자) 단위 BIO를 쓴다. 예를 들어 "이순신은 아산에서"는 다음과 같다.
| 글자 | 이 | 순 | 신 | 은 | (공백) | 아 | 산 | 에 | 서 |
|---|---|---|---|---|---|---|---|---|---|
| 꼬리표 | B-PS | I-PS | I-PS | O | O | B-LC | I-LC | O | O |
한국어는 조사가 명사에 붙어 어절을 이루므로, 어절(공백) 단위로 꼬리표를 달면 "이순신은" 전체가 개체가 되어 버린다. KLUE 논문은 기존 한국어 데이터셋 대부분이 어절 단위라 한국어 형태론과 충돌한다고 지적하고 음절 단위를 택했다.[2] 형태소 분석 결과를 단위로 쓰는 방식도 있다.
표준 지표는 개체 단위 정확 일치 F1이다. 예측한 개체의 시작, 끝, 유형이 모두 정답과 같아야 맞은 것으로 센다. 정밀도는 예측 개체 중 맞은 비율, 재현율은 정답 개체 중 찾은 비율이며 F1은 두 값의 조화 평균이다(F1 Score 참고). 토큰 단위 정확도는 O 꼬리표가 대부분이라 높게 나오므로 쓰지 않는다. 파이썬에서는 seqeval 라이브러리가 BIO 열을 받아 개체 단위 정밀도·재현율·F1을 계산한다. KLUE-NER는 개체 단위 F1과 함께 글자 단위 F1도 보고해, 경계가 한두 글자 어긋난 경우를 부분적으로 인정한다.[2]
초기 시스템은 인명·지명 사전(gazetteer)과 "~씨", "~주식회사", "~시 ~구" 같은 패턴 규칙을 썼다. 정밀도는 높지만 새로운 이름과 다른 분야에 약하다. 지금도 주민등록번호, 전화번호, 이메일처럼 형식이 정해진 개체는 정규 표현식이 가장 정확하고 빠르다.
통계적 방법은 은닉 마르코프 모델, 최대 엔트로피 모델을 거쳐 선형 체인 조건부 랜덤 필드(CRF)로 정착했다. 입력 에 대한 꼬리표 열 의 확률을
로 둔다. 는 t번째 토큰이 꼬리표 k일 점수(방출 점수), 는 꼬리표 i 다음에 j가 올 점수(전이 점수)다. 분모 는 전방 알고리즘으로, 가장 점수 높은 꼬리표 열은 비터비 알고리즘으로 에 구한다(는 꼬리표 수). 전이 점수 덕분에 "O 다음에 I-PS가 오는" 것 같은 불가능한 열을 피한다. 고전적 CRF는 방출 점수를 단어 모양, 접두·접미사, 사전 포함 여부 같은 수작업 특징으로 만들었다.
대형 언어 모델에 "다음 글에서 인명과 기관명을 JSON으로 뽑아라" 같은 지시를 주면 학습 데이터 없이도 개체를 뽑을 수 있다. 새 개체 유형을 바로 정의할 수 있다는 장점이 있지만, 원문에 없는 개체를 만들어 내거나 범위를 원문과 다르게 고쳐 쓰는 문제가 있어 원문 위치(오프셋)를 검증하는 후처리가 필요하다. 출력 형식을 강제하는 방법은 구조화된 출력 (언어 모델) 참고. 대량 처리에는 여전히 미세 조정한 인코더 모델이 빠르고 싸다.
| 방식 | 장점 | 단점 | 언제 쓰나 |
|---|---|---|---|
| 규칙·사전 | 정확하고 설명 가능, 학습 데이터 불필요 | 재현율 낮음, 유지보수 부담 | 형식이 고정된 개체(번호, 이메일), 다른 방식의 보조 |
| CRF | 가볍고 빠름, 적은 데이터로 학습 | 특징 설계 필요 | 자원이 매우 제한된 환경 |
| BiLSTM-CRF | 특징 설계 불필요 | 사전 학습 모델보다 낮은 성능 | 지금은 주로 비교 기준선 |
| 인코더 미세 조정(BERT, RoBERTa) | 높은 정확도, 빠른 추론 | 레이블 데이터 수천 문장 이상 필요 | 유형이 정해진 대량 처리의 기본값 |
| LLM 프롬프트 | 학습 불필요, 유형 정의 자유 | 느리고 비쌈, 범위 오류와 환각 | 초기 탐색, 레이블 데이터 만들기, 소량 처리 |
한국어의 어려움은 다음과 같다.
- 조사·어미 결합: 개체 경계가 어절 안에 있다. 하위 단어 토큰화가 "신은"처럼 개체 끝과 조사를 한 토큰으로 묶으면 글자 단위 정답과 어긋나므로, 토큰 오프셋을 글자 단위로 되돌리는 처리가 필요하다.
- 대소문자 없음: 영어에서 강력한 단서인 대문자 시작이 없다.
- 띄어쓰기 오류: 사용자 글에서는 띄어쓰기가 틀리거나 없는 경우가 많다.
KLUE 논문에 따르면 2021년 당시 한국어 NER 데이터셋은 한국해양대(KMOU), 창원대, 국립국어원, ETRI가 만든 네 가지가 있었고 모두 TTA 체계를 따랐다. 이 가운데 KMOU와 창원대 데이터는 공개되어 있었으나 ETRI와 국립국어원 데이터는 이용이 제한되었다.[2] KLUE-NER는 누구나 쓸 수 있게 공개한 데이터로, 뉴스(위키트리)와 영화 리뷰(NSMC) 문장에 음절 단위 BIO로 6개 유형을 달았으며 학습 21,008문장, 검증 5,000문장, 테스트 5,000문장으로 이루어졌다.[2] 허깅 페이스 Hub의 klue/klue 데이터셋 ner 구성에는 학습·검증 분할이 올라와 있다.
토큰마다 가장 높은 꼬리표를 따로 고르면 "B-LC 다음 I-PS"처럼 불가능한 열이 나올 수 있다. 전이 점수에 BIO 제약을 넣고 비터비로 복호하면 이를 막는다. 아래는 넘파이로 짠 최소 구현이다(실행 확인).
import numpy as np
def bio_to_spans(tags):
"""['B-PS','I-PS','O','B-LC'] -> [('PS',0,2), ('LC',3,4)] (끝 위치 미포함)"""
spans, start, typ = [], None, None
for i, t in enumerate(tags + ["O"]):
if t.startswith("B-") or t == "O" or (t.startswith("I-") and t[2:] != typ):
if typ is not None:
spans.append((typ, start, i))
start, typ = (i, t[2:]) if t != "O" else (None, None)
return spans
def viterbi(emissions, transitions):
"""emissions: (T, K) 방출 점수, transitions: (K, K) 앞 꼬리표 -> 뒤 꼬리표 점수"""
T, K = emissions.shape
score, back = emissions[0].copy(), np.zeros((T, K), dtype=int)
for t in range(1, T):
total = score[:, None] + transitions + emissions[t][None, :]
back[t], score = total.argmax(0), total.max(0)
path = [int(score.argmax())]
for t in range(T - 1, 0, -1):
path.append(int(back[t, path[-1]]))
return path[::-1]
labels = ["O", "B-PS", "I-PS", "B-LC", "I-LC"]
trans = np.zeros((5, 5))
for i, a in enumerate(labels): # I-X 앞에는 B-X나 I-X만 올 수 있다
for j, b in enumerate(labels):
if b.startswith("I-") and a[2:] != b[2:]:
trans[i, j] = -1e4
chars = list("이순신은아산에서")
em = np.full((len(chars), 5), -1.0); em[:, 0] = 0.5
em[0, 1] = em[1, 2] = em[2, 2] = 2.0 # 이순신
em[4, 3], em[5, 4], em[5, 2] = 2.0, 1.0, 1.5 # '산'은 I-PS 점수가 더 높다
print([labels[i] for i in em.argmax(1)]) # 탐욕 복호: ... 'B-LC', 'I-PS' ... (불가능한 열)
best = [labels[i] for i in viterbi(em, trans)]
print(best, bio_to_spans(best)) # ... 'B-LC', 'I-LC' ... [('PS',0,3), ('LC',4,6)]
하위 단어 토큰화 때문에 단어 하나가 여러 토큰으로 쪼개지므로 레이블을 다시 맞춰야 한다. 허깅 페이스 Transformers 튜토리얼은 word_ids()로 토큰과 단어를 대응시키고, 특수 토큰과 단어의 두 번째 이후 하위 토큰에는 손실에서 무시되는 값 -100을 주는 방식을 쓴다.[6]
def tokenize_and_align_labels(examples):
enc = tokenizer(examples["tokens"], truncation=True, is_split_into_words=True)
all_labels = []
for i, word_labels in enumerate(examples["ner_tags"]):
prev, ids = None, []
for w in enc.word_ids(batch_index=i):
if w is None:
ids.append(-100) # [CLS], [SEP]
elif w != prev:
ids.append(word_labels[w]) # 단어의 첫 하위 토큰만 학습
else:
ids.append(-100)
prev = w
all_labels.append(ids)
enc["labels"] = all_labels
return enc
추론은 pipeline("ner", model=..., aggregation_strategy="simple")로 하면 하위 토큰 예측을 개체 범위로 묶어 글자 오프셋(start, end)과 함께 돌려준다.
- 중첩 개체: "서울대학교 병원"은 기관이면서 안에 "서울"이라는 장소를 품는다. BIO는 한 토큰에 꼬리표 하나만 달 수 있어 중첩을 표현하지 못하므로, 범위 후보를 모두 열거해 분류하는 방식 등이 따로 연구된다.
- 중의성: "삼성"이 기업인지 지명인지, "씨네21"이 잡지인지 출판사인지는 문맥으로 정해진다. KLUE 지침은 이런 경우 문맥에 따라 유형을 정한다.[2]
- 분야 이동: 뉴스로 학습한 모델은 의료 기록, 채팅, 코드 주석에서 성능이 크게 떨어진다.
- 새 이름: 신제품, 신조어, 새 기관명은 학습 데이터에 없다. 글자 단위 정보와 사전 학습 모델의 일반화에 기댄다.
- ↑ Erik F. Tjong Kim Sang, Fien De Meulder, 〈Introduction to the CoNLL-2003 Shared Task: Language-Independent Named Entity Recognition〉, CoNLL 2003. arXiv:cs/0306050
- ↑ 2.0 2.1 2.2 2.3 2.4 2.5 2.6 2.7 Sungjoon Park 외, 〈KLUE: Korean Language Understanding Evaluation〉, 2021. arXiv:2105.09680
- ↑ Zhiheng Huang, Wei Xu, Kai Yu, 〈Bidirectional LSTM-CRF Models for Sequence Tagging〉, 2015. arXiv:1508.01991
- ↑ Guillaume Lample 외, 〈Neural Architectures for Named Entity Recognition〉, NAACL 2016. arXiv:1603.01360
- ↑ Jacob Devlin 외, 〈BERT: Pre-training of Deep Bidirectional Transformers for Language Understanding〉, NAACL 2019. arXiv:1810.04805
- ↑ Hugging Face Transformers, Token classification 튜토리얼