[LLM] VLM으로 표를 정확히 추출하기
Bedrock의 Qwen3-VL 모델로 PDF 테이블을 추출할 때 rowspan/colspan 병합 셀 처리 오류를 프롬프트 개선으로 해결하는 방법을 소개합니다.
개요
RAG 파이프라인을 구축하기 위해 PDF 추출을 테스트했습니다.
Bedrock 모델을 사용하다보니 Claude Haiku 4.5를 주로 사용했는데, Claude 모델의 경우 이미지 제한 등으로 인해 표, 이미지를 OCR하는 성능이 예상보다 좋지 않습니다.
이를 위해 이미지와 비디오를 이해하는데 장점이 있는 Bedrock Qwen3-VL-235B-A22B을 사용하여 텍스트와 표를 정확히 추출하는 방법에 대해 정리했습니다.
PDF 표 추출의 문제점
VLM에게 표 이미지를 던져주면 대부분은 HTML로 잘 추출해줍니다.
따라서 다음과 같은 파이프라인을 구성하게 됩니다.
- PDF → PyMuPDF(페이지 이미지) → Bedrock API → Qwen3-VL 235B → Markdown + HTML 테이블
VLM은 파이프라인 중 텍스트 추출 뿐만 아니라 복잡한 병합 셀을 통째로 이해하니 이 문제를 우회할 수 있습니다. 하지만 300개 이상의 테이블을 추출해봤을 때 대부분 잘 동작했지만 rowspan과 colspan이 뒤섞인 복잡한 병합 테이블에서 문제가 발생했습니다.
깨진 테이블의 증상

<table>
<tr>
<th rowspan="2">구분</th>
<th colspan="2">시험</th>
<th rowspan="2">치수</th>
<th colspan="4">판정</th>
</tr>
<tr>
<th>A</th><th>B</th><th>C</th><th>D</th>
</tr>
</table>
Row 1은 정상이다. 문제는 Row 2다. 시험(colspan=2) 아래에 두 셀이 와야 하는데 통째로 빠졌습니다. 6개 셀이 필요하지만 4개만 출력되다보니 브라우저에서 렌더링하면 열이 밀리면서 테이블 전체가 무너졌습니다.
tbody에서도 빈 셀(<td></td>)을 생략하는 바람에 에러가 발생했습니다. 가장 까다로운 점은 에러가 없다는 것입니다. HTML 문법은 유효하고, 구조도 그럴듯하니 이후 문서를 사람이 직접 평가하지 않는 이상 에러를 발견하기 어려웠습니다.
테이블 구조 테스트
temperature, 프롬프트 등을 테스트하며 추론, 이해의 문제라는 것을 파악했습니다.
프롬프트로 테이블을 정확하게 인식하지만 그 결과를 생성에 반영하지 않았다. 프롬프트를 아무리 장황하게 써봐야 인식될 뿐 추출되지 않는다는 것을 확인했습니다.
VLM은 왜 테이블 구조를 틀리나
디버깅 과정에서 모델의 더 정확한 동작을 확인했습니다. CHiTab(arXiv 2511.08298)은 , 최신 VLM도 계층적 헤더 인식 정확도가 약 44%에 불과하며 OmniDocBench(CVPR 2025, arXiv 2412.07626)에서는 VLM의 테이블 변환에서 가장 빈번한 실패 패턴 1위로 빈 셀 생략을 확인했습니다.
정리하면 VLM은 표의 내용같은 의미적 맥락은 이해하지만 테이블 구조의 일관성 등 시각적인 정보는 스스로 검증하지 않는다는 점 입니다. 이러한 구성에서 rowspan/colspan을 통한 테이블 추출지시는 VLM에서 맥락으로 인식될 뿐 구조적 정확성을 보장할 수 없었습니다.
VLM OCR 프롬프트 개선
초기 프롬프트에서 테이블 관련 지시가 단순히 rowspan/colspan 규칙이었다면
- Use rowspan/colspan for merged cells.
2가지 규칙을 추가하면 됩니다.
- Multi-row headers: When thead has 2+ rows, first determine
the total column count from the widest data row. Then ensure
each header row accounts for exactly that many columns
(counting colspan as multiple, skipping cells covered by
rowspan from above). If a cell has colspan=N, the next header
row MUST have exactly N sub-header <th> cells in the
corresponding positions. Never omit sub-headers even if
labels are unclear from the image.
- Every <tr> in tbody must also account for exactly the total
column count. Include empty <td></td> for blank cells.
내용을 정리하면 총 3가지인데 산술 제약을 추가하며 추론이 가능하게 구성하는 것입니다. 열 수 = colspan 합이라는 등식을 제공하여 VLM이 생성 중에 자체 검증할 수 있는 규칙을 추가합니다. 또한 MUST, exactly같은 명령형 키워드가 VLM에서 효과적이라는 점은 CHiTab 연구에서도 제안하는 패턴 중 하나입니다. 마지막으로 빈 셀 생략이라는 알려진 실패 모드를 Include empty <td></td>로 직접 차단하도록 구성합니다.
교정된 출력은 다음과 같습니다.

<thead>
<tr>
<th rowspan="2">구분</th>
<th colspan="2">강도 시험</th>
<th rowspan="2">치수</th>
<th colspan="4">적합성 판정</th>
</tr>
<tr>
<th>압축</th><th>인장</th>
<th>A</th><th>B</th><th>C</th><th>D</th>
</tr>
</thead>
Row 2에 정확히 6개 셀이 들어가고 colspan=2 아래에 압축, 인장 2개, colspan=4 아래에 A, B, C, D 4개 들어갑니다. rowspan으로 위에서 내려오는 구분과 치수를 빼면 정확히 8열이 될 수 있습니다.
정리
복잡한 테이블에서 단순 HTML로 추출하는 것만으로는 정확한 추출이 어려울 수 있습니다. 이러한 문제는 VLM이 표를 의미적 맥락으로 이해하며 시각/구조적인 표로써 인식하지 않기 때문입니다.
이를 해결하기 위해 다음과 같이 정리했습니다.
- 표는 열 수가 정확히 일치해야한다.
- VLM은 숫자를 계산할 수 있기 때문에 정확히라는 단어와 셀 수 있는 규칙을 설정해야한다.
- 실패 패턴(빈 셀을 생략하지 말 것)을 정확히 구조화해야한다
VLM이라도 사람의 구조적, 시각적인 인식보다 이미지 자체를 텍스트로써 의미적으로 추론한다는 것을 다시 확인했습니다. 이를 인지하고 이후 추출을 정확히 할 수 있는 여러가지 방법들을 확인해볼 예정입니다.