第 3 部分:TensorFlow 2.00 Alpha 的神经网络应用
在本节中,我们将研究许多人工神经网络(ANN)应用。 这些包括图像识别,神经风格迁移,文本风格生成,时尚识别以及电影评论的 IMDb 数据库的语义分析。
本节包含以下章节:
本章分为两部分,但我们将同时学习使用 TensorFlow 进行图像分类。
在本章中,我们将涵盖以下主要主题:
在第一部分中,我们将使用在前几章中学到的技术开发 TensorFlow 2 模型以进行图像识别,尤其是第 2 章, “Keras,TensorFlow 2 的高级 API”。 这将使我们能够看到如何使用 TensorFlow 2 将所有相关技术结合在一起来创建,训练和评估完整的模型。我们将利用 Google 提供的 QuickDraw 图片数据集可帮助您解决此问题。
我们将使用从 Google QuickDraw 拍摄的图像数据集。 这是一个公开的开放源代码,它包含 345 个类别的 5000 万张图像的数据集,所有这些图像都是由参与挑战的 1500 万名用户在 20 秒或更短的时间内绘制的。 我们将训练 10 个类别的 10,000 张图像,其中一些被选择为相似图像,以便我们可以测试模型的区分能力。 您可以在这个页面上查看这些图像的示例。 这些图片有多种格式,请参见这个页面中的所有格式。
在这里,我们将使用已存储为.npy
文件的图像。 .npy
文件的公共数据集托管在这个页面上。 从这里可以一次下载一组。 要使用不同的图像运行此示例,请从数据目录中删除图像文件,然后将所需的图像下载到存储库中的同一目录中。 该程序从文件名中读取标签。
在本节中,我们将涵盖以下主题:
.h5
格式保存和加载 NumPy 图像数据我们将逐步开发和呈现代码片段。 这些代码段通过螺栓连接在一起,成为存储库中的完整程序。
我们将需要从 Google 下载数据。 您可以将数据下载到一个空目录data_files
。
转到这里并将 10 个数据集下载到data_files
文件夹中。 以下是将要下载的文件的示例:
'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 个文件执行此操作。
首先,我们需要导入依赖项:
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
。 接下来,我们将建立一些常量供以后使用:
batch_size = 128
img_rows, img_cols = 28, 28 # image dimensions
接下来,我们将使用os.walk
方法从data_files
文件夹中收集数据集的文件名:
请注意,文件名收集在列表变量filenames
中。
data_path = "data_files/"
for (dirpath, dirnames, filenames) in walk(data_path):
pass # filenames accumulate in list 'filenames'
print(filenames)
对于我们的示例,文件名(对应于label
类别)如下:
['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
)可在此处更改:
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
控制。 然后,我们将集合x
和y
切片,以将图像和标签放回x
和y
中。 之后,我们将x
和y
累积到x_all
和y_all
中,如果这是它们第一次进入循环(即i=0
),则创建这两个新列表,并将x
和[ 如果这不是他们第一次通过循环(即i>0
),则将它们移到y
上。 当此循环终止时,x_all
和y_all
将分别包含带有标签的图像:
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_all
和y_all
分为训练和测试集,并以 80/20 的训练/测试进行分割:
#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_train
和x_test
重塑为28 x 28 x 1
图像,它们开始出现时的样子,其中前两个维度是图像的高度和宽度(以像素为单位),第三个维度是每个像素的灰度。 我们还将建立input_shape
,并将其用于convNet
的第一层:
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_train
和y_test
标签进行一次热编码:
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 的更小的测试集:
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 架构有足够的实验空间。
然后使用分类交叉熵的损失来编译模型:
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
列表还可以用于诸如保存最佳模型或在学习停止时终止训练(如果在所有周期完成之前发生这种情况)的操作。 有关详细信息,请参见这里:
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
方法。 请注意,测试集用于此评估:
score = model.evaluate(x_test, y_test, verbose=1)
print('Test loss:', score[0])
print('Test accuracy:', score[1])
我们还可以对测试图像进行随机采样,并使用以下代码查看模型的效果。 从文件名中检索标签并打印以供参考,然后再打印成对的预测标签与实际标签:
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 actual\n")
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 回调的完整签名如下:
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 可以从命令行调用,如下所示:
tensorboard --logdir=/full_path_to_your_logs
例如,我们可以使用tensorboard --logdir=./logs
作为默认目录。 将histogram_freq
设置为非 0 的值会导致在写入数据时epochs
之间有明显的停顿,并且仅在需要模型所有层的激活和权重直方图时才应使用。
现在,我们可以保存模型并将其删除:
model.save("./QDrawModel.h5")
del model
然后,我们需要重新加载它:
from tensorflow.keras.models import load_model
model = load_model('./QDrawModel.h5')
最后,我们必须对其进行总结以表明我们已经成功地重新加载了保存的模型:
model.summary()
最后,我们打印出 20 种时尚商品的测试样本,以确保网络正常运行:
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 图像数据如果需要保存先前程序中的训练和测试数据,则可以使用以下代码:
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()
方法保存数据集时使用的名称相同:
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% 以上的测试准确率,已保存在存储库中。 您已经看过此代码; 为方便起见,在此复制。
因此,重申一下,您可以使用以下代码加载此经过训练的模型:
from keras.models import load_model
model = load_model('./QDrawModel.h5')
model.summary()
同样,可以使用以下代码加载训练/测试数据:
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"][:])
再次重申,我们可以使用以下代码获取标签(我们看到的标签对应于图像文件名):
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 上进行计算:
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("-------------------------")
在第二部分中,我们将研究训练模型以识别 CIFAR10 图像数据集中的图像。 这将使我们有机会举例说明顺序模型创建的稍有不同的风格。
具有 10 个类别的 CIFAR 10 图像数据集是 8000 万个微型图像数据集的标记子集。 这些图像由 Alex Krizhevsky,Vinod Nair 和 Geoffrey Hinton 收集。 有关此数据集的完整详细信息,请访问这里。
在 10 个类别中,总共有 60,000 个32 x 32
彩色图像,包括 50,000 个训练图像和 10,000 个测试图像。
类别如下:
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)]
首先,以下是设置所需的导入:
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
。
接下来,我们将在其余的代码中使用一组值:
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
然后,我们可以加载并查看数据的形状:
(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')
这将产生预期的输出:
x_train shape: (50000, 32, 32, 3) 50000 train samples 10000 test samples
现在,我们有了一个显示图像子集的函数。 这将在网格中显示它们:
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()
让我们执行以下函数的调用:
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]
:
x_train = x_train.astype('float32')/255
x_test = x_test.astype('float32')/255
如果将标签作为一站式向量提供,则最好了解它们,因此,我们现在将这样做:
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)
接下来,我们可以指定模型的架构。 请注意,与之前的操作相比,我们使用的激活指定方法略有不同:
model.add(Activation('elu'))
elu
激活函数代表指数线性单元。 在这个页面中有很好的描述。
注意,我们正在使用具有卷积层,BatchNormalization
和 MaxPooling 层的顺序模型。 倒数第二层使结构变平,最后一层使用 softmax 激活,因此我们预测的类将显示为具有最高激活的输出神经元:
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'))
接下来,我们必须定义我们的优化器; RMSprop
。 decay
是每次更新后学习率降低的速度:
opt = tf.keras.optimizers.RMSprop(lr=0.0001, decay=decay)
现在,我们将编译我们的模型:
model.compile(loss='categorical_crossentropy', optimizer=opt,metrics=['accuracy'])
为了帮助模型学习和推广,我们将实现实时数据增强。
这是通过ImageDataGenerator()
函数完成的。 其签名如下:
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)
但是,我们将主要使用前面签名中所示的默认值。 数据将分批循环。
这是对图像应用各种转换,例如水平翻转,高度偏移,宽度偏移,旋转等。 我们将使用以下代码进行演示:
# 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
回调的签名如下:
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
,则使用最新权重)。
我们将有以下代码:
callback = tf.keras.callbacks.EarlyStopping(monitor='accuracy', min_delta=0, patience=1, verbose=1,mode='max', restore_best_weights=True)
现在,我们可以训练模型了。 fit.generator()
函数用于根据flow()
生成器批量显示的数据训练模型。 可以在这个页面中找到更多详细信息:
model.fit_generator(datagen.flow(x_train, y_train, batch_size=batch_size), epochs=epochs, callbacks=[callback])
让我们保存模型,以便以后可以重新加载它:
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)
现在让我们重新加载它:
model1 = tf.keras.models.load_model(model_path)
model1.summary()
最后,让我们看看我们的模型在测试集上的表现如何。 我们需要重新加载数据,因为它已被损坏:
(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
这里又是标签:
labels = ['airplane','automobile','bird','cat','deer','dog','frog','horse','ship','truck']
最后,我们可以检查预测的标签:
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 张图像的测试结果如下:
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%。
在下一章中,我们将研究神经风格迁移,其中涉及获取一个图像的内容并将第二个图像的风格强加于其上,以生成第三个混合图像。
神经风格迁移是一种使用神经网络将一幅图像的艺术风格施加到另一幅图像的内容上的技术,因此最终得到的是两种图像的混合体。 您开始使用的图像称为内容图像。 您在内容图像上加上风格的图像称为风格参考图像。 Google 将转换后的图像称为输入图像,这似乎令人困惑(输入是从两个不同来源获取输入的意思); 让我们将其称为混合图像。 因此,混合图像是具有风格参考图像风格的内容图像。
神经风格迁移通过定义两个损失函数来工作-一个描述两个图像的内容之间的差异,另一个描述两个图像之间的风格差异。
为了开始该过程,用内容图像初始化混合图像。 然后,使用反向传播将内容和内容以及混合图像的风格之间的差异(也称为损失或距离)最小化。 这将创建具有风格参考图像风格和内容图像内容的新图像(即混合图像)。
此过程中涉及一些技术-使用函数式 API,使用预训练的模型及其特征图以及使用自定义训练循环以最小化loss
函数。 我们将在下面的代码中满足所有这些要求。
要充分利用该技术,有两个先决条件-Gatys 等人在 2015 年发表的原始论文虽非必要,但确实可以解释该技术。 技术非常好,因此非常有必要了解如何通过梯度下降来减少损失。
我们将使用 VGG19 架构中的特征层(已在著名的 ImageNet 数据集上进行了训练,其中包含 1400 万张图像和 1000 个类别)。
我们将检查的代码源自 Google 提供的代码; 它使用了急切的执行程序,我们当然不需要编写代码,因为它是 TensorFlow 2 中的默认代码。该代码在 GPU 上运行得更快,但在耐心等待的情况下仍可以在 CPU 上合理的时间内进行训练。
在本章中,我们将介绍以下主题:
要对您自己的图像使用此实现,您需要将这些图像保存在下载的存储库的./tmp/nst
目录中,然后编辑content_path
和style_path
路径,如以下代码所示。
与往常一样,我们要做的第一件事是导入(并配置)所需的模块:
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 模块:
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
这是我们最初将使用的两个图像:
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 384
。Image.ANTIALIAS
参数保留最佳图像质量。 接下来,使用img_to_array()
调用(tensorflow.keras.preprocessing
的方法)将 PIL 图像转换为 NumPy 数组。
最后,为了与以后的使用兼容,图像需要沿零轴的批次尺寸(由于图像是彩色的,因此共给出了四个尺寸)。 这可以通过调用np.expand_dims()
实现:
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 位整数:
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 使用一排两列的网格:
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:
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.939
,116.779
和123.68
。
因此,接下来,我们将这些值添加回 BGR(彩色)通道以恢复原始值,然后将 BGR 序列翻转回 RGB。
最后,对于此函数,我们需要确保我们的值是无符号的 8 位整数,其值在 0 到 255 之间; 这可以通过np.clip()
函数实现:
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 是一个相当简单的体系结构,由卷积层的块组成,每个块的末尾都有一个最大池化层。
对于内容层,我们使用block5
中的第二个卷积层。 之所以使用这个最高的块,是因为较早的块具有更能代表单个像素的特征图。 网络中的高层会根据对象及其在输入图像中的排列来捕获高级内容,但不会限制重建的实际精确像素值。
对于风格层,我们将在每个层块中使用第一个卷积层,即block1_conv1
到block5_conv5
。
然后保存内容和风格层的长度,以供以后使用:
# 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 中的最终分类层,我们将不再使用。
我们将创建一个输出图像,以使输出和相应特征层上的输入/风格之间的距离(差异)最小化:
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
中调整为相同大小:
def rms_loss(image1,image2):
loss = tf.reduce_mean(input_tensor=tf.square(image1 - image2))
return loss
接下来,我们定义content_loss
函数。 这只是函数签名中content
和target
之间的均方差:
def content_loss(content, target):
return rms_loss(content, target)
风格损失是根据称为 Gram 矩阵的数量定义的。 Gram 矩阵(也称为度量)是风格矩阵及其自身的转置的点积。 因为这意味着图像矩阵的每一列都与每一行相乘,所以我们可以认为原始表示中包含的空间信息已经分配。 结果是有关图像的非本地化信息,例如纹理,形状和权重,即其风格。
产生gram_matrix
的代码如下:
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 矩阵)如下:
def style_loss(style, gram_target):
gram_style = gram_matrix(style)
return rms_loss(gram_style, gram_target)
接下来,我们通过获取content_image
和style_image
并将它们馈入模型来找到content_features
和style_features
表示形式。 此代码分为两个块,一个用于content_features
,另一个用于style_features
。 对于内容块,我们加载图像,在其上调用我们的模型,最后,提取先前分配的特征层。 style_features
的代码是相同的,除了我们首先加载风格图像:
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_weight
,style_weight
和总变化权重)的每个贡献的权重。 然后,我们有了初始图像,即我们正在通过优化过程更新的图像。 接下来是gram_style_features
和content_features
,分别对应于我们正在使用的风格层和内容层。
首先从方法签名中复制风格和content_weight
。 然后,在我们的初始图像上调用模型。 我们的模型可以直接调用,因为我们使用的是急切执行,如我们所见,这是 TensorFlow 2 中的默认执行。此调用返回所有模型输出值。
然后,我们有两个类似的块,一个块用于内容,一个块用于风格。 对于第一个(内容)块,获取我们所需层中的内容和风格表示。 接下来,我们累积来自所有内容损失层的内容损失,其中每一层的贡献均被加权。
第二个块与第一个块相似,不同之处在于,这里我们累积来自所有风格损失层的风格损失,其中每个损失层的每个贡献均被平均加权。
最后,该函数返回总损失,风格损失和内容损失,如以下代码所示:
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
接下来,我们有一个计算梯度的函数:
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
的函数很长,因此我们将分节介绍。 其签名如下:
def run_style_transfer(content_path,
style_path,
number_of_iterations=1000,
content_weight=1e3,
style_weight=1e-2):
由于我们实际上不想训练模型中的任何层,因此只需使用如前所述的层的输出值即可。 我们相应地设置其可训练属性:
model = get_model()
for layer in model.layers:
layer.trainable = False
接下来,我们使用先前定义的函数从模型的各层获得style_features
和content_features
表示形式:
style_features, content_features = get_feature_representations(model, content_path, style_path)
gram_style_features
使用style_features
上的循环,如下所示:
gram_style_features = [gram_matrix(style_feature) for style_feature in style_features]
接下来,我们通过加载内容图像并将其转换为张量,来初始化将成为算法输出的图像,即混合图像(也称为 Pastiche 图像):
initial_image = load_and_process_image(content_path)
initial_image = tf.Variable(initial_image, dtype=tf.float32)
下一行定义所需的AdamOptimizer
函数:
optimizer = tf.compat.v1.train.AdamOptimizer(learning_rate=5, beta1=0.99, epsilon=1e-1)
我们将继续保存best_image
和best_loss
,因此请初始化变量以存储它们:
best_loss, best_image = float('inf'), None
接下来,我们设置将被传递到compute_grads()
函数的配置值字典:
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
}
这是显示常量:
number_rows = 2
number_cols = 5
display_interval = number_of_iterations/(number_rows*number_cols)
接下来,我们计算图像边界,如下所示:
norm_means = np.array(channel_means)
minimum_vals = -norm_means
maximum_vals = 255 - norm_means
此列表将存储混合图像:
images = []
接下来,我们开始主图像处理循环,如下所示:
for i in range(number_of_iterations):
因此,接下来我们计算梯度,计算损失,调用优化器以应用梯度,并将图像裁剪到我们先前计算的边界:
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_loss
和best_image
,因此下一步:
if loss < best_loss:
# Update best loss and best image from total loss.
best_loss = loss
best_image = deprocess_image(initial_image.numpy()
然后,我们有条件地保存混合图像(总共 10 张图像),并将其与训练指标一起显示:
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_image
和best_loss
:
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_image
和best_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)]
的代码如下:
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
一起显示内容和风格图像:
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()
接下来是对该函数的调用,如下所示:
show_results(best_image, content_path, style_path)
到此结束我们对神经风格迁移的研究。 我们看到了如何拍摄内容图像和风格图像并生成混合图像。 我们使用训练有素的 VGG19 模型中的层来完成此任务。
在下一章中,我们将研究循环神经网络。 这些网络可以处理顺序的输入值,并且输入值和输出值中的一个或两个具有可变长度。
包括卷积网络(CNN)在内的许多神经网络体系结构的主要缺点之一是它们不允许处理顺序数据。 换句话说,一个完整的特征(例如图像)必须一次全部呈现。 因此,输入是固定长度张量,而输出必须是固定长度张量。 先前特征的输出值也不会以任何方式影响当前特征。 同样,所有输入值(和输出值)都应视为彼此独立。 例如,在我们的fashion_mnist
模型(第 4 章“使用 TensorFlow 2的监督机器学习”)中,每个输入时尚图像都独立于并且完全不了解先前图像。
循环神经网络(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)]
矩形代表张量,箭头代表函数,红色是输入,蓝色是输出,绿色是张量状态。
从左到右,我们有以下内容:
因此,需要一种新的体系结构来处理顺序到达的数据,并且其输入值和输出值中的一个或两个具有可变长度,例如,语言翻译应用中句子中的单词。 在这种情况下,模型的输入和输出都具有不同的长度,就像之前的第四种模式一样。 同样,为了预测给定当前词的后续词,还需要知道先前的词。 这种新的神经网络架构称为 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
时,网络已经忘记了先前的输入值为a
,a
和r
,因此无法预测下一个va
。 另一方面,在给定相同输入的情况下,循环网络“记住”先前的输入值为a
,a
和r
,因此有可能根据其先前的训练来预测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。 U
,V
和W
是权重。o[t]
是时间步长t
的输出。 例如,如果我们要计算字符序列中的下一个字母,它将是字符集o[t] = Vs[t]
的概率向量。如前所述,我们可以将s[t]
视为网络的内存,因为它包含有关网络中较早时间步长发生了什么的信息。 请注意,权重U
,V
和W
在每个步骤中都是共享的,因为我们在每个步骤都执行相同的计算,只是使用不同的输入值( 结果是学习权重的数量大大减少了)。 还要注意,我们可能不需要每个时间步长的输出值(如图所示)。 如果我们要进行情感分析,每个步骤都是一个词,比如说电影评论,那么我们可能只关心最终的输出(正面或负面)。
现在,让我们看一个使用 RNN 的有趣示例,在该示例中,我们尝试以给定的写作风格创建文本。
在此应用中,我们将看到如何使用基于字符的循环神经网络创建文本。 更改要使用的文本的语料库很容易(请参见下面的示例); 在这里,我们将使用查尔斯·狄更斯(Charles Dickens)的小说《伟大的期望》。 我们将在此文本上训练网络,以便如果我们给它一个字符序列,例如thousan
,它将产生序列中的下一个字符d
。 此过程可以继续进行,可以通过在不断演变的序列上反复调用模型来创建更长的文本序列。
这是训练模型之前创建的文本的示例:
Input:
'o else is there to inform?”\n\n“Is there no chance person who might identify you in the street?” said\n'
Next Char Predictions:
"dUFdZ!mig())'(ZIon“4g&HZ”@\nWGWtlinnqQY*dGJ7ioU'6(vLKL&cJ29LG'lQW8n-,M!JSVy”cjN;1cH\ndEEeMXhtW$U8Mt&sp"
这是一些文本,其中包含Pip
序列,该序列是在模型经过 0.1 个温度(请参阅下文)进行 100 个周期(约 10 秒每个)的训练后创建的:
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
)上训练网络。
接下来,我们将查看用于设置,训练和测试循环神经网络的代码。
此应用基于 Google 根据 Apache 2 许可提供的应用。
像往常一样,我们会将代码分解成片段,然后将您引到存储库中获取许可证和完整的工作版本。 首先,我们有模块导入,如下所示:
import tensorflow as tf
import numpy as np
import os
import time
接下来,我们有文本文件的下载链接。
您可以通过在file
中指定文件名和在url
中指定文件的完整 URL,轻松地将其更改为所需的任何文本:
file='1400-0.txt'
url='https://www.gutenberg.org/files/1400/1400-0.txt' # Great Expectations by Charles Dickens
然后,我们为该文件设置了 Keras get_file()
工具,如下所示:
path = tf.keras.utils.get_file(file,url)
然后,我们打开并读取文件,并以字符为单位查看文件的长度:
text = open(path).read()
print ('Length of text: {} characters'.format(len(text)))
在文件开头没有我们不需要的文本,因此我们将其剥离掉,然后再看一下前几个字符就很有帮助了,接下来我们要做:
# strip off text we don't need
text = text[835:]
# Take a look at the first 300 characters in text
print(text[:300])
输出应如下所示:
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 码的顺序对其进行排序:
# The unique characters in the file
vocabulary = sorted(set(text))
print ('{} unique characters.'.format(len(vocabulary)))
这应该提供 84 个唯一字符。
接下来,我们创建一个字典,其中字符是键,而连续的整数是值。
这样我们就可以找到索引,表示任何给定字符的数值:
# 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)
输出如下:
{'\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
:
index_to_char = np.array(vocabulary)
print(index_to_char)
输出如下:
['\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
:
text_as_int = np.array([char_to_index[char] for char in text]
这是字符及其索引的示例:
print('{')
for char,_ in zip(char_to_index, range(20)):
print(' {:4s}: {:3d},'.format(repr(char), char_to_index[char]))
print(' ...\n}')
输出如下:
{
'\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,
...
}
接下来,查看文本如何映射为整数很有用; 这是前几个:
# 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]))
输出如下:
"My father's fam" ---- characters mapped to int ---- > [40 78 1 59 54 73 61 58 71 6 72 1 59 54 66]
然后,我们设置每个输入的句子长度,并因此设置训练周期中的示例数:
# The maximum length sentence we want for a single input in characters
sequence_length = 100
examples_per_epoch = len(text)//seq_length
接下来,我们创建data.Dataset
以在以后的训练中使用:
# 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()])
输出如下:
M y f a
我们需要批量此数据以将其馈送到我们的 RNN,因此接下来我们要这样做:
sequences = char_dataset.batch(sequence_length+1, drop_remainder=True)
请记住,我们已经设置了sequence_length = 100
,所以批量中的字符数是 101。
现在,我们有了一个函数来创建我们的输入数据和目标数据(必需的输出)。
该函数返回我们一直在处理的文本以及相同的文本,但是一起移动了一个字符,即,如果第一个单词是Python
和sequence_length = 5
,则该函数返回Pytho
和ython
。
然后,我们通过连接输入和输出字符序列来创建数据集:
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()
方法来查找张量的值:
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()])))
输出如下:
Input data: "My father's family name being Pirrip, and my Christian name Philip, my\ninfant tongue could make of b" Target data: "y father's family name being Pirrip, and my Christian name Philip, my\ninfant tongue could make of bo"
现在,我们可以通过几个步骤显示输入和预期输出:
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])))
以下是此输出:
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')
接下来,我们为训练进行设置,如下所示:
# 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
这给出了以下数据集结构:
<RepeatBatchDataset shapes: ((64, 100), (64, 100)), types: (tf.int64, tf.int64)>
此处,64
是批次大小,100
是序列长度。 以下是我们训练所需的一些值:
# 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 中实现内存的一种方式。 下一节将实现此想法,如下所示:
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)]
因此,实现此模型的代码如下:
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
现在我们可以实例化我们的模型,如下所示:
model = build_model(
vocabulary_size = len(vocabulary),
embedding_dimension=embedding_dimension,
recurrent_nn_units=recurrent_nn_units,
batch_size=batch)
现在,我们可以进行健全性检查,以确保我们的模型输出正确的形状。 注意使用dataset.take()
提取数据集的元素:
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)")
以下是此输出:
(64, 100, 84) # (batch, sequence_length, vocabulary_length)
这是预期的; 回想一下,我们的字符集中有84
个唯一字符。
这是显示我们的模型外观的代码:
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=-1
与tf.squeeze
删除张量的最后一个维度。
tf.random.categorical
的签名如下:
tf.random.categorical(logits, num_samples, seed=None, name=None, output_dtype=None)
将其与调用进行比较,我们看到我们正在从预测(example_batch_predictions[0]
)中获取一个样本(长度为sequence_length = 100
)。 然后删除了多余的尺寸,因此我们可以查找与示例相对应的字符:
sampled_indices = tf.random.categorical(logits=batch_predictions_example[0], num_samples=1)
sampled_indices = tf.squeeze(sampled_indices,axis=-1).numpy()
sampled_indices
这将产生以下输出:
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])
让我们看一下到训练之前的一些输入和输出*:*
print("Input: \n", repr("".join(index_to_char[batch_input_example[0]])))
print("Next Char Predictions: \n", repr("".join(index_to_char[sampled_indices ])))
#
因此输出如下。 输入的文本之后是下一个字符预测(在训练之前):
Input:
'r, that I might refer to it again; but I could not find it, and\nwas uneasy to think that it must hav'
Next Char Predictions:
"hFTzJe;rAô:G*'”x4d?&ôce9QekL:*O7@KuoZM&“$r0mg\n%/2-6QaE&$)/'Y8m.x)94b?fKp.rRô.3IMMTMjMMag.iL1LuM6 ?';"
接下来,我们定义loss
函数:
def loss(labels, logits):
return tf.keras.losses.sparse_categorical_crossentropy(labels, logits, from_logits=True)
然后,我们在训练之前查看模型的损失,并进行另一次尺寸完整性检查:
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())
这将产生以下输出:
Prediction shape: (64, 100, 84) # (batch, sequence_length, vocabulary_length)
scalar_loss: 4.429237
为了准备我们的训练模型,我们现在使用AdamOptimizer
和 softmax 交叉熵损失对其进行编译:
#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)
我们将保存模型的权重,因此,接下来,我们为此准备检查点:
# 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()
的调用来训练模型:
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)
这给出以下输出:
Epoch 1/50 158/158 [==============================] - 10s 64ms/step - loss: 2.6995 .................... Epoch 50/50 158/158 [==============================] - 10s 65ms/step - loss: 0.6143
以下是最新的检查点:
tf.train.latest_checkpoint(directory)
可以解决以下结果:
'./checkpoints/ckpt_45'
因此,我们可以重建模型(以展示其完成方式):
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
实现; 现在我们准备生成我们的预测文本:
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
因此,在定义函数之后,我们可以调用它以返回生成的文本。
在给定的函数参数中,低温给出更多可预测的文本,而高温给出更多随机的文本。 同样,您可以在此处更改起始字符串并更改函数生成的字符数:
generated_text = generate_text(model=model, start_string="Pip", temperature=0.1, characters_to_generate = 1000)
print(generated_text)
经过 30 个训练周期后,将产生以下输出:
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
; 该文本或多或少地被正确地拼写和标点,尽管其含义(我们并未试图实现)的含义在很大程度上是愚蠢的。 它还没有学习如何正确使用语音标记。 只有两个无意义的单词(forgetter
和weaking
),经过检查,在语义上仍然是合理的。 生成的是否为 Charles Dickens 风格是一个悬而未决的问题。
周期数的实验表明,损失在约 45 周期时达到最小值,此后它开始增加。
45 个周期后,输出如下:
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 提供简单的高级 API,其次,我们将研究 TensorFlow Hub 如何包含可在自己的应用中使用的模块。
在本章中,我们将涵盖以下主要主题:
tf.estimator
是 TensorFlow 的高级 API。 它通过提供用于服务模型的直接训练,评估,预测和导出的方法来简化机器学习编程。
估计器为 TensorFlow 开发人员带来了许多优势。 与低级 API 相比,使用估计器开发模型更容易,更直观。 特别是,同一模型可以在本地计算机或分布式多服务器系统上运行。 该模型也不了解其所处的处理器,即 CPU,GPU 或 TPU。 估计器还通过简化模型开发人员共享实现的过程,简化了开发过程,并且由于构建在 Keras 层上,因此使自定义更加简单。
估计器会处理与 TensorFlow 模型一起使用的所有背景管线。 它们支持安全,分布式的训练循环,用于图构建,变量初始化,数据加载,异常处理,创建检查点文件,从故障中恢复以及为 TensorBoard 保存摘要。 正如我们将看到的,由于它们创建检查点,因此它们支持在给定数量的步骤之后停止和开始训练。
开发估计器模型的过程分为四个步骤:
我们将在以下代码中举例说明这些步骤。
我们之前已经看过fashion_mnist
数据集(在第 5 章“将 TensorFlow 2 用于无监督学习”),因此我们将再次使用该数据集来演示估计器的用例。
首先,这是必需的导入:
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
),供估计工具再次使用。
尽管可以用以下方法试验该超参数值,但将学习率设置为一个很小的值:
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
。
其签名如下:
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
, 并在每一步之前将数据在队列中混洗。 其他参数保留为其默认值:
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 所建议的那样,我们不会对数据进行混洗。 同样,其余参数保留为其默认值:
test_input_fn = tf.compat.v1.estimator.inputs.numpy_input_fn(
x={"x": x_test},
y=y_test,
num_epochs=1,
shuffle=False
)
接下来,我们建立特征列。 特征列是一种将数据传递给估计器的方法。
特征列函数的签名如下。 key
是唯一的字符串,是与我们先前在输入函数中指定的字典名称相对应的列名称(有关不同类型的特征列的更多详细信息,请参见这里):
tf.feature_column.numeric_column(
key,
shape=(1,),
default_value=None,
dtype=tf.float32,
normalizer_fn=None
)
在我们的特定特征列中,我们可以看到关键是"x"
,并且形状就是fashion_mnist
数据集图像的28 x 28
像素形状:
feature_columns = [tf.feature_column.numeric_column("x", shape=[28, 28])]
接下来,我们实例化我们的估计器,它将进行分类。 它将为我们构建一个深度神经网络。 它的签名很长很详细,因此我们将带您参考这里,因为我们将主要使用其默认参数。 它的第一个参数是我们刚刚指定的特征,而第二个参数是我们的网络规模。 (输入层和输出层由估计器在后台添加。)AdamOptimizer
是安全的选择。 n_classes
对应于我们fashion_mnist
数据集的y
标签数量,我们在其中添加了0.1
的适度dropout
。 然后,model_dir
是我们保存模型参数及其图和检查点的目录。 此目录还用于将检查点重新加载到估计器中以继续训练:
# 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
删除指定的目录):
classifier.train(input_fn=train_input_fn, steps=10000)
典型的输出线如下所示:
INFO:tensorflow:loss = 25.540459, step = 1600 (0.179 sec) INFO:tensorflow:global_step/sec: 523.471
最终输出如下所示:
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
方法。 其签名如下:
classifier.evaluate(input_fn, steps=None, hooks=None, checkpoint_path=None, name=None)
这将返回一个字典,因此在我们的调用中,我们正在提取准确率指标。
在此,steps
默认为None
。 这将评估模型,直到input_fn
引发输入结束异常,即,它将评估整个测试集:
accuracy_score = classifier.evaluate(input_fn=test_input_fn)["accuracy"]
print("\nTest Accuracy: {0:f}%\n".format(accuracy_score*100))
我们还可以使用以下命令在 TensorBoard 中查看训练的进度:
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 图的一个独立部分及其权重,可以在其他类似任务中重复使用。
在本节中,我们将研究一种基于 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)]
现在,让我们看一下在这些数据上训练的代码。 在程序的顶部,我们有通常的导入,以及可能需要与pip
– tensorflow_hub
,pandas
和seaborn
一起安装的三个额外的导入。 如前所述,我们将使用tensorflow_hub
中的模块; 我们还将使用pandas
的一些DataFrame
属性和seaborn
的一些绘制方法:
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
另外,这是一些值和我们稍后需要的方法:
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 目录包含两个子目录:train
和test
。 train
和test
子目录分别包含另外两个子目录pos
和neg
:
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
返回。
用description
和sentiment
键初始化directory_data
字典,然后将它们分配为空列表作为值。
然后,该函数循环遍历directory
中的每个文件,并且对于每个文本文件,读取其内容(作为电影评论)并将其附加到情感列表中。 然后,它使用正则表达式分析文件名并提取数字情感,如前所示,该数字情感紧随文件名中的下划线(_
)。 该函数将此数字情感附加到sentiment
列表中。 当所有.txt
文件都循环通过后,该函数将返回已转换为 pandas DataFrame
的字典:
# 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)
从pos
和neg
子目录创建一个DataFrame
。 它将适当的极性作为额外字段添加到每个DataFrame
。 然后,它返回一个新的DataFrame
,该数据帧由pos
和neg
的DataFrame
的连接组成,经过混洗(sample(frac=1)
),并插入了新的数字索引(因为我们已经对行进行了混排):
# 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
:
# 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
:
train_df, test_df = acquire_data()
此时,train_df
和test_df
包含我们要使用的数据。
在查看下一个片段之前,让我们看一下它的签名。 这是一个估计器,它返回用于将 Pandas DataFrame
馈入模型的输入函数:
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')
调用本身如下:
# 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
表示以随机顺序读取记录,即文件的每一行。
接下来是预测训练结果的函数:
# 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)
我们还具有预测测试结果的函数:
# 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
:
embedded_text_feature_column = hub.text_embedding_column(
key="description",
module_spec="https://tfhub.dev/google/nnlm-en-dim128/1")
接下来,我们有我们的深度神经网络估计器。 估计器是用于处理模型的高级工具。
估计器的示例包括DNNClassifier
,即用于 TensorFlow 深层神经网络的分类器(在以下代码中使用),以及LinearRegressor
,即用于线性回归问题的分类器。 其签名如下:
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)
让我们将此与通话进行比较:
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
。
现在,我们可以使用以下代码来训练我们的网络:
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 |
对于混淆矩阵,重要的是,对角线的值(左上到右下)要比该对角线的值高得多。 我们可以从混淆矩阵中立即看到,我们的模型在训练和测试集上都表现良好(如果在测试集上差一些)。
在代码中,我们首先有一个获取预测的函数:
def get_predictions(estimator, input_fn):
return [prediction["class_ids"][0] for prediction in estimator.predict(input_fn=input_fn)]
TensorFlow 有一种创建混淆矩阵的方法(如前所述,它们可以显示在原始图中)。
其签名如下:
tf.math.confusion_matrix(labels, predictions, num_classes=None, dtype=tf.int32, name=None, weights=None)
在这里,labels
是真实的标签。
我们的代码调用如下方法:
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:
# 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
上。
我们在这里只需要四个参数:
sns.heatmap(confusion_train, annot=True, xticklabels=LABELS, yticklabels=LABELS)
plt.xlabel("Predicted")
plt.ylabel("True")
在这里,我们得到以下内容:
LABELS = ["negative", "positive"]
除了使用测试集代替训练集之外,用于显示测试集的混淆矩阵的代码是相同的:
# 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 的概述。
Google 提供了一个名为 tf_upgrade_v2
的命令行脚本,该脚本会将 1.12 版文件(.py
和.ipynb
文件)转换为 TensorFlow 2 兼容文件。
此转换的语法如下:
tf_upgrade_v2 --infile file_to_convert --outfile converted_file
这里是更新脚本的实战演示,以及有关它的更多详细信息,请参见这里 。
重要的是要注意,在运行脚本之前,不应该手动更新代码部分。
该脚本不会解决所有问题,但是它生成的报告将标识那些必须手动解决的问题。
特别是,tf.contrib
已从 TF2 中删除,因此必须跟踪并手动修复以前驻留在其中的函数。
这是脚本生成的报告的示例:
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'