(5-2-01)DeepSeek多模态大模型架构:Janus模型(1)

5.2  Janus模型

Janus多模态模型的设计核心在于视觉编码的解耦。传统多模态模型通常使用单一的视觉编码器来处理多模态理解和视觉生成任务,但由于这两种任务对视觉特征的需求存在显著差异,单一编码器往往难以同时满足两种任务的需求,从而导致性能瓶颈。为了解决这一问题,Janus模型提出了双路径视觉编码架构,将多模态理解和视觉生成任务的视觉编码过程完全分离,从而避免了任务间的冲突,并显著提升了模型在多模态任务中的表现。

5.2.1  架构介绍

Janus模型的整体架构基于自回归Transformer,这是一种强大的序列生成框架,广泛应用于自然语言处理和多模态任务中。自回归Transformer通过逐个生成序列中的元素(如文本中的单词或图像中的像素),能够有效地捕捉序列中的依赖关系。在Janus模型中,自回归Transformer不仅处理文本输入,还整合了来自视觉模态的特征,从而实现多模态数据的统一处理。

1. 视觉编码路径

Janus模型的核心设计是将视觉编码分为如下两个独立的路径:

(1)多模态理解:专门用于处理需要理解图像语义的任务,如视觉问答(VQA)、图像描述生成、图文匹配等。这一路径的目标是从图像中提取高维语义特征,并将其映射到与语言模型兼容的输入空间。

(2)视觉生成:专门用于处理需要生成图像的任务,如文本到图像的生成。这一路径的目标是将图像转换为离散的视觉token,并通过生成适配器将其嵌入到语言模型的输入空间中。

这两种路径分别处理不同任务的输入数据,但最终会将生成的特征序列拼接在一起,形成一个统一的多模态特征序列,这个序列随后被输入到自回归Transformer中进行进一步处理。通过这种设计,Janus模型能够在同一个框架下高效地处理多模态理解和视觉生成任务,同时避免了传统模型中视觉编码器在两种任务间的功能冲突。

2. Janus架构设计的优势

Janus多模态模型通过解耦视觉编码路径,实现了多模态理解和视觉生成任务的高效统一。这种架构设计不仅解决了传统模型中视觉编码器的功能冲突问题,还提升了模型的性能和扩展性。具体来说,Janus模型的架构设计带来了以下显著优势。

  1. 解耦视觉编码:通过将视觉编码分为两个独立路径,Janus模型能够分别优化多模态理解和视觉生成任务。这种解耦设计避免了传统模型中视觉编码器在这两种任务中的功能冲突,使得模型能够更好地处理复杂的多模态任务。例如,在多模态理解任务中,模型可以专注于提取图像的语义信息;而在视觉生成任务中,模型可以专注于生成高质量的图像内容。
  2. 高效扩展性:Janus模型的架构设计支持模型规模的扩展。例如,Janus-Pro版本将模型参数扩展到7B,显著提升了模型在多模态理解和视觉生成任务中的性能。这种扩展性使得模型能够处理更复杂的任务,并生成更高质量的输出。
  3. 统一框架:尽管Janus模型将视觉编码分为两个独立路径,但整个模型仍然在同一个自回归Transformer框架下运行。这种统一框架简化了训练和推理过程,使得模型能够高效地处理多模态数据。同时,这种设计也使得模型能够灵活地扩展到其他多模态任务,如视频理解、多模态对话等。

5.2.2  多模态理解

多模态理解的目标是从图像中提取丰富的语义信息,以支持视觉问答、图像描述等任务。为了实现这一目标,Janus 模型在这一部分采用了专门设计的模块和操作流程,具体说明如下所示。

1. 视觉编码器

Janus 模型选用了 SigLIP 编码器作为多模态理解路径的核心。SigLIP 是一种基于 Transformer 的视觉编码器,设计初衷是捕捉图像中的高层语义信息,SigLIP的工作如下:

  1. 高维语义特征:SigLIP 能够从图像中提取出既包含整体语义(如场景、对象类别)又兼顾细节和局部关系的高维特征。这些特征不仅能描述图像的全局内容,还能捕捉图像中细微的纹理和结构信息。
  2. 与语言模型兼容:由于 SigLIP 的设计考虑到了与语言模型的融合需求,其输出特征的格式和分布经过精心设计,从而能够无缝地与文本特征对齐,为后续的多模态融合打下基础。

2. 特征处理

SigLIP 编码器输出的是一个二维的特征图,这个特征图类似于一个由多个特征向量构成的网格,每个向量对应图像中某一局部区域的语义描述。为了更好地利用这些视觉特征,Janus 模型在后续处理中引入了两项关键策略,这两项策略相辅相成,共同确保了视觉信息能够在多模态建模中发挥最大效用。特征处理的工作如下:

  1. 保留空间顺序信息:为了让这些视觉特征能够被自回归 Transformer 模型处理,Janus 模型将二维特征图展平为一维序列。在展平过程中,模型不仅简单地将二维矩阵转换为线性序列,还保留了原始空间中的顺序信息,这对于捕捉图像中局部与全局语义关系至关重要。
  2. 统一格式:与此同时,展平后的特征序列与文本 token 序列格式保持一致,使得后续的多模态融合和自回归建模能够在同一输入空间中进行。这种格式统一有助于模型同时考虑图像与文本信息的上下文关联,从而实现更有效的信息融合。

3. 理解适配器

为了将展平后的高维视觉特征进一步映射到与语言模型相同的嵌入空间,Janus 模型引入了一个两层多层感知机(MLP)作为理解适配器。

  1. 特征映射:该适配器对输入的视觉特征进行非线性变换,使得这些特征能够更好地表达与文本信息对应的语义含义。经过映射后的视觉特征与文本特征在语义层面上实现了对齐,便于后续的跨模态融合。
  2. 无缝融合:通过这种映射操作,理解适配器确保了从图像中提取的语义信息能够与文本数据结合在一起,形成一个统一的多模态输入序列。这样,模型在处理诸如视觉问答和图像描述任务时,可以直接利用来自不同模态的互补信息,从而提高理解准确性和生成质量。

在DeepSeek的开源代码中,文件projector.py定义了一个名为 MlpProjector 的类,作为一个多层感知机(MLP)投影器,用于将输入数据映射到指定的嵌入空间。在多模态模型中,投影器是连接不同模态特征的关键组件。MlpProjector投影器根据配置(cfg)的不同,支持多种类型的投影方式:identity、linear、mlp_gelu、和 low_high_hybrid_split_mlp_gelu。如果配置为 low_high_hybrid_split_mlp_gelu,会将输入分为两部分(高分辨率和低分辨率),然后分别进行投影并合并。前向传播方法接受一个输入(可以是元组,也可以是单一张量),并通过配置的投影器进行转换,返回投影后的结果。

class MlpProjector(nn.Module):
    def __init__(self, cfg):
        super().__init__()

        self.cfg = cfg

        if cfg.projector_type == "identity":
            modules = nn.Identity()

        elif cfg.projector_type == "linear":
            modules = nn.Linear(cfg.input_dim, cfg.n_embed)

        elif cfg.projector_type == "mlp_gelu":
            mlp_depth = cfg.get("depth", 1)
            modules = [nn.Linear(cfg.input_dim, cfg.n_embed)]
            for _ in range(1, mlp_depth):
                modules.append(nn.GELU())
                modules.append(nn.Linear(cfg.n_embed, cfg.n_embed))
            modules = nn.Sequential(*modules)

        elif cfg.projector_type == "low_high_hybrid_split_mlp_gelu":
            mlp_depth = cfg.get("depth", 1)
            self.high_up_proj = nn.Linear(cfg.input_dim, cfg.n_embed // 2)
            self.low_up_proj = nn.Linear(cfg.input_dim, cfg.n_embed // 2)

            modules = []
            for _ in range(1, mlp_depth):
                modules.append(nn.GELU())
                modules.append(nn.Linear(cfg.n_embed, cfg.n_embed))
            modules = nn.Sequential(*modules)

        else:
            raise ValueError(f"Unknown projector type: {cfg.projector_type}")

        self.layers = modules

    def forward(
        self, x_or_tuple: Union[Tuple[torch.Tensor, torch.Tensor], torch.Tensor]
    ):
        """
        参数:
            x_or_tuple (Union[Tuple[torch.Tensor, torch.Tensor], torch.Tensor]):如果是一个元组,则来自混合视觉编码器,其中x = high_res_x,low_res_x;
                否则,它是来自单一视觉编码器的特征。

        返回:
            x (torch.Tensor):[b, s, c]
        """

        if isinstance(x_or_tuple, tuple):
            # self.cfg.projector_type == "low_high_hybrid_split_mlp_gelu":
            high_x, low_x = x_or_tuple
            high_x = self.high_up_proj(high_x)
            low_x = self.low_up_proj(low_x)
            x = torch.concat([high_x, low_x], dim=-1)
        else:
            x = x_or_tuple

        return self.layers(x)


if __name__ == "__main__":
    cfg = AttrDict(
        input_dim=1024,
        n_embed=2048,
        depth=2,
        projector_type="low_high_hybrid_split_mlp_gelu",
    )
    inputs = (torch.rand(4, 576, 1024), torch.rand(4, 576, 1024))

    m = MlpProjector(cfg)
    out = m(inputs)
    print(out.shape)

通过上述流程,Janus 的多模态模型有效地将图像中的视觉信息转换为与文本特征相兼容的表示。该过程不仅保留了图像的全局和局部语义,同时也为自回归 Transformer 模型提供了一个统一的输入,从而在后续任务中实现高效的多模态融合与信息建模​。

5.2.3  视觉生成路径

视觉生成的核心目标是将图像转换为离散的ID序列,并根据文本描述生成对应的图像。设计旨在解决传统多模态模型中视觉生成任务的挑战,例如如何高效地将图像内容与文本描述对齐,以及如何生成高质量且语义一致的图像。通过将图像离散化为视觉token,并将其嵌入到语言模型的输入空间中,Janus模型能够以一种类似于处理文本的方式处理图像,从而实现高效的视觉生成。

1. 视觉编码器:VQ Tokenizer

在视觉生成中,Janus模型使用了VQ Tokenizer作为核心组件。VQ Tokenizer基于矢量量化(Vector Quantization)技术,能够将图像分割为离散的视觉token。具体实现过程如下:

(1)图像分割:VQ Tokenizer首先将输入图像划分为多个小块(patches)。这些小块通常是固定大小的正方形区域,例如16×16像素。通过这种方式,图像被分解为多个局部区域,每个区域代表图像的一个局部特征。

(2)矢量量化:每个小块被提取为一个特征向量,并通过矢量量化技术映射到一个离散的编码空间中。矢量量化是一种将连续的特征向量映射到离散符号的技术,类似于将图像中的每个小块“编码”为一个特定的符号或token。这些离散的token能够有效地表示图像的局部特征,同时减少了计算复杂度。

(3)离散化处理:通过矢量量化,图像被转换为一系列离散的ID序列。这种离散化处理使得图像能够以一种类似于文本的方式被处理,每个视觉token类似于文本中的单词或字符。这种设计不仅便于与语言模型的输入格式对齐,还使得图像生成过程能够利用语言模型的强大生成能力。

在介绍了Janus模型中视觉生成路径的核心组件——VQ Tokenizer之后,我们转向模型的另一个重要组成部分:视觉编码器配置。在DeepSeek Janus模型的开源代码中,文件clip_encoder.py定义了一个名为 CLIPVisionTower 的类,用于构建并使用不同类型的视觉模型(如 siglip、sam 或 HuggingFace 的 CLIP)。这个类包括图像预处理(如像素均值和标准差归一化)、选择不同层输出的功能,以及根据不同模型配置生成相应的视觉模型。具体来说,CLIPVisionTower 将图像通过视觉塔(vision tower)进行特征提取,提取指定层的特征,并根据 select_feature 参数选择合适的特征(如“patch”或“cls_patch”)。它还支持自定义图像归一化。

class CLIPVisionTower(nn.Module):
    def __init__(
        self,
        model_name: str = "siglip_large_patch16_384",
        image_size: Union[Tuple[int, int], int] = 336,
        select_feature: str = "patch",
        select_layer: int = -2,
        select_layers: list = None,
        ckpt_path: str = "",
        pixel_mean: Optional[List[float]] = None,
        pixel_std: Optional[List[float]] = None,
        **kwargs,
    ):
        super().__init__()

        self.model_name = model_name
        self.select_feature = select_feature
        self.select_layer = select_layer
        self.select_layers = select_layers

        vision_tower_params = {
            "model_name": model_name,
            "image_size": image_size,
            "ckpt_path": ckpt_path,
            "select_layer": select_layer,
        }
        vision_tower_params.update(kwargs)
        self.vision_tower, self.forward_kwargs = self.build_vision_tower(
            vision_tower_params
        )

        if pixel_mean is not None and pixel_std is not None:
            image_norm = torchvision.transforms.Normalize(
                mean=pixel_mean, std=pixel_std
            )
        else:
            image_norm = None

        self.image_norm = image_norm

    def build_vision_tower(self, vision_tower_params):
        if self.model_name.startswith("siglip"):
            self.select_feature = "same"
            vision_tower = create_siglip_vit(**vision_tower_params)
            forward_kwargs = dict()

        elif self.model_name.startswith("sam"):
            vision_tower = create_sam_vit(**vision_tower_params)
            forward_kwargs = dict()

        else:  # huggingface
            from transformers import CLIPVisionModel

            vision_tower = CLIPVisionModel.from_pretrained(**vision_tower_params)
            forward_kwargs = dict(output_hidden_states=True)

        return vision_tower, forward_kwargs

    def feature_select(self, image_forward_outs):
        if isinstance(image_forward_outs, torch.Tensor):
            # 输出已经是 self.select_layer 对应的特征
            image_features = image_forward_outs
        else:
            image_features = image_forward_outs.hidden_states[self.select_layer]

        if self.select_feature == "patch":
            # 如果输出中包含 cls_token
            image_features = image_features[:, 1:]
        elif self.select_feature == "cls_patch":
            image_features = image_features
        elif self.select_feature == "same":
            image_features = image_features

        else:
            raise ValueError(f"Unexpected select feature: {self.select_feature}")
        return image_features

    def forward(self, images):
        """
        参数:
            images (torch.Tensor): [b, 3, H, W]

        返回:
            image_features (torch.Tensor): [b, n_patch, d]
        """

        if self.image_norm is not None:
            images = self.image_norm(images)

        image_forward_outs = self.vision_tower(images, **self.forward_kwargs)
        image_features = self.feature_select(image_forward_outs)
        return image_features

总之,类CLIPVisionTower为多模态模型提供了图像特征提取功能,这些特征可以用于与文本特征的对齐和融合。

2. 特征处理:生成适配器

VQ Tokenizer输出的是一系列离散的ID序列,这些ID序列需要进一步处理以适应语言模型的输入格式。具体步骤如下:

(1)序列展平:VQ Tokenizer输出的ID序列通常是二维的(对应于图像的行和列),为了与语言模型的输入格式一致,Janus模型将这些二维ID序列展平为一维序列。这种展平操作保留了图像的空间顺序信息,使得语言模型能够更好地理解图像的局部和全局结构。

(2)码本嵌入映射:每个离散ID对应于一个码本嵌入(codebook embeddings),这些嵌入是VQ Tokenizer在训练过程中学习到的特征表示。为了将视觉token嵌入到语言模型的输入空间中,Janus模型使用了一个生成适配器。生成适配器由两层多层感知机(MLP)组成,其作用是将码本嵌入映射到语言模型的输入空间中。

(3)交互与融合:通过生成适配器的映射操作,视觉token能够与文本token在同一个空间中进行交互。这种交互使得语言模型能够同时处理文本和视觉特征,从而实现高效的视觉生成任务。例如,在文本到图像的生成任务中,模型可以根据文本描述中的语义信息,选择合适的视觉token来生成对应的图像内容。

3. 向量量化模型

视觉生成路径的目标是将图像转换为离散的 ID 序列,并根据文本描述生成对应的图像。这一路径的核心组件是基于向量量化(Vector Quantization)的模型,具体实现为 VQ-VAE(Vector Quantized Variational Autoencoder)。在DeepSeek Janus模型的开源代码中,文件 vq_model.py 实现了这一模型,包括编码器、解码器和向量量化器(Vector Quantizer),支持图像的压缩和重建。这是多模态模型中图像处理的核心组件,为图像的嵌入和生成提供了基础。文件的核心思想是借助 VQ-VAE 对输入数据进行编码、离散化,并使用向量量化方法来学习更好的表示。文件 vq_model.py的具体实现流程如下所示。

(1)类Encoder 是 VQ-VAE(或者类似自编码器)中的编码器,负责将输入图像转换为紧凑的特征表示(即 潜变量)。整个过程包括特征提取、分辨率逐步降低(下采样)、残差学习、注意力机制 等,以提取关键信息并减少冗余,为后续的离散化和解码提供高质量的潜在特征。

  1. 方法__init__():初始化了编码器的所有模块,包括输入卷积层、多个下采样层、残差块和注意力块。它根据输入通道数、基础通道数、通道倍增因子等参数动态创建网络结构。该方法还定义了是否使用卷积进行上/下采样、Dropout 概率、归一化类型以及中间特征的通道数等,确保网络在不同分辨率下有效提取信息并进行特征压缩。
  2. 方法forward():前向传播方法,用于处理输入图像的前向传播过程。首先,输入通过 conv_in 卷积层进行初步处理。然后,图像依次通过每个分辨率级别的残差块和注意力块,在逐步降低分辨率的同时提取更深层次的特征。接下来,图像通过中间层的进一步处理,最后经过输出层的归一化和卷积,生成潜在特征图输出。

Encoder的结构如下:

  1. conv_in层:是编码器的第一层卷积层,它接受输入图像并通过 3×3 的卷积核进行特征提取,将输入转换为指定通道数 ch 的特征图,作为后续处理的起点。
  2. conv_blocks 层:包含多个下采样模块,每个模块由 ResnetBlock 和 AttnBlock 组成,用于逐层提取高级特征并降低图像分辨率。每个下采样模块的输出由不同数量的残差块和可能的注意力机制块组成,确保模型在各个分辨率下有效地提取信息。
  3. mid 层:是中间特征处理模块,包含若干 ResnetBlock 和 AttnBlock,用于在最小分辨率级别后进一步处理和优化特征表示。这些模块帮助模型捕获更深层次的语义信息,并增强特征的表现力。
  4. conv_out层:是编码器的最后一层卷积层,它将经过多次处理后的特征图进行归一化、非线性激活,并通过 3×3 的卷积层生成 z_channels 维度的输出,作为最终的潜在特征图供后续解码使用。

(2)类Decoder 是一种神经网络解码器结构,主要用于将编码器产生的潜在空间特征图(z)转化为最终的输出图像。它通过若干残差块和注意力机制模块逐层恢复图像的分辨率,同时保持输入特征图的语义信息。解码器的核心目标是利用输入的潜在特征图 z 生成和输入图像维度相同的输出,通常用于图像生成任务。类Decoder中的成员如下:

  1. __init__ ()方法:负责初始化解码器的各个模块,包括输入卷积层、多个上采样模块、残差块和注意力块。它通过设定参数如中间特征通道数、基础通道数、通道倍增因子等来定义解码器的结构。通过这一方法,解码器能够根据输入的潜在特征生成最终输出图像。
  2. last_layer()方法(最后一层的权重):是一个属性方法,它返回解码器最后一层卷积层 (conv_out) 的权重。这可以用于检查或操作解码器的最后一层,通常在模型分析或特定的优化任务中使用。
  3. forward()方法(前向传播):forward 方法定义了解码器的前向传播流程。首先,潜在特征图 z 通过输入卷积层 (conv_in) 转换为初始特征图。接下来,特征图经过中间层的处理,这些层包括残差块和注意力机制。然后,特征图通过一系列上采样模块逐渐恢复分辨率。最后,经过输出层生成最终的输出图像。该方法是解码器的核心操作,确保特征图从潜在空间恢复到图像空间。class Decoder(nn.Module):

(3)VectorQuantizer 是一种用于量化的神经网络模块,主要用于将连续的向量表示映射到一个离散的码本空间。这种技术通常用于变分自编码器(VAE)和生成模型中,通过向量量化来增强模型的表达能力。该模块使用嵌入式码本来实现向量量化,同时支持可调的损失函数和不同的正则化策略,帮助模型在训练过程中有效地逼近目标分布。

VectorQuantizer中的成员如下所示:

  1. __init__()方法(构造函数):用于初始化 VectorQuantizer 类的各个参数和组件。它定义了码本的大小(n_e)、嵌入维度(e_dim)、码本损失权重(beta)以及是否进行 L2 归一化等选项。通过 nn.Embedding 层初始化码本,并对其进行必要的初始化操作。同时,如果启用了 L2 归一化,嵌入的权重会进行归一化处理。此外,show_usage 参数用于决定是否跟踪码本的使用情况,若启用,会额外注册一个缓冲区。
  2. forward()方法(前向传播):定义了向量量化的核心操作。首先,输入张量 z 被重塑为适合量化的形状。然后,计算 z 与码本嵌入之间的距离(通过欧几里得距离的方式),并找到最近的码本向量。量化后的向量 z_q 被用于计算量化损失(vq_loss)、提交损失(commit_loss)以及熵损失(entropy_loss)。这些损失会在训练时被用来优化模型,同时 z_q 通过加入 detach 操作保持梯度流动。最后,返回量化后的结果和损失值。
  3. get_codebook_entry()方法(获取码本项):用于根据给定的索引从码本中提取对应的向量。它可以接收索引 indices 和目标形状 shape,并根据 channel_first 参数来决定如何重塑输出的形状。如果启用了 L2 归一化,嵌入向量会进行归一化处理。这一方法通常用于检索特定的码本向量,在推理和生成过程中可能被调用以获取相应的离散表示。

(4)AttnBlock 是一个实现自注意力机制的神经网络模块,该模块通过使用三个卷积层(分别用于查询、键、值)来计算输入特征图的注意力权重。首先,输入特征图经过归一化处理后,生成查询(Q)、键(K)和值(V)。然后,计算查询和键之间的相似度,生成注意力权重,并使用 softmax 函数进行归一化。接着,这些权重与值进行加权平均,得到加权后的特征图,并通过一个卷积层进行最终的输出投影。最后,输出结果与原始输入特征图相加,形成残差连接。该模块的主要目的是使网络能够捕捉不同空间位置之间的依赖关系,从而增强特征的表达能力。

(5)类VQModel 是一个向量量化模型,主要用于图像或其他数据的编码和解码过程。该模型包括编码器、解码器以及向量量化模块,具体说明如下:

  1. 初始化:模型通过配置参数初始化编码器(Encoder)、解码器(Decoder)以及向量量化器(VectorQuantizer)。此外,还包括了用于量化的卷积层(quant_conv)和解码后的卷积层(post_quant_conv)。
  2. 编码(encode):输入数据首先通过编码器处理,然后经过卷积层,最后进行向量量化得到离散化的代码,并返回量化结果、嵌入损失以及信息。
  3. 解码(decode):量化后的编码通过解码器进行还原,生成重建数据。
  4. 解码代码(decode_code):根据给定的编码(code_b),将其转换为具体的量化值并经过解码,生成解码后的数据。
  5. 前向传播(forward):在前向传播中,输入数据经过编码和解码过程,返回解码后的输出及嵌入损失(用于正则化)。

VQModel模型通过向量量化将连续的输入映射到离散的编码空间,在图像生成和重建任务中起到重要作用。

(5-2-01)DeepSeek多模态大模型架构:Janus模型(1)_第1张图片

你可能感兴趣的:(训练,RAG,多模态),架构,人工智能,transformer,Deepseek,大模型,多模态)