tensorflow有两种数据输入方法,比较简单的一种是使用feed_dict,这种方法在画graph的时候使用placeholder来站位,在真正run的时候通过feed字典把真实的输入传进去。比较简单不再介绍。
比较恼火的是第二种方法,直接从文件中读取数据(其实第一种也可以我们自己从文件中读出来之后使用feed_dict传进去,但方法二tf提供很完善的一套类和函数形成一个类似pipeline一样的读取线):
1.使用tf.train.string_input_producer函数把我们需要的全部文件打包为一个tf内部的queue类型,之后tf开文件就从这个queue中取目录了,要注意一点的是这个函数的shuffle参数默认是True,也就是你传给他文件顺序是1234,但是到时候读就不一定了,我一开始每次跑训练第一次迭代的样本都不一样,还纳闷了好久,就是这个原因。
files_in = ["./data/data_batch%d.bin" % i for i in range(1, 6)]
files = tf.train.string_input_producer(files_in)
2.搞一个reader,不同reader对应不同的文件结构,比如度bin文件tf.FixedLengthRecordReader就比较好,因为每次读等长的一段数据。如果要读什么别的结构也有相应的reader。
reader = tf.FixedLengthRecordReader(record_bytes=1+32*32*3)
3.用reader的read方法,这个方法需要一个IO类型的参数,就是我们上边string_input_producer输出的那个queue了,reader从这个queue中取一个文件目录,然后打开它经行一次读取,reader的返回是一个tensor(这一点很重要,我们现在写的这些读取代码并不是真的在读数据,还是在画graph,和定义神经网络是一样的,这时候的操作在run之前都不会执行,这个返回的tensor也没有值,他仅仅代表graph中的一个结点)。
key, value = reader.read(files)
4.对这个tensor做些数据与处理,比如CIFAR1-10中label和image数据是糅在一起的,这里用slice把他们切开,切成两个tensor(注意这个两个tensor是对应的,一个image对一个label,对叉了后便训练就完了),然后对image的tensor做data augmentation。
data = tf.decode_raw(value, tf.uint8)
label = tf.cast(tf.slice(data, [0], [1]), tf.int64)
raw_image = tf.reshape(tf.slice(data, [1], [32*32*3]), [3, 32, 32])
image = tf.cast(tf.transpose(raw_image, [1, 2, 0]), tf.float32)
lr_image = tf.image.random_flip_left_right(image)
br_image = tf.image.random_brightness(lr_image, max_delta=63)
rc_image = tf.image.random_contrast(br_image, lower=0.2, upper=1.8)
std_image = tf.image.per_image_standardization(rc_image)
5.这时候可以发现,这个tensor代表的是一个样本([高宽管道]),但是训练网络的时候的输入一般都是一推样本([样本数高宽*管道]),我们就要用tf.train.batch或者tf.train.shuffle_batch这个函数把一个一个小样本的tensor打包成一个高一维度的样本batch,这些函数的输入是单个样本,输出就是4D的样本batch了,其内部原理似乎是创建了一个queue,然后不断调用你的单样本tensor获得样本,直到queue里边有足够的样本,然后一次返回一堆样本,组成样本batch。
images, labels = tf.train.batch([std_image, label],
batch_size=100,
num_threads=16,
capacity=int(50000* 0.4 + 3 * batch_size))
5.事实上一直到上一部的images这个tensor,都还没有真实的数据在里边,我们必须用Session run一下这个4D的tensor,才会真的有数据出来。这个原理就和我们定义好的神经网络run一下出结果一样,你一run这个4D tensor,他就会顺着自己的operator找自己依赖的其他tensor,一路最后找到最开始reader那里。
除了上边讲的原理,其中还要注意几点
1.tf.train.start_queue_runners(sess=sess)这一步一定要运行,且其位置要在定义好读取graph之后,在真正run之前,其作用是把queue里边的内容初始化,不跑这句一开始string_input_producer那里就没用,整个读取流水线都没用了。
training_images = tf.train.batch(XXXXXXXXXXXXXXX)
tf.train.start_queue_runners(sess=self.sess)
real_images = sess.run(training_images)
2.image和label一定要一起run,要记清楚我们的image和label是在一张graph里边的,跑一次那个graph,这两个tensor都会出结果,且同一次跑出来的image和label才是对应的,如果你run两次,第一次为了拿image第二次为了拿label,那整个就叉了,因为第一次跑出来第0到100号image和0到100号label,第二次跑出来第100到200的image和第100到200的label,你拿到了0~100的image和100~200的label,整个样本分类全不对,最后网络肯定跑不出结果。
training_images, training_labels = read_image()
tf.train.start_queue_runners(sess=self.sess)
real_images = sess.run(training_images) # 读出来是真的图片,但是和label对不上
real_labels = sess.run(training_labels) # 读出来是真的label,但是和image对不上
# 正确调用方法,通过跑一次graph,将成套的label和image读出来
real_images, real_labels = sess.run([training_images, training_labels])