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() |
동일 | 동일 |
Trainer가 Seq2SeqTrainer로 바뀐 것만 다르다. 분류가 아니라 텍스트 생성이니까.
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를 시키는 것과 중학생에게 가르치는 것의 차이.
핵심 개념
データ準備 — 계약서 이미지 줄 단위 크롭 + 정답 텍스트 CSV
Dataset 클래스 — 이미지 → pixel_values, 텍스트 → labels (토큰 ID)
TrOCR 로드 — VisionEncoderDecoderModel.from_pretrained()
Seq2SeqTrainer.train() — 감정 분석의 Trainer.train()과 같은 것
내 모델로 추론 — 새 계약서 이미지 → 정확한 텍스트