加载 Pandas DataFrame

在 TensorFlow.org 上查看 在 Google Colab 中运行 在 GitHub 上查看源代码 下载笔记本

本教程提供了一些将 pandas DataFrame 加载到 TensorFlow 中的示例。

您将使用 UCI 机器学习资源库提供的 小型心脏病数据集。CSV 文件中包含数百行数据。每行描述一位患者,每列描述一个属性。您将使用这些信息来预测患者是否患有心脏病,这是一个二元分类任务。

使用 pandas 读取数据

import pandas as pd
import tensorflow as tf

SHUFFLE_BUFFER = 500
BATCH_SIZE = 2

下载包含心脏病数据集的 CSV 文件

csv_file = tf.keras.utils.get_file('heart.csv', 'https://storage.googleapis.com/download.tensorflow.org/data/heart.csv')

使用 pandas 读取 CSV 文件

df = pd.read_csv(csv_file)

数据看起来是这样的

df.head()
df.dtypes

您将构建模型来预测 target 列中包含的标签。

target = df.pop('target')

DataFrame 作为数组

如果您的数据具有统一的数据类型或 dtype,则可以在可以使用 NumPy 数组的任何地方使用 pandas DataFrame。这是因为 pandas.DataFrame 类支持 __array__ 协议,而 TensorFlow 的 tf.convert_to_tensor 函数接受支持该协议的对象。

从数据集中获取数值特征(暂时跳过分类特征)

numeric_feature_names = ['age', 'thalach', 'trestbps',  'chol', 'oldpeak']
numeric_features = df[numeric_feature_names]
numeric_features.head()

可以使用 DataFrame.values 属性或 numpy.array(df) 将 DataFrame 转换为 NumPy 数组。要将其转换为张量,请使用 tf.convert_to_tensor

tf.convert_to_tensor(numeric_features)

通常,如果可以使用 tf.convert_to_tensor 将对象转换为张量,则可以在可以传递 tf.Tensor 的任何地方传递该对象。

使用 Model.fit

可以将解释为单个张量的 DataFrame 直接用作 Model.fit 方法的参数。

以下是在数据集的数值特征上训练模型的示例。

第一步是规范化输入范围。为此,请使用 tf.keras.layers.Normalization 层。

在运行层之前,请务必调用 Normalization.adapt 方法来设置层的均值和标准差。

normalizer = tf.keras.layers.Normalization(axis=-1)
normalizer.adapt(numeric_features)

在 DataFrame 的前三行上调用该层,以可视化该层输出的示例。

normalizer(numeric_features.iloc[:3])

将规范化层用作简单模型的第一层

def get_basic_model():
  model = tf.keras.Sequential([
    normalizer,
    tf.keras.layers.Dense(10, activation='relu'),
    tf.keras.layers.Dense(10, activation='relu'),
    tf.keras.layers.Dense(1)
  ])

  model.compile(optimizer='adam',
                loss=tf.keras.losses.BinaryCrossentropy(from_logits=True),
                metrics=['accuracy'])
  return model

将 DataFrame 作为 x 参数传递给 Model.fit 时,Keras 会像对待 NumPy 数组一样对待 DataFrame。

model = get_basic_model()
model.fit(numeric_features, target, epochs=15, batch_size=BATCH_SIZE)

使用 tf.data

如果您想对具有统一 dtype 的 DataFrame 应用 tf.data 变换,则 Dataset.from_tensor_slices 方法将创建一个数据集,该数据集将遍历 DataFrame 的行。每行最初都是一个值向量。要训练模型,您需要 (inputs, labels) 对,因此请传递 (features, labels)Dataset.from_tensor_slices 将返回所需的切片对。

numeric_dataset = tf.data.Dataset.from_tensor_slices((numeric_features, target))

for row in numeric_dataset.take(3):
  print(row)
numeric_batches = numeric_dataset.shuffle(1000).batch(BATCH_SIZE)

model = get_basic_model()
model.fit(numeric_batches, epochs=15)

DataFrame 作为字典

当您开始处理异构数据时,就不再可能将 DataFrame 视为单个数组。TensorFlow 张量要求所有元素具有相同的 dtype

因此,在这种情况下,您需要开始将其视为列字典,其中每列都具有统一的 dtype。DataFrame 非常类似于数组字典,因此通常您只需要将 DataFrame 转换为 Python 字典即可。许多重要的 TensorFlow API 支持(嵌套)数组字典作为输入。

tf.data 输入管道可以很好地处理这种情况。所有 tf.data 操作会自动处理字典和元组。因此,要从 DataFrame 创建字典示例数据集,只需将其转换为字典,然后使用 Dataset.from_tensor_slices 对其进行切片。

numeric_dict_ds = tf.data.Dataset.from_tensor_slices((dict(numeric_features), target))

以下是该数据集的前三个示例。

for row in numeric_dict_ds.take(3):
  print(row)

带有 Keras 的字典

通常,Keras 模型和层期望单个输入张量,但这些类可以接受和返回字典、元组和张量的嵌套结构。这些结构称为“嵌套”(有关详细信息,请参阅 tf.nest 模块)。

您可以通过两种等效的方式编写接受字典作为输入的 Keras 模型。

1. 模型子类样式

您编写 tf.keras.Model(或 tf.keras.Layer)的子类。您直接处理输入并创建输出。

def stack_dict(inputs, fun=tf.stack):
    values = []
    for key in sorted(inputs.keys()):
      values.append(tf.cast(inputs[key], tf.float32))

    return fun(values, axis=-1)

此模型可以接受列字典或字典元素数据集进行训练。

model.fit(dict(numeric_features), target, epochs=5, batch_size=BATCH_SIZE)
numeric_dict_batches = numeric_dict_ds.shuffle(SHUFFLE_BUFFER).batch(BATCH_SIZE)
model.fit(numeric_dict_batches, epochs=5)

以下是前三个示例的预测。

model.predict(dict(numeric_features.iloc[:3]))

2. Keras 函数式样式

inputs = {}
for name, column in numeric_features.items():
  inputs[name] = tf.keras.Input(
      shape=(1,), name=name, dtype=tf.float32)

inputs
x = stack_dict(inputs, fun=tf.concat)

normalizer = tf.keras.layers.Normalization(axis=-1)
normalizer.adapt(stack_dict(dict(numeric_features)))

x = normalizer(x)
x = tf.keras.layers.Dense(10, activation='relu')(x)
x = tf.keras.layers.Dense(10, activation='relu')(x)
x = tf.keras.layers.Dense(1)(x)

model = tf.keras.Model(inputs, x)

model.compile(optimizer='adam',
              loss=tf.keras.losses.BinaryCrossentropy(from_logits=True),
              metrics=['accuracy'],
              run_eagerly=True)
tf.keras.utils.plot_model(model, rankdir="LR", show_shapes=True)

您可以像训练模型子类一样训练函数式模型。

model.fit(dict(numeric_features), target, epochs=5, batch_size=BATCH_SIZE)
numeric_dict_batches = numeric_dict_ds.shuffle(SHUFFLE_BUFFER).batch(BATCH_SIZE)
model.fit(numeric_dict_batches, epochs=5)

完整示例

如果您将异构 DataFrame 传递给 Keras,则每列可能需要唯一的预处理。您可以在 DataFrame 中直接进行此预处理,但为了使模型正常工作,输入始终需要以相同的方式进行预处理。因此,最佳方法是将预处理构建到模型中。 Keras 预处理层 涵盖了许多常见任务。

构建预处理头部

在此数据集中,原始数据中的一些“整数”特征实际上是分类索引。这些索引并非真正有序的数值(有关详细信息,请参阅 数据集描述)。由于这些索引是无序的,因此不适合直接馈送到模型中;模型会将其解释为有序的。要使用这些输入,您需要对其进行编码,无论是作为独热向量还是嵌入向量。字符串分类特征也是如此。

另一方面,二元特征通常不需要编码或规范化。

首先创建一个包含属于每个组的特征的列表。

binary_feature_names = ['sex', 'fbs', 'exang']
categorical_feature_names = ['cp', 'restecg', 'slope', 'thal', 'ca']

下一步是构建一个预处理模型,该模型将对每个输入应用适当的预处理并将结果连接起来。

本节使用 Keras 函数式 API 来实现预处理。您首先为 DataFrame 的每一列创建一个 tf.keras.Input

inputs = {}
for name, column in df.items():
  if type(column[0]) == str:
    dtype = tf.string
  elif (name in categorical_feature_names or
        name in binary_feature_names):
    dtype = tf.int64
  else:
    dtype = tf.float32

  inputs[name] = tf.keras.Input(shape=(), name=name, dtype=dtype)
inputs

对于每个输入,您将使用 Keras 层和 TensorFlow 操作应用一些变换。每个特征最初都是一批标量 (shape=(batch,))。每个特征的输出都应该是一批 tf.float32 向量 (shape=(batch, n))。最后一步将所有这些向量连接在一起。

二元输入

由于二元输入不需要任何预处理,因此只需添加向量轴,将其转换为 float32 并将其添加到预处理输入列表中。

preprocessed = []

for name in binary_feature_names:
  inp = inputs[name]
  inp = inp[:, tf.newaxis]
  float_value = tf.cast(inp, tf.float32)
  preprocessed.append(float_value)

preprocessed

数值输入

与上一节类似,您需要在使用这些数值输入之前,先将其通过 tf.keras.layers.Normalization 层。不同之处在于,这次它们作为字典输入。以下代码从 DataFrame 中收集数值特征,将它们堆叠在一起,并将这些特征传递给 Normalization.adapt 方法。

normalizer = tf.keras.layers.Normalization(axis=-1)
normalizer.adapt(stack_dict(dict(numeric_features)))

以下代码堆叠数值特征并将它们通过规范化层。

numeric_inputs = {}
for name in numeric_feature_names:
  numeric_inputs[name]=inputs[name]

numeric_inputs = stack_dict(numeric_inputs)
numeric_normalized = normalizer(numeric_inputs)

preprocessed.append(numeric_normalized)

preprocessed

分类特征

要使用分类特征,您首先需要将其编码为二元向量或嵌入。由于这些特征只包含少量类别,因此可以使用 output_mode='one_hot' 选项将输入直接转换为独热向量,该选项受 tf.keras.layers.StringLookuptf.keras.layers.IntegerLookup 层支持。

以下是如何使用这些层的示例。

vocab = ['a','b','c']
lookup = tf.keras.layers.StringLookup(vocabulary=vocab, output_mode='one_hot')
lookup(['c','a','a','b','zzz'])
vocab = [1,4,7,99]
lookup = tf.keras.layers.IntegerLookup(vocabulary=vocab, output_mode='one_hot')

lookup([-1,4,1])

要确定每个输入的词汇表,请创建一个层来将该词汇表转换为独热向量。

for name in categorical_feature_names:
  vocab = sorted(set(df[name]))
  print(f'name: {name}')
  print(f'vocab: {vocab}\n')

  if type(vocab[0]) is str:
    lookup = tf.keras.layers.StringLookup(vocabulary=vocab, output_mode='one_hot')
  else:
    lookup = tf.keras.layers.IntegerLookup(vocabulary=vocab, output_mode='one_hot')

  x = inputs[name][:, tf.newaxis]
  x = lookup(x)
  preprocessed.append(x)

组装预处理头部

此时,preprocessed 只是一个包含所有预处理结果的 Python 列表,每个结果的形状为 (batch_size, depth)

preprocessed

沿着 depth 轴连接所有预处理的特征,以便将每个字典示例转换为单个向量。该向量包含分类特征、数值特征和分类独热特征,按此顺序排列。

preprocessed_result = tf.concat(preprocessed, axis=-1)
preprocessed_result

现在,从该计算中创建一个模型,以便可以重复使用它。

preprocessor = tf.keras.Model(inputs, preprocessed_result)
tf.keras.utils.plot_model(preprocessor, rankdir="LR", show_shapes=True)

要测试预处理器,请使用 DataFrame.iloc 访问器来从 DataFrame 中切片第一个示例。然后将其转换为字典,并将该字典传递给预处理器。结果是一个包含二元特征、规范化数值特征和独热分类特征的单个向量,按此顺序排列。

preprocessor(dict(df.iloc[:1]))

创建和训练模型

现在构建模型的主体。使用与上一个示例中相同的配置:几个 Dense 线性整流层和一个用于分类的 Dense(1) 输出层。

body = tf.keras.Sequential([
  tf.keras.layers.Dense(10, activation='relu'),
  tf.keras.layers.Dense(10, activation='relu'),
  tf.keras.layers.Dense(1)
])

现在使用 Keras 函数式 API 将这两部分组合在一起。

inputs
x = preprocessor(inputs)
x
result = body(x)
result
model = tf.keras.Model(inputs, result)

model.compile(optimizer='adam',
                loss=tf.keras.losses.BinaryCrossentropy(from_logits=True),
                metrics=['accuracy'])

此模型期望一个输入字典。将数据传递给它的最简单方法是将 DataFrame 转换为字典,并将该字典作为 x 参数传递给 Model.fit

history = model.fit(dict(df), target, epochs=5, batch_size=BATCH_SIZE)

使用 tf.data 也可以。

ds = tf.data.Dataset.from_tensor_slices((
    dict(df),
    target
))

ds = ds.batch(BATCH_SIZE)
import pprint

for x, y in ds.take(1):
  pprint.pprint(x)
  print()
  print(y)
history = model.fit(ds, epochs=5)