マルチモーダル埋め込み & Sentence Transformers を使ったリランキングモデル
マルチモーダル埋め込みモデルは、異なるモダリティの入力を共通の埋め込み空間へマッピングします。一方、マルチモーダル再ランカー(reranker)モデルは、異種モダリティのペアの関連性をスコア付けします。これにより、視覚ドキュメント検索、クロスモーダル検索、そしてマルチモーダルRAGパイプラインといったユースケースが可能になります。
目次
マルチモーダルモデルとは?
従来の埋め込みモデルはテキストを固定長のベクトルに変換します。マルチモーダル埋め込みモデルはこれを拡張し、異なるモダリティ(テキスト、画像、音声、または動画)からの入力を共通の埋め込み空間へマッピングします。つまり、同じ類似度関数を使って、テキストクエリと画像ドキュメント(あるいはその逆)を比較できるということです。
同様に、従来の再ランカー(Cross Encoder)モデルは、テキスト同士のペア間の関連性スコアを計算します。マルチモーダル再ランカーは、片方または両方の要素が画像であるペア、テキストと画像を組み合わせたドキュメント、またはその他のモダリティで構成されたペアをスコア付けできます。
たとえば、テキストクエリを画像ドキュメントと比較して一致するものを見つけたり、説明に合う動画クリップを検索したり、モダリティをまたいで動作するRAGパイプラインを構築したりできます。
インストール
マルチモーダルモデルには、追加の依存関係がいくつか必要です。必要なモダリティに対応するextrasをインストールしてください(詳しくはインストールを参照):
# 画像サポートの場合
pip install -U "sentence-transformers[image]"
# 音声サポートの場合
pip install -U "sentence-transformers[audio]"
# 動画サポートの場合
pip install -U "sentence-transformers[video]"
# 必要に応じて組み合わせる
pip install -U "sentence-transformers[image,video,train]"
Qwen3-VL-2BのようなVLMベースのモデルでは、少なくとも約8 GBのVRAMを備えたGPUが必要です。8Bのバリアントでは約20 GBを見込んでください。ローカルにGPUがない場合は、クラウドGPUサービスやGoogle Colabの利用を検討してください。CPUの場合、これらのモデルは非常に遅くなります。CPU推論には、テキストのみのモデルやCLIPモデルのほうが適しています。
マルチモーダル 埋め込みモデル
モデルの読み込み
マルチモーダル埋め込みモデルを読み込むことは、テキストのみのモデルを読み込むのと全く同じように動作します。
from sentence_transformers import SentenceTransformer
model = SentenceTransformer("Qwen/Qwen3-VL-Embedding-2B", revision="refs/pr/23")
現時点では
revision引数が必須です。これらのモデルの統合用プルリクエストがまだ保留のためです。マージされれば、revision を指定しなくても読み込めるようになります。
モデルは自動的に対応するモダリティを検出するため、追加で設定する必要はありません。画像の解像度やモデルの精度などを制御したい場合は、Processor and Model kwargs を参照してください。
画像のエンコード
マルチモーダルモデルを読み込むと、model.encode() はテキストとともに画像を受け付けます。画像は URL、ローカルファイルパス、または PIL Image オブジェクトとして指定できます(受け付けるすべての形式については Supported Input Types を参照してください)。
from sentence_transformers import SentenceTransformer
model = SentenceTransformer("Qwen/Qwen3-VL-Embedding-2B", revision="refs/pr/23")
# URL から画像をエンコード
img_embeddings = model.encode([
"https://huggingface.co/datasets/huggingface/documentation-images/resolve/main/transformers/tasks/car.jpg",
"https://huggingface.co/datasets/huggingface/documentation-images/resolve/main/bee.jpg",
])
print(img_embeddings.shape)
# (2, 2048)
クロスモーダル類似度
モデルは両方を同じ空間に写像するため、テキスト埋め込みと画像埋め込みの間の類似度を計算できます。
from sentence_transformers import SentenceTransformer
model = SentenceTransformer("Qwen/Qwen3-VL-Embedding-2B", revision="refs/pr/23")
# 画像をエンコード
img_embeddings = model.encode([
"https://huggingface.co/datasets/huggingface/documentation-images/resolve/main/transformers/tasks/car.jpg",
"https://huggingface.co/datasets/huggingface/documentation-images/resolve/main/bee.jpg",
])
# テキストクエリをエンコード(画像ごとに一致1つ+ハードネガティブ1つ)
text_embeddings = model.encode([
"A green car parked in front of a yellow building",
"A red car driving on a highway",
"A bee on a pink flower",
"A wasp on a wooden table",
])
# クロスモーダル類似度を計算する
similarities = model.similarity(text_embeddings, img_embeddings)
print(similarities)
# tensor([[0.5115, 0.1078],
# [0.1999, 0.1108],
# [0.1255, 0.6749],
# [0.1283, 0.2704]])
予想どおり、「黄色い建物の前に駐車された緑の車」は車の画像(0.51)に最も似ており、「ピンクの花の上のハチ」はハチの画像(0.67)に最も似ています。難しいネガティブ(「高速道路を走行している赤い車」、「木のテーブルの上のスズメバチ」)は、正しくより低いスコアを受け取っています。
最も良い一致スコア(0.51、0.67)であっても、1.0 にあまり近くないことに気づくかもしれません。これはモダリティ・ギャップによるものです。異なるモダリティの埋め込みは、空間の別々の領域にクラスタリングされやすいのです。クロスモーダルの類似度は、通常、同一モダリティ内(たとえばテキスト同士)のものより低くなりますが、相対的な順位付けは維持されるため、検索は引き続きうまく機能します。
クエリとドキュメントのエンコード
検索タスクでは、encode_query()とencode_document()が推奨されるメソッドです。多くの検索モデルは、入力がクエリなのかドキュメントなのかによって異なる指示プロンプトを前置しますが、これはチャットモデルが目標に応じて異なるシステムプロンプトを適用しうるのと似ています。モデル作者はモデル設定でプロンプトを指定でき、encode_query()/encode_document()はそれを自動的に読み込み、正しいものを適用します:
encode_query()は、利用可能ならモデルの"query"プロンプトを使用し、task="query"を設定します。encode_document()は、"document"、"passage"、または"corpus"のうち最初に利用可能なプロンプトを使用し、task="document"を設定します。
内部では、どちらもencode()の薄いラッパーであり、プロンプト選択だけを担ってくれます。以下がクロスモーダル検索の様子です:
from sentence_transformers import SentenceTransformer
model = SentenceTransformer("Qwen/Qwen3-VL-Embedding-2B", revision="refs/pr/23")
# クエリプロンプトでテキストクエリをエンコードする
query_embeddings = model.encode_query([
"建物の近くに駐車された車の写真を見つけてください",
"送粉(受粉)する昆虫の画像を見せてください",
])
# ドキュメントプロンプトでドキュメントのスクリーンショットをエンコードする
doc_embeddings = model.encode_document([
"https://huggingface.co/datasets/huggingface/documentation-images/resolve/main/transformers/tasks/car.jpg",
"https://huggingface.co/datasets/huggingface/documentation-images/resolve/main/bee.jpg",
])
# 類似度を計算する
similarities = model.similarity(query_embeddings, doc_embeddings)
print(similarities)
# tensor([[0.3907, 0.1490],
# [0.1235, 0.4872]])
これらのメソッドは、encode()と同じ入力タイプ(画像、URL、マルチモーダルのdictなど)を受け取り、同じパラメータをそのまま渡します。クエリ/ドキュメント専用プロンプトを持たないモデルでは、これらはencode()とまったく同じ動作をします。
マルチモーダル・リランカー・モデル
マルチモーダル・リランカー(CrossEncoder)モデルは、入力のペア間の関連性をスコアリングします。ここで各要素は、テキスト、画像、音声、動画、またはそれらの組み合わせになり得ます。品質の面では埋め込みモデルよりも高い性能を発揮する傾向がありますが、各ペアを個別に処理するため遅くなります。現在利用可能な事前学習済みのマルチモーダル・リランカーはテキストと画像入力に焦点を当てていますが、アーキテクチャは、基盤となるモデルが扱える任意のモダリティをサポートしています。
ランキング混合モダリティドキュメント
rank() メソッドは、クエリに対して文書のリストをスコア付けし順位付けします。混合モダリティをサポートしています:
from sentence_transformers import CrossEncoder
model = CrossEncoder("Qwen/Qwen3-VL-Reranker-2B", revision="refs/pr/11")
query = "A green car parked in front of a yellow building"
documents = [
# 画像ドキュメント(URL またはローカルファイルパス)
"https://huggingface.co/datasets/huggingface/documentation-images/resolve/main/transformers/tasks/car.jpg",
"https://huggingface.co/datasets/huggingface/documentation-images/resolve/main/bee.jpg",
# テキストドキュメント
"A vintage Volkswagen Beetle painted in bright green sits in a driveway.",
# テキスト + 画像ドキュメントを組み合わせたもの
{
"text": "A car in a European city",
"image": "https://huggingface.co/datasets/huggingface/documentation-images/resolve/main/transformers/tasks/car.jpg",
},
]
rankings = model.rank(query, documents)
for rank in rankings:
print(f"<span class=\"hljs-subst\">{rank['score']:.4f}</span>\t(document <span class=\"hljs-subst\">{rank['corpus_id']}</span>)")
"""
0.9375 (document 0)
0.5000 (document 3)
-1.2500 (document 2)
-2.4375 (document 1)
"""
絶対スコアには モダリティギャップ が影響し得る点に留意してください。テキスト-画像のペアのスコアは、テキスト-テキストや画像-画像のペアのスコアとは別の範囲に収まる可能性があります。
埋め込みモデルと同様に、リランカーがどのモダリティをサポートしているかも、modalities と supports() を使って確認できます:
print(model.modalities)
# ['text', 'image', 'video', 'message']
print(model.supports("image"))
# True
# モダリティの特定のペアをサポートしているか確認する
print(model.supports(("image", "text")))
# True
ペアスコアの予測
特定の入力ペアに対して生の関連性スコアを得るには、predict() も使用できます:
from sentence_transformers import CrossEncoder
model = CrossEncoder("jinaai/jina-reranker-m0", trust_remote_code=True)
返却形式: {"translated": "翻訳されたHTML"}scores = model.predict([
("緑の車", "https://huggingface.co/datasets/huggingface/documentation-images/resolve/main/transformers/tasks/car.jpg"),
("花の上のハチ", "https://huggingface.co/datasets/huggingface/documentation-images/resolve/main/bee.jpg"),
("緑の車", "https://huggingface.co/datasets/huggingface/documentation-images/resolve/main/bee.jpg"),
])
print(scores)
# [0.9389156 0.96922314 0.46063158]
取得(Retrieve)と再ランキング(Rerank)
よくあるパターンとして、まず埋め込みモデルで高速に初期取得し、その後に上位結果を再ランキングで精密化します:
from sentence_transformers import SentenceTransformer, CrossEncoder
# ステップ1:埋め込みモデルで取得する
embedder = SentenceTransformer("Qwen/Qwen3-VL-Embedding-2B", revision="refs/pr/23")
query = "revenue growth chart"
query_embedding = embedder.encode_query(query)
# コーパスの埋め込みを事前計算(1回だけ実行して保存する)
document_screenshots = [
"path/to/doc1.png",
"path/to/doc2.png",
# ... 何百万ものドキュメントのスクリーンショットである可能性
]
corpus_embeddings = embedder.encode_document(document_screenshots, show_progress_bar=True)
# 単純なコサイン類似度による取得(埋め込みがメモリに収まる限り実行可能)
similarities = embedder.similarity(query_embedding, corpus_embeddings)
top_k_indices = similarities.argsort(descending=True)[0][:10]
# ステップ2:上位k件を再ランキングモデルで再評価する
reranker = CrossEncoder(
"nvidia/llama-nemotron-rerank-vl-1b-v2",
trust_remote_code=True,
revision="refs/pr/9",
)
top_k_documents = [document_screenshots[i] for i in top_k_indices]
rankings = reranker.rank(query, top_k_documents)
for rank in rankings:
print(f"{rank['score']:.4f}\t{top_k_documents[rank['corpus_id']]}")
コーパスの埋め込みが事前計算されているため、何百万件ものドキュメントに対しても初期取得は高速です。その後、再ランキングは小さな候補集合に対して、より正確なスコアリングを提供します。
入力フォーマットと設定
サポートされている入力タイプ
マルチモーダルモデルは、さまざまな入力形式を受け付けます。以下は、model.encode() に渡せる内容の概要です:
| モダリティ | 受け付ける形式 |
|---|---|
| テキスト | - 文字列 |
| 画像 | - PIL.Image.Image オブジェクト- ファイルパス(例: "./photo.jpg")- URL(例: "https://.../image.jpg")- NumPy配列、torchテンソル |
| 音声 | - ファイルパス(例:"./audio.wav")- URL(例: "https://.../audio.wav")- NumPy/torch配列 - キーが "array" と "sampling_rate" である辞書- torchcodec.AudioDecoder インスタンス |
| 動画 | - ファイルパス(例:"./video.mp4")- URL(例: "https://.../video.mp4")- NumPy/torch配列 - キーが "array" と "video_metadata" である辞書返却形式: {"translated": "翻訳されたHTML"}- torchcodec.VideoDecoder インスタンス |
| Multimodal | - モダリティ名を値に対応付ける Dict です。 例: {"text": "a caption", "image": "https://.../image.jpg"}有効なキー: "text"、"image"、"audio"、"video" |
| Message | - "role" キーと "content" キーを持つメッセージ dict のリストです。例: [{"role": "user", "content": [...]}] |
モダリティの対応状況を確認
modalities プロパティと supports() メソッドを使うことで、モデルが対応しているモダリティを確認できます:
from sentence_transformers import SentenceTransformer
model = SentenceTransformer("Qwen/Qwen3-VL-Embedding-2B", revision="refs/pr/23")
# 対応しているモダリティをすべて一覧表示
print(model.modalities)
# ['text', 'image', 'video', 'message']
# 特定のモダリティの確認
print(model.supports("image"))
# True
print(model.supports("audio"))
# False
"message" モダリティは、モデルが内容が交互に混在したチャット形式のメッセージ入力を受け付けることを示します。実際には、このモダリティを直接使う必要はほとんどありません。文字列、URL、またはマルチモーダル dict を渡すと、モデルが内部でそれらを適切なメッセージ形式に変換します。Sentence Transformers は 2 種類のメッセージ形式をサポートしています:
- Structured(ほとんどの VLM、例: Qwen3-VL): 内容は型付き dict のリストです。例:
[{"type": "text", "text": "..."}, {"type": "image", "image": ...}] - Flat(例: Deepseek-V3): 内容は直接の値です。例:
"some text"
形式はモデルのチャットテンプレートから自動検出されます。
すべての入力は内部で同じメッセージ形式に変換されるため、単一のencode() 呼び出しで入力タイプを混在させることができます:
embeddings = model.encode([
# テキスト入力
"A green car parked in front of a yellow building",
# 画像入力(URL)
"https://huggingface.co/datasets/huggingface/documentation-images/resolve/main/transformers/tasks/car.jpg",
# 組み合わせ(テキスト + 画像)入力
{
"text": "A car in a European city",
"image": "https://huggingface.co/datasets/huggingface/documentation-images/resolve/main/transformers/tasks/car.jpg",
},
])
生のメッセージ入力を渡す必要がある場合はこちらをクリック
モデルが上記のいずれの形式にも従わず、完全な制御が必要な場合は、role キーと content キーを持つ生のメッセージ dict を直接渡せます:
embeddings = model.encode([
[
{
"role": "user",
"content": [
{"type": "image", "image": "https://huggingface.co/datasets/huggingface/documentation-images/resolve/main/transformers/tasks/car.jpg"},
{"type": "text", "text": "Describe this vehicle."},
],
}
],
])
これにより自動の形式変換がスキップされ、メッセージがそのままプロセッサの apply_chat_template() に渡されます。
プロセッサとモデルの kwargs
画像の解像度の範囲やモデルの精度を制御したくなるかもしれません。モデルを読み込む際には processor_kwargs と model_kwargs を使います:
model = SentenceTransformer(
"Qwen/Qwen3-VL-Embedding-2B",
model_kwargs={"attn_implementation": "flash_attention_2", "torch_dtype": "bfloat16"},
processor_kwargs={"min_pixels": 28 * 28, "max_pixels": 600 * 600},
revision="refs/pr/23",
)
processor_kwargsは、入力の前処理の方法(例: 画像解像度の上限・下限)を制御します。より大きいmax_pixelsは品質の向上につながりますが、メモリと計算量が増えます。これらはAutoProcessor.from_pretrained(...)にそのまま渡されます。model_kwargsは、基盤となるモデルの読み込み方法(例: 精度、注意(attention)の実装)を制御します。これらは、(モデルモジュールの設定に応じて)適切なAutoModel.from_pretrained(...)の呼び出し(例:AutoModel、AutoModelForCausalLM、AutoModelForSequenceClassificationなど)にそのまま渡されます。
これらの kwargs の詳細については、SentenceTransformer API リファレンスのドキュメントを参照してください。
Sentence Transformers v5.4 では、マルチモーダルモデルがトークナイザだけでなくプロセッサを使用することを反映して、
tokenizer_kwargsがprocessor_kwargsに改名されました。旧名は引き続き受け付けられますが、非推奨です。
対応モデル
以下は v5.4 で対応しているマルチモーダルモデルで、v5.4 の統合コレクションでも利用可能です:
対応マルチモーダル埋め込み(Embedding)モデル
| モデル | パラメータ | モダリティ | リビジョン |
|---|---|---|---|
| Qwen/Qwen3-VL-Embedding-2B | 2B | テキスト、画像、動画 | revision="refs/pr/23" |
| Qwen/Qwen3-VL-Embedding-8B | 8B | テキスト、画像、動画 | revision="refs/pr/11" |
| nvidia/llama-nemotron-embed-vl-1b-v2 | 1.7B | テキスト、画像 | revision="refs/pr/6" |
| nvidia/omni-embed-nemotron-3b | 4.7B | テキスト、画像 | revision="refs/pr/3" |
対応マルチモーダル再ランキング(Reranker)モデル
| モデル | パラメータ | モダリティ | リビジョン |
|---|---|---|---|
| Qwen/Qwen3-VL-Reranker-2B | 2B | テキスト、画像、動画 | revision="refs/pr/11" |
| Qwen/Qwen3-VL-Reranker-8B | 8B | テキスト、画像、動画 | revision="refs/pr/9" |
| nvidia/llama-nemotron-rerank-vl-1b-v2 | 2B | テキスト、画像 | revision="refs/pr/9" |
| jinaai/jina-reranker-m0 | 2B | テキスト、画像 | リビジョン revision は不要 |
テキスト専用リランカー・モデル(v5.4でも新登場)
| モデル | パラメータ数 | 改訂 |
|---|---|---|
| Qwen/Qwen3-Reranker-0.6B | 0.6B | revision="refs/pr/24" |
| Qwen/Qwen3-Reranker-4B | 4B | revision="refs/pr/11" |
| Qwen/Qwen3-Reranker-8B | 8B | revision="refs/pr/11" |
| mixedbread-ai/mxbai-rerank-base-v2 | 0.5B | revision は不要 |
| mixedbread-ai/mxbai-rerank-large-v2 | 2B | revision は不要 |
テキスト専用リランカーの使用例はこちら
from sentence_transformers import CrossEncoder
model = CrossEncoder("mixedbread-ai/mxbai-rerank-base-v2")
query = "How do I bake sourdough bread?"
documents = [
"Sourdough bread requires a starter made from flour and water, fermented over several days.",
"The history of bread dates back to ancient Egypt around 8000 BCE.",
"To bake sourdough, mix your starter with flour, water, and salt, then let it rise overnight.",
"Rye bread is a popular alternative to wheat-based breads in Northern Europe.",
]
pairs = [(query, doc) for doc in documents]
scores = model.predict(pairs)
print(scores)
# [ 7.3077507 -2.6217823 8.724761 -2.2488995]
rankings = model.rank(query, documents)
for rank in rankings:
print(f"{rank['score']:.4f}\t{documents[rank['corpus_id']]}")
# 8.7248 To bake sourdough, mix your starter with flour, water, and salt, then let it rise overnight.
# 7.3078 Sourdough bread requires a starter made from flour and water, fermented over several days.
# -2.2489 Rye bread is a popular alternative to wheat-based breads in Northern Europe.
# -2.6218 The history of bread dates back to ancient Egypt around 8000 BCE.
CLIPモデル
古いCLIPモデルも引き続きサポートされています:
| モデル | ImageNet Zero-Shot Top-1 精度 | 注記 |
|---|---|---|
| sentence-transformers/clip-ViT-L-14 | 75.4 | |
| sentence-transformers/clip-ViT-B-16 | 68.1 | |
| sentence-transformers/clip-ViT-B-32 | 63.3 | |
| sentence-transformers/clip-ViT-B-32-multilingual-v1 | N/A | 多言語テキストエンコーダ、50+言語 |
これらのシンプルなCLIPモデルは、低スペックのハードウェアでも引き続きうまく動作します。
CLIPの使用例はこちら
from sentence_transformers import SentenceTransformer
model = SentenceTransformer("sentence-transformers/clip-ViT-L-14")
images = [
"https://huggingface.co/datasets/huggingface/documentation-images/resolve/main/transformers/tasks/car.jpg",
"https://huggingface.co/datasets/huggingface/documentation-images/resolve/main/bee.jpg",
"https://huggingface.co/datasets/huggingface/cats-image/resolve/main/cats_image.jpeg"
]
texts = ["A green car", "A bee on a flower", "Some cats on a couch", "One cat sitting in the window"]
image_embeddings = model.encode(images)
text_embeddings = model.encode(texts)
print(image_embeddings.shape, text_embeddings.shape)
# (3, 768) (4, 768)
追加のリソース
ドキュメント
- Sentence Transformer > 使い方
- Sentence Transformer > 事前学習済みモデル
- Cross Encoder > 使い方
- Cross Encoder > 事前学習済みモデル
- インストール
トレーニング
今後数週間のうちに、マルチモーダルモデルのトレーニングとファインチューニングに関するブログ記事を公開する予定なので、お楽しみに!それまでの間は、事前学習済みモデルで推論を試したり、トレーニングドキュメントを使ってトレーニングの実験をしてみたりできます:
- Sentence Transformer > トレーニング概要
- Sentence Transformer > トレーニング例
- Cross Encoder > トレーニング概要
- Cross Encoder > トレーニング例
- Sparse Encoder > トレーニング概要
- Sparse Encoder > トレーニング例



