Qwen2-VL 2B/7B で日本語 OCR 試すメモ(2B でもいい感じ!)

2024/09/02に公開

Google Colab で Qwen2-VL を試す
https://note.com/npaka/n/n6e2a00a0c0e7

npaka 先生, ありがとうございます.

2B は 8GB VRAM で, 7B は 24 GB VRAM でだいたい GPU で動くのを確認しました.

セットアップ

執筆時点では Qwen2-VL は出たばかりで, transformers ではまだ対応していません.

ImportError: cannot import name 'Qwen2VLForConditionalGeneration' from 'transformers'

そのため git repo の最新のを使います.

$ python -m pip install  git+https://github.com/huggingface/transformers

あとは torch, torchvision, qwen_vl_utils, accelerate が入っていればいけます.

OCR として使ってみる

WSL2, 128GB mem Ryzen 3900X, 3070 8GB で動かしました.

テスト画像は競売物件のをつかいました(画像スキャンの PDF) : https://www.bit.courts.go.jp/app/top/pt001/h01

npaka 先生のコードを参考にしつつこんな感じになりました.

  from PIL import Image
  import requests

  from transformers import Qwen2VLForConditionalGeneration, AutoTokenizer, AutoProcessor

  model = Qwen2VLForConditionalGeneration.from_pretrained(
      "Qwen/Qwen2-VL-7B-Instruct",
      torch_dtype="auto",
      device_map="auto"
  )
  processor = AutoProcessor.from_pretrained(
      "Qwen/Qwen2-VL-7BB-Instruct"
  )

  image = Image.open("keibai.png")

  messages = [
      {
          "role": "user",
          "content": [
              {
                  "type": "image",
              },
              {"type": "text", "text": "文章を抽出してください"},
          ],
      }
  ]

  text_prompt = processor.apply_chat_template(messages, add_generation_prompt=True)
  inputs = processor(
      text=[text_prompt],
      images=[image],
      padding=True,
      return_tensors="pt"
  )
  inputs = inputs.to("cuda")

  output_ids = model.generate(**inputs, max_new_tokens=2048)
  generated_ids = [
      output_ids[len(input_ids) :]
      for input_ids, output_ids in zip(inputs.input_ids, output_ids)
  ]
  output_text = processor.batch_decode(
      generated_ids, skip_special_tokens=True, clean_up_tokenization_spaces=True
  )
  print(output_text)

まず 2B です. model size は 4.5 GB ほど

https://huggingface.co/Qwen/Qwen2-VL-2B-Instruct

サンプルだと max_new_tokens=128 で文字数 128 なので, max_new_tokens=2048 として十分な文字数確保します!

一応 GPU に収まるようですが, GPU は powerlimit とかしているためか, 1 分ほどかかりました.

指示は以下を与えました

文章を抽出してください

結果

以下は抽出した文章です:\n\n令和 6年 8月 8日\n\n横浜地方裁判所第 3 民事部\n\n裁判所書記官 佐 藤 雅 一\n\n別紙物件目録記載の不動産を 下記のとおり期間入札に付します。\n\n記\n\n入札期間 | 令和 6年 9月10日から | 令和 6年 9月17日 午後 5時00分まで\n\n開札期日 | 日 時場 所 | 令和 6年 9月24日 午前10時00分 | 横浜地方裁判所売却場\n\n売却決定期日 | 日 時場 所 | 令和 6年 10月7日 午後 1時10分 | 横浜地方裁判所 第 3 民事部\n\n特別売却実施期間 | 令和 6年 9月30日 午前10時00分から | 令和 6年 9月30日 午後 3時00分まで\n\n買受申出の保証の提供方法 | 下記のいずれかによる。\n(1) 当裁判所定の預金口座に金銭を振り込んだ旨の金融機関の証明書。\n(2) 銀行、損害保険会社、農林中央金庫、株式会社商工組合中央金庫、全国を地区とする信用金庫連合会、信用金庫又は労働金庫の支払保証委託契約締結証明書。\n\n買受申出の資格の制限(民事執行規則 33条) | ☆印を付した物件は農地であるので、権限を有する行政庁の交付した買受適格証明書を有する者及び買受けについて農地法上の許可 又は届出を必要としない者に限り、買受申出をすることができます。\n\n一般の閲覧に供するため、物件明細書・現況調査報告書・評価書の各写しを令和 6年 8月 8日から当庁物件明細等閲覧室に備え置きます。

Voila~
日付やタイトルは抜けてしまっていますが, 他はいい感じになりました.
ぱっと見, 文字間違いはなさそうです. 「☆印」もうまく抽出できています.

7B は model ちょっと大きい. 18 GB ほど. そのままでは普通の GPU には収まりきりません.
今回は device="auto" で CPU オフロードしつつ動かしました.

20 分ほどかかりました.

結果

以下は、指定された文章の抽出結果です。\n\n令和 5年(ケ)第 382号\n\n期間 入札の公告\n\n令和 6年 8月 8日\n横浜地方裁判所第3民事部\n裁判所書記官 佐 藤 雅 一\n\n別紙物件目録記載の不動産を下記のとおり期間入札に付します。\n\n記\n\n入札期間\n令和 6年 9月10日から\n令和 6年 9月17日 午後 5時00分まで\n\n開札期日\n日 時 場 所\n令和 6年 9月24日 午前10時00分\n横浜地方裁判所売却場\n\n売却決定期日\n日 時 場 所\n令和 6年 10月 7日 午後 1時10分\n横浜地方裁判所第3民事部\n\n特別売却実施期間\n令和 6年 9月30日 午前10時00分から\n令和 6年 9月30日 午後 3時00分まで\n\n買受申出の保証の提供方法\n下記のいずれかによる。\n(1) 当裁判所定の預金口座に金銭を振り込んだ旨の金融機関の証明書。\n(2) 銀行、損害保険会社、農林中央金庫、株式会社商工組合中央金庫、全国を地区とする信用金庫連合会、信用金庫又は労働金庫の支払保証委託契約締 結証明書。\n\n買受申出の資格の制限(民事執行規則 33条)\n☆印を付した物件は農地であるので、権限を有する行政庁の交付した買受適格証明書を有する者及び買受けについて農地法上の許可又は届出を必要としない者に限り、買受申出をすることができます。\n\n一般的の閲覧に供するために、物件明細書・現況調査報告書・評価書の各写しを令和 6年 8月 8日から当庁物件明細等閲覧室に備え置きます。

Super cool!

さらなる高みへ

VL なら, OCR だけでなく, 各種問い合わせもできますので...

2B で以下を試しました.

入札の期間について教えてください。

結果

['期間入札の公告によると、令和6年9月10日から9月17日までの間で、特別売却実施期間は令和6年9月30日午前10時00分から3時00分までです。']

時間がちょっと抜けてますがいい感じです!

複数ページまとめる?

PDF を複数ページまとめて一つの画像にさせて認識させるのもできるでしょう.
ただし, 多くの PDF2img で使われている poppler では 32K の制限ありますので https://zenn.dev/syoyo/articles/e34ee63f9592ab , 複数ページを 1 画像に変換ではなく, 1 ページごと画像化 + 画像結合としたほうがよいでしょう.

まとめ

2B では GPU 使っても 1 分ほどでちょっと遅いですが, 表組されたものでもいい感じに OCR できました. 簡単な指示では抽出漏れありましたが, プロンプト指示しっかりすればページ全体の文章抜き出すのも可能そうだとは思います.

あと, 別のページで試したところでは一部中国語の文字になっているのがありました.
後処理で中国語文字は除く https://zenn.dev/syoyo/scraps/4598c3a667fb48 みたいな処理を加えればよいでしょうか...

7B は, 遅くはありますが, (1 ページで試した限りですが)しっかり抽出できました. ページ数(画像数)数枚ほどで, いそぎでなければ高価な GPU 用意しなくとも, 昼休みや夜間とかにバッチ処理させるみたいなことはできるでしょう.

VL 系は llama.cpp とかではまだサポートされておらず,
(https://github.com/ggerganov/llama.cpp/discussions/5092 などでいくらか取り組みはあるが)
現状では, CPU 最適実行できない(pytorch CPU 実行だと 1 thread になるようでとてもおそい)ので, ローカルで動かすには GPU が必要です.
そのうちいろいろ最適化されてローカルで CPU でいい感じに動くようになるかとは思います.
(あとは M 系 mac とか?)

VL(Vision Language)ですと, 単に OCR で抽出するだけでなく, 内容についていろいろ問い合わせしたりできるのもいいところですね.

おまけ(M1 mac mini)

M1 mac mini(16GB)で Qwen2-VL 動かしました.
↑のテキスト抽出は 2 分くらいでした. さすがに GPU よりは遅いですが, すごい遅いわけでもなく, toks/sec 換算では 7 toks/sec くらいなので, 抽出結果をプログレッシブ表示するぶんには速度問題なさそうでしょうか...

おまけ その2(7B @ 3090 24GB)

WSL2 で RTX 3090 で 7B 試しました.
VRAM 消費量は画像サイズによるようですが, 1000x1500 程度(↑の競売物件 PDF の画像サイズ)であれば 22 GB くらいで 3090 に収まりました.

min_pixels = 256*28*28
max_pixels = 1280*28*28
processor = AutoProcessor.from_pretrained("Qwen/Qwen2-VL-7B-Instruct", min_pixels=min_pixels, max_pixels=max_pixels)

で 18GB ほど. 処理時間は 640 tokens で 40 secs くらい(16 toks/sec くらい)

min_pixels = 512*28*28
max_pixels = 2560*28*28
processor = AutoProcessor.from_pretrained("Qwen/Qwen2-VL-7B-Instruct", min_pixels=min_pixels, max_pixels=max_pixels)

で 22GB ほど(たぶん元画像サイズに limit)

1K ちょっとくらいの画像であれば, 3090/4090 など 24 GB mem の GPU で十分動くでしょう!
(あとは 16GB mem GPU x 2 とか...)

AQW, Int8 版などの量子化版を使えばより GPU mem 減らせるでしょうが, 2024/09/09 時点では最新 transformers と autoawq パッケージあたりがなんかうまく連携できなかったので確認できずです.

他のツール

ColPaliを用いたPDFの検索 & Florence-2-largeを用いたキャプションの生成とバウンディングボックスの配置を試してみた
https://zenn.dev/yumefuku/articles/pdf-image-search

ありがとうございます! ColPali & Florence も良さそうです!

TODO

  • Bounding box 抽出する
  • Gemma2 と連携する https://zenn.dev/syoyo/articles/c19df2d06bbe02
  • FLUX など 画像生成 AI と連携する(古い書籍のスキャンとか, 子供が手書き文字で指定した文章から, 画像生成とか!)

Discussion