當前位置:
首頁 > 最新 > 【從caffe到Tensorflow 1】io 操作

【從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 程序員碼說攝影圖像基礎》上線了,主要從一個圖像處理工程師的角度來說說攝影中的基礎技術和概念,歡迎大家訂閱交流。

加入我們做點趣事


喜歡這篇文章嗎?立刻分享出去讓更多人知道吧!

本站內容充實豐富,博大精深,小編精選每日熱門資訊,隨時更新,點擊「搶先收到最新資訊」瀏覽吧!


請您繼續閱讀更多來自 視若觀火 的精彩文章:

caffe從數學公式到代碼實現5-caffe中的卷積

TAG:視若觀火 |