《TensorFlow自然语言处理》—2.2 输入、变量、输出和操作
2.2 输入、变量、输出和操作
在了解底层架构之后,我们将介绍构成TensorFlow客户端的最常见元素。如果你阅读过网上数百万个TensorFlow客户端中的任何一个,那么它们(TensorFlow相关的代码)都属于下面这些类型之一:
输入:用来训练和测试算法的数据。
变量:可变的张量,大部分用于定义算法的参数。
输出:不可变的张量,用于存储中间和最终的输出。
操作:对输入做不同的变换以产生想要的输出。
在之前的sigmoid示例中,我们可以找到所有这些类别的实例,表2.1中列出这些元素:
表2.1 TensorFlow元素类型
下面更详细地解释每个TensorFlow元素。
2.2.1 在TensorFlow中定义输入
客户端主要以三种方式接收数据:
使用Python代码在算法的每个步骤中提供数据
将数据预加载并存储为TensorFlow张量
搭建输入管道
让我们来看看这些方式。
2.2.1.1 使用Python代码提供数据
在第一种方法中,可以使用传统的Python代码将数据馈送到TensorFlow客户端。在之前的示例中,x是这种方法的一个例子。为了从外部数据结构(例如,numpy.ndarray)向客户端提供数据,TensorFlow库提供了一种优雅的符号数据结构,称为占位符,它被定义为tf.placeholder(…)。顾名思义,占位符在图的构建阶段不需要实际数据,相反,仅在图执行过程中通过session.run(…,feed_dict = {placeholder:value})将外部数据以Python字典的形式传递给feed_dict参数,其中键是tf .placeholder变量,相应的值是实际数据(例如,numpy.ndarray)。占位符定义采用以下形式:
参数如下:
dtype:这是送入占位符的数据类型
shape:这是占位符的形状,以1维向量形式给出
name:这是占位符的名称,这对调试很重要
2.2.1.2 将数据预加载并存储为张量
第二种方法类似于第一种方法,但可以少担心一件事。由于数据已预先加载,因此我们无须在图的执行期间提供数据。为了明白这一点,让我们修改我们的sigmoid示例。请记住,我们之前将x定义为占位符:
现在,让我们将其定义为包含特定值的张量:
以下是完整的代码:
你会注意到,代码与我们原来的sigmoid示例有两个主要区别。首先,我们定义x的方式不同,我们现在直接指定一个特定的值并将x定义为张量,而不是使用占位符对象并在图执行时输入实际值。另外,正如你所看到的,我们没有在session.run(…)中提供任何额外的参数。但缺点是,现在你无法在session.run(…)中向x提供不同的值,并看到输出是如何改变的。
2.2.1.3 搭建输入管道
输入管道是专门为需要快速处理大量数据的更重型客户端设计的。实际上会创建一个保存数据的队列,直到我们需要它为止。TensorFlow还提供各种预处理步骤(例如,用于调整图像对比度/亮度,或进行标准化),这些步骤可在将数据送到算法之前执行。为了提高效率,可以让多个线程并行读取和处理数据。
一个典型的通道包含以下元素:
文件名列表
文件名队列,用于为输入(记录)读取器生成文件名
记录读取器,用于读取输入(记录)
解码器,用于解码读取记录(例如,JPEG图像解码)
预处理步骤(可选)
一个示例(即解码输入)队列
让我们使用TensorFlow编写一个输入管道的快速示例。在这个例子中,我们有三个CSV格式的文本文件(text1.txt、text2.txt和text3.txt),每个文件有五行,每行有10个用逗号分隔的数字(示例行:0.1, 0.2, 0.3, 0.4, 0.5, 0.6, 0.7, 0.8, 0.9, 1.0)。我们需要从文件一直到表示文件中那些输入的张量,搭建一个输入管道,来分批读取数据(多行数据向量),下面一步一步地介绍这个过程。
有关更多信息,请参阅官方TensorFlow页面上有关导入数据的内容:https://www.tensorf?low.org/programmers_guide/reading_data。
首先,像以前一样导入一些重要库:
接着,定义图和会话对象:
然后,定义一个文件名队列,这是一个包含文件名的队列数据结构。它将作为参数传递给读取器(很快将被定义)。队列将根据读取器的请求生成文件名,以便读取器可以用这些文件名访问文件以读取数据:
这里,capacity是给定时间队列持有的数据量,shuff?le告诉队列是否应该在吐出数据之前将其打乱。
TensorFlow有几种不同类型的读取器(https://www.tensorf?low.org/api_guides/python/io_ops#Readers提供了可用读取器列表)。由于我们有一些单独的文本文件,其中一行代表一个单独的数据点,因此TextLineReader最适合我们:
定义读取器后,我们可以使用read()函数从文件中读取数据,它的输出是键值对,其中,键标识文件和文件中正在读取的记录(即文本行),我们可以省略它,而值返回读取器读取的行的实际值:
接下来,我们定义record_defaults,如果发现任何错误记录,将输出它:
现在我们将读取到的文本行解码为数字列(因为我们有CSV文件),为此,我们使用decode_csv()方法。如果使用文本编辑器打开文件(例如,test1.txt),你会看到每一行有10列:
然后,我们把这些列拼接起来,形成单个张量(称为特征),这些张量被传给另一个方法tf.train.shuff?le_batch(),该方法的输入是前面定义的张量(特征),然后将张量进行打乱按批次输出:
batch_size参数是在给定的步骤中对数据采样的批次大小,capacity是数据队列的容量(大队列需要更多内存),min_after_dequeue表示出队后留在队列中的最小元素数量。最后,num_threads定义用于生成一批数据的线程数。如果管道中有大量的预处理,则可以增加此线程数。此外,如果需要在不打乱数据(使用tf.train.shuff?le_batch)的情况下读取数据,则可以使用tf.train.batch操作。然后,我们将通过调用以下代码启动此管道:
可以将类tf.train.Coordinator()视为线程管理器,它实现了各种管理线程的机制(例如,启动线程并在任务完成后让线程加入主线程)。我们需要tf.train.Coordinator()类,因为输入管道会产生许多线程来执行队列填充(即入队)、队列出队和许多其他任务。接下来,我们将使用之前创建的线程管理器执行tf.train.start_queue_runners(…)。QueueRunner()保存队列的入队操作,并在定义输入管道时自动创建它们。因此,要填充已定义的队列,我们??需要使用tf.train.start_queue_runners函数启动这些队列运行程序。
接下来,在我们感兴趣的任务完成之后,我们需要显式地停止线程,并让它们加入主线程,否则程序将无限期挂起,这是通过coord.request_stop()和coord.join(threads)来实现的。这种输入管道可以与我们的sigmoid示例相合,以便它直接从文件中读取数据,如下所示:
- 点赞
- 收藏
- 关注作者
评论(0)