更新至Tensorflow 1.4 I. 讀輸入資料 1. 如果資料庫大小可以全部被記憶體讀入 使用最簡單的Numpy arrays格式:
1). 將npy檔案轉換成tf.Tensor
2). 使用Dataset.from_tensor_slices()
樣本:
# Load the training data into two NumPy arrays, for example using `np.load()`.with np.load("/var/data/training_data.npy") as data:features = data["features"]labels = data["labels"]# Assume that each row of features corresponds to the same row as `labels`.assert features.shape[0] == labels.shape[0]dataset = tf.data.Dataset.from_tensor_slices((features, labels))
請注意,上面的程式碼片段會將TensorFlow graph中的features和labels數組作為tf.constant()操作嵌入。 這適用於小資料集,但會浪費記憶體—因為數組的內容將被複製多次—並且可以運行到tf.GraphDef協議緩衝區(有2GB限制)。
作為替代,可以使用tf.placeholder()張量來定義資料集,並在初始化資料集上的迭代器時提供NumPy數組。
# Load the training data into two NumPy arrays, for example using `np.load()`.with np.load("/var/data/training_data.npy") as data: features = data["features"] labels = data["labels"]# Assume that each row of `features` corresponds to the same row as `labels`.assert features.shape[0] == labels.shape[0]features_placeholder = tf.placeholder(features.dtype, features.shape)labels_placeholder = tf.placeholder(labels.dtype, labels.shape)dataset = tf.data.Dataset.from_tensor_slices((features_placeholder, labels_placeholder))# [Other transformations on `dataset`...]dataset = ...iterator = dataset.make_initializable_iterator()sess.run(iterator.initializer, feed_dict={features_placeholder: features, labels_placeholder: labels}) 2. 建立TFRecord資料
Dataset API支援多種檔案格式,因此可以處理不匹配現有記憶體的大型資料集。 例如,TFRecord檔案格式是簡單的面向記錄(record-oriented)的二進位格式,許多TensorFlow應用TFRecord來訓練資料。 tf.data.TFRecordDataset類使您可以將一個或多個TFRecord檔案的內容作為input pipline的一部分進行串流。
# Creates a dataset that reads all of the examples from two files.filenames = ["/var/data/file1.tfrecord", "/var/data/file2.tfrecord"]dataset = tf.data.TFRecordDataset(filenames)
TFRecordDataset初始化程式的filenames參數可以是strings,a list of strings或tf.Tensor of strings。 因此,如果您有兩組檔案用於訓練和驗證,則可以使用tf.placeholder(tf.string)來表示檔案名稱,並使用相應的檔案名稱初始化迭代器:
filenames = tf.placeholder(tf.string, shape=[None])dataset = tf.data.TFRecordDataset(filenames)dataset = dataset.map(...) # Parse the record into tensors.dataset = dataset.repeat() # Repeat the input indefinitely.dataset = dataset.batch(32)iterator = dataset.make_initializable_iterator()# You can feed the initializer with the appropriate filenames for the current# phase of execution, e.g. training vs. validation.# Initialize `iterator` with training data.training_filenames = ["/var/data/file1.tfrecord", "/var/data/file2.tfrecord"]sess.run(iterator.initializer, feed_dict={filenames: training_filenames})# Initialize `iterator` with validation data.validation_filenames = ["/var/data/validation1.tfrecord", ...]sess.run(iterator.initializer, feed_dict={filenames: validation_filenames}) 3. 建立text資料
許多資料集分布為一個或多個text檔案。tf.data.TextLineDataset提供了從一個或多個文字檔中提取行(lines)的簡單方法。 給定一個或多個檔案名稱,TextLineDataset將為這些檔案的每行產生一個字串值元素(string-valued element)。 像TFRecordDataset一樣,TextLineDataset接受檔案名稱作為tf.Tensor,所以你可以通過傳遞一個tf.placeholder(tf.string)來對它進行參數化。
filenames = ["/var/data/file1.txt", "/var/data/file2.txt"]dataset = tf.data.TextLineDataset(filenames)
預設情況下,TextLineDataset產生每個檔案的每一行,這可能不是所希望的,例如存在檔案以標題列開頭或包含注釋。 這些行可以使用Dataset.skip()和Dataset.filter()的轉換來刪除。 要將這些轉換分別應用於每個檔案,我們使用Dataset.flat_map()為每個檔案建立一個嵌套的資料集。
filenames = ["/var/data/file1.txt", "/var/data/file2.txt"]dataset = tf.data.Dataset.from_tensor_slices(filenames)# Use `Dataset.flat_map()` to transform each file as a separate nested dataset,# and then concatenate their contents sequentially into a single "flat" dataset.# * Skip the first line (header row).# * Filter out lines beginning with "#" (comments).dataset = dataset.flat_map( lambda filename: ( tf.data.TextLineDataset(filename) .skip(1) .filter(lambda line: tf.not_equal(tf.substr(line, 0, 1), "#"))))
有關使用資料集解析CSV檔案的完整樣本,可以參閱imports85.py II. 使用Dataset.map()來預先處理資料
Dataset.map(f)變換是通過將給定的函數f應用於輸入資料集的每個元素來產生新的資料集。 它基於通常應用於函數式程式設計語言中的列表(和其他結構)的map()函數。 函數f採用代表輸入中單個元素的tf.Tensor對象,並返回將表示新資料集中單個元素的tf.Tensor對象。 其實現使用標準的TensorFlow操作將一個元素轉換為另一個元素。
本節介紹如何使用Dataset.map()的常見樣本。 1. 解析tf.Example協議緩衝區訊息(protocol buffer messages)
許多輸入資料流水線從TFRecord格式檔案中提取tf.train.Example協議緩衝區訊息(如使用tf.python_io.TFRecordWriter編寫)。 每個tf.train.Example記錄包含一個或多個“features”,輸入管道通常將這些features轉換為張量(tensors)。
# Transforms a scalar string `example_proto` into a pair of a scalar string and# a scalar integer, representing an image and its label, respectively.def _parse_function(example_proto): features = {"image": tf.FixedLenFeature((), tf.string, default_value=""), "label": tf.FixedLenFeature((), tf.int32, default_value=0)} parsed_features = tf.parse_single_example(example_proto, features) return parsed_features["image"], parsed_features["label"]# Creates a dataset that reads all of the examples from two files, and extracts# the image and label features.filenames = ["/var/data/file1.tfrecord", "/var/data/file2.tfrecord"]dataset = tf.data.TFRecordDataset(filenames)dataset = dataset.map(_parse_function) 2. 解碼映像資料並調整大小
當在實際映像資料上訓練神經網路時,經常需要將不同大小的映像轉換成通用大小,以便它們可以批量化(batch into)為固定大小。
# Reads an image from a file, decodes it into a dense tensor, and resizes it# to a fixed shape.def _parse_function(filename, label): image_string = tf.read_file(filename) image_decoded = tf.image.decode_image(image_string) image_resized = tf.image.resize_images(image_decoded, [28, 28]) return image_resized, label# A vector of filenames.filenames = tf.constant(["/var/data/image1.jpg", "/var/data/image2.jpg", ...])# `labels[i]` is the label for the image in `filenames[i].labels = tf.constant([0, 37, ...])dataset = tf.data.Dataset.from_tensor_slices((filenames, labels))dataset = dataset.map(_parse_function)
3. 用tf.py_func()應用任意的Python邏輯
出於效能原因,鼓勵儘可能使用TensorFlow操作來預先處理資料。 但是,在解析輸入資料時,調用外部Python庫有時會很有用。 為此,請調用Dataset.map()轉換中的tf.py_func()操作。這裡用opencv的python庫cv2舉例。
import cv2# Use a custom OpenCV function to read the image, instead of the standard# TensorFlow `tf.read_file()` operation.def _read_py_function(filename, label): image_decoded = cv2.imread(image_string, cv2.IMREAD_GRAYSCALE) return image_decoded, label# Use standard TensorFlow operations to resize the image to a fixed shape.def _resize_function(image_decoded, label): image_decoded.set_shape([None, None, None]) image_resized = tf.image.resize_images(image_decoded, [28, 28]) return image_resized, labelfilenames = ["/var/data/image1.jpg", "/var/data/image2.jpg", ...]labels = [0, 37, 29, 1, ...]dataset = tf.data.Dataset.from_tensor_slices((filenames, labels))dataset = dataset.map( lambda filename, label: tuple(tf.py_func( _read_py_function, [filename, label], [tf.uint8, label.dtype])))dataset = dataset.map(_resize_function)
III. 批處理(Batching)資料集 1. 簡單批處理
最簡單的批量形式將資料集中的n個連續元素堆疊成一個元素。 Dataset.batch()轉換正是這樣做的,它與tf.stack()運算子的約束條件相同,應用於元素的每個元素,也就是說對於每個元素i,都必須具有完全相同形狀的張量。
inc_dataset = tf.data.Dataset.range(100)dec_dataset = tf.data.Dataset.range(0, -100, -1)dataset = tf.data.Dataset.zip((inc_dataset, dec_dataset))batched_dataset = dataset.batch(4)iterator = batched_dataset.make_one_shot_iterator()next_element = iterator.get_next()print(sess.run(next_element)) # ==> ([0, 1, 2, 3], [ 0, -1, -2, -3])print(sess.run(next_element)) # ==> ([4, 5, 6, 7], [-4, -5, -6, -7])print(sess.run(next_element)) # ==> ([8, 9, 10, 11], [-8, -9, -10, -11])
2. 使用padding填充來批量化張量
上述方法適用於所有尺寸相同的張量。 然而,許多模型(如序列模型)與可能具有不同大小的輸入資料(例如,不同長度的序列)一起工作。 為了處理這種情況,通過Dataset.padded_batch()轉換,您可以通過指定一個或多個可能被填充的維度來批量處理不同形狀的張量。
dataset = tf.data.Dataset.range(100)dataset = dataset.map(lambda x: tf.fill([tf.cast(x, tf.int32)], x))dataset = dataset.padded_batch(4, padded_shapes=[None])iterator = dataset.make_one_shot_iterator()next_element = iterator.get_next()print(sess.run(next_element)) # ==> [[0, 0, 0], [1, 0, 0], [2, 2, 0], [3, 3, 3]]print(sess.run(next_element)) # ==> [[4, 4, 4, 4, 0, 0, 0], # [5, 5, 5, 5, 5, 0, 0], # [6, 6, 6, 6, 6, 6, 0], # [7, 7, 7, 7, 7, 7, 7]]
Dataset.padded_batch()轉換允許為每個組件的每個維度設定不同的填充(padding),並且它可以是可變長度的(在上面的樣本中由None表示)或恒定長度。 也可以重寫填儲值(預設為0)。 IV. 訓練流程 1. 處理多epoches
Dataset API提供了兩種主要方法來處理相同資料的多個epoches。
在多個epoches迭代資料集的最簡單方法是使用Dataset.repeat()。 例如要建立一個重複10個epoches輸入的資料集:
filenames = ["/var/data/file1.tfrecord", "/var/data/file2.tfrecord"]dataset = tf.data.TFRecordDataset(filenames)dataset = dataset.map(...)dataset = dataset.repeat(10)dataset = dataset.batch(32)
應用不帶參數的Dataset.repeat()將無限地重複輸入。 Dataset.repeat() 可以不用指示一個epoch的結束和下一個epoch的開始的前提下將其參數串連(concatenate)起來。
如果想在每個epoch結束時收到一個訊號,可以編寫一個訓練迴圈,捕捉資料集末尾的tf.errors.OutOfRangeError ,這樣可以收集一些統計資訊(例如驗證錯誤)。
filenames = ["/var/data/file1.tfrecord", "/var/data/file2.tfrecord"]dataset = tf.data.TFRecordDataset(filenames)dataset = dataset.map(...)dataset = dataset.batch(32)iterator = dataset.make_initializable_iterator()next_element = iterator.get_next()# Compute for 100 epochs.for _ in range(100): sess.run(iterator.initializer) while True: try: sess.run(next_element) except tf.errors.OutOfRangeError: break # [Perform end-of-epoch calculations here.]
2. 隨機亂序(shuffle)輸入資料
Dataset.shuffle()使用與tf.RandomShuffleQueue類似的演算法對輸入資料集進行隨機亂序排列:保持一個固定大小的緩衝區,並從該緩衝區隨機播放下一個元素。
filenames = ["/var/data/file1.tfrecord", "/var/data/file2.tfrecord"]dataset = tf.data.TFRecordDataset(filenames)dataset = dataset.map(...)dataset = dataset.shuffle(buffer_size=10000)dataset = dataset.batch(32)dataset = dataset.repeat()
3. 使用進階別的APIs
tf.train.MonitoredTrainingSession API簡化了在分布式設定中運行TensorFlow的許多方面。 MonitoredTrainingSession使用tf.errors.OutOfRangeError表示訓練已完成,因此要將其與Dataset API結合使用,建議使用Dataset.make_one_shot_iterator()。 舉例如下:
filenames = ["/var/data/file1.tfrecord", "/var/data/file2.tfrecord"]dataset = tf.data.TFRecordDataset(filenames)dataset = dataset.map(...)dataset = dataset.shuffle(buffer_size=10000)dataset = dataset.batch(32)dataset = dataset.repeat(num_epochs)iterator = dataset.make_one_shot_iterator()next_example, next_label = iterator.get_next()loss = model_function(next_example, next_label)training_op = tf.train.AdagradOptimizer(...).minimize(loss)with tf.train.MonitoredTrainingSession(...) as sess: while not sess.should_stop(): sess.run(training_op)
要在tf.estimator.Estimator的input_fn中使用Dataset,依然推薦使用Dataset.make_one_shot_iterator()。 例如:
def dataset_input_fn(): filenames = ["/var/data/file1.tfrecord", "/var/data/file2.tfrecord"] dataset = tf.data.TFRecordDataset(filenames) # Use `tf.parse_single_example()` to extract data from a `tf.Example` # protocol buffer, and perform any additional per-record preprocessing. def parser(record): keys_to_features = { "image_data": tf.FixedLenFeature((), tf.string, default_value=""), "date_time": tf.FixedLenFeature((), tf.int64, default_value=""), "label": tf.FixedLenFeature((), tf.int64, default_value=tf.zeros([], dtype=tf.int64)), } parsed = tf.parse_single_example(record, keys_to_features) # Perform additional preprocessing on the parsed data. image = tf.decode_jpeg(parsed["image_data"]) image = tf.reshape(image, [299, 299, 1]) label = tf.cast(parsed["label"], tf.int32) return {"image_data": image, "date_time": parsed["date_time"]}, label # Use `Dataset.map()` to build a pair of a feature dictionary and a label # tensor for each example. dataset = dataset.map(parser) dataset = dataset.shuffle(buffer_size=10000) dataset = dataset.batch(32) dataset = dataset.repeat(num_epochs) iterator = dataset.make_one_shot_iterator() # `features` is a dictionary in which each value is a batch of values for # that feature; `labels` is a batch of labels. features, labels = iterator.get_next() return features, labels