マルチモーダルLLM(Large Language Model)は、テキストだけでなく画像や音声などの複数のモダリティ(情報の種類)を理解・生成できる大規模言語モデルです。GPT-4V、Gemini、Claude 3といった最新のAIモデルは、画像を入力として受け取り、その内容について自然言語で対話できます。
従来のLLMがテキストのみを扱っていたのに対し、マルチモーダルLLMは「見て、理解して、話す」ことができます。本記事では、マルチモーダルLLMの設計パターン、画像とテキストの統合方法、学習戦略について解説します。
本記事の内容
- マルチモーダルLLMの概要と分類
- アーキテクチャパターン(Early/Late Fusion)
- 画像エンコーダとプロジェクション
- 学習戦略(事前学習、ファインチューニング)
- 代表的なモデル(GPT-4V、Gemini、LLaVA)
前提知識
この記事を読む前に、以下の記事を読んでおくと理解が深まります。
マルチモーダルLLMとは
定義
マルチモーダルLLM(Multimodal Large Language Model、MLLM)は、複数のモダリティの入力を受け取り、テキストを生成するモデルです。
主なモダリティ: – テキスト: 自然言語 – 画像: 静止画 – 動画: フレームの系列 – 音声: 音声波形またはスペクトログラム
本記事では、最も一般的な画像+テキストのマルチモーダルLLMに焦点を当てます。
タスク例
マルチモーダルLLMは以下のようなタスクを実行できます。
画像理解: – 画像キャプション生成 – Visual Question Answering(VQA) – 画像内のテキスト読解(OCR) – 物体検出・カウント
対話: – 画像についての質問応答 – 画像の詳細な説明 – 画像の比較・分析
推論: – 画像内の関係性の理解 – 常識的推論 – 数学的問題の解決(図を含む)
アーキテクチャの基本パターン
マルチモーダルLLMのアーキテクチャは、大きく分けて以下のコンポーネントで構成されます。
[画像入力] [テキスト入力]
↓ ↓
[画像エンコーダ] [テキストトークナイザ]
↓ ↓
[画像特徴] [テキスト埋め込み]
↓ ↓
└──────┬───────────┘
↓
[融合モジュール]
↓
[LLMバックボーン]
↓
[テキスト出力]
1. 画像エンコーダ
画像を特徴ベクトルに変換するモジュールです。
主な選択肢: – CLIP ViT: CLIP事前学習済みのVision Transformer – SigLIP: 改良されたCLIP(シグモイド損失を使用) – EVA: より大規模なViT
画像エンコーダは通常、事前学習済みのモデルを使用し、学習中は凍結または低いlearning rateで更新します。
2. プロジェクション(融合モジュール)
画像特徴とテキスト埋め込みを同じ空間に写像するモジュールです。
主な選択肢:
線形プロジェクション: $$ \bm{h}_{\text{img}} = \bm{W}_{\text{proj}} \bm{z}_{\text{img}} + \bm{b}_{\text{proj}} $$
シンプルで効果的。LLaVAなどで採用。
MLP: $$ \bm{h}_{\text{img}} = \text{MLP}(\bm{z}_{\text{img}}) = \bm{W}_2 \cdot \text{GELU}(\bm{W}_1 \bm{z}_{\text{img}}) $$
より表現力が高い。
Q-Former(BLIP-2): 学習可能なクエリトークンを使って画像特徴から情報を抽出するTransformer。
Perceiver Resampler(Flamingo): Cross-Attentionで可変長の画像特徴を固定長に変換。
3. LLMバックボーン
テキスト生成を担うLLMです。
主な選択肢: – LLaMA系: LLaMA 2, Vicuna – Mistral – GPT系: 独自モデル(GPT-4V) – Gemini: Google独自
LLMは大量のテキストで事前学習されており、その言語能力を活用します。
Early Fusion vs Late Fusion
Early Fusion
画像とテキストを早い段階で結合し、統一的に処理します。
[画像トークン] + [テキストトークン]
↓
[Transformer層 ×N]
↓
[出力トークン]
画像特徴を「ビジュアルトークン」としてテキストトークン列に挿入し、通常のTransformerで処理します。
利点: – 画像とテキストの深い相互作用 – シンプルな実装
欠点: – 画像トークン数が多いと計算コストが高い – 画像エンコーダとLLMを密結合
採用モデル: LLaVA, GPT-4V
Late Fusion
画像とテキストを別々に処理し、後から結合します。
[画像] → [画像エンコーダ] → [画像特徴]
↓
[テキスト] → [テキスト埋め込み] → [Cross-Attention] → [LLM]
Cross-Attentionを使って、テキスト処理時に画像特徴を参照します。
利点: – 計算効率が良い(Cross-Attentionの計算量は線形) – モジュール性が高い
欠点: – 相互作用が限定的
採用モデル: Flamingo
ハイブリッドアプローチ
多くの最新モデルは、両方の要素を組み合わせています。
画像トークンの処理
パッチトークン
ViTからの出力は、各パッチに対応するトークンの列です。
224×224画像、パッチサイズ14の場合: $$ N_{\text{tokens}} = \left(\frac{224}{14}\right)^2 = 256 $$
これらをそのままLLMに入力すると、系列長が大幅に増加します。
トークン数の削減
計算コストを抑えるため、様々な手法でトークン数を削減します。
1. プーリング:
# 2×2プーリングで256→64トークン
pooled = F.avg_pool2d(tokens.view(B, H, W, D).permute(0, 3, 1, 2), 2)
2. Perceiver Resampler: 固定数の学習可能クエリで画像特徴をリサンプリング。
3. 選択的パッチ: 重要なパッチのみを選択。
高解像度対応
高解像度画像を扱うための手法:
1. タイル分割: 画像を複数のタイルに分割し、各タイルを独立にエンコード。
2. マルチスケール: 異なる解像度でエンコードした特徴を組み合わせ。
学習戦略
段階的学習
多くのマルチモーダルLLMは、段階的な学習戦略を採用しています。
Stage 1: プロジェクション学習(Alignment)
目的: 画像特徴とテキスト埋め込み空間の整合性を取る
設定: – 画像エンコーダ: 凍結 – LLM: 凍結 – プロジェクション: 学習
データ: 画像-キャプションペア(CC3M等)
Stage 2: 指示チューニング(Instruction Tuning)
目的: 対話的なタスクに適応
設定: – 画像エンコーダ: 凍結または低LR – LLM: LoRAまたはフル学習 – プロジェクション: 学習
データ: VQAデータセット、指示データ
損失関数
標準的なオートレグレッシブ言語モデリング損失を使用:
$$
\mathcal{L} = -\sum_{t} \log p(y_t | y_{ ここで $\bm{I}$ は画像、$\bm{x}$ は入力テキスト、$y$ は出力テキストです。 通常、入力部分(画像トークン、ユーザーのプロンプト)の損失は計算せず、モデルの応答部分のみで学習します。 OpenAIの最新マルチモーダルモデル(2023年)。 特徴:
– 高度な視覚理解能力
– 複雑な推論タスクに対応
– 画像内テキストの読解(OCR)
– 詳細は非公開 Google DeepMindのマルチモーダルモデル(2023年)。 特徴:
– 最初からマルチモーダルとして設計
– 画像、音声、動画に対応
– Ultra, Pro, Nanoの3サイズ Anthropicのマルチモーダルモデル(2024年)。 特徴:
– 長いコンテキスト(200K tokens)
– 詳細な画像分析
– Opus, Sonnet, Haikuの3サイズ オープンソースのマルチモーダルLLM(2023年)。 特徴:
– シンプルなアーキテクチャ
– 効率的な学習
– 再現可能なパイプライン 詳細は次の記事で解説: LLaVAアーキテクチャを解説 Salesforceのマルチモーダルモデル(2023年)。 特徴:
– Q-Formerによる効率的な画像-テキスト橋渡し
– 凍結されたモデルを活用
– 計算効率が高い マルチモーダルLLMの評価には、様々なベンチマークが使用されます。 画像に関する質問に答えるタスク。 本記事では、マルチモーダルLLMの仕組みを解説しました。 マルチモーダルLLMは、AIの次の大きなフロンティアであり、急速に発展している分野です。 次のステップとして、以下の記事も参考にしてください。代表的なモデル
GPT-4V(Vision)
Gemini
Claude 3
LLaVA
BLIP-2
PyTorchでの簡易実装
シンプルなマルチモーダルLLM
import torch
import torch.nn as nn
class SimpleMultimodalLLM(nn.Module):
"""シンプルなマルチモーダルLLM"""
def __init__(
self,
vision_encoder,
llm,
vision_dim=1024,
llm_dim=4096,
num_vision_tokens=256,
):
super().__init__()
# 画像エンコーダ(事前学習済み、凍結)
self.vision_encoder = vision_encoder
for param in self.vision_encoder.parameters():
param.requires_grad = False
# プロジェクション(学習対象)
self.vision_projection = nn.Sequential(
nn.Linear(vision_dim, llm_dim),
nn.GELU(),
nn.Linear(llm_dim, llm_dim),
)
# LLM(LoRAなどで効率的に学習)
self.llm = llm
def encode_image(self, image):
"""画像をLLMトークン空間にエンコード"""
with torch.no_grad():
# (B, num_patches, vision_dim)
vision_features = self.vision_encoder(image)
# (B, num_patches, llm_dim)
vision_tokens = self.vision_projection(vision_features)
return vision_tokens
def forward(self, image, input_ids, attention_mask=None, labels=None):
"""
Args:
image: (B, 3, H, W) 入力画像
input_ids: (B, seq_len) テキストトークンID
attention_mask: (B, seq_len) アテンションマスク
labels: (B, seq_len) 学習ターゲット
"""
# 画像をエンコード
vision_tokens = self.encode_image(image) # (B, num_img_tokens, llm_dim)
# テキスト埋め込み
text_embeds = self.llm.get_input_embeddings()(input_ids) # (B, seq_len, llm_dim)
# 画像トークンとテキストトークンを結合
# [IMG_1, IMG_2, ..., IMG_N, TEXT_1, TEXT_2, ...]
inputs_embeds = torch.cat([vision_tokens, text_embeds], dim=1)
# アテンションマスクも調整
num_img_tokens = vision_tokens.shape[1]
img_attention_mask = torch.ones(
vision_tokens.shape[0], num_img_tokens,
dtype=attention_mask.dtype, device=attention_mask.device
)
full_attention_mask = torch.cat([img_attention_mask, attention_mask], dim=1)
# ラベルも調整(画像トークン部分は-100で無視)
if labels is not None:
img_labels = torch.full(
(labels.shape[0], num_img_tokens),
-100, # 損失計算で無視
dtype=labels.dtype, device=labels.device
)
full_labels = torch.cat([img_labels, labels], dim=1)
else:
full_labels = None
# LLMに入力
outputs = self.llm(
inputs_embeds=inputs_embeds,
attention_mask=full_attention_mask,
labels=full_labels,
)
return outputs
@torch.no_grad()
def generate(self, image, prompt_ids, max_new_tokens=100, **kwargs):
"""画像とプロンプトから生成"""
vision_tokens = self.encode_image(image)
prompt_embeds = self.llm.get_input_embeddings()(prompt_ids)
inputs_embeds = torch.cat([vision_tokens, prompt_embeds], dim=1)
# 生成
outputs = self.llm.generate(
inputs_embeds=inputs_embeds,
max_new_tokens=max_new_tokens,
**kwargs
)
return outputs
使用例
# 仮想的な使用例
# モデル作成
from transformers import CLIPVisionModel, AutoModelForCausalLM
vision_encoder = CLIPVisionModel.from_pretrained("openai/clip-vit-large-patch14")
llm = AutoModelForCausalLM.from_pretrained("meta-llama/Llama-2-7b-hf")
model = SimpleMultimodalLLM(
vision_encoder=vision_encoder,
llm=llm,
vision_dim=1024,
llm_dim=4096,
)
# 推論
image = load_image("example.jpg") # (1, 3, 224, 224)
prompt = "この画像について説明してください。"
prompt_ids = tokenizer(prompt, return_tensors="pt").input_ids
output_ids = model.generate(image, prompt_ids, max_new_tokens=100)
response = tokenizer.decode(output_ids[0])
print(response)
評価ベンチマーク
VQA(Visual Question Answering)
画像キャプション
複合ベンチマーク
課題と今後の方向性
現在の課題
今後の方向性
まとめ