多模态模型实现原理
适合读者:有一点 Python / PyTorch 基础,知道张量、
nn.Linear、训练五步。阅读目标:搞清多模态「输入如何变成统一表示、如何融合、如何训练与推理」,而不是死记论文名词。
1. 多模态是什么
模态(Modality) = 信息的一种形态。常见有:
| 模态 | 例子 | 计算机里通常先变成 |
|---|---|---|
| 文本 | 句子、问题、字幕 | token 序列 → 向量序列 |
| 图像 | 照片、截图 | 像素 / 图像块 → 向量序列 |
| 音频 | 语音、音乐 | 波形 / 频谱 → 向量序列 |
| 视频 | 连续帧 + 可选声音 | 时空特征序列 |
| 表格 / 数值 | 学习时长、出勤率 | 特征向量(你做过的小模型) |
多模态模型 = 能同时理解并联合使用两种及以上模态的模型。
典型任务:
- 图文检索:「找和这句话最像的图」
- 视觉问答(VQA):看图回答问题
- 图像描述:给图片写一段话
- 语音助手:听声音 + 看屏幕再回答
- 文档理解:PDF 里的文字 + 版面 + 插图一起读
和单模态的本质差别:
text
单模态:同一种数据 → 一种编码器 → 输出
多模态:多种数据 → 各自编码 → 对齐/融合 → 联合推理 → 输出
2. 核心原理(先建立一张总图)
多模态实现可以压成一句话:
把不同模态编码到可比较、可交互的表示空间,再在这个空间里融合与推理。
总流程:
text
图像 / 文本 / 音频 ...
│
▼
各模态编码器(Encoder)
│
▼
投影 / 对齐(Projection / Alignment)
│
▼
融合模块(Fusion)
│
▼
任务头(分类 / 生成 / 检索)
三个关键词:
- 编码:每种模态先变成向量(或向量序列)
- 对齐:让「狗的图片」和「狗」这个词在空间里靠近
- 融合:让模型在推理时能互相参照(看图答题时既看字也看图)
没有对齐,融合只是把两堆无关数字拼在一起;没有融合,模型很难做「结合两种信息才能回答」的任务。
3. 为什么要对齐:共享语义空间
不同模态原始形态差很远:
- 图像:像素网格
- 文本:离散 token
- 音频:时间序列波形
直接相加或拼接,模型很难知道「左边这堆数」和「右边那堆数」在说同一件事。
因此常见做法是学一个共享语义空间:
text
图片编码 → 投影 → 向量 v_img
文本编码 → 投影 → 向量 v_txt
训练目标:若图文匹配,则 v_img 与 v_txt 相似;
若不匹配,则尽量不相似。
相似度常用余弦相似度。训练后会出现:
- 「一只猫」的文本向量 ≈ 猫图片的向量
- 「汽车」远离猫图片向量
这就是 CLIP 一类模型的核心思想:对比学习对齐图文。
直觉公式(概念层面):
sim(vimg,vtxt)=vimg⋅vtxt∥vimg∥ ∥vtxt∥ \text{sim}(v_{img}, v_{txt}) = \frac{v_{img} \cdot v_{txt}}{\|v_{img}\|\,\|v_{txt}\|} sim(vimg,vtxt)=∥vimg∥∥vtxt∥vimg⋅vtxt
批量里让正确图文对的相似度相对更高,就是在「拉近配对、推远非配对」。
4. 各模态怎么变成向量
4.1 文本编码器
常见路径:
text
字符串
→ 分词(Tokenizer)得到 token id
→ Embedding 查表
→ Transformer Encoder
→ 句向量或 token 序列
输出两种常用形态:
- 一个向量:整句摘要(适合检索)
- 一串向量:每个 token 一个(适合生成、细粒度交互)
4.2 图像编码器
现代常见路径(Vision Transformer 思路):
text
图片
→ 切成图像块(Patch)
→ 每个块变成向量
→ Transformer Encoder
→ 图像特征序列(或再池化成一个向量)
也可以用 CNN(ResNet 等)提特征,再投影。入门理解用 ViT 路径即可。
4.3 音频编码器
text
波形
→ 分帧 / 梅尔频谱等
→ CNN 或 Transformer
→ 音频特征序列
语音识别、语音翻译多模态系统里很常见。
4.4 数值 / 表格(连接你已学的小模型)
你做过的「学习时长、出勤、作业」本质也是一种模态,只是更简单:
text
数值特征 → 可选 MLP/Linear → 向量
多模态系统里,表格特征同样可以投影后与文本/图像一起融合。
5. 融合怎么做:四种常见结构
「融合」决定模型如何把多路信息合在一起用。
5.1 早期融合(Early Fusion)
很早就拼在一起:
text
把特征在输入侧拼接 → 一个网络一起处理
- 优点:实现简单
- 缺点:各模态采样率、长度、噪声差异大时不好训
- 适合:强对齐、结构相近的输入
5.2 晚期融合(Late Fusion)
各自走完,再在决策层合并:
text
图像分支 → 分数A
文本分支 → 分数B
最终 = 融合(A, B) # 如加权平均、再接一层 MLP
- 优点:分支独立、好调试
- 缺点:模态之间交互弱,难做深度跨模态推理
- 适合:分类、简单打分
5.3 中间融合 / 交叉注意力(最常见于强多模态)
用注意力让一种模态「查询」另一种模态:
text
文本特征作为 Query
图像特征作为 Key / Value
→ Cross-Attention
→ 文本在「看图」后更新自己的表示
这样回答「图里有几只狗」时,问题里的词可以去图像特征里找依据。
5.4 编码器 + 大语言模型(当前主流工程形态)
很多「能看图聊天」的模型(如 LLaVA 一类思路)结构是:
text
图像 → 视觉编码器(常冻结或少训)
→ 投影层(MLP / Linear)把视觉特征映到 LLM 词向量空间
→ 与文本 token 拼成一段序列
→ 送进大语言模型生成回答
伪流程:
text
[图像特征1, 图像特征2, ..., 用户问题tokens...] → LLM → 逐词生成答案
对工程实现来说,这是目前最好懂的一条路:
视觉侧负责看,语言侧负责说;投影层负责翻译成 LLM 能读的「外语」。
6. 投影层:被低估但极关键的零件
不同编码器输出的维度、分布往往不同。例如:
- 视觉编码器输出维度
1024 - LLM 词嵌入维度
4096
中间需要 Projection(投影头):
python
# 概念代码,不是完整可跑项目
vision_feat = vision_encoder(image) # [B, T, 1024]
lang_tokens = proj(vision_feat) # [B, T, 4096]
# 再与文本 embedding 在序列维拼接,送入 LLM
投影层可以很简单(一层 Linear),也可以是小型 MLP。
它的职责不是「认识世界」,而是:
- 对齐维度
- 尽量把视觉特征翻译到语言模型习惯的空间
很多高效微调方案会:
- 冻住视觉编码器
- 冻住大部分 LLM
- 主要训练投影层 + 少量 LoRA
这与「大模型微调」里的参数高效思想一致。
7. 训练原理:通常分阶段,而不是一次到位
多模态系统很少「随机初始化后一次训完所有能力」,更常见是课程式训练。
阶段 A:单模态预训练(可借用现成模型)
- 文本:现成 LLM / 文本 Encoder
- 图像:现成 ViT / CLIP 视觉塔
- 音频:现成语音 Encoder
目的:先让每个塔自己会提取好特征。
阶段 B:跨模态对齐(Contrastive / 匹配)
典型目标(CLIP 风格):
- 一批图文对里,识别「谁和谁是一对」
- 拉近正样本对,推远负样本对
练完后,模型具备检索与粗语义对齐能力,但未必会流畅答题。
阶段 C:多模态指令微调(SFT)
数据形态类似:
text
输入:图片 + 「请描述这张图」
输出:一段标准回答
或:
text
输入:图片 + 「图中的人在做什么?」
输出:「他在骑自行车。」
训练方式和语言模型微调类似:让模型生成的下一个词接近标准答案。
差异只是输入序列里混进了视觉(或音频)特征 token。
阶段 D(可选):偏好对齐 / 安全对齐
用更好/更差回答做偏好优化,让输出更稳、更符合人类偏好。对入门非必须。
8. 推理原理:前向怎么走
以「看图回答」为例:
text
1. 读入图片,视觉编码器提取特征序列
2. 投影到语言空间,得到「视觉伪 token」
3. 用户问题分词,变成文本 token
4. 拼接:视觉伪 token + 文本 token
5. LLM 自回归生成:一次预测下一个词,循环直到结束
检索任务则不同,通常不生成长文本:
text
图 → 向量
文 → 向量
算相似度,取 Top-K
所以实现前先问清任务:
- 生成类:要解码器 / LLM
- 检索类:要共享向量空间 + 相似度
- 分类类:要融合特征 + 分类头
任务不同,融合与输出头都不同,但「编码 → 对齐 → 融合」骨架不变。
9. 用 PyTorch 视角看「内核」是什么
结合你已学的小模型知识,多模态并没有换一套物理学,仍然是:
| 层级 | 作用 |
|---|---|
| 张量 | 图像、文本、音频最终都变成张量 |
nn.Module |
各编码器、投影、融合、任务头都是模块 |
| 前向 | 推理 = 按图计算一遍 |
| 损失 + Autograd + Optimizer | 训练 = 用损失驱动参数更新 |
变复杂的只是模块变多、数据管道变复杂:
text
小模型:
x_数值 → Linear → 概率
多模态:
x_图 → VisionEnc → Proj ┐
├→ Fusion / LLM → 输出
x_文 → TextEnc / Tokenizer ┘
训练五步仍然成立:
text
zero_grad → forward → loss → backward → optimizer.step
只是 forward 里要同时处理多种输入,loss 可能是对比损失、生成损失或二者组合。
10. 三种经典实现路线对照
| 路线 | 代表思想 | 擅长 | 实现要点 |
|---|---|---|---|
| 双塔检索 | CLIP | 图文互搜、向量库 | 双编码器 + 对比学习 |
| 交叉编码 | 早期 VQA / 融合 Transformer | 细粒度匹配、分类 | Cross-Attention 融合 |
| 视觉语言模型 VLM | LLaVA 等 | 看图聊天、复杂问答 | 视觉塔 + 投影 + LLM |
选型建议:
- 只要「以图搜文 / 以文搜图」→ 双塔
- 只要「看图选类别 / 简单问答选项」→ 融合 + 分类头也可能够
- 要「自由文本回答、多轮对话」→ VLM 路线
11. 数据长什么样(实现时最容易忽略)
多模态质量高度依赖配对数据。
检索 / 对齐数据:
text
image_path, caption
cat.jpg, 一只橙色的猫趴在窗台
car.jpg, 一辆红色轿车停在路边
视觉问答数据:
text
image_path, question, answer
shop.jpg, 货架上有几种饮料?, 三种
指令数据:
text
image_path, instruction, response
chart.png, 请总结这张图的趋势, 销售额连续三个月上升...
实现时要注意:
- 图文是否真对齐(错配会直接毒化对齐)
- 分辨率、长宽比、文本长度要统一预处理
- 缺失模态(只有图没有文)要有策略:丢弃、掩码或单模态降级
12. 一个最小「概念级」模块划分
若用 PyTorch 拆项目,常见文件/模块是:
text
vision_encoder.py # 图像 → 特征
text_encoder.py # 文本 → 特征(检索双塔时)
projector.py # 维度对齐 / 空间翻译
fusion.py # 拼接、注意力融合等
llm_wrapper.py # 可选:接生成式语言模型
loss.py # 对比损失 / 生成损失
dataset.py # 同时读图和文本
train.py # 训练循环
infer.py # 检索或生成推理
最小检索模型的前向概念:
python
# 概念代码
img_vec = normalize(proj_img(vision_encoder(images)))
txt_vec = normalize(proj_txt(text_encoder(texts)))
logits = img_vec @ txt_vec.T # 相似度矩阵
# 对比学习:对角线位置应是配对样本
最小看图对话前向概念:
python
# 概念代码
v = projector(vision_encoder(images)) # [B, T_v, D]
t = embed_tokens(question_ids) # [B, T_t, D]
seq = concat(v, t, dim=seq_len) # 序列维拼接
out = llm(inputs_embeds=seq) # 再做语言建模
13. 和 RAG、纯微调的关系
多模态系统里这三者经常一起出现:
| 技术 | 作用 |
|---|---|
| 多模态编码 / VLM | 让模型「看得懂」图或音 |
| RAG | 检索外部知识(也可检索多模态向量库) |
| 微调(LoRA 等) | 让模型更会按你的格式与领域说话 |
例如企业文档助手:
text
用户上传截图
→ 多模态模型理解截图内容
→ RAG 检索内部知识库
→ LLM 组织最终回答
不要把「多模态」理解成只有一种训练方式;它是输入形态扩展,训练仍可用对比、SFT、RAG 增强等组合。
14. 实现时常见误区
-
以为拼接就是多模态
不对齐语义时,拼接只是把两路噪声捆在一起。
-
一上来就端到端猛训所有模块
更容易崩。更稳的是:强预训练塔 + 先训投影 / 对齐 + 再指令微调。
-
忽略序列长度
一张图可能变成几十到数百个视觉 token,再加长文本,上下文很容易爆。
-
用分类准确率衡量一切
生成任务要看文本质量;检索任务要看 Recall@K;问答要看是否吃到了图像证据。
-
模态不平衡
若文本极强、图像分支几乎不被梯度更新,会出现「根本没看图也能靠语言先验瞎答」。需要在数据与损失上强迫模型用图。
15. 学习路径(接你当前进度)
你已经走过:张量 → nn.Linear → 单任务小模型。建议这样接多模态:
| 步骤 | 学什么 | 验收 |
|---|---|---|
| 1 | 复习向量相似度、余弦距离 | 能手算/代码算两向量是否接近 |
| 2 | 做最小双塔:假图特征 + 假文特征对比学习 | 配对样本相似度高于非配对 |
| 3 | 读懂「视觉特征投影进 LLM」的数据流 | 能画出 LLaVA 式框图 |
| 4 | 跑一个现成小 VLM 或 CLIP 推理 demo | 会调用、会看输入输出 |
| 5 | 再谈训练:冻哪些层、训哪些层 | 能解释为何常冻视觉塔 |
不必先啃完所有论文。先能画出数据流,再能改一处模块观察变化,就比只记名词扎实。
16. 一张最终对照图
text
┌─ Vision Encoder ─┐
Image ──────────►│ │──► Projector ──┐
└──────────────────┘ │
▼
Shared / Fused
Space
▲
┌─ Text / Audio Enc ─┐ │
Text/Audio ─────►│ │──► Projector ┘
└───────────────────┘
│
▼
检索:算相似度
分类:融合后接分类头
生成:送入 LLM 解码
17. 总结
多模态实现原理可以收束为四句:
- 编码:各模态先变成向量或向量序列
- 对齐:用对比学习等手段,让同一语义的不同模态表示靠近
- 融合:用拼接、晚期融合或交叉注意力 / LLM 序列拼接做联合推理
- 训练:通常「预训练单模态塔 → 对齐 → 指令微调」,推理时按任务走检索或生成
它和 PyTorch 小模型开发共享同一套内核:张量计算 + 模块化前向 + 损失驱动的反向更新。多出来的,是「多种输入如何被翻译到同一套可计算语言里」。
读完本文后,若继续动手,优先做「双塔相似度」最小实验,再看「图像特征如何拼进 LLM 输入」,两条线分别对应检索式与生成式多模态的主干。