机器学习中的数据加载

1 tensorflow中数据加载的通常方式

1.1 feed_dict方式

这种方式是网络上介绍得最多的方式,也是目前很多厂商技术人员经常使用的方式。它先使用tf.placeholder定位数据占位符,在Graph中读取数据,数据直接嵌入到Graph中,在会话中运行该Graph,使用feed_dict方式喂数据。这种方式在数据量较小,或预处理数据不那么复杂时时可以接受的,当数据量较大,或者预处理数据较为复杂耗时时,Graph的传输就会遇到效率低下的问题。因此这种方式并不推荐。

1.2 队列

与feed_dict的单线程相比,队列的实现方式利用了多线程思想,减少了GPU等待数据的时间,使用了两个线程(文件名队列和内存队列)分别执行数据的读入和数据计算。文件名队列源源不断的将硬盘中的数据读取内存,内存队列负责给GPU传输数据,所需数据直接从内存队列中获取。两个线程互不干扰,同时运行。
但这种方式对于不同类型的数据处理方式或提供的API都不一样,如二进制文件一种API,图像数据一种API,文本数据一种API,并且需要做线程管理,稍显麻烦。

1.3 tf.data.Dataset方式

tensorflow官方推荐使用次方式,它使用多线程/进程和数据预加载的实现方式,让GPU不再为等待数据而空闲。

2 为什么要解决数据加载问题

上一章已经交代了目前大多数的任务都使用的时feed_dict方式,而这种方式是最慢的,最浪费计算资源的一种方式,下图就是使用feed_dict方式时的CPU和GPU之间的协作状态。
在这里插入图片描述
众所周知GPU极擅长矩阵运算,就上图而言,其实GPU处理的时间,也就是training的时间,非常短,大部分的时间都花在了CPU的预处理/加载批数据上面了,这就直接导致CPU利用率超高,GPU利用率极低,造成了GPU资源浪费。
tf.data方式利用多线程/进程和预加载技术可以完美解决这一问题,下图为使用tf.data方式的CPU和GPU之间的协作状态图。
在这里插入图片描述
如此,让GPU只用等待一次数据加载的时间,往后GPU和CPU同步处理,互不干扰。这时候CPU利用率也是超高,但GPU利用率通常会提升数倍到数十倍。在我们的实际使用中,发现相比使用feed_dict方式,GPU利用率始终在个位数徘徊,使用了tf.data方式后GPU使用率在整个训练过程中都维持在70%-85%之间。

3 tf.data.Dataset的使用

使用tf.data方式大致有以下步骤:

  • 创建tf.data.Dataset对象
  • tf.data.Dataset对象配置
  • 迭代器创建
  • 会话

3.1 创建Dataset对象

tensorflow提供了多种创建Dataset对象的方式,可以通过numpy数组、文本数据、CSV文件、tensor等创建。

3.1.1 from_tensor_slices

接受单个或多个numpy数组或tensor对象

import numpy as np

# 数据准备
data = np.arange(10)
# 创建
dataset = tf.data.Dataset.from_tensor_slices(data)

3.1.2 from_tensors

同样接受单个或多个numpy数组或tensor对象

data = tf.arange(10)
dataset = tf.data.Dataset.from_tensors(data)

3.1.3 from_generator

从generator创建

def generator():
    for i in range(10):
        yield 2*i
    
dataset = tf.data.Dataset.from_generator(generator, (tf.int32))

3.2 Dataset对象配置

3.2.1 Batches

在之前为了避免将数据全部读入造成内存溢出,我们一般给模型喂数据时都是一个batch一个batch的喂,这里也不例外。

data = np.arange(10,40)

dataset = tf.data.Dataset.from_tensor_slices(data)
# 配置batch为10
dataset = dataset.batch(10)

3.2.2 Zip

和python的内置函数zip一样,这里的作用是粘合两个dataset对象。适用于标签和数据分离的场景。

datax = np.arange(10,20)
datay = np.arange(11,21)

datasetx = tf.data.Dataset.from_tensor_slices(datax)
datasety = tf.data.Dataset.from_tensor_slices(datay)

# 粘合
dcombined = tf.data.Dataset.zip((datasetx, datasety))

3.2.3 Repeat

重复使用数据

dataset = tf.data.Dataset.from_tensor_slices(tf.range(10))
dataset = dataset.repeat(count=2)

3.2.4 Map

同样的,对比python的内置函数map,它是对Dataset对象中的每一个元素进行操作。一般是在将数据喂入模型之前使用。

def map_fnc(x):
    return x*2;
data = np.arange(10)
dataset = tf.data.Dataset.from_tensor_slices(data)
dataset = dataset.map(map_fnc)

3.3 创建迭代器

创建迭代器的目的就是为了使用我们的数据,可以想象Dataset就是一个水库,数据就是水库中的水,迭代器就是一根管子,通过管子数据才能被不断取出。迭代器拥有get_next方法,可以在计算图中创建一个OP节点,在会话中运行后就会返回实际的数据。当Dataset被读完后,自动抛出tf.errors.OutOfRangeError异常。Tensorflow提供多种创建迭代器的方式(对比多种规格的水管),下面一一介绍。

3.3.1 One-shot迭代器

单次迭代器,就像是一次性水管,不需要任何初始化。

data = np.arange(10,15)
# 创建dataset
dataset = tf.data.Dataset.from_tensor_slices(data)
# 创建迭代器
iterator = dataset.make_one_shot_iterator()

3.3.2 Initializable迭代器

可初始化的迭代器,使用该迭代器必须在会话中对其初始化,可以通过palceholder占位符向它传入初始化数据。

# 定义两个表示最小值和最大值的占位符
min_val = tf.placeholder(tf.int32, shape=[])
max_val = tf.placeholder(tf.int32, shape=[])

data = tf.range(min_val, max_val)
dataset = tf.data.Dataset.from_tensor_slices(data)

iterator = dataset.make_initializable_iterator()
next_ele = iterator.get_next()

# 创建会话
with tf.Session() as sess:
  # 初始化迭代器,初始化数据为{min_val:10, max_val:15}
  sess.run(iterator.initializer, feed_dict={min_val:10, max_val:15})
  try:
    while True:
      val = sess.run(next_ele)
      print(val)
  except tf.errors.OutOfRangeError:
    pass
      
  # 初始化迭代器,初始化数据为{min_val:1, max_val:10}
  sess.run(iterator.initializer, feed_dict={min_val:1, max_val:10})
  try:
    while True:
      val = sess.run(next_ele)
      print(val)
  except tf.errors.OutOfRangeError:
    pass

3.3.3 Reinitializable迭代器

可重复初始化的迭代器,即可看作可以从不同水库抽水的管子。这种迭代器可以从不同的Dataset对象创建并初始化(注意:Dataset要有相同结构),比如训练过程中的训练集和验证集。

def map_fnc(ele):
  return ele*2

# 定义两个表示最小值和最大值的占位符
min_val = tf.placeholder(tf.int32, shape=[])
max_val = tf.placeholder(tf.int32, shape=[])
data = tf.range(min_val, max_val)

# 训练集和验证集的Dataset对象创建
train_dataset =  tf.data.Dataset.from_tensor_slices(data)
val_dataset = tf.data.Dataset.from_tensor_slices(data).map(map_fnc)
# 迭代器创建
iterator=tf.data.Iterator.from_structure(train_dataset.output_types,train_dataset.output_shapes)

# 准备初始化迭代器
train_initializer = iterator.make_initializer(train_dataset)
val_initializer = iterator.make_initializer(val_dataset)

next_ele = iterator.get_next()

# 创建会话
with tf.Session() as sess:
  
  # 初始化迭代器
  sess.run(train_initializer, feed_dict={min_val:10, max_val:15})
  try:
    while True:
      val = sess.run(next_ele)
      print(val)
  except tf.errors.OutOfRangeError:
    pass
      
  # 再初始化迭代器, 注意initializer是不一样的,这里连其他水库去了
  sess.run(val_initializer, feed_dict={min_val:1, max_val:10})
  try:
    while True:
      val = sess.run(next_ele)
      print(val)
  except tf.errors.OutOfRangeError:
    pass

3.3.4 Feedable迭代器

tensorflow中最美妙的就是feeding机制了,它决定了一些东西可以事先在计算图中定义好,在会话运行时动态填充,当然这就包括了迭代器。其思想就是不同的Dataset对象用不同的迭代器,可以通过feeding机制动态的来决定。
通常无论是在机器学习还是深度学习当中,训练集、验证集、测试集是大家绕不开的话题,但偏偏它们要分离开来,偏偏它们的数据类型又一致,因此经常我们要写同样的重复的代码,而这一机制就是解决重复代码的问题,同时又将这些数据集操作分离得很清晰。就像不同的水库使用不同规格的水管抽水,不再使用同一根了。在具体实现上与reinitilizable iterator 类似,并且在切换数据集的时候不需要在开始的时候初始化iterator。

def map_fnc(ele):
  return ele*2

# 定义两个表示最小值和最大值的占位符
min_val = tf.placeholder(tf.int32, shape=[])
max_val = tf.placeholder(tf.int32, shape=[])
data = tf.range(min_val, max_val)

handle = tf.placeholder(tf.string, shape=[])

# Dataset对象创建
train_dataset = tf.data.Dataset.from_tensor_slices(data)
val_dataset = tf.data.Dataset.from_tensor_slices(data).map(map_fnc)
test_dataset = tf.data.Dataset.from_tensor_slices(tf.range(10,15))

# 迭代器创建,训练集和验证集具有相同的结构
train_val_iterator = tf.data.Iterator.from_structure(train_dataset.output_types , train_dataset.output_shapes)
# 测试集一般没有标签,因此直接使用一次性的就可以
test_iterator = test_dataset.make_one_shot_iterator()

# 定义feedable迭代器,达到数据集切换的目的
iterator = tf.data.Iterator.from_string_handle(handle, train_dataset.output_types, train_dataset.output_shapes)

# 准备初始化迭代器
train_initializer = train_val_iterator.make_initializer(train_dataset)
val_initializer = train_val_iterator.make_initializer(val_dataset)

next_ele = iterator.get_next()

with tf.Session() as sess:
    # 上面定义了iterator, 这里要让程序自己决定在运行中使用哪一个Dataset
    train_val_handle = sess.run(train_val_iterator.string_handle())
    test_handle = sess.run(test_iterator.string_handle())

    # 初始化训练的迭代器
    sess.run(train_initializer, feed_dict={min_val:10, max_val:15})
    try:
        while True:
            # runnext_ele, 喂的是handle,有些不一样
            val = sess.run(next_ele, feed_dict={handle:train_val_handle})
            print(val)
    except tf.errors.OutOfRangeError:
        pass

    # 初始化验证的迭代器
    sess.run(val_initializer, feed_dict={min_val:1, max_val:10})
    try:
        while True:
            # runnext_ele, 喂的是handle,有些不一样
            val = sess.run(next_ele, feed_dict={handle:train_val_handle})
            print(val)
    except tf.errors.OutOfRangeError:
        pass

    # 测试,一次性的迭代器不需要初始化
    try:
        while True:
            # runnext_ele, 喂的是handle,有些不一样
            val = sess.run(next_ele, feed_dict={handle:test_handle})
            print(val)
    except tf.errors.OutOfRangeError:
        pass

4 示例

在这里我们定义一个模型,以LeNet-5模型为例,这里仅仅是一个示例。

from tensorflow.examples.tutorials.mnist import input_data

# MNIST数据集包含了60000张训练图像和10000张测试图像,每一张图像大小为28×28,为单通道图像
mnist = input_data.read_data_sets("MNIST_data/", reshape=False, one_hot = True)
X_train, y_train = mnist.train.images, mnist.train.labels
X_val, y_val = mnist.validation.images, mnist.validation.labels
X_test, y_test = mnist.test.images, mnist.test.labels
X_train = np.pad(X_train, ((0,0), (2,2), (2,2), (0,0)), 'constant')
X_val =   np.pad(X_val, ((0,0), (2,2), (2,2), (0,0)), 'constant')
X_test =  np.pad(X_test, ((0,0), (2,2), (2,2), (0,0)), 'constant')

def forward_pass(X):
    W1 = tf.get_variable("W1", [5,5,1,6], initializer = tf.contrib.layers.xavier_initializer(seed=0))
    # for conv layer2
    W2 = tf.get_variable("W2", [5,5,6,16], initializer = tf.contrib.layers.xavier_initializer(seed=0))
    Z1 = tf.nn.conv2d(X, W1, strides = [1,1,1,1], padding='VALID')
    A1 = tf.nn.relu(Z1)
    P1 = tf.nn.max_pool(A1, ksize = [1,2,2,1], strides = [1,2,2,1], padding='VALID')
    Z2 = tf.nn.conv2d(P1, W2, strides = [1,1,1,1], padding='VALID')
    A2= tf.nn.relu(Z2)
    P2= tf.nn.max_pool(A2, ksize = [1,2,2,1], strides=[1,2,2,1], padding='VALID')
    P2 = tf.contrib.layers.flatten(P2)
   
    Z3 = tf.contrib.layers.fully_connected(P2, 120)
    Z4 = tf.contrib.layers.fully_connected(Z3, 84)
    Z5 = tf.contrib.layers.fully_connected(Z4,10, activation_fn= None)
    return Z5

def model(X,Y):
    
    logits = forward_pass(X)
    cost = tf.reduce_mean( tf.nn.softmax_cross_entropy_with_logits_v2(logits=logits, labels=Y))
    optimizer = tf.train.AdamOptimizer(learning_rate=0.0009)
    learner = optimizer.minimize(cost)
    correct_predictions = tf.equal(tf.argmax(logits,1),   tf.argmax(Y,1))
    accuracy = tf.reduce_mean(tf.cast(correct_predictions, tf.float32))
    
    return (learner, accuracy)

上面的代码已经将模型搭建好了,结合之前对tf.data和迭代器的介绍,我们采用feedable迭代器消费数据。

epochs = 10 
batch_size = 64

tf.reset_default_graph()

# 定义数据 将图像resize成32×32
X_data = tf.placeholder(tf.float32, [None, 32,32,1])
Y_data = tf.placeholder(tf.float32, [None, 10])

# 创建Dataset对象,并定义batch size
train_dataset = tf.data.Dataset.from_tensor_slices((X_data, Y_data)).batch(batch_size)
val_dataset =  tf.data.Dataset.from_tensor_slices((X_data, Y_data)).batch(batch_size)
test_dataset =  tf.data.Dataset.from_tensor_slices((X_test, y_test.astype(np.float32)).batch(batch_size)

# 定义handle, 让会话运行后程序自动判断使用哪一个Dataset
handle = tf.placeholder(tf.string, shape=[])
iterator = tf.data.Iterator.from_string_handle(handle, train_dataset.output_types, train_dataset.output_shapes)

X_batch , Y_batch = iterator.get_next()
                                                   
# 定义模型, 返回的是优化器和精度
(learner, accuracy) = model(X_batch, Y_batch)
                                                   
# 创建迭代器, 训练集和验证集拥有相同的数据结构
train_val_iterator = tf.data.Iterator.from_structure(train_dataset.output_types, train_dataset.output_shapes)
train_iterator = train_val_iterator.make_initializer(train_dataset)

# 准备初始化,虽然切换数据时不需要初始化,但还是得初始化训练集、验证集的迭代器,以及在会话中决定他们如何切换
val_iterator = train_val_iterator.make_initializer(val_dataset)
test_iterator = test_dataset.make_one_shot_iterator()
                                                   
# 创建会话
with tf.Session() as sess:
    # 需要初始化全局参数,这是必须的
    sess.run(tf.global_variables_initializer())
    # 程序自己决定在运行中使用哪一个Dataset
    train_val_string_handle = sess.run(train_val_iterator.string_handle())
    test_string_handle = sess.run(test_iterator.string_handle())

    for epoch in range(epochs):
        # 模型训练
        # 初始化Reinitializable迭代器
        sess.run(train_iterator, feed_dict={X_data:X_train, Y_data:y_train})
        total_train_accuracy = 0
        no_train_examples = len(y_train)
        try:
            while True:
                temp_train_accuracy, _ = sess.run([accuracy, learner], feed_dict={handle:train_val_string_handle})
                total_train_accuracy += temp_train_accuracy*batch_size
        except tf.errors.OutOfRangeError:
            pass

        # 模型验证
        # 初始化Reinitializable迭代器
        sess.run(val_iterator, feed_dict={X_data:X_val, Y_data:y_val})
        total_val_accuracy = 0
        no_val_examples = len(y_val)
        try:
            while True:
                temp_val_accuracy, _ = sess.run([accuracy, learner], feed_dict={handle:train_val_string_handle})
                total_val_accuracy += temp_val_accuracy*batch_size
        except tf.errors.OutOfRangeError:
            pass

        print('Epoch %d' % (epoch+1))
        print("---------------------------")
        print('Training accuracy is {}'.format(total_train_accuracy/no_train_examples))
        print('Validation accuracy is {}'.format(total_val_accuracy/no_val_examples))


    print("Testing the model --------")

    total_test_accuracy = 0
    no_test_examples = len(y_test)
    try:
        while True:
            temp_test_accuracy, _ = sess.run([accuracy, learner], feed_dict={handle:test_string_handle})
            total_test_accuracy += temp_test_accuracy*batch_size
    except tf.errors.OutOfRangeError:
        pass

    print('Testing accuracy is {}'.format(total_test_accuracy/no_test_examples))

5 名词解释

5.1 计算图

计算图是tensorflow中的表达指令依赖关系的一种方式,它是由数据(tensor)和操作(OP节点)组成的。

5.2 Tensor

中文名叫张量是tensorflow中重要的数据形式。可以对比数组的定义,常量对比tensorflow中就称为纯量,二阶数组对比tensorflow就称为二阶张量,依次类推。

5.3 会话

tensorflow的底层是由C++开发的,提供python接口供上层开发使用,可以理解其目的就是为python和C++搭建资源沟通的桥梁。实际上会话之前定义的一些参数都是在即一个计算图中定义的,只有在会话中使用run或eval函数才能真正让这些数据流动起来,让程序运行起来,它是程序获得计算资源的地方,因此使用后一定记得删除会话。

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

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

yougwypf1991

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

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

余额充值