이력서 처리 NER 실전 구현: 데이터 어노테이션부터 프로덕션 서비스까지
Named Entity Recognition (NER)은 현대 NLP 시스템의 핵심 구성 요소로, 특히 HR 자동화에서 필수적입니다. 이 글에서는 이력서에서 구조화된 데이터를 추출하는 NER 솔루션 구현의 기술적 측면을 자세히 분석하겠습니다. 러시아어 텍스트에 초점을 맞추고 프로덕션 배포의 구체적인 사항을 다룹니다.
작업 범위 정의
개발을 시작하기 전에 요구사항을 명확히 공식화하는 것이 중요합니다. HR 시스템의 주요 엔티티는 다음과 같습니다:
- 개인 정보 (성명, 이메일, 전화번호)
- 전문 기술 (기술, 도구)
- 재무 정보 (예상 급여 및 통화)
이력서의 기술 항목은 종종 구분자 없이 나열되는 경우가 많습니다 (예: "Python SQL Docker"). 모델이 인접 엔티티를 정확히 분리할 수 있도록 간단한 IO 대신 BIO 태깅 방식을 사용해야 합니다. 러시아어의 경우 어간 변화와 약어 처리("JS" 대신 "JavaScript")가 핵심입니다.
각 엔티티에 대한 정확한 규칙을 정의하세요:
- SKILL: 전문 용어만 (소프트 스킬 제외)
- SALARY: 금액 + 통화 ("150 thousand. rubles.", "$2500")
- PHONE: 국가 코드 포함 모든 숫자 형식
데이터 준비: 전략과 도구
기존 데이터셋 검색
첫 단계 — 기존 자원 확인:
- Hugging Face Datasets
필터: Task=Token Classification, Language=Russian. 이 글 기준으로 이력서 전문 데이터셋은 없었습니다. 영어 유사 데이터셋 (예: Resume Entities Dataset)은 번역과 파인튜닝으로 적응해야 합니다.
- Kaggle and Zenodo
키워드 "resume NER", "CV entities"로 검색. 발견된 데이터셋은 종종 다음 문제를 가집니다:
* 불완전한 어노테이션 (기술만)
* 러시아어 예시 부족
* 다른 태깅 방식 (BIOES vs BIO)
- 합성 생성
데이터가 부족할 때 nlpaug 라이브러리를 사용해:
* 실제 데이터 대체 ("Ivanov" → "Petrov")
* 연락처 데이터에 오타 삽입
* 기술 이름 변형 생성 ("PyTorch" → "pytorch")
Label Studio를 사용한 수동 어노테이션
기존 데이터가 부족하면 커스텀 데이터셋을 만드세요. 주요 단계:
- PDF 문서 준비
구조를 유지하며 텍스트 추출에 pdfplumber 사용:
```python
with pdfplumber.open('resume.pdf') as pdf:
text = '\n\n'.join([page.extract_text() for page in pdf.pages])
```
- Label Studio용 JSON 변환
PDF를 플랫폼 형식으로 변환하는 스크립트:
```python
import json
import os
def convert_to_ls_format(pdf_dir):
tasks = [{"text": open(f, 'r').read()}
for f in os.listdir(pdf_dir) if f.endswith('.txt')]
with open('import.json', 'w') as f:
json.dump(tasks, f, ensure_ascii=False, indent=2)
```
- 어노테이션 인터페이스 설정
Label Studio에서 태그 템플릿 생성:
```xml
<View>
<Text name="text" value="$text"/>
<Labels name="ner" toName="text">
<Label value="SKILL" background="#ffcc00"/>
<Label value="SALARY" background="#66ccff"/>
</Labels>
</View>
```
일관된 어노테이션이 매우 중요합니다: 모호한 경우 규칙 수립 (예: "C++" — 하나의 기술인가 두 개인가). 품질 향상을 위해 어노테이터 2명을 투입하고 코헨 카파 지표를 계산하세요.
모델 선택과 훈련
기본 아키텍처
러시아어 텍스트를 위한 추천 접근법:
- 사전 훈련 모델 파인튜닝
* RuBERT (DeepPavlov) — 러시아어 최적
* mBERT — 필요 시 다국어 지원
* 핵심 매개변수: max_seq_length=512 (이력서 긴 설명 처리)
- 토크나이저 커스터마이징
어휘에 특정 용어 추가:
```python
tokenizer.add_tokens(["CI/CD", "ETL", "MLflow"])
model.resize_token_embeddings(len(tokenizer))
```
- 클래스 불균형 처리
가중 손실 함수 사용:
```python
class_weights = compute_class_weight('balanced',
classes=np.unique(labels),
y=labels)
criterion = nn.CrossEntropyLoss(weight=torch.tensor(class_weights))
```
평가 지표
NER에는 표준 정확도가 적합하지 않습니다. 다음을 추적하세요:
- 토큰 수준 F1 — 주요 지표
- 엔티티 수준 precision/recall —
seqeval라이브러리 사용 - 처리 속도 — CPU 초당 토큰
SALARY 엔티티의 recall에 특별 주의: 여기서 하락 시 중요한 데이터 손실 발생.
프로덕션 배포
서비스 아키텍처
추천 통합 방식:
- 전처리
* PDF/DOCX를 텍스트로 변환 (antiword와 pdf2text 사용)
* 텍스트 정규화 (공백 제거, 통화 인코딩)
- NER 코어
```python
class NERServer:
def __init__(self):
self.model = AutoModelForTokenClassification.from_pretrained('ner_model')
def extract_entities(self, text):
inputs = tokenizer(text, return_tensors='pt', truncation=True)
outputs = self.model(**inputs)
# Process logits into entities
```
- 후처리
* BIO 태그로 토큰 그룹화하여 엔티티 생성
* 형식 검증 (이메일/전화번호 regex)
* 매핑으로 기술 정규화 ("JS" → "JavaScript")
성능 최적화
- 캐싱 — 반복 요청 처리
- 배치 처리 — 여러 이력서 배치 결합
- 양자화 —
torch.quantization으로 FP16 변환
고부하 시스템의 경우 대용량 파일 처리 시 타임아웃 방지를 위해 RabbitMQ 작업 큐 구현.
핵심 요점
- 도메인 적응이 핵심: 뉴스 훈련 모델은 이력서에서 실패
- 어노테이션 품질이 최종 결과의 80% 결정 — 어노테이터에 투자
- BIO 방식은 구분자 없는 기술 목록에 필수
- 엣지 케이스 검증 — 비표준 형식 처리 테스트 ("100k $", "~2000 evro")
- 데이터 드리프트 모니터링 — 신규 이력서에서 지표 정기 재계산
— Editorial Team
아직 댓글이 없습니다.