YOLO-World开放词汇检测:PyTorch推理、微调与ONNX导出 简介面向计算机视觉开发者、目标检测学习者与需要开放词汇检测方案的工程人员这份 YOLO-World 配套资料提供 PyTorch 实现、预训练权重及预训练/微调代码可用于复现实验、课程项目、算法二次开发与快速验证。压缩包共 161 个文件约 2.35MB主体为 110 个 Python 源码并配有 Markdown 文档、文本说明、JSON 配置、示例图片、Shell 脚本、Notebook、Dockerfile 及少量 C 文件覆盖代码阅读、环境搭建与推理演示等用途。已有 883 人学习下载适合希望理解 YOLO-World 开放词汇检测与视觉定位流程的读者参考。资料围绕 prompt-then-detect 范式组织预训练权重可直接用于开放词汇推理训练与微调代码便于替换数据集及类别文本读者可据此了解模型结构、数据配置、训练与微调入口并通过文档和示例脚本完成权重加载、检测验证与结果排查目录结构较为清晰能减少从零搭建环境的成本。1. 从一个“换类别不用重训”的检测需求说起YOLO-World 源码和文档能落地到什么程度产线临时加一种物料传统 YOLO 要重新采集、标注、训练周期一拖就错过排产YOLO-World 源码和文档吸引人的点正是把类别先写成文本 prompt在不重训的前提下跑出第一版检测结果再决定是否补数据微调。它基于 PyTorch 生态核心是视觉特征与文本嵌入的开放词汇检测适合需要快速验证新类别、做竞品分析、给标注团队定边界的从业者也适合想拆开源码理解文本编码器和检测头如何对接的开发者。资料给的是源码和文档不是现成业务系统能不能用取决于你是否愿意先跑通基线、记录参数、再用自己的验证集卡住误检和漏检。2. 把 PyTorch 环境跑通从依赖检查到第一张图推理2.1 先确认 CUDA、PyTorch 和 torchvision 的匹配关系拿到 YOLO-World 源码后最忌讳的做法是直接pip install -r requirements.txt然后跑 demo。开放词汇检测比普通 YOLO 多一个文本编码器依赖链里同时牵扯 PyTorch、torchvision、CUDA 运行时和分词器组件任何一处版本错位都可能在加载权重时炸出形状不匹配。我一般先建一个干净虚拟环境再确认机器上的 CUDA 驱动能支撑当前 PyTorch 轮子最后才装项目依赖。下面这段检查脚本不依赖项目代码先把底座摸清楚。# check_env.py import torch import torchvision print(torch:, torch.__version__) print(torchvision:, torchvision.__version__) print(cuda available:, torch.cuda.is_available()) print(cuda version:, torch.version.cuda) print(gpu count:, torch.cuda.device_count()) if torch.cuda.is_available(): print(gpu name:, torch.cuda.get_device_name(0)) # 做一次小矩阵乘法确认 CUDA 不只是在“可见”层面可用 x torch.randn(256, 256, devicecuda) y torch.randn(256, 256, devicecuda) z x y print(cuda matmul ok:, z.shape)这段脚本的作用不是炫技而是把“驱动可见 CUDA”和“PyTorch 真能跑算子”分开验证。torch.cuda.is_available()为真但矩阵乘法报错的情况并不少见常见原因是容器里的 CUDA 运行时和宿主机驱动不匹配或者装了 CPU 版 PyTorch。torch.version.cuda显示的是 PyTorch 编译时使用的 CUDA 版本不是驱动版本两者不需要完全相同但大版本跨度太大就容易翻车。确认底座后再按资料里的依赖说明安装不要自己随手升级torch否则文本编码器依赖的算子可能对不上。如果机器没有 GPU也能跑通推理只是device要改成cpuimgsz先降到 640 以内别拿大图硬扛。CPU 推理适合验证类别名和预处理链路不适合做批量评估。另一个常见坑是torchvision和torch分开安装导致版本错配表现是导入torchvision.ops时报符号未定义解决方式是按 PyTorch 官方轮子组合重装而不是单独升级某一个包。2.2 权重与目录文本编码器不能漏YOLO-World 的权重通常不止一个视觉检测权重还涉及文本编码器权重或已经融合好的文本嵌入。资料里的文档一般会写清楚权重放置位置但很多人只把检测权重放进目录跑起来发现类别设置无效或者报找不到 CLIP 相关文件。我一般先不猜文件名而是从入口脚本和配置文件里反查它实际读取哪些路径再用一个路径检查脚本把缺失项一次性列出来。# 在项目根目录先看文档和配置里出现的权重路径关键词 grep -RIn --exclude-dir.git -E weight|ckpt|checkpoint|clip|text_encoder . | head -80这条命令只用来定位线索不是让你无脑改路径。重点看入口脚本里default或配置里model.weights、text_encoder这类字段确认它是相对路径还是绝对路径。相对路径通常以项目根目录或配置文件所在目录为基准不同代码风格不一样跑之前先pwd确认当前目录再决定要不要改配置。权重缺失的报错有时很绕比如先报KeyError再报形状不匹配本质都是文本侧没加载完整。还有一个细节如果资料里提供的是已经重参数化的权重类别在导出时就被固化了此时再改set_classes可能不生效。重参数化适合部署固定类别不适合继续做开放词汇实验。验证方法是拿两个完全不同的类别列表各跑一次同一张图如果输出类别名和框完全不变基本可以判断你加载的是固化权重或者文本嵌入被缓存后没有刷新。2.3 跑通最小推理类别列表、置信度和 NMS最小推理不要一上来接摄像头也不要用业务大图。选一张光线正常、目标清晰的测试图先用三到五个英文类别跑通确认输出框、类别名和分数都正常再逐步换自己的类别。下面这段脚本按常见 Ultralytics 风格入口写如果资料里的推理入口是配置式脚本模型加载那两行换成文档里的入口set_classes和预测参数的含义一致。# minimal_infer.py from ultralytics import YOLOWorld # 权重路径按资料文档替换不要写死不存在的文件 weights path/to/yolo_world_weights.pt model YOLOWorld(weights) # 类别先用英文短词避免一上来就叠加分词和同义词问题 classes [person, safety helmet, forklift] model.set_classes(classes) results model.predict( sourcepath/to/test.jpg, imgsz640, conf0.25, iou0.5, device0, max_det100, saveTrue, ) for r in results: # 打印类别名和分数别只看保存出来的图 for box in r.boxes: cls_id int(box.cls.item()) print(classes[cls_id], round(float(box.conf.item()), 3), box.xyxy.tolist())set_classes要放在predict之前且每次换类别后重新调用有些封装会缓存文本嵌入换类别不重新设置就会出现“明明改了列表结果还是旧类别”的假象。imgsz控制推理分辨率小目标多时可以提高但显存和耗时也会上升conf是置信度阈值开放词汇检测里这个值不要照搬封闭词汇模型类别名越抽象分数分布越玄学建议先用 0.15 到 0.35 之间做几组对比。iou是 NMS 阈值密集目标场景可以适当降低但太低会吞掉相邻框。device0表示第一张 GPUCPU 环境改成cpu。max_det限制每张图最多保留多少框评估时不要设得太小否则漏检会被误判成模型不行。参数常见取值作用调整建议imgsz640 / 800推理输入尺寸小目标多先试 800显存不够再降conf0.150.35置信度过滤开放类别先低后高记录误检变化iou0.450.6NMS 重叠阈值密集目标降低稀疏目标保持 0.5device0/cpu推理设备GPU 不可用时先 CPU 验证链路max_det100300单图最大检测数评估场景不要压得太低2.4 基线记录别凭一张图判断能用跑出第一张图后最容易犯的错是“看着还行”就直接接业务数据。开放词汇检测的稳定性跟类别措辞、图像域、阈值强相关单图好看不代表批量可用。我一般会建一个很小的基线表至少覆盖十张图、三组类别、两组conf把检出数、误检数、漏检数和耗时记下来。这个表不追求漂亮只用来判断下一步该调 prompt、调阈值还是补数据微调。图片编号类别组conf检出数明显误检明显漏检备注img_001基础三类别0.25610远处小目标分数偏低img_001基础三类别0.15830误检增加适合召回优先img_002加入同义词0.25502复合词拆分后漏检记录时把“类别组”写清楚不要只写“改过 prompt”。后面排查问题时你会需要知道当时到底传了哪些字符串、有没有加模板、有没有换权重。基线跑通的标准不是检出所有目标而是同一组输入重复跑两次结果一致、换类别后输出确实变化、GPU 和 CPU 的类别名映射一致。这三条过了再进入源码结构和自定义数据。3. 源码目录怎么读文本编码、视觉融合和检测头三条线3.1 从入口脚本倒推配置先找 build model 和 text encoderYOLO-World 源码看起来目录多真正要抓的入口只有几条线推理脚本、训练脚本、模型构建配置、数据配置。不要从最底层算子开始读那样很容易陷进黑匣子。我一般先在入口脚本里搜build、model、text、set_classes这几个关键词确认模型对象从哪里来、文本编码器在哪里初始化、类别列表最终传给了谁。这样读一遍比对着目录树猜半小时有效。# trace_entry.py from pathlib import Path # 把入口脚本路径换成资料里的实际文件 entry Path(path/to/inference_or_train_entry.py) text entry.read_text(encodingutf-8, errorsignore) for i, line in enumerate(text.splitlines(), 1): low line.lower() if any(k in low for k in [build, text_encoder, set_classes, clip, model(]): print(f{i:04d}: {line.strip()})这段脚本只做静态定位不执行项目代码。输出里重点看三类行模型构建处、文本编码器初始化处、类别设置处。如果set_classes只是把字符串存进列表真正编码发生在predict内部那缓存问题就要去预测封装里找如果入口脚本自己调用了 tokenizer 和文本编码器那类别模板和最大 token 长度就会影响结果。clip关键词可能出现在配置名里也可能只是注释别看到就改先确认它是不是实际加载路径。读配置时还要注意一个分叉有些实现把文本嵌入预先算好存成文件推理时直接加载有些实现每次set_classes都重新编码。前者启动快但改类别后必须清缓存或重新生成嵌入后者灵活但批量推理时别在循环里反复set_classes否则文本编码器会成为瓶颈。我一般把类别设置放在循环外图片循环内只做predict这样耗时更可控。3.2 文本 prompt 进入模型后发生了什么YOLO-World 的核心链路可以粗分成三段图像经过视觉 backbone 和 neck 得到多尺度特征类别名经过 tokenizer 和文本编码器得到文本嵌入视觉特征与文本嵌入在融合模块里交互检测头输出框和类别分数。开放词汇能力来自文本侧提供的语义锚点所以类别名不是随便写的标签它直接参与分类决策。把类别写成person和写成a photo of a person在某些实现里分数分布会不一样原因就是文本编码器看到的上下文变了。这也解释了为什么同一个模型换一组同义词误检和漏检会跳变。类别名太短可能语义太宽比如helmet会把安全帽、骑行头盔、摩托车头盔都拉进来类别名太长又可能偏离训练分布导致分数整体偏低。我一般先用短词跑基线再针对误检类别加限定词比如把helmet改成safety helmet把car改成sedan car。每次只改一个类别保留记录否则出了问题不知道是哪次改动造成的。如果资料里的配置支持 prompt 模板不要把模板写死在推理脚本里。下面这种小函数适合放在配置层模板和类别分开维护后面做类别扰动实验也方便。# prompt_config.py def build_prompts(classes, templatea photo of a {}): # 模板只作用于需要上下文的类别短词类别可以传 templateNone if template is None: return list(classes) return [template.format(c) for c in classes] base_classes [person, safety helmet, forklift] prompts_with_template build_prompts(base_classes, templatea photo of a {}) prompts_plain build_prompts(base_classes, templateNone) print(plain:, prompts_plain) print(template:, prompts_with_template)这段代码不直接调用模型目的是把 prompt 生成逻辑独立出来。template参数决定是否给类别名加语境classes保持原始英文短词方便和标注文件里的类别名对应。实际传给set_classes的应该是最终字符串列表而不是原始类别名和模板混在一起。模板不是越多越好开放词汇检测里最常见的翻车是“训练时没见过这种模板推理时分数整体塌了”所以模板要和资料文档里的示例保持一致改一次记一次。3.3 自定义类别列表的写法英文、同义词和复合词自定义类别列表时先遵守三个原则用英文、用具体名词、一个类别只表达一个概念。中文类别名不是绝对不能跑但文本编码器对中文的语义覆盖通常不如英文稳定尤其遇到专业术语和缩写分词结果可能完全偏离。常见做法是维护一张中文到英文的映射表推理时传英文展示时再映射回中文这样既不破坏文本侧也不影响业务可读性。复合词要特别小心。worker_with_helmet这种下划线拼接不一定比worker wearing a helmet好前者可能被 tokenizer 拆成奇怪片段后者更接近自然语言。我的习惯是先把复合需求拆成两个单类别person和safety helmet分别检测后在业务层做空间关联而不是硬压成一个类别。这样模型侧更稳定后处理逻辑也更清楚。# class_mapping.py cn_to_en { 人员: person, 安全帽: safety helmet, 叉车: forklift, 反光背心: high visibility vest, } en_classes list(cn_to_en.values()) # 传给模型的是英文列表展示时用反查表 en_to_cn {v: k for k, v in cn_to_en.items()} def show_name(en_name): return en_to_cn.get(en_name, en_name) print(en_classes) print(show_name(forklift))这段映射代码解决的是“模型侧稳定、业务侧可读”的分离问题。cn_to_en放在配置里维护不要散落在多个脚本en_classes的顺序要和后续结果解析一致因为很多模型输出的是类别索引索引错位比类别名写错更难查。show_name只做展示不参与推理。如果资料里的模型支持中文文本编码也要先用小批量图验证中文类别和英文类别的检出差异别假设它一定等价。3.4 重参数化导出快但动态类别会收窄重参数化的价值是把文本嵌入固化进模型推理时不再跑文本编码器速度更稳部署链路更简单。代价是类别被固定导出后不能再像开放词汇模型那样随便set_classes。这一步适合“类别已经验证完、准备上固定场景”的阶段不适合还在做类别探索的阶段。导出前先确认三件事当前类别列表就是最终列表、预处理和后处理参数已经固定、导出的输入输出形状和推理引擎对得上。# export_model.py from ultralytics import YOLOWorld model YOLOWorld(path/to/yolo_world_weights.pt) model.set_classes([person, safety helmet, forklift]) # 导出格式按部署端选择opset 和动态轴参数要跟推理引擎匹配 model.export(formatonnx, opset12, simplifyTrue)format决定导出目标opset影响算子兼容性simplify会尝试简化计算图但可能改变节点名。导出后不要只看文件生成成功要拿同一张图对比导出前后的框数量和分数排序如果导出后类别名全变成class_0说明类别映射没有随模型一起保存需要在部署端自己维护顺序。重参数化模型适合固定类别产线如果业务经常加类别保留开放词汇权重和导出权重两套别把探索路径一刀切掉。4. 用自己的数据验证YOLO 格式转换、微调配置和评估口径4.1 数据准备YOLO 格式目录与 data.yaml开放词汇模型可以先 zero-shot 跑一遍但要上业务最终还是要用自己的数据验证。数据格式优先跟资料里的训练脚本对齐如果文档没有特别说明常见做法是 YOLO 格式images/train、images/val、labels/train、labels/val标签文件里每行是class_id x_center y_center width height坐标归一化到 0 到 1。类别 ID 从 0 开始连续编号data.yaml里的names顺序必须和标签文件里的 ID 一致。这个顺序错位是最隐蔽的坑之一训练 loss 看着正常评估时类别全对不上。# data.yaml path: /data/my_detection_dataset train: images/train val: images/val names: 0: person 1: safety helmet 2: forkliftpath是数据集根目录train和val是相对路径如果你的训练入口不支持相对路径就改成绝对路径。names的键必须是整数值用英文短词和推理时的类别列表保持一致。标签文件里不要出现空行以外的多余字符Windows 换行和 BOM 都可能让解析器读错第一行。转换脚本写完先抽查十张图的可视化确认框的位置和类别 ID 都对再开始训练。4.2 微调时冻结哪些层文本编码器、backbone、检测头微调策略取决于数据量。数据量很小的时候我一般冻结文本编码器和大部分 backbone只让融合层和检测头适应新域数据量足够且类别差异大时再逐步解冻。冻结太少容易过拟合冻结太多又学不动新类别。资料里的训练配置通常会暴露freeze参数常见做法是冻结前若干层具体层数要看模型结构不要照搬别人的数字。文本编码器是否冻结也要看目标如果只是适应图像域文本侧可以冻如果要让类别名更贴合业务语义才考虑解冻文本侧但学习率要压低。# Ultralytics 风格入口的示意模型和路径按资料替换 yolo detect train \ modelpath/to/yolo_world_weights.pt \ data/data/my_detection_dataset/data.yaml \ imgsz640 \ epochs50 \ batch8 \ freeze10 \ lr00.001 \ device0freeze10表示冻结前 10 层不是所有实现都按层号计数有的按模块名匹配跑之前看日志里实际冻结了哪些参数。lr0是初始学习率微调通常比从头训练小一个量级batch受显存限制先小后大别为了追求大 batch 把imgsz偷偷降了却不记录。epochs不要一上来拉满先跑短周期看验证集是否过拟合。训练日志里重点看分类 loss 和回归 loss 是否同步下降如果分类 loss 降但验证 mAP 不升可能是类别名和标签语义不匹配或者数据里同一类有多种叫法。4.3 训练命令与日志看什么训练启动后不要只盯总 loss。开放词汇微调里文本侧和视觉侧的适应速度不一样总 loss 下降可能只是背景更容易分了。我一般同时看验证集的mAP50、mAP50-95、每类 precision 和 recall再看混淆矩阵里有没有固定类别互相吞。如果某一类 recall 一直上不去先检查标注框是否漏标再检查类别名是否太泛。比如vest可能把普通马甲也算进去改成high visibility vest后误检下降但召回也可能降这时候要回到数据里看真实分布而不是只调阈值。# 验证入口示意按资料里的评估脚本替换 yolo detect val \ modelruns/detect/train/weights/best.pt \ data/data/my_detection_dataset/data.yaml \ imgsz640 \ conf0.001 \ iou0.6 \ device0评估时conf设低一点让 mAP 计算拿到完整 PR 曲线iou设成和部署接近的值但不要为了好看把评估阈值和部署阈值混为一谈。best.pt是按验证集指标选出来的不代表业务场景最优最终还要拿独立测试集跑一遍。日志里如果出现某类instances为 0说明验证集没有该类样本mAP 里这一类的值没有参考意义别拿它下结论。4.4 评估不要只看 demo 图mAP、误检、漏检demo 图只能证明链路通不能证明模型可用。我一般把评估拆成三层整体 mAP 看趋势每类 P/R 看短板业务抽样看误检和漏检的具体形态。整体 mAP 高但某一类 recall 低说明这类需要补数据或改 prompt整体 mAP 一般但误检集中在背景纹理说明负样本不够。开放词汇模型的误检往往不是“框歪了”而是“语义近邻被拉进来了”比如把helmet检成hat这时候改类别名比加数据更快见效。指标看什么常见异常处理方向mAP50宽松匹配下的整体检出高但业务误检多看每类 precisionmAP50-95框质量低但分类对检查标注框和回归 loss每类 recall漏检某类持续低补样本或改类别措辞每类 precision误检背景纹理被误检加负样本或收紧类别名评估结果要按类别组记录不要只存一个总分。后面做 prompt 对比时同一套验证集、同一个conf口径才有可比性。数据量少的时候训练集和验证集要按场景分不要随机切图导致同一场景的相似图同时出现在两边那样指标会虚高。5. 避坑与排查开放词汇检测最容易翻车的五件事5.1 类别名写错导致“什么都检测不到”现象是模型跑完不报错但输出框极少甚至为零换一张图也一样。原因通常不是模型坏了而是类别字符串和文本编码器预期不匹配用了中文但文本侧英文为主、用了下划线拼接的复合词、类别名过长被截断或者类别名和资料示例的格式差异太大。解决时先把类别退回到三到五个英文短词用资料文档里的示例类别跑同一张图如果示例能出框、你的类别不出框问题就在 prompt。再把类别逐个替换一次只换一个找到触发问题的那个词。最后检查 tokenizer 的最大长度和是否截断长短语不要硬塞。5.2 缓存文本嵌入后改类别不生效现象是set_classes明明改了列表输出类别名还是旧的那几个或者分数完全不变。原因是推理封装把文本嵌入缓存在模型对象或全局变量里换类别没有触发重新编码也可能是你加载的是重参数化权重类别已经固化。解决时先确认权重类型如果是开放词汇权重就在每次换类别后新建模型对象或调用文档里的刷新接口如果封装没有刷新接口就把类别设置放在进程启动时不要在一个进程里反复换。验证方法是拿两个语义差异很大的类别各跑一次输出完全一致就说明缓存或固化权重在作怪。5.3 导出 ONNX 后动态类别失效现象是 PyTorch 下能正常set_classes导出 ONNX 后只能输出固定类别或者类别索引全变成数字。原因是重参数化导出把文本嵌入固化成了分类头权重动态文本输入被裁掉有些导出配置还会把类别名映射留在 Python 侧不写进模型。解决时先明确部署需求要动态类别就保留开放词汇模型和文本编码器不要走固化导出要固定类别就在导出前把类别列表定死并在部署端按同样顺序维护映射表。导出后拿同一张图对比框数量和分数排序别只看文件能不能加载。5.4 数据标签 ID 与类别顺序错位现象是训练 loss 正常评估时某一类指标异常低混淆矩阵里两类互相吞。原因是data.yaml的names顺序和标签文件里的class_id不一致或者转换脚本按字母序重排了类别而标签没有同步。解决时先抽十张图做可视化把框上的类别名和原图对照再用脚本统计每个 ID 的样本数和names逐项核对。转换脚本里不要用集合去重后直接排序集合顺序不稳定必须显式维护class_to_id映射。评估前再跑一次标签合法性检查确认没有越界 ID 和归一化坐标超出 0 到 1。5.5 显存和 batch size 的假象现象是训练一开始正常跑几十步后显存暴涨或直接 OOM或者把batch调大后 loss 看起来更稳但验证指标下降。原因是开放词汇模型里文本编码器和融合模块的中间激活占用比普通检测器高batch变大后每步样本数增加等效学习率也变了却没有同步调lr0。解决时先固定imgsz和模型配置只调batch记录显存峰值和每轮耗时batch翻倍时学习率不要机械翻倍先小步试。OOM 不一定是模型太大也可能是评估阶段没关梯度或缓存了过多中间结果训练和验证分开跑能快速定位。6. 进阶验证用“类别扰动实验”判断模型边界类别能检出不等于类别稳定。我习惯在固定图片集上做一轮类别扰动同一批图分别用原始类别、同义词、上下位词、加模板短语跑一遍记录每组的检出数、误检数和分数分布。这个实验不追求指标最高而是看模型对措辞变化有多敏感。敏感度过高说明业务侧必须锁死 prompt敏感度低才适合把类别配置交给现场人员维护。# prompt_perturbation.py groups { base: [person, safety helmet, forklift], synonym: [worker, hard hat, forklift truck], hypernym: [person, helmet, vehicle], template: [a photo of a person, a photo of a safety helmet, a photo of a forklift], } for name, classes in groups.items(): model.set_classes(classes) # 每次换组都重设避免缓存干扰 results model.predict(path/to/fixed_test_dir, imgsz640, conf0.25, iou0.5, device0) # 这里只记录数量和平均分详细框信息落 CSV 方便复查 count sum(len(r.boxes) for r in results) avg_conf sum(float(b.conf.item()) for r in results for b in r.boxes) / max(count, 1) print(name, count, round(avg_conf, 3))这段脚本的关键是固定图片目录、固定conf和iou只变类别组。set_classes放在每组循环开头防止缓存count和avg_conf只是粗指标真正排查还要把每张图的框落表。跑完你会看到某个同义词组检出数暴涨但平均分下降那通常是语义变宽导致的误检模板组分数整体偏低说明模板偏离了资料里的示例分布。把这几组结果和业务抽样对照就能判断该锁 prompt、补数据还是换权重。从那以后我每次换类别配置都强制走一遍扰动实验不再凭单张 demo 图下结论。希望帮到你。本文还有配套的精品资源点击获取