3.25-3.31学习周报
提示:文章写完后,目录可以自动生成,如何生成可参考右边的帮助文档
文章目录
- 摘要
- Abstract
- 一、方法介绍
- 1.DPC(双模式协作框架)
- 2.动态硬负优化器
- 二、实验
- 1.实验概况
- 2.实验代码
- 3.实验结果
- 总结
文章链接
摘要
本博客介绍了论文《DPC: Dual-Prompt Collaboration for Tuning Vision-Language Models》提出了用于调整视觉语言模型的双提示协作(DPC)框架,以解决基于CLIP的提示调优中普遍存在的基类 - 新类权衡(BNT)问题。该框架通过在提示级别解耦基类和新类任务的优化过程,避免了优化方向的冲突。具体而言,基于骨干提示克隆可学习的并行提示,并引入可变的加权解耦框架独立控制双提示的优化方向。同时,设计了动态硬负样本优化器,利用双提示构建更具挑战性的优化任务,增强基类性能。实验表明,DPC能显著提升基类性能,同时保持对新类的泛化能力,且无需额外的外部知识。
Abstract
This blog introduces the paper “DPC: Dual-Prompt Collaboration for Tuning Vision-Language Models,” which proposes a Dual-Prompt Collaboration (DPC) framework for tuning vision-language models to address the prevalent Base-New Trade-off (BNT) issue in CLIP-based prompt tuning. The framework avoids conflicts in optimization directions by decoupling the optimization processes of base-class and new-class tasks at the prompt level. Specifically, it clones learnable parallel prompts based on backbone prompts and introduces a variable weighted decoupling framework to independently control the optimization directions of the dual prompts. Additionally, a dynamic hard negative samples optimizer is designed to construct more challenging optimization tasks using dual prompts, thereby enhancing base-class performance. Experiments demonstrate that DPC significantly improves base-class performance while maintaining generalization to new classes, without requiring additional external knowledge.
一、方法介绍
1.DPC(双模式协作框架)
冻结视觉和文本编码器,它采用可学习的轻量级提示向量作为查询,以引导CLIP的输出朝向目标任务。但是即时调优的优化过程经常遇到一个新的基础权衡问题。随着提示与目标任务越来越一致,模型可能会过拟合到基础类,导致新类的泛化性能降低。
针对上述问题研究者提出了双提示协作(DPC)框架.这种方法是第一个引入双提示优化在两个不同的方向,克服BNT问题的解耦基地和新的任务在提示级别。由于提示调优中的优化基本上针对可学习的提示.
DPC的框架如下图所示:
与现有的即时调整研究相同,DPC利用CLIP作为预训练的VLM骨干模型,用于图像文本特征提取和模态交互。CLIP采用“A photo of a [CLASS]”作为文本模态的提示模板,并导入基于ViT的视觉编码器f(·)和文本编码器g(·),分别将图像V和文本T转换为补丁嵌入和单词嵌入。在零触发推理期间,对于候选对象集合C = {Ti}n i=1,图像特征f(V)和文本特征g(T)之间的匹配概率由下式给出:
其中sim(·,·)表示余弦相似性和τ作为温度系数。
为了将基础CLIP模型转移到特定的下游任务,提示学习器冻结编码器f(·)和g(·),并将一组长度为M的可学习向量附加到文本或视觉输入,通常组织为:
在大多数现有的研究中,文本提示Pt取代CLIP的原始提示模板作为文本模态的输入。可选的视觉提示Pv通常作为前缀连接到视觉模态,与图像块标记连接为(Pv,V)。在调整过程中,通常应用交叉熵损失来不断优化提示向量:
其中hi是候选对象集C的独热标签。
在DPC的初始化过程中,采用两步调优的方法,首先对提示调优主干模型中的原始提示向量进行适度的微调,完全遵循基线设置,得到调优后的提示向量P;然后冻结调优后的提示向量,并在此基础上建立一组可学习的并行提示向量P′,其形式、大小和参数均克隆自主干模型。
如果微调的时期或时间长度受到严格限制,则骨干模型上的调优时期可以减半,后半部分由基于DPC优化器的微调取代。
2.动态硬负优化器
该模块独立于原有的主干优化过程,通过在基类上构造更具挑战性的优化任务,不断微调并行提示符P′,有效提升基类性能。它由三个子模块构成:负采样器、特征滤波和硬负优化.
负采样器:负采样器取代了主干模型使用的随机采样策略,鼓励模型利用坚韧准确分类的硬负样本构建小批次。通过加强样本匹配的难度,可以促进并行提示以适应具有调优的基类。
特征滤波:为了保持主干的完整性能,为了获得文本编码器从硬否定对象集合C′中生成的文本特征g(C′)∈ RL×d,DPC首先在微调过程中对文本模态C进行L2归一化,该文本模态C是由基类中所有候选n的并行提示P′构造的。其目的是保持提示学习器对基类的全局特征分布不变,防止与调整后的提示P协作时参数发生变化。
然后,引入一个选择矩阵Q ∈ RL×n来提取与硬否定词相关的文本特征。
Q可以表示如下。eij是C′中的标准基向量,其中对应于第j个硬否定对象的标签索引位置ij为1,其余为0。
通过特征过滤,DPC重组输入到硬否定优化过程的图像和文本特征,以进行后续优化。
硬负优化:为了在基类上实现更鲁棒的跨模态对齐,研究者将传统提示学习器的交叉熵损失升级为针对硬否定的更强的图像-文本对比损失。对于由长度为L的硬负图像-文本对组成的小批量(V′,C′),采用InfoNCE损失函数来创建对称图像-文本对比学习任务。
加权解耦模块(WDM):集成了调优和推理过程,允许调优提示P和并行提示P′灵活地解耦和协作。在调谐和推理阶段,WDM统一地作用于双提示的输入。如下图所示:
在模型初始化期间,引入加权子模块F(),其通过控制为基类推断构造的特定于基类的加权系数ωB,将调谐提示和并行提示组合成混合提示Pe B。
随后,混合提示被传递到解耦子模块,这是加权模块的逆变换。如图4(B)所示,在此过程中,混合提示被F-1()分解回并行提示P′和原始调谐提示P。前者在调整过程中引入到DPC优化器中,实现综合梯度传播。相比之下,在新类推断期间,通过新类特定加权系数ωn重新分配两者以获得ePn:
上述设计保证了模型的完整性,同时允许独立的加权系数应用于基类和新类推理,灵活地平衡了由并行提示P′优化的基类性能和经调整的提示P的新类泛化的潜在特征。
二、实验
1.实验概况
数据集:研究者使用了11个具有不同数据分布的与泛化相关的数据集,包括ImageNet ,Caltech 101 ,OxfordPets ,StanfordCars ,Flowers 102 ,Food 101,FGVCAircraft ,SUN 397 ,EuroSAT和UCF 101。对于跨域任务,我们选择ImageNet-V2 ,ImageNet-Sketch ,ImageNet-A 和ImageNet-R 。
基线:文本提示的CoOp ,采用集成视觉和文本提示的Maple ,以及使用单独视觉和文本提示的AptSRC 和AptKD 。此外,研究者比较了用于提示学习者的领先即插即用模块DePT ,以验证DPC的高级解耦策略的优越性。
2.实验代码
完整项目代码:https://github.com/JREion/DPC
以下展示DPC关键实现代码:
import torch
import torch.nn as nn
import torch.nn.functional as F
from transformers import CLIPModel, CLIPProcessor
from torch.utils.data import DataLoader
# DPC模型定义
class DPC(nn.Module):
def __init__(self, backbone_model, prompt_length, num_classes, K=5, tau=0.07):
"""
初始化DPC模型。
参数:
backbone_model: 预训练的CLIP模型
prompt_length: 提示向量的长度
num_classes: 类别数量
K: Top-K硬负样本的数量
tau: InfoNCE损失的温度系数
"""
super(DPC, self).__init__()
self.backbone = backbone_model
self.prompt_length = prompt_length
self.num_classes = num_classes
self.K = K
self.tau = tau
# 初始化双提示
hidden_size = backbone_model.text_model.config.hidden_size
self.tuned_prompt = nn.Parameter(torch.randn(prompt_length, hidden_size))
self.parallel_prompt = nn.Parameter(self.tuned_prompt.clone())
# 冻结tuned_prompt
self.tuned_prompt.requires_grad = False
def get_hard_negatives(self, images, labels):
"""
使用tuned_prompt采样硬负样本。
参数:
images: 输入图像张量
labels: 真实标签
返回:
hard_negatives: 硬负样本的索引列表
"""
with torch.no_grad():
# 使用tuned_prompt计算图像-文本相似度
image_features = self.backbone.get_image_features(images)
text_features = self.backbone.get_text_features(self.tuned_prompt.expand(self.num_classes, -1, -1))
logits = image_features @ text_features.T / self.tau
_, topk_indices = logits.topk(self.K, dim=1)
# 排除正类标签,获取硬负样本
hard_negatives = []
for i in range(len(labels)):
neg_indices = topk_indices[i][topk_indices[i] != labels[i]][:self.K-1]
hard_negatives.append(neg_indices)
return hard_negatives
def compute_contrastive_loss(self, images, texts, hard_negatives):
"""
计算InfoNCE对比损失。
参数:
images: 输入图像张量
texts: 输入文本张量
hard_negatives: 硬负样本索引
返回:
loss: 对称对比损失
"""
batch_size = images.size(0)
image_features = F.normalize(self.backbone.get_image_features(images), dim=-1)
text_features = F.normalize(self.backbone.get_text_features(texts), dim=-1)
# 计算logits
logits_img2txt = image_features @ text_features.T / self.tau
logits_txt2img = text_features @ image_features.T / self.tau
# 对称InfoNCE损失
labels = torch.arange(batch_size, device=images.device)
loss_img = F.cross_entropy(logits_img2txt, labels)
loss_txt = F.cross_entropy(logits_txt2img, labels)
return (loss_img + loss_txt) / 2
def forward(self, images, texts, labels=None, is_train=True, is_base=True, omega_b=0.2, omega_n=1e-6):
"""
前向传播。
参数:
images: 输入图像张量
texts: 输入文本张量
labels: 真实标签(训练时需要)
is_train: 是否为训练模式
is_base: 是否为基础类推理
omega_b: 基础类权重
omega_n: 新类权重
返回:
loss 或 logits: 训练时返回损失,推理时返回logits
"""
if is_train:
# 训练阶段:优化parallel_prompt
hard_negatives = self.get_hard_negatives(images, labels)
# 构造包含硬负样本的mini-batch
batch_texts = []
batch_images = []
for i, neg_indices in enumerate(hard_negatives):
batch_texts.append(texts[labels[i]]) # 正样本
batch_texts.extend([texts[idx] for idx in neg_indices]) # 硬负样本
batch_images.append(images[i].unsqueeze(0).repeat(len(neg_indices) + 1, 1, 1, 1))
batch_texts = torch.stack(batch_texts)
batch_images = torch.cat(batch_images)
# 计算对比损失
loss = self.compute_contrastive_loss(batch_images, batch_texts, hard_negatives)
return loss
else:
# 推理阶段
omega = omega_b if is_base else omega_n
mixed_prompt = omega * self.parallel_prompt + (1 - omega) * self.tuned_prompt
# 获取特征并计算logits
image_features = F.normalize(self.backbone.get_image_features(images), dim=-1)
text_features = F.normalize(self.backbone.get_text_features(mixed_prompt.expand(self.num_classes, -1, -1)), dim=-1)
logits = image_features @ text_features.T / self.tau
return logits
# 示例使用
def main():
# 加载预训练CLIP模型和处理器
backbone_model = CLIPModel.from_pretrained("openai/clip-vit-base-patch32")
processor = CLIPProcessor.from_pretrained("openai/clip-vit-base-patch32")
# 初始化DPC模型
num_classes = 1000 # 示例类别数
dpc_model = DPC(backbone_model, prompt_length=4, num_classes=num_classes)
# 假设数据加载器
# train_dataloader: 基础类训练数据
# base_dataloader: 基础类测试数据
# new_dataloader: 新类测试数据
# 优化器
optimizer = torch.optim.SGD([dpc_model.parallel_prompt], lr=0.002)
# 训练
num_epochs = 20
dpc_model.train()
for epoch in range(num_epochs):
for batch in train_dataloader:
images, texts, labels = batch
images = processor(images=images, return_tensors="pt")["pixel_values"].to(device)
texts = processor(text=texts, return_tensors="pt")["input_ids"].to(device)
labels = torch.tensor(labels).to(device)
loss = dpc_model(images, texts, labels, is_train=True)
optimizer.zero_grad()
loss.backward()
optimizer.step()
print(f"Epoch {epoch}, Loss: {loss.item()}")
# 推理
dpc_model.eval()
with torch.no_grad():
# 基础类推理
for batch in base_dataloader:
images, texts = batch
images = processor(images=images, return_tensors="pt")["pixel_values"].to(device)
texts = processor(text=texts, return_tensors="pt")["input_ids"].to(device)
logits = dpc_model(images, texts, is_train=False, is_base=True)
# 计算准确率...
# 新类推理
for batch in new_dataloader:
images, texts = batch
images = processor(images=images, return_tensors="pt")["pixel_values"].to(device)
texts = processor(text=texts, return_tensors="pt")["input_ids"].to(device)
logits = dpc_model(images, texts, is_train=False, is_base=False)
# 计算准确率...
if __name__ == "__main__":
device = torch.device("cuda" if torch.cuda.is_available() else "cpu")
main()
3.实验结果
DPC在所有4个主干上实现了上级HM性能,同时在多个数据集上超过了当前最先进的HMKD 。如下表所示,性能的提高主要源于对基类的优化。此外,对于新类上的任务,验证了DPC的解耦结构,以彻底保持原始骨干的泛化性能。
与从基础到新的任务类似,DPC优化了ImageNet上的性能,在Tab中的所有骨干中获得了增强。同时,对于涉及未知数据分布的跨数据集和跨域任务,通过原始调优提示P的覆盖来一致地保持泛化水平。
DPC中组件的消融研究,CoOp基线为基础到新任务。TS:两步调谐。动态硬负优化。WE & DE:加权解耦。
DPC中协作权重对(a)基本任务和(B)新任务的影响。
DPC的增强效果明显上级或等于DePT。,即在提示级的解耦更彻底,为即插即用模型提供了更广泛的优化空间。
总结
研究的创新性:一是提出具有灵活加权解耦结构的双提示协作(DPC)框架,首次在提示层面解耦以克服BNT问题;二是设计动态硬负优化器,利用双提示构建更具挑战性的视觉 - 文本对齐任务,提升基类性能至新的最优水平;三是模型具备即插即用和自包含特性,降低对外部知识的依赖,适应性和可迁移性强。
研究的不足之处:一方面,获取调优提示时继承原骨干设置,这些配置可能并非泛化的全局最优解,难以通过骨干自适应获得新类的最佳性能;另一方面,DPC需要可学习的文本提示和图像特征进行对比学习,对于基于纯视觉提示或特征提取层的研究,难以作为即插即用方法整体适配。