手搓多模态-05 transformer编码层

前情回顾

前面我们已经实现一个图像嵌入顶层模型调度

class SiglipVisionTransformer(nn.Module): ##视觉模型的第二层,将模型的调用分为了图像嵌入模型和transformer编码器模型的调用
	def __init__(self, config:SiglipVisionConfig):
		super().__init__()
		self.config = config
		self.embed_dim = config.hidden_size
		self.embeddings = SiglipVisionEmbeddings(config) ## 负责将图像嵌入成向量
		self.encoder = SiglipEncoder(config) ## 负责将向量编码成注意力相关的向量
		self.post_layer_norm = nn.LayerNorm(embed_dim, eps=config.layer_norm_eps) ## 层归一化

	def forward(self, pixel_values:torch.Tensor) -> torch.Tensor:
		"""
		pixel_values: [Batch_size,Channels,Height,Width]
		"""
		## [ Batch_size,Channels,Height,Width] -> [Batch_size,Num_Patches,Embedding_size] 
		hidden_states = self.embeddings(pixel_values) ## 将图像嵌入成向量

		# [Batch_size,Num_Patches,Embedding_size] -> [Batch_size,Num_Patches,Embedding_size]
		last_hidden_state = self.encoder(hidden_states) ## 将向量编码成注意力相关的向量

		# [Batch_size,Num_Patches,Embedding_size] -> [Batch_size,Num_Patches,Embedding_size]
		last_hidden_state = self.post_layer_norm(last_hidden_state)

		return last_hidden_state

这里我们传入一个图像数据通过SiglipVisionEmbeddings 图像编码嵌入向量此时向量不是上下文相关所以我们加入一个SiglipEncoder注意力嵌入嵌入完了之后通过归一化即可返回一个图像上下相关嵌入向量有关图像嵌入部分归一化部分之前已经提及这里我们着重实现transformer注意力

编码器结构

"Attention is all you need"这篇论文,我们可以了解到,编码器的架构如上图所示,输入嵌入 + 位置编码形成了编码器的输入,在Encoder层中会有N个这样的Encoder块,每个Encoder块中先通过一个多头注意力计算,再进行残差连接和归一化,然后再通过前向传播的MLP层,再进行一次残差连接和归一化。

这里残差连接的作用是防止梯度消失,多头注意力层可以让不同的token(在图像里面是patch)相关联,然后再通过一个MLP层增加整体的参数和模型的上限。

于是我们也创建一个SiglipEncoder层:

class SiglipEncoder(nn.Module):
	def __init__(self, config:SiglipVisionConfig):
		super().__init__()
		self.config = config
		self.embed_dim = config.hidden_size
		self.num_hidden_layers = config.num_hidden_layers
		self.layers = nn.ModuleList([SiglipEncoderLayer(config) for _ in range(self.num_hidden_layers)]) ## 多层编码器

	def forward(self, input_embeddings:torch.Tensor) -> torch.Tensor:
		hidden_states = input_embeddings
		for layer in self.layers:
			hidden_states = layer(hidden_states)
		return hidden_states

一个Encoder若干SiglipEncoderLayer具体多少个作为超参数配置修改接着我们需要实现每个SiglipEncoderLayer

SiglipEncoderLayer结构

注意:这里我们稍作修改我们在模型第二层调用这里加了一个post_layer_norm

self.post_layer_norm = nn.LayerNorm(embed_dim, eps=config.layer_norm_eps) ## 层归一化

这是因为我们希望嵌入向量进入编码之前之后一次归一化所以每个EncodeLayer我们归一化自注意力归一化MLP然后整个Encoder调用输出我们会用post_layer_norm做一次归一化参考SiglipVisionTransformer

根据之前结构我们编写如下代码

class SiglipEncoderLayer(nn.Module):
	def __init__(self, config:SiglipVisionConfig):
		super().__init__()
		self.config = config
		self.embed_dim = config.hidden_size
		self.self_atten = SiglipAttention(config) ## 注意力层
		self.layer_norm1 = nn.LayerNorm(self.embed_dim, eps=config.layer_norm_eps) ## 层归一化
		self.mlp = SiglipMLP(config) ## MLP层
		self.layer_norm2 = nn.LayerNorm(self.embed_dim, eps=config.layer_norm_eps) ## 层归一化
	
	def forward(self, hidden_states:torch.Tensor) -> torch.Tensor:
		"""
		hidden_states: [Batch_size,Num_Patches,Embedding_size]
		"""

		residual = hidden_states 
		hidden_states = self.layer_norm1(hidden_states) ## 层归一化 
		hidden_states = self.self_atten(hidden_states) ## 注意力层
		## 残差连接 [Batch_size,Num_Patches,Embedding_size]
		residual = hidden_states = hidden_states + residual	
		hidden_states = self.layer_norm2(hidden_states)
		hidden_states = self.mlp(hidden_states) ## MLP层

		## 残差连接 [Batch_size,Num_Patches,Embedding_size]
		return hidden_states + residual

MLP层的结构

我们实现简单MLP这里将自注意力的输出进行线性变换主要为了增加参数扩展模型性能上限代码如下

class SiglipMLP(nn.Module):
	
	def __init__(self, config:SiglipVisionConfig):
		super().__init__()
		self.config = config
		self.embed_dim = config.hidden_size
		self.intermediate_size = config.intermediate_size
		self.fc1 = nn.Linear(self.embed_dim, self.intermediate_size)
		self.fc2 = nn.Linear(self.intermediate_size, self.embed_dim)
		
	def forward(self, hidden_states:torch.Tensor) -> torch.Tensor:
		"""
		hidden_states: [Batch_size,Num_Patches,Embedding_size]
		"""
		hidden_states = self.fc1(hidden_states) ## [Batch_size,Num_Patches,Embedding_size] -> [Batch_size,Num_Patches,Intermediate_size]

		hidden_states = nn.functional.gelu(hidden_states,approximate="tanh") ## [Batch_size,Num_Patches,Intermediate_size] gelu激活函数

		hidden_states = self.fc2(hidden_states) ## [Batch_size,Num_Patches,Intermediate_size] -> [Batch_size,Num_Patches,Embedding_size]

		return hidden_states

值得一提这里激活函数用的gelu激活函数那我们gelu激活函数做一个简单介绍

Gelu激活函数

gelu激活函数relu激活函数变体我们一下激活函数发展

激活函数是什么?

激活函数的主要作用是提供网络的非线性建模能力。如果没有激活函数,那么该网络仅能够表达线性映射,此时即便有再多的隐藏层,其整个网络跟单层神经网络也是等价的。因此也可以认为,只有加入了激活函数之后,深度神经网络才具备了分层的非线性映射学习能力。

所以激活函数作用主要为模型引入非线性

早期的激活函数sigmoid激活函数公式和图像如下

这里图像就可以看出来这是一个非线性函数并且单调R数值放缩01之间

但是sigmoid函数有一些问题

  • 输入小于-5或者大于5时候梯度非常平缓容易导致梯度消失问题
  • 函数计算公式复杂这在梯度时候花费很大计算资源

于是为了改进这些缺点提出relu激活函数

relu激活函数公式如下

  • relu全称Rectified Linear Units ,即整流线性单元这里我们可以看到梯度保留下来计算复杂降低

但是不够完美因为小于0部分置为0模型无法小于0神经元学习任何知识所以人们进行优化提出gelu函数

Gelu函数

GELU函数全称Gaussian Error Linear Unit也叫高斯误差线性激活单元公式如下

其中φ(x)表示标准正态分布累计概率密度函数累计概率密度函数定义我们可以知道R01递增GELU函数图像所示

可以看到函数小于0部分并非简单输出0而是对一些信息做了保留同时函数更加平滑相比RELU来说GELU函数连续可导

计算复杂GELU虽然RELU计算复杂度高上不少但是人们近似公式计算GELU使得GELU函数计算复杂sigmoid函数相似这是可以接受因为改进sigmoid梯度消失问题近似公式如下

### 构建多模态大模型 Zhipu 的实现方法 构建一个多模态大型模型,尤其是像Zhipu这样的复杂系统,涉及多个方面的考量和技术细节。以下是关于如何从头构建这样一个系统的概述。 #### 1. 模型架构设计 对于Zhipu而言,其核心在于融合视觉和语言处理的能力。这通常涉及到预训练阶段使用大规模的数据集来学习通用特征表示[^3]。具体来说,可以采用Transformer结构作为基础框架,并在此基础上加入跨模态注意力机制(Cross-modal Attention),使得模型能够理解图像中的对象及其描述之间的关系。 ```python class CrossModalAttention(nn.Module): def __init__(self, dim_model=768): super().__init__() self.attn = nn.MultiheadAttention(embed_dim=dim_model, num_heads=8) def forward(self, query, key, value): attn_output, _ = self.attn(query=query, key=key, value=value) return attn_output ``` #### 2. 数据准备与预处理 为了使模型具备强大的泛化能力,需要收集并整理大量的图文配对数据用于训练过程。这些数据应该覆盖广泛的主题领域,以确保最终得到的模型具有足够的表达力去应对各种应用场景下的任务需求。 #### 3. 微调策略 当拥有一个已经过充分预训练的基础模型之后,则可以根据特定的应用场景对其进行进一步优化调整——即所谓的“微调”。在这个过程中,除了针对目标域内的新样本继续迭代更新权重外,还可以考虑引入一些额外的技术段如迁移学习、自监督等方式提高效率降低资源消耗[^4]。 #### 4. 部署方案 考虑到不同用户的计算设备差异较大(比如有的可能只有CPU而另一些则配备了高端显卡),因此提供灵活易用的一键式安装脚本就显得尤为重要了。通过容器化技术(例如Docker),不仅可以简化环境搭建流程还能有效规避因依赖版本冲突所引发的各种兼容性问题[^1]。
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值