TensorFlow 实战(五)(2)

简介: TensorFlow 实战(五)

TensorFlow 实战(五)(1)https://developer.aliyun.com/article/1522830

11.2.1 文本矢量化层

文本矢量化层接受一个字符串,对其进行标记化,并通过词汇表(或字典)查找将标记转换为 ID。它以字符串列表(或字符串数组)作为输入,其中每个字符串可以是单词/短语/句子(等等)。然后它从语料库中学习词汇。最后,该层可以用于将字符串列表转换为包含该列表中每个字符串的标记 ID 序列的张量。让我们看看这个层的作用。首先,导入该层:

from tensorflow.keras.layers.experimental.preprocessing import 
➥ TextVectorization

然后,按以下方式定义该层。在这里,我们为英语定义了该层。请记住,我们的模型中需要两个 TextVectorization 层,一个用于英语,一个用于德语:

en_vectorize_layer = TextVectorization(
    max_tokens=en_vocab,
    output_mode='int',
    output_sequence_length=None
)

值得停下来看看我们提供的不同参数:

  • max_tokens—指定词汇表中的单词数。如果词汇表中没有某个单词(即,超出词汇表的单词),则将其转换为[UNK]。
  • output_mode—指定最终输出的类型。可以是"int"、“binary”、"count"和"tf-idf"之一。"int"表示层将为每个标记输出一个标记 ID。"binary"意味着输出将是一个[<批量大小>, <词汇大小>]张量,在这个例子中,如果该标记所指示的索引在这个示例中存在,则给定值为 1。"count"给出了与"binary"类似的输出,但其中包含了该示例中标记出现的次数。"tf-id"给出了与"binary"类似的输出,但每个位置处的 TF-IDF 值。
  • output_sequence_length—指定转换为标记 ID 后的批量输入序列的长度。如果设置为 None,则意味着序列长度将设置为批量中最长序列的长度。较短的序列将使用特殊标记(特殊标记默认为"")进行填充。

要充分利用这个层,我们必须在文本语料库上适应它,以便它可以学习词汇表。调用 adapt()函数并向其传递字符串列表(或字符串数组)可以实现这一目的。换句话说,adapt()产生了与 scikit-learn 模型的 fit()方法相同的结果(mng.bz/aJmB)。它接受一些数据并根据数据训练(或适应)模型。对于标记器来说,除其他外,它构建了一个词典(从单词到 ID 的映射):

en_vectorize_layer.adapt(np.array(train_df["EN"].tolist()).astype('str'))

安装该层后,可以获得词汇表

print(en_vectorize_layer.get_vocabulary()[:10])

which prints

['', '[UNK]', 'tom', 'to', 'you', 'the', 'i', 'a', 'is', 'that']

换句话说,词汇表是一个标记列表,其中 ID 对应于它们在列表中的索引。你可以通过计算词汇表的大小来得到:

print(len(en_vectorize_layer.get_vocabulary()))

which returns

2238

接下来,要使用这个层将字符串转换为数字 ID,我们必须将其封装在一个模型中。为此,首先让我们定义一个 Keras 顺序模型。让我们将模型命名为 toy_model,因为这只用于学习文本向量化器的行为:

toy_model = tf.keras.models.Sequential()

定义一个输入层,将其大小设置为接受单列张量(即,一个字符串列表),并将数据类型设置为 tf.string:

toy_model.add(tf.keras.Input(shape=(1,), dtype=tf.string))

然后添加我们定义的文本向量化层:

toy_model.add(en_vectorize_layer)

你可以像使用其他 Keras 模型一样使用它,并将任意文本转换为数字 ID 序列。具体地,你可以在一些输入数据上使用 model.predict()函数,该函数接受输入并根据模型中使用的层进行相应转换:

input_data = [["run"], ["how are you"],["ectoplasmic residue"]]
pred = toy_model.predict(input_data)

最后,按以下方式打印输入和结果

print("Input data: \n{}\n".format(input_data))
print("\nToken IDs: \n{}".format(pred))

which gives

Input data: 
[['run'], ['how are you'], ['ectoplasmic residue']]
Token IDs: 
[[427   0   0]
 [ 40  23   4]
 [  1   1   0]]

该层按照一切都进行。 首先让我们看一下输出的形状。 由于我们设置了 output_sequence_length=None,所以将所有输入示例填充到输入中最长输入的长度。 在这里,“how are you”是最长的,其中有三个单词。 因此,所有行都用零填充,以便每个示例都有三列。 通常,该层返回一个大小为 [, sequence_length] 的输出。

如果单词在词汇表中找到,它将转换为某个数字(例如,“run”转换为 427)。 如果单词未在词汇表中找到(例如,“ectoplasmic”),则会用表示词汇表外单词的特殊 ID(1)替换。

11.2.2 为 seq2seq 模型定义 TextVectorization 层

通过对 TextVectorization 层有很好的理解,让我们定义一个函数,返回一个包装在 Keras Model 对象中的文本向量化层。 此函数名为 get_vectorizer(),接受以下参数:

  • 语料库—接受字符串列表(或数组)(即要构建词汇表的语料库)。
  • n_vocab—词汇量大小。 保留最常见的 n_vocab 个词以构建词汇表。
  • max_length(可选)—结果标记序列的长度。 默认为 None,此时序列长度将为最长文本序列的长度。
  • return_vocabulary(可选)—是否返回词汇表(即字符串标记列表)。
  • name(可选)—用于设置模型名称的字符串

它定义了一个接受字符串批次(总共形状为 [None, 1])的输入层。 接下来,函数定义了一个文本向量化层。 请注意,该层的词汇量为 n_vocab + 2。额外的 2 是为了容纳特殊标记" “和”[UNK]"。 该层适应了传递给函数的文本语料库。 最后,我们使用输入层(inp)和文本向量化层的输出(vectorize_out)定义了一个 Keras 模型。 如果 return_vocabulary 设置为 True,则还会返回 vectorize_layer 的词汇表,如下一列表所示。

列表 11.3 为编码器-解码器模型定义文本向量化器

def get_vectorizer(
    corpus, n_vocab, max_length=None, return_vocabulary=True, name=None
):
    """ Return a text vectorization layer or a model """
    inp = tf.keras.Input(shape=(1,), dtype=tf.string, name='encoder_input')❶
    vectorize_layer = 
➥ tf.keras.layers.experimental.preprocessing.TextVectorization(
        max_tokens=n_vocab+2,                                              ❷
        output_mode='int',
        output_sequence_length=max_length,                
    )
    vectorize_layer.adapt(corpus)                                          ❸
    vectorized_out = vectorize_layer(inp)                                  ❹
    if not return_vocabulary: 
        return tf.keras.models.Model(
            inputs=inp, outputs=vectorized_out, name=name
        )                                                                  ❺
    else:
        return tf.keras.models.Model(
            inputs=inp, outputs=vectorized_out, name=name                  ❻
        ), vectorize_layer.get_vocabulary()

❶ 定义一个接受字符串列表(或字符串数组)的输入层。

❷ 在定义词汇量大小时,我们使用 n_vocab + 2,因为自动添加了两个特殊标记“(填充)”和“[UNK]”。

❸ 在数据上拟合向量化层。

❹ 获取输入数据的标记 ID。

❺ 仅返回模型。 该模型接受字符串数组并输出标记 ID 的张量。

❻ 除了模型外,返回词汇表。

既然我们已经定义了该函数,让我们使用它并定义两个向量化器,一个用于英文输入,一个用于德文输入:

# Get the English vectorizer/vocabulary
en_vectorizer, en_vocabulary = get_vectorizer(
    corpus=np.array(train_df[“EN”].tolist()), n_vocab=en_vocab, 
    max_length=en_seq_length, name=’en_vectorizer’
)
# Get the German vectorizer/vocabulary
de_vectorizer, de_vocabulary = get_vectorizer(
    corpus=np.array(train_df[“DE”].tolist()), n_vocab=de_vocab, 
    max_length=de_seq_length-1, name=’de_vectorizer’
)

在这里,语料库接受一个文本列表或数组。每个文本都是一个包含英语或德语短语/句子的字符串。n_vocab 定义词汇表的大小,max_length 定义了我们应该对数据进行填充的序列长度。请注意,我们在解码器中使用 de_seq_length-1。这里减 1 的操作是由于在模型训练期间数据呈现给解码器的方式所决定的。当我们到达模型训练时,我们将讨论具体细节。最后,我们可以定义一个名称来跟踪不同的层。

11.2.3 定义编码器

接下来我们来到编码器,我们将在编码器的核心使用一个 GRU 模型。编码器负责处理源输入序列。它的责任是处理源输入并生成一个上下文向量(有时称为思考向量)。该向量以紧凑的、向量化的形式捕获输入序列的本质。通常情况下,这个上下文向量将是 GRU 单元在处理完整输入序列后的最后输出状态。

让我们看看准备编码器所涉及的步骤。为此,我们将使用 Keras Functional 层。序列到序列模型不是顺序的,并且在编码器和解码器之间涉及非线性连接。因此,我们不能使用 Keras Sequential API。首先,我们定义输入层:

# The input is (None,1) shaped and accepts an array of strings
inp = tf.keras.Input(shape=(1,), dtype=tf.string, name='e_input')

输入接受一个字符串列表。请注意,我们将形状设置为(1,)以确保模型接受具有一个列的张量,并将 dtype 设置为 tf.string。接下来,我们将由 inp 前向传递的文本输入进行向量化。

# Vectorize the data (assign token IDs)
vectorized_out = en_vectorizer(inp)

这里,向量化器是由我们之前定义的 get_vectorizer()函数输出的执行文本向量化的模型。

接下来,我们定义一个嵌入层,它将由向量化器返回的标记 ID 转换为单词向量。这是一个具有可训练权重的层。因此,在训练期间,模型将调整单词嵌入以反映解决手头任务的有用表示:

# Define an embedding layer to convert IDs to word vectors
emb_layer = tf.keras.layers.Embedding(
    input_dim=n_vocab+2, output_dim=128, mask_zero=True, name=’e_embedding’
)
# Get the embeddings of the token IDs
emb_out = emb_layer(vectorized_out)

在定义嵌入层时,您需要始终传递词汇表大小(input_dim)和 output_dim。请注意,词汇表大小已经增加了 2 以容纳引入的两个特殊标记(即 UNK 和 PAD)。我们将 output_dim 设置为 128。我们还希望屏蔽过多的零,因此设置 mask_zero=True。最后,我们还将传递一个名称以便于识别该层。

现在我们要来到我们模型的核心:循环神经网络(RNN)。正如前面提到的,我们将使用一个 GRU 模型,但是带有一个额外的特点!我们将使我们的 GRU 模型成为双向的!双向 RNN 是一种特殊类型的 RNN,它同时处理序列的前向和后向。这与标准 RNN 相反,后者只处理序列的前向:

gru_layer = tf.keras.layers.Bidirectional(tf.keras.layers.GRU(128))

双向 RNN:前向和后向阅读文本

标准的循环神经网络逐步阅读文本,一次处理一个时间步,然后输出一系列输出。双向循环神经网络正如其名称所示,不仅向前阅读文本,而且向后阅读文本。这意味着双向循环神经网络有两个输出序列。然后,这两个序列使用组合策略(例如连接)进行组合,以产生最终输出。通常双向循环神经网络的性能优于标准循环神经网络,因为它们可以理解文本前后的关系,如下图所示。


标准 RNN 和双向 RNN 之间的比较

阅读文本倒置为什么有帮助呢?有一些语言是倒序阅读的(例如阿拉伯语、希伯来语)。除非文本经过特殊处理以考虑这种书写风格,否则标准的循环神经网络将很难理解这种语言。通过使用双向循环神经网络,您可以消除模型对语言始终从左到右或从右到左的依赖。

如果考虑英语,可能存在只从前面推断关系是不可能的情况。考虑以下两个句子

约翰走向了克拉伦斯街上的银行。

约翰朝河边的银行走去。

由于这两个句子在“bank”一词之前是相同的,因此在阅读其余部分之前,不可能知道“bank”是指金融机构还是河岸。对于双向循环神经网络来说,这是微不足道的。

然后我们获得 gru_layer 的输出,并将其分配给 gru_out:

gru_out = gru_layer(emb_out)

最后,我们将编码器模型定义为 tf.keras.models.Model 对象。它接受 inp(即 tf.string 类型的单列张量)并输出 gru_out(即双向 GRU 模型的最终状态)。这个 GRU 模型的最终状态被认为是上下文向量,为解码器提供有关源语言句子/短语输入的信息:

encoder = tf.keras.models.Model(inputs=inp, outputs=gru_out)

可以观察在以下清单中显示的逐步构建编码器模型的函数封装方式。

清单 11.4 返回编码器的函数

def get_encoder(n_vocab, vectorizer):
    """ Define the encoder of the seq2seq model"""
    inp = tf.keras.Input(shape=(1,), dtype=tf.string, name='e_input')   ❶
    vectorized_out = vectorizer(inp)                                    ❷
    emb_layer = tf.keras.layers.Embedding(
        n_vocab+2, 128, mask_zero=True, name='e_embedding'              ❸
    )
    emb_out = emb_layer(vectorized_out)                                 ❹
    gru_layer = tf.keras.layers.Bidirectional(
        tf.keras.layers.GRU(128, name='e_gru'),                         ❺
        name='e_bidirectional_gru'
    )
    gru_out = gru_layer(emb_out)                                        ❻
    encoder = tf.keras.models.Model(
        inputs=inp, outputs=gru_out, name='encoder'
    )                                                                   ❼
    return encoder

❶ 输入形状为(None,1),接受一个字符串数组。

❷ 对数据进行向量化(分配令牌 ID)

❸ 定义一个嵌入层,将 ID 转换为词向量。

❹ 获取令牌 ID 的嵌入。

❺ 定义一个双向 GRU 层。编码器同时查看英文文本(即输入)的前向和后向。

❻ 获取 gru 的最后一个输出(模型返回的最后一个输出状态向量)。

❼ 定义编码器模型;它接受一个字符串列表/数组并返回 GRU 模型的最后输出状态。

定义函数后,您可以简单地调用它来构建编码器模型:

encoder = get_encoder(en_vocab, en_vectorizer)

11.2.4 定义解码器和最终模型

编码器已经完成,现在是看看解码器的时候了。解码器看起来会比编码器稍微复杂一些。解码器的核心模型再次是一个 GRU 模型。然后是一个完全连接的隐藏层和一个完全连接的预测层。预测层对每个时间步输出来自德语词汇表的一个单词(通过计算整个词汇表上的概率)。

在模型训练期间,解码器预测给定目标序列中的下一个单词。例如,给定目标序列 [A, B, C, D],解码器将根据以下输入-输出元组对模型进行三个时间步的训练:(A, B),(B, C) 和 (C, D)。换句话说,给定标记 A,预测标记 B;给定标记 B,预测标记 C;依此类推。如果你考虑编码器-解码器模型的端到端过程,将会发生以下步骤:

  1. 编码器处理源输入序列(即英文)并生成上下文向量(即 GRU 模型的最后输出状态)。
  2. 解码器使用编码器产生的上下文向量作为其循环组件的初始状态。
  3. 解码器接收目标输入序列(即德语)并根据前一个标记预测下一个标记。对于每个时间步,它使用完全连接的层和 softmax 层在完整的目标词汇表上预测一个标记。

这种模型训练的方式被称为教师强制,因为你正在用目标序列(即老师)引导解码器。与在编码器-解码器类型模型的训练中不使用教师强制相比,使用教师强制很快会导致更好的性能。让我们深入了解解码器,并看看编码器和解码器如何相互联系以创建更深入的最终模型,如图 11.3 所示。


图 11.3 最终的序列到序列模型的实现,重点关注各种层和输出

现在是讨论构建解码器和最终模型的具体细节的时候了。我们首先必须做的事情是通过传递一个输入来获得编码器的输出。我们定义了一个与编码器的输入相同的输入层,并将其传递给我们之前定义的编码器模型:

e_inp = tf.keras.Input(shape=(1,), dtype=tf.string, name='e_input_final') 

然后我们将 e_inp 传递给编码器模型,它将给出 GRU 模型的最后输出状态作为输出。这是解码器的重要输入:

d_init_state = encoder(e_inp)

作为解码器的起点,我们定义了一个具有与编码器输入相同规格的输入层:

d_inp = tf.keras.Input(shape=(1,), dtype=tf.string, name='d_input')

然后我们将输入传递给一个文本向量化模型,给定 get_vectorizer() 函数:

vectorized_out = de_vectorizer(inp)

我们像对编码器一样定义一个嵌入层,以便文本向量化层生成的标记 ID 转换为单词向量。请注意,我们为编码器和解码器分别定义了两个单独的嵌入层,因为它们使用来自两种不同语言的序列:

emb_layer = tf.keras.layers.Embedding(
    input_dim=n_vocab+2, output_dim=128, mask_zero=True, name='d_embedding'
)
emb_out = emb_layer(vectorized_out)

现在是时候实现解码器的循环组件了。与编码器类似,我们使用 GRU 模型来处理序列:

gru_layer = tf.keras.layers.GRU(256, return_sequences=True)

但请注意,与编码器相比,在解码器中,我们没有在 GRU 模型上使用双向包装器。解码器不能依赖于向后阅读能力,因为它应该仅根据前一个和当前输入生成下一个输出。还要注意,我们设置了 return_sequences=True:

gru_out = gru_layer(emb_out, initial_state=d_init_state)

最后,将嵌入层的输出传递给 gru_layer,我们得到输出。之前我们声明过 d_init_state(即编码器的输出)是解码器的重要输入之一。在这里,我们将 d_init_state 作为初始状态传递给解码器的 GRU 层。这意味着,解码器不是从零初始化状态向量开始,而是使用编码器的上下文向量作为初始状态。由于我们设置了 return_sequences=True,因此输出将包含所有时间步的输出状态向量,而不仅仅是最后一个时间步。这意味着输出的大小为[<批大小>,<时间步长>,256]。

清单 11.5 定义解码器和最终模型

def get_final_seq2seq_model(n_vocab, encoder, vectorizer):
    """ Define the final encoder-decoder model """    
    e_inp = tf.keras.Input(
        shape=(1,), dtype=tf.string, name='e_input_final'
    )                                                                    ❶
    d_init_state = encoder(e_inp)                                        ❶
    d_inp = tf.keras.Input(shape=(1,), dtype=tf.string, name='d_input')  ❷
    d_vectorized_out = vectorizer(d_inp)                                 ❸
    d_emb_layer = tf.keras.layers.Embedding(
        n_vocab+2, 128, mask_zero=True, name='d_embedding'               ❹
    )
    d_emb_out = d_emb_layer(d_vectorized_out)
    d_gru_layer = tf.keras.layers.GRU(
        256, return_sequences=True, name='d_gru'
    )                                                                    ❺
    d_gru_out = d_gru_layer(d_emb_out, initial_state=d_init_state)       ❻
    d_dense_layer_1 = tf.keras.layers.Dense(
        512, activation='relu', name='d_dense_1'
    )                                                                    ❼
    d_dense1_out = d_dense_layer_1(d_gru_out)                            ❼
    d_dense_layer_final = tf.keras.layers.Dense(
        n_vocab+2, activation='softmax', name='d_dense_final'            ❽
    )
    d_final_out = d_dense_layer_final(d_dense1_out)                      ❽
    seq2seq = tf.keras.models.Model(
        inputs=[e_inp, d_inp], outputs=d_final_out, name='final_seq2seq' ❾
    )
    return seq2seq

❶定义编码器输入层并获取编码器输出(即上下文向量)。

❷输入的形状为(None,1),并接受字符串数组作为输入。我们将德语序列作为输入并要求模型使用偏移一个(即下一个)单词的单词预测它。

❸获取解码器的向量化输出。

❹定义嵌入层,将 ID 转换为单词向量。这是与编码器嵌入层不同的层。

❺定义 GRU 层。与编码器不同,我们不能为解码器定义双向 GRU。

❻获取解码器的 GRU 层输出。

❼定义中间的 Dense 层并获取输出。

❽最终预测层使用 softmax

❾定义完整模型。

我们现在可以定义所需的所有内容,以制定我们的最终模型

# Get the English vectorizer/vocabulary
en_vectorizer, en_vocabulary = get_vectorizer(
    corpus=np.array(train_df["EN"].tolist()), n_vocab=en_vocab, 
    max_length=en_seq_length, name='e_vectorizer'
)
# Get the German vectorizer/vocabulary
de_vectorizer, de_vocabulary = get_vectorizer(
    corpus=np.array(train_df["DE"].tolist()), n_vocab=de_vocab,
    max_length=de_seq_length-1, name='d_vectorizer'
)
# Define the final model
encoder = get_encoder(n_vocab=en_vocab, vectorizer=en_vectorizer)
   final_model = get_final_seq2seq_model(
       n_vocab=de_vocab, encoder=encoder, vectorizer=de_vectorizer
   )

在这里,我们定义英语和德语的向量器(分别为 en_vectorizer 和 de_vectorizer)。然后使用英语词汇量和英语向量器定义编码器。最后,使用德语词汇量(de_vocab)编码器和德语向量器(de_vectorizer)定义最终的编码器-解码器模型。

TensorFlow 实战(五)(3)https://developer.aliyun.com/article/1522832

相关文章
|
27天前
|
机器学习/深度学习 自然语言处理 TensorFlow
TensorFlow 实战(六)(2)
TensorFlow 实战(六)
24 0
|
13天前
|
机器学习/深度学习 TensorFlow API
TensorFlow与Keras实战:构建深度学习模型
本文探讨了TensorFlow和其高级API Keras在深度学习中的应用。TensorFlow是Google开发的高性能开源框架,支持分布式计算,而Keras以其用户友好和模块化设计简化了神经网络构建。通过一个手写数字识别的实战案例,展示了如何使用Keras加载MNIST数据集、构建CNN模型、训练及评估模型,并进行预测。案例详述了数据预处理、模型构建、训练过程和预测新图像的步骤,为读者提供TensorFlow和Keras的基础实践指导。
147 59
|
27天前
|
机器学习/深度学习 自然语言处理 TensorFlow
TensorFlow 实战(五)(5)
TensorFlow 实战(五)
19 1
|
27天前
|
自然语言处理 算法 TensorFlow
TensorFlow 实战(六)(3)
TensorFlow 实战(六)
21 0
|
27天前
|
机器学习/深度学习 数据可视化 TensorFlow
TensorFlow 实战(六)(1)
TensorFlow 实战(六)
25 0
|
27天前
|
存储 自然语言处理 TensorFlow
TensorFlow 实战(五)(4)
TensorFlow 实战(五)
21 0
|
27天前
|
数据可视化 TensorFlow 算法框架/工具
TensorFlow 实战(八)(4)
TensorFlow 实战(八)
25 1
|
27天前
|
TensorFlow API 算法框架/工具
TensorFlow 实战(八)(3)
TensorFlow 实战(八)
25 1
|
27天前
|
机器学习/深度学习 自然语言处理 TensorFlow
TensorFlow 实战(八)(5)
TensorFlow 实战(八)
25 0
|
27天前
|
并行计算 TensorFlow 算法框架/工具
TensorFlow 实战(八)(2)
TensorFlow 实战(八)
21 0