软件文档写作
4
Tensorflow教程/TensorFlow-Code Framework.md
Normal file
@@ -0,0 +1,4 @@
|
|||||||
|
## Framework
|
||||||
|
|
||||||
|
> 根据网络日志,详细解释Framework的工作原理
|
||||||
|
|
||||||
964
Tensorflow教程/TensorFlow-Dateset.md
Normal file
@@ -0,0 +1,964 @@
|
|||||||
|
导入数据(Reading data)
|
||||||
|
上一篇介绍了TensorFlow读取数据的四种方法:tf.data、Feeding、QueueRunner、Preloaded data。
|
||||||
|
|
||||||
|
推荐:如何构建高性能的输入 pipeline
|
||||||
|
|
||||||
|
本篇的内容主要介绍 tf.data API的使用
|
||||||
|
|
||||||
|
文章目录
|
||||||
|
导入数据(Reading data)
|
||||||
|
1. Dataset 的基本机制 ¶
|
||||||
|
1.1 了解 Dataset 的结构并尝试创建 Dataset ¶
|
||||||
|
1.2 了解迭代器的作用,并创建 Iterator ¶
|
||||||
|
1.3 从迭代器中读取数据 ¶
|
||||||
|
1.4 保存迭代器的状态 ¶
|
||||||
|
2. 构建 Dataset ¶
|
||||||
|
2.1 基于 NumPy 数组构建Dataset ¶
|
||||||
|
2.2 基于 tf.data.TFRecordDataset 构建 Dataset ¶
|
||||||
|
2.3 基于 tf.data.FixedLengthRecordDataset 构建 Dataset ¶
|
||||||
|
2.4 基于 tf.data.TextLineDataset 构建 Dataset ¶
|
||||||
|
2.5 基于 tf.contrib.data.CsvDataset 构建 Dataset ¶
|
||||||
|
2.5 直接从文件读取,解析数据 ¶
|
||||||
|
3. 用 Dataset.map() 进行数据预处理 ¶
|
||||||
|
3.1 从 tf.Example 中解析出数据 ¶
|
||||||
|
3.2 解码图片数据并调整其大小 / 直接从文件读取文件 ¶
|
||||||
|
3.3 基于 tf.py_func 使用 Python 函数进行预处理 ¶
|
||||||
|
4. 数据集进行 batch ¶
|
||||||
|
4.1 最简单的 batch(直接 stack) ¶
|
||||||
|
4.2 将 Tensor 填充成统一大小,然后 batch ¶
|
||||||
|
5. 训练时数据集的配置 ¶
|
||||||
|
5.1 迭代多个 epoch ¶
|
||||||
|
5.2 随机 shuffle 数据集 ¶
|
||||||
|
5.3 tf.data 和使用高阶 API 的混合使用 ¶
|
||||||
|
5.3.1 在 tf.train.MonitoredTrainingSession 中使用 tf.data ¶
|
||||||
|
5.3.2 在 tf.estimator.Estimator 中使用 tf.data ¶
|
||||||
|
|
||||||
|
基于 tf.data API,我们可以使用简单的代码来构建复杂的输入 pipeline。 (例1,从分布式文件系统中读取数据、进行预处理、合成为 batch、训练中使用数据集;例2,文本模型的输入 pipeline 需要从原始文本数据中提取符号、根据对照表将其转换为嵌入标识符,以及将不同长度的序列组合成batch数据等。) 使用 tf.data API 可以轻松处理大量数据、不同的数据格式以及复杂的转换。
|
||||||
|
tf.data API 在 TensorFlow 中引入了两个新概念:
|
||||||
|
|
||||||
|
tf.data.Dataset:表示一系列元素,其中每个元素包含一个或多个 Tensor 对象。例如,在图片管道中,一个元素可能是单个训练样本,具有一对表示图片数据和标签的张量。可以通过两种不同的方式来创建数据集。
|
||||||
|
|
||||||
|
直接从 Tensor 创建 Dataset(例如 Dataset.from_tensor_slices());当然 Numpy 也是可以的,TensorFlow 会自动将其转换为 Tensor。
|
||||||
|
|
||||||
|
通过对一个或多个 tf.data.Dataset 对象来使用变换(例如 Dataset.batch())来创建 Dataset
|
||||||
|
|
||||||
|
tf.data.Iterator:这是从数据集中提取元素的主要方法。Iterator.get_next() 指令会在执行时生成 Dataset 的下一个元素,并且此指令通常充当输入管道和模型之间的接口。最简单的迭代器是“单次迭代器”,它会对处理好的 Dataset 进行单次迭代。要实现更复杂的用途,您可以通过 `Iterator.initializer` 指令使用不同的数据集重新初始化和参数化迭代器,这样一来,您就可以在同一个程序中对训练和验证数据进行多次迭代(举例而言)。
|
||||||
|
|
||||||
|
1. Dataset 的基本机制 ¶
|
||||||
|
本部分将介绍:
|
||||||
|
|
||||||
|
Dataset 的基础知识,并尝试创建 Dataset
|
||||||
|
Iterator 的基础知识,并尝试创建 Iterator
|
||||||
|
通过 Iterator 来提取 Dataset 中的数据
|
||||||
|
要构建输入 pipeline,你必须首先根据数据集的存储方式选择相应的方法创建 Dataset 对象来读取数据。(如果你的数据在内存中,请使用tf.data.Dataset.from_tensors() 或 tf.data.Dataset.from_tensor_slices() 来创建 Dataset;如果你的数据是 tfrecord 格式的,那么请使用 tf.data.TFRecordDataset 来创建 Dataset)
|
||||||
|
|
||||||
|
有了 Dataset 对象以后,您就可以通过使用 tf.data.Dataset 对象的各种方法对其进行处理。例如,您可以对Dataset的每一个元素使用某种变换,例 Dataset.map()(为每个元素使用一个函数),也可以对多个元素使用某种变换(例如 Dataset.batch())。 要了解所有可用的变换,请参阅 tf.data.Dataset 的文档。
|
||||||
|
|
||||||
|
消耗 Dataset 中值的最常见方法是构建迭代器对象。通过迭代器对象,每次可以访问数据集中的一个元素 (例如,通过调用 Dataset.make_one_shot_iterator())。 tf.data.Iterator 提供了两个指令:Iterator.initializer,您可以通过此指令(重新)初始化迭代器的状态;以及 Iterator.get_next(),此指令返回迭代器中的下一个元素的 tf.Tensor 对象。根据您的需求,您可以选择不同类型的迭代器,下文将对此进行详细介绍。
|
||||||
|
|
||||||
|
1.1 了解 Dataset 的结构并尝试创建 Dataset ¶
|
||||||
|
一个 Dataset 对象包含多个元素,每个元素的结构都相同。每个元素包含一个或多个 tf.Tensor 对象,这些对象被称为组件。每个组件都有 tf.DType 属性,表示 Tensor 中元素的类型;以及 tf.TensorShape 属性,表示每个元素(可能部分指定)的静态形状。您可以通过 Dataset.output_types 和 Dataset.output_shapes 属性检查数据集元素各个组件的类型和形状。Dataset 的属性由构成该 Dataset 的元素的属性映射得到,元素可以是单个张量、张量元组,也可以是张量的嵌套元组。例如:
|
||||||
|
|
||||||
|
dataset1 = tf.data.Dataset.from_tensor_slices(tf.random_uniform([4, 10]))
|
||||||
|
print(dataset1.output_types) # ==> "tf.float32"
|
||||||
|
print(dataset1.output_shapes) # ==> "(10,)"
|
||||||
|
|
||||||
|
dataset2 = tf.data.Dataset.from_tensor_slices(
|
||||||
|
(tf.random_uniform([4]),
|
||||||
|
tf.random_uniform([4, 100], maxval=100, dtype=tf.int32)))
|
||||||
|
print(dataset2.output_types) # ==> "(tf.float32, tf.int32)"
|
||||||
|
print(dataset2.output_shapes) # ==> "((), (100,))"
|
||||||
|
|
||||||
|
dataset3 = tf.data.Dataset.zip((dataset1, dataset2))
|
||||||
|
print(dataset3.output_types) # ==> (tf.float32, (tf.float32, tf.int32))
|
||||||
|
print(dataset3.output_shapes) # ==> "(10, ((), (100,)))"
|
||||||
|
1
|
||||||
|
2
|
||||||
|
3
|
||||||
|
4
|
||||||
|
5
|
||||||
|
6
|
||||||
|
7
|
||||||
|
8
|
||||||
|
9
|
||||||
|
10
|
||||||
|
11
|
||||||
|
12
|
||||||
|
13
|
||||||
|
为 Dataset 中的元素的各个组件命名通常会带来便利性(例如,元素的各个组件表示不同特征时)。除了元组之外,还可以使用 命名元组(collections.namedtuple) 或 字典 来表示 Dataset 的单个元素。
|
||||||
|
|
||||||
|
dataset = tf.data.Dataset.from_tensor_slices(
|
||||||
|
{"a": tf.random_uniform([4]),
|
||||||
|
"b": tf.random_uniform([4, 100], maxval=100, dtype=tf.int32)})
|
||||||
|
print(dataset.output_types) # ==> "{'a': tf.float32, 'b': tf.int32}"
|
||||||
|
print(dataset.output_shapes) # ==> "{'a': (), 'b': (100,)}"
|
||||||
|
1
|
||||||
|
2
|
||||||
|
3
|
||||||
|
4
|
||||||
|
5
|
||||||
|
Dataset 的变换支持任何结构的数据集。在使用 Dataset.map()、Dataset.flat_map() 和 Dataset.filter() 函数时(这些转换会对每个元素应用一个函数),元素结构决定了函数的参数:
|
||||||
|
|
||||||
|
dataset1 = dataset1.map(lambda x: ...)
|
||||||
|
|
||||||
|
dataset2 = dataset2.flat_map(lambda x, y: ...)
|
||||||
|
|
||||||
|
# Note: Argument destructuring is not available in Python 3.
|
||||||
|
dataset3 = dataset3.filter(lambda x, (y, z): ...)
|
||||||
|
1
|
||||||
|
2
|
||||||
|
3
|
||||||
|
4
|
||||||
|
5
|
||||||
|
6
|
||||||
|
1.2 了解迭代器的作用,并创建 Iterator ¶
|
||||||
|
构建了表示输入数据的 Dataset 后,下一步就是创建 Iterator 来访问该数据集中的元素。tf.data API 目前支持下列迭代器,其复杂程度逐渐上升:
|
||||||
|
|
||||||
|
单次迭代器
|
||||||
|
可初始化迭代器
|
||||||
|
可重新初始化迭代器
|
||||||
|
可 feeding 迭代器
|
||||||
|
单次迭代器是最简单的迭代器形式,仅支持对数据集进行一次迭代,不需要显式初始化。单次迭代器可以处理现有的基于队列的输入管道支持的几乎所有情况,但不支持参数化。以 Dataset.range() 为例:
|
||||||
|
|
||||||
|
dataset = tf.data.Dataset.range(100)
|
||||||
|
iterator = dataset.make_one_shot_iterator()
|
||||||
|
next_element = iterator.get_next()
|
||||||
|
|
||||||
|
for i in range(100):
|
||||||
|
value = sess.run(next_element)
|
||||||
|
assert i == value
|
||||||
|
1
|
||||||
|
2
|
||||||
|
3
|
||||||
|
4
|
||||||
|
5
|
||||||
|
6
|
||||||
|
7
|
||||||
|
注意:目前,单次迭代器是唯一可轻松与 Estimator 配合使用的类型。
|
||||||
|
|
||||||
|
您需要先运行显式 iterator.initializer 指令,才能使用可初始化迭代器。虽然有些不便,但它允许您使用一个或多个 tf.placeholder() 张量(可在初始化迭代器时馈送)参数化数据集的定义。继续以 Dataset.range() 为例:
|
||||||
|
|
||||||
|
max_value = tf.placeholder(tf.int64, shape=[])
|
||||||
|
dataset = tf.data.Dataset.range(max_value)
|
||||||
|
iterator = dataset.make_initializable_iterator()
|
||||||
|
next_element = iterator.get_next()
|
||||||
|
|
||||||
|
# Initialize an iterator over a dataset with 10 elements.
|
||||||
|
sess.run(iterator.initializer, feed_dict={max_value: 10})
|
||||||
|
for i in range(10):
|
||||||
|
value = sess.run(next_element)
|
||||||
|
assert i == value
|
||||||
|
|
||||||
|
# Initialize the same iterator over a dataset with 100 elements.
|
||||||
|
sess.run(iterator.initializer, feed_dict={max_value: 100})
|
||||||
|
for i in range(100):
|
||||||
|
value = sess.run(next_element)
|
||||||
|
assert i == value
|
||||||
|
1
|
||||||
|
2
|
||||||
|
3
|
||||||
|
4
|
||||||
|
5
|
||||||
|
6
|
||||||
|
7
|
||||||
|
8
|
||||||
|
9
|
||||||
|
10
|
||||||
|
11
|
||||||
|
12
|
||||||
|
13
|
||||||
|
14
|
||||||
|
15
|
||||||
|
16
|
||||||
|
可重新初始化迭代器 可以通过多个不同的 Dataset 对象进行初始化。例如,您可能有一个训练输入管道,它会对输入图片进行随机扰动来改善泛化;还有一个验证输入管道,它会评估对未修改数据的预测。这些管道通常会使用不同的 Dataset 对象,这些对象具有相同的结构(即每个组件具有相同类型和兼容形状)。
|
||||||
|
|
||||||
|
# Define training and validation datasets with the same structure.
|
||||||
|
training_dataset = tf.data.Dataset.range(100).map(
|
||||||
|
lambda x: x + tf.random_uniform([], -10, 10, tf.int64))
|
||||||
|
validation_dataset = tf.data.Dataset.range(50)
|
||||||
|
|
||||||
|
# A reinitializable iterator is defined by its structure. We could use the
|
||||||
|
# `output_types` and `output_shapes` properties of either `training_dataset`
|
||||||
|
# or `validation_dataset` here, because they are compatible.
|
||||||
|
iterator = tf.data.Iterator.from_structure(training_dataset.output_types,
|
||||||
|
training_dataset.output_shapes)
|
||||||
|
next_element = iterator.get_next()
|
||||||
|
|
||||||
|
training_init_op = iterator.make_initializer(training_dataset)
|
||||||
|
validation_init_op = iterator.make_initializer(validation_dataset)
|
||||||
|
|
||||||
|
# Run 20 epochs in which the training dataset is traversed, followed by the
|
||||||
|
# validation dataset.
|
||||||
|
for _ in range(20):
|
||||||
|
# Initialize an iterator over the training dataset.
|
||||||
|
sess.run(training_init_op)
|
||||||
|
for _ in range(100):
|
||||||
|
sess.run(next_element)
|
||||||
|
|
||||||
|
# Initialize an iterator over the validation dataset.
|
||||||
|
sess.run(validation_init_op)
|
||||||
|
for _ in range(50):
|
||||||
|
sess.run(next_element)
|
||||||
|
1
|
||||||
|
2
|
||||||
|
3
|
||||||
|
4
|
||||||
|
5
|
||||||
|
6
|
||||||
|
7
|
||||||
|
8
|
||||||
|
9
|
||||||
|
10
|
||||||
|
11
|
||||||
|
12
|
||||||
|
13
|
||||||
|
14
|
||||||
|
15
|
||||||
|
16
|
||||||
|
17
|
||||||
|
18
|
||||||
|
19
|
||||||
|
20
|
||||||
|
21
|
||||||
|
22
|
||||||
|
23
|
||||||
|
24
|
||||||
|
25
|
||||||
|
26
|
||||||
|
27
|
||||||
|
可 feeding 迭代器可以与 tf.placeholder 一起使用,通过熟悉的 feed_dict 机制来选择每次调用 tf.Session.run 时所使用的 Iterator。它提供的功能与可重新初始化迭代器的相同,但在迭代器之间切换时不需要从数据集的开头初始化迭代器。例如,以上面的同一训练和验证数据集为例,您可以使用 tf.data.Iterator.from_string_handle 定义一个可让您在两个数据集之间切换的可 feeding 迭代器:
|
||||||
|
|
||||||
|
# Define training and validation datasets with the same structure.
|
||||||
|
training_dataset = tf.data.Dataset.range(100).map(
|
||||||
|
lambda x: x + tf.random_uniform([], -10, 10, tf.int64)).repeat()
|
||||||
|
validation_dataset = tf.data.Dataset.range(50)
|
||||||
|
|
||||||
|
# A feedable iterator is defined by a handle placeholder and its structure. We
|
||||||
|
# could use the `output_types` and `output_shapes` properties of either
|
||||||
|
# `training_dataset` or `validation_dataset` here, because they have
|
||||||
|
# identical structure.
|
||||||
|
handle = tf.placeholder(tf.string, shape=[])
|
||||||
|
iterator = tf.data.Iterator.from_string_handle(
|
||||||
|
handle, training_dataset.output_types, training_dataset.output_shapes)
|
||||||
|
next_element = iterator.get_next()
|
||||||
|
|
||||||
|
# You can use feedable iterators with a variety of different kinds of iterator
|
||||||
|
# (such as one-shot and initializable iterators).
|
||||||
|
training_iterator = training_dataset.make_one_shot_iterator()
|
||||||
|
validation_iterator = validation_dataset.make_initializable_iterator()
|
||||||
|
|
||||||
|
# The `Iterator.string_handle()` method returns a tensor that can be evaluated
|
||||||
|
# and used to feed the `handle` placeholder.
|
||||||
|
training_handle = sess.run(training_iterator.string_handle())
|
||||||
|
validation_handle = sess.run(validation_iterator.string_handle())
|
||||||
|
|
||||||
|
# Loop forever, alternating between training and validation.
|
||||||
|
while True:
|
||||||
|
# Run 200 steps using the training dataset. Note that the training dataset is
|
||||||
|
# infinite, and we resume from where we left off in the previous `while` loop
|
||||||
|
# iteration.
|
||||||
|
for _ in range(200):
|
||||||
|
sess.run(next_element, feed_dict={handle: training_handle})
|
||||||
|
|
||||||
|
# Run one pass over the validation dataset.
|
||||||
|
sess.run(validation_iterator.initializer)
|
||||||
|
for _ in range(50):
|
||||||
|
sess.run(next_element, feed_dict={handle: validation_handle})
|
||||||
|
1
|
||||||
|
2
|
||||||
|
3
|
||||||
|
4
|
||||||
|
5
|
||||||
|
6
|
||||||
|
7
|
||||||
|
8
|
||||||
|
9
|
||||||
|
10
|
||||||
|
11
|
||||||
|
12
|
||||||
|
13
|
||||||
|
14
|
||||||
|
15
|
||||||
|
16
|
||||||
|
17
|
||||||
|
18
|
||||||
|
19
|
||||||
|
20
|
||||||
|
21
|
||||||
|
22
|
||||||
|
23
|
||||||
|
24
|
||||||
|
25
|
||||||
|
26
|
||||||
|
27
|
||||||
|
28
|
||||||
|
29
|
||||||
|
30
|
||||||
|
31
|
||||||
|
32
|
||||||
|
33
|
||||||
|
34
|
||||||
|
35
|
||||||
|
36
|
||||||
|
1.3 从迭代器中读取数据 ¶
|
||||||
|
Iterator.get_next() 方法返回一个或多个 tf.Tensor 对象,这些对象对应于迭代器的下一个元素。每次 eval 这些张量时,它们都会获取底层数据集中下一个元素的值。(请注意,与 TensorFlow 中的其他有状态对象一样,调用 Iterator.get_next() 并不会立即使迭代器进入下个状态。相反,您必须使用 TensorFlow 表达式中返回的 tf.Tensor 对象,并将该表达式的结果传递到 tf.Session.run(),以获取下一个元素并使迭代器进入下个状态。)
|
||||||
|
|
||||||
|
如果迭代器到达数据集的末尾,则执行 Iterator.get_next() 指令会产生 tf.errors.OutOfRangeError。在此之后,迭代器将处于不可用状态;如果需要继续使用,则必须对其重新初始化。
|
||||||
|
|
||||||
|
dataset = tf.data.Dataset.range(5)
|
||||||
|
iterator = dataset.make_initializable_iterator()
|
||||||
|
next_element = iterator.get_next()
|
||||||
|
|
||||||
|
# Typically `result` will be the output of a model, or an optimizer's
|
||||||
|
# training operation.
|
||||||
|
result = tf.add(next_element, next_element)
|
||||||
|
|
||||||
|
sess.run(iterator.initializer)
|
||||||
|
print(sess.run(result)) # ==> "0"
|
||||||
|
print(sess.run(result)) # ==> "2"
|
||||||
|
print(sess.run(result)) # ==> "4"
|
||||||
|
print(sess.run(result)) # ==> "6"
|
||||||
|
print(sess.run(result)) # ==> "8"
|
||||||
|
try:
|
||||||
|
sess.run(result)
|
||||||
|
except tf.errors.OutOfRangeError:
|
||||||
|
print("End of dataset") # ==> "End of dataset"
|
||||||
|
1
|
||||||
|
2
|
||||||
|
3
|
||||||
|
4
|
||||||
|
5
|
||||||
|
6
|
||||||
|
7
|
||||||
|
8
|
||||||
|
9
|
||||||
|
10
|
||||||
|
11
|
||||||
|
12
|
||||||
|
13
|
||||||
|
14
|
||||||
|
15
|
||||||
|
16
|
||||||
|
17
|
||||||
|
18
|
||||||
|
一种常用的方法是将“训练循环”封装在 try-except 块中:
|
||||||
|
|
||||||
|
sess.run(iterator.initializer)
|
||||||
|
while True:
|
||||||
|
try:
|
||||||
|
sess.run(result)
|
||||||
|
except tf.errors.OutOfRangeError:
|
||||||
|
break
|
||||||
|
1
|
||||||
|
2
|
||||||
|
3
|
||||||
|
4
|
||||||
|
5
|
||||||
|
6
|
||||||
|
如果数据集的每个元素都具有嵌套结构,则 Iterator.get_next() 的返回值将是一个或多个 tf.Tensor 对象,这些对象具有相同的嵌套结构:
|
||||||
|
|
||||||
|
dataset1 = tf.data.Dataset.from_tensor_slices(tf.random_uniform([4, 10]))
|
||||||
|
dataset2 = tf.data.Dataset.from_tensor_slices((tf.random_uniform([4]), tf.random_uniform([4, 100])))
|
||||||
|
dataset3 = tf.data.Dataset.zip((dataset1, dataset2))
|
||||||
|
|
||||||
|
iterator = dataset3.make_initializable_iterator()
|
||||||
|
|
||||||
|
sess.run(iterator.initializer)
|
||||||
|
next1, (next2, next3) = iterator.get_next()
|
||||||
|
1
|
||||||
|
2
|
||||||
|
3
|
||||||
|
4
|
||||||
|
5
|
||||||
|
6
|
||||||
|
7
|
||||||
|
8
|
||||||
|
注意:next1、next2、next3 由相同的 op / node 产生,因此eval next1、next2 或 next3 中的任何一个都会使所有组件的迭代器进入下个状态。
|
||||||
|
|
||||||
|
1.4 保存迭代器的状态 ¶
|
||||||
|
tf.contrib.data.make_saveable_from_iterator 函数会从迭代器创建一个 SaveableObject,这个对象可以用来保存、恢复迭代器的当前状态(甚至是整个输入 pipeline)。
|
||||||
|
|
||||||
|
# Create saveable object from iterator.
|
||||||
|
saveable = tf.contrib.data.make_saveable_from_iterator(iterator)
|
||||||
|
|
||||||
|
# Save the iterator state by adding it to the saveable objects collection.
|
||||||
|
tf.add_to_collection(tf.GraphKeys.SAVEABLE_OBJECTS, saveable)
|
||||||
|
saver = tf.train.Saver()
|
||||||
|
|
||||||
|
with tf.Session() as sess:
|
||||||
|
|
||||||
|
if should_checkpoint:
|
||||||
|
saver.save(path_to_checkpoint)
|
||||||
|
|
||||||
|
# Restore the iterator state.
|
||||||
|
with tf.Session() as sess:
|
||||||
|
saver.restore(sess, path_to_checkpoint)
|
||||||
|
1
|
||||||
|
2
|
||||||
|
3
|
||||||
|
4
|
||||||
|
5
|
||||||
|
6
|
||||||
|
7
|
||||||
|
8
|
||||||
|
9
|
||||||
|
10
|
||||||
|
11
|
||||||
|
12
|
||||||
|
13
|
||||||
|
14
|
||||||
|
15
|
||||||
|
2. 构建 Dataset ¶
|
||||||
|
2.1 基于 NumPy 数组构建Dataset ¶
|
||||||
|
如果您的所有输入数据都适合存储在内存中,则根据输入数据创建 Dataset 的最简单方法是将它们转换为 tf.Tensor 对象,并使用 Dataset.from_tensor_slices()。
|
||||||
|
|
||||||
|
# Load the training data into two NumPy arrays, for example using `np.load()`.
|
||||||
|
with np.load("/var/data/training_data.npy") as data:
|
||||||
|
features = data["features"]
|
||||||
|
labels = data["labels"]
|
||||||
|
|
||||||
|
# Assume that each row of `features` corresponds to the same row as `labels`.
|
||||||
|
assert features.shape[0] == labels.shape[0]
|
||||||
|
|
||||||
|
dataset = tf.data.Dataset.from_tensor_slices((features, labels))
|
||||||
|
1
|
||||||
|
2
|
||||||
|
3
|
||||||
|
4
|
||||||
|
5
|
||||||
|
6
|
||||||
|
7
|
||||||
|
8
|
||||||
|
9
|
||||||
|
注意:上面的代码段会将 features 和 labels 数组作为 tf.constant() 指令嵌入在 TensorFlow 图中。这非常适合小型数据集,但会浪费内存,因为这会多次复制数组的内容,并可能会达到 tf.GraphDef 协议缓冲区的 2GB 上限。
|
||||||
|
|
||||||
|
作为替代方案,您可以基于 tf.placeholder() 张量定义 Dataset,并使用可初始化 Iterator,然后在初始化 dataset 的 Iterator 时将 NumPy 数组供给程序。
|
||||||
|
|
||||||
|
# Load the training data into two NumPy arrays, for example using `np.load()`.
|
||||||
|
with np.load("/var/data/training_data.npy") as data:
|
||||||
|
features = data["features"]
|
||||||
|
labels = data["labels"]
|
||||||
|
|
||||||
|
# Assume that each row of `features` corresponds to the same row as `labels`.
|
||||||
|
assert features.shape[0] == labels.shape[0]
|
||||||
|
|
||||||
|
features_placeholder = tf.placeholder(features.dtype, features.shape)
|
||||||
|
labels_placeholder = tf.placeholder(labels.dtype, labels.shape)
|
||||||
|
|
||||||
|
dataset = tf.data.Dataset.from_tensor_slices((features_placeholder, labels_placeholder))
|
||||||
|
# [Other transformations on `dataset`...]
|
||||||
|
dataset = ...
|
||||||
|
iterator = dataset.make_initializable_iterator()
|
||||||
|
|
||||||
|
sess.run(iterator.initializer, feed_dict={features_placeholder: features,
|
||||||
|
labels_placeholder: labels})
|
||||||
|
1
|
||||||
|
2
|
||||||
|
3
|
||||||
|
4
|
||||||
|
5
|
||||||
|
6
|
||||||
|
7
|
||||||
|
8
|
||||||
|
9
|
||||||
|
10
|
||||||
|
11
|
||||||
|
12
|
||||||
|
13
|
||||||
|
14
|
||||||
|
15
|
||||||
|
16
|
||||||
|
17
|
||||||
|
18
|
||||||
|
2.2 基于 tf.data.TFRecordDataset 构建 Dataset ¶
|
||||||
|
tf.data API 支持多种文件格式,因此您可以处理那些不适合存储在内存中的大型数据集。例如,TFRecord 文件格式是一种面向记录的简单二进制格式,很多 TensorFlow 应用采用此格式来训练数据。通过 tf.data.TFRecordDataset 类,您可以将一个或多个 TFRecord 文件的内容作为输入管道的一部分进行流式传输。
|
||||||
|
|
||||||
|
# Creates a dataset that reads all of the examples from two files.
|
||||||
|
filenames = ["/var/data/file1.tfrecord", "/var/data/file2.tfrecord"]
|
||||||
|
dataset = tf.data.TFRecordDataset(filenames)
|
||||||
|
1
|
||||||
|
2
|
||||||
|
3
|
||||||
|
TFRecordDataset 初始化程序的 filenames 参数可以是字符串、字符串列表,也可以是字符串 tf.Tensor。因此,如果您有两组分别用于训练和验证的文件,则可以使用 tf.placeholder(tf.string) 来表示文件名,并使用适当的文件名初始化迭代器:
|
||||||
|
|
||||||
|
filenames = tf.placeholder(tf.string, shape=[None])
|
||||||
|
dataset = tf.data.TFRecordDataset(filenames)
|
||||||
|
#如何将数据解析(parse)为Tensor见 3.1 节
|
||||||
|
dataset = dataset.map(...) # Parse the record into tensors.
|
||||||
|
dataset = dataset.repeat() # Repeat the input indefinitely.
|
||||||
|
dataset = dataset.batch(32)
|
||||||
|
iterator = dataset.make_initializable_iterator()
|
||||||
|
|
||||||
|
# You can feed the initializer with the appropriate filenames for the current
|
||||||
|
# phase of execution, e.g. training vs. validation.
|
||||||
|
|
||||||
|
# Initialize `iterator` with training data.
|
||||||
|
training_filenames = ["/var/data/file1.tfrecord", "/var/data/file2.tfrecord"]
|
||||||
|
sess.run(iterator.initializer, feed_dict={filenames: training_filenames})
|
||||||
|
|
||||||
|
# Initialize `iterator` with validation data.
|
||||||
|
validation_filenames = ["/var/data/validation1.tfrecord", ...]
|
||||||
|
sess.run(iterator.initializer, feed_dict={filenames: validation_filenames})
|
||||||
|
1
|
||||||
|
2
|
||||||
|
3
|
||||||
|
4
|
||||||
|
5
|
||||||
|
6
|
||||||
|
7
|
||||||
|
8
|
||||||
|
9
|
||||||
|
10
|
||||||
|
11
|
||||||
|
12
|
||||||
|
13
|
||||||
|
14
|
||||||
|
15
|
||||||
|
16
|
||||||
|
17
|
||||||
|
18
|
||||||
|
2.3 基于 tf.data.FixedLengthRecordDataset 构建 Dataset ¶
|
||||||
|
有很多数据集都是二进制文件。tf.data.FixedLengthRecordDataset 提供了一种从一个或多个二进制文件中读取数据的简单方法。给定一个或多个文件名,FixedLengthRecordDataset
|
||||||
|
|
||||||
|
filenames = ["/var/data/file1.bin", "/var/data/file2.bin"]
|
||||||
|
dataset = tf.data.FixedLengthRecordDataset(filenames, record_bytes, header_bytes, footer_bytes, buffer_size)
|
||||||
|
1
|
||||||
|
2
|
||||||
|
filenames : tf.string,包含一个或多个文件名;
|
||||||
|
record_bytes :tf.int64,一个 record 占的 bytes;
|
||||||
|
header_bytes :(可选)tf.int64,每个文件开头需要跳过多少 bytes;
|
||||||
|
footer_bytes :(可选)tf.int64,每个文件结尾需要忽略多少 bytes;
|
||||||
|
buffer_size :(可选)tf.int64,读取时,缓冲多少bytes;
|
||||||
|
|
||||||
|
2.4 基于 tf.data.TextLineDataset 构建 Dataset ¶
|
||||||
|
很多数据集都是作为一个或多个文本文件分布的。tf.data.TextLineDataset 提供了一种从一个或多个文本文件中提取行的简单方法。给定一个或多个文件名,TextLineDataset 会为这些文件的每行生成一个字符串值元素。像 TFRecordDataset 一样,TextLineDataset 将 filenames 视为 tf.Tensor,因此您可以通过传递 tf.placeholder(tf.string) 来进行参数化。
|
||||||
|
|
||||||
|
filenames = ["/var/data/file1.txt", "/var/data/file2.txt"]
|
||||||
|
dataset = tf.data.TextLineDataset(filenames)
|
||||||
|
1
|
||||||
|
2
|
||||||
|
默认情况下,TextLineDataset 每次读取每个文件的一行,这可能是不是我们想要的,例如,如果文件以标题行开头或包含评论。可以使用 Dataset.skip() 和 Dataset.filter() 转换来移除这些行。为了将这些转换分别应用于每个文件,我们使用 Dataset.flat_map() 为每个文件创建一个嵌套的 Dataset。
|
||||||
|
|
||||||
|
filenames = ["/var/data/file1.txt", "/var/data/file2.txt"]
|
||||||
|
|
||||||
|
dataset = tf.data.Dataset.from_tensor_slices(filenames)
|
||||||
|
|
||||||
|
# Use `Dataset.flat_map()` to transform each file as a separate nested dataset,
|
||||||
|
# and then concatenate their contents sequentially into a single "flat" dataset.
|
||||||
|
# * Skip the first line (header row).
|
||||||
|
# * Filter out lines beginning with "#" (comments).
|
||||||
|
dataset = dataset.flat_map(
|
||||||
|
lambda filename: (
|
||||||
|
tf.data.TextLineDataset(filename)
|
||||||
|
.skip(1)
|
||||||
|
.filter(lambda line: tf.not_equal(tf.substr(line, 0, 1), "#"))))
|
||||||
|
1
|
||||||
|
2
|
||||||
|
3
|
||||||
|
4
|
||||||
|
5
|
||||||
|
6
|
||||||
|
7
|
||||||
|
8
|
||||||
|
9
|
||||||
|
10
|
||||||
|
11
|
||||||
|
12
|
||||||
|
13
|
||||||
|
2.5 基于 tf.contrib.data.CsvDataset 构建 Dataset ¶
|
||||||
|
csv 是一种以纯文本方式储存表格数据的文件格式。tf.contrib.data.CsvDataset 类提供了一种方式去从一个或多个符合 RFC 4180 规范的 CSV 文件中提取 records。
|
||||||
|
|
||||||
|
# Creates a dataset that reads all of the records from two CSV files, each with
|
||||||
|
# eight float columns
|
||||||
|
filenames = ["/var/data/file1.csv", "/var/data/file2.csv"]
|
||||||
|
record_defaults = [tf.float32] * 8 # Eight required float columns
|
||||||
|
dataset = tf.contrib.data.CsvDataset(filenames, record_defaults)
|
||||||
|
1
|
||||||
|
2
|
||||||
|
3
|
||||||
|
4
|
||||||
|
5
|
||||||
|
如果一些列是空的,你可以设置默认值。
|
||||||
|
|
||||||
|
# Creates a dataset that reads all of the records from two CSV files, each with
|
||||||
|
# four float columns which may have missing values
|
||||||
|
record_defaults = [[0.0]] * 8
|
||||||
|
dataset = tf.contrib.data.CsvDataset(filenames, record_defaults)
|
||||||
|
1
|
||||||
|
2
|
||||||
|
3
|
||||||
|
4
|
||||||
|
默认情况下,一个 CsvDataset 每次从文件中读取一行,这可能不是想要的(例如:如果文件的 header line 应该被忽略;或者输入中的一些列是不需要的)。可以使用 header 及 select_cols 参数完成这些想法。
|
||||||
|
|
||||||
|
# Creates a dataset that reads all of the records from two CSV files, each with
|
||||||
|
# four float columns which may have missing values
|
||||||
|
record_defaults = [[0.0]] * 8
|
||||||
|
dataset = tf.contrib.data.CsvDataset(filenames, record_defaults)
|
||||||
|
1
|
||||||
|
2
|
||||||
|
3
|
||||||
|
4
|
||||||
|
2.5 直接从文件读取,解析数据 ¶
|
||||||
|
这一部分其实就是3.2节代码所示
|
||||||
|
|
||||||
|
3. 用 Dataset.map() 进行数据预处理 ¶
|
||||||
|
Dataset.map(f) 转换通过将指定函数 f 应用于输入数据集的每个元素来生成新数据集。此转换基于 map() 函数(通常应用于函数式编程语言中的列表(和其他结构))。函数 f 会接受表示输入中单个元素的 tf.Tensor 对象,并返回表示新数据集中单个元素的 tf.Tensor 对象。此函数的实现使用标准的 TensorFlow 指令将一个元素转换为另一个元素。
|
||||||
|
|
||||||
|
本部分介绍了如何使用 Dataset.map() 的常见示例。
|
||||||
|
|
||||||
|
3.1 从 tf.Example 中解析出数据 ¶
|
||||||
|
很多输入管道都从 TFRecord 格式的文件(例如使用 tf.python_io.TFRecordWriter 编写)中提取 tf.train.Example 协议缓冲区消息。每个 tf.train.Example 记录都包含一个或多个“特征”,输入管道通常会将这些特征转换为张量。
|
||||||
|
|
||||||
|
# Transforms a scalar string `example_proto` into a pair of a scalar string and
|
||||||
|
# a scalar integer, representing an image and its label, respectively.
|
||||||
|
def _parse_function(example_proto):
|
||||||
|
features = {"image": tf.FixedLenFeature((), tf.string, default_value=""),
|
||||||
|
"label": tf.FixedLenFeature((), tf.int32, default_value=0)}
|
||||||
|
parsed_features = tf.parse_single_example(example_proto, features)
|
||||||
|
return parsed_features["image"], parsed_features["label"]
|
||||||
|
|
||||||
|
# Creates a dataset that reads all of the examples from two files, and extracts
|
||||||
|
# the image and label features.
|
||||||
|
filenames = ["/var/data/file1.tfrecord", "/var/data/file2.tfrecord"]
|
||||||
|
dataset = tf.data.TFRecordDataset(filenames)
|
||||||
|
dataset = dataset.map(_parse_function)
|
||||||
|
1
|
||||||
|
2
|
||||||
|
3
|
||||||
|
4
|
||||||
|
5
|
||||||
|
6
|
||||||
|
7
|
||||||
|
8
|
||||||
|
9
|
||||||
|
10
|
||||||
|
11
|
||||||
|
12
|
||||||
|
13
|
||||||
|
3.2 解码图片数据并调整其大小 / 直接从文件读取文件 ¶
|
||||||
|
在用真实的图片数据训练神经网络时,通常需要将不同大小的图片转换为通用大小,这样就可以将它们批处理为具有固定大小的数据。
|
||||||
|
|
||||||
|
# Reads an image from a file, decodes it into a dense tensor, and resizes it
|
||||||
|
# to a fixed shape.
|
||||||
|
def _parse_function(filename, label):
|
||||||
|
image_string = tf.read_file(filename)
|
||||||
|
image_decoded = tf.image.decode_image(image_string)
|
||||||
|
image_resized = tf.image.resize_images(image_decoded, [28, 28])
|
||||||
|
return image_resized, label
|
||||||
|
|
||||||
|
# A vector of filenames.
|
||||||
|
filenames = tf.constant(["/var/data/image1.jpg", "/var/data/image2.jpg", ...])
|
||||||
|
|
||||||
|
# `labels[i]` is the label for the image in `filenames[i].
|
||||||
|
labels = tf.constant([0, 37, ...])
|
||||||
|
|
||||||
|
dataset = tf.data.Dataset.from_tensor_slices((filenames, labels))
|
||||||
|
dataset = dataset.map(_parse_function)
|
||||||
|
1
|
||||||
|
2
|
||||||
|
3
|
||||||
|
4
|
||||||
|
5
|
||||||
|
6
|
||||||
|
7
|
||||||
|
8
|
||||||
|
9
|
||||||
|
10
|
||||||
|
11
|
||||||
|
12
|
||||||
|
13
|
||||||
|
14
|
||||||
|
15
|
||||||
|
16
|
||||||
|
3.3 基于 tf.py_func 使用 Python 函数进行预处理 ¶
|
||||||
|
为了确保性能,我们建议您尽可能使用 TensorFlow 指令预处理数据。不过,在解析输入数据时,调用外部 Python 库有时很有用。为此,请在 Dataset.map() 转换中调用 tf.py_func() 指令。
|
||||||
|
|
||||||
|
# tf.py_func
|
||||||
|
tf.py_func(func, # 一个Python函数
|
||||||
|
inp, # 一个Tensor列表
|
||||||
|
Tout, # 输出的Tensor的dtype或Tensors的dtype列表
|
||||||
|
stateful=True, # 布尔值,输入值相同,输出值就相同,那么就将stateful设置为False
|
||||||
|
name=None)
|
||||||
|
1
|
||||||
|
2
|
||||||
|
3
|
||||||
|
4
|
||||||
|
5
|
||||||
|
6
|
||||||
|
下面是一个借助opencv进行图像预处理的例子。
|
||||||
|
|
||||||
|
import cv2
|
||||||
|
|
||||||
|
# Use a custom OpenCV function to read the image, instead of the standard
|
||||||
|
# TensorFlow `tf.read_file()` operation.
|
||||||
|
def _read_py_function(filename, label):
|
||||||
|
image_decoded = cv2.imread(filename.decode(), cv2.IMREAD_GRAYSCALE)
|
||||||
|
return image_decoded, label
|
||||||
|
|
||||||
|
# Use standard TensorFlow operations to resize the image to a fixed shape.
|
||||||
|
def _resize_function(image_decoded, label):
|
||||||
|
image_decoded.set_shape([None, None, None])
|
||||||
|
image_resized = tf.image.resize_images(image_decoded, [28, 28])
|
||||||
|
return image_resized, label
|
||||||
|
|
||||||
|
filenames = ["/var/data/image1.jpg", "/var/data/image2.jpg", ...]
|
||||||
|
labels = [0, 37, 29, 1, ...]
|
||||||
|
|
||||||
|
dataset = tf.data.Dataset.from_tensor_slices((filenames, labels))
|
||||||
|
dataset = dataset.map(
|
||||||
|
lambda filename, label: tuple(tf.py_func(
|
||||||
|
_read_py_function, [filename, label], [tf.uint8, label.dtype])))
|
||||||
|
dataset = dataset.map(_resize_function)
|
||||||
|
1
|
||||||
|
2
|
||||||
|
3
|
||||||
|
4
|
||||||
|
5
|
||||||
|
6
|
||||||
|
7
|
||||||
|
8
|
||||||
|
9
|
||||||
|
10
|
||||||
|
11
|
||||||
|
12
|
||||||
|
13
|
||||||
|
14
|
||||||
|
15
|
||||||
|
16
|
||||||
|
17
|
||||||
|
18
|
||||||
|
19
|
||||||
|
20
|
||||||
|
21
|
||||||
|
22
|
||||||
|
4. 数据集进行 batch ¶
|
||||||
|
4.1 最简单的 batch(直接 stack) ¶
|
||||||
|
最简单的 batch 处理方法是将数据集中的 n 个连续元素堆叠为一个元素。Dataset.batch() 转换正是这么做的,它与 tf.stack() 运算符具有相同的限制(被应用于元素的每个组件):即对于每个组件 i,所有元素的张量形状必须完全相同。
|
||||||
|
|
||||||
|
inc_dataset = tf.data.Dataset.range(100)
|
||||||
|
dec_dataset = tf.data.Dataset.range(0, -100, -1)
|
||||||
|
dataset = tf.data.Dataset.zip((inc_dataset, dec_dataset))
|
||||||
|
batched_dataset = dataset.batch(4)
|
||||||
|
|
||||||
|
iterator = batched_dataset.make_one_shot_iterator()
|
||||||
|
next_element = iterator.get_next()
|
||||||
|
|
||||||
|
print(sess.run(next_element)) # ==> ([0, 1, 2, 3], [ 0, -1, -2, -3])
|
||||||
|
print(sess.run(next_element)) # ==> ([4, 5, 6, 7], [-4, -5, -6, -7])
|
||||||
|
print(sess.run(next_element)) # ==> ([8, 9, 10, 11], [-8, -9, -10, -11])
|
||||||
|
1
|
||||||
|
2
|
||||||
|
3
|
||||||
|
4
|
||||||
|
5
|
||||||
|
6
|
||||||
|
7
|
||||||
|
8
|
||||||
|
9
|
||||||
|
10
|
||||||
|
11
|
||||||
|
4.2 将 Tensor 填充成统一大小,然后 batch ¶
|
||||||
|
使用填充批处理张量
|
||||||
|
上述方法适用于具有相同大小的张量。不过,很多模型(例如序列模型)处理的输入数据可能具有不同的大小(例如序列的长度不同)。为了解决这种情况,可以通过 Dataset.padded_batch() 转换来指定一个或多个会被填充的维度,从而批处理不同形状的张量。
|
||||||
|
|
||||||
|
dataset = tf.data.Dataset.range(100)
|
||||||
|
dataset = dataset.map(lambda x: tf.fill([tf.cast(x, tf.int32)], x))
|
||||||
|
dataset = dataset.padded_batch(4, padded_shapes=[None])
|
||||||
|
|
||||||
|
iterator = dataset.make_one_shot_iterator()
|
||||||
|
next_element = iterator.get_next()
|
||||||
|
|
||||||
|
print(sess.run(next_element)) # ==> [[0, 0, 0],
|
||||||
|
# [1, 0, 0],
|
||||||
|
# [2, 2, 0],
|
||||||
|
# [3, 3, 3]]
|
||||||
|
|
||||||
|
print(sess.run(next_element)) # ==> [[4, 4, 4, 4, 0, 0, 0],
|
||||||
|
# [5, 5, 5, 5, 5, 0, 0],
|
||||||
|
# [6, 6, 6, 6, 6, 6, 0],
|
||||||
|
# [7, 7, 7, 7, 7, 7, 7]]
|
||||||
|
1
|
||||||
|
2
|
||||||
|
3
|
||||||
|
4
|
||||||
|
5
|
||||||
|
6
|
||||||
|
7
|
||||||
|
8
|
||||||
|
9
|
||||||
|
10
|
||||||
|
11
|
||||||
|
12
|
||||||
|
13
|
||||||
|
14
|
||||||
|
15
|
||||||
|
16
|
||||||
|
您可以通过 Dataset.padded_batch() 转换为每个组件的每个维度设置不同的填充,并且可以采用可变长度(在上面的示例中用 None 表示)或恒定长度。也可以替换填充值,默认设置为 0。
|
||||||
|
|
||||||
|
5. 训练时数据集的配置 ¶
|
||||||
|
5.1 迭代多个 epoch ¶
|
||||||
|
tf.data API 提供了两种主要方式来处理同一数据的多个周期。
|
||||||
|
|
||||||
|
要迭代数据集多个周期,最简单的方法是使用 Dataset.repeat()。例如,要创建一个将其输入重复 10 个周期的数据集:
|
||||||
|
|
||||||
|
filenames = ["/var/data/file1.tfrecord", "/var/data/file2.tfrecord"]
|
||||||
|
dataset = tf.data.TFRecordDataset(filenames)
|
||||||
|
dataset = dataset.map(...)
|
||||||
|
dataset = dataset.repeat(10)
|
||||||
|
dataset = dataset.batch(32)
|
||||||
|
1
|
||||||
|
2
|
||||||
|
3
|
||||||
|
4
|
||||||
|
5
|
||||||
|
应用不带参数的 Dataset.repeat() 转换将无限次地重复输入。Dataset.repeat() 转换将其参数连接起来,而不会在一个周期结束和下一个周期开始时发出信号。
|
||||||
|
|
||||||
|
如果您想在每个周期结束时收到信号,则可以编写在数据集结束时捕获 tf.errors.OutOfRangeError 的训练循环。此时,您可以收集关于该周期的一些统计信息(例如验证错误)。
|
||||||
|
|
||||||
|
filenames = ["/var/data/file1.tfrecord", "/var/data/file2.tfrecord"]
|
||||||
|
dataset = tf.data.TFRecordDataset(filenames)
|
||||||
|
dataset = dataset.map(...)
|
||||||
|
dataset = dataset.batch(32)
|
||||||
|
iterator = dataset.make_initializable_iterator()
|
||||||
|
next_element = iterator.get_next()
|
||||||
|
|
||||||
|
# Compute for 100 epochs.
|
||||||
|
for _ in range(100):
|
||||||
|
sess.run(iterator.initializer)
|
||||||
|
while True:
|
||||||
|
try:
|
||||||
|
sess.run(next_element)
|
||||||
|
except tf.errors.OutOfRangeError:
|
||||||
|
break
|
||||||
|
|
||||||
|
# [Perform end-of-epoch calculations here.]
|
||||||
|
1
|
||||||
|
2
|
||||||
|
3
|
||||||
|
4
|
||||||
|
5
|
||||||
|
6
|
||||||
|
7
|
||||||
|
8
|
||||||
|
9
|
||||||
|
10
|
||||||
|
11
|
||||||
|
12
|
||||||
|
13
|
||||||
|
14
|
||||||
|
15
|
||||||
|
16
|
||||||
|
17
|
||||||
|
5.2 随机 shuffle 数据集 ¶
|
||||||
|
Dataset.shuffle() 转换使用一个类似于 tf.RandomShuffleQueue 的算法来随机重排输入数据集:它保留一个固定大小的缓冲区,并以相同方式从此缓冲区中随机选择下一个元素。
|
||||||
|
|
||||||
|
filenames = ["/var/data/file1.tfrecord", "/var/data/file2.tfrecord"]
|
||||||
|
dataset = tf.data.TFRecordDataset(filenames)
|
||||||
|
dataset = dataset.map(...)
|
||||||
|
dataset = dataset.shuffle(buffer_size=10000)
|
||||||
|
dataset = dataset.batch(32)
|
||||||
|
dataset = dataset.repeat()
|
||||||
|
1
|
||||||
|
2
|
||||||
|
3
|
||||||
|
4
|
||||||
|
5
|
||||||
|
6
|
||||||
|
5.3 tf.data 和使用高阶 API 的混合使用 ¶
|
||||||
|
5.3.1 在 tf.train.MonitoredTrainingSession 中使用 tf.data ¶
|
||||||
|
tf.train.MonitoredTrainingSession API 简化了在分布式设置下运行 TensorFlow 的很多方面。MonitoredTrainingSession 使用 tf.errors.OutOfRangeError 表示训练已完成,因此要将其与 tf.data API 结合使用,我们建议使用 Dataset.make_one_shot_iterator()。例如:
|
||||||
|
|
||||||
|
filenames = ["/var/data/file1.tfrecord", "/var/data/file2.tfrecord"]
|
||||||
|
dataset = tf.data.TFRecordDataset(filenames)
|
||||||
|
dataset = dataset.map(...)
|
||||||
|
dataset = dataset.shuffle(buffer_size=10000)
|
||||||
|
dataset = dataset.batch(32)
|
||||||
|
dataset = dataset.repeat(num_epochs)
|
||||||
|
iterator = dataset.make_one_shot_iterator()
|
||||||
|
|
||||||
|
next_example, next_label = iterator.get_next()
|
||||||
|
loss = model_function(next_example, next_label)
|
||||||
|
|
||||||
|
training_op = tf.train.AdagradOptimizer(...).minimize(loss)
|
||||||
|
|
||||||
|
with tf.train.MonitoredTrainingSession(...) as sess:
|
||||||
|
while not sess.should_stop():
|
||||||
|
sess.run(training_op)
|
||||||
|
|
||||||
|
5.3.2 在 tf.estimator.Estimator 中使用 tf.data ¶
|
||||||
|
要在 tf.estimator.Estimator 的 input_fn 中使用 Dataset,我们建议使用 Dataset.make_one_shot_iterator()。例如:
|
||||||
|
|
||||||
|
def dataset_input_fn():
|
||||||
|
filenames = ["/var/data/file1.tfrecord", "/var/data/file2.tfrecord"]
|
||||||
|
dataset = tf.data.TFRecordDataset(filenames)
|
||||||
|
|
||||||
|
# Use `tf.parse_single_example()` to extract data from a `tf.Example`
|
||||||
|
# protocol buffer, and perform any additional per-record preprocessing.
|
||||||
|
def parser(record):
|
||||||
|
keys_to_features = {
|
||||||
|
"image_data": tf.FixedLenFeature((), tf.string, default_value=""),
|
||||||
|
"date_time": tf.FixedLenFeature((), tf.int64, default_value=""),
|
||||||
|
"label": tf.FixedLenFeature((), tf.int64,
|
||||||
|
default_value=tf.zeros([], dtype=tf.int64)),
|
||||||
|
}
|
||||||
|
parsed = tf.parse_single_example(record, keys_to_features)
|
||||||
|
|
||||||
|
# Perform additional preprocessing on the parsed data.
|
||||||
|
image = tf.image.decode_jpeg(parsed["image_data"])
|
||||||
|
image = tf.reshape(image, [299, 299, 1])
|
||||||
|
label = tf.cast(parsed["label"], tf.int32)
|
||||||
|
|
||||||
|
return {"image_data": image, "date_time": parsed["date_time"]}, label
|
||||||
|
|
||||||
|
# Use `Dataset.map()` to build a pair of a feature dictionary and a label
|
||||||
|
# tensor for each example.
|
||||||
|
dataset = dataset.map(parser)
|
||||||
|
dataset = dataset.shuffle(buffer_size=10000)
|
||||||
|
dataset = dataset.batch(32)
|
||||||
|
dataset = dataset.repeat(num_epochs)
|
||||||
|
iterator = dataset.make_one_shot_iterator()
|
||||||
|
|
||||||
|
# `features` is a dictionary in which each value is a batch of values for
|
||||||
|
# that feature; `labels` is a batch of labels.
|
||||||
|
features, labels = iterator.get_next()
|
||||||
|
return features, labels
|
||||||
|
1
|
||||||
|
2
|
||||||
|
3
|
||||||
|
4
|
||||||
|
5
|
||||||
|
6
|
||||||
|
7
|
||||||
|
8
|
||||||
|
9
|
||||||
|
10
|
||||||
|
11
|
||||||
|
12
|
||||||
|
13
|
||||||
|
14
|
||||||
|
15
|
||||||
|
16
|
||||||
|
17
|
||||||
|
18
|
||||||
|
19
|
||||||
|
20
|
||||||
|
21
|
||||||
|
22
|
||||||
|
23
|
||||||
|
24
|
||||||
|
25
|
||||||
|
26
|
||||||
|
27
|
||||||
|
28
|
||||||
|
29
|
||||||
|
30
|
||||||
|
31
|
||||||
|
32
|
||||||
|
33
|
||||||
|
34
|
||||||
|
注:本文来自于TenosrFlow官方使用tf.data导入数据的 Develop > GUIDE > Importing data
|
||||||
|
---------------------
|
||||||
|
作者:黑暗星球
|
||||||
|
来源:CSDN
|
||||||
|
原文:https://blog.csdn.net/u014061630/article/details/80728694
|
||||||
|
版权声明:本文为博主原创文章,转载请附上博文链接!
|
||||||
195
Tensorflow教程/TensorFlow-IO.md
Normal file
@@ -0,0 +1,195 @@
|
|||||||
|
## TensorFlow - IO
|
||||||
|
|
||||||
|
> 在 tf.data 之前,一般使用 QueueRunner,但 QueueRunner 基于 Python 的多线程及队列等,效率不够高,所以 Google发布了tf.data,其基于C++的多线程及队列,彻底提高了效率。所以不建议使用 QueueRunner 了,取而代之,使用 tf.data 模块吧:简单、高效。
|
||||||
|
|
||||||
|
|
||||||
|
### preload
|
||||||
|
|
||||||
|
直接将数据设置为常量,加载到TensorFlow的graph中。
|
||||||
|
```
|
||||||
|
import tensorflow as tf
|
||||||
|
x1 = tf.constant([2,3,4])
|
||||||
|
x2 = tf.constant([4,0,1])
|
||||||
|
|
||||||
|
y = tf.add(x1,x2)
|
||||||
|
|
||||||
|
with tf.Session() as sess:
|
||||||
|
print(sess.run(y))
|
||||||
|
```
|
||||||
|
|
||||||
|
### feed_dict
|
||||||
|
|
||||||
|
使用Python代码获取数据,通过给run()或者eval()函数输入feed_dict参数,传入数据,可以启动运算过程。
|
||||||
|
|
||||||
|
```
|
||||||
|
with tf.Session():
|
||||||
|
input = tf.placeholder(tf.float32)
|
||||||
|
classifier = ...
|
||||||
|
print classifier.eval(feed_dict={input: my_python_preprocessing_fn()})
|
||||||
|
```
|
||||||
|
或者通过sess.run的feed参数
|
||||||
|
```
|
||||||
|
with tf.Session() as sess:
|
||||||
|
result = sess.run(fetches=[mul, intermed],feed_dict={x1:inputX,y:outputY})
|
||||||
|
print(result)
|
||||||
|
```
|
||||||
|
### FileRead & QueueRuuner
|
||||||
|
|
||||||
|
> 基于队列(Queue)API构建输入通道(pipelines),读取文件中的数据
|
||||||
|
|
||||||
|
##### 原理介绍
|
||||||
|
|
||||||
|
* 使用字符串张量(比如["file0", "file1"]) 或者tf.train.match_filenames_once 函数来产生文件名列表。
|
||||||
|
* 文件名打乱(可选)(Optional filename shuffling)。
|
||||||
|
* epoch限制(可选)(Optional epoch limit)
|
||||||
|
* tf.train.string_input_producer 函数.string_input_producer来生成一个先入先出的文件名队列。
|
||||||
|
* 将文件名队列提供给阅读器Reader的read方法。文件阅读器来读取数据。需要根据不同的文件内容选取不同的文件格式。
|
||||||
|
* decoder(A decoder for a record read by the reader)
|
||||||
|
* 预处理(可选)(Optional preprocessing)。然后你可以对examples进行你想要的预处理(preprocessing)。预处理是独立的(不依赖于模型参数)。常见的预处理有:数据的标准化(normalization of your data)、挑选一个随机的切片,添加噪声(noise)或者畸变(distortions)等。
|
||||||
|
* 在pipeline的末端,我们通过调用 tf.train.shuffle_batch 来创建两个queue,一个将example batch起来 for training、evaluation、inference;另一个来shuffle examples的顺序。
|
||||||
|
|
||||||
|
##### 文件列表和文件队列生成
|
||||||
|
|
||||||
|
可以使用字符串张量(比如["file0", "file1"], [("file%d" % i) for i in range(2)], [("file%d" % i) for i in range(2)]) 或者tf.train.match_filenames_once 函数来产生文件名列表。
|
||||||
|
|
||||||
|
将文件名列表交给tf.train.string_input_producer 函数.string_input_producer来生成一个先入先出的队列, 文件阅读器会需要它来读取数据。
|
||||||
|
|
||||||
|
string_input_producer 提供的可配置参数来设置文件名乱序和最大的训练迭代数, QueueRunner会为每次迭代(epoch)将所有的文件名加入文件名队列中, 如果shuffle=True的话, 会对文件名进行乱序处理。这一过程是比较均匀的,因此它可以产生均衡的文件名队列。
|
||||||
|
|
||||||
|
##### CSV 文件读取
|
||||||
|
使用步骤:
|
||||||
|
* 使用textLineReader()阅读器。读取文件名队列
|
||||||
|
* 使用decode_csv()对内容解析
|
||||||
|
|
||||||
|
过程介绍:
|
||||||
|
|
||||||
|
read 方法每执行一次,会从文件中读取一行。然后 decode_csv 将读取的内容解析成一个Tensor列表。参数 record_defaults 决定解析产生的Tensor的类型,另外,如果输入中有缺失值,则用record_defaults 指定的默认值来填充。
|
||||||
|
|
||||||
|
在使用run或者eval 执行 read 方法前,你必须调用 tf.train.start_queue_runners 去填充 queue。否则,read 方法将会堵塞(等待 filenames queue 中 enqueue 文件名)。
|
||||||
|
|
||||||
|
代码示例:
|
||||||
|
```
|
||||||
|
filename_queue = tf.train.string_input_producer(["file0.csv", "file1.csv"])
|
||||||
|
|
||||||
|
reader = tf.TextLineReader()
|
||||||
|
key, value = reader.read(filename_queue)
|
||||||
|
|
||||||
|
# Default values, in case of empty columns. Also specifies the type of the
|
||||||
|
# decoded result.
|
||||||
|
record_defaults = [[1], [1], [1], [1], [1]]
|
||||||
|
col1, col2, col3, col4, col5 = tf.decode_csv(
|
||||||
|
value, record_defaults=record_defaults)
|
||||||
|
features = tf.concat(0, [col1, col2, col3, col4])
|
||||||
|
|
||||||
|
with tf.Session() as sess:
|
||||||
|
# Start populating the filename queue.
|
||||||
|
coord = tf.train.Coordinator()
|
||||||
|
threads = tf.train.start_queue_runners(coord=coord)
|
||||||
|
|
||||||
|
for i in range(1200):
|
||||||
|
# Retrieve a single instance:
|
||||||
|
example, label = sess.run([features, col5])
|
||||||
|
|
||||||
|
coord.request_stop()
|
||||||
|
coord.join(threads)
|
||||||
|
```
|
||||||
|
##### bin文件读取
|
||||||
|
|
||||||
|
使用步骤:
|
||||||
|
|
||||||
|
* 使用一个 tf.FixedLengthRecordReader进行读取。
|
||||||
|
* tf.decode_raw进行解析。解析成一个uint8 tensor。
|
||||||
|
|
||||||
|
使用过程:
|
||||||
|
the CIFAR-10 dataset的文件格式定义是:每条记录的长度都是固定的,一个字节的标签,后面是3072字节的图像数据。uint8的张量的标准操作就可以从中获取图像片并且根据需要进行重组。
|
||||||
|
|
||||||
|
|
||||||
|
##### tfrecord文件读取(Standard TensorFlow format)
|
||||||
|
|
||||||
|
使用步骤:
|
||||||
|
|
||||||
|
* 使用tf.TFRecordReader()进行读取
|
||||||
|
* 使用decode()函数进行解析
|
||||||
|
|
||||||
|
使用过程:
|
||||||
|
|
||||||
|
另一种保存记录的方法可以允许你讲任意的数据转换为TensorFlow所支持的格式, 这种方法可以使TensorFlow的数据集更容易与网络应用架构相匹配。这种建议的方法就是使用TFRecords文件,TFRecords文件包含了tf.train.Example 协议内存块(protocol buffer)(协议内存块包含了字段 Features)。你可以写一段代码获取你的数据, 将数据填入到Example协议内存块(protocol buffer),将协议内存块序列化为一个字符串, 并且通过tf.python_io.TFRecordWriter class写入到TFRecords文件。tensorflow/g3doc/how_tos/reading_data/convert_to_records.py就是这样的一个例子。
|
||||||
|
|
||||||
|
从TFRecords文件中读取数据, 可以使用tf.TFRecordReader的tf.parse_single_example解析器。这个parse_single_example操作可以将Example协议内存块(protocol buffer)解析为张量。 MNIST的例子就使用了convert_to_records 所构建的数据。
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
##### 预处理
|
||||||
|
|
||||||
|
你可以对输入的样本进行任意的预处理, 这些预处理不依赖于训练参数, 你可以在tensorflow/models/image/cifar10/cifar10.py找到数据归一化, 提取随机数据片,增加噪声或失真等等预处理的例子。
|
||||||
|
|
||||||
|
##### 批处理
|
||||||
|
|
||||||
|
在数据输入管线的末端, 我们需要有另一个队列来执行输入样本的训练,评价和推理。
|
||||||
|
|
||||||
|
代码示例:
|
||||||
|
```
|
||||||
|
def read_my_file_format(filename_queue):
|
||||||
|
reader = tf.SomeReader()
|
||||||
|
key, record_string = reader.read(filename_queue)
|
||||||
|
example, label = tf.some_decoder(record_string)
|
||||||
|
processed_example = some_processing(example)
|
||||||
|
return processed_example, label
|
||||||
|
|
||||||
|
def input_pipeline(filenames, batch_size, num_epochs=None):
|
||||||
|
filename_queue = tf.train.string_input_producer(
|
||||||
|
filenames, num_epochs=num_epochs, shuffle=True)
|
||||||
|
example, label = read_my_file_format(filename_queue)
|
||||||
|
# min_after_dequeue defines how big a buffer we will randomly sample
|
||||||
|
# from -- bigger means better shuffling but slower start up and more
|
||||||
|
# memory used.
|
||||||
|
# capacity must be larger than min_after_dequeue and the amount larger
|
||||||
|
# determines the maximum we will prefetch. Recommendation:
|
||||||
|
# min_after_dequeue + (num_threads + a small safety margin) * batch_size
|
||||||
|
min_after_dequeue = 10000
|
||||||
|
capacity = min_after_dequeue + 3 * batch_size
|
||||||
|
example_batch, label_batch = tf.train.shuffle_batch(
|
||||||
|
[example, label], batch_size=batch_size, capacity=capacity,
|
||||||
|
min_after_dequeue=min_after_dequeue)
|
||||||
|
return example_batch, label_batch
|
||||||
|
```
|
||||||
|
|
||||||
|
##### 创建线程并使用QueueRunner对象
|
||||||
|
|
||||||
|
工作原理:
|
||||||
|
|
||||||
|
使用上面列出的许多tf.train函数添加QueueRunner到你的数据流图中。在你运行任何训练步骤之前,需要调用tf.train.start_queue_runners函数,否则数据流图将一直挂起。tf.train.start_queue_runners 这个函数将会启动输入管道的线程,填充样本到队列中,以便出队操作可以从队列中拿到样本。这种情况下最好配合使用一个tf.train.Coordinator,这样可以在发生错误的情况下正确地关闭这些线程。如果你对训练迭代数做了限制,那么需要使用一个训练迭代数计数器,并且需要被初始化。
|
||||||
|
|
||||||
|
代码示例:
|
||||||
|
```
|
||||||
|
# Create the graph, etc.
|
||||||
|
init_op = tf.initialize_all_variables()
|
||||||
|
|
||||||
|
# Create a session for running operations in the Graph.
|
||||||
|
sess = tf.Session()
|
||||||
|
|
||||||
|
# Initialize the variables (like the epoch counter).
|
||||||
|
sess.run(init_op)
|
||||||
|
|
||||||
|
# Start input enqueue threads.
|
||||||
|
coord = tf.train.Coordinator()
|
||||||
|
threads = tf.train.start_queue_runners(sess=sess, coord=coord)
|
||||||
|
|
||||||
|
try:
|
||||||
|
while not coord.should_stop():
|
||||||
|
# Run training steps or whatever
|
||||||
|
sess.run(train_op)
|
||||||
|
|
||||||
|
except tf.errors.OutOfRangeError:
|
||||||
|
print 'Done training -- epoch limit reached'
|
||||||
|
finally:
|
||||||
|
# When done, ask the threads to stop.
|
||||||
|
coord.request_stop()
|
||||||
|
|
||||||
|
# Wait for threads to finish.
|
||||||
|
coord.join(threads)
|
||||||
|
sess.close()
|
||||||
|
```
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
7
Tensorflow教程/TensorFlow-OP(控制).md
Normal file
@@ -0,0 +1,7 @@
|
|||||||
|
### merge
|
||||||
|
|
||||||
|
### swich
|
||||||
|
|
||||||
|
### enter
|
||||||
|
|
||||||
|
### leave
|
||||||
98
Tensorflow教程/TensorFlow-OP(计算).md
Normal file
@@ -0,0 +1,98 @@
|
|||||||
|
## TensorFlow-OP
|
||||||
|
|
||||||
|
### 元素op
|
||||||
|
* 求和
|
||||||
|
```
|
||||||
|
tf.add(x,y,name=None)
|
||||||
|
```
|
||||||
|
* 减法
|
||||||
|
```
|
||||||
|
tf.sub(x,y,name=None)
|
||||||
|
```
|
||||||
|
|
||||||
|
* 乘法
|
||||||
|
```
|
||||||
|
tf.mutipy(x,y,name=None)
|
||||||
|
```
|
||||||
|
* 除法
|
||||||
|
```
|
||||||
|
tf.div(x,y,name=None)
|
||||||
|
```
|
||||||
|
* 取模
|
||||||
|
```
|
||||||
|
tf.mod(x,y,name=None)
|
||||||
|
```
|
||||||
|
* 求绝对值
|
||||||
|
```
|
||||||
|
tf.abs(x,name=None)
|
||||||
|
```
|
||||||
|
|
||||||
|
* 取反
|
||||||
|
```
|
||||||
|
tf.neg(x,name=None)
|
||||||
|
```
|
||||||
|
* 赋值
|
||||||
|
```
|
||||||
|
tf.sign(x,name=None)
|
||||||
|
```
|
||||||
|
* 返回符号
|
||||||
|
```
|
||||||
|
tf.inv(x,name=None)
|
||||||
|
```
|
||||||
|
* 计算平方
|
||||||
|
```
|
||||||
|
tf.square(x,name=None)
|
||||||
|
```
|
||||||
|
* 开根号
|
||||||
|
```
|
||||||
|
tf.sqrt(x,name=None)
|
||||||
|
```
|
||||||
|
* 计算e的次方
|
||||||
|
```
|
||||||
|
tf.exp(x,name=None)
|
||||||
|
```
|
||||||
|
* 计算log
|
||||||
|
```
|
||||||
|
tf.log(x,name=None)
|
||||||
|
```
|
||||||
|
* 返回最大值
|
||||||
|
```
|
||||||
|
tf.maximum(x,y,name=None)
|
||||||
|
```
|
||||||
|
* 返回最小值
|
||||||
|
```
|
||||||
|
tf.minimum(x,y,name=None)
|
||||||
|
```
|
||||||
|
* 三角函数cos
|
||||||
|
```
|
||||||
|
tf.cos(x,name=None)
|
||||||
|
```
|
||||||
|
|
||||||
|
* 三角函数sin
|
||||||
|
```
|
||||||
|
tf.sin(x,name=None)
|
||||||
|
```
|
||||||
|
* 三角函数tan
|
||||||
|
```
|
||||||
|
tf.tan(x,name=None)
|
||||||
|
```
|
||||||
|
* 三角函数ctan
|
||||||
|
```
|
||||||
|
tf.atan(x,name=None)
|
||||||
|
```
|
||||||
|
|
||||||
|
### 向量op
|
||||||
|
|
||||||
|
### 矩阵op
|
||||||
|
|
||||||
|
```
|
||||||
|
tf.diag(diagnal,name=None)
|
||||||
|
tf.diag_par(input,name=None)
|
||||||
|
tf.trace(x,name=None)
|
||||||
|
tf.transpose(a,perm=None)
|
||||||
|
tf.matmul(a,b,transpose_a=False,transpose_b=False,a_is_sparse=False,name=None)
|
||||||
|
tf.matrix_determinant(input,name=None)
|
||||||
|
tf.matrix_inverse(input,adjoint=None,name=None)
|
||||||
|
tf.cholesky(input,name=None)
|
||||||
|
tf.matrix_solve(matrix,rhs,adjoint=None,name=None)
|
||||||
|
```
|
||||||
0
Tensorflow教程/TensorFlow-Optimizer.md
Normal file
149
Tensorflow教程/TensorFlow-Queue & Thread.md
Normal file
@@ -0,0 +1,149 @@
|
|||||||
|
## TensorFlow-Queue & Thread
|
||||||
|
|
||||||
|
> 使用TensorFlow进行异步计算
|
||||||
|
|
||||||
|
### Queue
|
||||||
|
|
||||||
|
> tf.queue.*
|
||||||
|
|
||||||
|
##### 分类
|
||||||
|
* tf.queue.FIFOQueue :按入列顺序出列的队列
|
||||||
|
* tf.queue.RandomShuffleQueue :随机顺序出列的队列
|
||||||
|
* tf.queue.PaddingFIFOQueue :以固定长度批量出列的队列
|
||||||
|
* tf.queue.PriorityQueue :带优先级出列的队列
|
||||||
|
|
||||||
|
##### 基本方法(tf.queue.queuebase)
|
||||||
|
|
||||||
|
* dequeue(name=None)
|
||||||
|
Dequeues one element from this queue.If the queue is empty when this operation executes, it will block until there is an element to dequeue.
|
||||||
|
|
||||||
|
The tuple of tensors that was dequeued.
|
||||||
|
|
||||||
|
* dequeuemany(n,name=None)
|
||||||
|
Dequeues and concatenates n elements from this queue.This operation concatenates queue-element component tensors along the 0th dimension to make a single component tensor. All of the components in the dequeued tuple will have size n in the 0th dimension.
|
||||||
|
|
||||||
|
The list of concatenated tensors that was dequeued.
|
||||||
|
|
||||||
|
* dequeue_up_to(n,name=None)
|
||||||
|
|
||||||
|
Dequeues and concatenates n elements from this queue.
|
||||||
|
|
||||||
|
Note This operation is not supported by all queues. If a queue does not support DequeueUpTo, then a tf.errors.UnimplementedError is raised.
|
||||||
|
|
||||||
|
This operation concatenates queue-element component tensors along the 0th dimension to make a single component tensor. If the queue has not been closed, all of the components in the dequeued tuple will have size n in the 0th dimension.
|
||||||
|
* enqueue(vals,name=None)
|
||||||
|
Enqueues one element to this queue.
|
||||||
|
|
||||||
|
If the queue is full when this operation executes, it will block until the element has been enqueued.
|
||||||
|
|
||||||
|
* enqueue_many(vals,name=None)
|
||||||
|
Enqueues zero or more elements to this queue.
|
||||||
|
|
||||||
|
This operation slices each component tensor along the 0th dimension to make multiple queue elements. All of the tensors in vals must have the same size in the 0th dimension.
|
||||||
|
|
||||||
|
If the queue is full when this operation executes, it will block until all of the elements have been enqueued.
|
||||||
|
|
||||||
|
### Coordinator
|
||||||
|
|
||||||
|
> tf.train.Coordinator
|
||||||
|
|
||||||
|
##### 功能概述
|
||||||
|
* Coordinator类可以用来同时停止多个工作线程并且向那个在等待所有工作线程终止的程序报告异常。
|
||||||
|
|
||||||
|
##### 主要方法
|
||||||
|
|
||||||
|
* should_stop():如果线程应该停止则返回True。
|
||||||
|
* request_stop(<exception>): 请求该线程停止。
|
||||||
|
* join(<list of threads>):等待被指定的线程终止。
|
||||||
|
|
||||||
|
##### 工作原理
|
||||||
|
|
||||||
|
* 创建一个Coordinator对象
|
||||||
|
* 然后建立一些使用Coordinator对象的线程。这些线程通常一直循环运行,一直到should_stop()返回True时停止。
|
||||||
|
* 任何线程都可以决定计算什么时候应该停止。它只需要调用request_stop()同时其他线程的should_stop()将会返回True,然后都停下来。
|
||||||
|
|
||||||
|
##### 代码示例
|
||||||
|
|
||||||
|
```
|
||||||
|
# 线程体:循环执行,直到`Coordinator`收到了停止请求。
|
||||||
|
# 如果某些条件为真,请求`Coordinator`去停止其他线程。
|
||||||
|
def MyLoop(coord):
|
||||||
|
while not coord.should_stop():
|
||||||
|
...do something...
|
||||||
|
if ...some condition...:
|
||||||
|
coord.request_stop()
|
||||||
|
|
||||||
|
# Main code: create a coordinator.
|
||||||
|
coord = Coordinator()
|
||||||
|
|
||||||
|
# Create 10 threads that run 'MyLoop()'
|
||||||
|
threads = [threading.Thread(target=MyLoop, args=(coord)) for i in xrange(10)]
|
||||||
|
|
||||||
|
# Start the threads and wait for all of them to stop.
|
||||||
|
for t in threads:
|
||||||
|
t.start()
|
||||||
|
coord.join(threads)
|
||||||
|
```
|
||||||
|
|
||||||
|
### QueueRunner
|
||||||
|
|
||||||
|
> tf.train.QueueRunner
|
||||||
|
|
||||||
|
##### 功能概述
|
||||||
|
QueueRunner类会创建一组线程, 这些线程可以重复的执行Enquene操作, 他们使用同一个Coordinator来处理线程同步终止。此外,一个QueueRunner会运行一个closer thread,当Coordinator收到异常报告时,这个closer thread会自动关闭队列。
|
||||||
|
|
||||||
|
##### 工作原理
|
||||||
|
|
||||||
|
* 首先建立一个TensorFlow图表,这个图表使用队列来输入样本。
|
||||||
|
* 增加处理样本并将样本推入队列中的操作。
|
||||||
|
* 队列和入队操作,作为参数,构件quuuerunner。一个QueueRunner管理多个入队线程。
|
||||||
|
* 通过QueueRunner.create_threads()创建并启动多线程。通过sess.run进行启动。
|
||||||
|
* 增加training操作来移除队列中的样本。
|
||||||
|
|
||||||
|
##### 主要方法
|
||||||
|
* QueueRunner(queue,[enqueue_op] * numbers)
|
||||||
|
* create_threads(sess,coord,start)
|
||||||
|
|
||||||
|
##### 代码示例
|
||||||
|
```
|
||||||
|
# Create a queue runner that will run 4 threads in parallel to enqueue
|
||||||
|
# examples.
|
||||||
|
qr = tf.train.QueueRunner(queue, [enqueue_op] * 4)
|
||||||
|
|
||||||
|
# Launch the graph.
|
||||||
|
sess = tf.Session()
|
||||||
|
# Create a coordinator, launch the queue runner threads.
|
||||||
|
coord = tf.train.Coordinator()
|
||||||
|
enqueue_threads = qr.create_threads(sess, coord=coord, start=True)
|
||||||
|
# Run the training loop, controlling termination with the coordinator.
|
||||||
|
for step in xrange(1000000):
|
||||||
|
if coord.should_stop():
|
||||||
|
break
|
||||||
|
sess.run(train_op)
|
||||||
|
# When done, ask the threads to stop.
|
||||||
|
coord.request_stop()
|
||||||
|
# And wait for them to actually do it.
|
||||||
|
coord.join(threads)
|
||||||
|
```
|
||||||
|
|
||||||
|
|
||||||
|
### 异常处理
|
||||||
|
|
||||||
|
通过queue runners启动的线程不仅仅只处理推送样本到队列。他们还捕捉和处理由队列产生的异常,包括OutOfRangeError异常,这个异常是用于报告队列被关闭。
|
||||||
|
|
||||||
|
##### 代码示例
|
||||||
|
|
||||||
|
```
|
||||||
|
try:
|
||||||
|
for step in xrange(1000000):
|
||||||
|
if coord.should_stop():
|
||||||
|
break
|
||||||
|
sess.run(train_op)
|
||||||
|
except Exception, e:
|
||||||
|
# Report exceptions to the coordinator.
|
||||||
|
coord.request_stop(e)
|
||||||
|
|
||||||
|
# Terminate as usual. It is innocuous to request stop twice.
|
||||||
|
coord.request_stop()
|
||||||
|
coord.join(threads)
|
||||||
|
```
|
||||||
272
Tensorflow教程/TensorFlow-Summary&Tensorboard.md
Normal file
@@ -0,0 +1,272 @@
|
|||||||
|
## tensorflow 数据可视化
|
||||||
|
|
||||||
|
> tf.summary
|
||||||
|
|
||||||
|
> 在TensorFlow中,最常用的可视化方法有三种途径,分别为TensorFlow与OpenCv的混合编程、利用Matpltlib进行可视化、利用TensorFlow自带的可视化工具TensorBoard进行可视化。
|
||||||
|
|
||||||
|
### 原理介绍
|
||||||
|
|
||||||
|
tf.summary中相关的方法会输出一个含tensor的Summary protocol buffer,这是一种能够被tensorboard模块解析的结构化数据格式。
|
||||||
|
|
||||||
|
protocol buffer(protobuf)是谷歌专用的数据序列化工具。速度更快,但格式要求更严格,应用范围可能更小。
|
||||||
|
|
||||||
|
|
||||||
|
### 用法详解
|
||||||
|
##### 1. tf.summary.scalar
|
||||||
|
|
||||||
|
用来显示标量信息:
|
||||||
|
```
|
||||||
|
tf.summary.scalar(tags, values, collections=None, name=None)
|
||||||
|
例如:tf.summary.scalar('mean', mean)
|
||||||
|
一般在画loss,accuary时会用到这个函数。
|
||||||
|
```
|
||||||
|
|
||||||
|
参数说明:
|
||||||
|
* name:生成节点的名字,也会作为TensorBoard中的系列的名字。
|
||||||
|
* tensor:包含一个值的实数Tensor。
|
||||||
|
* collection:图的集合键值的可选列表。新的求和op被添加到这个集合中。缺省为[GraphKeys.SUMMARIES]
|
||||||
|
* family:可选项;设置时用作求和标签名称的前缀,这影响着TensorBoard所显示的标签名。
|
||||||
|
|
||||||
|
主要用途:
|
||||||
|
* 将【计算图】中的【标量数据】写入TensorFlow中的【日志文件】,以便为将来tensorboard的可视化做准备。
|
||||||
|
* 一般在画loss曲线和accuary曲线时会用到这个函数。
|
||||||
|
|
||||||
|
|
||||||
|
##### 2. tf.summary.histogram
|
||||||
|
|
||||||
|
用来显示直方图信息,其格式为:
|
||||||
|
```
|
||||||
|
tf.summary.histogram(tags, values, collections=None, name=None)
|
||||||
|
例如: tf.summary.histogram('histogram', var)
|
||||||
|
一般用来显示训练过程中变量的分布情况
|
||||||
|
```
|
||||||
|
参数说明:
|
||||||
|
* name :一个节点的名字,如下图红色矩形框所示
|
||||||
|
* values:要可视化的数据,可以是任意形状和大小的数据
|
||||||
|
|
||||||
|
主要用途:
|
||||||
|
* 将【计算图】中的【数据的分布/数据直方图】写入TensorFlow中的【日志文件】,以便为将来tensorboard的可视化做准备
|
||||||
|
* 一般用来显示训练过程中变量的分布情况
|
||||||
|
|
||||||
|
|
||||||
|
##### 3. tf.summary.distribution
|
||||||
|
分布图,一般用于显示weights分布
|
||||||
|
|
||||||
|
##### 4. tf.summary.text
|
||||||
|
可以将文本类型的数据转换为tensor写入summary中:
|
||||||
|
|
||||||
|
```
|
||||||
|
text = """/a/b/c\\_d/f\\_g\\_h\\_2017"""
|
||||||
|
summary_op0 = tf.summary.text('text', tf.convert_to_tensor(text))
|
||||||
|
```
|
||||||
|
##### 5. tf.summary.image
|
||||||
|
|
||||||
|
输出带图像的probuf,汇总数据的图像的的形式如下: ' tag /image/0', ' tag /image/1'...,如:input/image/0等。
|
||||||
|
|
||||||
|
```
|
||||||
|
tf.summary.image(tag, tensor, max_images=3, collections=None, name=None)
|
||||||
|
```
|
||||||
|
参数说明:
|
||||||
|
|
||||||
|
* name :一个节点的名字,如下图红色矩形框所示
|
||||||
|
* tensor:要可视化的图像数据,一个四维的张量,元素类型为uint8或者float32,维度为[batch_size, height,width, channels]
|
||||||
|
* max_outputs:输出的通道数量,可以结合下面的示例代码进行理解
|
||||||
|
|
||||||
|
主要用途:
|
||||||
|
* 将【计算图】中的【图像数据】写入TensorFlow中的【日志文件】,以便为将来tensorboard的可视化做准备
|
||||||
|
* 输出一个包含图像的summary,这个图像是通过一个4维张量构建的,这个张量的四个维度如下所示:[batch_size,height, width, channels]
|
||||||
|
* 其中参数channels有三种取值:
|
||||||
|
|
||||||
|
1: tensor is interpreted as Grayscale,如果为1,那么这个张量被解释为灰度图像
|
||||||
|
3: tensor is interpreted as RGB,如果为3,那么这个张量被解释为RGB彩色图像
|
||||||
|
4: tensor is interpreted as Grayscale,如果为4,那么这个张量被解释为RGBA四通道图
|
||||||
|
|
||||||
|
##### 6. tf.summary.audio
|
||||||
|
|
||||||
|
展示训练过程中记录的音频
|
||||||
|
|
||||||
|
##### 7. tf.summary.merge_all
|
||||||
|
|
||||||
|
merge_all 可以将所有summary全部保存到磁盘,以便tensorboard显示。如果没有特殊要求,一般用这一句就可一显示训练时的各种信息了。
|
||||||
|
|
||||||
|
```
|
||||||
|
tf.summaries.merge_all(key='summaries')
|
||||||
|
```
|
||||||
|
参数说明:
|
||||||
|
|
||||||
|
* key : 用于收集summaries的GraphKey,默认的为GraphKeys.SUMMARIES
|
||||||
|
* scope:可选参数
|
||||||
|
|
||||||
|
|
||||||
|
函数说明:
|
||||||
|
|
||||||
|
* 将之前定义的所有summary整合在一起
|
||||||
|
* 和TensorFlow中的其他操作类似,tf.summary.scalar、tf.summary.histogram、tf.summary.image函数也是一个op,它们在定义的时候,也不会立即执行,需要通过sess.run来明确调用这些函数。因为,在一个程序中定义的写日志操作比较多,如果一一调用,将会十分麻烦,所以Tensorflow提供了tf.summary.merge_all()函数将所有的summary整理在一起。在TensorFlow程序执行的时候,只需要运行这一个操作就可以将代码中定义的所有【写日志操作】执行一次,从而将所有的日志写入【日志文件】。
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
##### 8. tf.summary.FileWriter
|
||||||
|
|
||||||
|
指定一个文件用来保存图。可以调用其add_summary()方法将训练过程数据保存在filewriter指定的文件中
|
||||||
|
|
||||||
|
```
|
||||||
|
tf.summary.FileWritter(path,sess.graph)
|
||||||
|
```
|
||||||
|
|
||||||
|
Tensorflow Summary 用法示例:
|
||||||
|
|
||||||
|
```
|
||||||
|
tf.summary.scalar('accuracy',acc) #生成准确率标量图
|
||||||
|
merge_summary = tf.summary.merge_all()
|
||||||
|
train_writer = tf.summary.FileWriter(dir,sess.graph)#定义一个写入summary的目标文件,dir为写入文件地址
|
||||||
|
......(交叉熵、优化器等定义)
|
||||||
|
for step in xrange(training_step): #训练循环
|
||||||
|
train_summary = sess.run(merge_summary,feed_dict = {...})#调用sess.run运行图,生成一步的训练过程数据
|
||||||
|
train_writer.add_summary(train_summary,step)#调用train_writer的add_summary方法将训练过程以及训练步数保存
|
||||||
|
```
|
||||||
|
此时开启tensorborad:
|
||||||
|
```
|
||||||
|
tensorboard --logdir=/summary_dir
|
||||||
|
```
|
||||||
|
便能看见accuracy曲线了。
|
||||||
|
|
||||||
|
##### 9. tf.summary.merge
|
||||||
|
|
||||||
|
可以调用其add_summary()方法将训练过程数据保存在filewriter指定的文件中
|
||||||
|
```
|
||||||
|
tf.summary.merge(inputs, collections=None, name=None)
|
||||||
|
```
|
||||||
|
一般选择要保存的信息还需要用到tf.get_collection()函数
|
||||||
|
|
||||||
|
```
|
||||||
|
tf.summary.scalar('accuracy',acc) #生成准确率标量图
|
||||||
|
merge_summary = tf.summary.merge([tf.get_collection(tf.GraphKeys.SUMMARIES,'accuracy'),...(其他要显示的信息)])
|
||||||
|
train_writer = tf.summary.FileWriter(dir,sess.graph)#定义一个写入summary的目标文件,dir为写入文件地址
|
||||||
|
......(交叉熵、优化器等定义)
|
||||||
|
for step in xrange(training_step): #训练循环
|
||||||
|
train_summary = sess.run(merge_summary,feed_dict = {...})#调用sess.run运行图,生成一步的训练过程数据
|
||||||
|
train_writer.add_summary(train_summary,step)#调用train_writer的add_summary方法将训练过程以及训练步数保存
|
||||||
|
```
|
||||||
|
|
||||||
|
使用tf.get_collection函数筛选图中summary信息中的accuracy信息,这里的
|
||||||
|
|
||||||
|
tf.GraphKeys.SUMMARIES 是summary在collection中的标志。
|
||||||
|
|
||||||
|
当然,也可以直接:
|
||||||
|
```
|
||||||
|
acc_summary = tf.summary.scalar('accuracy',acc) #生成准确率标量图
|
||||||
|
merge_summary = tf.summary.merge([acc_summary ,...(其他要显示的信息)]) #这里的[]不可省
|
||||||
|
```
|
||||||
|
如果要在tensorboard中画多个数据图,需定义多个tf.summary.FileWriter并重复上述过程。
|
||||||
|
|
||||||
|
|
||||||
|
### 步骤说明
|
||||||
|
|
||||||
|
* summary记录的tensor
|
||||||
|
* 使用merge_all或者merge函数收集所有的summary
|
||||||
|
* 定制summary的存储对象
|
||||||
|
* 运行summary,按照训练步骤,传入测试数据,提取summary的值
|
||||||
|
* 最后将summary的结果添加到日志当中
|
||||||
|
|
||||||
|
> 如果目标网址不行可以尝试localhost:6006
|
||||||
|
|
||||||
|
### 代码示例
|
||||||
|
```
|
||||||
|
import tensorflow as tf
|
||||||
|
import tensorflow.examples.tutorials.mnist.input_data as input_data
|
||||||
|
mnist = input_data.read_data_sets("MNIST_data/", one_hot=True)
|
||||||
|
# 也能成功运行了
|
||||||
|
|
||||||
|
x_data = tf.placeholder("float32",[None,784])
|
||||||
|
# x_data = tf.placeholder("float32",[100,784])
|
||||||
|
|
||||||
|
weight = tf.Variable(tf.ones([784,10]))
|
||||||
|
bias = tf.Variable(tf.ones([10]))
|
||||||
|
y_model = tf.nn.softmax(tf.matmul(x_data,weight)+bias)
|
||||||
|
y_data = tf.placeholder("float32",[None,10])
|
||||||
|
# y_data = tf.placeholder("float32",[100,10])
|
||||||
|
|
||||||
|
loss = tf.reduce_sum(tf.pow((y_model-y_data),2))
|
||||||
|
|
||||||
|
train_step = tf.train.GradientDescentOptimizer(0.01).minimize(loss)
|
||||||
|
|
||||||
|
init = tf.initialize_all_variables()
|
||||||
|
sess = tf.Session()
|
||||||
|
sess.run(fetches=init)
|
||||||
|
|
||||||
|
# 用来显示loss
|
||||||
|
tf.summary.scalar("loss",loss)
|
||||||
|
|
||||||
|
# 用来计算准确度
|
||||||
|
correct_prediction = tf.equal(tf.argmax(y_model, 1), tf.argmax(y_data, 1))
|
||||||
|
accuracy = tf.reduce_mean(tf.cast(correct_prediction, "float"))
|
||||||
|
summary_acc = tf.summary.scalar("acc", accuracy)
|
||||||
|
|
||||||
|
# 用来显示weight的训练过程
|
||||||
|
tf.summary.histogram("weight",weight)
|
||||||
|
|
||||||
|
# 用来显示bias的训练过程
|
||||||
|
tf.summary.histogram("bias",bias)
|
||||||
|
|
||||||
|
# 用来将所有的summary收集起来
|
||||||
|
summary_merge = tf.summary.merge_all()
|
||||||
|
# 定义了一个文件读写对象,用来写入summary的probuf到文件当中
|
||||||
|
summary_writer = tf.summary.FileWriter('mnist_logs', sess.graph)
|
||||||
|
|
||||||
|
|
||||||
|
for _ in range(100):
|
||||||
|
batch_xs, batch_ys = mnist.train.next_batch(100)
|
||||||
|
sess.run(train_step,feed_dict={x_data:batch_xs,y_data:batch_ys})
|
||||||
|
# 调用sess.run运行图,生成一步的训练过程数据
|
||||||
|
summary_trian = sess.run(summary_merge, feed_dict={x_data:mnist.test.images,y_data:mnist.test.labels})
|
||||||
|
# 调用train_writer的add_summary方法将训练过程以及训练步数保存
|
||||||
|
summary_writer.add_summary(summary_trian, _)
|
||||||
|
|
||||||
|
|
||||||
|
```
|
||||||
|
|
||||||
|
# 网上的一份不错的代码
|
||||||
|
```
|
||||||
|
1 import tensorflow as tf
|
||||||
|
2 import numpy as np
|
||||||
|
3
|
||||||
|
4 ## prepare the original data
|
||||||
|
5 with tf.name_scope('data'):
|
||||||
|
6 x_data = np.random.rand(100).astype(np.float32)
|
||||||
|
7 y_data = 0.3*x_data+0.1
|
||||||
|
8 ##creat parameters
|
||||||
|
9 with tf.name_scope('parameters'):
|
||||||
|
10 with tf.name_scope('weights'):
|
||||||
|
11 weight = tf.Variable(tf.random_uniform([1],-1.0,1.0))
|
||||||
|
12 tf.summary.histogram('weight',weight)
|
||||||
|
13 with tf.name_scope('biases'):
|
||||||
|
14 bias = tf.Variable(tf.zeros([1]))
|
||||||
|
15 tf.summary.histogram('bias',bias)
|
||||||
|
16 ##get y_prediction
|
||||||
|
17 with tf.name_scope('y_prediction'):
|
||||||
|
18 y_prediction = weight*x_data+bias
|
||||||
|
19 ##compute the loss
|
||||||
|
20 with tf.name_scope('loss'):
|
||||||
|
21 loss = tf.reduce_mean(tf.square(y_data-y_prediction))
|
||||||
|
22 tf.summary.scalar('loss',loss)
|
||||||
|
23 ##creat optimizer
|
||||||
|
24 optimizer = tf.train.GradientDescentOptimizer(0.5)
|
||||||
|
25 #creat train ,minimize the loss
|
||||||
|
26 with tf.name_scope('train'):
|
||||||
|
27 train = optimizer.minimize(loss)
|
||||||
|
28 #creat init
|
||||||
|
29 with tf.name_scope('init'):
|
||||||
|
30 init = tf.global_variables_initializer()
|
||||||
|
31 ##creat a Session
|
||||||
|
32 sess = tf.Session()
|
||||||
|
33 #merged
|
||||||
|
34 merged = tf.summary.merge_all()
|
||||||
|
35 ##initialize
|
||||||
|
36 writer = tf.summary.FileWriter("logs/", sess.graph)
|
||||||
|
37 sess.run(init)
|
||||||
|
38 ## Loop
|
||||||
|
39 for step in range(101):
|
||||||
|
40 sess.run(train)
|
||||||
|
41 rs=sess.run(merged)
|
||||||
|
42 writer.add_summary(rs, step)
|
||||||
|
```
|
||||||
82
Tensorflow教程/TensorFlow-Variable &Constant & Random.md
Normal file
@@ -0,0 +1,82 @@
|
|||||||
|
## 类说明
|
||||||
|
|
||||||
|
### tf.variable
|
||||||
|
|
||||||
|
##### 创建
|
||||||
|
|
||||||
|
```
|
||||||
|
# Create two variables.
|
||||||
|
weights = tf.Variable(tf.random_normal([784, 200], stddev=0.35),
|
||||||
|
name="weights")
|
||||||
|
biases = tf.Variable(tf.zeros([200]), name="biases")
|
||||||
|
```
|
||||||
|
* 一个Variable操作存放变量的值。
|
||||||
|
* 一个初始化op将变量设置为初始值。这事实上是一个tf.assign操作.
|
||||||
|
* 初始值的操作,例如示例中对biases变量的zeros操作也被加入了graph。
|
||||||
|
|
||||||
|
##### 初始化
|
||||||
|
```
|
||||||
|
# Add an op to initialize the variables.
|
||||||
|
init_op = tf.initialize_all_variables()
|
||||||
|
|
||||||
|
# Later, when launching the model
|
||||||
|
with tf.Session() as sess:
|
||||||
|
# Run the init operation.
|
||||||
|
sess.run(init_op)
|
||||||
|
...
|
||||||
|
# Use the model
|
||||||
|
...
|
||||||
|
|
||||||
|
|
||||||
|
# Create a variable with a random value.
|
||||||
|
weights = tf.Variable(tf.random_normal([784, 200], stddev=0.35),
|
||||||
|
name="weights")
|
||||||
|
# Create another variable with the same value as 'weights'.
|
||||||
|
w2 = tf.Variable(weights.initialized_value(), name="w2")
|
||||||
|
# Create another variable with twice the value of 'weights'
|
||||||
|
w_twice = tf.Variable(weights.initialized_value() * 0.2, name="w_twice")
|
||||||
|
```
|
||||||
|
|
||||||
|
* 可以直接指明初始化的值,使用sess.run运行。也可以用一个张量的initialized_value来初始化另外一个variable
|
||||||
|
|
||||||
|
### 常量tf.Constant
|
||||||
|
|
||||||
|
##### 创建
|
||||||
|
|
||||||
|
```
|
||||||
|
tf.Constant("Hello world",dtype=tf.string)
|
||||||
|
```
|
||||||
|
|
||||||
|
##### 数据类型
|
||||||
|
|
||||||
|
```
|
||||||
|
tf.int
|
||||||
|
tf.intl
|
||||||
|
tf.int32
|
||||||
|
tf.int64
|
||||||
|
tf.uint8
|
||||||
|
tf.uint16
|
||||||
|
tf.float16
|
||||||
|
tf.float32
|
||||||
|
tf.string
|
||||||
|
tf.bool
|
||||||
|
tf.complex64
|
||||||
|
tf.complex128
|
||||||
|
tf.float32
|
||||||
|
```
|
||||||
|
|
||||||
|
### 随机数Random
|
||||||
|
|
||||||
|
##### 生成
|
||||||
|
|
||||||
|
```
|
||||||
|
tf.random_normal(shape,mean=0,stddev=1.0,dtype=tf.float32,seed=None,name=None)
|
||||||
|
|
||||||
|
tf.truncate_normal(shape,mean=0.0,stddev=1.0,dtype=tf.float32,seed=None,name=None)
|
||||||
|
|
||||||
|
tf.random_uniform(shape,minval=0,maxval=None,dtype=tf.float32,seed=None,name=None)
|
||||||
|
```
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
37
Tensorflow教程/TensorFlow-code Bazel.md
Normal file
@@ -0,0 +1,37 @@
|
|||||||
|
## Bazel
|
||||||
|
|
||||||
|
> 对TensorFlow编译过程中bazel的行为解析。了解bazel构建TensorFlow的方法,有助于对源码和源码框架的理解。
|
||||||
|
|
||||||
|
## 结构概念:Workspace Packages Targets
|
||||||
|
|
||||||
|
##### Workspace (根文件夹)
|
||||||
|
一个workspace就是一个project(项目)的根目录。workspace里包含构建这个项目所需的源文件,以及symboliclinks(符号链接)。每个workspace目录都必须有一个名为WORKSPACE的文件。这个WORKSPACE文件可能是空的。也可能包含构建项目所需的外部依赖。
|
||||||
|
|
||||||
|
##### Package (子文件夹)
|
||||||
|
在一个workspace中package是主要的代码组织单元。一个package就是一个相关文件的集合。也是一个这些相关文件之间的规范。一个package被定义为一个目录.这个目录里必须包含一个名为BUILD的文件。package目录必须在workspace目录下。package包含其目录中的所有文件.以及其下的所有子目录。但是不包含那些包含了BUILD文件的子目录.
|
||||||
|
|
||||||
|
##### Target
|
||||||
|
package是一个容器,即目录。他里面的元素被称为target。target可以分为三类: file(文件) rule(规则) package group(数量很少)
|
||||||
|
* file
|
||||||
|
file进一步分类又可以分为两种:源文件和派生文件.
|
||||||
|
源文件通常就是程序员编写的类文件.会被上传到远程仓库.
|
||||||
|
派生文件是由编译器根据指定规则生成的文件.不会被上传到远程仓库.
|
||||||
|
|
||||||
|
* rule
|
||||||
|
rule不是一个文件。他是被保存在BUILD文件里的一个函数或者叫方法。他是一个规则。
|
||||||
|
1. rule指定输入和输出之间的关系.以及构建输出的步骤.rule的输出始终是派生文件.rule的输入可以是源文件.也可以是派生文件.也就是说.rule的输出也可能是另一个rule的输入.Bazel允许构建长链规则.
|
||||||
|
2. rule的输入还可以包含其他rule.即A rule可能有另一个B rule作为输入.
|
||||||
|
3.通过rule生成的文件始终属于该rule所属的package.不能将生成文件放到另一个package里.但是rule的输入却可以来自另一个package
|
||||||
|
4.每个rule都有一个name.由name属性指定.类型为string.这个被你指定的名字将作为生成的文件的名称。所以推荐名称可以遵守一定的规则: 如: _binary和_test.让人看名字就知道你要生成的文件的作用.
|
||||||
|
5.每个规则都有一组属性.每个属性都是rule类里的函数.每个属性都有一个名称和一个类型.类型可以是: 整数; label; label列表; 字符串; 字符串列表; 输出label; 输出label列表.在每个规则中不是每个属性都需要被实现的.即有的属性是可选的.
|
||||||
|
|
||||||
|
* package group
|
||||||
|
package group顾名思义就是一组package.他的目的是限制某些规则的可访问性.packagegroup由package_group函数定义.他有两个属性:他包含的包列表及其名称.唯一能决定他能否被引用的属性是:rule里的visibility属性或者package函数里的default_visibility属性.他不生成或者使用文件.仅仅是定义.
|
||||||
|
|
||||||
|
|
||||||
|
* label
|
||||||
|
所有的target属于一个package.target的名字被称为label.一个典型target的label如下所示:
|
||||||
|
//src/business/GXPhone:GXPhone_binary
|
||||||
|
每个label有两个部分
|
||||||
|
src/business/GXPhone被称为package name.
|
||||||
|
GXPhone_binary被称为target name
|
||||||
0
Tensorflow教程/TensorFlow-code IO.md
Normal file
14
Tensorflow教程/TensorFlow-code Swig.md
Normal file
@@ -0,0 +1,14 @@
|
|||||||
|
前端多语言编程环境与后端C++实现系统的通道归功于 Swig 的包装器
|
||||||
|
|
||||||
|
TensorFlow使用Bazel的构建工具,在系统编译之前启动Swig的代码生成过程,通过tensorflow.i自动生成了两个适配 (Wrapper)文件:
|
||||||
|
|
||||||
|
|
||||||
|
1. pywrap_tensorflow_internal.py: 负责对接上层 Python 调用;
|
||||||
|
2. pywrap_tensorflow_internal.cc: 负责对接下层 C API 调用。
|
||||||
|
|
||||||
|
pywrap_tensorflow_internal.py 模块被导入时,会加载_pywrap_tensorflow_internal.so动态链接库,它里面包含了所有运行时接口的符号。而pywrap_tensorflow_internal.cc中,则注册了一个函数符号表,实现Python接口和C接口的映射。运行时,就可以通过映射表,找到Python接口在C层的实现了。
|
||||||
|
|
||||||
|

|
||||||
|
|
||||||
|
https://blog.csdn.net/u013510838/article/details/84103503
|
||||||
|
|
||||||
475
Tensorflow教程/TensorFlow-code architecture.md
Normal file
@@ -0,0 +1,475 @@
|
|||||||
|
http://www.360doc.com/content/17/0220/19/39202731_630623149.shtml
|
||||||
|
|
||||||
|
|
||||||
|
## TF系统依赖
|
||||||
|
TF托管在github平台,有google groups和contributors共同维护。
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
TF提供了丰富的深度学习相关的API,支持Python和C/C++接口。
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
TF提供了可视化分析工具Tensorboard,方便分析和调整模型。
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
TF支持Linux平台,Windows平台,Mac平台,甚至手机移动设备等各种平台
|
||||||
|
|
||||||
|
|
||||||
|
## TF的系统架构
|
||||||
|
|
||||||
|
第一层设备通信层负责网络通信和设备管理。设备管理可以实现TF设备异构的特性,支持CPU、GPU、Mobile等不同设备。网络通信依赖gRPC通信协议实现不同设备间的数据传输和更新。
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
第二层是Tensor的OpKernels实现。这些OpKernels以Tensor为处理对象,依赖网络通信和设备内存分配,实现了各种Tensor操作或计算。Opkernels不仅包含MatMul等计算操作,还包含Queue等非计算操作,这些将在第5章Kernels模块详细介绍。
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
第三层是图计算层(Graph),包含本地计算流图和分布式计算流图的实现。Graph模块包含Graph的创建、编译、优化和执行等部分,Graph中每个节点都是OpKernels类型表示。关于图计算将在第6章Graph模块详细介绍。
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
第四层是API接口层。Tensor C API是对TF功能模块的接口封装,便于其他语言平台调用。
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
第四层以上是应用层。不同编程语言在应用层通过API接口层调用TF核心功能实现相关实验和应用。
|
||||||
|
|
||||||
|
## TensorFlow代码的核心模块
|
||||||
|
|
||||||
|
> Tensorflow/core目录
|
||||||
|
|
||||||
|
|
||||||
|
```
|
||||||
|
public: API接口头文件目录,用于外部接口调用的API定义,主要是session.h 和tensor_c_api.h。
|
||||||
|
|
||||||
|
client: API接口实现文件目录。
|
||||||
|
|
||||||
|
platform: OS系统相关接口文件,如file system, env等。
|
||||||
|
|
||||||
|
protobuf: 均为.proto文件,用于数据传输时的结构序列化.
|
||||||
|
|
||||||
|
common_runtime: 公共运行库,包含session, executor, threadpool, rendezvous, memory管理, 设备分配算法等。
|
||||||
|
|
||||||
|
distributed_runtime: 分布式执行模块,如rpc session, rpc master, rpc worker, graph manager。
|
||||||
|
|
||||||
|
framework: 包含基础功能模块,如log, memory, tensor
|
||||||
|
|
||||||
|
graph: 计算流图相关操作,如construct, partition, optimize, execute等
|
||||||
|
|
||||||
|
kernels: 核心Op,如matmul, conv2d, argmax, batch_norm等
|
||||||
|
|
||||||
|
lib: 公共基础库,如gif、gtl(google模板库)、hash、histogram等。
|
||||||
|
|
||||||
|
ops: 基本ops运算,ops梯度运算,io相关的ops,控制流和数据流操作
|
||||||
|
|
||||||
|
|
||||||
|
```
|
||||||
|
|
||||||
|
> TensorFlow/* 其他目录
|
||||||
|
|
||||||
|
Tensorflow/stream_executor目录是并行计算框架,由google stream executor团队开发。
|
||||||
|
|
||||||
|
Tensorflow/contrib目录是contributor开发目录。
|
||||||
|
|
||||||
|
Tensroflow/python目录是python API客户端脚本。
|
||||||
|
|
||||||
|
Tensorflow/tensorboard目录是可视化分析工具,不仅可以模型可视化,还可以监控模型参数变化。
|
||||||
|
|
||||||
|
> third_party/*第三方库
|
||||||
|
|
||||||
|
eigen3: eigen矩阵运算库,TF基础ops调用
|
||||||
|
|
||||||
|
gpus: 封装了cuda/cudnn编程库
|
||||||
|
|
||||||
|
|
||||||
|
## TF核心概念
|
||||||
|
|
||||||
|
TF的核心是围绕Graph展开的,简而言之,就是Tensor沿着Graph传递闭包完成Flow的过程。所以在介绍Graph之前需要讲述一下符号编程、计算流图、梯度计算、控制流的概念
|
||||||
|
|
||||||
|
##### tensor
|
||||||
|
|
||||||
|
Tensor在高维空间数学运算比Matrix计算复杂,计算量也非常大,加速张量并行运算是TF优先考虑的问题,如add, contract, slice, reshape, reduce, shuffle等运算。
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
TF中Tensor的维数描述为阶,数值是0阶,向量是1阶,矩阵是2阶,以此类推,可以表示n阶高维数据。
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
TF中Tensor支持的数据类型有很多,如tf.float16, tf.float32, tf.float64, tf.uint8, tf.int8, tf.int16, tf.int32, tf.int64, tf.string, tf.bool, tf.complex64等,所有Tensor运算都使用泛化的数据类型表示。
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
TF的Tensor定义和运算主要是调用Eigen矩阵计算库完成的。TF中Tensor的UML定义如图 2 2。其中TensorBuffer指针指向Eigen::Tensor类型。其中,Eigen::Tensor[5][6]不属于Eigen官方维护的程序,由贡献者提供文档和维护,所以Tensor定义在Eigen unsupported模块中。
|
||||||
|
|
||||||
|
##### 符号编程
|
||||||
|
|
||||||
|
|
||||||
|
编程模式通常分为命令式编程(imperative style programs)和符号式编程(symbolic style programs)。
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
命令式编程容易理解和调试,命令语句基本没有优化,按原有逻辑执行。符号式编程涉及较多的嵌入和优化,不容易理解和调试,但运行速度有同比提升。
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
这两种编程模式在实际中都有应用,Torch是典型的命令式风格,caffe、theano、mxnet和Tensorflow都使用了符号式编程。其中caffe、mxnet采用了两种编程模式混合的方法,而Tensorflow是完全采用了符号式编程,Theano和Tensorflow的编程模式更相近。
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
命令式编程是常见的编程模式,编程语言如python/C++都采用命令式编程。命令式编程明确输入变量,并根据程序逻辑逐步运算,这种模式非常在调试程序时进行单步跟踪,分析中间变量。
|
||||||
|
|
||||||
|
|
||||||
|
##### 梯度计算
|
||||||
|
|
||||||
|
梯度计算主要应用在误差反向传播和数据更新,是深度学习平台要解决的核心问题。梯度计算涉及每个计算节点,每个自定义的前向计算图都包含一个隐式的反向计算图。从数据流向上看,正向计算图是数据从输入节点到输出节点的流向过程,反向计算图是数据从输出节点到输入节点的流向过程。
|
||||||
|
|
||||||
|
反向计算限制了符号编程中内存空间复用的优势,因为在正向计算中的计算数据在反向计算中也可能要用到。从这一点上讲,粗粒度的计算节点比细粒度的计算节点更有优势,而TF大部分为细粒度操作,虽然灵活性很强,但细粒度操作涉及到更多的优化方案,在工程实现上开销较大,不及粗粒度简单直接。在神经网络模型中,TF将逐步侧重粗粒度运算
|
||||||
|
|
||||||
|
##### 控制流
|
||||||
|
|
||||||
|
TF的计算图如同数据流一样,数据流向表示计算过程,如图 2 6。数据流图可以很好的表达计算过程,为了扩展TF的表达能力,TF中引入控制流。
|
||||||
|
|
||||||
|
在编程语言中,if…else…是最常见的逻辑控制,在TF的数据流中也可以通过这种方式控制数据流向。接口函数如下,pred为判别表达式,fn1和fn2为运算表达式。当pred为true是,执行fn1操作;当pred为false时,执行fn2操作。
|
||||||
|
|
||||||
|
|
||||||
|
TF还可以协调多个数据流,在存在依赖节点的场景下非常有用,例如节点B要读取模型参数θ更新后的值,而节点A负责更新参数θ,则节点B必须等节点A完成后才能执行,否则读取的参数θ为更新前的数值,这时需要一个运算控制器。接口函数如下,tf.control_dependencies函数可以控制多个数据流执行完成后才能执行接下来的操作,通常与tf.group函数结合使用。
|
||||||
|
|
||||||
|
|
||||||
|
TF支持的控制算子有Switch、Merge、Enter、Leave和NextIteration等。
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
TF不仅支持逻辑控制,还支持循环控制。TF使用和MIT Token-Tagged machine相似的表示系统,将循环的每次迭代标记为一个tag,迭代的执行状态标记为一个frame,但迭代所需的数据准备好的时候,就可以开始计算,从而多个迭代可以同时执行。
|
||||||
|
|
||||||
|
|
||||||
|
## TF开发工具介绍
|
||||||
|
|
||||||
|
> TF系统开发使用了bazel工具实现工程代码自动化管理,使用了protobuf实现了跨设备数据传输,使用了swig库实现python接口封装。本章将从这三方面介绍TF开发工具的使用。
|
||||||
|
|
||||||
|
##### Swig封装
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
Tensorflow核心框架使用C++编写,API接口文件定义在tensorflow/core/public目录下,主要文件是tensor_c_api.h文件,C++语言直接调用这些头文件即可。
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
Python通过Swig工具封装TF库包间接调用,接口定义文件tensorflow/python/ tensorflow.i。其中swig全称为Simplified Wrapper and Interface Generator,是封装C/C++并与其它各种高级编程语言进行嵌入联接的开发工具,对swig感兴趣的请参考相关文档。
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
在tensorflow.i文件中包含了若干个.i文件,每个文件是对应模块的封装,其中tf_session.i文件中包含了tensor_c_api.h,实现client向session发送请求创建和运行graph的功能。
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
##### Bazel编译和调试
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
Bazel是Google开源的自动化构建工具,类似于Make和CMake工具。Bazel的目标是构建“快速并可靠的代码”,并且能“随着公司的成长持续调整其软件开发实践”。
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
TF中几乎所有代码编译生成都是依赖Bazel完成的,了解Bazel有助于进一步学习TF代码,尤其是编译测试用例进行gdb调试。
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
Bazel假定每个目录为[package]单元,目录里面包含了源文件和一个描述文件BUILD,描述文件中指定了如何将源文件转换成构建的输出。
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
以图 3 13为例,左子图为工程中不同模块间的依赖关系,右子图是对应模块依赖关系的BUILD描述文件。
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
图 3 13中name属性来命名规则,srcs属性为模块相关源文件列表,deps属性来描述规则之间的依赖关系。”//search: google_search_page”中”search”是包名,”google_search_page”为规则名,其中冒号用来分隔包名和规则名;如果某条规则所依赖的规则在其他目录下,就用'//'开头,如果在同一目录下,可以忽略包名而用冒号开头。
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
图 3 13中cc_binary表示编译目标是生成可执行文件,cc_library表示编译目标是生成库文件。如果要生成google_search_page规则可运行
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
TF中首次运行bazel时会自动下载很多依赖包,如果有的包下载失败,打开tensorflow/workspace.bzl查看是哪个包下载失败,更改对应依赖包的new_http_archive中的url地址,也可以把new_http_archive设置为本地目录new_local_repository。
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
TF中测试用例跟相应代码文件放在一起,如MatMul操作的core/kernels/matmul_op.cc文件对应的测试用例文件为core/kernels/matmul_op_test.cc文件。运行这个测试用例需要查找这个测试用例对应的BUILD文件和对应的命令规则,如matmul_op_test.cc文件对应的BUILD文件为core/kernels/BUILD文件,如下
|
||||||
|
|
||||||
|
|
||||||
|
其中tf_cuda_cc_test函数是TF中自定义的编译函数,函数定义在/tensorflow/ tensorflow.bzl文件中,它会把matmul_op_test.cc放进编译文件中。要生成matmul_op_test可执行文件可运行如下脚本:
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
##### Protobuf序列化
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
Protocol Buffers 是一种轻便高效的结构化数据存储格式,可以用于结构化数据串行化,或者说序列化。它很适合做数据存储或 RPC 数据交换格式。可用于通讯协议、数据存储等领域的语言无关、平台无关、可扩展的序列化结构数据格式。
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
Protobuf对象描述文件为.proto类型,编译后生成.pb.h和.pb.cc文件。
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
Protobuf主要包含读写两个函数:Writer(序列化)函数SerializeToOstream() 和 Reader(反序列化)函数 ParseFromIstream()。
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
Tensorflow在core/probobuf目录中定义了若干与分布式环境相关的.proto文件,同时在core/framework目录下定义了与基本数据类型和结构的.proto文件,在core/util目录中也定义部分.proto文件,感觉太随意了。
|
||||||
|
|
||||||
|
|
||||||
|
在分布式环境中,不仅需要传输数据序列化,还需要数据传输协议。Protobuf在序列化处理后,由gRPC完成数据传输。gRPC数据传输架构图见图 3 14。
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
gRPC服务包含客户端和服务端。gRPC客户端调用stub 对象将请求用 protobuf 方式序列化成字节流,用于线上传输,到 server 端后调用真正的实现对象处理。gRPC的服务端通过observer观察处理返回和关闭通道。
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
TF使用gRPC完成不同设备间的数据传输,比如超参数、梯度值、graph结构。
|
||||||
|
|
||||||
|
## Eigen介绍
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
在Tensoflow中核心数据结构和运算主要依赖于Eigen和Stream Executor库,其中Eigen支持CPU和GPU加速计算,Stream Executor主要用于GPU环境加速计算。下面简单讲述Eigen库的相关特性,有助于进一步理解Tensorflow。
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
3.2.1 Eigen简述
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
Eigen是高效易用的C++开源库,有效支持线性代数,矩阵和矢量运算,数值分析及其相关的算法。不依赖于任何其他依赖包,安装使用都很简便[8]。具有如下特性:
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
支持整数、浮点数、复数,使用模板编程,可以为特殊的数据结构提供矩阵操作。比如在用ceres-solver进行做优化问题(比如bundle adjustment)的时候,有时候需要用模板编程写一个目标函数,ceres可以将模板自动替换为内部的一个可以自动求微分的特殊的double类型。而如果要在这个模板函数中进行矩阵计算,使用Eigen就会非常方便。
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
支持逐元素、分块、和整体的矩阵操作。
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
内含大量矩阵分解算法包括LU,LDLt,QR、SVD等等。
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
支持使用Intel MKL加速
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
部分功能支持多线程
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
稀疏矩阵支持良好,到今年新出的Eigen3.2,已经自带了SparseLU、SparseQR、共轭梯度(ConjugateGradient solver)、bi conjugate gradient stabilized solver等解稀疏矩阵的功能。同时提供SPQR、UmfPack等外部稀疏矩阵库的接口。
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
支持常用几何运算,包括旋转矩阵、四元数、矩阵变换、AngleAxis(欧拉角与Rodrigues变换)等等。
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
更新活跃,用户众多(Google、WilliowGarage也在用),使用Eigen的比较著名的开源项目有ROS(机器人操作系统)、PCL(点云处理库)、Google Ceres(优化算法)。OpenCV自带到Eigen的接口。
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
Eigen库包含 Eigen模块和unsupported模块,其中Eigen模块为official module,unsupported模块为开源贡献者开发的。
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
Eigen unsupported 模块中定义了数据类型Tensor及相关函数,包括Tensor的存储格式,Tensor的符号表示,Tensor的编译加速,Tensor的一元运算、二元运算、高维度泛化矩阵运算,Tensor的表达式计算。本章后续所述Tensor均为Eigen::Tensor
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
Eigen运算性能评估如图 3 6所示[9],eigen3的整体性能比eigen2有很大提升,与GOTO2、INTEL_MKL基本持平。
|
||||||
|
|
||||||
|
|
||||||
|
图 3 6矩阵运算常用库比较
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
3.2.2 Eigen 存储顺序
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
Eigen中的Tensor支持两种存储方式:
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
Row-major表示矩阵存储时按照row-by-row的方式。
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
Col-major表示矩阵存储时按照column-by-column的方式。
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
Eigen默认采用Col-major格式存储的(虽然也支持Row-major,但不推荐),具体采用什么存储方式取决于算法本身是行遍历还是列遍历为主。例如:A=[[a11, a12, a13], [a21, a22, a23]]的存储序列见图 3 7。
|
||||||
|
|
||||||
|
|
||||||
|
图 3 7 Row-major和Column-major存储顺序
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
3.2.3 Eigen 惰性求值
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
在编程语言理论中,存在及早求值(Eager Evaluation) 和惰性求值(Lazy Evaluation)
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
及早求值:大多数编程语言所拥有的普通计算方式
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
惰性求值:也认为是“延迟求值”,可以提高计算性能,最重要的好处是它可以构造一个无限的数据类型。
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
关于惰性求值,举例如下:
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
Vec3 = vec1 + vec2;
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
及早求值形式需要临时变量vec_temp存储运算结果,再赋值给vec3,计算效率和空间效率都不高:
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
Vec_temp = vec1 + vec2;
|
||||||
|
|
||||||
|
Vec3 = vec_temp
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
而惰性求值不需要临时变量保存中间结果,提高了计算性能:
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
Vec_symbol_3 = (vec_symbol_1 + vec_symbol_2);
|
||||||
|
|
||||||
|
Vec3 = vec_symbol_3.eval(vec1, vec2)
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
由于Eigen默认采用惰性计算,如果要求表达式的值可以使用Tensor::eval()函数。Tensor::eval()函数也是session.run()的底层运算。例如:
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
Tensor result = ((t1 + t2).eval() * 0.2f).exp();
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
3.2.4 Eigen 编译加速
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
编译加速可以充分发挥计算机的并行计算能力,提高程序运行速度。
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
举例如下:
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
普通的循环相加运算时间复杂度是O(n):
|
||||||
|
|
||||||
|
|
||||||
|
如果指令集支持128bit并行计算,则时间复杂度可缩短为O(n/4):
|
||||||
|
|
||||||
|
|
||||||
|
Eigen编译时使用了SSE2加速。假设处理float32类型,指令集支持128bit并行计算,则一次可以计算4个float32类型,速度提升4倍。
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
3.2.5 Eigen::half
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
Tensorflow支持的浮点数类型有float16, float32, float64,其中float16本质上是Eigen::half类型,即半精度浮点数[10]。关于半精度浮点数,英伟达2002年首次提出使用半精度浮点数达到降低数据传输和存储成本的目的。
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
在分布式计算中,如果对数据精度要求不那么高,可以将传输数据转换为float16类型,这样可以大大缩短设备间的数据传输时间。在GPU运算中,float16还可以减少一般的内存占用。
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
在Tensorflow的分布式传输中,默认会将float32转换为float16类型。Tensorflow的转换方式不同于nvidia的标准,采用直接截断尾数的方式转化为半精度浮点数,以减少转换时间。
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
浮点数存储格式分成3部分,符号位,指数和尾数。不同精度是指数位和尾数位的长度不一样。
|
||||||
|
|
||||||
|
|
||||||
|
## 设备内存管理
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
TF设备内存管理模块利用BFC算法(best-fit with coalescing)实现。BFC算法是Doung Lea’s malloc(dlmalloc)的一个非常简单的版本。它具有内存分配、释放、碎片管理等基本功能[11]。
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
BFC将内存分成一系列内存块,每个内存块由一个chunk数据结构管理。从chunk结构中可以获取到内存块的使用状态、大小、数据的基址、前驱和后继chunk等信息。整个内存可以通过一个chunk的双链表结构来表示。
|
||||||
|
|
||||||
|
|
||||||
|
图 3 11内存分块结构图
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
用户申请一个内存块(malloc)。根据建立的chunk双链表找到一个合适的内存块(后面会说明什么是合适的内存块),如果该内存块的大小是用户申请大小的两倍以上,那么将该内存块切分成两块,这就是split操作。返回其中一块给用户,并将该内存块标识为占用。Spilt操作会新增一个chunk,所以需要修改chunk双链表以维持前驱和后继关系。
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
用户释放一个内存块(free)。先将该块标记为空闲。然后根据chunk数据结构中的信息找到其前驱和后继内存块。如果前驱和后继块中有空闲的块,那么将刚释放的块和空闲的块合并成一个更大的chunk(这就是merge操作,合并当前块和其前后的空闲块)。再修改双链表结构以维持前驱后继关系。这就做到了内存碎片的回收。
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
BFC的核心思想是:将内存分块管理,按块进行空间分配和释放;通过split操作将大内存块分解成小内存块;通过merge操作合并小的内存块,做到内存碎片回收。
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
但是还留下许多疑问。比如说申请内存空间时,什么样的块算合适的内存块?如何快速管理这种块?
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
BFC算法采取的是被动分块的策略。最开始整个内存是一个chunk,随着用户申请空间的次数增加,最开始的大chunk会被不断的split开来,从而产生越来越多的小chunk。当chunk数量很大时,为了寻找一个合适的内存块而遍历双链表无疑是一笔巨大的开销。为了实现对空闲块的高效管理,BFC算法设计了bin这个抽象数据结构。
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
Bin数据结构中,每个bin都有一个size属性,一个bin是一个拥有chunk size >= bin size的空闲chunk的集合。集合中的chunk按照chunk size的升序组织成单链表。BFC算法维护了一个bin的集合:bins。它由多个bin以及从属于每个bin的chunks组成。内存中所有的空闲chunk都由bins管理。
|
||||||
|
|
||||||
|
|
||||||
|
图 3 12 bins集合的结构图
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
图 3 12中每一列表示一个bin,列首方格中的数字表示bin的size。bin size的大小都是256的2^n的倍。每个bin下面挂载了一系列的空闲chunk,每个chunk的chunk size都大于等于所属的bin的bin size,按照chunk size的升序挂载成单链表。BFC算法针对bins这个集合设计了三个操作:search、insert、delete。
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
Search 操作:给定一个chunk size,从bins中找到大于等于该chunk size的最小的那个空闲chunk。Search操作具体流程如下。如果bin以数组的形式组织,那么可以从index = chunk size /256 >>2的那个bin开始查找。最好的情况是开始查找的那个bin的chunk链表非空,那么直接返回链表头即可。这种情况时间复杂度是常数级的。最坏的情况是遍历bins数组中所有的bin。对于一般大小的内存来说,bins数组元素非常少,比如4G空间只需要23个bin就足够了(256 * 2 ^ 23 > 4G),因此也很快能返回结果。总体来说search操作是非常高效的。对于固定大小内存来说,查找时间是常数量级的。
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
Insert 操作:将一个空闲的chunk插入到一个bin所挂载的chunk链表中,同时需要维持chunk链表的升序关系。具体流程是直接将chunk插入到index = chunk size /256 >>2的那个bin中即可。
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
Delete操作:将一个空闲的chunk从bins中移除。
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
TF中内存分配算法实现文件core/common_runtime/bfc_allocator.cc,GPU内存分配算法实现文件core/common_runtime/gpu/gpu_bfc_allocator.cc。
|
||||||
36
Tensorflow教程/TensorFlow-code kernels.md
Normal file
@@ -0,0 +1,36 @@
|
|||||||
|
http://www.360doc.com/content/17/0307/19/39202731_634787879.shtml
|
||||||
|
|
||||||
|
### kernels简介
|
||||||
|
|
||||||
|
TF中包含大量Op算子,这些算子组成Graph的节点集合。这些算子对Tensor实现相应的运算操作。
|
||||||
|
|
||||||
|
OpKernel类(core/framework/op_kernel.h)是所有Op类的基类。继承OpKernel还可以自定义新的Op类。用的较多的Op如(MatMul, Conv2D, SoftMax, AvgPooling, Argmax等)。
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
所有Op包含注册(Register Op)和实现(正向计算、梯度定义)两部分。
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
所有Op类的实现需要overide抽象基函数 void Compute(OpKernelContext* context),实现自身Op功能。用户可以根据需要自定义新的Op操作,参考[12]。
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
TF中所有Op操作的属性定义和描述都在 ops/ops.pbtxt。如下Add操作,定义了输入参数x、y,输出参数z。
|
||||||
|
|
||||||
|
|
||||||
|
> 下面介绍不同的op实现的办法
|
||||||
|
|
||||||
|
### UnaryOp & BinaryOp
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
UnaryOp和BinaryOp定义了简单的一元操作和二元操作,类定义在/core/kernels/ cwise_ops.h文件,类实现在/core/kernels/cwise_op_*.cc类型的文件中,如cwise_op_sin.cc文件。
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
一元操作全称为Coefficient-wise unary operations,一元运算有abs, sqrt, exp, sin, cos,conj(共轭)等。如abs的基本定义:
|
||||||
|
|
||||||
|
|
||||||
|
二元操作全称为Coefficient-wise binary operations,二元运算有add,sub, div, mul,mod等。如sum的基本定义:
|
||||||
|
|
||||||
22
Tensorflow教程/TensorFlow-自定义IO.md
Normal file
@@ -0,0 +1,22 @@
|
|||||||
|
## TensorFlow-自定义IO
|
||||||
|
|
||||||
|
### 基本介绍
|
||||||
|
|
||||||
|
##### 架构
|
||||||
|
|
||||||
|
* 文件格式: 我们使用 Reader Op来从文件中读取一个 record (可以使任意字符串)。
|
||||||
|
* 记录格式: 我们使用解码器或者解析运算将一个字符串记录转换为TensorFlow可以使用的张量。
|
||||||
|
|
||||||
|
|
||||||
|
### 读取数据方法
|
||||||
|
> 本质上Dateset是一个封装好的上层接口,其本质上是调用Python中的数据读取底层接口,不择维护队列和栈。Python中的底层接口调用了C++中Core提供的核心方法。
|
||||||
|
|
||||||
|
> 现在需要根据一根成熟的代码编译完整的代码来阅读相关的函数调用过程。
|
||||||
|
|
||||||
|
|
||||||
|
### 调用栈
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
### core中的函数
|
||||||
18
Tensorflow教程/TensorFlow概述.md
Normal file
@@ -0,0 +1,18 @@
|
|||||||
|
## 基本概念
|
||||||
|
|
||||||
|
##### 人工智能
|
||||||
|
|
||||||
|
机器模拟人的意识和思维。是一种科研领域。
|
||||||
|
|
||||||
|
##### 机器学习
|
||||||
|
|
||||||
|
1. 定义:机器学习是一种统计学方法。计算机利用已有数据,得出某种模型,再利用此模型预测结果。
|
||||||
|
2. 应用:对连续数据的预测;对离散数据的分类。
|
||||||
|
3. 是人工智能的一种方法。
|
||||||
|
|
||||||
|
> 决策树模型是一种简单的深度学习模型。
|
||||||
|
|
||||||
|
##### 深度学习
|
||||||
|
是基于深层次神经网络的学习模型,是机器学习的一种方式。机器学习还有很多种学习模型。机器学习是一种领域,而深度学习,是一种算法。(之前好像写过相关的区别。
|
||||||
|
|
||||||
|
> 神经网络的发展过程:单层神经网络(感知机)->双层次神经网络->深度神经网络+卷积神经网络+循环神经网络。
|
||||||
140
Tensorflow教程/tensorflow安装常见问题.md
Normal file
@@ -0,0 +1,140 @@
|
|||||||
|
1、python里import tensorflow时报
|
||||||
|
```
|
||||||
|
“ImportError: /lib64/libc.so.6: version 'GLIBC_2.17' not found (required by /usr/local/lib/python2.7/site-packages/tensorflow/python/_pywrap_tensorflow.so)”
|
||||||
|
```
|
||||||
|
##### 原因
|
||||||
|
> 主要是glibc的版本太低,默认的CentOS 6.5 glibc版本最高为2.12
|
||||||
|
|
||||||
|
##### 解决
|
||||||
|
执行: strings /lib64/libc.so.6|grep GLIBC
|
||||||
|
|
||||||
|
查看目前系统支持的glibc的版本
|
||||||
|
```
|
||||||
|
[root@zhx-tserver2 build-2.17]# strings /lib64/libc.so.6|grep GLIBC
|
||||||
|
GLIBC_2.2.5
|
||||||
|
GLIBC_2.2.6
|
||||||
|
GLIBC_2.3
|
||||||
|
GLIBC_2.3.2
|
||||||
|
GLIBC_2.3.3
|
||||||
|
GLIBC_2.3.4
|
||||||
|
GLIBC_2.4
|
||||||
|
GLIBC_2.5
|
||||||
|
GLIBC_2.6
|
||||||
|
GLIBC_2.7
|
||||||
|
GLIBC_2.8
|
||||||
|
GLIBC_2.9
|
||||||
|
GLIBC_2.10
|
||||||
|
GLIBC_2.11
|
||||||
|
GLIBC_2.12
|
||||||
|
GLIBC_PRIVATE
|
||||||
|
```
|
||||||
|
|
||||||
|
tensorflow需要glibc-2.17,需要升级glibc
|
||||||
|
|
||||||
|
1. 下载glibc2.17:
|
||||||
|
```
|
||||||
|
wget http://ftp.gnu.org/gnu/glibc/glibc-2.17.tar.gz
|
||||||
|
```
|
||||||
|
2. 解压:
|
||||||
|
```
|
||||||
|
tar -xzf glibc-2.17.tar.gz
|
||||||
|
```
|
||||||
|
3. 创建build目录:
|
||||||
|
```
|
||||||
|
mkdir build
|
||||||
|
```
|
||||||
|
4. 进入build目录编译glibc:
|
||||||
|
```
|
||||||
|
cd build
|
||||||
|
../glibc-2.17/configure --prefix=/usr --disable-profile --enable-add-ons --with-headers=/usr/include --with-binutils=/usr/bin
|
||||||
|
make -j4
|
||||||
|
make install
|
||||||
|
```
|
||||||
|
|
||||||
|
> 注:如果执行configure的时候报configure: error: support for --no-whole-archive is needed,则把configure命令改成
|
||||||
|
../glibc-2.17/configure --prefix=/usr --disable-profile --enable-add-ons --with-headers=/usr/include
|
||||||
|
|
||||||
|
|
||||||
|
5. 查看GLIBC版本执行 strings /lib64/libc.so.6|grep GLIBC查看版本已经支持 GLIBC_2.17
|
||||||
|
```
|
||||||
|
[root@zhx-tserver2 build-2.17]# strings /lib64/libc.so.6|grep GLIBC
|
||||||
|
GLIBC_2.2.5
|
||||||
|
GLIBC_2.2.6
|
||||||
|
GLIBC_2.3
|
||||||
|
GLIBC_2.3.2
|
||||||
|
GLIBC_2.3.3
|
||||||
|
GLIBC_2.3.4
|
||||||
|
GLIBC_2.4
|
||||||
|
GLIBC_2.5
|
||||||
|
GLIBC_2.6
|
||||||
|
GLIBC_2.7
|
||||||
|
GLIBC_2.8
|
||||||
|
GLIBC_2.9
|
||||||
|
GLIBC_2.10
|
||||||
|
GLIBC_2.11
|
||||||
|
GLIBC_2.12
|
||||||
|
GLIBC_2.13
|
||||||
|
GLIBC_2.14
|
||||||
|
GLIBC_2.15
|
||||||
|
GLIBC_2.16
|
||||||
|
GLIBC_2.17
|
||||||
|
GLIBC_PRIVATE
|
||||||
|
```
|
||||||
|
|
||||||
|
|
||||||
|
2、python里import tensorflow时
|
||||||
|
```
|
||||||
|
ImportError: /usr/lib64/libstdc++.so.6: version `GLIBCXX_3.4.18' not found (required by /opt/jmr/anaconda2/lib/python2.7/site-packages/tensorflow/python/_pywrap_tensorflow.so)
|
||||||
|
```
|
||||||
|
|
||||||
|
##### 原因
|
||||||
|
|
||||||
|
GLIBCXX的版本太低,也需要更新。
|
||||||
|
|
||||||
|
##### 解决
|
||||||
|
|
||||||
|
拷贝一个libstdc++.so.6.0.18到/usr/lib64/目录,做个软连接即可
|
||||||
|
```
|
||||||
|
cp libstdc++.so.6.0.18 /usr/lib64/
|
||||||
|
cd /usr/lib64/
|
||||||
|
ln -sf libstdc++.so.6.0.18 libstdc++.so.6
|
||||||
|
```
|
||||||
|
|
||||||
|
|
||||||
|
3、python里import tensorflow时
|
||||||
|
```
|
||||||
|
ImportError: /usr/local/python27/lib/python2.7/site-packages/tensorflow/python/_pywrap_tensorflow_internal.so: undefined symbol: PyUnicodeUCS4_FromString
|
||||||
|
```
|
||||||
|
|
||||||
|
##### 原因
|
||||||
|
如果自己单独升级了python,或者有多个版本的python时,便有可能出现此问题.
|
||||||
|
|
||||||
|
问题表象为:
|
||||||
|
```
|
||||||
|
undefined symbol: PyUnicodeUCS2_AsUTF8String
|
||||||
|
```
|
||||||
|
或者
|
||||||
|
```
|
||||||
|
undefined symbol: PyUnicodeUCS4_AsUTF8String.
|
||||||
|
```
|
||||||
|
|
||||||
|
根本原因时python和某个你用的库编译时指定的UCS编码方式不对.
|
||||||
|
编译python时,可以通过指定--enable-unicode[=ucs[24]]来选择使用UCS2或者UCS4.
|
||||||
|
|
||||||
|
如果你的错误是undefined symbol: PyUnicodeUCS2_AsUTF8String,说明你的python编译时使用的是UCS4,反之依然.
|
||||||
|
##### 解决
|
||||||
|
|
||||||
|
1. 重新编译python2或者重新编译库.选择一般是重新编译库.
|
||||||
|
|
||||||
|
我这重新编译python,因为报错是PyUnicodeUCS4_FromString,说明tensorflow是用UCS4编译的,而python是UCS2编译的:
|
||||||
|
重新编译时设置unicode为ucs4
|
||||||
|
```
|
||||||
|
./configure --prefix=/usr/local/python27 --enable-unicode=ucs4
|
||||||
|
```
|
||||||
|
|
||||||
|
> python2.7.默认是使用UCS2.
|
||||||
|
|
||||||
|
> linux比windows好用好多啊,python2与python3可以完美共存,而别只需要建立不同的软连接就能实现不同的调用,太完美了。而且所有的软件也都是文件,不会有后缀名不同的各种文件混淆,只要文件内容是正确的就能完整使用。
|
||||||
|
|
||||||
|
---------------------
|
||||||
|
作者:[黑色外套](https://blog.csdn.net/qq708986022/article/details/77896791)
|
||||||
4
Tensorflow教程/编译失败记录.md
Normal file
@@ -0,0 +1,4 @@
|
|||||||
|
1. 第一次编译失败:忘记声明变量N
|
||||||
|
2. 第二次编译失败:命名空间没有使用tensorflow导致很多变量没有办法找到。
|
||||||
|
3. 第三次编译失败:指针使用错误,忘记加箭头了
|
||||||
|
4. 第四次运行失败:数组out of range
|
||||||
15
Tensorflow教程/说明.md
Normal file
@@ -0,0 +1,15 @@
|
|||||||
|
## 基本概念
|
||||||
|
|
||||||
|
### 人工智能
|
||||||
|
机器模拟人的意识和思维。是一种科研领域。
|
||||||
|
|
||||||
|
### 机器学习
|
||||||
|
定义:机器学习是一种统计学方法。计算机利用已有数据,得出某种模型,再利用此模型预测结果。
|
||||||
|
应用:对连续数据的预测;对离散数据的分类。
|
||||||
|
是人工智能的一种方法。
|
||||||
|
决策树模型是一种简单的深度学习模型。
|
||||||
|
|
||||||
|
### 深度学习
|
||||||
|
是基于深层次神经网络的学习模型,是机器学习的一种方式。机器学习还有很多种学习模型。机器学习是一种领域,而深度学习,是一种算法。(之前好像写过相关的区别。
|
||||||
|
|
||||||
|
> 神经网络的发展过程:单层神经网络(感知机)->双层次神经网络->深度神经网络+卷积神经网络+循环神经网络。
|
||||||
1
软件文档写作/1 软件文档协作.md
Normal file
@@ -0,0 +1 @@
|
|||||||
|
## 丢失
|
||||||
63
软件文档写作/2 软件文档综述.md
Normal file
@@ -0,0 +1,63 @@
|
|||||||
|
### 软件文档的定义
|
||||||
|
软件 = 程序+文档+数据。
|
||||||
|
文档是某种数据媒体和其中所记录的数据。
|
||||||
|
|
||||||
|
### 软件文档的特点
|
||||||
|
文档具有永久性,可供人阅读。
|
||||||
|
文档是计算机软件的重要组成部分
|
||||||
|
|
||||||
|
### 软件的生存周期
|
||||||
|
* 从构思软件开始到该产品不再使用为止的时间段
|
||||||
|
* 分为计划、开发、运行三个时期。不同实践不同角色参与其中。
|
||||||
|
* 计划时期:
|
||||||
|
* 问题分析
|
||||||
|
* 可行性研究
|
||||||
|
* 制定计划
|
||||||
|
* 开发时期
|
||||||
|
* 需求分析
|
||||||
|
* 概要设计
|
||||||
|
* 详细设计
|
||||||
|
* 编码测试
|
||||||
|
* 软件发布
|
||||||
|
* 运行维护时期
|
||||||
|
* 运行维护
|
||||||
|
|
||||||
|
### 软件文档的作用
|
||||||
|
管理的依据
|
||||||
|
技术交流的语言
|
||||||
|
软件质量的保证
|
||||||
|
支持培训与参考
|
||||||
|
支持软件维护
|
||||||
|
记录软件的历史
|
||||||
|
|
||||||
|
### 软件文档的分类
|
||||||
|
* 使用范围:
|
||||||
|
* 管理类文档,记录项目管理的信息
|
||||||
|
* 开发类文档,描述软件开发过程本身
|
||||||
|
* 产品类文档,描述开发过程的产物
|
||||||
|
* 阅读对象:
|
||||||
|
* 管理人员
|
||||||
|
* 开发人员
|
||||||
|
* 维护人员
|
||||||
|
* 最终用户
|
||||||
|
* 软件开发方法
|
||||||
|
* 面向过成的文档
|
||||||
|
* 面向对象的文档
|
||||||
|
|
||||||
|
### 软件工程文档标准化的意义
|
||||||
|
* 提高软件的可靠性、可维护性和可移植性
|
||||||
|
* 提高软件的生产率和软件人员的技术水平
|
||||||
|
* 提高软件人员之间的通信效率,减少差错和误解
|
||||||
|
* 有利于软件管理
|
||||||
|
* 有利于降低软件产品的成本和运行维护成本
|
||||||
|
* 有利于缩短软件开发周期
|
||||||
|
|
||||||
|
### 软件工程文档标准的层次:
|
||||||
|
国际标准
|
||||||
|
国家标准
|
||||||
|
行业标准
|
||||||
|
企业规范
|
||||||
|
项目规范
|
||||||
|
|
||||||
|
### 常见的软件文档类型
|
||||||
|
|
||||||
BIN
软件文档写作/2021-03-08-21-12-53.png
Normal file
|
After Width: | Height: | Size: 104 KiB |
BIN
软件文档写作/2021-03-08-21-30-24.png
Normal file
|
After Width: | Height: | Size: 28 KiB |
BIN
软件文档写作/2021-03-08-21-30-38.png
Normal file
|
After Width: | Height: | Size: 23 KiB |
BIN
软件文档写作/2021-03-08-21-30-56.png
Normal file
|
After Width: | Height: | Size: 108 KiB |
BIN
软件文档写作/2021-03-08-21-31-23.png
Normal file
|
After Width: | Height: | Size: 90 KiB |
BIN
软件文档写作/2021-03-08-21-31-42.png
Normal file
|
After Width: | Height: | Size: 50 KiB |
BIN
软件文档写作/2021-03-08-21-32-15.png
Normal file
|
After Width: | Height: | Size: 21 KiB |
BIN
软件文档写作/2021-03-08-21-32-30.png
Normal file
|
After Width: | Height: | Size: 171 KiB |
BIN
软件文档写作/2021-03-08-21-32-53.png
Normal file
|
After Width: | Height: | Size: 49 KiB |
BIN
软件文档写作/2021-03-08-21-33-09.png
Normal file
|
After Width: | Height: | Size: 88 KiB |
BIN
软件文档写作/2021-03-08-21-33-26.png
Normal file
|
After Width: | Height: | Size: 60 KiB |
BIN
软件文档写作/2021-03-08-21-33-46.png
Normal file
|
After Width: | Height: | Size: 68 KiB |
57
软件文档写作/3 软件文档编制要求.md
Normal file
@@ -0,0 +1,57 @@
|
|||||||
|
|
||||||
|
### 软件文档编制的原则
|
||||||
|
应该适应文档的读者
|
||||||
|
应有必要的重复性
|
||||||
|
应具有一定的灵活性
|
||||||
|
应覆盖整个软件的生存周期
|
||||||
|
应是可管理的
|
||||||
|
应采用并标明文档标准
|
||||||
|
应规定支持的工具
|
||||||
|
|
||||||
|
### 软件文档编制的灵活性
|
||||||
|
文档的种类
|
||||||
|
文档的详细程度
|
||||||
|
文档的扩展
|
||||||
|
章节的扩展与缩并
|
||||||
|
程序设计的表现形式
|
||||||
|
文档的表现形式
|
||||||
|
文档的其他种类
|
||||||
|
|
||||||
|
### 软件文档标准的建立步骤
|
||||||
|
选择软件生存周期模型
|
||||||
|
规定文档类型和内容
|
||||||
|
确定文档的质量等级
|
||||||
|
最低限度文档(一级文档):个人开发自用
|
||||||
|
内部文档(2级文档):团队内部交流
|
||||||
|
工作文档(3级文档):项目开发
|
||||||
|
正式文档(4级文档):产品开发
|
||||||
|
|
||||||
|
### 软件文档编制的步骤
|
||||||
|
制定文档编制计划
|
||||||
|
编写文档
|
||||||
|
文档编号
|
||||||
|
文档评审
|
||||||
|
文档部署
|
||||||
|
文档的归档与保存
|
||||||
|
文档维护
|
||||||
|
|
||||||
|
### 软件文档的质量要求
|
||||||
|
针对性
|
||||||
|
精确性
|
||||||
|
清晰性
|
||||||
|
完整性
|
||||||
|
灵活性
|
||||||
|
可追溯性
|
||||||
|
|
||||||
|
### 软件文档的编制技巧
|
||||||
|
* 从技术角度进行文档的编制和评价
|
||||||
|
* 明确文档编制人员的责任
|
||||||
|
* 让编制人员对开发项目有准确的认识
|
||||||
|
* 让开发和设计人员参与文档审阅工作
|
||||||
|
|
||||||
|
### 项目开发文档化
|
||||||
|
* 记录项目中的各种数据
|
||||||
|
* 总结项目开发经验
|
||||||
|
* 规范团队开发过程
|
||||||
|
* 增强项目可见度
|
||||||
|
* 提高团队沟通效率
|
||||||
64
软件文档写作/4 软件文档协作.md
Normal file
@@ -0,0 +1,64 @@
|
|||||||
|
## 传统的软件过程
|
||||||
|
| 生命周期模型 | 强 项 | 弱 项 |
|
||||||
|
|---|---|---|
|
||||||
|
| 构造与调试模型 | 适合小程序,不需要维护的程序 | 几乎没有软件工程的概念,不适合任何稍大的程序 |
|
||||||
|
| 瀑布模型 | 有纪律的方法,文档驱动 | 交付的产品可能无法满足用户的要求 |
|
||||||
|
| 快速原型模型 | 快速体现用户需求,确保交付的产品满足用户的要求,非常适合界面和人机交互的系统的快速开发 | 原型通常只是一个参考。很多软件的原型很难做 |
|
||||||
|
| 增量模型 | 早期投入得到最大化的回报,提升可维护性 | 要求体系结构必须开放,可能退化为建造和调试模型 |
|
||||||
|
| 螺旋模型 | 结合上述的所有优点 | 只能用于大规模的内部产品,开发者必须有风险分析和回避的能力 |
|
||||||
|
| 喷泉模型 | 支持每个阶段内部的迭代以及阶段间的并行工作 | 可能退化为CABTAB(code a bit, test a bit) |
|
||||||
|
|
||||||
|
|
||||||
|
## 构造与调试
|
||||||
|
|
||||||
|

|
||||||
|
|
||||||
|
## 瀑布模型
|
||||||
|
|
||||||
|

|
||||||
|
|
||||||
|
## 快速原型模型
|
||||||
|
|
||||||
|

|
||||||
|
|
||||||
|
## 增量模型
|
||||||
|

|
||||||
|
## 螺旋模型
|
||||||
|
|
||||||
|

|
||||||
|
|
||||||
|
|
||||||
|
## 喷泉模型
|
||||||
|
|
||||||
|
|
||||||
|

|
||||||
|
|
||||||
|
|
||||||
|
## 敏捷开发
|
||||||
|
个体和交互 胜过 过程和工具
|
||||||
|
可以工作的软件 胜过 面面俱到的文档
|
||||||
|
客户合作 胜过 合同谈判
|
||||||
|
响应变化 胜过 遵循计划
|
||||||
|
|
||||||
|
|
||||||
|

|
||||||
|
|
||||||
|
|
||||||
|
## Scrum敏捷开发过程
|
||||||
|
步骤:项目开发——迭代——冲刺——模块
|
||||||
|
一次完整的项目开发过程由若干次迭代开发过程组成,每次迭代完成后将发布一个功能有限的软件产品,经历若干次迭代后,最终发布功能完备的软件产品。
|
||||||
|
|
||||||
|

|
||||||
|
|
||||||
|
一次迭代开发过程需要在需求稳定的前提下开展,分别经历需求分析、架构设计、数据库设计(可选)、界面设计、若干次冲刺开发过程,以及集成测试,最终发布产品
|
||||||
|

|
||||||
|
|
||||||
|
一次冲刺开发过程由若干次模块开发过程组成,本次冲刺中包含的所有模块开发完成后,本次冲刺开发过程结束。
|
||||||
|
|
||||||
|

|
||||||
|
|
||||||
|
一次模块开发过程需经历物理设计、编码自测、代码评审、验收测试、模块测试等活动,最终以通过模块测试为结束依据。
|
||||||
|
|
||||||
|

|
||||||
|
|
||||||
|
|
||||||
48
软件文档写作/5 软件文档评审.md
Normal file
@@ -0,0 +1,48 @@
|
|||||||
|
|
||||||
|
## 为什么要评审
|
||||||
|
提高项目的生产率,减少开发时间,加快开发进度。
|
||||||
|
改善软件质量,改进开发过程。
|
||||||
|
在评审过程中,使开发团队的其他成员更熟悉产品和开发过程。
|
||||||
|
通过评审,标志着软件开发的一个阶段的完成。
|
||||||
|
生产出更以维护的软件。
|
||||||
|
|
||||||
|
## 参与评审的角色
|
||||||
|
评审组长
|
||||||
|
评审的组织者,确保评审入口准则得到满足确保遵循评审的流程,指明评审的关注点,确保所有评审软院已经充分检视。确保评审的进度,确保发现、整理、修正问题。
|
||||||
|
宣读员
|
||||||
|
在评审会议上通过讲解来引导评审团队浏览工作产品
|
||||||
|
记录员
|
||||||
|
记录会议上的信息
|
||||||
|
作者
|
||||||
|
确保工作产品已经准备好,在评审会议上详解工作产品。在评审会议结束后修改所有已经确认的缺陷。
|
||||||
|
评审员
|
||||||
|
寻找被评审工作产品中的缺陷,填写评审表单并上交。参加评审会议,就评审出的缺陷提出建设性建议。
|
||||||
|
|
||||||
|
## 评审的内容
|
||||||
|
管理评审——组织的最高管理者就管理体系就现状、适宜性、充分性和有效性以及方针和目标的贯彻落实及实现情况进行正式的评价。
|
||||||
|
技术评审——发现软件在功能、逻辑、实现上的错误,验证软件符合他的需求规格。确认软件符合预先定义的开发规范和标准。保证软件在同一模式下进行开发。
|
||||||
|
过程评审——评估主要的质量保证流程;卡利率如何处理和解决评审过程中发现的不符合问题;总结和共享好的经验;支出需要进一步完善和改进的部分。
|
||||||
|
文档评审——对象有:需求文档评审、设计文档评审、测试文档评审。
|
||||||
|
|
||||||
|
## 需求文档评审
|
||||||
|
分层次评审
|
||||||
|
正式评审与非正式评审结合
|
||||||
|
分阶段评审
|
||||||
|
充分准备评审
|
||||||
|
精心挑选评审员
|
||||||
|
对评审员进行培训
|
||||||
|
充分利用需求评审检查单
|
||||||
|
做好评审后的跟踪工作
|
||||||
|
|
||||||
|
## 设计文档评审
|
||||||
|
1. 概要设计说明书是否与软件需求说明书要求一致
|
||||||
|
2. 概要设计说明书是否正确完整、一致。
|
||||||
|
3. 系统的模块划分是否合理
|
||||||
|
4. 接口定义是否明确
|
||||||
|
5. 文档是否符合有关标准规定
|
||||||
|
|
||||||
|
## 测试文档评审
|
||||||
|
1. 软件测试计划评审
|
||||||
|
2. 软件测试说明评审
|
||||||
|
3. 软件测试报告评审
|
||||||
|
4. 软件测试记录评审
|
||||||
13
软件文档写作/6 软件文档管理组成.md
Normal file
@@ -0,0 +1,13 @@
|
|||||||
|
## 软件文档管理组成
|
||||||
|
### 文档形成
|
||||||
|
严格按照规定,保证编制、评审、版本管理等环节的质量。
|
||||||
|
### 标识文档类型
|
||||||
|
便于保存、查找、使用、修改。文档所属项目标识、文档种类标识、同类文档中的版本号标识、文档的性质、保密界别和阅读范文标识。
|
||||||
|
### 文档控制
|
||||||
|
保证文档的一致性、各个版本之间的有序性、文档的安全性。
|
||||||
|
### 文档修改管理
|
||||||
|
提出修改建议说明修改的理由、修改的内容。
|
||||||
|
对文档修改建议进行评论。
|
||||||
|
审核修改建议
|
||||||
|
批准修改建议
|
||||||
|
实施修改
|
||||||
25
软件文档写作/7 管理文档规范.md
Normal file
@@ -0,0 +1,25 @@
|
|||||||
|
## 管理类文档的作用
|
||||||
|
软件开发阶段工作成果的体现。
|
||||||
|
把软件开发过程可视化
|
||||||
|
记录开发过程中的技术信息
|
||||||
|
掌握开发过程,控制开发质量和维护工作提供原始信息
|
||||||
|
提供团队内外之间相互沟通、协调的窗口,有利于把我软件的正确性和可用性。
|
||||||
|
|
||||||
|
## 管理类文档的分类
|
||||||
|

|
||||||
|
|
||||||
|
## 各类文档说明
|
||||||
|
### 《可行性分析报告FAR》
|
||||||
|
项目初期策划的结果,分析了项目的要求、目标和环境;提出了集中可供选择的方案;并从技术、经济和法律各方面进行了可行性分析。
|
||||||
|
建议内容包括:可行性分析的前提、可选方案、所建议的系统、经济可行性、技术可行性、法律可行性、用户使用可行性。
|
||||||
|
### 《软件开发计划SDP》
|
||||||
|
了解和监督软件开发过程、所使用的方法、每项活动的途径、项目的安排、组织及资源的一种手段。
|
||||||
|
|
||||||
|
建议内容包括:
|
||||||
|
* 《软件测试计划STP》
|
||||||
|
* 《软件安装计划SIP》
|
||||||
|
* 《软件移交计划STRP》
|
||||||
|
* 《软件配置管理计划SCMP》
|
||||||
|
* 《软件质量保证计划SQAP》
|
||||||
|
* 《开发进度月报DPMR》
|
||||||
|
* 《项目开发总结报告PDSR》
|
||||||