TensorFlow.org教程筆記(二) DataSets 快速入門


本文翻譯自www.tensorflow.org的英文教程。

tf.data 模塊包含一組類,可以讓你輕松加載數據,操作數據並將其輸入到模型中。本文通過兩個簡單的例子來介紹這個API

  • 從內存中的numpy數組讀取數據。
  • 從csv文件中讀取行

基本輸入

對於剛開始使用tf.data,從數組中提取切片(slices)是最簡單的方法。

筆記(1)TensorFlow初上手里提到了訓練輸入函數train_input_fn,該函數將數據傳輸到Estimator中:

def train_input_fn(features, labels, batch_size):
    """An input function for training"""
    # Convert the inputs to a Dataset.
    dataset = tf.data.Dataset.from_tensor_slices((dict(features), labels))
    
    # Shuffle, repeat, and batch the examples.
    dataset = dataset.shuffle(1000).repeat().batch(batch_size)
    
    # Build the Iterator, and return the read end of the pipeline.
    return dataset.make_one_shot_iterator().get_next()

讓我們進一步來看看這個過程。

參數

這個函數需要三個參數。期望“array”的參數幾乎可以接受任何可以使用numpy.array轉換為數組的東西。其中有一個例外是對Datasets有特殊意義的元組(tuple)。

  • features :一個包含原始特征輸入的{'feature_name':array}的字典(或者pandas.DataFrame)
  • labels :一個包含每個樣本標簽的數組
  • batch_size:指示所需批量大小的整數。

在前面的筆記中,我們使用iris_data.load_data()函數加載了鳶尾花的數據。你可以運行下面的代碼來獲取結果:

import iris_data

# Fetch the data.
train, test = iris_data.load_data()
features, labels = train

然后你可以將數據輸入到輸入函數中,類似這樣:

batch_size = 100
iris_data.train_input_fn(features, labels, batch_size)

我們來看看這個train_input_fn

切片(Slices)

在最簡單的情況下,tf.data.Dataset.from_tensor_slices函數接收一個array並返回一個表示array切片的tf.data.Dataset。例如,mnist訓練集的shape是(60000, 28, 28)。將這個array傳遞給from_tensor_slices將返回一個包含60000個切片的數據集對象,每個切片大小為28X28的圖像。(其實這個API就是把array的第一維切開)。

這個例子的代碼如下:

train, test = tf.keras.datasets.mnist.load_data()
mnist_x, mnist_y = train

mnist_ds = tf.data.Dataset.from_tensor_slices(mnist_x)
print(mnist_ds)

將產生下面的結果:顯示數據集中項目的type和shape。注意,數據集不知道它含有多少個sample。

<TensorSliceDataset shapes: (28,28), types: tf.uint8>

上面的數據集代表了簡單數組的集合,但Dataset的功能還不止如此。Dataset能夠透明地處理字典或元組的任何嵌套組合。例如,確保features是一個標准的字典,你可以將數組字典轉換為字典數據集。

先來回顧下features,它是一個pandas.DataFrame類型的數據:

SepalLength SepalWidth PetalLength PetalWidth
0.6 0.8 0.9 1
... ... ... ...

dict(features)是一個字典,它的形式如下:

{key:value,key:value...}  # key是string類型的列名,即SepalLength等
			# value是pandas.core.series.Series類型的變量,即數據的一個列,是一個標量

對它進行切片

dataset = tf.data.Dataset.from_tensor_slices(dict(features))
print(dataset)

結果如下:

<TensorSliceDataset

  shapes: {
    SepalLength: (), PetalWidth: (),
    PetalLength: (), SepalWidth: ()},

  types: {
      SepalLength: tf.float64, PetalWidth: tf.float64,
      PetalLength: tf.float64, SepalWidth: tf.float64}
>

這里我們看到,當數據集包含結構化元素時,數據集的形狀和類型采用相同的結構。該數據集包含標量字典,所有類型為tf.float64。

train_input_fn的第一行使用了相同的函數,但它增加了一層結構-----創建了一個包含(feature, labels)對的數據集

我們繼續回顧labels的結構,它其實是一個pandas.core.series.Series類型的變量,即它與dict(features)的value是同一類型。且維度一致,都是標量,長度也一致。

以下代碼展示了這個dataset的形狀:

# Convert the inputs to a Dataset.
dataset = tf.data.Dataset.from_tensor_slices((dict(features), labels))
print(dataset)
<TensorSliceDataset
    shapes: (
        {
          SepalLength: (), PetalWidth: (),
          PetalLength: (), SepalWidth: ()},
        ()),

    types: (
        {
          SepalLength: tf.float64, PetalWidth: tf.float64,
          PetalLength: tf.float64, SepalWidth: tf.float64},
        tf.int64)>

操縱

對於目前的數據集,將以固定的順序遍歷數據一次,並且每次只生成一個元素。在它可以被用來訓練之前,還需做進一步處理。幸運的是,tf.data.Dataset類提供了接口以便於更好地在訓練之前准備數據。輸入函數的下一行利用了以下幾種方法:

# Shuffle, repeat, and batch the examples.
dataset = dataset.shuffle(1000).repeat().batch(batch_size)

shuffle方法使用一個固定大小的緩沖區來隨機對數據進行shuffle。設置大於數據集中sample數目的buffer_size可以確保數據完全混洗。鳶尾花數據集只包含150個數據。

repeat方法在讀取到組后的數據時重啟數據集。要限制epochs的數量,可以設置count參數。

batch方法累計樣本並堆疊它們,從而創建批次。這個操作的結果為這批數據的形狀增加了一個維度。新維度被添加為第一維度。以下代碼是早期使用mnist數據集上的批處理方法。這使得28x28的圖像堆疊為三維的數據批次。

print(mnist_ds.batch(100))
<BatchDataset
  shapes: (?, 28, 28),
  types: tf.uint8>

請注意,數據集具有未知的批量大小,因為最后一批的元素數量較少。

train_input_fn中,批處理后,數據集包含一維向量元素,其中每個標量先前都是:

print(dataset)
<TensorSliceDataset
    shapes: (
        {
          SepalLength: (?,), PetalWidth: (?,),
          PetalLength: (?,), SepalWidth: (?,)},
        (?,)),

    types: (
        {
          SepalLength: tf.float64, PetalWidth: tf.float64,
          PetalLength: tf.float64, SepalWidth: tf.float64},
        tf.int64)>

返回值

每個Estimatortrainpredictevaluate方法都需要輸入函數返回一個包含Tensorflow張量的(features, label)對。train_input_fn使用以下代碼將數據集轉換為預期的格式:

# Build the Iterator, and return the read end of the pipeline.
features_result, labels_result = dataset.make_one_shot_iterator().get_next()

結果是TensorFlow張量的結構,匹配數據集中的項目層。

print((features_result, labels_result))
({
    'SepalLength': <tf.Tensor 'IteratorGetNext:2' shape=(?,) dtype=float64>,
    'PetalWidth': <tf.Tensor 'IteratorGetNext:1' shape=(?,) dtype=float64>,
    'PetalLength': <tf.Tensor 'IteratorGetNext:0' shape=(?,) dtype=float64>,
    'SepalWidth': <tf.Tensor 'IteratorGetNext:3' shape=(?,) dtype=float64>},
Tensor("IteratorGetNext_1:4", shape=(?,), dtype=int64))

讀取CSV文件

Dataset最常見的實際用例是按流的方式從磁盤上讀取文件。tf.data模塊包含各種文件讀取器。讓我們來看看如何使用Dataset從csv文件中分析鳶尾花數據集。

以下對iris_data.maybe_download函數的調用在需要時會下載數據,並返回下載結果文件的路徑名稱:

import iris_data
train_path, test_path = iris_data.maybe_download()

iris_data.csv_input_fn函數包含使用Dataset解析csv文件的替代實現。

構建數據集

我們首先構建一個TextLineDataset對象,一次讀取一行文件。然后,我們調用skip方法跳過文件第一行,它包含一個頭部,而不是樣本:

ds = tf.data.TextLineDataset(train_path).skip(1)

構建csv行解析器

最終,我們需要解析數據集中的每一行,以產生必要的(features, label)對。

我們將開始構建一個函數來解析單個行。

下面的iris_data.parse_line函數使用tf.decode_csv函數和一些簡單的代碼完成這個任務:

我們必須解析數據集中的每一行以生成必要的(features, label)對。以下的_parse_line函數調用tf.decode_csv將單行解析為其featureslabel。由於Estimator要求將特征表示為字典,因此我們依靠python的內置字典和zip函數來構建該字典。特征名是該字典的key。然后我們調用字典的pop方法從特征字典中刪除標簽字段。

# Metadata describing the text columns
COLUMNS = ['SepalLength', 'SepalWidth',
           'PetalLength', 'PetalWidth',
           'label']
FIELD_DEFAULTS = [[0.0], [0.0], [0.0], [0.0], [0]]
def _parse_line(line):
    # Decode the line into its fields
    fields = tf.decode_csv(line, FIELD_DEFAULTS)
    
    # Pack the result into a dictionary
    features = dict(zip(COLUMNS, fields))
    
    # Separate the label from the features
    label = features.pop('label')
    
    return features, label

解析行

Datasets有很多方法用於在數據傳輸到模型時處理數據。最常用的方法是map,它將轉換應用於Dataset的每個元素。

map方法使用一個map_func參數來描述Dataset中每個項目應該如何轉換。 map.png

因此為了解析流出csv文件的行,我們將_parse_line函數傳遞給map方法:

ds = ds.map(_parse_line)
print(ds)
<MapDataset
shapes: (
    {SepalLength: (), PetalWidth: (), ...},
    ()),
types: (
    {SepalLength: tf.float32, PetalWidth: tf.float32, ...},
    tf.int32)>

現在的數據集不是簡單的標量字符串,而是包含了(features, label)對。

iris_data.csv_input_fn函數其余部分與基本輸入部分中涵蓋的iris_data.train_input_fn相同。

試試看

該函數可以用來替代iris_data.train_input_fn。它可以用來提供一個如下的Estimator

train_path, test_path = iris_data.maybe_download()

# All the inputs are numeric
feature_columns = [
    tf.feature_column.numeric_column(name)
    for name in iris_data.CSV_COLUMN_NAMES[:-1]]

# Build the estimator
est = tf.estimator.LinearClassifier(feature_columns,
                                    n_classes = 3)
# Train the estimator
batch_size = 100
est.train(
	steps=1000
	input_fn=lambda:iris_data.csv_input_fn(train_path, batch_size))

Estimator期望input_fn不帶任何參數。為了解除這個限制,我們使用lambda來捕獲參數並提供預期的接口。

總結

tf.data模塊提供了一組用於輕松讀取各種來源數據的類和函數。此外,tf.data具有簡單強大的方法來應用各種標准和自定義轉換。

現在你已經了解如何有效地將數據加載到Estimator中的基本想法。接下來考慮以下文檔:

  • 創建自定義估算器,演示如何構建自己的自定義估算器模型。
  • 低層次簡介,演示如何使用TensorFlow的低層API直接實驗tf.data.Datasets
  • 導入詳細了解數據集附加功能的數據。


免責聲明!

本站轉載的文章為個人學習借鑒使用,本站對版權不負任何法律責任。如果侵犯了您的隱私權益,請聯系本站郵箱yoyou2525@163.com刪除。



 
粵ICP備18138465號   © 2018-2025 CODEPRJ.COM