TensorFlow 2.0 快速入门指南:第三部分

2023-04-23 11:15:03 浏览数 (3)

第 3 部分:TensorFlow 2.00 Alpha 的神经网络应用

在本节中,我们将研究许多人工神经网络ANN)应用。 这些包括图像识别,神经风格迁移,文本风格生成,时尚识别以及电影评论的 IMDb 数据库的语义分析。

本节包含以下章节:

  • 第 6 章“使用 TensorFlow 2 识别图像”
  • 第 7 章“TensorFlow 2 和神经风格迁移”
  • 第 8 章“Tensorflow 2 和循环神经网络”
  • 第 9 章“TensorFlow 估计器和 TensorFlow HUB”

六、使用 TensorFlow 2 识别图像

本章分为两部分,但我们将同时学习使用 TensorFlow 进行图像分类。

在本章中,我们将涵盖以下主要主题:

  • QuickDraw – 使用 TensorFlow 进行图像分类
  • 使用 TensorFlow 的 CIFAR 10 图像分类

在第一部分中,我们将使用在前几章中学到的技术开发 TensorFlow 2 模型以进行图像识别,尤其是第 2 章, “Keras,TensorFlow 2 的高级 API”。 这将使我们能够看到如何使用 TensorFlow 2 将所有相关技术结合在一起来创建,训练和评估完整的模型。我们将利用 Google 提供的 QuickDraw 图片数据集可帮助您解决此问题。

QuickDraw – 使用 TensorFlow 进行图像分类

我们将使用从 Google QuickDraw 拍摄的图像数据集。 这是一个公开的开放源代码,它包含 345 个类别的 5000 万张图像的数据集,所有这些图像都是由参与挑战的 1500 万名用户在 20 秒或更短的时间内绘制的。 我们将训练 10 个类别的 10,000 张图像,其中一些被选择为相似图像,以便我们可以测试模型的区分能力。 您可以在这个页面上查看这些图像的示例。 这些图片有多种格式,请参见这个页面中的所有格式。

在这里,我们将使用已存储为.npy文件的图像。 .npy文件的公共数据集托管在这个页面上。 从这里可以一次下载一组。 要使用不同的图像运行此示例,请从数据目录中删除图像文件,然后将所需的图像下载到存储库中的同一目录中。 该程序从文件名中读取标签。

在本节中,我们将涵盖以下主题:

  • 采集数据
  • 预处理数据
  • 建立模型
  • 训练和测试模型
  • 保存,加载和重新测试模型
  • 使用.h5格式保存和加载 NumPy 图像数据
  • 加载预训练的模型
  • 使用预训练的模型

我们将逐步开发和呈现代码片段。 这些代码段通过螺栓连接在一起,成为存储库中的完整程序。

采集数据

我们将需要从 Google 下载数据。 您可以将数据下载到一个空目录data_files

转到这里并将 10 个数据集下载到data_files文件夹中。 以下是将要下载的文件的示例:

代码语言:javascript复制
'alarm_clock.npy', 'broom.npy', 'ant.npy', 'bee.npy', 'cell_phone.npy', 'baseball.npy', 'dolphin.npy', 'crocodile.npy', 'aircraft_carrier.npy', 'asparagus.npy'

您将下载的文件名称前会带有多余的位,例如full_numpy_bitmap_alarm clock.npy

为了使这些内容更简洁,请删除开头的位,然后重命名文件,以使文件名在我们的示例中变为alarm_clock.npy。 对所有 10 个文件执行此操作。

建立环境

首先,我们需要导入依赖项:

代码语言:javascript复制
import tensorflow as tf
import keras
import numpy as np
from sklearn.model_selection import train_test_split
from os import walk

您可能需要运行pip install sklearn。 接下来,我们将建立一些常量供以后使用:

代码语言:javascript复制
batch_size = 128
img_rows, img_cols = 28, 28 # image dimensions

接下来,我们将使用os.walk方法从data_files文件夹中收集数据集的文件名:

请注意,文件名收集在列表变量filenames中。

代码语言:javascript复制
data_path = "data_files/" 
for (dirpath, dirnames, filenames) in walk(data_path):
     pass # filenames accumulate in list 'filenames'
print(filenames)

对于我们的示例,文件名(对应于label类别)如下:

代码语言:javascript复制
['alarm_clock.npy', 'broom.npy', 'ant.npy', 'bee.npy', 'cell_phone.npy', 'baseball.npy', 'dolphin.npy', 'crocodile.npy', 'aircraft_carrier.npy', 'asparagus.npy']

要使用不同的图像运行该示例,只需将 10 个不同的文件下载到data文件夹中。

接下来,我们将定义模型所需的更多值。 图像总数(num_images)可在此处更改:

代码语言:javascript复制
num_images = 1000000 ### was 100000, reduce this number if memory issues.
num_files = len(filenames) # we have 10 files
images_per_category = num_images//num_files
seed = np.random.randint(1, 10e7)
i=0
print(images_per_category)

预处理数据

接下来是将图像加载到内存中的代码。 我们将遍历文件,并在获取文件路径的值之后,加载该文件或一组图像(x)。 然后,将x转换为浮点数,然后除以 255,将其设置为 0 到 1 的范围。之后,我们为该组图像x创建一个数字标签y。 对于第一组图像,该值为 0,对于下一组图像,此值为 1,一直到最后一组图像的 9,由变量i控制。 然后,我们将集合xy切片,以将图像和标签放回xy中。 之后,我们将xy累积到x_ally_all中,如果这是它们第一次进入循环(即i=0),则创建这两个新列表,并将x和[ 如果这不是他们第一次通过循环(即i>0),则将它们移到y上。 当此循环终止时,x_ally_all将分别包含带有标签的图像:

代码语言:javascript复制
i=0
for file in filenames:
     file_path = data_path   file
     x = np.load(file_path)
     x = x.astype('float32') ##normalize images
     x /= 255.0
     y = [i] * len(x) # create numeric label for this image

     x = x[:images_per_category] # get the sample of images 
     y = y[:images_per_category] # get the sample of labels 

     if i == 0: 
         x_all = x
         y_all = y
     else: 
         x_all = np.concatenate((x,x_all), axis=0)
         y_all = np.concatenate((y,y_all), axis=0)
     i  = 1

之后,我们将使用sklearn.model_selection模块中的train_test_split方法将x_ally_all分为训练和测试集,并以 80/20 的训练/测试进行分割:

代码语言:javascript复制
#split data arrays into train and test segments
x_train, x_test, y_train, y_test = train_test_split(x_all, y_all, test_size=0.2, random_state=42)

由于我们将使用卷积神经网络(convNet)对快速抽奖进行分类! 图像,接下来要做的是将x_trainx_test重塑为28 x 28 x 1图像,它们开始出现时的样子,其中前两个维度是图像的高度和宽度(以像素为单位),第三个维度是每个像素的灰度。 我们还将建立input_shape,并将其用于convNet的第一层:

代码语言:javascript复制
x_train = x_train.reshape(x_train.shape[0], img_rows, img_cols, 1) 
x_test = x_test.reshape(x_test.shape[0], img_rows, img_cols, 1) 
input_shape = (img_rows, img_cols, 1)

此后,我们将根据convNet的要求对y_trainy_test标签进行一次热编码:

代码语言:javascript复制
y_train = tf.keras.utils.to_categorical(y_train, num_files) 
y_test = tf.keras.utils.to_categorical(y_test, num_files)

接下来,我们将训练和测试x集进一步与验证集一起分成 90/10 的更小的测试集:

代码语言:javascript复制
x_train, x_valid, y_train, y_valid = train_test_split(x_train, y_train, test_size=0.1, random_state=42)

建立模型

现在,我们准备创建convNet模型。

有两个卷积层(具有 ReLU 激活),每个卷积层都插入最大池化和丢弃层,然后是一个将卷积层的输出展平为一维的层。 在这些层之后是密集的(完全连接的)一维层(同样具有 ReLU 激活),最后的丢弃层,最后是具有 10 个单元的 softmax 层。 softmax 层中每个输出单元的激活给出了该图像是 10 张图像之一的可能性。 这种 ANN 架构有足够的实验空间。

然后使用分类交叉熵的损失来编译模型:

代码语言:javascript复制
model = tf.keras.Sequential()

model.add(tf.keras.layers.Conv2D(32, kernel_size=(3, 3), activation='relu', input_shape=input_shape)) 
model.add(tf.keras.layers.MaxPooling2D(pool_size=(2, 2))) 
model.add(tf.keras.layers.Dropout(0.25))

model.add(tf.keras.layers.Conv2D(64, (3, 3), activation='relu')) 
model.add(tf.keras.layers.MaxPooling2D(pool_size=(2, 2))) 
model.add(tf.keras.layers.Dropout(0.25))

model.add(tf.keras.layers.Flatten())
model.add(tf.keras.layers.Dense(128, activation='relu')) 
model.add(tf.keras.layers.Dropout(0.5))
model.add(tf.keras.layers.Dense(num_files, activation='softmax')) 

print("Compiling...........")
model.compile(loss=tf.keras.losses.categorical_crossentropy,
 optimizer=tf.keras.optimizers.Adadelta(),
 metrics=['accuracy'])

训练和测试模型

现在,我们可以使用fit方法训练模型。 注意验证集的使用,它不同于训练集。 callbacks列表还可以用于诸如保存最佳模型或在学习停止时终止训练(如果在所有周期完成之前发生这种情况)的操作。 有关详细信息,请参见这里:

代码语言:javascript复制
epochs=25
callbacks=[tf.keras.callbacks.TensorBoard(logdir = "./tb_log_dir")]
model.fit( x_train, y_train,
 batch_size=batch_size,
 epochs=epochs,
 callbacks=callbacks,
 verbose=1,
 validation_data=(x_valid, y_valid)
)

根据模型所处的硬件配置,如果该模型在 GPU 上运行,或者在 CPU 上运行缓慢,则训练速度将非常快。 为了说明的目的,可以减少周期的数量。 在 NVIDIA GTX 1080 GPU 上,时间/周期约为 38 秒。

为了确定模型的准确率,按以下方法使用evaluate方法。 请注意,测试集用于此评估:

代码语言:javascript复制
score = model.evaluate(x_test, y_test, verbose=1)
print('Test loss:', score[0])
print('Test accuracy:', score[1])

我们还可以对测试图像进​​行随机采样,并使用以下代码查看模型的效果。 从文件名中检索标签并打印以供参考,然后再打印成对的预测标签与实际标签:

代码语言:javascript复制
import os
labels = [os.path.splitext(file)[0] for file in filenames]
print(labels)
print("nFor each pair in the following, the first label is predicted, second is actualn")
for i in range(20):
  t = np.random.randint(len(x_test) )
  x1= x_test[t]
  x1 = x1.reshape(1,28,28,1) 
  p = model.predict(x1)
  print("-------------------------")
  print(labels[np.argmax(p)])
  print(labels[np.argmax(y_test[t])])
  print("-------------------------")

TensorBoard 回调

TensorBoard 是用于训练模型的可视化工具。 TensorBoard 回调的完整签名如下:

代码语言:javascript复制
tf.keras.callbacks.TensorBoard(log_dir='./logs', histogram_freq=0, batch_size=32, write_graph=True, write_grads=False, write_images=False, embeddings_freq=0, embeddings_layer_names=None, embeddings_metadata=None, embeddings_data=None, update_freq='epoch')

在这个页面 上有所有这些参数的非常清晰而详细的描述。 TensorBoard 可以从命令行调用,如下所示:

代码语言:javascript复制
tensorboard --logdir=/full_path_to_your_logs

例如,我们可以使用tensorboard --logdir=./logs作为默认目录。 将histogram_freq设置为非 0 的值会导致在写入数据时epochs之间有明显的停顿,并且仅在需要模型所有层的激活和权重直方图时才应使用。

保存,加载和重新测试模型

现在,我们可以保存模型并将其删除:

代码语言:javascript复制
model.save("./QDrawModel.h5")
del model

然后,我们需要重新加载它:

代码语言:javascript复制
from tensorflow.keras.models import load_model
model = load_model('./QDrawModel.h5')

最后,我们必须对其进行总结以表明我们已经成功地重新加载了保存的模型:

代码语言:javascript复制
model.summary()

最后,我们打印出 20 种时尚商品的测试样本,以确保网络正常运行:

代码语言:javascript复制
print("For each pair, first is predicted, second is actual")
for i in range(20):
  t = np.random.randint(len(x_test))
  x1= x_test[t]
  x1 = x1.reshape(1,28,28,1) 
  p = model.predict(x1)
  print("-------------------------")
  print(labels[np.argmax(p)])
  print(labels[np.argmax(y_test[t])])
  print("-------------------------")

使用.h5格式保存和加载 NumPy 图像数据

如果需要保存先前程序中的训练和测试数据,则可以使用以下代码:

代码语言:javascript复制
import h5py
with h5py.File('x_train.h5', 'w') as hf:
  hf.create_dataset("QuickDraw", data=x_train)
with h5py.File('y_train.h5', 'w') as hf:
  hf.create_dataset("QuickDraw", data=y_train)
with h5py.File('x_test.h5', 'w') as hf:
  hf.create_dataset("QuickDraw", data=x_test)
with h5py.File('y_test.h5', 'w') as hf:
  hf.create_dataset("QuickDraw", data=y_test)

请注意,加载数据集时传递给h5py.File()方法的数据集名称必须与使用h5py.File.create_dataset()方法保存数据集时使用的名称相同:

代码语言:javascript复制
import h5py
hf = h5py.File('x_train.h5', 'r')
x_train = np.array(hf["QuickDraw"][:])
hf = h5py.File('x_test.h5', 'r')
x_test = np.array(hf["QuickDraw"][:])
hf = h5py.File('y_train.h5', 'r')
y_train = np.array(hf["QuickDraw"][:])
hf = h5py.File('y_test.h5', 'r')
y_test = np.array(hf["QuickDraw"][:])

使用预训练的模型进行加载和推断

经过训练的模型'QDrawModel.h5'已运行 25 个周期,并且达到了 90% 以上的测试准确率,已保存在存储库中。 您已经看过此代码; 为方便起见,在此复制。

因此,重申一下,您可以使用以下代码加载此经过训练的模型:

代码语言:javascript复制
from keras.models import load_model
model = load_model('./QDrawModel.h5')
model.summary()

同样,可以使用以下代码加载训练/测试数据:

代码语言:javascript复制
import h5py
import numpy as np
hf = h5py.File('x_train.h5', 'r')
x_train = np.array(hf["QuickDraw"][:])
hf = h5py.File('x_test.h5', 'r')
x_test = np.array(hf["QuickDraw"][:])
hf = h5py.File('y_train.h5', 'r')
y_train = np.array(hf["QuickDraw"][:])
hf = h5py.File('y_test.h5', 'r')
y_test = np.array(hf["QuickDraw"][:])

再次重申,我们可以使用以下代码获取标签(我们看到的标签对应于图像文件名):

代码语言:javascript复制
from os import walk
import os
data_path = "data_files/" # folder for image files
for (dirpath, dirnames, filenames) in walk(data_path):
  pass # filenames accumulate in list 'filenames'
labels = [os.path.splitext(file)[0] for file in filenames]
print(labels)

然后,可以通过以下代码使用我们加载的模型进行推理。 请注意,如果有必要,这还将演示如何强制在 CPU 上进行计算:

代码语言:javascript复制
import tensorflow as tf
with tf.device('/cpu:0'):
     for i in range(10):
         t = np.random.randint(len(x_test) )
         x1= x_test[t]
         x1 = x1.reshape(1,28,28,1) 
         p = model.predict(x1)
         y1 = y_test[t]
         print("-------------------------")
         print(labels[np.argmax([p])])
         print(labels[y1]) 
         print("-------------------------")

使用 TensorFlow 的 CIFAR 10 图像分类

在第二部分中,我们将研究训练模型以识别 CIFAR10 图像数据集中的图像。 这将使我们有机会举例说明顺序模型创建的稍有不同的风格。

介绍

具有 10 个类别的 CIFAR 10 图像数据集是 8000 万个微型图像数据集的标记子集。 这些图像由 Alex Krizhevsky,Vinod Nair 和 Geoffrey Hinton 收集。 有关此数据集的完整详细信息,请访问这里。

在 10 个类别中,总共有 60,000 个32 x 32彩色图像,包括 50,000 个训练图像和 10,000 个测试图像。

类别如下:

代码语言:javascript复制
labels = ['airplane','automobile','bird','cat','deer','dog','frog','horse','ship','truck']

以下是这些类别的图像的一些示例:

[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-Ig4X2Gp6-1681568242386)(https://gitcode.net/apachecn/apachecn-dl-zh/-/raw/master/docs/tf-20-quick-start-guide/img/477a48fd-96b7-4311-a7ac-a7760a1626da.png)]

应用

首先,以下是设置所需的导入:

代码语言:javascript复制
import tensorflow as tf
import numpy as np
from tensorflow.keras.datasets import cifar10
from tensorflow.keras.preprocessing.image import ImageDataGenerator
from tensorflow.keras.models import Sequential
from tensorflow.keras.layers import Dense, Dropout, Activation, Flatten
from tensorflow.keras.layers import Conv2D, MaxPooling2D,BatchNormalization
from tensorflow.keras import regularizers
from tensorflow.keras.models import load_model
import os
from matplotlib import pyplot as plt
from PIL import Image

您可能需要运行pip install PIL

接下来,我们将在其余的代码中使用一组值:

代码语言:javascript复制
batch_size = 32
number_of_classes = 10
epochs = 100 # for testing; use epochs = 100 for training ~30 secs/epoch on CPU
weight_decay = 1e-4
save_dir = os.path.join(os.getcwd(), 'saved_models')
model_name = 'keras_cifar10_trained_model.h5'
number_of_images = 5

然后,我们可以加载并查看数据的形状:

代码语言:javascript复制
(x_train, y_train), (x_test, y_test) = cifar10.load_data()
print('x_train shape:', x_train.shape)
print(x_train.shape[0], 'train samples')
print(x_test.shape[0], 'test samples')

这将产生预期的输出:

代码语言:javascript复制
x_train shape: (50000, 32, 32, 3) 50000 train samples 10000 test samples

现在,我们有了一个显示图像子集的函数。 这将在网格中显示它们:

代码语言:javascript复制
def show_images(images):
    plt.figure(1)
    image_index = 0
    for i in range(0,number_of_images):
        for j in range(0,number_of_images):
            plt.subplot2grid((number_of_images, number_of_images),(i,j))
            plt.imshow(Image.fromarray(images[image_index]))
            image_index  =1
            plt.gca().axes.get_yaxis().set_visible(False)
            plt.gca().axes.get_xaxis().set_visible(False) 
    plt.show()

让我们执行以下函数的调用:

代码语言:javascript复制
show_images(x_test[:number_of_images*number_of_images])

这给我们以下输出:

[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-C1Ad1Gkp-1681568242387)(https://gitcode.net/apachecn/apachecn-dl-zh/-/raw/master/docs/tf-20-quick-start-guide/img/b2b5bc7d-7dd7-4644-9c34-cf2222dcab0d.png)]

请注意,图像在原始数据集中故意很小。

现在,我们可以将图像投射到浮动对象上,并将其范围更改为[0, 1]

代码语言:javascript复制
x_train = x_train.astype('float32')/255
x_test = x_test.astype('float32')/255

如果将标签作为一站式向量提供,则最好了解它们,因此,我们现在将这样做:

代码语言:javascript复制
y_train = tf.keras.utils.to_categorical(y_train, number_of_classes) # or use tf.one_hot()
y_test = tf.keras.utils.to_categorical(y_test, number_of_classes)

接下来,我们可以指定模型的架构。 请注意,与之前的操作相比,我们使用的激活指定方法略有不同:

代码语言:javascript复制
model.add(Activation('elu'))

elu激活函数代表指数线性单元。 在这个页面中有很好的描述。

注意,我们正在使用具有卷积层,BatchNormalization和 MaxPooling 层的顺序模型。 倒数第二层使结构变平,最后一层使用 softmax 激活,因此我们预测的类将显示为具有最高激活的输出神经元:

代码语言:javascript复制
model = Sequential()
model.add(Conv2D(32, (3,3), padding='same', kernel_regularizer=regularizers.l2(weight_decay), input_shape=x_train.shape[1:]))
model.add(Activation('elu'))
model.add(BatchNormalization())
model.add(Conv2D(32, (3,3), padding='same', kernel_regularizer=regularizers.l2(weight_decay)))
model.add(Activation('elu'))
model.add(BatchNormalization())
model.add(MaxPooling2D(pool_size=(2,2)))
model.add(Dropout(0.2))

model.add(Conv2D(64, (3,3), padding='same', kernel_regularizer=regularizers.l2(weight_decay)))
model.add(Activation('elu'))
model.add(BatchNormalization())
model.add(Conv2D(64, (3,3), padding='same', kernel_regularizer=regularizers.l2(weight_decay)))
model.add(Activation('elu'))
model.add(BatchNormalization())
model.add(MaxPooling2D(pool_size=(2,2)))
model.add(Dropout(0.3))

model.add(Conv2D(128, (3,3), padding='same', kernel_regularizer=regularizers.l2(weight_decay)))
model.add(Activation('elu'))
model.add(BatchNormalization())
model.add(Conv2D(128, (3,3), padding='same', kernel_regularizer=regularizers.l2(weight_decay)))
model.add(Activation('elu'))
model.add(BatchNormalization())
model.add(MaxPooling2D(pool_size=(2,2)))
model.add(Dropout(0.4))

model.add(Flatten())
model.add(Dense(number_of_classes, activation='softmax'))

接下来,我们必须定义我们的优化器; RMSpropdecay是每次更新后学习率降低的速度:

代码语言:javascript复制
opt = tf.keras.optimizers.RMSprop(lr=0.0001, decay=decay)

现在,我们将编译我们的模型:

代码语言:javascript复制
model.compile(loss='categorical_crossentropy', optimizer=opt,metrics=['accuracy'])

为了帮助模型学习和推广,我们将实现实时数据增强。

这是通过ImageDataGenerator()函数完成的。 其签名如下:

代码语言:javascript复制
keras.preprocessing.image.ImageDataGenerator(featurewise_center=False, samplewise_center=False, featurewise_std_normalization=False, samplewise_std_normalization=False, zca_whitening=False, zca_epsilon=1e-06, rotation_range=0, width_shift_range=0.0, height_shift_range=0.0, brightness_range=None, shear_range=0.0, zoom_range=0.0, channel_shift_range=0.0, fill_mode='nearest', cval=0.0, horizontal_flip=False, vertical_flip=False, rescale=None, preprocessing_function=None, data_format=None, validation_split=0.0, dtype=None)

但是,我们将主要使用前面签名中所示的默认值。 数据将分批循环。

这是对图像应用各种转换,例如水平翻转,高度偏移,宽度偏移,旋转等。 我们将使用以下代码进行演示:

代码语言:javascript复制
 # This will do preprocessing and real-time data augmentation:
datagen = ImageDataGenerator(
 rotation_range=10, # randomly rotate images in the range 0 to 10 degrees

 width_shift_range=0.1,# randomly shift images horizontally (fraction of total width)

 height_shift_range=0.1,# randomly shift images vertically (fraction of total height)

 horizontal_flip=True, # randomly flip images

 validation_split=0.1)

我们还将建立一个回调,以便如果模型的准确率停止提高,训练将停止,并且将为模型恢复最佳权重。

EarlyStopping回调的签名如下:

代码语言:javascript复制
keras.callbacks.EarlyStopping(monitor='val_loss', min_delta=0, patience=0, verbose=0, mode='auto', baseline=None, restore_best_weights=False)

Monitor是要跟踪的数量,min_delta是被算作改进的跟踪数量的最小变化,patience是没有变化的周期数,之后将停止训练,而mode 是[‘min’,‘max’,‘auto’]之一,它分别确定所跟踪的值是应该减少还是增加,或者分别从其名称中确定。 baseline是要达到的跟踪值的值,而restore_best_weights确定是否应恢复最佳周期的模型权重(如果使用false,则使用最新权重)。

我们将有以下代码:

代码语言:javascript复制
callback = tf.keras.callbacks.EarlyStopping(monitor='accuracy', min_delta=0, patience=1, verbose=1,mode='max', restore_best_weights=True) 

现在,我们可以训练模型了。 fit.generator()函数用于根据flow()生成器批量显示的数据训练模型。 可以在这个页面中找到更多详细信息:

代码语言:javascript复制
model.fit_generator(datagen.flow(x_train, y_train, batch_size=batch_size), epochs=epochs, callbacks=[callback])

让我们保存模型,以便以后可以重新加载它:

代码语言:javascript复制
if not os.path.isdir(save_dir):
  os.makedirs(save_dir)

model_path = os.path.join(save_dir, model_name)
model.save(model_path)
print('Model saved at: %s ' % model_path)

现在让我们重新加载它:

代码语言:javascript复制
model1 = tf.keras.models.load_model(model_path)
model1.summary()

最后,让我们看看我们的模型在测试集上的表现如何。 我们需要重新加载数据,因为它已被损坏:

代码语言:javascript复制
(x_train, y_train), (x_test, y_test) = cifar10.load_data()
show_images(x_test[:num_images*num_images])
x_test = x_test.astype('float32')/255

这里又是标签:

代码语言:javascript复制
labels = ['airplane','automobile','bird','cat','deer','dog','frog','horse','ship','truck']

最后,我们可以检查预测的标签:

代码语言:javascript复制
indices = tf.argmax(input=model1.predict(x_test[:number_of_images*number_of_images]),axis=1)
i = 0
print('Learned t True')
print('=====================')
for index in indices:
    print(labels[index], 't', labels[y_test[i][0]])
    i =1

在一次运行中,提前停止开始了 43 个周期,测试准确率为 81.4%,并且前 25 张图像的测试结果如下:

代码语言:javascript复制
Learned  True
=====================
cat      cat
ship     ship
ship     ship
ship     airplane
frog     frog
frog     frog
automobile       automobile
frog     frog
cat      cat
automobile       automobile
airplane         airplane
truck    truck
dog      dog
horse    horse
truck    truck
ship     ship
dog      dog
horse    horse
ship     ship
frog     frog
horse    horse
airplane         airplane
deer     deer
truck    truck
deer     dog

可以通过进一步调整模型架构和超参数(例如学习率)来提高此准确率。

到此结束了我们对 CIFAR 10 图像数据集的了解。

总结

本章分为两个部分。 在第一部分中,我们研究了来自 Google 的数据集 QuickDraw。 我们介绍了它,然后看到了如何将其加载到内存中。 这很简单,因为 Google 善意地将数据集作为一组.npy文件提供,这些文件可以直接加载到 NumPy 数组中。 接下来,我们将数据分为训练,验证和测试集。 创建ConvNet模型后,我们对数据进行了训练并进行了测试。 在测试中,经过 25 个周期,该模型的准确率刚好超过 90%,我们注意到,通过进一步调整模型,可能会改善这一精度。 最后,我们看到了如何保存经过训练的模型,然后如何重新加载它并将其用于进一步的推断。

在第二部分中,我们训练了一个模型来识别 CIFAR 10 图像数据集中的图像。 该数据集包含 10 类图像,是用于测试体系结构和进行超参数研究的流行数据集。 我们的准确率刚刚超过 81%。

在下一章中,我们将研究神经风格迁移,其中涉及获取一个图像的内容并将第二个图像的风格强加于其上,以生成第三个混合图像。

七、TensorFlow 2 和神经风格迁移

神经风格迁移是一种使用神经网络将一幅图像的艺术风格施加到另一幅图像的内容上的技术,因此最终得到的是两种图像的混合体。 您开始使用的图像称为内容图像。 您在内容图像上加上风格的图像称为风格参考图像。 Google 将转换后的图像称为输入图像,这似乎令人困惑(输入是从两个不同来源获取输入的意思); 让我们将其称为混合图像。 因此,混合图像是具有风格参考图像风格的内容图像。

神经风格迁移通过定义两个损失函数来工作-一个描述两个图像的内容之间的差异,另一个描述两个图像之间的风格差异。

为了开始该过程,用内容图像初始化混合图像。 然后,使用反向传播将内容和内容以及混合图像的风格之间的差异(也称为损失或距离)最小化。 这将创建具有风格参考图像风格和内容图像内容的新图像(即混合图像)。

此过程中涉及一些技术-使用函数式 API,使用预训练的模型及其特征图以及使用自定义训练循环以最小化loss函数。 我们将在下面的代码中满足所有这些要求。

要充分利用该技术,有两个先决条件-Gatys 等人在 2015 年发表的原始论文虽非必要,但确实可以解释该技术。 技术非常好,因此非常有必要了解如何通过梯度下降来减少损失。

我们将使用 VGG19 架构中的特征层(已在著名的 ImageNet 数据集上进行了训练,其中包含 1400 万张图像和 1000 个类别)。

我们将检查的代码源自 Google 提供的代码; 它使用了急切的执行程序,我们当然不需要编写代码,因为它是 TensorFlow 2 中的默认代码。该代码在 GPU 上运行得更快,但在耐心等待的情况下仍可以在 CPU 上合理的时间内进行训练。

在本章中,我们将介绍以下主题:

  • 配置导入
  • 预处理图像
  • 查看原始图像
  • 使用 VGG19 架构
  • 建立模型
  • 计算损失
  • 执行风格迁移

配置导入

要对您自己的图像使用此实现,您需要将这些图像保存在下载的存储库的./tmp/nst目录中,然后编辑content_pathstyle_path路径,如以下代码所示。

与往常一样,我们要做的第一件事是导入(并配置)所需的模块:

代码语言:javascript复制
import numpy as np
from PIL import Image
import time
import functools

import matplotlib.pyplot as plt
import matplotlib as mpl
# set things up for images display
mpl.rcParams['figure.figsize'] = (10,10)
mpl.rcParams['axes.grid'] = False

您可能需要pip install pillow,这是 PIL 的分支。 接下来是 TensorFlow 模块:

代码语言:javascript复制
import tensorflow as tf

from tensorflow.keras.preprocessing import image as kp_image
from tensorflow.keras import models
from tensorflow.keras import losses
from tensorflow.keras import layers
from tensorflow.keras import backend as K
from tensorflow.keras import optimizers

这是我们最初将使用的两个图像:

代码语言:javascript复制
content_path = './tmp/nst/elephant.jpg'#Andrew Shiva / Wikipedia / CC BY-SA 4.0
style_path = './tmp/nst/zebra.jpg' # zebra:Yathin S Krishnappa, https://creativecommons.org/licenses/by-sa/4.0/deed.en

预处理图像

下一个函数只需稍作预处理即可加载图像。 Image.open()是所谓的惰性操作。 该函数找到文件并将其打开以进行读取,但是实际上直到从您尝试对其进行处理或加载数据以来,才从文件中读取图像数据。 下一组三行会调整图像的大小,以便任一方向的最大尺寸为 512(max_dimension)像素。 例如,如果图像为1,024 x 768,则scale将为 0.5(512 / 1,024),并且这将应用于图像的两个尺寸,从而将图像大小调整为512 x 384Image.ANTIALIAS参数保留最佳图像质量。 接下来,使用img_to_array()调用(tensorflow.keras.preprocessing的方法)将 PIL 图像转换为 NumPy 数组。

最后,为了与以后的使用兼容,图像需要沿零轴的批次尺寸(由于图像是彩色的,因此共给出了四个尺寸)。 这可以通过调用np.expand_dims()实现:

代码语言:javascript复制
def load_image(path_to_image):
    max_dimension = 512
    image = Image.open(path_to_image)
    longest_side = max(image.size)
    scale = max_dimension/longest_side
    image = image.resize((round(image.size[0]*scale), round(image.size[1]*scale)), Image.ANTIALIAS)

    image = kp_image.img_to_array(image) # keras preprocessing

    # Broadcast the image array so that it has a batch dimension on axis 0
    image = np.expand_dims(image, axis=0)
    return image

下一个函数显示已由load_image()预处理过的图像。 由于我们不需要额外的尺寸来显示,因此可以通过调用np.squeeze()将其删除。 之后,根据对plt.imshow()的调用(后面带有可选标题)的要求,将图像数据中的值转换为无符号的 8 位整数:

代码语言:javascript复制
def show_image(image, title=None):
  # Remove the batch dimension from the image
    image1 = np.squeeze(image, axis=0)
  # Normalize the image for display 
    image1 = image1.astype('uint8')
    plt.imshow(image1)
    if title is not None:
        plt.title(title)
    plt.imshow(image1)

查看原始图像

接下来,我们使用对前面两个函数的调用来显示内容和风格图像,请记住图像像素必须是无符号 8 位整数类型。 plt.subplot(1,2,1)函数意味着在位置 1 使用一排两列的网格; plt.subplot(1,2,2)表示在位置 2 使用一排两列的网格:

代码语言:javascript复制
channel_means = [103.939, 116.779, 123.68] # means of the BGR channels, for VGG processing

plt.figure(figsize=(10,10))

content_image = load_image(content_path).astype('uint8')
style_image = load_image(style_path).astype('uint8')

plt.subplot(1, 2, 1)
show_image(content_image, 'Content Image')

plt.subplot(1, 2, 2)
show_image(style_image, 'Style Image')

plt.show()

输出显示在以下屏幕截图中:

[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-nBLqDvBd-1681568242387)(https://gitcode.net/apachecn/apachecn-dl-zh/-/raw/master/docs/tf-20-quick-start-guide/img/c2cdcfbd-80c8-4aca-bc8d-a0fbf9cab5ce.png)]

接下来是加载图像的函数。 正如我们将要提到的那样,在经过训练的vgg19模型中,我们需要相应地预处理图像数据。

tf.keras模块为我们提供了执行此操作的方法。 这里的预处理将我们的 RGB 彩色图像翻转为 BGR:

代码语言:javascript复制
def load_and_process_image(path_to_image):
  image = load_image(path_to_image)
  image = tf.keras.applications.vgg19.preprocess_input(image)
  return image

为了显示我们的图像,我们需要一个函数来获取用load_and_process_image处理的数据,并将图像数据返回到其原始状态。 这必须手动完成。

首先,我们检查图像的尺寸是否正确,如果不是 3 或 4,则会引发错误。

预处理从每个通道中减去其平均值,因此通道的平均值为零。 减去的值来自 ImageNet 分析,其中 BGR 通道的均值分别为103.939116.779123.68

因此,接下来,我们将这些值添加回 BGR(彩色)通道以恢复原始值,然后将 BGR 序列翻转回 RGB。

最后,对于此函数,我们需要确保我们的值是无符号的 8 位整数,其值在 0 到 255 之间; 这可以通过np.clip()函数实现:

代码语言:javascript复制
def deprocess_image(processed_image):
  im = processed_image.copy()
  if len(im.shape) == 4:
    im = np.squeeze(im, 0)
  assert len(im.shape) == 3, ("Input to deprocess image must be an image of "
                             "dimension [1, height, width, channel] or [height, width, channel]")
  if len(im.shape) != 3:
    raise ValueError("Invalid input to deprocessing image")

  # the inverse of the preprocessing step
  im[:, :, 0]  = channel_means[0] # these are the means subtracted by the preprocessing step
  im[:, :, 1]  = channel_means[1]
  im[:, :, 2]  = channel_means[2]
  im= im[:, :, ::-1] # channel last

  im = np.clip(im, 0, 255).astype('uint8')
  return im

使用 VGG19 架构

了解下一个代码片段的最好方法是查看 VGG19 架构。 这是一个好地方(大约位于页面的一半)。

在这里,您将看到 VGG19 是一个相当简单的体系结构,由卷积层的块组成,每个块的末尾都有一个最大池化层。

对于内容层,我们使用block5中的第二个卷积层。 之所以使用这个最高的块,是因为较早的块具有更能代表单个像素的特征图。 网络中的高层会根据对象及其在输入图像中的排列来捕获高级内容,但不会限制重建的实际精确像素值。

对于风格层,我们将在每个层块中使用第一个卷积层,即block1_conv1block5_conv5

然后保存内容和风格层的长度,以供以后使用:

代码语言:javascript复制
# The feature maps are obtained from this content layer
content_layers = ['block5_conv2']

# Style layers we need
style_layers = ['block1_conv1',
                'block2_conv1',
                'block3_conv1',
                'block4_conv1',
                'block5_conv1'
               ]

number_of_content_layers = len(content_layers)
number_of_style_layers = len(style_layers)

建立模型

现在,接下来是一系列函数,这些函数最终导致执行风格迁移(run_style_transfer())的主要函数。

此序列中的第一个函数get_model()创建我们将要使用的模型。

它首先加载训练后的vgg_model(已在ImageNet上进行训练),而没有其分类层(include_top=False)。 接下来,它冻结加载的模型(vgg_model.trainable = False)。

然后,使用列表推导获取风格和内容层的输出值,该列表推导遍历我们在上一节中指定的层的名称。

然后将这些输出值与 VGG 输入一起使用,以创建可以访问 VGG 层的新模型,即get_model()返回 Keras 模型,该模型输出已训练的 VGG19 模型的风格和内容中间层。 不必使用顶层,因为这是 VGG19 中的最终分类层,我们将不再使用。

我们将创建一个输出图像,以使输出和相应特征层上的输入/风格之间的距离(差异)最小化:

代码语言:javascript复制
def get_model():
 vgg_model = tf.keras.applications.vgg19.VGG19(include_top=False, weights='imagenet')
 vgg_model.trainable = False

 # Acquire the output layers corresponding to the style layers and the content layers 
 style_outputs = [vgg_model.get_layer(name).output for name in style_layers]
 content_outputs = [vgg_model.get_layer(name).output for name in content_layers]
 model_outputs = style_outputs   content_outputs

# Build model 
 return models.Model(vgg_model.input, model_outputs)

计算损失

现在,我们需要两个图像的内容和风格之间的损失。 我们将使用均方损失如下。 请注意,image1 - image2中的减法是两个图像数组之间逐元素的。 此减法有效,因为图像已在load_image中调整为相同大小:

代码语言:javascript复制
def rms_loss(image1,image2):
    loss = tf.reduce_mean(input_tensor=tf.square(image1 - image2))
    return loss

接下来,我们定义content_loss函数。 这只是函数签名中contenttarget之间的均方差:

代码语言:javascript复制
def content_loss(content, target):
  return rms_loss(content, target)

风格损失是根据称为 Gram 矩阵的数量定义的。 Gram 矩阵(也称为度量)是风格矩阵及其自身的转置的点积。 因为这意味着图像矩阵的每一列都与每一行相乘,所以我们可以认为原始表示中包含的空间信息已经分配。 结果是有关图像的非本地化信息,例如纹理,形状和权重,即其风格。

产生gram_matrix的代码如下:

代码语言:javascript复制
def gram_matrix(input_tensor):
  channels = int(input_tensor.shape[-1]) # channels is last dimension
  tensor = tf.reshape(input_tensor, [-1, channels]) # Make the image channels first 
  number_of_channels = tf.shape(input=tensor)[0] # number of channels
  gram = tf.matmul(tensor, tensor, transpose_a=True) # produce tensorT*tensor
  return gram / tf.cast(number_of_channels, tf.float32) # scaled by the number of channels.

因此,风格损失(其中gram_target将是混合图像上风格激活的 Gram 矩阵)如下:

代码语言:javascript复制
def style_loss(style, gram_target):
  gram_style = gram_matrix(style)
  return rms_loss(gram_style, gram_target) 

接下来,我们通过获取content_imagestyle_image并将它们馈入模型来找到content_featuresstyle_features表示形式。 此代码分为两个块,一个用于content_features,另一个用于style_features。 对于内容块,我们加载图像,在其上调用我们的模型,最后,提取先前分配的特征层。 style_features的代码是相同的,除了我们首先加载风格图像:

代码语言:javascript复制
def get_feature_representations(model, content_path, style_path):
  #Function to compute content and style feature representations.

  content_image = load_and_process_image(content_path)
  content_outputs = model(content_image)
  #content_features = [content_layer[0] for content_layer in content_outputs[:number_of_content_layers]]
  content_features = [content_layer[0] for content_layer in content_outputs[number_of_style_layers:]]

  style_image = load_and_process_image(style_path)
  style_outputs = model(style_image)
  style_features = [style_layer[0] for style_layer in style_outputs[:number_of_style_layers]]

  return style_features, content_features

接下来,我们需要计算总损失。 查看该方法的签名,我们可以看到,首先,我们传入模型(可以访问 VGG19 的中间层)。 接下来,进入loss_weights,它们是每个损失函数(content_weightstyle_weight和总变化权重)的每个贡献的权重。 然后,我们有了初始图像,即我们正在通过优化过程更新的图像。 接下来是gram_style_featurescontent_features,分别对应于我们正在使用的风格层和内容层。

首先从方法签名中复制风格和content_weight。 然后,在我们的初始图像上调用模型。 我们的模型可以直接调用,因为我们使用的是急切执行,如我们所见,这是 TensorFlow 2 中的默认执行。此调用返回所有模型输出值。

然后,我们有两个类似的块,一个块用于内容,一个块用于风格。 对于第一个(内容)块,获取我们所需层中的内容和风格表示。 接下来,我们累积来自所有内容损失层的内容损失,其中每一层的贡献均被加权。

第二个块与第一个块相似,不同之处在于,这里我们累积来自所有风格损失层的风格损失,其中每个损失层的每个贡献均被平均加权。

最后,该函数返回总损失,风格损失和内容损失,如以下代码所示:

代码语言:javascript复制
def compute_total_loss(model, loss_weights, init_image, gram_style_features, content_features):

   style_weight, content_weight = loss_weights
   model_outputs = model(init_image)

   content_score = 0
   content_output_features = model_outputs[number_of_style_layers:] 
   weight_per_content_layer = 1.0 / float(number_of_content_layers)
   for target_content, comb_content in zip(content_features, content_output_features):
      content_score  = weight_per_content_layer*content_loss(comb_content[0], target_content)
   content_score *= content_weight

   style_score = 0
   style_output_features = model_outputs[:number_of_style_layers]
   weight_per_style_layer = 1.0 / float(number_of_style_layers)
   for target_style, comb_style in zip(gram_style_features, style_output_features):
     style_score  = weight_per_style_layer *style_loss(comb_style[0], target_style)
   style_score ***= style_weight

 total_loss = style_score   content_score
 return total_loss, style_score, content_score

接下来,我们有一个计算梯度的函数:

代码语言:javascript复制
def compute_grads(config):
   with tf.GradientTape() as tape: 
      all_loss = compute_total_loss(**config)
    # Compute gradients wrt input image
  total_loss = all_loss[0]
  return tape.gradient(total_loss, config['init_image']), all_loss

import IPython.display

执行风格迁移

执行style_transfer的函数很长,因此我们将分节介绍。 其签名如下:

代码语言:javascript复制
def run_style_transfer(content_path,
                       style_path,
                       number_of_iterations=1000,
                       content_weight=1e3,
                       style_weight=1e-2):

由于我们实际上不想训练模型中的任何层,因此只需使用如前所述的层的输出值即可。 我们相应地设置其可训练属性:

代码语言:javascript复制
model = get_model() 
for layer in model.layers:
  layer.trainable = False

接下来,我们使用先前定义的函数从模型的各层获得style_featurescontent_features表示形式:

代码语言:javascript复制
style_features, content_features = get_feature_representations(model, content_path, style_path)

gram_style_features使用style_features上的循环,如下所示:

代码语言:javascript复制
gram_style_features = [gram_matrix(style_feature) for style_feature in style_features]

接下来,我们通过加载内容图像并将其转换为张量,来初始化将成为算法输出的图像,即混合图像(也称为 Pastiche 图像):

代码语言:javascript复制
initial_image = load_and_process_image(content_path)
initial_image = tf.Variable(initial_image, dtype=tf.float32)

下一行定义所需的AdamOptimizer函数:

代码语言:javascript复制
optimizer = tf.compat.v1.train.AdamOptimizer(learning_rate=5, beta1=0.99, epsilon=1e-1)

我们将继续保存best_imagebest_loss,因此请初始化变量以存储它们:

代码语言:javascript复制
 best_loss, best_image = float('inf'), None

接下来,我们设置将被传递到compute_grads()函数的配置值字典:

代码语言:javascript复制
loss_weights = (style_weight, content_weight)
  config = {
      'model': model,
      'loss_weights': loss_weights,
      'init_image': initial_image,
      'gram_style_features': gram_style_features,
      'content_features': content_features
  }

这是显示常量:

代码语言:javascript复制
number_rows = 2
number_cols = 5
display_interval = number_of_iterations/(number_rows*number_cols)

接下来,我们计算图像边界,如下所示:

代码语言:javascript复制
norm_means = np.array(channel_means)
minimum_vals = -norm_means
maximum_vals = 255 - norm_means 

此列表将存储混合图像:

代码语言:javascript复制
images = []

接下来,我们开始主图像处理循环,如下所示:

代码语言:javascript复制
for i in range(number_of_iterations):

因此,接下来我们计算梯度,计算损失,调用优化器以应用梯度,并将图像裁剪到我们先前计算的边界:

代码语言:javascript复制
   grads, all_loss = compute_grads(config)
   loss, style_score, content_score = all_loss
   optimizer.apply_gradients([(grads, initial_image)])
   clipped_image = tf.clip_by_value(initial_image, minimum_vals, maximum_vals)
   initial_image.assign(clipped_image)

我们将继续保存best_lossbest_image,因此下一步:

代码语言:javascript复制
 if loss < best_loss:
 # Update best loss and best image from total loss. 
   best_loss = loss
   best_image = deprocess_image(initial_image.numpy()

然后,我们有条件地保存混合图像(总共 10 张图像),并将其与训练指标一起显示:

代码语言:javascript复制
if i % display_interval== 0:
  # Use the .numpy() method to get the numpy image array, needs eager execution
  plot_image = initial_image.numpy()
  plot_image = deprocess_image(plot_image)
  images.append(plot_image)
  IPython.display.clear_output(wait=True)
  IPython.display.display_png(Image.fromarray(plot_image))
  print('Iteration: {}'.format(i)) 
  print('Total loss: {:.4e}, ' 
        'style loss: {:.4e}, '
        'content loss: {:.4e} '
        .format(loss, style_score, content_score))

最后,对于此函数,我们显示所有best_imagebest_loss

代码语言:javascript复制
 IPython.display.clear_output(wait=True)
 plt.figure(figsize=(14,4))
 for i,image in enumerate(images):
   plt.subplot(number_rows,number_cols,i 1)
   plt.imshow(image)
   plt.xticks([])
   plt.yticks([])

 return best_image, best_loss

接下来,我们调用前面的函数来获取best_imagebest_loss(还将显示 10 张图像):

[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-HXgr5GW0-1681568242388)(https://gitcode.net/apachecn/apachecn-dl-zh/-/raw/master/docs/tf-20-quick-start-guide/img/b5dbdb99-d9bd-49a3-94ff-1bf19b9e814b.png)]

的代码如下:

代码语言:javascript复制
best_image, best_loss = run_style_transfer(content_path, style_path, number_of_iterations=100)
Image.fromarray(best_image)

以下是best_image的显示:

[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-FFvgqWEB-1681568242388)(https://gitcode.net/apachecn/apachecn-dl-zh/-/raw/master/docs/tf-20-quick-start-guide/img/a98dd3a7-6f07-43a7-a4e1-71a4d002cc40.png)]

最终展示

最后,我们有一个函数,它与best_image一起显示内容和风格图像:

代码语言:javascript复制
def show_results(best_image, content_path, style_path, show_large_final=True):
 plt.figure(figsize=(10, 5))
  content = load_image(content_path)
  style = load_image(style_path)

  plt.subplot(1, 2, 1)
  show_image(content, 'Content Image')

  plt.subplot(1, 2, 2)
  show_image(style, 'Style Image')

  if show_large_final:
    plt.figure(figsize=(10, 10))

    plt.imshow(best_image)
    plt.title('Output Image')
    plt.show()

接下来是对该函数的调用,如下所示:

代码语言:javascript复制
show_results(best_image, content_path, style_path)

总结

到此结束我们对神经风格迁移的研究。 我们看到了如何拍摄内容图像和风格图像并生成混合图像。 我们使用训练有素的 VGG19 模型中的层来完成此任务。

在下一章中,我们将研究循环神经网络。 这些网络可以处理顺序的输入值,并且输入值和输出值中的一个或两个具有可变长度。

八、TensorFlow 2 和循环神经网络

包括卷积网络(CNN)在内的许多神经网络体系结构的主要缺点之一是它们不允许处理顺序数据。 换句话说,一个完整的特征(例如图像)必须一次全部呈现。 因此,输入是固定长度张量,而输出必须是固定长度张量。 先前特征的输出值也不会以任何方式影响当前特征。 同样,所有输入值(和输出值)都应视为彼此独立。 例如,在我们的fashion_mnist模型(第 4 章“使用 TensorFlow 2的监督机器学习”)中,每个输入时尚图像都独立于并且完全不了解先前图像。

循环神经网络RNN)克服了这个问题,并使许多新的应用成为可能。

在本章中,我们将研究以下主题:

  • 神经网络处理模式
  • 循环架构
  • RNN 的应用
  • 我们的 RNN 示例的代码
  • 建立并实例化我们的模型
  • 训练和使用我们的模型

神经网络处理模式

下图说明了各种神经网络处理模式:

[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-aK6d9UUU-1681568242389)(https://gitcode.net/apachecn/apachecn-dl-zh/-/raw/master/docs/tf-20-quick-start-guide/img/8e01e880-e868-421b-a5a7-0a152e8795ed.png)]

矩形代表张量,箭头代表函数,红色是输入,蓝色是输出,绿色是张量状态。

从左到右,我们有以下内容:

  • 普通前馈网络,固定尺寸的输入和固定尺寸的输出,例如图像分类
  • 序列输出,例如,拍摄一张图像并输出一组用于标识图像中项目的单词的图像字幕
  • 序列输入,例如情感识别(如我们的 IMDb 应用),其中句子被分为正面情感或负面情感
  • 序列输入和输出,例如机器翻译,其中 RNN 接受英语句子并将其翻译为法语输出
  • 逐帧同步输入和输出的序列,例如,类似于视频分类的两者

循环架构

因此,需要一种新的体系结构来处理顺序到达的数据,并且其输入值和输出值中的一个或两个具有可变长度,例如,语言翻译应用中句子中的单词。 在这种情况下,模型的输入和输出都具有不同的长度,就像之前的第四种模式一样。 同样,为了预测给定当前词的后续词,还需要知道先前的词。 这种新的神经网络架构称为 RNN,专门设计用于处理顺序数据。

出现术语循环是因为此类模型对序列的每个元素执行相同的计算,其中每个输出都依赖于先前的输出。 从理论上讲,每个输出都取决于所有先前的输出项,但实际上,RNN 仅限于回顾少量步骤。 这种布置等效于具有存储器的 RNN,该存储器可以利用先前的计算结果。

RNN 用于顺序输入值,例如时间序列,音频,视频,语音,文本,财务和天气数据。 它们在消费产品中的使用示例包括 Apple 的 Siri,Google 翻译和亚马逊的 Alexa。

将传统前馈网络与 RNN 进行比较的示意图如下:

[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-F6LRavAT-1681568242389)(https://gitcode.net/apachecn/apachecn-dl-zh/-/raw/master/docs/tf-20-quick-start-guide/img/98b325df-dafc-40e2-9825-80f9a1985632.png)]

每个 RNN 单元上的回送代表记忆。 前馈网络无法区分序列中的项目顺序,而 RNN 从根本上取决于项目的顺序。 例如,假设前馈网络接收到输入字符串aardvark:到输入为d时,网络已经忘记了先前的输入值为aar,因此无法预测下一个va。 另一方面,在给定相同输入的情况下,循环网络“记住”先前的输入值为aar,因此有可能根据其先前的训练来预测va是下一个。

RNN 的每个单独项目到网络的输入称为时间步长。 因此,例如,在字符级 RNN 中,每个字符的输入都是一个时间步。 下图说明了 RNN 的展开

时间步长从t = 0开始,输入为X[0],一直到时间步长t = t,输入为X[t],相应的输出值为h[0]h[t],如下图所示:

[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-jtG7eGKi-1681568242389)(https://gitcode.net/apachecn/apachecn-dl-zh/-/raw/master/docs/tf-20-quick-start-guide/img/b33bb6fe-e10b-4a19-be19-ab717ecda115.png)]

展开式循环神经网络

RNN 在称为沿时间反向传播BPTT)的过程中通过反向传播进行训练。 在此可以想象 RNN 的展开(也称为展开)会创建一系列神经网络,并且会针对每个时间步长计算误差并将其合并,以便可以使用反向传播更新网络中的权重。 例如,为了计算梯度,从而计算误差,在时间步t = 6时,我们将向后传播五个步,并对梯度求和。 但是,在尝试学习长期依赖关系时(即在相距很远的时间步之间),这种方法存在问题,因为梯度可能变得太小而使学习变得不可能或非常缓慢,或者它们可能变得太大并淹没了网络。 这被称为消失/爆炸梯度问题,并且已经发明了各种修改方法来解决它,包括长短期记忆LSTM)网络和门控循环单元GRU s),我们将在以后使用。

下图显示了有关展开(或展开)的更多详细信息:

[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-yiMjoKBM-1681568242389)(https://gitcode.net/apachecn/apachecn-dl-zh/-/raw/master/docs/tf-20-quick-start-guide/img/e6d1b2a6-b40b-45bf-acbc-6c6c1433d13d.png)]

循环神经网络的示意图

在该图中,我们可以看到以下内容:

  • x[t]是时间步长t的输入。 例如,x[t]可以是基于字符的 RNN 中的第十个字符,表示为来自字符集的索引。
  • s[t]是时间步t的隐藏状态,因此是网络的内存。
  • s[t]的计算公式为s[t] = f(Ux[t] Ws[t-1]),其中f是非线性函数,例如 ReLU。 UVW是权重。
  • o[t]是时间步长t的输出。 例如,如果我们要计算字符序列中的下一个字母,它将是字符集o[t] = Vs[t]的概率向量。

如前所述,我们可以将s[t]视为网络的内存,因为它包含有关网络中较早时间步长发生了什么的信息。 请注意,权重UVW在每个步骤中都是共享的,因为我们在每个步骤都执行相同的计算,只是使用不同的输入值( 结果是学习权重的数量大大减少了)。 还要注意,我们可能不需要每个时间步长的输出值(如图所示)。 如果我们要进行情感分析,每个步骤都是一个词,比如说电影评论,那么我们可能只关心最终的输出(正面或负面)。

现在,让我们看一个使用 RNN 的有趣示例,在该示例中,我们尝试以给定的写作风格创建文本。

RNN 的应用

在此应用中,我们将看到如何使用基于字符的循环神经网络创建文本。 更改要使用的文本的语料库很容易(请参见下面的示例); 在这里,我们将使用查尔斯·狄更斯(Charles Dickens)的小说《伟大的期望》。 我们将在此文本上训练网络,以便如果我们给它一个字符序列,例如thousan,它将产生序列中的下一个字符d。 此过程可以继续进行,可以通过在不断演变的序列上反复调用模型来创建更长的文本序列。

这是训练模型之前创建的文本的示例:

代码语言:javascript复制
Input: 
 'o else is there to inform?”nn“Is there no chance person who might identify you in the street?” saidn'
Next Char Predictions: 
 "dUFdZ!mig())'(ZIon“4g&HZ”@nWGWtlinnqQY*dGJ7ioU'6(vLKL&cJ29LG'lQW8n-,M!JSVy”cjN;1cHndEEeMXhtW$U8Mt&sp"

这是一些文本,其中包含Pip序列,该序列是在模型经过 0.1 个温度(请参阅下文)进行 100 个周期(约 10 秒每个)的训练后创建的:

代码语言:javascript复制
Pip; it was not to be done. I had been a little while I was a look out and the strength of considerable particular by the windows of the rest of his prospering look at the windows of the room wing and the courtyard in the morning was the first time I had been a very much being strictly under the wall of my own person to me that he had done my sister, and I went on with the street common, I should have been a very little for an air of the river by the fire. For the man who was all the time of the money. My dear Herbert, who was a little way to the marshes he had ever seemed to have had once more than once and the more was a ragged hand before I had ever seemed to have him a dreadful loveriement in his head and with a falling to the table, and I went on with his arms, I saw him ever so many times, and we all the courtyard to the fire to be so often to be on some time when I saw his shoulder as if it were a long time in the morning I was a woman and a singer at the tide was remained by the 

对于不了解语法或拼写的系统来说,这并不是一个坏结果。 这显然是荒谬的,但那时我们并不是在追求理性。 只有一个不存在的单词(loveriement)。 因此,网络已经完成了学习拼写和学习单词是文本单元的工作。 还要注意,在下面的代码中,仅在短序列(sequence_length = 100)上训练网络。

接下来,我们将查看用于设置,训练和测试循环神经网络的代码。

我们的 RNN 示例的代码

此应用基于 Google 根据 Apache 2 许可提供的应用。

像往常一样,我们会将代码分解成片段,然后将您引到存储库中获取许可证和完整的工作版本。 首先,我们有模块导入,如下所示:

代码语言:javascript复制
import tensorflow as tf
import numpy as np
import os
import time

接下来,我们有文本文件的下载链接。

您可以通过在file中指定文件名和在url中指定文件的完整 URL,轻松地将其更改为所需的任何文本:

代码语言:javascript复制
file='1400-0.txt'
url='https://www.gutenberg.org/files/1400/1400-0.txt' # Great Expectations by Charles Dickens

然后,我们为该文件设置了 Keras get_file()工具,如下所示:

代码语言:javascript复制
path = tf.keras.utils.get_file(file,url)

然后,我们打开并读取文件,并以字符为单位查看文件的长度:

代码语言:javascript复制
text = open(path).read()
print ('Length of text: {} characters'.format(len(text)))

在文件开头没有我们不需要的文本,因此我们将其剥离掉,然后再看一下前几个字符就很有帮助了,接下来我们要做:

代码语言:javascript复制
# strip off text we don't need
text = text[835:]

# Take a look at the first 300 characters in text
print(text[:300])

输出应如下所示:

代码语言:javascript复制
My father's family name being Pirrip, and my Christian name Philip, my
infant tongue could make of both names nothing longer or more explicit
than Pip. So, I called myself Pip, and came to be called Pip.

I give Pirrip as my father's family name, on the authority of his
tombstone and my sister,--Mrs

现在,让我们看一下文本中有多少个唯一字符,使用一组字符来获取它们,并按其 ASCII 码的顺序对其进行排序:

代码语言:javascript复制
# The unique characters in the file
vocabulary = sorted(set(text))
print ('{} unique characters.'.format(len(vocabulary)))

这应该提供 84 个唯一字符。

接下来,我们创建一个字典,其中字符是键,而连续的整数是值。

这样我们就可以找到索引,表示任何给定字符的数值:

代码语言:javascript复制
# Create a  dictionary of unique character keys to index values
char_to_index = {char:index for index, char in enumerate(vocabulary)}
print(char_to_index)

输出如下:

代码语言:javascript复制
{'n': 0, ' ': 1, '!': 2, '$': 3, '%': 4, '&': 5, "'": 6, '(': 7, ')': 8, '*': 9, ',': 10, '-': 11, '.': 12, '/': 13, '0': 14, '1': 15, '2': 16, '3': 17, '4': 18, '5': 19, '6': 20, '7': 21, '8': 22, '9': 23, ':': 24, ';': 25, '?': 26, '@': 27, 'A': 28, 'B': 29, 'C': 30, 'D': 31, 'E': 32, 'F': 33, 'G': 34, 'H': 35, 'I': 36, 'J': 37, 'K': 38, 'L': 39, 'M': 40, 'N': 41, 'O': 42, 'P': 43, 'Q': 44, 'R': 45, 'S': 46, 'T': 47, 'U': 48, 'V': 49, 'W': 50, 'X': 51, 'Y': 52, 'Z': 53, 'a': 54, 'b': 55, 'c': 56, 'd': 57, 'e': 58, 'f': 59, 'g': 60, 'h': 61, 'i': 62, 'j': 63, 'k': 64, 'l': 65, 'm': 66, 'n': 67, 'o': 68, 'p': 69, 'q': 70, 'r': 71, 's': 72, 't': 73, 'u': 74, 'v': 75, 'w': 76, 'x': 77, 'y': 78, 'z': 79, 'ê': 80, 'ô': 81, '“': 82, '”': 83}

我们还需要将字符存储在数组中。 这样我们就可以找到与任何给定数值对应的字符,即index

代码语言:javascript复制
index_to_char = np.array(vocabulary)
print(index_to_char)

输出如下:

代码语言:javascript复制
['n' ' ' '!' '$' '%' '&' "'" '(' ')' '*' ',' '-' '.' '/' '0' '1' '2' '3' '4' '5' '6' '7' '8' '9' ':' ';' '?' '@' 'A' 'B' 'C' 'D' 'E' 'F' 'G' 'H' 'I' 'J' 'K' 'L' 'M' 'N' 'O' 'P' 'Q' 'R' 'S' 'T' 'U' 'V' 'W' 'X' 'Y' 'Z' 'a' 'b' 'c' 'd' 'e' 'f' 'g' 'h' 'i' 'j' 'k' 'l' 'm' 'n' 'o' 'p' 'q' 'r' 's' 't' 'u' 'v' 'w' 'x' 'y' 'z' 'ê' 'ô' '“' '”']

现在,我们正在使用的整个文本已转换为我们作为字典创建的整数数组char_to_index

代码语言:javascript复制
text_as_int = np.array([char_to_index[char] for char in text]

这是字符及其索引的示例:

代码语言:javascript复制
print('{')
for char,_ in zip(char_to_index, range(20)):
    print(' {:4s}: {:3d},'.format(repr(char), char_to_index[char]))
print(' ...n}')

输出如下:

代码语言:javascript复制
{
  'n':   0,
  ' ' :   1,
  '!' :   2,
  '$' :   3,
  '%' :   4,
  '&' :   5,
  "'" :   6,
  '(' :   7,
  ')' :   8,
  '*' :   9,
  ',' :  10,
  '-' :  11,
  '.' :  12,
  '/' :  13,
  '0' :  14,
  '1' :  15,
  '2' :  16,
  '3' :  17,
  '4' :  18,
  '5' :  19,
  ...
}

接下来,查看文本如何映射为整数很有用; 这是前几个:

代码语言:javascript复制
# Show how the first 15 characters from the text are mapped to integers
print ('{} ---- characters mapped to int ---- > {}'.format(repr(text[:15]), text_as_int[:15]))

输出如下:

代码语言:javascript复制
"My father's fam" ---- characters mapped to int ---- > [40 78  1 59 54 73 61 58 71  6 72  1 59 54 66]

然后,我们设置每个输入的句子长度,并因此设置训练周期中的示例数:

代码语言:javascript复制
# The maximum length sentence we want for a single input in characters
sequence_length = 100
examples_per_epoch = len(text)//seq_length

接下来,我们创建data.Dataset以在以后的训练中使用:

代码语言:javascript复制
# Create training examples / targets
char_dataset = tf.data.Dataset.from_tensor_slices(text_as_int)
# Display , sanity check
for char in char_dataset.take(5):
  print(index_to_char[char.numpy()])

输出如下:

代码语言:javascript复制
M y f a

我们需要批量此数据以将其馈送到我们的 RNN,因此接下来我们要这样做:

代码语言:javascript复制
sequences = char_dataset.batch(sequence_length 1, drop_remainder=True)

请记住,我们已经设置了sequence_length = 100,所以批量中的字符数是 101。

现在,我们有了一个函数来创建我们的输入数据和目标数据(必需的输出)。

该函数返回我们一直在处理的文本以及相同的文本,但是一起移动了一个字符,即,如果第一个单词是Pythonsequence_length = 5,则该函数返回Pythoython

然后,我们通过连接输入和输出字符序列来创建数据集:

代码语言:javascript复制
def split_input_target(chunk):
   input_text = chunk[:-1]
   target_text = chunk[1:]
   return input_text, target_text

dataset = sequences.map(split_input_target)

接下来,我们执行另一个健全性检查。 我们使用先前创建的数据集来显示输入和目标数据。

请注意,dataset.take(n)方法从数据集中返回n批次。

在这里还请注意,由于我们已经启用了急切执行(当然,默认情况下,在 TensorFlow 2 中是这样),因此我们可以使用numpy()方法来查找张量的值:

代码语言:javascript复制
for input_example, target_example in dataset.take(1):
 print ('Input data: ', repr(''.join(index_to_char[input_example.numpy()]))) #101 characters
 print ('Target data:', repr(''.join(index_to_char[target_example.numpy()])))

输出如下:

代码语言:javascript复制
Input data: "My father's family name being Pirrip, and my Christian name Philip, myninfant tongue could make of b" Target data: "y father's family name being Pirrip, and my Christian name Philip, myninfant tongue could make of bo"

现在,我们可以通过几个步骤显示输入和预期输出:

代码语言:javascript复制
for char, (input_index, target_index) in enumerate(zip(input_example[:5], target_example[:5])):
    print("Step {:4d}".format(char))
    print(" input: {} ({:s})".format(input_index, repr(index_to_char[input_index])))
    print(" expected output: {} ({:s})".format(target_index, repr(index_to_char[target_index])))

以下是此输出:

代码语言:javascript复制
Step 0:      input: 40 ('M'),  expected output: 78 ('y') Step 1:      input: 78 ('y'),  expected output: 1 (' ') Step 2:      input: 1 (' '),  expected output: 59 ('f') Step 3:      input: 59 ('f'),  expected output: 54 ('a') Step 4:      input: 54 ('a'),  expected output: 73 ('t')

接下来,我们为训练进行设置,如下所示:

代码语言:javascript复制
# how many characters in a batch
batch = 64

# the number of training steps taken in each epoch
steps_per_epoch = examples_per_epoch//batch # note integer division

# TF data maintains a buffer in memory in which to shuffle data 
# since it is designed to work with possibly endless data
buffer = 10000

dataset = dataset.shuffle(buffer).batch(batch, drop_remainder=True)

# call repeat() on dataset so data can be re-fed into the model from the beginning
dataset = dataset.repeat()

dataset

这给出了以下数据集结构:

代码语言:javascript复制
<RepeatBatchDataset shapes: ((64, 100), (64, 100)), types: (tf.int64, tf.int64)>

此处,64是批次大小,100是序列长度。 以下是我们训练所需的一些值:

代码语言:javascript复制
# The vocabulary length in characters
vocabulary_length = len(vocabulary)

# The embedding dimension 
embedding_dimension = 256

# The number of recurrent neural network units
recurrent_nn_units = 1024

我们正在使用 GRU,在 CUDA 深度神经网络cuDNN)库中,如果代码在 GPU 上运行,则可以使用这些例程进行快速计算。 GRU 是在 RNN 中实现内存的一种方式。 下一节将实现此想法,如下所示:

代码语言:javascript复制
if tf.test.is_gpu_available():
    recurrent_nn = tf.compat.v1.keras.layers.CuDNNGRU
    print("GPU in use")
else:
    import functools
    recurrent_nn = functools.partial(tf.keras.layers.GRU, recurrent_activation='sigmoid')
    print("CPU in use")

建立并实例化我们的模型

如我们先前所见,一种用于构建模型的技术是将所需的层传递到tf.keras.Sequential()构造器中。 在这种情况下,我们分为三层:嵌入层,RNN 层和密集层。

第一嵌入层是向量的查找表,一个向量用于每个字符的数值。 它的尺寸为embedding_dimension。 中间,循环层是 GRU; 其大小为recurrent_nn_units。 最后一层是长度为vocabulary_length单元的密集输出层。

该模型所做的是查找嵌入,使用嵌入作为输入来运行 GRU 一次,然后将其传递给密集层,该层生成下一个字符的对数(对数赔率)。

如下图所示:

[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-FOZG37ap-1681568242390)(https://gitcode.net/apachecn/apachecn-dl-zh/-/raw/master/docs/tf-20-quick-start-guide/img/3832c4d7-fb9f-40af-99bf-9f2c4acd2584.png)]

因此,实现此模型的代码如下:

代码语言:javascript复制
def build_model(vocabulary_size, embedding_dimension, recurrent_nn_units, batch_size):
    model = tf.keras.Sequential(
        [tf.keras.layers.Embedding(vocabulary_size, embedding_dimension, batch_input_shape=[batch_size, None]),
    recurrent_nn(recurrent_nn_units, return_sequences=True, recurrent_initializer='glorot_uniform', stateful=True),
    tf.keras.layers.Dense(vocabulary_length)
  ])
    return model

现在我们可以实例化我们的模型,如下所示:

代码语言:javascript复制
model = build_model(
  vocabulary_size = len(vocabulary),
  embedding_dimension=embedding_dimension,
  recurrent_nn_units=recurrent_nn_units,
  batch_size=batch)

现在,我们可以进行健全性检查,以确保我们的模型输出正确的形状。 注意使用dataset.take()提取数据集的元素:

代码语言:javascript复制
for batch_input_example, batch_target_example in dataset.take(1):
    batch_predictions_example = model(batch_input_example)
    print(batch_predictions_example.shape, "# (batch, sequence_length, vocabulary_length)")

以下是此输出:

代码语言:javascript复制
(64, 100, 84) # (batch, sequence_length, vocabulary_length)

这是预期的; 回想一下,我们的字符集中有84个唯一字符。

这是显示我们的模型外观的代码:

代码语言:javascript复制
model.summary()

我们的模型架构摘要的输出如下:

[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-P248FnJV-1681568242390)(https://gitcode.net/apachecn/apachecn-dl-zh/-/raw/master/docs/tf-20-quick-start-guide/img/c16b150d-45b6-4f82-a596-8317ae714c1d.png)]

再次回想一下,我们有84输入值,我们可以看到,对于嵌入层,84 * 256 = 21,504,对于密集层,1024 * 84 84(偏置单元)= 86,100

使用我们的模型获得预测

为了从我们的模型中获得预测,我们需要从输出分布中抽取一个样本。 此采样将为我们提供该输出分布所需的字符(对输出分布进行采样很重要,因为像通常那样对它进行argmax提取,很容易使模型陷入循环)。

在显示索引之前,tf.random.categorical进行此采样,axis=-1tf.squeeze删除张量的最后一个维度。

tf.random.categorical的签名如下:

代码语言:javascript复制
tf.random.categorical(logits, num_samples, seed=None, name=None, output_dtype=None)

将其与调用进行比较,我们看到我们正在从预测(example_batch_predictions[0])中获取一个样本(长度为sequence_length = 100)。 然后删除了多余的尺寸,因此我们可以查找与示例相对应的字符:

代码语言:javascript复制
sampled_indices = tf.random.categorical(logits=batch_predictions_example[0], num_samples=1)

sampled_indices = tf.squeeze(sampled_indices,axis=-1).numpy()

sampled_indices

这将产生以下输出:

代码语言:javascript复制
array([79, 43, 3, 12, 20, 24, 54, 10, 61, 43, 46, 15, 0, 24, 39, 77, 2, 73, 4, 78, 5, 60, 13, 65, 1, 75, 47, 33, 61, 13, 64, 41, 32, 42, 40, 20, 37, 10, 60, 51, 21, 17, 69, 8, 3, 74, 64, 68, 2, 3, 35, 13, 67, 16, 46, 48, 47, 1, 38, 80, 47, 8, 32, 53, 50, 28, 63, 33, 35, 72, 80, 0, 7, 64, 2, 79, 1, 56, 61, 13, 55, 28, 62, 30, 40, 22, 32, 40, 27, 46, 21, 51, 10, 76, 64, 47, 72, 83, 45, 8])

让我们看一下到训练之前的一些输入和输出*:*

代码语言:javascript复制
print("Input: n", repr("".join(index_to_char[batch_input_example[0]])))

print("Next Char Predictions: n", repr("".join(index_to_char[sampled_indices ])))
#

因此输出如下。 输入的文本之后是下一个字符预测(在训练之前):

代码语言:javascript复制
Input: 
 'r, that I might refer to it again; but I could not find it, andnwas uneasy to think that it must hav'
Next Char Predictions: 
 "hFTzJe;rAô:G*'”x4d?&ôce9QekL:*O7@KuoZM&“$r0mgn%/2-6QaE&$)/'Y8m.x)94b?fKp.rRô.3IMMTMjMMag.iL1LuM6 ?';"

接下来,我们定义loss函数:

代码语言:javascript复制
def loss(labels, logits):
 return tf.keras.losses.sparse_categorical_crossentropy(labels, logits, from_logits=True)

然后,我们在训练之前查看模型的损失,并进行另一次尺寸完整性检查:

代码语言:javascript复制
batch_loss_example = tf.compat.v1.losses.sparse_softmax_cross_entropy(batch_target_example, batch_predictions_example)
print("Prediction shape: ", batch_predictions_example.shape, " # (batch_size, sequence_length, vocab_size)")
print("scalar_loss: ", batch_loss_example.numpy())

这将产生以下输出:

代码语言:javascript复制
Prediction shape: (64, 100, 84) # (batch, sequence_length, vocabulary_length) 
scalar_loss: 4.429237

为了准备我们的训练模型,我们现在使用AdamOptimizer和 softmax 交叉熵损失对其进行编译:

代码语言:javascript复制
#next produced by upgrade script.... 
#model.compile(optimizer = tf.compat.v1.train.AdamOptimizer(), loss = loss) 
#.... but following optimizer is available.
model.compile(optimizer = tf.optimizers.Adam(), loss = loss)

我们将保存模型的权重,因此,接下来,我们为此准备检查点:

代码语言:javascript复制
# The checkpoints will be saved in this directory
directory = './checkpoints'

# checkpoint files
file_prefix = os.path.join(directory, "ckpt_{epoch}")
callback=[tf.keras.callbacks.ModelCheckpoint(filepath=file_prefix, save_weights_only=True)]

最后,我们可以使用对model.fit()的调用来训练模型:

代码语言:javascript复制
epochs=45 # *much* faster on GPU, ~10s / epoch, reduce this figure significantly if on CPU 

history = model.fit(dataset, epochs=epochs, steps_per_epoch=steps_per_epoch, callbacks=callback)

这给出以下输出:

代码语言:javascript复制
Epoch 1/50 158/158 [==============================] - 10s 64ms/step - loss: 2.6995 .................... Epoch 50/50 158/158 [==============================] - 10s 65ms/step - loss: 0.6143

以下是最新的检查点:

代码语言:javascript复制
tf.train.latest_checkpoint(directory)

可以解决以下结果:

代码语言:javascript复制
'./checkpoints/ckpt_45'

因此,我们可以重建模型(以展示其完成方式):

代码语言:javascript复制
model = build_model(vocabulary_size, embedding_dimension, recurrent_nn_units, batch_size=1)

model.load_weights(tf.train.latest_checkpoint(directory))

model.build(tf.TensorShape([1, None]))

model.summary()

下表显示了我们模型的摘要:

[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-pZULE1kS-1681568242390)(https://gitcode.net/apachecn/apachecn-dl-zh/-/raw/master/docs/tf-20-quick-start-guide/img/3066d8ab-9f14-4449-a8eb-f5416c4e6af9.png)]

接下来,在给定训练有素的模型,起始字符串和温度的情况下,我们使用一个函数来生成新文本,其值确定文本的随机性(低值给出更多可预测的文本;高值给出更多随机的文本)。

首先,我们确定要生成的字符数,然后向量化起始字符串,并为其添加空白尺寸。 我们将额外的维添加到input_string变量中,因为 RNN 单元需要它(两个必需的维是批量长度和序列长度)。 然后,我们初始化一个变量,用于存储生成的文本。

temperature的值确定生成的文本的随机性(较低的随机性较小,意味着更可预测)。

在一个循环中,对于要生成的每个新字符,我们使用包含 RNN 状态的模型来获取下一个字符的预测分布。 然后使用多项式分布来找到预测字符的索引,然后将其用作模型的下一个输入。 由于存在循环,模型返回的 RNN 状态将反馈到模型中,因此它现在不仅具有一个字符,而且具有更多信息。 一旦预测了下一个字符,就将修改后的 RNN 状态反复反馈到模型中,以便模型学习,因为它从先前预测的字符获得的上下文会增加。

下图显示了它是如何工作的:

[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-HQsXxfir-1681568242391)(https://gitcode.net/apachecn/apachecn-dl-zh/-/raw/master/docs/tf-20-quick-start-guide/img/8cf34e04-9d73-46cc-b1aa-7bcd0a5342b3.png)]

在这里,多项式用tf.random.categorical实现; 现在我们准备生成我们的预测文本:

代码语言:javascript复制
def generate_text(model, start_string, temperature, characters_to_generate):

# Vectorise the start string into numbers 
  input_string = [char_to_index[char] for char in start_string]
# add extra dimension to input_string
  input_string = tf.expand_dims(input_string, 0)

# Empty list to store generated text
  generated = []

# (batch size is 1)
  model.reset_states()
  for i in range(characters_to_generate):
    predictions = model(input_string) #here's where we need the extra dimension

    # remove the batch dimension
    predictions = tf.squeeze(predictions, 0)

    # using a random categorical (multinomial) distribution to predict word returned by the model
    predictions = predictions / temperature
    predicted_id = tf.random.categorical(logits=predictions, num_samples=1)[-1,0].numpy()

    # Pass  predicted word as  next input to the model along with previous hidden state
    input_string = tf.expand_dims([predicted_id], 0)

    generated.append(index_to_char[predicted_id])
return (start_string   ''.join(generated)) # generated is a list

因此,在定义函数之后,我们可以调用它以返回生成的文本。

在给定的函数参数中,低温给出更多可预测的文本,而高温给出更多随机的文本。 同样,您可以在此处更改起始字符串并更改函数生成的字符数:

代码语言:javascript复制
generated_text = generate_text(model=model, start_string="Pip", temperature=0.1, characters_to_generate = 1000)
print(generated_text)

经过 30 个训练周期后,将产生以下输出:

代码语言:javascript复制
Pip; it was a much better to and the Aged and weaking his hands of the windows of the way who went them on which the more I had been a very little for me, and I went on with his back in the soldiers of the room with the whole hand the other gentleman with the hand on the service, when I was a look of half of the room was was the first time of the money. I forgetter, Mr. Pip?” “I don't know that I have no more than I know what I have no inquiry with the rest of its being straight up again. He came out of the room, and in the midst of the room was was all the words, “and he came into the Castle. One would repeat it to your expectations condition of the courtyard. In a moment was the first time in the house to the fork, and we all lighted and at his being so beautiful looking at the convicts. My depression of the morning, I looked at him in the morning, I should not have been made a strong for the first time of the wall before the table to the forefinger of the room, and had not quite diffi

Loss = 0.6761; 该文本或多或少地被正确地拼写和标点,尽管其含义(我们并未试图实现)的含义在很大程度上是愚蠢的。 它还没有学习如何正确使用语音标记。 只有两个无意义的单词(forgetterweaking),经过检查,在语义上仍然是合理的。 生成的是否为 Charles Dickens 风格是一个悬而未决的问题。

周期数的实验表明,损失在约 45 周期时达到最小值,此后它开始增加。

45 个周期后,输出如下:

代码语言:javascript复制
Pip; or I should
have felt painfully consciousness that he was the man with his back to the kitchen, and he seemed to have no
strength, and as I had often seen her shutters with the poker on
the parlor, through having been every disagreeable to be seen; I thought I would give him more letters of my own
eyes and flared about the fire, and showed the greatest state of mind,
I thought I would give up of his having fastened out of the room, and had
made some advance in that respect to me to feel an
indescribable awe as it was a to be even than ever of her steps, or for old
asked, “Yes.”

“What is it?” repeated Mr. Jaggers. “You know I was in my mind by his blue eyes most of all admirers,
and that she had shaken hands contributing the poker out of his
hands in his pockets and his dinner loosely tied in a busy preparation for the reference to my United and
self-possession when Miss Havisham and Estella now that I had been too much to be the salvey dark night, which seemed so long
ago. “Yes, de

Loss = 0.6166; 该模型现在似乎已正确配对了语音标记,并且没有无意义的单词。

总结

这样就结束了我们对 RNN 的研究。 在本章中,我们首先讨论了 RNN 的一般原理,然后介绍了如何获取和准备一些供模型使用的文本,并指出在此处使用替代文本源很简单。 然后,我们看到了如何创建和实例化我们的模型。 然后,我们训练了模型并使用它从起始字符串中产生文本,并注意到网络已了解到单词是文本的单元以及如何拼写各种各样的单词(有点像文本作者的风格), 几个非单词。

在下一章中,我们将研究 TensorFlow Hub 的使用,它是一个软件库。

九、TensorFlow 估计器和 TensorFlow HUB

本章分为两部分,但是此处的技术是相关的。 首先,我们将研究 TensorFlow 估计器如何为 TensorFlow 提供简单的高级 API,其次,我们将研究 TensorFlow Hub 如何包含可在自己的应用中使用的模块。

在本章中,我们将涵盖以下主要主题:

  • TensorFlow 估计器
  • TensorFlow HUB

TensorFlow 估计器

tf.estimator是 TensorFlow 的高级 API。 它通过提供用于服务模型的直接训练,评估,预测和导出的方法来简化机器学习编程。

估计器为 TensorFlow 开发人员带来了许多优势。 与低级 API 相比,使用估计器开发模型更容易,更直观。 特别是,同一模型可以在本地计算机或分布式多服务器系统上运行。 该模型也不了解其所处的处理器,即 CPU,GPU 或 TPU。 估计器还通过简化模型开发人员共享实现的过程,简化了开发过程,并且由于构建在 Keras 层上,因此使自定义更加简单。

估计器会处理与 TensorFlow 模型一起使用的所有背景管线。 它们支持安全,分布式的训练循环,用于图构建,变量初始化,数据加载,异常处理,创建检查点文件,从故障中恢复以及为 TensorBoard 保存摘要。 正如我们将看到的,由于它们创建检查点,因此它们支持在给定数量的步骤之后停止和开始训练。

开发估计器模型的过程分为四个步骤:

  1. 采集数据并创建数据函数
  2. 创建特征列
  3. 实例化估计器
  4. 评估模型的表现

我们将在以下代码中举例说明这些步骤。

我们之前已经看过fashion_mnist数据集(在第 5 章“将 TensorFlow 2 用于无监督学习”),因此我们将再次使用该数据集来演示估计器的用例。

代码

首先,这是必需的导入:

代码语言:javascript复制
import tensorflow as tf
import numpy as np

接下来,我们获取并预处理数据。 注意,tf.keras.datasets中方便地存在fashion_mnist。 数据集中的x值采用整数 NumPy 数组的形式,每个元素的范围为 0 到 255,代表28 x 28像素时尚图像中每个像素的灰度值。 为了进行训练,必须将这些值转换为 0 到 1 范围内的浮点数。y值采用无符号 8 位整数(uint8)的形式,并且必须转换为 32 位整数(int32 ),供估计工具再次使用。

尽管可以用以下方法试验该超参数值,但将学习率设置为一个很小的值:

代码语言:javascript复制
fashion = tf.keras.datasets.fashion_mnist
(x_train, y_train),(x_test, y_test) = fashion.load_data()
print(type(x_train))
x_train, x_test = x_train / 255.0, x_test / 255.0

y_train, y_test = np.int32(y_train), np.int32(y_test)

learning_rate = 1e-4

之后,是我们的训练输入特征。

当您具有数组中的完整数据集并需要快速进行批量,混排和/或重复的方法时,将使用tf.compat.v1.estimator.inputs.numpy_input_fn

其签名如下:

代码语言:javascript复制
tf.compat.v1.estimator.inputs.numpy_input_fn(
 x,
 y=None,
 batch_size=128,
 num_epochs=1,
 shuffle=None,
 queue_capacity=1000,
 num_threads=1
)

将此与我们对函数的调用进行比较,您可以看到x值如何作为 NumPy 数组的字典(与张量兼容)传递,以及y照原样传递。 在此阶段,我们尚未指定周期数,即该函数将永远运行(稍后将指定步骤),我们的批量大小(即一步中显示的图像数)为50, 并在每一步之前将数据在队列中混洗。 其他参数保留为其默认值:

代码语言:javascript复制
train_input_fn = tf.compat.v1.estimator.inputs.numpy_input_fn(
    x={"x": x_train},
        y=y_train,
        num_epochs=None,
        batch_size=50,
        shuffle=True
)

值得一提的是,尽管这样的便利函数虽然在 TensorFlow 2.0 alpha 中不可用,但仍有望改用 TensorFlow2。

测试函数具有相同的签名,但是在这种情况下,我们仅指定一个周期,并且正如 Google 所建议的那样,我们不会对数据进行混洗。 同样,其余参数保留为其默认值:

代码语言:javascript复制
test_input_fn = tf.compat.v1.estimator.inputs.numpy_input_fn(
    x={"x": x_test},
        y=y_test,
        num_epochs=1,
        shuffle=False
)

接下来,我们建立特征列。 特征列是一种将数据传递给估计器的方法。

特征列函数的签名如下。 key是唯一的字符串,是与我们先前在输入函数中指定的字典名称相对应的列名称(有关不同类型的特征列的更多详细信息,请参见这里):

代码语言:javascript复制
tf.feature_column.numeric_column(
    key,
    shape=(1,),
    default_value=None,
    dtype=tf.float32,
    normalizer_fn=None
)

在我们的特定特征列中,我们可以看到关键是"x",并且形状就是fashion_mnist数据集图像的28 x 28像素形状:

代码语言:javascript复制
feature_columns = [tf.feature_column.numeric_column("x", shape=[28, 28])]

接下来,我们实例化我们的估计器,它将进行分类。 它将为我们构建一个深度神经网络。 它的签名很长很详细,因此我们将带您参考这里,因为我们将主要使用其默认参数。 它的第一个参数是我们刚刚指定的特征,而第二个参数是我们的网络规模。 (输入层和输出层由估计器在后台添加。)AdamOptimizer是安全的选择。 n_classes对应于我们fashion_mnist数据集的y标签数量,我们在其中添加了0.1的适度dropout。 然后,model_dir是我们保存模型参数及其图和检查点的目录。 此目录还用于将检查点重新加载到估计器中以继续训练:

代码语言:javascript复制
# Build 2 layer DNN classifier
classifier = tf.estimator.DNNClassifier(
    feature_columns=feature_columns,
    hidden_units=[256, 32],
    optimizer=tf.compat.v1.train.AdamOptimizer(learning_rate),
    n_classes=10,
    dropout=0.1,
    model_dir="./tmp/mnist_modelx"
, loss_reduction=tf.compat.v1.losses.Reduction.SUM)

现在,我们准备训练模型。 如果您第二次或之后运行.train循环,则 Estimator 将从model_dir加载其模型参数,并进行进一步的steps训练(要完全从头开始,只需通过model_dir删除指定的目录):

代码语言:javascript复制
classifier.train(input_fn=train_input_fn, steps=10000)

典型的输出线如下所示:

代码语言:javascript复制
INFO:tensorflow:loss = 25.540459, step = 1600 (0.179 sec) INFO:tensorflow:global_step/sec: 523.471

最终输出如下所示:

代码语言:javascript复制
INFO:tensorflow:Saving checkpoints for 10000 into ./tmp/mnist_modelx/model.ckpt.
INFO:tensorflow:Loss for final step: 13.06977.

model_dir中指定的目录如下所示:

[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-U64r8MOy-1681568242391)(https://gitcode.net/apachecn/apachecn-dl-zh/-/raw/master/docs/tf-20-quick-start-guide/img/27a244c6-21a0-4834-a28a-4718d7197716.png)]

为了评估模型的表现,使用了classifier.evaluate方法。 其签名如下:

代码语言:javascript复制
classifier.evaluate(input_fn, steps=None, hooks=None, checkpoint_path=None, name=None)

这将返回一个字典,因此在我们的调用中,我们正在提取准确率指标。

在此,steps默认为None。 这将评估模型,直到input_fn引发输入结束异常,即,它将评估整个测试集:

代码语言:javascript复制
 accuracy_score = classifier.evaluate(input_fn=test_input_fn)["accuracy"]
 print("nTest Accuracy: {0:f}%n".format(accuracy_score*100))

我们还可以使用以下命令在 TensorBoard 中查看训练的进度:

代码语言:javascript复制
tensorboard --logdir=./tmp/mnist_modelx

此处,损失图如下所示,其中x轴以 1,000(k)单位表示:

[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-cBbtQqFb-1681568242391)(https://gitcode.net/apachecn/apachecn-dl-zh/-/raw/master/docs/tf-20-quick-start-guide/img/5b1262f0-e388-4537-ae0f-fe7ee09b7bb9.png)]

到此结束我们对时尚估计器分类器的了解。 现在我们来看看 TensorFlow Hub。

TensorFlow HUB

TensorFlow Hub 是一个软件库。 其目的是提供可重用的组件(称为模块),这些组件可在开发组件的原始上下文之外的上下文中使用。 所谓模块,是指 TensorFlow 图的一个独立部分及其权重,可以在其他类似任务中重复使用。

IMDb(电影评论数据库)

在本节中,我们将研究一种基于 Google 的应用,该应用在情感分析中分析了电影评论的 IMDb 的子集。 该子集由斯坦福大学主持,包含每部电影的评论,以及情感积极性等级为 1 到 4(差)和 7 到 10(好)的情感。 问题在于确定关于每个电影的文本句子中表达的视图的极性,即针对每个评论,以确定它是正面评论还是负面评论。 我们将在 TensorFlow Hub 中使用一个模块,该模块先前已经过训练以生成单词嵌入。

词嵌入是数字的向量,因此具有相似含义的词也具有类似的向量。 这是监督学习的示例,因为评论的训练集将使用 IMDB 数据库提供的阳性值来训练模型。 然后,我们将在测试集上使用经过训练的模型,并查看其预测与 IMDB 数据库中存储的预测相比如何,从而为我们提供了一种准确率度量。

可以在这个页面中找到该数据库论文的引文。

数据集

以下是数据库随附的自述文件:

“The core dataset contains 50,000 reviews split evenly into 25k train and 25k test sets. The overall distribution of labels is balanced (25k pos and 25k neg).” “In the entire collection, no more than 30 reviews are allowed for any given movie because reviews for the same movie tend to have correlated ratings. Further, the train and test sets contain a disjoint set of movies, so no significant performance is obtained by memorizing movie-unique terms and their associated with observed labels. In the labeled train/test sets, a negative review has a score <= 4 out of 10, and a positive review has a score >= 7 out of 10. Thus, reviews with more neutral ratings are not included in the train/test sets.”

这是从 IMDb 训练头的顶部起的五行示例:

句子

情感

极性

0

I came here for a review last night before dec...

3

0

1

Look, I'm reading and reading these comments and...

4

0

2

I was overtaken by the emotion. Unforgettable ...

10

1

3

This movie could have been a decent B-movie if...

4

0

4

I have a thing for old black and white movies ...

10

1

这是其尾部的五行:

句子

情感

极性

24995

I have watched some pretty poor films in the p...

1

0

24996

This film is a calculated attempt to cash in t...

1

0

24997

This movie was so very badly written. The char...

1

0

24998

I am a huge Stooges fan but the one and only r...

2

0

24999

Well, let me start off by saying how utterly H...

3

0

以下是测试集:

[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-6rt0t3NU-1681568242392)(https://gitcode.net/apachecn/apachecn-dl-zh/-/raw/master/docs/tf-20-quick-start-guide/img/2bfa077e-c4cd-43a1-9140-74b375ee7897.png)]

代码

现在,让我们看一下在这些数据上训练的代码。 在程序的顶部,我们有通常的导入,以及可能需要与piptensorflow_hubpandasseaborn一起安装的三个额外的导入。 如前所述,我们将使用tensorflow_hub中的模块; 我们还将使用pandas的一些DataFrame属性和seaborn的一些绘制方法:

代码语言:javascript复制
import tensorflow as tf
import tensorflow_hub as hub
import matplotlib.pyplot as plt
import numpy as np
import os
import pandas as pd
import re
import seaborn as sns

另外,这是一些值和我们稍后需要的方法:

代码语言:javascript复制
n_classes = 2
hidden_units = [500,100]
learning_rate = 1e-4
steps = 1000
optimizer = tf.optimizers.Adagrad(learning_rate=learning_rate)
# upgrade script gave this:
#optimizer = tf.compat.v1.train.AdagradOptimizer(learning_rate = learning_rate)

重要的是要认识到,这里使用的 IMDb 数据是目录的分层结构形式。

顶级 IMDb 目录包含两个子目录:traintesttraintest子目录分别包含另外两个子目录posneg

  • pos:包含文本文件的集合。 每个文本文件都是正面评价(极性为 1)。
  • neg:包含文本文件的集合。 每个文本文件都是负面评论(极性为 0)。

情感(分别为 7 到 10 或 1 到 4)记录在文件名中; 例如,文件名为18_7.txt的文本文件评论的情感为 7(pos),而文件名为38_2.txt的文本文件评论的情感为 2(neg):

[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-PM7BuP4E-1681568242392)(https://gitcode.net/apachecn/apachecn-dl-zh/-/raw/master/docs/tf-20-quick-start-guide/img/5b2f5728-0221-484e-b748-dd9191aa5d53.png)]

IMDb 目录/文件层次结构

我们从调用层次结构中的三个函数开始,这些函数获取并预处理审阅数据。

在第一个函数load_data(directory)中,directory_data是一个字典,其中加载了directory中的数据,该数据作为参数传入并作为 pandas DataFrame返回。

descriptionsentiment键初始化directory_data字典,然后将它们分配为空列表作为值。

然后,该函数循环遍历directory中的每个文件,并且对于每个文本文件,读取其内容(作为电影评论)并将其附加到情感列表中。 然后,它使用正则表达式分析文件名并提取数字情感,如前所示,该数字情感紧随文件名中的下划线(_)。 该函数将此数字情感附加到sentiment列表中。 当所有.txt文件都循环通过后,该函数将返回已转换为 pandas DataFrame的字典:

代码语言:javascript复制
# Load all files from a directory into a Pandas DataFrame.
def load_data(directory):
    directory_data = {}
    directory_data["description"] = []
    directory_data["sentiment"] = []
    for file in os.listdir(directory):
        with tf.io.gfile.GFile(os.path.join(directory, file), "r") as f:
            directory_data["description"].append(f.read())
            directory_data["sentiment"].append(re.match("d _(d ).txt", file).group(1))
    return pd.DataFrame.from_dict(directory_data)

如我们前面所述,下一个函数load(directory)调用load_data(directory)posneg子目录创建一个DataFrame。 它将适当的极性作为额外字段添加到每个DataFrame。 然后,它返回一个新的DataFrame,该数据帧由posnegDataFrame的连接组成,经过混洗(sample(frac=1)),并插入了新的数字索引(因为我们已经对行进行了混排):

代码语言:javascript复制
# Merge positive and negative examples, add a polarity column and shuffle.
def load(directory):
    positive_df = load_data(os.path.join(directory, "pos"))
    positive_df["polarity"] = 1

    negative_df = load_data(os.path.join(directory, "neg"))
    negative_df["polarity"] = 0
    return pd.concat([positive_df, negative_df]).sample(frac=1).reset_index(drop=True)

第三个也是最后一个函数是acquire_data()。 如果缓存中不存在该函数,则使用 Keras 工具从 Stanford URL 中获取我们所需的文件。 默认情况下,高速缓存是位于~/.keras/datasets的目录,如有必要,文件将提取到该位置。 该工具将返回到我们的 IMDb 的路径。 然后将其传递给load_dataset()的两个调用,以获取训练和测试DataFrame

代码语言:javascript复制
# Download and process the dataset files.
def acquire_data():
    data = tf.keras.utils.get_file(
    fname="aclImdb.tar.gz",
    origin="http://ai.stanford.edu/~amaas/data/sentiment/aclImdb_v1.tar.gz", extract=True)

    train_df = load(os.path.join(os.path.dirname(data), "aclImdb", "train"))
    test_df = load(os.path.join(os.path.dirname(data), "aclImdb", "test"))

    return train_df, test_df
tf.compat.v1.logging.set_verbosity(tf.compat.v1.logging.ERROR)

主程序要做的第一件事是通过调用我们刚刚描述的函数来获取训练并测试 pandas DataFrame

代码语言:javascript复制
train_df, test_df = acquire_data()

此时,train_dftest_df包含我们要使用的数据。

在查看下一个片段之前,让我们看一下它的签名。 这是一个估计器,它返回用于将 Pandas DataFrame馈入模型的输入函数:

代码语言:javascript复制
tf.compat.v1.estimator.inputs.pandas_input_fn(x, y=None, batch_size=128, num_epochs=1, shuffle=None, queue_capacity=1000, num_threads=1, target_column='target')

调用本身如下:

代码语言:javascript复制
# Training input on the whole training set with no limit on training epochs
train_input_fn = tf.compat.v1.estimator.inputs.pandas_input_fn(train_df, train_df["polarity"], num_epochs=None, shuffle=True)

通过将此调用与函数签名进行比较,我们可以看到训练数据帧train_df与每个评论的极性一起传入。 num_epochs =None表示对训练周期的数量没有限制,因为我们将在后面进行指定; shuffle=True表示以随机顺序读取记录,即文件的每一行。

接下来是预测训练结果的函数:

代码语言:javascript复制
# Prediction on the whole training set.
predict_train_input_fn = tf.compat.v1.estimator.inputs.pandas_input_fn(train_df, train_df["polarity"], shuffle=False)

我们还具有预测测试结果的函数:

代码语言:javascript复制
# Prediction on the test set.
predict_test_input_fn = tf.compat.v1.estimator.inputs.pandas_input_fn(test_df, test_df["polarity"], shuffle=False)

然后,我们有特征列。 特征列是原始数据和估计器之间的中介。 共有九种特征列类型。 它们根据其类型采用数值或分类数据,然后将数据转换为适用于估计器的格式。 在这个页面上有一个出色的描述以及许多示例。

请注意,嵌入来自tf.hub

代码语言:javascript复制
embedded_text_feature_column = hub.text_embedding_column(
    key="description",
    module_spec="https://tfhub.dev/google/nnlm-en-dim128/1")

接下来,我们有我们的深度神经网络估计器。 估计器是用于处理模型的高级工具。

估计器的示例包括DNNClassifier,即用于 TensorFlow 深层神经网络的分类器(在以下代码中使用),以及LinearRegressor,即用于线性回归问题的分类器。 其签名如下:

代码语言:javascript复制
tf.estimator.DNNClassifier(hidden_units, feature_columns, model_dir=None, n_classes=2, weight_column=None, label_vocabulary=None, optimizer='Adagrad', activation_fn=<function relu at 0x7fbb75512488>, dropout=None, input_layer_partitioner=None, config=None, warm_start_from=None, loss_reduction='weighted_sum', batch_norm=False, loss_reduction=None)

让我们将此与通话进行比较:

代码语言:javascript复制
estimator = tf.estimator.DNNClassifier(
    hidden_units = hidden_units,
    feature_columns=[embedded_text_feature_column],
    n_classes=n_classes,
    optimizer= optimiser,
    model_dir = "./tmp/IMDbModel"
, loss_reduction=tf.compat.v1.losses.Reduction.SUM)

我们可以看到,我们将使用具有 500 和 100 个单元的隐藏层的神经网络,我们先前定义的特征列,两个输出类(标签)和ProximalAdagrad优化器。

请注意,与前面的示例一样,由于我们指定了model_dir,因此估计器将保存一个检查点和各种模型参数,以便在重新训练时,将从该目录加载模型并对其进行进一步的训练steps

现在,我们可以使用以下代码来训练我们的网络:

代码语言:javascript复制
estimator.train(input_fn=train_input_fn, steps=steps);

此代码块为我们的结果造成混淆矩阵。

在我们的上下文中,混淆矩阵是一个图表,显示了经过训练的模型的以下内容:

  • 真阳性:真实的正面情感被正确地预测为正面的评论(右下)
  • 真阴性:真实的负面情感被正确地预测为负面的评论(左上)
  • 假阳性:真实的负面情感被错误地预测为正面的评论(右上)
  • 假阴性:真实的正面情感被错误地预测为负面的评论(左下)

以下是我们的训练集的混淆矩阵:

[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-XJyW4h3v-1681568242392)(https://gitcode.net/apachecn/apachecn-dl-zh/-/raw/master/docs/tf-20-quick-start-guide/img/788706bf-771d-4d36-be04-a6c134703cfb.png)]

训练集的混淆矩阵

原始数据如下:

| 9,898 | 2602 | | 2,314 | 10,186 |

注意总数是 25,000,这是我们使用的训练示例的数量。

这是我们测试集的混淆矩阵:

[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-OhYxNAfB-1681568242392)(https://gitcode.net/apachecn/apachecn-dl-zh/-/raw/master/docs/tf-20-quick-start-guide/img/0c50c8c8-6cec-4269-8fc3-7dfdf34ea031.png)]

测试集的混淆矩阵

原始数据如下:

| 9859 | 2641 | | 2500 | 10000 |

对于混淆矩阵,重要的是,对角线的值(左上到右下)要比该对角线的值高得多。 我们可以从混淆矩阵中立即看到,我们的模型在训练和测试集上都表现良好(如果在测试集上差一些)。

在代码中,我们首先有一个获取预测的函数:

代码语言:javascript复制
def get_predictions(estimator, input_fn):
    return [prediction["class_ids"][0] for prediction in estimator.predict(input_fn=input_fn)]

TensorFlow 有一种创建混淆矩阵的方法(如前所述,它们可以显示在原始图中)。

其签名如下:

代码语言:javascript复制
tf.math.confusion_matrix(labels, predictions, num_classes=None, dtype=tf.int32, name=None, weights=None)

在这里,labels是真实的标签。

我们的代码调用如下方法:

代码语言:javascript复制
confusion_train = tf.math.confusion_matrix(labels=train_df["polarity"], predictions=get_predictions(estimator, predict_train_input_fn))
print("Raw figures:")
print(confusion_train.numpy())

接下来,我们对混淆矩阵进行归一化,以便其行总计为 1:

代码语言:javascript复制
# Normalize the confusion matrix so that each row sums to 1.

top = confusion_train.numpy()
bottom = np.sum(top)
confusion_train = 2*top/bottom

最后,我们使用seaborn方法heatmap绘制混淆矩阵。 此方法的签名很长且很详细,因此,查看它的最简单方法是在 Jupyter 笔记本中将光标放在Shift TAB上。

我们在这里只需要四个参数:

代码语言:javascript复制
sns.heatmap(confusion_train, annot=True, xticklabels=LABELS, yticklabels=LABELS)
plt.xlabel("Predicted")
plt.ylabel("True")

在这里,我们得到以下内容:

代码语言:javascript复制
LABELS = ["negative", "positive"]

除了使用测试集代替训练集之外,用于显示测试集的混淆矩阵的代码是相同的:

代码语言:javascript复制
# Create a confusion matrix on test data.
confusion_test = tf.math.confusion_matrix(labels=test_df["polarity"], predictions=get_predictions(estimator, predict_test_input_fn))
print(confusion_test.numpy())
# Normalize the confusion matrix so that each row sums to 1.
top = confusion_test.numpy()
bottom = np.sum(top)
confusion_test = 2*top/bottom
sns.heatmap(confusion_test, annot=True, xticklabels=LABELS, yticklabels=LABELS);
plt.xlabel("Predicted");
plt.ylabel("True");

到此结束我们对 IMDb 情感分析的研究。

总结

在本章中,我们介绍了用于训练时装数据集的估计器。 我们了解了估计器如何为 TensorFlow 提供简单直观的 API。

然后,我们查看了另一个应用,这一次是对 IMDb 中电影评论的情感分类。 我们看到了 TensorFlow Hub 如何为我们提供文本嵌入,即单词的向量,这是具有相似含义的单词具有相似向量的地方。

在本书中,我们看到了 TensorFlow 2.0 alpha 的概述。

十、从 tf1.12 转换为 tf2

Google 提供了一个名为 tf_upgrade_v2的命令行脚本,该脚本会将 1.12 版文件(.py.ipynb文件)转换为 TensorFlow 2 兼容文件。

此转换的语法如下:

代码语言:javascript复制
 tf_upgrade_v2   --infile  file_to_convert --outfile  converted_file

这里是更新脚本的实战演示,以及有关它的更多详细信息,请参见这里 。

重要的是要注意,在运行脚本之前,不应该手动更新代码部分。

该脚本不会解决所有问题,但是它生成的报告将标识那些必须手动解决的问题。

特别是,tf.contrib已从 TF2 中删除,因此必须跟踪并手动修复以前驻留在其中的函数。

这是脚本生成的报告的示例:

代码语言:javascript复制
Processing file 'Chapter1_TF2_Snippets.ipynb'
 outputting to 'Chapter1_TF2_alpha'
 --------------------------------------------------------------------------------

 37:4: INFO: Added keywords to args of function 'tf.size'
 48:13: INFO: Added keywords to args of function 'tf.transpose'
 74:0: INFO: Added keywords to args of function 'tf.reduce_mean'
 75:0: INFO: Added keywords to args of function 'tf.reduce_mean'
 76:0: INFO: Added keywords to args of function 'tf.reduce_mean'
 77:0: INFO: Added keywords to args of function 'tf.reduce_mean'
 78:0: INFO: Added keywords to args of function 'tf.reduce_mean'
 110:4: INFO: Added keywords to args of function 'tf.argmax'
 114:4: INFO: Added keywords to args of function 'tf.argmin'
 121:4: INFO: Added keywords to args of function 'tf.argmax'
 123:4: INFO: Added keywords to args of function 'tf.argmin'
 127:4: INFO: Added keywords to args of function 'tf.argmax'
 129:4: INFO: Added keywords to args of function 'tf.argmin'
 136:0: ERROR: Using member tf.contrib.integrate.odeint in deprecated module tf.contrib. tf.contrib.integrate.odeint cannot be converted automatically. tf.contrib will not be distributed with TensorFlow 2.0, please consider an alternative in non-contrib TensorFlow, a community-maintained repository, or fork the required code.
 162:10: INFO: Added keywords to args of function 'tf.transpose'
 173:11: INFO: Added keywords to args of function 'tf.reduce_mean'

1 人点赞