Tnesorpack入门教程

Tensorpack架构

  • DataFlow是一个用于在Python中高效地加载数据的库。除了DataFlow之外,本地TF运营商也可以用于数据加载。它们最终将被封装在相同的InputSource接口下并进行预取。
  • 可以使用任何基于TF的符号函数库来定义模型,其中包括tensorpack中的一小组函数。 ModelDesc是连接模型的接口和InputSource的接口。
  • Tensorpack的Trainers用于管理训练过程中的循环迭代。它们还包括用于多GPU或分布式训练的数据并行逻辑。同时,也拥有很强大的定制能力。
  • Callbacks就像tf.train.SessionRunHook或者plugins。在训练过程中,除了主迭代以外,您想要做的所有事情都可以通过回调进行定义并轻松重用。
  • 所有组件尽管完美地结合在一起,但都具有高度的去相关性:您可以:
    • 单独使用DataFlow作为数据加载库,根本不用tensorfow。
    • 使用tensorpack构建具有多GPU或分布式支持的图结构,然后使用自己的循环进行训练。
    • 自行构建图形,并使用tensorpack回调进行训练。

DataFlow(数据流接口)

DataFlow是什么?

DataFlow是一个用于构建Python迭代器以提高数据加载效率的库。

定义:DataFlow有一个get_data()生成器方法,它产生数据点(datapoints)。datapoints是被称为数据点组件(components of a datapoin)的Python列表对象。

例子:要训练MNIST数据集,需要使用DataFlowget_data()方法,该方法需要生成两个组件的数据点(列表):一个形状为(64,28,28)的numpy的数组(图片数据)和一个形状(64, )数组(图片标签)。

DataFlow的组成

有一个标准接口的好处是能够提供最大的代码可重用性。 tensorpack中有很多现有的DataFlow实用程序,您可以使用这些实用程序来组合具有长数据管道的复杂DataFlow。常见的流水线通常会从磁盘(或其他来源)读取应用转换( apply transformations)分组批处理(group into batches)预取数据(group into batches)。一个简单的例子如下:

1
2
3
4
5
6
7
8
# a DataFlow you implement to produce [tensor1, tensor2, ..] lists from whatever sources:
df = MyDataFlow(dir='/my/data', shuffle=True)
# resize the image component of each datapoint
df = AugmentImageComponent(df, [imgaug.Resize((225, 225))])
# group data into batches of size 128
df = BatchData(df, 128)
# start 3 processes to run the dataflow in parallel
df = PrefetchDataZMQ(df, 3)

为什么要使用DataFlow

  • 很简单:用纯Python编写所有内容,并重用现有的实用程序。相反,在TF操作员中编写数据加载器通常很痛苦,性能很难调整。
  • 速度非常快:可以构建具有并行性的快速DataFlow。在tensorpack中使用DataFlow,可以采用Input Pipeline,进一步加速图形中的数据加载。

尽管如此,tensorpack还支持用本地TF操作与TF数据集加载数据。

使用DataFlow(Tensorpack外部)

通常,tensorpack InputSource接口将DataFlow链接到图结构进行训练。如果在某些自定义代码中使用DataFlow,需要首先调用reset_state()初始化,然后使用生成器:

1
2
3
4
5
6
7

df = SomeDataFlow()

df.reset_state() #初始化
generator = df.get_data()
for dp in generator:
# dp is now a list. do whatever

DataFlow独立于tensorpack和TensorFlow。要导入tensorpack.dataflow,甚至不必安装TensorFlow。可以简单地将其用作数据处理管道并将其插入任何其他框架

Input Pipeline(输入流水线)

本教程包含关于“如何高效地读取数据以使用TensorFlow”以及tensorpack如何支持这些方法的主题的一般性讨论。 您不必阅读它,因为这些是tensorpack接口下的细节,但知道它可以帮助您在构建任务时,高效的选择最佳输入管道。

数据准备并行化

无论使用什么框架,都可以有一个常识:Prepare data in parallel with the training!
原因如下:

  • 数据准备通常会耗费不重要的时间(取决于实际问题)。
  • 数据准备与训练是独立的!这个并行的最根本前提!
  • 数据准备与训练通常使用完全不同的资源(参见上图):Training过程使用GPU,加载数据的过程通过磁盘,处理数据采用CPU,拷贝数据到TF经过的是内存带宽,而拷贝到GPU是通过PCI-e总线。因此训练和数据准备两项任务一起完成并不会降低工作效率。事实上,你可以进一步并行化数据准备实现的不同阶段,因为他们也使用不同的资源。

我们可以简单的计算一下:

4台P100 GPU可以852张/秒的速度训练ResNet50,这些图像的大小为852 224 224 3 4bytes = 489MB。 假设你有 5GB / s的memcpy带宽(如果你运行单线程拷贝,大致就像这样),只需复制一次数据就需要0.1s ——将训练速度减慢10%。 并且在预处理期间还有很多运算成本依然需要耗费大量时间!。

未能隐藏数据准备延迟是看不到良好GPU利用率的主要原因。 因此一定要选择一个能够延迟隐藏的框架。 但是大多数其他TensorFlow封装都是基于feed_dict设计的。 Tensorpack有内置的机制来隐藏上述阶段的延迟。 这是Tensorpack速度更快的主要原因。

Python Reader or TF Reader ?

无论您使用什么来加载/预处理数据,上述讨论都是有效的,无论是Python代码还是TensorFlow运算符,或者是两者的组合。这两个都支持tensorpack,推荐使用Python。

TensorFlow Reader优点

人们经常认为他们应该使用tf.data,因为它很快。

  • 事实上,它一般情况下很快,但不一定。使用Python,您可以访问许多其他快速库,这些库在TF中可能不受支持。
  • Python足够快。只要数据准备与训练保持同步,并且上图中所有四个模块的延迟都隐藏起来,更快的读取不会对整体吞吐量产生任何影响。 对于大多数类型的问题,直到多GPU ImageNet训练的规模,如果您使用快速库(例如tensorpack.dataflow),Python可以提供足够的速度。
TensorFlow Reader缺点

TensorFlow Reader的缺点显而易见——接口设计太复杂了!
与运行数学模型不同,数据处理是一项复杂且结构不良( poorly-structured)的任务。 您需要处理不同的格式,处理特殊案例,嘈杂的数据,数据组合。 这样做需要条件操作,循环,数据结构,有时甚至是异常处理。 这些操作自然不是符号图的该做的相关任务。

我们来看看用户对tf.data的要求:

  • 填充数据,混洗数据的不同方式
  • 处理数据中的任何值
  • 处理不是批量大小倍数的数据集
  • 排序/跳过一些数据
  • 将数据写入文件

为了支持所有这些可以在Python中使用3行代码完成的功能,您需要一个新的TF API,或者向Dataset.from_generator(即Python再次)提出要求。
如果您的数据本来就非常干净并且格式正确,那么使用TF来读取数据才有意义。如果没有,你可能会想写一个脚本来格式化你的数据,但是你几乎已经写了一个Python加载器了!
考虑一下:编写一个Python脚本以便从某种格式转换为TF友好格式,然后是从这种格式转换为张量的TF脚本是浪费时间。 中间格式不一定存在。 您只需要正确的界面即可直接高效地将Python连接到图形。 tensorpack.InputSource就是这样的一个接口。

InputSource

InputSource是tensorpack中的抽象接口,用于描述输入来自哪里以及它们如何进入图形。例如,

  • FeedInput:来自DataFlow并获取图表(缓慢)。
  • QueueInput:来自DataFlow并由TF队列缓存在CPU上。
  • StagingInput:来自某个InputSource,然后由TF StagingArea在GPU上预取。
  • TFDatasetInput:来自tf.data.Dataset
  • dataflow_to_dataset:来自DataFlow,并由tf.data.Dataset进一步处理。
  • TensorInput:来自你定义的张量(例如可以是读操作)。
  • ZMQInput:来自一些ZeroMQ管道,读取/预处理可能发生在不同的过程中,甚至不同的机器中。

通常,我们推荐QueueInput + StagingInput,因为它对大多数用例都很有用。 如果您的数据因任何原因必须来自单独的进程,请使用ZMQInput。 如果您仍然想使用TF读取操作,请定义一个tf.data.Dataset并使用TFDatasetInput

Symbolic Layers(符号层)

Tensorpack包含一小部分通用模型基元,如conv / deconv,fc,bn,pooling层。 这些层的编写只是因为在tensorpack开发时没有其他选择。 现在,这些实现实际上可以直接调用tf.layers

现在,可以在tensorpack中使用tf.layers或任何其他符号库。使用tensorpack实现,您还可以通过argscopeLinearWrap,以简化代码。

请注意,为了保持代码和预先训练模型的向后兼容性,tensorpack层与tf.layers有一些细微差别,包括变量名称和默认选项。有关详细信息,请参阅tensorpack API文档。

argscope and LinearWrap

argscope为您提供默认参数的上下文。 LinearWrap是简化构建“线性结构”模型的糖衣语法(syntax sugar)。

代码如下:

1
2
3
4
5
6
7
8
9
10
11
with argscope(Conv2D, filters=32, kernel_size=3, activation=tf.nn.relu):
l = (LinearWrap(image) # the starting brace is only for line-breaking
.Conv2D('conv0')
.MaxPooling('pool0', 2)
.Conv2D('conv1', padding='SAME')
.Conv2D('conv2', kernel_size=5)
.FullyConnected('fc0', 512, activation=tf.nn.relu)
.Dropout('dropout', rate=0.5)
.tf.multiply(0.5)
.apply(func, *args, **kwargs)
.FullyConnected('fc1', units=10, activation=tf.identity)())

上面示例代码等价如下:

1
2
3
4
5
6
7
8
9
l = Conv2D('conv0', image, 32, 3, activation=tf.nn.relu)
l = MaxPooling('pool0', l, 2)
l = Conv2D('conv1', l, 32, 3, padding='SAME', activation=tf.nn.relu)
l = Conv2D('conv2', l, 32, 5, activation=tf.nn.relu)
l = FullyConnected('fc0', l, 512, activation=tf.nn.relu)
l = Dropout('dropout', l, rate=0.5)
l = tf.multiply(l, 0.5)
l = func(l, *args, **kwargs)
l = FullyConnected('fc1', l, 10, activation=tf.identity)

访问相关的张量

层中的变量将命名为name / Wname / b等。有关详细信息,请参阅每个图层的API文档。在构建图时,可以像这样访问变量:

1
2
3
l = Conv2D('conv1', l, 32, 3)
print(l.variables.W)
print(l.variables.b)

但请注意,这是一种很冒险的方式,可能不适用于未来版本的TensorFlow。此方法也不适用于LinearWrap,并且无法访问由激活函数创建的变量。除非在API中有不同的说明,否则层的输出通常被命名为name/output。你可以打印张量来查看它的名字。

在Tensorpack外使用模型

您可以单独使用tensorpack模型作为简单的符号函数库。为此,只需在定义模型时输入TowerContext

1
2
with TowerContext('', is_training=True):
# call any tensorpack layer

某些层(特别是BatchNorm)具有不同的训练/测试时间行为,此行为由TowerContext控制。如果您需要在测试时间使用它们的tensorpack版本,则需要在另一个上下文中为它们创建操作。

1
2
3
# Open a `reuse=True` variable scope here if you're sharing variables, then:
with TowerContext('some_name_or_empty_string', is_training=False):
# build the graph again

在Tensorpack中使用其他符号库

在定义模型的过程中可以使用任何觉得舒服的库来构建图结构。
通常,slim / tflearn / tensorlayer只是符号函数包装器,调用它们与调用tf.add没什么区别。 不过,需要小心如何正规化/ BN更新应该在这些库中的处理。
这与使用 sonnet/ Keras有点不同。 sonnet / Keras 通过自己的模型类来管理变量范围,调用它们的符号函数总是创建新的变量范围。 请参阅Keras示例以了解如何在tensorpack内使用它。

Trainers

Tensorpack训练器包含以下逻辑:

  • 构件图结构
  • 运行迭代(带回调)

    通常我们不会直接触摸这些方法,而是在训练器上使用更高级的接口。我们只需选择要使用的训练器。但是他们如何工作的一些基本知识是有用的:

Tower Trainer

遵循TensorFlow中的术语,Tower Trainer是一个可调用的函数,它接受输入张量并将模型的一个重复项添加到图中。这种概念可以描述大多数类型的神经网络训练。Tower的概念主要用于支持:

  • 数据并行多GPU训练,其中在每个GPU上构建复制品。
  • 用于推理的图构造,其中复制是在推理模式下构建的。
    用户需要提供Tower函数才能使用TowerTrainer。特别是,在使用ModelDesc接口时,build_graph方法将成为Tower函数。

Tower函数需要遵循一些约定:

  • 它可能被多次调用以进行数据并行训练或推理。
    • 因此,要使用tensorflow-hub模块,需要在tower函数外初始化模块,并在tower函数内调用模块。
  • 它必须遵循变量集合
    • (必需)只能将可通过梯度下降调整的变量放入TRAINABLE_VARIABLES中。
    • (推荐)将需要用于推理的不可训练变量放入MODEL_VARIABLES中。
  • 它必须遵循变量范围:
    • 在函数中创建的任何可训练变量的名称必须与“variable_scope_name / custom / name”类似。不要依赖于name_scope的名字。不要使用两次variable_scope的名字。
  • 创建任何可训练变量都必须遵守重用变量范围。要遵守变量重用,请在函数中使用tf.get_variable而不是tf.Variable。另一方面,对于不可训练的变量,可以使用tf.Variable确保在每个tower中创建新变量,即使reuse= True时也是如此。
  • 总是在TowerContext下调用,可以通过get_current_tower_contxt()来访问它。上下文包含有关训练/推理模式,重用等信息。

    这些约定很容易遵循,并且大多数层封装(例如,tf.layers / slim / tensorlayer)确实遵循它们。 请注意,某些Keras层不遵循这些约定,如果在tensorpack中使用,将需要一些变通方法。

当然你也可以不这样编写,但所有现有的tensorpack trainers都是TowerTrainer的子类。

MultiGPU Trainers

对于数据并行多GPU训练,不同的多GPU训练将实施不同的分配策略。他们以有效的方式照顾器件布局,梯度平均和同步,并且都达到了官方TF基准测试的相同性能。只需要一行代码更改即可使用它们,即trainer=SyncMultiGPUTrainerReplicated()
请注意使用这些trains时的一些常见问题

  • 在每次迭代中,所有GPU(模型的所有复制品)从InputSource中获取张量,而不是全部和分割。 所以总的批量大小将变成(batch size of InputSource) * #GPU

    为数据并行训练分割张量根本没有意义。 首先,为什么要将时间串联起来分成大批量然后再分开呢? 其次,这会对数据造成不必要的形状限制。 通过让每个GPU训练自己的输入张量,他们可以同时训练不同形状的输入。

  • tower函数(您的模型代码)将被称为乘法时间。 因此,在修改这些函数中的全局状态时需要小心,例如 将操作添加到TF集合中。

Distributed Trainers

分布式培训需要提供高性能allreduce实现的horovod库。要运行分布式培训,首先正确安装horovod,然后参阅HorovodTrainer的文档。

Tensorpack已经使用TF的本地API实现了一些其他分布式训练,但即使在今天,TF对分布式训练的本地支持也不是很高的性能。 因此这些训练没有积极维护,不推荐使用。

Training Interface

Tensorpack有一个详尽的接口以获得最大的灵活性。当不想过多定制,训练时可以使用tensorpack提供的接口来简化代码。

使用ModelDesc和TrainConfig

这是旧Tensorpack用户最熟悉的接口,仅用于单一成本任务( single-cost tasks )。这个接口有很多例子。
SingleCost trainers需要4个参数来设置图:InputDescInputSourceget_cost函数和optimizerModelDesc通过将它们中的三个打包成一个对象来描述一个模型:

1
2
3
4
5
6
7
8
9
10
11
class MyModel(ModelDesc):
def _get_inputs(self):
return [tf.placeholder(dtype, shape, name), tf.placeholder(dtype, shape, name), ... ]

def _build_graph(self, inputs):
tensorA, tensorB = inputs
# build the graph
self.cost = xxx # define the cost tensor

def _get_optimizer(self):
return tf.train.GradientDescentOptimizer(0.1)

_get_inputs 需要定义构建图的所有输入的元信息。

_build_graph获取与_get_inputs匹配的inputs张量列表。

_build_graph中可以使用任何符号函数,包括TensorFlow核心库函数和其他符号库。_build_graph是tower函数,所以需要遵循一些规则。同时还需要在此函数中设置self.cost

定义了这样一个模型后,使用TrainConfiglaunch_train_with_config

1
2
3
4
5
6
7
8
9
10
11
12
config = TrainConfig(
model=MyModel()
dataflow=my_dataflow,
# data=my_inputsource, # alternatively, use a customized InputSource
callbacks=[...], # some default callbacks are automatically applied
# some default monitors are automatically applied
steps_per_epoch=300, # default to the size of your InputSource/DataFlow
)

trainer = SomeTrainer()
# trainer = SyncMultiGPUTrainerParameterServer(8)
launch_train_with_config(config, trainer)

Raw Trainer Interface

要获得较低级别的控件,您还可以直接访问训练的方法:

  • 构建图:对于一般训练,请自行构建图结构。 对于单成本训练( single-cost trainer),请通过SingleCostTrainer.setup_graph构建图结构。
  • 运行迭代:调用Trainer.train()或`Trainer.train_with_defaults()``,它为正常用例提供了一些默认选项。

Callbacks

回调是除了训练迭代以外的其他所有事情的接口。
除了最小化cost的实际训练迭代之外,你可能想要做一些其他事情。如下:

  • 在训练开始之前(例如:初始化保存程序,转储图)
  • 随着每次训练迭代(例如,在图中运行一些其他操作)
  • 在训练迭代之间(例如更新进度条,更新超参数)
  • 在周期之间(例如保存模型,运行一些验证)
  • 训练后(例如,在某处发送模型,发送消息到您的手机)

人们传统上倾向于将训练循环与这些额外功能一起编写。这会使循环冗长,同一特征的代码可能会分开(设想一个特征,它需要在开始时进行初始化,然后在迭代之间进行一些实际工作)。

通过编写回调来实现在每个地方做什么,tensorpack训练器将在适当的时候调用回调。因此,只要您使用tensorpack,这些功能就可以一条线(single line)的重复使用。

例如,这些在训练ResNet时使用的回调:

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
37
38
39
40
41
42
43
44

callbacks=[
# save the model every epoch
ModelSaver(),
# backup the model with best validation error
MinSaver('val-error-top1'),
# run inference on another Dataflow every epoch, compute classification error and log to monitors
InferenceRunner(dataset_val, [
ClassificationError('wrong-top1', 'val-error-top1'),
ClassificationError('wrong-top5', 'val-error-top5')]),
# schedule the learning rate based on epoch number
ScheduledHyperParamSetter('learning_rate',
[(30, 1e-2), (60, 1e-3), (85, 1e-4), (95, 1e-5)]),
# can manually change the learning rate through a file, without interrupting training
HumanHyperParamSetter('learning_rate'),
# send validation error to my phone through pushbullet
SendStat('curl -u your_id_xxx: https://api.pushbullet.com/v2/pushes \\
-d type=note -d title="validation error" \\
-d body={val-error-top1} > /dev/null 2>&1',
'val-error-top1'),
# record GPU utilizations during training
GPUUtilizationTracker(),
# touch a file to pause the training and start a debug shell, to observe what's going on
InjectShell(shell='ipython'),
# estimate time until completion
EstimatedTimeLeft()
] + [ # these callbacks are enabled by default already, though you can customize them
# maintain those moving average summaries defined in the model (e.g. training loss, training error)
MovingAverageSummary(),
# draw a progress bar
ProgressBar(),
# run `tf.summary.merge_all` every epoch and log to monitors
MergeAllSummaries(),
# run ops in GraphKeys.UPDATE_OPS collection along with training, if any
RunUpdateOps(),
],
monitors=[ # monitors are a special kind of callbacks. these are also enabled by default
# write everything to tensorboard
TFEventWriter(),
# write all scalar data to a json file, for easy parsing
JSONWriter(),
# print all scalar data every epoch (can be configured differently)
ScalarPrinter(),
]

请注意,回调涵盖了训练的每个细节,从图操作到进度条。这意味着我们可以根据自己的喜好自定义训练的各个部分,例如,在进度条上显示不同的内容,以不同训练阶段评估部分结果等。

这些功能并不总是必要的,但是想想如果你将这些逻辑与循环一起写入,主循环看起来有多混乱。如果您在需要时只用一条线即可启用这些功能,那么将变得多么简单。

Save and Load models

Work with TF Checkpoint

ModelSaver回调以TensorFlow检查点(checkpoint)格式将模型保存到logger.get_logger_dir()定义的目录中。TF检查点通常包含.data-xxxxx文件和.index文件。两者都是必要的。
tf.train.NewCheckpointReader是解析TensorFlow检查点的最佳工具。我们有两个示例脚本来演示它的用法,但请阅读TF文档以获取详细信息。

  • scripts/ls-checkpoint.py演示如何在检查点打印所有变量及其形状。
  • scripts/dump-model-params.py可用于删除检查点中不必要的变量。它需要一个metagraph文件(它也保存在ModelSaver中),只保存模型在推理时需要的变量。它可以将模型转储到以npz格式保存的var-name:value字典中。

Load a Model

模型加载(在训练或测试中)是通过session_init接口。 目前有两种方法可以恢复会话:session_init = SaverRestore(…)来恢复TF检查点,或session_init = DictRestore(…)来恢复字典。get_model_loader帮助决定使用文件名中哪一个。要加载多个模型,请使用ChainInit
变量还原完全基于当前图中变量与session_init初始值设定项中的变量之间的名称匹配。仅出现在一侧的变量将被打印为警告。

Transfer Learning

迁移学习是很简单的。如果你想加载一些模型,只需使用相同的变量名称即可。如果你想重新训练一些层,只需重新命名相关层即可。

Summary and Logging

在训练期间,除迭代以外的所有内容均通过回调执行。本教程将解释如何在回调中处理汇总和日志记录,以及如何定制它们。默认的日志记录行为应该足够用于正常的用例,所以你可以跳过本教程。

TensorFlow Summaries

这是TensorFlow如何最终被记录/保存/打印的总结:

  • What to Log:当在构造图代码中调用tf.summary.xxx时,TensorFlow会将一个操作添加到tf.GraphKeys.SUMMARIES集合(默认情况下)。
  • When to LogMergeAllSummaries回调位于默认回调中。它每个周期(默认情况下)在SUMMARIES集合(默认情况下)运行,并将结果写入监视器。
  • Where to Log:默认情况下启用多个监视器。
    • TFEventWriter通过tensorboard将东西写入所使用的事件中。
    • ScalarPrinter打印终端中的所有标量。
    • JSONWriter将标量保存到JSON文件。

所有的 “what, when, where”可以在图或者回调(callbacks)/监视器(monitors)设置中自定义。
由于TF summaries在默认情况下不会频繁被评估(每个epoch),如果内容是依赖于数据的,那么这些值可能具有很高的方差。 为了解决这个问题,你可以:

  • 更改“What to Log”:更频繁地记录日志,但请注意,记录某些summaries可能很昂贵。可能需要使用单独的集合进行频繁日志记录。
  • 更改“When to Log”:可以在标量张量上调用tfutils.summary.add_moving_summary,它将总结这些标量的移动平均值,而不是它们的即时值。移动平均由MovingAverageSummary回调(默认启用)维护。

Other Logging Data

除了TensorFlow summaries外,回调还可以在训练开始后的任何时候通过self.trainer.monitors.put_xxx将其他数据写入监视器后端。只要支持数据类型,数据将被分派到并记录到同一个地方。
因此,tensorboard不仅会显示图中的信息,还会显示我们自定义的数据。例如,验证集的准确度通常需要在TensorFlow图之外计算。通过一个统一的监视器后端,这个数字也会显示在tensorboard上。

Inference

Inference During Training

训练期间有两种方法可以进行推理。

  • 最简单的方法是编写回调函数,并使用self.trainer.get_predictor()在推理模式下获取可调用的函数。
  • 如果推理过程中遵循以下范例:“每个输入获取一些张量,并汇总结果”。可以使用InferenceRunner接口和一些Inferencer。这将进一步支持预取和数据并行推断。

在这两种方法中,tower函数都会被再次调用,使用TowerContext.is_training == False构建不同的图。

Inference After Training

Tensorpack不关心训练后的操作。它将模型保存为标准检查点格式,以及metagraph protobuf文件。它们足以用于TensorFlow进行任何部署方法。但是您需要阅读TF文档并自行完成。

FAQs

-------------本文结束 感谢您的阅读-------------
0%