当前位置:   article > 正文

源码解读:InternVL 1.5_internvl 代码详解

internvl 代码详解

源码地址:https://github.com/OpenGVLab/InternVL/tree/main/internvl_chat/internvl/model/internvl_chat

配置

在internvl_chat/internvl/model/internvl_chat/configuration_intern_vit.py

用InternVisionConfig类来设置类,用于初始化模型

			num_channels=3,
            patch_size=14,
            image_size=224,
            qkv_bias=False,
            hidden_size=3200,
            num_attention_heads=25,
            intermediate_size=12800,
            qk_normalization=True,
            num_hidden_layers=48,
            use_flash_attn=True,
            hidden_act='gelu',
            norm_type='rms_norm',
            layer_norm_eps=1e-6,
            dropout=0.0,
            drop_path_rate=0.0,
            attention_dropout=0.0,
            initializer_range=0.02,
            initializer_factor=0.1,
            **kwargs,
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11
  • 12
  • 13
  • 14
  • 15
  • 16
  • 17
  • 18
  • 19

重要的参数:
1.num_channels:输入图像的颜色通道数量。这是一个与图像数据格式相关的重要参数。不同的图像格式具有不同的颜色通道数量。

以下是一些常见的分类和解释:

  1. RGB 图像

    • 通道数:3
    • 描述:RGB 图像使用三个颜色通道来表示颜色信息,即红色(Red)、绿色(Green)和蓝色(Blue)。这是最常见的图像格式,用于大多数彩色图像。
    • 如何处理:RGB 图像中的每个像素由三个值组成,分别表示红、绿、蓝的强度。这三个值通常在 0 到 255 之间(对于 8 位图像)。
  2. 灰度图像

    • 通道数:1
    • 描述:灰度图像只有一个通道,用于表示亮度信息,没有颜色信息。每个像素的值表示该点的灰度强度。
    • 如何处理:灰度图像中的每个像素通常在 0 到 255 之间,0 表示黑色,255 表示白色。
  3. RGBA 图像

    • 通道数:4
    • 描述:RGBA 图像在 RGB 的基础上增加了一个透明度通道(Alpha 通道),用于表示像素的透明度。
    • 如何处理:RGBA 图像中的每个像素由四个值组成,分别表示红、绿、蓝的强度和透明度。这四个值通常在 0 到 255 之间。
  4. 其他多通道图像

    • 通道数:任意数量
    • 描述:某些专业领域(如医学成像、遥感)可能使用超过三个或四个通道的图像。例如,多光谱图像和超光谱图像可以有数十个甚至数百个通道,每个通道对应不同波段的光谱信息。
    • 如何处理:这些图像中的每个像素由多个值组成,每个值表示在不同光谱波段的强度。处理这类图像通常需要特定的算法和工具。

2.patch_size:定义了输入图像被划分成的小块(patch)的大小。

patch_size 指的是将图像划分成的小块(patch)的边长。例如,如果 patch_size 设置为 16,那么每个
patch 的大小就是 16x16 像素。

为什么使用 Patch?

在基于 Transformer 的视觉模型中,输入图像会被划分成若干个不重叠的 patch,这些 patch
会被展平并线性嵌入到较高维度的向量中,随后作为 Transformer
的输入。这样做是为了将图像转换成类似于自然语言处理(NLP)中的词嵌入(word embedding)的形式,使得图像数据可以用
Transformer 模型进行处理。

Patch Size 的影响

  1. Patch 数量

    • 图像的分辨率是固定的,例如 224x224 像素。如果 patch_size 设置为 16,那么整个图像会被划分成 (224/16) x (224/16) = 14 x 14 = 196 个 patch。
    • 较小的 patch size 会导致更多的 patch,从而提供更细粒度的图像信息。
    • 较大的 patch size 会导致较少的 patch,从而减少模型的计算复杂度,但可能丢失一些细节信息。
  2. 计算复杂度

    • 较大的 patch size 通常会降低模型的计算复杂度,因为需要处理的 patch 数量减少了。
    • 较小的 patch size 会增加计算复杂度,但可能提高模型的细节捕捉能力。
  3. 特征提取

    • 较小的 patch size 可以更好地捕捉图像中的细微特征和局部模式。
    • 较大的 patch size 更关注全局信息和较大范围的特征。

3.qk_normalization:指在自注意力机制中对查询(queries)和键(keys)进行归一化处理。

归一化 Queries 和 Keys 的原因

  1. 稳定数值计算

    • 在没有归一化的情况下,( Q ) 和 ( K ) 的值可能会很大,导致 ( QK^T ) 的值也会很大。这样,经过 softmax 后的值会变得非常尖锐(即,大部分权重集中在几个元素上)。
    • 归一化可以将 ( Q ) 和 ( K ) 的值缩小到较小的范围,从而避免数值计算中的溢出问题,使计算更加稳定。
  2. 提高模型的性能和收敛速度

    • 归一化后的 ( Q ) 和 ( K ) 可以使注意力权重更加平滑和稳定,避免过度依赖某些特定的查询或键。
    • 这有助于模型在训练过程中更快地收敛,并可能提高模型的整体性能。
  3. 避免极端权重

    • 未归一化的情况下,某些查询和键的点积可能会非常大,导致 softmax 函数在这些位置生成接近 1 的值,而在其他位置生成接近 0 的值。这样会导致模型的注意力机制过于集中在某些位置,忽略了其他重要的信息。
    • 归一化有助于避免这种极端权重的情况,使得注意力机制更加平衡和合理。

归一化的实现

常见的归一化方法包括:

  • 标准化:将每个向量减去其均值,并除以其标准差,使得数据符合标准正态分布。
  • 单位向量归一化:将向量除以其 L2 范数,使其长度为 1。这种方法通常用于 self-attention 机制。

总结

对查询和键进行归一化可以稳定数值计算,提高模型的性能和收敛速度,并避免极端权重情况的发生。这种归一化处理在实际应用中已经证明是有效的,可以帮助模型在复杂的任务中表现得更好。

from_pretrained函数接受cls和pretrained_model_name_or_path参数,用于从预训练模型的路径或名称加载配置。
cls 是一个用于类方法中的通用参数名,指代调用该方法的类本身。

同级的chat文件配置

继承了上个VFM的配置,然后配置了LLM的参数。
1.use_backbone_lora:指定是否在模型的骨干网络中使用 LoRA(Low-Rank Adaptation)技术。
2.pad2square:是否将输入图像填充成正方形。
3.force_image_size:指定一个固定的图像尺寸。
4.downsample_ratio:在处理图像时,输入图像可以通过下采样来减少尺寸。这个参数指定下采样的比例,例如 0.5 表示将图像尺寸减半。
5.use_thumbnail:是否使用缩略图。
6.ps_version:像素混洗(pixel shuffle)的版本。

PixelShuffle 的基本概念

PixelShuffle
是一种将低分辨率特征图转换为高分辨率图像的上采样方法。它通过重新排列低分辨率特征图中的元素来生成高分辨率图像。这个过程通常用于图像超分辨率任务,即从低分辨率图像生成高分辨率图像。

工作原理

PixelShuffle 的核心思想是将特征图的通道维度转换为空间维度。具体步骤如下:

  1. 低分辨率特征图

    • 输入一个形状为 ((N, C \times r^2, H, W)) 的低分辨率特征图,其中 (N) 是批量大小,(C) 是通道数,(H) 和 (W) 是特征图的高度和宽度,(r) 是上采样倍数。
  2. 通道转换

    • 将通道数 (C \times r^2) 拆分为 (r \times r) 个小块,每个小块的通道数为 (C)。
  3. 重新排列

    • 将这些小块重新排列到空间维度,生成一个新的特征图,形状为 ((N, C, H \times r, W \times r))。

通过这个过程,特征图的分辨率从 ((H, W)) 增加到 ((H \times r, W \times r)),实现上采样。

应用场景

PixelShuffle 主要用于以下场景:

  1. 图像超分辨率:提高图像分辨率,从低分辨率图像生成高分辨率图像。
  2. 上采样:在生成模型(如生成对抗网络,GAN)中用于生成高分辨率图像。
  3. 视频处理:提高视频帧的分辨率,实现视频的超分辨率。

视觉模型类

modeling_intern_vit.py文件

InternVisionEmbeddings 视觉嵌入类
InternAttention 注意力机制
InternMLP MLP实现
InternVisionEncoderLayer 单层的encoder
InternVisionEncoder 完整的encoder
InternVisionModel 视觉模型类

直接给出模型类的注释

# 视觉模型类
class InternVisionModel(PreTrainedModel):
    main_input_name = 'pixel_values'
    config_class = InternVisionConfig
    _no_split_modules = ['InternVisionEncoderLayer']

    # 参数定义,从预定义的嵌入类和encoder类加载
    def __init__(self, config: InternVisionConfig):
        super().__init__(config)
        self.config = config

        self.embeddings = InternVisionEmbeddings(config)
        self.encoder = InternVisionEncoder(config)

    # 调整位置嵌入,适应不同尺寸的图像
    def resize_pos_embeddings(self, old_size, new_size, patch_size):
        # 从嵌入类获取位置嵌入
        pos_emb = self.embeddings.position_embedding
        _, num_positions, embed_dim = pos_emb.shape # 获取位置数量、嵌入维度
        cls_emb = pos_emb[:, :1, :] # 取出第一个位置的嵌入,是分类嵌入,表示图像整体的信息
        # 针对其他位置的嵌入,调整形状,变为(1, embed_dim, old_size // patch_size, old_size // patch_size)
        pos_emb = pos_emb[:, 1:, :].reshape(1, old_size // patch_size, old_size // patch_size, -1).permute(0, 3, 1, 2)
        # 使用 F.interpolate 函数对位置嵌入进行双三次插值,将其尺寸调整为 (new_size // patch_size, new_size // patch_size)
        pos_emb = F.interpolate(pos_emb.float(), size=new_size // patch_size, mode='bicubic', align_corners=False) # 上采样或下采样
        # 还原位置嵌入形状
        pos_emb = pos_emb.to(cls_emb.dtype).reshape(1, embed_dim, -1).permute(0, 2, 1)
        # 拼接分类嵌入和位置嵌入
        pos_emb = torch.cat([cls_emb, pos_emb], dim=1)
        # 将新的位置嵌入赋值给模型的嵌入参数,并更新图像尺寸
        self.embeddings.position_embedding = nn.Parameter(pos_emb) # 将一个张量转换为模型的可学习参数
        self.embeddings.image_size = new_size
        logger.info('Resized position embeddings from {} to {}'.format(old_size, new_size))

    # 返回图像嵌入
    def get_input_embeddings(self):
        return self.embeddings

    # 将输入图像数据转换为特征表示
    def forward(
            self,
            pixel_values: Optional[torch.FloatTensor] = None, # 输入图像的像素值,形状为 (batch_size, channels, height, width)
            output_hidden_states: Optional[bool] = None, # 是否输出所有层的隐藏状态
            return_dict: Optional[bool] = None, # 是否返回字典格式的输出
            pixel_embeds: Optional[torch.FloatTensor] = None, # 预先计算好的图像嵌入特征,形状为 (batch_size, sequence_length, hidden_size)
    ) -> Union[Tuple, BaseModelOutputWithPooling]:
        
        # 是否输出所有层的隐藏状态,
        output_hidden_states = (
            output_hidden_states if output_hidden_states is not None else self.config.output_hidden_states
        )
        # 是否返回字典格式的输出
        return_dict = return_dict if return_dict is not None else self.config.use_return_dict

        # 检查输入,要么输入像素值,要么输入嵌入
        if pixel_values is None and pixel_embeds is None:
            raise ValueError('You have to specify pixel_values or pixel_embeds')
        
        # 优先选择图像嵌入,将其作为隐藏状态
        if pixel_embeds is not None:
            hidden_states = pixel_embeds
        else: # 否则,检查图像像素值的形状,拿他生成嵌入,作为隐藏状态
            if len(pixel_values.shape) == 4:
                hidden_states = self.embeddings(pixel_values)
            else:
                raise ValueError(f'wrong pixel_values size: {
     pixel_values.shape}')
        
        # 将隐藏状态输入到编码器 self.encoder 中,得到编码器的输出
        encoder_outputs = self.encoder(
            inputs_embeds=hidden_states,
            output_hidden_states=output_hidden_states,
            return_dict=return_dict,
        )
        # 从编码器输出中取出最后的隐藏状态 last_hidden_state,迭代取出第一个位置的输出,是池化的分类嵌入
        last_hidden_state = encoder_outputs.last_hidden_state
        pooled_output = last_hidden_state[:, 0, :]

        # 如果 return_dict 为 False,返回元组格式的结果,包括最后的隐藏状态、池化输出和编码器的其他输出
        if not return_dict: # + 运算符用于元组时,它的作用是连接两个元组
            return (last_hidden_state, pooled_output) + encoder_outputs[1:] # (last_hidden_state, pooled_output, hidden_states, attentions)

        # 如果是字典形式的输出,就返回 BaseModelOutputWithPooling 对象,包含最后的隐藏状态、池化输出、隐藏状态和注意力。
        return BaseModelOutputWithPooling(
            last_hidden_state=last_hidden_state,
            pooler_output=pooled_output,
            hidden_states=encoder_outputs.hidden_states,
            attentions=encoder_outputs.attentions,
        )

  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11
  • 12
  • 13
  • 14
  • 15
  • 16
  • 17
  • 18
  • 19
  • 20
  • 21
  • 22
  • 23
  • 24
  • 25
  • 26
  • 27
  • 28
  • 29
  • 30
  • 31
  • 32
  • 33
  • 34
  • 35
  • 36
  • 37
  • 38
  • 39
  • 40
  • 41
  • 42
  • 43
  • 44
  • 45
  • 46
  • 47
  • 48
  • 49
  • 50
  • 51
  • 52
  • 53
  • 54
  • 55
  • 56
  • 57
  • 58
  • 59
  • 60
  • 61
  • 62
  • 63
  • 64
  • 65
  • 66
  • 67
  • 68
  • 69
  • 70
  • 71
  • 72
  • 73
  • 74
  • 75
  • 76
  • 77
  • 78
  • 79
  • 80
  • 81
  • 82
  • 83
  • 84
  • 85
  • 86
  • 87
  • 88
  • 89

chat模型类

初始化了很多参数,主要还是VFM和LLM、MLP,还有是否对这两个模型使用Lora化,以减少训练的参数量。

前向传播

输入图像的像素值、文本的id、图像标志、标签,返回loss、预测的概率等。
重点在于

input_embeds[selected] = input_embeds[selected] * 0.0 + vit_embeds.reshape(-1, C)
  • 1

将图片和文本嵌入拼接。

像素混洗函数 pixel_shuffle

自己实现的。

声明:本文内容由网友自发贡献,不代表【wpsshop博客】立场,版权归原作者所有,本站不承担相应法律责任。如您发现有侵权的内容,请联系我们。转载请注明出处:https://www.wpsshop.cn/w/酷酷是懒虫/article/detail/909963
推荐阅读
相关标签
  

闽ICP备14008679号