編寫基於TensorFlow的應用之構建數據pipeline

詳見:www.sigai.cn 知識庫

本文主要以MNIST數據集爲例介紹TFRecords文件如何製作以及加載使用。所講內容可以在SIGAI 在線編程功能中的sharedata/intro_to_tf文件夾中可以免費獲取。此項功能對所有註冊用戶免費開放。非註冊用戶在官網註冊即可使用。

官網地址:www.sigai.cn,

推薦使用chrome瀏覽器

在線編程功能使用指南見SIGAI官網->知識庫->在線編程使用說明小視頻

圖1 典型的基於TensorFlow 的應用的workflow

通常情況下,一個基於TensorFlow 的應用訓練過程中所採用的workflow 如圖1 所示。針對與原始數據的格式,首先採用不同的轉換方式在運行過程中生成Tensor格式的數據,然後將其送到TensorFlow Graph中運行,根據設定的目標函數,不斷的在訓練數據上迭代並週期性地保存checkpoint到文件中,checkpoint文件可以用於後續的模型持久化操作。TensorFlow框架下訓練輸入pipeline是一個標準的ETL過程:

1、提取數據(Extract): 從存儲空間內部讀取原始數據

2、數據轉換(Transform): 使用CPU解析原始數據並執行一些預處理的操作: 文本數據轉換爲數組,圖片大小變換,圖片數據增強操作等等

3、數據加載(Load): 加載轉換後的數據並傳給GPU,FPGA,ASIC等加速芯片進行計算

在TensorFlow框架之下,使用 tf.dataset API 可以完成上述過程中所需的所有操作,其過程如下圖所示:

圖2 TensorFlow中的ETL過程

相較於TFRecords文件,文本文件,numpy數組,csv文件等文件格式更爲常見。接下來,本文將以常用的MNIST數據集爲例簡要介紹TFRecord文件如何生成以及如何從TFrecord構建數據pipeline。

TFRecord文件簡介

TFRecord文件是基於Google Protocol Buffers的一種保存數據的格式,我們推薦在數據預處理過程中儘可能使用這種方式將訓練數據保存成這種格式。Protocol Buffers 是一種簡潔高效的序列化格式化的方法,其採用了語言無關,平臺無關且可擴展的機制。 採用這種方式的優勢在於:

1、採用二進制格式存儲,減少存儲空間,提高讀取效率

2、針對TensorFlow框架進行優化,支持合併多個數據源,並且支持TensorFlow內置的其他數據預處理方式

3、支持序列化數據的存儲(時序數據或者詞向量)

圖3 TFRecord文件中存儲內容結構

TFRecords中存儲的層級如圖3所示,從圖中可以看到:

一個TFRecord文件中包含了多個tf.train.Example, 每個tf.train.Example是一個Protocol Buffer

每個tf.train.Example包含了tf.train.Features

每個tf.train.Features是由多個feature 構成的feature set

以MNIST爲例生成TFRecord文件

圖4 TFRecord文件製作和加載過程

從原始文件生成TFRecord的過程如圖4所示:

1、從文件中讀取數據信息,如果是類別,長度,高度等數值型數據就轉換成Int64List, FloatList格式的特徵,如果是圖片等raw data,則直接讀取其二進制編碼內容,再轉換成BytesList即可

2、將多個特徵合併爲 tf.train.Features,並傳遞到tf.train.Example中

3、最後使用TFRecordWriter寫入到文件中

對於MNIST文件,從http://yann.lecun.com/exdb/mnist/網站下載下來的是以二進制方式存儲的數據集,本文略過下載並讀取MNIST爲numpy 數組的過程,有興趣的讀者可以查看mnist_data.py中的read_mnist函數。接下來我們重要講解從一個numpy 數組到tfrecord文件需要執行的主要步驟:

1、對於整個數組,需要遍歷整個數組並依次將其轉換成一個tf.train.Exam

with TFRecordWriter(output_file) as writer:
    for (img, label) in tqdm(zip(imgs, labels)):
       mnist_example = feature_to_example(img, label)
       writer.write(mnist_example.SerializeToString())

2、對於每個圖片來說,需要做如下轉換

def feature_to_example(img, label):
  """convert numpy array to a `tf.train.example`

  Args:
    img : An `np.ndarray`. Img in numpy array format
    label : An `np.int32`. label of the image
  """
  # convert raw data corresponding to the numpy array in memory into pytho bytes
  img = img.tostring()
  return tf.train.Example(
      features=tf.train.Features(
          feature={
              'img': bytes_feature(img),
              'label': int_feature(label)
          }
      )
  )

這其中使用到的bytes_feature和int_feature分別是用來將圖片和標籤轉換成二進制的feature和int列表的特徵的函數

def int_feature(value):
  return tf.train.Feature(int64_list=tf.train.Int64List(value=[value]))


def bytes_feature(value):
  return tf.train.Feature(bytes_list=tf.train.BytesList(value=[value]))

3、在使用SerializeToString函數將protocol buffer中的內容序列化之後, 將其內容寫入到文件中

至此,MNIST的tfrecord文件就製作完成了。以上步驟各位讀者可以在sharedata/intro_to_tf路徑下的 tfrecords.ipynb 文件中進行實驗。由於MNIST中涉及到的特徵僅有數組和標籤兩類內容,對於讀者在使用TensorFlow過程中可能會遇到的其他數據格式,建議參考https://github.com/tensorflow/models/blob/master/research/object_detection/dataset_tools/create_pascal_tf_record.py 文件編寫適合自己數據集內容的函數

加載TFRecord文件並構建數據pipeline

從圖4中,可以看到加載一個TFRrecord文件需要執行的步驟,其過程中使用了TensorFlow dataset類提供的函數:

1、shuffle:打亂輸入數據的順序

2、repeat: 重複數據集內容若干次

3、map: 對數據集中的每個數據使用map函數中傳入的方法進行變換,這個過程中可以包含解析tf.train.Example內容,數據歸一化以及data augmentation等其他操作

4、batch: 根據需要設置每次訓練採用多少數據

5、prefetch:提前加載n個數據,保證每個session運行之前數據是可以立即使用的

在mnist_tfrecords.py文件中有兩個不同的加載數據的方式,我們建議使用第二種優化過的加載方式,其優點在於:

1、shuffle_and_repeat可以保證加載數據的速度以及確保數據之間的順序正確

2、map_and_batch 整合了map和batch 過程,提高了效率

經過優化過的加載TFRecord文件函數如下:

def load_data_optimized(cache_dir='data/cache',
                        split='train',
                        batch_size=64,
                        epochs_between_evals=3):
  tfrecord_file = os.path.join(cache_dir,
                               'mnist_{}.tfrecord'.format(split))

  # load the tfrecord data
  dataset = tf.data.TFRecordDataset(tfrecord_file)

  # shuffle and repeat data
  if split == 'train':
    dataset = dataset.apply(shuffle_and_repeat(60000, epochs_between_evals))
  else:
    dataset = dataset.apply(shuffle_and_repeat(10000, epochs_between_evals))

  # fuse map and batch
  dataset = dataset.apply(map_and_batch(parse_example,
                                        batch_size=batch_size,
                                        drop_remainder=True,
                                        num_parallel_calls=8))

  dataset = dataset.prefetch(1)

  return dataset

在SIGAI提供的實驗過程中,驗證讀取數據的內容如下圖所示:

本文主要介紹了TFRecord文件,然後以MNIST數據集爲例講解了如何製作MNIST數據集的TFRecord文件,接着講述瞭如何加載文件並構建數據 pipeline。大家在做實驗過程中使用了Eager模式,我們將在下一篇文章中介紹Eager 模式的使用。

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