原文:Generative Deep Learning

译者:飞龙

协议:CC BY-NC-SA 4.0

第三部分:应用

在第三部分中,我们将探索迄今为止所见的生成建模技术在图像、文本、音乐和游戏等领域的一些关键应用。我们还将看到如何使用最先进的多模态模型穿越这些领域。

在第九章中,我们将把注意力转向 Transformers,这是一种现代文本生成模型的先进架构。特别是,我们将探索 GPT 的内部工作原理,并使用 Keras 构建我们自己的版本,我们将看到它如何构建了诸如 ChatGPT 之类的工具的基础。

在第十章中,我们将看一些对图像生成产生影响的最重要的 GAN 架构,包括 ProGAN、StyleGAN、StyleGAN2、SAGAN、BigGAN、VQ-GAN 和 ViT VQ-GAN。我们将探索每个架构的关键贡献,并了解这种技术如何随着时间的推移而发展。

第十一章探讨音乐生成,这带来了额外的挑战,比如对音乐音高和节奏进行建模。我们将看到许多适用于文本生成的技术(如 Transformers)也可以应用于这个领域,但我们还将探索一种称为 MuseGAN 的深度学习架构,该架构应用了基于 GAN 的方法来生成音乐。

第十二章展示了生成模型如何在其他机器学习领域中使用,比如强化学习。我们将重点关注“世界模型”论文,该论文展示了如何将生成模型用作代理训练的环境,使其能够在幻想的梦境版本的环境中进行训练,而不是真实环境。

在第十三章中,我们将探索跨越图像和文本等领域的最先进的多模态模型。这包括文本到图像模型,如 DALL.E 2、Imagen 和 Stable Diffusion,以及视觉语言模型,如 Flamingo。

最后,在第十四章中总结了迄今为止的生成人工智能之旅,当前的生成人工智能格局,以及我们未来可能走向何方。我们将探讨生成人工智能如何改变我们的生活和工作方式,以及考虑它是否有潜力在未来几年解锁更深层次的人工智能形式。

第九章:Transformer

我们在第五章中看到,我们可以使用循环神经网络(RNNs)(如 LSTM 和 GRU)在文本数据上构建生成模型。这些自回归模型一次处理一个令牌的顺序数据,不断更新一个捕获输入当前潜在表示的隐藏向量。可以设计 RNN 以通过在隐藏向量上应用密集层和 softmax 激活来预测序列中的下一个单词。直到 2017 年,这被认为是生成文本的最复杂方式,当一篇论文永久改变了文本生成的格局。

介绍

谷歌 Brain 的论文,自信地命名为“注意力就是一切”¹,因推广注意力的概念而闻名,这个概念现在驱动着大多数最先进的文本生成模型。

作者展示了如何创建称为Transformer的强大神经网络,用于顺序建模,而不需要复杂的循环或卷积架构,而只依赖于注意机制。这种方法克服了 RNN 方法的一个关键缺点,即难以并行化,因为它必须一次处理一个令牌的序列。Transformer 是高度可并行化的,使它们能够在大规模数据集上进行训练。

在本章中,我们将深入探讨现代文本生成模型如何利用 Transformer 架构在文本生成挑战中达到最先进的性能。特别是,我们将探索一种称为生成式预训练 Transformer(GPT)的自回归模型,它驱动着 OpenAI 的 GPT-4 模型,被广泛认为是当前文本生成领域的最先进技术。

GPT

OpenAI 于 2018 年 6 月推出了 GPT,在论文“通过生成式预训练改进语言理解”中²,几乎与原始 Transformer 论文出现一年后完全一致。

在本文中,作者展示了如何训练 Transformer 架构以预测序列中的下一个单词,然后随后对特定下游任务进行微调。

GPT 的预训练过程涉及在名为 BookCorpus 的大型文本语料库上训练模型(来自不同流派的 7,000 本未发表书籍的 4.5 GB 文本)。在预训练期间,模型被训练以预测给定前面单词的序列中的下一个单词。这个过程被称为语言建模,用于教导模型理解自然语言的结构和模式。

在预训练之后,GPT 模型可以通过提供较小的、特定于任务的数据集来进行微调以适应特定任务。微调涉及调整模型的参数以更好地适应手头的任务。例如,模型可以针对分类、相似性评分或问题回答等任务进行微调。

自 GPT 架构推出以来,OpenAI 通过发布后续模型如 GPT-2、GPT-3、GPT-3.5 和 GPT-4 对其进行了改进和扩展。这些模型在更大的数据集上进行训练,并具有更大的容量,因此可以生成更复杂和连贯的文本。研究人员和行业从业者广泛采用了 GPT 模型,并为自然语言处理任务的重大进展做出了贡献。

在本章中,我们将构建我们自己的变体 GPT 模型,该模型在较少数据上进行训练,但仍利用相同的组件和基本原则。

运行此示例的代码

此示例的代码可以在位于书籍存储库中的 Jupyter 笔记本中找到,位置为notebooks/09_transformer/01_gpt/gpt.ipynb

该代码改编自由 Apoorv Nandan 创建的优秀GPT 教程,该教程可在 Keras 网站上找到。

葡萄酒评论数据集

我们将使用通过 Kaggle 提供的Wine Reviews 数据集。这是一个包含超过 130,000 条葡萄酒评论的数据集,附带元数据,如描述和价格。

您可以通过在书库中运行 Kaggle 数据集下载脚本来下载数据集,如示例 9-1 所示。这将把葡萄酒评论和相关元数据保存在本地的*/data*文件夹中。

示例 9-1. 下载葡萄酒评论数据集
bash scripts/download_kaggle_data.sh zynicide wine-reviews

`数据准备步骤与第五章中用于准备输入到 LSTM 的数据的步骤是相同的,因此我们不会在这里详细重复它们。如图 9-1 所示,步骤如下:

  1. 加载数据并创建每种葡萄酒的文本字符串描述列表。

  2. 用空格填充标点符号,以便每个标点符号被视为一个单独的单词。

  3. 通过TextVectorization层将字符串传递,对数据进行标记化,并将每个字符串填充/裁剪到固定长度。

  4. 创建一个训练集,其中输入是标记化的文本字符串,输出是预测的相同字符串向后移动一个标记。

https://github.com/OpenDocCN/ibooker-dl-zh/raw/master/docs/gen-dl/img/gdl2_0901.png

图 9-1. Transformer 的数据处理 ## 注意力

了解 GPT 如何工作的第一步是了解注意力机制的工作原理。这个机制是使 Transformer 架构与循环方法在语言建模方面独特和不同的地方。当我们对注意力有了扎实的理解后,我们将看到它如何在 GPT 等 Transformer 架构中使用。

当您写作时,句子中下一个词的选择受到您已经写过的其他单词的影响。例如,假设您开始一个句子如下:

The pink elephant tried to get into the car but it was too

显然,下一个词应该是与big同义的。我们怎么知道这一点?

句子中的某些其他单词对帮助我们做出决定很重要。例如,它是大象而不是树懒,意味着我们更喜欢big而不是slow。如果它是游泳池而不是汽车,我们可能会选择scared作为big的一个可能替代。最后,getting into汽车的行为意味着大小是问题所在——如果大象试图压扁汽车,我们可能会选择fast作为最后一个词,现在it指的是汽车。

句子中的其他单词一点都不重要。例如,大象是粉红色这个事实对我们选择最终词汇没有影响。同样,句子中的次要单词(thebutit等)给句子以语法形式,但在这里并不重要,以确定所需形容词。

换句话说,我们正在关注句子中的某些单词,而基本上忽略其他单词。如果我们的模型也能做同样的事情,那不是很好吗?

Transformer 中的注意力机制(也称为注意力头)旨在做到这一点。它能够决定从输入的哪个位置提取信息,以有效地提取有用信息而不被无关细节混淆。这使得它非常适应各种情况,因为它可以在推断时决定在哪里寻找信息。

相比之下,循环层试图建立一个捕捉每个时间步输入的整体表示的通用隐藏状态。这种方法的一个弱点是,已经合并到隐藏向量中的许多单词对当前任务(例如,预测下一个单词)并不直接相关,正如我们刚刚看到的。注意力头不会遇到这个问题,因为它们可以选择如何从附近的单词中组合信息,具体取决于上下文。

查询、键和值

那么,注意力头如何决定在哪里查找信息呢?在深入细节之前,让我们以高层次的方式探讨它是如何工作的,使用我们的粉色大象示例。

想象一下,我们想预测跟在单词too后面的是什么。为了帮助完成这个任务,其他前面的单词发表意见,但他们的贡献受到他们对自己预测跟在too后面的单词的信心程度的加权。例如,单词elephant可能自信地贡献说,它更有可能是与大小或响度相关的单词,而单词was没有太多可以提供来缩小可能性。

换句话说,我们可以将注意力头视为一种信息检索系统,其中一个“查询”(“后面跟着什么词?”)被转换为一个键/值存储(句子中的其他单词),输出结果是值的加权和,权重由查询和每个键之间的共鸣决定。

我们现在将详细介绍这个过程(图 9-2),再次参考我们的粉色大象句子。

https://github.com/OpenDocCN/ibooker-dl-zh/raw/master/docs/gen-dl/img/gdl2_0902.png

图 9-2。注意力头的机制

查询(Q)可以被视为当前任务的表示(例如,“后面跟着什么词?”)。在这个例子中,它是从单词too的嵌入中导出的,通过将其通过权重矩阵W Q传递来将向量的维度从d e更改为d k。

向量(K)是句子中每个单词的表示——您可以将这些视为每个单词可以帮助的预测任务的描述。它们以类似的方式导出查询,通过将每个嵌入通过权重矩阵W K传递来将每个向量的维度从d e更改为d k。请注意,键和查询具有相同的长度(d k)。

在注意力头内部,每个键与查询之间的向量对之间使用点积进行比较(Q K T)。这就是为什么键和查询必须具有相同的长度。对于特定的键/查询对,这个数字越高,键与查询的共鸣就越强,因此它可以更多地对注意力头的输出做出贡献。结果向量被缩放为d k,以保持向量和的方差稳定(大约等于 1),并且应用 softmax 以确保贡献总和为 1。这是一个注意力权重向量。

向量(V)也是句子中单词的表示——您可以将这些视为每个单词的未加权贡献。它们通过将每个嵌入通过权重矩阵W V传递来导出,以将每个向量的维度从d e更改为d v。请注意,值向量不一定要与键和查询具有相同的长度(但通常为了简单起见)。

值向量乘以注意力权重,给出给定Q,K和V的注意力,如方程 9-1 所示。

方程 9-1。注意力方程

A t t e n t i o n ( Q , K , V ) = s o f t m a x ( QK T d k ) V

从注意力头中获取最终输出向量,将注意力求和得到长度为d v的向量。这个上下文向量捕捉了句子中单词对于预测接下来的单词是什么的任务的混合意见。

多头注意力

没有理由只停留在一个注意力头上!在 Keras 中,我们可以构建一个MultiHeadAttention层,将多个注意力头的输出连接起来,使每个头学习不同的注意力机制,从而使整个层能够学习更复杂的关系。

连接的输出通过一个最终的权重矩阵W O传递,将向量投影到所需的输出维度,这在我们的情况下与查询的输入维度相同(d e),以便层可以顺序堆叠在一起。

图 9-3 展示了一个MultiHeadAttention层的输出是如何构建的。在 Keras 中,我们可以简单地写下示例 9-2 中显示的代码来创建这样一个层。

示例 9-2。在 Keras 中创建一个MultiHeadAttention
layers.MultiHeadAttention(
    num_heads = 4, # ①
    key_dim = 128, # ②
    value_dim = 64, # ③
    output_shape = 256 # ④
    )

这个多头注意力层有四个头。

键(和查询)是长度为 128 的向量。

值(因此也是每个头的输出)是长度为 64 的向量。

输出向量的长度为 256。

https://github.com/OpenDocCN/ibooker-dl-zh/raw/master/docs/gen-dl/img/gdl2_0903.png

图 9-3。一个具有四个头的多头注意力层

因果掩码

到目前为止,我们假设我们的注意力头的查询输入是一个单一的向量。然而,在训练期间为了效率,我们理想情况下希望注意力层能够一次操作输入中的每个单词,为每个单词预测接下来的单词。换句话说,我们希望我们的 GPT 模型能够并行处理一组查询向量(即一个矩阵)。

您可能会认为我们可以将向量批量处理成一个矩阵,让线性代数处理剩下的部分。这是正确的,但我们需要一个额外的步骤——我们需要对查询/键的点积应用一个掩码,以避免未来单词的信息泄漏。这被称为因果掩码,在图 9-4 中显示。

https://github.com/OpenDocCN/ibooker-dl-zh/raw/master/docs/gen-dl/img/gdl2_0904.png

图 9-4。对一批输入查询计算注意力分数的矩阵,使用因果注意力掩码隐藏对查询不可用的键(因为它们在句子中后面)

如果没有这个掩码,我们的 GPT 模型将能够完美地猜测句子中的下一个单词,因为它将使用单词本身的键作为特征!创建因果掩码的代码显示在示例 9-3 中,结果的numpy数组(转置以匹配图表)显示在图 9-5 中。

示例 9-3。因果掩码函数
def causal_attention_mask(batch_size, n_dest, n_src, dtype):
    i = tf.range(n_dest)[:, None]
    j = tf.range(n_src)
    m = i >= j - n_src + n_dest
    mask = tf.cast(m, dtype)
    mask = tf.reshape(mask, [1, n_dest, n_src])
    mult = tf.concat(
        [tf.expand_dims(batch_size, -1), tf.constant([1, 1], dtype=tf.int32)], 0
    )
    return tf.tile(mask, mult)

np.transpose(causal_attention_mask(1, 10, 10, dtype = tf.int32)[0])

https://github.com/OpenDocCN/ibooker-dl-zh/raw/master/docs/gen-dl/img/gdl2_0905.png

图 9-5。作为numpy数组的因果掩码——1 表示未掩码,0 表示掩码
提示

因果掩码仅在解码器 Transformer(如 GPT)中需要,其中任务是根据先前的标记顺序生成标记。在训练期间屏蔽未来标记因此至关重要。

其他类型的 Transformer(例如编码器 Transformer)不需要因果掩码,因为它们不是训练来预测下一个标记。例如,Google 的 BERT 预测给定句子中的掩码单词,因此它可以使用单词之前和之后的上下文。³

我们将在本章末尾更详细地探讨不同类型的 Transformer。

这结束了我们对存在于所有 Transformer 中的多头注意力机制的解释。令人惊讶的是,这样一个有影响力的层的可学习参数仅由每个注意力头的三个密集连接权重矩阵(W Q,W K,W V)和一个进一步的权重矩阵来重塑输出(W O)。在多头注意力层中完全没有卷积或循环机制!

接下来,我们将退一步,看看多头注意力层如何形成更大组件的一部分,这个组件被称为Transformer 块

Transformer 块

Transformer 块是 Transformer 中的一个单一组件,它应用一些跳跃连接、前馈(密集)层和在多头注意力层周围的归一化。Transformer 块的示意图显示在图 9-6 中。

https://github.com/OpenDocCN/ibooker-dl-zh/raw/master/docs/gen-dl/img/gdl2_0906.png

图 9-6。一个 Transformer 块

首先,注意到查询是如何在多头注意力层周围传递并添加到输出中的——这是一个跳跃连接,在现代深度学习架构中很常见。这意味着我们可以构建非常深的神经网络,不会受到梯度消失问题的困扰,因为跳跃连接提供了一个无梯度的高速公路,允许网络将信息向前传递而不中断。

其次,在 Transformer 块中使用层归一化来提供训练过程的稳定性。我们已经在本书中看到了批归一化层的作用,其中每个通道的输出被归一化为均值为 0,标准差为 1。归一化统计量是跨批次和空间维度计算的。

相比之下,在 Transformer 块中,层归一化通过计算跨通道的归一化统计量来归一化批次中每个序列的每个位置。就归一化统计量的计算方式而言,它与批归一化完全相反。显示批归一化和层归一化之间差异的示意图显示在图 9-7 中。

https://github.com/OpenDocCN/ibooker-dl-zh/raw/master/docs/gen-dl/img/gdl2_0907.png

图 9-7。层归一化与批归一化——归一化统计量是跨蓝色单元计算的(来源:Sheng 等人,2020)⁴

层归一化与批归一化

层归一化在原始 GPT 论文中使用,并且通常用于基于文本的任务,以避免在批次中的序列之间创建归一化依赖关系。然而,最近的工作,如 Shen 等人的挑战了这一假设,显示通过一些调整,一种形式的批归一化仍然可以在 Transformer 中使用,胜过更传统的层归一化。

最后,在 Transformer 块中包含了一组前馈(即密集连接)层,以允许组件在网络深入时提取更高级别的特征。

在 Keras 中展示了一个 Transformer 块的实现,详见示例 9-4。

示例 9-4。Keras 中的TransformerBlock
class TransformerBlock(layers.Layer):
    def __init__(self, num_heads, key_dim, embed_dim, ff_dim, dropout_rate=0.1): # ①
        super(TransformerBlock, self).__init__()
        self.num_heads = num_heads
        self.key_dim = key_dim
        self.embed_dim = embed_dim
        self.ff_dim = ff_dim
        self.dropout_rate = dropout_rate
        self.attn = layers.MultiHeadAttention(
            num_heads, key_dim, output_shape = embed_dim
        )
        self.dropout_1 = layers.Dropout(self.dropout_rate)
        self.ln_1 = layers.LayerNormalization(epsilon=1e-6)
        self.ffn_1 = layers.Dense(self.ff_dim, activation="relu")
        self.ffn_2 = layers.Dense(self.embed_dim)
        self.dropout_2 = layers.Dropout(self.dropout_rate)
        self.ln_2 = layers.LayerNormalization(epsilon=1e-6)

    def call(self, inputs):
        input_shape = tf.shape(inputs)
        batch_size = input_shape[0]
        seq_len = input_shape[1]
        causal_mask = causal_attention_mask(
            batch_size, seq_len, seq_len, tf.bool
        ) # ②
        attention_output, attention_scores = self.attn(
            inputs,
            inputs,
            attention_mask=causal_mask,
            return_attention_scores=True
        ) # ③
        attention_output = self.dropout_1(attention_output)
        out1 = self.ln_1(inputs + attention_output) # ④
        ffn_1 = self.ffn_1(out1) # ⑤
        ffn_2 = self.ffn_2(ffn_1)
        ffn_output = self.dropout_2(ffn_2)
        return (self.ln_2(out1 + ffn_output), attention_scores) # ⑥

构成TransformerBlock层的子层在初始化函数中定义。

因果掩码被创建用来隐藏查询中的未来键。

创建了多头注意力层,并指定了注意力掩码。

第一个加和归一化层。

前馈层。

第二个加和归一化层。

位置编码

在我们能够将所有内容整合在一起训练我们的 GPT 模型之前,还有一个最后的步骤要解决。您可能已经注意到,在多头注意力层中,没有任何关心键的顺序的内容。每个键和查询之间的点积是并行计算的,而不是像递归神经网络那样顺序计算。这是一种优势(因为并行化效率提高),但也是一个问题,因为我们显然需要注意力层能够预测以下两个句子的不同输出:

  • 狗看着男孩然后…(叫?)

  • 男孩看着狗然后…(微笑?)

为了解决这个问题,我们在创建初始 Transformer 块的输入时使用一种称为位置编码的技术。我们不仅使用标记嵌入对每个标记进行编码,还使用位置嵌入对标记的位置进行编码。

标记嵌入是使用标准的Embedding层创建的,将每个标记转换为一个学习到的向量。我们可以以相同的方式创建位置嵌入,使用标准的Embedding层将每个整数位置转换为一个学习到的向量。

提示

虽然 GPT 使用Embedding层来嵌入位置,但原始 Transformer 论文使用三角函数——我们将在第十一章中介绍这种替代方法,当我们探索音乐生成时。

为构建联合标记-位置编码,将标记嵌入加到位置嵌入中,如图 9-8 所示。这样,序列中每个单词的含义和位置都被捕捉在一个向量中。

https://github.com/OpenDocCN/ibooker-dl-zh/raw/master/docs/gen-dl/img/gdl2_0908.png

图 9-8. 将标记嵌入添加到位置嵌入以给出标记位置编码

定义我们的TokenAndPositionEmbedding层的代码显示在示例 9-5 中。

示例 9-5. TokenAndPositionEmbedding
class TokenAndPositionEmbedding(layers.Layer):
    def __init__(self, maxlen, vocab_size, embed_dim):
        super(TokenAndPositionEmbedding, self).__init__()
        self.maxlen = maxlen
        self.vocab_size =vocab_size
        self.embed_dim = embed_dim
        self.token_emb = layers.Embedding(
            input_dim=vocab_size, output_dim=embed_dim
        ) # ①
        self.pos_emb = layers.Embedding(input_dim=maxlen, output_dim=embed_dim) # ②

    def call(self, x):
        maxlen = tf.shape(x)[-1]
        positions = tf.range(start=0, limit=maxlen, delta=1)
        positions = self.pos_emb(positions)
        x = self.token_emb(x)
        return x + positions # ③

标记使用Embedding层进行嵌入。

标记的位置也使用Embedding层进行嵌入。

该层的输出是标记和位置嵌入的总和。

训练 GPT

现在我们准备构建和训练我们的 GPT 模型!为了将所有内容整合在一起,我们需要将输入文本通过标记和位置嵌入层,然后通过我们的 Transformer 块。网络的最终输出是一个简单的具有 softmax 激活函数的Dense层,覆盖词汇表中的单词数量。

提示

为简单起见,我们将只使用一个 Transformer 块,而不是论文中的 12 个。

整体架构显示在图 9-9 中,相应的代码在示例 9-6 中提供。

https://github.com/OpenDocCN/ibooker-dl-zh/raw/master/docs/gen-dl/img/gdl2_0909.png

图 9-9. 简化的 GPT 模型架构
示例 9-6. 在 Keras 中的 GPT 模型
MAX_LEN = 80
VOCAB_SIZE = 10000
EMBEDDING_DIM = 256
N_HEADS = 2
KEY_DIM = 256
FEED_FORWARD_DIM = 256

inputs = layers.Input(shape=(None,), dtype=tf.int32) # ①
x = TokenAndPositionEmbedding(MAX_LEN, VOCAB_SIZE, EMBEDDING_DIM)(inputs) # ②
x, attention_scores = TransformerBlock(
    N_HEADS, KEY_DIM, EMBEDDING_DIM, FEED_FORWARD_DIM
)(x) # ③
outputs = layers.Dense(VOCAB_SIZE, activation = 'softmax')(x) # ④
gpt = models.Model(inputs=inputs, outputs=[outputs, attention]) # ⑤
gpt.compile("adam", loss=[losses.SparseCategoricalCrossentropy(), None]) # ⑥
gpt.fit(train_ds, epochs=5)

输入被填充(用零填充)。

文本使用TokenAndPositionEmbedding层进行编码。

编码通过TransformerBlock传递。

转换后的输出通过具有 softmax 激活的Dense层传递,以预测后续单词的分布。

Model以单词标记序列作为输入,并输出预测的后续单词分布。还返回了 Transformer 块的输出,以便我们可以检查模型如何引导其注意力。

模型使用预测的单词分布上的SparseCategoricalCrossentropy损失进行编译。

GPT 的分析

现在我们已经编译并训练了我们的 GPT 模型,我们可以开始使用它生成长文本字符串。我们还可以询问从TransformerBlock输出的注意权重,以了解 Transformer 在生成过程中不同点处寻找信息的位置。

生成文本

我们可以通过以下过程生成新文本:

  1. 将现有单词序列馈送到网络中,并要求它预测接下来的单词。

  2. 将此单词附加到现有序列并重复。

网络将为每个单词输出一组概率,我们可以从中进行抽样,因此我们可以使文本生成具有随机性,而不是确定性。

我们将使用在第五章中引入的相同TextGenerator类进行 LSTM 文本生成,包括指定采样过程的确定性程度的temperature参数。让我们看看这在两个不同的温度值(图 9-10)下是如何运作的。

https://github.com/OpenDocCN/ibooker-dl-zh/raw/master/docs/gen-dl/img/gdl2_0910.png

图 9-10。在temperature = 1.0temperature = 0.5时生成的输出。

关于这两段文字有几点需要注意。首先,两者在风格上与原始训练集中的葡萄酒评论相似。它们都以葡萄酒的产地和类型开头,而葡萄酒类型在整个段落中保持一致(例如,它不会在中途更换颜色)。正如我们在第五章中看到的,使用温度为 1.0 生成的文本更加冒险,因此比温度为 0.5 的示例不够准确。因此,使用温度为 1.0 生成多个样本将导致更多的变化,因为模型正在从具有更大方差的概率分布中进行抽样。

查看注意力分数

我们还可以要求模型告诉我们在决定句子中的下一个单词时,每个单词放置了多少注意力。TransformerBlock输出每个头的注意权重,这是对句子中前面单词的 softmax 分布。

为了证明这一点,图 9-11 显示了三个不同输入提示的前五个具有最高概率的标记,以及两个注意力头的平均注意力,针对每个前面的单词。根据其注意力分数对前面的单词进行着色,两个注意力头的平均值。深蓝色表示对该单词放置更多的注意力。

https://github.com/OpenDocCN/ibooker-dl-zh/raw/master/docs/gen-dl/img/gdl2_0911.png

图 9-11。各种序列后单词概率分布

在第一个示例中,模型密切关注国家(德国),以决定与地区相关的单词。这是有道理的!为了选择一个地区,它需要从与国家相关的单词中获取大量信息,以确保它们匹配。它不需要太关注前两个标记(葡萄酒评论),因为它们不包含有关地区的任何有用信息。

在第二个例子中,它需要参考葡萄(雷司令),因此它关注第一次提到它的时间。它可以通过直接关注这个词来提取这个信息,无论这个词在句子中有多远(在 80 个单词的上限内)。请注意,这与递归神经网络非常不同,后者依赖于隐藏状态来维护整个序列的所有有趣信息,以便在需要时可以利用——这是一种效率低下得多的方法。

最终的序列展示了我们的 GPT 模型如何基于信息的组合选择适当的形容词的例子。这里的注意力再次集中在葡萄(雷司令)上,但也集中在它含有残留糖的事实上。由于雷司令通常是一种甜酒,而且已经提到了糖,因此将其描述为略带甜味而不是略带泥土味是有道理的。

以这种方式询问网络非常有启发性,可以准确了解它从哪里提取信息,以便对每个后续单词做出准确的决策。我强烈建议尝试玩弄输入提示,看看是否可以让模型关注句子中非常遥远的单词,以说服自己关注模型的注意力模型比传统的递归模型更具有力量! # 其他 Transformer

我们的 GPT 模型是一个解码器 Transformer——它一次生成一个标记的文本字符串,并使用因果屏蔽只关注输入字符串中的先前单词。还有编码器 Transformer,它不使用因果屏蔽——相反,它关注整个输入字符串以提取输入的有意义的上下文表示。对于其他任务,比如语言翻译,还有编码器-解码器 Transformer,可以将一个文本字符串翻译成另一个;这种模型包含编码器 Transformer 块和解码器 Transformer 块。

表 9-1 总结了三种 Transformer 的类型,以及每种架构的最佳示例和典型用例。

表 9-1。三种 Transformer 架构

类型 示例 用例
编码器 BERT(谷歌) 句子分类、命名实体识别、抽取式问答
编码器-解码器 T5(谷歌) 摘要、翻译、问答
解码器 GPT-3(OpenAI) 文本生成

一个众所周知的编码器 Transformer 的例子是谷歌开发的双向编码器表示来自 Transformer(BERT)模型,它可以根据缺失单词的上下文预测句子中的缺失单词(Devlin 等,2018)。

编码器 Transformer

编码器 Transformer 通常用于需要全面理解输入的任务,比如句子分类、命名实体识别和抽取式问答。它们不用于文本生成任务,因此我们不会在本书中详细探讨它们——有关更多信息,请参阅 Lewis Tunstall 等人的使用 Transformer 进行自然语言处理(O’Reilly)。

在接下来的章节中,我们将探讨编码器-解码器 Transformer 的工作原理,并讨论 OpenAI 发布的原始 GPT 模型架构的扩展,包括专门为对话应用设计的 ChatGPT。

T5

一个使用编码器-解码器结构的现代 Transformer 的例子是谷歌的 T5 模型。这个模型将一系列任务重新构建为文本到文本的框架,包括翻译、语言可接受性、句子相似性和文档摘要,如图 9-12 所示。

https://github.com/OpenDocCN/ibooker-dl-zh/raw/master/docs/gen-dl/img/gdl2_0912.png

图 9-12。T5 如何将一系列任务重新构建为文本到文本框架的示例,包括翻译、语言可接受性、句子相似性和文档摘要(来源:Raffel et al., 2019

T5 模型架构与原始 Transformer 论文中使用的编码器-解码器架构非常相似,如图 9-13 所示。关键区别在于 T5 是在一个庞大的 750GB 文本语料库(Colossal Clean Crawled Corpus,或 C4)上进行训练的,而原始 Transformer 论文仅关注语言翻译,因此它是在 1.4GB 的英德句对上进行训练的。

https://github.com/OpenDocCN/ibooker-dl-zh/raw/master/docs/gen-dl/img/gdl2_0913.png

图 9-13。编码器-解码器 Transformer 模型:每个灰色框是一个 Transformer 块(来源:Vaswani et al., 2017

这个图表中的大部分内容对我们来说已经很熟悉了——我们可以看到 Transformer 块被重复,并且使用位置嵌入来捕捉输入序列的顺序。这个模型与我们在本章前面构建的 GPT 模型之间的两个关键区别如下:

  • 在左侧,一组编码器Transformer 块对待翻译的序列进行编码。请注意,注意力层上没有因果屏蔽。这是因为我们不生成更多文本来扩展要翻译的序列;我们只想学习一个可以提供给解码器的整个序列的良好表示。因此,编码器中的注意力层可以完全不加屏蔽,以捕捉单词之间的所有交叉依赖关系,无论顺序如何。

  • 在右侧,一组解码器Transformer 块生成翻译文本。初始注意力层是自指的(即,键、值和查询来自相同的输入),并且使用因果屏蔽确保来自未来标记的信息不会泄漏到当前要预测的单词。然而,我们可以看到随后的注意力层从编码器中提取键和值,只留下查询从解码器本身传递。这被称为交叉引用注意力,意味着解码器可以关注输入序列的编码器表示。这就是解码器知道翻译需要传达什么含义的方式!

图 9-14 展示了一个交叉引用注意力的示例。解码器层的两个注意力头能够共同提供单词the的正确德语翻译,当它在the street的上下文中使用时。在德语中,根据名词的性别有三个定冠词(der, die, das),但 Transformer 知道选择die,因为一个注意力头能够关注单词street(德语中的一个女性词),而另一个关注要翻译的单词(the)。

https://github.com/OpenDocCN/ibooker-dl-zh/raw/master/docs/gen-dl/img/gdl2_0914.png

图 9-14。一个示例,展示一个注意力头关注单词“the”,另一个关注单词“street”,以便正确将单词“the”翻译为德语单词“die”,作为“Straße”的女性定冠词
提示

这个例子来自Tensor2Tensor GitHub 存储库,其中包含一个 Colab 笔记本,让您可以玩转一个经过训练的编码器-解码器 Transformer 模型,并查看编码器和解码器的注意力机制如何影响将给定句子翻译成德语。

GPT-3 和 GPT-4

自 2018 年 GPT 的原始出版以来,OpenAI 已发布了多个更新版本,改进了原始模型,如表 9-2 所示。

表 9-2。OpenAI 的 GPT 系列模型的演变

模型 日期 注意力头 词嵌入大小 上下文窗口 参数数量 训练数据
GPT 2018 年 6 月 12 12 768 512 120,000,000 BookCorpus:来自未发表书籍的 4.5 GB 文本
GPT-2 2019 年 2 月 48 48 1,600 1,024 1,500,000,000 WebText:来自 Reddit 外链的 40 GB 文本
GPT-3 2020 年 5 月 96 96 12,888 2,048 175,000,000,000 CommonCrawl,WebText,英文维基百科,书籍语料库等:570 GB
GPT-4 2023 年 3 月 - - - - - -

GPT-3 的模型架构与原始 GPT 模型非常相似,只是规模更大,训练数据更多。在撰写本文时,GPT-4 处于有限的测试阶段——OpenAI 尚未公开发布模型的结构和规模的详细信息,尽管我们知道它能够接受图像作为输入,因此首次跨越成为多模态模型。GPT-3 和 GPT-4 的模型权重不是开源的,尽管这些模型可以通过商业工具和 API获得。

GPT-3 也可以根据您自己的训练数据进行微调——这使您可以提供多个示例,说明它应该如何对特定风格的提示做出反应,通过物理更新网络的权重。在许多情况下,这可能是不必要的,因为 GPT-3 可以通过在提示本身提供几个示例来告诉它如何对特定风格的提示做出反应(这被称为few-shot learning)。微调的好处在于,您不需要在每个单独的输入提示中提供这些示例,从长远来看可以节省成本。

给定系统提示句子的 GPT-3 输出示例显示在图 9-15 中。

https://github.com/OpenDocCN/ibooker-dl-zh/raw/master/docs/gen-dl/img/gdl2_0915.png

图 9-15。GPT-3 如何扩展给定系统提示的示例

诸如 GPT 之类的语言模型在规模上受益巨大——无论是模型权重的数量还是数据集的大小。大型语言模型能力的上限尚未达到,研究人员继续推动着使用越来越大的模型和数据集所能实现的边界。

ChatGPT

在 GPT-4 的测试版发布几个月前,OpenAI 宣布了ChatGPT——这是一个允许用户通过对话界面与其一系列大型语言模型进行交互的工具。2022 年 11 月的原始版本由GPT-3.5提供支持,这个版本比 GPT-3 更强大,经过微调以进行对话回应。

示例对话显示在图 9-16 中。请注意,代理能够在输入之间保持状态,理解第二个问题中提到的attention指的是 Transformer 上下文中的注意力,而不是一个人的专注能力。

https://github.com/OpenDocCN/ibooker-dl-zh/raw/master/docs/gen-dl/img/gdl2_0916.png

图 9-16。ChatGPT 回答有关 Transformer 的问题的示例

在撰写本文时,尚无描述 ChatGPT 工作详细信息的官方论文,但根据官方博客文章,我们知道它使用一种称为reinforcement learning from human feedback(RLHF)的技术来微调 GPT-3.5 模型。这种技术也在 ChatGPT 小组早期的论文⁶中使用,该论文介绍了InstructGPT模型,这是一个经过微调的 GPT-3 模型,专门设计用于更准确地遵循书面说明。

ChatGPT 的训练过程如下:

  1. 监督微调:收集人类编写的对话输入(提示)和期望输出的演示数据集。这用于使用监督学习微调基础语言模型(GPT-3.5)。

  2. 奖励建模:向人类标记者展示提示的示例和几个抽样的模型输出,并要求他们将输出从最好到最差进行排名。训练一个奖励模型,预测给定对话历史的每个输出的得分。

  3. 强化学习:将对话视为一个强化学习环境,其中策略是基础语言模型,初始化为从步骤 1 中微调的模型。给定当前的状态(对话历史),策略输出一个动作(一系列标记),由在步骤 2 中训练的奖励模型评分。然后可以训练一个强化学习算法——近端策略优化(PPO),通过调整语言模型的权重来最大化奖励。

强化学习

有关强化学习的介绍,请参阅第十二章,在那里我们探讨了生成模型如何在强化学习环境中使用。

RLHF 过程如图 9-17 所示。

https://github.com/OpenDocCN/ibooker-dl-zh/raw/master/docs/gen-dl/img/gdl2_0917.png

图 9-17。ChatGPT 中使用的强化学习来自人类反馈微调过程的示意图(来源:OpenAI

虽然 ChatGPT 仍然存在许多限制(例如有时“产生”事实不正确的信息),但它是一个强大的示例,展示了 Transformers 如何用于构建生成模型,可以产生复杂、长期和新颖的输出,往往难以区分是否为人类生成的文本。像 ChatGPT 这样的模型迄今取得的进展证明了人工智能的潜力及其对世界的变革性影响。

此外,显而易见的是,基于人工智能的沟通和互动将继续在未来快速发展。像Visual ChatGPT⁷这样的项目现在正在将 ChatGPT 的语言能力与 Stable Diffusion 等视觉基础模型相结合,使用户不仅可以通过文本与 ChatGPT 互动,还可以通过图像。在像 Visual ChatGPT 和 GPT-4 这样的项目中融合语言和视觉能力,有望开启人机交互的新时代。

总结

在本章中,我们探讨了 Transformer 模型架构,并构建了一个 GPT 的版本——用于最先进文本生成的模型。

GPT 利用一种称为注意力的机制,消除了循环层(例如 LSTM)的需求。它类似于信息检索系统,利用查询、键和值来决定它想要从每个输入标记中提取多少信息。

注意力头可以组合在一起形成所谓的多头注意力层。然后将它们包装在一个 Transformer 块中,其中包括围绕注意力层的层归一化和跳过连接。Transformer 块可以堆叠以创建非常深的神经网络。

因果屏蔽用于确保 GPT 不能从下游标记泄漏信息到当前预测中。此外,还使用一种称为位置编码的技术,以确保输入序列的顺序不会丢失,而是与传统的词嵌入一起嵌入到输入中。

在分析 GPT 的输出时,我们看到不仅可以生成新的文本段落,还可以审查网络的注意力层,以了解它在句子中查找信息以改善预测的位置。GPT 可以在不丢失信号的情况下访问远处的信息,因为注意力分数是并行计算的,不依赖于通过网络顺序传递的隐藏状态,这与循环神经网络的情况不同。

我们看到了 Transformer 有三个系列(编码器、解码器和编码器-解码器)以及每个系列可以完成的不同任务。最后,我们探讨了其他大型语言模型的结构和训练过程,如谷歌的 T5 和 OpenAI 的 ChatGPT。

¹ Ashish Vaswani 等人,“注意力就是一切”,2017 年 6 月 12 日,https://arxiv.org/abs/1706.03762

² Alec Radford 等人,“通过生成式预训练改进语言理解”,2018 年 6 月 11 日,https://openai.com/research/language-unsupervised

³ Jacob Devlin 等人,“BERT: 深度双向 Transformer 的语言理解预训练”,2018 年 10 月 11 日,https://arxiv.org/abs/1810.04805

⁴ Sheng Shen 等人,“PowerNorm: 重新思考 Transformer 中的批归一化”,2020 年 6 月 28 日,https://arxiv.org/abs/2003.07845

⁵ Colin Raffel 等人,“探索统一文本到文本 Transformer 的迁移学习极限”,2019 年 10 月 23 日,https://arxiv.org/abs/1910.10683

⁶ Long Ouyang 等人,“使用人类反馈训练语言模型遵循指令”,2022 年 3 月 4 日,https://arxiv.org/abs/2203.02155

⁷ Chenfei Wu 等人,“Visual ChatGPT: 使用视觉基础模型进行对话、绘画和编辑”,2023 年 3 月 8 日,https://arxiv.org/abs/2303.04671

第十章:高级 GANs

第四章介绍了生成对抗网络(GANs),这是一类生成模型,在各种图像生成任务中取得了最先进的结果。模型架构和训练过程的灵活性导致学术界和深度学习从业者找到了设计和训练 GAN 的新方法,从而产生了许多不同的高级架构,我们将在本章中探讨。

介绍

详细解释所有 GAN 发展及其影响可能需要另一本书。GitHub 上的GAN Zoo 代码库包含了 500 多个不同的 GAN 示例,涵盖了从 ABC-GAN 到 ZipNet-GAN 的各种 GAN,并附有相关论文链接!

在本章中,我们将介绍对该领域产生影响的主要 GANs,包括对每个模型的模型架构和训练过程的详细解释。

我们将首先探讨 NVIDIA 推动图像生成边界的三个重要模型:ProGAN、StyleGAN 和 StyleGAN2。我们将对每个模型进行足够详细的分析,以理解支撑架构的基本概念,并看看它们如何各自建立在早期论文的想法基础上。

我们还将探讨另外两种重要的 GAN 架构,包括引入注意力机制的 Self-Attention GAN(SAGAN)和 BigGAN,后者在 SAGAN 论文中的许多想法基础上构建。我们已经在第九章中看到了注意力机制在变换器中的威力。

最后,我们将介绍 VQ-GAN 和 ViT VQ-GAN,它们融合了变分自动编码器、变换器和 GAN 的思想。VQ-GAN 是谷歌最先进的文本到图像生成模型 Muse 的关键组成部分。我们将在第十三章中更详细地探讨所谓的多模型。

训练您自己的模型

为了简洁起见,我选择不在本书的代码库中直接构建这些模型的代码,而是将尽可能指向公开可用的实现,以便您可以根据需要训练自己的版本。

ProGAN

ProGAN 是 NVIDIA 实验室在 2017 年开发的一种技术,旨在提高 GAN 训练的速度和稳定性。ProGAN 论文建议,不要立即在全分辨率图像上训练 GAN,而是首先在低分辨率图像(例如 4×4 像素)上训练生成器和鉴别器,然后在训练过程中逐步添加层以增加分辨率。

让我们更详细地了解渐进式训练的概念。

训练您自己的 ProGAN

Bharath K 在Paperspace 博客上提供了一个关于使用 Keras 训练自己的 ProGAN 的优秀教程。请记住,训练 ProGAN 以达到论文中的结果需要大量的计算能力。

渐进式训练

与 GANs 一样,我们构建两个独立的网络,生成器和鉴别器,在训练过程中进行统治之争。

在普通的 GAN 中,生成器总是输出全分辨率图像,即使在训练的早期阶段也是如此。可以合理地认为,这种策略可能不是最佳的——生成器可能在训练的早期阶段学习高级结构较慢,因为它立即在复杂的高分辨率图像上操作。首先训练一个轻量级的 GAN 以输出准确的低分辨率图像,然后逐渐增加分辨率,这样做会更好吗?

这个简单的想法引导我们进入渐进式训练,这是 ProGAN 论文的一个关键贡献。ProGAN 分阶段训练,从一个已经通过插值压缩到 4×4 像素图像的训练集开始,如图 10-1 所示。

https://github.com/OpenDocCN/ibooker-dl-zh/raw/master/docs/gen-dl/img/gdl2_1001.png

图 10-1。数据集中的图像可以使用插值压缩到较低分辨率

然后,我们可以最初训练生成器,将潜在输入噪声向量z(比如长度为 512)转换为形状为 4×4×3 的图像。匹配的鉴别器需要将大小为 4×4×3 的输入图像转换为单个标量预测。这第一步的网络架构如图 10-2 所示。

生成器中的蓝色框表示将特征图转换为 RGB 图像的卷积层(toRGB),鉴别器中的蓝色框表示将 RGB 图像转换为一组特征图的卷积层(fromRGB)。

https://github.com/OpenDocCN/ibooker-dl-zh/raw/master/docs/gen-dl/img/gdl2_1002.png

图 10-2。ProGAN 训练过程的第一阶段的生成器和鉴别器架构

在论文中,作者训练这对网络,直到鉴别器看到了 800,000 张真实图像。现在我们需要了解如何扩展生成器和鉴别器以处理 8×8 像素图像。

为了扩展生成器和鉴别器,我们需要融入额外的层。这在两个阶段中进行,过渡和稳定,如图 10-3 所示。

https://github.com/OpenDocCN/ibooker-dl-zh/raw/master/docs/gen-dl/img/gdl2_1003.png

图 10-3。ProGAN 生成器训练过程,将网络从 4×4 图像扩展到 8×8(虚线代表网络的其余部分,未显示)

让我们首先看一下生成器。在过渡阶段中,新的上采样和卷积层被附加到现有网络中,建立了一个残差连接以保持现有训练过的toRGB层的输出。关键的是,新层最初使用一个参数α进行掩蔽,该参数在整个过渡阶段逐渐从 0 增加到 1,以允许更多新的toRGB输出通过,减少现有的toRGB层。这是为了避免网络在新层接管时出现冲击

最终,旧的toRGB层不再有输出流,网络进入稳定阶段——进一步的训练期间,网络可以微调输出,而不经过旧的toRGB层。

鉴别器使用类似的过程,如图 10-4 所示。

https://github.com/OpenDocCN/ibooker-dl-zh/raw/master/docs/gen-dl/img/gdl2_1004.png

图 10-4。ProGAN 鉴别器训练过程,将网络从 4×4 图像扩展到 8×8(虚线代表网络的其余部分,未显示)

在这里,我们需要融入额外的降采样和卷积层。同样,这些层被注入到网络中——这次是在网络的开始部分,就在输入图像之后。现有的fromRGB层通过残差连接连接,并在过渡阶段逐渐淡出,随着新层在过渡阶段接管时逐渐淡出。稳定阶段允许鉴别器使用新层进行微调。

所有过渡和稳定阶段持续到鉴别器已经看到了 800,000 张真实图像。请注意,即使网络是渐进训练的,也没有层被冻结。在整个训练过程中,所有层都保持完全可训练。

这个过程继续进行,将 GAN 从 4×4 图像扩展到 8×8,然后 16×16,32×32,依此类推,直到达到完整分辨率(1,024×1,024),如图 10-5 所示。

https://github.com/OpenDocCN/ibooker-dl-zh/raw/master/docs/gen-dl/img/gdl2_1005.png

图 10-5。ProGAN 训练机制,以及一些示例生成的人脸(来源:Karras 等人,2017

完整渐进训练过程完成后,生成器和鉴别器的整体结构如图 10-6 所示。

https://github.com/OpenDocCN/ibooker-dl-zh/raw/master/docs/gen-dl/img/gdl2_1006.png

图 10-6. 用于生成 1,024×1,024 像素 CelebA 面孔的 ProGAN 生成器和鉴别器的结构(来源:Karras 等人,2018)

该论文还提出了其他几个重要贡献,即小批量标准差、均衡学习率和像素级归一化,以下部分将简要描述。

小批量标准差

小批量标准差层是鉴别器中的额外层,附加了特征值的标准差,跨所有像素和整个小批量平均作为额外(常数)特征。这有助于确保生成器在输出中创建更多的变化——如果整个小批量中的变化较小,则标准差将很小,鉴别器可以使用此特征来区分假批次和真实批次!因此,生成器被激励确保它生成与真实训练数据中存在的变化量相似的数量。

均衡学习率

ProGAN 中的所有全连接和卷积层都使用均衡学习率。通常,神经网络中的权重是使用诸如He 初始化之类的方法进行初始化的——这是一个高斯分布,其标准差被缩放为与层的输入数量的平方根成反比。这样,具有更多输入的层将使用与零的偏差较小的权重进行初始化,通常会提高训练过程的稳定性。

ProGAN 论文的作者发现,当与 Adam 或 RMSProp 等现代优化器结合使用时,这会导致问题。这些方法会对每个权重的梯度更新进行归一化,使得更新的大小与权重的规模(幅度)无关。然而,这意味着具有较大动态范围(即具有较少输入的层)的权重将比具有较小动态范围(即具有更多输入的层)的权重需要更长时间来调整。发现这导致了 ProGAN 中生成器和鉴别器不同层的训练速度之间的不平衡,因此他们使用均衡学习率来解决这个问题。

在 ProGAN 中,权重使用简单的标准高斯进行初始化,而不管层的输入数量如何。归一化是动态应用的,作为对层的调用的一部分,而不仅仅是在初始化时。这样,优化器会将每个权重视为具有大致相同的动态范围,因此会应用相同的学习率。只有在调用层时,权重才会按照 He 初始化器的因子进行缩放。

像素级归一化

最后,在 ProGAN 中,生成器中使用像素级归一化,而不是批归一化。这将每个像素中的特征向量归一化为单位长度,并有助于防止信号在网络中传播时失控。像素级归一化层没有可训练的权重。

输出

除 CelebA 数据集外,ProGAN 还应用于大规模场景理解(LSUN)数据集的图像,并取得了出色的结果,如图 10-7 所示。这展示了 ProGAN 相对于早期 GAN 架构的强大之处,并为未来的迭代(如 StyleGAN 和 StyleGAN2)铺平了道路,我们将在接下来的部分中探讨。

https://github.com/OpenDocCN/ibooker-dl-zh/raw/master/docs/gen-dl/img/gdl2_1007.png

图 10-7. 在 LSUN 数据集上渐进训练的 ProGAN 生成的示例,分辨率为 256×256(来源:Karras 等人,2017

StyleGAN

StyleGAN³是 2018 年的一个 GAN 架构,建立在 ProGAN 论文中的早期思想基础上。实际上,鉴别器是相同的;只有生成器被改变。

通常在训练 GAN 时,很难将潜在空间中对应于高级属性的向量分离出来——它们经常是纠缠在一起,这意味着调整潜在空间中的图像以使脸部更多雀斑,例如,可能也会无意中改变背景颜色。虽然 ProGAN 生成了极其逼真的图像,但它也不例外。我们理想情况下希望完全控制图像的风格,这需要在潜在空间中对特征进行分离。

StyleGAN 通过在网络的不同点显式注入风格向量来实现这一点:一些控制高级特征(例如,面部方向)的向量,一些控制低级细节(例如,头发如何落在额头上)的向量。

StyleGAN 生成器的整体架构如图 10-8 所示。让我们逐步走过这个架构,从映射网络开始。

https://github.com/OpenDocCN/ibooker-dl-zh/raw/master/docs/gen-dl/img/gdl2_1008.png

图 10-8。StyleGAN 生成器架构(来源:Karras et al., 2018

训练您自己的 StyleGAN

Soon-Yau Cheong 在Keras 网站上提供了一个关于使用 Keras 训练自己的 StyleGAN 的优秀教程。请记住,要实现论文中的结果,训练 StyleGAN 需要大量的计算资源。

映射网络

映射网络 f 是一个简单的前馈网络,将输入噪声 𝐳 ∈ 𝒵 转换为不同的潜在空间 𝐰 ∈ 𝒲。这使得生成器有机会将嘈杂的输入向量分解为不同的变化因素,这些因素可以被下游的风格生成层轻松捕捉到。

这样做的目的是将图像的风格选择过程(映射网络)与生成具有给定风格的图像的过程(合成网络)分开。

合成网络

合成网络是生成具有给定风格的实际图像的生成器,由映射网络提供。如图 10-8 所示,风格向量 𝐰 被注入到合成网络的不同点,每次通过不同的密集连接层 A i,生成两个向量:一个偏置向量 𝐲 b,i 和一个缩放向量 𝐲 s,i。这些向量定义了应该在网络中的这一点注入的特定风格,也就是告诉合成网络如何调整特征图以使生成的图像朝着指定的风格方向移动。

通过自适应实例归一化(AdaIN)层实现这种调整。

自适应实例归一化

AdaIN 层是一种神经网络层,通过参考风格偏差𝐲 b,i和比例𝐲 s,i调整每个特征图𝐱 i的均值和方差。这两个向量的长度等于合成网络中前一卷积层输出的通道数。自适应实例归一化的方程如下:

AdaIN ( 𝐱 i , 𝐲 ) = 𝐲 s,i 𝐱 i -μ(𝐱 i ) σ(𝐱 i ) + 𝐲 b,i

自适应实例归一化层确保注入到每一层的风格向量只影响该层的特征,防止任何风格信息在层之间泄漏。作者表明,这导致潜在向量𝐰比原始𝐳向量更具解耦性。

由于合成网络基于 ProGAN 架构,因此是逐步训练的。在合成网络中较早层的风格向量(当图像分辨率最低时为 4×4、8×8)将影响比网络后期(64×64 到 1,024×1,024 像素分辨率)更粗糙的特征。这意味着我们不仅可以通过潜在向量𝐰完全控制生成的图像,还可以在合成网络的不同点切换𝐰向量以改变各种细节级别的风格。

风格混合

作者使用一种称为风格混合的技巧,确保生成器在训练过程中不能利用相邻风格之间的相关性(即,每层注入的风格尽可能解耦)。不仅仅是采样单个潜在向量𝐳,而是采样两个( 𝐳 1 , 𝐳 2 ),对应两个风格向量( 𝐰 1 , 𝐰 2 )。然后,在每一层,随机选择( 𝐰 1或𝐰 2 ),以打破可能存在的向量之间的任何相关性。

随机变化

合成器网络在每个卷积后添加噪音(通过一个学习的广播层B传递),以考虑诸如单个头发的放置或面部背后的背景等随机细节。再次强调,噪音注入的深度会影响对图像的影响粗糙程度。

这也意味着合成网络的初始输入可以简单地是一个学习到的常量,而不是额外的噪音。在风格输入和噪音输入中已经存在足够的随机性,以生成图像的足够变化。

StyleGAN 的输出

图 10-9 展示了 StyleGAN 的工作原理。

https://github.com/OpenDocCN/ibooker-dl-zh/raw/master/docs/gen-dl/img/gdl2_1009.png

图 10-9. 在不同细节级别上合并两个生成图像的风格(来源:Karras 等人,2018

这里,两个图像,源 A 和源 B,是从两个不同的 𝐰 向量生成的。为了生成一个合并的图像,源 A 的 𝐰 向量通过合成网络,但在某个时刻,被切换为源 B 的 𝐰 向量。如果这个切换发生得很早(4 × 4 或 8 × 8 分辨率),则从源 B 传递到源 A 的是粗略的风格,如姿势、脸型和眼镜。然而,如果切换发生得更晚,只有来自源 B 的细粒度细节被传递,比如脸部的颜色和微结构,而来自源 A 的粗略特征被保留。

StyleGAN2

在这一系列重要的 GAN 论文中的最终贡献是 StyleGAN2。这进一步构建在 StyleGAN 架构之上,通过一些关键改变提高了生成输出的质量。特别是,StyleGAN2 生成不会像 伪影 那样受到严重影响——在 StyleGAN 中发现的图像中的水滴状区域,这些伪影是由于 StyleGAN 中的自适应实例归一化层引起的,如 图 10-10 所示。

https://github.com/OpenDocCN/ibooker-dl-zh/raw/master/docs/gen-dl/img/gdl2_1010.png

图 10-10. 一个 StyleGAN 生成的人脸图像中的伪影(来源:Karras et al., 2019

StyleGAN2 中的生成器和鉴别器与 StyleGAN 不同。在接下来的章节中,我们将探讨这两种架构之间的关键区别。

训练您自己的 StyleGAN2

使用 TensorFlow 训练您自己的 StyleGAN 的官方代码可在 GitHub 上找到。请注意,为了实现论文中的结果,训练一个 StyleGAN2 需要大量的计算资源。

权重调制和去调制

通过删除生成器中的 AdaIN 层并将其替换为权重调制和去调制步骤,解决了伪影问题,如 图 10-11 所示。 𝐰 代表卷积层的权重,在 StyleGAN2 中通过调制和去调制步骤直接在运行时更新。相比之下,StyleGAN 的 AdaIN 层在图像张量通过网络时操作。

StyleGAN 中的 AdaIN 层只是一个实例归一化,后面跟着样式调制(缩放和偏置)。StyleGAN2 中的想法是在运行时直接将样式调制和归一化(去调制)应用于卷积层的权重,而不是卷积层的输出,如 图 10-11 所示。作者展示了这如何消除了伪影问题,同时保持对图像样式的控制。

https://github.com/OpenDocCN/ibooker-dl-zh/raw/master/docs/gen-dl/img/gdl2_1011.png

图 10-11. StyleGAN 和 StyleGAN2 样式块之间的比较

在 StyleGAN2 中,每个密集层 A 输出一个单一的样式向量 s i,其中 i 索引了相应卷积层中的输入通道数。然后将这个样式向量应用于卷积层的权重,如下所示:

w i,j,k ' = s i · w i,j,k

这里,j 索引了层的输出通道,k 索引了空间维度。这是过程的 调制 步骤。

然后,我们需要归一化权重,使它们再次具有单位标准差,以确保训练过程的稳定性。这是 去调制 步骤:

w i,j,k ‘’ = w i,j,k ‘ ∑ i,k w i,j,k ’ 2 +ε

其中 ϵ 是一个小的常数值,用于防止除以零。

在论文中,作者展示了这个简单的改变足以防止水滴状伪影,同时通过样式向量保持对生成图像的控制,并确保输出的质量保持高水平。

路径长度正则化

StyleGAN 架构的另一个变化是在损失函数中包含了额外的惩罚项——这被称为路径长度正则化

我们希望潜在空间尽可能平滑和均匀,这样在任何方向上潜在空间中的固定大小步长会导致图像的固定幅度变化。

为了鼓励这一属性,StyleGAN2 旨在最小化以下术语,以及通常的 Wasserstein 损失和梯度惩罚:

𝔼 𝑤,𝑦 𝐉 𝑤 ⊤ 𝑦 2 -a 2

在这里,𝑤是由映射网络创建的一组样式向量,𝑦是从𝒩 ( 0 , 𝐈 )中绘制的一组嘈杂图像,𝐉 𝑤 = ∂g ∂𝑤是生成器网络相对于样式向量的雅可比矩阵。

术语𝐉 𝑤 ⊤ 𝑦 2测量了经雅可比矩阵给出的梯度变换后图像𝑦的幅度。我们希望这个值接近一个常数a,这个常数是动态计算的,作为训练进行时𝐉 𝑤 ⊤ 𝑦 2的指数移动平均值。

作者发现,这个额外的术语使探索潜在空间更可靠和一致。此外,损失函数中的正则化项仅在每 16 个小批次中应用一次,以提高效率。这种技术称为懒惰正则化,不会导致性能的明显下降。

没有渐进增长

StyleGAN2 训练的另一个重大更新是在训练方式上。StyleGAN2 不再采用通常的渐进式训练机制,而是利用生成器中的跳过连接和鉴别器中的残差连接来将整个网络作为一个整体进行训练。它不再需要独立训练不同分辨率,并将其作为训练过程的一部分混合。

图 10-12 展示了 StyleGAN2 中的生成器和鉴别器块。

https://github.com/OpenDocCN/ibooker-dl-zh/raw/master/docs/gen-dl/img/gdl2_1012.png

图 10-12。StyleGAN2 中的生成器和鉴别器块

我们希望能够保留的关键属性是,StyleGAN2 从学习低分辨率特征开始,并随着训练的进行逐渐完善输出。作者表明,使用这种架构确实保留了这一属性。在训练的早期阶段,每个网络都受益于在较低分辨率层中细化卷积权重,而通过跳过和残差连接将输出传递到较高分辨率层的方式基本上不受影响。随着训练的进行,较高分辨率层开始占主导地位,因为生成器发现了更复杂的方法来改善图像的逼真度,以欺骗鉴别器。这个过程在图 10-13 中展示。

https://github.com/OpenDocCN/ibooker-dl-zh/raw/master/docs/gen-dl/img/gdl2_1013.png

图 10-13。每个分辨率层对生成器输出的贡献,按训练时间(改编自Karras 等人,2019

StyleGAN2 的输出

一些 StyleGAN2 输出的示例显示在图 10-14 中。迄今为止,StyleGAN2 架构(以及诸如 StyleGAN-XL 这样的扩展变体)仍然是 Flickr-Faces-HQ(FFHQ)和 CIFAR-10 等数据集上图像生成的最先进技术,根据基准网站Papers with Code

https://github.com/OpenDocCN/ibooker-dl-zh/raw/master/docs/gen-dl/img/gdl2_1014.png

图 10-14。FFHQ 人脸数据集和 LSUN 汽车数据集的未筛选 StyleGAN2 输出(来源:Karras 等人,2019)

其他重要的 GAN

在这一部分中,我们将探讨另外两种架构,它们也对 GAN 的发展做出了重大贡献——SAGAN 和 BigGAN。

自注意力生成对抗网络(SAGAN)

自注意力生成对抗网络(SAGAN)是 GAN 的一个重要发展,因为它展示了如何将驱动序列模型(如 Transformer)的注意机制也纳入到基于 GAN 的图像生成模型中。图 10-15 展示了介绍这种架构的论文中的自注意力机制。

https://github.com/OpenDocCN/ibooker-dl-zh/raw/master/docs/gen-dl/img/gdl2_1015.png

图 10-15。SAGAN 模型中的自注意机制(来源:Zhang 等人,2018

不包含注意力的基于 GAN 的模型的问题在于,卷积特征图只能在局部处理信息。连接图像一侧的像素信息到另一侧需要多个卷积层,这会减小图像的尺寸,同时增加通道数。在这个过程中,精确的位置信息会被减少,以捕捉更高级的特征,这使得模型学习远距离像素之间的长距离依赖性变得计算上低效。SAGAN 通过将我们在本章前面探讨过的注意力机制纳入到 GAN 中来解决这个问题。这种包含的效果在图 10-16 中展示。

https://github.com/OpenDocCN/ibooker-dl-zh/raw/master/docs/gen-dl/img/gdl2_1016.png

图 10-16。SAGAN 生成的一幅鸟的图像(最左侧单元格)以及由最终基于注意力的生成器层生成的像素的注意力图(右侧单元格)(来源:Zhang 等人,2018)

红点是鸟身体的一部分,因此注意力自然地集中在周围的身体细胞上。绿点是背景的一部分,这里注意力实际上集中在鸟头的另一侧,即其他背景像素上。蓝点是鸟的长尾的一部分,因此注意力集中在其他尾部像素上,其中一些与蓝点相距较远。对于没有注意力的像素来说,尤其是对于图像中的长、细结构(例如这种情况下的尾巴),要维持这种长距离依赖性将会很困难。

训练您自己的 SAGAN

使用 TensorFlow 训练自己的 SAGAN 的官方代码可在GitHub上找到。请注意,要实现论文中的结果,训练 SAGAN 需要大量的计算资源。

BigGAN

BigGAN,由 DeepMind 开发,扩展了 SAGAN 论文中的思想。图 10-17 展示了一些由 BigGAN 生成的图像,该模型在 ImageNet 数据集上进行了训练,分辨率为 128×128。

https://github.com/OpenDocCN/ibooker-dl-zh/raw/master/docs/gen-dl/img/gdl2_1017.png

图 10-17。由 BigGAN 生成的图像示例(来源:Brock 等人,2018)

除了对基本 SAGAN 模型进行一些增量更改外,论文中还概述了将模型提升到更高层次的几项创新。其中一项创新是所谓的“截断技巧”。这是指用于采样的潜在分布与训练期间使用的 z ∼ 𝒩 ( 0 , 𝐈 ) 分布不同。具体来说,采样期间使用的分布是“截断正态分布”(重新采样具有大于一定阈值的 z 值)。截断阈值越小,生成样本的可信度越高,但变异性降低。这个概念在图 10-18 中展示。

https://github.com/OpenDocCN/ibooker-dl-zh/raw/master/docs/gen-dl/img/gdl2_1018.png

图 10-18. 截断技巧:从左到右,阈值设置为 2、1、0.5 和 0.04(来源:Brock 等人,2018

正如其名称所示,BigGAN 在某种程度上是对 SAGAN 的改进,仅仅是因为它更“大”。BigGAN 使用的批量大小为 2,048,比 SAGAN 中使用的 256 的批量大小大 8 倍,并且每一层的通道大小增加了 50%。然而,BigGAN 还表明,通过包含共享嵌入、正交正则化以及将潜在向量 z 包含到生成器的每一层中,而不仅仅是初始层,可以在结构上改进 SAGAN。

要全面了解 BigGAN 引入的创新,我建议阅读原始论文和相关演示材料

使用 BigGAN

TensorFlow 网站上提供了一个使用预训练的 BigGAN 生成图像的教程。

VQ-GAN

另一种重要的 GAN 类型是 2020 年推出的 Vector Quantized GAN(VQ-GAN)。这种模型架构建立在 2017 年的论文“神经离散表示学习”中提出的一个想法之上,即 VAE 学习到的表示可以是离散的,而不是连续的。这种新型模型,即 Vector Quantized VAE(VQ-VAE),被证明可以生成高质量的图像,同时避免了传统连续潜在空间 VAE 经常出现的一些问题,比如“后验坍缩”(学习到的潜在空间由于过于强大的解码器而变得无信息)。

提示

OpenAI 在 2021 年发布的文本到图像模型 DALL.E 的第一个版本(参见第十三章)使用了具有离散潜在空间的 VAE,类似于 VQ-VAE。

通过“离散潜在空间”,我们指的是一个学习到的向量列表(“码书”),每个向量与相应的索引相关联。VQ-VAE 中编码器的工作是将输入图像折叠到一个较小的向量网格中,然后将其与码书进行比较。然后,将每个网格方格向量(通过欧氏距离)最接近的码书向量传递给解码器进行解码,如图 10-19 所示。码书是一个长度为 d(嵌入大小)的学习向量列表,与编码器输出和解码器输入中的通道数相匹配。例如,e 1 是一个可以解释为“背景”的向量。

https://github.com/OpenDocCN/ibooker-dl-zh/raw/master/docs/gen-dl/img/gdl2_1019.png

图 10-19. VQ-VAE 的示意图

代码本可以被看作是一组学习到的离散概念,这些概念由编码器和解码器共享,以描述给定图像的内容。VQ-VAE 必须找到一种方法,使这组离散概念尽可能具有信息量,以便编码器可以准确地用特定的代码向量标记每个网格方块,这对解码器是有意义的。因此,VQ-VAE 的损失函数是重构损失加上两个项(对齐和承诺损失),以确保编码器的输出向量尽可能接近代码本中的向量。这些项取代了典型 VAE 中编码分布和标准高斯先验之间的 KL 散度项。

然而,这种架构提出了一个问题——我们如何对新颖的代码网格进行采样,以传递给解码器生成新的图像?显然,使用均匀先验(为每个网格方块均等概率选择每个代码)是行不通的。例如,在 MNIST 数据集中,左上角的网格方块很可能被编码为背景,而靠近图像中心的网格方块不太可能被编码为这样。为了解决这个问题,作者使用了另一个模型,一个自回归的 PixelCNN(参见第五章),来预测网格中下一个代码向量,给定先前的代码向量。换句话说,先验是由模型学习的,而不是像普通 VAE 中的标准高斯先验那样静态的。

训练您自己的 VQ-VAE

有一篇由 Sayak Paul 撰写的优秀教程,介绍如何使用 Keras 在Keras 网站上训练自己的 VQ-VAE。

VQ-GAN 论文详细介绍了 VQ-VAE 架构的几个关键变化,如图 10-20 所示。

https://github.com/OpenDocCN/ibooker-dl-zh/raw/master/docs/gen-dl/img/gdl2_1020.png

图 10-20。VQ-GAN 的图表:GAN 鉴别器通过额外的对抗损失项帮助 VAE 生成更清晰的图像

首先,正如名称所示,作者包括一个 GAN 鉴别器,试图区分 VAE 解码器的输出和真实图像,损失函数中还有一个对抗项。众所周知,GAN 生成的图像比 VAE 更清晰,因此这个添加改善了整体图像质量。请注意,尽管名称中有 VAE,但 VAE 仍然存在于 VQ-GAN 模型中——GAN 鉴别器是一个额外的组件,而不是 VAE 的替代品。将 VAE 与 GAN 鉴别器(VAE-GAN)结合的想法首次由 Larsen 等人在他们 2015 年的论文中提出。

其次,GAN 鉴别器预测图像的小块是否真实或伪造,而不是一次性预测整个图像。这个想法(PatchGAN)被应用在 2016 年由 Isola 等人介绍的成功的pix2pix图像到图像模型中,并且也成功地作为CycleGAN的一部分应用,另一个图像到图像的风格转移模型。PatchGAN 鉴别器输出一个预测向量(每个块的预测),而不是整个图像的单个预测。使用 PatchGAN 鉴别器的好处在于,损失函数可以衡量鉴别器在基于风格而不是内容来区分图像方面的表现如何。由于鉴别器预测的每个单独元素基于图像的一个小方块,它必须使用块的风格而不是内容来做出决定。这是有用的,因为我们知道 VAE 生成的图像在风格上比真实图像更模糊,因此 PatchGAN 鉴别器可以鼓励 VAE 解码器生成比其自然产生的更清晰的图像。

第三,与使用单个 MSE 重建损失不同,该损失将输入图像像素与 VAE 解码器输出像素进行比较,VQ-GAN 使用感知损失项,计算编码器中间层的特征图与解码器相应层之间的差异。这个想法来自于侯等人 2016 年的论文,¹⁴作者在其中展示了这种对损失函数的改变导致更逼真的图像生成。

最后,模型的自回归部分使用 Transformer 而不是 PixelCNN,训练以生成代码序列。Transformer 在 VQ-GAN 完全训练后的一个单独阶段中进行训练。作者选择仅使用在要预测的令牌周围的滑动窗口内的令牌,而不是完全自回归地使用所有先前的令牌。这确保了模型可以扩展到需要更大潜在网格大小和因此需要 Transformer 生成更多令牌的更大图像。

ViT VQ-GAN

Yu 等人在 2021 年的论文“Vector-Quantized Image Modeling with Improved VQGAN”中对 VQ-GAN 进行了最后一个扩展。¹⁵ 在这里,作者展示了如何将 VQ-GAN 的卷积编码器和解码器替换为 Transformer,如图 10-21 所示。

对于编码器,作者使用Vision Transformer(ViT)。¹⁶ ViT 是一种神经网络架构,将最初设计用于自然语言处理的 Transformer 模型应用于图像数据。ViT 不使用卷积层从图像中提取特征,而是将图像分成一系列补丁,对其进行标记化,然后将其作为输入馈送到编码器 Transformer 中。

具体来说,在 ViT VQ-GAN 中,非重叠的输入补丁(每个大小为 8×8)首先被展平,然后投影到低维嵌入空间中,位置嵌入被添加。然后,这个序列被馈送到标准编码器 Transformer 中,生成的嵌入根据学习的码书进行量化。这些整数代码然后由解码器 Transformer 模型处理,最终输出是一系列补丁,可以被拼接在一起形成原始图像。整体的编码器-解码器模型被作为自动编码器端到端训练。

https://github.com/OpenDocCN/ibooker-dl-zh/raw/master/docs/gen-dl/img/gdl2_1021.png

图 10-21。ViT VQ-GAN 的图表:GAN 鉴别器通过额外的对抗损失项帮助 VAE 生成更清晰的图像(来源:Yu and Koh, 2022)¹⁷

与原始 VQ-GAN 模型一样,训练的第二阶段涉及使用自回归解码器 Transformer 生成代码序列。因此,在 ViT VQ-GAN 中总共有三个 Transformer,另外还有 GAN 鉴别器和学习的码书。论文中 ViT VQ-GAN 生成的图像示例显示在图 10-22 中。

https://github.com/OpenDocCN/ibooker-dl-zh/raw/master/docs/gen-dl/img/gdl2_1022.png

图 10-22。ViT VQ-GAN 在 ImageNet 上训练生成的示例图像(来源:Yu et al., 2021

总结

在本章中,我们回顾了自 2017 年以来一些最重要和有影响力的 GAN 论文。特别是,我们探讨了 ProGAN、StyleGAN、StyleGAN2、SAGAN、BigGAN、VQ-GAN 和 ViT VQ-GAN。

我们从 2017 年 ProGAN 论文中首创的渐进训练概念开始探索。2018 年 StyleGAN 论文引入了几个关键改变,使对图像输出有更大的控制,例如用于创建特定样式向量的映射网络和允许在不同分辨率注入样式的合成网络。最后,StyleGAN2 用权重调制和解调制步骤替换了 StyleGAN 的自适应实例归一化,同时还进行了额外的增强,如路径正则化。该论文还展示了如何保留渐进分辨率细化的可取属性,而无需逐步训练网络。

我们还看到了如何将注意力的概念构建到 GAN 中,2018 年引入了 SAGAN。这使网络能够捕捉长距离依赖关系,例如图像相对两侧的相似背景颜色,而无需依赖深度卷积映射将信息传播到图像的空间维度。BigGAN 是这个想法的延伸,进行了几个关键改变,并训练了一个更大的网络以进一步提高图像质量。

在 VQ-GAN 论文中,作者展示了如何将几种不同类型的生成模型结合起来产生很好的效果。在最初引入具有离散潜在空间的 VAE 概念的 VQ-VAE 论文的基础上,VQ-GAN 还包括一个鼓励 VAE 通过额外的对抗损失项生成更清晰图像的鉴别器。自回归 Transformer 用于构建一个新颖的代码令牌序列,可以由 VAE 解码器解码以生成新颖图像。ViT VQ-GAN 论文进一步扩展了这个想法,通过用 Transformer 替换 VQ-GAN 的卷积编码器和解码器。

¹ Huiwen Chang 等人,“Muse: 通过遮罩生成 Transformer 进行文本到图像生成”,2023 年 1 月 2 日,https://arxiv.org/abs/2301.00704

² Tero Karras 等人,“用于改善质量、稳定性和变化的 GAN 的渐进增长”,2017 年 10 月 27 日,https://arxiv.org/abs/1710.10196

³ Tero Karras 等人,“用于生成对抗网络的基于样式的生成器架构”,2018 年 12 月 12 日,https://arxiv.org/abs/1812.04948

⁴ Xun Huang 和 Serge Belongie,“使用自适应实例归一化实时进行任意风格转移”,2017 年 3 月 20 日,https://arxiv.org/abs/1703.06868

⁵ Tero Karras 等人,“分析和改进 StyleGAN 的图像质量”,2019 年 12 月 3 日,https://arxiv.org/abs/1912.04958

⁶ Axel Sauer 等人,“StyleGAN-XL: 将 StyleGAN 扩展到大型多样数据集”,2022 年 2 月 1 日,https://arxiv.org/abs/2202.00273v2

⁷ Han Zhang 等人,“自注意力生成对抗网络”,2018 年 5 月 21 日,https://arxiv.org/abs/1805.08318

⁸ Andrew Brock 等人,“用于高保真自然图像合成的大规模 GAN 训练”,2018 年 9 月 28 日,https://arxiv.org/abs/1809.11096

⁹ Patrick Esser 等人,“驯服 Transformer 以进行高分辨率图像合成”,2020 年 12 月 17 日,https://arxiv.org/abs/2012.09841

¹⁰ Aaron van den Oord 等人,“神经离散表示学习”,2017 年 11 月 2 日,https://arxiv.org/abs/1711.00937v2

¹¹ Anders Boesen Lindbo Larsen 等人,“超越像素的自动编码:使用学习的相似度度量”,2015 年 12 月 31 日,https://arxiv.org/abs/1512.09300

¹² Phillip Isola 等人,“带条件对抗网络的图像到图像翻译”,2016 年 11 月 21 日,https://arxiv.org/abs/1611.07004v3

¹³ Jun-Yan Zhu 等人,“使用循环一致性对抗网络进行无配对图像到图像翻译”,2017 年 3 月 30 日,https://arxiv.org/abs/1703.10593

¹⁴ Xianxu Hou 等人,“深度特征一致变分自动编码器”,2016 年 10 月 2 日,https://arxiv.org/abs/1610.00291

¹⁵ Jiahui Yu 等人,“改进的 VQGAN 进行矢量量化图像建模”,2021 年 10 月 9 日,https://arxiv.org/abs/2110.04627

¹⁶ Alexey Dosovitskiy 等人,“一幅图像价值 16x16 个词:规模化图像识别的 Transformer”,2020 年 10 月 22 日,https://arxiv.org/abs/2010.11929v2

¹⁷ Jiahui Yu 和 Jing Yu Koh,“改进的 VQGAN 进行矢量量化图像建模”,2022 年 5 月 18 日,https://ai.googleblog.com/2022/05/vector-quantized-image-modeling-with.html

第十一章:音乐生成

音乐作曲是一个复杂而创造性的过程,涉及将不同的音乐元素(如旋律、和声、节奏和音色)结合在一起。虽然传统上认为这是一种独特的人类活动,但最近的进展使得生成既能让耳朵愉悦又具有长期结构的音乐成为可能。

音乐生成最流行的技术之一是 Transformer,因为音乐可以被视为一个序列预测问题。这些模型已经被调整为通过将音符视为一系列标记(类似于句子中的单词)来生成音乐。Transformer 模型学会根据先前的音符预测序列中的下一个音符,从而生成一段音乐。

MuseGAN 采用了一种完全不同的方法来生成音乐。与 Transformer 逐音符生成音乐不同,MuseGAN 通过将音乐视为一个图像,由音高轴和时间轴组成,一次生成整个音乐曲目。此外,MuseGAN 将不同的音乐组成部分(如和弦、风格、旋律和节奏)分开,以便可以独立控制它们。

在本章中,我们将学习如何处理音乐数据,并应用 Transformer 和 MuseGAN 来生成与给定训练集风格相似的音乐。

介绍

为了让机器创作出我们耳朵愉悦的音乐,它必须掌握我们在第九章中看到的与文本相关的许多技术挑战。特别是,我们的模型必须能够学习并重新创建音乐的顺序结构,并能够从一系列可能性中选择后续音符。

然而,音乐生成面临着文本生成所没有的额外挑战,即音高和节奏。音乐通常是复调的,即有几条音符流同时在不同乐器上演奏,这些音符组合在一起形成既不和谐(冲突)又和谐(和谐)的和声。文本生成只需要我们处理一条文本流,与音乐中存在的并行和弦流相比。

此外,文本生成可以逐字处理。与文本数据不同,音乐是一种多部分、交织在一起的声音织锦,不一定同时传递——听音乐的乐趣很大程度上来自于整个合奏中不同节奏之间的相互作用。例如,吉他手可能弹奏一连串更快的音符,而钢琴家则弹奏一个较长的持续和弦。因此,逐音符生成音乐是复杂的,因为我们通常不希望所有乐器同时改变音符。

我们将从简化问题开始,专注于为单一(单声部)音乐线生成音乐。许多来自第九章关于文本生成的技术也可以用于音乐生成,因为这两个任务有许多共同的主题。我们将首先训练一个 Transformer 来生成类似于 J.S.巴赫大提琴组曲风格的音乐,并看看注意机制如何使模型能够专注于先前的音符,以确定最自然的后续音符。然后,我们将处理复调音乐生成的任务,并探讨如何使用基于 GAN 的架构来为多声部创作音乐。

用于音乐生成的 Transformer

我们将在这里构建的模型是一个解码器 Transformer,灵感来自于 OpenAI 的MuseNet,它也利用了一个解码器 Transformer(类似于 GPT-3),训练以预测给定一系列先前音符的下一个音符。

在音乐生成任务中,随着音乐的进行,序列的长度N变得很大,这意味着每个头部的N × N注意力矩阵变得昂贵且难以存储和计算。我们理想情况下不希望将输入序列剪切为少量标记,因为我们希望模型围绕长期结构构建乐曲,并重复几分钟前的主题和乐句,就像人类作曲家一样。

为了解决这个问题,MuseNet 利用了一种称为Sparse Transformer的 Transformer 形式。注意矩阵中的每个输出位置仅计算一部分输入位置的权重,从而减少了训练模型所需的计算复杂性和内存。MuseNet 因此可以在 4,096 个标记上进行全注意力操作,并可以学习跨多种风格的长期结构和旋律结构。 (例如,查看 OpenAI 在 SoundCloud 上的肖邦莫扎特的录音。)

要看到音乐短语的延续通常受几个小节前的音符影响,看看巴赫大提琴组曲第 1 号前奏的开头小节吧(图 11-1)。

https://github.com/OpenDocCN/ibooker-dl-zh/raw/master/docs/gen-dl/img/gdl2_1101.png

图 11-1。巴赫的大提琴组曲第 1 号(前奏)

小节

小节(或节拍)是包含固定数量的拍子的音乐小单位,并由穿过五线谱的垂直线标记出来。如果你能够数 1、2、1、2,那么每个小节有两拍,你可能在听进行曲。如果你能够数 1、2、3、1、2、3,那么每个小节有三拍,你可能在听华尔兹。

你认为接下来会是什么音符?即使你没有音乐训练,你可能仍然能猜到。如果你说是 G(与乐曲的第一个音符相同),那么你是正确的。你是怎么知道的?你可能能够看到每个小节和半小节都以相同的音符开头,并利用这些信息来做出决定。我们希望我们的模型能够执行相同的技巧——特别是,我们希望它能够关注前半小节中的特定音符,当前一个低 G 被记录时。基于注意力的模型,如 Transformer,将能够在不必在许多小节之间保持隐藏状态的情况下,合并这种长期回顾。

任何尝试音乐生成任务的人首先必须对音乐理论有基本的了解。在下一节中,我们将介绍阅读音乐所需的基本知识以及如何将其数值化,以便将音乐转换为训练 Transformer 所需的输入数据。

运行此示例的代码

此示例的代码可以在位于书存储库中的 Jupyter 笔记本notebooks/11_music/01_transformer/transformer.ipynb中找到。

巴赫大提琴组曲数据集

我们将使用的原始数据集是 J.S.巴赫的大提琴组曲的一组 MIDI 文件。您可以通过在书的存储库中运行数据集下载脚本来下载数据集,如示例 11-1 所示。这将把 MIDI 文件保存到本地的*/data*文件夹中。

示例 11-1。下载 J.S.巴赫大提琴组曲数据集
bash scripts/download_music_data.sh

要查看并听取模型生成的音乐,您需要一些能够生成乐谱的软件。[MuseScore](https://musescore.org)是一个很好的工具,可以免费下载。 `## 解析 MIDI 文件

我们将使用 Python 库music21将 MIDI 文件加载到 Python 中进行处理。示例 11-2 展示了如何加载一个 MIDI 文件并可视化它(图 11-2),既作为乐谱又作为结构化数据。

https://github.com/OpenDocCN/ibooker-dl-zh/raw/master/docs/gen-dl/img/gdl2_1102.png

图 11-2。音乐符号
示例 11-2。导入 MIDI 文件
import music21

file = "/app/data/bach-cello/cs1-2all.mid"
example_score = music21.converter.parse(file).chordify()

八度

每个音符名称后面的数字表示音符所在的八度——因为音符名称(A 到 G)重复,这是为了唯一标识音符的音高。例如,G2是低于G3的一个八度。

现在是时候将乐谱转换成更像文本的东西了!我们首先循环遍历每个乐谱,并将乐曲中每个元素的音符和持续时间提取到两个单独的文本字符串中,元素之间用空格分隔。我们将乐曲的调号和拍号编码为特殊符号,持续时间为零。

单声部与复调音乐

在这个第一个例子中,我们将把音乐视为单声部(一条单独的线),只取任何和弦的最高音。有时我们可能希望保持各声部分开,以生成复调性质的音乐。这带来了我们将在本章后面解决的额外挑战。

这个过程的输出显示在图 11-3 中——将其与图 11-2 进行比较,以便看到原始音乐数据如何转换为这两个字符串。

https://github.com/OpenDocCN/ibooker-dl-zh/raw/master/docs/gen-dl/img/gdl2_1103.png

图 11-3。音符文本字符串和持续时间文本字符串的示例,对应于图 11-2

这看起来更像我们之前处理过的文本数据。单词是音符-持续时间组合,我们应该尝试构建一个模型,根据先前音符和持续时间的序列来预测下一个音符和持续时间。音乐和文本生成之间的一个关键区别是,我们需要构建一个可以同时处理音符和持续时间预测的模型——即,我们需要处理两个信息流,而不是我们在第九章中看到的单一文本流。

标记化

为了创建将训练模型的数据集,我们首先需要像之前为文本语料库中的每个单词所做的那样,对每个音符和持续时间进行标记化。我们可以通过使用TextVectorization层,分别应用于音符和持续时间,来实现这一点,如示例 11-3 所示。

示例 11-3。标记化音符和持续时间
def create_dataset(elements):
    ds = (
        tf.data.Dataset.from_tensor_slices(elements)
        .batch(BATCH_SIZE, drop_remainder = True)
        .shuffle(1000)
    )
    vectorize_layer = layers.TextVectorization(
        standardize = None, output_mode="int"
    )
    vectorize_layer.adapt(ds)
    vocab = vectorize_layer.get_vocabulary()
    return ds, vectorize_layer, vocab

notes_seq_ds, notes_vectorize_layer, notes_vocab = create_dataset(notes)
durations_seq_ds, durations_vectorize_layer, durations_vocab = create_dataset(
    durations
)
seq_ds = tf.data.Dataset.zip((notes_seq_ds, durations_seq_ds))

完整的解析和标记化过程显示在图 11-4 中。

https://github.com/OpenDocCN/ibooker-dl-zh/raw/master/docs/gen-dl/img/gdl2_1104.png

图 11-4。解析 MIDI 文件并对音符和持续时间进行标记化

创建训练集

预处理的最后一步是创建我们将馈送给 Transformer 的训练集。

我们通过将音符和持续时间字符串分成 50 个元素的块来实现这一点,使用滑动窗口技术。输出只是输入窗口向后移动一个音符,这样 Transformer 就被训练来预测未来一个时间步的元素的音符和持续时间,给定窗口中的先前元素。这个示例(仅用四个元素的滑动窗口进行演示)显示在图 11-5 中。

https://github.com/OpenDocCN/ibooker-dl-zh/raw/master/docs/gen-dl/img/gdl2_1105.png

图 11-5。音乐 Transformer 模型的输入和输出——在这个例子中,使用宽度为 4 的滑动窗口创建输入块,然后将其移动一个元素以创建目标输出

我们将在 Transformer 中使用的架构与我们在第九章中用于文本生成的架构相同,但有一些关键的区别。

正弦位置编码

首先,我们将介绍一种不同类型的令牌位置编码。在第九章中,我们使用了一个简单的Embedding层来编码每个令牌的位置,有效地将每个整数位置映射到模型学习的不同向量。因此,我们需要定义一个最大长度(N),该序列可以是,并在这个序列长度上进行训练。这种方法的缺点是无法推断出比这个最大长度更长的序列。您将不得不将输入剪切到最后的N个令牌,如果您试图生成长篇内容,则这并不理想。

为了避免这个问题,我们可以转而使用一种称为sine position embedding的不同类型的嵌入。这类似于我们在第八章中用来编码扩散模型噪声方差的嵌入。具体来说,以下函数用于将输入序列中单词的位置(p o s)转换为长度为d的唯一向量:

P E pos,2i = sin ( pos 10,000 2i/d ) P E pos,2i+1 = cos ( pos 10,000 (2i+1)/d )

对于较小的i,这个函数的波长很短,因此函数值沿着位置轴快速变化。较大的i值会产生更长的波长。因此,每个位置都有自己独特的编码,这是不同波长的特定组合。

提示

请注意,此嵌入是为所有可能的位置值定义的。它是一个确定性函数(即,模型不会学习它),它使用三角函数来为每个可能的位置定义一个唯一的编码。

Keras NLP模块具有一个内置层,为我们实现了这种嵌入 - 因此,我们可以定义我们的TokenAndPositionEmbedding层,如示例 11-4 所示。

示例 11-4。对音符和持续时间进行标记化
class TokenAndPositionEmbedding(layers.Layer):
    def __init__(self, vocab_size, embed_dim):
        super(TokenAndPositionEmbedding, self).__init__()
        self.vocab_size = vocab_size
        self.embed_dim = embed_dim
        self.token_emb = layers.Embedding(input_dim=vocab_size, output_dim=embed_dim)
        self.pos_emb = keras_nlp.layers.SinePositionEncoding()

    def call(self, x):
        embedding = self.token_emb(x)
        positions = self.pos_emb(embedding)
        return embedding + positions

图 11-6 显示了如何将这两种嵌入(令牌和位置)相加以产生序列的整体嵌入。

https://github.com/OpenDocCN/ibooker-dl-zh/raw/master/docs/gen-dl/img/gdl2_1106.png

图 11-6。TokenAndPositionEmbedding层将令牌嵌入添加到正弦位置嵌入中,以产生序列的整体嵌入

多个输入和输出

现在我们有两个输入流(音符和持续时间)和两个输出流(预测音符和持续时间)。因此,我们需要调整我们的 Transformer 架构以适应这一点。

处理双输入流的方法有很多种。我们可以创建代表每个音符-持续时间对的令牌,然后将序列视为单个令牌流。然而,这样做的缺点是无法表示在训练集中未见过的音符-持续时间对(例如,我们可能独立地看到了G#2音符和1/3持续时间,但从未一起出现过,因此没有G#2:1/3的令牌)。

相反,我们选择分别嵌入音符和持续时间令牌,然后使用连接层创建输入的单一表示,该表示可以被下游 Transformer 块使用。类似地,Transformer 块的输出传递给两个独立的密集层,代表了预测的音符和持续时间概率。整体架构如图 11-7 所示。层输出形状显示了批量大小b和序列长度l

https://github.com/OpenDocCN/ibooker-dl-zh/raw/master/docs/gen-dl/img/gdl2_1107.png

图 11-7。音乐生成 Transformer 的架构

另一种方法是将音符和持续时间标记交错到一个单一的输入流中,并让模型学习输出应该是一个音符和持续时间标记交替的单一流。这增加了确保当模型尚未学会如何正确交错标记时,输出仍然可以解析的复杂性。

提示

设计您的模型没有的方式——其中一部分乐趣就是尝试不同的设置,看看哪种对您最有效!

音乐生成 Transformer 的分析

我们将从头开始生成一些音乐,通过向网络提供一个START音符标记和0.0持续时间标记(即,我们告诉模型假设它是从乐曲的开头开始)。然后我们可以使用与我们在第九章中用于生成文本序列的相同迭代技术来生成一个音乐段落,如下所示:

  1. 给定当前序列(音符和持续时间),模型预测两个分布,一个是下一个音符的分布,另一个是下一个持续时间的分布。

  2. 我们从这两个分布中进行采样,使用一个temperature参数来控制在采样过程中我们希望有多少变化。

  3. 选择的音符和持续时间被附加到相应的输入序列中。

  4. 这个过程会重复进行,对于我们希望生成的元素数量,会有新的输入序列。

图 11-8 展示了在训练过程的各个时期由模型从头开始生成的音乐示例。我们对音符和持续时间使用了 0.5 的温度。

https://github.com/OpenDocCN/ibooker-dl-zh/raw/master/docs/gen-dl/img/gdl2_1108.png

图 11-8。当仅使用一个START音符标记和0.0持续时间标记作为种子时,模型生成的乐段示例

在本节中,我们大部分的分析将集中在音符预测上,而不是持续时间,因为对于巴赫的大提琴组曲来说,和声的复杂性更难捕捉,因此更值得研究。然而,您也可以将相同的分析应用于模型的节奏预测,这对于您可能用来训练该模型的其他音乐风格可能特别相关(比如鼓声)。

关于在图 11-8 中生成的乐段有几点需要注意。首先,看到随着训练的进行,音乐变得越来越复杂。一开始,模型通过坚持使用相同的音符和节奏来保险。到了第 10 个时期,模型已经开始生成小段音符,到了第 20 个时期,它产生了有趣的节奏,并且牢固地确立在一个固定的调(E ♭大调)中。

其次,我们可以通过绘制每个时间步的预测分布的热图来分析随时间变化的音符分布。图 11-9 展示了在图 11-8 中第 20 个时期的示例的热图。

https://github.com/OpenDocCN/ibooker-dl-zh/raw/master/docs/gen-dl/img/gdl2_1109.png

图 11-9。随着时间推移可能的下一个音符的分布(在第 20 个时期):方块越暗,模型对下一个音符在这个音高的确定性就越高

这里需要注意的一个有趣的点是,模型显然已经学会了哪些音符属于特定的,因为在不属于该调的音符处存在分布中的间隙。例如,在音符 54(对应于 G ♭/F ♯)的行上有一个灰色间隙。在 E ♭大调的音乐作品中,这个音符极不可能出现。模型在生成过程的早期就确立了调,并且随着乐曲的进行,模型选择更有可能出现在该调中的音符,通过关注代表它的标记。

值得一提的是,模型还学会了巴赫特有的风格,即在大提琴上降到低音结束一个乐句,然后又反弹回来开始下一个乐句。看看大约在第 20 个音符附近,乐句以低音 E♭结束——在巴赫大提琴组曲中,通常会回到乐器更高、更响亮的音域开始下一个乐句,这正是模型的预测。在低音 E♭(音高编号 39)和下一个音符之间有一个很大的灰色间隙,预测下一个音符将在音高编号 50 左右,而不是继续在乐器的低音区域漂浮。

最后,我们应该检查我们的注意力机制是否按预期工作。图 11-10 中的水平轴显示了生成的音符序列;垂直轴显示了网络在预测水平轴上的每个音符时所关注的位置。每个方块的颜色显示了在生成序列的每个点上所有头部中的最大注意力权重。方块越暗,表示在序列中这个位置上应用的注意力越多。为简单起见,我们在这个图表中只显示了音符,但网络也会关注每个音符的持续时间。

我们可以看到,在初始调号、拍号和休止符中,网络选择几乎全部注意力放在START标记上。这是有道理的,因为这些特征总是出现在音乐片段的开头——一旦音符开始流动,START标记基本上就不再受到关注。

当我们超过最初的几个音符时,我们可以看到网络主要关注大约最后两到四个音符,并很少对四个音符之前的音符给予重要权重。再次,这是有道理的;前四个音符中可能包含足够的信息,以了解乐句可能如何继续。此外,一些音符更强烈地回到 D 小调的调号上——例如E3(乐曲的第 7 个音符)和B-2(B♭-乐曲的第 14 个音符)。这很有趣,因为这些正是依赖 D 小调调号来消除任何模糊的确切音符。网络必须回顾调号才能知道调号中有一个 B♭(而不是 B 自然音),但调号中没有一个 E♭(必须使用 E 自然音)。

https://github.com/OpenDocCN/ibooker-dl-zh/raw/master/docs/gen-dl/img/gdl2_1110.png

图 11-10。矩阵中每个方块的颜色表示在水平轴上预测音符时,对垂直轴上每个位置给予的注意力量

还有一些例子表明,网络选择忽略附近的某些音符或休止符,因为它们对理解乐句并没有提供额外信息。例如,倒数第二个音符(A2)对三个音符前的B-2并不特别关注,但对四个音符前的A2稍微更关注。对于模型来说,看位于节拍上的A2比看位于节拍外的B-2更有趣,后者只是一个过渡音。

请记住,我们并没有告诉模型哪些音符相关,哪些音符属于哪个调号——它通过研究巴赫的音乐自己弄清楚了这一点。

多声部音乐的标记化

我们在本节中探讨的 Transformer 对单线(单声部)音乐效果很好,但它能够适应多线(复调)音乐吗?

挑战在于如何将不同的音乐线表示为单个令牌序列。在前一节中,我们决定将音符和音符持续时间分成网络的两个不同输入和输出,但我们也看到我们可以将这些令牌交错成一个单一流。我们可以使用相同的想法来处理复调音乐。这里将介绍两种不同的方法:网格标记化基于事件的标记化,正如 2018 年的论文“音乐 Transformer:生成具有长期结构的音乐”中所讨论的那样。¹

网格标记化

考虑 J.S.巴赫赞美诗中的两小节音乐。有四个不同的声部(女高音[S],中音[A],男高音[T],低音[B]),分别写在不同的五线谱上。

https://github.com/OpenDocCN/ibooker-dl-zh/raw/master/docs/gen-dl/img/gdl2_1111.png

图 11-11。J.S.巴赫赞美诗的前两小节

我们可以想象在网格上绘制这段音乐,其中 y 轴表示音符的音高,x 轴表示自作品开始以来经过的 16 分音符(四分音符)的数量。如果网格方块被填充,那么在那个时间点有音符在播放。所有四个声部都绘制在同一个网格上。这个网格被称为钢琴卷,因为它类似于一卷纸上打孔的物理卷,这在数字系统发明之前被用作记录机制。

我们可以通过首先沿着四个声部,然后沿着时间步骤顺序移动,将网格序列化为令牌流。这将产生一个令牌序列S 1 , A 1 , T 1 , B 1 , S 2 , A 2 , T 2 , B 2 , …,其中下标表示时间步骤,如图 11-12 所示。

https://github.com/OpenDocCN/ibooker-dl-zh/raw/master/docs/gen-dl/img/gdl2_1112.png

图 11-12。为巴赫赞美诗的前两小节创建网格标记化

然后,我们将训练我们的 Transformer 模型以预测给定先前令牌的下一个令牌。我们可以通过将序列在时间上以四个音符一组(每个声部一个)展开来将生成的序列解码回网格结构。尽管同一个音符经常被分割成多个令牌,并且在其他声部的令牌之间有令牌,但这种技术效果出奇地好。

然而,也存在一些缺点。首先,请注意,模型无法区分一个长音符和相同音高的两个较短相邻音符。这是因为标记化并没有明确编码音符的持续时间,只是在每个时间步是否存在音符。

其次,这种方法要求音乐具有可以分成合理大小块的规则节拍。例如,使用当前系统,我们无法编码三连音(在一个拍子中演奏的三个音符)。我们可以将音乐分成每个四分音符(四分音符)12 个步骤,而不是 4 个步骤,这将使表示相同音乐段所需的令牌数量增加三倍,增加训练过程的开销,并影响模型的回溯能力。

最后,我们不清楚如何将其他组件添加到标记化中,比如动态(每个声部的音乐是大声还是安静)或速度变化。我们被钢琴卷的二维网格结构所限制,这提供了一种方便的表示音高和节奏的方式,但不一定是一种容易融入使音乐听起来有趣的其他组件的方式。

基于事件的标记化

更灵活的方法是使用基于事件的令牌化。这可以被看作是一个词汇表,字面上描述了音乐是如何作为一系列事件创建的,使用丰富的令牌集。

例如,在图 11-13 中,我们使用三种类型的令牌:

  • NOTE_ON<*音高*>(开始播放给定音高的音符)

  • NOTE_OFF<*音高*>(停止播放给定音高的音符)

  • TIME_SHIFT<*步骤*>(按给定步骤向前移动时间)

这个词汇表可以用来创建一个描述音乐构造的序列,作为一组指令。

https://github.com/OpenDocCN/ibooker-dl-zh/raw/master/docs/gen-dl/img/gdl2_1113.png

图 11-13。巴赫赞美诗第一小节的事件令牌化

我们可以轻松地将其他类型的令牌纳入这个词汇表中,以表示后续音符的动态和速度变化。通过使用TIME_SHIFT<0.33>令牌,这种方法还提供了一种在四分音符背景下生成三连音的方法。总的来说,这是一个更具表现力的令牌化框架,尽管对于 Transformer 来说,学习训练集音乐中固有模式可能更复杂,因为它在定义上比网格方法更少结构化。

提示

我鼓励您尝试实施这些复调技术,并使用您在本书中迄今为止积累的所有知识在新的令牌化数据集上训练 Transformer。我还建议查看我们的 Tristan Behrens 博士关于音乐生成研究的指南,可在GitHub上找到,该指南提供了关于使用深度学习进行音乐生成的不同论文的全面概述。

在下一节中,我们将采用完全不同的方法来进行音乐生成,使用 GAN。# MuseGAN

您可能认为图 11-12 中显示的钢琴卷看起来有点像现代艺术品。这引发了一个问题——我们实际上是否可以将这个钢琴卷视为图片,并利用图像生成方法而不是序列生成技术?

正如我们将看到的,对于这个问题的答案是肯定的,我们可以直接将音乐生成视为图像生成问题。这意味着我们可以应用对图像生成问题非常有效的基于卷积的技术,特别是 GAN。

MuseGAN 是在 2017 年的论文“MuseGAN:用于符号音乐生成和伴奏的多轨序列生成对抗网络”中引入的。作者展示了通过一种新颖的 GAN 框架训练模型生成复调、多轨、多小节音乐是可能的。此外,他们展示了通过将喂给生成器的噪声向量的责任分解,他们能够对音乐的高级时间和基于轨道的特征进行精细控制。

让我们首先介绍 J.S.巴赫赞美诗数据集。

运行此示例的代码

此示例的代码可以在书籍存储库中的notebooks/11_music/02_musegan/musegan.ipynb中找到。

巴赫赞美诗数据集

要开始这个项目,您首先需要下载我们将用于训练 MuseGAN 的 MIDI 文件。我们将使用包含四声部的 229 首 J.S.巴赫赞美诗数据集。

您可以通过在书籍存储库中运行巴赫赞美诗数据集下载器脚本来下载数据集,如示例 11-5 所示。这将把 MIDI 文件保存到本地的*/data*文件夹中。

示例 11-5。下载巴赫赞美诗数据集
bash scripts/download_bach_chorale_data.sh

数据集由每个时间步长的四个数字数组组成:每个声部的 MIDI 音符音高。在这个数据集中,一个时间步长等于一个 16 分音符(半音符)。因此,例如,在 4 个四分音符拍的单个小节中,有 16 个时间步长。数据集会自动分成训练验证测试集。我们将使用训练数据集来训练 MuseGAN。

首先,我们需要将数据整理成正确的形状以供 GAN 使用。在这个示例中,我们将生成两小节音乐,因此我们将提取每个赞美诗的前两小节。每小节包括 16 个时间步长,四个声部中有潜在的 84 个音高。

提示

从现在开始,声部将被称为轨道,以保持术语与原始论文一致。

因此,转换后的数据将具有以下形状:

[BATCH_SIZE, N_BARS, N_STEPS_PER_BAR, N_PITCHES, N_TRACKS]

其中:

BATCH_SIZE = 64
N_BARS = 2
N_STEPS_PER_BAR = 16
N_PITCHES = 84
N_TRACKS = 4

为了将数据整理成这种形状,我们将音高数字进行独热编码,转换为长度为 84 的向量,并将每个音符序列分成两小节,每小节包括 16 个时间步长。我们在这里做出的假设是数据集中的每个赞美诗每小节有四拍,这是合理的,即使不是这种情况,也不会对模型的训练产生不利影响。

图 11-14 展示了两小节原始数据如何转换为我们将用来训练 GAN 的转换后的钢琴卷帘数据集。

https://github.com/OpenDocCN/ibooker-dl-zh/raw/master/docs/gen-dl/img/gdl2_1114.png

图 11-14。将两小节原始数据处理成我们可以用来训练 GAN 的钢琴卷帘数据 ## MuseGAN 生成器

像所有 GAN 一样,MuseGAN 由一个生成器和一个评论家组成。生成器试图用其音乐创作愚弄评论家,评论家试图通过确保能够区分生成器伪造的巴赫赞美诗和真实的赞美诗来阻止这种情况发生。

MuseGAN 的不同之处在于生成器不仅接受单个噪声向量作为输入,而是有四个单独的输入,分别对应音乐的四个不同特征:和弦、风格、旋律和节奏。通过独立操纵这些输入中的每一个,我们可以改变生成音乐的高级属性。

生成器的高级视图显示在图 11-15 中。

https://github.com/OpenDocCN/ibooker-dl-zh/raw/master/docs/gen-dl/img/gdl2_1115.png

MuseGAN 生成器的高级图表

图表显示和弦和旋律输入首先通过一个时间网络,输出一个维度等于要生成的小节数的张量。风格和节奏输入不会以这种方式在时间上拉伸,因为它们在整个乐曲中保持不变。

然后,为了为特定轨道的特定小节生成特定小节,来自和弦、风格、旋律和节奏部分的相关输出被连接起来形成一个更长的向量。然后将其传递给小节生成器,最终输出指定轨道的指定小节。

通过连接所有轨道的生成小节,我们创建了一个可以与评论家的真实分数进行比较的分数。

让我们首先看看如何构建一个时间网络。

时间网络

时间网络的工作是将长度为Z_DIM = 32的单个输入噪声向量转换为每个小节的不同噪声向量(长度也为 32)的神经网络,由卷积转置层组成。构建这个网络的 Keras 代码在示例 11-6 中显示。

示例 11-6。构建时间网络
def conv_t(x, f, k, s, a, p, bn):
    x = layers.Conv2DTranspose(
                filters = f
                , kernel_size = k
                , padding = p
                , strides = s
                , kernel_initializer = initializer
                )(x)
    if bn:
        x = layers.BatchNormalization(momentum = 0.9)(x)

    x = layers.Activation(a)(x)
    return x

def TemporalNetwork():
    input_layer = layers.Input(shape=(Z_DIM,), name='temporal_input') # ①
    x = layers.Reshape([1,1,Z_DIM])(input_layer) # ②
    x = conv_t(
        x, f=1024, k=(2,1), s=(1,1), a = 'relu', p = 'valid', bn = True
    ) # ③
    x = conv_t(
        x, f=Z_DIM, k=(N_BARS - 1,1), s=(1,1), a = 'relu', p = 'valid', bn = True
    )
    output_layer = layers.Reshape([N_BARS, Z_DIM])(x) # ④
    return models.Model(input_layer, output_layer)

时间网络的输入是长度为 32 的向量(Z_DIM)。

我们将这个向量重塑为一个具有 32 个通道的 1×1 张量,以便我们可以对其应用二维卷积转置操作。

我们应用Conv2DTranspose层来沿一个轴扩展张量的大小,使其与N_BARS的长度相同。

我们使用 Reshape 层去除不必要的额外维度。

我们使用卷积操作而不是要求两个独立的向量进入网络的原因是,我们希望网络学习如何以一种一致的方式让一个小节跟随另一个小节。使用神经网络沿着时间轴扩展输入向量意味着模型有机会学习音乐如何跨越小节流动,而不是将每个小节视为完全独立于上一个的。

和弦、风格、旋律和 groove

现在让我们更仔细地看一下喂给生成器的四种不同输入:

和弦

和弦输入是一个长度为 Z_DIM 的单一噪声向量。这个向量的作用是控制音乐随时间的总体进展,跨越轨道共享,因此我们使用 TemporalNetwork 将这个单一向量转换为每个小节的不同潜在向量。请注意,虽然我们称这个输入为和弦,但它实际上可以控制音乐中每个小节变化的任何内容,比如一般的节奏风格,而不是特定于任何特定轨道。

风格

风格输入也是长度为 Z_DIM 的向量。这个向量在不经过转换的情况下传递,因此在所有小节和轨道上都是相同的。它可以被视为控制乐曲整体风格的向量(即,它会一致地影响所有小节和轨道)。

旋律

旋律输入是一个形状为 [N_TRACKS, Z_DIM] 的数组—也就是说,我们为每个轨道提供长度为 Z_DIM 的随机噪声向量。

这些向量中的每一个都通过轨道特定的 TemporalNetwork,其中轨道之间的权重不共享。输出是每个轨道的每个小节的长度为 Z_DIM 的向量。因此,模型可以使用这些输入向量来独立地微调每个小节和轨道的内容。

Groove

groove 输入也是一个形状为 [N_TRACKS, Z_DIM] 的数组,即每个轨道的长度为 Z_DIM 的随机噪声向量。与旋律输入不同,这些向量不通过时间网络,而是直接传递,就像风格向量一样。因此,每个 groove 向量将影响轨道的整体属性,跨越所有小节。

我们可以总结每个 MuseGAN 生成器组件的责任,如 表 11-1 所示。

表 11-1. MuseGAN 生成器的组件

输出在小节之间不同吗? 输出在部分之间不同吗?
风格
Groove
和弦
旋律

MuseGAN 生成器的最后一部分是 小节生成器—让我们看看如何使用它来将和弦、风格、旋律和 groove 组件的输出粘合在一起。

小节生成器

小节生成器接收四个潜在向量——来自和弦、风格、旋律和 groove 组件。这些被连接起来产生长度为 4 * Z_DIM 的输入向量。输出是单个轨道的单个小节的钢琴卷表示—即,形状为 [1, n_steps_per_bar, n_pitches, 1] 的张量。

小节生成器只是一个使用卷积转置层来扩展输入向量的时间和音高维度的神经网络。我们为每个轨道创建一个小节生成器,轨道之间的权重不共享。构建 BarGenerator 的 Keras 代码在 示例 11-7 中给出。

示例 11-7. 构建 BarGenerator
def BarGenerator():

    input_layer = layers.Input(shape=(Z_DIM * 4,), name='bar_generator_input') # ①

    x = layers.Dense(1024)(input_layer) # ②
    x = layers.BatchNormalization(momentum = 0.9)(x)
    x = layers.Activation('relu')(x)
    x = layers.Reshape([2,1,512])(x)

    x = conv_t(x, f=512, k=(2,1), s=(2,1), a= 'relu',  p = 'same', bn = True) # ③
    x = conv_t(x, f=256, k=(2,1), s=(2,1), a= 'relu', p = 'same', bn = True)
    x = conv_t(x, f=256, k=(2,1), s=(2,1), a= 'relu', p = 'same', bn = True)
    x = conv_t(x, f=256, k=(1,7), s=(1,7), a= 'relu', p = 'same', bn = True) # ④
    x = conv_t(x, f=1, k=(1,12), s=(1,12), a= 'tanh', p = 'same', bn = False) # ⑤

    output_layer = layers.Reshape([1, N_STEPS_PER_BAR , N_PITCHES ,1])(x) # ⑥

    return models.Model(input_layer, output_layer)

bar 生成器的输入是长度为 4 * Z_DIM 的向量。

通过一个 Dense 层后,我们重新塑造张量以准备进行卷积转置操作。

首先我们沿着时间步长轴扩展张量…​

…​然后沿着音高轴。

最终层应用了 tanh 激活,因为我们将使用 WGAN-GP(需要 tanh 输出激活)来训练网络。

张量被重塑以添加两个大小为 1 的额外维度,以准备与其他小节和轨道连接。

将所有内容整合在一起

最终,MuseGAN 生成器接受四个输入噪声张量(和弦、风格、旋律和节奏),并将它们转换为一个多轨多小节乐谱。构建 MuseGAN 生成器的 Keras 代码在示例 11-8 中提供。

示例 11-8。构建 MuseGAN 生成器
def Generator():
    chords_input = layers.Input(shape=(Z_DIM,), name='chords_input') # ①
    style_input = layers.Input(shape=(Z_DIM,), name='style_input')
    melody_input = layers.Input(shape=(N_TRACKS, Z_DIM), name='melody_input')
    groove_input = layers.Input(shape=(N_TRACKS, Z_DIM), name='groove_input')

    chords_tempNetwork = TemporalNetwork() # ②
    chords_over_time = chords_tempNetwork(chords_input)

    melody_over_time = [None] * N_TRACKS
    melody_tempNetwork = [None] * N_TRACKS
    for track in range(N_TRACKS):
        melody_tempNetwork[track] = TemporalNetwork() # ③
        melody_track = layers.Lambda(lambda x, track = track: x[:,track,:])(
            melody_input
        )
        melody_over_time[track] = melody_tempNetworktrack

    barGen = [None] * N_TRACKS
    for track in range(N_TRACKS):
        barGen[track] = BarGenerator() # ④

    bars_output = [None] * N_BARS
    c = [None] * N_BARS
    for bar in range(N_BARS): # ⑤
        track_output = [None] * N_TRACKS

        c[bar] = layers.Lambda(lambda x, bar = bar: x[:,bar,:])(chords_over_time)
        s = style_input

        for track in range(N_TRACKS):

            m = layers.Lambda(lambda x, bar = bar: x[:,bar,:])(
                melody_over_time[track]
            )
            g = layers.Lambda(lambda x, track = track: x[:,track,:])(
                groove_input
            )

            z_input = layers.Concatenate(
                axis = 1, name = 'total_input_bar_{}_track_{}'.format(bar, track)
            )([c[bar],s,m,g])

            track_output[track] = barGentrack

        bars_output[bar] = layers.Concatenate(axis = -1)(track_output)

    generator_output = layers.Concatenate(axis = 1, name = 'concat_bars')(
        bars_output
    ) # ⑥

    return models.Model(
        [chords_input, style_input, melody_input, groove_input], generator_output
    ) # ⑦

generator = Generator()

定义生成器的输入。

通过时间网络传递和弦输入。

通过时间网络传递旋律输入。

为每个轨道创建一个独立的小节生成器网络。

循环遍历轨道和小节,为每种组合创建一个生成的小节。

将所有内容连接在一起形成单个输出张量。

MuseGAN 模型接受四个不同的噪声张量作为输入,并输出一个生成的多轨多小节乐谱。

MuseGAN 评论家

与生成器相比,评论家的架构要简单得多(这在 GAN 中经常是这样)。

评论家试图区分生成器创建的完整多轨多小节乐谱和巴赫赞美诗的真实节选。它是一个卷积神经网络,主要由将乐谱折叠成单个输出预测的Conv3D层组成。

Conv3D 层

到目前为止,在本书中,我们只使用了适用于三维输入图像(宽度、高度、通道)的Conv2D层。在这里,我们必须使用Conv3D层,它们类似于Conv2D层,但接受四维输入张量(n_barsn_steps_per_barn_pitchesn_tracks)。

我们在评论家中不使用批量归一化层,因为我们将使用 WGAN-GP 框架来训练 GAN,这是不允许的。

构建评论家的 Keras 代码在示例 11-9 中给出。

示例 11-9。构建 MuseGAN 评论家
def conv(x, f, k, s, p):
    x = layers.Conv3D(filters = f
                , kernel_size = k
                , padding = p
                , strides = s
                , kernel_initializer = initializer
                )(x)
    x = layers.LeakyReLU()(x)
    return x

def Critic():
    critic_input = layers.Input(
        shape=(N_BARS, N_STEPS_PER_BAR, N_PITCHES, N_TRACKS),
        name='critic_input'
    ) # ①

    x = critic_input
    x = conv(x, f=128, k = (2,1,1), s = (1,1,1), p = 'valid') # ②
    x = conv(x, f=128, k = (N_BARS - 1,1,1), s = (1,1,1), p = 'valid')

    x = conv(x, f=128, k = (1,1,12), s = (1,1,12), p = 'same') # ③
    x = conv(x, f=128, k = (1,1,7), s = (1,1,7), p = 'same')

    x = conv(x, f=128, k = (1,2,1), s = (1,2,1), p = 'same') # ④
    x = conv(x, f=128, k = (1,2,1), s = (1,2,1), p = 'same')
    x = conv(x, f=256, k = (1,4,1), s = (1,2,1), p = 'same')
    x = conv(x, f=512, k = (1,3,1), s = (1,2,1), p = 'same')

    x = layers.Flatten()(x)

    x = layers.Dense(1024, kernel_initializer = initializer)(x)
    x = layers.LeakyReLU()(x)

    critic_output = layers.Dense(
        1, activation=None, kernel_initializer = initializer
    )(x) # ⑤

    return models.Model(critic_input, critic_output)

critic = Critic()

评论家的输入是一个多轨多小节乐谱数组,每个形状为[N_BARS, N_STEPS_PER_BAR, N_PITCHES, N_TRACKS]

首先,我们沿着小节轴折叠张量。由于我们使用的是 4D 张量,所以在评论家中应用Conv3D层。

接下来,我们沿着音高轴折叠张量。

最后,我们沿着时间步轴折叠张量。

输出是一个具有单个单元且没有激活函数的Dense层,这是 WGAN-GP 框架所需的。

MuseGAN 的分析

我们可以通过生成一个乐谱,然后调整一些输入噪声参数来查看对输出的影响来进行一些实验。

生成器的输出是一个值范围在[-1, 1]的数组(由于最终层的 tanh 激活函数)。为了将其转换为每个轨道的单个音符,我们选择每个时间步的所有 84 个音高中具有最大值的音符。在原始的 MuseGAN 论文中,作者使用了阈值 0,因为每个轨道可以包含多个音符;然而,在这种情况下,我们可以简单地取最大值来确保每个时间步每个轨道恰好有一个音符,这与巴赫赞美诗的情况相同。

图 11-16 显示了模型从随机正态分布的噪声向量生成的乐谱(左上角)。我们可以通过欧几里德距离找到数据集中最接近的乐谱,并检查我们生成的乐谱是否是数据集中已经存在的音乐片段的副本——最接近的乐谱显示在其正下方,我们可以看到它与我们生成的乐谱并不相似。

现在让我们玩弄输入噪声来微调我们生成的乐谱。首先,我们可以尝试改变和弦噪声向量——图 11-16 中左下角的乐谱显示了结果。我们可以看到每个轨道都已经改变,正如预期的那样,而且两个小节展现出不同的特性。在第二小节中,贝斯线更加动态,顶部音乐线的音高比第一小节更高。这是因为影响两个小节的潜在向量是不同的,因为输入和弦向量通过了一个时间网络。

图 11-16。MuseGAN 预测乐谱的示例,显示训练数据中最接近的真实乐谱以及通过改变输入噪声而影响生成乐谱的情况

总结

当我们改变风格向量(右上角)时,两个小节以类似的方式改变。整个乐段的风格已经从原始生成的乐谱中改变,以一种一致的方式(即,相同的潜在向量被用来调整所有轨道和小节)。

我们还可以通过旋律和节奏输入单独改变轨道。在图 11-16 中间右侧的乐谱中,我们可以看到仅改变顶部音乐线的旋律噪声输入的效果。所有其他部分保持不变,但顶部音符发生了显著变化。此外,我们可以看到顶部音乐线两个小节之间的节奏变化:第二小节比第一小节更动态,包含比第一小节更快的音符。

最后,图中右下角的乐谱显示了当我们仅改变贝斯的节奏输入参数时预测的乐谱。同样,所有其他部分保持不变,但贝斯部分是不同的。此外,贝斯的整体模式在小节之间保持相似,这是我们所期望的。

这展示了如何每个输入参数可以直接影响生成的音乐序列的高级特征,就像我们之前能够调整 VAE 和 GAN 的潜在向量以改变生成图像的外观一样。模型的一个缺点是必须预先指定要生成的小节数。为了解决这个问题,作者展示了模型的一个扩展,允许将先前的小节作为输入馈入,使模型能够通过不断将最近预测的小节作为额外输入来生成长形乐谱。

在本章中,我们探讨了两种不同类型的音乐生成模型:Transformer 和 MuseGAN。

Transformer 的设计类似于我们在第九章中看到的用于文本生成的网络。音乐和文本生成有很多共同点,通常可以同时用于两者的类似技术。我们通过将两个输入和输出流(音符和持续时间)纳入 Transformer 架构来扩展了 Transformer 架构。我们看到模型能够通过准确生成巴赫音乐来学习关于调式和音阶等概念。

我们还探讨了如何调整标记化过程以处理多声部(多轨)音乐生成。网格标记化将乐谱的钢琴卷表示序列化,使我们能够在描述每个音轨中存在哪个音符的令牌的单个流上训练 Transformer,在离散的、等间隔的时间步长间隔内。基于事件的标记化产生了一个配方,描述了如何以顺序方式创建多行音乐,通过一系列指令的单个流。这两种方法都有优缺点——Transformer 基于的音乐生成方法的成功或失败往往严重依赖于标记化方法的选择。

我们还看到生成音乐并不总是需要顺序方法——MuseGAN 使用卷积来生成具有多轨的多声部乐谱,将乐谱视为图像,其中轨道是图像的各个通道。MuseGAN 的新颖之处在于四个输入噪声向量(和弦、风格、旋律和节奏)的组织方式,使得可以对音乐的高级特征保持完全控制。虽然底层的和声仍然不像巴赫的那样完美或多样化,但这是对一个极其难以掌握的问题的良好尝试,并突显了 GAN 处理各种问题的能力。

¹ 黄成志安娜等人,“音乐 Transformer:生成具有长期结构的音乐”,2018 年 9 月 12 日,https://arxiv.org/abs/1809.04281

² 董浩文等人,“MuseGAN:用于符号音乐生成和伴奏的多轨序列生成对抗网络”,2017 年 9 月 19 日,https://arxiv.org/abs/1709.06298

第十二章:世界模型

本章介绍了近年来生成模型最有趣的应用之一,即它们在所谓的世界模型中的使用。

介绍

2018 年 3 月,David Ha 和 Jürgen Schmidhuber 发表了他们的“World Models”论文。该论文展示了如何通过在自己生成的梦境环境中进行实验来训练一个模型,而不是在真实环境中。这是一个很好的例子,说明了当与强化学习等其他机器学习技术一起应用时,生成建模如何解决实际问题。

架构的一个关键组件是一个生成模型,它可以构建给定当前状态和动作的下一个可能状态的概率分布。通过随机移动建立对环境基础物理的理解后,模型能够在自己对环境的内部表示中完全自行训练新任务。这种方法导致了在测试时两个任务的世界最佳得分。

在本章中,我们将详细探讨论文中的模型,特别关注一个需要代理学习如何尽可能快地在虚拟赛道上驾驶汽车的任务。虽然我们将使用 2D 计算机模拟作为我们的环境,但相同的技术也可以应用于在现实环境中测试策略昂贵或不可行的情况。

提示

在本章中,我们将引用“World Models”论文的优秀 TensorFlow 实现,该实现公开在 GitHub 上,我鼓励您克隆并运行!

在我们开始探索模型之前,我们需要更仔细地了解强化学习的概念。

强化学习

强化学习可以定义如下:

强化学习(RL)是一种机器学习领域,旨在训练一个代理在给定环境中以达到特定目标的最佳表现。

虽然判别建模和生成建模都旨在在观察数据集上最小化损失函数,但强化学习旨在最大化给定环境中代理的长期奖励。它通常被描述为机器学习的三大分支之一,与监督学习(使用标记数据进行预测)和无监督学习(从未标记数据中学习结构)并列。

让我们首先介绍一些与强化学习相关的关键术语:

环境

代理操作的世界。它定义了规则集,这些规则管理游戏状态更新过程和奖励分配,考虑到代理的先前动作和当前游戏状态。例如,如果我们正在教一个强化学习算法下棋,环境将包括规定给定动作(例如,兵的移动e2e4)如何影响下一个游戏状态(棋盘上棋子的新位置)的规则,并且还会指定如何评估给定位置是否为将军,并在获胜移动后为获胜玩家分配奖励 1。

代理

在环境中采取行动的实体。

游戏状态

代理可能会遇到的特定情况的数据(也称为状态)。例如,具有伴随游戏信息的特定棋盘配置,例如哪个玩家将进行下一步移动。

行动

代理可以采取的可行移动。

奖励

环境在采取行动后向代理返回的值。代理的目标是最大化其奖励的长期总和。例如,在国际象棋游戏中,将对手的国王将军的奖励为 1,而其他每一步的奖励为 0。其他游戏在整个 episode 中不断授予奖励(例如,在Space Invaders游戏中的得分)。

Episode

环境中代理的一次运行;这也被称为rollout

时间步

对于离散事件环境,所有状态、动作和奖励都被标注以显示它们在时间步t的值。

这些概念之间的关系在图 12-1 中显示。

https://github.com/OpenDocCN/ibooker-dl-zh/raw/master/docs/gen-dl/img/gdl2_1201.png

图 12-1. 强化学习图表

环境首先使用当前游戏状态s 0进行初始化。在时间步t,代理接收当前游戏状态s t并使用它来决定下一个最佳动作a t,然后执行。给定这个动作,环境然后计算下一个状态s t+1和奖励r t+1,并将它们传递回代理,以便循环再次开始。这个循环会持续直到 episode 的结束条件满足(例如,经过给定数量的时间步或代理赢得/输掉)。

我们如何设计一个代理来最大化在给定环境中的奖励总和?我们可以构建一个包含一组规则的代理,用于如何响应任何给定的游戏状态。然而,随着环境变得更加复杂,这很快变得不可行,并且永远不允许我们构建一个在特定任务中具有超人能力的代理,因为我们正在硬编码规则。强化学习涉及创建一个代理,通过反复游戏在复杂环境中学习最佳策略。

现在让我们来看一下模拟汽车在赛道上行驶的CarRacing环境。

赛车环境

CarRacing是通过Gymnasium包提供的环境。Gymnasium 是一个用于开发强化学习算法的 Python 库,其中包含几个经典的强化学习环境,如CartPolePong,以及提出更复杂挑战的环境,比如训练代理在不平坦地形上行走或赢得 Atari 游戏。

Gymnasium

Gymnasium 是 OpenAI 的 Gym 库的维护分支——自 2021 年以来,Gym 的进一步开发已转移到 Gymnasium。因此,在本书中,我们将 Gymnasium 环境称为 Gym 环境。

所有环境都提供了一个step方法,通过该方法您可以提交一个给定的动作;环境将返回下一个状态和奖励。通过反复调用代理选择的动作来调用 step 方法,您可以在环境中玩出一个 episode。还有一个reset方法,用于将环境恢复到初始状态,以及一个render方法,允许您观看您的代理在给定环境中执行。这对于调试和找到代理可以改进的地方非常有用。

让我们看看CarRacing环境中游戏状态、动作、奖励和 episode 是如何定义的:

游戏状态

一个 64×64 像素的 RGB 图像,描绘了赛道和汽车的俯视图。

动作

一组三个值:方向盘方向(-1 到 1)、加速度(0 到 1)和刹车(0 到 1)。代理必须在每个时间步设置这三个值。

奖励

每个时间步骤都会受到-0.1 的负惩罚,如果访问了新的赛道瓷砖,则会获得 1000/N的正奖励,其中N是构成赛道的瓷砖总数。

剧集

当汽车完成赛道或驶出环境边缘,或者经过了 3000 个时间步骤时,剧集结束。

这些概念在图 12-2 中的游戏状态的图形表示中显示。

https://github.com/OpenDocCN/ibooker-dl-zh/raw/master/docs/gen-dl/img/gdl2_1202.png

图 12-2. CarRacing环境中一个游戏状态的图形表示

视角

我们应该想象代理人漂浮在赛道上方,从鸟瞰视角控制汽车,而不是从驾驶员的视角看赛道。

世界模型概述

我们现在将对整个世界模型架构和训练过程进行高层概述,然后深入研究每个组件。

架构

解决方案由三个不同部分组成,如图 12-3 所示,它们分别进行训练:

V

变分自动编码器(VAE)

M

带有混合密度网络(MDN-RNN)的递归神经网络

C

一个控制器

https://github.com/OpenDocCN/ibooker-dl-zh/raw/master/docs/gen-dl/img/gdl2_1203.png

图 12-3. 世界模型架构图

VAE

当您驾驶时做出决策时,您并不会主动分析视野中的每个像素—而是将视觉信息压缩成较少数量的潜在实体,例如道路的直线程度、即将到来的弯道以及您相对于道路的位置,以指导您的下一个动作。

我们在第三章中看到,VAE 可以将高维输入图像压缩成一个潜在随机变量,该变量近似遵循标准高斯分布,通过最小化重构误差和 KL 散度。这确保了潜在空间是连续的,我们能够轻松从中进行采样以生成有意义的新观察。

在汽车赛道示例中,VAE 将 64×64×3(RGB)输入图像压缩成一个 32 维正态分布的随机变量,由两个变量mulogvar参数化。这里,logvar是分布方差的对数。我们可以从该分布中采样以产生代表当前状态的潜在向量z。这将传递给网络的下一个部分,MDN-RNN。

MDN-RNN

当您驾驶时,每个后续观察对您来说并不是完全意外的。如果当前观察表明前方道路左转,您向左转动方向盘,您期望下一个观察显示您仍然与道路保持一致。

如果您没有这种能力,您的汽车可能会在道路上蛇行,因为您无法看到稍微偏离中心会在下一个时间步骤中变得更糟,除非您现在采取措施。

这种前瞻性是 MDN-RNN 的任务,它试图根据先前的潜在状态和先前的动作来预测下一个潜在状态的分布。

具体来说,MDN-RNN 是一个具有 256 个隐藏单元的 LSTM 层,后面跟着一个混合密度网络(MDN)输出层,允许下一个潜在状态实际上可以从几个正态分布中的任何一个中抽取。

“世界模型”论文的一位作者 David Ha 也将相同的技术应用于手写生成任务,如图 12-4 所示,描述了下一个笔尖可能落在几个不同红色区域中的事实。

https://github.com/OpenDocCN/ibooker-dl-zh/raw/master/docs/gen-dl/img/gdl2_1204.png

图 12-4. 用于手写生成的 MDN

在汽车赛道示例中,我们允许下一个观察到的潜在状态的每个元素都可以从五个正态分布中的任何一个中抽取。

控制器

到目前为止,我们还没有提到选择动作的事情。这个责任在于控制器。控制器是一个密集连接的神经网络,其中输入是z(从 VAE 编码的分布中采样的当前潜在状态)和 RNN 的隐藏状态的串联。三个输出神经元对应于三个动作(转向、加速、刹车),并且被缩放以落入适当的范围内。

控制器使用强化学习进行训练,因为没有训练数据集会告诉我们某个动作是还是。相反,代理通过反复实验自己发现这一点。

正如我们将在本章后面看到的那样,“世界模型”论文的关键在于它展示了如何在代理的环境生成模型中进行强化学习,而不是在 Gym 环境中。换句话说,它发生在代理对环境行为的幻想版本中,而不是真实的环境中。

为了理解三个组件的不同角色以及它们如何共同工作,我们可以想象它们之间的对话:

VAE(查看最新的 64×64×3 观察):这看起来像一条笔直的道路,接近一个轻微的左弯,汽车面向道路的方向(z)。

RNN:基于那个描述(z)和控制器选择在上一个时间步加速的事实(action),我将更新我的隐藏状态(h),以便下一个观察被预测为仍然是一条笔直的道路,但在视野中有稍微更多的左转。

控制器:基于来自 VAE 的描述(z)和来自 RNN 的当前隐藏状态(h),我的神经网络输出[0.34, 0.8, 0]作为下一个动作。

然后,控制器的动作传递给环境,环境返回更新后的观察结果,循环再次开始。

训练

训练过程包括五个步骤,按顺序运行,概述如下:

  1. 收集随机回滚数据。在这里,代理不关心给定任务,而是简单地使用随机动作探索环境。多个剧集被模拟,每个时间步的观察状态、动作和奖励被存储。这个想法是建立一个关于环境物理工作方式的数据集,然后 VAE 可以从中学习以有效地捕捉状态作为潜在向量。MDN-RNN 随后可以学习潜在向量随时间的演变方式。

  2. 训练 VAE。使用随机收集的数据,我们在观察图像上训练 VAE。

  3. 收集数据以训练 MDN-RNN。一旦我们有了训练好的 VAE,我们使用它将每个收集到的观察编码为mulogvar向量,并将其保存在当前动作和奖励旁边。

  4. 训练 MDN-RNN。我们获取一批批次的剧集,并在每个时间步加载在步骤 3 生成的mulogvaractionreward变量。然后我们从mulogvar向量中采样一个z向量。给定当前的z向量、actionreward,然后训练 MDN-RNN 来预测随后的z向量和reward

  5. 训练控制器。通过训练好的 VAE 和 RNN,我们现在可以训练控制器,以输出一个动作,给定当前的z和 RNN 的隐藏状态h。控制器使用进化算法 CMA-ES 作为其优化器。该算法奖励生成导致任务整体得分较高的动作的矩阵权重,以便未来的代际也可能继承这种期望的行为。

让我们现在更详细地看看每个步骤。

收集随机回滚数据

第一步是从环境中收集 rollout 数据,使用一个代理器执行随机动作。这可能看起来很奇怪,因为我们最终希望我们的代理器学会如何采取智能动作,但这一步将提供代理器将用于学习世界运作方式以及其动作(尽管起初是随机的)如何影响随后观察的数据。

我们可以通过启动多个 Python 进程并行捕获多个 episode,每个进程运行环境的单独实例。每个进程将在单独的核心上运行,因此如果您的计算机有很多核心,您可以比只有几个核心时更快地收集数据。

这一步使用的超参数如下:

parallel_processes

要运行的并行进程数(例如,如果您的计算机有≥8 个核心,则为8

max_trials

每个进程应总共运行多少个 episode(例如,125,因此 8 个进程将总共创建 1,000 个 episode)

max_frames

每个 episode 的最大时间步数(例如,300

图 12-5 显示了一个 episode 的第 40 到 59 帧的摘录,汽车驶向一个拐角,同时显示了随机选择的动作和奖励。请注意,随着汽车经过新的赛道瓷砖,奖励变为 3.22,但其他情况下为-0.1。

https://github.com/OpenDocCN/ibooker-dl-zh/raw/master/docs/gen-dl/img/gdl2_1205.png

图 12-5。一个 episode 的第 40 到 59 帧

训练 VAE

现在我们在收集的数据上构建一个生成模型(VAE)。请记住,VAE 的目的是让我们将一个 64×64×3 的图像折叠成一个正态分布的随机变量z,其分布由两个向量mulogvar参数化。这两个向量的长度均为 32。这一步的超参数如下:

vae_batch_size

训练 VAE 时使用的批量大小(每批次观察数量)(例如,100

z_size

潜在z向量的长度(因此mulogvar变量)(例如,32

vae_num_epoch

训练时的 epoch 数量(例如,10

VAE 架构

正如我们之前看到的,Keras 允许我们不仅定义将进行端到端训练的 VAE 模型,还可以定义额外的子模型,分别定义训练网络的编码器和解码器。例如,当我们想要对特定图像进行编码或解码给定的z向量时,这些将非常有用。我们将定义 VAE 模型和三个子模型,如下所示:

vae

这是经过训练的端到端 VAE。它接受一个 64×64×3 的图像作为输入,并输出一个重建的 64×64×3 的图像。

encode_mu_logvar

这接受一个 64×64×3 的图像作为输入,并输出与该输入对应的mulogvar向量。多次通过该模型运行相同的输入图像将每次产生相同的mulogvar向量。

encode

这接受一个 64×64×3 的图像作为输入,并输出一个采样的z向量。多次通过该模型运行相同的输入图像将每次产生不同的z向量,使用计算出的mulogvar值来定义采样分布。

decode

这接受一个z向量作为输入,并返回重建的 64×64×3 图像。

模型和子模型的图表显示在图 12-6 中。

https://github.com/OpenDocCN/ibooker-dl-zh/raw/master/docs/gen-dl/img/gdl2_1206.png

图 12-6。《World Models》论文中的 VAE 架构

探索 VAE

现在我们将查看 VAE 和每个子模型的输出,然后看看 VAE 如何用于生成全新的赛道观察。

VAE 模型

如果我们将一个观察输入到 VAE 中,它能够准确重建原始图像,如图 12-7 所示。这对于直观检查 VAE 是否正常工作非常有用。

https://github.com/OpenDocCN/ibooker-dl-zh/raw/master/docs/gen-dl/img/gdl2_1207.png

图 12-7。VAE 模型的输入和输出

编码器模型

如果我们用一个观察来喂encode_mu_logvar模型,输出将是描述多元正态分布的生成mulogvar向量。encode模型进一步采样特定的z向量。显示两个编码器模型输出的图表在图 12-8 中。

https://github.com/OpenDocCN/ibooker-dl-zh/raw/master/docs/gen-dl/img/gdl2_1208.png

图 12-8。编码器模型的输出

潜变量z是从由mulogvar定义的高斯分布中采样的,通过从标准高斯中采样,然后缩放和移位采样的向量(示例 12-1)。

示例 12-1。从由mulogvar定义的多元正态分布中采样z
eps = tf.random_normal(shape=tf.shape(mu))
sigma = tf.exp(logvar * 0.5)
z = mu + eps * sigma

解码器模型

decode模型接受一个z向量作为输入,并重构原始图像。在图 12-9 中,我们线性插值z的两个维度,以展示每个维度似乎编码轨道的特定方面——在这个例子中,z[4]控制了最接近汽车的轨道的左右方向,z[7]控制了即将到来的左转的急剧程度。

这表明 VAE 学习到的潜在空间是连续的,可以用来生成代理以前从未观察过的新轨迹段。

https://github.com/OpenDocCN/ibooker-dl-zh/raw/master/docs/gen-dl/img/gdl2_1209.png

图 12-9。z的两个维度的线性插值

收集数据以训练 MDN-RNN

现在我们有了一个经过训练的 VAE,我们可以用它来为我们的 MDN-RNN 生成训练数据。

在这一步中,我们通过encode_mu_logvar模型传递所有随机回滚观察,并存储与每个观察相对应的mulogvar向量。这些编码数据,以及已经收集的actionrewarddone变量,将用于训练 MDN-RNN。这个过程在图 12-10 中显示。

https://github.com/OpenDocCN/ibooker-dl-zh/raw/master/docs/gen-dl/img/gdl2_1210.png

图 12-10。创建 MDN-RNN 训练数据集

训练 MDN-RNN

现在我们可以训练 MDN-RNN 来预测下一个z向量的分布,并在未来一个时间步骤内奖励,给定当前的z向量、当前的动作和先前的奖励。然后我们可以使用 RNN 的内部隐藏状态(可以被视为模型对环境动态的当前理解)作为控制器的输入之一,控制器最终将决定最佳的下一步动作。

这个过程的超参数如下:

rnn_batch_size

训练 MDN-RNN 时使用的批量大小(每批次多少个序列)(例如,100

rnn_num_steps

训练的总迭代次数(例如,4000

MDN-RNN 架构

MDN-RNN 的架构在图 12-11 中显示。

https://github.com/OpenDocCN/ibooker-dl-zh/raw/master/docs/gen-dl/img/gdl2_1211.png

图 12-11。MDN-RNN 架构

MDN-RNN 由一个 LSTM 层(RNN)组成,后面是一个密集连接层(MDN),将 LSTM 的隐藏状态转换为混合分布的参数。让我们逐步走过网络。

LSTM 层的输入是一个长度为 36 的向量,是从 VAE 的编码z向量(长度为 32)、当前动作(长度为 3)和先前奖励(长度为 1)连接而成的。

LSTM 层的输出是一个长度为 256 的向量,每个 LSTM 单元在该层中有一个值。这被传递给 MDN,MDN 只是一个密集连接层,将长度为 256 的向量转换为长度为 481 的向量。

为什么是 481?图 12-12 解释了从 MDN-RNN 的输出组成。混合密度网络的目的是模拟我们的下一个z可能从几个可能的分布中以一定概率抽取的事实。在汽车赛车示例中,我们选择了五个正态分布。我们需要多少参数来定义这些分布?对于这 5 个混合物,我们需要一个mu和一个logvar(来定义分布)以及被选择的这个混合物的对数概率(logpi),对于z的每个 32 个维度。这使得 5 × 3 × 32 = 480 个参数。额外的一个参数是用于奖励预测。

从混合密度网络的输出

图 12-12。混合密度网络的输出

从 MDN-RNN 中抽样

我们可以从 MDN 输出中抽样,通过以下过程生成下一个z和下一个时间步的奖励的预测:

  1. 将 481 维输出向量分割为 3 个变量(logpimulogvar)和奖励值。

  2. logpi进行指数化和缩放,以便将其解释为 5 个混合索引上的 32 个概率分布。

  3. 对于z的 32 个维度中的每一个,从由logpi创建的分布中抽样(即选择哪个分布应该用于z的每个维度)。

  4. 获取此分布的相应mulogvar的值。

  5. 从由所选参数mulogvar参数化的正态分布中为z的每个维度抽样一个值。

MDN-RNN 的损失函数是z向量重构损失和奖励损失的总和。z向量重构损失是 MDN-RNN 预测的分布的负对数似然,给定z的真实值,奖励损失是预测奖励和真实奖励之间的均方误差。

训练控制器

最后一步是使用协方差矩阵适应进化策略(CMA-ES)来训练控制器(输出选择的动作的网络)。

该步骤的超参数如下:

controller_num_worker

将以并行方式测试解决方案的工作者数量

controller_num_worker_trial

每个工作者在每一代将被给予测试的解决方案数量

controller_num_episode

每个解决方案将被测试的情节数量,以计算平均奖励

controller_eval_steps

评估当前最佳参数集之间的代数数量

控制器架构

控制器的架构非常简单。它是一个没有隐藏层的密集连接神经网络。它将输入向量直接连接到动作向量。

输入向量是当前z向量(长度 32)和 LSTM 当前隐藏状态(长度 256)的串联,得到长度为 288 的向量。由于我们将每个输入单元直接连接到 3 个输出动作单元,所以要调整的权重总数为 288 × 3 = 864,再加上 3 个偏置权重,总共为 867。

我们应该如何训练这个网络?请注意,这不是一个监督学习问题——我们不是在尝试预测正确的动作。没有正确动作的训练集,因为我们不知道对于环境的给定状态来说最佳动作是什么。这就是将这个问题区分为强化学习问题的原因。我们需要代理通过在环境中进行实验并根据接收到的反馈更新其权重来发现权重的最佳值。

进化策略是解决强化学习问题的流行选择,因为它们简单、高效且可扩展。我们将使用一种特定的策略,称为 CMA-ES。

CMA-ES

进化策略通常遵循以下过程:

  1. 创建一组代理并随机初始化每个代理要优化的参数。

  2. 循环以下步骤:

    1. 评估环境中的每个代理,返回多个周期的平均奖励。

    2. 繁殖得分最高的代理,以创建种群的新成员。

    3. 为新成员的参数添加随机性。

    4. 通过添加新创建的代理和删除表现不佳的代理来更新种群池。

这类似于动物在自然界中进化的过程 - 因此称为进化策略。在这种情况下,“繁殖”简单地意味着结合现有的得分最高的代理,使得下一代更有可能产生高质量的结果,类似于它们的父母。与所有强化学习解决方案一样,需要在贪婪地寻找局部最优解和探索参数空间中未知区域以寻找潜在更好解决方案之间找到平衡。这就是为什么向种群中添加随机性很重要,以确保我们的搜索领域不会太狭窄。

CMA-ES 只是进化策略的一种形式。简而言之,它通过维护一个正态分布来采样新代理的参数。在每一代中,它更新分布的均值以最大化从上一个时间步采样高分代理的可能性。同时,它更新分布的协方差矩阵以最大化在给定先前均值的情况下采样高分代理的可能性。它可以被视为一种自然产生的梯度下降形式,但它的优势在于它是无导数的,这意味着我们不需要计算或估计昂贵的梯度。

在图 12-13 中展示了算法在一个玩具示例上的一个代的演示。在这里,我们试图找到一个高度非线性函数在二维空间中的最小点 - 图像中红/黑区域的函数值大于图像中白/黄区域的函数值。

https://github.com/OpenDocCN/ibooker-dl-zh/raw/master/docs/gen-dl/img/gdl2_1213.png

图 12-13. CMA-ES 算法的一个更新步骤(来源:Ha, 2017

步骤如下:

  1. 我们从随机生成的 2D 正态分布开始,并从中采样候选人种群,如图 12-13 中的蓝色所示。

  2. 然后我们计算每个候选者的函数值,并将最佳 25%孤立出来,如图 12-13 中的紫色所示 - 我们将这组点称为P

  3. 我们将新正态分布的均值设置为P中点的均值。这可以被视为繁殖阶段,在这个阶段我们只使用最佳候选者来生成分布的新均值。我们还将新正态分布的协方差矩阵设置为P中点的协方差矩阵,但在协方差计算中使用现有的均值而不是P中点的当前均值。现有均值与P中点的均值之间的差异越大,下一个正态分布的方差就越大。这会自然地在寻找最佳参数的过程中产生动量效应。

  4. 然后我们可以从具有更新均值和协方差矩阵的新正态分布中采样一个新的候选人种群。

图 12-14 展示了该过程的几代。请看均值向最小值大步移动时协方差如何扩大,但当均值稳定在真实最小值时,协方差如何变窄。

https://github.com/OpenDocCN/ibooker-dl-zh/raw/master/docs/gen-dl/img/gdl2_1214.png

图 12-14. CMA-ES(来源:维基百科)

对于汽车赛车任务,我们没有一个明确定义的函数来最大化,而是一个环境,其中要优化的 867 个参数决定了代理的得分如何。最初,一些参数集将以随机方式生成比其他参数更高的得分,算法将逐渐将正态分布移向在环境中得分最高的那些参数的方向。

并行化 CMA-ES

CMA-ES 的一个巨大优势是它可以很容易地并行化。算法中最耗时的部分是计算给定参数集的得分,因为它需要在环境中模拟具有这些参数的代理。然而,这个过程可以并行化,因为个别模拟之间没有依赖关系。有一个协调器进程,它将要测试的参数集并行发送给许多节点进程。节点将结果返回给协调器,协调器累积结果,然后将该代的整体结果传递给 CMA-ES 对象。该对象根据图 12-13 更新正态分布的均值和协方差矩阵,并为协调器提供一个新的人口进行测试。然后循环重新开始。图 12-15 在图表中解释了这一点。

https://github.com/OpenDocCN/ibooker-dl-zh/raw/master/docs/gen-dl/img/gdl2_1215.png

图 12-15. 并行化 CMA-ES——这里有一个人口规模为八个和四个节点(因此 t = 2,每个节点负责的试验次数)

协调器向 CMA-ES 对象(es)请求一组要试验的参数。

协调器将参数分成可用节点的数量。在这里,每个四个节点进程都会得到两组参数进行试验。

节点运行一个工作进程,循环遍历每组参数,并为每组参数运行几集。在这里,我们为每组参数运行三集。

每集剧集的奖励被平均以给出每组参数的单个得分。

每个节点将其得分列表返回给协调器。

协调器将所有得分组合在一起,并将此列表发送给es对象。

es对象使用这个奖励列表来计算新的正态分布,如图 12-13 所示。

大约经过 200 代,训练过程为汽车赛车任务实现了约 840 的平均奖励分数,如图 12-16 所示。

https://github.com/OpenDocCN/ibooker-dl-zh/raw/master/docs/gen-dl/img/gdl2_1216.png

图 12-16. 控制器训练过程的平均剧集奖励,按代数(来源:Zac Wellmer,“World Models”

梦中训练

到目前为止,控制器训练是使用 Gym 的CarRacing环境来实现将模拟从一个状态移动到下一个状态的步骤方法。该函数根据环境的当前状态和选择的动作计算下一个状态和奖励。

注意步骤方法在我们模型中执行的功能与 MDN-RNN 非常相似。从 MDN-RNN 中采样输出了下一个z和奖励的预测,给出了当前z和选择的动作。

事实上,MDN-RNN 可以被视为一个独立的环境,但是在z空间中运行,而不是在原始图像空间中。令人难以置信的是,这意味着我们实际上可以用 MDN-RNN 的副本替换真实环境,并在 MDN-RNN 启发的梦境中完全训练控制器,以模拟环境应该如何行为。

换句话说,MDN-RNN 已经从原始随机移动数据集中学到了关于真实环境的一般物理知识,因此可以在训练控制器时作为真实环境的代理使用。这是非常了不起的——这意味着代理可以通过思考如何在梦境环境中最大化奖励来训练自己学习新任务,而无需在真实世界中测试策略。然后,它可以在第一次尝试任务时表现良好,而实际上从未尝试过这项任务。

接下来是在真实环境和梦境中进行训练的架构比较:真实世界架构显示在图 12-17 中,梦境训练设置在图 12-18 中说明。

https://github.com/OpenDocCN/ibooker-dl-zh/raw/master/docs/gen-dl/img/gdl2_1217.png

图 12-17。在 Gym 环境中训练控制器

请注意,在梦境架构中,控制器的训练完全在z空间中进行,而无需将z向量解码回可识别的轨道图像。当然,我们可以这样做,以便视觉检查代理的性能,但这并不是训练所必需的。

https://github.com/OpenDocCN/ibooker-dl-zh/raw/master/docs/gen-dl/img/gdl2_1218.png

图 12-18。在 MDN-RNN 梦境环境中训练控制器

在 MDN-RNN 梦境环境中完全训练代理的一个挑战是过拟合。当代理在梦境环境中找到一种有益的策略,但在真实环境中泛化能力不强时,就会发生这种情况,这是因为 MDN-RNN 没有完全捕捉到在某些条件下真实环境的行为方式。

原始论文的作者强调了这一挑战,并展示了如何包含一个温度参数来控制模型的不确定性可以帮助缓解问题。增加这个参数会放大通过 MDN-RNN 对z进行采样时的方差,导致在梦境环境中训练时出现更多波动。控制器对于遇到已知状态的更安全策略会获得更高的奖励,因此往往更容易泛化到真实环境。然而,增加温度需要平衡,以免使环境变得太波动,以至于控制器无法学习任何策略,因为梦境环境在时间上的演变不够一致。

在原始论文中,作者展示了这种技术成功应用于不同的环境:DoomTakeCover,基于电脑游戏Doom。图 12-19 显示了改变温度参数如何影响虚拟(梦境)得分和真实环境中的实际得分。

https://github.com/OpenDocCN/ibooker-dl-zh/raw/master/docs/gen-dl/img/gdl2_1219.png

图 12-19。使用温度控制梦境环境波动性(来源:Ha and Schmidhuber, 2018

在真实环境中,最佳温度设置为 1.15,在发表时超过了当前 Gym 领导者的得分 1,092。这是一个惊人的成就——请记住,控制器从未在真实环境中尝试过这项任务。它只是在真实环境中随机行动(用于训练 VAE 和 MDN-RNN 模型),然后使用梦境环境来训练控制器。

使用生成世界模型作为强化学习方法的一个关键优势是,在梦境环境中的每一代训练比在真实环境中的训练要快得多。这是因为 MDN-RNN 对z和奖励预测比 Gym 环境中的z和奖励计算更快。

总结

在本章中,我们看到了如何在强化学习环境中利用生成模型(VAE)使代理能够通过在自己生成的梦境中测试策略来学习有效策略,而不是在真实环境中进行测试。

VAE 被训练来学习环境的潜在表示,然后作为输入传递给一个递归神经网络,该网络在潜在空间内预测未来轨迹。令人惊讶的是,代理可以使用这个生成模型作为伪环境,通过演化方法迭代地测试策略,以便在真实环境中得到良好的泛化。

有关该模型的更多信息,请参阅原始论文作者编写的出色互动解释,可在在线获取。

¹ 大卫·哈和尤尔根·施密德胡伯,“世界模型”,2018 年 3 月 27 日,https://arxiv.org/abs/1803.10122

² 大卫·哈,“演化策略的视觉指南”,2017 年 10 月 29 日,https://blog.otoro.net/2017/10/29/visual-evolution-strategies

第十三章:多模态模型

到目前为止,我们已经分析了专注于单一数据模态的生成学习问题:文本、图像或音乐。我们已经看到了 GAN 和扩散模型如何生成最先进的图像,以及 Transformer 如何引领文本和图像生成的方式。然而,作为人类,我们没有跨模态的困难——例如,描述给定照片中正在发生的事情,创作数字艺术来描绘书中虚构的幻想世界,或将电影配乐与给定场景的情感相匹配。我们能训练机器做同样的事吗?

介绍

多模态学习涉及训练生成模型以在两种或更多种不同类型的数据之间进行转换。在过去两年中引入的一些最令人印象深刻的生成模型具有多模态性质。在本章中,我们将详细探讨它们的工作原理,并考虑未来的生成建模将如何受到大型多模态模型的影响。

我们将探讨四种不同的视觉语言模型:来自 OpenAI 的 DALL.E 2;来自 Google Brain 的 Imagen;来自 Stability AI、CompVis 和 Runway 的 Stable Diffusion;以及来自 DeepMind 的 Flamingo。

提示

本章的目的是简明扼要地解释每个模型的工作原理,而不深入探讨每个设计决策的细节。有关更多信息,请参考每个模型的各自论文,其中详细解释了所有设计选择和架构决策。

文本到图像生成侧重于从给定的文本提示生成最先进的图像。例如,给定输入“用造型粘土制成的一颗西兰花头,在阳光下微笑”,我们希望模型能够输出一个与文本提示精确匹配的图像,如图 13-1 所示。

这显然是一个极具挑战性的问题。文本理解和图像生成本身就很难解决,正如我们在本书的前几章中所看到的。这样的多模态建模提出了额外的挑战,因为模型还必须学习如何跨越两个领域之间的鸿沟,并学习一个共享表示,使其能够准确地将一段文本转换为高保真图像而不丢失信息。

https://github.com/OpenDocCN/ibooker-dl-zh/raw/master/docs/gen-dl/img/gdl2_1301.png

图 13-1。DALL.E 2 进行文本到图像生成的示例

此外,为了取得成功,模型必须能够结合可能从未见过的概念和风格。例如,没有米开朗基罗的壁画中有人们戴着虚拟现实头盔,但我们希望我们的模型能够在我们要求时创建这样的图像。同样,模型准确推断生成图像中的对象如何与彼此相关,基于文本提示。例如,“宇航员骑着甜甜圈穿越太空”的图片应该与“宇航员在拥挤的空间里吃甜甜圈”的图片看起来截然不同。模型必须学习单词如何通过上下文赋予意义,以及如何将实体之间的明确文本关系转换为暗示相同含义的图像。

DALL.E 2

我们将要探索的第一个模型是DALL.E 2,这是由 OpenAI 设计用于文本到图像生成的模型。该模型的第一个版本,DALL.E,是在 2021 年 2 月发布的,引发了对生成多模态模型的新一波兴趣。在本节中,我们将调查该模型的第二次迭代,DALL.E 2,于 2022 年 4 月发布,距离第一个版本发布仅一年多一点。

DALL.E 2 是一个非常令人印象深刻的模型,进一步增进了我们对 AI 解决这类多模态问题能力的理解。它不仅在学术上具有影响力,还迫使我们提出与 AI 在创造性过程中的角色有关的重大问题,这些问题以前被认为是人类独有的。我们将从探索 DALL.E 2 的工作方式开始,建立在本书前面已经探讨过的关键基本思想之上。

架构

要理解 DALL.E 2 的工作原理,我们必须首先了解其整体架构,如图 13-2 所示。

https://github.com/OpenDocCN/ibooker-dl-zh/raw/master/docs/gen-dl/img/gdl2_1302.png

图 13-2. DALL.E 2 架构

有三个不同的部分需要考虑:文本编码器先验解码器。文本首先通过文本编码器传递,以产生文本嵌入向量。然后,该向量通过先验进行转换,以产生图像嵌入向量。最后,这通过解码器传递,连同原始文本,以生成图像。我们将依次逐个步骤地介绍每个组件,以全面了解 DALL.E 2 在实践中的工作方式。

文本编码器

文本编码器的目的是将文本提示转换为表示文本提示概念含义的嵌入向量,该向量位于潜在空间内。正如我们在前几章中所看到的,将离散文本转换为连续潜在空间向量对于所有下游任务都是至关重要的,因为我们可以根据特定目标进一步操纵向量。

在 DALL.E 2 中,作者并不是从头开始训练文本编码器,而是利用了一个名为对比语言-图像预训练(CLIP)的现有模型,也是由 OpenAI 制作的。因此,要理解文本编码器,我们必须首先了解 CLIP 的工作原理。

CLIP

CLIP³是 OpenAI 于 2021 年 2 月发布的一篇论文中公布的(就在第一篇 DALL.E 论文发布几天后),该论文将其描述为“一种能够有效地从自然语言监督中学习视觉概念的神经网络。”

它使用一种称为对比学习的技术将图像与文本描述进行匹配。该模型在从互联网上抓取的 4 亿个文本-图像对数据集上进行训练——一些示例对显示在图 13-3 中。作为比较,ImageNet 中有 1400 万个手动注释的图像。给定一幅图像和一组可能的文本描述,它的任务是找到实际与图像匹配的描述。

https://github.com/OpenDocCN/ibooker-dl-zh/raw/master/docs/gen-dl/img/gdl2_1303.png

图 13-3. 文本-图像对的示例

对比学习背后的关键思想很简单。我们训练两个神经网络:一个文本编码器,将文本转换为文本嵌入,以及一个图像编码器,将图像转换为图像嵌入。然后,给定一批文本-图像对,我们使用余弦相似度比较所有文本和图像嵌入组合,并训练网络,以最大化匹配文本-图像对之间的分数,并最小化不正确的文本-图像对之间的分数。这个过程在图 13-4 中显示。

CLIP 不是生成模型

请注意,CLIP 本身不是生成模型——它不能生成图像或文本。它更接近于判别模型,因为最终输出是关于给定图像最接近哪个文本描述(或反之亦然,哪个图像最接近给定文本描述)的预测。

https://github.com/OpenDocCN/ibooker-dl-zh/raw/master/docs/gen-dl/img/gdl2_1304.png

图 13-4. CLIP 训练过程

文本编码器和图像编码器都是 Transformer——图像编码器是 Vision Transformer(ViT),在“ViT VQ-GAN”中介绍,它将注意力的相同概念应用于图像。作者测试了其他模型架构,但发现这种组合产生了最好的结果。

CLIP 特别有趣的地方在于它可以用于对从未接触过的任务进行零样本预测。例如,假设我们想使用 CLIP 来预测 ImageNet 数据集中给定图像的标签。我们可以首先通过使用模板(例如“一张<标签>的照片”)将 ImageNet 标签转换为句子,如图 13-5 所示。

https://github.com/OpenDocCN/ibooker-dl-zh/raw/master/docs/gen-dl/img/gdl2_1305.png

图 13-5. 将新数据集中的标签转换为标题,以生成 CLIP 文本嵌入

为了预测给定图像的标签,我们可以通过 CLIP 图像编码器传递图像,并计算图像嵌入与所有可能文本嵌入之间的余弦相似度,以找到得分最高的标签,如图 13-6 所示。

https://github.com/OpenDocCN/ibooker-dl-zh/raw/master/docs/gen-dl/img/gdl2_1306.png

图 13-6. 使用 CLIP 预测图像内容

请注意,我们无需重新训练 CLIP 神经网络,即可将其应用于新任务。它使用语言作为一个通用领域,通过它可以表达任何一组标签。

使用这种方法,可以证明 CLIP 在各种图像数据集标签挑战中表现良好(图 13-7)。其他模型通常在应用于具有相同标签的不同数据集时失败,因为它们高度优化于它们训练的个别数据集。CLIP 更加稳健,因为它学习了对完整文本描述和图像的深刻概念理解,而不仅仅擅长于将单个标签分配给给定数据集中的图像的狭窄任务。

https://github.com/OpenDocCN/ibooker-dl-zh/raw/master/docs/gen-dl/img/gdl2_1307.png

图 13-7. CLIP 在各种图像标签数据集上表现良好(来源:Radford 等人,2021

如前所述,CLIP 是根据其区分能力来衡量的,那么它如何帮助我们构建生成模型,如 DALL.E 2 呢?

答案是,我们可以将训练好的文本编码器作为 DALL.E 2 等更大模型的一部分,冻结权重。训练好的编码器只是一个将文本转换为文本嵌入的通用模型,对于生成图像等下游任务应该是有用的。文本编码器能够捕捉文本的丰富概念理解,因为它经过训练,使其尽可能与其匹配的图像嵌入对应物相似,后者仅由配对图像产生。因此,它是我们需要能够从文本领域跨越到图像领域的桥梁的第一部分。

先验

下一阶段的过程涉及将文本嵌入转换为 CLIP 图像嵌入。DALL.E 2 的作者尝试了两种不同的方法来训练先验模型:

  • 自回归模型

  • 扩散模型

他们发现扩散方法优于自回归模型,并且在计算效率上更高。在本节中,我们将看看两者的区别。

自回归先验

自回归模型按顺序生成输出,通过对输出标记(例如单词、像素)进行排序,并将下一个标记的生成条件放在前面的标记上。我们已经在之前的章节中看到了这在循环神经网络(例如 LSTMs)、Transformer 和 PixelCNN 中的应用。

DALL.E 2 的自回归先验是一个编码器-解码器 Transformer。它经过训练,可以在给定 CLIP 文本嵌入的情况下重现 CLIP 图像嵌入,如图 13-8 所示。请注意,原始论文中提到了一些自回归模型的附加组件,为了简洁起见,我们在这里省略了。

https://github.com/OpenDocCN/ibooker-dl-zh/raw/master/docs/gen-dl/img/gdl2_1308.png

图 13-8. DALL.E 2 的自回归先验的简化图

该模型在 CLIP 文本-图像对数据集上进行训练。您可以将其视为我们需要的桥梁的第二部分,以便从文本领域跳转到图像领域:我们正在将一个向量从文本嵌入潜在空间转换为图像嵌入潜在空间。

输入文本嵌入由 Transformer 的编码器处理,产生另一个表示,传递给解码器,同时传递当前生成的输出图像嵌入。输出是逐个元素生成的,使用教师强制来比较预测的下一个元素与实际的 CLIP 图像嵌入。

生成的顺序性意味着自回归模型在计算效率上不如作者尝试的其他方法,接下来我们将看一下这些方法。

扩散先验

正如我们在第八章中看到的,扩散模型正迅速成为生成建模从业者的首选之一,与 Transformer 并列。在 DALL.E 2 中,一个仅使用解码器的 Transformer 作为先验,通过扩散过程进行训练。

训练和生成过程如图 13-9 所示。再次强调,这是一个简化版本;原始论文包含了扩散模型结构的所有细节。

https://github.com/OpenDocCN/ibooker-dl-zh/raw/master/docs/gen-dl/img/gdl2_1309.png

图 13-9。DALL.E 2 扩散先验训练和生成过程的简化图示

在训练过程中,每个 CLIP 文本和图像嵌入对首先被连接成一个单一向量。然后,图像嵌入在 1,000 个时间步长内被加入噪声,直到它与随机噪声无法区分。然后扩散先验被训练以预测上一个时间步长的去噪图像嵌入。先验在整个过程中都可以访问文本嵌入,因此能够根据这些信息对其预测进行条件化,逐渐将随机噪声转换为预测的 CLIP 图像嵌入。损失函数是去噪步骤中的平均均方误差。

为了生成新的图像嵌入,我们随机采样一个向量,将相关文本嵌入前置,并通过训练好的扩散先验多次传递。

解码器

DALL.E 2 的最后部分是解码器。这是模型的一部分,根据文本提示和先验输出的预测图像嵌入生成最终图像。

解码器的架构和训练过程借鉴了早前 OpenAI 发表的一篇论文,该论文于 2021 年 12 月发表,介绍了一种名为 Guided Language to Image Diffusion for Generation and Editing (GLIDE)的生成模型。⁴

GLIDE 能够从文本提示中生成逼真的图像,这与 DALL.E 2 的工作方式非常相似。不同之处在于 GLIDE 不使用 CLIP 嵌入,而是直接使用原始文本提示进行训练,从头开始训练整个模型,如图 13-10 所示。

https://github.com/OpenDocCN/ibooker-dl-zh/raw/master/docs/gen-dl/img/gdl2_1310.png

图 13-10。DALL.E 2 和 GLIDE 之间的比较—GLIDE 从头开始训练整个生成模型,而 DALL.E 2 利用 CLIP 嵌入将信息从初始文本提示传递下去

让我们先看看 GLIDE 是如何工作的。

GLIDE

GLIDE 作为一个扩散模型进行训练,使用 U-Net 架构作为去噪器,使用 Transformer 架构作为文本编码器。它学会了根据文本提示消除添加到图像中的噪声。最后,一个上采样器被训练以将生成的图像缩放到 1,024×1,024 像素。

GLIDE 从头开始训练 35 亿(B)参数模型—模型的视觉部分(U-Net 和上采样器)有 23 亿参数,Transformer 有 12 亿参数。它在 2.5 亿文本-图像对上进行训练。

扩散过程如图 13-11 所示。使用 Transformer 创建输入文本提示的嵌入,然后用于引导 U-Net 进行去噪过程。我们在第八章中探讨了 U-Net 架构;当图像的整体大小应保持不变时(例如,用于风格转移、去噪等),这是一个完美的模型选择。

https://github.com/OpenDocCN/ibooker-dl-zh/raw/master/docs/gen-dl/img/gdl2_1311.png

图 13-11。GLIDE 扩散过程

DALL.E 2 解码器仍然使用 U-Net 去噪器和 Transformer 文本编码器架构,但另外还有预测的 CLIP 图像嵌入来进行条件。这是 GLIDE 和 DALL.E 2 之间的关键区别,如图 13-12 所示。

https://github.com/OpenDocCN/ibooker-dl-zh/raw/master/docs/gen-dl/img/gdl2_1312.png

图 13-12。DALL.E 2 解码器还额外依赖于先验产生的图像嵌入

与所有扩散模型一样,要生成新图像,我们只需对一些随机噪声进行多次 U-Net 去噪,条件是 Transformer 文本编码和图像嵌入。输出是一个 64×64 像素的图像。

上采样器

解码器的最后部分是上采样器(两个单独的扩散模型)。第一个扩散模型将图像从 64×64 转换为 256×256 像素。第二个再次转换,从 256×256 到 1,024×1,024 像素,如图 13-13 所示。

上采样很有用,因为这意味着我们不必构建处理高维图像的大型上游模型。我们可以在整个过程的最后阶段之前使用小图像,然后应用上采样器。这节省了模型参数,并确保更高效的上游训练过程。

https://github.com/OpenDocCN/ibooker-dl-zh/raw/master/docs/gen-dl/img/gdl2_1313.png

图 13-13。第一个 Upsampler 扩散模型将图像从 64×64 像素转换为 256×256 像素,而第二个将图像从 256×256 像素转换为 1,024×1,024 像素

这就是 DALL.E 2 模型的解释!总之,DALL.E 2 利用预训练的 CLIP 模型立即生成输入提示的文本嵌入。然后使用称为先验的扩散模型将其转换为图像嵌入。最后,它实现了一个 GLIDE 风格的扩散模型,以生成输出图像,条件是预测的图像嵌入和 Transformer 编码的输入提示。

DALL.E 2 的示例

可以在官方网站上找到 DALL.E 2 生成的更多图像示例。该模型能够以令人惊讶的方式将复杂、不同的概念结合在一起,以一种现实、可信的方式,这代表了 AI 和生成建模的重大进步。

在论文中,作者展示了该模型可以用于除文本到图像生成之外的其他目的。其中一个应用是创建给定图像的变化,我们将在下一节中探讨。

图像变化

如前所述,使用 DALL.E 2 解码器生成图像时,我们对由纯随机噪声组成的图像进行采样,然后逐渐减少噪声量,使用依赖于提供的图像嵌入的去噪扩散模型。选择不同的初始随机噪声样本将导致不同的图像。

为了生成给定图像的变化,我们只需要建立其图像嵌入以供解码器使用。我们可以使用原始的 CLIP 图像编码器来获得这个,它专门设计用于将图像转换为其 CLIP 图像嵌入。这个过程如图 13-14 所示。

https://github.com/OpenDocCN/ibooker-dl-zh/raw/master/docs/gen-dl/img/gdl2_1314.png

图 13-14。DALL.E 2 可用于生成给定图像的变化

先验的重要性

作者探索的另一条途径是建立先验的重要性。先验的目的是为解码器提供一个有用的图像表示,利用预训练的 CLIP 模型。然而,这一步骤可能是不必要的——也许我们可以直接将文本嵌入传递给解码器,而不是图像嵌入,或者完全忽略 CLIP 嵌入,只根据文本提示进行条件化。这会影响生成的质量吗?

为了测试这一点,作者尝试了三种不同的方法:

  1. 只将文本提示(以及图像嵌入的零向量)提供给解码器。

  2. 将文本提示和文本嵌入(就像它是图像嵌入一样)提供给解码器。

  3. 将文本提示和图像嵌入(即完整模型)提供给解码器。

示例结果显示在图 13-15 中。我们可以看到,当解码器缺乏图像嵌入信息时,它只能产生文本提示的粗略近似,缺少关键信息,如计算器。将文本嵌入视为图像嵌入稍微好一些,尽管它无法捕捉刺猬和计算器之间的关系。只有带有先验的完整模型才能产生准确反映提示中包含的所有信息的图像。

https://github.com/OpenDocCN/ibooker-dl-zh/raw/master/docs/gen-dl/img/gdl2_1315.png

图 13-15。先验为模型提供了额外的上下文,并帮助解码器产生更准确的生成物(来源:Ramesh 等人,2022

限制

在 DALL.E 2 论文中,作者还强调了模型的几个已知限制。其中两个(属性绑定和文本生成)显示在图 13-16 中。

https://github.com/OpenDocCN/ibooker-dl-zh/raw/master/docs/gen-dl/img/gdl2_1316.png

图 13-16。DALL.E 2 的两个限制在于其将属性绑定到对象和再现文本信息的能力——顶部提示:“一个红色立方体放在一个蓝色立方体上”;底部提示:“一个写着深度学习的标志”(来源:Ramesh 等人,2022

属性绑定是模型理解给定文本提示中单词之间关系的能力,特别是属性如何与对象相关联。例如,提示“一个红色立方体放在一个蓝色立方体上”在视觉上必须与“一个蓝色立方体放在一个红色立方体上”有明显区别。与之前的模型(如 GLIDE)相比,DALL.E 在这方面有些困难,尽管生成的整体质量更好且更多样化。

此外,DALL.E 2 无法准确再现文本——这可能是因为 CLIP 嵌入不捕捉拼写,而只包含文本的更高级表示。这些表示可以部分成功地解码为文本(例如,单个字母大多正确),但没有足够的组合理解来形成完整的单词。

Imagen

在 OpenAI 发布 DALL.E 2 一个多月后,Google Brain 团队发布了他们自己的文本到图像模型称为 Imagen。我们在本章中已经探讨的许多核心主题也与 Imagen 相关:例如,它使用文本编码器和扩散模型解码器。

在接下来的部分中,我们将探讨 Imagen 的整体架构,并将其与 DALL.E 2 进行比较。

架构

Imagen 架构的概述显示在图 13-17 中。

https://github.com/OpenDocCN/ibooker-dl-zh/raw/master/docs/gen-dl/img/gdl2_1317.png

图 13-17。Imagen 架构(来源:Saharia 等人,2022

冻结文本编码器是预训练的 T5-XXL 模型,一个大型编码器-解码器 Transformer。与 CLIP 不同,这个模型仅在文本上进行训练,而不是图像,因此它不是一个多模态模型。然而,作者发现它仍然在 Imagen 中作为文本编码器表现非常出色,并且扩展这个模型对整体性能的影响比扩展扩散模型解码器更大。

与 DALL.E 2 一样,Imagen 的解码扩散模型基于 U-Net 架构,以文本嵌入为条件。对标准 U-Net 架构进行了几项架构改进,以产生作者称之为高效 U-Net的模型。该模型使用更少的内存,收敛更快,并且比以前的 U-Net 模型具有更好的样本质量。

将生成的图像从 64×64 像素升级到 1,024×1,024 像素的上采样器超分辨率模型也是扩散模型,继续使用文本嵌入来指导上采样过程。

DrawBench

Imagen 论文的另一个贡献是DrawBench——一个包含 200 个文本提示的套件,用于文本到图像的评估。文本提示涵盖 11 个类别,如计数(生成指定数量的对象的能力)、描述(生成描述对象的复杂和长文本提示的能力)和文本(生成引用文本的能力)。为了比较两个模型,DrawBench 文本提示通过每个模型,并将输出交给一组人类评分员进行评估,评估涵盖两个指标:

对齐

哪张图更准确地描述了标题?

保真度

哪张图更逼真(看起来更真实)?

DrawBench 人类评估的结果显示在图 13-18 中。

DALL.E 2 和 Imagen 都是在文本到图像生成领域做出了重大贡献的显著模型。虽然 Imagen 在许多 DrawBench 基准测试中表现优于 DALL.E 2,但 DALL.E 2 提供了 Imagen 中没有的额外功能。例如,因为 DALL.E 2 利用了 CLIP(一个多模态文本-图像模型),它能够接受图像作为输入来生成图像嵌入。这意味着 DALL.E 2 能够提供图像编辑和图像变化的功能。这在 Imagen 中是不可能的;文本编码器是一个纯文本模型,因此无法输入图像。

https://github.com/OpenDocCN/ibooker-dl-zh/raw/master/docs/gen-dl/img/gdl2_1318.png

图 13-18. 在对齐和图像保真度方面比较 Imagen 和 DALL.E 2(来源:Saharia 等人,2022

Imagen 的示例

示例 Imagen 生成显示在图 13-19 中。

https://github.com/OpenDocCN/ibooker-dl-zh/raw/master/docs/gen-dl/img/gdl2_1319.png

图 13-19. 示例 Imagen 生成(来源:Saharia 等人,2022

稳定扩散

我们将探讨的最后一个文本到图像扩散模型是稳定扩散,由Stability AI于 2022 年 8 月发布,与慕尼黑路德维希·马克西米利安大学计算机视觉与学习研究小组Runway合作。它与 DALL.E 2 和 Imagen 不同,因为它的代码和模型权重已经通过Hugging Face公开发布。这意味着任何人都可以在自己的硬件上与模型互动,而无需使用专有 API。

架构

稳定扩散和之前讨论的文本到图像模型之间的主要架构差异在于它使用潜在扩散作为其基础生成模型。潜在扩散模型(LDMs)是由 Rombach 等人在 2021 年 12 月提出的,在论文“使用潜在扩散模型进行高分辨率图像合成”中。⁶ 该论文的关键思想是将扩散模型包装在一个自动编码器中,使得扩散过程在图像的潜在空间表示上运行,而不是在图像本身上运行,如图 13-20 所示。

https://github.com/OpenDocCN/ibooker-dl-zh/raw/master/docs/gen-dl/img/gdl2_1320.png

图 13-20. 稳定扩散架构

这一突破意味着去噪 U-Net 模型相对轻量化,与操作完整图像的 U-Net 模型相比。自动编码器处理将图像细节编码到潜在空间并将潜在空间解码回高分辨率图像的繁重工作,使扩散模型纯粹在潜在的概念空间中工作。这为训练过程带来了显著的速度和性能提升。

去噪过程也可以选择由通过文本编码器传递的文本提示引导。稳定扩散的第一个版本使用了 OpenAI 的预训练 CLIP 模型(与 DALL.E 2 中相同),但稳定扩散 2 使用了一个名为OpenCLIP的自定义训练的 CLIP 模型,该模型是从头开始训练的。

稳定扩散的示例

图 13-21 展示了稳定扩散 2.1 的一些示例输出—您可以通过Hugging Face上托管的模型尝试自己的提示。

https://github.com/OpenDocCN/ibooker-dl-zh/raw/master/docs/gen-dl/img/gdl2_1321.png

图 13-21. 稳定扩散 2.1 的示例输出

探索潜在空间

如果您想探索稳定扩散模型的潜在空间,我强烈推荐在 Keras 网站上进行的演练

Flamingo

到目前为止,我们已经看过三种不同类型的文本到图像模型。在本节中,我们将探索一种多模态模型,它可以根据文本和视觉数据流生成文本。Flamingo 是 DeepMind 在 2022 年 4 月发表的一篇论文中介绍的,⁷是一系列视觉语言模型(VLMs),作为预训练的仅视觉和仅语言模型之间的桥梁。

在这一部分,我们将介绍 Flamingo 模型的架构,并将其与我们迄今为止看到的文本到图像模型进行比较。

架构

Flamingo 的整体架构显示在图 13-22 中。为了简洁起见,我们将仅探讨该模型的核心组件—视觉编码器、感知器重采样器和语言模式—以足够的细节来突出使 Flamingo 独特的关键思想。我强烈建议阅读原始研究论文,对模型的每个部分进行彻底审查。

https://github.com/OpenDocCN/ibooker-dl-zh/raw/master/docs/gen-dl/img/gdl2_1322.png

图 13-22. Flamingo 架构(来源:Alayrac 等人,2022

视觉编码器

Flamingo 模型与纯文本到图像模型(如 DALL.E 2 和 Imagen)之间的第一个区别是,Flamingo 可以接受交错的文本和视觉数据的组合。这里,视觉数据包括视频和图像。

视觉编码器的工作是将输入中的视觉数据转换为嵌入向量(类似于 CLIP 中的图像编码器)。Flamingo 中的视觉编码器是一个预训练的无归一化 ResNet(NFNet),由 Brock 等人在 2021 年介绍⁸—具体来说,是一个 NFNet-F6(NFNet 模型从 F0 到 F6,大小和功率逐渐增加)。这是 CLIP 图像编码器和 Flamingo 视觉编码器之间的一个关键区别:前者使用 ViT 架构,而后者使用 ResNet 架构。

视觉编码器是使用与 CLIP 论文中引入的对比目标相同的图像-文本对进行训练的。训练后,权重被冻结,以便对 Flamingo 模型的任何进一步训练不会影响视觉编码器的权重。

视觉编码器的输出是一个特征的 2D 网格,然后在传递给 Perceiver Resampler 之前被展平为 1D 向量。视频通过每秒采样 1 帧处理,并将每个快照独立通过视觉编码器传递以产生几个特征网格;然后在展平特征之前添加了学习的时间编码,并将结果连接成一个单一向量。

Perceiver Resampler

传统编码器 Transformer(例如 BERT)中的内存需求随着输入序列长度呈二次方增长,这就是为什么输入序列通常被限制在一定数量的标记上(例如 BERT 中的 512 个)。然而,视觉编码器的输出是一个长度可变的向量(由于可变的输入图像分辨率和可变的视频帧数),因此可能非常长。

Perceiver 架构专门设计用于高效处理长输入序列。它不是对整个输入序列执行自注意力,而是使用固定长度的潜在向量,并仅将输入序列用于交叉注意力。具体来说,在 Flamingo Perceiver Resampler 中,keyvalue是输入序列和潜在向量的串联,而query仅是潜在向量本身。图 13-23 显示了视频数据的视觉编码器和 Perceiver Resampler 过程的图示。

图 13-23。应用于视频输入的 Perceiver Resampler(来源:Alayrac 等人,2022

Perceiver Resampler 的输出是一个固定长度的潜在向量,传递给语言模型。

语言模型

语言模型由几个堆叠的块组成,以解码器 Transformer 的风格输出预测的文本延续。事实上,语言模型的大部分来自一个名为Chinchilla的预训练 DeepMind 模型。2022 年 3 月发表的 Chinchilla 论文⁹展示了一个设计得比同行要小得多的语言模型(例如,Chinchilla 的参数为 70B,而 GPT-3 的参数为 170B),同时在训练中使用了更多标记。作者表明,该模型在一系列任务上优于更大的模型,突出了在训练更大的模型和在训练期间使用更多标记之间优化权衡的重要性。

Flamingo 论文的一个关键贡献是展示了 Chinchilla 如何适应与插入语言数据(Y)一起工作的额外视觉数据(X)。让我们首先探讨语言和视觉输入是如何结合以产生语言模型的输入的(图 13-24)。

首先,文本通过用<image>标记替换视觉数据(例如图像),并使用<EOC>(块结束)标记将文本分成。每个块最多包含一个图像,该图像始终位于块的开头,即假定后续文本仅与该图像相关。序列的开头也用<BOS>(句子开头)标记。

接下来,序列被标记化,每个标记被赋予一个索引(phi),对应于前面的图像索引(如果在块中没有前置图像,则为0)。这样,文本标记(Y)可以被强制只与对应于其特定块的图像标记(X)进行交叉关注,通过掩蔽。例如,在图 13-24 中,第一个块不包含图像,因此 Perceiver Resampler 的所有图像标记都被掩盖。第二个块包含图像 1,因此这些标记可以与图像 1 的图像标记进行交互。同样,最后一个块包含图像 2,因此这些标记可以与图像 2 的图像标记进行交互。

https://github.com/OpenDocCN/ibooker-dl-zh/raw/master/docs/gen-dl/img/gdl2_1324.png

图 13-24。掩蔽的交叉关注(XATTN),结合视觉和文本数据——浅蓝色条目被掩盖,深蓝色条目未被掩盖(来源:Alayrac 等人,2022)

现在我们可以看到这个掩蔽的交叉关注组件如何融入语言模型的整体架构中(图 13-25)。

蓝色的 LM 层组件是来自 Chinchilla 的冻结层,这些层在训练过程中不会更新。紫色的GATED XATTN-DENSE层作为 Flamingo 的一部分进行训练,包括混合语言和视觉信息的掩蔽交叉关注组件,以及随后的前馈(密集)层。

该层是门控的,因为它通过两个不同的 tanh 门传递来自交叉关注和前馈组件的输出,这两个门都初始化为零。因此,当网络初始化时,GATED XATTN-DENSE层没有贡献——语言信息直接通过。alpha门控参数由网络学习,随着训练的进行逐渐混合视觉数据的信息。

https://github.com/OpenDocCN/ibooker-dl-zh/raw/master/docs/gen-dl/img/gdl2_1325.png

图 13-25。Flamingo 语言模型块,包括来自 Chinchilla 的冻结语言模型层和一个GATED XATTN-DENSE层(来源:Alayrac 等人,2022

来自 Flamingo 的例子

Flamingo 可以用于各种目的,包括图像和视频理解,对话提示和视觉对话。在图 13-26 中,我们可以看到 Flamingo 的一些示例。

https://github.com/OpenDocCN/ibooker-dl-zh/raw/master/docs/gen-dl/img/gdl2_1326.png

图 13-26。从 80B 参数 Flamingo 模型获得的输入和输出示例(来源:Alayrac 等人,2022

请注意,在每个示例中,Flamingo 以真正的多模式风格混合文本和图像信息。第一个示例使用图像代替文字,并能够建议一个适当的书籍来继续提示。第二个示例展示了视频中的帧,Flamingo 正确地识别了行动的后果。最后三个示例都展示了 Flamingo 如何交互使用,通过对话提供额外信息或通过进一步提问进行探究。

看到一台机器能够回答如此广泛的模态和输入任务范围内的复杂问题,真是令人惊讶。在论文中,作者们量化了 Flamingo 在一组基准任务上的能力,并发现在许多基准测试中,Flamingo 能够超越专门针对特定任务的模型的性能。这突显了大型多模型可以迅速适应各种任务,并为开发不仅仅局限于单一任务的 AI 代理铺平了道路,而是真正可以在推理时由用户引导的通用代理。

总结

在本章中,我们探讨了四种不同的最先进多模型:DALL.E 2,Imagen,Stable Diffusion 和 Flamingo。

DALL.E 2 是来自 OpenAI 的大规模文本到图像模型,可以根据文本提示生成各种风格的逼真图像。它通过将预训练模型(例如 CLIP)与先前作品(GLIDE)中的扩散模型架构相结合来工作。它还具有额外的功能,例如能够通过文本提示编辑图像并提供给定图像的变体。尽管它存在一些限制,例如不一致的文本渲染和属性绑定,但 DALL.E 2 是一个非常强大的 AI 模型,已经帮助推动生成建模领域进入一个新时代。

另一个超越先前基准的模型是 Google Brain 的 Imagen。这个模型与 DALL.E 2 有许多相似之处,例如文本编码器和扩散模型解码器。两个模型之间的一个关键区别是 Imagen 文本编码器是在纯文本数据上训练的,而 DALL.E 2 文本编码器的训练过程涉及图像数据(通过对比 CLIP 学习目标)。作者表明,这种方法在各种任务中取得了最先进的性能,通过他们的 DrawBench 评估套件。

稳定扩散是来自 Stability AI、CompVis 和 Runway 的开源产品。这是一个文本到图像的模型,其模型权重和代码都是免费提供的,因此您可以在自己的硬件上运行它。稳定扩散特别快速和轻量,因为它使用了一个在自动编码器的潜在空间上运行的潜在扩散模型,而不是图像本身。

最后,DeepMind 的 Flamingo 是一种视觉语言模型,即它接受交错的文本和视觉数据流(图像和视频),并能够继续通过附加文本提示的方式进行文本输出,类似解码器 Transformer 的风格。其关键贡献在于展示了如何通过视觉编码器和感知器重采样器将视觉信息输入到 Transformer 中,将视觉输入特征编码为少量的视觉标记。语言模型本身是 DeepMind 早期 Chinchilla 模型的扩展,经过调整以融入视觉信息。

所有这四个都是多模态模型强大性能的显著例子。未来,生成建模很可能会变得更加多模态化,AI 模型将能够通过交互式语言提示轻松跨越模态和任务。

阿迪蒂亚·拉梅什等人,“零样本文本到图像生成”,2021 年 2 月 24 日,https://arxiv.org/abs/2102.12092。

阿迪蒂亚·拉梅什等人,“具有 CLIP 潜在特征的分层文本条件图像生成”,2022 年 4 月 13 日,https://arxiv.org/abs/2204.06125。

亚历克斯·拉德福德等人,“从自然语言监督中学习可转移的视觉模型”,2021 年 2 月 26 日,https://arxiv.org/abs/2103.00020。

亚历克斯·尼科尔等人,“GLIDE: 朝向逼真图像生成和编辑的文本引导扩散模型”,2021 年 12 月 20 日,https://arxiv.org/abs/2112.10741。

奇特万·萨哈里亚等人,“具有深度语言理解的逼真文本到图像扩散模型”,2022 年 5 月 23 日,https://arxiv.org/abs/2205.11487。

罗宾·隆巴赫等人,“使用潜在扩散模型进行高分辨率图像合成”,2021 年 12 月 20 日,https://arxiv.org/abs/2112.10752。

让-巴蒂斯特·阿拉拉克等人,“Flamingo: 一种用于少样本学习的视觉语言模型”,2022 年 4 月 29 日,https://arxiv.org/abs/2204.14198。

⁸ Andrew Brock 等人,“无归一化的高性能大规模图像识别”,2021 年 2 月 11 日,https://arxiv.org/abs/2102.06171

⁹ Jordan Hoffmann 等人,“训练计算优化的大型语言模型”,2022 年 3 月 29 日,https://arxiv.org/abs/2203.15556v1

Logo

AtomGit 是由开放原子开源基金会联合 CSDN 等生态伙伴共同推出的新一代开源与人工智能协作平台。平台坚持“开放、中立、公益”的理念,把代码托管、模型共享、数据集托管、智能体开发体验和算力服务整合在一起,为开发者提供从开发、训练到部署的一站式体验。

更多推荐