Hugging Face 将 Perceiver IO 加入 Transformers,支持任意模态
Perceiver IO: a scalable, fully-attentional model that works on any modality
Hugging Face 将 Perceiver IO 加入 Transformers,这是首个可处理文本、图像、音频、视频、点云及其组合的 Transformer 类模型,随模型提供图像分类与光流预测示例和多个 notebook。
原文给出 Perceiver IO 加入 Transformers 的实现细节与多模态示例,读者可据此判断跨模态统一架构的可用性。
TLDR
我们已将 Perceiver IO 添加到 Transformers 中,这是第一个能够处理各种模态(文本、图像、音频、视频、点云等)及其组合的基于 Transformer 的神经网络。请查看以下 Spaces 以了解一些示例:
我们还提供了几个 notebook。
下面,您可以找到该模型的技术解释。
引言
Transformer 最初由 Vaswani 等人在 2017 年提出,在 AI 社区引发了一场革命,最初提升了机器翻译的最先进(SOTA)结果。2018 年,BERT 发布,这是一个仅含编码器的 Transformer 模型,它刷新了自然语言处理(NLP)的基准测试,其中最著名的是 GLUE 基准。
不久之后,AI 研究人员开始将 BERT 的思想应用于其他领域。举几个例子:
- Facebook AI 的 Wav2Vec2 表明该架构可以扩展到音频
- Google AI 的 Vision Transformer (ViT) 表明该架构在视觉方面表现非常出色
- 最近,同样来自 Google AI 的 Video Vision Transformer (ViViT) 将该架构应用于视频。
在所有这些领域中,得益于这一强大架构与大规模预训练的结合,最先进的结果得到了显著提升。
然而,Transformer 的架构有一个重要限制:由于其自注意力机制,它在计算和内存方面的扩展性非常差。在每一层中,所有输入都被用来生成查询和键,并计算它们之间的成对点积。因此,如果不进行某种形式的预处理,就无法对高维数据应用自注意力。例如,Wav2Vec2 通过使用特征编码器将原始波形转换为基于时间的特征序列来解决这个问题。Vision Transformer (ViT) 将图像划分为一系列不重叠的 patch,作为“token”。Video Vision Transformer (ViViT) 从视频中提取不重叠的时空“tube”,作为“token”。要使 Transformer 在特定模态上工作,通常需要将其离散化为 token 序列。
Perceiver
Perceiver 旨在通过在潜在变量集合上而非输入上使用自注意力机制来解决这一限制。inputs(可以是文本、图像、音频、视频)仅用于与潜在变量进行交叉注意力。这样做的优点是大部分计算发生在潜在空间中,计算成本较低(通常使用 256 或 512 个潜在变量)。由此产生的架构对输入大小没有二次依赖:Transformer 编码器仅线性依赖于输入大小,而潜在注意力则与之无关。在后续论文 Perceiver IO 中,作者扩展了这一思想,使 Perceiver 也能处理任意输出。思路类似:仅使用输出与潜在变量进行交叉注意力。请注意,在本博客文章中,我将交替使用“Perceiver”和“Perceiver IO”来指代 Perceiver IO 模型。
在接下来的部分中,我们将通过介绍 Perceiver IO 在 HuggingFace Transformers 中的实现,更详细地了解其工作原理。HuggingFace Transformers 是一个流行的库,最初实现了基于 Transformer 的 NLP 模型,但现在也开始为其他领域实现这些模型。在下面的章节中,我们将以张量的形状为视角,详细解释 Perceiver 实际上是如何对任意类型的模态进行预处理和后处理的。
HuggingFace Transformers 中的所有 Perceiver 变体都基于 PerceiverModel 类。要初始化一个 PerceiverModel,可以向模型提供 3 个额外的实例:
- 一个预处理器
- 一个解码器
- 一个后处理器。
请注意,这些都是可选的。只有在尚未自行嵌入 inputs(如文本、图像、音频、视频)的情况下,才需要 preprocessor。只有在希望将 Perceiver 编码器的输出(即潜在变量的最后隐藏状态)解码为更有用的内容(如分类 logits 或光流)时,才需要 decoder。只有在希望将解码器的输出转换为特定特征时,才需要 postprocessor(这仅在执行自动编码时需要,我们将在后面看到)。架构概览如下图所示。

Perceiver 架构。
换句话说,inputs(可以是任意模态或其组合)首先可选地使用 preprocessor 进行预处理。接下来,预处理后的输入与 Perceiver 编码器的潜在变量执行交叉注意力操作。在此操作中,潜在变量产生查询(Q),而预处理后的输入产生键和值(KV)。此操作之后,Perceiver 编码器采用一个(可重复的)自注意力层块来更新潜在变量的嵌入。编码器最终将产生一个形状为 (batch_size, num_latents, d_latents) 的张量,包含潜在变量的最后隐藏状态。接下来,有一个可选的 decoder,可用于将潜在变量的最终隐藏状态解码为更有用的内容,例如分类 logits。这是通过执行交叉注意力操作完成的,其中可训练的嵌入用于产生查询(Q),而潜在变量用于产生键和值(KV)。最后,有一个可选的 postprocessor,可用于将解码器输出后处理为特定特征。
让我们首先展示 Perceiver 是如何实现以处理文本的。
用于文本的 Perceiver
假设有人想将 Perceiver 应用于执行文本分类。由于 Perceiver 自注意力机制的内存和时间需求不依赖于输入的大小,因此可以直接向模型提供原始 UTF-8 字节。这是有益的,因为熟悉的基于 Transformer 的模型(如 BERT 和 RoBERTa)都采用某种形式的显式分词,例如 WordPiece、BPE 或 SentencePiece,这可能 有害。为了与 BERT(使用 512 个子词标记的序列长度)进行公平比较,作者使用了 2048 字节的输入序列。假设还添加了批次维度,那么模型的 inputs 形状为 (batch_size, 2048)。inputs 包含单个文本的字节 ID(类似于 BERT 的 input_ids)。可以使用 PerceiverTokenizer 将文本转换为字节 ID 序列,并填充至长度 2048:
from transformers import PerceiverTokenizer
tokenizer = PerceiverTokenizer.from_pretrained("deepmind/language-perceiver")
text = "hello world"
inputs = tokenizer(text, padding="max_length", return_tensors="pt").input_ids
在这种情况下,我们向模型提供了一个PerceiverTextPreprocessor作为预处理器,它将负责嵌入inputs(即将每个字节ID转换为相应的向量),并添加绝对位置嵌入。作为解码器,我们向模型提供了PerceiverClassificationDecoder(这会将潜在变量的最后隐藏状态转换为分类逻辑值)。不需要后处理器。换句话说,用于文本分类的Perceiver模型(在HuggingFace Transformers中称为PerceiverForSequenceClassification)的实现方式如下:
from torch import nn
from transformers import PerceiverModel
from transformers.models.perceiver.modeling_perceiver import PerceiverTextPreprocessor, PerceiverClassificationDecoder
class PerceiverForSequenceClassification(nn.Module):
def __init__(self, config):
super().__init__(config)
self.perceiver = PerceiverModel(
config,
input_preprocessor=PerceiverTextPreprocessor(config),
decoder=PerceiverClassificationDecoder(
config,
num_channels=config.d_latents,
trainable_position_encoding_kwargs=dict(num_channels=config.d_latents, index_dims=1),
use_query_residual=True,
),
)
这里已经可以看出,解码器使用了可训练的位置编码参数进行初始化。为什么呢?让我们详细了解一下Perceiver IO的工作原理。在初始化时,PerceiverModel内部定义了一组潜在变量,如下所示:
from torch import nn
self.latents = nn.Parameter(torch.randn(config.num_latents, config.d_latents))
在Perceiver IO论文中,使用了256个潜在变量,并将潜在变量的维度设置为1280。如果再添加一个批次维度,Perceiver的潜在变量形状为(batch_size, 256, 1280)。首先,预处理器(在初始化时提供)将负责将UTF-8字节ID嵌入到嵌入向量中。因此,PerceiverTextPreprocessor会将形状为(batch_size, 2048)的inputs转换为形状为(batch_size, 2048, 768)的张量——假设每个字节ID被转换为大小为768的向量(这是由PerceiverConfig的d_model属性决定的)。
在此之后,Perceiver IO在形状为(batch_size, 256, 1280)的潜在变量(产生查询)和形状为(batch_size, 2048, 768)的预处理输入(产生键和值)之间应用交叉注意力。这个初始交叉注意力操作的输出是一个形状与查询相同的张量(在这种情况下,查询就是潜在变量)。换句话说,交叉注意力操作的输出形状为(batch_size, 256, 1280)。
接下来,应用一个(可重复的)自注意力层块来更新潜在变量的表示。请注意,这些不依赖于所提供输入的长度(即字节),因为它们仅在交叉注意力操作期间使用。在Perceiver IO论文中,使用了由26个自注意力层组成的单个块(每个层有8个注意力头)来更新文本模型的潜在变量表示。请注意,经过这26个自注意力层后的输出仍然与最初提供给编码器的输入形状相同:(batch_size, 256, 1280)。这些也被称为潜在变量的“最后隐藏状态”。这与提供给BERT的标记的“最后隐藏状态”非常相似。
好的,现在得到了形状为(batch_size, 256, 1280)的最终隐藏状态。很好,但实际上我们想将这些转换为形状为(batch_size, num_labels)的分类逻辑值。我们如何让Perceiver输出这些呢?
这由 PerceiverClassificationDecoder 处理。其思路与将输入映射到潜在空间时所做的非常相似:使用交叉注意力。但现在,潜在变量将生成键和值,并且我们提供一个任意形状的张量——在这里,我们将提供一个形状为 (batch_size, 1, num_labels) 的张量作为查询(作者称之为“解码器查询”,因为它们用于解码器)。该张量将在训练开始时随机初始化,并进行端到端训练。可以看到,我们只是提供了一个长度为 1 的虚拟序列维度。注意,QKV 注意力层的输出形状始终与查询的形状相同——因此解码器将输出形状为 (batch_size, 1, num_labels) 的张量。然后解码器只需将这个张量压缩为形状 (batch_size, num_labels),瞧,就得到了分类 logits1。
很棒,不是吗?Perceiver 的作者还展示了,可以像 BERT 一样直接预训练 Perceiver 进行掩码语言建模。这个模型也在 HuggingFace Transformers 中提供,称为 PerceiverForMaskedLM。与 PerceiverForSequenceClassification 的唯一区别是,它不使用 PerceiverClassificationDecoder 作为解码器,而是使用 PerceiverBasicDecoder,将潜在变量解码为形状为 (batch_size, 2048, 1280) 的张量。之后,添加一个语言建模头,将其转换为形状为 (batch_size, 2048, vocab_size) 的张量。Perceiver 的词表大小仅为 262,即 256 个 UTF-8 字节 ID 以及 6 个特殊标记。通过在英文维基百科和 C4 上预训练 Perceiver,作者表明,微调后可以在 GLUE 上达到 81.8 的总体分数。
Perceiver 用于图像
现在我们已经了解了如何将 Perceiver 应用于文本分类,将其应用于图像分类也很直接。唯一的区别是,我们将向模型提供不同的 preprocessor,用于嵌入图像 inputs。Perceiver 的作者实际上尝试了 3 种不同的预处理方式:
- 展平像素值,应用卷积核大小为 1 的卷积层,并添加学习到的绝对一维位置嵌入。
- 展平像素值,并添加固定的二维傅里叶位置嵌入。
- 应用二维卷积 + 最大池化层,并添加固定的二维傅里叶位置嵌入。
这些都在 Transformers 库中实现,分别称为 PerceiverForImageClassificationLearned、PerceiverForImageClassificationFourier 和 PerceiverForImageClassificationConvProcessing。它们仅在 PerceiverImagePreprocessor 的配置上有所不同。让我们仔细看看 PerceiverForImageClassificationLearned。它如下初始化一个 PerceiverModel:
from torch import nn
from transformers import PerceiverModel
from transformers.models.perceiver.modeling_perceiver import PerceiverImagePreprocessor, PerceiverClassificationDecoder
class PerceiverForImageClassificationLearned(nn.Module):
def __init__(self, config):
super().__init__(config)
self.perceiver = PerceiverModel(
config,
input_preprocessor=PerceiverImagePreprocessor(
config,
prep_type="conv1x1",
spatial_downsample=1,
out_channels=256,
position_encoding_type="trainable",
concat_or_add_pos="concat",
project_pos_dim=256,
trainable_position_encoding_kwargs=dict(num_channels=256, index_dims=config.image_size ** 2),
),
decoder=PerceiverClassificationDecoder(
config,
num_channels=config.d_latents,
trainable_position_encoding_kwargs=dict(num_channels=config.d_latents, index_dims=1),
use_query_residual=True,
),
)
可以看到,PerceiverImagePreprocessor 是用 prep_type = "conv1x1" 初始化的,并且添加了可训练位置编码的参数。那么这个预处理器具体是如何工作的呢?假设我们向模型提供一批图像。比如说,首先应用中心裁剪到分辨率 224,并对颜色通道进行归一化,使得 inputs 的形状为 (batch_size, num_channels, height, width) = (batch_size, 3, 224, 224)。为此,可以使用 PerceiverImageProcessor,如下所示:
from transformers import PerceiverImageProcessor
import requests
from PIL import Image
processor = PerceiverImageProcessor.from_pretrained("deepmind/vision-perceiver")
url = 'http://images.cocodataset.org/val2017/000000039769.jpg'
image = Image.open(requests.get(url, stream=True).raw)
inputs = processor(image, return_tensors="pt").pixel_values
PerceiverImagePreprocessor(使用上文定义的设置)会先应用一个卷积核大小为 (1, 1) 的卷积层,将 inputs 转换为形状为 (batch_size, 256, 224, 224) 的张量——从而增加了通道维度。随后它会将通道维度放到最后——所以现在得到的张量形状为 (batch_size, 224, 224, 256)。接着,它展平空间(高度 + 宽度)维度,从而得到形状为 (batch_size, 50176, 256) 的张量。下一步,它将其与可训练的 1D 位置嵌入进行拼接。由于位置嵌入的维度被定义为 256(参见上文的 num_channels 参数),最终得到的张量形状为 (batch_size, 50176, 512)。该张量将用于与潜变量进行交叉注意力操作。
作者为所有图像模型使用 512 个潜变量,并将潜变量的维度设为 1024。因此,潜变量是一个形状为 (batch_size, 512, 1024) 的张量——假设我们添加一个批次维度。交叉注意力层接收形状为 (batch_size, 512, 1024) 的查询,以及形状为 (batch_size, 50176, 512) 的键 + 值作为输入,并生成一个与查询形状相同的张量,因此输出一个形状为 (batch_size, 512, 1024) 的新张量。接着,一个由 6 个自注意力层组成的块被重复应用(8 次),以生成潜变量的最终隐藏状态,形状为 (batch_size, 512, 1024)。为了将这些转换为分类 logits,使用了 PerceiverClassificationDecoder,其工作方式与文本分类类似:它使用潜变量作为键 + 值,并使用形状为 (batch_size, 1, num_labels) 的可训练位置嵌入作为查询。交叉注意力操作的输出是一个形状为 (batch_size, 1, num_labels) 的张量,将其压缩后得到形状为 (batch_size, num_labels) 的分类 logits。
Perceiver 作者表明,与主要为图像分类设计的模型(如 ResNet 或 ViT)相比,该模型能够取得强劲的结果。在 JFT 上进行大规模预训练后,使用 conv+maxpool 预处理的模型(PerceiverForImageClassificationConvProcessing)在 ImageNet 上达到了 84.5 的 top-1 准确率。值得注意的是,PerceiverForImageClassificationLearned,即仅采用 1D 完全学习的位置编码的模型,尽管没有关于图像 2D 结构的特权信息,仍达到了 72.7 的 top-1 准确率。
用于光流的 Perceiver
作者表明,让 Perceiver 也能用于光流是很容易的,光流是计算机视觉中一个已有数十年历史的问题,有着许多更广泛的应用。关于光流的介绍,我参考这篇博客文章。给定同一场景的两张图像(例如视频中连续的两帧),任务是估计第一张图像中每个像素的 2D 位移。现有算法相当依赖手工设计且复杂,然而使用 Perceiver,这变得相对简单。该模型在 Transformers 库中实现,并作为 PerceiverForOpticalFlow 提供。其实现方式如下:
from torch import nn
from transformers import PerceiverModel
from transformers.models.perceiver.modeling_perceiver import PerceiverImagePreprocessor, PerceiverOpticalFlowDecoder
class PerceiverForOpticalFlow(nn.Module):
def __init__(self, config):
super().__init__(config)
fourier_position_encoding_kwargs_preprocessor = dict(
num_bands=64,
max_resolution=config.train_size,
sine_only=False,
concat_pos=True,
)
fourier_position_encoding_kwargs_decoder = dict(
concat_pos=True, max_resolution=config.train_size, num_bands=64, sine_only=False
)
image_preprocessor = PerceiverImagePreprocessor(
config,
prep_type="patches",
spatial_downsample=1,
conv_after_patching=True,
conv_after_patching_in_channels=54,
temporal_downsample=2,
position_encoding_type="fourier",
# position_encoding_kwargs
fourier_position_encoding_kwargs=fourier_position_encoding_kwargs_preprocessor,
)
self.perceiver = PerceiverModel(
config,
input_preprocessor=image_preprocessor,
decoder=PerceiverOpticalFlowDecoder(
config,
num_channels=image_preprocessor.num_channels,
output_image_shape=config.train_size,
rescale_factor=100.0,
use_query_residual=False,
output_num_channels=2,
position_encoding_type="fourier",
fourier_position_encoding_kwargs=fourier_position_encoding_kwargs_decoder,
),
)
正如所见,PerceiverImagePreprocessor 被用作预处理器(即为与 latents 进行交叉注意力操作准备两张图像),而 PerceiverOpticalFlowDecoder 被用作解码器(即将 latents 的最终隐藏状态解码为实际预测的 flow)。对于这两帧中的每一帧,作者围绕每个像素提取一个 3 x 3 的 patch,从而为每个像素得到 3 x 3 x 3 = 27 个值(因为每个像素还有 3 个颜色通道)。作者使用的训练分辨率为 (368, 496)。如果将每个训练样本中两帧大小为 (368, 496) 的图像堆叠在一起,那么输入模型的 inputs 形状为 (batch_size, 2, 27, 368, 496)。
预处理器(使用上述定义的设置)首先沿通道维度拼接这些帧,得到一个形状为 (batch_size, 368, 496, 54) 的张量——假设也将通道维度移到最后。作者在论文中(第 8 页)解释了为什么沿通道维度拼接是合理的。接下来,空间维度被展平,得到形状为 (batch_size, 368*496, 54) = (batch_size, 182528, 54) 的张量。然后,位置嵌入(每个维度为 258)被拼接上去,得到最终预处理输入,形状为 (batch_size, 182528, 322)。这些将用于与 latents 进行交叉注意力。
作者为光流模型使用了 2048 个 latents(没错,2048 个!),每个 latent 的维度为 512。因此,latents 的形状为 (batch_size, 2048, 512)。交叉注意力之后,再次得到一个相同形状的张量(因为 latents 充当查询)。接下来,应用一个由 24 个自注意力层组成的单个块(每个层有 16 个注意力头)来更新 latents 的嵌入。
为了将 latents 的最终隐藏状态解码为实际预测的 flow,PerceiverOpticalFlowDecoder 简单地使用形状为 (batch_size, 182528, 322) 的预处理输入作为交叉注意力操作的查询。接下来,这些被投影为形状为 (batch_size, 182528, 2) 的张量。最后,将其重新缩放并重塑回原始图像大小,以得到形状为 (batch_size, 368, 496, 2) 的预测 flow。作者声称,在 Sintel 和 KITTI 等重要基准上,在 AutoFlow(一个包含 400,000 个标注图像对的大型合成数据集)上训练时取得了最先进的结果。
下面的视频展示了在 2 个示例上的预测 flow。
Perceiver IO 的光流估计。每个像素的颜色表示模型估计的运动方向和速度,如右侧图例所示。
用于多模态自动编码的 Perceiver
作者还将 Perceiver 用于多模态自编码。多模态自编码的目标是学习一个模型,该模型能够在架构引入的瓶颈存在的情况下,准确重建多模态输入。作者在 Kinetics-700 数据集 上训练该模型,其中每个样本由一系列图像(即帧)、音频和一个类别标签(700 个可能标签之一)组成。该模型也在 HuggingFace Transformers 中实现,并可作为 PerceiverForMultimodalAutoencoding 使用。为简洁起见,我将省略定义此模型的代码,但需要注意的重要一点是,它使用 PerceiverMultimodalPreprocessor 来为模型准备 inputs。该预处理器将首先分别对每种模态(图像、音频、标签)使用各自的预处理器。假设有一个包含 16 帧、分辨率为 224x224 的视频和 30,720 个音频样本,则模态的预处理如下:
- 图像——实际上是形状为 (batch_size, 16, 3, 224, 224) 的帧序列——使用
PerceiverImagePreprocessor转换为形状为 (batch_size, 50176, 243) 的张量。这是一个“空间到深度”的变换,之后将固定的二维傅里叶位置嵌入拼接上去。 - 音频的形状为 (batch_size, 30720, 1),使用
PerceiverAudioPreprocessor(将固定的傅里叶位置嵌入拼接到原始音频上)转换为形状为 (batch_size, 1920, 401) 的张量。 - 形状为 (batch_size, 700) 的类别标签使用
PerceiverOneHotPreprocessor转换为形状为 (batch_size, 1, 700) 的张量。换句话说,这个预处理器只是添加了一个虚拟的时间(索引)维度。注意,在评估期间用零张量初始化类别标签,以便让模型充当视频分类器。
接下来,PerceiverMultimodalPreprocessor 将用特定于模态的可训练嵌入对预处理后的模态进行填充,以便沿时间维度拼接。在这种情况下,通道维度最高的模态是类别标签(它有 700 个通道)。作者强制最小填充大小为 4,因此每个模态将被填充到 704 个通道。然后它们可以拼接起来,因此最终预处理后的输入是形状为 (batch_size, 50176 + 1920 + 1, 704) = (batch_size, 52097, 704) 的张量。
作者使用 784 个潜在变量,每个潜在变量的维度为 512。因此,潜在变量的形状为 (batch_size, 784, 512)。在交叉注意力之后,得到的张量形状相同(因为潜在变量充当查询)。接下来,应用一个由 8 个自注意力层(每层有 8 个注意力头)组成的单个块来更新潜在变量的嵌入。
接下来是 PerceiverMultimodalDecoder,它将首先为每种模态分别创建输出查询。然而,由于无法在单次前向传递中解码整个视频,作者改为分块进行自编码。每个块将为每种模态对某些索引维度进行子采样。假设我们将视频处理为 128 个块,则解码器查询的生成方式如下:
- 对于图像模态,解码器查询的总大小为 16x3x224x224 = 802,816。然而,在自编码第一个块时,会对前 802,816/128 = 6272 个值进行子采样。图像输出查询的形状为 (batch_size, 6272, 195)——195 来自使用了固定的傅里叶位置嵌入。
- 对于音频模态,总共有 30,720 个输入值。然而,仅对前 30720/128/16 = 15 个值进行子采样。因此,音频查询的形状为 (batch_size, 15, 385)。这里的 385 来自使用了固定的傅里叶位置嵌入。
- 对于类别标签模态,无需子采样。因此,子采样索引设为 1。标签输出查询的形状为 (batch_size, 1, 1024)。查询使用可训练的位置嵌入(大小为 1024)。
与预处理器类似,PerceiverMultimodalDecoder 将不同模态填充到相同的通道数,以便沿着时间维度拼接模态特定的查询。这里,类别标签再次拥有最高的通道数(1024),并且作者强制最小填充大小为 2,因此每个模态都将被填充至 1026 个通道。拼接后,最终解码器查询的形状为 (batch_size, 6272 + 15 + 1, 1026) = (batch_size, 6288, 1026)。该张量在交叉注意力操作中生成查询,而潜在变量作为键和值。因此,交叉注意力操作的输出是一个形状为 (batch_size, 6288, 1026) 的张量。接下来,PerceiverMultimodalDecoder 使用线性层减少输出通道,得到形状为 (batch_size, 6288, 512) 的张量。
最后是 PerceiverMultimodalPostprocessor。该类对解码器的输出进行后处理,以生成每个模态的实际重建。它首先根据不同的模态分割解码器输出的时间维度:(batch_size, 6272, 512) 用于图像,(batch_size, 15, 512) 用于音频,(batch_size, 1, 512) 用于类别标签。接着,应用各模态相应的后处理器:
- 图像后处理器(在 Transformers 中称为
PerceiverProjectionPostprocessor)简单地将 (batch_size, 6272, 512) 张量转换为形状为 (batch_size, 6272, 3) 的张量——即将最终维度投影为 RGB 值。 PerceiverAudioPostprocessor将 (batch_size, 15, 512) 张量转换为形状为 (batch_size, 240) 的张量。PerceiverClassificationPostprocessor简单地取第一个(也是唯一的索引),得到形状为 (batch_size, 700) 的张量。
于是,现在得到了包含图像、音频和类别标签模态重建的张量。由于整个视频是以块为单位进行自动编码的,需要将每个块的重建拼接起来,以获得整个视频的最终重建。下图展示了一个示例:
上图:原始视频(左),前16帧的重建(右)。视频取自 UCF101 数据集。下图:重建的音频(取自论文)。

上述视频的前5个预测标签。通过掩蔽类别标签,Perceiver 变成了一个视频分类器。
通过这种方法,模型学习了跨3种模态的联合分布。作者确实指出,由于潜在变量在模态间共享且未明确分配,每个模态的重建质量对其损失项的权重和其他训练超参数敏感。通过更加强调分类准确性,他们能够达到45%的top-1准确率,同时保持视频的20.7 PSNR(峰值信噪比)。
Perceiver的其他应用
请注意,Perceiver 的应用没有限制!在原始Perceiver 论文中,作者展示了该架构可用于处理 3D 点云——这是配备激光雷达传感器的自动驾驶汽车常见的问题。他们在 ModelNet40 上训练了模型,这是一个从跨越 40 个对象类别的 3D 三角网格导出的点云数据集。该模型在测试集上达到了 85.7% 的 top-1 准确率,与 PointNet++ 相竞争,后者是一个高度专业化的模型,使用额外的几何特征并执行更高级的增强。
作者还使用 Perceiver 替换了 AlphaStar 中的原始 Transformer,这是用于复杂游戏 StarCraft II 的最先进的强化学习系统。在没有调整任何额外参数的情况下,作者观察到所得智能体达到了与原始 AlphaStar 智能体相同的性能水平,在对人类数据进行行为克隆后,与 Elite 机器人对战的胜率达到 87%。
重要的是要注意,当前实现的模型(如 PerceiverForImageClassificationLearned、PerceiverForOpticalFlow)只是你可以用 Perceiver 做什么的示例。这些都是 PerceiverModel 的不同实例,只是有不同的预处理器和/或解码器(以及可选的后处理器,如多模态自动编码的情况)。人们可以提出新的预处理器、解码器和后处理器,使模型解决不同的问题。例如,可以扩展 Perceiver 来执行命名实体识别(NER)或类似于 BERT 的问答、类似于 Wav2Vec2 的音频分类或类似于 DETR 的目标检测。
结论
在这篇博客文章中,我们介绍了 Perceiver IO 的架构,这是 Google Deepmind 对 Perceiver 的扩展,并展示了其处理各种模态的通用性。Perceiver 的大优势在于,自注意力机制的计算和内存需求不依赖于输入和输出的大小,因为大部分计算发生在潜在空间(一组不太大的向量)中。尽管其架构与任务无关,但该模型能够在语言、视觉、多模态数据和点云等模态上取得出色结果。未来,同时在一个共享的 Perceiver 编码器上训练多个模态,并使用特定模态的预处理器和后处理器,可能会很有趣。正如 Karpathy 所说,这种架构很可能可以将所有模态统一到一个共享空间中,并配有一个编码器/解码器库。
说到库,该模型从今天起可在 HuggingFace Transformers 中使用。看到人们用它构建什么将会很令人兴奋,因为它的应用似乎无穷无尽!
附录
HuggingFace Transformers 中的实现基于原始的 JAX/Haiku 实现,可在此处找到。
HuggingFace Transformers 中 Perceiver IO 模型的文档可在此处获取。
关于 Perceiver 在多种模态上的教程笔记本可在此处找到。
脚注
1 请注意,在官方论文中,作者使用了两层 MLP 来生成输出 logits,此处为简洁起见省略了。↩
来源:Hugging Face Blog · hf-mirror.com