← 목록

정확도 99.97%인데 쓸 수 없던 모델: 한글 OCR 파인튜닝 6라운드

한글 OCR이 쉼표와 소수점을 읽지 못하는 문제를 해결하기 위해 Recognition 모델을 여섯 번 파인튜닝한 기록이다. 정확도 99.97%를 기록하고도 폐기해야 했던 버전의 이유를 정리한다.

2026-05-20AIOCRPaddleOCRTroubleshooting

들어가며

OCR 단계는 영문 문서에서는 기본 모델로 충분했다. 파인튜닝 없이도 숫자와 기호를 잘 읽었기 때문에, 한국어도 비슷할 거라고 생각했다.

그런데 한글 문서에서는 글자는 잘 읽히는데 숫자 주변이 계속 틀렸다.

정답: 7306.40      →  출력: 7306%0
정답: 12,500.00 KG →  출력: 12 500.00 &

금액과 단위가 틀리면 그 뒤의 필드 분류가 아무리 정확해도 결과는 쓸 수 없다. OCR과 NER을 나눈 구조에서 앞 단계의 오류는 뒤로 그대로 전파되기 때문이다.

이 문제를 잡기 위해 Recognition 모델을 여섯 번 파인튜닝했고, 그 과정에서 정확도가 더 높은데도 폐기해야 하는 버전을 두 번 만났다.


여섯 번의 라운드

한글 OCR 파인튜닝 버전 이력

버전학습 데이터설정결과사용 여부
베이스--한글은 인식, 쉼표·소수점 실패-
v1합성 crop 6,480건ep.100acc 96.8%❌ 무효
v2실제 crop 4,673건ep.50, 161분acc 99.97%❌ 무효
v3실제 crop 28,206건ep.20, 64분acc 99.74%✅
v4동일 28,206건ep.20, 약 80분쉼표 미인식❌
v5동일 28,206건ep.50, 135분쉼표 인식 성공✅
v6약 45,700건ep.20, 약 100분한글 인식 강화✅ 배포

정확도만 보면 v2(99.97%)가 가장 좋다. 그런데 v2는 쓸 수 없었다.


v1: 학습이 돌지 않았다

첫 라운드는 합성 데이터로 시작했다. 정답 텍스트를 이미지로 렌더링해 crop을 만드는 방식이다.

결과는 acc 96.8%였지만, 로그를 보니 학습 자체가 진행되지 않았다. 라벨 파일의 줄 끝에 \r이 남아 있어 경로 매칭이 전부 실패했고, 모델은 사실상 베이스 그대로였다. 측정된 96.8%는 베이스 모델의 점수였던 셈이다.

학습 스크립트가 에러 없이 끝났다고 해서 학습이 된 것은 아니다. loss 곡선이 움직였는지부터 확인해야 한다.


v2: 사전에 쉼표가 없었다

두 번째는 실제 문서에서 잘라낸 crop 4,673건으로 학습했다. acc 99.97%. 숫자만 보면 끝난 것처럼 보였다.

그런데 실제 출력에는 여전히 쉼표가 없었다. 원인은 학습이 아니라 문자 사전이었다.

korean_dict.txt  ← 모델이 출력할 수 있는 문자 목록
  가
  나
  ...
  (쉼표 없음)

PaddleOCR의 Recognition은 이 사전에 있는 문자만 출력할 수 있다. 사전에 ,가 없으면 모델이 아무리 학습해도 쉼표를 출력할 방법이 없다. 그리고 평가 데이터에도 쉼표가 없으니, 정확도는 오히려 깨끗하게 99.97%가 나왔다.

측정값이 높았던 이유가 곧 문제의 원인이었다.

사전에 , ₩ .를 추가하고 다시 학습한 것이 v3이다. 데이터도 28,206건으로 늘렸고, 쉼표 인식에 성공했다.


v4~v5: 학습 환경과 배포 환경이 달랐다

v3은 image_shape=[3,48,320]으로 학습했다. 그런데 서버에서 로딩하자 에러가 났다.

학습(SageMaker, PaddlePaddle 3.0):  image_shape = [3, 48, 320]
배포(서버, PaddleX):                image_shape = [3, 32, 320]  ← 강제 적용
→ ReshapeOp 에러

배포 환경이 입력 크기를 32px로 강제하는데, 모델은 48px로 학습된 상태였다. 급한 대로 PaddleX를 우회해 Paddle Inference를 직접 호출하는 방식으로 v3을 띄웠지만, 임시방편이었다.

그래서 32px로 다시 학습한 것이 v4이다. 이번엔 쉼표를 인식하지 못했다. 입력 해상도가 줄면 같은 글자가 더 작게 들어오니, 쉼표처럼 작은 기호는 학습이 더 어렵다. epoch 20으로는 부족했던 것이다.

epoch를 50으로 올린 v5에서 32px + 쉼표 인식을 모두 만족했다.


v6: 데이터를 열 배로

마지막 라운드는 폰트와 크기를 다양화해 약 45,700건으로 학습했다. 현재 배포 버전이다.

학습 데이터 증가 추이

4,673건에서 45,700건까지 열 배 가까이 늘어났는데, 이건 처음부터 계획한 규모가 아니었다. 라운드를 돌면서 무엇이 부족한지 하나씩 알게 된 결과이다.


라운드마다 추가된 확인 절차

무효가 된 두 라운드는 시간을 버렸지만, 대신 학습 전후에 무엇을 확인해야 하는지를 알려줬다. 지금은 새 모델을 학습할 때 이 순서로 확인한다.

학습 전

학습 중

학습 후

세 번째 묶음이 가장 중요하다. 이 목록의 절반은 숫자가 아니라 눈으로 확인하는 항목이다. v2의 99.97%는 샘플 몇 개만 열어봤어도 바로 드러났을 문제였다.


정리

라운드배운 것
v1학습이 실제로 돌았는지부터 확인한다
v2모델이 출력할 수 있는 문자 집합(사전)을 먼저 본다
v3데이터를 늘리는 것보다 출력 가능 범위를 여는 게 먼저다
v4배포 환경의 입력 규격을 학습 전에 맞춘다
v5해상도를 줄이면 작은 기호는 더 많은 epoch가 필요하다
v6폰트·크기 다양성이 실문서 대응력을 만든다

배운 점

정확도 숫자를 믿지 않게 됐다. v2의 99.97%는 거짓이 아니었다. 다만 그 시험지에 쉼표가 없었을 뿐이다. 지표는 "무엇을 측정했는가"와 함께 읽어야 한다는 걸 이때 배웠다. 이후로는 수치를 받으면 실제 출력 샘플을 먼저 열어본다.

문제가 학습 밖에 있을 수 있다는 것도 배웠다. 쉼표를 못 읽는 문제를 계속 "학습이 부족하다"로 생각했는데, 실제 원인은 사전 파일 한 줄이었다. 데이터를 더 넣는 건 언제나 그럴듯한 해법처럼 보이지만, 그 전에 구조적으로 불가능한 조건이 있는지 봐야 했다.

학습 환경과 배포 환경을 맞추는 일은 미룰수록 비싸진다. v3을 우회 방식으로 띄운 뒤 v4, v5로 두 라운드를 더 쓴 것은, 배포 환경의 입력 규격을 학습 전에 확인했다면 없었을 비용이다. 지금은 새 모델을 학습하기 전에 배포 쪽 제약부터 확인한다.

모델을 바꿀 때마다 판단 기준이 있었던 게 컸다. 정확도 99.97%를 보고도 쓰지 않을 수 있었던 건, 무엇을 기준으로 합격을 판정할지 미리 정해뒀기 때문이다. 그 기준을 세운 과정은 Document AI 성능 측정 체계 구축에, 고정 데이터로 라운드를 비교한 방식은 평가 지표와 고정 벤치마크에 정리했다.


이어 읽기