文章目录
说明
本文所有观点和代码解读来自于搜索引擎以及GPT生成的内容,并且加上自己的理解后综合表达,并不一定正确,非常希望大佬能指导提出问题,也希望大家共同进步学习!
概要
主要针对LLAVA中model的multimodal_encoder中的两个python源代码解读
- clip_encoder.py
- builder.py
代码解读
clip_encoder.py
该程序主要是用于构建一整个基于CLIP的VIT视觉模型,是整个LLAVA模型视觉框架的构建,重要部分在于内部的CLIPVisionTower类
init函数
def __init__(self, vision_tower, args, delay_load=False):
super().__init__()
self.is_loaded = False # 初始没有加载模型 用于判断是否加载好了模型
self.vision_tower_name = vision_tower # 传参设置视觉模块名称
self.select_layer = args.mm_vision_select_layer
# 这行代码从args对象中获取mm_vision_select_layer属性,并将其赋值给select_layer实例变量。这可能是用来选择模型中的特定层 比如为-2的时候,可能就是加载输出层的前一层
self.select_feature = getattr(args, 'mm_vision_select_feature', 'patch') # 设置有两个可选择的 一个是patch 另一个是cls_patch
if not delay_load: # 默认调用load_model来加载模型
self.load_model()
else:
self.cfg_only = CLIPVisionConfig.from_pretrained(self.vision_tower_name)
load_model函数
def load_model(self):
# 这个好理解,一个是 img的处理模块 一个是vit视觉模块,llava中并不训练vit,因此vit的梯度为False 最后设置is_load为true代表已经加载完毕了
# 整个模型调用transformer中的CLIP库加载模型。
self.image_processor = CLIPImageProcessor.from_pretrained(self.vision_tower_name)
self.vision_tower = CLIPVisionModel.from_pretrained(self.vision_tower_name)
self.vision_tower.requires_grad_(False)
self.is_loaded = True
feature_select函数
def feature_select(self, image_forward_outs):
# image_feature是图像的特征,源码中 select_layer = -2(finetune.sh中mm_vision_select_layer 默认值为-2 代表的是拿到了img经过VIT的计算后的倒数第二层的特征值)
# 具体来说应该是 分类的前一个头,对应于VIT的原文中,应该是MLP的输出结果 也有可能是transformer encoder的输出结果。
image_features = image_forward_outs.hidden_states[self.select_layer]
# 如果是patch 那么不要第一个特征,应该是cls(VIT中第一个token做的是cls)
if self.select_feature == 'patch':
image_features = image_features[:, 1:]
# cls_patch 是完全保留的
elif self.select_feature == 'cls_patch':
image_features = image_features
else:
raise ValueError(f'Unexpected select feature: {self.select_feature}')
return image_features
forward函数
# 注意这里设置为不需要梯度计算,因为llava不训练整个clip的vit
@torch.no_grad()
def forward(self, images):
if type(images) is list: # 如果输入图像是list时
image_features = []
for image in images: # 遍历
# 计算前向输出结果 并且增加维度,应该是表示 批次顺序的维度
image_forward_out = self.vision_tower(image.to(device=self.device, dtype=self.dtype).unsqueeze(0), output_hidden_states=True)
# 拿到特征
image_feature = self.feature_select(image_forward_out).to(image.dtype)
image_features.append(image_feature)
else:
# 单一图片 直接计算这张图片, 并且拿到feature
image_forward_outs = self.vision_tower(images.to(device=self.device, dtype=self.dtype), output_hidden_states=True)
image_features = self.feature_select(image_forward_outs).to(images.dtype)
return image_features
其余的函数较为简单,没有说明的必要
builder.py
builder.py主要用于调用上面的multimodal_encoder中的clipvisiontower,从而构建一个llava的视觉encoder,只有一个函数时 build_vision_tower
build_vision_tower函数
def build_vision_tower(vision_tower_cfg, **kwargs):
# 根据参数传递来选择vision_tower
vision_tower = getattr(vision_tower_cfg, 'mm_vision_tower', getattr(vision_tower_cfg, 'vision_tower', None))
is_absolute_path_exists = os.path.exists(vision_tower)
if is_absolute_path_exists or vision_tower.startswith("openai") or vision_tower.startswith("laion"):
# 在这调用用于构建一个vision_tower对象
return CLIPVisionTower(vision_tower, args=vision_tower_cfg, **kwargs)
raise ValueError(f'Unknown vision tower: {vision_tower}')