系列 · NLP 技术前沿 · 第 11 篇

自然语言处理(十一):多模态大模型

多模态大模型深度解析:CLIP 的对比学习、BLIP-2 的 Q-Former 桥接架构、LLaVA 的视觉指令微调、Whisper 语音识别、GPT-4V 能力图谱以及 MMBench/MME/MMMU 评测体系——配可运行代码。

人类从来不会只通过单一感官来理解世界——我们看图表时会同时读标题,观察表情时会留意语气,讨论代码 bug 时也会瞥一眼截图。然而,纯文本语言模型对这些丰富的多模态信息完全无感,既“听不见”也“看不见”。多模态大语言模型(Multimodal Large Language Models, MLLMs) 的出现正是为了填补这一空白:它们将图像、音频和视频映射到与语言模型一致的表示空间中,从而让模型能够理解和处理多模态信息。

过去四年的发展可以用三个关键节点概括:首先是 CLIP(2021),它证明了从网络上收集的 4 亿对带噪声的图文数据足以训练出一个统一的嵌入空间,在这个空间里,文本和图像可以通过余弦相似度相互检索;接着是 BLIP-2(2023),它大幅降低了对齐成本,只需冻结一个强大的视觉编码器和一个强大的语言模型,然后在两者之间训练一个仅约 1.88 亿参数的“Q-Former”模块;最后是 LLaVA / GPT-4V(2023–2024),它们进一步简化了桥接方式,用一行线性投影替代复杂的中间模块,并引入了视觉指令微调——将图像嵌入当作词嵌入直接输入语言模型,再用 GPT-4 生成的视觉对话数据进行微调。最终得到的模型不仅能读懂图表、分析截图,还能像聊天助手一样流畅地描述照片内容。

本文将沿着这条技术主线深入探讨多模态 NLP 的方方面面:从对比对齐背后的数学原理到可扩展的模型架构,从音频领域的进展(如 Whisper)到 GPT-4V 的能力边界,以及如何正确解读 MMBench、MME 和 MMMU 等基准测试的结果——所有内容均附有可运行的代码示例。

自然语言处理(十一):多模态大模型 — 章节概览图


你将学到什么#

自然语言处理(十一):多模态大模型 — 章节小结图

  • CLIP: InfoNCE 对比损失的核心原理、双编码器架构的设计逻辑、零样本分类的实现方式,以及温度参数 $\tau$ 如何影响模型训练
  • BLIP-2: Q-Former 的工作原理及其优势、 32 个可学习查询的作用,以及两阶段预训练策略的具体方法
  • LLaVA:视觉指令微调的技术细节、为什么一个仅有 4M 参数的线性投影层能媲美更复杂的连接器,以及利用 GPT-4 蒸馏生成指令数据的独特技巧
  • Whisper:基于 log-mel 频谱的多语言语音识别技术、编码器-解码器架构的应用、特殊 token 的控制机制,以及模型大小与性能之间的权衡
  • GPT-4V 和前沿 MLLM:这些模型的能力矩阵、跨领域推理的实际表现,以及常见的失效模式(如计数问题、细粒度定位困难和幻觉现象)
  • 评测方法:深入解析 MMBench、 MME、 MMMU、 SEED-Bench 和 POPE 等评测工具,了解它们各自的侧重点,并掌握如何公平地对比不同模型的得分
  • 实战应用:构建基于 CLIP 的多模态检索系统、实现图像描述生成、视觉问答(VQA)功能,以及搭建 Whisper 的语音转写流水线

前置知识#

  • 了解 Transformer 架构(详见 第 4 部分
  • 掌握预训练和指令微调的相关知识(可参考 第 5 部分第 8 部分
  • 熟悉 PyTorch 和 Hugging Face 工具生态;如果对计算机视觉中的 patch 或 ViT 有一定概念会更有帮助,但这不是硬性要求

视觉-语言模型#

CLIP:对比学习实现视觉与语言对齐#

CLIP 双编码器架构与批内相似度矩阵

CLIP (Radford 等人, OpenAI 2021)通过两个并行的编码器实现了图像与文本的对齐。图像编码器 $f_I$ (ViT-L/14 或 ResNet)将图片 $x$ 转换为一个 512 维向量 $I = f_I(x) \in \mathbb{R}^{512}$文本编码器 $f_T$ (12 层 Transformer)将文本 $t$ 转换为相同维度的向量 $T = f_T(t)$ 。两者经过 L2 归一化后,点积 $I \cdot T$ 即为余弦相似度。训练目标是最大化匹配图文对的相似度,同时压低其他不相关对的相似度。

$$\mathcal{L} = -\frac{1}{2N}\sum_{i=1}^{N}\Bigg[\log\frac{e^{s_{ii}}}{\sum_{j} e^{s_{ij}}} + \log\frac{e^{s_{ii}}}{\sum_{j} e^{s_{ji}}}\Bigg].$$

两项分别对应图→文和文→图的分类损失:在 batch 内的 $N$ 个候选中做 softmax,对角线位置即为正确答案。温度参数 $\tau \approx 0.07$ 是一个可学习标量,初始化为 $\log(1/\tau) = \log 100$ 并裁剪以防数值崩溃。batch 越大负样本越难: CLIP 在训练 4 亿图文对时使用了跨数百块 GPU 的 32,768 大小的 batch。

从这一目标自然衍生出三个核心能力:

  1. 零样本分类:无需标注数据,只需将每个候选标签写成 "a photo of a {label}",选择余弦相似度最高的即可。
  2. 跨模态检索:图→文或文→图检索退化为点积索引(如 FAISS、 ScaNN)。
  3. 生成模型条件信号: DALL·E 2、 Stable Diffusion 的文本编码器以及各类 image-to-image 系统都用 CLIP 的 embedding 作为监督信号。
 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
from transformers import CLIPProcessor, CLIPModel
from PIL import Image
import torch

model = CLIPModel.from_pretrained("openai/clip-vit-base-patch32").eval()
processor = CLIPProcessor.from_pretrained("openai/clip-vit-base-patch32")

image = Image.open("cat.jpg")
labels = ["a photo of a cat", "a photo of a dog",
          "a photo of a car", "a photo of a bird"]

with torch.no_grad():
    inputs = processor(text=labels, images=image,
                       return_tensors="pt", padding=True)
    out = model(**inputs)

# logits_per_image 已经按 1/τ 缩放
probs = out.logits_per_image.softmax(dim=-1)[0]
for label, p in zip(labels, probs):
    print(f"{p.item():>6.2%}  {label}")

对于一张干净的猫照片,模型通常会在 "a photo of a cat" 上输出 >95% 的概率——而模型从未见过任何关于 "cat" 的标注数据。

CLIP 的局限性。 CLIP 类似词袋模型:它会混淆 “a red cube on a blue sphere”“a blue cube on a red sphere”。它不擅长计数(两个 vs. 三个)、细粒度分类(约克夏梗 vs. 诺里奇梗),也无法识别图像中的文字(需要带 OCR 的模型)。中文场景推荐使用 Chinese-CLIP 或通义千问 VL——原始 WIT 数据集以英文为主。

BLIP-2:连接冻结的视觉与语言#

CLIP 只能对齐,无法生成文本。BLIP-2(Li 等人, ICML 2023)解决了生成问题,同时大幅降低了预训练成本。图像编码器(ViT-g/14, 10 亿参数)和 LLM (OPT-2.7B/6.7B 或 FlanT5)均保持冻结状态,仅训练桥接模块——Q-Former

BLIP-2 架构:Q-Former 连接冻结 ViT 与冻结 LLM

Q-Former 是一个 12 层 Transformer,其设计有两个关键点:

  • 32 个可学习的 query embedding 作为输入——这些与图像 token 无关,只是 32 个可训练向量。
  • 每层包含交叉注意力机制, query 去关注冻结 ViT 输出的 patch 特征,从而主动提取与语言相关的信息。

经过 12 层后, 32 个 query 被转换为 32 个“软视觉 token”,再通过线性投影对齐到 LLM 的 embedding 维度,并以前缀形式拼接到文本输入。 LLM 将它们视为普通词 embedding。

BLIP-2 的训练分为两阶段:

  1. 视觉-语言表示学习: Q-Former 仅对接冻结的 ViT,联合优化三个损失:图文对比(ITC,类似 CLIP)、图文匹配(ITM,硬负样本上的二分类)、图像条件文本生成(ITG, captioning 损失)。这一阶段教会 query 如何理解视觉内容。
  2. 视觉到语言生成: Q-Former 输出接入冻结 LLM,用标准语言模型损失在图像条件文本上训练。这一阶段教会 query 如何“说 LLM 的语言”。

总可训练参数约 1.88 亿——仅为 OPT-6.7B 的 1.4%。然而 BLIP-2 在 VQA-v2 和图像描述任务上的零样本表现可媲美端到端训练的更大模型。

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
from transformers import Blip2Processor, Blip2ForConditionalGeneration
from PIL import Image
import torch

processor = Blip2Processor.from_pretrained("Salesforce/blip2-opt-2.7b")
model = Blip2ForConditionalGeneration.from_pretrained(
    "Salesforce/blip2-opt-2.7b",
    torch_dtype=torch.float16, device_map="auto",
)

image = Image.open("street.jpg")
prompt = "Question: What is happening in this photo? Answer:"
inputs = processor(image, text=prompt, return_tensors="pt").to("cuda", torch.float16)

with torch.no_grad():
    ids = model.generate(**inputs, max_new_tokens=60)
print(processor.batch_decode(ids, skip_special_tokens=True)[0].strip())

LLaVA:视觉指令微调#

BLIP-2 巧妙地预训练了一个桥接模块,而 LLaVA (Liu 等人, NeurIPS 2023)则更进一步:直接用单层线性投影(或 2 层 MLP)替代桥接模块,转而专注于高质量的视觉指令跟随数据

LLaVA:视觉编码器 → 投影器 → LLM,及连接器对比

$$H_v = W \cdot Z_v.$$

$H_v$ 与用户文本 token embedding 拼接后喂给 Vicuna,自回归预测助手的回答。训练分为两阶段:

  1. 特征对齐:仅训练 $W$ ,数据来自 558K 图文对(LAION/CC/SBU)。 LLM 完全冻结——只让投影器学会将 CLIP 特征映射到 LLM 能读懂的空间。
  2. 端到端视觉指令微调:解冻 LLM,与 $W$ 联合训练,数据是 158K 由 GPT-4 从 COCO 描述与边界框生成的图像-指令对。 GPT-4 被引导生成三类对话:详细描述、对话问答、复杂推理。

单层线性投影最多 ~17M 参数,但在 MMBench 上 LLaVA-1.5 (MLP-2 连接器)取得 80.0 分——在自家擅长的基准上击败了更复杂的 Q-Former 设计。经验总结:对于指令跟随型多模态大模型(MLLM),数据质量和端到端微调比连接器设计更重要

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
# LLaVA-1.5 推理(Transformers 版)
from transformers import LlavaForConditionalGeneration, AutoProcessor
from PIL import Image
import torch

model_id = "llava-hf/llava-1.5-7b-hf"
processor = AutoProcessor.from_pretrained(model_id)
model = LlavaForConditionalGeneration.from_pretrained(
    model_id, torch_dtype=torch.float16, device_map="auto",
)

image = Image.open("ui_screenshot.png")
prompt = ("USER: <image>\n这个 UI 是干什么的?是否存在 bug?请具体说明。"
          "ASSISTANT:")

inputs = processor(text=prompt, images=image,
                   return_tensors="pt").to("cuda", torch.float16)
with torch.no_grad():
    out = model.generate(**inputs, max_new_tokens=200, do_sample=False)
print(processor.batch_decode(out, skip_special_tokens=True)[0])

视觉-语言任务: VQA、描述、检索#

VQA、图像描述与跨模态检索的任务流程

在多模态领域,以下三类经典任务占据了评测和实际应用的核心地位:

  • 视觉问答(VQA)。给定一张图片和一个相关问题,模型需要生成准确的答案。常用数据集包括 VQA-v2、 OK-VQA (开放知识型)、 GQA (组合推理型)以及 TextVQA (需要 OCR 能力)。现代多模态大语言模型(MLLM)通过指令微调,可以用单一模型应对这些任务。
  • 图像描述生成。根据输入的图片,生成一段自然语言描述。主流数据集有 COCO Captions、 NoCaps 和 Flickr30K。在生成策略上,使用 num_beams=3length_penalty>1.0 的束搜索(beam search)通常能在描述细节和语言流畅性之间取得较好的平衡。
  • 跨模态检索。根据文本查询找到最相关的图片(或反过来)。这一任务本质上是对归一化的 CLIP 特征向量进行最近邻搜索。在生产环境中,通常使用 FAISS-IVF 或 ScaNN 等高效索引技术,支持十亿级别的数据规模。

图像描述生成#

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
from transformers import BlipProcessor, BlipForConditionalGeneration
from PIL import Image
import torch

class ImageCaptioner:
    def __init__(self, model_id="Salesforce/blip-image-captioning-large"):
        self.proc = BlipProcessor.from_pretrained(model_id)
        self.model = BlipForConditionalGeneration.from_pretrained(model_id).eval()

    @torch.no_grad()
    def caption(self, image_path, prefix=None,
                max_length=50, num_beams=3):
        img = Image.open(image_path).convert("RGB")
        # `prefix` 用于条件生成,例如 "A photo of"
        inputs = self.proc(img, text=prefix, return_tensors="pt") \
            if prefix else self.proc(img, return_tensors="pt")
        out = self.model.generate(
            **inputs, max_length=max_length,
            num_beams=num_beams, length_penalty=1.0, early_stopping=True,
        )
        return self.proc.decode(out[0], skip_special_tokens=True)

cap = ImageCaptioner()
print(cap.caption("dog.jpg"))                        # 无条件生成
print(cap.caption("dog.jpg", prefix="A photo of"))   # 条件生成

视觉问答#

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
from transformers import BlipProcessor, BlipForQuestionAnswering
from PIL import Image
import torch

class VQA:
    def __init__(self, model_id="Salesforce/blip-vqa-base"):
        self.proc = BlipProcessor.from_pretrained(model_id)
        self.model = BlipForQuestionAnswering.from_pretrained(model_id).eval()

    @torch.no_grad()
    def answer(self, image_path, question, max_length=30):
        img = Image.open(image_path).convert("RGB")
        inputs = self.proc(img, question, return_tensors="pt")
        out = self.model.generate(**inputs, max_length=max_length)
        return self.proc.decode(out[0], skip_special_tokens=True)

vqa = VQA()
print(vqa.answer("market.jpg", "How many apples are on the table?"))
print(vqa.answer("market.jpg", "What color is the basket?"))

生产级 CLIP 检索实现#

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
import numpy as np
import torch
from PIL import Image
from transformers import CLIPProcessor, CLIPModel

class CLIPRetrieval:
    """基于 CLIP 的轻量级图像检索工具。超过百万条目时建议改用 FAISS-IVF。"""

    def __init__(self, model_id="openai/clip-vit-base-patch32",
                 device=None):
        self.device = device or ("cuda" if torch.cuda.is_available() else "cpu")
        self.model = CLIPModel.from_pretrained(model_id).to(self.device).eval()
        self.proc = CLIPProcessor.from_pretrained(model_id)
        self.embeds = []   # 存储形状为 (512,) 的 np.ndarray 列表
        self.paths = []

    @torch.no_grad()
    def add_images(self, paths, batch_size=32):
        for i in range(0, len(paths), batch_size):
            batch_paths = paths[i:i + batch_size]
            imgs = [Image.open(p).convert("RGB") for p in batch_paths]
            inputs = self.proc(images=imgs, return_tensors="pt").to(self.device)
            emb = self.model.get_image_features(**inputs)
            emb = emb / emb.norm(dim=-1, keepdim=True)
            self.embeds.extend(emb.cpu().numpy())
            self.paths.extend(batch_paths)

    @torch.no_grad()
    def search_text(self, query, top_k=5):
        inputs = self.proc(text=[query], return_tensors="pt",
                           padding=True).to(self.device)
        q = self.model.get_text_features(**inputs)
        q = q / q.norm(dim=-1, keepdim=True)
        sims = np.vstack(self.embeds) @ q.cpu().numpy().T
        sims = sims.flatten()
        idx = np.argpartition(-sims, top_k)[:top_k]
        idx = idx[np.argsort(-sims[idx])]
        return [(self.paths[i], float(sims[i])) for i in idx]

当图像数量超过百万时,建议切换到 FAISS 的 IVF-PQ 索引方案:原始的 512 维 float32 向量大约占用 2 GB 存储空间,而通过 PQ 压缩后可降至 100 MB 以内。


音频: Whisper#

语音是第二大非文本模态,而 Whisper(Radford 等人, ICML 2023)则是这一领域的开源主力工具。 Whisper 是一个标准的编码器-解码器 Transformer 模型,训练数据来自互联网上抓取的 68 万小时弱监督多语言音频。它通过单一模型和目标函数,同时支持语音转写、翻译、语种识别以及语音活动检测。

Whisper 输入 log-mel 频谱与编码器-解码器结构

处理流程#

  1. 首先将音频重采样为 16 kHz 单声道,然后将其分割成 30 秒的片段。对于短于 30 秒的音频,用静音填充;而对于较长的音频,则采用滑动窗口的方式进行分段处理。
  2. 将每个片段转换为 80 通道的 log-mel 频谱图,窗口长度为 25 毫秒,步长为 10 毫秒——每 30 秒的音频片段会生成 3000 帧数据。
  3. 使用两层 stride-2 Conv1D + GELU 进行降采样,得到 1500 个音频 token,随后送入 编码器(根据模型大小,包含 12 至 32 层自注意力机制)进行处理。
  4. 解码器采用自回归方式,并在输入前添加控制标记:<|startoftranscript|> <|en|> <|transcribe|> <|notimestamps|>。如果将 <|transcribe|> 替换为 <|translate|>,同一模型即可生成外语音频的英文翻译。

模型规格#

Whisper 提供多种尺寸的模型: tiny (39M)、 base (74M)、 small (244M)、 medium (769M) 和 large (1.55B)。在大多数生产环境中,使用 smallmedium 模型在 GPU 上推理,可以实现 0.1–0.3× 实时延迟,并且在常见英语音频上的词错误率(WER)低于 10%。如果遇到口音较重、语码转换(code-switching)或噪声干扰较大的情况,建议使用 large-v3 模型。

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
import whisper

class WhisperASR:
    def __init__(self, size="base", device="cuda"):
        self.model = whisper.load_model(size, device=device)

    def transcribe(self, audio_path, language=None, task="transcribe"):
        # task 可选值为 {"transcribe", "translate"}
        return self.model.transcribe(
            audio_path,
            language=language,           # None 表示自动检测语种
            task=task,
            word_timestamps=True,        # 输出每个单词的时间戳
            condition_on_previous_text=False,  # 长音频更稳定
            no_speech_threshold=0.6,
            verbose=False,
        )

asr = WhisperASR(size="small")
result = asr.transcribe("meeting.mp3")
print(f"识别语种: {result['language']}")
for seg in result["segments"]:
    print(f"[{seg['start']:6.2f} - {seg['end']:6.2f}] {seg['text'].strip()}")

实战建议#

  • 对于长时间录音,务必关闭 condition_on_previous_text 参数。否则,一旦模型在早期出现幻觉(hallucination),错误可能会级联放大。
  • 如果需要批量处理以提高吞吐量,推荐使用 0 (基于 CTranslate2 后端,速度提升 4–8 倍)。
  • 如果需要进行说话人分割(即识别“谁在何时说话”),可以将 Whisper 与 pyannote.audio 结合使用。

GPT-4V 与前沿 MLLM 全景#

GPT-4V 能力矩阵与交互示例

GPT-4V(以及其后续版本 GPT-4o 和 GPT-4.1)将图像作为与文本同等重要的输入形式,能够直接处理多种任务,开箱即用:

  • OCR 和文档解析:读取发票、表格、表单和截图内容。
  • 图表分析:提取数据值、总结趋势、回答对比问题。
  • 流程图与示意图解读:解释 UML 图、系统架构图,甚至手绘白板内容。
  • 代码与界面调试:通过代码截图排查问题,或根据页面渲染效果提出 CSS 优化建议。
  • 视觉推理:比较多张图片、从试卷照片中逐步推导数学题解、判断场景安全性。
 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
from openai import OpenAI
import base64, mimetypes

client = OpenAI()

def analyse_image(path: str, prompt: str, model: str = "gpt-4o") -> str:
    mime, _ = mimetypes.guess_type(path)
    with open(path, "rb") as f:
        b64 = base64.b64encode(f.read()).decode()
    resp = client.chat.completions.create(
        model=model,
        messages=[{
            "role": "user",
            "content": [
                {"type": "text", "text": prompt},
                {"type": "image_url",
                 "image_url": {"url": f"data:{mime};base64,{b64}",
                               "detail": "high"}},
            ],
        }],
        max_tokens=500,
        temperature=0.2,
    )
    return resp.choices[0].message.content

print(analyse_image("dashboard.png",
                    "总结关键指标,并指出是否存在异常。"))

detail 参数(可选值为 "low" | "high" | "auto")在成本和分辨率之间做出权衡——选择 "high" 时,图像会被分割为 512 像素的小块,消耗的视觉 token 数量大约是默认情况下的 10 倍。

值得关注的开源替代方案: LLaVA-1.6 (基础性能优秀,易于微调)、通义千问 VL-Max (中文和 OCR 表现突出)、 InternVL (在 grounding 任务上表现优异)、 CogVLM (感知能力强大)。这些模型均可本地部署,既节省成本,又能满足隐私需求。

多模态大模型仍需改进的地方(各厂商普遍存在):

  • 计数任务:超过 5 个目标时准确性下降。
  • 精细空间推理:例如「哪支笔离杯子更近?」这类问题。
  • 低分辨率密集文字识别:如截图中的小字体内容难以准确读取。
  • 物体幻觉问题:当提示词具有诱导性时,模型可能会描述不存在的对象(例如要求「描述这只狗」,但图片中根本没有狗)。

视频理解#

视频可以看作是一个 4D 张量(时间、高度、宽度、通道),如果逐帧处理, token 数量会迅速膨胀到难以承受的地步。到了 2024 年,主流的处理方式已经形成了一套固定的模式:

  1. 均匀采样帧:从视频片段中均匀抽取 $N \in \{8, 16, 32\}$ 帧,确保覆盖整个时间轴。
  2. 逐帧视觉编码:使用冻结的 ViT 模型提取特征,有时会搭配时间池化适配器(例如 VideoLLaMA、 Video-LLaVA),在投影之前对相邻帧进行混合处理,捕捉时间维度上的信息。
  3. Token 拼接:将每帧的 token (或经过池化后的 token)拼接到大语言模型(LLM)的上下文中,通常还会加入时序位置编码,让模型能够感知帧的先后顺序。

对于超长视频(比如几个小时的录像),推荐采用“粗到细”的策略:先用一个生成描述的模型(captioner)对短时间段进行总结,然后用 LLM 分析这些描述,定位出值得进一步详细分析的几秒钟内容。

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
import cv2
import numpy as np

def sample_frames(video_path, num_frames=8):
    """均匀采样视频帧,返回 PIL.Image 对象列表。"""
    from PIL import Image
    cap = cv2.VideoCapture(video_path)
    total = int(cap.get(cv2.CAP_PROP_FRAME_COUNT))
    if total <= 0:
        cap.release()
        raise RuntimeError(f"无法读取视频文件 {video_path}")
    indices = np.linspace(0, total - 1, num_frames).astype(int)
    frames = []
    for idx in indices:
        cap.set(cv2.CAP_PROP_POS_FRAMES, int(idx))
        ok, frame = cap.read()
        if not ok:
            continue
        frame = cv2.cvtColor(frame, cv2.COLOR_BGR2RGB)
        frames.append(Image.fromarray(frame))
    cap.release()
    return frames

像 Gemini 1.5 Pro 这样的模型可以直接接受原生视频输入,通过其长上下文注意力机制流式处理长达数小时的视频片段,效率和效果都非常出色。

MLLM 评测: MMBench、 MME、 MMMU、 SEED、 POPE#

多模态基准对比与能力雷达图

选择合适的评测基准比很多人想象的更重要。每个基准都有其侧重点,单纯依赖一个榜单上的分数很容易被误导。

基准测评内容题型备注
MMBench20 项能力维度:感知、推理、 OCR、细粒度识别四选一选择题使用“循环评测”打乱选项顺序,避免字母偏好干扰。
MME14 个子任务:感知(10)+ 认知(4)是/否问答得分为准确率总和(满分 2800)。容易通过“全选是”作弊。
MMMU涵盖 30 个学科的大学水平问题(医学、工程、艺术等)开放式 + 选择题2024 年最难的基准——前沿模型得分仍低于 60%。
SEED-Bench空间关系、实例计数、场景理解、时间序列选择题包含视频相关子集(SEED-Bench v2)。
POPE物体幻觉检测是/否判断物体是否存在将真实物体与虚构物体配对,揭示模型幻觉倾向。
TextVQA / DocVQA以 OCR 为核心的阅读理解开放式回答检测视觉模块是否真正具备“读”的能力。
RefCOCO短语定位(返回边界框)区域预测用于评估空间精度的表现。

一份全面的评测报告应至少包括以下内容:一个感知类基准(如 MME 或 SEED)、一个推理类基准(如 MMMU)、一个幻觉检测基准(如 POPE),以及针对实际应用场景的领域特定测试集。

多模态应用工程经验#

在实际部署 MLLM 系统的过程中,我们总结了一些宝贵的经验教训:

  • 大胆进行预处理。将图像调整为模型的原生分辨率(CLIP: 224, LLaVA-1.5: 336, GPT-4V high-detail: 768)。更大的输入不仅不会提升效果,反而会浪费视觉 token。
  • 缓存图像嵌入向量。使用 CLIP 对图像编码一次,远比每次查询都重新编码划算得多——直接存储 512 维的向量并重复利用即可。
  • 对语言模型部分进行量化。对 LLaVA-1.5 的语言模型部分采用 4-bit AWQ/GPTQ 量化技术,可以将显存占用从 14 GB 降低到约 5 GB,而 MMBench 分数的损失不到 1 分。
  • 确保 prompt 表述清晰明确。措辞对结果影响极大。例如,“What color is the car?” 是一个合理的提问;但如果改成 “What color is the car if there is one?",在没有汽车的图片上,模型产生幻觉的概率会降低约 40%。务必让模型能够明确回答“我不知道”。
  • 警惕 OCR 偏移问题。许多多模态模型会先对图片中的文字进行 OCR 提取,然后直接“信任”这些内容——但图片角落里的对抗性文字可能会劫持模型的输出。在安全性要求较高的场景中,务必对输入进行清洗和校验。

常见问题#

CLIP、 BLIP 和 LLaVA 应该怎么选?#

如果你需要做检索、零样本分类,或者只需要固定嵌入的系统,那就用 CLIP。如果要在算力有限的情况下实现视觉问答(VQA)或图像描述生成,并且希望冻结大语言模型(LLM),那就选 BLIP-2。而如果你的目标是构建一个多模态对话系统,能够理解指令并进行交互,那么 LLaVA 风格的指令微调多模态模型(MLLM)会更适合。

为什么 BLIP-2 的训练参数这么少?#

其实它的核心组件——10 亿参数的视觉 Transformer (ViT)和 70 亿参数的大语言模型(LLM)——都是冻结的,不需要重新训练。真正需要训练的只有 1.88 亿参数的 Q-Former,它充当了两者之间的桥梁。通过两阶段的训练策略,这个桥接模块可以专注于特定任务:第一阶段让它深入理解视觉信息,第二阶段则将其输出对齐到 LLM 的词汇表。

微调需要多少数据?#

如果是针对预训练好的多模态模型(比如 LLaVA 或通义千问 VL)进行领域适配,通常只需要 5K 到 50K 高质量的图像-指令对就足够了。这种情况下,可以对 LLM 使用 LoRA 方法,同时对投影器进行全量训练。但如果是从零开始预训练,那就需要准备 1 亿到 10 亿对数据了。

如何把显存控制在 24 GB 以内?#

可以通过三种方法来实现:对大语言模型(LLM)进行 4-bit 量化(可以用 bitsandbytes 或 AWQ 工具)、在注意力机制的投影层上应用 LoRA 技术,以及启用梯度检查点(gradient checkpointing)。这样一来,即便是 7B 参数的 LLaVA 模型,也能轻松在一张 RTX 4090 显卡上完成微调。

如何科学地评估模型性能?#

首先选择一个与你的应用场景高度相关的主基准测试(例如,处理文档场景时可以选择 DocVQA),然后搭配一个专门检测幻觉问题的基准(如 POPE),最后再准备一个独立的内部测试集。记住,至少要报告三个基准测试的结果,因为单一指标往往会掩盖模型在不同任务上的权衡。

CLIP 能用来处理中文吗?#

原始的 OpenAI CLIP 模型中 95% 的数据是英文的,因此直接用于中文效果可能不理想。如果需要支持中文,可以选择 Chinese-CLIP(由 OFA-Sys 提供)、通义千问 VL 或者 Wukong-CLIP。这些模型都兼容 CLIP 的 API,可以直接替换使用。

本系列

NLP 技术前沿 12 篇

  1. 01 自然语言处理(一):NLP 入门与文本预处理
  2. 02 自然语言处理(二):词向量与语言模型
  3. 03 自然语言处理(三):RNN 与序列建模
  4. 04 自然语言处理(四):注意力机制与 Transformer
  5. 05 自然语言处理(五):BERT 与预训练模型
  6. 06 自然语言处理(六):GPT 与生成式语言模型
  7. 07 自然语言处理(七):提示工程与 In-Context Learning
  8. 08 自然语言处理(八):模型微调与 PEFT
  9. 09 自然语言处理(九):大语言模型架构深度解析
  10. 10 自然语言处理(十):RAG 与知识增强系统
  11. 11 自然语言处理(十一):多模态大模型 当前
  12. 12 自然语言处理(十二):前沿技术与实战应用

读有所得?

GitHub 关注我 → 新文周更

GitHub