🏠

OCR 파인튜닝 입문 — 일본어 부동산 계약서를 읽는 내 모델 만들기

TrOCR + Trainer.train() — 감정 분석 파인튜닝과 완전히 같은 패턴이다

감정 분석 vs OCR — 구조가 같다

이전에 배운 감정 분석 파인튜닝:

data: 텍스트 + 라벨 ("I love this" → positive)
model: DistilBERT
trainer.train() → 내 모델

OCR 파인튜닝:

data: 이미지 + 정답 텍스트 (계약서 사진 → "甲は乙に対して...")
model: TrOCR
trainer.train() → 내 모델

input이 텍스트에서 이미지로 바뀌었을 뿐. Trainer.train()은 동일하다.

왜 TrOCR이 가장 쉬운가

OCR 모델 파인튜닝 난이도 이유
TrOCR ★★☆☆☆ HuggingFace Trainer 그대로. 공식 튜토리얼 있음
EasyOCR ★★★★☆ 자체 훈련 파이프라인. HuggingFace 비호환
PaddleOCR ★★★☆☆ PaddlePaddle 프레임워크 필요. PyTorch 아님
Donut ★★★☆☆ 가능하지만 문서 이해 모델이라 구조가 다름
manga-ocr ★★☆☆☆ TrOCR과 같은 아키텍처 (VisionEncoderDecoder)

TrOCR은 VisionEncoderDecoderModel이다:

이미지 → ViT(인코더) → 벡터 → RoBERTa(디코더) → 텍스트

이게 HuggingFace의 표준 모델이라 Trainer와 바로 호환된다.

시나리오: 일본어 부동산 계약서 OCR

일반 OCR(EasyOCR, Tesseract)로 일본어 부동산 계약서를 돌리면:

  • "甲" → "申" (비슷한 한자 오인식)

  • "賃貸借" → "賃貸偕" (부동산 전문 용어 오류)

  • "㎡" → "m2" (특수 단위 못 읽음)

  • 세로쓰기 항목이 가로로 섞임

파인튜닝으로 "부동산 계약서에서 자주 나오는 글자·용어"를 가르치면 이런 오류가 줄어든다.

Step 1 — 데이터 준비

파인튜닝에는 이미지 + 정답 텍스트 쌍이 필요하다.

data/
├── images/
│   ├── line_001.png   ← 계약서에서 잘라낸 텍스트 한 줄 이미지
│   ├── line_002.png
│   └── ...
└── labels.csv
    line_001.png, 甲は乙に対して本物件を賃貸する
    line_002.png, 賃料は月額150,000円とする
    line_003.png, 契約期間は2年間とする
    ...

데이터를 어떻게 모으나:

  • 실제 계약서 스캔 → 줄 단위로 크롭 → 직접 텍스트 입력 (가장 정확)

  • 부동산 용어로 합성 이미지 생성 (PIL로 텍스트를 이미지로 렌더링)

  • 기존 OCR로 1차 인식 → 사람이 교정 (반자동)

합성 데이터 생성 예시:

from PIL import Image, ImageDraw, ImageFont
import csv
import os

# 부동산 계약서에서 자주 나오는 문장
sentences = [
    "甲は乙に対して本物件を賃貸する",
    "賃料は月額150,000円とする",
    "契約期間は2年間とする",
    "敷金は賃料の2ヶ月分とする",
    "物件所在地:東京都渋谷区",
    "専有面積:45.5㎡",
    "構造:鉄筋コンクリート造",
    "築年数:15年",
    "重要事項説明書の交付を受けた",
    "連帯保証人:丙",
    # ... 수백 개 추가
]

os.makedirs("data/images", exist_ok=True)
font = ImageFont.truetype("/path/to/japanese_font.ttf", 32)

with open("data/labels.csv", "w") as f:
    writer = csv.writer(f)
    for i, text in enumerate(sentences):
        img = Image.new("RGB", (800, 60), "white")
        draw = ImageDraw.Draw(img)
        draw.text((10, 10), text, fill="black", font=font)
        filename = f"line_{i:04d}.png"
        img.save(f"data/images/{filename}")
        writer.writerow([filename, text])

Step 2 — Dataset 클래스 만들기

import torch
from torch.utils.data import Dataset
from PIL import Image
import csv

class ContractOCRDataset(Dataset):
    def __init__(self, csv_path, image_dir, processor):
        self.image_dir = image_dir
        self.processor = processor
        self.data = []
        with open(csv_path) as f:
            for row in csv.reader(f):
                self.data.append((row[0], row[1]))  # (filename, text)

    def __len__(self):
        return len(self.data)

    def __getitem__(self, idx):
        filename, text = self.data[idx]
        image = Image.open(f"{self.image_dir}/{filename}").convert("RGB")

        # 이미지 → 픽셀 텐서 (전처리)
        pixel_values = self.processor(images=image, return_tensors="pt").pixel_values.squeeze()

        # 텍스트 → 토큰 ID (정답 라벨)
        labels = self.processor.tokenizer(
            text, padding="max_length", max_length=64, truncation=True
        ).input_ids
        # -100은 loss 계산에서 무시됨 (패딩 부분)
        labels = [l if l != self.processor.tokenizer.pad_token_id else -100 for l in labels]

        return {
            "pixel_values": pixel_values,
            "labels": torch.tensor(labels)
        }

이 Dataset이 하는 일:

line_001.png + "甲は乙に対して本物件を賃貸する"
  ↓
pixel_values: [3, 384, 384] 이미지 텐서
labels: [43521, 234, 8832, ...] 토큰 ID 배열

감정 분석에서 tokenizer("I love this") → [101, 1045, ...]했던 것과 동일. 이미지 전처리가 추가된 것뿐.

Step 3 — 파인튜닝

from transformers import (
    TrOCRProcessor,
    VisionEncoderDecoderModel,
    Seq2SeqTrainer,
    Seq2SeqTrainingArguments
)

# 1. 베이스 모델 + 프로세서 로드
processor = TrOCRProcessor.from_pretrained("microsoft/trocr-base-printed")
model = VisionEncoderDecoderModel.from_pretrained("microsoft/trocr-base-printed")

# 일본어 토크나이저로 디코더 교체 (영어 모델이라 필요)
# 또는 일본어 TrOCR 모델이 있으면 그걸 사용

# 2. 데이터셋
train_dataset = ContractOCRDataset("data/labels.csv", "data/images", processor)

# 3. 훈련 설정
training_args = Seq2SeqTrainingArguments(
    output_dir="./contract-ocr-model",
    num_train_epochs=10,
    per_device_train_batch_size=8,
    learning_rate=5e-5,
    predict_with_generate=True,  # OCR은 생성 태스크
    fp16=True,                   # GPU 메모리 절약
    save_steps=500,
    logging_steps=100,
)

# 4. 훈련 — ★ 이 한 줄이 파인튜닝 ★
trainer = Seq2SeqTrainer(
    model=model,
    args=training_args,
    train_dataset=train_dataset,
)
trainer.train()

# 5. 저장
trainer.save_model("./contract-ocr-model")
processor.save_pretrained("./contract-ocr-model")

감정 분석 파인튜닝과 비교:

감정 분석 OCR
데이터 텍스트 + 라벨 이미지 + 텍스트
모델 AutoModelForSequenceClassification VisionEncoderDecoderModel
Trainer Trainer Seq2SeqTrainer
출력 분류 (긍정/부정) 생성 (텍스트)
trainer.train() 동일 동일

TrainerSeq2SeqTrainer로 바뀐 것만 다르다. 분류가 아니라 텍스트 생성이니까.

Step 4 — 내 모델로 추론

from transformers import TrOCRProcessor, VisionEncoderDecoderModel
from PIL import Image

# 내가 훈련한 모델 로드
processor = TrOCRProcessor.from_pretrained("./contract-ocr-model")
model = VisionEncoderDecoderModel.from_pretrained("./contract-ocr-model")

# 새 계약서 이미지 인식
image = Image.open("new_contract_line.png").convert("RGB")
pixel_values = processor(images=image, return_tensors="pt").pixel_values
generated_ids = model.generate(pixel_values)
text = processor.batch_decode(generated_ids, skip_special_tokens=True)[0]
print(text)
# → "賃料は月額150,000円とする"  (파인튜닝 전보다 정확)

전체 흐름 정리

[데이터 준비]
계약서 스캔 → 줄 단위 크롭 → line_001.png + "甲は乙に..." 쌍 수백 개

[파인튜닝]
TrOCR(영어 이해) + 계약서 데이터 → trainer.train() → 내 모델(부동산 용어 이해)

[추론]
새 계약서 이미지 → 내 모델 → "賃料は月額150,000円とする"

감정 분석에서 했던 것과 구조가 완전히 같다:

감정 분석: DistilBERT + IMDB      → train() → 내 감정 분류 모델
OCR:      TrOCR     + 계약서 이미지 → train() → 내 계약서 OCR 모델

더 쉬운 대안: manga-ocr 파인튜닝

manga-ocr도 TrOCR과 같은 VisionEncoderDecoderModel이고, 이미 일본어를 이해한다. 영어 TrOCR을 일본어에 맞추는 것보다 manga-ocr을 계약서에 맞추는 게 더 효율적일 수 있다.

# manga-ocr의 베이스 모델을 로드해서 파인튜닝
processor = TrOCRProcessor.from_pretrained("kha-white/manga-ocr-base")
model = VisionEncoderDecoderModel.from_pretrained("kha-white/manga-ocr-base")
# 이후는 위와 동일: Dataset + Seq2SeqTrainer + train()

manga-ocr:        만화 일본어를 이해하는 모델
  ↓ 계약서 데이터로 파인튜닝
내 모델:          부동산 계약서 일본어를 이해하는 모델

이미 일본어를 이해하는 모델(manga-ocr)에 부동산 용어를 추가로 가르치는 것이므로, 영어 모델(TrOCR)에서 시작하는 것보다 데이터가 적어도 된다.

필요한 데이터 양

전략 필요 데이터 이유
영어 TrOCR → 일본어 계약서 수만 개 일본어 자체를 배워야 함
manga-ocr → 계약서 수백~수천 개 일본어는 이미 앎. 용어만 추가
Sarashina2.2 → 계약서 수백 개 일본어 문서 최고 모델 베이스

베이스 모델이 이미 타겟 언어를 알수록 파인튜닝에 필요한 데이터가 줄어든다. 대학 졸업생에게 OJT를 시키는 것과 중학생에게 가르치는 것의 차이.

핵심 개념

1

データ準備 — 계약서 이미지 줄 단위 크롭 + 정답 텍스트 CSV

2

Dataset 클래스 — 이미지 → pixel_values, 텍스트 → labels (토큰 ID)

3

TrOCR 로드 — VisionEncoderDecoderModel.from_pretrained()

4

Seq2SeqTrainer.train() — 감정 분석의 Trainer.train()과 같은 것

5

내 모델로 추론 — 새 계약서 이미지 → 정확한 텍스트

사용 사례

부동산 계약서 OCR — 甲/乙, 賃貸借, ㎡ 같은 전문 용어 인식 정확도 향상 의료 문서 OCR — 처방전, 진단서의 의학 용어 특화 고문서 디지털화 — 특정 시대/서체의 문서에 특화