Python 与 TensorFlow2 生成式 AI(四)(1)https://developer.aliyun.com/article/1512052
为了最佳的内存利用,我们可以利用tf.data
API 将我们的数据切片为可管理的片段。我们将我们的输入序列限制在 100 个字符长度,并且这个 API 帮助我们创建这个数据集的连续片段。这在下面的代码片段中展示:
seq_length = 100 examples_per_epoch = len(text)//(seq_length+1) # Create training examples / targets char_dataset = tf.data.Dataset.from_tensor_slices(text_as_int) for i in char_dataset.take(10): print(idx2char[i.numpy()])
B O O K O N E ... split_input_target to prepare the target output as a one-position-shifted transformation of the input itself. In this way, we will be able to generate consecutive (input, output) training pairs using just a single shift in position:
def split_input_target(chunk): """ Utility which takes a chunk of input text and target as one position shifted form of input chunk. Parameters: chunk: input list of words Returns: Tuple-> input_text(i.e. chunk minus last word),target_text(input chunk minus the first word) """ input_text = chunk[:-1] target_text = chunk[1:] return input_text, target_text dataset = sequences.map(split_input_target) for input_example, target_example in dataset.take(1): print ('Input data: ', repr(''.join(idx2char[input_example.numpy()]))) print ('Target data:', repr(''.join(idx2char[target_example.numpy()])))
Input data: '\r\nBOOK ONE: 1805\r\n\r\n\r\n\r\n\r\n\r\nCHAPTER I\r\n\r\n"Well, Prince, so Genoa and Lucca are now just family estat' Target data: '\nBOOK ONE: 1805\r\n\r\n\r\n\r\n\r\n\r\nCHAPTER I\r\n\r\n"Well, Prince, so Genoa and Lucca are now just family estate' build_model that prepares a single layer LSTM-based language model:
def build_model(vocab_size, embedding_dim, rnn_units, batch_size): """ Utility to create a model object. Parameters: vocab_size: number of unique characters embedding_dim: size of embedding vector. This typically in powers of 2, i.e. 64, 128, 256 and so on rnn_units: number of GRU units to be used batch_size: batch size for training the model Returns: tf.keras model object """ model = tf.keras.Sequential([ tf.keras.layers.Embedding(vocab_size, embedding_dim, batch_input_shape=[batch_size, None]), tf.keras.layers.LSTM(rnn_units, return_sequences=True, stateful=True, recurrent_initializer='glorot_uniform'), tf.keras.layers.Dense(vocab_size) ]) return model # Length of the vocabulary in chars vocab_size = len(vocab) # The embedding dimension embedding_dim = 256 # Number of RNN units rnn_units = 1024 model = build_model( vocab_size = len(vocab), embedding_dim=embedding_dim, rnn_units=rnn_units, batch_size=BATCH_SIZE)
我们已经创建了模型对象。从代码片段中可以看出,模型是一堆嵌入层、LSTM 层和稠密层。嵌入层有助于将原始文本转换为向量形式,然后是 LSTM 和稠密层,它们学习上下文和语言语义。
下一组步骤涉及定义损失函数和编译模型。我们将使用稀疏分类交叉熵作为我们的损失函数。下面的代码片段定义了损失函数和编译模型;我们使用 Adam 优化器进行最小化:
def loss(labels, logits): return tf.keras.losses.sparse_categorical_crossentropy(labels, logits, from_logits=True) model.compile(optimizer='adam', loss=loss)
由于我们使用 TensorFlow 和高级 Keras API,训练模型就像调用fit
函数一样简单。我们只训练了 10 个时代,使用ModelCheckpoint
回调来保存模型的权重,如下面的代码片段所示:
# Directory where the checkpoints will be saved checkpoint_dir = r'data/training_checkpoints' # Name of the checkpoint files checkpoint_prefix = os.path.join(checkpoint_dir, "ckpt_{epoch}") checkpoint_callback=tf.keras.callbacks.ModelCheckpoint( filepath=checkpoint_prefix, save_weights_only=True) EPOCHS = 10 history = model.fit(dataset, epochs=EPOCHS, callbacks=[checkpoint_callback])
Epoch 1/10 254/254 [==============================] - 38s 149ms/step - loss: 2.4388 Epoch 2/10 254/254 [==============================] - 36s 142ms/step - loss: 1.7407 . . . Epoch 10/10 254/254 [==============================] - 37s 145ms/step - loss: 1.1530
恭喜,你已经训练了你的第一个语言模型。现在,我们将使用它来生成一些假文本。在我们做到这一点之前,我们需要了解如何解码我们模型生成的输出。
解码策略
早些时候,我们将所有文本数据转换为适合训练和推理的向量形式。现在我们有了一个训练好的模型,下一步是输入一些上下文词,以及生成下一个词作为输出。这个输出生成步骤正式被称为解码步骤。它被称为"解码",因为模型输出一个向量,必须经过处理才能得到实际的词作为输出。有一些不同的解码技术;让我们简要讨论一下流行的:贪婪解码、束搜索和抽样。
贪婪解码
这是最简单和最快的解码策略。正如其名,贪婪解码是一种在每次预测步骤中选择最高概率项的方法。
尽管这样快速高效,但贪婪会在生成文本时产生一些问题。通过仅关注最高概率的输出,模型可能会生成不一致或不连贯的输出。在字符语言模型的情况下,这甚至可能导致非词典词的输出。贪婪解码还限制了输出的差异性,这也可能导致重复的内容。
波束搜索
波束搜索是广泛使用的贪婪解码的替代方法。该解码策略不是选择最高概率的术语,而是在每个时间步长跟踪n个可能的输出。下图说明了波束搜索解码策略。它展示了从步骤 0 开始形成的多个波束,创建了一个树状结构:
图 9.7:基于波束搜索的解码策略
如图 9.7所示,波束搜索策略通过在每个时间步长跟踪n个预测,并最终选择具有总体最高概率的路径来工作,在图中用粗线突出显示。让我们逐步分析在上述图中使用的波束搜索解码示例,假设波束大小为 2。
在时间步骤t[0]:
- 模型预测以下三个单词(带概率)为(the, 0.3),(when, 0.6)和(and, 0.1)。
- 在贪婪解码的情况下,我们将选择"when",因为它的概率最高。
- 在这种情况下,由于我们的波束大小为 2,我们将跟踪前两个输出。
在时间步骤t[2]:
- 我们重复相同的步骤;即,我们跟踪两个波束中的前两个输出。
- 通过沿着分支计算分支的概率,计算波束分数如下:
- (when, 0.6) –> (the, 0.4) = 0.60.4 = 0.24*
- (the, 0.3) –> (war, 0.9) = 0.30.9 = 0.27*
根据上述讨论,生成的最终输出是"It was July, 1805 the war"。与"它是 1805 年 7 月,当时的战争"这样的输出相比,它的最终概率为 0.27,而"它是 1805 年 7 月,当时的"这样的输出的分数为 0.24,这是贪婪解码给我们的结果。
这种解码策略大大改进了我们在前一节讨论的天真贪婪解码策略。这在某种程度上为语言模型提供了额外的能力,以选择最佳的可能结果。
抽样
抽样是一个过程,在此过程中从更大的总体中选择了预定义数量的观察结果。作为对贪婪解码的改进,可以采用随机抽样解码方法来解决变化/重复问题。一般来说,基于抽样的解码策略有助于根据迄今为止的上下文选择下一个词,即:
在这里,w[t]是在时间步t上的输出,已经根据在时间步t-1之前生成的单词进行了条件化。延续我们之前解码策略的示例,以下图像突出显示了基于采样的解码策略将如何选择下一个单词:
图 9.8:基于采样的解码策略
正如图 9.8所示,该方法在每个时间步从给定的条件概率中随机选择一个单词。在我们的示例中,模型最终通过随机选择in,然后选择Paris作为随后的输出。如果你仔细观察,在时间步t[1],模型最终选择了概率最低的单词。这带来了与人类使用语言方式相关的非常必要的随机性。 Holtzman 等人在其题为神经文本退化的奇特案例的作品⁵中通过陈述人类并不总是简单地使用概率最高的单词来提出了这个确切的论点。他们提出了不同的场景和示例,以突显语言是单词的随机选择,而不是由波束搜索或贪婪解码形成的典型高概率曲线。
这将引入一个重要的参数称为温度。
温度
正如我们之前讨论的,基于采样的解码策略有助于改善输出的随机性。然而,过多的随机性也不理想,因为它可能导致无意义和不连贯的结果。为了控制这种随机性的程度,我们可以引入一个可调参数称为温度。该参数有助于增加高概率项的概率,同时减少低概率项的概率,从而产生更尖锐的分布。高温度导致更多的随机性,而较低的温度则带来可预测性。值得注意的是,这可以应用于任何解码策略。
Top-k 采样
波束搜索和基于采样的解码策略都有各自的优缺点。Top-k 采样是一种混合策略,它兼具两者的优点,提供了更复杂的解码方法。简单来说,在每个时间步,我们不是选择一个随机单词,而是跟踪前 k 个条目(类似于波束搜索),并在它们之间重新分配概率。这给了模型生成连贯样本的额外机会。
实践操作:解码策略
现在,我们对一些最广泛使用的解码策略有了足够的理解,是时候看看它们的实际效果了。
第一步是准备一个实用函数generate_text
,根据给定的解码策略生成下一个单词,如下面的代码片段所示:
def generate_text(model, mode='greedy', context_string='Hello', num_generate=1000, temperature=1.0): """ Utility to generate text given a trained model and context Parameters: model: tf.keras object trained on a sufficiently sized corpus mode: decoding mode. Default is greedy. Other mode is sampling (set temperature) context_string: input string which acts as context for the model num_generate: number of characters to be generated temperature: parameter to control randomness of outputs Returns: string : context_string+text_generated """ # vectorizing: convert context string into string indices input_eval = [char2idx[s] for s in context_string] input_eval = tf.expand_dims(input_eval, 0) # String for generated characters text_generated = [] model.reset_states() # Loop till required number of characters are generated for i in range(num_generate): predictions = model(input_eval) predictions = tf.squeeze(predictions, 0) if mode == 'greedy': predicted_id = np.argmax(predictions[0]) elif mode == 'sampling': # temperature helps control the character # returned by the model. predictions = predictions / temperature # Sampling over a categorical distribution predicted_id = tf.random.categorical(predictions, num_samples=1)[-1,0].numpy() # predicted character acts as input for next step input_eval = tf.expand_dims([predicted_id], 0) text_generated.append(idx2char[predicted_id]) return (context_string + ''.join(text_generated))
代码首先将原始输入文本转换为整数索引。然后我们使用模型进行预测,根据所选择的模式进行操作,贪婪或采样。我们已经从前面的练习中训练了一个字符语言模型,以及一个辅助工具,帮助我们根据所选择的解码策略生成下一个词。我们在以下片段中使用了这两者来理解使用不同策略生成的不同输出:
# greedy decoding print(generate_text(model, context_string=u"It was in July, 1805 ",num_generate=50,mode="greedy")) # sampled decoding with different temperature settings print(generate_text(model, context_string=u"It was in July, 1805 ",num_generate=50, mode="sampling", temperature=0.3)) print(generate_text(model, context_string=u"It was in July, 1805 ",num_generate=50, mode="sampling",temperature=0.9))
使用相同种子与不同解码策略的结果在以下屏幕截图中展示:
图 9.9:基于不同解码策略的文本生成。粗体文本是种子文本,后面是模型生成的输出文本。
这个输出突显了我们迄今讨论的所有解码策略的一些问题以及显著特征。我们可以看到温度的增加如何使模型更具表现力。我们还可以观察到模型已经学会了配对引号甚至使用标点符号。模型似乎还学会了如何使用大写字母。温度参数的增加表现力是以牺牲模型稳定性为代价的。因此,通常在表现力和稳定性之间存在权衡。
这就是我们生成文本的第一种方法的总结;我们利用了 RNNs(特别是 LSTMs)来使用不同的解码策略生成文本。接下来,我们将看一些 LSTM 模型的变体,以及卷积。
LSTM 变体和文本的卷积
当处理序列数据集时,RNNs 非常有用。我们在前一节中看到,一个简单的模型有效地学会了根据训练数据集学到的内容生成文本。
多年来,我们在对 RNNs 建模和使用的方式方面取得了许多改进。在本节中,我们将讨论前一节中讨论的单层 LSTM 网络的两种广泛使用的变体:堆叠和双向 LSTMs。
堆叠的 LSTMs
我们非常清楚神经网络的深度在处理计算机视觉任务时如何帮助其学习复杂和抽象的概念。同样,一个堆叠的 LSTM 架构,它有多个 LSTMs 层依次堆叠在一起,已经被证明能够带来相当大的改进。堆叠的 LSTMs 首次由格雷夫斯等人在他们的工作中提出使用深度循环神经网络进行语音识别⁶。他们强调了深度-多层 RNNs-与每层单位数目相比,对性能的影响更大。
尽管没有理论证明可以解释这种性能提升,经验结果帮助我们理解影响。这些增强可以归因于模型学习复杂特征甚至输入的抽象表示的能力。由于 LSTM 和 RNNs 一般具有时间成分,更深的网络学习在不同时间尺度上运行的能力。⁷
build_model function to do just that:
def build_model(vocab_size, embedding_dim, rnn_units, batch_size,is_bidirectional=False): """ Utility to create a model object. Parameters: vocab_size: number of unique characters embedding_dim: size of embedding vector. This typically in powers of 2, i.e. 64, 128, 256 and so on rnn_units: number of LSTM units to be used batch_size: batch size for training the model Returns: tf.keras model object """ model = tf.keras.Sequential() model.add(tf.keras.layers.Embedding(vocab_size, embedding_dim, batch_input_shape=[batch_size, None])) if is_bidirectional: model.add(tf.keras.layers.Bidirectional(tf.keras.layers.LSTM(rnn_units, return_sequences=True, stateful=True, recurrent_initializer='glorot_uniform'))) else: model.add(tf.keras.layers.LSTM(rnn_units, return_sequences=True, stateful=True, recurrent_initializer='glorot_uniform')) model.add(tf.keras.layers.LSTM(rnn_units, return_sequences=True, stateful=True, recurrent_initializer='glorot_uniform')) model.add(tf.keras.layers.Dense(vocab_size)) return model
数据集、训练循环,甚至推理工具保持不变。为了简洁起见,我们跳过了再次展示这些代码摘录。我们不久将讨论我们在这里引入的双向参数。
现在,让我们看看这个更深的基于 LSTM 的语言模型的结果是如何的。下面的截图展示了这个模型的结果:
图 9.10: 基于不同解码策略的堆叠 LSTM 的语言模型的文本生成
我们可以清楚地看到,生成的文本如何更好地捕捉到书中的书写风格、大写、标点等方面,比图 9.9中显示的结果更好。这突出了我们讨论过的关于更深的 RNN 结构的一些优势。
双向 LSTM
现在非常广泛使用的第二个变体是双向 LSTM。我们已经讨论过 LSTMs 和 RNNs 生成它们的输出通过利用之前的时间步。当涉及到文本或任何序列数据时,这意味着 LSTM 能够利用过去的上下文来预测未来的时间步。虽然这是一个非常有用的特性,但这并不是我们可以达到的最好水平。让我们通过一个例子来说明为什么这是一个限制:
图 9.11: 查看给定单词的过去和未来上下文窗口
从这个例子可以明显看出,不看目标单词“Teddy”右侧的内容,模型无法正确地获取上下文。为了处理这种情况,引入了双向 LSTM。它们的背后的想法非常简单和直接。双向 LSTM(或者 biLSTM)是两个 LSTM 层同时工作的组合。第一个是通常的前向 LSTM,它按照原始顺序接受输入序列。第二个被称为后向 LSTM,它接受倒置的一份复制作为输入序列。下面的图表展示了一个典型的双向 LSTM 设置:
图 9.12: 双向 LSTM 设置
如图 9.12所示,前向和后向 LSTM 协作处理输入序列的原始和反转副本。由于在任何给定的时间步上有两个 LSTM 单元在不同的上下文上工作,我们需要一种定义输出的方式,以供网络中的下游层使用。输出可以通过求和、乘积、连接,甚至隐藏状态的平均值来组合。不同的深度学习框架可能会设置不同的默认值,但最常用的方法是双向 LSTM 输出的连接。请注意,与双向 LSTM 类似,我们可以使用双向 RNN 或甚至双向 GRU(门控循环单元)。
与普通 LSTM 相比,双向 LSTM 设置具有优势,因为它可以查看未来的上下文。当无法窥视未来时,这种优势也变成了限制。对于当前的文本生成用例,我们利用双向 LSTM 在编码器-解码器类型的架构中。我们利用双向 LSTM 来学习更好地嵌入输入,但解码阶段(我们使用这些嵌入去猜测下一个单词)只使用普通的 LSTM。与早期的实践一样,我们可以使用相同的一套工具来训练这个网络。我们把这留给你来练习;现在我们将继续讨论卷积。
卷积和文本
RNN 在序列对序列任务(例如文本生成)方面非常强大和表现出色。但它们也面临一些挑战:
- 当上下文窗口非常宽时,RNN 会受到消失梯度的影响。虽然 LSTM 和 GRU 在一定程度上克服了这一问题,但是与我们在正常用法中看到的非局部交互的典型情况相比,上下文窗口仍然非常小。
- RNN 的反复出现使其变得顺序且最终在训练和推断时变得缓慢。
- 我们在上一节介绍的架构试图将整个输入语境(或种子文本)编码成单个向量,然后由解码器用于生成下一组单词。这在种子/语境非常长时会受到限制,正如 RNN 更多地关注上下文中最后一组输入的事实一样。
- 与其他类型的神经网络架构相比,RNN 具有更大的内存占用空间;也就是说,在实现过程中需要更多的参数和更多的内存。
另一方面,我们有卷积网络,在计算机视觉领域经过了战斗的检验。最先进的架构利用 CNN 提取特征,在不同的视觉任务上表现良好。CNN 的成功使研究人员开始探索它们在自然语言处理任务中的应用。
使用 CNN 处理文本的主要思想是首先尝试创建一组单词的向量表示,而不是单个单词。更正式地说,这个想法是在给定句子中生成每个单词子序列的向量表示。
让我们考虑一个示例句子,“流感爆发迫使学校关闭”。首先的目标将是将这个句子分解为所有可能的子序列,比如“流感爆发迫使”,“爆发迫使学校”,…,“学校关闭”,然后为每个子序列生成一个向量表示。虽然这样的子序列可能或可能不带有太多含义,但它们为我们提供了一种理解不同上下文中的单词以及它们的用法的方式。由于我们已经了解如何准备单词的密集向量表示(参见Distributed representation一节),让我们在此基础上了解如何利用 CNNs。
继续前面的例子,图 9.13(A) 描述了每个单词的向量形式。为了方便理解,向量仅为 4 维:
图 9.13:(A)示例句子中每个单词的向量表示(1x4)。 (B)两个大小为 3 的内核/过滤器。 (C)取 Hadamard 乘积后的每个内核的维度为 1x2 的短语向量,然后进行步幅为 1 的求和。
两个大小为 3 的内核分别显示在图 9.13(B) 中。在文本/NLP 用例中,内核的选择为单词向量维度的宽度。大小为 3 表示每个内核关注的上下文窗口。由于内核宽度与单词向量宽度相同,我们将内核沿着句子中的单词移动。这种尺寸和单向移动的约束是这些卷积滤波器被称为 1-D 卷积的原因。输出短语向量显示在图 9.13(C) 中。
类似于用于计算机视觉用例的深度卷积神经网络,上述设置也使我们能够为自然语言处理用例堆叠 1-D 卷积层。更大的深度不仅允许模型捕获更复杂的表示,还允许捕获更广泛的上下文窗口(这类似于增加视觉模型的感受野随深度增加)。
使用 CNNs 用于自然语言处理用例还提高了计算速度,同时减少了训练这种网络所需的内存和时间。事实上,这些都是以下研究中探索的一些使用 1-D CNNs 的自然语言处理任务的优势:
- 自然语言处理(几乎)从零开始,Collobert 等⁸
- 用于文本分类的字符级卷积网络,Zhang 等⁹
- 用于句子分类的卷积神经网络,Kim¹⁰
- 用于文本分类的循环卷积神经网络,Lai 和 Xu 等¹¹
到目前为止,我们已经讨论了 CNNs 如何用于提取特征并为自然语言处理用例捕获更大的上下文。与语言相关的任务,特别是文本生成,与之相关的有一定的时间方面。因此,下一个显而易见的问题是,我们是否可以利用 CNNs 来理解时间特征,就像 RNNs 一样?
研究人员已经探索使用 CNN 进行时间或序贯处理已经有一段时间了。虽然我们讨论了 CNN 如何是捕捉给定单词上下文的好选择,但这对于某些用例来说存在问题。例如,像语言建模/文本生成这样的任务需要模型理解上下文,但只需来自一侧。简单来说,语言模型通过查看已处理的单词(过去上下文)来生成未来单词。但 CNN 也可以覆盖未来的时间步。
从 NLP 领域稍微偏离,Van den Oord 等人关于 PixelCNNs¹²和 WaveNets¹³的作品对于理解 CNN 在时间设置中的应用特别重要。他们提出了因果卷积的概念,以确保 CNN 只利用过去而不是未来上下文。这一概念在下图中得到了突出:
图 9.14:基于 Van den Oord 等人¹³的 CNN 的因果填充。 图 2
因果卷积确保模型在任何给定的时间步t都会进行p(x[t+1] | x[1:][t])类型的预测,并且不依赖于未来的时间步x[t+1],x[t+2] … x[t+][T],正如 图 9.14所示。在训练过程中,可以并行地为所有时间步进行条件预测;但生成/推理步骤是顺序的;每个时间步的输出都会反馈给模型以进行下一个时间步的预测。
由于这个设置没有任何循环连接,模型训练速度更快,哪怕是更长序列也一样。因果卷积的设置最初来源于图像和音频生成用例,但也已扩展到 NLP 用例。WaveNet 论文的作者此外利用了一个称为dilated convolutions的概念,以提供更大的感知域,而不需要非常深的架构。
这种利用 CNN 捕捉和使用时间组件的想法已经为进一步探索打开了大门。
在我们进入下一章涉及更复杂的注意力和变换器架构之前,有必要强调一些先前的重要作品:
- Kalchbrenner 等人¹⁴的时间内神经机器翻译介绍了基于编码器-解码器架构的 ByteNet 神经翻译模型。总体设置利用 1-D 因果卷积,以及扩张核,以在英德翻译任务上提供最先进的性能。
- Dauphin 等人在他们名为具有门控卷积的语言建模的作品中提出了一个基于门控卷积的语言模型。(15) 他们观察到他们的门控卷积提供了显着的训练加速和更低的内存占用。
- Gehring 等人¹⁶和 Lea 等人¹⁷的作品进一步探讨了这些想法,并提供了更好的结果。
- 感兴趣的读者还可以探索 Bai 等人的一篇题为基于序列建模的通用卷积和循环网络的实证评估的论文¹⁸。该论文为基于循环神经网络(RNN)和卷积神经网络(CNN)的架构提供了一个很好的概述,用于序列建模任务。
这结束了我们对语言建模旧架构的基本要素的讨论。
总结
祝贺你完成了涉及大量概念的复杂章节。在本章中,我们涵盖了处理文本数据以进行文本生成任务的各种概念。我们首先了解了不同的文本表示模型。我们涵盖了大多数广泛使用的表示模型,从词袋到 word2vec 甚至 FastText。
本章的下一部分重点讨论了发展对基于循环神经网络(RNN)的文本生成模型的理解。我们简要讨论了什么构成了语言模型以及我们如何为这样的任务准备数据集。之后我们训练了一个基于字符的语言模型,生成了一些合成文本样本。我们涉及了不同的解码策略,并用它们来理解我们 RNN 模型的不同输出。我们还深入探讨了一些变种,比如堆叠 LSTM 和双向 LSTM 的语言模型。最后,我们讨论了在 NLP 领域使用卷积网络的情况。
在下一章中,我们将重点关注 NLP 领域一些最新和最强大架构的基本构件,包括注意力和Transformers。
参考文献
- Mikolov, T., Chen, K., Corrado, G., & Dean, J. (2013). 词向量的高效估计. arXiv.
arxiv.org/abs/1301.3781
- Rumelhart, D.E., & McClelland, J.L. (1987). 分布表示, in 并行分布式处理: 认知微结构探索:基础, pp.77-109. MIT Press.
web.stanford.edu/~jlmcc/papers/PDP/Chapter3.pdf
- Pennington, J., Socher, R., & Manning, C.D. (2014). GloVe: 全局词向量表示. Proceedings of the 2014 Conference on Empirical Methods in Natural Language Processing (EMNLP).
nlp.stanford.edu/pubs/glove.pdf
- Bojanowski, P., Grave, E., Joulin, A., & Mikolov, T. (2017). 使用子词信息丰富词向量. arXiv.
arxiv.org/abs/1607.04606
- Holtzman, A., Buys, J., Du, L., Forbes, M., & Choi, Y. (2019). 神经文本退化的好奇案例. arXiv.
arxiv.org/abs/1904.09751
- Graves, A., Mohamed, A., & Hinton, G. (2013). 深度循环神经网络语音识别. arXiv.
arxiv.org/abs/1303.5778
- Pascanu, R., Gulcehre, C., Cho, K., & Bengio, Y. (2013). 如何构建深度循环神经网络. arXiv.
arxiv.org/abs/1312.6026
- Collobert, R., Weston, J., Karlen, M., Kavukcuoglu, K., & Kuksa, P. (2011). 几乎从零开始的自然语言处理. arXiv.
arxiv.org/abs/1103.0398
- Zhang, X., Zhao, J., & LeCun, Y. (2015). 用于文本分类的字符级卷积网络. arXiv.
arxiv.org/abs/1509.01626
- Kim, Y. (2014). 用于句子分类的卷积神经网络. arXiv.
arxiv.org/abs/1408.5882
- Lai, S., Xu, L., Liu, K., & Zhao, J. (2015). 用于文本分类的循环卷积神经网络. 第二十九届 AAAI 人工智能大会论文集。
zhengyima.com/my/pdfs/Textrcnn.pdf
- van den Oord, A., Kalchbrenner, N., Vinyals, O., Espeholt, L., Graves, A., & Kavukcuoglu, K. (2016). 具有 PixelCNN 解码器的条件图像生成. arXiv.
arxiv.org/abs/1606.05328
- van den Oord, A., Dieleman, S., Simonyan, K., Vinyals, O., Graves, A., Kalchbrenner, N., Senior, A., Kavukcuoglu, K. (2016). WaveNet:用于原始音频的生成模型.
arxiv.org/abs/1609.03499
- Kalchbrenner, N., Espeholt, L., Simonyan, K., van den Oord, A., Graves, A., & Kavukcuoglu, K. (2016). 线性时间内的神经机器翻译. arXiv.
arxiv.org/abs/1609.03499
- Dauphin, Y.N., Fan, A., Auli, M., & Grangier, D. (2016). 带门卷积网络的语言建模. arXiv.
arxiv.org/abs/1612.08083
- Gehring, J., Auli, M., Grangier, D., Yarats, D., & Dauphin, Y.N. (2017). 卷积序列到序列学习. arXiv.
arxiv.org/abs/1705.03122
- Lea, C., Flynn, M.D., Vidal, R., Reiter, A., & Hager, G.D. (2016). 用于动作分割和检测的时态卷积网络. arXiv.
arxiv.org/abs/1611.05267
- Bai, S., Kolter, J.Z., & Koltun, V. (2018). 对序列建模的通用卷积和循环网络的经验评估. arXiv.
arxiv.org/abs/1803.01271
第十章:NLP 2.0:使用 Transformers 生成文本
正如我们在上一章中看到的,NLP 领域在我们理解、表示和处理文本数据的方式上取得了一些显著的飞跃。从使用 LSTMs 和 GRUs 处理长距离依赖/序列到使用 word2vec 和相关技术构建密集向量表示,该领域整体上取得了显著的改进。随着词嵌入几乎成为事实上的表示方法,以及 LSTMs 成为 NLP 任务的主力军,我们在进一步增强方面遇到了一些障碍。将嵌入与 LSTM 结合使用的这种设置充分利用了编码器-解码器(以及相关体系结构)风格模型。
我们在上一章中简要地看到了由于基于 CNN 的体系结构在 NLP 用例中的研究和应用而实现的某些改进。在本章中,我们将涉及导致当前最先进的 transformer 架构开发的下一组增强功能。我们将重点关注:
- 注意力的概述以及 transformers 如何改变 NLP 领域
- GPT 系列模型,提供基于 GPT-2 的文本生成流程的逐步指南
我们将涵盖注意力、自注意力、上下文嵌入,最后是 transformer 架构等主题。
本章中呈现的所有代码片段都可以直接在 Google Colab 中运行。由于空间原因,未包含依赖项的导入语句,但读者可以参考 GitHub 存储库获取完整的代码:github.com/PacktPublishing/Hands-On-Generative-AI-with-Python-and-TensorFlow-2
。
让我们首先把注意力转向注意力。
注意力
我们用于准备第一个文本生成语言模型的基于 LSTM 的架构存在一个主要限制。RNN 层(一般来说,可能是 LSTM 或 GRU 等)以定义大小的上下文窗口作为输入,并将其全部编码为单个向量。在解码阶段可以使用它开始生成下一个标记之前,这个瓶颈向量需要在自身中捕获大量信息。
注意力是深度学习空间中最强大的概念之一,真正改变了游戏规则。注意力机制背后的核心思想是在解码阶段使用之前利用 RNN 的所有中间隐藏状态来决定要关注哪个。更正式地表达注意力的方式是:
给定一组值的向量(所有 RNN 的隐藏状态)和一个查询向量(这可能是解码器的状态),注意力是一种计算值的加权和的技术,依赖于查询。
加权和作为隐藏状态(值向量)中包含的信息的选择性摘要,而查询决定了要关注哪些值。注意机制的根源可以在与神经机器翻译(NMT)架构相关的研究中找到。NMT 模型特别在对齐问题上遇到困难,而注意力在这方面大大帮助了。例如,从英语翻译成法语的句子可能不是单词一对一匹配的。注意力不仅限于 NMT 用例,而且广泛应用于其他 NLP 任务,如文本生成和分类。
这个想法非常简单,但我们如何实现和使用它呢?图 10.1展示了注意力机制的工作示例。图表展示了时间步t上展开的 RNN。
图 10.1:带有注意机制的简单 RNN
提到图表,我们来逐步了解注意力是如何计算的:
- 让 RNN 编码器隐藏状态表示为h[1]、h[2]…、h[N],当前输出向量为s[t]。
- 首先,我们计算时间步t的注意分数 e^t 如下:
这一步也被称为对齐步骤。 - 然后,我们将这个分数转换成注意力分布:。
- 使用 softmax 函数帮助我们将分数转换为总和为 1 的概率分布。
- 最后一步是计算注意力向量a[t],也称为上下文向量,方法是将编码器隐藏状态加权求和::
一旦我们得到了注意向量,我们就可以简单地将其与先前时间步的解码器状态向量连接起来,并像以前一样继续解码向量。
到目前为止,各种研究人员已经探索了注意机制的不同变体。需要注意的一些重要点包括:
- 注意计算的前述步骤在所有变体中都相同。
- 然而,区别在于计算注意力分数(表示为e^t)的方式。
广泛使用的注意力评分函数有基于内容的注意力,加法注意力,点积和缩放的点积。鼓励读者进一步探索以更好地了解这些。
上下文嵌入
从基于 BoW 的文本表示模型到无监督的密集表示技术(如 word2vec、GloVe、fastText 等)的大跨越是改善深度学习模型在 NLP 任务上表现的秘密武器。然而,这些表示方法也有一些局限性,我们会提醒自己:
- 单词的含义取决于使用的上下文。这些技术导致无论上下文如何,都会得到相同的向量表示。虽然可以通过使用非常强大的词义消歧方法(例如使用监督学习算法消除单词歧义)来解决这个问题,但从本质上来讲,这并没有被任何已知技术所捕捉到。
- 单词可以有不同的用法、语义和句法行为,但单词表示保持不变。
仔细思考一下,我们在上一章中使用 LSTMs 准备的架构正试图在内部解决这些问题。为了进一步阐述,让我们快速回顾一下我们建立的架构:
- 我们开始将输入文本转换为字符或单词嵌入。
- 然后,这些嵌入向量通过一个 LSTM 层(或者一组 LSTM 层,甚至是双向 LSTM 层),最终的隐藏状态被转换和解码以生成下一个标记。
虽然起始点利用了预训练嵌入,这些嵌入在每个上下文中具有相同的表示,但 LSTM 层引入了上下文。一组 LSTM 层分析令牌序列,每一层都试图学习与语言句法、语义等相关的概念。这为每个令牌(单词或字符)的表示提供了非常重要的上下文。
Peters 等人在 2017 年提出的TagLM架构是第一批提供见解的工作之一,说明了如何将预训练的词嵌入与预训练的神经语言模型结合起来,为下游 NLP 任务生成具有上下文意识的嵌入向量。
改变了 NLP 领域的巨大突破是ELMo,即来自语言模型的嵌入。ELMo 架构由 Peters 等人在他们 2018 年的作品Deep Contextualized Word Representations中提出。不详细展开,ELMo 架构的主要亮点是:
- 模型使用基于双向 LSTM 的语言模型。
- Character CNNs 被用来生成嵌入向量,取代了预训练的词向量,这些向量利用了 4096 个 LSTM 单元,但通过前向传播层转换成了更小的 512 大小的向量。
- 模型利用剩余层来帮助在架构的深层之间传递梯度。这有助于防止梯度消失等问题。
- 主要创新之处在于利用所有隐藏的双向 LSTM 层来生成输入表示。与以前的作品不同,在以前的作品中,只使用最终的 LSTM 层来获取输入表示,这项工作对所有隐藏层的隐藏状态进行加权平均。这有助于模型学习上下文词嵌入,其中每一层都有助于语法和语义等方面。
ELMo 备受关注的原因并不是它帮助提高了性能,而是 ELMo 学习的上下文嵌入帮助它在以往的架构上改进了最先进的性能,不仅在几个 NLP 任务上,而且几乎所有的任务上(详见论文)。
Howard 和 Ruder 在 2018 年提出的ULMFiT模型基于类似的概念,并帮助推广了在 NLP 领域的迁移学习的广泛应用。³
Python 与 TensorFlow2 生成式 AI(四)(3)https://developer.aliyun.com/article/1512055