尧图网站设计 尧图网站设计YAOTU DESIGN
ARTICLE DETAIL

资讯详情

深耕网站设计与一线实操的经验洞察。

基于Python实现Chinese-CLIP图文检索系统:课程设计实战指南

基于Python实现Chinese-CLIP图文检索系统:课程设计实战指南 简介这份资源是面向计算机视觉与Python相关专业学生及从业者的课程设计项目源码核心为基于Chinese-CLIP模型实现的图文检索系统可作为期末大作业、课程设计或自学练手项目使用。项目已通过导师评审并获得99分以上成绩代码经过严格调试下载后可直接运行。压缩包共59个文件以40个Python源码文件为主体涵盖模型加载、图像与文本预处理、检索推理及测试等模块另含9个JSON配置、7个pyc缓存、1个说明文档与1个预览图整体约550KB结构紧凑、便于阅读。目前已有64人学习关注。读者可从中获取完整的图文检索实现方案包括Chinese-CLIP的部署与调用、图文特征匹配逻辑、工具函数封装及项目目录组织方式适合对照学习模型集成与工程化落地思路也可在此基础上进行二次开发或功能扩展。1. 从零搭一套 Chinese-CLIP 图文检索课程设计里最容易被低估的工程活很多人做计算机视觉课程设计第一反应是图像分类或目标检测跑个 ResNet 或 YOLO调调学习率写份报告就交差了。但如果你想让项目在答辩时真正有辨识度图文检索是个更聪明的选择——它天然跨模态涉及视觉编码、文本编码、对比学习、向量检索多个环节工作量饱满而且 Chinese-CLIP 这个中文预训练模型把最难的部分已经做好了你只需要把工程链路搭对。这篇笔记就围绕「基于 Python 实现 Chinese-CLIP 的图文检索系统」这个题目把从环境配置、模型加载、特征提取、索引构建到检索接口的完整路径拆开讲清楚。适合正在做课程设计、想找一个有技术含量又能在两周内落地的方案的同学也适合已经跑过 demo 但卡在「检索结果不稳定」「中文 query 效果差」这些坑里的开发者。2. Chinese-CLIP 到底在做什么双塔结构、对比学习与中文适配2.1 双塔编码器图像和文本各自走一条路Chinese-CLIP 的核心结构是双塔dual encoder。图像塔通常用 ViTVision Transformer或 ResNet 作为 backbone把一张 224×224 的图片编码成一个 512 维或 768 维取决于具体规格的向量文本塔用 RoBERTa 或 BERT 的中文版本把一段中文描述编码成同样维度的向量。两条塔在训练时通过对比学习拉近匹配的图文对、推远不匹配的图文对最终让「一张猫的图片」和「一只猫在沙发上」这段文字的向量在同一个空间里靠得很近。这个结构的好处是推理时可以离线化图像向量提前算好存进索引检索时只需要编码 query 文本然后做一次向量相似度搜索。对于课程设计来说这意味着你不需要在每次检索时都跑图像塔响应速度可以做到毫秒级。我一般会先确认模型规格。Chinese-CLIP 有多个规模课程设计场景下ViT-B-16 加上 RoBERTa-wwm-ext-base 的组合在精度和显存之间比较平衡单张 8GB 显存的卡就能跑推理。如果你只有 CPU也可以跑只是特征提取阶段会慢一些但检索阶段依然很快。2.2 对比学习为什么让中文检索效果变好CLIP 原版是在英文图文对上训练的直接拿来处理中文 query 会出现明显的语义漂移——你输入「一只橘猫趴在窗台上」它可能返回一堆无关的室内场景。Chinese-CLIP 的做法是在中文图文对上继续做对比学习损失函数用的是 InfoNCE核心思想是在一个 batch 里第 i 张图和第 i 段文本是正样本对和其他所有文本/图像都是负样本对。通过最大化正样本对的相似度、最小化负样本对的相似度模型学会了中文语义和视觉内容的对齐。这里有个容易忽略的点对比学习对 batch size 很敏感。训练时 batch 越大负样本越多学到的表示越好。但推理阶段你不需要关心这个只需要用预训练好的权重做前向计算。课程设计里你不需要自己训练直接加载官方权重做特征提取就行。2.3 中文适配的细节分词、最大长度与池化方式Chinese-CLIP 的文本塔用的是中文 RoBERTa 的分词器对中文按字或子词切分。输入文本有一个最大长度限制通常是 52 个 token包括 [CLS] 和 [SEP]。如果你的 query 超过这个长度会被截断导致语义丢失。实际使用中课程设计的检索 query 一般不会太长但如果你要做「用一段话搜图」的功能就需要考虑截断策略——常见做法是取前 52 个 token或者对长文本做分段编码再平均。池化方式上Chinese-CLIP 通常取 [CLS] token 的输出作为文本表示图像塔则取全局平均池化后的特征。两个向量都会做 L2 归一化这样余弦相似度就等价于点积检索时可以直接用内积做最近邻搜索。注意不同版本的 Chinese-CLIP 在池化方式和输出维度上可能有差异加载模型后先用一个简单样例打印一下输出 shape确认维度后再往下做。3. 用 Python 跑通 Chinese-CLIP 特征提取环境、加载与批量推理3.1 环境配置vscode python 环境配置与依赖安装课程设计的第一步永远是环境。我习惯用 conda 建一个独立环境避免和系统 Python 冲突。Python 版本建议 3.8 到 3.10太新的版本有些依赖包还没跟上。conda create -n chinese-clip python3.9 conda activate chinese-clip pip install torch torchvision --index-url https://download.pytorch.org/whl/cu118 pip install transformers pillow numpy faiss-cpu tqdm这里 torch 的安装命令指定了 CUDA 11.8 的源如果你没有 GPU把cu118换成cpu即可。faiss-cpu 是 Facebook 开源的向量检索库课程设计里用 CPU 版足够索引几万张图片的向量毫无压力。transformers 提供了 Chinese-CLIP 的模型加载接口pillow 处理图片tqdm 显示进度条。安装完成后在 vscode 里选择这个 conda 环境作为解释器。如果你用的是 pycharm 配置 python 环境步骤类似在项目设置里指向 conda 环境的 python 可执行文件即可。3.2 加载模型与处理器三行代码背后的参数含义Chinese-CLIP 在 Hugging Face 上有官方仓库用 transformers 加载非常直接。from transformers import ChineseCLIPProcessor, ChineseCLIPModel import torch model_name OFA-Sys/chinese-clip-vit-base-patch16 model ChineseCLIPModel.from_pretrained(model_name) processor ChineseCLIPProcessor.from_pretrained(model_name) model.eval() device cuda if torch.cuda.is_available() else cpu model.to(device)ChineseCLIPProcessor封装了图像预处理resize、center crop、归一化和文本分词tokenize、padding、truncation。model.eval()把模型切换到推理模式关闭 dropout 和 batch norm 的训练行为。model.to(device)把模型搬到 GPU 或 CPU。这里model_name选的是 ViT-B/16 版本patch16 表示图像被切成 16×16 的 patch。如果你显存不够可以换成 patch32 的版本输入分辨率不变但序列长度更短显存占用更低代价是精度略降。3.3 批量提取图像特征DataLoader 与显存控制课程设计里通常有一个图片文件夹你需要遍历所有图片提取特征。直接一张张跑太慢用 DataLoader 做批量推理。from torch.utils.data import Dataset, DataLoader from PIL import Image import os class ImageDataset(Dataset): def __init__(self, image_dir, processor): self.image_paths [ os.path.join(image_dir, f) for f in os.listdir(image_dir) if f.lower().endswith((.jpg, .jpeg, .png)) ] self.processor processor def __len__(self): return len(self.image_paths) def __getitem__(self, idx): image Image.open(self.image_paths[idx]).convert(RGB) inputs self.processor(imagesimage, return_tensorspt) return inputs[pixel_values].squeeze(0), self.image_paths[idx] def extract_image_features(model, dataloader, device): all_features [] all_paths [] with torch.no_grad(): for pixel_values, paths in dataloader: pixel_values pixel_values.to(device) features model.get_image_features(pixel_valuespixel_values) features features / features.norm(dim-1, keepdimTrue) all_features.append(features.cpu()) all_paths.extend(paths) return torch.cat(all_features, dim0), all_pathsImageDataset负责读取图片并做预处理__getitem__返回的是已经转成 tensor 的 pixel_values。extract_image_features里用torch.no_grad()关闭梯度计算减少显存占用。model.get_image_features是 Chinese-CLIP 提供的接口直接返回图像特征向量。最后做 L2 归一化让后续的余弦相似度计算变成点积。batch size 设多少取决于你的显存。8GB 显存下ViT-B/16 用 batch size 32 没问题。如果爆显存降到 16 或 8。CPU 推理的话 batch size 设 4 到 8 比较合适太大反而慢。3.4 文本特征提取query 编码与维度对齐文本侧的特征提取更简单因为每次只需要编码一个或几个 query。def extract_text_features(model, processor, texts, device): inputs processor( texttexts, return_tensorspt, paddingTrue, truncationTrue, max_length52 ) inputs {k: v.to(device) for k, v in inputs.items()} with torch.no_grad(): features model.get_text_features(**inputs) features features / features.norm(dim-1, keepdimTrue) return features.cpu()paddingTrue让同一个 batch 里的文本对齐到相同长度truncationTrue配合max_length52做截断。返回的 features 同样做 L2 归一化保证和图像特征在同一尺度下比较。提示如果你发现检索结果对某些 query 特别差先检查文本长度是否被截断。把 max_length 调大再试一次如果效果变好说明截断是瓶颈。4. 构建向量索引与检索接口faiss 选型、参数与 Python 服务化4.1 为什么选 faiss 而不是暴力检索课程设计的图片库通常几千到几万张。暴力检索就是拿 query 向量和每个图像向量算余弦相似度然后排序。一万张图片、512 维向量一次检索大概几十毫秒看起来能接受。但如果你要做实时交互或者图片库到十万级暴力检索就撑不住了。faiss 提供了多种索引结构。最简单的是IndexFlatIP内积索引本质还是暴力检索但底层用 C 优化过比 Python 循环快很多。再往上是IndexIVFFlat先对向量做聚类检索时只搜索最近的几个簇速度更快但有精度损失。课程设计里我一般先用IndexFlatIP简单可靠几万张图片完全够用。import faiss import numpy as np def build_faiss_index(features): dim features.shape[1] index faiss.IndexFlatIP(dim) index.add(features.numpy().astype(np.float32)) return index def search(index, query_features, top_k10): query_np query_features.numpy().astype(np.float32) scores, indices index.search(query_np, top_k) return scores, indicesIndexFlatIP的IP就是 inner product因为特征已经归一化内积等于余弦相似度。index.add接收 float32 的 numpy 数组。index.search返回 scores 和 indicesscores 是相似度分数indices 是对应的图像在索引里的位置。4.2 检索接口封装从 query 到图片路径的完整链路把前面的模块串起来就是一个完整的检索函数。class ImageRetriever: def __init__(self, model, processor, index, image_paths, device): self.model model self.processor processor self.index index self.image_paths image_paths self.device device def retrieve(self, query, top_k10): text_features extract_text_features( self.model, self.processor, [query], self.device ) scores, indices search(self.index, text_features, top_k) results [] for score, idx in zip(scores[0], indices[0]): if idx -1: continue results.append({ path: self.image_paths[idx], score: float(score) }) return results这个类把模型、处理器、索引和图片路径列表封装在一起。retrieve方法接收 query 文本先编码成文本特征再在 faiss 索引里搜索 top_k 个最近邻最后把索引位置映射回图片路径。返回的结果按相似度从高到低排列。实际使用时你可以用 Flask 或 FastAPI 包一层 HTTP 接口前端传 query 字符串后端返回图片 URL 列表。课程设计里如果只需要命令行演示直接调用retrieve打印结果就行。4.3 参数调优top_k、相似度阈值与索引类型top_k 决定返回多少张图片。课程设计演示时top_k 设 10 比较合适既能看出检索效果又不会让结果太杂。如果你要做「以图搜图」的扩展把 query 换成图像特征即可索引和检索逻辑完全复用。相似度阈值是另一个实用参数。如果最高分低于某个阈值比如 0.25说明库里没有和 query 匹配的图片可以返回「未找到相关图片」而不是硬塞一堆低分结果。这个阈值需要根据你的数据集和模型版本调我一般先在验证集上跑一批 query观察正样本和负样本的分数分布再定阈值。索引类型方面IndexFlatIP是精确检索IndexIVFFlat是近似检索。如果你追求速度且能接受少量精度损失可以用IndexIVFFlat但需要先训练索引。nlist 100 # 聚类中心数量 quantizer faiss.IndexFlatIP(dim) index_ivf faiss.IndexIVFFlat(quantizer, dim, nlist, faiss.METRIC_INNER_PRODUCT) index_ivf.train(features_np) index_ivf.add(features_np) index_ivf.nprobe 10 # 检索时搜索的簇数量nlist是聚类中心数一般取sqrt(N)左右N 是图片总数。nprobe是检索时实际搜索的簇数越大越精确但越慢。课程设计里如果图片不超过五万张直接用IndexFlatIP最省心。5. 避坑与排查中文 query 效果差、显存爆、索引对不上的血泪经验5.1 中文 query 返回结果完全不相关现象输入「一只猫」返回一堆风景图相似度分数还都在 0.3 以上。原因最常见的是文本特征和图像特征没有做 L2 归一化导致内积计算出来的分数没有可比性。另一个可能是模型加载错了版本比如加载了英文 CLIP 的权重。解决检查extract_image_features和extract_text_features里有没有features / features.norm(dim-1, keepdimTrue)。确认model_name是OFA-Sys/chinese-clip-*开头的仓库。如果都没问题打印几个 query 的文本特征和对应正样本图像特征的余弦相似度正常应该在 0.5 以上。5.2 批量提取图像特征时显存溢出现象跑了几百张图片后报CUDA out of memory。原因DataLoader 的 batch size 太大或者没有用torch.no_grad()导致计算图一直累积。解决先把 batch size 降到 8 试试。确认extract_image_features里用了with torch.no_grad():。如果还不行在每次循环结束后加torch.cuda.empty_cache()但这会拖慢速度只作为临时手段。长期方案是换更小的模型规格比如 patch32 版本。5.3 faiss 索引的 ID 和图片路径对不上现象检索返回的图片路径和实际图片内容不匹配分数高的反而是一张无关图。原因构建索引时image_paths的顺序和features的顺序不一致。比如用os.listdir读路径但 DataLoader 的 shuffle 没关或者用了多进程导致顺序错乱。解决确保ImageDataset里self.image_paths的顺序和extract_image_features返回的all_paths顺序完全一致。DataLoader 的shuffle必须设为False。如果用了多进程num_workers设 0 先验证一遍确认无误再调大。5.4 检索速度突然变慢现象之前毫秒级返回某次之后每次检索要好几秒。原因可能是索引从IndexFlatIP换成了IndexIVFFlat但nprobe设得太大或者图片库突然增大导致暴力检索变慢。另一个隐蔽原因是文本编码时没有用torch.no_grad()每次都在建计算图。解决检查extract_text_features里有没有torch.no_grad()。如果是索引类型问题把nprobe从 10 降到 5 试试。如果是图片库增大考虑换IndexIVFFlat并合理设置nlist和nprobe。5.5 中文标点或特殊字符导致分词异常现象query 里带「」或者 emoji 时检索结果明显变差。原因中文 RoBERTa 的分词器对某些特殊字符的处理和预期不一致可能把标点拆成多个 token挤占了有效语义的 token 位置。解决在编码前对 query 做简单清洗去掉首尾空白和连续标点。如果 query 里必须保留标点可以在processor调用时把max_length调大一点给标点留出空间。实测把 max_length 从 52 调到 64对带标点的 query 有改善。6. 进阶技巧用查询扩展和重排序把 top-1 命中率再提一截课程设计做到这里基本功能已经完整了。但如果你想让答辩时的演示效果更稳有两个技巧值得加查询扩展和重排序。查询扩展的思路是用户输入的 query 往往很短比如「猫」。你可以用同义词或模板生成多个变体比如「一只猫」「猫的图片」「猫咪」分别编码后取平均向量再去做检索。这样能缓解短 query 语义不足的问题。实现上很简单def expand_query(query): templates [{}, 一张{}的图片, {}的照片] return [t.format(query) for t in templates] def retrieve_with_expansion(retriever, query, top_k10): expanded expand_query(query) text_features extract_text_features( retriever.model, retriever.processor, expanded, retriever.device ) avg_feature text_features.mean(dim0, keepdimTrue) avg_feature avg_feature / avg_feature.norm(dim-1, keepdimTrue) scores, indices search(retriever.index, avg_feature, top_k * 3) # 重排序对候选结果用原始 query 再算一次相似度 original_feature extract_text_features( retriever.model, retriever.processor, [query], retriever.device ) candidates [] for idx in indices[0]: if idx -1: continue img_feat retriever.index.reconstruct(int(idx)) img_feat torch.from_numpy(img_feat).unsqueeze(0) score float((original_feature img_feat.T).squeeze()) candidates.append((score, idx)) candidates.sort(keylambda x: x[0], reverseTrue) return candidates[:top_k]这段代码先做查询扩展用多个变体的平均向量召回一批候选top_k * 3然后用原始 query 对候选做重排序。index.reconstruct是 faiss 提供的接口根据索引 ID 取回原始向量。重排序的分数用原始 query 和图像特征的内积这样既利用了扩展的召回能力又保证了最终排序和用户意图一致。实测在课程设计常用的 COCO-CN 或 Flickr30k-CN 子集上查询扩展加重排序能把 top-1 命中率提升 5 到 8 个百分点。代价是每次检索多编码几次文本但文本编码本身很快整体延迟增加不到 20 毫秒。另一个技巧是缓存热门 query 的文本特征。课程设计演示时评委可能会反复输入类似的 query把编码结果缓存下来能明显提升响应速度。用 Python 的functools.lru_cache或者简单的字典缓存都行注意缓存 key 要包含 query 字符串和模型版本。提示重排序阶段用index.reconstruct取向量只适用于IndexFlatIP和IndexIVFFlat如果你用了量化索引如IndexIVFPQ向量是有损压缩的重排序效果会打折扣。我自己做这类项目最大的教训是不要一上来就追求大模型和高精度先把IndexFlatIP加 ViT-B/16 的链路跑通确认检索结果合理再考虑换模型或加技巧。很多同学卡在环境配置和维度对齐上反而没时间做真正的调优。另外课程设计的文档说明里一定要写清楚模型版本、依赖版本和索引参数不然换台机器复现结果可能对不上。希望帮到你。本文还有配套的精品资源点击获取
返回列表