English | 한국어
한국어 문서의 개인정보를 검출하고 가역적으로 가명화하는 Python 라이브러리. 외부 ML 의존성 없이 룰 + 사전 + 체크섬만으로 동작. 공공 문서에서 특히 강하며, 어떤 ML 파이프라인의 전처리 레이어로도 활용 가능.
공개 벤치마크 — 룰 기반 한국어 PII 도구 중 정확도 1위. 인간 라벨 KDPII(4,891건)에서 Microsoft Presidio · openai/privacy-filter를 앞서고(F1 0.66 vs 0.27 / 0.26), 0.19 ms/문서(~5,350 문서/s) — Presidio(4.2ms)보다 22배 빠름, 100만 문서를 ~$0 · 완전 온프레미스로 처리합니다. 결정적 ID(주민번호 · 카드 · 전화 · 이메일)는 체크섬 검증으로 F1 ≈ 1.0. 모든 수치는 단일 채점기로 측정 · 재현 가능 — 종합 비교표(한글) · 벤치마크 상세(영문).
from ko_pii import Anonymizer, ProcessingMode
result = Anonymizer(mode=ProcessingMode.STRICT, strategy="tokenize").process(
"신청인 홍길동 (880101-1234568) 연락처 010-1234-5678"
)
print(result.text)
# 신청인 <PERSON_1> (<RRN_1>) 연락처 <PHONE_1>
print(result.vault.reveal("<RRN_1>")) # 880101-1234568 (권한자만 복원)
print(result.combined_risk.combined_risk.name) # CRITICAL원본:
신청인 홍길동 (880101-1234568) 연락처 010-1234-5678
주소: 서울특별시 강남구 테헤란로 152
tokenize (토큰 치환 + Vault 복원 가능):
신청인 <PERSON_1> (<RRN_1>) 연락처 <PHONE_1>
주소: <ADDRESS_1>
partial (일부만 가림 — 실무 양식):
신청인 홍OO (880101-1******) 연락처 010-****-5678
주소: 서울특별시 강남구 ***
redact (카테고리명 치환):
신청인 [성명] ([주민등록번호]) 연락처 [전화번호]
주소: [주소]
처음이시면:
mode=ProcessingMode.STRICT+strategy="tokenize"추천. 가장 안전한 기본 설정 (MEDIUM 위험도 이상 차단 + Vault 복원 가능).
- 조사 붙어있어도 잡힘 — "홍길동이" "홍길동에게" "홍길동의" → 조사 자동 분리 후 PERSON 검출
- 한자 병기 — "홍길동(洪吉童)" → 한글+한자 모두 인식
- 로마자 이름 — "Hong Gildong" → 한글로 정규화 후 매칭
- HWP/HWPX/DOCX/PDF 직접 입력 —
ko-pii report.hwp --strategy tokenize(표·머리말·꼬리말·메타데이터 모두 처리) - CSV/XLSX 헤더 자동 인식 — "성명/주민번호/연락처" 헤더 → 자동으로 PERSON/RRN/PHONE 매핑
- 공문서 날짜 자동 거부 — "시행일자: 2026-05-21" "감사기간: 3월~4월" → 생일 아님 (비-생일 키워드 30+)
- 가명 표기 자동 거부 — "박씨" "김모씨" "○○○ 시민" → 이미 가명화됨 (PII 아님)
- 결합 위험도 자동 평가 — 이름만으로는 PII 아닐 수 있지만, 이름 + 주민번호 + 주소 가 같이 나오면 → CRITICAL (「개인정보 비식별 조치 가이드라인」 의 준식별자 결합 검증)
- 감사 로그 — 누가·언제·어떤 토큰을 복원했는지 JSONL 추적 (개인정보보호법 제29조)
- 왜 필요한가
- 주요 특징
- 설치
- 사용 시나리오
- 평가 결과
- 사용법
- 33 PII 카테고리
- 검출 정책 — 어떤 접두어·anchor 가 작동하는가
- 처리 모드 + 치환 전략
- 부가 기능
- FAQ
- 개발
- 라이선스
RAG·LLM 파이프라인은 미정제 비정형 데이터를 그대로 인덱싱·검색해 모델에 넣습니다 — PII 가 벡터 DB 와 응답 양쪽에 노출됩니다.
- 법적 의무 — 개인정보보호법(PIPA): 주민등록번호·건강정보 등 민감정보 처리 제한 (해외 GDPR·HIPAA 대응)
- 주권·폐쇄망 — 공공기관 망분리 환경은 외부 API 로 PII 를 보낼 수 없음 → 오프라인 결정적 검출이 필수
- 신뢰·평판 — 유출 1건이 서비스 신뢰를 무너뜨림
- ML 보완 — NER·LLM 검출은 할루시네이션·재현 불가. 체크섬으로 검증되는 PII(RRN·카드·사업자)는 ko-pii 가 F1 ≈ 1.0 으로 확정 검출해 ML 의 빈틈을 메움
ko-pii 는 RAG 의 인제스트(벡터 DB 진입 전)와 검색(LLM 전달 전) 양단에서 PII 를 차단합니다. 같은 인물은 같은 토큰으로 치환해 문맥을 보존하고(LlamaIndex·LangChain 연동 제공), Vault 로 권한 기반 복원과 감사 추적을 지원합니다.
- 한국 특화 — 한국어 PII 33 카테고리 (RRN · FRN · 여권 · 사업자 · 카드 · 계좌 · 전화 · 이메일 · 주소 · 차량 · 인명 · 직책 · 국적 등). 공공 문서에서 특히 강함
- 결정적 검출 — 룰 + 사전 + 체크섬. 주민등록번호·카드·사업자번호 등은 체크섬 검증으로 F1 ≈ 1.000
- 우회 차단 — 전각 숫자(
010)·제로폭 문자 삽입 등 유니코드 우회를 정규화로 무력화 (검출 offset 은 원본 보존) - 외부 의존성 없음 — Python 표준 라이브러리만 사용. 오프라인/폐쇄망 동작, GPU 불필요
- 전처리 레이어 —
DetectionResult(label/start/end/text/confidence) 표준 객체 출력. ML 파이프라인 앞단에 끼워넣기 편함 - 가역 가명화 + Vault — 토큰 ↔ 원본 매핑을 별도 저장소에 분리, 복원 가능
- 법적 근거 자동 부착 — 각 검출에 개인정보보호법 조항 자동 부착 (감사 추적)
- 다양한 입력 — TXT · CSV · XLSX · HWP · HWPX · DOCX · PDF (
[file]extras)
| 도메인 | 권장 설정 | 비고 |
|---|---|---|
| 공공 문서 (공문서·민원·인사) | STRICT + tokenize |
기본값. 가장 잘 맞는 도메인 |
| LLM 학습 데이터 전처리 | PARANOID + tokenize 또는 redact |
누수 차단 우선 |
| 의약품·바이오 | STRICT + exclude={"AGE","HEIGHT","WEIGHT"} |
"체중 1kg당" 같은 용법 오탐 방지 |
| 금융·보험 | STRICT + tokenize |
RRN·카드·계좌 결정적 검출 |
| 일반 사무 (사내 문서) | BALANCED + partial |
읽기 편한 부분 마스킹 |
# 의약품 도메인 — PERSON FP 방지 + 신체속성 오탐 방지
anon = Anonymizer(
mode=ProcessingMode.STRICT,
strategy="tokenize",
exclude={"AGE", "HEIGHT", "WEIGHT"}, # "체중 1kg당" 오탐 방지
)
# PERSON FP 가 많다면 — 도메인 사전 주입
# src/ko_pii/dictionaries/common_words.py 에 의약품 성분명·제조사명 추가
# 예: "이부프로펜", "한미약품", "메트포르민" → PERSON 에서 자동 제외pip install ko-piiextras 옵션 (필요 시):
pip install "ko-pii[file]" # HWP/HWPX/DOCX/PDF
pip install "ko-pii[security]" # Vault AES-256-GCMPython 3.10 이상. 코어는 표준 라이브러리만 사용.
from pathlib import Path
from ko_pii import Anonymizer, ProcessingMode
anon = Anonymizer(mode=ProcessingMode.PARANOID, strategy="tokenize")
for path in Path("./공문서/").glob("*.hwp"):
result = anon.process(path.read_text(encoding="utf-8"))
Path(f"./가명화/{path.name}").write_text(result.text, encoding="utf-8")
# vault.json 분리 보관 (권한 있는 사용자만 복원 가능)
result.vault.save(f"./vault/{path.stem}.json")- PARANOID 모드 — LOW 위험도 이상 모두 차단 (LLM/외부 전송 안전)
- 가명화 결과는 외부에, Vault 는 사내 저장소에 분리 보관
- HWP/HWPX 파서:
pip install "ko-pii[file]"
from ko_pii import Anonymizer, ProcessingMode, RiskLevel
anon = Anonymizer(mode=ProcessingMode.AUDIT) # 차단 X, 검출만 보고
result = anon.process(incoming_petition_text)
# 결합 위험도가 CRITICAL 이면 담당자에게 알림
if result.combined_risk.combined_risk >= RiskLevel.CRITICAL:
notify_admin(
identifiers=result.combined_risk.distinct_identifiers, # ["RRN"]
quasi=result.combined_risk.distinct_quasi, # ["ADDRESS", "PERSON", "PHONE"]
)
# 응대 직원에게는 가명화 버전 제공
masked = Anonymizer(mode=ProcessingMode.STRICT, strategy="partial").process(
incoming_petition_text
).text- AUDIT 모드 — 차단 없이 검출만 보고 (감사·통계용)
- 결합 위험도 자동 평가 — 「개인정보 비식별 조치 가이드라인」 의 준식별자 결합 검증
- 응대 직원에게는
partial전략으로 일부만 마스킹 (880101-1******)
# 코드 어디서든 logger.info("...") 호출 시 자동 가명화
import logging
from ko_pii import Anonymizer, ProcessingMode
_anon = Anonymizer(mode=ProcessingMode.STRICT, strategy="redact")
class PIIFilter(logging.Filter):
def filter(self, record):
record.msg = _anon.process(str(record.msg)).text
return True
logging.getLogger().addFilter(PIIFilter())
logging.info("신청인 홍길동 (880101-1234568) 처리 완료")
# → "신청인 [성명] ([주민등록번호]) 처리 완료"| 평가 도메인 | 문서 수 | ko-pii | openai/PF | Presidio |
|---|---|---|---|---|
| 생성 평가셋 (행정/서식체, 검증 gold) | 540 | 0.790 | 0.451 | 0.483 |
| KDPII (한국어 일상 대화) | 4,891 | 0.660 | 0.264 | 0.273 |
| KLUE-NER (신문기사 풀네임) | 5,000 | 0.419 | 0.155 | 0.000 |
룰 기반 한국어 PII 도구 중 정확도 1위. ko-pii는 속도·비용·결정성에서도 뚜렷한 운영점을 점합니다:
| 시스템 | 지연/문서 | 처리량 | 100만 문서 비용 | 특성 |
|---|---|---|---|---|
| ko-pii | 0.19 ms | ~5,350/s | ~$0 (CPU 1코어 ~3분) | 결정적 · 체크섬 검증 · 온프레미스 |
| Presidio | 4.2 ms | ~238/s | ~$0 | 한국 카테고리 다수 미지원 |
| openai/privacy-filter | 481 ms | ~2/s | ~$0 | 한국 카테고리 7종만 |
→ 0.19 ms/문서(~5,350 문서/s)로 Presidio(4.2ms)보다 22배 빠르고, 결정적이라 같은 입력에 항상 같은 결과 · 검출마다 근거 조항/evidence 부착. 속도·비용·정성 전체 비교: 종합 비교표(한글).
- KDPII는 단일 매처(
match_forms_overlap,person_min_length=3)로 전체 4,891문서 재측정 — openai/privacy-filter (660M ML) · Microsoft Presidio 등 룰·NER 도구 대비 우위. - 공정 비교: 위 전체 점수는 해외 도구가 라벨 자체가 없는 항목(AGE·POSITION·RRN 등)에서 0점이라 격차가 커진다. 각 도구가 실제 지원하는 카테고리만으로 좁혀도 ko-pii가 앞선다 — vs openai/PF 0.61 : 0.37(그쪽 7라벨), vs Presidio 0.87 : 0.65(그쪽 9라벨).
- 결정적 PII (RRN·PHONE·EMAIL·카드·사업자) 는 체크섬 검증 → F1 ≈ 1.000.
- **생성 평가셋(540)**은 ko-pii 룰을 참조하지 않고 생성한 독립 데이터(행정/서식체,
26라벨)이며 gold를 2단계 검증(98.8% 정확,
data/generated_eval.jsonl)했다 — 자기 주입이 아니라 과적합 우려가 없다 (상세 BENCHMARK §3b). - 상세·재현:
docs/BENCHMARK.md·docs/EVALUATION_REPORT.md. (KLUE 는 이전 방법론 수치)
운영 전 권장: 사용하시는 도메인의 실제 문서 30~100건을 직접 테스트해보세요. 도메인마다 성능 차이가 있습니다.
- PERSON 오탐 (FP) + 도메인 의존성 — 룰 기반 PERSON 검출의 가장 큰 약점. 성씨 글자로 시작하는 일반명사·생약명·외래어가 사람 이름으로 잡힐 수 있음. 공공문서 도메인 특화라, 공공문서 gold 에선 F1 0.97 이지만 외부 신문 NER(KLUE-NER PS, 5K 문장) 대비로는 F1 0.38(P 0.36 / R 0.41) — 신문체 인명(역사·외국인·공인)과 일반명사 과탐이 크다. 일반 도메인 NER 용도라면
common_words.py도메인 사전 주입,exclude={"PERSON"}, 또는 하이브리드 분류기(dev/classifier)를 권한다. (재현:python -c "from ko_pii.eval.klue_ner import load_klue_ner, evaluate_person; print(evaluate_person(load_klue_ner('data/klue_ner/klue-ner-v1.1_dev.tsv')).format())") - ADDRESS 비정형 — "강남 쪽에 살아" 같은 비정형 주소는 약함 (anchor 필요). 정형 주소 ("서울특별시 강남구 테헤란로 152") 는 OK
- 형식이 겹치는 비-PII — 대표번호 4-4(
1588-2024)는 제품·연식 번호와, 무구분자 13~19자리는 바코드·IMEI와 형식이 같다. 길이-브랜드 일관성·단어경계 문맥으로 상당수 거르지만, recall 우선이라 형식만으로 완전 구분은 불가 — 최종 정밀 판단은 주변 문맥(검토 큐) 또는 하이브리드 분류기(dev/classifier)의 몫 - 결정적 PII (RRN·PHONE·EMAIL·카드·사업자) 는 체크섬/형식 검증이라 오탐 거의 없음
상세 평가: docs/EVALUATION_REPORT.md.
# 기본
ko-pii input.txt --mode STRICT --strategy tokenize \
--vault vault.json -o output.txt --report report.html
# 배치 (디렉토리 일괄, 병렬)
ko-pii ./incoming/ --batch --workers 4 --output-dir ./anonymized/
# Vault 암호화 + 감사 로그
KPII_VAULT_PASSWORD=secret ko-pii doc.hwp \
--vault vault.kvault --audit-log audit.jsonlfrom ko_pii import Anonymizer, ProcessingMode
anon = Anonymizer(mode=ProcessingMode.STRICT, strategy="tokenize")
result = anon.process(text)
print(result.text) # 가명화된 텍스트
print(result.vault.reveal("<RRN_1>")) # 원본 복원 (권한자만)
print(result.summary["by_label"]) # {"RRN": 1, "PHONE": 1, "PERSON": 1}# 검출 결과의 결합 위험도 자동 평가
print(result.combined_risk.combined_risk.name) # CRITICAL
print(result.combined_risk.distinct_identifiers) # ["RRN"]
print(result.combined_risk.distinct_quasi) # ["PERSON", "PHONE"]
# k-익명성 평가 (집단 데이터)
from ko_pii.analytics import k_anonymity
report = k_anonymity(records, quasi_keys=["age", "city", "job"], threshold=5)
print(report.k) # 최소 그룹 크기
print(report.satisfies_threshold) # True/False
print(report.rationale) # ["준식별자 ['age', 'city', 'job'] 기준 N개 그룹", ...]from ko_pii.tabular import anonymize_records
import csv
rows = list(csv.DictReader(open("employees.csv")))
# 헤더 "성명/주민번호/연락처/주소" → 자동으로 PERSON/RRN/PHONE/ADDRESS 매핑
anon_rows, vault = anonymize_records(rows, strategy="tokenize")
print(anon_rows[0])
# {'성명': '<PERSON_1>', '주민번호': '<RRN_1>', '연락처': '<PHONE_1>', '주소': '<ADDRESS_1>'}confidence 낮은 검출 → 검토 큐에 저장 → 사용자가 FP/OK/FN 마킹 → 누적 마킹에서 사전 추천 패치 자동 생성 (자동 반영 X, 사람 검토 후 반영).
result = anon.process(text)
# 1. confidence 낮아 REVIEW 분류된 검출 (모드별 자동 분류)
for record in result.review_items():
d = record.detection
print(d.text, d.confidence, d.evidence)
# 2. 별도 JSONL 큐에 저장 → 사용자가 verdict 마킹
from ko_pii.review.queue import ReviewQueue
q = ReviewQueue("review.jsonl")
q.enqueue_review_records(result.review_items(), document=text)
# 3. 누적 마킹 → 패치 파일 생성 (common_words 후보 / 이름 후보)
from ko_pii.review.feedback import apply_feedback
apply_feedback(
queue_path="review.jsonl",
output_dir="feedback_patches/",
min_repeat=2, # 같은 토큰이 2회 이상 FP → 후보 (사전 오염 방지)
)
# → feedback_patches/common_words_additions.txt (PERSON FP 후보)
# → feedback_patches/names_to_add.txt (FN 표시 이름)
# → feedback_patches/summary.jsonfrom ko_pii.patterns.rrn import detect
for r in detect("신청인 880101-1234568"):
print(r.label, r.text, r.confidence, r.legal_basis)
# RRN 880101-1234568 1.0 개인정보보호법 제24조의2전체 라벨 키는
ko-pii --labels(CLI) 또는from ko_pii.labels import ALL_LABELS, LABEL_INFO(Python) 로 조회할 수 있습니다.
| 카테고리 | 검증 | 위험도 |
|---|---|---|
| RRN (주민등록번호) | 13자리 + 날짜 + 한국 체크섬 | CRITICAL |
| FRN (외국인등록번호) | gender 5-8 + 체크섬 | CRITICAL |
| 사업자등록번호 | 국세청 가중합 체크섬 | HIGH |
| 법인등록번호 | 법인 체크섬 (RRN 우선) | MEDIUM |
| 운전면허번호 | 지방청 코드 11-28 화이트리스트 | HIGH |
| 여권번호 | prefix (M/S/PP/PD 등) + 8자리 | CRITICAL |
| 신용카드 | BIN 화이트리스트 + Luhn | CRITICAL |
| 필지고유번호 (PNU) | 19자리 + 시·도 코드 | LOW |
| 카테고리 | 키워드 |
|---|---|
| 건강보험증 | 건강보험 / 의료보험 / 보험증 |
| 처방번호 | 처방번호 / Rx / 교부번호 |
| 의약품 코드 | 약품코드 / KD코드 + 한국 GS1 |
| 팩스번호 | 팩스 / FAX |
| 계좌번호 | 계좌 / 은행명 60+ (3-way anchor) |
| 사번 | 사번 / 공무원번호 / 직원번호 / 임용번호 |
| 민원번호 | 민원 / 청구 / 정보공개 / 행정심판 |
| 사건번호 | 사건유형 (가합/고합/구합/헌가 등) |
| 카테고리 | 검증 |
|---|---|
| 전화번호 | 모바일 010-019 / 서울 02 / 지방 031-064 / VoIP 070 / 대표 15xx-18xx / +82 국제 |
| 이메일 | RFC 5322 |
| IP | IPv4 옥텟 + IPv6 RFC 4291 |
| URL | http(s) / ftp |
| 우편번호 | 시·도 첫자리 매핑 |
| 차량번호 | 신형 NN[가-힣]NNNN + 용도 한글 화이트리스트 |
| 공문서번호 | 부처명 + 형식 |
| 카테고리 | 사전 규모 |
|---|---|
| 인명 (PERSON) | 성씨 286 + 직책 인접 + 17 거부 룰 |
| 주소 (ADDRESS) | 광역 17 + 기초 226 + 빈출 동 240 + 법정동 10K (anchor 조건부) + 건물 접미사 38 + 동/호/층 bridge 확장 |
| 국적 (NATIONALITY) | 국가명 70+ (대한민국·미국·일본 등) |
| 학력 (EDUCATION) | 대학 ~330 + 약칭 |
| 전공 (MAJOR) | 학과 ~400 (KEDI 분류) |
| 직책 (POSITION) | titles 250+ (정부·경찰·소방·군·검사·법관·민간) |
| 카테고리 | 검증 | 위험도 |
|---|---|---|
| 생년월일 | 날짜 + 키워드/풀네임/년생 marker | HIGH |
| 나이 | "32세 / 32살 / 환갑 / 12개월 아기 / 30대" | INFO |
| 신장 | "175cm / 1.75m" 50–250 | INFO |
| 체중 | "70kg / 70킬로" 1–300 | INFO |
준식별자 (Quasi-Identifier) 단독으로는 식별 불가지만 다른 정보와 결합 시 재식별 위험.
analytics/combined_risk가 자동 평가.
각 PII 검출은 단순 정규식 매칭이 아닌 multi-gate: 접두어 라벨 / 키워드 anchor / 문맥 사전 / 형식 검증 의 조합.
라벨 직후 1~4글자 한글 → 강한 PERSON 후보.
| 도메인 | 라벨 |
|---|---|
| 기본 | 성명 이름 성함 이 름 |
| 민원·행정 | 신청인 신청자 민원인 청구인 보호자 대리인 당사자 |
| 결재 | 기안자 결재자 검토자 보고자 수신자 발신자 참조 |
| 사법 | 원고 피고 고소인 피고소인 증인 감정인 |
| 경찰·소방 | 피의자 피해자 용의자 참고인 신고자 수사관 출동대장 |
| 인사 | 평가자 피평가자 면담자 추천인 |
| 의료 | 환자 |
라벨 variant 7종 인식: 성명: 홍길동 / [성명] 홍길동 / (성명) 홍길동 / <성명> 홍길동 등.
- 단성 + 직급/지역/학교/은행:
김부장김포시이화여대→ 거부 - 한국어 어말 형태소 16종:
~은데~는데~라서~까지끝 → 거부 - 부처·기관명:
보건복지부행정안전부→ 거부 - 이미 가명화된 표기:
박씨김모씨○○○ 시민→ 거부
✓ 성명: 김도윤 (필드 라벨)
✓ 박지훈 과장님께 (직책 인접)
✓ 홍길동(洪吉童) (한자 병기)
✓ 880101-1234568 (RRN — 체크섬)
✓ 120-81-47521 (사업자 — 국세청 체크섬)
✓ 4242-4242-4242-4242 (카드 — Luhn)
✓ M12345678 (여권)
✗ 김부장이 협조 안 함 (호칭 = 거부)
✗ 보건복지부는 검토 후 (부처명)
✗ 시행일자: 2026-05-20 (비-생일 거부)
✗ 881301-1000004 (RRN — 13월 무효)
✗ A12345678 (여권 — A prefix 거부)
| 모드 | 차단 기준 | 용도 |
|---|---|---|
PARANOID |
LOW 이상 모두 차단 | 외부 공개·LLM 전송 전 |
STRICT |
MEDIUM 이상 차단 | 실무 표준 (기본값) |
BALANCED |
HIGH 이상 차단 | 내부 협업 |
PERMISSIVE |
CRITICAL 만 차단 | 분석가 작업 |
AUDIT |
차단 없음, 검출만 보고 | 감사·통계 |
| 전략 | 880101-1234568 → |
가역 | 설명 |
|---|---|---|---|
tokenize |
<RRN_1> |
✓ | 토큰 치환, Vault 에 원본 보관 |
redact |
[주민등록번호] |
✗ | 카테고리명으로 치환 |
partial |
880101-1****** |
✗ | 일부만 가림 (실무 표준) |
asterisk |
************** |
✗ | 별표 마스킹 |
hashed |
<RRN:abc123> |
✗ | 해시 (같은 값 → 같은 토큰) |
fpe |
771202-2345671 |
✗ | 형식 유지 암호화 (FPE) |
| 기능 | 설명 | 설치 |
|---|---|---|
| HWP/HWPX/DOCX/PDF 파서 | 한컴오피스·MS Word·PDF 자동 파싱 (본문 + 표 + 머리말 + 메타데이터). 아래 파서 상세 참고 | [file] |
| Vault 암호화 | AES-256-GCM + PBKDF2 480k 반복 | [security] |
| 감사 로그 (JSONL) | 모든 reveal() 호출 기록 (개인정보보호법 제29조) |
코어 |
| 배치 처리 | 디렉토리 일괄 + 병렬 워커 | 코어 |
| 검토 큐 | confidence 낮은 검출 → 사람 검토 → 오탐 어휘 자동 학습 | 코어 |
| HTML 리포트 | 정탐 초록 / 오탐 빨강 / 미탐 노랑 시각화 | 코어 |
| 한자/로마자 변형 | 洪吉童 → 홍길동, Hong Gildong → 홍길동 |
코어 |
| RAG 연동 | LlamaIndex·LangChain 검색 결과 PII 마스킹 (검색→마스킹→LLM) | [llamaindex] / [langchain] |
| 룰+ML 하이브리드 | 룰+ML 결합 모드 4종 + 임계값으로 검출 민감도 조절 (opt-in) | [classifier] |
| 포맷 | 사용 라이브러리 | 비고 |
|---|---|---|
| HWP 5.x | olefile | OLE 바이너리 레코드 직접 파싱, 본문 텍스트 추출 |
| HWPX | stdlib (zipfile + xml) |
ZIP+XML 구조, 외부 의존성 없음 |
| DOCX | stdlib (zipfile + xml) |
ZIP+XML 구조, 외부 의존성 없음 |
| XLSX | stdlib (zipfile + xml) |
sharedStrings + sheet XML |
| pdfplumber (우선) / pypdf (fallback) | 텍스트 레이어만 추출 (스캔 PDF는 OCR 필요) |
PDF 참고: PDF는 글자 좌표 기반이라 칸별 공백·줄바꿈 삽입이 흔합니다. ko-pii는 내장 정규화 엔진(
text_normalizer)으로 PII 패턴 중간의 불필요 공백/줄바꿈을 자동 보정합니다. pdfplumber가 pypdf보다 레이아웃 분석이 우수하므로 pdfplumber 설치를 권장합니다.
검색된 문서를 LLM 에 넣기 전에 PII 를 마스킹합니다. 한 번의 검색 결과 안에서 같은 인물은 같은 토큰(<PERSON_1>)으로 치환돼 문맥이 보존되고, vault 를 넘기면 답변 생성 후 vault.reveal() 로 복원할 수 있습니다.
# LlamaIndex — node postprocessor (검색 → 마스킹 → LLM)
from ko_pii.integrations.llamaindex import KoPiiNodePostprocessor
qe = index.as_query_engine(
node_postprocessors=[KoPiiNodePostprocessor(mode="STRICT")]
)
# LangChain — Runnable 체인에 그대로 삽입
from ko_pii.integrations.langchain import KoPiiRedactor
chain = retriever | KoPiiRedactor(mode="STRICT") | prompt | llm코어는 ML 없이 동작하되, 정확도가 더 필요하면 ML 을 얹을 수 있습니다. 하이브리드는 두 종류이며 서로 다른 기능입니다:
| ① 토큰 NER 하이브리드 (span 교체) | ② 문서 분류기 하이브리드 (confidence 결합) | |
|---|---|---|
| 무엇 | 룰=결정적 ID(체크섬), ML=퍼지(이름·주소 등) 검출 자체를 교체 | 문서 수준 "PII 있음/없음" 분류기로 룰 결과를 보강 (span 추가 없음) |
| 사용 | Anonymizer(secondary_detector=..., merge_mode="role_split") |
ko_pii.classifier.HybridAnonymizer |
| 성능 | 외부 검증 F1 0.97 (docs/HYBRID_NER.md) |
검토 트리거·민감도 조절용 |
① 토큰 NER 하이브리드 — docs/HYBRID_NER.md 레시피로 직접 학습한 NER 모델을 꽂으면 됩니다:
from ko_pii import Anonymizer
from ko_pii.integrations.hf_token_ner import HFTokenNERAdapter
ml = HFTokenNERAdapter("out/ner_fuzzy/final") # 직접 학습한 모델 (레시피 참조)
anon = Anonymizer(secondary_detector=ml, merge_mode="role_split")
result = anon.process(text) # 모든 전략(tokenize/partial/redact)·Vault 그대로 동작② 문서 분류기 하이브리드 — [classifier] extra 로 결합 방식과 민감도를 직접 설정:
| 결합 모드 | 동작 | 용도 |
|---|---|---|
SCORE |
룰 검출 + 분류기 확신도 보강 | 기본 |
GATED |
분류기 점수 < 임계값이면 룰 skip | 속도 우선 |
REVIEW_FLAG |
룰 0건인데 분류기 높으면 '검토 권고' | 사람 검토 트리거 |
UNION_BLOCK |
한쪽이라도 PII로 보면 차단 | 보수적 마스킹 |
classifier_threshold · gate_threshold 로 룰 ↔ ML 결합 비율(민감도)을 세밀하게 조절합니다.
from ko_pii import Anonymizer, ProcessingMode
from ko_pii.classifier import PIIClassifier, HybridAnonymizer, HybridMode
clf = PIIClassifier.from_pretrained("models/...")
hybrid = HybridAnonymizer(
Anonymizer(mode=ProcessingMode.BALANCED), clf,
mode=HybridMode.REVIEW_FLAG, # 결합 방식
classifier_threshold=0.5, # 민감도(비율) 조절
)pip install ko-pii[classifier] # torch + transformers + scikit-learn
python -m ko_pii.classifier.train ... # 모델은 직접 학습사전학습 가중치는 배포하지 않습니다(학습 데이터 라이선스) — 코드·학습 레시피 제공. 튜닝 NER 모델은 추후 공개 예정(라이선스·일반화 평가 후).
하이브리드 평가(룰단독 vs 하이브리드, base vs tuned 매트릭스):
docs/HYBRID_NER.md— 룰 대비 +0.14~0.19(klue), 단 제대로 튜닝된 한국어 NER이라야 일관 효과(base zero-shot 은 역효과). 실험 전 과정(학습·평가 코드, 실행 로그, 원시 결과)은 (내부 NER 실험 아카이브 — 비공개) 아카이브 참조.
Q1. ML 없이 룰만으로 정말 잘 되나요? 한국 핵심 PII (주민번호·여권·카드·사업자 등) 는 체크섬 검증으로 F1 ≈ 1.000 — ML 로 대체 불가능한 영역. PERSON 같은 맥락 의존적 PII 는 ML 이 더 나을 수 있지만, 공공/행정 문서에서는 F1 0.790 로 실용적 (생성 평가셋 540문서·검증 gold, docs/BENCHMARK.md §3b 참조).
Q2. 오탐이 많으면?
common_words.py 에 도메인 사전 주입, exclude={"PERSON"} 으로 특정 카테고리 끄기, 모드 변경 (STRICT → BALANCED).
Q3. Vault 분실하면?
복원 불가 (보안 설계). [security] extras 로 암호화 보관 또는 strategy="redact" (카테고리명 치환, Vault 불필요) 사용.
Q4. HWP 표·머리말 다 잡히나요?
네. [file] extras 설치 시 본문 + 표 + 머리말 + 꼬리말 + 메타데이터 모두 추출.
Q5. 다른 도구 (Presidio / openai) 와 뭐가 다르나요?
- Presidio — 영어 위주. 한국 특화 PII (RRN/FRN/여권 등) 부재
- openai/privacy-filter — 다국어 일반 PII. 한국 핵심 14 카테고리 라벨 없음
- ko-pii — 한국 특화 33 카테고리, 체크섬 검증, 법적 근거 자동 부착
Q6. 이름 단독 검출이 오탐이 많아요. 이름+전화번호 같이 있을 때만 차단할 수 있나요?
PERMISSIVE 모드 (CRITICAL만 차단) + combined_risk 조건부 재처리:
result = Anonymizer(mode=ProcessingMode.PERMISSIVE).process(text)
if result.combined_risk.combined_risk >= RiskLevel.HIGH:
result = Anonymizer(mode=ProcessingMode.STRICT).process(text)git clone https://github.com/Marker-Inc-Korea/ko-pii
cd ko-pii
pip install -e ".[dev]"
pytest # 전체 테스트 통과상세 문서: docs/ 디렉토리 참조.
MIT License
- 개인정보보호법 (제2조, 제23조, 제24조, 제24조의2, 제28조의2-5, 제29조)
- 개인정보보호위원회 「가명정보 처리 가이드라인」 · 「개인정보 비식별 조치 가이드라인」
- 상법 제40조 · 출입국관리법 제31조 · 국민건강보험법 제96조 · 금융실명법