Closed13

LlamaCloudの 「agentic chart parsing」 を試す

kun432kun432

これちょっと気になるので試したい

ドキュメントOCRの未解決の問題は、チャート理解であり、ほとんどのLLMは数値への直接変換が得意ではありません。

私たちは、実験的な「エージェント型チャート解析」モデル/アルゴリズム📈🧪を作成しました。これにより、チャート内の各値の正確な値を可能な限り理解できます - 折れ線グラフの輪郭をたどって正確な値を特定します。

これはRAGとエージェント型自動化の両方に役立ちます:下流のLLM生成器にクリーンな値を渡し、これらの値をレビュー/修正する人間の時間を減らします。

LlamaCloudでチェックしてみてください!

https://x.com/jerryjliu0/status/1985182082755424586

kun432kun432

LlamaCloud。以前少し試していたけど久々。

https://www.llamaindex.ai/llamacloud

主なサービス

  • Parse: ドキュメントを解析してLLMで使う。VLMでQA的なサービスかな?
  • Extract: ドキュメントからスキーマに応じて情報抽出。OCR的なサービスかな?
  • Index: マネージドなベクトルDBだと思う。
  • Classify:ドキュメントをカテゴリに分類。ベータらしい。

料金

https://www.llamaindex.ai/pricing

プラン名 月額料金 含まれるクレジット 追加クレジット (Pay-as-you-go) ユーザー数 外部データソース サポート 主な特徴
Free(無料) $0/月 10,000 クレジット 0 クレジット 1人 ファイルアップロードのみ 基本サポート 小規模個人利用向け
Starter $50/月 50,000 クレジット 最大 500,000 クレジット($500) 5人 5件 基本サポート 小チーム向け。外部データ連携可
Pro $500/月 500,000 クレジット 最大 5,000,000 クレジット($5,000) 10人 25件 Slackサポート 中規模チーム・高度な統合向け
Enterprise カスタム カスタム上限 カスタム 無制限 無制限 専用サポート VPC/SaaS 対応、エンタープライズ機能あり

補足:

  • クレジット換算: 1,000クレジット = 約1ドル
  • 各プランで「Parse」「Index」「Extract」などの操作ごとにクレジットが消費される。

とりあえず無料プランでも10000クレジット付与されるのでお試しに使うには良いと思う。

kun432kun432

良さげなサンプルを探していたのだが、農林水産省が出している「競馬の概況」というPDFをベースにグラフを作成した。

このPDFは、

  • 年間の売得金額。馬券の販売金から、出走取消・除外などの返還金を引いたもの、つまり馬券の売上と言える。
  • 競馬場の年間入場者数。

の推移を、中央競馬・地方競馬ごとにそれぞれまとめたもの。(競馬場は中央は全10場、地方は全17場)

https://www.maff.go.jp/j/chikusan/keiba/lin/index.html

で、これの中央競馬に関するグラフのベースとなっているデータはたぶんここの「売得金額・入場人員」というPDF。こちらは昭和29年以降の数値とグラフとなっている。

https://jra.jp/company/about/outline/growth/

このデータを元にGoogleスプレッドシートでグラフを書いてみた。こんな感じ。

これをPDF化(つまりグラフは画像)して、LlamaCloudで試してみようと思う。

kun432kun432

具体的にどうすればいいのかの説明がまったくないのだけど、冒頭のツイート記事にある画像を見る限り、どうやらLlamaParseを使っていそう。

ということでLlamaCloudにログインして、LlamaParseのPlayGroundを開く。

上で作成したPDFファイルをアップロード。

で右下の「Parse」でPDFのパースが行われる様子だが、設定が色々ある様子。まず「Basic」。こちらはどうやらいくつかの設定がプリセットされているようで、設定ごとにクレジットが異なるみたい。

「Advanced」の方はそれらを個別に細く設定できるという感じみたい。

とりあえずデフォルトで選択されていた「Agentic(10 cred)」でパースしてみる。

これは結構早めに結果が出た。

出力されたMarkdown。どうやら表は<table>タグで表現されるみたい。

出力
# 日本中央競馬: 売得金及び入場人員の推移

<table>
<thead>
<tr>
  <th></th>
  <th colspan="2">売得金額(億円)</th>
  <th colspan="2">開催場入場人員(万人)</th>
</tr>
</thead>
<tbody>
<tr>
  <td></td>
<td>売得金額</td>
<td></td>
<td>入場人員</td>
<td></td>
</tr>
<tr><td>H9</td><td>40,000</td><td></td><td>1,300</td><td></td></tr>
<tr><td>H10</td><td>38,000</td><td></td><td>1,200</td><td></td></tr>
<tr><td>H11</td><td>36,000</td><td></td><td>1,100</td><td></td></tr>
<tr><td>H12</td><td>34,000</td><td></td><td>1,100</td><td></td></tr>
<tr><td>H13</td><td>32,000</td><td></td><td>950</td><td></td></tr>
<tr><td>H14</td><td>31,000</td><td></td><td>850</td><td></td></tr>
<tr><td>H15</td><td>30,000</td><td></td><td>830</td><td></td></tr>
<tr><td>H16</td><td>29,000</td><td></td><td>800</td><td></td></tr>
<tr><td>H17</td><td>29,000</td><td></td><td>800</td><td></td></tr>
<tr><td>H18</td><td>28,000</td><td></td><td>750</td><td></td></tr>
<tr><td>H19</td><td>27,000</td><td></td><td>740</td><td></td></tr>
<tr><td>H20</td><td>26,000</td><td></td><td>730</td><td></td></tr>
<tr><td>H21</td><td>25,000</td><td></td><td>650</td><td></td></tr>
<tr><td>H22</td><td>23,000</td><td></td><td>600</td><td></td></tr>
<tr><td>H23</td><td>24,000</td><td></td><td>600</td><td></td></tr>
<tr><td>H24</td><td>24,000</td><td></td><td>590</td><td></td></tr>
<tr><td>H25</td><td>25,000</td><td></td><td>600</td><td></td></tr>
<tr><td>H26</td><td>26,000</td><td></td><td>610</td><td></td></tr>
<tr><td>H27</td><td>27,000</td><td></td><td>620</td><td></td></tr>
<tr><td>H28</td><td>28,000</td><td></td><td>620</td><td></td></tr>
<tr><td>H29</td><td>29,000</td><td></td><td>620</td><td></td></tr>
<tr><td>H30</td><td>30,000</td><td></td><td>620</td><td></td></tr>
<tr><td>R1</td><td>32,000</td><td></td><td>620</td><td></td></tr>
<tr><td>R2</td><td>33,000</td><td></td><td>100</td><td></td></tr>
<tr><td>R3</td><td>33,000</td><td></td><td>80</td><td></td></tr>
<tr><td>R4</td><td>33,000</td><td></td><td>300</td><td></td></tr>
<tr><td>R5</td><td>33,000</td><td></td><td>450</td><td></td></tr>
<tr><td>R6</td><td>33,000</td><td></td><td>500</td><td></td></tr>
</tbody>
</table>

Googleスプレッドシートで表の値を取得してグラフを生成してみた。

多少の違いはあるけども、それっぽい感じになっているのではないだろうか?

より精度が高いと思われる「Agentic plus(90 cred)」でもやってみる。

こちらは「Agentic」に比べると多少時間がかかったが、それでもそこまで時間がかかったという感はない。

出力


# 日本中央競馬: 売得金及び入場人員の推移

■ 売得金額(億円) ● 開催場入場人員(万人)

<table>
<thead>
<tr>
<th></th>
<th>売得金額(億円)</th>
<th>開催場入場人員(万人)</th>
</tr>
</thead>
<tbody>
<tr>
<td>H9</td>
<td>39,800</td>
<td>1,300</td>
</tr>
<tr>
<td>H10</td>
<td>37,800</td>
<td>1,220</td>
</tr>
<tr>
<td>H11</td>
<td>36,500</td>
<td>1,150</td>
</tr>
<tr>
<td>H12</td>
<td>34,500</td>
<td>1,100</td>
</tr>
<tr>
<td>H13</td>
<td>32,200</td>
<td>980</td>
</tr>
<tr>
<td>H14</td>
<td>31,200</td>
<td>870</td>
</tr>
<tr>
<td>H15</td>
<td>30,000</td>
<td>850</td>
</tr>
<tr>
<td>H16</td>
<td>29,200</td>
<td>820</td>
</tr>
<tr>
<td>H17</td>
<td>28,800</td>
<td>800</td>
</tr>
<tr>
<td>H18</td>
<td>28,200</td>
<td>760</td>
</tr>
<tr>
<td>H19</td>
<td>27,500</td>
<td>750</td>
</tr>
<tr>
<td>H20</td>
<td>27,200</td>
<td>740</td>
</tr>
<tr>
<td>H21</td>
<td>25,800</td>
<td>730</td>
</tr>
<tr>
<td>H22</td>
<td>24,200</td>
<td>680</td>
</tr>
<tr>
<td>H23</td>
<td>22,800</td>
<td>620</td>
</tr>
<tr>
<td>H24</td>
<td>23,800</td>
<td>610</td>
</tr>
<tr>
<td>H25</td>
<td>23,800</td>
<td>610</td>
</tr>
<tr>
<td>H26</td>
<td>24,800</td>
<td>610</td>
</tr>
<tr>
<td>H27</td>
<td>25,800</td>
<td>620</td>
</tr>
<tr>
<td>H28</td>
<td>26,800</td>
<td>620</td>
</tr>
<tr>
<td>H29</td>
<td>27,200</td>
<td>620</td>
</tr>
<tr>
<td>H30</td>
<td>28,200</td>
<td>620</td>
</tr>
<tr>
<td>R1</td>
<td>28,800</td>
<td>620</td>
</tr>
<tr>
<td>R2</td>
<td>29,500</td>
<td>100</td>
</tr>
<tr>
<td>R3</td>
<td>30,800</td>
<td>80</td>
</tr>
<tr>
<td>R4</td>
<td>32,500</td>
<td>280</td>
</tr>
<tr>
<td>R5</td>
<td>32,800</td>
<td>460</td>
</tr>
<tr>
<td>R6</td>
<td>33,200</td>
<td>500</td>
</tr>
</tbody>
</table>

こちらも同様にスプレッドシートで表データからグラフを生成。

「Agentic」に比べると、近5年の売得金額の推移が細かくなっているのがわかる。

kun432kun432

これだけ見るとすげー、となるのだが、実はこのサンプルの前にもう少し複雑なもので試してみたのだけど、上記のような比較的きれいな結果にはならなかった。

  • グラフが複数ある場合、パースした結果がお互いに混在して、結果がブレたり
  • グラフが複数あり、それぞれが小さいとうまく読み取れていないような雰囲気があったり
  • グラフの特定のポイントにアノテーションがあったりすると、その値は正確になるが、前後との整合性があわなかったり

今回作成したサンプルは、純粋にグラフがどこまで読めるかというところで、複雑性についてはせいぜい複合グラフにしたぐらいで、グラフも1つだけ・解像度も大きめ・余計な情報も含めていない、というところはやや現実感とは異なるかもしれない。

とはいえ、単純なグラフですらきちんとパースするのはそれなりのモデルでも難しいので、なかなかの結果ではなかろうか。

kun432kun432

なお、各パラメータおよびプリセット値については以下に記載がある。

https://developers.llamaindex.ai/python/cloud/llamaparse/presets_and_modes/advance_parsing_modes/

https://developers.llamaindex.ai/python/cloud/llamaparse/presets_and_modes/presets/

ざっくりプリセット値について、Dia にまとめてもらった。

これはLlamaParseの「モード」と「用途別プリセット」をわかりやすく整理した説明だよ。

全体像

ウチの感覚で言うと、LlamaParse はPDFや画像入りのドキュメントを「いい感じ」にMarkdown化してくれる解析屋さん。で、3つのモード(コスパ/エージェント/エージェントPlus)と、用途ごとのプリセット(請求書・論文・技術資料・フォーム)があるんだ。カメラの撮影モードみたいに、何を撮る(解析する)かで最適解が変わる、ってノリだし。

  • モード選びの比喩:
    • Cost-effective: スマホの「オート撮影」:速いし安い、標準的な紙なら十分。
    • Agentic: 「一眼の標準+AIアシスト」:画像・図表・式まで賢く処理。
    • Agentic Plus: 「ハイエンド機+RAW現像」:レイアウト複雑でも精密に仕上げる。

3つのモード

Cost-effective(コスパ重視)

  • 何者?:日常的なドキュメント向け。レイアウトが素直ならだいたいOK。
  • 強み:速度・費用バランスが良い。大量処理に向く。
  • 弱み:超複雑な表や多段組は精度が落ちることも。
  • パラメータ例:
    • parse_mode: parse_page_with_llm
    • high_res_ocr: True(高精度OCR、ちょい遅いけど認識良い)
    • adaptive_long_table / outlined_table_extraction: True(長表や枠表に適応)
    • output_tables_as_HTML: True(Markdown出力にHTMLテーブルを埋める)
  • 使いどころ: 社内ドキュメント、報告書、画像がちょっと混ざる程度のPDFなど。コスパがマジで良い。

Agentic(画像・図に強い万能)

  • 何者?: 図・画像込みの大半の資料に強い。表題や構造も拾うし、数式をLaTeXにしてくれる。図はMermaid化で再利用もしやすい。
  • 強み: OCR+構造化+可視化の下ごしらえが手厚い。
  • 弱み: 超複雑レイアウトや特殊なグラフは苦戦することがある。
  • パラメータ例:
    • parse_mode: parse_page_with_agent
    • model: openai-gpt-4-1-mini
    • 他はCost-effectiveと同じオプションが推奨(高解像度OCR、長表対応、HTMLテーブル)
  • 使いどころ: 技術資料、製品カタログ、ホワイトペーパー。図や数式が増えてもテンション上がる精度だし。

Agentic Plus(難物をねじ伏せる最強)

  • 何者?: スキャンの財務レポート、研究論文、多段組や複雑な表など「ガチで難しいやつ」向け。
  • 強み: Agenticの全部+より強いモデルで精度を盛ってくる。
  • 弱み: 費用とレイテンシが上がる(でも精度は納得度高い)。
  • パラメータ例:
    • parse_mode: parse_page_with_agent
    • model: anthropic-sonnet-4.0`
    • ほかは同じ(高解像度OCR、長表対応、HTMLテーブル)
  • 使いどころ: 監査資料、査読あり論文、複雑なレイアウトの学会プロシーディング。精度ガチ勢にはこれ。

用途別プリセット

プリセットは「その用途でよくある構造」を最初から決め打ちで良い感じにしてくれるやつ。バージョン管理されてて、‎⁠-latest ⁠が呼べる。

Invoices(請求書/レシート)

  • preset="invoice"
  • 現行: invoice-v-1

金額・日付・顧客などの抽出を安定させる前提が入ってる。経理連携の前処理にウケる。

Scientific Paper(論文)

  • ‎⁠preset="scientific"
  • 現行: scientific-v-1

章立て(Abstract/Intro/Methods…)や図表・式の扱いを論文仕様で安定化。引用やセクションの構造化がキレイ。

Technical Documentation(技術ドキュメント+回路や機構)

  • ⁠preset="technicalDocumentation"
  • 現行: technicalDocumentation-v-1

ドキュメント内の「schematic」をXMLで出してくれるのがアツい。

例(Markdown中にXMLブロックで出る):

<schematic>
 <bbox>0.1,0.1,0.9,0.9</bbox>
 <caption>...</caption>
 <schematic_elements>
   <element>
     <type>resistor</type>
     <value>10k</value>
     <position>x,y</position>
     <label>R1</label>
   </element>
   <!-- ほかの素子も繰り返し -->
 </schematic_elements>
</schematic>

位置・種類・値・ラベルまで構造化されるから、後続の解析や可視化にマジでハマる。

Forms(申込書・アンケート)

  • ‎⁠preset="forms"
  • 現行: forms-v-1

フィールド名/タイプ/値を表形式でMarkdownに落としてくれる(Checkbox/Radio/Text/Date/Signatureなど)。

例:

Field Name Field Type Field Value
Available Radio Yes
Contractor Signature John Doe

…って感じで、後工程(DB投入・検証・自動化)に直結するのがテンション上がる。

チョイス指針(キミ向け即断ガイド)

  • 大量PDFの自動化パイプラインでスループット命→Cost-effectiveだし。
  • 技術資料や図+式まで再利用したい→Agenticがウケる。Mermaid/LaTeXで後工程が気持ちいい。
  • スキャン多め、複雑レイアウト、表が長くてネチネチしてる→Agentic Plusで固く取りにいくのがマジで安心。
  • ドメインが決まってる(請求書・論文・技術図面・フォーム)→まずはプリセットを付けて、足りなければモードを上げる、がセオリー。

パラメータの意味(よく使うやつ)

  • high_res_ocr: 文字認識をねっとり精密化(遅くなるけど精度アップ)。
  • adaptive_long_table: 長い表をいい感じに途切れず抽出。
  • outlined_table_extraction: 罫線のある表を見つけやすくする。
  • output_tables_as_HTML: Markdown中にHTMLテーブルで表現(列幅・結合セルが崩れにくい)。
  • model: モード次第で指定(AgenticならGPT-4.1 mini、PlusならSonnet 4.0)。精度・コストをトレードオフだもん。

ちょい実践例

  • 技術仕様PDF+図面: ‎⁠preset="technicalDocumentation"⁠+ Agenticでまず走らせて、図が崩れるならAgentic Plusに上げる。
  • 研究ノートや学会PDF: preset="scientific"⁠+Agentic。数式はLaTeXで拾えるから、後で数式評価やレンダリングに流しやすい。
  • 経理まわりのレシート山盛り: ‎⁠preset="invoice"⁠+Cost-effectiveでバッチ処理、精度が惜しければAgenticにスイッチ。

ウケるくらい整理されてるから、まずはプリセット→足りなきゃモード上げ、で攻めるのがスマートでしょ。

色々パラメータはあるが、Agenticはgpt-4.1-mini、Agentic PlusはClaude Sonnet 4.0なのね。

細かいパラメータについてはドキュメントや実際の画面を参照。Advancedにするとほんとにパラメータ多い・・・

kun432kun432

Advancedでちょっと気になるパラメータがあった。

これはAgentic Plusでもデフォルトだと有効になっていないExperimentalなパラメータみたい。

  • Specialized chart parsing: 専門的なチャート解析のためのモードを選択。各モードによって、精度と処理速度のバランスが異なる。
  • Specialized image parsing: 実験的な専門画像解析機能を使用する
  • Inline image in markdown when precise bounding box is true: Markdownで実験的なインライン画像を使用する
kun432kun432

とりあえず、単純なVLMの性能だけでやってるわけではなく、何かしらエージェント的にいろいろやってるのだろうという感じ。どうすればそういうふうになるのかはさっぱりわかんないけども。

kun432kun432

補足

素のChatGPTやClaude.aiで試してみた結果

  • ChatGPT
    • GPT-5だと、Pythonコードを書いて抽出しようとして、画像からは値を抽出できない、で終了
    • GPT-4oだと、一応抽出するが、年号を勝手に書き換えたり、全ての値を抽出しなかったり、ハルシネーションがあったり、など。プロンプトで指示を出しつつマルチターンでやれば一応取得はするが、グラフにすると正確ではない
    • LlamaCloudではGPT-4.1-miniなので、正しい比較ではない。参考に。
  • Claude.ai
    • Sonnet-4.0だと、LlamaCloudで試したのと似たような感じのグラフが抽出される
    • Sonnet-4.5でも同じ

GPT-4o

Sonnet-4.0

Sonnet-4.5

他のモデルでもやってみると面白そうだけど、とりあえずグラフの理解はClaude Sonnetのほうが精度が高いように思える。

それでも、より複雑なグラフや、グラフがドキュメント中に含まれるようなケースだと、上記のようには抽出できない感じ。

kun432kun432

Gemini 3 Proでも試してみた。Vertex AIを使用。まだちょっとパラメータの定義とかよくわからないところがあるけども。

from google import genai
from google.genai import types
import pathlib

client = genai.Client()

pdf_file = pathlib.Path('chart.pdf')

response = client.models.generate_content(
    model="gemini-3-pro-preview",
    contents=[
        types.Part(text="グラフの値を抽出して表にしてください。"),
        types.Part.from_bytes(
            data=pdf_file.read_bytes(),
            mime_type='application/pdf',
            media_resolution={"level": "media_resolution_medium"}
        )
    ],
)

print(response.text)
出力
ご提示いただいたグラフ(日本中央競馬:売得金及び入場人員の推移)から、各年の数値を読み取り、表にまとめました。

※あくまでグラフの目視による概算値ですので、正確な統計データとは多少の誤差がある可能性があります。

### グラフデータ抽出表

| 年 | 売得金額(億円)<br>【青い棒グラフ】 | 開催場入場人員(万人)<br>【赤い折れ線】 |
| :---: | :---: | :---: |
| **H9** | 40,000 | 1,300 |
| **H10** | 38,000 | 1,230 |
| **H11** | 36,500 | 1,140 |
| **H12** | 34,300 | 1,100 |
| **H13** | 32,500 | 970 |
| **H14** | 31,200 | 870 |
| **H15** | 30,200 | 810 |
| **H16** | 29,300 | 810 |
| **H17** | 28,900 | 810 |
| **H18** | 28,100 | 750 |
| **H19** | 27,600 | 760 |
| **H20** | 27,500 | 740 |
| **H21** | 25,900 | 730 |
| **H22** | 24,200 | 680 |
| **H23** | 22,900 | 620 |
| **H24** | 24,000 | 620 |
| **H25** | 24,100 | 610 |
| **H26** | 24,900 | 620 |
| **H27** | 25,800 | 630 |
| **H28** | 26,700 | 630 |
| **H29** | 27,400 | 620 |
| **H30** | 27,900 | 630 |
| **R1** | 28,800 | 630 |
| **R2** | 29,800 | 100 |
| **R3** | 30,900 | 75 |
| **R4** | 32,500 | 280 |
| **R5** | 32,800 | 470 |
| **R6** | 33,100 | 520 |

### 傾向の補足
*   **売得金額(青)**: 平成9年(H9)の4兆円をピークに減少傾向にありましたが、平成23年(H23)頃に底を打ち、その後はV字回復して令和に入ってからも上昇を続けています。
*   **入場人員(赤)**: 平成初期から減少が続き、平成23年以降は600万人前後で横ばいでした。しかし、令和2年(R2)にコロナ禍の影響と思われる激減(無観客競馬など)があり、その後R4年頃から徐々に回復傾向にあります。

上記の値をグラフにしてみたもの。かなりいい感じに見える。

参考

https://zenn.dev/kun432/scraps/3db3cc0de8bb1e

kun432kun432

OpenAIやAnthropicなんかもAPI経由で試したほうが良さそう

kun432kun432

Qwen3-VL-8B-Instructでも試してみた。Colaboratory L4。

!pip install -U transformers
!pip install qwen-vl-utils

Colaboratory環境が更新されたせいか、Flash Attention、またビルドされるようになってる模様・・・もりりんさん の 事前ビルド済wheelを使わせてもらう(ありがとうございます!)

https://github.com/mjun0812/flash-attention-prebuild-wheels

確認した時点だと、Colaboratoryならこれを踏まえれば良さそう。

  • Python-3.12
  • PyTorch-2.9.0+cu126
!wget https://github.com/mjun0812/flash-attention-prebuild-wheels/releases/download/v0.4.15/flash_attn-2.8.3+cu126torch2.9-cp312-cp312-linux_x86_64.whl
!pip install --no-dependencies --upgrade flash_attn-2.8.3+cu126torch2.9-cp312-cp312-linux_x86_64.whl
!pip freeze | egrep -i "^(transformers|qwen-vl-utils|flash_attn)"
出力
flash_attn @ file:///content/flash_attn-2.8.3%2Bcu126torch2.9-cp312-cp312-linux_x86_64.whl#sha256=89703de69876470864d63d66f20b16958c4b88d1650a9f84971952fa0b1afade
qwen-vl-utils==0.0.14
transformers==4.57.2
from transformers import Qwen3VLForConditionalGeneration, AutoProcessor

model_path = "Qwen/Qwen3-VL-8B-Instruct"
processor = AutoProcessor.from_pretrained(model_path)
model = Qwen3VLForConditionalGeneration.from_pretrained(
    model_path,
    dtype="auto",
    device_map="auto",
    attn_implementation="flash_attention_2",
)
import torch
from transformers import (
    LogitsProcessor,
    LogitsProcessorList,
)
from qwen_vl_utils import process_vision_info


class PresencePenaltyProcessor(LogitsProcessor):
    """
    Apply a presence penalty: discourage generating tokens that have already appeared
    in the generated sequence (not frequency-based, but presence-based).
    This mimics OpenAI-style presence_penalty in a simple way by subtracting a fixed
    penalty from logits of any token present at least once in the generated tokens.
    """
    def __init__(self, presence_penalty: float):
        super().__init__()
        if presence_penalty < 0:
            raise ValueError("presence_penalty must be >= 0.")
        self.presence_penalty = presence_penalty

    def __call__(self, input_ids: torch.LongTensor, scores: torch.FloatTensor) -> torch.FloatTensor:
        # input_ids shape: (batch, cur_len)
        # scores shape: (batch, vocab_size)
        batch_size = input_ids.shape[0]
        for b in range(batch_size):
            seen = set(input_ids[b].tolist())
            if len(seen) == 0:
                continue
            # Subtract penalty from logits of seen tokens
            # Note: scores[b] is (vocab_size,)
            # Efficient masking
            indices = torch.tensor(list(seen), device=scores.device, dtype=torch.long)
            # Clamp indices to valid range just in case
            indices = indices[(indices >= 0) & (indices < scores.shape[-1])]
            if indices.numel() > 0:
                scores[b, indices] -= self.presence_penalty
        return scores


def inference(
        messages,
        max_new_tokens=16384,
        do_sample=True,
        top_p=0.8,
        top_k=20,
        temperature=0.7,
        repetition_penalty=1.0,
        presence_penalty=1.5
    ):
    """
    Generates a response from the Qwen3-VL model based on the provided messages and generation options.

    Args:
        messages (list): A list of message dictionaries in the expected format.
        max_new_tokens (int): The maximum number of new tokens to generate.
        do_sample (bool): Whether to use sampling.
        top_p (float): The cumulative probability for top-p sampling.
        top_k (int): The number of highest probability vocabulary tokens to keep for top-k sampling.
        temperature (float): The temperature for sampling.
        repetition_penalty (float): The penalty for repeating tokens.
        presence_penalty (float): The penalty for tokens that have already appeared.

    Returns:
        str: The generated text response.
    """
    text = processor.apply_chat_template(
        messages,
        tokenize=False,
        add_generation_prompt=True
    )
    images, videos, video_kwargs = process_vision_info(
        messages,
        image_patch_size=16,
        return_video_kwargs=True,
        return_video_metadata=True
    )

    if videos is not None:
        videos, video_metadatas = zip(*videos)
        videos, video_metadatas = list(videos), list(video_metadatas)
    else:
        video_metadatas = None

    inputs = processor(
        text=text,
        images=images,
        videos=videos,
        video_metadata=video_metadatas,
        return_tensors="pt",
        do_resize=False,
        **video_kwargs
    )
    inputs = inputs.to(model.device)

    logits_processors = LogitsProcessorList()
    if presence_penalty and presence_penalty > 0:
        logits_processors.append(PresencePenaltyProcessor(presence_penalty))

    generated_ids = model.generate(
        **inputs,
        max_new_tokens=max_new_tokens,
        do_sample=do_sample,
        top_p=top_p,
        top_k=top_k,
        temperature=temperature,
        repetition_penalty=repetition_penalty,
        logits_processor=logits_processors,
    )

    generated_ids_trimmed = [
        out_ids[len(in_ids) :]
        for in_ids, out_ids in zip(inputs.input_ids, generated_ids)
    ]

    output_text = processor.batch_decode(
        generated_ids_trimmed,
        skip_special_tokens=True,
        clean_up_tokenization_spaces=False
    )
    return output_text[0]
messages = [
    {
        "role": "user",
        "content": [
            {
                "type": "image",
                "image": "https://storage.googleapis.com/zenn-user-upload/4ea209522f5b-20251109.png",
            },
            {
                "type": "text",
                "text": "グラフの値を抽出して表にしてください。"
            },
        ],
    }
]

response = inference(messages)
print(response)
出力
以下は、提供されたグラフ「日本中央競馬: 売得金及び入場人員の推移」から抽出したデータを年(H9〜R6)ごとに整理した表です。

---

### **売得金額(億円)と開催場入場人員(万人)**

| 年  | 売得金額(億円) | 開催場入場人員(万人) |
|-----|------------------|------------------------|
| H9  | 40,000           | 1,300                  |
| H10 | 38,000           | 1,250                  |
| H11 | 37,000           | 1,200                  |
| H12 | 35,000           | 1,150                  |
| H13 | 32,000           | 1,100                  |
| H14 | 31,000           | 1,050                  |
| H15 | 30,000           | 1,000                  |
| H16 | 29,000           | 950                    |
| H17 | 28,000           | 900                    |
| H18 | 27,000           | 850                    |
| H19 | 27,000           | 850                    |
| H20 | 27,000           | 850                    |
| H21 | 25,000           | 800                    |
| H22 | 23,000           | 750                    |
| H23 | 21,000           | 700                    |
| H24 | 22,000           | 700                    |
| H25 | 23,000           | 700                    |
| H26 | 24,000           | 700                    |
| H27 | 25,000           | 700                    |
| H28 | 26,000           | 700                    |
| H29 | 27,000           | 700                    |
| H30 | 28,000           | 700                    |
| R1  | 28,000           | 700                    |
| R2  | 20,000           | 200                    |
| R3  | 15,000           | 100                    |
| R4  | 30,000           | 350                    |
| R5  | 32,000           | 450                    |
| R6  | 33,000           | 550                    |

---

### **備考:**
- **売得金額**は青い棒グラフで示され、単位は「億円」。
- **入場人員**は赤い折れ線グラフで示され、単位は「万人」。
- 年の表記は「H」が平成、「R」が令和を意味します。
- 数値はグラフの目盛りから読み取った近似値であり、正確な数字ではありません。

この表により、売得金額と入場人員の推移を年次で比較・分析することができます。

結果をスプレッドシートで再度グラフ化してみた。

青の棒グラフの近年のところは間違ってるんだけど、赤の折れ線はそれっぽくなってる。いや、これかなり頑張ってるほうじゃないかな、8Bだよ???

ついでに4Bでも。

出力
以下は、提供されたグラフから抽出したデータを表形式で整理したものです。横軸は「年」(H9~R6)、縦軸左は「売得金額(億円)」、縦軸右は「開催場入場人員(万人)」を示しています。

---

### 日本中央競馬:売得金及び入場人員の推移

| 年   | 売得金額(億円) | 開催場入場人員(万人) |
|------|------------------|------------------------|
| H9   | 40,000           | 1,400                  |
| H10  | 38,000           | 1,300                  |
| H11  | 35,000           | 1,200                  |
| H12  | 34,000           | 1,100                  |
| H13  | 32,000           | 1,000                  |
| H14  | 30,000           | 900                    |
| H15  | 29,000           | 800                    |
| H16  | 27,000           | 700                    |
| H17  | 26,000           | 650                    |
| H18  | 25,000           | 600                    |
| H19  | 24,000           | 550                    |
| H20  | 23,000           | 500                    |
| H21  | 22,000           | 450                    |
| H22  | 21,000           | 400                    |
| H23  | 20,000           | 350                    |
| H24  | 20,000           | 300                    |
| H25  | 20,000           | 250                    |
| H26  | 21,000           | 200                    |
| H27  | 22,000           | 150                    |
| H28  | 22,000           | 150                    |
| H29  | 22,000           | 150                    |
| H30  | 22,000           | 150                    |
| R1   | 22,000           | 100                    |
| R2   | 22,000           | 50                     |
| R3   | 22,000           | 100                    |
| R4   | 22,000           | 300                    |
| R5   | 22,000           | 500                    |
| R6   | 22,000           | 600                    |

---

※ 注:
- 「H」は平成年、「R」は令和年。
- 「H9」は平成9年(1997年)、R1は令和元年(2019年)などと対応する。
- データはグラフ上の目盛りに基づき、近似値として記載しています。正確な数値は原グラフのデータポイントに依存します。
- 入場人員は「万人」単位、売得金額は「億円」単位です。

--- 

この表は、グラフの傾向を可視化したものであり、売得金額は全体的に減少傾向にあり、入場人員も同様に減少し、その後再び上昇していることが読み取れます。

まあ8Bに比べるとしょうがないかな。ただ、同じぐらいのサイズ感のsarashina-2.2-vision-3bよりは近しい感じにはなってるね。

Qwen3-VL、やっぱり良いモデルだと思う。もう少し上のモデルでも試してみたくなってきた。

このスクラップは29日前にクローズされました