前言
文章性质:简陋的学习记录 📖
主要内容:本文简单记录了作者在阅读 CLIP 代码及其 LoRA 微调时的部分笔记。
冷知识+1:小伙伴们不经意的 点赞 👍🏻 与 收藏 ✨ 可以让作者更有创作动力!
目录
一、CLIPModel
__init__ 是初始化方法,接受两个参数:name 模型名称,num_classes 分类任务的类别数。
调用父类 nn.Module 的初始化方法,确保模型正确继承其功能。
使用 clip 库的 load 函数加载指定名称的预训练模型和预处理函数,并将模型加载到 CPU 上。
self.model 是加载的 CLIP 模型,self.preprocess 是对应的预处理函数。
self.fc 是全连接层,将模型的输出特征映射到指定的类别数。
CHANNELS[name] 是字典,根据模型名称获取相应的特征维度。
forward 是前向传播方法,接受输入 x 和布尔参数 return_feature,用于控制是否返回中间特征。
调用加载的 CLIP 模型的 encode_image 方法,对输入图像进行编码,提取其特征表示。
如果 return_feature 为真,则返回提取的图像特征。这在需要获取图像嵌入表示时非常有用。
如果 return_feature 为假,则将提取的特征通过全连接层 fc,输出分类结果。
from .clip import clip
from PIL import Image
import torch.nn as nn
CHANNELS = {
"RN50" : 1024,
"ViT-L/14" : 768
}
class CLIPModel(nn.Module):
def __init__(self, name, num_classes=1):
super(CLIPModel, self).__init__()
self.model, self.preprocess = clip.load(name, device="cpu")
self.fc = nn.Linear(CHANNELS[name], num_classes)
def forward(self, x, return_feature=False):
features = self.model.encode_image(x)
if return_feature:
return features
return self.fc(features)
总结:CLIPModel 类利用预训练的 CLIP 模型对输入图像进行特征提取,并通过一个全连接层完成特定任务的分类。
二、Bottleneck
第一层卷积(conv1)是 1x1 的卷积层,用于减少通道数,从 inplanes 降维到 planes。
随后进行批归一化和 ReLU 激活。
self.conv1 = nn.Conv2d(inplanes, planes, 1, bias=False)
self.bn1 = nn.BatchNorm2d(planes)
self.relu1 = nn.ReLU(inplace=True)
第二层卷积(conv2)是3x3 的卷积层,保持通道数不变,主要用于特征提取。
随后同样进行批归一化和 ReLU 激活。
self.conv2 = nn.Conv2d(planes, planes, 3, padding=1, bias=False)
self.bn2 = nn.BatchNorm2d(planes)
self.relu2 = nn.ReLU(inplace=True)
如果步幅大于 1,则在第二层卷积后添加一个平均池化层进行下采样;否则,使用恒等映射。
self.avgpool = nn.AvgPool2d(stride) if stride > 1 else nn.Identity()
第三层卷积(conv3)是 1x1 的卷积层,用于恢复通道数,从 planes 扩展到 planes*4。
随后同样进行批归一化和 ReLU 激活。
self.conv3 = nn.Conv2d(planes, planes * self.expansion, 1, bias=False)
self.bn3 = nn.BatchNorm2d(planes * self.expansion)
self.relu3 = nn.ReLU(inplace=True)
当输入和输出的尺寸或通道数不匹配时,需要使用下采样层使它们一致。
这里通过一个平均池化层和一个 1x1 的卷积层来调整尺寸和通道数,并进行批归一化。
self.downsample = None
self.stride = stride
if stride > 1 or inplanes != planes * Bottleneck.expansion:
self.downsample = nn.Sequential(OrderedDict([
("-1", nn.AvgPool2d(stride)),
("0", nn.Conv2d(inplanes, planes * self.expansion, 1, stride=1, bias=False)),
("1", nn.BatchNorm2d(planes * self.expansion))
]))
在前向传播中,输入 x 经过三层卷积和相应的批归一化、激活函数处理,得到 out。
如果存在下采样层,则对输入 x 进行下采样以匹配 out 的尺寸和通道数。
然后将 out 与 identity(即原始输入或下采样后的输入)相加,形成残差连接。
最后通过 ReLU 激活函数输出结果。
def forward(self, x: torch.Tensor):
identity = x
out = self.relu1(self.bn1(self.conv1(x)))
out = self.relu2(self.bn2(self.conv2(out)))
out = self.avgpool(out)
out = self.bn3(self.conv3(out))
if self.downsample is not None:
identity = self.downsample(x