本文介绍了视觉语言模型(VLM)的核心组件和实现细节,包括图像编码器、视觉-语言投影器、分词器、位置编码和解码器等。文章详细解析了如何从零开始实现VLM,并强调了多模态融合的关键步骤,以及训练策略和数据需求。文章还提供了关于如何进一步探索和研究VLM的建议。
VLM包括图像编码器、视觉-语言投影器、分词器、位置编码和解码器等核心组件。图像编码器从图像中提取视觉特征,视觉-语言投影器将图像嵌入投影到文本嵌入空间,与文本嵌入拼接后传递给解码器生成文本。
实现VLM需要创建PatchEmbeddings类以接受图像并创建一系列小块,用于使Transformer架构能够有效地处理视觉数据。注意力机制是视觉编码器和语言解码器的核心。此外,还需要实现视觉-语言投影器模块,该模块在对齐视觉和文本表示中起关键作用。
在训练VLM时,需要考虑预训练策略、训练阶段和数据需求。通常使用预训练的组件,如视觉编码器来自CLIP或SigLIP,语言解码器来自Llama或GPT等模型。训练阶段包括在冻结的编码器和解码器下预训练,仅更新投影器,以及微调投影器和解码器以适应特定任务。
视觉语言模型(Vision Language Model,VLM)正在改变计算机对视觉和文本信息的理解与交互方式。本文将介绍 VLM 的核心组件和实现细节,可以让你全面掌握这项前沿技术。我们的目标是理解并实现能够通过指令微调来执行有用任务的视觉语言模型。
总体架构
1)图像编码器(Image Encoder)
:用于从图像中提取视觉特征。本文将从 CLIP 中使用的原始视觉 Transformer。
2)视觉-语言投影器(Vision-Language Projector)
:由于图像嵌入的形状与解码器使用的文本嵌入不同,所以需要对图像编码器提取的图像特征进行投影,匹配文本嵌入空间,使图像特征成为解码器的视觉标记(visual tokens)。这可以通过单层或多层感知机(MLP)实现,本文将使用 MLP。
3)分词器和嵌入层(Tokenizer + Embedding Layer)
:分词器将输入文本转换为一系列标记 ID,这些标记经过嵌入层,每个标记 ID 被映射为一个密集向量。
4)位置编码(Positional Encoding)
:帮助模型理解标记之间的序列关系,对于理解上下文至关重要。
5)共享嵌入空间(Shared Embedding Space)
:将文本嵌入与来自位置编码的嵌入进行拼接(concatenate),然后传递给解码器。
6)解码器(Decoder-only Language Model)
:负责最终的文本生成。
上图是来自CLIP 论文的方法示意图,主要介绍文本和图片进行投影
综上,我们使用图像编码器从图像中提取特征,获得图像嵌入,通过视觉-语言投影器将图像嵌入投影到文本嵌入空间,与文本嵌入拼接后,传递给自回归解码器生成文本。
VLM 的关键在于视觉和文本信息的融合,具体步骤如下:
深度解析:图像编码器的实现
2.1 图像编码器:视觉 Transformer
为将图像转换为密集表示(图像嵌入),我们将图像分割为小块(patches),因为 Transformer 架构最初是为处理词序列设计的。
为从零开始实现视觉 Transformer,我们需要创建一个 PatchEmbeddings 类,接受图像并创建一系列小块。该过程对于使 Transformer 架构能够有效地处理视觉数据至关重要,特别是在后续的注意力机制中。实现如下:
class PatchEmbeddings(nn.Module):
def __init__(self, img_size=96, patch_size=16, hidden_dim=512):
super().__init__()
self.img_size = img_size
self.patch_size = patch_size
self.num_patches = (img_size // patch_size) ** 2
self.conv = nn.Conv2d(
in_channels=3,
out_channels=hidden_dim,
kernel_size=patch_size,
stride=patch_size
)
nn.init.xavier_uniform_(self.conv.weight)
if self.conv.bias is not None:
nn.init.zeros_(self.conv.bias)
def forward(self, X):
"""
参数:
X: 输入张量,形状为 [B, 3, H, W]
返回:
小块嵌入,形状为 [B, num_patches, hidden_dim]
"""
if X.size(2) != self.img_size or X.size(3) != self.img_size:
raise ValueError(f"输入图像尺寸必须为 {self.img_size}x{self.img_size}")
X = self.conv(X)
X = X.flatten(2)
X = X.transpose(1, 2)
return X
在上述代码中,输入图像通过卷积层被分解为 (img_size // patch_size) 2** 个小块,并投影为具有通道维度为 512 的向量(在 PyTorch 实现中,三维张量的形状通常为 [B, T, C])。
2.2 注意力机制
视觉编码器和语言解码器的核心都是注意力机制。关键区别在于解码器使用因果(掩码)注意力,而编码器使用双向注意力。以下是对单个注意力头的实现:
class Head(nn.Module):
def __init__(self, n_embd, head_size, dropout=0.1, is_decoder=False):
super().__init__()
self.key = nn.Linear(n_embd, head_size, bias=False)
self.query = nn.Linear(n_embd, head_size, bias=False)
self.value = nn.Linear(n_embd, head_size, bias=False)
self.dropout = nn.Dropout(dropout)
self.is_decoder = is_decoder
def forward(self, x):
B, T, C = x.shape
k = self.key(x)
q = self.query(x)
v = self.value(x)
wei = q @ k.transpose(-2, -1) * (C ** -0.5)
if self.is_decoder:
tril = torch.tril(torch.ones(T, T, dtype=torch.bool, device=x.device))
wei = wei.masked_fill(tril == 0, float('-inf'))
wei = F.softmax(wei, dim=-1)
wei = self.dropout(wei)
out = wei @ v
return out
2.3 视觉-语言投影器
投影器模块在对齐视觉和文本表示中起关键作用。我们将其实现为一个多层感知机(MLP):
class MultiModalProjector(nn.Module):
def __init__(self, n_embd, image_embed_dim, dropout=0.1):
super().__init__()
self.net = nn.Sequential(
nn.Linear(image_embed_dim, 4 * image_embed_dim),
nn.GELU(),
nn.Linear(4 * image_embed_dim, n_embd),
nn.Dropout(dropout)
)
def forward(self, x):
return self.net(x)
2.4 综合实现
class VisionLanguageModel(nn.Module):
def __init__(self, n_embd, image_embed_dim, vocab_size, n_layer,
img_size, patch_size, num_heads, num_blks,
emb_dropout, blk_dropout):
super().__init__()
num_hiddens = image_embed_dim
assert num_hiddens % num_heads == 0
self.vision_encoder = ViT(
img_size, patch_size, num_hiddens, num_heads,
num_blks, emb_dropout, blk_dropout
)