本文主要是介绍【机器学习】使用Perceiver模型解决Transformer效率低下问题,希望对大家解决编程问题提供一定的参考价值,需要的开发者们随着小编来一起学习吧!
1.引言
1.1.技术背景
Transformer在高维度输入上计算效率低下,制约了Transformer框架的应用和迭代:
-
内存消耗大:
- 当处理高维度输入,如长文本或高分辨率图像时,Transformer需要将模型参数和中间状态都保存到内存中。这会导致巨大的内存消耗。
- 例如,在KV存储机制下,对于batch size为512、上下文长度为2048的设置,KV缓存里需要的空间规模可能达到3TB,这是模型大小的3倍。
-
推理成本高:
- Transformer的注意力机制的推理成本和输入序列的长度呈正相关。因此,随着输入维度的增加,推理所需的时间和计算资源会显著增加。
- 特别是当处理长文本时,由于需要同时考虑输入序列中所有的标记,可能会遇到内存限制或计算效率低下等问题。
-
低并行性:
- Transformer的推理生成过程以自回归的方式执行,这使得解码过程难以并行化。对于高维度输入,由于计算量增大,这种低并行性会进一步降低计算效率。
-
模型复杂度:
- Transformer模型通常比传统的循环神经网络(RNN)和卷积神经网络(CNN)更复杂,具有更多的参数和计算步骤。这增加了模型在高维度输入上的计算负担。
-
硬件资源限制:
- 在实际应用中,硬件资源的限制(如GPU内存大小、计算能力等)会进一步加剧Transformer在高维度输入上的计算效率问题。
为了解决这些问题,研究者们提出了多种优化方案,如:
- 在多GPU上应用各种并行机制来实现对模型的扩展。
- 将暂时未使用的数据卸载到CPU,并在需要时读回,以减轻内存负担。
- 采用智能批处理策略,如EffectiveTransformer中的序列打包技术,以减少内存占用和计算量。
- 使用神经网络压缩技术,如剪枝、量化和蒸馏,来减小模型的尺寸和计算复杂度。
综上所述,Transformer在高维度输入上计算效率低下的问题主要源于其内存消耗大、推理成本高、低并行性、模型复杂度高以及硬件资源限制等因素。通过采用适当的优化策略,可以有效地缓解这些问题并提高Transformer在实际应用中的性能。
1.2. Perceiver模型
Perceiver模型是一种深度学习架构,旨在解决Transformer在高维度输入上计算效率低下的问题。以下是关于Perceiver模型的清晰回答,包括其主要特点和机制:
- 模型概述
Perceiver模型是一种基于迭代注意力的通用感知模型,它通过限制自注意力层的大小,在保持性能的同时显著减少了计算资源的需求。Perceiver模型的核心思想是利用一个较小的Latent Bottleneck来捕获输入数据的关键信息,并通过非对称注意力机制迭代地将输入映射到这个Latent Bottleneck中。
- 模型特点
-
高效性:通过限制自注意力层的大小和引入Latent Bottleneck,Perceiver模型能够在保持性能的同时显著降低计算成本。特别是,通过使用较小的Latent数组作为查询,而较大的Byte数组作为键和值,Perceiver模型能够将cross
attention层的复杂度从O(M^2)降低到O(MN),其中M是输入数据的维度,N是Latent数组的大小(N<<M)。 -
灵活性:Perceiver模型适用于处理多种类型和大小的输入,包括图像、音频、文本等。这种灵活性使得Perceiver模型能够无缝地应用于各种任务,而无需为每种新任务定制模型。
-
可扩展性:Perceiver模型的设计使其易于添加新的输入通道和输出头,从而方便构建多任务模型。这种可扩展性使得Perceiver模型能够应对更加复杂的任务和数据集。
-
模块化设计:Perceiver模型采用模块化设计,便于理解、调试和优化。每个模块都执行特定的功能,并且可以通过替换或调整模块来改进模型的性能。
- 模型机制
- Latent Bottleneck:Perceiver模型使用一个较小的Latent数组作为模型的核心组件,用于捕获输入数据的关键信息。Latent数组的大小是超参数,通常远小于输入数据的维度。通过迭代地将输入映射到Latent Bottleneck中,Perceiver模型能够高效地处理高维度输入。
- 非对称注意力机制:Perceiver模型利用非对称注意力机制将输入数据映射到Latent Bottleneck中。具体而言,它使用Latent数组作为查询,而使用较大的Byte数组作为键和值。这种非对称注意力机制使得Perceiver模型能够在保持性能的同时降低计算成本。
- 参数共享:为了避免Latent Bottleneck导致忽略输入信号的必要细节,Perceiver模型由多个可共享参数的cross attention层与transformer层构成。这些层可以迭代地从输入图像中提取信息,并通过共享参数来减少模型的复杂性。
总体来看,Perceiver模型通过引入Latent Bottleneck和非对称注意力机制,以及采用参数共享和模块化设计等策略,有效地解决了Transformer在高维度输入上计算效率低下的问题。这种模型具有高效性、灵活性、可扩展性和模块化设计等特点,适用于处理多种类型和大小的输入,并能够在各种任务中取得优异的性能。
1.3.探讨内容
介绍
本文实现了由Andrew Jaegle等人提出的Perceiver模型,这是一个利用迭代注意力机制进行通用感知的深度学习模型,并特别展示了其在CIFAR-100数据集上的图像分类能力。
Perceiver模型的核心思想是通过非对称注意力机制,将高维输入数据(如图像)迭代地提炼到一个紧凑的潜在空间(latent space)中,从而有效地处理大规模输入数据。
具体来说,假设输入数据(如图像)包含M个元素(如像素或图像块),在标准的Transformer模型中,对这些元素执行自注意力操作将产生O( M 2 M^2 M2)的复杂度。然而,Perceiver模型通过创建一个大小为N的潜在数组(其中N远小于M),并迭代执行以下两个操作来降低复杂度:
- 潜在数组与输入数据之间的交叉注意力Transformer操作,其复杂度为O(M×N)。
- 潜在数组上的自注意力Transformer操作,其复杂度为O( N 2 N^2 N2)。
通过这种方式,Perceiver模型能够在保持性能的同时,显著降低计算成本,使其能够处理更大规模的输入数据。
请注意,为了运行此示例,您需要安装Keras 3.0或更高版本。
2. 建立并训练Perceiver模型
2.1.设置
# 导入 TensorFlow 的 Keras API
import tensorflow as tf
from tensorflow.keras import layers, activations # 注意:ops 模块在 tf.keras 中通常不是直接导入的,因为大多数操作都通过层或函数实现
# 如果你需要特定操作,你可以直接调用 TensorFlow API 中的函数
2.2.数据预处理
2.2.1.加载数据
import tensorflow as tf
from tensorflow.keras.datasets import cifar100
from tensorflow.keras.utils import to_categorical # 设置类别数量和输入数据形状
num_classes = 100
input_shape = (32, 32, 3) # 加载 CIFAR-100 数据集
(x_train, y_train), (x_test, y_test) = cifar100.load_data() # CIFAR-100 的标签是整数形式的,我们需要将其转换为 one-hot 编码
y_train = to_categorical(y_train, num_classes)
y_test = to_categorical(y_test, num_classes) # 打印训练集和测试集的形状
print(f"x_train 形状: {x_train.shape} - y_train 形状: {y_train.shape}")
print(f"x_test 形状: {x_test.shape} - y_test 形状: {y_test.shape}") # 注意:由于 CIFAR-100 的数据是 uint8 类型且范围在 0-255,
# 通常在训练之前,我们需要对数据进行归一化处理,将其缩放到 0-1 的范围。
x_train = x_train.astype('float32') / 255.0
x_test = x_test.astype('float32') / 255.0 # 在这里可以继续编写代码来构建、编译和训练 Perceiver 模型
# ...
代码主要用于准备和预处理CIFAR-100数据集,为后续的模型训练作准备。
-
导入库:导入TensorFlow库和Keras数据集相关的模块。
-
设置类别和输入形状:定义类别数量
num_classes
为100,输入数据的形状input_shape
为(32, 32, 3),即32x32像素的彩色图像。 -
加载数据集:使用
cifar100.load_data()
函数加载CIFAR-100数据集,该数据集包含训练集和测试集,每组数据包括图像和对应的标签。 -
转换标签格式:将CIFAR-100数据集中的整数形式的标签转换为one-hot编码格式,以便于神经网络处理。
to_categorical
函数用于执行这一转换。 -
打印数据形状:打印训练集和测试集的图像和标签的形状,以确认数据加载和处理的结果。
-
数据归一化:由于CIFAR-100图像数据是uint8类型,数值范围在0到255之间,因此在训练之前需要对数据进行归一化处理,将其缩放到0到1的范围,以利于模型收敛。
-
数据类型转换:将图像数据转换为float32类型,这是大多数深度学习框架所期望的数据类型。
-
准备模型训练:注释中提到,接下来可以编写代码构建、编译和训练Perceiver模型,但具体的模型构建代码没有给出。
代码为使用CIFAR-100数据集进行深度学习模型训练做好了准备,包括数据加载、预处理、归一化等关键步骤。
2.2.2. 配置超参数
import tensorflow as tf# 设置学习率
learning_rate = 0.001
# 设置权重衰减
weight_decay = 0.0001
# 设置批量大小
batch_size = 64
# 设置训练周期数,实际使用时应设置为50
num_epochs = 2
# 设置Dropout比率
dropout_rate = 0.2
# 设置图像尺寸,我们将图像调整至该尺寸
image_size = 64
# 设置从图像中提取的块的尺寸
patch_size = 2
# 计算每张图像的块的数量
num_patches = (image_size // patch_size) ** 2
# 设置潜在数组的尺寸
latent_dim = 256
# 设置每个元素在数据和潜在数组中的嵌入尺寸
projection_dim = 256
# 设置Transformer头的数量
num_heads = 8
# 设置Transformer Feedforward网络的尺寸
ffn_units = [projection_dim,projection_dim,
]
# 设置Transformer块的数量
num_transformer_blocks = 4
# 设置交叉注意力和Transformer模块的重复次数
num_iterations = 2
# 设置最终分类器的Feedforward网络的尺寸
classifier_units = [projection_dim,num_classes,
]# 打印图像尺寸信息
print(f"图像尺寸: {image_size} X {image_size} = {image_size ** 2}")
# 打印块尺寸信息
print(f"块尺寸: {patch_size} X {patch_size} = {patch_size ** 2} ")
# 打印每张图像的块数量
print(f"每张图像的块数: {num_patches}")
# 打印每个块的元素数量(3个通道)
print(f"每个块的元素数(3个通道): {(patch_size ** 2) * 3}")
# 打印潜在数组的形状
print(f"潜在数组形状: {latent_dim} X {projection_dim}")
# 打印数据数组的形状
print(f"数据数组形状: {num_patches} X {projection_dim}")# 注意:这段代码仅设置了用于构建模型的参数,并未实际构建模型。
代码功能:
- 定义了用于模型训练和构建的一系列超参数,包括学习率、权重衰减、批量大小、训练周期数、Dropout比率、图像尺寸、块尺寸、潜在数组和数据数组的维度等。
- 计算了每张图像可以被分割成的块的数量。
- 打印了与图像和块相关的尺寸信息,以及潜在数组和数据数组的形状,这些信息对于理解模型输入和构建模型结构非常有用。
- 此代码段是模型构建和训练的配置阶段,不包含模型的实际构建和训练过程。
代码的运行结果如下:
Image size: 64 X 64 = 4096
Patch size: 2 X 2 = 4
Patches per image: 1024
Elements per patch (3 channels): 12
Latent array shape: 256 X 256
Data array shape: 1024 X 256
2.2.3.数据增强
import tensorflow as tf
from tensorflow.keras import layers# 创建数据增强序列模型
data_augmentation = tf.keras.Sequential([# 对数据进行标准化处理,使数据具有零均值和单位方差layers.Normalization(),# 调整图像大小至指定尺寸layers.Resizing(image_size, image_size),# 水平平移图像layers.RandomFlip("horizontal"),# 随机缩放图像的高度和宽度,因子为0.2layers.RandomZoom(height_factor=0.2, width_factor=0.2),],name="data_augmentation",
)# 对训练数据进行适应,计算标准化处理所需的均值和方差
# 这里假设 x_train 是已经定义好的训练数据集
data_augmentation.layers[0].adapt(x_train)
代码功能:
- 定义了一个名为
data_augmentation
的keras.Sequential
模型,该模型用于数据增强。 Normalization
层用于对数据进行标准化处理,使其具有零均值和单位方差,这有助于模型训练的稳定性和收敛速度。Resizing
层用于将图像调整到指定的尺寸,这里设置为image_size
ximage_size
像素。RandomFlip
层以水平方向随机翻转图像,这是一种常见的数据增强技术,可以提高模型的泛化能力。RandomZoom
层随机缩放图像的高度和宽度,缩放因子为0.2,即图像的20%,这增加了数据的多样性。- 通过调用
adapt
方法,Normalization
层可以学习训练数据x_train
的均值和方差,以便在标准化过程中使用。这一步通常在模型训练之前完成一次,以确保训练和验证/测试数据使用相同的统计量进行标准化。
2.3.建立模型
2.3.1.定义FFN
import tensorflow as tf
from tensorflow.keras import layers
from tensorflow.keras.activations import gelu # 导入GELU激活函数# 定义创建Feedforward网络的函数
def create_ffn(hidden_units, dropout_rate):# 初始化Feedforward网络层的列表ffn_layers = []# 遍历hidden_units列表中的单元数,排除最后一个元素for units in hidden_units[:-1]:# 添加一个具有GELU激活函数的全连接层ffn_layers.append(layers.Dense(units, activation=gelu))# 添加最后一个全连接层,不使用激活函数ffn_layers.append(layers.Dense(units=hidden_units[-1]))# 添加Dropout层,Dropout比率由参数dropout_rate指定ffn_layers.append(layers.Dropout(dropout_rate))# 使用Sequential模型将Feedforward网络层堆叠起来ffn = tf.keras.Sequential(ffn_layers)# 返回创建的Feedforward网络模型return ffn
代码功能:
- 定义了一个名为
create_ffn
的函数,它接收hidden_units
和dropout_rate
两个参数。hidden_units
是一个整数列表,表示Feedforward网络(FFN)中每层的单元数;dropout_rate
是一个浮点数,表示Dropout层的丢弃概率。 - 函数内部初始化了一个空列表
ffn_layers
,用于存储FFN的层。 - 使用 for 循环遍历
hidden_units
列表中除了最后一个元素之外的所有单元数,为每一层创建一个使用 GELU 激活函数的全连接层(Dense
层),并将这些层添加到ffn_layers
列表中。 - 循环结束后,添加最后一个全连接层,该层的单元数与
hidden_units
列表中的最后一个元素相等,但不加激活函数。 - 在最后一个全连接层之后,添加一个Dropout层,用于在训练过程中随机丢弃一部分神经元的激活值,以防止过拟合。
- 使用
tf.keras.Sequential
将所有层堆叠成一个序列模型,即FFN。 - 返回创建的FFN模型,该模型可以作为Transformer或其他模型组件的一部分。
2.3.2定义图像分割层patch
import tensorflow as tf
from tensorflow import keras
from tensorflow.keras import layers
from tensorflow.python.ops import array_ops, math_ops, opsclass Patches(layers.Layer):def __init__(self, patch_size):super().__init__()# 初始化块大小self.patch_size = patch_sizedef call(self, images):# 获取图像的批量大小batch_size = array_ops.shape(images)[0]# 使用 TensorFlow 的 extract_patches 函数从图像中提取块patches = ops.image.extract_patches(image=images,size=(self.patch_size, self.patch_size), # 块的大小strides=(self.patch_size, self.patch_size), # 步长dilation_rate=1, # 膨胀率padding="valid", # 填充方式)# 计算每个块的维度patch_dims = patches.shape[-1]# 重塑块的形状,从 [batch_size, height, width, channels] 到 [batch_size, num_patches, patch_dims]patches = array_ops.reshape(patches, [batch_size, -1, patch_dims])return patches
代码功能:
- 定义了一个名为
Patches
的tf.keras.layers.Layer
子类,用于从图像中提取大小为patch_size
xpatch_size
的块(patches)。 - 在初始化方法
__init__
中,接受一个参数patch_size
并将其存储为实例变量,表示每个块的大小。 call
方法接收一批图像作为输入,并使用ops.image.extract_patches
函数从每张图像中提取块。此函数根据指定的patch_size
和步长(strides
)来分割图像。extract_patches
函数的padding
参数设置为"valid"
,表示不使用任何填充(padding)。- 计算每个块的维度
patch_dims
,然后使用array_ops.reshape
方法将块重塑为(batch_size, num_patches, patch_dims)
的形状,其中num_patches
是每张图像中的块数量。 - 最后,
call
方法返回重塑后的块。
此类可以用于深度学习模型中,将图像数据转换为适合 Transformer 模型处理的格式。通过提取图像块,模型可以学习图像的局部特征。
2.3.3.定义PatchEncoder 层
PatchEncoder 层将会执行两个主要操作:
-
线性变换:它将每个 patch 通过一个线性层(也称为全连接层或密集层)投影到一个固定大小的向量上,这个向量的大小通常被称为
latent_dim
(潜在维度)。 -
位置嵌入:除了线性变换外,该层还会将可学习的位置嵌入添加到投影后的向量中。位置嵌入用于捕捉 patches 在原始图像中的空间位置信息。
在 Perceiver 论文中,虽然使用了基于傅里叶特征的位置编码,但在这个简化的实现中,我们将使用可学习的位置嵌入。
以下是 PatchEncoder 层功能的非代码描述:
PatchEncoder 层
输入:
- Patches:一个四维张量,形状为 (batch_size, num_patches, patch_height, patch_width, channels),其中 num_patches 是从输入图像中提取的 patches 的数量。
- latent_dim:投影后向量的目标大小。
输出:
- Encoded patches:一个三维张量,形状为 (batch_size, num_patches, latent_dim),其中包含了每个 patch 的线性变换结果和位置嵌入。
操作:
-
线性变换:对于每个 patch,使用线性层将其从 (patch_height, patch_width, channels) 投影到 (latent_dim)。这可以通过一个权重矩阵和一个偏置项来完成,权重矩阵的形状为 (channels, latent_dim),偏置项的形状为 (latent_dim)。
-
位置嵌入:为每个 patch 分配一个位置嵌入向量。这些位置嵌入是可学习的参数,并且与线性变换的结果相加。位置嵌入的形状为 (num_patches, latent_dim)。它们通常是通过一个位置嵌入层(PositionEmbeddingLayer)来生成,该层可以是基于固定规则的(如正弦和余弦函数),也可以是基于学习的。
-
组合:将线性变换的结果与位置嵌入相加,得到最终的编码 patches。这个加和操作是逐元素进行的。
-
输出:返回编码后的 patches,这是一个形状为 (batch_size, num_patches, latent_dim) 的三维张量。
这样,每个 patch 都被转换为一个固定大小的向量,并融入了其空间位置信息,以便后续的网络层进行处理。
import tensorflow as tf
from tensorflow import keras
from tensorflow.keras import layers
from tensorflow.python.ops import array_ops, math_opsclass PatchEncoder(layers.Layer):def __init__(self, num_patches, projection_dim):super().__init__()# 设置数据数组中的块的数量self.num_patches = num_patches# 创建一个全连接层,用于将每个块投影到更高维度的空间self.projection = layers.Dense(units=projection_dim)# 创建一个嵌入层,用于为每个位置的块添加位置信息self.position_embedding = layers.Embedding(input_dim=num_patches, # 输入维度等于块的数量output_dim=projection_dim # 输出维度等于投影维度)def call(self, patches):# 获取每个块的位置索引positions = array_ops.range(start=0, stop=self.num_patches, step=1)# 使用全连接层对每个块进行投影encoded_patches = self.projection(patches)# 为每个块添加位置嵌入encoded = encoded_patches + self.position_embedding(positions)return encoded
代码功能:
- 定义了一个名为
PatchEncoder
的tf.keras.layers.Layer
子类,用于将图像块编码为具有位置信息的高维表示。 - 在初始化方法
__init__
中,接收num_patches
和projection_dim
两个参数。num_patches
表示每个图像被分割成的块的数量,projection_dim
表示投影到的高维空间的维度。 - 定义了一个
Dense
层,用于将每个图像块投影到更高维度的空间。 - 定义了一个
Embedding
层,用于为每个块的位置添加位置嵌入,使模型能够学习序列中每个块的顺序信息。 call
方法首先使用range
函数生成位置索引。- 然后,使用
projection
层对输入的图像块进行编码,将其从原始特征空间映射到更高维度的潜在空间。 - 接着,使用
position_embedding
层为每个块添加位置信息,这有助于模型捕捉块之间的顺序关系。 - 最后,将投影后的块与位置嵌入相加,得到最终的编码表示,并将其返回。
PatchEncoder
类可用于深度学习模型中,将图像分割为小块后,对这些块进行编码并添加位置信息,为后续的 Transformer 层或其他处理模块做准备。
2.3.4.构建 Perceiver 模型
Perceiver 模型由两个模块组成:交叉注意力模块(cross-attention module)和一个带有自注意力(self-attention)的标准 Transformer。
交叉注意力模块
交叉注意力模块期望的输入是一个 (latent_dim, projection_dim) 的潜在数组(latent array)和一个 (data_dim, projection_dim) 的数据数组(data array),并产生一个 (latent_dim, projection_dim) 的潜在数组作为输出。为了应用交叉注意力,查询向量(query vectors)从潜在数组中生成,而键向量(key vectors)和值向量(value vectors)从编码后的图像中生成。
请注意,在这个例子中,数据数组指的是图像,其中 data_dim 被设置为 num_patches(补丁的数量)。
以下是 Perceiver 模型的基本构建步骤(非代码描述):
-
输入:
- 潜在数组(Latent Array):一个大小为 (latent_dim, projection_dim) 的数组,表示潜在空间中的特征。
- 数据数组(Data Array):在这里是编码后的图像,大小为 (num_patches, projection_dim)。每个补丁都被编码为一个固定大小的向量。
-
交叉注意力模块:
- 查询生成(Query Generation):从潜在数组中生成查询向量。这通常涉及将潜在数组通过一个线性层(或密集层),以产生与投影维度相匹配的查询向量。
- 键和值生成(Key and Value Generation):从数据数组中生成键向量和值向量。这同样是通过线性层来完成的,确保键和值的维度与投影维度相匹配。
- 注意力计算(Attention Calculation):使用查询、键和值来计算交叉注意力。这涉及计算查询和键之间的相似度分数,并使用这些分数来加权值向量,生成输出。
- 输出(Output):交叉注意力模块的输出是一个新的潜在数组,大小为 (latent_dim, projection_dim)。这个输出潜在数组现在包含了从数据数组(即图像)中抽取的信息。
-
标准 Transformer:
- 在交叉注意力模块之后,使用标准的 Transformer 结构,包括自注意力层和前馈神经网络(FFN)。这些层继续处理和变换潜在数组,进一步提取和组合特征。
- 自注意力层允许潜在数组中的不同位置相互交互,从而捕获输入数据中的长期依赖关系。
- FFN 层在自注意力层之后应用非线性变换,进一步增强模型的表示能力。
-
输出:
- Transformer 的最终输出是一个更新后的潜在数组,它可以被进一步处理以产生模型的最终输出,例如分类标签或回归值。
import tensorflow as tf
from tensorflow.keras import layersdef create_cross_attention_module(latent_dim, data_dim, projection_dim, ffn_units, dropout_rate):# 定义输入层inputs = {"latent_array": layers.Input(shape=(latent_dim, projection_dim), name="latent_array"),"data_array": layers.Input(shape=(data_dim, projection_dim), name="data_array"),}# 对输入的潜在数组和数据数组应用层归一化latent_array = layers.LayerNormalization(epsilon=1e-6)(inputs["latent_array"])data_array = layers.LayerNormalization(epsilon=1e-6)(inputs["data_array"])# 创建查询张量(query),形状为 [1, latent_dim, projection_dim]query = layers.Dense(units=projection_dim)(latent_array)# 创建键张量(key),形状为 [batch_size, data_dim, projection_dim]key = layers.Dense(units=projection_dim)(data_array)# 创建值张量(value),形状为 [batch_size, data_dim, projection_dim]value = layers.Dense(units=projection_dim)(data_array)# 生成交叉注意力输出,形状为 [batch_size, latent_dim, projection_dim]attention_output = layers.Attention(use_scale=True, dropout=0.1)([query, key, value], return_attention_scores=False)# 应用第一个跳跃连接attention_output = layers.Add()([attention_output, latent_array])# 再次应用层归一化attention_output = layers.LayerNormalization(epsilon=1e-6)(attention_output)# 创建并应用Feedforward网络ffn = create_ffn(hidden_units=ffn_units, dropout_rate=dropout_rate)outputs = ffn(attention_output)# 应用第二个跳跃连接outputs = layers.Add()([outputs, attention_output])# 创建Keras模型model = keras.Model(inputs=inputs, outputs=outputs)return model# 注意:这里的create_ffn函数需要提前定义,用于创建Feedforward网络。
代码功能:
- 定义了一个名为
create_cross_attention_module
的函数,用于创建交叉注意力模块。 - 该函数接收五个参数:潜在数组的维度
latent_dim
,数据数组的维度data_dim
,投影维度projection_dim
,Feedforward网络的单元配置ffn_units
,以及Dropout比率dropout_rate
。 - 定义了两个输入层,分别对应潜在数组和数据数组。
- 对输入的潜在数组和数据数组进行层归一化处理。
- 使用
Dense
层创建查询(query)、键(key)和值(value)张量。 - 使用
Attention
层计算交叉注意力输出。 - 应用跳跃连接和层归一化处理交叉注意力的输出。
- 调用
create_ffn
函数创建Feedforward网络,并将其应用于经过跳跃连接和层归一化处理的输出。 - 再次应用跳跃连接,然后将结果作为输出。
- 使用
keras.Model
创建模型,将输入和输出连接起来。
交叉注意力模块是Transformer架构中的关键组成部分,允许模型在处理数据时考虑潜在数组和数据数组之间的关系。这种模块通常用于处理序列数据,如自然语言处理或时间序列分析任务。在这里,它被用于处理编码后的图像数据,以提取特征并进行进一步的处理。
Transformer 模块
Transformer 模块接收来自交叉注意力模块的潜在向量作为输入。它首先对这个潜在向量的 latent_dim
个元素应用多头自注意力机制,随后通过前馈神经网络(Feed Forward Network, FFN)进行处理。这个过程最终生成另一个 (latent_dim
, projection_dim
) 形状的潜在数组。
多头自注意力机制允许 Transformer 在不同的表示子空间中同时关注输入序列中的不同位置信息,从而捕捉长期依赖关系。接着,前馈神经网络通过非线性变换进一步处理和增强这些注意力加权后的特征。
这样,Transformer 模块就能够在潜在空间中有效地处理和转换来自交叉注意力模块的输入,以产生一个更富含信息、表达能力更强的潜在数组,供后续的网络层进一步使用。
import tensorflow as tf
from tensorflow.keras import layersdef create_transformer_module(latent_dim,projection_dim,num_heads,num_transformer_blocks,ffn_units,dropout_rate,
):# 输入形状:[1, latent_dim, projection_dim]inputs = layers.Input(shape=(latent_dim, projection_dim))# 定义Transformer模块的初始输入x = inputs# 循环创建Transformer块的多个层for _ in range(num_transformer_blocks):# 应用第一层归一化x1 = layers.LayerNormalization(epsilon=1e-6)(x)# 创建多头自注意力层attention_output = layers.MultiHeadAttention(num_heads=num_heads, key_dim=projection_dim, dropout=0.1)(x1, x1)# 第一个跳跃连接x2 = layers.Add()([attention_output, x])# 应用第二层归一化x3 = layers.LayerNormalization(epsilon=1e-6)(x2)# 应用Feedforward网络ffn = create_ffn(hidden_units=ffn_units, dropout_rate=dropout_rate)x3 = ffn(x3)# 第二个跳跃连接x = layers.Add()([x3, x2])# 创建Keras模型model = keras.Model(inputs=inputs, outputs=x)return model# 注意:这里的create_ffn函数需要提前定义,用于创建Feedforward网络。
代码功能:
- 定义了一个名为
create_transformer_module
的函数,用于创建一个包含多个Transformer块的模块。 - 该函数接收六个参数:潜在数组的维度
latent_dim
,投影维度projection_dim
,注意力头的数量num_heads
,Transformer块的数量num_transformer_blocks
,Feedforward网络的单元配置ffn_units
,以及Dropout比率dropout_rate
。 - 定义了一个输入层,用于接收形状为
(latent_dim, projection_dim)
的输入数据。 - 在循环中,为每个Transformer块创建了两个跳跃连接和两层归一化处理,以及一个多头自注意力层和一个Feedforward网络。
- 在每个Transformer块中,首先应用层归一化,然后是多头自注意力机制,接着是第一个跳跃连接和第二层归一化,之后是Feedforward网络,最后是第二个跳跃连接。
- 使用
keras.Model
创建模型,将输入和经过Transformer模块处理的输出连接起来。
Transformer模块是Transformer架构的核心,通过多头自注意力机制和Feedforward网络来处理序列数据,允许模型在处理时考虑长距离依赖关系。在这里,该模块被用于处理潜在表示的数据,以提取特征并进行进一步的处理。
Perceiver 模型
Perceiver 模型通过迭代地应用交叉注意力模块和 Transformer 模块 num_iterations
次来逐步提取和整合输入图像中的信息。在每次迭代中,这些模块使用共享的权重,并通过跳跃连接来融合不同迭代步骤中的信息。通过这种方式,潜在数组能够随着迭代次数的增加,逐步丰富和深化对输入图像的理解。
import tensorflow as tf
from tensorflow.keras import layers
from tensorflow.python.ops import array_ops, math_ops, opsclass Perceiver(keras.Model):def __init__(self, **kwargs):super().__init__(**kwargs)# 提取模型参数self.latent_dim = kwargs['latent_dim']self.data_dim = kwargs['data_dim']self.patch_size = kwargs['patch_size']self.projection_dim = kwargs['projection_dim']self.num_heads = kwargs['num_heads']self.num_transformer_blocks = kwargs['num_transformer_blocks']self.ffn_units = kwargs['ffn_units']self.dropout_rate = kwargs['dropout_rate']self.num_iterations = kwargs['num_iterations']self.classifier_units = kwargs['classifier_units']def build(self, input_shape):# 创建潜在数组self.latent_array = self.add_weight(shape=(self.latent_dim, self.projection_dim),initializer='random_normal',trainable=True,)# 创建图像块编码模块self.patch_encoder = PatchEncoder(self.data_dim, self.projection_dim)# 创建交叉注意力模块self.cross_attention = create_cross_attention_module(self.latent_dim,self.data_dim,self.projection_dim,self.ffn_units,self.dropout_rate,)# 创建Transformer模块self.transformer = create_transformer_module(self.latent_dim,self.projection_dim,self.num_heads,self.num_transformer_blocks,self.ffn_units,self.dropout_rate,)# 创建全局平均池化层self.global_average_pooling = layers.GlobalAveragePooling1D()# 创建分类头self.classification_head = create_ffn(hidden_units=self.classifier_units, dropout_rate=self.dropout_rate)super().build(input_shape)def call(self, inputs):# 对输入数据进行增强augmented = data_augmentation(inputs)# 将增强后的数据分割为图像块patches = self.patcher(augmented)# 对图像块进行编码encoded_patches = self.patch_encoder(patches)# 准备交叉注意力的输入cross_attention_inputs = {"latent_array": ops.expand_dims(self.latent_array, 0),"data_array": encoded_patches,}# 迭代地应用交叉注意力和Transformer模块for _ in range(self.num_iterations):# 应用从潜在数组到数据数组的交叉注意力latent_array = self.cross_attention(cross_attention_inputs)# 应用自注意力Transformer到潜在数组latent_array = self.transformer(latent_array)# 设置下一次迭代的潜在数组cross_attention_inputs["latent_array"] = latent_array# 应用全局平均池化,生成表示张量,形状为 [batch_size, projection_dim]representation = self.global_average_pooling(latent_array)# 生成分类的logitslogits = self.classification_head(representation)return logits# 注意:上述代码中的 create_cross_attention_module 和 create_ffn 函数需要提前定义。
# data_augmentation 和 self.patcher 也需要定义或初始化。
代码功能:
- 定义了一个名为
Perceiver
的类,它继承自tf.keras.Model
,实现了 Perceiver 模型的结构。 - 在初始化方法
__init__
中,接收多个参数,用于定义模型的不同部分,如潜在数组的维度、数据数组的维度、投影维度、注意力头的数量等。 - 在
build
方法中,根据传入的参数创建模型的不同组件,包括潜在数组、图像块编码模块、交叉注意力模块、Transformer模块、全局平均池化层和分类头。 call
方法定义了模型的前向传播过程,包括数据增强、图像块的创建和编码、交叉注意力和Transformer模块的迭代应用,以及最终的分类头输出。Perceiver
类中的latent_array
作为模型的潜在表示,通过随机正态分布初始化,并在训练过程中进行更新。- 模型使用了数据增强、图像块编码、多头自注意力机制、Feedforward网络和分类头来处理输入数据,并生成最终的分类结果。
2.3.5.编译和训练模型
import tensorflow as tf
from tensorflow.keras import layers# 定义运行实验的函数
def run_experiment(model):# 创建优化器,使用Adam而非LAMB,因为LAMB目前不受支持optimizer = tf.keras.optimizers.Adam(learning_rate=0.001)# 编译模型model.compile(optimizer=optimizer,loss=tf.keras.losses.SparseCategoricalCrossentropy(from_logits=True),metrics=[tf.keras.metrics.SparseCategoricalAccuracy(name="acc"),tf.keras.metrics.SparseTopKCategoricalAccuracy(5, name="top5-acc"),],)# 创建学习率衰减的回调函数reduce_lr = tf.keras.callbacks.ReduceLROnPlateau(monitor="val_loss", factor=0.2, patience=3)# 创建提前停止训练的回调函数early_stopping = tf.keras.callbacks.EarlyStopping(monitor="val_loss", patience=15, restore_best_weights=True)# 训练模型history = model.fit(x=x_train, # 训练数据y=y_train, # 训练标签batch_size=64, # 批量大小epochs=2, # 训练周期数,实际使用时应设置为50validation_split=0.1, # 验证集比例callbacks=[early_stopping, reduce_lr], # 训练回调)# 在测试集上评估模型性能_, accuracy, top_5_accuracy = model.evaluate(x_test, y_test)print(f"测试准确率: {round(accuracy * 100, 2)}%")print(f"测试前5准确率: {round(top_5_accuracy * 100, 2)}%")# 返回训练历史,用于绘制学习曲线return history# 假设以下变量已经被定义:
# x_train, y_train, x_test, y_test, patch_size, num_patches, latent_dim, projection_dim, num_heads,
# num_transformer_blocks, ffn_units, dropout_rate, num_iterations, classifier_units# 创建Perceiver模型实例
perceiver_classifier = Perceiver(patch_size=patch_size,data_dim=num_patches,latent_dim=latent_dim,projection_dim=projection_dim,num_heads=num_heads,num_transformer_blocks=num_transformer_blocks,ffn_units=ffn_units,dropout_rate=dropout_rate,num_iterations=num_iterations,classifier_units=classifier_units,
)# 运行实验并获取训练历史
history = run_experiment(perceiver_classifier)
代码功能:
- 定义了一个名为
run_experiment
的函数,用于编译、训练和评估传入的模型。 - 使用Adam优化器代替LAMB优化器,因为示例中提到LAMB不受支持。
- 编译模型时,使用稀疏分类交叉熵损失函数,并设置两个评估指标:准确率和前5准确率。
- 创建了两个回调函数:
ReduceLROnPlateau
用于学习率衰减,EarlyStopping
用于提前停止训练。 - 使用
model.fit
方法训练模型,传入训练数据、标签、批量大小、训练周期数、验证集比例和回调函数。 - 在测试集上评估模型性能,并打印测试准确率和前5准确率。
- 返回训练历史对象,可用于进一步分析训练过程或绘制学习曲线。
3.总结和展望
3.1. 总结
本文详细介绍了Perceiver模型的设计和实现,这是一种针对高维度输入数据优化的深度学习架构。从技术背景的探讨到模型的构建、训练和评估,我们涵盖了整个机器学习流程的关键步骤。
首先,我们指出了传统Transformer模型在处理大规模输入时面临的挑战,如内存消耗大、推理成本高、低并行性问题,以及硬件资源的限制。针对这些问题,我们提出了Perceiver模型,它通过引入Latent Bottleneck和非对称注意力机制,有效地降低了计算复杂度,同时保持了模型的性能。
在模型构建部分,我们展示了如何定义Perceiver模型的不同组件,包括图像块编码器、交叉注意力模块、Transformer模块、全局平均池化层和分类头。这些组件共同工作,将高维输入数据逐步转换为紧凑的潜在表示,并最终生成分类结果。
我们还提供了模型训练的详细代码,包括数据预处理、超参数设置、模型编译、训练和评估。通过在CIFAR-100数据集上的实验,我们证明了Perceiver模型在图像分类任务上的有效性。
3.2. 展望
展望未来,Perceiver模型及其变体在多个领域具有广泛的应用前景。以下是几个潜在的发展方向:
-
多模态学习:Perceiver模型可以扩展到多模态数据的处理,如结合图像、文本和音频数据,为多模态任务提供统一的表示学习框架。
-
大规模数据集应用:随着模型优化和硬件发展,Perceiver模型有望在更大规模的数据集上进行训练和应用,如大规模图像识别、自然语言处理等任务。
-
模型压缩和加速:研究如何进一步压缩和加速Perceiver模型,使其能够在资源受限的设备上运行,如移动设备和嵌入式系统。
-
跨领域适应性:探索Perceiver模型在不同领域的适应性,如医疗影像分析、生物信息学和金融风险评估等,以验证其通用性和灵活性。
-
模型解释性:提高模型的可解释性,帮助用户理解Perceiver模型的决策过程,增强对模型输出的信任。
-
持续优化:随着深度学习领域的不断发展,持续优化Perceiver模型的结构和训练策略,以应对新的挑战和需求。
Perceiver模型作为一种新兴的深度学习架构,其在处理高维度数据方面展现出的独特优势和潜力,值得我们进一步研究和探索。随着技术的不断进步,我们期待Perceiver模型能够在更多领域发挥重要作用,推动人工智能技术的发展。
参考文献
[1]Jaegle, A., Gimeno, F., Brock, A., Vinyals, O., Zisserman, A., & Carreira, J. (2021). Perceiver: General Perception with Iterative Attention. In M. Meila & T. Zhang (Eds.), Proceedings of the 38th International Conference on Machine Learning (Vol. 139, pp. 4651–4664). PMLR. [链接: https://proceedings.mlr.press/v139/jaegle21a.html]
[2]Khalid Salama.(2023).Perceiver模型在图像分类任务上的实现示例.[链接: https://keras.io/examples/vision/perceiver_image_classification/]
实验代码示例
"""
## 引言本示例实现了由 Andrew Jaegle 等人提出的
[Perceiver: General Perception with Iterative Attention](https://arxiv.org/abs/2103.03206) 模型,
用于图像分类,并在 CIFAR-100 数据集上进行了演示。Perceiver 模型利用非对称注意力机制迭代地将输入数据压缩成紧凑的潜在瓶颈,
使其能够扩展以处理非常大的输入。换句话说:假设你的输入数据数组(例如图像)有 `M` 个元素(即块),其中 `M` 很大。
在标准 Transformer 模型中,将对 `M` 个元素执行自注意力操作。
此操作的复杂度为 `O(M^2)`。
然而,Perceiver 模型创建了一个大小为 `N` 的潜在数组元素,其中 `N << M`,
并迭代执行以下两个操作:1. 潜在数组和数据数组之间的交叉注意力 Transformer 操作 - 此操作的复杂度为 `O(M.N)`。
2. 潜在数组上的自注意力 Transformer 操作 - 此操作的复杂度为 `O(N^2)`。本示例需要 Keras 3.0 或更高版本。
""""""
## 设置
"""import keras
from keras import layers, activations, ops"""
## 准备数据
"""num_classes = 100
input_shape = (32, 32, 3)# 加载 CIFAR-100 数据集
(x_train, y_train), (x_test, y_test) = keras.datasets.cifar100.load_data()# 打印训练和测试数据的形状
print(f"x_train 的形状: {x_train.shape} - y_train 的形状: {y_train.shape}")
print(f"x_test 的形状: {x_test.shape} - y_test 的形状: {y_test.shape}")"""
## 配置超参数
"""# 设置学习率
learning_rate = 0.001
# 设置权重衰减
weight_decay = 0.0001
# 设置批量大小
batch_size = 64
# 设置训练周期数,建议运行 50 个周期以观察准确率的提高
num_epochs = 2
# 设置Dropout比率
dropout_rate = 0.2
# 设置图像尺寸,我们将输入图像调整到此大小
image_size = 64
# 设置从输入图像中提取的块的尺寸
patch_size = 2
# 计算每张图像的块的数量
num_patches = (image_size // patch_size) ** 2
# 设置潜在数组的尺寸
latent_dim = 256
# 设置数据和潜在数组中每个元素的嵌入尺寸
projection_dim = 256
# 设置Transformer头的数量
num_heads = 8
# 设置Transformer Feedforward网络的尺寸
ffn_units = [projection_dim,projection_dim,
]
# 设置Transformer块的数量
num_transformer_blocks = 4
# 设置交叉注意力和Transformer模块的重复次数
num_iterations = 2
# 设置最终分类器的Feedforward网络的尺寸
classifier_units = [projection_dim,num_classes,
]# 打印图像尺寸信息
print(f"图像尺寸: {image_size} X {image_size} = {image_size ** 2}")
# 打印块尺寸信息
print(f"块尺寸: {patch_size} X {patch_size} = {patch_size ** 2} ")
# 打印每张图像的块数量
print(f"每张图像的块数: {num_patches}")
# 打印每个块的元素数量(3个通道)
print(f"每个块的元素数(3个通道): {(patch_size ** 2) * 3}")
# 打印潜在数组的形状
print(f"潜在数组形状: {latent_dim} X {projection_dim}")
# 打印数据数组的形状
print(f"数据数组形状: {num_patches} X {projection_dim}")"""
注意:为了将每个像素作为数据数组中的独立输入使用,
应将 `patch_size` 设置为 1。
""""""
## 使用数据增强
"""# 创建数据增强序列模型
data_augmentation = keras.Sequential([# 对数据进行标准化处理,使数据具有零均值和单位方差layers.Normalization(),# 调整图像大小至指定尺寸layers.Resizing(image_size, image_size),# 水平平移图像layers.RandomFlip("horizontal"),# 随机缩放图像的高度和宽度,因子为0.2layers.RandomZoom(height_factor=0.2, width_factor=0.2),],name="data_augmentation",
)
# 计算训练数据的均值和方差,以进行标准化处理
data_augmentation.layers[0].adapt(x_train)"""
## 实现前馈网络 (FFN)
"""def create_ffn(hidden_units, dropout_rate):ffn_layers = []for units in hidden_units[:-1]:# 添加带有GELU激活函数的全连接层ffn_layers.append(layers.Dense(units, activation=activations.gelu))# 添加最后一个全连接层,不使用激活函数ffn_layers.append(layers.Dense(units=hidden_units[-1]))# 添加Dropout层ffn_layers.append(layers.Dropout(dropout_rate))# 使用Sequential模型堆叠FFN层ffn = keras.Sequential(ffn_layers)return ffn"""
## 实现图像块创建作为层
"""class Patches(layers.Layer):def __init__(self, patch_size):super().__init__()# 初始化块大小self.patch_size = patch_sizedef call(self, images):# 获取图像的批量大小batch_size = ops.shape(images)[0]# 使用 TensorFlow 的 extract_patches 函数从图像中提取块patches = ops.image.extract_patches(image=images,size=(self.patch_size, self.patch_size),strides=(self.patch_size, self.patch_size),dilation_rate=1,padding="valid",)# 计算每个块的维度patch_dims = patches.shape[-1]# 重塑块的形状patches = ops.reshape(patches, [batch_size, -1, patch_dims])return patches"""
## 实现补丁编码层`PatchEncoder` 层将通过线性变换将补丁投影到大小为 `latent_dim` 的向量中。
此外,它还向投影后的向量添加可学习的定位嵌入。注意:原始的 Perceiver 论文使用傅里叶特征位置编码。
"""class PatchEncoder(layers.Layer):def __init__(self, num_patches, projection_dim):super().__init__()# 设置数据数组中的块的数量self.num_patches = num_patches# 创建一个全连接层,用于将每个块投影到更高维度的空间self.projection = layers.Dense(units=projection_dim)# 创建一个嵌入层,用于为每个位置的块添加位置信息self.position_embedding = layers.Embedding(input_dim=num_patches, # 输入维度等于块的数量output_dim=projection_dim # 输出维度等于投影维度)def call(self, patches):# 获取每个块的位置索引positions = ops.arange(start=0, stop=self.num_patches, step=1)# 使用全连接层对每个块进行投影encoded = self.projection(patches) + self.position_embedding(positions)return encoded"""
## 构建 Perceiver 模型Perceiver 模型由两个模块组成:交叉注意力模块和标准带有自注意力的 Transformer。
""""""
### 交叉注意力模块交叉注意力期望一个形状为 `(latent_dim, projection_dim)` 的潜在数组,
以及一个形状为 `(data_dim, projection_dim)` 的数据数组作为输入,
以产生一个形状为 `(latent_dim, projection_dim)` 的潜在数组作为输出。
为了应用交叉注意力,查询向量从潜在数组生成,
而键和值向量从编码后的图像生成。注意:在此示例中,数据数组是图像,
其中 `data_dim` 设置为 `num_patches`。
"""def create_cross_attention_module(latent_dim, data_dim, projection_dim, ffn_units, dropout_rate
):# 定义输入层inputs = {"latent_array": layers.Input(shape=(latent_dim, projection_dim), name="latent_array"),"data_array": layers.Input(shape=(data_dim, projection_dim), name="data_array"),}# 对输入的潜在数组和数据数组应用层归一化latent_array = layers.LayerNormalization(epsilon=1e-6)(inputs["latent_array"])data_array = layers.LayerNormalization(epsilon=1e-6)(inputs["data_array"])# 创建查询张量:[1, latent_dim, projection_dim]。query = layers.Dense(units=projection_dim)(latent_array)# 创建键张量:[batch_size, data_dim, projection_dim]。key = layers.Dense(units=projection_dim)(data_array)# 创建值张量:[batch_size, data_dim, projection_dim]。value = layers.Dense(units=projection_dim)(data_array)# 生成交叉注意力输出:[batch_size, latent_dim, projection_dim]。attention_output = layers.Attention(use_scale=True, dropout=0.1)([query, key, value], return_attention_scores=False)# 第一个跳跃连接。attention_output = layers.Add()([attention_output, latent_array])# 应用层归一化。attention_output = layers.LayerNormalization(epsilon=1e-6)(attention_output)# 应用前馈网络。ffn = create_ffn(hidden_units=ffn_units, dropout_rate=dropout_rate)outputs = ffn(attention_output)# 第二个跳跃连接。outputs = layers.Add()([outputs, attention_output])# 创建 Keras 模型。model = keras.Model(inputs=inputs, outputs=outputs)return model"""
### Transformer 模块Transformer 接收来自交叉注意力模块的输出潜在向量作为输入,
对其 `latent_dim` 元素应用多头自注意力,然后是前馈网络,
以产生另一个形状为 `(latent_dim, projection_dim)` 的潜在数组。
"""def create_transformer_module(latent_dim,projection_dim,num_heads,num_transformer_blocks,ffn_units,dropout_rate,
):# 输入形状:[1, latent_dim, projection_dim]inputs = layers.Input(shape=(latent_dim, projection_dim))x0 = inputs# 创建多个 Transformer 块的层。for _ in range(num_transformer_blocks):# 应用第一层归一化。x1 = layers.LayerNormalization(epsilon=1e-6)(x0)# 创建多头自注意力层。attention_output = layers.MultiHeadAttention(num_heads=num_heads, key_dim=projection_dim, dropout=0.1)(x1, x1)# 第一个跳跃连接。x2 = layers.Add()([attention_output, x0])# 应用第二层归一化。x3 = layers.LayerNormalization(epsilon=1e-6)(x2)# 应用前馈网络。ffn = create_ffn(hidden_units=ffn_units, dropout_rate=dropout_rate)x3 = ffn(x3)# 第二个跳跃连接。x0 = layers.Add()([x3, x2])# 创建 Keras 模型。model = keras.Model(inputs=inputs, outputs=x0)return model"""
### Perceiver 模型Perceiver 模型通过重复 `num_iterations` 次交叉注意力和 Transformer 模块,
使用共享权重和跳跃连接,允许潜在数组根据需要从输入图像中迭代提取信息。
"""class Perceiver(keras.Model):def __init__(self, **kwargs):super().__init__(**kwargs)# 提取模型参数self.latent_dim = kwargs['latent_dim']self.data_dim = kwargs['data_dim']self.patch_size = kwargs['patch_size']self.projection_dim = kwargs['projection_dim']self.num_heads = kwargs['num_heads']self.num_transformer_blocks = kwargs['num_transformer_blocks']self.ffn_units = kwargs['ffn_units']self.dropout_rate = kwargs['dropout_rate']self.num_iterations = kwargs['num_iterations']self.classifier_units = kwargs['classifier_units']def build(self, input_shape):# 创建潜在数组self.latent_array = self.add_weight(shape=(self.latent_dim, self.projection_dim),initializer="random_normal",trainable=True,)# 创建图像块分割模块self.patcher = Patches(self.patch_size)# 创建补丁编码器self.patch_encoder = PatchEncoder(self.data_dim, self.projection_dim)# 创建交叉注意力模块self.cross_attention = create_cross_attention_module(self.latent_dim,self.data_dim,self.projection_dim,self.ffn_units,self.dropout_rate,)# 创建 Transformer 模块self.transformer = create_transformer_module(self.latent_dim,self.projection_dim,self.num_heads,self.num_transformer_blocks,self.ffn_units,self.dropout_rate,)# 创建全局平均池化层self.global_average_pooling = layers.GlobalAveragePooling1D()# 创建分类头self.classification_head = create_ffn(hidden_units=self.classifier_units, dropout_rate=self.dropout_rate)super().build(input_shape)def call(self, inputs):# 对输入数据进行增强augmented = data_augmentation(inputs)# 创建块patches = self.patcher(augmented)# 编码块encoded_patches = self.patch_encoder(patches)# 准备交叉注意力输入cross_attention_inputs = {"latent_array": ops.expand_dims(self.latent_array, 0),"data_array": encoded_patches,}# 迭代地应用交叉注意力和 Transformer 模块for _ in range(self.num_iterations):# 应用从潜在数组到数据数组的交叉注意力latent_array = self.cross_attention(cross_attention_inputs)# 应用自注意力 Transformer 到潜在数组latent_array = self.transformer(latent_array)# 设置下一次迭代的潜在数组cross_attention_inputs["latent_array"] = latent_array# 应用全局平均池化,生成 [batch_size, projection_dim] 表示张量representation = self.global_average_pooling(latent_array)# 生成 logitslogits = self.classification_head(representation)return logits"""
## 编译、训练并评估模型
"""def run_experiment(model):# 创建优化器,使用 Adam 而非 LAMB,因为 LAMB 目前不受支持optimizer = keras.optimizers.Adam(learning_rate=learning_rate)# 编译模型model.compile(optimizer=optimizer,loss=keras.losses.SparseCategoricalCrossentropy(from_logits=True),metrics=[keras.metrics.SparseCategoricalAccuracy(name="acc"),keras.metrics.SparseTopKCategoricalAccuracy(5, name="top5-acc"),],)# 创建学习率衰减的回调函数reduce_lr = keras.callbacks.ReduceLROnPlateau(monitor="val_loss", factor=0.2, patience=3)# 创建提前停止训练的回调函数early_stopping = keras.callbacks.EarlyStopping(monitor="val_loss", patience=15, restore_best_weights=True)# 训练模型history = model.fit(x=x_train,y=y_train,batch_size=batch_size,epochs=num_epochs,validation_split=0.1,callbacks=[early_stopping, reduce_lr],)# 在测试集上评估模型性能_, accuracy, top_5_accuracy = model.evaluate(x_test, y_test)# 打印测试准确率和前5准确率print(f"测试准确率: {round(accuracy * 100, 2)}%")print(f"测试前5准确率: {round(top_5_accuracy * 100, 2)}%")# 返回训练历史,用于绘制学习曲线return history"""
注意:使用当前设置在 V100 GPU 上训练 perceiver 模型大约需要 200 秒。
"""# 创建 Perceiver 模型实例
perceiver_classifier = Perceiver(patch_size,num_patches,latent_dim,projection_dim,num_heads,num_transformer_blocks,ffn_units,dropout_rate,num_iterations,classifier_units,
)# 运行实验并获取训练历史
history = run_experiment(perceiver_classifier)"""
经过 40 个周期后,Perceiver 模型在测试数据上达到了大约 53% 的准确率和 81% 的前5准确率。如 [Perceiver 论文](https://arxiv.org/abs/2103.03206) 中的消融研究所述,
你可以通过增加潜在数组的大小、增加潜在数组和数据数组元素的(投影)维度、
增加 Transformer 模块中的块数以及增加应用交叉注意力和潜在 Transformer 模块的迭代次数来获得更好的结果。
你也可以尝试增加输入图像的大小和使用不同的块尺寸。Perceiver 从增加模型大小中受益。然而,更大的模型需要更大的加速器来适应并有效训练。
这就是为什么在 Perceiver 论文中他们使用了 32 个 TPU 核心来运行实验。
"""
这篇关于【机器学习】使用Perceiver模型解决Transformer效率低下问题的文章就介绍到这儿,希望我们推荐的文章对编程师们有所帮助!