TensorflowSimilarity学习笔记3

子车青青
2023-12-01

2021SC@SDUSC

学习内容:Keras 预处理层

为您的数据确定正确的特征表示可能是构建模型中最棘手的部分之一。 想象一下,您正在使用分类输入功能,例如颜色名称。 您可以对特征进行单热编码,以便每种颜色在特定索引中获得 1 ('red' = [0, 0, 1, 0, 0]),或者您可以嵌入该特征,以便每种颜色映射到唯一的可训练 向量('红色' = [0.1, 0.2, 0.5, -0.2])。 较大的类别空间使用嵌入可能会更好,而较小的空间作为 one-hot 编码可能会更好,但答案尚不明确。 这将需要在您的特定数据集上进行实验。

理想情况下,我们希望对特征表示的更新和对模型架构的更新发生在一个紧密的迭代循环中,在更改模型架构的同时对我们的数据应用新的转换。 在实践中,特征预处理和模型构建通常由完全不同的库、框架或语言处理。 这会减慢实验过程。

 在 Keras 团队中,最近发布了 Keras Preprocessing Layers,这是一组 Keras 层,旨在使预处理数据更自然地适应模型开发工作流程。 在这篇文章中,我们将使用这些层使用 imdb 电影评论数据集构建一个简单的情感分类模型。 目标将是展示如何灵活地开发和应用预处理。 首先,我们可以导入 tensorflow 并下载训练数据。

import tensorflow as tf
import tensorflow_datasets as tfds

train_ds = tfds.load('imdb_reviews', split='train', as_supervised=True).batch(32)

Keras 预处理层可以处理广泛的输入,包括结构化数据、图像和文本。 在这种情况下,我们将使用原始文本,因此我们将使用 TextVectorization 层。

默认情况下,TextVectorization 层将分三个阶段处理文本:

首先,删除标点符号并小写输入。
接下来,将文本拆分为单个字符串单词的列表。
最后,使用已知单词的词汇表将字符串映射到数字输出。
我们可以在这里尝试一种简单的方法是多热编码,我们只考虑评论中是否存在术语。 例如,假设图层词汇是 ['movie', 'good', 'bad'],并且评论为“This movie was bad.”。 我们将其编码为 [1, 0, 1],其中存在电影(第一个词汇术语)和坏(最后一个词汇术语)。

text_vectorizer = tf.keras.layers.TextVectorization(
     output_mode='multi_hot', max_tokens=2500)
features = train_ds.map(lambda x, y: x)
text_vectorizer.adapt(features)

上面,我们创建了一个具有多热输出的 TextVectorization 层,并做两件事来设置层的状态。 首先,我们映射我们的训练数据集并丢弃表示正面或负面评论的整数标签。 这为我们提供了一个仅包含评论文本的数据集。 接下来,我们对这个数据集上的层进行适配(),这使得该层学习所有文档中最常用术语的词汇表,上限为 2500。

Adapt 是所有有状态预处理层上的实用函数,它允许层根据输入数据设置其内部状态。 调用adapt 始终是可选的。 对于 TextVectorization,我们可以提供一个关于层构建的预先计算的词汇表,并跳过适配步骤。

我们现在可以在这种多热编码的基础上训练一个简单的线性模型。 我们将定义两个函数:预处理,它将原始输入数据转换为我们想要的模型表示,以及 forward_pass,它应用可训练层。

 

def preprocess(x):
  return text_vectorizer(x)

def forward_pass(x):
  return tf.keras.layers.Dense(1)(x)  # Linear model

inputs = tf.keras.Input(shape=(1,), dtype='string')
outputs = forward_pass(preprocess(inputs))
model = tf.keras.Model(inputs, outputs)
model.compile(loss=tf.keras.losses.BinaryCrossentropy(from_logits=True))
model.fit(train_ds, epochs=5)

这就是端到端的训练示例,并且已经足以达到 85% 的准确率。 您可以在本文底部找到此示例的完整代码。

让我们试验一个新功能。 我们的多热编码不包含任何评论长度的概念,因此我们可以尝试为标准化字符串长度添加一个功能。 预处理层可以根据需要与 TensorFlow 操作和自定义层混合。 在这里,我们可以将 tf.strings.length 函数与归一化层结合起来,将输入缩放为 0 均值和 1 方差。 我们只更新了下面预处理函数的代码,但为了清楚起见,我们将展示其余的训练。

# This layer will scale our review length feature to mean 0 variance 1.
normalizer = tf.keras.layers.Normalization(axis=None)
normalizer.adapt(features.map(lambda x: tf.strings.length(x)))

def preprocess(x):
  multi_hot_terms = text_vectorizer(x)
  normalized_length = normalizer(tf.strings.length(x))
  # Combine the multi-hot encoding with review length.
  return tf.keras.layers.concatenate((multi_hot_terms, normalized_length))

def forward_pass(x):
  return tf.keras.layers.Dense(1)(x)  # Linear model.

inputs = tf.keras.Input(shape=(1,), dtype='string')
outputs = forward_pass(preprocess(inputs))
model = tf.keras.Model(inputs, outputs)
model.compile(loss=tf.keras.losses.BinaryCrossentropy(from_logits=True))
model.fit(train_ds, epochs=5)

上面,我们创建了归一化层并使其适应我们的输入。 在预处理函数中,我们简单地将我们的多热编码和长度特征连接在一起。 我们在两个特征表示的联合上学习了一个线性模型。

我们可以做的最后一个改变是加速训练。 我们有一个重要的机会来提高我们的训练吞吐量。 现在,每个训练步骤,我们都会花一些时间在 CPU 上执行字符串操作(不能在加速器上运行),然后在 GPU 上计算损失函数和梯度。

 

这种加速器使用上的差距是完全没有必要的! 预处理不同于我们模型的实际前向传递。 预处理不使用任何正在训练的参数。 这是一个我们可以预先计算的静态转换。

为了加快速度,我们希望预取我们的预处理批次,以便每次我们训练一个批次时,我们都在预处理下一个批次。 使用 tf.data 库很容易做到这一点,该库专为此类用途而构建。 我们唯一需要做的主要改变是将我们的整体 keras.Model 拆分为两个:一个用于预处理,一个用于训练。 使用 Keras 的函数式 API,这很容易。

inputs = tf.keras.Input(shape=(1,), dtype="string")
preprocessed_inputs = preprocess(inputs)
outputs = forward_pass(preprocessed_inputs)

# The first model will only apply preprocessing.
preprocessing_model = tf.keras.Model(inputs, preprocessed_inputs)
# The second model will only apply the forward pass.
training_model = tf.keras.Model(preprocessed_inputs, outputs)
training_model.compile(
    loss=tf.keras.losses.BinaryCrossentropy(from_logits=True))

# Apply preprocessing asynchronously with tf.data.
# It is important to call prefetch and remember the AUTOTUNE options.
preprocessed_ds = train_ds.map(
    lambda x, y: (preprocessing_model(x), y),
    num_parallel_calls=tf.data.AUTOTUNE).prefetch(tf.data.AUTOTUNE)

# Now the GPU can focus on the training part of the model.
training_model.fit(preprocessed_ds, epochs=5)

 

在上面的例子中,我们通过我们的 preprocess 和 forward_pass 函数传递了一个 keras.Input,但是在转换后的输入上定义了两个单独的模型。 这将我们的单个操作图一分为二。 另一个有效的选择是只制作一个训练模型,并在我们映射我们的数据集时直接调用预处理函数。 在这种情况下, keras.Input 需要反映预处理特征的类型和形状,而不是原始字符串。

我们甚至可以比这更进一步,使用 tf.data 将我们预处理的数据集缓存在内存或磁盘上。 我们只需在调用预取之前直接添加一个 .cache() 调用。 通过这种方式,我们可以在第一个训练时期之后完全跳过计算我们的预处理批次。

训练后,我们可以在推理过程中将我们的拆分模型重新加入单个模型。 这允许我们保存可以直接处理原始输入数据的模型。

inputs = preprocessing_model.input
outputs = training_model(preprocessing_model(inputs))
inference_model = tf.keras.Model(inputs, outputs)
inference_model.predict(
    tf.constant(["Terrible, no good, trash.", "I loved this movie!"]))

Keras 预处理层旨在提供一种灵活且富有表现力的方式来构建数据预处理管道。 预建层可以与自定义层和其他 tensorflow 函数混合和匹配。 预处理可以从训练中分离出来,并与 tf.data 一起有效地应用,然后加入以进行推理。 我们希望它们允许对模型中的特征表示进行更自然和有效的迭代。

要在 Colab 中使用这篇文章中的代码,您可以点击此链接。 要查看您可以使用预处理层执行的各种任务,请参阅我们的预处理指南的快速食谱部分。 您还可以查看我们关于基本文本分类、图像数据增强和结构化数据分类的完整教程。

 类似资料: