# 모델 A — 추출 파이프라인 (A단계: 이진 분류 + B단계: 카테고리 분류) **담당: 윤정** · `model/extraction/` · KoELECTRA fine-tuned --- ## 한 줄 요약 > 가정통신문 원문 텍스트를 넣으면, 학부모가 실행해야 할 **후보 문장 리스트**가 나옵니다 (B단계 입력용). --- ## 2단계 구조 ```text ┌──────────────────────────────────────────────────────────────┐ │ 가정통신문 원문 텍스트 (OCR 출력) │ └─────────────────────────┬────────────────────────────────────┘ │ ┌─────────────────▼──────────────────┐ │ A단계 — 윤정 담당 │ │ file/predict.py │ │ │ │ ① split_sentences() │ │ 줄바꿈 복원 + 문장 분리 │ │ (헤더·OCR 노이즈 조기 차단) │ │ │ │ ② is_likely_todo() │ │ 정규식 1차 필터 │ │ → KoELECTRA 이진 분류 │ │ 0: 노이즈 1: 할 일·중요 일정 │ │ │ │ ③ extract_due_date() │ │ extract_amount() │ │ extract_action_hint() │ │ 정규식으로 날짜·금액·행동 추출 │ └─────────────────┬──────────────────┘ │ list[dict] — B단계 입력 스키마 {"text", "source", "due_date", "amount", "confidence", "action_hint"} │ ┌─────────────────▼──────────────────┐ │ B단계 — 경이 담당 │ │ KoELECTRA 5-class 카테고리 분류 │ │ (제출·준비물·건강·안전·비용·일정·기타) │ └────────────────────────────────────┘ ``` --- ## 기술 스택 | | | | --- | --- | | A단계 모델 | KoELECTRA-small-v3 이진 분류 (`checkpoints/koelectra-binary/`) | | B단계 모델 | KoELECTRA-base-v3 5-class · `yunjeong116/koelectra-extractor` | | 날짜 / 금액 | 정규식 (regex) | | 실행 환경 | CPU / GPU 자동 선택 | | 의존성 | `torch` · `transformers` · `huggingface_hub` · `sklearn` | > **이전 → 현재:** Llama-3-Korean 8B (Colab T4, 4-bit 양자화) → KoELECTRA 하이브리드 > 추론 속도 ↑, 서버 배포 용이성 ↑ --- ## 카테고리 & 중요도 (B단계) | 카테고리 | 설명 | 기본 점수 | | --- | --- | --- | | `제출` | 서류 · 동의서 · 과제 | **1.00** | | `준비물` | 지참물 안내 | 0.85 | | `건강·안전` | 건강 · 안전 사항 | 0.80 | | `비용` | 금액 포함 항목 (정규식) | 0.75 | | `일정` | 행사 · 일정 안내 | 0.70 | | `기타` | 위 외 항목 | 0.50 | 긴급 키워드(`반드시`, `마감`, `즉시` 등) 포함 시 +0.05 / due_date 있을 시 +0.05 --- ## 모델 성능 (B단계 카테고리 분류기) ### v2 재학습 결과 (2026-04-28 · 15 epoch · cosine LR · WeightedCrossEntropy) ```text precision recall f1-score support 일정 1.0000 1.0000 1.0000 4 준비물 1.0000 0.5000 0.6667 4 제출 0.8000 1.0000 0.8889 4 건강·안전 0.8571 0.8571 0.8571 7 기타 0.5000 1.0000 0.6667 1 accuracy 0.8500 20 macro avg 0.8314 0.8714 0.8159 20 weighted avg 0.8850 0.8500 0.8444 20 ``` ### v1 → v2 비교 | 지표 | v1 (10 epoch) | v2 (15 epoch) | 변화 | | ------ | -------------- | -------------- | ------ | | **accuracy** | 0.7500 | **0.8500** | +0.10 ✅ | | **macro F1** | 0.5988 | **0.8159** | +0.22 ✅ | | 기타 F1 | 0.0000 | **0.6667** | 완전 회복 ✅ | > MVP 목표 (accuracy ≥ 0.80, macro F1 ≥ 0.75) 달성 > 학습 파라미터: `num_train_epochs=15`, `lr=2e-5`, `scheduler=cosine`, `warmup_ratio=0.1`, `WeightedCrossEntropy(balanced)` --- ## 버그 수정 이력 (2026-04-27) | # | 현상 | 원인 | 수정 | | --- | ------ | ------ | ------ | | Bug 1 | 인사말이 TODO로 잡힘 | `NON_TODO_PATTERNS`에 `안녕하세요` 미포함 | 패턴 3개 추가 | | Bug 2 | NLLB 첫 번역 문장 어색 | 제목 줄이 인사말과 합쳐져 번역 전달 | `_HEADER_ONLY` 필터 추가 | | Bug 3 | `원→won` 오탐 | `원하시는`, `원인` 등 substring 매칭 | `MONEY_PATTERN`에 숫자 선행 조건 강제 | --- ## 파일 구성 ```text model/extraction/ ├── fill_original2.py ← notices_original2.jsonl 자동 채우기 스크립트 ├── file/ │ ├── predict.py ← A단계 메인 파이프라인 (백엔드 진입점) │ ├── evaluate_model.py ← Base vs Fine-tuned 성능 비교 (강사 제출용) │ ├── preprocess_txt_to_jsonl.py ← PDF txt → JSONL 변환 (문장 단위, 라벨링용) │ └── txt_to_jsonl.py ← PDF txt → JSONL 변환 (문서 단위) ├── data/ │ ├── notices_labeled_v2.jsonl ← 학습 라벨 데이터 (100문장, is_todo + original_id) │ ├── notices_original2.jsonl ← 원문 27장 + category/keywords/importance 자동 채우기 │ ├── notices_original2.csv ← CSV 버전 │ └── galsan_txt/ ← 갈산초 가정통신문 txt 원본 (~100건) ├── checkpoints/ │ └── koelectra-extractor/ ← B단계 5-class 카테고리 분류 체크포인트 (Hub 백업) ├── docs/ │ ├── devlog-2026-04-27.md ← Bug 수정 3종 + 학습 파라미터 개선 │ ├── devlog-2026-04-28.md ← notices_original2.jsonl 자동 채우기 작업 │ └── devlog-2026-04-28-v2.md ← v2 재학습 결과 (accuracy 0.85 달성) └── x/ ← 구버전 보관 (Llama Few-shot + 구 predict.py) ├── MODEL.py ├── predict.py ├── notices_labeled_v2.csv └── extracted_results.json ``` --- ## A단계 출력 예시 (`file/predict.py`) B단계(경이 모델) 입력 스키마: ```json [ { "text": "개인용 이어폰(3.5mm) 4월 20일까지 준비해 주세요.", "source": "sample.txt", "due_date": "2026-04-20", "amount": null, "confidence": 0.9312, "action_hint": "준비" }, { "text": "구입비는 5,000원 이내의 잔돈으로 준비합니다.", "source": "sample.txt", "due_date": null, "amount": 5000, "confidence": 0.8741, "action_hint": "준비" } ] ``` --- ## 실행 ### A단계 추출 (직접 테스트) ```bash pip install torch transformers python model/extraction/file/predict.py ``` ### 모델 성능 평가 (Base vs Fine-tuned 비교) ```bash pip install scikit-learn pandas python model/extraction/file/evaluate_model.py # 테스트 데이터 직접 지정 시: python model/extraction/file/evaluate_model.py --test_data data/test_data.jsonl ``` ### txt → JSONL 변환 (데이터 전처리) ```bash # 문서 단위 (notices_original2.jsonl 스키마) python model/extraction/file/txt_to_jsonl.py \ --input data/galsan_txt/*.txt \ --output data/notices_original2.jsonl \ --source_type 초등학교 # 문장 단위 (학습 라벨링용) python model/extraction/file/preprocess_txt_to_jsonl.py \ --input_dir data/galsan_txt \ --output data/notices_labeled.jsonl ``` ### notices_original2.jsonl 자동 채우기 ```bash python model/extraction/fill_original2.py ``` 멱등성 보장 — 재실행 시 항상 재계산하여 덮어씀. --- ## 백엔드 연동 ```python from model.extraction.file.predict import predict # A단계: 후보 문장 추출 → B단계 입력 candidates = predict(notice_text, source="파일명.pdf") # [{"text", "source", "due_date", "amount", "confidence", "action_hint"}, ...] ``` 모델은 첫 호출 시 로컬 체크포인트(`checkpoints/koelectra-binary/`)를 우선 로드하고, 없으면 HuggingFace Hub(`yunjeong116/koelectra-extractor`)에서 자동 다운로드합니다. --- ## 데이터 구성 | 파일 | 내용 | 건수 | | ------ | ------ | ------ | | `notices_labeled_v2.jsonl` | 문장 단위 라벨 (is_todo + category + original_id) | 100문장 (N01~N19) | | `notices_original2.jsonl` | 원문 문서 단위 + category/keywords/importance | 27건 | | `galsan_txt/` | 갈산초 가정통신문 원본 txt | ~100건 | > `notices_labeled_v2.jsonl`의 `original_id` 필드로 원문(`notices_original2.jsonl`)과 연결 가능. > N16~N19(알림장 4건)는 27장 원문 미포함 → `original_id: null`. --- ## 잔존 한계 및 향후 작업 | 항목 | 내용 | | ------ | ------ | | 준비물 recall 0.50 | 4개 중 2개 오분류. 데이터 추가 시 개선 여지 있음 | | 기타 support=1 | 검증셋 샘플 1개 → F1 신뢰도 낮음. 가상 데이터 증강 필요 | | 전체 샘플 100개 | 데이터 절대량 부족. 가상 데이터 추가 후 3차 학습 예정 (목표: 준비물·기타 F1 ≥ 0.70) | | Binary 체크포인트 | `checkpoints/koelectra-binary/` 미배포 상태 — Colab 재학습 후 교체 필요 |