多模态 AI 与视觉语言模型
大约 11 分钟约 3276 字
多模态 AI 与视觉语言模型
简介
多模态 AI 能够同时处理文本、图像、音频等多种数据类型。视觉语言模型(VLM)如 GPT-4V、LLaVA、CLIP 实现了图像理解和图文推理。理解多模态架构、CLIP 对齐和 VLM 训练,有助于构建跨模态的 AI 应用。
特点
CLIP 对齐模型
图像-文本对比学习
import torch
import torch.nn as nn
import torch.nn.functional as F
# CLIP 核心思想:对比学习将图像和文本映射到共享空间
# 训练目标:匹配的图文对相似度高,不匹配的相似度低
class SimpleCLIP(nn.Module):
"""简化版 CLIP"""
def __init__(self, embed_dim=512, image_dim=2048, text_dim=768, temperature=0.07):
super().__init__()
self.temperature = nn.Parameter(torch.tensor(temperature).log())
# 图像编码器投影头
self.image_projection = nn.Sequential(
nn.Linear(image_dim, embed_dim),
nn.ReLU(),
nn.Linear(embed_dim, embed_dim)
)
# 文本编码器投影头
self.text_projection = nn.Sequential(
nn.Linear(text_dim, embed_dim),
nn.ReLU(),
nn.Linear(embed_dim, embed_dim)
)
def forward(self, image_features, text_features):
"""
image_features: (batch, image_dim) — 图像编码器输出
text_features: (batch, text_dim) — 文本编码器输出
"""
# 投影到共享空间
image_embeddings = self.image_projection(image_features)
text_embeddings = self.text_projection(text_features)
# L2 归一化
image_embeddings = F.normalize(image_embeddings, dim=-1)
text_embeddings = F.normalize(text_embeddings, dim=-1)
# 计算相似度矩阵
logit_scale = self.temperature.exp()
logits = logit_scale * image_embeddings @ text_embeddings.T
# logits: (batch, batch) — 对角线为正样本
# 对称损失
labels = torch.arange(logits.size(0), device=logits.device)
loss = (F.cross_entropy(logits, labels) + F.cross_entropy(logits.T, labels)) / 2
return loss, logits
def get_similarity(self, image_features, text_features):
"""计算图像-文本相似度"""
image_emb = F.normalize(self.image_projection(image_features), dim=-1)
text_emb = F.normalize(self.text_projection(text_features), dim=-1)
return image_emb @ text_emb.T
# 零样本分类
class ZeroShotClassifier:
"""CLIP 零样本分类"""
def __init__(self, clip_model, tokenizer, text_encoder):
self.clip = clip_model
self.tokenizer = tokenizer
self.text_encoder = text_encoder
def classify(self, image, class_names):
"""零样本图像分类"""
# 生成文本提示
prompts = [f"a photo of a {name}" for name in class_names]
# 编码文本
text_features = self.encode_texts(prompts)
text_features = F.normalize(text_features, dim=-1)
# 编码图像
image_features = self.encode_image(image)
image_features = F.normalize(image_features, dim=-1)
# 计算相似度
similarity = (image_features @ text_features.T).softmax(dim=-1)
return similarity
# 使用示例:
# class_names = ["cat", "dog", "bird", "fish"]
# probs = classifier.classify(image, class_names)
# predicted = class_names[probs.argmax()]视觉语言模型
LLaVA 架构
# LLaVA(Large Language and Vision Assistant)
# 架构:视觉编码器(CLIP ViT)+ 投影层 + LLM(LLaMA)
class LlavaProjector(nn.Module):
"""视觉特征到文本嵌入空间的投影层"""
def __init__(self, vision_dim=1024, embed_dim=4096):
super().__init__()
self.projector = nn.Sequential(
nn.Linear(vision_dim, embed_dim),
nn.GELU(),
nn.Linear(embed_dim, embed_dim)
)
def forward(self, vision_features):
return self.projector(vision_features)
class LlavaModel:
"""LLaVA 模型推理"""
def __init__(self, model_name="llava-hf/llava-1.5-7b-hf"):
from transformers import LlavaForConditionalGeneration, AutoProcessor
self.model = LlavaForConditionalGeneration.from_pretrained(
model_name,
torch_dtype=torch.float16,
device_map="auto"
)
self.processor = AutoProcessor.from_pretrained(model_name)
def chat(self, image, prompt, max_new_tokens=512):
"""多模态对话"""
conversation = [
{
"role": "user",
"content": [
{"type": "image"},
{"type": "text", "text": prompt}
]
}
]
prompt_text = self.processor.apply_chat_template(
conversation, add_generation_prompt=True
)
inputs = self.processor(
images=image,
text=prompt_text,
return_tensors="pt"
).to(self.model.device)
output = self.model.generate(
**inputs,
max_new_tokens=max_new_tokens,
temperature=0.7,
do_sample=True
)
response = self.processor.decode(
output[0][inputs.input_ids.shape[1]:],
skip_special_tokens=True
)
return response
# 使用示例:
# from PIL import Image
# image = Image.open("photo.jpg")
# response = model.chat(image, "描述这张图片中的场景")
# response = model.chat(image, "图片中有多少人?他们在做什么?")
# 批量图像理解
class ImageUnderstandingPipeline:
"""批量图像理解管线"""
def __init__(self, model_name="llava-hf/llava-1.5-7b-hf"):
self.model = LlavaModel(model_name)
def describe_images(self, images, prompt="详细描述这张图片"):
"""批量描述图像"""
results = []
for image in images:
description = self.model.chat(image, prompt)
results.append(description)
return results
def extract_text(self, image):
"""OCR — 从图像中提取文本"""
prompt = "请仔细阅读图片中的所有文字,并逐行列出。"
return self.model.chat(image, prompt)
def analyze_chart(self, image):
"""分析图表数据"""
prompt = "分析这个图表,列出关键数据点和趋势。"
return self.model.chat(image, prompt)
def compare_images(self, image1, image2):
"""比较两张图像"""
# 拼接两张图片
prompt = "比较这两张图片的相同点和不同点。"
# 需要支持多图输入的模型
return self.model.chat(image1, prompt)多模态检索
图文检索系统
class MultimodalRetriever:
"""基于 CLIP 的图文检索系统"""
import faiss
def __init__(self, clip_model, dim=512):
self.clip = clip_model
self.dim = dim
self.index = faiss.IndexFlatIP(dim) # 内积索引
self.documents = []
def add_images(self, images, metadata=None):
"""添加图像到索引"""
features = self.clip.encode_images(images)
features = F.normalize(features, dim=-1).cpu().numpy()
self.index.add(features.astype('float32'))
for i, feat in enumerate(features):
self.documents.append({
'type': 'image',
'feature': feat,
'metadata': metadata[i] if metadata else {}
})
def add_texts(self, texts, metadata=None):
"""添加文本到索引"""
features = self.clip.encode_texts(texts)
features = F.normalize(features, dim=-1).cpu().numpy()
self.index.add(features.astype('float32'))
for i, feat in enumerate(features):
self.documents.append({
'type': 'text',
'feature': feat,
'text': texts[i],
'metadata': metadata[i] if metadata else {}
})
def search_by_text(self, query_text, top_k=10):
"""用文本搜索相关内容"""
query_feature = self.clip.encode_texts([query_text])
query_feature = F.normalize(query_feature, dim=-1).cpu().numpy().astype('float32')
scores, indices = self.index.search(query_feature, top_k)
results = []
for score, idx in zip(scores[0], indices[0]):
if idx < len(self.documents):
doc = self.documents[idx].copy()
doc['score'] = float(score)
results.append(doc)
return results
def search_by_image(self, query_image, top_k=10):
"""用图像搜索相关内容"""
query_feature = self.clip.encode_images([query_image])
query_feature = F.normalize(query_feature, dim=-1).cpu().numpy().astype('float32')
scores, indices = self.index.search(query_feature, top_k)
results = []
for score, idx in zip(scores[0], indices[0]):
if idx < len(self.documents):
doc = self.documents[idx].copy()
doc['score'] = float(score)
results.append(doc)
return results优点
缺点
总结
CLIP 通过对比学习将图像和文本映射到共享语义空间,支持零样本分类和跨模态检索。视觉语言模型(LLaVA)通过投影层连接视觉编码器和 LLM,实现图像理解和多模态对话。多模态检索使用 CLIP 特征 + FAISS 索引构建图文检索系统。应用场景包括:图像描述、OCR 文字提取、图表分析、图文搜索和视觉问答。建议生产环境使用量化模型和 vLLM 加速推理。
关键知识点
- 先分清模型能力边界、数据边界和工程边界。
- 任何 AI 主题都不只看效果,还要看延迟、成本、可解释性和安全性。
- 评估方式和失败样例往往比“换哪个模型”更重要。
项目落地视角
- 给数据来源、Prompt 模板、Embedding 版本、评估集和实验结果做版本管理。
- 上线前准备兜底策略,例如拒答、回退、人工审核或缓存降级。
- 观察错误类型时,区分数据问题、召回问题、提示词问题和模型问题。
常见误区
- 只关注 Demo 效果,不考虑线上稳定性和可复现性。
- 没有评估集就频繁调参,最后无法解释为什么变好或变差。
- 忽略权限、审计、隐私和模型输出的安全边界。
进阶路线
- 继续补齐训练、推理、评估、MLOps 和治理链路。
- 把主题放回真实业务流程,思考谁提供数据、谁消费结果、谁负责兜底。
- 把 PoC 逐步升级到可观测、可回滚、可演进的生产方案。
适用场景
- 当你准备把《多模态 AI 与视觉语言模型》真正落到项目里时,最适合先在一个独立模块或最小样例里验证关键路径。
- 适合企业知识问答、内容生成、分类抽取和智能助手等场景。
- 当需求同时关注效果、时延、成本和安全边界时,这类主题最有价值。
落地建议
- 先定义评估集、成功标准和失败样例,再开始调模型或调提示。
- 把数据来源、分块方式、Embedding 版本和 Prompt 模板纳入版本管理。
- 上线前准备兜底策略,例如拒答、回退、人工审核或检索降级。
排错清单
- 先判断问题出在数据、检索、Prompt、模型还是后处理。
- 检查上下文是否过长、分块是否过碎或召回是否偏题。
- 对错误回答做分类,区分幻觉、事实过时、指令误解和格式错误。
复盘问题
- 如果把《多模态 AI 与视觉语言模型》放进你的当前项目,最先要验证的输入、输出和失败路径分别是什么?
- 《多模态 AI 与视觉语言模型》最容易在什么规模、什么边界条件下暴露问题?你会用什么指标或日志去确认?
- 相比默认实现或替代方案,采用《多模态 AI 与视觉语言模型》最大的收益和代价分别是什么?
GPT-4V API 集成
from openai import OpenAI
import base64
from pathlib import Path
client = OpenAI(api_key="your-api-key")
def encode_image(image_path: str) -> str:
"""将图片编码为 base64"""
with open(image_path, "rb") as f:
return base64.standard_b64encode(f.read()).decode("utf-8")
# 单图对话
def analyze_image(image_path: str, question: str) -> str:
base64_image = encode_image(image_path)
response = client.chat.completions.create(
model="gpt-4o",
messages=[
{
"role": "user",
"content": [
{"type": "text", "text": question},
{
"type": "image_url",
"image_url": {
"url": f"data:image/jpeg;base64,{base64_image}"
}
}
]
}
],
max_tokens=1000
)
return response.choices[0].message.content
# 使用示例
result = analyze_image("screenshot.png", "描述这个界面的布局和主要功能")
print(result)
# 多图对比
def compare_images(image_path1: str, image_path2: str) -> str:
response = client.chat.completions.create(
model="gpt-4o",
messages=[
{
"role": "user",
"content": [
{"type": "text", "text": "比较这两张图片,列出相同点和不同点。"},
{
"type": "image_url",
"image_url": {"url": f"data:image/jpeg;base64,{encode_image(image_path1)}"}
},
{
"type": "image_url",
"image_url": {"url": f"data:image/jpeg;base64,{encode_image(image_path2)}"}
}
]
}
]
)
return response.choices[0].message.content多模态在 RAG 中的应用
class MultimodalRAG:
"""多模态 RAG — 同时检索文本和图片"""
def __init__(self, clip_model, text_embed_model, llm_client):
self.clip = clip_model
self.text_embed = text_embed_model
self.llm = llm_client
self.text_store = [] # 文本块 + embedding
self.image_store = [] # 图片路径 + embedding
def add_documents(self, documents: list[str]):
"""添加文本文档"""
for doc in documents:
emb = self.text_embed.encode(doc)
self.text_store.append({"text": doc, "embedding": emb})
def add_images(self, image_paths: list[str], descriptions: list[str]):
"""添加图片及其描述"""
for path, desc in zip(image_paths, descriptions):
# 使用 CLIP 编码图片描述
emb = self.clip.encode_texts([desc])[0]
self.image_store.append({"path": path, "description": desc, "embedding": emb})
def retrieve(self, query: str, top_k: int = 5):
"""混合检索文本和图片"""
query_text_emb = self.text_embed.encode(query)
query_clip_emb = self.clip.encode_texts([query])[0]
text_scores = []
for item in self.text_store:
score = self._cosine_similarity(query_text_emb, item["embedding"])
text_scores.append(("text", score, item["text"]))
image_scores = []
for item in self.image_store:
score = self._cosine_similarity(query_clip_emb, item["embedding"])
image_scores.append(("image", score, f"[图片: {item['description']}]"))
# 合并并排序
all_scores = text_scores + image_scores
all_scores.sort(key=lambda x: x[1], reverse=True)
return all_scores[:top_k]
def generate(self, query: str, top_k: int = 5) -> str:
"""检索并生成回答"""
results = self.retrieve(query, top_k)
context = "\n".join([f"[{r[0]}] {r[2]}" for r in results])
prompt = f"""根据以下参考信息回答问题。
参考信息:
{context}
问题:{query}
回答:"""
response = self.llm.chat.completions.create(
model="gpt-4o-mini",
messages=[{"role": "user", "content": prompt}],
temperature=0.3
)
return response.choices[0].message.content
@staticmethod
def _cosine_similarity(a, b):
import numpy as np
a, b = np.array(a), np.array(b)
return float(np.dot(a, b) / (np.linalg.norm(a) * np.linalg.norm(b) + 1e-8))
# 使用示例
# rag = MultimodalRAG(clip_model, text_embed_model, llm_client)
# rag.add_documents(["系统架构采用微服务设计...", "数据库使用 PostgreSQL..."])
# rag.add_images(["arch.png"], ["系统架构图"])
# answer = rag.generate("系统的整体架构是怎样的?")视频理解与多帧处理
def process_video_frames(video_path: str, sample_rate: int = 5):
"""视频关键帧提取与理解
从视频中每隔 sample_rate 秒提取一帧,
使用 VLM 逐帧分析,汇总生成视频描述。
适合场景:
- 视频内容审核
- 视频摘要生成
- 监控视频分析
- 教学视频知识点提取
"""
import cv2
cap = cv2.VideoCapture(video_path)
fps = cap.get(cv2.CAP_PROP_FPS)
total_frames = int(cap.get(cv2.CAP_PROP_FRAME_COUNT))
duration = total_frames / fps
frames_to_extract = int(duration / sample_rate)
frame_descriptions = []
for i in range(frames_to_extract):
timestamp = i * sample_rate
frame_number = int(timestamp * fps)
cap.set(cv2.CAP_PROP_POS_FRAMES, frame_number)
ret, frame = cap.read()
if ret:
# 保存帧为临时图片
temp_path = f"/tmp/frame_{i}.jpg"
cv2.imwrite(temp_path, frame)
# 使用 VLM 分析帧
# description = analyze_image(temp_path, "描述这个画面中的关键内容")
frame_descriptions.append({
"timestamp": timestamp,
"description": f"第 {timestamp} 秒的画面描述"
})
cap.release()
# 汇总生成视频描述
timeline = "\n".join([
f"- {fd['timestamp']}s: {fd['description']}"
for fd in frame_descriptions
])
return f"视频时长: {duration:.1f}秒\n关键帧描述:\n{timeline}"
# 视频理解的局限性:
# 1. 帧采样率影响信息完整性
# 2. 没有时序建模(不知道动作顺序)
# 3. 音频信息未处理
# 4. 计算成本高(每帧都需要模型推理)多模态评估方法
def explain_multimodal_evaluation():
"""多模态模型评估方法
1. 图像描述评估:
- BLEU/ROUGE: 与参考描述的文本重叠度
- CIDEr: 专门为图像描述设计的指标
- SPICE: 基于场景图语义的评估
- CLIPScore: 描述与图像的语义一致性
2. 视觉问答(VQA)评估:
- Accuracy: 答案精确匹配率
- Soft Accuracy: 允许近义词匹配
- VQA Score: 考虑答案一致性
3. 零样本分类评估:
- Top-1 / Top-5 Accuracy
- 各类别的 Precision / Recall / F1
4. OCR 评估:
- Character Error Rate (CER)
- Word Error Rate (WER)
- Sequence Accuracy
5. 幻觉检测:
- 对比模型输出与图像实际内容
- 使用另一个 VLM 做交叉验证
- 人工审核高置信度回答
评估建议:
- 建立领域相关的评估集(至少 200-500 条)
- 分别评估不同任务类型(描述、OCR、推理)
- 关注边界案例(模糊图像、罕见物体、复杂场景)
- 定期更新评估集以反映真实分布
"""
print("多模态评估维度:")
print(" 图像描述: CIDEr + 人工评估")
print(" VQA: Accuracy + Soft Accuracy")
print(" OCR: CER + WER")
print(" 分类: Top-1/5 Accuracy")
print(" 幻觉: 交叉验证 + 人工审核")
explain_multimodal_evaluation()多模态 AI 生产部署注意事项:
- 推理成本:VLM 推理是纯文本的 5-10 倍,需要控制调用频率
- 延迟控制:图像编码 + LLM 生成,P99 可能超过 10 秒
- 输入限制:图片分辨率和 Token 数限制
- 安全过滤:对输入图片做内容安全检测
- 结果校验:对 OCR 和计数结果做后处理校验