載入 pandas DataFrame

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

本教學課程提供如何將 pandas DataFrames 載入 TensorFlow 的範例。

您將使用 UCI Machine Learning Repository 提供的少量 心臟疾病資料集。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 可以使用 DataFrame.values 屬性或 numpy.array(df) 轉換為 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 會將 DataFrame 視為 NumPy 陣列。

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

搭配 tf.data

如果您想要將 tf.data 轉換套用至一致 dtype 的 DataFrame,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)