赞
踩
对app.py文件中DA-CLIP模型的创建过程中模型配置读取、预训练权重地址读取、相关参数等代码进行解析。下面是DA-CLIP模型的创建过程执行的代码
clip_model, preprocess = open_clip.create_model_from_pretrained('daclip_ViT-B-32', pretrained=opt['path']['daclip']) 输入的参数 # 自定义的daclip_ViT-B-32模型名称,该模型基于VIT-B-32训练 # pretrained用的是test.yml的模型地址,,读取pt文件 clip_model = clip_model.to(device)
本文当初写的时候主要是对我探究过程的一个记录, 不想看过程的也可以直接看结果。
open_clip.create_model_and_transforms,open_clip.create_model_from_pretrained
create_model()
这个比较多样。以openai模型为例,open_clip\openai.py。还有CoCa、CustomTextCLIP、CLIP等直接创建类实例,而非第五层才创建,该途径需要根据create_model()输入的custom_text布尔值设置。具体见create_model()
load_openai_model()
以openai模型为例
build_model_from_openai_state_dict()
CLIP类、CustomTextCLIP类 open_clip\model.py
CoCa类 open_clip\coca_model.py
DA-CLIP接受一个CLIP实例作为参数初始化
def __init__(self, clip_model: CLIP):
这是调用universal-image-restoration文件夹下open_clip包的init.py中,从factory.py定义的函数。
yml文件提供了对应预训练权重路径,已读取到opt['path']['daclip']参数中。
返回的clip_model 是加载的 CLIP 模型。函数定义如下:
- def create_model_from_pretrained(
- model_name: str,
- pretrained: Optional[str] = None,
- precision: str = 'fp32',
- device: Union[str, torch.device] = 'cpu',
- jit: bool = False,
- force_quick_gelu: bool = False,
- force_custom_text: bool = False,
- force_image_size: Optional[Union[int, Tuple[int, int]]] = None,
- return_transform: bool = True,
- image_mean: Optional[Tuple[float, ...]] = None,
- image_std: Optional[Tuple[float, ...]] = None,
- cache_dir: Optional[str] = None,
- ):
- model = create_model(
- model_name,
- pretrained,
- precision=precision,
- device=device,
- jit=jit,
- force_quick_gelu=force_quick_gelu,
- force_custom_text=force_custom_text,
- force_image_size=force_image_size,
- cache_dir=cache_dir,
- require_pretrained=True,
- )
-
- if not return_transform:
- return model
-
- image_mean = image_mean or getattr(model.visual, 'image_mean', None)
- image_std = image_std or getattr(model.visual, 'image_std', None)
- preprocess = image_transform(
- model.visual.image_size,
- is_train=False,
- mean=image_mean,
- std=image_std,
- )
-
- return model, preprocess
这个函数的设计允许用户灵活地创建和定制模型,包括选择不同的预训练权重来源、设置运行精度、移动设备、以及加载预处理配置等。
继续深挖该函数寻找'daclip_ViT-B-32'配置藏在哪里,我们可以发现该str为create_model()的输入参数,同级目录下的create_model()函数将近160行代码
- def create_model(
- model_name: str,
- pretrained: Optional[str] = None,
- precision: str = 'fp32',
- device: Union[str, torch.device] = 'cpu',
- jit: bool = False,
- force_quick_gelu: bool = False,
- force_custom_text: bool = False,
- force_patch_dropout: Optional[float] = None,
- force_image_size: Optional[Union[int, Tuple[int, int]]] = None,
- pretrained_image: bool = False,
- pretrained_hf: bool = True,
- cache_dir: Optional[str] = None,
- output_dict: Optional[bool] = None,
- require_pretrained: bool = False,
- ):
model_name
: 模型名称,用于指定要创建的模型类型。pretrained
: 预训练权重的来源,可以是一个URL、文件路径或预训练模型的名称。precision
: 指定模型运行时的数值精度,如 'fp32', 'fp16', 'bf16' 等。device
: 指定模型应该运行在的设备,如 'cpu' 或具体的GPU设备名称。jit
: 是否将模型转换为 PyTorch 的 JIT(Just-In-Time)编译版本。force_quick_gelu
: 是否强制使用 QuickGELU 激活函数。force_custom_text
: 是否强制使用自定义的文本编码器。force_patch_dropout
: 是否强制设置特定的补丁丢弃(patch dropout)值。force_image_size
: 是否强制设置特定的图像大小。pretrained_image
: 是否预训练图像塔(image tower)。pretrained_hf
: 是否使用预训练的Hugging Face(HF)文本模型。cache_dir
: 缓存目录,用于存储下载的预训练权重和配置文件。output_dict
: 是否将模型的输出格式设置为字典。1.model_name
初步判断:Hugging Face模型检测- has_hf_hub_prefix = model_name.startswith(HF_HUB_PREFIX)
- if has_hf_hub_prefix:
- model_id = model_name[len(HF_HUB_PREFIX):]
- checkpoint_path = download_pretrained_from_hf(model_id, cache_dir=cache_dir)
- config_path = download_pretrained_from_hf(model_id, filename='open_clip_config.json', cache_dir=cache_dir)
-
- with open(config_path, 'r', encoding='utf-8') as f:
- config = json.load(f)
- pretrained_cfg = config['preprocess_cfg']
- model_cfg = config['model_cfg']
- else:
- model_name = model_name.replace('/', '-') # for callers using old naming with / in ViT names
- checkpoint_path = None
- pretrained_cfg = {}
- model_cfg = None
检查 model_name
是否以Hugging Face Hub的前缀开头,如果是,说明用户希望从HF Hub加载模型,函数会下载相应的配置文件和预训练权重。
否则,根据 model_name
加载或设置模型配置。如果 model_name
包含特定的路径分隔符(如'/'),则将其替换为连字符('-'),以匹配新的命名约定。
2.pretrained判断,该代码读取了daclip模型配置和预训练模型参数
代码很长就不完全截取,只选取与daclip有关部分
输入参数为model_name='daclip_ViT-B-32', pretrained=opt['path']['daclip']
- elif "daclip" in model_name:
- clip_model = CLIP(**model_cfg, cast_dtype=cast_dtype)
- model = DaCLIP(clip_model)
读取配置的简要伪代码上下文:
- 如果指定了
pretrained
参数并且其值为 'openai',则从OpenAI加载预训练模型。- 否则,model_cfg = model_cfg or get_model_config(model_name)
- 如果 custom_text值存在
model_name
包含 'coca'model_name
包含'daclip',则创建特定的模型实例DaCLIP
。
在else中根据model_name进行了判断,,
model_cfg,是上文Hugging Face模型名称检测中生成的变量,显然daclip不满足该判断。model_cfg为返回值NONE,执行get_model_config()
- def get_model_config(model_name):
- if model_name in _MODEL_CONFIGS:
- return deepcopy(_MODEL_CONFIGS[model_name])
- else:
- return None
重返该factor.py文件头部代码查找相关函数和变量,定义了模块级别变量,并执行了模型配置文件扫描函数。
- HF_HUB_PREFIX = 'hf-hub:'
- _MODEL_CONFIG_PATHS = [Path(__file__).parent / f"model_configs/"]
- _MODEL_CONFIGS = {} # directory (model_name: config) of model architecture configs
-
-
- def _natural_key(string_):
- return [int(s) if s.isdigit() else s for s in re.split(r'(\d+)', string_.lower())]
-
-
- def _rescan_model_configs():
- global _MODEL_CONFIGS
-
- config_ext = ('.json',)
- config_files = []
- for config_path in _MODEL_CONFIG_PATHS:
- if config_path.is_file() and config_path.suffix in config_ext:
- config_files.append(config_path)
- elif config_path.is_dir():
- for ext in config_ext:
- config_files.extend(config_path.glob(f'*{ext}'))
-
- for cf in config_files:
- with open(cf, 'r') as f:
- model_cfg = json.load(f)
- if all(a in model_cfg for a in ('embed_dim', 'vision_cfg', 'text_cfg')):
- _MODEL_CONFIGS[cf.stem] = model_cfg
-
- _MODEL_CONFIGS = {k: v for k, v in sorted(_MODEL_CONFIGS.items(), key=lambda x: _natural_key(x[0]))}
-
-
- _rescan_model_configs() # initial populate of model config registry
配置如下
{ "embed_dim": 512, "vision_cfg": { "image_size": 224, "layers": 12, "width": 768, "patch_size": 32 }, "text_cfg": { "context_length": 77, "vocab_size": 49408, "width": 512, "heads": 8, "layers": 12 }, "custom_text": true }
配置解读:
embed_dim
: 嵌入维度,这是模型中嵌入层的维度。在这个配置中,嵌入维度被设置为 512。
vision_cfg
: 视觉配置对象,包含了构建视觉塔(处理图像的神经网络部分)所需的配置信息:
image_size
: 输入图像的大小,这里设置为 224x224 像素。layers
: 视觉塔中的层数,这里设置为 12 层。width
: 视觉塔中层的宽度,即每层的神经元数量,这里设置为 768。patch_size
: 图像分块(patch)的大小,这是视觉变换器中使用的图像分割大小,这里设置为 32x32 像素。text_cfg
: 文本配置对象,包含了构建文本处理部分(如文本编码器)所需的配置信息:
context_length
: 上下文长度,即模型在处理文本时考虑的最大长度,这里设置为 77 个token。vocab_size
: 文本词汇表的大小,这里设置为 49,408 个不同的token。width
: 文本处理部分的宽度,即每层的神经元数量,这里设置为 512。heads
: 多头注意力机制中的头数,这里设置为 8 个头。layers
: 文本处理部分的层数,这里设置为 12 层。custom_text
: 一个布尔值,指示是否使用自定义的文本处理配置。这里设置为 true
,意味着模型将使用特定的文本处理设置,而不是默认的配置。
还是在create_model()函数中
- if pretrained:
- checkpoint_path = ''
-
- f "daclip" in model_name:
- pretrained_cfg = get_pretrained_cfg(model_name[7:], pretrained)
-
- else:
- pretrained_cfg = get_pretrained_cfg(model_name, pretrained)
-
- if pretrained_cfg:
- checkpoint_path = download_pretrained(pretrained_cfg, cache_dir=cache_dir)
-
- elif os.path.exists(pretrained):
- checkpoint_path = pretrained
pretrained_cfg = get_pretrained_cfg(model_name[7:], pretrained)
如果模型名称包含 "daclip",则调用 get_pretrained_cfg
函数来获取与模型名称后缀(去掉 "daclip" 前缀的部分)和预训练配置相关的信息。
这里 model_name[7:]
是因为 "daclip" 通常是模型名称的前缀。
get_pretrained_cfg函数在pretrained.py中,该文件包含所有读取预训练权重文件的配置
- def get_pretrained_cfg(model: str, tag: str):
- if model not in _PRETRAINED:
- return {}
- model_pretrained = _PRETRAINED[model]
- return model_pretrained.get(_clean_tag(tag), {})
对 model_name='daclip_ViT-B-32'
执行 model_name[7:]
将会返回字符串 'ViT-B-32'
。
尽管model_pretrained获取了模型名称对应的地址字典,由于返回函数中执行模型路径判断.get(),对清理后的_clean_tag(tag),即本地test.yml设置的模型参数地址。显然字典里是没有该本地地址的。所以最后函数返回{}。
return model_pretrained.get(_clean_tag(tag), {})
- def _clean_tag(tag: str):
- # normalize pretrained tags
- return tag.lower().replace('-', '_')
再次回看代码判断,故执行最后一个elseif
elif os.path.exists(pretrained):
checkpoint_path = pretrained
继续进行下列代码。
- if checkpoint_path:
- logging.info(f'Loading pretrained {model_name} weights ({pretrained}).')
- if pretrained_cfg and "daclip" in model_name:
- load_checkpoint(model.clip, checkpoint_path)
- model.initial_controller()
- model.lock_clip()
- def load_checkpoint(model, checkpoint_path, strict=True):
- state_dict = load_state_dict(checkpoint_path)
- # detect old format and make compatible with new format
- if 'positional_embedding' in state_dict and not hasattr(model, 'positional_embedding'):
- state_dict = convert_to_custom_text_state_dict(state_dict)
- resize_pos_embed(state_dict, model)
- incompatible_keys = model.load_state_dict(state_dict, strict=strict)
- return incompatible_keys
model
: 要加载预训练权重的模型实例。checkpoint_path
: 预训练权重文件的路径。strict
: 布尔值,指示在加载状态字典时是否要求模型和权重的键完全匹配。state_dict = load_state_dict(checkpoint_path)
: 调用 load_state_dict
函数来加载指定路径下的预训练权重文件,并将其内容存储在 state_dict
变量中。
接下来的代码块检查 state_dict
中是否存在 positional_embedding
键,并且模型实例中没有 positional_embedding
属性。这可能意味着模型的结构已经更新,而预训练权重是按照旧格式保存的。
如果检测到这种情况,调用 convert_to_custom_text_state_dict
函数来将权重转换成与新模型格式兼容的形式。
resize_pos_embed(state_dict, model)
: 调用 resize_pos_embed
函数来调整(如果需要的话)位置嵌入(positional embedding)的大小,以匹配模型的期望大小。
incompatible_keys = model.load_state_dict(state_dict, strict=strict)
: 使用 load_state_dict
方法将处理后的 state_dict
加载到模型中。这个方法会尝试匹配并加载所有键,如果 strict
为 True
,那么只有完全匹配的键才会被加载,不匹配的键将被报告为不兼容的键。如果 strict
为 False
,那么即使键不完全匹配,也会尝试加载权重。
return incompatible_keys
: 返回一个包含不兼容键的列表,这些键在加载过程中没有被加载到模型中。
函数返回一个列表,包含在加载过程中检测到的不兼容键。
load_checkpoint
函数负责加载预训练权重到模型实例中,并处理可能的格式不兼容问题。这个函数确保了即使在模型结构发生变化的情况下,也能够尽可能地加载预训练权重。通过返回不兼容的键,它还提供了有关哪些权重未能加载的信息,这对于调试和进一步的模型调整非常有用
load_checkpoint(model.clip, checkpoint_path)
model.initial_controller()
model.lock_clip()
该代码依赖于读取配置阶段执行生成的model实例
尽管我们在open_clip文件夹下的factory.py找到了模型配置。并有了create_model()和create_model_from_pretrained()的相关默认参数配置,然而这些都只是外层包装,更深入的代码需要我们查看model.py中的CLIP()和daclip_model.py下的DACLIP()
- elif "daclip" in model_name:
- clip_model = CLIP(**model_cfg, cast_dtype=cast_dtype)
- model = DaCLIP(clip_model)
clip_model = CLIP(**model_cfg, cast_dtype=cast_dtype)
: 这行代码创建了一个 CLIP
类的实例,名为 clip_model
。这个类的实例化是通过传递 model_cfg
字典中的配置参数以及 cast_dtype
参数来完成的。model_cfg
包含了初始化 CLIP
模型所需的配置,如嵌入维度、视觉和文本配置等。
model = DaCLIP(clip_model)
: 紧接着,使用刚刚创建的 clip_model
实例作为参数,创建了另一个类的实例,名为 model
。这个类是 DaCLIP
,它是 CLIP
类的一个扩展或变体,用于实现特定的功能或适配。
**model_cfg
: 这是 Python 中的参数解包语法,它将 model_cfg
字典中的键值对作为关键字参数传递给 CLIP
类的构造函数。例如,如果 model_cfg
包含了 {'embed_dim': 512, 'vision_cfg': {...}, 'text_cfg': {...}}
,那么这些键值对将会被用作 CLIP
类构造函数的参数。cast_dtype
: 这是一个变量,它指定了模型参数和激活值的数据类型。这个参数可能会被用来设置模型的精度,例如在混合精度训练中使用 torch.float16
或 torch.bfloat16
。关于cast_dtype,根据create_model()的预设参数和处理函数
precision: str = 'fp32'cast_dtype = get_cast_dtype(precision)
结合model.py 中get_cast_dtyp()的定义,可了解该处理流程
- def get_cast_dtype(precision: str):
- cast_dtype = None
- if precision == 'bf16':
- cast_dtype = torch.bfloat16
- elif precision == 'fp16':
- cast_dtype = torch.float16
- return cast_dtype
下文只对init()和配置参数传递流程解读,该类下的其他方法可参考
open_clip仓库成分与模型文件model.py 介绍http://t.csdnimg.cn/rirBq
- def __init__(
- self,
- embed_dim: int,
- vision_cfg: CLIPVisionCfg,
- text_cfg: CLIPTextCfg,
- quick_gelu: bool = False,
- cast_dtype: Optional[torch.dtype] = None,
- output_dict: bool = False,
- ):
- super().__init__()
- self.output_dict = output_dict
- self.visual = _build_vision_tower(embed_dim, vision_cfg, quick_gelu, cast_dtype)
-
- text = _build_text_tower(embed_dim, text_cfg, quick_gelu, cast_dtype)
- self.transformer = text.transformer
- self.context_length = text.context_length
- self.vocab_size = text.vocab_size
- self.token_embedding = text.token_embedding
- self.positional_embedding = text.positional_embedding
- self.ln_final = text.ln_final
- self.text_projection = text.text_projection
- self.register_buffer('attn_mask', text.attn_mask, persistent=False)
-
- self.logit_scale = nn.Parameter(torch.ones([]) * np.log(1 / 0.07))
参数
embed_dim
: 嵌入维度,这是模型中嵌入层的维度。vision_cfg
: 图像配置对象,包含了构建视觉塔(处理图像的神经网络部分)所需的配置信息。text_cfg
: 文本配置对象,包含了构建文本处理部分(如文本编码器)所需的配置信息。quick_gelu
: 布尔值,指示是否使用快速的GELU(Gaussian Error Linear Unit)激活函数。cast_dtype
: 可选参数,指定数据类型,用于将模型参数转换为指定的数据类型。output_dict
: 布尔值,指示模型输出是否应该是一个字典。方法体解释
super().__init__()
: 调用父类的构造函数。self.output_dict = output_dict
: 存储传入的output_dict
参数,这可能影响模型输出的格式。self.visual = _build_vision_tower(...)
: 调用一个内部函数_build_vision_tower
来构建视觉塔,并存储结果。text = _build_text_tower(...)
: 调用一个内部函数_build_text_tower
来构建文本塔,并存储结果。self.transformer = text.transformer
: 从文本塔中提取变换器(transformer)模块。self.context_length = text.context_length
: 存储文本塔的上下文长度。self.vocab_size = text.vocab_size
: 存储文本塔的词汇表大小。self.token_embedding = text.token_embedding
: 存储文本塔的词嵌入层。self.positional_embedding = text.positional_embedding
: 存储文本塔的位置嵌入层。self.ln_final = text.ln_final
: 存储文本塔的最终层归一化(Layer Normalization)。self.text_projection = text.text_projection
: 存储文本塔的文本投影层。self.register_buffer('attn_mask', text.attn_mask, persistent=False)
: 注册一个缓冲区,用于存储文本塔的注意力掩码(attention mask),这个掩码在自注意力机制中用于指示哪些位置应该被模型关注。self.logit_scale = nn.Parameter(torch.ones([]) * np.log(1 / 0.07))
: 创建一个可学习的参数,用于缩放模型的输出(logits),初始化为一个全1的向量,乘以一个基于经验的对数缩放因子。- class CLIPVisionCfg:
- layers: Union[Tuple[int, int, int, int], int] = 12
- width: int = 768
- head_width: int = 64
- mlp_ratio: float = 4.0
- patch_size: int = 16
- image_size: Union[Tuple[int, int], int] = 224
-
- ls_init_value: Optional[float] = None # layer scale initial value
- patch_dropout: float = 0. # what fraction of patches to dropout during training (0 would mean disabled and no patches dropped) - 0.5 to 0.75 recommended in the paper for optimal results
- input_patchnorm: bool = False # whether to use dual patchnorm - would only apply the input layernorm on each patch, as post-layernorm already exist in original clip vit design
- global_average_pool: bool = False # whether to global average pool the last embedding layer, instead of using CLS token (https://arxiv.org/abs/2205.01580)
- attentional_pool: bool = False # whether to use attentional pooler in the last embedding layer
- n_queries: int = 256 # n_queries for attentional pooler
- attn_pooler_heads: int = 8 # n heads for attentional_pooling
- output_tokens: bool = False
-
- timm_model_name: str = None # a valid model name overrides layers, width, patch_size
- timm_model_pretrained: bool = False # use (imagenet) pretrained weights for named model
- timm_pool: str = 'avg' # feature pooling for timm model ('abs_attn', 'rot_attn', 'avg', '')
- timm_proj: str = 'linear' # linear projection for timm model output ('linear', 'mlp', '')
- timm_proj_bias: bool = False # enable bias final projection
- timm_drop: float = 0. # head dropout
- timm_drop_path: Optional[float] = None # backbone stochastic depth
类是一个配置类,用于定义和存储与 CLIP 模型中视觉(图像处理)部分相关的配置参数。这个类提供了一系列的属性,允许用户自定义和初始化 CLIP 模型的视觉塔(Vision Transformer)的各种设置。下面是对这个类的主要属性的解释:
layers
: 视觉塔中的层数,可以是一个整数或者一个包含四个整数的元组,表示不同层的层数。width
: 视觉塔中每层的宽度,即特征维度。head_width
: 头部(分类器)的宽度,通常是视觉塔最后一层的特征维度。mlp_ratio
: MLP(多层感知机)中的隐藏层宽度与输入层宽度的比率。patch_size
: 图像分块的大小,决定了如何将图像切分成小块来输入模型。image_size
: 输入图像的大小,可以是一个整数或者一个包含两个整数的元组,表示图像的宽度和高度。接下来是一些与正则化和池化相关的选项:
ls_init_value
: 层尺度(Layer Scale)的初始值,用于正则化。patch_dropout
: 训练过程中要丢弃的补丁(patch)的比例,用于正则化。input_patchnorm
: 是否在每个补丁上使用输入层归一化(input layernorm)。global_average_pool
: 是否使用全局平均池化来代替使用 CLS 标记的策略。attentional_pool
: 是否在最后一层嵌入层使用注意力池化器(attentional pooler)。n_queries
: 注意力池化器使用的查询数。attn_pooler_heads
: 注意力池化器的头数。最后是与使用 timm 库相关的配置选项:
timm_model_name
: 如果提供了有效的模型名称,将覆盖 layers
, width
, patch_size
等参数。timm_model_pretrained
: 是否使用预训练的(在 ImageNet 上)timm 模型权重。timm_pool
: timm 模型的特征池化类型。timm_proj
: timm 模型输出的线性投影类型。timm_proj_bias
: 是否在最终投影中启用偏置。timm_drop
: 头部丢弃(head dropout)的比率。timm_drop_path
: 背部随机深度(backbone stochastic depth)的比率。- class CLIPTextCfg:
- context_length: int = 77
- vocab_size: int = 49408
- width: int = 512
- heads: int = 8
- layers: int = 12
- ls_init_value: Optional[float] = None # layer scale initial value
- hf_model_name: str = None
- hf_tokenizer_name: str = None
- hf_model_pretrained: bool = True
- proj: str = 'mlp'
- pooler_type: str = 'mean_pooler'
- embed_cls: bool = False
- pad_id: int = 0
- output_tokens: bool = False
CLIPTextCfg
类是一个配置类,用于定义和存储与 CLIP 模型中文本处理部分相关的配置参数。这个类提供了一系列的属性,允许用户自定义和初始化 CLIP 模型的文本塔(Text Transformer)的各种设置。下面是对这个类的主要属性的解释:
context_length
: 文本处理的上下文长度,即模型一次性处理的最大文本长度。vocab_size
: 文本词汇表的大小,表示模型能够识别的不同词汇的数量。width
: 文本塔中每层的宽度,即特征维度。heads
: 多头注意力机制中的头数,用于并行处理信息。layers
: 文本塔中的层数。ls_init_value
: 层尺度(Layer Scale)的初始值,用于正则化。接下来是与使用 Hugging Face(HF)相关的配置选项:
hf_model_name
: Hugging Face 模型的名称,用于加载预训练的文本模型。hf_tokenizer_name
: Hugging Face 分词器的名称,用于将文本转换为模型可以理解的格式。hf_model_pretrained
: 是否使用预训练的 Hugging Face 模型权重。其他配置选项:
proj
: 文本投影的类型,这里是 'mlp',表示使用多层感知机进行投影。pooler_type
: 池化器的类型,这里是 'mean_pooler',表示使用平均池化器来聚合文本信息。embed_cls
: 是否将分类标记(通常是一个特殊的 [CLS] 标记)嵌入到文本中。pad_id
: 填充标记的 ID,用于处理不同长度的文本序列。output_tokens
: 是否输出每个文本标记的特征,而不是仅仅输出文本的整体表示- def _build_vision_tower(
- embed_dim: int,
- vision_cfg: CLIPVisionCfg,
- quick_gelu: bool = False,
- cast_dtype: Optional[torch.dtype] = None
- ):
- if isinstance(vision_cfg, dict):
- vision_cfg = CLIPVisionCfg(**vision_cfg)
-
- # OpenAI models are pretrained w/ QuickGELU but native nn.GELU is both faster and more
- # memory efficient in recent PyTorch releases (>= 1.10).
- # NOTE: timm models always use native GELU regardless of quick_gelu flag.
- act_layer = QuickGELU if quick_gelu else nn.GELU
-
- if vision_cfg.timm_model_name:
- visual = TimmModel(
- vision_cfg.timm_model_name,
- pretrained=vision_cfg.timm_model_pretrained,
- pool=vision_cfg.timm_pool,
- proj=vision_cfg.timm_proj,
- proj_bias=vision_cfg.timm_proj_bias,
- drop=vision_cfg.timm_drop,
- drop_path=vision_cfg.timm_drop_path,
- patch_drop=vision_cfg.patch_dropout if vision_cfg.patch_dropout > 0 else None,
- embed_dim=embed_dim,
- image_size=vision_cfg.image_size,
- )
- elif isinstance(vision_cfg.layers, (tuple, list)):
- vision_heads = vision_cfg.width * 32 // vision_cfg.head_width
- visual = ModifiedResNet(
- layers=vision_cfg.layers,
- output_dim=embed_dim,
- heads=vision_heads,
- image_size=vision_cfg.image_size,
- width=vision_cfg.width,
- )
- else:
- vision_heads = vision_cfg.width // vision_cfg.head_width
- norm_layer = LayerNormFp32 if cast_dtype in (torch.float16, torch.bfloat16) else LayerNorm
- visual = VisionTransformer(
- image_size=vision_cfg.image_size,
- patch_size=vision_cfg.patch_size,
- width=vision_cfg.width,
- layers=vision_cfg.layers,
- heads=vision_heads,
- mlp_ratio=vision_cfg.mlp_ratio,
- ls_init_value=vision_cfg.ls_init_value,
- patch_dropout=vision_cfg.patch_dropout,
- input_patchnorm=vision_cfg.input_patchnorm,
- global_average_pool=vision_cfg.global_average_pool,
- attentional_pool=vision_cfg.attentional_pool,
- n_queries=vision_cfg.n_queries,
- attn_pooler_heads=vision_cfg.attn_pooler_heads,
- output_tokens=vision_cfg.output_tokens,
- output_dim=embed_dim,
- act_layer=act_layer,
- norm_layer=norm_layer,
- )
-
- return visual
参数解释
embed_dim
: 嵌入维度,这是模型中嵌入层的维度。vision_cfg
: 视觉配置对象,包含了构建视觉塔所需的配置信息。quick_gelu
: 布尔值,指示是否使用快速的GELU激活函数。cast_dtype
: 可选参数,指定数据类型,用于将模型参数转换为指定的数据类型。函数逻辑
如果 vision_cfg
是一个字典,那么使用这个字典来创建一个 CLIPVisionCfg
实例。
根据 quick_gelu
参数的值选择使用 QuickGELU
激活层还是使用 PyTorch 原生的 nn.GELU
。
如果 vision_cfg
包含 timm_model_name
,则使用 TimmModel
来创建一个基于 timm 库的模型。
如果 vision_cfg.layers
是一个元组或列表,假设配置是一个修改版的 ResNet 架构,使用 ModifiedResNet
来创建模型。
如果上述条件都不满足,那么使用 VisionTransformer
来创建一个标准的 Vision Transformer 架构。
在创建 VisionTransformer
时,根据 vision_cfg
中的参数配置模型的不同部分,如层数、头数、激活层、归一化层等。
返回创建好的 visual
模型实例。
- def _build_text_tower(
- embed_dim: int,
- text_cfg: CLIPTextCfg,
- quick_gelu: bool = False,
- cast_dtype: Optional[torch.dtype] = None,
- ):
- if isinstance(text_cfg, dict):
- text_cfg = CLIPTextCfg(**text_cfg)
-
- if text_cfg.hf_model_name:
- text = HFTextEncoder(
- text_cfg.hf_model_name,
- output_dim=embed_dim,
- proj=text_cfg.proj,
- pooler_type=text_cfg.pooler_type,
- pretrained=text_cfg.hf_model_pretrained,
- output_tokens=text_cfg.output_tokens,
- )
- else:
- act_layer = QuickGELU if quick_gelu else nn.GELU
- norm_layer = LayerNormFp32 if cast_dtype in (torch.float16, torch.bfloat16) else LayerNorm
-
- text = TextTransformer(
- context_length=text_cfg.context_length,
- vocab_size=text_cfg.vocab_size,
- width=text_cfg.width,
- heads=text_cfg.heads,
- layers=text_cfg.layers,
- ls_init_value=text_cfg.ls_init_value,
- output_dim=embed_dim,
- embed_cls=text_cfg.embed_cls,
- output_tokens=text_cfg.output_tokens,
- pad_id=text_cfg.pad_id,
- act_layer=act_layer,
- norm_layer=norm_layer,
- )
- return text
函数逻辑
如果 text_cfg
是一个字典,那么使用这个字典来创建一个 CLIPTextCfg
实例。
如果 text_cfg
包含 hf_model_name
,则使用 HFTextEncoder
来创建一个基于 Hugging Face 的文本编码器。
如果上述条件不满足,那么使用 TextTransformer
来创建一个标准的文本 Transformer 架构。
在创建 TextTransformer
时,根据 text_cfg
中的参数配置模型的不同部分,如上下文长度、词汇表大小、激活层、归一化层等。
返回创建好的 text
模型实例。
根据daclip_ViT-B-32.json文件里的字典通过__init__()到_build_vision_tower()再到
CLIPVisionCfg()这样的流程完成了参数传递。
{ "embed_dim": 512, "vision_cfg": { "image_size": 224, "layers": 12, "width": 768, "patch_size": 32 }, "text_cfg": { "context_length": 77, "vocab_size": 49408, "width": 512, "heads": 8, "layers": 12 }, "custom_text": true }
- class DaCLIP(nn.Module):
- def __init__(self, clip_model: CLIP):
- super().__init__()
- self.clip = clip_model
- self.visual = clip_model.visual
- self.visual_control = copy.deepcopy(clip_model.visual)
- self.visual_control.transformer = ControlTransformer(self.visual_control.transformer)
- self.logit_scale = copy.deepcopy(clip_model.logit_scale)
-
- def initial_controller(self):
- for (kv, param_v), (kc, param_c) in zip(self.clip.visual.named_parameters(), self.visual_control.named_parameters()):
- if 'transformer' not in kv:
- param_c.data.copy_(param_v.data)
-
- for param_v, param_c in zip(self.clip.visual.transformer.parameters(), self.visual_control.transformer.parameters()):
- param_c.data.copy_(param_v.data)
-
- self.logit_scale.data.copy_(self.clip.logit_scale.data)
-
- def lock_clip(self):
- for param in self.clip.parameters():
- param.requires_grad = False
-
- @torch.jit.ignore
- def set_grad_checkpointing(self, enable=True):
- self.clip.visual.set_grad_checkpointing(enable)
- self.clip.transformer.grad_checkpointing = enable
- self.visual_control.set_grad_checkpointing(enable)
-
- def encode_image(self, image, control=False, normalize: bool = False):
- if control:
- degra_features, hiddens = self.visual_control(image, output_hiddens=True)
- image_features = self.clip.visual(image, control=hiddens)
-
- image_features = F.normalize(image_features, dim=-1) if normalize else image_features
- degra_features = F.normalize(degra_features, dim=-1) if normalize else degra_features
- return image_features, degra_features
- else:
- return self.clip.encode_image(image, normalize)
-
- def encode_text(self, text, normalize: bool = False):
- return self.clip.encode_text(text, normalize)
-
- def forward(
- self,
- image: Optional[torch.Tensor] = None,
- text: Optional[torch.Tensor] = None,
- ):
- (caption, degradation) = text.chunk(2, dim=-1) if text is not None else (None, None)
- image_features, image_degra_features = self.encode_image(image, control=True, normalize=True) if image is not None else None
- text_features = self.encode_text(caption, normalize=True) if text is not None else None
- text_degra_features = self.encode_text(degradation, normalize=True) if degradation is not None else None
-
- return {
- "image_features": image_features,
- "text_features": text_features,
- "image_degra_features": image_degra_features,
- "text_degra_features": text_degra_features,
- "logit_scale": self.logit_scale.exp()
- }
在这个例子中,CLIP
类是父类,而 DaCLIP
类是子类。DaCLIP
类继承自 CLIP
类,并在其基础上进行了扩展和修改。以下是子类 DaCLIP
相对于父类 CLIP
所做的主要更改和添加:
初始化 (__init__
方法):
DaCLIP
类在初始化时创建了 clip_model
的一个副本,命名为 self.visual
,并创建了一个名为 self.visual_control
的控制塔,它是 self.visual
的深拷贝,但使用了 ControlTransformer
替换了原有的 transformer
。DaCLIP
还复制了父类的 logit_scale
参数。控制塔 (initial_controller
方法):
DaCLIP
类提供了一个方法来初始化控制塔的参数,确保它们与父类的 visual
塔的参数一致。锁定 (lock_clip
方法):
DaCLIP
类添加了一个方法来锁定父类 CLIP
的所有参数,使其在训练过程中不会更新。梯度检查点设置 (set_grad_checkpointing
方法):
DaCLIP
类扩展了父类的方法,同时为 clip
和 visual_control
设置梯度检查点。图像编码 (encode_image
方法):
DaCLIP
类重写了图像编码方法,允许控制塔生成特征,并将这些特征与父类 CLIP
生成的特征结合起来。前向传播 (forward
方法):
DaCLIP
类修改了前向传播方法,以处理额外的 degradation
输入,并输出控制塔生成的图像和文本的退化特征。- def __init__(self, clip_model: CLIP):
- super().__init__()
- self.clip = clip_model
- self.visual = clip_model.visual
- self.visual_control = copy.deepcopy(clip_model.visual)
- self.visual_control.transformer = ControlTransformer(self.visual_control.transformer)
- self.logit_scale = copy.deepcopy(clip_model.logit_scale)
创建 self.visual
:
DaCLIP
类的 __init__
方法中,首先创建了一个名为 self.visual
的属性,它是 clip_model
(即父类 CLIP
的实例)的 visual
属性的一个引用。这意味着 self.visual
直接指向了父类中的 visual
属性,它们指向相同的对象。深拷贝 self.visual
以创建 self.visual_control
:该代码就是论文所需的Image Controller部分
copy.deepcopy
方法对 self.visual
进行了深拷贝,创建了一个新的对象 self.visual_control
。深拷贝意味着创建了 self.visual
中所有属性和子对象的完整副本,而不是简单地复制引用。这样,self.visual_control
就是一个独立的、与 self.visual
完全相同的新对象,对它的任何修改都不会影响原始的 self.visual
。替换 self.visual_control
中的 transformer
:
DaCLIP
类将 self.visual_control
中的 transformer
属性替换为 ControlTransformer
类的实例。ControlTransformer
是一个自定义的Transformer类,它可能包含了一些额外的逻辑或参数,用于实现对特征的控制。这一步是 DaCLIP
类区别于父类 CLIP
的关键之处,因为它引入了控制机制。复制 logit_scale
参数:
DaCLIP
类创建了 logit_scale
参数的一个深拷贝,并将其存储在 self
中。这样做是为了确保 DaCLIP
实例有自己的 logit_scale
参数,它的初始值与父类 CLIP
实例中的 logit_scale
相同。- class ControlTransformer(nn.Module):
- def __init__(self, transformer):
- super().__init__()
- self.transformer = transformer
- self.layers = transformer.layers
- self.width = transformer.width
-
- self.zero_modules = nn.ModuleList([
- self.zero_module(nn.Linear(self.width, self.width, 1))
- for _ in range(self.layers)]).cuda()
- self.grad_checkpointing = transformer.grad_checkpointing
-
- def zero_module(self, module):
- """
- Zero out the parameters of a module and return it.
- """
- for p in module.parameters():
- p.detach().zero_()
- return module
-
- def forward(self, x: torch.Tensor, attn_mask: Optional[torch.Tensor] = None,
- output_hiddens: Optional[bool] = False, control: Optional[torch.Tensor] = None):
- if output_hiddens:
- hiddens = []
- for z, r in zip(self.zero_modules, self.transformer.resblocks):
- if self.grad_checkpointing and not torch.jit.is_scripting():
- # TODO: handle kwargs https://github.com/pytorch/pytorch/issues/79887#issuecomment-1161758372
- x = checkpoint(r, x, None, None, attn_mask)
- else:
- x = r(x, attn_mask=attn_mask)
- zx = z(x)
-
- if output_hiddens:
- hiddens.append(zx)
-
- if control is not None:
- x += control.pop()
- return (x, hiddens) if output_hiddens else x
这个类的目的是在 Transformer 模型的基础上添加控制机制,以便在前向传播过程中对特征进行调整。下面是对这个类的主要组成部分的详细解释:
初始化 (__init__
方法):
self.transformer
: 存储传入的 Transformer 模型,这个模型的参数将被用于初始化 ControlTransformer
。self.layers
: 获取 Transformer 模型中的层数。self.width
: 获取 Transformer 模型的宽度,即特征的维度。self.zero_modules
: 创建一个模块列表,其中包含 self.layers
个 nn.Linear
层,每个层都是一个全连接层,用于生成控制信号。这些层的权重被初始化为零,这意味着它们不会对输入 x
产生影响,直到它们被进一步训练或调整。self.grad_checkpointing
: 从传入的 Transformer 模型中获取梯度检查点设置。零化模块 (zero_module
方法):
前向传播 (forward
方法):
x
: 输入特征张量。attn_mask
: 可选的注意力掩码,用于 Transformer 中的自注意力机制。output_hiddens
: 可选的布尔值,指示是否输出每个层的隐藏状态。control
: 可选的控制张量,用于调整 Transformer 层的输出。在前向传播过程中,ControlTransformer
执行以下步骤:
output_hiddens
为 True
,则创建一个空列表 hiddens
用于存储每个层的输出。self.zero_modules
和 Transformer 的残差块 (self.transformer.resblocks
)。r
和对应的零化模块 z
:
self.grad_checkpointing
为 True
) 并且不是在 JIT 脚本模式下运行,则使用 checkpoint
函数来保存梯度。r
到输入 x
上,得到输出。z
到输出上,得到 zx
。output_hiddens
为 True
,则将 zx
添加到 hiddens
列表中。control
,则将控制信号添加到输出 x
上。output_hiddens
为 True
,则返回 (x, hiddens)
,否则只返回 x
。ControlTransformer
类的关键特性是它允许通过 control
参数动态调整 Transformer 层的输出,这为模型提供了额外的灵活性和控制能力。
- def initial_controller(self):
- for (kv, param_v), (kc, param_c) in zip(self.clip.visual.named_parameters(), self.visual_control.named_parameters()):
- if 'transformer' not in kv:
- param_c.data.copy_(param_v.data)
-
- for param_v, param_c in zip(self.clip.visual.transformer.parameters(), self.visual_control.transformer.parameters()):
- param_c.data.copy_(param_v.data)
-
- self.logit_scale.data.copy_(self.clip.logit_scale.data)
这个方法的目的是确保 DaCLIP
实例在开始任何进一步的操作之前,其控制塔的参数与父类 CLIP
模型中的视觉塔参数保持一致。这样,控制塔就可以在保持父类模型特征的基础上,通过额外的控制信号来调整输出特征,从而实现更灵活的特征表示。
参数复制:
self.clip.visual
(即父类 CLIP
的视觉塔)的所有参数及其对应的名称。kv
, param_v
)和(kc
, param_c
),如果参数名称不包含 'transformer'
,则将控制塔中相应参数的值设置为视觉塔中参数的值。这样做是为了确保控制塔的非Transformer部分与父类模型的视觉塔具有相同的初始参数。Transformer参数复制:
self.clip.visual.transformer
的所有参数,并将其与 self.visual_control.transformer
的参数进行比较。param_v
, param_c
),将父类模型的视觉塔中的参数值复制到控制塔的对应参数中。这样做是为了确保控制塔的Transformer部分也具有与父类模型相同的初始参数。Logit Scale参数复制:
CLIP
模型中的 logit_scale
参数值复制到 DaCLIP
实例中的同名参数中。- def encode_image(self, image, control=False, normalize: bool = False):
- if control:
- degra_features, hiddens = self.visual_control(image, output_hiddens=True)
- image_features = self.clip.visual(image, control=hiddens)
-
- image_features = F.normalize(image_features, dim=-1) if normalize else image_features
- degra_features = F.normalize(degra_features, dim=-1) if normalize else degra_features
- return image_features, degra_features
- else:
- return self.clip.encode_image(image, normalize)
这个方法提供了两种不同的编码方式,一种是正常的编码,另一种是通过控制塔进行的编码。下面是对这个方法的详细解释:
方法逻辑:
正常编码:
如果 control
参数为 False
,则直接调用父类 CLIP
的 encode_image
方法来编码图像,并返回结果。这是标准的编码流程,不涉及控制塔。
控制编码:
如果 control
参数为 True
,则执行以下步骤:
self.visual_control
方法来生成控制信号。这个方法接收图像作为输入,并返回控制塔的输出特征 degra_features
和每个层的隐藏状态 hiddens
。CLIP
的 visual
属性来编码图像,并将 hiddens
作为控制信号传递给 self.clip.visual
。这样,控制信号就可以影响图像编码的过程。normalize
参数为 True
,则对生成的图像特征 image_features
和 degra_features
进行归一化处理。归一化通常是将特征向量的范数缩放到一个固定值,例如 1,这有助于提高模型的泛化能力。image_features
和控制塔生成的退化特征 degra_features
。这个方法的设计允许 DaCLIP
模型在需要时使用控制信号来调整图像编码,这可以用于各种高级任务,如图像编辑、风格迁移等。通过设置 control
参数,用户可以选择使用标准的图像编码方式,或者使用包含控制信号的编码方式。
- def forward(
- self,
- image: Optional[torch.Tensor] = None,
- text: Optional[torch.Tensor] = None,
- ):
- (caption, degradation) = text.chunk(2, dim=-1) if text is not None else (None, None)
- image_features, image_degra_features = self.encode_image(image, control=True, normalize=True) if image is not None else None
- text_features = self.encode_text(caption, normalize=True) if text is not None else None
- text_degra_features = self.encode_text(degradation, normalize=True) if degradation is not None else None
-
- return {
- "image_features": image_features,
- "text_features": text_features,
- "image_degra_features": image_degra_features,
- "text_degra_features": text_degra_features,
- "logit_scale": self.logit_scale.exp()
- }
forward
方法是 DaCLIP
类的核心方法,它定义了模型如何处理输入的图像和文本数据,并输出相应的特征表示。这个方法接收两个可选参数 image
和 text
,分别代表输入的图像张量和文本张量。以下是该方法的详细解释:
方法逻辑:
处理文本输入:
如果 text
不为 None
,则使用 chunk
方法将文本张量分成两部分,这里假设文本张量是由两部分组成的,可能是描述(caption)和退化(degradation)信息。如果没有提供文本,这两部分都设置为 None
。
编码图像:
如果 image
不为 None
,则调用 self.encode_image
方法来编码图像。这里使用 control=True
来告诉 encode_image
方法使用控制塔生成的特征,normalize=True
表示输出的特征需要进行归一化处理。如果没有提供图像,image_features
和 image_degra_features
将被设置为 None
。
编码文本特征:
使用 self.encode_text
方法分别对 caption
和 degradation
进行编码,生成文本特征和退化特征。如果 caption
或 degradation
为 None
,则不会生成相应的特征。
返回结果:
最后,方法返回一个字典,包含以下键值对:
"image_features"
: 编码后的图像特征。
"text_features"
: 编码后的文本特征。
"image_degra_features"
: 控制塔生成的图像退化特征。
"text_degra_features"
: 控制塔生成的文本退化特征。
"logit_scale"
: logit_scale
参数的指数,通常用于调整模型输出的缩放。
这个方法的设计使得 DaCLIP
模型能够同时处理图像和文本输入,并且能够利用控制塔来调整特征表示。这对于执行复杂的多模态任务非常有用,例如在图像和文本之间建立细粒度的关联,或者在生成任务中控制生成内容的风格和质量。通过这种方式,DaCLIP
模型可以灵活地适应各种应用场景。
经过以上方法生成了DaCLIP模型实例但还未加载模型参数
load_checkpoint(model.clip, checkpoint_path)
model.initial_controller()
model.lock_clip()
在第二章末尾介绍了 load_checkpoint函数,根据checkpoint_path的本地模型参数地址,经过一系列处理后使用model.load_state_dict(state_dict, strict=strict),
初始化了model.clip
关于model.clip:
model.clip
指的是 DaCLIP
类的一个属性,它直接引用了 DaCLIP
实例中的 CLIP
模型。这里的 model
是 DaCLIP
类的一个实例,而 clip_model
是在创建 DaCLIP
实例时传入的 CLIP
模型的实例。
在 DaCLIP
类的 __init__
方法中,clip_model
被用作参数来创建 DaCLIP
实例。这个参数是一个 CLIP
类的实例,它包含了原始的 CLIP
模型的所有组件,如视觉塔(visual
)、文本塔(text
)等。DaCLIP
类通过 self.clip = clip_model
将这个原始的 CLIP
模型实例存储为 DaCLIP
实例的一个属性。
因此,当你访问 model.clip
时,你实际上是访问了 DaCLIP
实例中嵌入的原始 CLIP
模型。这意味着你可以通过 model.clip
访问和使用原始 CLIP
模型的所有功能和属性,例如编码图像和文本、生成特征表示等。这种设计允许 DaCLIP
在保留原始 CLIP
模型功能的基础上,增加额外的控制机制和可能的其他功能。
遍历 DaCLIP
实例中的 CLIP
模型的所有参数,并将它们的 requires_grad
属性设置为 False
。这样做会锁定这些参数,使它们在后续的训练过程中不会更新。
- def lock_clip(self):
- for param in self.clip.parameters():
- param.requires_grad = False
Copyright © 2003-2013 www.wpsshop.cn 版权所有,并保留所有权利。