【Tensorflow】io 操作
文章首發于微信公眾號《有三AI》
【從caffe到Tensorflow 1】io 操作
最近項目要頻繁用到tensorflow,所以不得不認真研究下tensorflow而不是跟之前一樣遇到了就搞一下了。
首先我覺得所有這些框架里面caffe是最清晰的,所以就算是學習tensorflow,我也會以caffe的思路去學習,這就是這個系列的用意。
今天是第1篇,咱們說io操作,也就是文件讀取,載入內存。
?
01 Caffe的io操作
caffe的io,是通過在prototxt中定義數據輸入,默認支持data,imagedata,hdf5data,window?data等類型。Datalayer,輸入是LMDB數據格式,image?data?支持的是imagelist的數據格式。
對于LMDB來說,我們在caffe?layer中配置準備好的二進制數據即可。
對于image?data,我們準備一個data?list,官方的imagedata是一個分類任務的list,格式為每行image,label,當然隨著任務的不同我們可以自定義。比如分割任務image,mask。檢測任務,image?num?of?object,?objectrect1,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不止是讀取文件這一種方法,它可以包含以下幾種方式。
-
預加載數據:?在TensorFlow圖中定義常量或變量來保存所有數據(僅適用于數據量比較小的情況)
如上,x1,x2都是預加載好的數據。在設計Graph的時候,x1和x2就已經被定義成了兩個有值的列表,在計算y的時候直接取x1和x2的值。這種方法的問題是將數據直接內嵌到Graph中,再把Graph傳入Session中運行。當數據量比較大時,Graph的傳輸會遇到效率問題。
-
Feeding?它定義變量的時候用占位符替代數據,待運行的時候填充數據。
定義的時候,x1,?x2只是占位符所以沒有具體的值,運行的時候使用sess.run()中的feed_dict參數,將Python產生的數據喂給后端,并計算y。
-
Reading?From?File
前兩種方法很方便,但是遇到大型數據的時候就會很吃力,即使是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,A1Alpha2,A2Alpha3,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格式
首先,我們要把數據處理成tfrecord格式。
我們先定義一下存儲格式:
直接貼完整代碼了
import?tensorflow?as?tf import?os import?sysdef?_bytes_feature(value):returntf.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?examplefiles?=?os.listdir(sys.argv[1]) mask_dir?=?sys.argv[2] writer?=?tf.python_io.TFRecordWriter(sys.argv[3]) for?file?in?files:filename?=?filemask_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=",filenameexample?=?_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.records
讀取數據就可以使用tf.TFRecordReader的tf.parse_single_example解析器。它將Example?protocolbuffer解析為張量。
簡單的利用隊列讀取,可以采用下面的方法
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是必須的,因為沒有存儲尺寸信息。
下面這個函數就是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!
?
總結
以上是生活随笔為你收集整理的【Tensorflow】io 操作的全部內容,希望文章能夠幫你解決所遇到的問題。
- 上一篇: 【技术综述】深度学习中的数据增强(下)
- 下一篇: 怎样学会科学的调研并启动一个项目