들어가며
"이 모델 정확도가 얼마인가요?"
문서 AI에서 이 질문에 하나의 숫자로 답하면 거의 틀린다. 같은 모델이 영문 인보이스에서는 97%를 내고 한글 세무 문서에서는 훨씬 낮게 나올 수 있다. 스캔본 PDF와 텍스트가 살아 있는 PDF도 결과가 다르다.
그래서 테스트를 조합으로 설계했다.
세 개의 축
| 축 | 구분 |
|---|---|
| 문서 유형 | Invoice / B/L(선하증권) / 세무 문서 |
| 언어 | 한국어 / 영어 / 스페인어 / 기타 유럽어 |
| 입력 형태 | 스캔 PDF / 텍스트 추출 가능 PDF / 이미지 |
세 축을 곱하면 조합이 꽤 많아진다. 3 x 4 x 3이면 36칸이고, 칸마다 데이터를 모으고 라벨을 붙이고 결과를 관리해야 한다. 나누는 순간 관리 비용이 곱으로 늘어난다는 뜻이다.
그래서 전부를 같은 깊이로 보지 않았다. 실제 유입 비중이 높은 조합은 깊게, 나머지는 동작 확인 수준으로 잡았다. 쪼개는 목적은 모든 칸을 채우는 게 아니라 평균 뒤에 숨은 약점을 찾는 것이라서, 어느 칸을 비워둘지 정하는 것도 설계의 일부였다.

입력 형태를 나눈 이유는 경로가 아예 다르기 때문이다.
스캔 PDF / 이미지 → PaddleOCR로 글자를 읽어서 좌표와 함께 전달
텍스트 PDF → XML로 변환해 단어와 좌표를 그대로 추출
같은 문서라도 이 두 경로에서 나오는 단어 분할과 좌표가 다르다. 그래서 테스트도, 나중에는 평가 데이터도 경로별로 따로 관리했다.
문서의 생김새로도 나눴다
언어와 유형만으로는 부족했다. 실제로 모델이 어려워한 건 다른 축이었다.
| 문서 특징 | 왜 어려운가 |
|---|---|
| 표 테두리가 없는 문서 | 어디까지가 한 행인지 시각적 단서가 약함 |
| 리스트가 길게 나열되는 문서 | 같은 필드가 반복되어 행 구분이 필요 |
| 리스트 안의 값이 묶여 있는 문서 | 한 행 안에서 여러 필드가 붙어 나옴 |
처음에는 좌표 간격으로 행을 구분하는 후처리 코드를 직접 썼다. X와 Y 좌표의 간격이 일정 이상이면 다른 행으로 보는 방식이다. 이 방식은 양식이 바뀔 때마다 규칙을 다시 정해야 했다.
B-I-O 태깅으로 넘기기
그래서 NER의 표준 방식인 B-I-O 태깅을 도입했다.
B(Begin) : 필드의 첫 단어 / 새로운 행의 시작
I(Inside) : 같은 필드의 연속 단어
O(Outside) : 관심 없는 텍스트
행 경계를 모델이 직접 학습해서 출력하게 만든 것이다. 후처리 규칙 없이 모델 출력만으로 표의 행 구분이 가능해졌고, 요금 항목 표와 품목 리스트 표 모두 정상 동작을 확인했다.
라벨이 33개에서 89개로 늘어나는 변경이었는데, 성능은 유지됐다.

| 학습 문서 수 | F1 | 비고 |
|---|---|---|
| 40건 | 0.954 | SageMaker GPU 기준 학습 약 4분(10 epoch) |
| 103건 | 0.889 | |
| 184건 | 0.956 | |
| 184건 + 40건 | 0.954 | B-I-O 적용, 라벨 33 → 89개 |
라벨 수가 두 배 이상 늘면 보통 성능이 떨어진다. 그럼에도 유지됐다는 건, 모델이 행 경계라는 패턴을 실제로 학습했다는 뜻으로 읽었다.
한 가지 더 눈에 띄는 건 103건 구간이다. 40건(0.954)보다 데이터가 늘었는데 F1은 오히려 떨어졌다(0.889). 학습량이 늘면 성능이 오른다는 기대와 어긋나는 지점이다. 이때 의심할 것은 모델이 아니라 무엇이 늘었고 무엇으로 쟀는가다. 추가된 문서가 특정 양식에 쏠렸거나, 평가에 쓴 문서 구성이 달라졌을 수 있다.
이 구간을 설명하지 못한 게 이후 작업의 계기가 됐다. 라운드마다 평가 데이터가 달라지면 성능 변화가 데이터 탓인지 모델 탓인지 영영 구분할 수 없다. 고정 벤치마크를 만들게 된 배경이 여기에 있다(평가 지표와 고정 벤치마크).
새 양식은 몇 건이면 되는가
테스트를 조합으로 돌린 덕에 실무에 바로 쓰이는 답도 얻었다. 새 양식이 들어오면 몇 건을 학습시켜야 하는가이다.
- 소량(40~70건) 추가 학습으로 유의미한 성능 확보 가능
- 어떤 도메인은 20건 학습만으로 F1 0.44 → 0.97
- 다만 완전히 새로운 개념의 필드가 등장하면 더 많은 데이터가 필요
이 감각이 생기니 영업이나 기획 쪽 질문에 답할 수 있게 됐다. "이 고객사 양식도 되나요?"에 "샘플 몇 건 주시면 며칠 안에 확인해 드린다"로 답하는 식이다.
한계도 기록했다
테스트 보고서에는 안 된 것도 같이 적었다.
| 항목 | 상태 |
|---|---|
| 한글 문서 OCR 추출 | 완료 |
| 한글 문서 분류 모델 학습 | 당시 미진행 |
| OCR 수정본의 학습 반영 | 구현 완료, 실제 학습 테스트 필요 |
| 공개 데이터셋 활용 | 필드 라벨이 없어 분류 학습에는 직접 사용 불가 |
특히 마지막 항목은 기대가 컸던 부분이라 아쉬웠다. 공개 데이터에 상업송장 이미지 약 17,000장과 좌표·텍스트 라벨은 있었지만, 필드별 Key:Value 라벨이 없었다. 분류 모델 학습에 쓰려면 라벨링 작업이 따로 필요했다.
배운 점
정확도를 하나의 숫자로 말하지 않게 됐다. "F1 97%"라고 적으면 듣는 사람은 모든 문서에서 97%라고 이해한다. 어떤 문서 유형, 어떤 언어, 어떤 입력 경로에서 잰 값인지 함께 적는 습관이 여기서 생겼다.
후처리 규칙으로 버티는 데는 한계가 있었다. 좌표 간격으로 행을 나누는 코드는 그 순간에는 잘 동작한다. 문제는 양식이 하나 늘어날 때마다 조건문이 하나씩 붙는다는 것이다. 모델이 배울 수 있는 형태(B-I-O)로 문제를 바꾸고 나서야 규칙 관리에서 벗어났다.
안 된 것을 적어두는 게 생각보다 중요했다. 테스트 보고서에 "미진행", "확인 필요"를 남겨두니 다음 라운드의 할 일 목록이 자동으로 만들어졌다. 잘된 것만 적었다면 몇 달 뒤에 같은 걸 다시 조사했을 것이다.
나눠서 재는 습관은 다른 영역에서도 그대로 쓰인다. 평균 하나로는 어디가 약한지 알 수 없다는 건 인프라 지표에서도 같았다. Replica SQL 적용 밀릴 때에서 평균 지연은 0인데 특정 시간대만 밀렸던 것처럼, 문제는 늘 평균 뒤에 숨는다. 무엇을 기준으로 쪼갤지 정하는 일이 측정 설계의 절반이다.
이어 읽기
- 무엇으로 잘한다고 말할 것인가: 평가 지표와 고정 벤치마크: 테스트 결과를 비교 가능하게 만드는 기준
- Document AI 성능 측정 체계 구축: OCR부터 MLflow까지: 측정 체계 전체의 밑그림
- B-SUPPLIER_NAME은 누가 봐야 하나: BIO 태깅의 경계를 정한 기록: 도입한 BIO를 서비스 전체에서 어디까지 드러낼지 정한 후속 기록