三分钟快速上手TensorFlow 2.0 (中)——常用模块和模型的部署
本文学习笔记参照来源:https://tf.wiki/zh/basic/basic.html
前文:三分钟快速上手TensorFlow 2.0 (上)——前置基础、模型建立与可视化
tf.train.Checkpoint
:变量的保存与恢复
只保存模型的参数,不保存模型的计算过程
需要导出模型(无需源代码也能运行模型),请参考 SavedModel
可以使用其 save()
和 restore()
方法将 TensorFlow 中所有包含 Checkpointable State 的对象进行保存和恢复。
tf.keras.optimizer
、 tf.Variable
、 tf.keras.Layer
或者 tf.keras.Model
实例都可以被保存。
checkpoint = tf.train.Checkpoint(model=model)
**kwargs
。具体而言,是一系列的键值对,键名可以随意取,值为需要保存的对象myAwesomeModel
是我们为待保存的模型 model
所取的任意键名。注意,在恢复变量的时候,我们还将使用这一键checkpoint.save(save_path_with_prefix)
save() 输入的是保存文件的目录 + 前缀。比如'./save/model.ckpt'
model_to_be_restored = MyModel() # 待恢复参数的同一模型 checkpoint = tf.train.Checkpoint(myAwesomeModel=model_to_be_restored) # 键名保持为“myAwesomeModel” checkpoint.restore(save_path_with_prefix_and_index)
save_path_with_prefix_and_index
是之前保存的文件的目录 + 前缀 + 编号。例如,调用 checkpoint.restore('./save/model.ckpt-1')
就可以载入前缀为 model.ckpt
,序号为 1 的文件来恢复模型。tf.train.latest_checkpoint(save_path)
这个辅助函数返回目录下最近一次 checkpoint 的文件名。# train.py 模型训练阶段 model = MyModel() # 实例化Checkpoint,指定保存对象为model(如果需要保存Optimizer的参数也可加入) checkpoint = tf.train.Checkpoint(myModel=model) # ...(模型训练代码) # 模型训练完毕后将参数保存到文件(也可以在模型训练过程中每隔一段时间就保存一次) checkpoint.save('./save/model.ckpt')
# test.py 模型使用阶段 model = MyModel() checkpoint = tf.train.Checkpoint(myModel=model) # 实例化Checkpoint,指定恢复对象为model checkpoint.restore(tf.train.latest_checkpoint('./save')) # 从文件恢复模型参数 # 模型使用代码
import tensorflow as tf import numpy as np import argparse from zh.model.mnist.mlp import MLP from zh.model.utils import MNISTLoader parser = argparse.ArgumentParser(description='Process some integers.') parser.add_argument('--mode', default='train', help='train or test') parser.add_argument('--num_epochs', default=1) parser.add_argument('--batch_size', default=50) parser.add_argument('--learning_rate', default=0.001) args = parser.parse_args() data_loader = MNISTLoader() def train(): model = MLP() optimizer = tf.keras.optimizers.Adam(learning_rate=args.learning_rate) num_batches = int(data_loader.num_train_data // args.batch_size * args.num_epochs) checkpoint = tf.train.Checkpoint(myAwesomeModel=model) # 实例化Checkpoint,设置保存对象为model for batch_index in range(1, num_batches+1): X, y = data_loader.get_batch(args.batch_size) with tf.GradientTape() as tape: y_pred = model(X) loss = tf.keras.losses.sparse_categorical_crossentropy(y_true=y, y_pred=y_pred) loss = tf.reduce_mean(loss) print("batch %d: loss %f" % (batch_index, loss.numpy())) grads = tape.gradient(loss, model.variables) optimizer.apply_gradients(grads_and_vars=zip(grads, model.variables)) if batch_index % 100 == 0: # 每隔100个Batch保存一次 path = checkpoint.save('./save/model.ckpt') # 保存模型参数到文件 print("model saved to %s" % path) def test(): model_to_be_restored = MLP() # 实例化Checkpoint,设置恢复对象为新建立的模型model_to_be_restored checkpoint = tf.train.Checkpoint(myAwesomeModel=model_to_be_restored) checkpoint.restore(tf.train.latest_checkpoint('./save')) # 从文件恢复模型参数 y_pred = np.argmax(model_to_be_restored.predict(data_loader.test_data), axis=-1) print("test accuracy: %f" % (sum(y_pred == data_loader.test_label) / data_loader.num_test_data)) if __name__ == '__main__': if args.mode == 'train': train() if args.mode == 'test': test()
--mode=test
并再次运行代码,将直接使用最后一次保存的变量值恢复模型并在测试集上测试模型性能tf.train.CheckpointManager可以完成
-
保留最后的几个 Checkpoint;
-
Checkpoint 默认从 1 开始编号,每次累加 1,但我们可能希望使用别的编号方式(例如使用当前 Batch 的编号作为文件编号)。
checkpoint = tf.train.Checkpoint(model=model) manager = tf.train.CheckpointManager(checkpoint, directory='./save', checkpoint_name='model.ckpt', max_to_keep=k)
directory
参数为文件保存的路径, checkpoint_name
为文件名前缀(不提供则默认为 ckpt
), max_to_keep
为保留的 Checkpoint 数目。
在需要保存模型的时候,我们直接使用 manager.save()
即可。如果我们希望自行指定保存的 Checkpoint 的编号,则可以在保存时加入 checkpoint_number
参数。例如 manager.save(checkpoint_number=100)
。
import tensorflow as tf import numpy as np import argparse from zh.model.mnist.mlp import MLP from zh.model.utils import MNISTLoader parser = argparse.ArgumentParser(description='Process some integers.') parser.add_argument('--mode', default='train', help='train or test') parser.add_argument('--num_epochs', default=1) parser.add_argument('--batch_size', default=50) parser.add_argument('--learning_rate', default=0.001) args = parser.parse_args() data_loader = MNISTLoader() def train(): model = MLP() optimizer = tf.keras.optimizers.Adam(learning_rate=args.learning_rate) num_batches = int(data_loader.num_train_data // args.batch_size * args.num_epochs) checkpoint = tf.train.Checkpoint(myAwesomeModel=model) # 使用tf.train.CheckpointManager管理Checkpoint manager = tf.train.CheckpointManager(checkpoint, directory='./save', max_to_keep=3) for batch_index in range(1, num_batches): X, y = data_loader.get_batch(args.batch_size) with tf.GradientTape() as tape: y_pred = model(X) loss = tf.keras.losses.sparse_categorical_crossentropy(y_true=y, y_pred=y_pred) loss = tf.reduce_mean(loss) print("batch %d: loss %f" % (batch_index, loss.numpy())) grads = tape.gradient(loss, model.variables) optimizer.apply_gradients(grads_and_vars=zip(grads, model.variables)) if batch_index % 100 == 0: # 使用CheckpointManager保存模型参数到文件并自定义编号 path = manager.save(checkpoint_number=batch_index) print("model saved to %s" % path) def test(): model_to_be_restored = MLP() checkpoint = tf.train.Checkpoint(myAwesomeModel=model_to_be_restored) checkpoint.restore(tf.train.latest_checkpoint('./save')) y_pred = np.argmax(model_to_be_restored.predict(data_loader.test_data), axis=-1) print("test accuracy: %f" % (sum(y_pred == data_loader.test_label) / data_loader.num_test_data)) if __name__ == '__main__': if args.mode == 'train': train() if args.mode == 'test': test()
TensorBoard:训练过程可视化
summary_writer = tf.summary.create_file_writer('./tensorboard') # 参数为记录文件所保存的目录
summary_writer = tf.summary.create_file_writer('./tensorboard') # 开始模型训练 for batch_index in range(num_batches): # ...(训练代码,当前batch的损失值放入变量loss中) with summary_writer.as_default(): # 希望使用的记录器 tf.summary.scalar("loss", loss, step=batch_index) tf.summary.scalar("MyScalar", my_scalar, step=batch_index) # 还可以添加其他自定义的变量
tf.summary.scalar(name, tensor, step=batch_index)
,即可将训练过程中参数在 step 时候的值记录下来。这里的 step 参数可根据自己的需要自行制定,一般可设置为当前训练过程中的 batch 序号tensorboard --logdir=./tensorboard
tf.summary.trace_on(graph=True, profiler=True) # 开启Trace,可以记录图结构和profile信息 # 进行训练 with summary_writer.as_default(): tf.summary.trace_export(name="model_trace", step=0, profiler_outdir=log_dir) # 保存Trace信息到文件
-
如果需要重新训练,需要删除掉记录文件夹内的信息并重启 TensorBoard(或者建立一个新的记录文件夹并开启 TensorBoard,
--logdir
参数设置为新建立的文件夹); -
记录文件夹目录保持全英文。
import tensorflow as tf from zh.model.mnist.mlp import MLP from zh.model.utils import MNISTLoader num_batches = 1000 batch_size = 50 learning_rate = 0.001 log_dir = 'tensorboard' model = MLP() data_loader = MNISTLoader() optimizer = tf.keras.optimizers.Adam(learning_rate=learning_rate) summary_writer = tf.summary.create_file_writer(log_dir) # 实例化记录器 tf.summary.trace_on(profiler=True) # 开启Trace(可选) for batch_index in range(num_batches): X, y = data_loader.get_batch(batch_size) with tf.GradientTape() as tape: y_pred = model(X) loss = tf.keras.losses.sparse_categorical_crossentropy(y_true=y, y_pred=y_pred) loss = tf.reduce_mean(loss) print("batch %d: loss %f" % (batch_index, loss.numpy())) with summary_writer.as_default(): # 指定记录器 tf.summary.scalar("loss", loss, step=batch_index) # 将当前损失函数的值写入记录器 grads = tape.gradient(loss, model.variables) optimizer.apply_gradients(grads_and_vars=zip(grads, model.variables)) with summary_writer.as_default(): tf.summary.trace_export(name="model_trace", step=0, profiler_outdir=log_dir) # 保存Trace信息到文件(可选)
tf.data
:数据集的构建与预处理
快速、高效地构建数据输入的流水线,尤其适用于数据量巨大的场景。
数据集对象的建立
tf.data
的核心是 tf.data.Dataset
类,提供了对数据集的高层封装。tf.data.Dataset
由一系列的可迭代访问的元素(element)组成
import tensorflow as tf import numpy as np X = tf.constant([2013, 2014, 2015, 2016, 2017]) Y = tf.constant([12000, 14000, 15000, 16500, 17500]) # 也可以使用NumPy数组,效果相同 # X = np.array([2013, 2014, 2015, 2016, 2017]) # Y = np.array([12000, 14000, 15000, 16500, 17500]) dataset = tf.data.Dataset.from_tensor_slices((X, Y)) for x, y in dataset: print(x.numpy(), y.numpy())
数据集对象的预处理
最常用的如:
-
Dataset.map(f)
:对数据集中的每个元素应用函数f
,得到一个新的数据集(这部分往往结合tf.io
进行读写和解码文件,tf.image
进行图像处理); -
Dataset.shuffle(buffer_size)
:将数据集打乱(设定一个固定大小的缓冲区(Buffer),取出前buffer_size
个元素放入,并从缓冲区中随机采样,采样后的数据用后续数据替换); -
Dataset.batch(batch_size)
:将数据集分成批次,即对每batch_size
个元素,使用tf.stack()
在第 0 维合并,成为一个元素。
还有 Dataset.repeat()
(重复数据集的元素)、 Dataset.reduce()
(与 Map 相对的聚合操作)、 Dataset.take()
(截取数据集中的前若干个元素)等
def rot90(image, label): image = tf.image.rot90(image) return image, label mnist_dataset = mnist_dataset.map(rot90) for image, label in mnist_dataset: plt.title(label.numpy()) plt.imshow(image.numpy()[:, :, 0]) plt.show(
mnist_dataset = mnist_dataset.batch(4) for images, labels in mnist_dataset: # image: [4, 28, 28, 1], labels: [4] fig, axs = plt.subplots(1, 4) for i in range(4): axs[i].set_title(labels.numpy()[i]) axs[i].imshow(images.numpy()[i, :, :, 0]) plt.show()
mnist_dataset = mnist_dataset.shuffle(buffer_size=10000).batch(4) for images, labels in mnist_dataset: fig, axs = plt.subplots(1, 4) for i in range(4): axs[i].set_title(labels.numpy()[i]) axs[i].imshow(images.numpy()[i, :, :, 0]) plt.show()
-
设定一个固定大小为
buffer_size
的缓冲区(Buffer); -
初始化时,取出数据集中的前
buffer_size
个元素放入缓冲区; -
每次需要从数据集中取元素时,即从缓冲区中随机采样一个元素并取出,然后从后续的元素中取出一个放回到之前被取出的位置,以维持缓冲区的大小。
因此,缓冲区的大小需要根据数据集的特性和数据排列顺序特点来进行合理的设置。比如:
-
当
buffer_size
设置为 1 时,其实等价于没有进行任何打散; -
当数据集的标签顺序分布极为不均匀(例如二元分类时数据集前 N 个的标签为 0,后 N 个的标签为 1)时,较小的缓冲区大小会使得训练时取出的 Batch 数据很可能全为同一标签,从而影响训练效果。一般而言,数据集的顺序分布若较为随机,则缓冲区的大小可较小,否则则需要设置较大的缓冲区。
使用 tf.data
的并行化策略提高训练流程效率
mnist_dataset=mnist_dataset.prefetch(buffer_size=tf.data.experimental.AUTOTUNE)
buffer_size
既可手工设置,也可设置为 tf.data.experimental.AUTOTUNE
从而由 TensorFlow 自动选择合适的数值mnist_dataset = mnist_dataset.map(map_func=rot90, num_parallel_calls=2)
rot90
)数据集元素的获取与使用
dataset = tf.data.Dataset.from_tensor_slices((A, B, C, ...)) for a, b, c, ... in dataset: # 对张量a, b, c等进行操作,例如送入模型进行训练
dataset = tf.data.Dataset.from_tensor_slices((A, B, C, ...)) it = iter(dataset) a_0, b_0, c_0, ... = next(it) a_1, b_1, c_1, ... = next(it)
Keras 支持使用 tf.data.Dataset
直接作为输入。当调用 tf.keras.Model
的 fit()
和 evaluate()
方法时,可以将参数中的输入数据 x
指定为一个元素格式为 (输入数据, 标签数据)
的 Dataset
,并忽略掉参数中的标签数据 y
。例如,对于上述的 MNIST 数据集
model.fit(x=train_data, y=train_label, epochs=num_epochs, batch_size=batch_size)
model.fit(mnist_dataset, epochs=num_epochs)
Dataset.batch()
方法划分了数据集的批次,所以这里也无需提供批次的大小。
import tensorflow as tf import os num_epochs = 10 batch_size = 32 learning_rate = 0.001 data_dir = 'C:/datasets/cats_vs_dogs' train_cats_dir = data_dir + '/train/cats/' train_dogs_dir = data_dir + '/train/dogs/' test_cats_dir = data_dir + '/valid/cats/' test_dogs_dir = data_dir + '/valid/dogs/' def _decode_and_resize(filename, label): image_string = tf.io.read_file(filename) # 读取原始文件 image_decoded = tf.image.decode_jpeg(image_string) # 解码JPEG图片 image_resized = tf.image.resize(image_decoded, [256, 256]) / 255.0 return image_resized, label if __name__ == '__main__': # 构建训练数据集 train_cat_filenames = tf.constant([train_cats_dir + filename for filename in os.listdir(train_cats_dir)]) train_dog_filenames = tf.constant([train_dogs_dir + filename for filename in os.listdir(train_dogs_dir)]) train_filenames = tf.concat([train_cat_filenames, train_dog_filenames], axis=-1) train_labels = tf.concat([ tf.zeros(train_cat_filenames.shape, dtype=tf.int32), tf.ones(train_dog_filenames.shape, dtype=tf.int32)], axis=-1) train_dataset = tf.data.Dataset.from_tensor_slices((train_filenames, train_labels)) train_dataset = train_dataset.map( map_func=_decode_and_resize, num_parallel_calls=tf.data.experimental.AUTOTUNE) # 取出前buffer_size个数据放入buffer,并从其中随机采样,采样后的数据用后续数据替换 train_dataset = train_dataset.shuffle(buffer_size=23000) train_dataset = train_dataset.batch(batch_size) train_dataset = train_dataset.prefetch(tf.data.experimental.AUTOTUNE) model = tf.keras.Sequential([ tf.keras.layers.Conv2D(32, 3, activation='relu', input_shape=(256, 256, 3)), tf.keras.layers.MaxPooling2D(), tf.keras.layers.Conv2D(32, 5, activation='relu'), tf.keras.layers.MaxPooling2D(), tf.keras.layers.Flatten(), tf.keras.layers.Dense(64, activation='relu'), tf.keras.layers.Dense(2, activation='softmax') ]) model.compile( optimizer=tf.keras.optimizers.Adam(learning_rate=learning_rate), loss=tf.keras.losses.sparse_categorical_crossentropy, metrics=[tf.keras.metrics.sparse_categorical_accuracy] ) model.fit(train_dataset, epochs=num_epochs)
tf.data
结合 tf.io
和 tf.image
建立 tf.data.Dataset
数据集,并进行训练和测试的完整过程data_dir
所设置的目录(此处默认设置为 C:/datasets/cats_vs_dogs
,可根据自己的需求进行修改)# 构建测试数据集 test_cat_filenames = tf.constant([test_cats_dir + filename for filename in os.listdir(test_cats_dir)]) test_dog_filenames = tf.constant([test_dogs_dir + filename for filename in os.listdir(test_dogs_dir)]) test_filenames = tf.concat([test_cat_filenames, test_dog_filenames], axis=-1) test_labels = tf.concat([ tf.zeros(test_cat_filenames.shape, dtype=tf.int32), tf.ones(test_dog_filenames.shape, dtype=tf.int32)], axis=-1) test_dataset = tf.data.Dataset.from_tensor_slices((test_filenames, test_labels)) test_dataset = test_dataset.map(_decode_and_resize) test_dataset = test_dataset.batch(batch_size) print(model.metrics_names) print(model.evaluate(test_dataset))
prefetch()
的使用和在 map()
过程中加入 num_parallel_calls
参数,模型训练的时间可缩减至原来的一半甚至更低
TFRecord :TensorFlow 数据集存储格式
TensorFlow 中的数据集存储格式。当我们将数据集整理成 TFRecord 格式后,TensorFlow 就可以高效地读取和处理这些数据集
TFRecord 可以理解为一系列序列化的 tf.train.Example
元素所组成的列表文件,而每一个 tf.train.Example
又由若干个 tf.train.Feature
的字典组成。形式如下:
# dataset.tfrecords
[
{ # example 1 (tf.train.Example)
'feature_1': tf.train.Feature,
...
'feature_k': tf.train.Feature
},
...
{ # example N (tf.train.Example)
'feature_1': tf.train.Feature,
...
'feature_k': tf.train.Feature
}
]
为了将形式各样的数据集整理为 TFRecord 格式,我们可以对数据集中的每个元素进行以下步骤:
-
读取该数据元素到内存;
-
将该元素转换为
tf.train.Example
对象(每一个tf.train.Example
由若干个tf.train.Feature
的字典组成,因此需要先建立 Feature 的字典); -
将该
tf.train.Example
对象序列化为字符串,并通过一个预先定义的tf.io.TFRecordWriter
写入 TFRecord 文件。
而读取 TFRecord 数据则可按照以下步骤:
-
通过
tf.data.TFRecordDataset
读入原始的 TFRecord 文件(此时文件中的tf.train.Example
对象尚未被反序列化),获得一个tf.data.Dataset
数据集对象; -
通过
Dataset.map
方法,对该数据集对象中的每一个序列化的tf.train.Example
字符串执行tf.io.parse_single_example
函数,从而实现反序列化。
将 cats_vs_dogs 二分类数据集的训练集部分转换为 TFRecord 文件,并读取该文件
存储为 TFRecord 文件
import tensorflow as tf import os data_dir = 'C:/datasets/cats_vs_dogs' train_cats_dir = data_dir + '/train/cats/' train_dogs_dir = data_dir + '/train/dogs/' tfrecord_file = data_dir + '/train/train.tfrecords' train_cat_filenames = [train_cats_dir + filename for filename in os.listdir(train_cats_dir)] train_dog_filenames = [train_dogs_dir + filename for filename in os.listdir(train_dogs_dir)] train_filenames = train_cat_filenames + train_dog_filenames train_labels = [0] * len(train_cat_filenames) + [1] * len(train_dog_filenames) # 将 cat 类的标签设为0,dog 类的标签设为1
with tf.io.TFRecordWriter(tfrecord_file) as writer: for filename, label in zip(train_filenames, train_labels): image = open(filename, 'rb').read() # 读取数据集图片到内存,image 为一个 Byte 类型的字符串 feature = { # 建立 tf.train.Feature 字典 'image': tf.train.Feature(bytes_list=tf.train.BytesList(value=[image])), # 图片是一个 Bytes 对象 'label': tf.train.Feature(int64_list=tf.train.Int64List(value=[label])) # 标签是一个 Int 对象 } example = tf.train.Example(features=tf.train.Features(feature=feature)) # 通过字典建立 Example writer.write(example.SerializeToString()) # 将Example序列化并写入 TFRecord 文件
tf.train.Feature
支持三种数据格式:
-
tf.train.BytesList
:字符串或原始 Byte 文件(如图片),通过bytes_list
参数传入一个由字符串数组初始化的tf.train.BytesList
对象; -
tf.train.FloatList
:浮点数,通过float_list
参数传入一个由浮点数数组初始化的tf.train.FloatList
对象; -
tf.train.Int64List
:整数,通过int64_list
参数传入一个由整数数组初始化的tf.train.Int64List
对象。
如果只希望保存一个元素而非数组,传入一个只有一个元素的数组即可
读取 TFRecord 文件
raw_dataset = tf.data.TFRecordDataset(tfrecord_file) # 读取 TFRecord 文件 feature_description = { # 定义Feature结构,告诉解码器每个Feature的类型是什么 'image': tf.io.FixedLenFeature([], tf.string), 'label': tf.io.FixedLenFeature([], tf.int64), } def _parse_example(example_string): # 将 TFRecord 文件中的每一个序列化的 tf.train.Example 解码 feature_dict = tf.io.parse_single_example(example_string, feature_description) feature_dict['image'] = tf.io.decode_jpeg(feature_dict['image']) # 解码JPEG图片 return feature_dict['image'], feature_dict['label'] dataset = raw_dataset.map(_parse_example)
feature_description
类似于一个数据集的 “描述文件”,通过一个由键值对组成的字典,告知 tf.io.parse_single_example
函数每个 tf.train.Example
数据项有哪些 Feature,以及这些 Feature 的类型、形状等属性。tf.io.FixedLenFeature
的三个输入参数 shape
、 dtype
和 default_value
(可省略)为每个 Feature 的形状、类型和默认值。这里我们的数据项都是单个的数值或者字符串,所以 shape
为空数组。dataset
,这已经是一个可以用于训练的 tf.data.Dataset
对象import matplotlib.pyplot as plt for image, label in dataset: plt.title('cat' if label == 0 else 'dog') plt.imshow(image.numpy()) plt.show()
@tf.function
:图执行模式 *
追求高性能或部署模型,将模型转换为高效的 TensorFlow 图模型
import tensorflow as tf import time from zh.model.mnist.cnn import CNN from zh.model.utils import MNISTLoader num_batches = 400 batch_size = 50 learning_rate = 0.001 data_loader = MNISTLoader() @tf.function def train_one_step(X, y): with tf.GradientTape() as tape: y_pred = model(X) loss = tf.keras.losses.sparse_categorical_crossentropy(y_true=y, y_pred=y_pred) loss = tf.reduce_mean(loss) # 注意这里使用了TensorFlow内置的tf.print()。@tf.function不支持Python内置的print方法 tf.print("loss", loss) grads = tape.gradient(loss, model.variables) optimizer.apply_gradients(grads_and_vars=zip(grads, model.variables)) if __name__ == '__main__': model = CNN() optimizer = tf.keras.optimizers.Adam(learning_rate=learning_rate) start_time = time.time() for batch_index in range(num_batches): X, y = data_loader.get_batch(batch_size) train_one_step(X, y) end_time = time.time() print(end_time - start_time)
当被 @tf.function 修饰的函数第一次被调用的时候,进行以下操作: 在即时执行模式关闭的环境下,函数内的代码依次运行。也就是说,每个 tf. 方法都只是定义了计算节点,而并没有进行任何实质的计算。这与 TensorFlow 1.X 的图执行模式是一致的; 使用 AutoGraph 将函数中的 Python 控制流语句转换成 TensorFlow 计算图中的对应节点(比如说 while 和 for 语句转换为 tf.while , if 语句转换为 tf.cond 等等; 基于上面的两步,建立函数内代码的计算图表示(为了保证图的计算顺序,图中还会自动加入一些 tf.control_dependencies 节点); 运行一次这个计算图; 基于函数的名字和输入的函数参数的类型生成一个哈希值,并将建立的计算图缓存到一个哈希表中。 在被 @tf.function 修饰的函数之后再次被调用的时候,根据函数名和输入的函数参数的类型计算哈希值,检查哈希表中是否已经有了对应计算图的缓存。如果是,则直接使用已缓存的计算图,否则重新按上述步骤建立计算图。
import tensorflow as tf @tf.function def square_if_positive(x): if x > 0: x = x * x else: x = 0 return x a = tf.constant(1) b = tf.constant(-1) print(square_if_positive(a), square_if_positive(b)) print(tf.autograph.to_code(square_if_positive.python_function))
tf.autograph
模块的低层 API tf.autograph.to_code
将函数 square_if_positive
转换成 TensorFlow 计算图optimizer = tf.compat.v1.train.AdamOptimizer(learning_rate=learning_rate) num_batches = int(data_loader.num_train_data // batch_size * num_epochs) # 建立计算图 X_placeholder = tf.compat.v1.placeholder(name='X', shape=[None, 28, 28, 1], dtype=tf.float32) y_placeholder = tf.compat.v1.placeholder(name='y', shape=[None], dtype=tf.int32) y_pred = model(X_placeholder) loss = tf.keras.losses.sparse_categorical_crossentropy(y_true=y_placeholder, y_pred=y_pred) loss = tf.reduce_mean(loss) train_op = optimizer.minimize(loss) sparse_categorical_accuracy = tf.keras.metrics.SparseCategoricalAccuracy() # 建立Session with tf.compat.v1.Session() as sess: sess.run(tf.compat.v1.global_variables_initializer()) for batch_index in range(num_batches): X, y = data_loader.get_batch(batch_size) # 使用Session.run()将数据送入计算图节点,进行训练以及计算损失函数 _, loss_value = sess.run([train_op, loss], feed_dict={X_placeholder: X, y_placeholder: y}) print("batch %d: loss %f" % (batch_index, loss_value)) num_batches = int(data_loader.num_test_data // batch_size) for batch_index in range(num_batches): start_index, end_index = batch_index * batch_size, (batch_index + 1) * batch_size y_pred = model.predict(data_loader.test_data[start_index: end_index]) sess.run(sparse_categorical_accuracy.update(y_true=data_loader.test_label[start_index: end_index], y_pred=y_pred)) print("test accuracy: %f" % sess.run(sparse_categorical_accuracy.result()))
tf.compat.v1
模块以支持 TensorFlow 1.X 版本的 APItf.TensorArray
:TensorFlow 动态数组 *
在即时执行模式下,你可以直接使用一个 Python 列表(List)存放数组。
不过,如果你需要基于计算图的特性(例如使用 @tf.function
加速模型运行或者使用 SavedModel 导出模型),就无法使用这种方式了。
因此,TensorFlow 提供了 tf.TensorArray
,一种支持计算图特性的 TensorFlow 动态数组。
其声明的方式为:
-
arr = tf.TensorArray(dtype, size, dynamic_size=False)
:声明一个大小为size
,类型为dtype
的 TensorArrayarr
。如果将dynamic_size
参数设置为True
,则该数组会自动增长空间。
其读取和写入的方法为:
-
write(index, value)
:将value
写入数组的第index
个位置; -
read(index)
:读取数组的第index
个值;
除此以外,TensorArray 还包括 stack()
、 unstack()
等常用操作,可参考 文档 以了解详情。
请注意,由于需要支持计算图, tf.TensorArray
的 write()
方法是不可以忽略左值的!也就是说,在图执行模式下,必须按照以下的形式写入数组:
arr = arr.write(index, value)
这样才可以正常生成一个计算图操作,并将该操作返回给 arr
。而不可以写成:
arr.write(index, value) # 生成的计算图操作没有左值接收,从而丢失
import tensorflow as tf @tf.function def array_write_and_read(): arr = tf.TensorArray(dtype=tf.float32, size=3) arr = arr.write(0, tf.constant(0.0)) arr = arr.write(1, tf.constant(1.0)) arr = arr.write(2, tf.constant(2.0)) arr_0 = arr.read(0) arr_1 = arr.read(1) arr_2 = arr.read(2) return arr_0, arr_1, arr_2 a, b, c = array_write_and_read() print(a, b, c)
tf.config
:GPU 的使用与分配 *
gpus = tf.config.experimental.list_physical_devices(device_type='GPU') cpus = tf.config.experimental.list_physical_devices(device_type='CPU') print(gpus, cpus)
gpus = tf.config.experimental.list_physical_devices(device_type='GPU') tf.config.experimental.set_visible_devices(devices=gpus[0:2], device_type='GPU')
GPU:0
和 GPU:1
)gpus = tf.config.experimental.list_physical_devices(device_type='GPU') for gpu in gpus: tf.config.experimental.set_memory_growth(device=gpu, enable=True)
gpus = tf.config.experimental.list_physical_devices(device_type='GPU') tf.config.experimental.set_virtual_device_configuration( gpus[0], [tf.config.experimental.VirtualDeviceConfiguration(memory_limit=1024)])
gpus = tf.config.experimental.list_physical_devices('GPU') tf.config.experimental.set_virtual_device_configuration( gpus[0], [tf.config.experimental.VirtualDeviceConfiguration(memory_limit=2048), tf.config.experimental.VirtualDeviceConfiguration(memory_limit=2048)])
GPU:0
的基础上建立了两个显存均为 2GB 的虚拟 GPU。