对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_clipfactory.py
open_clip.create_model_and_transforms,open_clip.create_model_from_pretrained
次外层:open_clipfactory.py
create_model()
第三层:
这个比较多样。以openai模型为例,open_clipopenai.py。还有CoCa、CustomTextCLIP、CLIP等直接创建类实例,而非第五层才创建,该途径需要根据create_model()输入的custom_text布尔值设置。具体见create_model()
load_openai_model()
第四层:open_clipmodel.py
以openai模型为例
build_model_from_openai_state_dict()
第五层:
CLIP类、CustomTextCLIP类 open_clipmodel.py
CoCa类 open_clipcoca_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 模型。函数定义如下:
这个函数的设计允许用户灵活地创建和定制模型,包括选择不同的预训练权重来源、设置运行精度、移动设备、以及加载预处理配置等。
继续深挖该函数寻找'daclip_ViT-B-32'配置藏在哪里,我们可以发现该str为create_model()的输入参数,同级目录下的create_model()函数将近160行代码
参数解释
- : 模型名称,用于指定要创建的模型类型。
- : 预训练权重的来源,可以是一个URL、文件路径或预训练模型的名称。
- : 指定模型运行时的数值精度,如 'fp32', 'fp16', 'bf16' 等。
- : 指定模型应该运行在的设备,如 'cpu' 或具体的GPU设备名称。
- : 是否将模型转换为 PyTorch 的 JIT(Just-In-Time)编译版本。
- : 是否强制使用 QuickGELU 激活函数。
- : 是否强制使用自定义的文本编码器。
- : 是否强制设置特定的补丁丢弃(patch dropout)值。
- : 是否强制设置特定的图像大小。
- : 是否预训练图像塔(image tower)。
- : 是否使用预训练的Hugging Face(HF)文本模型。
- : 缓存目录,用于存储下载的预训练权重和配置文件。
- : 是否将模型的输出格式设置为字典。
代码逻辑配置加载和预训练参数地址加载过程
初步判断:Hugging Face模型检测
检查 是否以Hugging Face Hub的前缀开头,如果是,说明用户希望从HF Hub加载模型,函数会下载相应的配置文件和预训练权重。
否则,根据 加载或设置模型配置。如果 包含特定的路径分隔符(如'/'),则将其替换为连字符('-'),以匹配新的命名约定。
代码很长就不完全截取,只选取与daclip有关部分
输入参数为model_name='daclip_ViT-B-32', pretrained=opt['path']['daclip']
读取配置的简要伪代码上下文:
- 如果指定了 参数并且其值为 'openai',则从OpenAI加载预训练模型。
- 否则,model_cfg = model_cfg or get_model_config(model_name)
- 如果 custom_text值存在
- 包含 'coca'
- 包含'daclip',则创建特定的模型实例。
在else中根据model_name进行了判断,,
model_cfg,是上文Hugging Face模型名称检测中生成的变量,显然daclip不满足该判断。model_cfg为返回值NONE,执行get_model_config()
重返该factor.py文件头部代码查找相关函数和变量,定义了模块级别变量,并执行了模型配置文件扫描函数。
结论:daclip_ViT-B-32.json模型配置定义在model_configs目录下。
配置如下
配置解读:
-
: 嵌入维度,这是模型中嵌入层的维度。在这个配置中,嵌入维度被设置为 512。
-
: 视觉配置对象,包含了构建视觉塔(处理图像的神经网络部分)所需的配置信息:
- : 输入图像的大小,这里设置为 224x224 像素。
- : 视觉塔中的层数,这里设置为 12 层。
- : 视觉塔中层的宽度,即每层的神经元数量,这里设置为 768。
- : 图像分块(patch)的大小,这是视觉变换器中使用的图像分割大小,这里设置为 32x32 像素。
-
: 文本配置对象,包含了构建文本处理部分(如文本编码器)所需的配置信息:
- : 上下文长度,即模型在处理文本时考虑的最大长度,这里设置为 77 个token。
- : 文本词汇表的大小,这里设置为 49,408 个不同的token。
- : 文本处理部分的宽度,即每层的神经元数量,这里设置为 512。
- : 多头注意力机制中的头数,这里设置为 8 个头。
- : 文本处理部分的层数,这里设置为 12 层。
-
: 一个布尔值,指示是否使用自定义的文本处理配置。这里设置为 ,意味着模型将使用特定的文本处理设置,而不是默认的配置。
还是在create_model()函数中
如果模型名称包含 "daclip",则调用 函数来获取与模型名称后缀(去掉 "daclip" 前缀的部分)和预训练配置相关的信息。
这里 是因为 "daclip" 通常是模型名称的前缀。
对 执行 将会返回字符串 。
尽管model_pretrained获取了模型名称对应的地址字典,由于返回函数中执行模型路径判断.get(),对清理后的_clean_tag(tag),即本地test.yml设置的模型参数地址。显然字典里是没有该本地地址的。所以最后函数返回{}。
return model_pretrained.get(_clean_tag(tag), {})
再次回看代码判断,故执行最后一个elseif
elif os.path.exists(pretrained):
checkpoint_path = pretrained
继续进行下列代码。
参数解释
- : 要加载预训练权重的模型实例。
- : 预训练权重文件的路径。
- : 布尔值,指示在加载状态字典时是否要求模型和权重的键完全匹配。
函数逻辑
-
: 调用 函数来加载指定路径下的预训练权重文件,并将其内容存储在 变量中。
-
接下来的代码块检查 中是否存在 键,并且模型实例中没有 属性。这可能意味着模型的结构已经更新,而预训练权重是按照旧格式保存的。
-
如果检测到这种情况,调用 函数来将权重转换成与新模型格式兼容的形式。
-
: 调用 函数来调整(如果需要的话)位置嵌入(positional embedding)的大小,以匹配模型的期望大小。
-
: 使用 方法将处理后的 加载到模型中。这个方法会尝试匹配并加载所有键,如果 为 ,那么只有完全匹配的键才会被加载,不匹配的键将被报告为不兼容的键。如果 为 ,那么即使键不完全匹配,也会尝试加载权重。
-
: 返回一个包含不兼容键的列表,这些键在加载过程中没有被加载到模型中。
返回值
函数返回一个列表,包含在加载过程中检测到的不兼容键。
总结
函数负责加载预训练权重到模型实例中,并处理可能的格式不兼容问题。这个函数确保了即使在模型结构发生变化的情况下,也能够尽可能地加载预训练权重。通过返回不兼容的键,它还提供了有关哪些权重未能加载的信息,这对于调试和进一步的模型调整非常有用
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()
-
: 这行代码创建了一个 类的实例,名为 。这个类的实例化是通过传递 字典中的配置参数以及 参数来完成的。 包含了初始化 模型所需的配置,如嵌入维度、视觉和文本配置等。
-
: 紧接着,使用刚刚创建的 实例作为参数,创建了另一个类的实例,名为 。这个类是 ,它是 类的一个扩展或变体,用于实现特定的功能或适配。
- : 这是 Python 中的参数解包语法,它将 字典中的键值对作为关键字参数传递给 类的构造函数。例如,如果 包含了 ,那么这些键值对将会被用作 类构造函数的参数。
- : 这是一个变量,它指定了模型参数和激活值的数据类型。这个参数可能会被用来设置模型的精度,例如在混合精度训练中使用 或 。
关于cast_dtype,根据create_model()的预设参数和处理函数
precision: str = 'fp32'cast_dtype = get_cast_dtype(precision)
结合model.py 中get_cast_dtyp()的定义,可了解该处理流程
下文只对init()和配置参数传递流程解读,该类下的其他方法可参考
初始化_init_()
参数
- : 嵌入维度,这是模型中嵌入层的维度。
- : 图像配置对象,包含了构建视觉塔(处理图像的神经网络部分)所需的配置信息。
- : 文本配置对象,包含了构建文本处理部分(如文本编码器)所需的配置信息。
- : 布尔值,指示是否使用快速的GELU(Gaussian Error Linear Unit)激活函数。
- : 可选参数,指定数据类型,用于将模型参数转换为指定的数据类型。
- : 布尔值,指示模型输出是否应该是一个字典。
方法体解释
- : 调用父类的构造函数。
- : 存储传入的参数,这可能影响模型输出的格式。
- : 调用一个内部函数来构建视觉塔,并存储结果。
- : 调用一个内部函数来构建文本塔,并存储结果。
- : 从文本塔中提取变换器(transformer)模块。
- : 存储文本塔的上下文长度。
- : 存储文本塔的词汇表大小。
- : 存储文本塔的词嵌入层。
- : 存储文本塔的位置嵌入层。
- : 存储文本塔的最终层归一化(Layer Normalization)。
- : 存储文本塔的文本投影层。
- : 注册一个缓冲区,用于存储文本塔的注意力掩码(attention mask),这个掩码在自注意力机制中用于指示哪些位置应该被模型关注。
- : 创建一个可学习的参数,用于缩放模型的输出(logits),初始化为一个全1的向量,乘以一个基于经验的对数缩放因子。
调用的 CLIPVisionCfg类进行视觉配置
类是一个配置类,用于定义和存储与 CLIP 模型中视觉(图像处理)部分相关的配置参数。这个类提供了一系列的属性,允许用户自定义和初始化 CLIP 模型的视觉塔(Vision Transformer)的各种设置。下面是对这个类的主要属性的解释:
- : 视觉塔中的层数,可以是一个整数或者一个包含四个整数的元组,表示不同层的层数。
- : 视觉塔中每层的宽度,即特征维度。
- : 头部(分类器)的宽度,通常是视觉塔最后一层的特征维度。
- : MLP(多层感知机)中的隐藏层宽度与输入层宽度的比率。
- : 图像分块的大小,决定了如何将图像切分成小块来输入模型。
- : 输入图像的大小,可以是一个整数或者一个包含两个整数的元组,表示图像的宽度和高度。
接下来是一些与正则化和池化相关的选项:
- : 层尺度(Layer Scale)的初始值,用于正则化。
- : 训练过程中要丢弃的补丁(patch)的比例,用于正则化。
- : 是否在每个补丁上使用输入层归一化(input layernorm)。
- : 是否使用全局平均池化来代替使用 CLS 标记的策略。
- : 是否在最后一层嵌入层使用注意力池化器(attentional pooler)。
- : 注意力池化器使用的查询数。
- : 注意力池化器的头数。
最后是与使用 timm 库相关的配置选项:
- : 如果提供了有效的模型名称,将覆盖 , , 等参数。
- : 是否使用预训练的(在 ImageNet 上)timm 模型权重。
- : timm 模型的特征池化类型。
- : timm 模型输出的线性投影类型。
- : 是否在最终投影中启用偏置。
- : 头部丢弃(head dropout)的比率。
- : 背部随机深度(backbone stochastic depth)的比率。
调用的 CLIPTextCfg进行文本处理相关配置
类是一个配置类,用于定义和存储与 CLIP 模型中文本处理部分相关的配置参数。这个类提供了一系列的属性,允许用户自定义和初始化 CLIP 模型的文本塔(Text Transformer)的各种设置。下面是对这个类的主要属性的解释:
- : 文本处理的上下文长度,即模型一次性处理的最大文本长度。
- : 文本词汇表的大小,表示模型能够识别的不同词汇的数量。
- : 文本塔中每层的宽度,即特征维度。
- : 多头注意力机制中的头数,用于并行处理信息。
- : 文本塔中的层数。
- : 层尺度(Layer Scale)的初始值,用于正则化。
接下来是与使用 Hugging Face(HF)相关的配置选项:
- : Hugging Face 模型的名称,用于加载预训练的文本模型。
- : Hugging Face 分词器的名称,用于将文本转换为模型可以理解的格式。
- : 是否使用预训练的 Hugging Face 模型权重。
其他配置选项:
- : 文本投影的类型,这里是 'mlp',表示使用多层感知机进行投影。
- : 池化器的类型,这里是 'mean_pooler',表示使用平均池化器来聚合文本信息。
- : 是否将分类标记(通常是一个特殊的 [CLS] 标记)嵌入到文本中。
- : 填充标记的 ID,用于处理不同长度的文本序列。
- : 是否输出每个文本标记的特征,而不是仅仅输出文本的整体表示
_build_vision_tower():根据提供的配置创建不同的视觉模型架构
参数解释
- : 嵌入维度,这是模型中嵌入层的维度。
- : 视觉配置对象,包含了构建视觉塔所需的配置信息。
- : 布尔值,指示是否使用快速的GELU激活函数。
- : 可选参数,指定数据类型,用于将模型参数转换为指定的数据类型。
函数逻辑
-
如果 是一个字典,那么使用这个字典来创建一个 实例。
-
根据 参数的值选择使用 激活层还是使用 PyTorch 原生的 。
-
如果 包含 ,则使用 来创建一个基于 timm 库的模型。
-
如果 是一个元组或列表,假设配置是一个修改版的 ResNet 架构,使用 来创建模型。
-
如果上述条件都不满足,那么使用 来创建一个标准的 Vision Transformer 架构。
-
在创建 时,根据 中的参数配置模型的不同部分,如层数、头数、激活层、归一化层等。
-
返回创建好的 模型实例。
_build_text_tower()创建不同的文本模型架构
函数逻辑
-
如果 是一个字典,那么使用这个字典来创建一个 实例。
-
如果 包含 ,则使用 来创建一个基于 Hugging Face 的文本编码器。
-
如果上述条件不满足,那么使用 来创建一个标准的文本 Transformer 架构。
-
在创建 时,根据 中的参数配置模型的不同部分,如上下文长度、词汇表大小、激活层、归一化层等。
-
返回创建好的 模型实例。
根据daclip_ViT-B-32.json文件里的字典通过__init__()到_build_vision_tower()再到
CLIPVisionCfg()这样的流程完成了参数传递。
在这个例子中, 类是父类,而 类是子类。 类继承自 类,并在其基础上进行了扩展和修改。以下是子类 相对于父类 所做的主要更改和添加:
-
初始化 ( 方法):
- 类在初始化时创建了 的一个副本,命名为 ,并创建了一个名为 的控制塔,它是 的深拷贝,但使用了 替换了原有的 。
- 还复制了父类的 参数。
-
控制塔 ( 方法):
- 类提供了一个方法来初始化控制塔的参数,确保它们与父类的 塔的参数一致。
-
锁定 ( 方法):
- 类添加了一个方法来锁定父类 的所有参数,使其在训练过程中不会更新。
-
梯度检查点设置 ( 方法):
- 类扩展了父类的方法,同时为 和 设置梯度检查点。
-
图像编码 ( 方法):
- 类重写了图像编码方法,允许控制塔生成特征,并将这些特征与父类 生成的特征结合起来。
-
前向传播 ( 方法):
- 类修改了前向传播方法,以处理额外的 输入,并输出控制塔生成的图像和文本的退化特征。
init方法
创建 :
- 在 类的 方法中,首先创建了一个名为 的属性,它是 (即父类 的实例)的 属性的一个引用。这意味着 直接指向了父类中的 属性,它们指向相同的对象。
深拷贝 以创建 :该代码就是论文所需的Image Controller部分
- 接下来,使用 方法对 进行了深拷贝,创建了一个新的对象 。深拷贝意味着创建了 中所有属性和子对象的完整副本,而不是简单地复制引用。这样, 就是一个独立的、与 完全相同的新对象,对它的任何修改都不会影响原始的 。
替换 中的 :
- 然后, 类将 中的 属性替换为 类的实例。 是一个自定义的Transformer类,它可能包含了一些额外的逻辑或参数,用于实现对特征的控制。这一步是 类区别于父类 的关键之处,因为它引入了控制机制。
复制 参数:
- 最后, 类创建了 参数的一个深拷贝,并将其存储在 中。这样做是为了确保 实例有自己的 参数,它的初始值与父类 实例中的 相同。
ControlTransformer类
这个类的目的是在 Transformer 模型的基础上添加控制机制,以便在前向传播过程中对特征进行调整。下面是对这个类的主要组成部分的详细解释:
初始化 ( 方法):
- : 存储传入的 Transformer 模型,这个模型的参数将被用于初始化 。
- : 获取 Transformer 模型中的层数。
- : 获取 Transformer 模型的宽度,即特征的维度。
- : 创建一个模块列表,其中包含 个 层,每个层都是一个全连接层,用于生成控制信号。这些层的权重被初始化为零,这意味着它们不会对输入 产生影响,直到它们被进一步训练或调整。
- : 从传入的 Transformer 模型中获取梯度检查点设置。
零化模块 ( 方法):
- 这个方法用于将给定模块的参数设置为零。这在初始化控制模块时很有用,因为它确保了控制模块在训练开始时不会对模型的行为产生影响。
前向传播 ( 方法):
- : 输入特征张量。
- : 可选的注意力掩码,用于 Transformer 中的自注意力机制。
- : 可选的布尔值,指示是否输出每个层的隐藏状态。
- : 可选的控制张量,用于调整 Transformer 层的输出。
在前向传播过程中, 执行以下步骤:
- 如果 为 ,则创建一个空列表 用于存储每个层的输出。
- 遍历 和 Transformer 的残差块 ()。
- 对于每个残差块 和对应的零化模块 :
- 如果启用了梯度检查点 ( 为 ) 并且不是在 JIT 脚本模式下运行,则使用 函数来保存梯度。
- 应用残差块 到输入 上,得到输出。
- 应用零化模块 到输出上,得到 。
- 如果 为 ,则将 添加到 列表中。
- 如果提供了控制张量 ,则将控制信号添加到输出 上。
- 如果 为 ,则返回 ,否则只返回 。
类的关键特性是它允许通过 参数动态调整 Transformer 层的输出,这为模型提供了额外的灵活性和控制能力。
initial_controller(self)
这个方法的目的是确保 实例在开始任何进一步的操作之前,其控制塔的参数与父类 模型中的视觉塔参数保持一致。这样,控制塔就可以在保持父类模型特征的基础上,通过额外的控制信号来调整输出特征,从而实现更灵活的特征表示。
参数复制:
- 方法首先遍历 (即父类 的视觉塔)的所有参数及其对应的名称。
- 对于每一对参数(, )和(, ),如果参数名称不包含 ,则将控制塔中相应参数的值设置为视觉塔中参数的值。这样做是为了确保控制塔的非Transformer部分与父类模型的视觉塔具有相同的初始参数。
Transformer参数复制:
- 接下来,方法遍历 的所有参数,并将其与 的参数进行比较。
- 对于每一对参数(, ),将父类模型的视觉塔中的参数值复制到控制塔的对应参数中。这样做是为了确保控制塔的Transformer部分也具有与父类模型相同的初始参数。
Logit Scale参数复制:
- 最后,方法将父类 模型中的 参数值复制到 实例中的同名参数中。
encode_image()
这个方法提供了两种不同的编码方式,一种是正常的编码,另一种是通过控制塔进行的编码。下面是对这个方法的详细解释:
方法逻辑:
-
正常编码:
如果 参数为 ,则直接调用父类 的 方法来编码图像,并返回结果。这是标准的编码流程,不涉及控制塔。
-
控制编码:
如果 参数为 ,则执行以下步骤:
- 首先,调用 方法来生成控制信号。这个方法接收图像作为输入,并返回控制塔的输出特征 和每个层的隐藏状态 。
- 然后,使用父类 的 属性来编码图像,并将 作为控制信号传递给 。这样,控制信号就可以影响图像编码的过程。
- 如果 参数为 ,则对生成的图像特征 和 进行归一化处理。归一化通常是将特征向量的范数缩放到一个固定值,例如 1,这有助于提高模型的泛化能力。
- 最后,返回处理后的图像特征 和控制塔生成的退化特征 。
这个方法的设计允许 模型在需要时使用控制信号来调整图像编码,这可以用于各种高级任务,如图像编辑、风格迁移等。通过设置 参数,用户可以选择使用标准的图像编码方式,或者使用包含控制信号的编码方式。
forward()
方法是 类的核心方法,它定义了模型如何处理输入的图像和文本数据,并输出相应的特征表示。这个方法接收两个可选参数 和 ,分别代表输入的图像张量和文本张量。以下是该方法的详细解释:
方法逻辑:
处理文本输入:
如果 不为 ,则使用 方法将文本张量分成两部分,这里假设文本张量是由两部分组成的,可能是描述(caption)和退化(degradation)信息。如果没有提供文本,这两部分都设置为 。
编码图像:
如果 不为 ,则调用 方法来编码图像。这里使用 来告诉 方法使用控制塔生成的特征, 表示输出的特征需要进行归一化处理。如果没有提供图像, 和 将被设置为 。
编码文本特征:
使用 方法分别对 和 进行编码,生成文本特征和退化特征。如果 或 为 ,则不会生成相应的特征。
返回结果:
最后,方法返回一个字典,包含以下键值对:
: 编码后的图像特征。
: 编码后的文本特征。
: 控制塔生成的图像退化特征。
: 控制塔生成的文本退化特征。
: 参数的指数,通常用于调整模型输出的缩放。
这个方法的设计使得 模型能够同时处理图像和文本输入,并且能够利用控制塔来调整特征表示。这对于执行复杂的多模态任务非常有用,例如在图像和文本之间建立细粒度的关联,或者在生成任务中控制生成内容的风格和质量。通过这种方式, 模型可以灵活地适应各种应用场景。
经过以上方法生成了DaCLIP模型实例但还未加载模型参数
load_checkpoint(model.clip, checkpoint_path)
model.initial_controller()
model.lock_clip()
load_checkpoint
在第二章末尾介绍了 load_checkpoint函数,根据checkpoint_path的本地模型参数地址,经过一系列处理后使用初始化了model.clip
关于model.clip:
指的是 类的一个属性,它直接引用了 实例中的 模型。这里的 是 类的一个实例,而 是在创建 实例时传入的 模型的实例。
在 类的 方法中, 被用作参数来创建 实例。这个参数是一个 类的实例,它包含了原始的 模型的所有组件,如视觉塔()、文本塔()等。 类通过 将这个原始的 模型实例存储为 实例的一个属性。
因此,当你访问 时,你实际上是访问了 实例中嵌入的原始 模型。这意味着你可以通过 访问和使用原始 模型的所有功能和属性,例如编码图像和文本、生成特征表示等。这种设计允许 在保留原始 模型功能的基础上,增加额外的控制机制和可能的其他功能。
调用DaCLIP的initial_controller()参考上文
lock_clip冻结预训练模型
遍历 实例中的 模型的所有参数,并将它们的 属性设置为 。这样做会锁定这些参数,使它们在后续的训练过程中不会更新。