使用特徵欄位分類結構化資料

在 TensorFlow.org 上檢視 在 Google Colab 中執行 在 GitHub 上檢視原始碼 下載筆記本

本教學課程示範如何分類結構化資料 (例如 CSV 中的表格資料)。我們將使用 Keras 定義模型,並使用 tf.feature_column 作為橋樑,將 CSV 中的欄位對應至用於訓練模型的特徵。本教學課程包含以下完整程式碼:

  • 使用 Pandas 載入 CSV 檔案。
  • 建構輸入管道,以使用 tf.data 批次處理和隨機排序資料列。
  • 使用特徵欄位,將 CSV 中的欄位對應至用於訓練模型的特徵。
  • 使用 Keras 建構、訓練及評估模型。

資料集

我們將使用 PetFinder 資料集的簡化版本。CSV 中有數千列。每一列描述一隻寵物,每一欄描述一個屬性。我們將使用這些資訊來預測寵物被領養的速度。

以下是此資料集的描述。請注意,其中同時包含數值和類別欄位。其中有一個自由文字欄位,我們在本教學課程中不會使用。

欄位 說明 特徵類型 資料類型
類型 動物類型 (狗、貓) 類別型 字串
年齡 寵物的年齡 數值型 整數
Breed1 寵物的主要品種 類別型 字串
Color1 寵物的顏色 1 類別型 字串
Color2 寵物的顏色 2 類別型 字串
MaturitySize 成熟時的大小 類別型 字串
FurLength 毛髮長度 類別型 字串
Vaccinated 寵物已接種疫苗 類別型 字串
Sterilized 寵物已絕育 類別型 字串
Health 健康狀況 類別型 字串
Fee 領養費用 數值型 整數
說明 個人檔案描述 文字 字串
PhotoAmt 此寵物上傳的照片總數 數值型 整數
AdoptionSpeed 領養速度 分類 整數

匯入 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 從網址下載資料集,並將其載入至資料框架中。

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_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

後續步驟

深入瞭解如何分類結構化資料的最佳方式是親自嘗試。我們建議您尋找另一個要處理的資料集,並使用類似於上述程式碼的程式碼訓練模型以分類該資料集。若要提高準確性,請仔細思考要在模型中納入哪些特徵,以及應如何表示這些特徵。