Tensorflow_07B_讀取 TFRecord 與反序列化過程

Brief 概述

前一個章節描述如何使用 Tensorflow 的函數把數據序列化後,妥善構建一個數據結構並把數據放入 protocol buffer 文檔中保存成二進制的格式,這也是上一章節中如下圖最左邊肩頭的環節:

保存好的 .tfrecord 文檔就好比我們的子彈,已經把作爲火藥的原始數據妥善的包裝到了彈殼當中隨時準備產生作用,這一節將描述如何把這些子彈裝入機槍中,而這把機槍就是 Tensorflow 的框架,是一個解譯 (decode) 數據結構的過程,並在每次成功解譯後,都打印出結果來比對其正確性。

 

Read TFRecord 讀取數據 (官網代碼)

理解完整的數據結構與儲存方式後,接着就是如何讀取已經儲存的數據。同樣的,根據兩種不同的儲存方式,也有兩種讀取方法,如下陳列:

1. Decode Data using tf.parse_single_example()

Step 1-1

定義哪些路徑下的 .tfrecord 數據文檔準備被讀取,是字符串形式,並且使用一個列表打包每一個路徑,每個路徑都做爲一個列表裏面的單元,如下代碼:

import tensorflow as tf
import numpy as np

data_path=['example.tfrecord']

Step 1-2

tf.train.string_input_producer() 方法來創建一個隊列,解析第一步給到的路徑集列表,該隊列是一個 FIFO 隊列,此方法還有 num_epochs 與 shuffle 參數提供調用,讓我們調整是否隨機排布數據順序,代碼如下:

queued_filename = tf.train.string_input_producer(data_path, 
                                                 shuffle=False,
                                                 num_epochs=None)

Step 1-3

接着使用 tf.TFRecordReader() 方法來創建一個讀取的物件並讀取內容,如下代碼:

reader = tf.TFRecordReader()
key, serialized_example = reader.read(queued_filename)
# The generated objects are all tf Tensors.
# ReaderReadV2(key=<tf.Tensor 'ReaderReadV2:0' shape=() dtype=string>, 
#              value=<tf.Tensor 'ReaderReadV2:1' shape=() dtype=string>)

Step 1-4

需要先創建一個解析器,用來解析 .tfrecord 裏面的數據,因此使用 tf.parse_single_example() ,但是解析前必須重新給一個定義的 features 數據格式,其中尤其注意 !!! 標籤的名字 和 數據格式 !!! 必須嚴格一模一樣的定義,定義方式有兩種如下陳列:

  1. tf.FixedLenFeature(shape, dtype, default_value): 回傳的是 Tensor,含有預設好的數據格式與 "一個" 張量值,對應好了 dtype 數據類型。
    1. shape: 類似 reshape 的功能,可以重塑儲存數據的維度
    2. dtype: 必須是 tf.float32, tf.int64, tf.string 其中一種
    3. default_value: 如果 feature 沒有值,那它就是預設值
  2. tf.VarLenFeature(dtype): 回傳的是 SparTensor 多個張量,可以用方法 indices, values, dense_shape 來呼叫個別的值,並且只預先使用 dtype 設定該些值的數據類型。

實際代碼如下:

read_features = {
    'name': tf.FixedLenFeature(shape=[], dtype=tf.string),
    'tall': tf.FixedLenFeature(shape=[1,], dtype=tf.float32),
    'age': tf.VarLenFeature(dtype=tf.int64),
    'email': tf.VarLenFeature(dtype=tf.string),
    'gender': tf.VarLenFeature(dtype=tf.string)
}
read_data = tf.parse_single_example(serialized=serialized_example,
                                    features=read_features)
print('read_data: ', read_data)
print('\nname: ', read_data['name'])
read_data:  {'age': <tensorflow.python.framework.sparse_tensor.SparseTensor object at 0x1206424e0>, 'email': <tensorflow.python.framework.sparse_tensor.SparseTensor object at 0x120642898>, 'gender': <tensorflow.python.framework.sparse_tensor.SparseTensor object at 0x120642a90>, 'name': <tf.Tensor 'ParseSingleExample/ParseSingleExample:9' shape=() dtype=string>, 'tall': <tf.Tensor 'ParseSingleExample/ParseSingleExample:10' shape=(1,) dtype=float32>}

name:  Tensor("ParseSingleExample/ParseSingleExample:9", shape=(), dtype=string)

Step 1-4 - Remind

完成了步驟四之後,原先那些 Bytes 形式的數據儲存時轉換二進制的動作,已經可以開始在這一步反轉換回來了,不過切記此時的數據還是留存在 Tensorflow 中的 Tensor,一般的模塊如 numpy 中的 frombuffer 或 fromstring 函數是無法解析此時此刻的數據的。

Tensorflow 提供了幾種解析數據的方法,如下:

  • tf.decode_raw(): 適合把二進制的任意類型數據轉換成 "數字類型" 的狀態,例如用矩陣表示的圖像數據解碼的過程則可以通過此方法轉換,但是注意不能轉換回文字。
  • tf.decode_csv()
  • tf.image_decode_jpeg()

這些函數保證數據轉換的過程中還是在張量的狀態完成,維持數據處理的高效率。

# If we use this to transform binary data into tf.string,
# some Errors would pop up like this below.
NAME = tf.decode_raw(read_data['name'], tf.string)
TypeError: Value passed to parameter 'out_type' has DataType string not in list of allowed values: float16, float32, float64, int32, uint16, uint8, int16, int8, int64
# If we want to use another tools to deal the the data format here,
# tensors can only be deposited in Tensorflow framewrok.
NAME = np.fromstring(read_data['name'], dtype='U4')
TypeError: a bytes-like object is required, not 'Tensor'

Step 1-5

最後開啓 tf.Session() 繪話,並在其中放入 tf.train.start_queue_runners() 方法運行,算是一種在 Tensorflow 當中一種宣告說已經有一個本來序列化的數據要開始被讀取了,接着打印當初封裝的數據結果,下面由兩個打印的方法分別使用 tf.InteractiveSession() 與 tf.Session() 方法,自行參考。

int_sess = tf.InteractiveSession()

coord = tf.train.Coordinator()
threads = tf.train.start_queue_runners(sess=int_sess, coord=coord)

tf.train.start_queue_runners(int_sess)
for name, tensor in read_data.items():
    print('{}: {}\n'.format(name, tensor.eval()))
    
coord.request_stop()
coord.join(threads)
age: SparseTensorValue(indices=array([[0]]), values=array([28]), dense_shape=array([1]))

email: SparseTensorValue(indices=array([[0]]), values=array([b'[email protected]'], dtype=object), dense_shape=array([1]))

gender: SparseTensorValue(indices=array([[0]]), values=array([b'M\x00\x00\x00a\x00\x00\x00l\x00\x00\x00e\x00\x00\x00'],
      dtype=object), dense_shape=array([1]))

name: b'J\x00\x00\x00a\x00\x00\x00m\x00\x00\x00e\x00\x00\x00s\x00\x00\x00'

tall: [1.78]

p.s. 切記使用 .eval( ) 的時候,開啓的 sess 必須是用 InteractiveSession( ) 方法開啓的纔不會報錯。

sess = tf.Session()
sess.run(tf.global_variables_initializer())

coord = tf.train.Coordinator()
threads = tf.train.start_queue_runners(sess=sess, coord=coord)

print('name: ', sess.run(read_data['name']))
print('tall: ', sess.run(read_data['tall']))
print('age: ', sess.run(read_data['age']))
print('age: ', sess.run(read_data['age']).indices)
print('age: ', sess.run(read_data['age']).values)
print('age: ', sess.run(read_data['age']).dense_shape)
print('age: ', type(sess.run(read_data['age']).values))
print('email: ', sess.run(read_data['email']).values[0].decode('utf-8'))

GENDER = sess.run(read_data['gender'].values[0])
print('gender: ', np.frombuffer(GENDER, dtype='U4'))

coord.request_stop()
coord.join(threads)
name:  b'J\x00\x00\x00a\x00\x00\x00m\x00\x00\x00e\x00\x00\x00s\x00\x00\x00'
tall:  [1.78]
age:  SparseTensorValue(indices=array([[0]]), values=array([28]), dense_shape=array([1]))
age:  [[0]]
age:  [28]
age:  [1]
age:  <class 'numpy.ndarray'>
email:  [email protected]
gender:  ['Male']
# int_sess.close()
# sess.close()

##########################################################################
# ERROR:tensorflow:Exception in QueueRunner: 
# Enqueue operation was cancelled
# ------------------------------------------------------------------------
# In order to prevent the ERROR shown above from the executing sessions,
# we should add "tf.train.Coordinator()" and the corresponding
# coord methods after .run execution.

2. Decode Data using tf.parse_single_sequence_example()

其傳入的參數分爲兩個,分別是裝了一般張量值的 context_features 與裝了列表的 sequence_features,完整代碼如下:

reader = tf.TFRecordReader()
queue_seq_file = tf.train.string_input_producer(['sequence_example.tfrecord'], 
                                                 shuffle=True,
                                                 num_epochs=None)
key, serialized_sequence_exp = reader.read(queue_seq_file)

context_features = {
    'name': tf.FixedLenFeature(shape=[], dtype=tf.string)
}

sequence_features = {
    'Info': tf.FixedLenSequenceFeature([], dtype=tf.string),
    'Number': tf.VarLenFeature(dtype=tf.float32)
}
context_data, sequence_data = \
    tf.parse_single_sequence_example(serialized=serialized_sequence_exp,
                                     context_features=context_features,
                                     sequence_features=sequence_features)
print('context_data: ', context_data)
print('\nsequence_data: ', sequence_data)
print('\nboth types: ', type(context_data), type(sequence_data))
context_data:  {'name': <tf.Tensor 'ParseSingleSequenceExample/ParseSingleSequenceExample:0' shape=() dtype=string>}

sequence_data:  {'Number': <tensorflow.python.framework.sparse_tensor.SparseTensor object at 0x120a37cf8>, 'Info': <tf.Tensor 'ParseSingleSequenceExample/ParseSingleSequenceExample:4' shape=(?,) dtype=string>}

both types:  <class 'dict'> <class 'dict'>

讀取方式: 同樣分 InteractiveSession 與 Session 兩種,如下代碼:

int_seq_sess = tf.InteractiveSession()
int_seq_sess.run(tf.global_variables_initializer())

int_seq_coord = tf.train.Coordinator()
threads = tf.train.start_queue_runners(sess=int_seq_sess, 
                                       coord=int_seq_coord)

print('Context:')
for name, tensor in context_data.items():
    print('{}: {}'.format(name, tensor.eval()))

print('\nData')
for name, tensor in sequence_data.items():
    print('{}: {}'.format(name, tensor.eval()))

int_seq_coord.request_stop()
int_seq_coord.join(threads)
Context:
name: b'J\x00\x00\x00a\x00\x00\x00m\x00\x00\x00e\x00\x00\x00s\x00\x00\x00'

Data
Number: SparseTensorValue(indices=array([[0, 0],
       [1, 0]]), values=array([ 1.78, 28.  ], dtype=float32), dense_shape=array([2, 1]))
Info: [b'[email protected]' b'M\x00\x00\x00a\x00\x00\x00l\x00\x00\x00e\x00\x00\x00']
seq_sess = tf.Session()
seq_sess.run(tf.global_variables_initializer())

seq_coord = tf.train.Coordinator()
threads = tf.train.start_queue_runners(sess=seq_sess, coord=seq_coord)

seq_name = seq_sess.run(context_data['name'])
decode_seq_name = np.frombuffer(seq_name, dtype='U5')[0]
print('name: ', seq_name)
print('decode name: ', decode_seq_name)

seq_info = seq_sess.run(sequence_data['Info'])
decode_email = seq_info[0].decode('utf-8')
decode_gender = np.frombuffer(seq_info[1], dtype='U4')
print('\ninfo: ', seq_info)
print('decode email: ', decode_email)
print('decode gender: ', decode_gender)

seq_num = seq_sess.run(sequence_data['Number'])
print('\nnumber: ', seq_num)
print('number values: ', seq_num.values)

seq_coord.request_stop()
seq_coord.join(threads)
name:  b'J\x00\x00\x00a\x00\x00\x00m\x00\x00\x00e\x00\x00\x00s\x00\x00\x00'
decode name:  James

info:  [b'[email protected]' b'M\x00\x00\x00a\x00\x00\x00l\x00\x00\x00e\x00\x00\x00']
decode email:  [email protected]
decode gender:  ['Male']

number:  SparseTensorValue(indices=array([[0, 0],
       [1, 0]]), values=array([ 1.78, 28.  ], dtype=float32), dense_shape=array([2, 1]))
number values:  [ 1.78 28.  ]
int_seq_sess.close()
seq_sess.close()

##########################################################################
# ERROR:tensorflow:Exception in QueueRunner: 
# Enqueue operation was cancelled
# ------------------------------------------------------------------------
# In order to prevent the ERROR shown above from the executing sessions,
# we should add "tf.train.Coordinator()" and the corresponding
# coord methods after .run execution.

但上面的過程就直接把數據解析出來還原成本來的樣子,並沒有如概述所說的放入機槍準備發射,原因是要檢查所有過程的正確性和簡潔性,後面章節接着描述如何完成快速引入數據的流程。

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章