机器学习笔记(十九):TensorFlow实战十一(多线程输入数据)

1 - 引言

为了加速模型训练的时间,TensorFlow提供了一套多线程处理输入数据的框架。

在这里插入图片描述
下面我们来详细的介绍如何使用多线程来加速我们的模型训练速度

2 - 队列与多线程

在TensorFlow中,队列和变量类似,我们可以修改它们的状态。下面给出一个示例来展示如何在TensorFlow中操作一个队列

import tensorflow as tf

#创建一个队列,指定队列长度为2,类型为Int32
q = tf.FIFOQueue(2,"int32")
#使用enqueue_many函数初始化队列中的元素。和变量初始化类似。
init = q.enqueue_many(([0,10],))
#使用Dequeue函数将队列中的第一个元素出队列
x  = q.dequeue()
y= x+1
#将Y重新加入队列
q_inc = q.enqueue([y])

with tf.Session() as sess:
    #运行初始化队列的操作
    init.run()
    for _ in range(5):
        v,_ = sess.run([x,q_inc])
        print(v)

0
10
1
11
2
TensorFlow提供了FIFOQueue和RandomShuffleQueue两种队列

  • FIFOQueue
    先进先出队列
  • RandomShuffleQueue
    从所有元素中随机一个出队
    因为在训练神经网络时希望每次使用的训练数据尽量随机。

在TensorFlow中,多个线程可以同时向一个队列中写元素,或者同时读取一个队列中的元素,主要使用

  • tf.Coordinator
  • tf.QueueRunner
    这两个类来完成多线程协同的功能
    下面给出一个示例来展示tf.Coordinator的用法:
import tensorflow as tf
import numpy as np
import threading
import time

#线程中运行的程序
def MyLoop(coord,worker_id):
    #使用tf.Coordinator类提供工具判断是否需要停止
    while not coord.should_stop():
        #随机停止所有的线程
        if np.random.rand() < 0.1:
            print("Stoping from id:%d\n" % worker_id)
            #调用coord.request_stop()函数来通知其他线程停止
            coord.request_stop()
        else:
            #打印当前线程的id
            print("working on id: %d\n" % worker_id)
        time.sleep(1)
#声明一个tf.train.Coordinator类来协同多个线程
coord = tf.train.Coordinator()
#声明创建5个线程
threads = [threading.Thread(target=MyLoop,args=(coord,i,)) for i in range(5)]
#启动所有的线程
for t in threads: t.start()
#等待所有线程退出
coord.join(threads)

working on id: 0

working on id: 1

working on id: 2

working on id: 3

working on id: 4

working on id: 0

Stoping from id:3

working on id: 4

tf.QueueRunner主要用于启动多个线程来操作同一个队列,启动的这些线程可以通过上面介绍的tf.Coordinator类来同一管理。以下代码示例展示了如何管理多线程队列操作

import tensorflow as tf
import numpy as np
import threading
import time

#声明一个先进先出的队列,最多100个元素,元素为实数
queue = tf.FIFOQueue(100,"float")
#定义队列的入队操作
enqueue_op = queue.enqueue([tf.random_normal([1])])

#使用tf.train.QueueRunner来创建多个线程运行队列的入队操作
#tf.train.QueueRunner的第一个参数给出了被操作的队列,[enqueue_op]*5
#表示了需要启动5个线程,每个线程中运行的是enqueue_op操作。
qr =  tf.train.QueueRunner(queue,[enqueue_op]*5)

#将定义过的QueueRunner加入TensorFlow计算图上指定的集合
tf.train.add_queue_runner(qr)
#定义出队操作
out_tensor = queue.dequeue()

with tf.Session() as sess:
    #使用tf.train.Coordinator来协同启动线程
    coord = tf.train.Coordinator()
    threads = tf.train.start_queue_runners(sess=sess,coord=coord)
    #获取队列中的值
    for _ in range(3):print(sess.run(out_tensor)[0])
    coord.request_stop()
    coord.join(threads)

0.19481687
-0.41217336
-0.38111967

3 - 输入文件队列

当输入数据较大时,可以将数据分成多个TFRecord文件来提高处理效率。文件在加入队列之前会被打乱顺序,所以出队的顺序也是随机的。随机打乱文件顺序以及加入输入队列的过程会跑在一个单独的线程上,这样可以加快获取文件的速度

首先我们先创建2个TFRecord文件

import tensorflow as tf

# 创建TFRecord文件的帮助函数
def _int64_feature(value):
    return tf.train.Feature(int64_list=tf.train.Int64List(value=[value]))

# 模拟海量数据情况下将数据写入不同的文件。num_shards定义了总共写入多少个文件
# instances_per_shard定义了每个文件有多少个数据
num_sards = 2
instances_per_shard = 2
for i in range(num_sards):
    # 将数据分为多个文件时,可以将不同文件以类似0000n-of-0000m的后缀区分。其中m表示
    # 数据总共被存在了多少个文件,n表示当前文件的编号。式样的方式既方便了通过正则表达式
    # 获取文件列表,又在文件名中加入更多的信息。
    filename = ('/path/to/data.tfrecords-%.5d-of-%.5d' % (i, num_sards))
    writer = tf.python_io.TFRecordWriter(filename)
    # 将数据封装成Example结构并写入TFRecord文件
    for j in range(instances_per_shard):
        # Example结构仅包含当前样例属于第几个文件以及是当前文件的第几个样本
        example = tf.train.Example(features=tf.train.Features(feature={
            'i':_int64_feature(i),
            'j':_int64_feature(j)
        }))
        writer.write(example.SerializeToString())
    writer.close()

如下图所示创建了2个文件
在这里插入图片描述

文件队列的生成主要使用两个函数

  • tf.train.match_filenames_once():获取符合正则表达式的文件列表
  • tf.train.string_input_producer():用文件列表创建一个输入队列
    通过设置 shuffle 参数为 True,string_input_producer 会将文件的入队顺序打乱,所以出队顺序是随机的。随机打乱文件顺序和入队操作会跑在一个单独的线程上,不会影响出队的速度

当输入队列中的所有文件都处理完后,它会将文件列表中的文件重新加入队列。可以通过设置 num_epochs 参数来限制加载初始文件列表的最大轮数

import tensorflow as tf

# 获取文件列表
files = tf.train.match_filenames_once('/path/to/data.tfrecords-*')

# 创建文件输入队列
filename_queue = tf.train.string_input_producer(files, shuffle=False)

# 读取并解析Example
reader = tf.TFRecordReader()
_, serialized_example = reader.read(filename_queue)
features = tf.parse_single_example(
    serialized_example,
    features={
        'i': tf.FixedLenFeature([], tf.int64),
        'j': tf.FixedLenFeature([], tf.int64)
    })

with tf.Session() as sess:
    # 使用match_filenames_once需要用local_variables_initializer初始化一些变量
    sess.run(
        [tf.global_variables_initializer(),
         tf.local_variables_initializer()])

    # 打印文件名
    print(sess.run(files))

    # 用Coordinator协同线程,并启动线程
    coord = tf.train.Coordinator()
    threads = tf.train.start_queue_runners(coord=coord)

    # 获取数据
    for i in range(6):
        print(sess.run([features['i'], features['j']]))
    coord.request_stop()
    coord.join(threads)

[0, 0]
[0, 1]
[1, 0]
[1, 1]
[0, 0]
[0, 1]
在不打乱文件列表的情况下,会依次读出样例数据中的每一个样例。

4 - 组合训练数据(batching)

将多个输入样例组成一个batch可以提高模型训练的效率。所以在得到单个样例的预处理结果之后,还需要将它们组成batch,然后再提供神经网络的输入层TensorFlow提供了tf.train.batch和tf.train.shuffle_batch函数来将单个的样例组织成batch的形式输出

import tensorflow as tf

# 获取文件列表
files = tf.train.match_filenames_once('/path/to/data.tfrecords-*')

# 创建文件输入队列
filename_queue = tf.train.string_input_producer(files, shuffle=False)

# 读取并解析Example
reader = tf.TFRecordReader()
_, serialized_example = reader.read(filename_queue)
features = tf.parse_single_example(
    serialized_example,
    features={
        'i': tf.FixedLenFeature([], tf.int64),
        'j': tf.FixedLenFeature([], tf.int64)
    })

# i代表特征向量,j代表标签
example, label = features['i'], features['j']

# 一个batch中的样例数
batch_size = 3

# 文件队列中最多可以存储的样例个数
capacity = 1000 + 3 * batch_size

# 组合样例
example_batch, label_batch = tf.train.batch(
    [example, label], batch_size=batch_size, capacity=capacity)

with tf.Session() as sess:
    # 使用match_filenames_once需要用local_variables_initializer初始化一些变量
    sess.run(
        [tf.global_variables_initializer(),
         tf.local_variables_initializer()])

    # 用Coordinator协同线程,并启动线程
    coord = tf.train.Coordinator()
    threads = tf.train.start_queue_runners(coord=coord)

    # 获取并打印组合之后的样例。真实问题中一般作为神经网路的输入
    for i in range(2):
        cur_example_batch, cur_label_batch = sess.run(
            [example_batch, label_batch])
        print(cur_example_batch, cur_label_batch)

    coord.request_stop()
    coord.join(threads)

[0 0 1] [0 1 0]
[1 0 0] [1 0 1]
可以看到单个的数据被组织成 3 个一组的 batch

以下是使用 tf.train.shuffle_batch() 的方法,min_after_dequeue 参数限制了出队时队列中元素的最少个数,当队列元素个数太少时,随机的意义就不大了

# 和tf.train.batch的样例代码一样产生example和label
example, label = features['i'], features['j']

# 使用tf.train,shuffle_batch函数来组合样例。tf.train.shuffle_batch函数
# 的参数大部分都和tf.train.batch函数相似,但是min_after_dequeue参数是
# tf.train.shuffle_batch函数特有的。min_after_dequeue参数限制了出队时队列中
# 元素的最少个数。当队列中元素太少时,随机打乱样侧顺序的作用就不大。所以
# tf.train.shuffle_batch函数提供了限制出队时最少元素的个数来保证随机打乱顺序的
# 作用。当出队函数被调用但是出队中元素不够时,出队操作将等待更多的元素入队才会完成
# 如果min_after_dequeue参数被设定,capacity也应该相应调整来满足性能需求
example_batch, label_batch = tf.train.shuffle_batch(
    [example, label], batch_size=batch_size,
    capacity=capacity, min_after_dequeue=30
)

5 - 完整的输入训练框架

以上,已经介绍完了利用队列 多线程从多个 TFRecord 文件中组织成训练数据 batch 的方法。这里给出一个完整的处理和训练框架

import tensorflow as tf

files = tf.train.match_filenames_once("/path/to/file_pattern-*")
filename_queue = tf.train.string_input_producer(files, shuffle=False)

reader = tf.TFRecordReader()
_, serialized_example = reader.read(filename_queue)
features = tf.parse_single_example(
    serialized_example,
    features={'image': tf.FixedLenFeature([], tf.string),
              'label': tf.FixedLenFeature([], tf.int64),
              'height': tf.FixedLenFeature([], tf.int64),
              'width': tf.FixedLenFeature([], ft.int64),
              'channels': tf.FixedLenFeature([], tf.int64),
              })

image, label = features['image'], features['label']
height, width = features['height'], features['width']
channels = features['channels']

# 从原始图像中解析出像素矩阵,并根据图片尺寸还原图像
decoded_image = tf.decode_raw(image, tf.uint8)
decoded_image.set_shape([height, width, channels])

# 定义神经网络输入图片大小
image_size = 299
# 假设 preprocess_for_train 为图片随机处理函数,具体参考第 11 节
''' 由于这里采用了多线程输入框架,所以图像处理过程不会造成训练的瓶颈 '''
distorted_image = preprocess_for_train(decoded_image, image_size, image_size, None)

# 通过 tf.train.shuffle_batch 组织成 batch 训练数据
min_after_dequeue = 10000
batch_size = 100
capacity = min_after_dequeue + 3 * batch_size
image_batch, label_batch = tf.train.shuffle_batch(
    [distorted_image, label], batch_size=batch_size,
    capacity=capacity, min_after_dequeue=min_after_dequeue)

# 定义神经网络传输和优化过程
logit = inference(image_batch)
loss = calc_loss(logit, label_batch)
# 采用随机梯度下降算法优化损失函数
train_step = tf.train.GradientDescentOptimizer(learning_rate).minimize(loss)

with tf.Session() as sess:
    # 初始化变量
    tf.initialize_all_variables().run()
    coord = tf.train.Coordinator()
    threads = tf.train.start_queue_runners(sess=sess, coord=coord)

    # 训练神经网络过程
    for i in range(TRAINING_STEPS):
        sess.run(train_step)

    coord.request_stop()
    coord.join(threads)

完整的输入数据框架如下图所示:
在这里插入图片描述
通过这种方式,可以有效地提高数据预处理的效率,避免数据预处理成为神经网络模型训练过程中的性能瓶颈

  • 0
    点赞
  • 3
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值