Transformers 中的 GroupViT 模型指南:基于文本监督的分组视觉 Transformer 与零样本语义分割
GroupViT(Grouping Vision Transformer)是 🤗 Transformers 中一类核心用于零样本语义分割的视觉-语言模型,通过图像文本对比学习自动习得分组能力。本文以仓库 docs/source/en/model_doc/groupvit.md 文档为骨架,结合 configuration_groupvit.py 与 modeling_groupvit.py 源码展开,帮助读者掌握 GroupViT 的架构原理、三类配置对象、五类公开 API 的用法,并给出可直接复制的零样本语义分割推理示例。
GroupViT 模型概述
GroupViT 于论文 GroupViT: Semantic Segmentation Emerges from Text Supervision 中提出,作者为 Jiarui Xu、Shalini De Mello、Sifei Liu、Wonmin Byeon、Thomas Breuel、Jan Kautz、Xiaolong Wang。该模型由 NVIDIA 研究团队提出,由社区贡献者 xvjiarui 贡献到 Hugging Face Transformers,模型页于 2022-02-22 发布在 HF papers 上,2022-06-28 正式合入 Transformers。
受 CLIP 启发,GroupViT 属于视觉-语言(vision-language)模型,能在任意给定类别词汇上执行零样本语义分割,即无需针对分割任务微调、也不需要任何像素级标注。
论文核心思想
论文摘要指出:分组(grouping)与识别(recognition)是视觉场景理解的重要组成部分,例如目标检测和语义分割。在端到端深度学习中,图像区域的分组通常通过像素级识别标签的自上而下监督隐式发生。而 GroupViT 的做法是将分组机制重新引入深度网络,使得语义片段仅通过文本监督即可自动涌现。
具体而言,作者提出层级化的分组视觉 Transformer(hierarchical Grouping Vision Transformer),它超越了常规的规则网格(regular grid)表征,学习将图像区域逐步分组为越来越大的任意形状片段。模型在大型图像-文本数据集上使用对比损失与文本编码器联合训练,在没有任何像素级标注的条件下,学会了将语义区域分组到一起,并以零样本方式成功迁移到语义分割任务。论文报告的数据为:在 PASCAL VOC 2012 上取得 52.3% mIoU、在 PASCAL Context 上取得 22.4% mIoU,性能可与需要更高监督程度的 SOTA 迁移学习方法相当。注意上述分割精度是论文原文的实验数据,仅在论文语境下成立,具体复现请以实际权重与评测脚本为准。
架构组件与源码位置
GroupViT 在仓库中的代码分为三个文件:
- 配置:
src/transformers/models/groupvit/configuration_groupvit.py - 建模:
src/transformers/models/groupvit/modeling_groupvit.py - 权重转换脚本:
src/transformers/models/groupvit/convert_groupvit_nvlab_to_hf.py(用于将 NVlabs 官方仓库的权重转换为 HF 格式) - 测试:
tests/models/groupvit/test_modeling_groupvit.py
从 modeling_groupvit.py 顶部可以清晰看到 GroupViT 的三个核心思想组件:
1. 文本侧(GroupViTTextTransformer):结构与 CLIP 文本编码器基本一致,代码注释明确标注 # Copied from transformers.models.clip.modeling_clip.CLIPTextEmbeddings with CLIP->GroupViT,词表大小默认 49408(对应 CLIP BPE 词表),max_position_embeddings 默认 77。文本侧以 EOS token 位置处的 hidden state 作为池化输出(见 GroupViTTextTransformer.forward 中基于 eos_token_id 的 argmax 取特征逻辑)。
2. 视觉侧(GroupViTVisionTransformer):这是与普通 ViT 最大的不同点。它在 GroupViTVisionEncoder 内按 depths 划分成多个 GroupViTStage(对应论文中的 Grouping Layer),每个 stage 内部由若干 GroupViTEncoderLayer(Transformer 层)+ 一个 GroupViTTokenAssign 分组下采样模块构成。
3. 投影与对比损失(GroupViTModel):视觉与文本侧各自经过 visual_projection / text_projection 两层 MLP(中间含 BatchNorm1d 与 ReLU),得到共同嵌入空间的特征,随后做归一化与余弦相似度计算;return_loss=True 时通过 image_text_contrastive_loss 计算对称的对比损失(该函数 Copied from transformers.models.clip.modeling_clip)。
关键的分组机制实现
GroupViTGroupRepresentation:负责将视觉特征分组为语义段。GroupViTAssignAttention(modeling_groupvit.py L160 附近):分组注意力。其get_attn在训练阶段使用gumbel_softmax(..., dim=-2, hard=hard)(gumbel-softmax 松弛 + 直通估计),推理阶段退化为hard_softmax或普通softmax;同时用assign_eps做数值稳定(attn / (attn.sum(...) + assign_eps))。GroupViTTokenAssign:stage 末尾的"下采样"模块,将num_group_tokens个 group token 通过project_group_token(Mixer MLP)投影为num_output_groups个,再以 assign attention 将图像 token 聚合到这些组上。hard_softmax/gumbel_softmax:硬分配 + 直通梯度(straight-through),保证"每个图像区域只能属于一个语义组"的离散分组可反向传播。get_grouping_from_attentions:将各 stage 的注意力图逐级矩阵相乘,得到从最高分辨率 patch 到最终语义组的分组图(grouping map),其形状为[batch_size, num_groups, height, width],这是零样本分割 logits 的关键。
与 CLIP 的关键差异
GroupViT 视觉编码器不采用 [CLS] token,测试代码中有明确注释 # no [CLS] token for GroupViT(见 test_modeling_groupvit.py)。视觉特征经过最后一级 stage 后,把剩余 token 的均值作为池化输出(pooled_output = last_hidden_state.mean(dim=1))。正因为保留了"分组后 token 与原始 patch 的对应关系",模型才能输出空间上的分组图,从而支撑分割任务。
配置类详解
GroupViT 使用组合式配置结构:GroupViTConfig 是顶层配置,内部以 sub_configs = {"text_config": GroupViTTextConfig, "vision_config": GroupViTVisionConfig} 组合两个子配置。三者都继承自 PreTrainedConfig,可序列化为 JSON 并随权重保存。
GroupViTTextConfig(文本子配置)
对应默认检查点为 nvidia/groupvit-gcc-yfcc。其主要参数及源码默认值(configuration_groupvit.py):
| 参数 | 默认值 | 说明 |
|---|---|---|
vocab_size |
49408 | 词表大小,与 CLIP BPE 词表一致 |
hidden_size |
256 | 文本 Transformer 隐层维度 |
intermediate_size |
1024 | FFN 中间层维度 |
num_hidden_layers |
12 | Transformer 层数 |
num_attention_heads |
4 | 注意力头数 |
max_position_embeddings |
77 | 最大序列长度(CLIP 风格,与 padding 到 77 的 tokenizer 配套) |
hidden_act |
"quick_gelu" |
激活函数,文本侧用 quick GELU |
layer_norm_eps |
1e-5 | LayerNorm epsilon |
dropout / attention_dropout |
0.0 | dropout 概率 |
initializer_range / initializer_factor |
0.02 / 1.0 | 权重初始化参数 |
pad_token_id |
1 | padding token id |
bos_token_id |
49406 | BOS token id |
eos_token_id |
49407 | EOS token id(池化取特征的位置) |
model_type = "groupvit_text_model",且设置了 base_config_key = "text_config"。
GroupViTVisionConfig(视觉子配置)
视觉侧的关键在于描述分组层级结构的四个参数(源码默认值):
| 参数 | 默认值 | 说明 |
|---|---|---|
depths |
[6, 3, 3] |
每个 stage 的编码层数 |
num_group_tokens |
[64, 8, 0] |
每个 stage 的 group token 数量 |
num_output_groups |
[64, 8, 8] |
每个 stage 的输出组数量,0 表示无分组 |
assign_eps |
1.0 | assign attention 中归一化使用的 epsilon |
assign_mlp_ratio |
[0.5, 4] |
用于推断分组 MLP 隐层维度的比例 |
视觉侧其余参数:
| 参数 | 默认值 | 说明 |
|---|---|---|
hidden_size |
384 | 视觉 Transformer 隐层维度 |
intermediate_size |
1536 | FFN 中间层维度 |
num_hidden_layers |
12 | 总层数,必须等于 sum(depths),配置类在 validate_architecture 中校验并在不一致时告警 |
num_attention_heads |
6 | 注意力头数 |
image_size |
224 | 输入图像尺寸 |
patch_size |
16 | patch 尺寸(每 patch 16×16,224 图像得到 14×14=196 个 patch) |
num_channels |
3 | 输入通道数 |
hidden_act |
"gelu" |
视觉侧激活函数 |
layer_norm_eps、dropout 等 |
与文本侧类似 | 常规超参 |
model_type = "groupvit_vision_model",base_config_key = "vision_config"。
理解 depths=[6,3,3]、num_group_tokens=[64,8,0]、num_output_groups=[64,8,8] 的含义,只需对照 GroupViTVisionEncoder 的构建循环:第 1 个 stage(depth 6)持 64 个 group token 并从 196 个 patch 聚合出 64 个输出组;第 2 个 stage(depth 3)接收上一 stage 的 64 组 token,投影为 8 个 group token 并输出 8 个更粗的语义组;第 3 个 stage(depth 3)无 group token(num_output_groups=8 时 downsample 分支依条件关闭)。token 数量沿层级逐级减少而语义粒度逐级变粗,最终 8 个输出组对应图像中若干主要语义区域。
GroupViTConfig(顶层组合配置)
| 参数 | 默认值 | 说明 |
|---|---|---|
projection_dim |
256 | 文本/图像投影后的共同嵌入维度 |
projection_intermediate_dim |
4096 | 投影层(两层 MLP)的中间层维度 |
logit_scale_init_value |
2.6592 | 可学习 logit 缩放参数的初始值(对应 self.logit_scale.exp() 的初始化) |
output_segmentation |
False | 前向时是否返回分割 logits |
text_config / vision_config |
None | 两个子配置;为 None 时自动初始化为默认子配置 |
model_type = "groupvit"。在 __post_init__ 中,顶层配置会处理三种传入方式并兼容旧版 text_config_dict / vision_config_dict 关键字(存在键冲突时以 dict 中的值为准并记录日志),最终统一构造出 GroupViTTextConfig 与 GroupViTVisionConfig 实例。
模型类与前向用法
GroupViT 共暴露 5 个模型类(顶层入口在 src/transformers/models/groupvit/__init__.py):
GroupViTModel:完整双塔模型,用于对比学习、图像文本检索与零样本分割;GroupViTTextModel:仅文本塔;GroupViTVisionModel:仅视觉塔。
三者均继承 GroupViTPreTrainedModel(基于 PreTrainedModel),支持 from_pretrained、save_pretrained、梯度检查点(supports_gradient_checkpointing = True)等标准能力。
GroupViTModel
这是最常用的入口,核心方法:
forward:接收input_ids、pixel_values、attention_mask、position_ids,可选return_loss、output_attentions、output_hidden_states、output_segmentation。返回GroupViTModelOutput,其中:loss:return_loss=True时的对比损失;logits_per_image/logits_per_text:图像与文本之间的相似度分数(由 L2 归一化嵌入做矩阵乘再乘以logit_scale.exp()得到);image_embeds/text_embeds:投影并归一化后的嵌入;segmentation_logits:output_segmentation=True时的逐像素分类分数;text_model_output/vision_model_output:两个子塔的完整输出。
get_text_features:只提取文本侧投影后的特征(应用于下游检索或最近邻分类);get_image_features:只提取图像侧投影后的特征。
最常用的零样本图像分类示例(与 CLIP 完全一致的范式,复制自 modeling_groupvit.py docstring):
from transformers import AutoProcessor, GroupViTModel
from transformers.image_utils import load_image
model = GroupViTModel.from_pretrained("nvidia/groupvit-gcc-yfcc")
processor = AutoProcessor.from_pretrained("nvidia/groupvit-gcc-yfcc")
image = load_image("http://images.cocodataset.org/val2017/000000039769.jpg")
inputs = processor(
text=["a photo of a cat", "a photo of a dog"], images=image, return_tensors="pt", padding=True
)
outputs = model(**inputs)
logits_per_image = outputs.logits_per_image # image-text similarity
probs = logits_per_image.softmax(dim=1) # label probabilities
输出分割 logits:output_segmentation=True
这是文档"Usage tips"中特别强调的用法:在 GroupViTModel 的 forward 中传入 output_segmentation=True,即可得到输入文本类别对应的分割 logits。源码中该选项会自动打开 output_attentions(if output_segmentation: output_attentions = True),随后经过一条完整流水线(modeling_groupvit.py L1285 附近):
- 从
vision_outputs.last_hidden_state取出分组后的组级特征image_group_embeds(形状[B, num_group, hidden])并投影; - 用
get_grouping_from_attentions把各 stage 的注意力图链式相乘并上采样,得到与原始图像分辨率一致的分组图([B, num_group, H, W]); - 组级特征与每条文本嵌入求相似度得
logits_per_image_group,再与分组图做矩阵乘并 reshape 为[B, text_batch, H, W],即segmentation_logits。
需要注意:GroupViTModelOutput 的 docstring 给出重要警告——分割 logits 的尺寸不一定与传入的 pixel_values 相同(避免在特征图上做两次插值损失质量),读者应在后处理时检查 logits 形状并按需上采样到原图尺寸。分组图的实际空间尺寸由输入图像与 patch 数共同决定。
GroupViTTextModel
仅文本编码器,返回 BaseModelOutputWithPooling(last_hidden_state + pooler_output)。其分词器使用 CLIPTokenizer(该模型无自有 tokenizer,沿用 CLIP 的 BPE tokenizer,参见源码 docstring):
from transformers import CLIPTokenizer, GroupViTTextModel
tokenizer = CLIPTokenizer.from_pretrained("nvidia/groupvit-gcc-yfcc")
model = GroupViTTextModel.from_pretrained("nvidia/groupvit-gcc-yfcc")
inputs = tokenizer(["a photo of a cat", "a photo of a dog"], padding=True, return_tensors="pt")
outputs = model(**inputs)
last_hidden_state = outputs.last_hidden_state
pooled_output = outputs.pooler_output # pooled (EOS token) states
GroupViTVisionModel
仅视觉编码器,同样返回 BaseModelOutputWithPooling(视觉侧 pooled output 为分组后 token 的均值)。预处理使用 AutoProcessor(对应 CLIPProcessor,图像侧按 CLIP 归一化统计量处理):
from PIL import Image
from io import BytesIO
import httpx
from transformers import AutoProcessor, GroupViTVisionModel
processor = AutoProcessor.from_pretrained("nvidia/groupvit-gcc-yfcc")
model = GroupViTVisionModel.from_pretrained("nvidia/groupvit-gcc-yfcc")
url = "http://images.cocodataset.org/val2017/000000039769.jpg"
with httpx.stream("GET", url) as response:
image = Image.open(BytesIO(response.read()))
inputs = processor(images=image, return_tensors="pt")
outputs = model(**inputs)
last_hidden_state = outputs.last_hidden_state
pooled_output = outputs.pooler_output
零样本语义分割的完整推理
将以上能力组合起来,即得到与文档导向一致的核心用法——用任意类别词汇做分割:
from transformers import AutoProcessor, GroupViTModel
import torch
model = GroupViTModel.from_pretrained("nvidia/groupvit-gcc-yfcc")
processor = AutoProcessor.from_pretrained("nvidia/groupvit-gcc-yfcc")
image = load_image("path/to/image.jpg") # 任意输入图像
texts = ["cat", "dog", "background"] # 任意给定的类别词汇
inputs = processor(text=texts, images=image, return_tensors="pt", padding=True)
with torch.inference_mode():
outputs = model(**inputs, output_segmentation=True)
seg_logits = outputs.segmentation_logits # [1, num_texts, H, W]
seg_pred = seg_logits.argmax(dim=1) # [1, H, W] 逐像素类别
这里 seg_logits 的第 0 维对应当前 batch 内单张图像,第 1 维与文本类别一一对应;argmax(dim=1) 即得到逐像素的类别图。类别词汇完全由使用者在推理时给出,体现了"零样本"的特性——模型训练阶段从未见过这些类别名对应的像素标注。
预训练检查点与权重转换
源码与文档中使用的官方检查点为 nvidia/groupvit-gcc-yfcc(在 GCC+YFCC 大规模图文数据集上训练)。从转换脚本 convert_groupvit_nvlab_to_hf.py 可见其支持两种权重名称:groupvit-gcc-yfcc 与 groupvit-gcc-redcaps。该脚本供权重复现使用:将 NVlabs 官方格式的 state_dict 经 rename_key / convert_state_dict 映射为 HF 命名后,调用 convert_groupvit_checkpoint 导出(可选 push_to_hub 上传 Hub),普通用户直接 from_pretrained 即可。
测试文件 test_modeling_groupvit.py 覆盖了视觉塔、文本塔与整模型的前向、注意力、池化、配置一致性等标准检查项,可作为理解各子模块输入输出形状与 API 契约的参考。
零样本迁移的关键机制总结
最后将"从文本监督到像素级分割"的实现链条归纳如下(全部可在 modeling_groupvit.py 中逐行对应):
- 图像经 patch embedding(Conv2d,kernel=stride=patch_size)切分为 patch 序列,加上可学习位置编码;不使用 [CLS] token;
- 每个
GroupViTStage由若干 Transformer 层 +GroupViTTokenAssign组成。Assign 模块先做 cross attention(GroupViTCrossAttentionLayer)让 group token 读取图像全局信息,再用GroupViTAssignAttention依据 group token 对图像 patch 做硬分配(训练用 gumbel-softmax、推理用 hard-softmax),把图像 token 聚合为更少、更粗的分组; - 多级分组使 token 从 196 → 64 → 8 逐级变少,语义从 patch 级聚合到片段级,这是"分组机制"在架构层面的落地;
- 文本侧编码类别描述,两塔通过投影层进入同一 256 维嵌入空间,训练时用对称对比损失约束图像整体与文本语义对齐(
image_text_contrastive_loss,实现与 CLIP 相同); - 推理零样本分割时,把各级注意力图相乘得到每个原始 patch 到 8 个语义组的软分组(grouping map),将每个组的图像嵌入与文本嵌入的相似度分数"分配回"各像素,即得逐类别分割 logits。
正因上述设计与任意文本类别的对齐,GroupViT 才能做到不需要任何像素级标注、也不需要下游微调,直接在推理阶段对任意类别词汇输出语义分割结果。
atomcodeClaude Code 的开源替代方案。连接任意大模型,编辑代码,运行命令,自动验证 — 全自动执行。用 Rust 构建,极致性能。 | An open-source alternative to Claude Code. Connect any LLM, edit code, run commands, and verify changes — autonomously. Built in Rust for speed. Get StartedRust0629
MiniCPM5-2BMiniCPM5-2B 是一款面向端侧、本地部署和资源受限场景的 2B 稠密 Transformer,能够达到同尺寸开源模型 SOTA 水平。Markdown00
GLM-5.3GLM-5.3 与 GLM-5.2 使用相同的基座模型——所有提升均来自后训练。与 GLM-5.2 相比,它在复杂编程和长程任务上的表现显著提升。Jinja00
HivisionIDPhotos⚡️HivisionIDPhotos: a lightweight and efficient AI ID photos tools. 一个轻量级的AI证件照制作算法。Python07
DragonOSDragonOS is an operating system developed from scratch using Rust, with Linux compatibility. It is designed for **Serverless** scenarios. 使用Rust从0自研内核,具有Linux兼容性的操作系统,面向云计算Serverless场景而设计。Rust00
Spark-X2.5-1.7BSpark-X2.5-1.7B 旨在让强大的 AI 更加实用、高效且易于获取。这些模型在广泛的日常任务中表现出色,涵盖对话、写作、翻译、推理、编程、工具调用和智能体工作流,并在同等规模的开源模型中取得领先结果。Spark-X2.5 将面向效率的架构与最高 1M tokens 的原生上下文窗口相结合,并支持 200 多种语言。Python00