【從caffe到Tensorflow 1】io 操作
最近項目要頻繁用到tensorflow,所以不得不認真研究下tensorflow而不是跟之前一樣遇到了就搞一下了。
首先我覺得所有這些框架裡面caffe是最清晰的,所以就算是學習tensorflow,我也會以caffe的思路去學習,這就是這個系列的用意。
今天是第1篇,咱們說io操作,也就是文件讀取,載入內存。
01
Caffe的io操作
caffe的io,是通過在prototxt中定義數據輸入,默認支持data,imagedata,hdf5data,window data等類型。Data layer,輸入是LMDB數據格式,image data 支持的是image list的數據格式。
對於LMDB來說,我們在caffe layer中配置準備好的二進位數據即可。
對於image data,我們準備一個data list,官方的image data是一個分類任務的list,格式為每行image,label,當然隨著任務的不同我們可以自定義。比如分割任務image,mask。檢測任務,image num of object, object rect1,object rect2等。
典型的格式是這樣:
具體的載入,就是在相關層的DataLayerSetUp函數中設置好輸入大小,load_batch函數中,讀取原始數據,再利用data_transform塞入內存。
當然caffe也可以自定義python層使用,不過我還是更習慣c++,何況這裡比較的也是官方自帶的layer。
從上面我們可以看出,caffe的io都是從文件中載入,只是文件的組織方式不同。
Tensorflow的io輸入則要複雜,全面很多,我們參考tensorflow1.5的API。
http://link.zhihu.com/?target=https%3A//
www.tensorflow.org/api_docs/python/tf/data
02
Tensorflow的io操作
Tensorflow不止是讀取文件這一種方法,它可以包含以下幾種方式。
import tensorflow as tf
# 設計Graph
x1 = tf.constant([2, 3, 4])
x2 = tf.constant([4, 0, 1])
y = tf.add(x1, x2)
with tf.Session() as sess:
print sess.run(y)
如上,x1,x2都是預載入好的數據。在設計Graph的時候,x1和x2就已經被定義成了兩個有值的列表,在計算y的時候直接取x1和x2的值。這種方法的問題是將數據直接內嵌到Graph中,再把Graph傳入Session中運行。當數據量比較大時,Graph的傳輸會遇到效率問題。
import tensorflow as tf
x1 = tf.placeholder(tf.int16)
x2 = tf.placeholder(tf.int16)
y = tf.add(x1, x2)
# 用Python產生數據
li1 = [2, 3, 4]
li2 = [4, 0, 1]
# 打開一個session --> 喂數據 --> 計算y
with tf.Session() as sess:
print sess.run(y, feed_dict=)
定義的時候,x1, x2隻是佔位符所以沒有具體的值,運行的時候使用sess.run()中的feed_dict參數,將Python產生的數據餵給後端,並計算y。
前兩種方法很方便,但是遇到大型數據的時候就會很吃力,即使是Feeding,中間環節的增加也是不小的開銷,比如數據類型轉換等等。而且,面對複雜類型的數據,也是處理不過來的。因此與caffe一樣,tensorflow也是支持從文件中讀取數據。
下面舉一個利用隊列讀取硬碟中的數據到內存的例子:假如需要讀取的數據存在一個list中。這篇博客舉了一個很好的例子;
http://honggang.io/2016/08/19/tensorflow-data-reading/
在上圖中,首先由一個單線程把文件名堆入隊列,兩個Reader同時從隊列中取文件名並讀取數據,Decoder將讀出的數據解碼後堆入樣本隊列。
利用了string_input_producer + tf.TextLineReader() + train.start_queue_runners來讀取數據,string_input_producer的定義在
https://github.com/tensorflow/tensorflow/blob/r1.5/tensorflow/python/training/input.py
string_input_producer(
string_tensor,
num_epochs=None,
shuffle=True,
seed=None,
capacity=32,
shared_name=None,
name=None,
cancel_op=None
)
從上面可見,可以指定num_epochs,是否shuffle等,這就是一個最簡單的從文件中讀取的例子了。
假設有文件A.csv如下:
Alpha1,A1
Alpha2,A2
Alpha3,A3
單個reader讀取單個數據腳本如下;
import tensorflow as tf
filenames = ["A.csv"] 必須要以數組的形式
filename_queue = tf.train.string_input_producer(filenames, shuffle=False)
reader = tf.TextLineReader()# 定義Reader
key, value = reader.read(filename_queue)
# 定義Decoder
example, label = tf.decode_csv(value, record_defaults=[["null"], ["null"]])
# 運行Graph
with tf.Session() as sess:
coord = tf.train.Coordinator() #創建一個協調器,管理線程
threads = tf.train.start_queue_runners(coord=coord) #啟動QueueRunner, 此時文件名隊列已經進隊。
for i in range(10):
print example.eval() #取樣本的時候,一個Reader先從文件名隊列中取出文件名,讀出數據,Decoder解析後進入樣本隊列。
coord.request_stop()
coord.join(threads)
講了上面的基礎例子之後,我們開始看更複雜的例子。
上面的例子包含兩類,一種是從placeholder讀內存中的數據,一種是使用queue讀硬碟中的數據,而1.3以後的Dataset API同時支持從內存和硬碟的讀取。
它們支持多種類型的輸入,分別是FixedLengthRecordDataset, TextLineDataset, TFRecordDataset類型的。
TextLineDataset:這個函數的輸入是一個文件的列表,輸出是一個dataset。dataset中的每一個元素就對應了文件中的一行。可以使用這個函數來讀入CSV文件,跟上面例子類似。
TFRecordDataset:這個函數是用來讀TFRecord文件的,dataset中的每一個元素就是一個TFExample,這是很常用的。
FixedLengthRecordDataset:這個函數的輸入是一個文件的列表和一個record_bytes,之後dataset的每一個元素就是文件中固定位元組數record_bytes的內容。通常用來讀取以二進位形式保存的文件,如CIFAR10數據集就是這種形式。
迭代器:提供了一種一次獲取一個數據集元素的方法。
所有定義都在tensorflow/python/data/ops/readers.py中。
參考文章
https://zhuanlan.zhihu.com/p/30751039
我們先理解一下dataset是什麼?
Dataset可以看作是相同類型「元素」的有序列表,而單個「元素」可以是向量,也可以是字元串、圖片,甚至是tuple或者dict。
先以最簡單的,Dataset的每一個元素是一個數字為例:
import tensorflow as tf
import numpy as np
dataset = tf.data.Dataset.from_tensor_slices(np.array([1.0, 2.0, 3.0, 4.0, 5.0]))
這樣,我們就創建了一個dataset,這個dataset中含有5個元素,分別是1.0, 2.0, 3.0, 4.0, 5.0。
如何將這個dataset中的元素取出呢?方法是從Dataset中示例化一個Iterator,然後對Iterator進行迭代。
iterator = dataset.make_one_shot_iterator()
one_element = iterator.get_next()
with tf.Session() as sess:
for i in range(5):
print(sess.run(one_element))
對應的輸出結果應該就是從1.0到5.0。語句iterator = dataset.make_one_shot_iterator()從dataset中實例化了一個Iterator,這個Iterator是一個「one shot iterator」,即只能從頭到尾讀取一次。one_element = iterator.get_next()表示從iterator里取出一個元素,調用sess.run(one_element)後,才能真正地取出一個值。
如果一個dataset中元素被讀取完了,再嘗試sess.run(one_element)的話,就會拋出tf.errors.OutOfRangeError異常,這個行為與使用隊列方式讀取數據的行為是一致的。在實際程序中,可以在外界捕捉這個異常以判斷數據是否讀取完,請參考下面的代碼:
dataset = tf.data.Dataset.from_tensor_slices(np.array([1.0, 2.0, 3.0, 4.0, 5.0]))
iterator = dataset.make_one_shot_iterator()
one_element = iterator.get_next()
with tf.Session() as sess:
try:
while True:
print(sess.run(one_element))
except tf.errors.OutOfRangeError:
print("end!")
dataset還可以有一些基本的數據變換操作,即transform操作,常見的有map,batch,shuffle,repeat
把數據+1dataset = dataset.map(lambda x: x + 1)
組合成batch,dataset = dataset.batch(32)
進行shuffle,dataset = dataset.shuffle(buffer_size=10000)
repeat 組成多個epoch,dataset = dataset.repeat(5)
03
來一個實例
理解了dataset之後,我們再看如何從文件中讀取數據。由於tfrecord是非常常用的格式,下面我們就以這個為例。
假如我們有兩個文件夾,一個是整理好的固定大小的圖片,一個是對應label圖片,這是一個分割任務,下面我們開始做。
首先,我們要把數據處理成tfrecord格式。
我們先定義一下存儲格式:
直接貼完整代碼了
import tensorflow as tf
import os
import sys
def _bytes_feature(value):
return tf.train.Feature(bytes_list=tf.train.BytesList(value=[value]))
def _convert_to_example(image_buffer, mask_buffer, filename, mask_filename):
example = tf.train.Example(features=tf.train.Features(feature={
"image": _bytes_feature(image_buffer),
"mask": _bytes_feature(mask_buffer),
"filename": _bytes_feature(bytes(filename.encode("UTF-8"))),
"mask_filename": _bytes_feature(bytes(mask_filename.encode("UTF-8")))
#"filename": _bytes_feature(bytes(filename, encoding="UTF-8")),
#"mask_filename": _bytes_feature(bytes(mask_filename, encoding="UTF-8"))
}))
return example
files = os.listdir(sys.argv[1])
mask_dir = sys.argv[2]
writer = tf.python_io.TFRecordWriter(sys.argv[3])
for file in files:
filename = file
mask_filename = os.path.join(mask_dir,filename.split(".")[0] + ".png")
filename = os.path.join(sys.argv[1],filename)
try:
image_buffer = tf.gfile.FastGFile(filename, "rb").read()
mask_buffer = tf.gfile.FastGFile(mask_filename, "rb").read()
print "filename=",filename
example = _convert_to_example(image_buffer, mask_buffer, filename, mask_filename)
writer.write(example.SerializeToString())
except StopIteration as e:
print "error"
_convert_to_example這個函數,就是定義存儲的格式;tf.gfile.FastGFile就是讀取圖片原始文件格式且不編解碼,writer = tf.python_io.TFRecordWriter(sys.argv[3])是定義writer,寫起來其實挺簡單。
tf.train.Example是一個protocol buffer,定義在
https://github.com/tensorflow/tensorflow/blob/r1.5/tensorflow/core/example/example.proto
將數據填入到Example後就可以序列化為一個字元串。一個Example中包含Features,Features里包含Feature,每一個feature其實就是一個字典,如上面的一個字典包含4個欄位。
讀取數據就可以使用tf.TFRecordReader的tf.parse_single_example解析器。它將Example protocol buffer解析為張量。
簡單的利用隊列讀取,可以採用下面的方法
filename_queue = tf.train.string_input_producer([filename])
reader = tf.TFRecordReader()
_, serialized_example = reader.read(filename_queue) #返迴文件名和文件
features = tf.parse_single_example(serialized_example,
features={"label": tf.FixedLenFeature([], tf.int64),
『img_raw" : tf.FixedLenFeature([], tf.string),})
img = tf.decode_raw(features["image"], tf.uint8)
label = tf.decode_raw(features["mask"], tf.uint8)
不過,我們這裡利用新的API的dataset來讀取,更加高效。直接貼上代碼如下:
上面定義過_convert_to_example,我們這裡先定義一個讀取格式。
def _extract_features(example):
features = {
"image": tf.FixedLenFeature((), tf.string),
"mask": tf.FixedLenFeature((), tf.string)
}
獲取一個example
parsed_example = tf.parse_single_example(example, features)
得到原始圖並轉換格式,set_shape是必須的,因為沒有存儲尺寸信息。
images = tf.cast(tf.image.decode_jpeg(parsed_example["image"]), dtype=tf.float32)
images.set_shape([224, 224, 3])
masks = tf.cast(tf.image.decode_jpeg(parsed_example["mask"]), dtype=tf.float32) / 255.
masks.set_shape([224, 224, 1])
return images, masks
下面這個函數就是create迭代器了,在這裡我們使用最簡單的iterator,one-shot iterator來迭代,當然它只支持在一個dataset上迭代一次,不需要顯式初始化。這裡不需要懷疑epoch的問題,因為dataset.repeat(num_epoch)就會設置epoch數目,所以雖然只在dataset上迭代一次,但是已經遍歷過數據epoch次。
def create_one_shot_iterator(filenames, batch_size, num_epoch):
dataset = tf.data.TFRecordDataset(filenames)
dataset = dataset.map(_extract_features)
dataset = dataset.shuffle(buffer_size=batch_size)
dataset = dataset.batch(batch_size)
dataset = dataset.repeat(num_epoch)
return dataset.make_one_shot_iterator()
用的時候,就是
train_iterator = create_one_shot_iterator(train_files, train_batch_size, num_epoch=num_epochs)
next_images, next_masks = train_iterator.get_next()
當然讀取出來之後可以做一些數據增強的操作。
就這樣完畢!
打一個小廣告,我的攝影中的圖像基礎技術公開課程《AI 程序員碼說攝影圖像基礎》上線了,主要從一個圖像處理工程師的角度來說說攝影中的基礎技術和概念,歡迎大家訂閱交流。
加入我們做點趣事
TAG:視若觀火 |