Closed12

ColPaliのシンプルなラッパー「Byaldi」を試す

kun432kun432

ColBERTを簡単に使えるようにするRAGatouille、みたいな感じで、ColPaliを簡単に使えるようにするByaldi、という感じっぽい。

https://twitter.com/bclavie/status/1831730946065793339

https://github.com/answerdotai/byaldi

Byaldiへようこそ

ご存知ですか? 映画『レミーのおいしいレストラン』(原題: Ratatouille)の中でレミーが作る料理は、実はラタトゥイユではなく、その改良版である「コンフィ・ビャルディ」なのだ。


referered from https://github.com/answerdotai/byaldi

⚠️ これはビャルディのプレリリース版です。 何か問題があったら報告してください!

Byaldi は RAGatouille のミニ姉妹プロジェクトです。 これは、ColPali リポジトリのシンプルなラッパーで、ColPali のようなレイトインタラクションのマルチモーダルモデルを使い慣れたAPIで簡単に使えるようにするものです。

はじめに

まず最初に警告ですが、これはプレリリース版ライブラリであり、非圧縮インデックスを使用しており、その他の種類の改良は施されていません。現時点では、vidore/colpali および更新版の vidore/colpali-v1.2 を含む、PaliGemma ベースの ColPali チェックポイントファミリーのみがサポートされています。将来的なアップデートで、その他のバックエンドもサポートされる予定です。最終的には、HNSWインデックス作成メカニズム、プール、そして、もしかしたら2ビット量子化も追加するかもしれません。

前提条件

ColPaliへのアクセス

ColPaliは現在、唯一の同種のモデルです。PaliGemmaをベースとしているため、HuggingFace上のPaliGemmaに関するGoogleのライセンス契約に同意し、独自のHFトークンを使用してモデルをダウンロードする必要があります。

Poppler

フレンドリーなライセンスでPDFを画像に変換するために、pdf2imageライブラリを使用しています。このライブラリを使用するには、popplerをシステムにインストールする必要があります。popplerのインストールは、ウェブサイトの手順に従って簡単に行うことができます。

Flash-Attention

Gemmaは最新のFlash Attentionを使用しています。 できるだけスムーズに動作させるために、ライブラリをインストールした後に、これをインストールすることをお勧めします。

ハードウェア

ColPaliは、数十億のパラメータモデルを使用して文書をエンコードします。スムーズな動作のためにはGPUの使用を推奨しますが、性能の低いGPUや古いGPUでも問題なく動作します。コレクションのエンコードは、CPUやMPSではパフォーマンスが低下します。

マルチモーダルエコシステムがさらに発展するにつれ、更新されていくでしょう!

kun432kun432

以下にサンプルのnotebookと使用するPDFが置かれている。

https://github.com/AnswerDotAI/byaldi/tree/main/examples

notebookは2つ用意されている

  • quick_overview.ipynb
  • chat_with_your_pdf.ipynb

とりあえず上の方からColaboratoryで試してみるとして、PDFは日本語のものを使いたいのでなにかに置き換えたいと思う(日本語が使えるかどうかはわからない)

kun432kun432

quick_overview.ipynb

https://github.com/AnswerDotAI/byaldi/tree/main/examples/quick_overview.ipynb

Colaboratoryのランタイムは、CPUのみだとぜんぜんインデックス作成が進まなかったので、T4に変更している

まず以下のHuggingFaceのPaliGemmaのページで利用規約に同意しておく。

https://huggingface.co/google/paligemma-3b-mix-448

で、notebookを進めるのだが、READMEの前提条件に書かれている内容はnotebookには含まれていないようなので、以下を最初に実行する。

%%bash
apt-get update
apt-get install -y poppler-utils
pip install --upgrade byaldi
pip install flash-attn

ここからはnotebookに従って進める

モデルをロード。ColPaliの最新バージョンに変更した。

import os
from google.colab import userdata
from pathlib import Path
from byaldi import RAGMultiModalModel

os.environ["HF_TOKEN"] = userdata.get('HF_TOKEN')

# RAGMultiModalModelを初期化
model = RAGMultiModalModel.from_pretrained("vidore/colpali-v1.2")

PDFを用意する。神戸市が公開している観光に関する統計・調査資料のうち、「令和4年度 神戸市観光動向調査結果について」を使う。

https://www.city.kobe.lg.jp/a64051/shise/toke/sightseeing.html

PDF直
https://www.city.kobe.lg.jp/documents/15123/r4_doukou.pdf

docsディレクトリを作成してそこにダウンロード

!mkdir -p docs
!wget -P docs https://www.city.kobe.lg.jp/documents/15123/r4_doukou.pdf

インデックスを作成。ここはそこそこ時間がかかる。

index_name = "attention_index"
model.index(
    input_path=Path("docs/"),
    index_name=index_name,
    store_collection_with_index=False,
    overwrite=True
)
Indexing file: docs/r4_doukou.pdf
Added page 1 of document 0 to index.
Added page 2 of document 0 to index.
Added page 3 of document 0 to index.
Added page 4 of document 0 to index.
Added page 5 of document 0 to index.
Added page 6 of document 0 to index.
Added page 7 of document 0 to index.
Added page 8 of document 0 to index.
Added page 9 of document 0 to index.
Added page 10 of document 0 to index.
Added page 11 of document 0 to index.
Added page 12 of document 0 to index.
Added page 13 of document 0 to index.
Added page 14 of document 0 to index.
Added page 15 of document 0 to index.
Added page 16 of document 0 to index.
Added page 17 of document 0 to index.
Added page 18 of document 0 to index.
Added page 19 of document 0 to index.
Added page 20 of document 0 to index.
Added page 21 of document 0 to index.
Index exported to .byaldi/kobe_kanko_index
Index exported to .byaldi/kobe_kanko_index
{0: 'docs/r4_doukou.pdf'}

検索してみる。

query = "神戸までの主な交通機関の内訳は?"
results = model.search(query, k=5)

print(f"Search results for '{query}':")
for result in results:
    print(f"Doc ID: {result.doc_id}, Page: {result.page_num}, Score: {result.score}")
Search results for '神戸までの主な交通機関の内訳は?':
Doc ID: 0, Page: 17, Score: 11.4375
Doc ID: 0, Page: 10, Score: 11.25
Doc ID: 0, Page: 13, Score: 11.0625
Doc ID: 0, Page: 5, Score: 11.0
Doc ID: 0, Page: 16, Score: 10.9375

んー、今回のクエリの例では、ドキュメントの内容からは6ページ(PDFとしてのページで、文書のページではない)が最も適切、あとは2ページにその要約が一部含稀ているという感じなんだけども、検索で上がってきたページは、

  • 17ページ: 神戸市以外の立ち寄り先
  • 10ページ: 旅行同伴者
  • 13ページ: 旅行の日程と宿泊地(のデータ)
  • 5ページ: 居住地
  • 16ページ: 割引制度の利用

という感じなので、これを見る限り日本語は難しそうである。実際にColPaliのモデルのページにも以下とある。

https://huggingface.co/vidore/colpali-v1.2

バージョン固有

このバージョンはcolpali-engine=0.2.0で学習されている。colpaliと比較すると、このバージョンはクエリ符号化における不要なトークンを修正するために、クエリの右パディングで学習されている。 また、決定論的な射影層の初期化を保証するために、vidore/colpaligemma-3b-pt-448-baseを固定している。 英語以外の言語の崩壊を減らすために、バッチ内否定とハードマイニングされた否定、1000ステップ(10倍長い)のウォームアップで5エポック学習された。 データは論文で説明されたColPaliデータと同じ

モデルトレーニグ

データセット

127,460 のクエリとページのペアからなるトレーニング データセットは、公開されている学術データセットのトレーニング セット (63%) と、Web クロールされた PDF ドキュメントのページで構成され、VLM 生成 (Claude-3 Sonnet) 疑似質問 (37%) で拡張された合成データセットで構成されています。トレーニング セットは完全に英語で設計されているため、英語以外の言語へのゼロ ショット一般化を研究できます。評価の汚染を防ぐため、 ViDoReとトレーニング セットの両方で複数ページの PDF ドキュメントが使用されていないことを明示的に検証します。ハイパーパラメータを調整するために、サンプルの 2% で検証セットが作成されます。

注: 多言語データは言語モデル (Gemma-2B) の事前トレーニング コーパスに存在し、PaliGemma-3B のマルチモーダル トレーニング中に発生する可能性があります。

ColPaliにはv1.0、v1.1もあるのだけど、そちらを見る限りデータセットは同じであり、モデルの説明からはv1.2で多言語への対応が(多少なりとも)含まれているように思えるので、他のバージョンでも厳しそう。現状日本語はちょっと難しそうな印象。

kun432kun432

https://twitter.com/kun432/status/1833768682390274086

こちらの方を参考に、英語のPDFを使ってみる。

出典:「HIGHLIGHTING Japan May2024」(政府広報オンライン)(https://www.gov-online.go.jp/hlj/en/may_2024/)
https://www.gov-online.go.jp/hlj/en/may_2024/

“HIGHLIGHTING Japan” is an official monthly online magazine to promote understanding of Japan for the people around the world, with variety of themes.

一旦ランタイムは削除してやり直す。手順は同じなので説明は割愛。

インデックス作成までの手順
%%bash
apt-get update
apt-get install -y poppler-utils
pip install --upgrade byaldi
pip install flash-attn
import os
from google.colab import userdata
from pathlib import Path
from byaldi import RAGMultiModalModel

os.environ["HF_TOKEN"] = userdata.get('HF_TOKEN')

# RAGMultiModalModelを初期化
model = RAGMultiModalModel.from_pretrained("vidore/colpali-v1.2")
%%bash
mkdir docs
wget -P docs https://www.gov-online.go.jp/en/assets/HIGHLIGHTING_Japan_May2024.pdf

インデックス作成

index_name = "highlighting_japan_index"
model.index(
    input_path=Path("docs/"),
    index_name=index_name,
    store_collection_with_index=False,
    overwrite=True
)
Indexing file: docs/HIGHLIGHTING_Japan_May2024.pdf
Added page 1 of document 0 to index.
Added page 2 of document 0 to index.
Added page 3 of document 0 to index.
Added page 4 of document 0 to index.
Added page 5 of document 0 to index.
Added page 6 of document 0 to index.
Added page 7 of document 0 to index.
Added page 8 of document 0 to index.
Added page 9 of document 0 to index.
Added page 10 of document 0 to index.
Added page 11 of document 0 to index.
Added page 12 of document 0 to index.
Added page 13 of document 0 to index.
Added page 14 of document 0 to index.
Added page 15 of document 0 to index.
Added page 16 of document 0 to index.
Added page 17 of document 0 to index.
Added page 18 of document 0 to index.
Added page 19 of document 0 to index.
Added page 20 of document 0 to index.
Added page 21 of document 0 to index.
Added page 22 of document 0 to index.
Added page 23 of document 0 to index.
Added page 24 of document 0 to index.
Added page 25 of document 0 to index.
Added page 26 of document 0 to index.
Added page 27 of document 0 to index.
Added page 28 of document 0 to index.
Added page 29 of document 0 to index.
Added page 30 of document 0 to index.
Added page 31 of document 0 to index.
Added page 32 of document 0 to index.
Index exported to .byaldi/highlighting_japan_index
Index exported to .byaldi/highlighting_japan_index
{0: 'docs/HIGHLIGHTING_Japan_May2024.pdf'}

検索

query = "季節を感じるアートとは?"
results = model.search(query, k=5)

print(f"Search results for '{query}':")
for result in results:
    print(f"Doc ID: {result.doc_id}, Page: {result.page_num}, Score: {result.score}")
Search results for '季節を感じるアートとは?':
Doc ID: 1, Page: 28, Score: 10.125
Doc ID: 1, Page: 23, Score: 9.6875
Doc ID: 1, Page: 11, Score: 8.8125
Doc ID: 1, Page: 3, Score: 8.6875
Doc ID: 1, Page: 17, Score: 8.6875

28ページはまさにそれだった。

query = "安綱の刀について"
results = model.search(query, k=5)

print(f"Search results for '{query}':")
for result in results:
    print(f"Doc ID: {result.doc_id}, Page: {result.page_num}, Score: {result.score}")
Search results for '安綱の刀について':
Doc ID: 1, Page: 32, Score: 10.9375
Doc ID: 1, Page: 3, Score: 10.5
Doc ID: 1, Page: 23, Score: 8.9375
Doc ID: 1, Page: 19, Score: 8.75
Doc ID: 1, Page: 28, Score: 8.0625

こちらも32ページが正解で、3ページの目次にも少し載っている。

query = "阿寒湖摩周国立公園について"
results = model.search(query, k=5)

print(f"Search results for '{query}':")
for result in results:
    print(f"Doc ID: {result.doc_id}, Page: {result.page_num}, Score: {result.score}")
Search results for '阿寒湖摩周国立公園について':
Doc ID: 1, Page: 10, Score: 14.5
Doc ID: 1, Page: 11, Score: 13.8125
Doc ID: 1, Page: 17, Score: 13.75
Doc ID: 1, Page: 2, Score: 13.0625
Doc ID: 1, Page: 13, Score: 12.8125

10〜11ページが正解。2は目次でここにも少し記載がある。

なるほど、今のところインデックスに入れて検索する対象は、英語のほうが良さそうではある。クエリは日本語なんだけど。

kun432kun432

引き続き、以下のPDFを使用する。

出典:「HIGHLIGHTING Japan May2024」(政府広報オンライン)(https://www.gov-online.go.jp/hlj/en/may_2024/)
https://www.gov-online.go.jp/hlj/en/may_2024/


インデックスは.byaldiに作成されていた。

$ pwd
/content

$ tree .byaldi/
.byaldi/
└── highlighting_japan_index
    ├── doc_ids_to_file_names.json.gz
    ├── embeddings
    │   └── embeddings_0.pt
    ├── embed_id_to_doc_id.json.gz
    ├── index_config.json.gz
    └── metadata.json.gz

2 directories, 5 files

すでに作成済みのインデックスを読み出すにはRAGMultiModalModel.from_index()を使う

from byaldi import RAGMultiModalModel

model = RAGMultiModalModel.from_index("highlighting_japan_index")

検索すると同じ結果になっていることがわかる。

results = model.search("阿寒湖摩周国立公園について", k=5)

print(f"Search results for '{query}':")
for result in results:
    print(f"Doc ID: {result.doc_id}, Page: {result.page_num}, Score: {result.score}")
Search results for '阿寒湖摩周国立公園について':
Doc ID: 0, Page: 10, Score: 14.5
Doc ID: 0, Page: 11, Score: 13.8125
Doc ID: 0, Page: 17, Score: 13.75
Doc ID: 0, Page: 2, Score: 13.0625
Doc ID: 0, Page: 13, Score: 12.8125
kun432kun432

引き続き、以下のPDFを使用する。

出典:「HIGHLIGHTING Japan May2024」(政府広報オンライン)(https://www.gov-online.go.jp/hlj/en/may_2024/)
https://www.gov-online.go.jp/hlj/en/may_2024/


上で、インデックスを作成した際に、store_collection_with_index=Falseを指定していたが、この状態はあくまでも検索用のインデックスが作成されるだけで、どうやら実際のPDFのデータ(というか画像かな?)は保持されない様子。

(再掲)
index_name = "highlighting_japan_index"
model.index(
    input_path=Path("docs/"),
    index_name=index_name,
    store_collection_with_index=False,  # ここ
    overwrite=True
)

マルチモーダルLLMと連携するようなケースでは、ここで実際のデータも取れると便利になる。ということで、store_collection_with_indexを有効にしてインデックスを作成してみる。

import os
from google.colab import userdata
from pathlib import Path
from byaldi import RAGMultiModalModel

os.environ["HF_TOKEN"] = userdata.get('HF_TOKEN')

model = RAGMultiModalModel.from_pretrained("vidore/colpali-v1.2")

pdf_path = Path("docs/HIGHLIGHTING_Japan_May2024.pdf")

index_name = "highlighting_japan_index_with_collection"
model.index(
    input_path=pdf_path,
    index_name=index_name,
    store_collection_with_index=True,
    overwrite=True
)
Verbosity is set to 1 (active). Pass verbose=0 to make quieter.
Loading checkpoint shards: 100%
 2/2 [00:02<00:00,  1.23s/it]
Added page 1 of document 0 to index.
Added page 2 of document 0 to index.
Added page 3 of document 0 to index.
Added page 4 of document 0 to index.
Added page 5 of document 0 to index.
Added page 6 of document 0 to index.
Added page 7 of document 0 to index.
Added page 8 of document 0 to index.
Added page 9 of document 0 to index.
Added page 10 of document 0 to index.
Added page 11 of document 0 to index.
Added page 12 of document 0 to index.
Added page 13 of document 0 to index.
Added page 14 of document 0 to index.
Added page 15 of document 0 to index.
Added page 16 of document 0 to index.
Added page 17 of document 0 to index.
Added page 18 of document 0 to index.
Added page 19 of document 0 to index.
Added page 20 of document 0 to index.
Added page 21 of document 0 to index.
Added page 22 of document 0 to index.
Added page 23 of document 0 to index.
Added page 24 of document 0 to index.
Added page 25 of document 0 to index.
Added page 26 of document 0 to index.
Added page 27 of document 0 to index.
Added page 28 of document 0 to index.
Added page 29 of document 0 to index.
Added page 30 of document 0 to index.
Added page 31 of document 0 to index.
Added page 32 of document 0 to index.
Index exported to .byaldi/highlighting_japan_index_with_collection
Index exported to .byaldi/highlighting_japan_index_with_collection
{0: 'docs/HIGHLIGHTING_Japan_May2024.pdf'}

検索。検索結果の.base64でbase64エンコードされた画像が取り出せる。

import base64
from io import BytesIO
from PIL import Image
import IPython.display as display

query = "阿寒湖摩周国立公園について"
results = model.search(query, k=3)

print(f"Search results for '{query}':")

base_64s = []
for result in results:
    print(f"Doc ID: {result.doc_id}, Page: {result.page_num}, Score: {result.score}")
    print(f"Base64: {result.base64[:100]}...")
    base_64s.append(result.base64)

html_images = ""
for base64_data in base_64s:
    image_data = base64.b64decode(base64_data)
    image = Image.open(BytesIO(image_data))

    new_width = 300
    aspect_ratio = image.height / image.width
    new_height = int(new_width * aspect_ratio)
    resized_image = image.resize((new_width, new_height))
    
    buffered = BytesIO()
    resized_image.save(buffered, format="PNG")
    img_str = base64.b64encode(buffered.getvalue()).decode("utf-8")

    html_images += f'<img src="data:image/png;base64,{img_str}" style="display:inline-block; margin-right: 10px;" />'

display.display(display.HTML(f'<div style="white-space: nowrap;">{html_images}</div>'))

該当するページが取り出せていることがわかる。マルチモーダルLLMにこれを渡せば、画像をベースにQAができる。

kun432kun432

インデックスへの追加は以下

model.add_to_index(pdf_path, store_collection_with_index=True)
kun432kun432

chat_with_your_pdf.ipynb

ByaldiとマルチモーダルLLMと連携させた例がこちらのnotebookになる。

https://github.com/AnswerDotAI/byaldi/tree/main/examples/chat_with_your_pdf.ipynb

このnotebookでは、マルチモーダルLLMとしてAnthropic Claude 3.5 sonnetが使用されている。なお、Claudeとの接続は、byaldiの開発元であるAnswer.AIが開発したClaudetteというラッパーが使用されている。

https://github.com/AnswerDotAI/claudette

Claudetteの使い方はこちらにまとめた。こういう用途で使うにはとてもシンプルに扱えて良いと思う。
https://zenn.dev/kun432/scraps/bb1c20e648fe52

ということで、notebookを参考に進めてみたが、非常に詳しい説明がされたnotebookなので、いちいちまとめなくても、そのまま実行するのがわかりやすいと思うw。一応、以下に日本語に翻訳したものを用意したので、参考になれば。

https://gist.github.com/kun432/f0c11fcb998948fbf71cbf4e48c3ed1f

簡単に紹介だけしておくと、2つの例が紹介されている

"Attention is All You Need"論文中の表データに対するRAG

"Attention is All You Need"論文をByaldiでインデックス作成、評価テスト結果の図が含まれたページを検索し、LLMにそのスコアについて質問・回答を行う。

該当の箇所は8ページの以下の箇所


refererd from https://arxiv.org/pdf/1706.03762 , https://github.com/AnswerDotAI/byaldi/blob/main/examples/docs/attention_table.png and arranged in the image by kun432

クエリ

Q: TransformerのベースモデルのBLEUスコアはいくつですか?

検索結果は8ページがトップに上がっている。

results = RAG.search(query, k=1)
results
[{'doc_id': 0, 'page_num': 8, 'score': 18.0, 'metadata': {}, 'base64': 'iVBORw0KGgoAAAANSUhEUgAABqQAAAiYCAIAAAA+NVHkAAEAA...

そしてLLMの回答

A: Transformerのベースモデル(Transformer (base model))のBLEUスコアは、表2によると以下の通りです:
EN-DE (英語からドイツ語への翻訳): 27.3 EN-FR (英語からフランス語への翻訳): 38.1

財務報告書内のグラフデータに対するRAG

架空の会社の財務報告書の中にある、特定プロダクトの月次収益グラフが含まれたページを検索し、最も収益を上げた月をLLMに質問回答させる。


refererd from https://github.com/AnswerDotAI/byaldi/blob/main/examples/docs/financial_report.pdf and arranged in the image by kun432

クエリ

Q: 製品Cが最も収益を上げたのはどの月ですか?

検索結果は4ページ目がトップに上がっている。

results = RAG.search(query, k=1)
results[0].page_num
4

LLMの回答

A: この画像から、製品Cが最も収益を上げた月は6月(Jun)であることがわかります。グラフを見ると、6月の棒グラフが最も高く、約2700ドルの月間収益を示しています。1月から5月にかけて収益が徐々に増加し、6月にピークを迎えた後、7月以降は徐々に減少していく傾向が見られます。

最後にnotebookの内容をまとめたフルのコードがあるが、とてもシンプルに書ける。

import base64
import os
os.environ["HF_TOKEN"] = "YOUR_HF_TOKEN" # ColPaliモデルのダウンロードに必要
os.environ["ANTHROPIC_API_KEY"] = "YOUR_ANTHROPIC_API_KEY"
from byaldi import RAGMultiModalModel
from claudette import *

# モデルのロード
RAG = RAGMultiModalModel.from_pretrained("vidore/colpali-v1.2", verbose=1)

# ドキュメントからインデックス作成
RAG.index(
    input_path="./docs/attention.pdf",
    index_name="attention",
    store_collection_with_index=True,
    overwrite=True
)

# クエリの定義
query = "What's the BLEU score for the transformer base model?"

# モデルに対してクエリ実行
results = RAG.search(query, k=1)

# 検索結果上位をClaudeにわたす
image_bytes = base64.b64decode(results[0].base64)
chat = Chat(models[1])

# Claudeの回答を出力
print(chat([image_bytes, query]))
kun432kun432

まとめ

自分が試した限り、日本語PDFには対応していないように思えるが、おそらくこれはColPaliモデルが英語でのみ学習されているためだと思っている。ColPaliのレポジトリには、トレーニングについても記載されているので、頑張ればできるのかも?知らんけど。

その点については残念ではあるものの、検索精度としては触ってみた感じは良さそうであるし、Byaldiのおかげで(あとclaudetteも)、かなり少ないコードでマルチモーダルRAGができるのは非常にありがたい。

上でポストを紹介した、ゆめふく(@y_fukumochi)さんがされてたように、Qwen2-VL-2B-Instructあたりを使えば、ローカルでもなかなかの精度でマルチモーダルRAGが実現できそうに思う。

このスクラップは3ヶ月前にクローズされました