拓十年匠心定制 · 商业建站与技术教学双线并行 咨询热线:400-886-1026 service@lmnt.cn
ARTICLE DETAIL

资讯详情

深耕网站建设与运营推广的一线实战洞察。

图文检索系统实战:Chinese-CLIP双塔原理与课程设计部署

图文检索系统实战:Chinese-CLIP双塔原理与课程设计部署 简介面向计算机视觉课程设计的基于Chinese-CLIP图文检索系统Python实现源码是期末大作业或课程设计的高分参考。项目围绕跨模态检索任务展开涵盖数据预处理、模型调用、检索链路与前端展示等完整流程代码注释清晰新手也可在配套文档指导下快速部署运行无需复杂依赖便于答辩前快速准备。资源包共59个文件以40个Python脚本为主体配合9个JSON配置文件、7个pyc编译文件以及txt说明、示例图片等辅助材料整体仅543KB体积轻量目录结构划分清晰便于按模块定位与修改。资源提供了文本索引图像、图像检索文本的双向示例并包含评估测试脚本与运行说明有助于学习Chinese-CLIP特征对齐、相似度计算以及跨模态匹配的具体实现思路。目前已有177人浏览学习该资源适合需要完成计算机视觉大作业的本科生或研究生以及希望快速搭建图文检索Demo的开发者参考。1. 图文检索课程设计我为什么推荐直接用 Chinese-CLIP 而不是自造双塔计算机视觉大作业做到图文检索这一题最常见的误区是一上来就想自己训一个双塔模型。实际拆完这套基于 Chinese-CLIP 的图文检索系统源码我的结论很直接课程设计阶段真正值钱的不是从零训练而是把 CLIP 的对比学习内核接上自己的数据、界面和评估。这套包的目录里已经齐了 cn_clip 内核、eval、training、deploy外面再套 utils.py、text2image.py、app.py属于“官方模型应用封装”的标准结构适合期末大作业、课程设计展示也适合想快速验证中文图文检索效果的计算机视觉项目。新手按顺序能跑通熟手拿它换数据集、做微调、导出部署格式都不算费劲。2. 双塔与对比学习CLIP 对齐原理和 Chinese-CLIP 的适配改动2.1 CLIP 双塔一张图和一个句子如何变成同一套坐标系里的向量先明确一个反直觉的事实CLIP 不是分类模型它从头到尾在做度量学习。图像走图像塔文本走文本塔。图像塔的骨干网络在 Chinese-CLIP 里通常是 ViT 或 ResNet把一张 224 或 336 分辨率的图编码成一个向量文本塔用的是中文 RoBERTa 这类 BERT 系结构把一句话编码成另一个向量。两个向量再各自过一层线性投影落到同一个维度空间里。训练时喂的是大量“图像-文本”配对目标是对角线上的匹配对距离近、非对角线上的不匹配对距离远损失函数是对称的对比损失。训练过程还学了一个温度标度推理阶段用不到把它当成黑匣子即可。这样设计的结果是两个塔在同一个空间里匹配关系用余弦相似度就能衡量。比如图库里有一张雪地里奔跑的哈士奇文本“一只在雪地里奔跑的哈士奇”编码后的向量会离它很近而“一只趴在沙发上的猫”会离它很远。检索时不需要任何分类头也不需要知道图库里有多少个类别这正是图文检索需要的能力开放的、没有固定类别集合的语义匹配。为什么检索环节都用余弦相似度而不是欧氏距离因为对比学习把特征拉到了单位超球面附近此时向量方向承载语义模长信息被压缩用欧氏距离排序会把带噪的模长差异也带进来。实际代码里就很直观归一化后余弦相似度退化成内积一行矩阵乘法搞定。2.2 Chinese-CLIP 与原版 CLIP 的差别数据、词表和初始化直接用 OpenAI 的 CLIP 做中文检索效果总隔着一层。原因不是模型结构而是训练语料里中文本就少。Chinese-CLIP 的适配改动主要在三个地方。第一是训练数据换成大规模中文图文对语义空间是按中文表达习惯对齐的第二是文本塔的 tokenizer 换成中文词表模型权重用中文 RoBERTa 初始化中文量词、叠词、口语化描述的处理明显比原版好第三是配套发布了多个规模的权重课程设计里最常用的是 ViT-B-16 这个档位。选型这件事值得多说两句。ViT-B-16 在速度、显存和效果之间最平衡一张消费级显卡能跑CPU 也能勉强凑合演示ViT-L-14 效果更好但显存占用和推理时间会成倍上涨为了大作业答辩去追高规格模型性价比不高。下面这个表是我拆项目时习惯用的参考。规模特征维度显存压力batch 16 预提取时建议场景RN501024低CPU 演示或老机器ViT-B-16512中课程设计默认选择ViT-L-14768高评测要刷指标时再用ViT-H-141024很高有卡、有耐心再碰判断该不该换大规模不是看显存够不够而是看你的图库类型。如果库里的图是街景、商品、海报这类垂直内容ViT-B-16 的通用语义已经够用如果库里是细分的物种、车型再考虑更大的模型或微调。课程设计阶段我一般默认 ViT-B-16 起步评估指标出来不够再往上升。有人会问加载预训练权重之后要不要在自己的图库上再微调。我的建议是先别动。Chinese-CLIP 在大规模中文图文对上已经对齐过语义课程设计通常只是换一个几十到几百张图的库属于领域不变、数据规模缩小全量微调反而容易过拟合。常见做法是先冻结直接检索效果不够再考虑第 6 章的评估和 training 目录里的轻量微调。2.3 从原理到最小代码闭环加载、编码、归一化原理落成代码第一步就是加载模型。cn_clip 包里的 load 函数会同时返回模型和配套的预处理对象这两者是一体的。常见做法是把权重统一放到 checkpoints 目录由 download_root 指定。import torch from cn_clip.clip import load device cuda if torch.cuda.is_available() else cpu model, preprocess load(ViT-B-16, devicedevice, download_root./checkpoints) model.eval()这里最关键的是 load 返回的 preprocess 必须直接用于后面的图像编码不要自己另写一套 resize 归一化。CLIP 对预处理非常敏感训练时怎么处理、推理时就得怎么处理换一个均值方差或裁剪尺寸检索效果会立刻掉一截。检索排序的最小闭环其实只有三步库特征归一化、查询特征归一化、矩阵乘法取 topk。下面的代码段是我在 text2image.py 里会写的样子逻辑上对文本检索图像和图像检索文本两个方向都适用。# db_feats 是提前算好的图库特征shape 为 [N, d] db_feats torch.from_numpy(img_features).to(device) db_feats db_feats / db_feats.norm(dim-1, keepdimTrue) # 文本查询向量 query model.encode_text(token_ids) # [1, d] query query / query.norm(dim-1, keepdimTrue) sims query db_feats.T # [N] top_indices sims.argsort(descendingTrue)[:5]归一化这一步不是可有可无的。对比学习训练出来的特征基本落在超球面附近只有先归一化余弦相似度才等于向量内积排序才不会有尺度干扰。topk 用的是 argsort 加切片和 torch.topk 等价区别是 argsort 能一次性拿全量排序方便后面做排除重复和日志打印。3. 项目结构与 Python 环境搭建解压之后先改这三个地方3.1 源码包结构哪些要改哪些可以当黑匣子拿到压缩包先别急着跑花两分钟把结构看一遍能省后面一整天的排查时间。顶层是项目主目录核心代码和相关脚本都在里面。我按用途拆开看路径作用要不要动cn_clip/官方内核包含模型定义、deploy、eval、training 等模块一般不动utils.py图像加载、路径、公共工具主要改这里text2image.py文本检索图像的主脚本跑通效果靠它改查询和 top_kapp.py界面入口课程设计演示靠它改标题和示例test.py冒烟测试验证模型加载和推理通路跑一遍即可eval/评估脚本算 RecallK答辩加分项training/微调脚本非必需deploy/ONNX 导出和部署非必需README.md运行说明先读注意 cn_clip 是带init.py 的 Python 包不是 PyPI 上的独立包名直接 import 的前提是项目根目录在 sys.path 里。常见做法是在项目根目录执行 pip install -e . --no-deps或者写一个路径注入。这一步不做跑 test.py 大概率直接报 ModuleNotFoundError。deploy、eval、training 这三个目录平时用不到可以当黑匣子但答辩想加分时它们都是现成的工具不需要自己重新写。3.2 Python 环境与依赖安装版本对齐比装得多重要课程设计最常见的第一道坎就是环境。我的建议是单独建一个 conda 环境别用系统 Python 直接装免得把别的项目环境搞坏。Python 版本选 3.8 到 3.10 之间不要追新3.12 上有些依赖编译会出问题。conda create -n clip_retrieval python3.8 -y conda activate clip_retrieval pip install torch torchvision --index-url https://download.pytorch.org/whl/cu118 pip install transformers ftfy regex tqdm如果机器没有 N 卡把 torch 那一行换成 CPU 版本即可。接下来把项目本身装进当前环境注意加 --no-deps避免它把依赖重复解析一遍。cd chinese-clip-retrieval pip install -e . --no-deps装完可以用一段小命令验证 import 是否畅通python -c from cn_clip.clip import load; print(import ok)在 VSCode 里开发的话记得把 Python 解释器指到刚才建好的 conda 环境否则终端里能 import、编辑器里飘红容易误判成代码问题。transformers 的版本我一般不用最新版遇到过新版本改了接口导致权重加载报错的情况按 README 里写明的版本装最省事。如果 README 里没写清楚就按 transformers 4.x 的中低版本起步报错再针对性升级。3.3 权重目录约定和第一次冒烟模型权重不去下载的话load 函数会卡在下载环节或者报找不到文件。建议按 README 或 Model Zoo 的说明把 ViT-B-16 对应的权重放到 checkpoints 目录下。目录结构约定成下面这样后面预提取特征、换数据集都不用改代码checkpoints/ clip_cn_vit-b-16.pt files/ images/ index/images 放待检索的图库index 放预提取的特征缓存。第一次跑建议先执行 python test.py 做冒烟它只加载一次模型、编码一张图确认模型和预处理链路是通的。判断冒烟是否通过看日志里打印的特征 shape 是不是 [1, 512]ViT-B-16 的特征维度是 512如果打印出别的维度说明模型装载错位趁早停下来查权重的对应关系。冒烟通过后再去跑完整检索这样能把“环境问题”和“效果问题”分开不会一上来就面对一长串报错。4. 检索链路代码走读text2image.py 与 app.py 各自负责哪一段4.1 预处理是检索的第一道坎说清楚一个容易混淆的点text2image.py 这里的含义是用文本描述去图库里找匹配图像属于检索不是 stable diffusion 那类文生图。搞清楚这一点后面读代码就不会往生成模型方向想。预处理是很多新手翻车的重灾区。图像侧必须使用 load 返回的 preprocess它内部包含了 resize、中心裁剪、转张量、按 ImageNet 统计值归一化这一整套。文本侧要用 cn_clip 自带的 tokenize不能拿通用 BERT tokenizer 顶替。from PIL import Image from cn_clip.clip import tokenize def load_image_for_retrieval(image_path, preprocess, device): # preprocess 是 load() 返回的配套处理器别自己手写 resize img Image.open(image_path).convert(RGB) img_t preprocess(img).unsqueeze(0).to(device) return img_t def encode_query_text(text, device): # context_length 官方默认 52 个 token中文一句话一般够用 text_t tokenize([text], context_length52).to(device) return text_tconvert(RGB) 的作用是把带透明通道的 PNG 统一成三通道避免通道数不一致导致编码报错。context_length 这里要解释清楚它不是越长越好CLIP 训练时文本就按这个长度截断随便拉长并不会让检索变准反而会让模型看到分布外的输入。如果查询句子确实长先做压缩或同义改写而不是改这个参数。4.2 库特征预提取把图和文本分头编码只有几百张图的课程设计全量计算相似度完全够用。但每一次检索都重新编码整个图库是浪费常见做法是启动时预提取一次特征缓存成 npy 文件之后所有查询只编码文本。这样图库特征只占很少显存ViT-B-16 的特征维度是 512一千张图缓存下来的 npy 约 2MB。import numpy as np import torch from cn_clip.clip import load, tokenize device cuda if torch.cuda.is_available() else cpu model, preprocess load(ViT-B-16, devicedevice, download_root./checkpoints) model.eval() # 如果缓存不存在就全量编码一次 import os, glob cache_path ./files/index/db_feats.npy if not os.path.exists(cache_path): feats [] for img_path in sorted(glob.glob(./files/images/*.jpg)): img_t load_image_for_retrieval(img_path, preprocess, device) with torch.no_grad(): feat model.encode_image(img_t) feats.append(feat.cpu().numpy()) db np.concatenate(feats, axis0).astype(float32) np.save(cache_path, db)重点说三点。第一特征要归一化后再存避免每次加载时重复计算第二astype(float32) 能把默认的 float64 压下来存储和后续矩阵乘法都快一倍第三这个缓存文件就是第 2 章里 db_feats 的来源界面端也复用它不会重复编码。4.3 检索主体逻辑一句话是怎么变成排序结果的text2image.py 的核心检索段本质上就是把缓存特征加载进来编码查询文本然后做一次矩阵乘法和排序。import argparse parser argparse.ArgumentParser() parser.add_argument(--text, default一只在草地上奔跑的金毛犬) parser.add_argument(--top_k, typeint, default6) args parser.parse_args() db_feats torch.from_numpy(np.load(cache_path)).to(device) db_feats db_feats / db_feats.norm(dim-1, keepdimTrue) text_t encode_query_text(args.text, device) with torch.no_grad(): q model.encode_text(text_t) q q / q.norm(dim-1, keepdimTrue) sims (q db_feats.T).squeeze(0) top_indices sims.argsort(descendingTrue)[:args.top_k] for idx in top_indices: print(idx.item(), round(sims[idx].item(), 4), image_paths[idx])代码逻辑很直白先归一化库特征再归一化查询特征内积得到相似度最后按相似度降序取前 N 个。排序结果里打印的是索引、相似度分数和对应的图像路径。常见做法是把分数保留四位小数方便写进报告。这里 argsort 返回的是索引不是特征本身所以后面要用 image_paths 列表去映射回真实文件名。4.4 app.py让演示不怯场的界面设计界面层推荐用 Gradio课程设计里最常见原因是它几十行就能出一个带输入框、结果画廊的页面不用写前端。app.py 的典型逻辑是把检索函数包一层输入中文文本输出排序后的图片列表和分数。import gradio as gr def search_image(text): # 复用 4.3 的检索逻辑返回 (图片路径列表, 分数列表) return get_topk_results(text, k6) gr.Interface( fnsearch_image, inputsgr.Textbox(label输入中文描述), outputsgr.Gallery(label检索结果), title基于 Chinese-CLIP 的图文检索系统, examples[一只在草地上奔跑的金毛犬, 落日下的城市天际线], ).launch()这里有两个实用细节。examples 里的默认例句是答辩的后悔药现场临时打字容易手滑预设例句能让流程快速走完。另一个细节是界面启动时只做一次图库特征加载查询时只编码文本否则每点一次检索都全量过一遍图像塔响应会慢到让人以为卡死了。5. 运行部署与常见问题排查五条实测踩坑记录5.1 从解压到界面起来的完整顺序把前面三章的步骤串起来按这个顺序执行每一步都有明确的验证点。python test.py # 冒烟模型加载 一次前向 python text2image.py --text 一只在草地上奔跑的金毛犬 # 检索验证 python app.py # 启动界面浏览器打开本地地址冒烟通过说明环境、权重、预处理链路没问题text2image.py 跑出带分数的结果说明检索排序逻辑没问题app.py 能起来说明界面层没问题。三步分开跑哪一步挂了就只在那一层排查不要从模型加载一路懵到界面。如果第一步就挂先看日志类别import 错误对应路径注入问题权重缺失对应下载问题显存不足对应模型规模问题三类问题处理方式完全不同不要上来就重装整个环境。5.2 环境与权重相关的三个高频问题问题一加载模型直接显存溢出。现象是报错 RuntimeError: CUDA out of memory。原因通常有两个图库一次性全量放进显存或者加载了 ViT-L-14 这类大权重。解决方法是预提取特征时把 batch 调到 8 或 16加载权重改成 ViT-B-16特征缓存放磁盘检索时只用很少显存。问题二权重加载报键名不匹配。现象是加载时提示 some weights were not used 或者 unexpected key。原因基本是模型规模和权重文件对不上比如把 ViT-L-14 的权重喂给了 ViT-B-16 的结构。解决方法是核对 load 的第一个参数和下载的权重名称一致别在代码里手动拼路径直接用 download_root 约定目录。问题三Windows 下中文输出乱码。现象是终端里检索结果的中文全部变成方块。原因是 Windows 默认编码是 GBKPython 打印中文时编码不一致。解决方法是运行时加 python -X utf8或者在脚本开头写 sys.stdout.reconfigure(encodingutf-8)终端里也可以先执行 chcp 65001 切换代码页。5.3 界面与效果相关的两个高频问题问题四检索结果看起来不相关。现象是输入“城市夜景”搜出来的图五花八门。原因多数不是模型坏了而是图库太小、语义空间稀疏。CLIP 学的是语义而不是关键词几十张图很难覆盖“城市夜景”的表达范围。解决方法是把图库扩充到几百张以上再检查图像预处理是否用了配套的 preprocess。如果效果还是欠佳再考虑用 training/ 目录做轻量微调。问题五界面能启动一点检索就报 list index out of range。现象是 Gradio 页面正常输入文本后直接抛索引错误。原因是图库路径没匹配到文件或者特征缓存是空的排序结果里根本没有候选。解决方法是启动时打印 len(db_feats) 和图库文件数量确认索引缓存不为空路径拼接用 os.path.join 避免 Windows 分隔符问题。5.4 常见误用与推荐做法对照误用现象推荐做法自己另写一套预处理检索分数普遍偏低直接用 load 返回的 preprocess每次查询都重编码图库响应慢到像卡死启动时预提取特征缓存把 context_length 拉长到几百长句效果反而变差保持默认改写查询文本跨规模混用权重加载报键名不匹配模型名和权重名严格对应这四条是课程设计里出现频率最高的误用前三条我都亲手踩过。尤其是预处理那一条换了自写的 resize 之后分数低了零点几排查了半天才发现是归一化参数没用对。从那以后凡是模型配套的 preprocess一律不自己重写。6. 进阶验证用 eval 脚本把“效果不错”变成报告里的量化指标6.1 构造评估集课程设计答辩最怕的一句话是“效果不错”。别用感觉打分用 RecallK 说话。先准备一个两列的 TSV 文件每行放图像路径和对应的参考文本一份评估集几十对就够用。数据不用多关键是覆盖不同的描述风格比如有动作描述、有场景描述、有带颜色的描述这样评估结果才不至于偏科。6.2 跑评估与结果呈现python -m cn_clip.eval.eval_retrieval \ --model_name ViT-B-16 \ --image_path ./files/images \ --text_file ./files/test_queries.tsv \ --top_k 1,5,10跑完把 R1、R5、R10 整理成表格放进课程设计报告比十张截图都有说服力。6.3 答辩前的两道保险还有两个加分操作。一个是 deploy 目录的 ONNX 导出把图像塔和文本塔转成 onnx 后用 onnxruntime 推理答辩时能明显感觉响应变快导出时注意 batch_size 固定、输入尺寸和 preprocess 保持一致。另一个是固定几条例句每次答辩前跑一遍 test.py 再手动查一次分数。我有一次答辩前把权重路径改错界面正常启动但检索结果全是随机图现场翻车。从那以后每次答辩前我都强制走一遍冒烟测试再用固定三条 query 打印分数确认分数分布合理才上演示机。希望帮到你。本文还有配套的精品资源点击获取
返回列表