用 tf.data 加载 CSV 数据

在 Tensorflow.org 上查看 在 Google Colab 运行 在 Github 上查看源代码 下载此 notebook

这篇教程通过一个示例展示了怎样将 CSV 格式的数据加载进 tf.data.Dataset

这篇教程使用的是泰坦尼克号乘客的数据。模型会根据乘客的年龄、性别、票务舱和是否独自旅行等特征来预测乘客生还的可能性。

设置

import functools

import numpy as np
import tensorflow as tf
import tensorflow_datasets as tfds
TRAIN_DATA_URL = "https://storage.googleapis.com/tf-datasets/titanic/train.csv"
TEST_DATA_URL = "https://storage.googleapis.com/tf-datasets/titanic/eval.csv"

train_file_path = tf.keras.utils.get_file("train.csv", TRAIN_DATA_URL)
test_file_path = tf.keras.utils.get_file("eval.csv", TEST_DATA_URL)
Downloading data from https://storage.googleapis.com/tf-datasets/titanic/train.csv
30874/30874 [==============================] - 0s 0us/step
Downloading data from https://storage.googleapis.com/tf-datasets/titanic/eval.csv
13049/13049 [==============================] - 0s 0us/step
# 让 numpy 数据更易读。
np.set_printoptions(precision=3, suppress=True)

加载数据

开始的时候,我们通过打印 CSV 文件的前几行来了解文件的格式。

head {train_file_path}
survived,sex,age,n_siblings_spouses,parch,fare,class,deck,embark_town,alone
0,male,22.0,1,0,7.25,Third,unknown,Southampton,n
1,female,38.0,1,0,71.2833,First,C,Cherbourg,n
1,female,26.0,0,0,7.925,Third,unknown,Southampton,y
1,female,35.0,1,0,53.1,First,C,Southampton,n
0,male,28.0,0,0,8.4583,Third,unknown,Queenstown,y
0,male,2.0,3,1,21.075,Third,unknown,Southampton,n
1,female,27.0,0,2,11.1333,Third,unknown,Southampton,n
1,female,14.0,1,0,30.0708,Second,unknown,Cherbourg,n
1,female,4.0,1,1,16.7,Third,G,Southampton,n

正如你看到的那样,CSV 文件的每列都会有一个列名。dataset 的构造函数会自动识别这些列名。如果你使用的文件的第一行不包含列名,那么需要将列名通过字符串列表传给 make_csv_dataset 函数的 column_names 参数。


CSV_COLUMNS = ['survived', 'sex', 'age', 'n_siblings_spouses', 'parch', 'fare', 'class', 'deck', 'embark_town', 'alone']

dataset = tf.data.experimental.make_csv_dataset(
     ...,
     column_names=CSV_COLUMNS,
     ...)

这个示例使用了所有的列。如果你需要忽略数据集中的某些列,创建一个包含你需要使用的列的列表,然后传给构造器的(可选)参数 select_columns


dataset = tf.data.experimental.make_csv_dataset(
  ...,
  select_columns = columns_to_use, 
  ...)

对于包含模型需要预测的值的列是你需要显式指定的。

LABEL_COLUMN = 'survived'
LABELS = [0, 1]

现在从文件中读取 CSV 数据并且创建 dataset。

(完整的文档,参考 tf.data.experimental.make_csv_dataset)

def get_dataset(file_path):
  dataset = tf.data.experimental.make_csv_dataset(
      file_path,
      batch_size=12, # 为了示例更容易展示,手动设置较小的值
      label_name=LABEL_COLUMN,
      na_value="?",
      num_epochs=1,
      ignore_errors=True)
  return dataset

raw_train_data = get_dataset(train_file_path)
raw_test_data = get_dataset(test_file_path)

dataset 中的每个条目都是一个批次,用一个元组(多个样本多个标签)表示。样本中的数据组织形式是以列为主的张量(而不是以行为主的张量),每条数据中包含的元素个数就是批次大小(这个示例中是 12)。

阅读下面的示例有助于你的理解。

examples, labels = next(iter(raw_train_data)) # 第一个批次
print("EXAMPLES: \n", examples, "\n")
print("LABELS: \n", labels)
EXAMPLES: 
 OrderedDict([('sex', <tf.Tensor: shape=(12,), dtype=string, numpy=
array([b'female', b'female', b'male', b'male', b'female', b'male',
       b'female', b'male', b'female', b'male', b'female', b'male'],
      dtype=object)>), ('age', <tf.Tensor: shape=(12,), dtype=float32, numpy=
array([28., 41., 28., 24., 63., 28., 28., 65., 34.,  9., 27., 30.],
      dtype=float32)>), ('n_siblings_spouses', <tf.Tensor: shape=(12,), dtype=int32, numpy=array([1, 0, 0, 0, 1, 0, 0, 0, 0, 5, 0, 0], dtype=int32)>), ('parch', <tf.Tensor: shape=(12,), dtype=int32, numpy=array([0, 5, 0, 0, 0, 0, 0, 1, 1, 2, 0, 0], dtype=int32)>), ('fare', <tf.Tensor: shape=(12,), dtype=float32, numpy=
array([82.171, 39.688,  7.896,  7.896, 77.958,  7.896,  7.75 , 61.979,
       23.   , 46.9  ,  7.925, 27.75 ], dtype=float32)>), ('class', <tf.Tensor: shape=(12,), dtype=string, numpy=
array([b'First', b'Third', b'Third', b'Third', b'First', b'Third',
       b'Third', b'First', b'Second', b'Third', b'Third', b'First'],
      dtype=object)>), ('deck', <tf.Tensor: shape=(12,), dtype=string, numpy=
array([b'unknown', b'unknown', b'unknown', b'unknown', b'D', b'unknown',
       b'unknown', b'B', b'unknown', b'unknown', b'unknown', b'C'],
      dtype=object)>), ('embark_town', <tf.Tensor: shape=(12,), dtype=string, numpy=
array([b'Cherbourg', b'Southampton', b'Southampton', b'Southampton',
       b'Southampton', b'Southampton', b'Queenstown', b'Cherbourg',
       b'Southampton', b'Southampton', b'Southampton', b'Cherbourg'],
      dtype=object)>), ('alone', <tf.Tensor: shape=(12,), dtype=string, numpy=
array([b'n', b'n', b'y', b'y', b'n', b'y', b'y', b'n', b'n', b'n', b'y',
       b'y'], dtype=object)>)]) 

LABELS: 
 tf.Tensor([1 0 0 0 1 0 0 0 1 0 1 0], shape=(12,), dtype=int32)

数据预处理

分类数据

CSV 数据中的有些列是分类的列。也就是说,这些列只能在有限的集合中取值。

使用 tf.feature_column API 创建一个 tf.feature_column.indicator_column 集合,每个 tf.feature_column.indicator_column 对应一个分类的列。

CATEGORIES = {
    'sex': ['male', 'female'],
    'class' : ['First', 'Second', 'Third'],
    'deck' : ['A', 'B', 'C', 'D', 'E', 'F', 'G', 'H', 'I', 'J'],
    'embark_town' : ['Cherbourg', 'Southhampton', 'Queenstown'],
    'alone' : ['y', 'n']
}
categorical_columns = []
for feature, vocab in CATEGORIES.items():
  cat_col = tf.feature_column.categorical_column_with_vocabulary_list(
        key=feature, vocabulary_list=vocab)
  categorical_columns.append(tf.feature_column.indicator_column(cat_col))
# 你刚才创建的内容
categorical_columns
[IndicatorColumn(categorical_column=VocabularyListCategoricalColumn(key='sex', vocabulary_list=('male', 'female'), dtype=tf.string, default_value=-1, num_oov_buckets=0)),
 IndicatorColumn(categorical_column=VocabularyListCategoricalColumn(key='class', vocabulary_list=('First', 'Second', 'Third'), dtype=tf.string, default_value=-1, num_oov_buckets=0)),
 IndicatorColumn(categorical_column=VocabularyListCategoricalColumn(key='deck', vocabulary_list=('A', 'B', 'C', 'D', 'E', 'F', 'G', 'H', 'I', 'J'), dtype=tf.string, default_value=-1, num_oov_buckets=0)),
 IndicatorColumn(categorical_column=VocabularyListCategoricalColumn(key='embark_town', vocabulary_list=('Cherbourg', 'Southhampton', 'Queenstown'), dtype=tf.string, default_value=-1, num_oov_buckets=0)),
 IndicatorColumn(categorical_column=VocabularyListCategoricalColumn(key='alone', vocabulary_list=('y', 'n'), dtype=tf.string, default_value=-1, num_oov_buckets=0))]

这将是后续构建模型时处理输入数据的一部分。

连续数据

连续数据需要标准化。

写一个函数标准化这些值,然后将这些值改造成 2 维的张量。

def process_continuous_data(mean, data):
  # 标准化数据
  data = tf.cast(data, tf.float32) * 1/(2*mean)
  return tf.reshape(data, [-1, 1])

现在创建一个数值列的集合。tf.feature_columns.numeric_column API 会使用 normalizer_fn 参数。在传参的时候使用 functools.partialfunctools.partial 由使用每个列的均值进行标准化的函数构成。

MEANS = {
    'age' : 29.631308,
    'n_siblings_spouses' : 0.545455,
    'parch' : 0.379585,
    'fare' : 34.385399
}

numerical_columns = []

for feature in MEANS.keys():
  num_col = tf.feature_column.numeric_column(feature, normalizer_fn=functools.partial(process_continuous_data, MEANS[feature]))
  numerical_columns.append(num_col)
# 你刚才创建的内容。
numerical_columns
[NumericColumn(key='age', shape=(1,), default_value=None, dtype=tf.float32, normalizer_fn=functools.partial(<function process_continuous_data at 0x7f74fc4f8c10>, 29.631308)),
 NumericColumn(key='n_siblings_spouses', shape=(1,), default_value=None, dtype=tf.float32, normalizer_fn=functools.partial(<function process_continuous_data at 0x7f74fc4f8c10>, 0.545455)),
 NumericColumn(key='parch', shape=(1,), default_value=None, dtype=tf.float32, normalizer_fn=functools.partial(<function process_continuous_data at 0x7f74fc4f8c10>, 0.379585)),
 NumericColumn(key='fare', shape=(1,), default_value=None, dtype=tf.float32, normalizer_fn=functools.partial(<function process_continuous_data at 0x7f74fc4f8c10>, 34.385399))]

这里使用标准化的方法需要提前知道每列的均值。如果需要计算连续的数据流的标准化的值可以使用 TensorFlow Transform

创建预处理层

将这两个特征列的集合相加,并且传给 tf.keras.layers.DenseFeatures 从而创建一个进行预处理的输入层。

preprocessing_layer = tf.keras.layers.DenseFeatures(categorical_columns+numerical_columns)

构建模型

preprocessing_layer 开始构建 tf.keras.Sequential

model = tf.keras.Sequential([
  preprocessing_layer,
  tf.keras.layers.Dense(128, activation='relu'),
  tf.keras.layers.Dense(128, activation='relu'),
  tf.keras.layers.Dense(1, activation='sigmoid'),
])

model.compile(
    loss='binary_crossentropy',
    optimizer='adam',
    metrics=['accuracy'])

训练、评估和预测

现在可以实例化和训练模型。

train_data = raw_train_data.shuffle(500)
test_data = raw_test_data
model.fit(train_data, epochs=20)
Epoch 1/20
WARNING:tensorflow:Layers in a Sequential model should only have a single input tensor. Received: inputs=OrderedDict([('sex', <tf.Tensor 'IteratorGetNext:8' shape=(None,) dtype=string>), ('age', <tf.Tensor 'IteratorGetNext:0' shape=(None,) dtype=float32>), ('n_siblings_spouses', <tf.Tensor 'IteratorGetNext:6' shape=(None,) dtype=int32>), ('parch', <tf.Tensor 'IteratorGetNext:7' shape=(None,) dtype=int32>), ('fare', <tf.Tensor 'IteratorGetNext:5' shape=(None,) dtype=float32>), ('class', <tf.Tensor 'IteratorGetNext:2' shape=(None,) dtype=string>), ('deck', <tf.Tensor 'IteratorGetNext:3' shape=(None,) dtype=string>), ('embark_town', <tf.Tensor 'IteratorGetNext:4' shape=(None,) dtype=string>), ('alone', <tf.Tensor 'IteratorGetNext:1' shape=(None,) dtype=string>)]). Consider rewriting this model with the Functional API.
WARNING:tensorflow:Layers in a Sequential model should only have a single input tensor. Received: inputs=OrderedDict([('sex', <tf.Tensor 'IteratorGetNext:8' shape=(None,) dtype=string>), ('age', <tf.Tensor 'IteratorGetNext:0' shape=(None,) dtype=float32>), ('n_siblings_spouses', <tf.Tensor 'IteratorGetNext:6' shape=(None,) dtype=int32>), ('parch', <tf.Tensor 'IteratorGetNext:7' shape=(None,) dtype=int32>), ('fare', <tf.Tensor 'IteratorGetNext:5' shape=(None,) dtype=float32>), ('class', <tf.Tensor 'IteratorGetNext:2' shape=(None,) dtype=string>), ('deck', <tf.Tensor 'IteratorGetNext:3' shape=(None,) dtype=string>), ('embark_town', <tf.Tensor 'IteratorGetNext:4' shape=(None,) dtype=string>), ('alone', <tf.Tensor 'IteratorGetNext:1' shape=(None,) dtype=string>)]). Consider rewriting this model with the Functional API.
53/53 [==============================] - 2s 11ms/step - loss: 0.5391 - accuracy: 0.7400
Epoch 2/20
53/53 [==============================] - 0s 5ms/step - loss: 0.4326 - accuracy: 0.8086
Epoch 3/20
53/53 [==============================] - 0s 5ms/step - loss: 0.4255 - accuracy: 0.8022
Epoch 4/20
53/53 [==============================] - 0s 5ms/step - loss: 0.4080 - accuracy: 0.8198
Epoch 5/20
53/53 [==============================] - 0s 5ms/step - loss: 0.4069 - accuracy: 0.8182
Epoch 6/20
53/53 [==============================] - 0s 5ms/step - loss: 0.3985 - accuracy: 0.8198
Epoch 7/20
53/53 [==============================] - 0s 5ms/step - loss: 0.3854 - accuracy: 0.8325
Epoch 8/20
53/53 [==============================] - 0s 5ms/step - loss: 0.3843 - accuracy: 0.8341
Epoch 9/20
53/53 [==============================] - 0s 5ms/step - loss: 0.3823 - accuracy: 0.8389
Epoch 10/20
53/53 [==============================] - 0s 5ms/step - loss: 0.3745 - accuracy: 0.8389
Epoch 11/20
53/53 [==============================] - 0s 5ms/step - loss: 0.3669 - accuracy: 0.8485
Epoch 12/20
53/53 [==============================] - 0s 5ms/step - loss: 0.3588 - accuracy: 0.8485
Epoch 13/20
53/53 [==============================] - 0s 5ms/step - loss: 0.3673 - accuracy: 0.8485
Epoch 14/20
53/53 [==============================] - 0s 5ms/step - loss: 0.3508 - accuracy: 0.8485
Epoch 15/20
53/53 [==============================] - 0s 5ms/step - loss: 0.3538 - accuracy: 0.8485
Epoch 16/20
53/53 [==============================] - 0s 5ms/step - loss: 0.3496 - accuracy: 0.8517
Epoch 17/20
53/53 [==============================] - 0s 5ms/step - loss: 0.3439 - accuracy: 0.8612
Epoch 18/20
53/53 [==============================] - 0s 5ms/step - loss: 0.3464 - accuracy: 0.8469
Epoch 19/20
53/53 [==============================] - 0s 5ms/step - loss: 0.3457 - accuracy: 0.8549
Epoch 20/20
53/53 [==============================] - 0s 5ms/step - loss: 0.3330 - accuracy: 0.8660
<keras.callbacks.History at 0x7f74fc4edbe0>

当模型训练完成的时候,你可以在测试集 test_data 上检查准确性。

test_loss, test_accuracy = model.evaluate(test_data)

print('\n\nTest Loss {}, Test Accuracy {}'.format(test_loss, test_accuracy))
WARNING:tensorflow:Layers in a Sequential model should only have a single input tensor. Received: inputs=OrderedDict([('sex', <tf.Tensor 'IteratorGetNext:8' shape=(None,) dtype=string>), ('age', <tf.Tensor 'IteratorGetNext:0' shape=(None,) dtype=float32>), ('n_siblings_spouses', <tf.Tensor 'IteratorGetNext:6' shape=(None,) dtype=int32>), ('parch', <tf.Tensor 'IteratorGetNext:7' shape=(None,) dtype=int32>), ('fare', <tf.Tensor 'IteratorGetNext:5' shape=(None,) dtype=float32>), ('class', <tf.Tensor 'IteratorGetNext:2' shape=(None,) dtype=string>), ('deck', <tf.Tensor 'IteratorGetNext:3' shape=(None,) dtype=string>), ('embark_town', <tf.Tensor 'IteratorGetNext:4' shape=(None,) dtype=string>), ('alone', <tf.Tensor 'IteratorGetNext:1' shape=(None,) dtype=string>)]). Consider rewriting this model with the Functional API.
22/22 [==============================] - 0s 5ms/step - loss: 0.4482 - accuracy: 0.8030


Test Loss 0.4481576979160309, Test Accuracy 0.8030303120613098

使用 tf.keras.Model.predict 推断一个批次或多个批次的标签。

predictions = model.predict(test_data)

# 显示部分结果
for prediction, survived in zip(predictions[:10], list(test_data)[0][1][:10]):
  print("Predicted survival: {:.2%}".format(prediction[0]),
        " | Actual outcome: ",
        ("SURVIVED" if bool(survived) else "DIED"))
WARNING:tensorflow:Layers in a Sequential model should only have a single input tensor. Received: inputs=OrderedDict([('sex', <tf.Tensor 'IteratorGetNext:8' shape=(None,) dtype=string>), ('age', <tf.Tensor 'IteratorGetNext:0' shape=(None,) dtype=float32>), ('n_siblings_spouses', <tf.Tensor 'IteratorGetNext:6' shape=(None,) dtype=int32>), ('parch', <tf.Tensor 'IteratorGetNext:7' shape=(None,) dtype=int32>), ('fare', <tf.Tensor 'IteratorGetNext:5' shape=(None,) dtype=float32>), ('class', <tf.Tensor 'IteratorGetNext:2' shape=(None,) dtype=string>), ('deck', <tf.Tensor 'IteratorGetNext:3' shape=(None,) dtype=string>), ('embark_town', <tf.Tensor 'IteratorGetNext:4' shape=(None,) dtype=string>), ('alone', <tf.Tensor 'IteratorGetNext:1' shape=(None,) dtype=string>)]). Consider rewriting this model with the Functional API.
22/22 [==============================] - 0s 4ms/step
Predicted survival: 90.08%  | Actual outcome:  SURVIVED
Predicted survival: 0.97%  | Actual outcome:  SURVIVED
Predicted survival: 0.98%  | Actual outcome:  DIED
Predicted survival: 10.06%  | Actual outcome:  SURVIVED
Predicted survival: 62.49%  | Actual outcome:  DIED
Predicted survival: 62.38%  | Actual outcome:  SURVIVED
Predicted survival: 11.18%  | Actual outcome:  SURVIVED
Predicted survival: 60.31%  | Actual outcome:  SURVIVED
Predicted survival: 9.72%  | Actual outcome:  DIED
Predicted survival: 21.93%  | Actual outcome:  DIED