文章详情

短信预约-IT技能 免费直播动态提醒

请输入下面的图形验证码

提交验证

短信预约提醒成功

使用 PyTorch 从头开始构建 CLIP | 对比语言图像预训练

2024-11-29 18:11

关注

CLIP(对比学习-图像预训练)

传统的机器学习模型通常需要大量特定任务的标记数据集进行微调。例如,一个训练用来识别狗的模型可能在识别猫方面表现不佳,除非它专门针对猫的图片进行了微调。

CLIP的架构支持零样本学习,这意味着它可以执行它没有直接训练过的任务,通过利用其在图像和文本之间学到的广泛关联。例如,基于它们的文本描述,它可以对它在训练期间从未见过的图片进行分类。引用他们的论文:“我们在零样本的情况下匹配原始ResNet-50在ImageNet上的准确性,而不需要使用它训练时的128万个训练样本。”

CLIP有以下我们需要构建的组件:

文本编码器

由于我们的主要目标是使文本和视觉表示的嵌入对齐,我们将需要一个文本编码器模型来为图像的文本描述创建特征。本文不会涵盖如何从头开始构建文本编码器,而是直接使用变换器库来创建编码器,尽管这将涵盖CLIP实现的主要思想。为了简单起见,使用Distil Bert模型是一个不错的选择,因为它轻量级,性能几乎和标准BERT模型一样好,具有类似的基础架构。这是需要记住的一点,我们不是加载预训练版本。

class TextEncoder(nn.Module):
    def __init__(self, embed_dim, proj_dim):
        super().__init__()
        self.model = DistilBertModel(config=DistilBertConfig())
        self.layer_norm = nn.LayerNorm(proj_dim)

    def forward(self, input_ids, attention_mask):
        x = self.model(input_ids=input_ids, attention_mask=attention_mask).last_hidden_state
        return self.layer_norm(x)

TextEncoder()类将期望两个输入,input_ids和attention_mask,这两个都将通过分词器生成。

tokenizer = DistilBertTokenizer.from_pretrained('distilbert-base-uncased')
texts = ["This is a sample sentence.", "This is another example."]
inputs= tokenizer(texts, padding=True, truncation=True, return_tensors="pt").to(device) 

encoder = ImageEncoder(embed_dim=768, proj_dim=256)
inputs = encoder(inputs['input_ids'], inputs['mask'])

现在,TextEncoder的前向传递输出将是(Batch_Size, Token_Size + 1, Embed_Size),在标准BERT架构中,模型的目标是两个任务,输出一个额外的CLS_Token,附加到原始令牌前面,通常用于进一步微调分类任务,以及预测掩蔽令牌,使用掩蔽令牌前后的所有令牌的信息。

由于我们关心的是为我们的文本数据获取特征嵌入,我们将只取[CLS]令牌,并将其投影到一个共同的空间,与图像编码器的视觉嵌入具有相同的嵌入大小。

class TextEncoder(nn.Module):
    def __init__(self, embed_dim, proj_dim):
        super().__init__()
        self.model = DistilBertModel(config=DistilBertConfig())
        self.projection = nn.Linear(embed_dim, proj_dim)
        self.layer_norm = nn.LayerNorm(proj_dim)

    def forward(self, input_ids, attention_mask):
        x = self.model(input_ids=input_ids, attention_mask=attention_mask).last_hidden_state

        x = x[:, 0, :] # B, T[cls], E

        x = self.projection(x)

        return self.layer_norm(x)

层归一化是深度学习中非常常见的概念,这不是我第一次解释它,但让我们再次解释一下,我们有一个网络的输入,其中包含来自不同类别或特征的数据,因为在每个训练周期中批次会变化,数据的分布也会变化,在一批中分布可能在[0, 2)范围内,而在下一批中它可能有样本分布在[0, 100]范围内。在训练过程中数据分布的变化被称为协变量偏移。由于输入的剧烈变化,输出也会变化,损失也会变化,如果损失剧烈变化,那么在反向传播过程中权重将以更高的幅度更新,导致梯度不平滑。简而言之,归一化输入将限制其在整个训练批次中的分布,因此,损失不会有剧烈变化,将导致更平滑的梯度和更快的训练,帮助模型更多地关注学习特征。

图像编码器

CLIP有两个图像编码器选项,ResNet或视觉变换器。我们已经开发了各种视觉变换器,因此将使用标准实现。如果你想使用ResNet作为图像编码器,你可以简单地用视觉变换器模型替换它,你可以使用PyTorch自己的ResNet模型或timm。

class ImageEncoder(nn.Module):
    def __init__(self, base_model, embed_dim, proj_dim):
        super().__init__()

        self.model = base_model

        for param in self.model.parameters():
            param.requires_grad = True

        self.projection = nn.Linear(embed_dim, proj_dim)
        self.layer_norm = nn.LayerNorm(proj_dim)

    def forward(self, x):
        x = self.projection(self.model(x))
        return self.layer_norm(x)

上面的编码器类将图像张量传递给模型,然后将其投影到与文本编码器输出相同的共同嵌入空间,后面是一个归一化层。

自定义数据集

现在CLIP是一个(相当)密集的模型,所以如果你想从头开始训练它,你必须在一个小数据集上训练它。由于本文只涉及如何从头开始实现架构,我们将不会进一步详细说明如何创建数据集,但为了示例,这可能是你想要做的。

class CustomDataset(Dataset):
    def __init__(self, texts, image_paths):

        self.image_paths = image_paths
        self.texts = texts
        tokenizer = DistilBertTokenizer.from_pretrained('distilbert-base-uncased')
        self.inputs = tokenizer(texts, padding=True, truncation=True, return_tensors="pt") 
        self.transform = torchvision.transforms.ToTensor()

    def __len__(self):
        return len(self.texts)

    def __getitem__(self, idx):
        img_path = self.image_paths[idx]
        image = Image.open(img_path)
        image = self.transform(image)

        caption, mask = self.inputs[idx].items()

        return {
            "image": image,
            "input_ids": caption["input_ids"],
            "mask": mask["attention_mask"]
        }

自定义数据集类在调用Dataset类时创建分词器并分词所有文本,我们使用的是distillbert分词器,这也是我们的文本编码器模型。

把所有放在一起

class CLIPModel(nn.Module):
    def __init__(self):
        super().__init__()
        self.device = "cuda" if torch.cuda.is_available() else "cpu"
        ViT = VissionTransformer( 
            num_layers=8,
            img_size=224,
            emb_size=768,
            patch_size=16,
            num_head=6,
            num_class=768).to(self.device)
        self.image_encoder = ImageEncoder(base_model=ViT, embed_dim=768, proj_dim=256)
        self.text_encoder = TextEncoder(embed_dim=768, proj_dim=256)

ClipModel()类是我们把所有放在一起的地方,架构将包括来自图像和文本编码器的嵌入,然后用于计算对称损失。这是核心实现的NumPy样式伪代码。

在我们的实现中,我们将在CLIPModel类的前向函数中计算损失。第一步是获取图像和文本嵌入,然后进行交叉乘法以获得相似性矩阵或logits。回到我们的第二张图。

logits是通过取图像和文本嵌入的点积来创建的,由于这篇论文基于对比学习,我们的主要目标是将文本表示与视觉对齐。那么计算相似性矩阵有什么帮助呢?

答案是每个从图像编码器接收到的图像令牌(图5:I_1,I_2,.., I_n; 其中I是嵌入,n是批次大小)乘以文本编码器接收到的每个令牌。得到最终矩阵(B, Token, Embed)@(B, Embed, Token)→(B, Token, Token)。现在我们的任务是最大化每个对角线元素(I1T1, I2T2,…, InTn)的值。由于我们想要对齐我们的文本和视觉表示,相应的图像令牌应该与其相应的文本最高相关。这就是我们将如何为批次中的所有图像完成的,但让我们看看单个令牌。

这里,图并不是真的不同,我们取图像嵌入I,并与批次中的每个文本嵌入计算点积。例如,当我们使用I3时,我们希望它与批次中相应的文本嵌入T3最强地对齐。理想情况下,I3行中最高的值应该是点积I3⋅T3,以同样的方式批量处理,看起来就像我们在最大化所有对角线元素,其中每个In与其相应的Tn最佳对齐。为了实现这一点,我们使用一个损失函数来衡量每一行中最大值与其他值的突出程度。这实际上是通过取行和列的交叉熵损失来实现的。

from vit import VissionTransformer # Importing ViT from previous implementaton (GitHub: Ml-Models)
import numpy as np
import torch.nn.functional as F

class CLIPModel(nn.Module):
    def __init__(self):
        super().__init__()
        self.device = "cuda" if torch.cuda.is_available() else "cpu"
        ViT = VissionTransformer( 
            num_layers=8,
            img_size=224,
            emb_size=768,
            patch_size=16,
            num_head=6,
            num_class=False).to(self.device)
        self.image_encoder = ImageEncoder(base_model=ViT, embed_dim=768, proj_dim=256)
        self.text_encoder = TextEncoder(embed_dim=768, proj_dim=256)

        self.temperature = nn.Parameter(torch.ones([])*np.log(1/7)).to(self.device)

    def forward(self, x):
        I_t = self.image_encoder(x["image"])
        T_t = self.text_encoder(x["input_ids"], x["mask"])

        logits = I_t@T_t.T * torch.exp(self.temperature)

        labels = torch.arange(I_t.size(0)).to(self.device)

        loss_I = F.cross_entropy(logits.T, labels)
        loss_T = F.cross_entropy(logits, labels)

        loss = (loss_I + loss_T)/2.0 

        return loss, logits

设置模型

texts = ["This is a sample sentence.", "This is another example."]

# You can Use a CustomDataset as we Implemented above for training
train_data = CustomDataset(texts, image_path)
train_loader = DataLoader(train_data, batch_size, shuffle=True)

# Example Usage
device = 'cuda' if torch.cuda.is_available() else 'cpu'
tokenizer = DistilBertTokenizer.from_pretrained('distilbert-base-uncased')
inputs= tokenizer(texts, padding=True, truncation=True, return_tensors="pt").to(device)

test = {
"image" : torch.rand(2, 3, 224, 224).to(device),
"input_ids" : inputs["input_ids"],
"mask" : inputs["attention_mask"]
}

model = CLIPModel().to(device)
loss, logits = model(test)
print("Loss:", loss, "Logits:", logits)

源码链接:https://github.com/mishra-18/ML-Models/blob/main/clip.py

来源:小白玩转Python内容投诉

免责声明:

① 本站未注明“稿件来源”的信息均来自网络整理。其文字、图片和音视频稿件的所属权归原作者所有。本站收集整理出于非商业性的教育和科研之目的,并不意味着本站赞同其观点或证实其内容的真实性。仅作为临时的测试数据,供内部测试之用。本站并未授权任何人以任何方式主动获取本站任何信息。

② 本站未注明“稿件来源”的临时测试数据将在测试完成后最终做删除处理。有问题或投稿请发送至: 邮箱/279061341@qq.com QQ/279061341

软考中级精品资料免费领

  • 历年真题答案解析
  • 备考技巧名师总结
  • 高频考点精准押题
  • 2024年上半年信息系统项目管理师第二批次真题及答案解析(完整版)

    难度     813人已做
    查看
  • 【考后总结】2024年5月26日信息系统项目管理师第2批次考情分析

    难度     354人已做
    查看
  • 【考后总结】2024年5月25日信息系统项目管理师第1批次考情分析

    难度     318人已做
    查看
  • 2024年上半年软考高项第一、二批次真题考点汇总(完整版)

    难度     435人已做
    查看
  • 2024年上半年系统架构设计师考试综合知识真题

    难度     224人已做
    查看

相关文章

发现更多好内容

猜你喜欢

AI推送时光机
位置:首页-资讯-后端开发
咦!没有更多了?去看看其它编程学习网 内容吧
首页课程
资料下载
问答资讯