![]() |
![]() |
![]() |
![]() |
本教學課程提供如何將 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.StringLookup
和 tf.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)