技术博客
多模态

CLIP 检索上手指南:从原理到跑通你的第一个 Demo

sky2026-07-09 10:57
CLIP 检索上手指南:从原理到跑通你的第一个 Demo

资源入口

论文链接:

https://arxiv.org/abs/2103.00020

代码地址:

https://github.com/rom1504/clip-retrieval

在线Demo:

https://rom1504.github.io/clip-retrieval/?back=https%3A%2F%2Fknn.laion.ai&index=laion5B-H-14&useMclip=false






你会得到什么




这篇文章的目标很明确:帮你在一两天内,亲手跑通一个能"用文字搜图片"的最小可用系统。读完之后,你应该能理解 CLIP 检索背后的核心思想,知道 clip-retrieval 这个开源项目每一块在做什么,避开大部分新人都会踩的坑,并且在自己的电脑上看到搜索结果真实地跳出来。





理解CLIP




CLIP 是一个什么样的模型

你大概接触过 ResNet 这样的图像分类模型,它的工作方式是:输入一张图,输出 1000 个类别的概率分布。这类模型有一个隐含的局限,它只认识训练时见过的那些类别。如果训练集里没有"水豚",它就永远不会告诉你图里是水豚。


CLIP 换了一条思路。OpenAI 在 2021 年发布它的时候提出了一个很巧妙的训练目标:不再让模型学"这张图属于哪一类",而是让它学"这张图和哪段文字在语义上匹配"。训练数据是从互联网上收集的 4 亿个图文对,模型里有两个编码器(一个处理图像,一个处理文本),训练目标是让匹配的图文对在向量空间里靠近、不匹配的远离。这个过程叫做对比学习(contrastive learning),也是 CLIP 名字里"Contrastive"的由来。


训练完成之后,CLIP 获得了一项非常有用的能力:把任意一张图、任意一段文字,分别映射到同一个高维向量空间中的某个点(通常是 512 维或 768 维)。语义相近的图和文,它们的向量就靠得近;语义无关的就离得远。这里的"近"和"远"通常用余弦相似度来度量。


clip-retrieval 这个项目到底是什么

它是 LAION 社区维护的一个开源项目,把从"原始图片"到"可搜索系统"这条路径上的所有工程环节都封装好了。你可以把它理解成一套流水线工具包,它负责以下几件事。


  1. 批量编码:给它一堆图片或者一个图片 URL 列表,它调用 CLIP 批量算向量。这件事自己写也能写,但要写得高效(GPU 并行、内存管理、断点续传)并不容易。


  2. 索引构建:把算好的向量用 FAISS 建成可快速搜索的索引文件。它集成了 autofaiss,能根据你的数据规模自动选合适的索引类型,省去手动调参的麻烦。


  3. 检索服务:提供一个 HTTP 接口和一个简单的前端页面,让你输入文字或上传图片,就能看到搜索结果。


  4. 数据集下载:如果你想用 LAION 这样的大型公开数据集,它还提供了配套的 img2dataset 工具来高效下载海量图片。





动手跑通最小 Demo




环境准备

先说清楚硬件要求。CLIP 推理本身对硬件不算苛刻,有一块入门级的 NVIDIA GPU(比如 GTX 1660 或更好)会让体验顺畅很多。但如果只是跑几百张图的 demo,纯 CPU 也能跑,只是慢一些。内存建议 8GB 以上。


软件方面,建议用 conda 建一个独立环境,避免和你其他项目的依赖打架。这一点对多模态项目尤其重要,因为它们往往依赖一些版本敏感的库。

# 创建并激活一个新环境,Python 3.10 是目前比较稳妥的选择
conda create -n clip_retrieval python=3.10 -y
conda activate clip_retrieval
# 安装 clip-retrieval 主包,会自动拉下 CLIP、FAISS、torch 等依赖
pip install clip-retrieval
# 如果你有 NVIDIA GPU,建议单独装一下带 CUDA 的 torch
# 具体版本请去 pytorch.org 查,这里以 CUDA 11.8 为例
pip install torch torchvision --index-url https://download.pytorch.org/whl/cu118

装完之后,验证一下能不能正常导入:

# 在 Python 里跑一下,确认没报错
import clip_retrieval
import torch
print("CUDA 可用:", torch.cuda.is_available())

如果这里报错,最常见的原因是 torch 和 CUDA 版本不匹配,按照报错信息回去调整即可。


准备你的图片数据

为了跑通 demo,我推荐你从一个现实的小场景入手。比如把你手机里的 500 张照片导出来,或者从 Unsplash 上爬几百张风景图,都可以。我们就假设你把这些图放在了 ./my_images/ 目录下,里面是一堆 .jpg 或 .png 文件。


如果你一时没有现成的图片集,可以用下面这段脚本从 Unsplash 免费 API 快速下载几百张测试图(需要自己注册一个免费的 Access Key)。

# quick_download.py —— 仅用于演示,实际使用请遵守 Unsplash API 规则
import os, requests
from pathlib import Path
ACCESS_KEY = "YOUR_UNSPLASH_ACCESS_KEY"  # 在 unsplash.com/developers 注册获取
SAVE_DIR = Path("./my_images")
SAVE_DIR.mkdir(exist_ok=True)
# 下载 300 张随机风景图作为测试集
for i in range(30):  # 每次请求最多拿 10 张
    r = requests.get(
        "https://api.unsplash.com/photos/random",
        params={"count": 10, "query": "landscape"},
        headers={"Authorization": f"Client-ID {ACCESS_KEY}"},
    )
    for idx, photo in enumerate(r.json()):
        img_url = photo["urls"]["regular"]
        img_data = requests.get(img_url).content
        with open(SAVE_DIR / f"img_{i*10+idx:04d}.jpg", "wb") as f:
            f.write(img_data)
    print(f"已下载 {(i+1)*10} 张")


四步走通整个流程

步骤一:为所有图片算向量,这一步是整个流程里最耗时的,因为每张图都要过一遍 CLIP 模型。好在只需要做一次。

# --input_dataset 指向你的图片目录
# --output_folder 是输出目录,会自动创建
# --enable_metadata 让它额外保存每张图的路径等元信息,后面检索时要用
# --clip_model 选择 CLIP 的具体型号,ViT-B/32 是速度和效果的平衡点
clip-retrieval inference \
    --input_dataset ./my_images \
    --output_folder ./embeddings \
    --enable_metadata True \
    --clip_model "ViT-B/32"

运行完之后,./embeddings/ 里会多出 .npy 格式的向量文件和一个存图片路径的元数据文件。你可以用 numpy 随便看一下,确认向量的形状确实是 (图片数量, 512)。


步骤二:建立 FAISS 索引,向量有了,但直接用 numpy 做检索会很慢。这一步把向量组织成 FAISS 索引,本质上是建一些加速用的数据结构。

clip-retrieval index \
    --embeddings_folder ./embeddings \
    --index_folder ./index

对 500 张图这种小规模数据,这一步几秒钟就能跑完。如果你的图片量上了百万级,autofaiss 会自动切换到更复杂的索引类型(比如 IVF+PQ),时间和内存占用都会增加,但搜索速度会大幅提升。


步骤三:启动后端服务,索引建好之后,起一个 HTTP 服务来对外提供检索能力。

# 这个命令会在 1337 端口上启动一个服务
# 它会把 CLIP 模型加载进内存,把 FAISS 索引也加载进来
clip-retrieval back \
    --port 1337 \
    --indices_paths indices.json

这里 indices.json 是一个配置文件,内容长这样(需要你自己创建):

{
  "my_first_index": {
    "indice_folder": "./index",
    "provide_safety_model": false,
    "enable_faiss_memory_mapping": true,
    "columns_to_return": ["image_path"],
    "clip_model": "ViT-B/32",
    "enable_hdf5": false,
    "use_arrow": false,
    "enable_mclip_option": false
  }
}

这些选项在最开始不用每个都搞明白,照着填就行。重要的是 clip_model 字段必须和你步骤一里用的那个型号完全一致,否则查询向量和图库向量对不上,搜出来的结果会一团糟。


步骤四:打开前端试一试,另开一个终端窗口(保持后端服务一直运行),启动前端。

clip-retrieval front --port 8090

然后浏览器打开 http://localhost:8090,在搜索框输入"a dog running on the beach"(CLIP 对英文的理解比中文好得多,建议先用英文测试),点搜索,你应该就能看到按相关性排序的图片结果了。


如果这一步成功了,恭喜!你已经跑通了一个完整的 CLIP 检索系统。后面的事情都是在此基础上的扩展。


用代码替代命令行

命令行方便上手,但真正做项目时你多半要把这套能力嵌到自己的代码里。下面是一个最简的 Python 客户端示例,展示怎么直接调后端服务做检索。

# simple_query.py
from clip_retrieval.clip_client import ClipClient, Modality
# 连接到你本地跑着的后端服务
client = ClipClient(
    url="http://localhost:1337/knn-service",
    indice_name="my_first_index",   # 对应 indices.json 里的键名
    num_images=10,                   # 返回前 10 个结果
    modality=Modality.IMAGE,         # 搜索的是图片
)
# 用一段文字做查询
results = client.query(text="a golden retriever on the beach at sunset")
# 打印看看结果长什么样
for i, r in enumerate(results):
    print(f"{i+1}. 相似度 {r['similarity']:.3f} —— {r['image_path']}")

如果想用一张图来搜相似图,把 text=... 换成 image=path_or_url 即可。这个设计很优雅,因为不管查询端是文字还是图片,走的都是同一个向量空间。





避坑指南




  1. 模型版本不一致是最隐蔽的坑,CLIP 有很多变体,ViT-B/32、ViT-B/16、ViT-L/14、ViT-L/14@336px,还有 OpenCLIP 社区训练的 open_clip 系列。它们的输出向量维度可能不同(比如 ViT-B/32 是 512 维,ViT-L/14 是 768 维),而且就算维度相同,不同模型训练出来的向量空间也完全不通用。


    如何解决:索引阶段用的模型,和查询阶段用的模型必须完全一致。如果你中间升级了模型,整个索引需要重建。很多新手会碰到"搜出来的图完全风马牛不相及"的情况,80% 都是这个原因。


  2. 小数据集不要急着追求"亿级检索",网上很多教程一上来就讲 LAION-5B、讲分布式索引、讲 IVF-PQ 量化。这些对大规模生产系统很重要,但如果你只有几万张图,强行用这些技术反而会让精度下降、代码复杂度飙升。


    一个经验法则:十万张图以下用 IndexFlatIP(暴力搜索)就行,速度够快、结果精确。十万到一千万之间可以考虑 IndexIVFFlat。再往上才需要 PQ 这类量化索引。autofaiss 会自动帮你选,但你要理解它在选什么,才能判断结果是否合理。


  3. 检索精度不好时的排查顺序。


    如果最终搜出来的结果不理想,按下面这个顺序排查,能覆盖绝大多数情况。


    第一步,先检查索引和查询的模型是否一致。第二步,用英文短语重新试一次,排除语言问题。第三步,直接取一张图库里存在的图去查它自己,如果它没排在第一位,那说明流水线哪里出了错(理论上自己和自己的相似度是 1.0)。第四步,查一下查询词对应的向量和图库向量的尺度是否归一化过,CLIP 的标准做法是做 L2 归一化,这样余弦相似度和内积等价,否则相似度计算会出偏差。





最后的建议




跑通 demo 只是开始。真正让你对这套技术产生掌控感的,是在跑通之后去折腾它,换一个更大的 CLIP 模型看看精度怎么变,把图库扩大十倍看看时间和内存怎么变,尝试用图搜图、以图搜文的双向检索,甚至自己写一个小前端替换掉默认的那个。每次折腾都会让你对"向量空间"这个抽象概念多一分直觉。


遇到问题是正常的,尤其是多模态领域,工具链还在快速演进。养成看 issue、读源码的习惯比记住任何具体命令都更重要

点赞收藏
// 评论0
0 / 500
还没有评论,快来抢沙发