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