使用特征列对结构化数据进行分类

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

本教程演示如何对结构化数据(例如 CSV 中的表格数据)进行分类。我们将使用 Keras 来定义模型,并使用 tf.feature_column 作为桥梁,将 CSV 中的列映射到用于训练模型的特征。本教程包含以下完整代码:

  • 使用 Pandas 加载 CSV 文件。
  • 使用 tf.data 构建输入管道,对行进行批处理和随机排序。
  • 使用特征列将 CSV 中的列映射到用于训练模型的特征。
  • 使用 Keras 构建、训练和评估模型。

数据集

我们将使用 PetFinder 数据集 的简化版本。CSV 中有数千行。每行描述一只宠物,每列描述一个属性。我们将使用这些信息来预测宠物被领养的速度。

以下是该数据集的描述。请注意,存在数值列和分类列。存在一个我们将在本教程中不使用的自由文本列。

描述 特征类型 数据类型
类型 动物类型(狗、猫) 分类 字符串
年龄 宠物的年龄 数值 整数
品种1 宠物的主要品种 分类 字符串
颜色1 宠物的颜色 1 分类 字符串
颜色2 宠物的颜色 2 分类 字符串
成熟尺寸 成熟时的尺寸 分类 字符串
毛发长度 毛发长度 分类 字符串
已接种疫苗 宠物已接种疫苗 分类 字符串
已绝育 宠物已绝育 分类 字符串
健康 健康状况 分类 字符串
费用 领养费用 数值 整数
描述 此宠物的简介 文本 字符串
照片数量 此宠物上传的照片总数 数值 整数
领养速度 领养速度 分类 整数

导入 TensorFlow 和其他库

pip install sklearn
import numpy as np
import pandas as pd

import tensorflow as tf

from tensorflow import feature_column
from tensorflow.keras import layers
from sklearn.model_selection import train_test_split
2023-10-27 05:23:51.727166: E external/local_xla/xla/stream_executor/cuda/cuda_dnn.cc:9261] Unable to register cuDNN factory: Attempting to register factory for plugin cuDNN when one has already been registered
2023-10-27 05:23:51.727210: E external/local_xla/xla/stream_executor/cuda/cuda_fft.cc:607] Unable to register cuFFT factory: Attempting to register factory for plugin cuFFT when one has already been registered
2023-10-27 05:23:51.728742: E external/local_xla/xla/stream_executor/cuda/cuda_blas.cc:1515] Unable to register cuBLAS factory: Attempting to register factory for plugin cuBLAS when one has already been registered

使用 Pandas 创建数据框

Pandas 是一个 Python 库,它提供了许多用于加载和处理结构化数据的有用工具。我们将使用 Pandas 从 URL 下载数据集,并将其加载到数据框中。

import pathlib

dataset_url = 'http://storage.googleapis.com/download.tensorflow.org/data/petfinder-mini.zip'
csv_file = 'datasets/petfinder-mini/petfinder-mini.csv'

tf.keras.utils.get_file('petfinder_mini.zip', dataset_url,
                        extract=True, cache_dir='.')
dataframe = pd.read_csv(csv_file)
Downloading data from http://storage.googleapis.com/download.tensorflow.org/data/petfinder-mini.zip
1668792/1668792 [==============================] - 0s 0us/step
dataframe.head()

创建目标变量

原始数据集中的任务是预测宠物被领养的速度(例如,在第一周、第一个月、前三个月等)。为了简化本教程,我们将将其转换为二元分类问题,并简单地预测宠物是否被领养。

修改标签列后,0 表示宠物未被领养,1 表示宠物被领养。

# In the original dataset "4" indicates the pet was not adopted.
dataframe['target'] = np.where(dataframe['AdoptionSpeed']==4, 0, 1)

# Drop un-used columns.
dataframe = dataframe.drop(columns=['AdoptionSpeed', 'Description'])

将数据框拆分为训练集、验证集和测试集

我们下载的数据集是一个单独的 CSV 文件。我们将将其拆分为训练集、验证集和测试集。

train, test = train_test_split(dataframe, test_size=0.2)
train, val = train_test_split(train, test_size=0.2)
print(len(train), 'train examples')
print(len(val), 'validation examples')
print(len(test), 'test examples')
7383 train examples
1846 validation examples
2308 test examples

使用 tf.data 创建输入管道

接下来,我们将使用 tf.data 包装数据框。这将使我们能够使用特征列作为桥梁,将 Pandas 数据框中的列映射到用于训练模型的特征。如果我们处理的是非常大的 CSV 文件(大到无法放入内存),我们将使用 tf.data 直接从磁盘读取它。本教程中没有涵盖这一点。

# A utility method to create a tf.data dataset from a Pandas Dataframe
def df_to_dataset(dataframe, shuffle=True, batch_size=32):
  dataframe = dataframe.copy()
  labels = dataframe.pop('target')
  ds = tf.data.Dataset.from_tensor_slices((dict(dataframe), labels))
  if shuffle:
    ds = ds.shuffle(buffer_size=len(dataframe))
  ds = ds.batch(batch_size)
  return ds
batch_size = 5 # A small batch sized is used for demonstration purposes
train_ds = df_to_dataset(train, batch_size=batch_size)
val_ds = df_to_dataset(val, shuffle=False, batch_size=batch_size)
test_ds = df_to_dataset(test, shuffle=False, batch_size=batch_size)

了解输入管道

现在我们已经创建了输入管道,让我们调用它来查看它返回的数据格式。我们使用了较小的批次大小,以使输出易于阅读。

for feature_batch, label_batch in train_ds.take(1):
  print('Every feature:', list(feature_batch.keys()))
  print('A batch of ages:', feature_batch['Age'])
  print('A batch of targets:', label_batch )
Every feature: ['Type', 'Age', 'Breed1', 'Gender', 'Color1', 'Color2', 'MaturitySize', 'FurLength', 'Vaccinated', 'Sterilized', 'Health', 'Fee', 'PhotoAmt']
A batch of ages: tf.Tensor([ 2 24  1  4  2], shape=(5,), dtype=int64)
A batch of targets: tf.Tensor([1 1 1 1 1], shape=(5,), dtype=int64)

我们可以看到,数据集返回一个字典,其中包含列名(来自数据框)映射到数据框中行中的列值。

演示几种类型的特征列

TensorFlow 提供了许多类型的特征列。在本节中,我们将创建几种类型的特征列,并演示它们如何转换数据框中的列。

# We will use this batch to demonstrate several types of feature columns
example_batch = next(iter(train_ds))[0]
# A utility method to create a feature column
# and to transform a batch of data
def demo(feature_column):
  feature_layer = layers.DenseFeatures(feature_column)
  print(feature_layer(example_batch).numpy())

数值列

特征列的输出成为模型的输入(使用上面定义的演示函数,我们将能够准确地看到数据框中的每一列是如何转换的)。数值列 是最简单的列类型。它用于表示实值特征。使用此列时,您的模型将接收来自数据框的列值,而不会进行任何更改。

photo_count = feature_column.numeric_column('PhotoAmt')
demo(photo_count)
WARNING:tensorflow:From /tmpfs/tmp/ipykernel_442856/2408317497.py:1: numeric_column (from tensorflow.python.feature_column.feature_column_v2) is deprecated and will be removed in a future version.
Instructions for updating:
Use Keras preprocessing layers instead, either directly or via the `tf.keras.utils.FeatureSpace` utility. Each of `tf.feature_column.*` has a functional equivalent in `tf.keras.layers` for feature preprocessing when training a Keras model.
[[2.]
 [1.]
 [2.]
 [3.]
 [1.]]

在 PetFinder 数据集中,数据框中的大多数列都是分类的。

分桶列

通常,您不希望将数字直接馈送到模型,而是希望根据数值范围将其值拆分为不同的类别。考虑表示一个人年龄的原始数据。与其将年龄表示为数值列,不如使用 分桶列 将年龄拆分为几个桶。请注意,下面的独热编码值描述了每行匹配的年龄范围。

age = feature_column.numeric_column('Age')
age_buckets = feature_column.bucketized_column(age, boundaries=[1, 3, 5])
demo(age_buckets)
WARNING:tensorflow:From /tmpfs/tmp/ipykernel_442856/4134348679.py:2: bucketized_column (from tensorflow.python.feature_column.feature_column_v2) is deprecated and will be removed in a future version.
Instructions for updating:
Use Keras preprocessing layers instead, either directly or via the `tf.keras.utils.FeatureSpace` utility. Each of `tf.feature_column.*` has a functional equivalent in `tf.keras.layers` for feature preprocessing when training a Keras model.
[[0. 0. 0. 1.]
 [0. 0. 0. 1.]
 [0. 0. 0. 1.]
 [0. 1. 0. 0.]
 [0. 0. 0. 1.]]

分类列

在此数据集中,类型表示为字符串(例如,“狗”或“猫”)。我们无法将字符串直接馈送到模型。相反,我们必须首先将它们映射到数值。分类词汇表列提供了一种将字符串表示为独热向量的方法(与您在上面看到的年龄桶类似)。可以使用 categorical_column_with_vocabulary_list 将词汇表作为列表传递,或者使用 categorical_column_with_vocabulary_file 从文件加载词汇表。

animal_type = feature_column.categorical_column_with_vocabulary_list(
      'Type', ['Cat', 'Dog'])

animal_type_one_hot = feature_column.indicator_column(animal_type)
demo(animal_type_one_hot)
WARNING:tensorflow:From /tmpfs/tmp/ipykernel_442856/1157957390.py:1: categorical_column_with_vocabulary_list (from tensorflow.python.feature_column.feature_column_v2) is deprecated and will be removed in a future version.
Instructions for updating:
Use Keras preprocessing layers instead, either directly or via the `tf.keras.utils.FeatureSpace` utility. Each of `tf.feature_column.*` has a functional equivalent in `tf.keras.layers` for feature preprocessing when training a Keras model.
WARNING:tensorflow:From /tmpfs/tmp/ipykernel_442856/1157957390.py:4: indicator_column (from tensorflow.python.feature_column.feature_column_v2) is deprecated and will be removed in a future version.
Instructions for updating:
Use Keras preprocessing layers instead, either directly or via the `tf.keras.utils.FeatureSpace` utility. Each of `tf.feature_column.*` has a functional equivalent in `tf.keras.layers` for feature preprocessing when training a Keras model.
[[0. 1.]
 [1. 0.]
 [1. 0.]
 [1. 0.]
 [1. 0.]]

嵌入列

假设每个类别不仅有几个可能的字符串,而是有数千个(甚至更多)值。由于多种原因,随着类别数量的增加,使用独热编码训练神经网络变得不可行。我们可以使用嵌入列来克服此限制。与其将数据表示为许多维度的独热向量,不如使用 嵌入列 将数据表示为低维稠密向量,其中每个单元格可以包含任何数字,而不仅仅是 0 或 1。嵌入的大小(在下面的示例中为 8)是一个必须调整的参数。

# Notice the input to the embedding column is the categorical column
# we previously created
breed1 = feature_column.categorical_column_with_vocabulary_list(
      'Breed1', dataframe.Breed1.unique())
breed1_embedding = feature_column.embedding_column(breed1, dimension=8)
demo(breed1_embedding)
WARNING:tensorflow:From /tmpfs/tmp/ipykernel_442856/689811331.py:5: embedding_column (from tensorflow.python.feature_column.feature_column_v2) is deprecated and will be removed in a future version.
Instructions for updating:
Use Keras preprocessing layers instead, either directly or via the `tf.keras.utils.FeatureSpace` utility. Each of `tf.feature_column.*` has a functional equivalent in `tf.keras.layers` for feature preprocessing when training a Keras model.
[[ 0.23340847 -0.22288084  0.41993982  0.48253137  0.14740573 -0.30386004
   0.30413502  0.14656945]
 [-0.23076059 -0.13627627 -0.05317891  0.6952521   0.46279088 -0.5734566
  -0.04382351 -0.5681491 ]
 [ 0.45319527  0.40937862 -0.21215594  0.4152906  -0.11821023 -0.20306908
   0.31819987 -0.0359318 ]
 [-0.23076059 -0.13627627 -0.05317891  0.6952521   0.46279088 -0.5734566
  -0.04382351 -0.5681491 ]
 [-0.23076059 -0.13627627 -0.05317891  0.6952521   0.46279088 -0.5734566
  -0.04382351 -0.5681491 ]]

哈希特征列

另一种表示具有大量值的分类列的方法是使用 categorical_column_with_hash_bucket。此特征列计算输入的哈希值,然后选择 hash_bucket_size 个桶中的一个来编码字符串。使用此列时,您不需要提供词汇表,并且可以选择使哈希桶的数量明显小于实际类别的数量,以节省空间。

breed1_hashed = feature_column.categorical_column_with_hash_bucket(
      'Breed1', hash_bucket_size=10)
demo(feature_column.indicator_column(breed1_hashed))
WARNING:tensorflow:From /tmpfs/tmp/ipykernel_442856/3606107843.py:1: categorical_column_with_hash_bucket (from tensorflow.python.feature_column.feature_column_v2) is deprecated and will be removed in a future version.
Instructions for updating:
Use Keras preprocessing layers instead, either directly or via the `tf.keras.utils.FeatureSpace` utility. Each of `tf.feature_column.*` has a functional equivalent in `tf.keras.layers` for feature preprocessing when training a Keras model.
[[0. 0. 0. 0. 0. 0. 0. 1. 0. 0.]
 [0. 0. 0. 0. 0. 0. 0. 0. 0. 1.]
 [0. 0. 0. 0. 0. 0. 0. 0. 1. 0.]
 [0. 0. 0. 0. 0. 0. 0. 0. 0. 1.]
 [0. 0. 0. 0. 0. 0. 0. 0. 0. 1.]]

交叉特征列

将特征组合成单个特征,更广为人知的是 特征交叉,使模型能够为每个特征组合学习单独的权重。在这里,我们将创建一个新的特征,它是年龄和类型的交叉。请注意,crossed_column 不会构建所有可能组合的完整表(这可能非常大)。相反,它由 hashed_column 支持,因此您可以选择表的尺寸。

crossed_feature = feature_column.crossed_column([age_buckets, animal_type], hash_bucket_size=10)
demo(feature_column.indicator_column(crossed_feature))
WARNING:tensorflow:From /tmpfs/tmp/ipykernel_442856/3676267184.py:1: crossed_column (from tensorflow.python.feature_column.feature_column_v2) is deprecated and will be removed in a future version.
Instructions for updating:
Use `tf.keras.layers.experimental.preprocessing.HashedCrossing` instead for feature crossing when preprocessing data to train a Keras model.
[[0. 0. 0. 0. 0. 0. 0. 0. 1. 0.]
 [1. 0. 0. 0. 0. 0. 0. 0. 0. 0.]
 [1. 0. 0. 0. 0. 0. 0. 0. 0. 0.]
 [0. 0. 0. 1. 0. 0. 0. 0. 0. 0.]
 [1. 0. 0. 0. 0. 0. 0. 0. 0. 0.]]

选择要使用的列

我们已经了解了如何使用几种类型的特征列。现在我们将使用它们来训练模型。本教程的目的是向您展示使用特征列所需的完整代码(例如机制)。我们任意选择了一些列来训练我们的模型。

feature_columns = []

# numeric cols
for header in ['PhotoAmt', 'Fee', 'Age']:
  feature_columns.append(feature_column.numeric_column(header))
# bucketized cols
age = feature_column.numeric_column('Age')
age_buckets = feature_column.bucketized_column(age, boundaries=[1, 2, 3, 4, 5])
feature_columns.append(age_buckets)
# indicator_columns
indicator_column_names = ['Type', 'Color1', 'Color2', 'Gender', 'MaturitySize',
                          'FurLength', 'Vaccinated', 'Sterilized', 'Health']
for col_name in indicator_column_names:
  categorical_column = feature_column.categorical_column_with_vocabulary_list(
      col_name, dataframe[col_name].unique())
  indicator_column = feature_column.indicator_column(categorical_column)
  feature_columns.append(indicator_column)
# embedding columns
breed1 = feature_column.categorical_column_with_vocabulary_list(
      'Breed1', dataframe.Breed1.unique())
breed1_embedding = feature_column.embedding_column(breed1, dimension=8)
feature_columns.append(breed1_embedding)
# crossed columns
age_type_feature = feature_column.crossed_column([age_buckets, animal_type], hash_bucket_size=100)
feature_columns.append(feature_column.indicator_column(age_type_feature))

创建特征层

现在我们已经定义了特征列,我们将使用 DenseFeatures 层将它们输入到 Keras 模型中。

feature_layer = tf.keras.layers.DenseFeatures(feature_columns)

之前,我们使用较小的批次大小来演示特征列的工作原理。我们使用更大的批次大小创建一个新的输入管道。

batch_size = 32
train_ds = df_to_dataset(train, batch_size=batch_size)
val_ds = df_to_dataset(val, shuffle=False, batch_size=batch_size)
test_ds = df_to_dataset(test, shuffle=False, batch_size=batch_size)

创建、编译和训练模型

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

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

model.fit(train_ds,
          validation_data=val_ds,
          epochs=10)
Epoch 1/10
WARNING: All log messages before absl::InitializeLog() is called are written to STDERR
I0000 00:00:1698384239.823402  443010 device_compiler.h:186] Compiled cluster using XLA!  This line is logged at most once for the lifetime of the process.
231/231 [==============================] - 8s 18ms/step - loss: 0.7295 - accuracy: 0.6794 - val_loss: 0.5710 - val_accuracy: 0.7443
Epoch 2/10
231/231 [==============================] - 2s 8ms/step - loss: 0.5687 - accuracy: 0.7081 - val_loss: 0.6360 - val_accuracy: 0.7329
Epoch 3/10
231/231 [==============================] - 2s 8ms/step - loss: 0.5303 - accuracy: 0.7181 - val_loss: 0.5047 - val_accuracy: 0.7275
Epoch 4/10
231/231 [==============================] - 2s 8ms/step - loss: 0.5037 - accuracy: 0.7303 - val_loss: 0.4990 - val_accuracy: 0.7514
Epoch 5/10
231/231 [==============================] - 2s 8ms/step - loss: 0.4980 - accuracy: 0.7320 - val_loss: 0.5052 - val_accuracy: 0.6777
Epoch 6/10
231/231 [==============================] - 2s 8ms/step - loss: 0.4927 - accuracy: 0.7378 - val_loss: 0.4964 - val_accuracy: 0.7210
Epoch 7/10
231/231 [==============================] - 2s 8ms/step - loss: 0.4835 - accuracy: 0.7399 - val_loss: 0.4912 - val_accuracy: 0.7438
Epoch 8/10
231/231 [==============================] - 2s 7ms/step - loss: 0.4775 - accuracy: 0.7417 - val_loss: 0.4991 - val_accuracy: 0.7178
Epoch 9/10
231/231 [==============================] - 2s 7ms/step - loss: 0.4719 - accuracy: 0.7440 - val_loss: 0.4956 - val_accuracy: 0.7205
Epoch 10/10
231/231 [==============================] - 2s 7ms/step - loss: 0.4669 - accuracy: 0.7519 - val_loss: 0.5177 - val_accuracy: 0.6907
<keras.src.callbacks.History at 0x7febf88ba1c0>
loss, accuracy = model.evaluate(test_ds)
print("Accuracy", accuracy)
73/73 [==============================] - 0s 5ms/step - loss: 0.5207 - accuracy: 0.7015
Accuracy 0.7014731168746948

后续步骤

了解有关对结构化数据进行分类的最佳方法是亲自动手尝试。我们建议您找到另一个数据集来处理,并使用类似于上面的代码训练一个模型来对其进行分类。为了提高准确性,请仔细考虑将哪些特征包含在您的模型中,以及如何表示它们。