一句话理解:先把图片和文本都变成向量,搜索时只需要找到方向最接近的那些向量。
上一篇《CLIP 的零样本分类:一组文本如何变成图像分类器?》中,我们把几个候选类别写成文本,再让图片与它们逐一比较:
如果不再把候选文本限制为类别名称,而是换成任意描述,会发生什么?
例如,我们可以输入:
然后从一整个图片库中找出最符合这句话的图片。反过来,我们也可以输入一张图片,从许多文字描述中找出与它最匹配的几条。
这一篇将完成一个可以直接运行的双向检索 Demo:
Text-to-Image:输入文字,搜索图片;
Image-to-Text:输入图片,搜索候选描述;
将图片和文本特征提前保存为索引;
返回相似度最高的 Top-K 结果;
使用 Recall@K 评估检索效果。
全文不需要训练模型,也不会更新 CLIP 的参数。
一、零样本分类和图文检索有什么关系?
零样本分类与图文检索看起来是两个任务,底层计算却几乎相同。
在零样本分类中,候选项是几个类别 Prompt:
模型计算图片与每条文本的相似度,再选择最高分。
在文本搜图中,候选项变成了图片库:
模型把查询文本与每张图片进行比较,返回得分最高的几张。
两者的区别主要在于“谁是查询,谁是候选库”:
任务 | 查询 | 候选库 | 输出 |
|---|---|---|---|
零样本分类 | 图片 | 类别文本 | 最可能的类别 |
文本搜图 | 文本 | 图片 | 最匹配的图片 |
以图搜文 | 图片 | 文本 | 最匹配的描述 |
所以,上一期的零样本分类其实可以看成一个规模很小、候选文本形式很特殊的图文检索任务。
二、CLIP 为什么能用于跨模态搜索?
普通搜索系统最容易处理的是同一种数据:用文本搜索文本,或者用图片搜索图片。
CLIP 的特别之处在于,它把两种不同模态映射到了同一个特征空间:
如果图片是一只在草地上奔跑的狗,而文本也在描述这个场景,它们的向量方向通常会比较接近。
如果文本描述的是一架停在机场的飞机,那么它与这张狗的图片之间的向量方向通常会更远。
经过 L2 归一化后,我们可以直接使用向量点积作为余弦相似度:
余弦相似度越大,代表语义越接近。
在检索任务中,我们通常只关心排序,因此不一定要再做 Softmax。把相似度从高到低排列,就能得到 Top-1、Top-5 或 Top-10 结果。
三、双向检索的完整流程
一个最小的 CLIP 检索系统可以分为两个阶段。
阶段 1:离线构建索引
图片库和文本库通常不会在每次搜索时发生变化,所以这些向量可以提前计算并保存。
阶段 2:在线执行查询
文本搜图:
以图搜文:
这两个方向使用的是同一套模型和同一种相似度计算,只是查询向量与候选向量互换了位置。
四、先准备一个最小数据集
我们使用一个 JSON 文件同时记录图片路径和对应描述。
项目结构如下:
这里的描述既是示例数据,也是“以图搜文”时的候选文本库。
如果一张图片有多条描述,可以在 JSON 中放入多条记录。正式评估时,要注意同一张图片可能对应多个正确文本,不能只把其中一条视为正确答案。
五、安装运行环境
安装 PyTorch、OpenCLIP、Pillow 和 tqdm:
本文使用下面这组模型配置:
第一次运行时需要下载权重,之后通常会从本地缓存加载。
原始 OpenAI CLIP 的英文能力更稳定,因此示例中的查询和描述都使用英文。如果要直接使用中文查询,应该选择支持中文或多语言的 CLIP 类模型,并在自己的数据上重新验证效果。
六、完整代码:构建索引并执行双向检索
七、构建图文索引
在项目目录中运行:
Windows PowerShell 可以写成一行:
它们分别保存:
所有图片的归一化特征;
所有文字描述的归一化特征;
图片路径、文字描述以及模型配置。
只要图片库、文本库和模型没有改变,就不需要重复构建索引。
下面这些情况需要重新构建:
添加、删除或替换了图片;
修改了候选描述;
更换了 CLIP 模型或预训练权重;
改变了与模型配套的图片预处理。
尤其不能使用模型 A 生成候选库特征,却使用模型 B 编码查询。即使两者输出维度相同,它们也不一定处于同一个特征空间。
八、用文本搜索图片
运行:
可能得到:
这里显示的是余弦相似度,不是概率。
候选库发生变化后,第一名的分数可能不变,但排名后的结果会发生变化;换一个模型后,分数范围也可能完全不同。
九、用图片搜索文本
输入一张查询图片:
可能得到:
CLIP 会回答:
候选文本中,哪一条与图片最匹配?
CLIP 不会直接回答:
请从零开始为这张图片写一句描述。
如果候选文本库中没有合适答案,CLIP 仍然会返回其中分数最高的几条。要生成全新的图片描述,需要使用具备文本生成能力的图像描述模型或视觉语言模型。
十、为什么构建索引比每次重新编码更合理?
假设图片库里有 10 万张图片。
最直接的做法,是每次收到一个文本查询时,都重新把 10 万张图片送入 Image Encoder。这会浪费大量时间,因为图片库根本没有变化。
更合理的方式是:
图片入库时只编码一次;
把图像向量保存到索引;
查询时只编码一条新文本;
用查询向量与已有图像向量计算相似度。
这样就把最昂贵的图像编码过程从“每次查询”移到了“数据入库”。
本例使用 PyTorch Tensor 保存特征,并通过矩阵乘法完成精确搜索。它适合学习、小型 Demo 和中等规模数据。
当候选数量增长到百万甚至更大时,通常会使用专门的向量检索系统,例如 FAISS 或其他向量数据库,以减少延迟和内存占用。
十一、矩阵乘法是如何完成批量检索的?
代码中对应这一行:
它一次性计算了查询与所有图片的相似度。
随后:
十二、查询文本应该怎样写?
下面几种写法表达的检索意图不同:
从上到下,查询越来越具体:
- dog 更关注主体;
- a photo of a dog 加入了图片语境;
- a dog running on grass 同时要求主体、动作和环境;
- a close-up photo of a wet dog 还加入了构图和状态。
查询越具体,检索结果未必一定越好。CLIP 对物体、场景和整体语义通常比较敏感,但对数量、精确空间关系、细微动作和复杂否定的理解可能不稳定。
例如,下面两个查询对人类来说差异很清晰:
十三、如何评估图文检索效果?
1. Recall@1
检查正确结果是否排在第 1 名。
2. Recall@5
检查正确结果是否出现在前 5 名中。
3. Recall@10
检查正确结果是否出现在前 10 名中。
评估时应该分别报告两个方向:
指标 | 含义 |
|---|---|
Text-to-Image R@1 | 正确图片排在文本查询第一名的比例 |
Text-to-Image R@5 | 正确图片进入文本查询前五名的比例 |
Image-to-Text R@1 | 正确描述排在图片查询第一名的比例 |
Image-to-Text R@5 | 正确描述进入图片查询前五名的比例 |
如果一张图片有多条正确描述,或者一条描述对应多张合理图片,评估代码需要把这些结果都当作正例,否则指标会低估模型表现。
十四、怎样进一步提升检索效果?
1. 选择更适合任务的模型
不同 CLIP 权重在速度、显存占用、多语言能力和特定领域表现上都有差异。更大的模型不一定在所有业务数据上都更好,应该在自己的验证集上比较。
2. 改善文本描述质量
过短、含糊或错误的描述会直接影响文本索引。对于商品、遥感或医学等领域,可以加入准确的类别、属性和场景信息,但不要堆叠与画面无关的关键词。
3. 为图片准备多条描述
同一张图片可以从主体、动作、环境等多个角度描述。多条文本能覆盖更多查询表达,但也会扩大索引,并带来去重和结果聚合问题。
4. 对查询进行改写
5. 使用两阶段检索
大型系统常先用向量相似度快速召回一批候选,再使用更精细的模型进行重排:
双编码器适合快速检索,因为候选特征可以提前保存;交叉编码或更复杂的视觉语言模型通常计算更慢,却可能更准确地理解细节。
参考资料
- Radford, A. et al. Learning Transferable Visual Models From Natural Language Supervision, 2021.
- OpenAI. CLIP: Connecting Text and Images.
- OpenAI. CLIP 官方代码仓库.
- ML Foundations. OpenCLIP 官方代码仓库.