Unlimited OCR 解读

周末,我刷了刷Twitter,想看看AI社区发生了什么。百度再次引起了全球关注。

6月25日——百度在6月22日发布了其Unlimited OCR模型,该模型总参数量为30亿,但推理时仅激活5亿参数。该模型旨在解决端到端OCR模型在处理长文档时速度越来越慢的问题。

最引人注目的特性是,在标准的32K最大上下文长度下,它首次让OCR模型能够一次性读取整本书。

请注意,这不是逐页处理、不是for循环任务拆分、也不依赖外部调度器来拼接结果。相反,它是一种真正的前向推理,直接完成数十页文档的解析。

更令人印象深刻的是,它不仅实现了这一点,而且做得非常出色。在主流文档解析基准OmniDocBench v1.5上,Unlimited OCR达到了最先进的93.23%分数,比DeepSeek OCR高出整整6个百分点。

1、为什么所有模型都在经历"逐页失忆症"?

说到OCR,当前的OCR模型仍然出奇地糟糕。

它们将一个长而连续的任务拆分成数十个不相关的小任务,然后依赖外部调度器勉强将结果拼接在一起。这就像运行一个for循环,处理一页,清除内存,然后从头开始处理下一页。

这种方式虽然可行,但本质上只是工程上的权宜之计,距离真正的智能还很遥远。

其原因是,随着输出变长,标准注意力机制下的KV缓存呈指数级增长——内存跟不上,速度越来越慢。

这是迫使所有模型逐页处理并频繁"遗忘"的主要原因。

但人类从未以这种方式抄写书籍。

我们保持着持续的认知状态——我们的眼睛固定在三个点上:原始书籍、我们刚刚写下的短段落,以及我们即将写下的下一个词。

较早的内容逐渐从我的脑海中淡出,而最近的上下文则用于跟踪当前进度。

这种能力有一个美妙的名字:"软遗忘"。

正是这种"遗忘该遗忘的"能力,让人们能够以非常低的认知负荷承受极其漫长的任务。例如,抄写一本书、翻译数百页的内容,或连续转录数小时的音频。

百度想要做的,就是将"看到整个原文但只记住最近几行"的人类注意力模式融入到其模型中,使OCR告别失忆症。

那么,让我快速演示一下实时聊天机器人,展示一切是如何工作的。

今天,我将对Unlimited OCR进行四项关键挑战的测试:公式识别、表格识别、阅读顺序和手写文本。

我们先从公式识别开始。我上传了一张包含复杂数学公式的图片。如您所见,模型处理得非常好——准确解释了上标、下标,甚至非常长且复杂的表达式。

接下来是表格识别。这是一个众所周知的难题,表格类型繁多,有时有边框有时没有,包含大量数字,模型很容易误判。我在几个表格示例上使用了Unlimited OCR,发现其准确率确实令人印象深刻。

另一个重大挑战是理解文档结构和阅读顺序。在现代文档中,内容不仅更加复杂,而且布局高度多样化。试想多列设计、图文混合、折页、彩印、倾斜扫描和手写注释——所有这些都使OCR变得复杂。正确的阅读顺序并不总是简单的自上而下、从左到右的流程。

Unlimited OCR的技术报告展示了该模型如何像人类一样理解这些复杂结构。无论是学术论文、多列报纸还是技术报告,它都能智能分析布局并恢复符合人类直觉的阅读顺序。

最后,在我的测试中,Unlimited OCR在处理手写内容时遇到了困难。虽然提取过程很快,但准确率明显低于预期。对于包含文本、数字、段落、图像和多列布局等混合元素的手写笔记,模型经常遗漏内容、生成错误文本,并且难以保留原始结构。

因此,输出结果需要大量手动修正,对于复杂的手写文档来说不够可靠。

2、Unlimited OCR有何独特之处?

要理解这个模型为何值得关注,我们需要看一下基于AI的文档系统的典型流程。

PDF文件到达后,OCR引擎逐页处理,第二步处理将各页重构为单一文本,然后将其索引到向量数据库中,使其可用于RAG。

每个阶段都会增加延迟和故障点:跨两页的表格、缺失的标题、错误的阅读顺序。

Unlimited-OCR的方法转变了视角。该模型将整个文档保持为上下文,但在文本生成期间,它只向前移动一个已生成文本的滑动窗口。

这就是关键区别。在传统的解码器中,工作内存随着每个输出token线性增长,这成为超长文档的瓶颈。

通过保持已生成文本窗口恒定,分析长文档变成了从头到尾的连续阅读,而不是重构过程。

对于构建文档流水线的人来说,其影响是直接的:更少的预处理、更少的拼接代码,以及传递给RAG的更一致的文本。

3、Unlimited OCR vs DeepSeek OCR

当我们谈到这一点时,我们回过头来审视了DeepSeek OCR,发现两者似乎专注于同一技术路线的两个不同方面。

DeepSeek OCR解决的是输入侧问题——如何将高分辨率文档压缩成尽可能少的视觉token。Unlimited OCR解决的是输出侧问题——如何防止模型在长文本生成过程中被不断膨胀的键值缓存所淹没。

一个发生在编码端,另一个发生在解码端。单独来看,两者都是有效的,但综合来看,它们惊人地相互关联。

更有趣的是,Unlimited OCR的技术报告相当频繁地讨论DeepSeek OCR,足足有40次。在许多地方,它读起来不像典型的竞争对手比较,而更像是延续了其思路并向前推进。

至于为什么会给人这种感觉,我们有一个大胆的猜测。

4、开始编码

现在让我们一步一步地探索,揭开构建一个强大推理应用的答案。我们将安装支持该模型的库。

pip install torch torchvision transformers pillow einops addict easydict pymupdf psutil langchain langchain-community langchain-openai faiss-gpu sentence-transformers openai

下一步是常规操作:我们将导入相关库,其重要性将随着我们的进展而变得明显,并进行一些基本配置。

Unlimited-OCR:将文档和图像转换为结构化的、AI友好的数据(如JSON和Markdown),具有行业领先的精度——为AI应用提供动力。

import os
import torch
import tempfile
import fitz  # PyMuPDF
from transformers import AutoModel, AutoTokenizer
from langchain.text_splitter import RecursiveCharacterTextSplitter
from langchain_community.vectorstores import FAISS
from langchain_community.embeddings import HuggingFaceEmbeddings
from langchain_openai import ChatOpenAI
from langchain.chains import RetrievalQA
from langchain.docstore.document import Document

所以我构建了这个SimpleRAG系统,它结合了Unlimited-OCR进行文本提取和OpenAI生成查询。让我带你了解我开发的内容。

在初始化中,我设置了核心组件——我使用HuggingFace的BGE嵌入进行向量表示,使用GPT-4o作为聊天模型,温度设置为零以获得一致的响应。我为稍后构建的向量存储和QA链初始化了占位符。

然后我开始从HuggingFace加载Unlimited-OCR模型和分词器到GPU内存。

当我调用extract_text_from_images()时,我提供一个图像或PDF文件列表。系统逐个处理每个文件。

如果是PDF文件,我首先将每一页转换为图像(PNG)并保存在临时文件夹中。然后,我将所有页面发送给Unlimited-OCR,以便它读取整个文档的文本。

如果文件已经是图像,我直接将其发送给Unlimited-OCR。这种模式适用于具有复杂布局、表格和表单的文档。

OCR完成后,模型将提取的文本保存到临时文本或markdown文件中。然后我读取这些文件,将所有文本合并成一个长字符串。

最后,我将文本存储为LangChain文档,并将原始文件路径保存为元数据。这有助于我在以后搜索或提问时知道文本来自哪个文档。

class SimpleRAG:
    def __init__(self):
        self.embeddings = HuggingFaceEmbeddings(model_name="BAAI/bge-small-en-v1.5")
        self.llm = ChatOpenAI(model="gpt-4o", temperature=0)
        self.vectorstore = None
        self.qa_chain = None

        model_name = "baidu/Unlimited-OCR"
        self.tokenizer = AutoTokenizer.from_pretrained(model_name, trust_remote_code=True)
        self.model = AutoModel.from_pretrained(
            model_name,
            trust_remote_code=True,
            use_safetensors=True,
            torch_dtype=torch.bfloat16,
        ).eval().cuda()

    def _pdf_to_images(self, pdf_path, dpi=300):
        doc = fitz.open(pdf_path)
        tmp_dir = tempfile.mkdtemp(prefix="pdf_ocr_")
        mat = fitz.Matrix(dpi / 72, dpi / 72)
        paths = []
        for i, page in enumerate(doc):
            out = os.path.join(tmp_dir, f"page_{i+1:04d}.png")
            page.get_pixmap(matrix=mat).save(out)
            paths.append(out)
        doc.close()
        return paths

    def _read_output_text(self, output_path):
        """Read all .md or .txt files written by save_results=True."""
        texts = []
        for fname in sorted(os.listdir(output_path)):
            if fname.endswith((".md", ".txt")):
                with open(os.path.join(output_path, fname), "r", encoding="utf-8") as f:
                    texts.append(f.read())
        return "\n\n".join(texts)

    def extract_text_from_images(self, image_paths: list):
        docs = []
        for path in image_paths:
            is_pdf = path.lower().endswith(".pdf")
            tmp_out = tempfile.mkdtemp(prefix="ocr_out_")

            if is_pdf:
                pages = self._pdf_to_images(path)
                self.model.infer_multi(
                    self.tokenizer,
                    prompt="<image>Multi page parsing.",
                    image_files=pages,
                    output_path=tmp_out,
                    image_size=1024,
                    max_length=32768,
                    no_repeat_ngram_size=35,
                    ngram_window=1024,
                    save_results=True,
                )
            else:
                self.model.infer(
                    self.tokenizer,
                    prompt="<image>document parsing.",
                    image_file=path,
                    output_path=tmp_out,
                    base_size=1024,
                    image_size=640,
                    crop_mode=True,
                    max_length=32768,
                    no_repeat_ngram_size=35,
                    ngram_window=128,
                    save_results=True,
                )

            text = self._read_output_text(tmp_out)
            if not text.strip():
                text = "No text found"

            docs.append(Document(page_content=text, metadata={"source": path}))

        return docs

build_index中,我从所有图像中提取文本,使用Recursive Character TextSplitter将文档分割成1000字符的块(重叠200字符),用BGE嵌入创建FAISS向量存储,并设置一个使用GPT-4o并检索每个查询的前3个相关块的RetrievalQA链。

对于query,我只是将问题传递给QA链,它处理检索和生成,返回答案。

def build_index(self, image_paths: list):
        docs = self.extract_text_from_images(image_paths)

        text_splitter = RecursiveCharacterTextSplitter(chunk_size=1000, chunk_overlap=200)
        splits = text_splitter.split_documents(docs)

        self.vectorstore = FAISS.from_documents(splits, self.embeddings)
        self.qa_chain = RetrievalQA.from_chain_type(
            llm=self.llm,
            retriever=self.vectorstore.as_retriever(search_kwargs={"k": 3}),
        )

    def query(self, question: str):
        return self.qa_chain.invoke(question)


# Usage
rag = SimpleRAG()
rag.build_index(["your_pic.jpg"])  # also accepts .pdf
answer = rag.query("extract all the table?")
print(answer)

5、结束语

Unlimited-OCR并不是第一个基于视觉语言模型的OCR,但它以正确的方式解决了正确的问题。分析长文档长期以来一直是RAG流水线的弱点,而无需拆分和拼接即可一次性读取文档的想法消除了一类重复出现的错误。

MIT许可证、30亿参数规模和可免费下载权重的组合,使其成为任何构建文档系统的人(从自由职业者到专业公司)的具体选择。


原文链接: Unlimited OCR + RAG: Revolutionize Complex Data Extraction (Open-Source)

汇智网翻译整理,转载请标明出处