라이브러리
병원·연구소에는 "사람이 쓴 글"로 된 의료 데이터가 산더미입니다 — 퇴원 요약지, 처방전, 검사 소견. 이걸 컴퓨터가 다루려면 먼저 "이 단어는 병명, 저 단어는 약 이름"처럼 의미를 붙여야 하고, 동시에 "이 이름·주민번호는 가려야 한다"는 개인정보 보호도 해야 합니다. 보통은 이걸 클라우드 의료 API에 보내 처리하는데, 그 순간 환자 정보가 외부 서버로 나갑니다.
OpenMed는 이 두 가지(의료 개체 추출 + 개인정보 비식별화)를 내 컴퓨터 안에서 끝냅니다. analyze_text("...") 한 줄이면 질병·약물·해부학·유전자 같은 개체를 뽑아 주고, deidentify("...") 한 줄이면 이름·날짜·주민번호를 가려 줍니다. 모델은 HuggingFace의 OpenMed 조직에 공개된 의료 모델 카탈로그에서 골라 쓰고, 네트워크 없이 로컬 폴더만 가리켜도 동작합니다.
from openmed import analyze_text
result = analyze_text(
# 만성 골수성 백혈병에 imatinib을 시작했다는 임상 문장
"Patient started on imatinib for chronic myeloid leukemia.",
model_name="disease_detection_superclinical",
)
# DISEASE chronic myeloid leukemia 0.98
# DRUG imatinib 0.95
지금까지는 환자 차트를 봉투에 넣어 외부 판독 센터(클라우드 API)로 보내고, 결과를 받아 봤습니다. 빠르지만 차트가 건물 밖으로 나가고, 보낼 때마다 비용이 듭니다.
OpenMed는 그 전문가를 병원 안에 직접 들이는 것입니다. 필요한 파일을 미리 준비하고 로컬 경로를 사용하면 텍스트를 기기 안에서 처리할 수 있습니다. 게다가 이 전문가는 질병·약물·유전자별로 여러 모델을 선택할 수 있습니다. PII 언어 코드 42개 중 모델 기반 경로는 35개로 구분합니다.
entity는 텍스트·라벨·신뢰도를 담는다.주목 이유는 크게 셋입니다. ① 규제·프라이버시 압박 — 환자 정보(PHI)를 외부 클라우드로 보내는 것 자체가 HIPAA·GDPR 리스크라, "데이터를 안 내보내는" 도구 수요가 큽니다. ② 모델 생태계 — 의료 모델을 공통 API로 묶으며, 품질과 이용 조건은 각 모델·데이터셋별로 확인해야 합니다. ③ 온디바이스 가속 — Apple Silicon의 MLX와 지원 모델의 CoreML·ONNX 경로를 제공합니다. 속도와 메모리는 모델·정밀도·기기별로 측정해야 합니다.
| 항목 | 클라우드 의료 API | OpenMed |
|---|---|---|
| 실행 위치 | 벤더 서버 | ○ 내 기기 / 내 서버 |
| 환자 데이터 전송 | 벤더로 전송됨 | 로컬 경로에서 기기 안 처리; 원격 연동은 별도 확인 |
| 비용 | 호출당 과금 | ○ 무료 · 오픈소스 |
| 전용 의료 모델 | 제한적 | README 기준 manifest 2,266개 항목; 고유 모델 수와 구분 |
| 지원 언어 | 제각각 | PII 코드 42개 경로 · 모델 기반 35개 |
| 오프라인 / 망분리 | ✕ | ○ 완전 오프라인 가능 |
| Apple Silicon(MLX) | 해당 없음 | MLX 가속 지원; 속도는 환경별 측정 |
| 네이티브 iOS/macOS 앱 | ✕ | ○ OpenMedKit |
| 벤더 종속 | 있음 | ○ 없음(Apache-2.0) |
같은 모델 이름을 적어도, Apple Silicon에서는 MLX 가속 체크포인트로, 그 외 하드웨어에서는 자동으로 해당 계열의 PyTorch 모델로 대체되어 돕니다(한 번 경고만 띄우고). 개발자는 "어디서 돌릴지"를 신경 쓰지 않고 한 줄만 쓰면 됩니다. CoreML 폴백 경로까지 있어 iOS 앱에도 그대로 들어갑니다.
비식별화는 단순히 [NAME]으로 지우는 것 말고도, Faker 기반으로 같은 형식의 그럴듯한 가짜 값으로 바꿀 수 있습니다. 게다가 브라질 CPF/CNPJ, 네덜란드 BSN, 프랑스 NIR, 이탈리아 Codice Fiscale, 인도 Aadhaar, 독일 Steuer-ID, 미국 NPI 같은 국가별 임상 식별자 생성기를 내장해, 다운스트림 시스템이 깨지지 않는 "현실적인 가짜 데이터셋"을 만듭니다.
한국어(ko)를 포함해 PII 코드 42개 경로를 문서화하며, 그중 모델 기반은 35개입니다. 언어 코드 지원과 전용 모델 보유는 같은 뜻이 아닙니다. extract_pii(text, lang="pt")처럼 언어만 바꾸면 포르투갈어 CPF·전화번호를 알아서 잡습니다. 러시아어는 다국어 기본 모델 placeholder로 명시되어 있습니다. 선택적인 Indic NER 가중치는 사용자가 설정하며 자동으로 포함되지 않습니다.
OpenMed가 화제인 이유는 "성능 좋은 의료 모델"이어서가 아니라, "규제가 가장 까다로운 데이터(환자 정보)를 외부로 안 보내고도 최첨단 처리를 가능하게 했다"는 점이다. 프라이버시(온디바이스)와 성능(SOTA 모델·MLX 가속)을 동시에 잡은 것이 핵심이다.
OpenMed의 설계 철학은 "얇은 핵심 + 갈아끼우는 백엔드 + 선택적 확장"입니다. 코어 의존성은 pysbd·faker·jieba·pyyaml·jsonschema 5개이고, 무거운 ML 런타임(transformers·mlx·coreml)은 전부 선택 설치(extras)로 빠져 있습니다. 필요한 백엔드만 골라 깔면 됩니다.
| 항목 | 내용 |
|---|---|
| 주 언어 | Python 3.11+ (라이브러리·REST 서비스) + Swift (네이티브 앱 OpenMedKit) |
| 빌드 백엔드 | hatchling — 버전은 openmed/__about__.py에서 동적으로 읽음 |
| 패키지 배포 | PyPI openmed + Swift Package Manager(SPM) |
| 핵심 의존성 | 5개 — pysbd·faker·jieba·pyyaml·jsonschema |
| 코어 외 모든 것 | 선택 설치(extras): hf · service · gliner · mlx · coreml · docs |
| 라이선스 | Apache-2.0 (상업 이용·재배포 자유) |
| extra | 설치 명령 | 들어오는 것 |
|---|---|---|
| hf | pip install "openmed[hf]" | transformers · huggingface-hub · accelerate · tokenizers (기본 추론 런타임) |
| service | "openmed[hf,service]" | fastapi · uvicorn (REST API 서버) |
| mlx | "openmed[mlx]" | mlx · tiktoken · safetensors (Apple Silicon 가속) |
| gliner | "openmed[gliner]" | gliner · torch (제로샷 NER) |
| coreml | "openmed[coreml]" | coremltools · torch (iOS/macOS용 모델 변환) |
| docs | "openmed[docs]" | mkdocs-material (문서 사이트 빌드) |
| 백엔드 | 역할 |
|---|---|
| PyTorch (torch/) | 기본 엔진 — CPU·CUDA GPU. transformers 토큰 분류 파이프라인 |
| MLX (mlx/) | Apple Silicon 전용 가속 — PII·Privacy Filter 계열 등 지원; 모델별 성능 측정 필요 |
| CoreML (coreml/) | iOS/macOS 네이티브 런타임 변환 — OpenMedKit의 폴백 경로 |
| GLiNER (zero_shot/) | 학습 없이 라벨만 주면 잡는 제로샷 NER (실험적) |
pip install openmed만 하면 transformers·torch 같은 수 GB짜리 의존성이 안 깔린다. 코어에는 문장 분할·가짜 데이터 생성 외에 분절·YAML·JSON Schema 의존성도 있습니다. 실제 모델을 돌릴 때 비로소 [hf]나 [mlx]를 더한다. 덕분에 "비식별화 후처리 로직만 쓰고 싶은 사람"과 "GPU로 대량 추론하는 사람"이 같은 패키지를 다른 무게로 쓸 수 있다.
라이브러리를 넘어 FastAPI 기반 REST 서비스로도 돌릴 수 있습니다. Docker 이미지는 python:3.11-slim 베이스에 CPU 전용 PyTorch를 깔고, uvicorn으로 openmed.service.app을 8080 포트에 띄웁니다. HEALTHCHECK로 /health를 주기 점검하고, OPENMED_SERVICE_KEEP_ALIVE 환경변수로 유휴 모델을 자동 내려 메모리를 회수합니다.
# 서비스 실행
uvicorn openmed.service.app:app --host 0.0.0.0 --port 8080
# 또는 Docker로
docker build -t openmed:1.5.5 .
docker run --rm -p 8080:8080 -e OPENMED_PROFILE=prod openmed:1.5.5
# 엔드포인트: /health /analyze /pii/extract /pii/deidentify
# /models/loaded /models/unload (모델 생명주기 관리)
같은 모델을 아이폰·아이패드·맥 앱에 그대로 넣을 수 있는 게 큰 강점입니다. swift/ 아래에 SPM 패키지 OpenMedKit과 데모 앱 2개(OpenMedDemo, OpenMedScanDemo)가 있습니다. 앱에 다음 한 줄로 의존성을 추가하면, PII 탐지·임상 추출이 완전 오프라인으로 기기 안에서 돕니다.
// Swift Package에 OpenMedKit 추가
dependencies: [
.package(url: "https://github.com/maziyarpanahi/openmed.git", from: "1.5.5"),
]
OpenMed의 진짜 배울 점은 "임상 텍스트 → 토큰 분류 → 개체 디코딩/병합 → 출력"의 파이프라인을, 백엔드와 언어에 독립적으로 추상화한 방식입니다. 어떤 엔진(torch/mlx/coreml)을 쓰든, 어떤 언어든, 같은 analyze_text() / extract_pii() 진입점으로 흐릅니다.
core/backends.py가 "어느 엔진으로 돌릴지"를 한 곳에서 결정합니다. Privacy Filter 계열에서 MLX 아티팩트를 요청했는데 MLX를 쓸 수 없으면, 계열별로 매핑된 PyTorch 모델로 대체하고 경고합니다. 모든 모델이 같은 가중치로 자동 변환되는 것은 아닙니다. 개발자 코드는 그대로 두고 실행 환경만 바뀌어도 동작하는 "write once, run anywhere"의 ML 버전입니다.
-mlx-8bit)도 제공한다.HuggingFace의 긴 체크포인트 경로를 매번 외울 수는 없습니다. core/model_registry.py(약 22KB)는 disease_detection_superclinical 같은 친근한 별명 → 실제 HF 체크포인트 매핑을 큐레이션해 둡니다. 처음 쓸 때 모델을 내려받아 로컬 캐시에 두고, 그다음부터는 네트워크 없이 씁니다.
| 모델 별명 | 전문 분야 | 개체 타입 | 크기 |
|---|---|---|---|
| disease_detection_superclinical | 질병·상태 | DISEASE, CONDITION, DIAGNOSIS | 434M |
| pharma_detection_superclinical | 약물 | DRUG, MEDICATION, TREATMENT | 434M |
| pii_superclinical_large | 개인정보 | NAME, DATE, SSN, PHONE, EMAIL... | 434M |
| anatomy_detection_electramed | 해부학 | ANATOMY, ORGAN, BODY_PART | 109M |
| gene_detection_genecorpus | 유전자·단백질 | GENE, PROTEIN | 109M |
망 분리 환경이라면 model_id="./models/..."처럼 로컬 디렉토리를 가리켜 HF Hub에 접속하지 않고 로드할 수 있습니다.
Transformer는 단어를 서브워드 토큰으로 쪼개 처리합니다. 그래서 01/15/1970 같은 날짜가 01, /, 15... 로 흩어지기 쉽습니다. core/pii_entity_merger.py(약 28KB)는 이렇게 조각난 토큰을 다시 하나의 개체로 붙여, 날짜·주민번호가 토막 나지 않게 합니다. 비식별화 품질을 좌우하는 숨은 핵심입니다.
core/anonymizer/는 찾아낸 PII를 어떻게 처리할지 4가지 방법을 제공합니다. 상황(연구용 공유, 로그 보관, 종단 추적)에 따라 고릅니다.
| method | 동작 | 쓰임새 |
|---|---|---|
| mask | [NAME], [DATE]로 라벨 치환 | 가장 단순·안전한 가림 |
| replace | Faker로 형식 보존 가짜값 | 현실적 테스트 데이터셋 |
| hash | 암호학적 해시로 치환 | 같은 사람=같은 해시(연결 추적) |
| shift_dates | 날짜를 일정 일수만큼 이동 | 시간 간격은 보존, 실제 날짜는 숨김 |
비식별화 모델 중 일부는 OpenAI Privacy Filter 아키텍처를 공유합니다. gpt-oss 계열의 희소 MoE 트랜스포머에 로컬 어텐션·sink 토큰·RoPE+YaRN·tiktoken o200k_base를 쓰는 구조입니다. 코드는 똑같고 학습 데이터(가중치)만 다른 3가지 변형이 있으며, 모두 같은 extract_pii() API로 부릅니다 — model_name=만 바꾸면 됩니다.
| 변형 | 특징 |
|---|---|
| OpenAI Privacy Filter | 원본 베이스라인 |
| Nemotron-PII 파인튜닝 | NVIDIA Nemotron PII 데이터셋으로 미세조정 |
| OpenMed Multilingual | 다국어 모델 계열; 전체 언어 라우팅 수와 구분 |
| 파일 | 역할 |
|---|---|
| service/app.py | FastAPI 앱 — 엔드포인트 정의·에러 봉투 통일 |
| service/runtime.py | 파이프라인 사전 로드·공유, 모델 적재/해제 |
| service/keep_alive.py | 유휴 시간 지나면 모델 자동 언로드(메모리 회수) |
| service/schemas.py | 요청/응답 검증 스키마(Pydantic) |
"얇은 코어 + 백엔드별 폴더 + 서비스/앱 분리" 철학이 구조에 그대로 보입니다. 두뇌는 openmed/core/에 모여 있고, 엔진은 torch/ mlx/ coreml/ zero_shot/으로 갈라지며, 배포 형태(서비스·Swift 앱)는 바깥에 있습니다.
처음 본다면 README → openmed/__init__.py(어떤 함수가 공개 API인가) → core/model_registry.py(어떤 모델이 어떤 별명인가) → core/backends.py(엔진이 어떻게 갈리나) → core/pii.py와 pii_entity_merger.py(비식별화의 심장) → service/app.py(서비스로 어떻게 노출되나) 순서가 좋다.
"문장 → 토큰별 라벨 → 개체로 합치기"는 정보 추출의 기본기입니다. OpenMed는 의료 도메인으로 이걸 실전 수준까지 끌고 갑니다. transformers의 토큰 분류 파이프라인이 어떻게 NER이 되는지, 라벨 체계(DISEASE/DRUG...)가 어떻게 정의되는지 통째로 볼 수 있습니다.
transformers의 pipeline("token-classification")으로 아무 영어 문장에서 사람·장소를 뽑아 보고, OpenMed의 analyze_text 결과와 출력 형태를 비교한다.같은 모델을 CPU·GPU·MLX·CoreML로 돌리는 추상화는, 의존성 역전과 어댑터 패턴의 ML판입니다. backends.py 한 파일이 "어느 엔진을 고를지"를 캡슐화해, 상위 코드가 하드웨어를 몰라도 되게 합니다.
"무엇이 개인정보인가", "어떻게 가려야 재식별이 안 되나"는 실무에서 가장 까다로운 주제입니다. HIPAA의 18개 Safe Harbor 식별자, 마스킹 vs 가짜치환 vs 해시의 트레이드오프, 형식 보존의 필요성을 코드로 익힐 수 있습니다.
method="mask", "replace", "hash"를 각각 적용해 출력 차이를 비교하고, "어떤 상황에 무엇을 써야 하나"를 표로 정리한다.서브워드 토큰화 때문에 날짜·번호가 조각나는 문제는 NER 품질의 숨은 적입니다. pii_entity_merger.py는 이 "조각을 다시 붙이는" 후처리가 왜 필수인지, 어떻게 구현하는지 보여 주는 좋은 교본입니다.
"010-1234-5678"을 토큰화해 몇 조각으로 쪼개지는지 보고, 인접한 같은-라벨 토큰을 다시 합치는 작은 병합 함수를 직접 짠다.모델을 "라이브러리"가 아니라 "API 서비스"로 만들 때 생기는 문제 — 사전 로드, 메모리 회수, 헬스체크, 컨테이너화 —를 한 레포에서 다룹니다. runtime.py의 파이프라인 공유와 keep_alive.py의 유휴 언로드가 특히 실전적입니다.
curl로 /pii/extract를 호출한 뒤, /models/loaded·/models/unload로 메모리에 올라온 모델을 직접 내려 본다.| 항목 | 요구사항 |
|---|---|
| 파이썬 | Python 3.11+. 기본 의존성 5개; 추론 백엔드는 선택 설치 |
| 추론(기본) | openmed[hf] — transformers·torch. 434M 모델은 메모리 ~2GB 안팎 권장 |
| Apple 가속 | openmed[mlx] + Apple Silicon. 지연시간은 모델·정밀도·기기별 측정 필요 |
| REST 서비스 | openmed[hf,service] 또는 Docker(python:3.11-slim, 8080 포트) |
| iOS / macOS 앱 | OpenMedKit(SPM), CoreML 변환 모델. iOS 17+ / macOS 14+ / 최신 Xcode |
| 모델 다운로드 | 최초 1회 HF Hub 접속(이후 로컬 캐시). 망분리 시 로컬 디렉토리 지정 |
| 네트워크 | 로컬 추론은 모델 사전 확보 후 오프라인 구성 가능. 원격 백엔드·텔레메트리·연동 설정은 별도 점검 |
OpenMed는 텍스트 처리 도구이지 의료기기·법적 컴플라이언스 인증이 아니다. ① 자동 비식별화 출력은 모델의 예측이며 100% 재식별 방지를 보장하지 않는다 — 실제 환자 데이터에 쓰기 전 반드시 사람 검수와 기관 정책(IRB·DPO) 검토를 거쳐야 한다. ② NER 결과(질병·약물)는 진단·치료 판단의 근거가 아니라 추출 보조일 뿐이다. ③ HIPAA "Safe Harbor 18종 지원"은 기능 범위를 뜻하지, 특정 데이터셋이 법적으로 비식별되었음을 보증하지 않는다. 규제 데이터에 적용할 때는 법무·보안 검토를 함께 진행하라.
pip install "openmed[hf]" 후, 합성(가짜) 임상 문장 하나를 disease_detection_superclinical로 분석해 DISEASE·DRUG 개체와 신뢰도를 출력한다. 실제 환자 데이터가 아니라 합성 문장으로 시작하는 습관을 들인다.
"Patient: John Doe, DOB: 01/15/1970" 같은 합성 문장에 mask·replace·hash·shift_dates를 각각 적용해 출력을 나란히 본다. 어떤 게 "연구 공유용", 어떤 게 "로그 보관용"에 맞는지 한 줄씩 메모한다.
uvicorn openmed.service.app:app(또는 Docker)로 서비스를 띄우고 POST /pii/extract에 JSON을 보내 응답을 확인한다. 이어 /models/loaded로 적재된 모델을 보고 /models/unload로 내려 메모리 변화를 관찰한다.
extract_pii(text, lang="pt")로 포르투갈어 CPF·전화를, lang="ja"로 일본어 マイナンバー를 잡아 본다. 언어만 바꿨을 뿐인데 국가별 식별자를 인식하는 과정을 따라가며, pii_i18n.py가 어떻게 라우팅하는지 코드를 본다.
모델을 로컬 디렉토리로 내려받은 뒤, 네트워크를 끊고 model_id="./models/..." + OpenMedConfig(device="cpu", local_only=True) 또는 OPENMED_OFFLINE=1로 로컬 전용 모드를 켜고 HF Hub 접속 없이 추론이 되는지 검증한다. "로컬 전용 설정이 실제로 적용되는" 진짜 온디바이스 환경을 직접 만들어 본다.
| 주차 | 주제 | 할 일 |
|---|---|---|
| 1주차 | 트랜스포머 NER 기초 | 토큰 분류·BIO 태깅 개념, HF pipeline으로 NER 실습, 라벨 체계 이해 |
| 2주차 | PII / 비식별화 / HIPAA | Safe Harbor 18종, mask/replace/hash 트레이드오프, Faker 형식 보존, 재식별 위험 |
| 3주차 | 온디바이스 추론 · 이식성 | MLX·CoreML 변환, 8비트 양자화, 백엔드 추상화·자동 폴백 설계 |
| 4주차 | ML 서비스화 + Swift | FastAPI 서빙·모델 생명주기·Docker, OpenMedKit로 iOS 앱에 모델 임베드 |
이 레포는 "의료 NLP 모델을 어떻게 실전 제품으로 만드나"의 완결판이다. 모델 추론이라는 한 점이 아니라, 전처리→백엔드 선택→추론→개체 병합→비식별화→서빙→앱 임베드까지 한 줄로 꿰여 있다. 각 단계의 "입력과 출력"을 적어 가며 따라가면, 4주 뒤엔 다른 도메인(법률·금융 문서의 PII)으로도 같은 골격을 옮길 수 있다.
analyze_text가 돌려주는 entities는 각각 텍스트·라벨·신뢰도를 가진다.zero_shot/에 실험적으로 둔다.Dr., mg.)가 많아 마침표만으로 자르면 틀린다. OpenMed의 코어 의존성 pysbd가 규칙 기반으로 이 경계를 잡는다.