作者: Dimitre Oliveira
创建日期 2023/07/01
最后修改日期 2025/01/03
描述: 如何使用 FeatureSpace 处理高级预处理用例。
此示例是对使用 FeatureSpace 进行结构化数据分类代码示例的扩展,在此示例中,我们将对其进行扩展,以涵盖 [keras.utils.FeatureSpace
](/api/utils/feature_space#featurespace-class) 预处理实用程序的更复杂用例,例如特征哈希、特征交叉、处理缺失值以及集成 Keras 预处理层 与 FeatureSpace。
总体任务仍然是使用包含数值特征、整数类别特征和字符串类别特征的数据进行结构化数据分类(也称为表格数据分类)。
我们的数据集由一家葡萄牙银行机构提供。它是一个 CSV 文件,包含 4119 行。每行包含有关基于电话的营销活动的信息,每列描述客户的属性。我们使用这些特征来预测客户是否订阅了该产品(银行定期存款),结果为“yes”(是)或“no”(否)。
以下是每个特征的描述
列 | 描述 | 特征类型 |
---|---|---|
年龄 | 客户的年龄 | 数值 |
工作 | 工作类型 | 类别 |
婚姻状况 | 婚姻状况 | 类别 |
教育 | 客户的教育水平 | 类别 |
默认 | 是否有信用违约? | 类别 |
住房 | 是否有住房贷款? | 类别 |
贷款 | 是否有个人贷款? | 类别 |
联系 | 联系沟通类型 | 类别 |
月份 | 最后一次联系的年份月份 | 类别 |
星期几 | 最后一次联系的星期几 | 类别 |
持续时间 | 最后一次联系的持续时间,以秒为单位 | 数值 |
活动 | 在此活动期间和为此客户执行的联系次数 | 数值 |
Pdays | 自上次从以前的活动联系客户以来经过的天数 | 数值 |
上一次 | 在此活动之前和为此客户执行的联系次数 | 数值 |
Poutcome | 上一次营销活动的结果 | 类别 |
Emp.var.rate | 就业变化率 | 数值 |
Cons.price.idx | 消费者价格指数 | 数值 |
Cons.conf.idx | 消费者信心指数 | 数值 |
Euribor3m | Euribor 3 个月利率 | 数值 |
Nr.employed | 员工人数 | 数值 |
Y | 客户是否订阅了定期存款? | 目标 |
关于特征 duration
的重要说明:此属性会严重影响输出目标(例如,如果 duration=0,则 y='no')。但是,在执行呼叫之前,持续时间是未知的。此外,在呼叫结束后,y 显然是已知的。因此,此输入仅应包含用于基准测试目的,如果目的是获得一个实际的预测模型,则应将其丢弃。因此,我们将删除它。
import os
os.environ["KERAS_BACKEND"] = "tensorflow"
import keras
from keras.utils import FeatureSpace
import pandas as pd
import tensorflow as tf
from pathlib import Path
from zipfile import ZipFile
让我们下载数据并将其加载到 Pandas 数据框中
data_url = "https://archive.ics.uci.edu/static/public/222/bank+marketing.zip"
data_zipped_path = keras.utils.get_file("bank_marketing.zip", data_url, extract=True)
keras_datasets_path = Path(data_zipped_path)
with ZipFile(f"{keras_datasets_path}/bank-additional.zip", "r") as zip:
# Extract files
zip.extractall(path=keras_datasets_path)
dataframe = pd.read_csv(
f"{keras_datasets_path}/bank-additional/bank-additional.csv", sep=";"
)
我们将创建一个新特征 previously_contacted
,以便能够演示一些有用的预处理技术,此特征基于 pdays
。根据数据集信息,如果 pdays = 999
,则表示之前未联系过该客户,因此让我们创建一个特征来捕获这一点。
# Droping `duration` to avoid target leak
dataframe.drop("duration", axis=1, inplace=True)
# Creating the new feature `previously_contacted`
dataframe["previously_contacted"] = dataframe["pdays"].map(
lambda x: 0 if x == 999 else 1
)
该数据集包含 4119 个样本,每个样本有 21 列(20 个特征加上目标标签),以下是一些样本的预览
print(f"Dataframe shape: {dataframe.shape}")
print(dataframe.head())
Dataframe shape: (4119, 21)
age job marital education default housing loan \
0 30 blue-collar married basic.9y no yes no
1 39 services single high.school no no no
2 25 services married high.school no yes no
3 38 services married basic.9y no unknown unknown
4 47 admin. married university.degree no yes no
contact month day_of_week ... pdays previous poutcome \
0 cellular may fri ... 999 0 nonexistent
1 telephone may fri ... 999 0 nonexistent
2 telephone jun wed ... 999 0 nonexistent
3 telephone jun fri ... 999 0 nonexistent
4 cellular nov mon ... 999 0 nonexistent
emp.var.rate cons.price.idx cons.conf.idx euribor3m nr.employed y \
0 -1.8 92.893 -46.2 1.313 5099.1 no
1 1.1 93.994 -36.4 4.855 5191.0 no
2 1.4 94.465 -41.8 4.962 5228.1 no
3 1.4 94.465 -41.8 4.959 5228.1 no
4 -0.1 93.200 -42.0 4.191 5195.8 no
previously_contacted
0 0
1 0
2 0
3 0
4 0
[5 rows x 21 columns]
“y” 列指示客户是否订阅了定期存款。
让我们将数据拆分为训练集和验证集
valid_dataframe = dataframe.sample(frac=0.2, random_state=0)
train_dataframe = dataframe.drop(valid_dataframe.index)
print(
f"Using {len(train_dataframe)} samples for training and "
f"{len(valid_dataframe)} for validation"
)
Using 3295 samples for training and 824 for validation
让我们为每个数据框生成 [tf.data.Dataset
](https://tensorflowcn.cn/api_docs/python/tf/data/Dataset) 对象,由于我们的目标列 y
是一个字符串,我们还需要将其编码为整数,以便能够使用它来训练我们的模型。为了实现这一点,我们将创建一个 StringLookup
层,将字符串“no”和“yes”分别映射为“0”和“1”。
label_lookup = keras.layers.StringLookup(
# the order here is important since the first index will be encoded as 0
vocabulary=["no", "yes"],
num_oov_indices=0,
)
def encode_label(x, y):
encoded_y = label_lookup(y)
return x, encoded_y
def dataframe_to_dataset(dataframe):
dataframe = dataframe.copy()
labels = dataframe.pop("y")
ds = tf.data.Dataset.from_tensor_slices((dict(dataframe), labels))
ds = ds.map(encode_label, num_parallel_calls=tf.data.AUTOTUNE)
ds = ds.shuffle(buffer_size=len(dataframe))
return ds
train_ds = dataframe_to_dataset(train_dataframe)
valid_ds = dataframe_to_dataset(valid_dataframe)
每个 Dataset
都会产生一个元组 (input, target)
,其中 input
是特征字典,target
是值 0
或 1
for x, y in dataframe_to_dataset(train_dataframe).take(1):
print(f"Input: {x}")
print(f"Target: {y}")
Input: {'age': <tf.Tensor: shape=(), dtype=int64, numpy=56>, 'job': <tf.Tensor: shape=(), dtype=string, numpy=b'admin.'>, 'marital': <tf.Tensor: shape=(), dtype=string, numpy=b'married'>, 'education': <tf.Tensor: shape=(), dtype=string, numpy=b'university.degree'>, 'default': <tf.Tensor: shape=(), dtype=string, numpy=b'no'>, 'housing': <tf.Tensor: shape=(), dtype=string, numpy=b'yes'>, 'loan': <tf.Tensor: shape=(), dtype=string, numpy=b'no'>, 'contact': <tf.Tensor: shape=(), dtype=string, numpy=b'cellular'>, 'month': <tf.Tensor: shape=(), dtype=string, numpy=b'jul'>, 'day_of_week': <tf.Tensor: shape=(), dtype=string, numpy=b'fri'>, 'campaign': <tf.Tensor: shape=(), dtype=int64, numpy=5>, 'pdays': <tf.Tensor: shape=(), dtype=int64, numpy=999>, 'previous': <tf.Tensor: shape=(), dtype=int64, numpy=0>, 'poutcome': <tf.Tensor: shape=(), dtype=string, numpy=b'nonexistent'>, 'emp.var.rate': <tf.Tensor: shape=(), dtype=float64, numpy=1.4>, 'cons.price.idx': <tf.Tensor: shape=(), dtype=float64, numpy=93.918>, 'cons.conf.idx': <tf.Tensor: shape=(), dtype=float64, numpy=-42.7>, 'euribor3m': <tf.Tensor: shape=(), dtype=float64, numpy=4.957>, 'nr.employed': <tf.Tensor: shape=(), dtype=float64, numpy=5228.1>, 'previously_contacted': <tf.Tensor: shape=(), dtype=int64, numpy=0>}
Target: 0
通常,我们的数据格式并非适合建模,这就是为什么大多数情况下我们需要对特征进行某种预处理,以使其与模型兼容或从中提取最多的信息以完成任务。我们需要在训练中执行此预处理步骤,但在推理时,我们还需要确保数据经过相同的过程,这正是像 FeatureSpace
这样的实用程序大放异彩的地方,我们可以定义一次所有预处理,并在系统的不同阶段重用它。
在这里,我们将了解如何使用 FeatureSpace
执行更复杂的转换及其灵活性,然后将所有内容组合到一个组件中,以预处理我们模型的数据。
FeatureSpace
实用程序通过使用 adapt()
函数从数据中学习如何处理数据,这需要一个仅包含特征的数据集,因此让我们创建一个数据集,并创建一个实用函数来实际展示预处理示例
train_ds_with_no_labels = train_ds.map(lambda x, _: x)
def example_feature_space(dataset, feature_space, feature_names):
feature_space.adapt(dataset)
for x in dataset.take(1):
inputs = {feature_name: x[feature_name] for feature_name in feature_names}
preprocessed_x = feature_space(inputs)
print(f"Input: {[{k:v.numpy()} for k, v in inputs.items()]}")
print(
f"Preprocessed output: {[{k:v.numpy()} for k, v in preprocessed_x.items()]}"
)
特征哈希是指将一组值哈希或编码为定义数量的箱,在这种情况下,我们有 campaign
(在此活动期间和为客户执行的联系次数),这是一个数值特征,可以采用不同的值范围,我们将将其哈希为 4 个箱,这意味着原始特征的任何可能值都将被放置在这些可能的 4 个箱之一中。此处的输出可以是单热编码向量或单个数字。
feature_space = FeatureSpace(
features={
"campaign": FeatureSpace.integer_hashed(num_bins=4, output_mode="one_hot")
},
output_mode="dict",
)
example_feature_space(train_ds_with_no_labels, feature_space, ["campaign"])
Input: [{'campaign': 1}]
Preprocessed output: [{'campaign': array([0., 1., 0., 0.], dtype=float32)}]
特征哈希 也可用于字符串特征。
feature_space = FeatureSpace(
features={
"education": FeatureSpace.string_hashed(num_bins=3, output_mode="one_hot")
},
output_mode="dict",
)
example_feature_space(train_ds_with_no_labels, feature_space, ["education"])
Input: [{'education': b'university.degree'}]
Preprocessed output: [{'education': array([0., 0., 1.], dtype=float32)}]
对于数值特征,我们可以使用 float_discretized
选项获得类似的行为,此选项和 integer_hashed
之间的主要区别在于,前者会对值进行分箱,同时保留一些数值关系(接近的值很可能被放置在同一个箱中),而后者(哈希)我们无法保证这些数字会被哈希到同一个箱中,这取决于哈希函数。
feature_space = FeatureSpace(
features={"age": FeatureSpace.float_discretized(num_bins=3, output_mode="one_hot")},
output_mode="dict",
)
example_feature_space(train_ds_with_no_labels, feature_space, ["age"])
Input: [{'age': 56}]
Preprocessed output: [{'age': array([0., 0., 1.], dtype=float32)}]
索引字符串特征本质上是指为其创建离散的数值表示,这对于字符串特征尤其重要,因为大多数模型仅接受数值特征。此转换会将字符串值放入不同的类别中。此处的输出可以是单热编码向量或单个数字。
请注意,通过指定 num_oov_indices=1
,我们在输出向量中为 OOV(词汇表外)值留下一个位置,这是在训练后处理缺失值或未见值的重要工具(在 adapt()
步骤中未见的值)
feature_space = FeatureSpace(
features={
"default": FeatureSpace.string_categorical(
num_oov_indices=1, output_mode="one_hot"
)
},
output_mode="dict",
)
example_feature_space(train_ds_with_no_labels, feature_space, ["default"])
Input: [{'default': b'no'}]
Preprocessed output: [{'default': array([0., 1., 0., 0.], dtype=float32)}]
我们还可以对整数特征执行特征索引,这对于某些类别特征被数字替换的数据集非常重要,例如 sex
或 gender
等特征,其中(1 和 0
)等值之间没有数值关系,它们只是不同的类别,此行为可以通过此转换完美捕获。
在此数据集中,我们可以使用我们创建的特征 previously_contacted
。对于这种情况,我们希望显式设置 num_oov_indices=0
,原因是对于该特征,我们只期望两个可能的值,其他任何值都将是错误的输入或数据创建问题,因此我们可能只是希望代码抛出错误,以便我们可以意识到问题并修复它。
feature_space = FeatureSpace(
features={
"previously_contacted": FeatureSpace.integer_categorical(
num_oov_indices=0, output_mode="one_hot"
)
},
output_mode="dict",
)
example_feature_space(train_ds_with_no_labels, feature_space, ["previously_contacted"])
Input: [{'previously_contacted': 0}]
Preprocessed output: [{'previously_contacted': array([1., 0.], dtype=float32)}]
通过交叉,我们可以在任意数量的混合类型特征之间进行特征交互,只要它们是类别特征,您可以想象一下,我们没有特征 {'age': 20} 和另一个特征 {'job': 'entrepreneur'},而是可以有 {'age_X_job': 20_entrepreneur},但使用 FeatureSpace
和交叉,我们可以对每个单独的特征以及特征交叉本身应用特定的预处理。此选项对于特定用例可能非常强大,在这里,这可能是一个很好的选择,因为年龄与工作相结合对于银行业领域可能具有不同的含义。
我们将交叉 age
和 job
,并将它们的组合输出哈希到大小为 8 的向量表示中。此处的输出可以是单热编码向量或单个数字。
有时,多个特征的组合可能会导致超大的特征空间,例如考虑将某人的邮政编码与其姓氏进行交叉,其可能性将成千上万,这就是为什么 crossing_dim
参数如此重要,它可以限制交叉特征的输出维度。
请注意,age
的 6 个箱和 job
的 12 个值的可能值组合将为 72,因此通过选择 crossing_dim = 8
,我们选择约束输出向量。
feature_space = FeatureSpace(
features={
"age": FeatureSpace.integer_hashed(num_bins=6, output_mode="one_hot"),
"job": FeatureSpace.string_categorical(
num_oov_indices=0, output_mode="one_hot"
),
},
crosses=[
FeatureSpace.cross(
feature_names=("age", "job"),
crossing_dim=8,
output_mode="one_hot",
)
],
output_mode="dict",
)
example_feature_space(train_ds_with_no_labels, feature_space, ["age", "job"])
Input: [{'age': 33}, {'job': b'admin.'}]
Preprocessed output: [{'age': array([0., 0., 1., 0., 0., 0.], dtype=float32)}, {'job': array([1., 0., 0., 0., 0., 0., 0., 0., 0., 0., 0., 0.], dtype=float32)}, {'age_X_job': array([0., 1., 0., 0., 0., 0., 0., 0.], dtype=float32)}]
要成为真正灵活且可扩展的特征,我们不能只依赖于这些预定义的转换,我们必须能够重用 Keras/TensorFlow 生态系统中的其他转换并自定义我们自己的转换,这就是 FeatureSpace
还被设计为与 Keras 预处理层 一起工作的原因,通过这种方式,我们可以使用框架提供的复杂数据转换,您甚至可以创建自己的自定义 Keras 预处理层并以相同的方式使用它。
这里我们将使用 keras.layers.TextVectorization
预处理层从我们的数据中创建 TF-IDF 特征。请注意,此特征并不是 TF-IDF 的真正用例,这仅用于演示目的。
custom_layer = keras.layers.TextVectorization(output_mode="tf_idf")
feature_space = FeatureSpace(
features={
"education": FeatureSpace.feature(
preprocessor=custom_layer, dtype="string", output_mode="float"
)
},
output_mode="dict",
)
example_feature_space(train_ds_with_no_labels, feature_space, ["education"])
Input: [{'education': b'high.school'}]
Preprocessed output: [{'education': array([0. , 0. , 1.6840783, 0. , 0. , 0. ,
0. , 0. , 0. ], dtype=float32)}]
FeatureSpace
既然我们知道如何在更复杂的用例中使用 FeatureSpace
,那么让我们选择对这项任务更有用的用例,并创建最终的 FeatureSpace
组件。
要配置每个特征应如何进行预处理,我们实例化一个 keras.utils.FeatureSpace
,并向其传递一个字典,该字典将特征名称映射到特征转换函数。
feature_space = FeatureSpace(
features={
# Categorical features encoded as integers
"previously_contacted": FeatureSpace.integer_categorical(num_oov_indices=0),
# Categorical features encoded as string
"marital": FeatureSpace.string_categorical(num_oov_indices=0),
"education": FeatureSpace.string_categorical(num_oov_indices=0),
"default": FeatureSpace.string_categorical(num_oov_indices=0),
"housing": FeatureSpace.string_categorical(num_oov_indices=0),
"loan": FeatureSpace.string_categorical(num_oov_indices=0),
"contact": FeatureSpace.string_categorical(num_oov_indices=0),
"month": FeatureSpace.string_categorical(num_oov_indices=0),
"day_of_week": FeatureSpace.string_categorical(num_oov_indices=0),
"poutcome": FeatureSpace.string_categorical(num_oov_indices=0),
# Categorical features to hash and bin
"job": FeatureSpace.string_hashed(num_bins=3),
# Numerical features to hash and bin
"pdays": FeatureSpace.integer_hashed(num_bins=4),
# Numerical features to normalize and bin
"age": FeatureSpace.float_discretized(num_bins=4),
# Numerical features to normalize
"campaign": FeatureSpace.float_normalized(),
"previous": FeatureSpace.float_normalized(),
"emp.var.rate": FeatureSpace.float_normalized(),
"cons.price.idx": FeatureSpace.float_normalized(),
"cons.conf.idx": FeatureSpace.float_normalized(),
"euribor3m": FeatureSpace.float_normalized(),
"nr.employed": FeatureSpace.float_normalized(),
},
# Specify feature cross with a custom crossing dim.
crosses=[
FeatureSpace.cross(feature_names=("age", "job"), crossing_dim=8),
FeatureSpace.cross(feature_names=("housing", "loan"), crossing_dim=6),
FeatureSpace.cross(
feature_names=("poutcome", "previously_contacted"), crossing_dim=2
),
],
output_mode="concat",
)
FeatureSpace
适应训练数据在我们开始使用 FeatureSpace
构建模型之前,我们必须使其适应训练数据。在 adapt()
期间,FeatureSpace
将会:
请注意,adapt()
应该在产生特征值字典的 tf.data.Dataset
上调用,不包含标签。
但首先让我们对数据集进行批处理
train_ds = train_ds.batch(32)
valid_ds = valid_ds.batch(32)
train_ds_with_no_labels = train_ds.map(lambda x, _: x)
feature_space.adapt(train_ds_with_no_labels)
此时,可以对原始特征值的字典调用 FeatureSpace
,并且因为我们设置了 output_mode="concat"
,它将为每个样本返回一个单独的连接向量,从而组合编码的特征和特征交叉。
for x, _ in train_ds.take(1):
preprocessed_x = feature_space(x)
print(f"preprocessed_x shape: {preprocessed_x.shape}")
print(f"preprocessed_x sample: \n{preprocessed_x[0]}")
preprocessed_x shape: (32, 77)
preprocessed_x sample:
[ 0. 0. 1. 0. -0.19560708 0.8937782
0.7249699 0. 1. 0. 0. 0.
1. 0. 0. 1. 0. 0.
0. 0. 0. 1. 0. 0.
0. 0.6566938 0.71815234 0. 0. 1.
0. 1. 0. 0. 0. 1.
1. 0. 0. 0. 1. 0.
0. 0. 0. 0. 0. 0.
0. 0. 0.33757654 0. 0. 1.
0. 1. 0. 0. -0.35691857 1.
0. 0. 0. 0. 0. 1.
0. 0. 0. 0. 0. 0.
1. 0. 0. 1. 0. ]
FeatureSpace
此时,我们可以选择保存我们的 FeatureSpace
组件,这有很多优点,例如在不同的实验中重用它,这些实验使用相同的模型,如果需要重新运行预处理步骤可以节省时间,主要用于模型部署,通过加载它可以确保您将应用相同的预处理步骤,无论设备或环境如何,这都是减少 训练/服务偏差 的好方法。
feature_space.save("myfeaturespace.keras")
FeatureSpace
作为 tf.data 管道的一部分进行预处理我们将选择异步使用我们的组件,方法是将其作为 tf.data 管道的一部分,正如在 之前的指南 中所述。这使得可以在数据到达模型之前,在 CPU 上进行异步并行数据预处理。通常,这在训练期间始终是正确的做法。
让我们创建一个预处理批次的训练和验证数据集
preprocessed_train_ds = train_ds.map(
lambda x, y: (feature_space(x), y), num_parallel_calls=tf.data.AUTOTUNE
).prefetch(tf.data.AUTOTUNE)
preprocessed_valid_ds = valid_ds.map(
lambda x, y: (feature_space(x), y), num_parallel_calls=tf.data.AUTOTUNE
).prefetch(tf.data.AUTOTUNE)
我们将利用我们的 FeatureSpace
组件来构建模型,因为我们希望模型与我们的预处理函数兼容,所以让我们使用 FeatureSpace
特征映射作为模型的输入。
encoded_features = feature_space.get_encoded_features()
print(encoded_features)
<KerasTensor shape=(None, 77), dtype=float32, sparse=False, name=keras_tensor_56>
此模型非常简单,仅用于演示目的,因此不要太关注其架构。
x = keras.layers.Dense(64, activation="relu")(encoded_features)
x = keras.layers.Dropout(0.5)(x)
output = keras.layers.Dense(1, activation="sigmoid")(x)
model = keras.Model(inputs=encoded_features, outputs=output)
model.compile(optimizer="adam", loss="binary_crossentropy", metrics=["accuracy"])
让我们训练模型 20 个 epoch。请注意,特征预处理是作为 tf.data 管道的一部分发生的,而不是作为模型的一部分。
model.fit(
preprocessed_train_ds, validation_data=preprocessed_valid_ds, epochs=10, verbose=2
)
Epoch 1/10
103/103 - 15s - 149ms/step - accuracy: 0.8753 - loss: 0.3639 - val_accuracy: 0.9102 - val_loss: 0.2747
Epoch 2/10
103/103 - 12s - 121ms/step - accuracy: 0.8965 - loss: 0.3058 - val_accuracy: 0.9078 - val_loss: 0.2716
Epoch 3/10
103/103 - 12s - 121ms/step - accuracy: 0.8947 - loss: 0.2972 - val_accuracy: 0.9053 - val_loss: 0.2712
Epoch 4/10
103/103 - 12s - 116ms/step - accuracy: 0.9002 - loss: 0.2877 - val_accuracy: 0.9102 - val_loss: 0.2677
Epoch 5/10
103/103 - 13s - 124ms/step - accuracy: 0.8974 - loss: 0.2815 - val_accuracy: 0.9041 - val_loss: 0.2688
Epoch 6/10
103/103 - 13s - 129ms/step - accuracy: 0.8986 - loss: 0.2917 - val_accuracy: 0.9066 - val_loss: 0.2658
Epoch 7/10
103/103 - 12s - 120ms/step - accuracy: 0.9029 - loss: 0.2779 - val_accuracy: 0.9053 - val_loss: 0.2670
Epoch 8/10
103/103 - 13s - 124ms/step - accuracy: 0.9011 - loss: 0.2809 - val_accuracy: 0.9090 - val_loss: 0.2660
Epoch 9/10
103/103 - 13s - 121ms/step - accuracy: 0.9008 - loss: 0.2748 - val_accuracy: 0.9041 - val_loss: 0.2689
Epoch 10/10
103/103 - 13s - 123ms/step - accuracy: 0.9038 - loss: 0.2768 - val_accuracy: 0.9053 - val_loss: 0.2674
<keras.src.callbacks.history.History at 0x723b293058d0>
现在,我们可以构建我们的推理模型(包括 FeatureSpace
)以基于原始特征值字典进行预测,如下所示:
FeatureSpace
首先,让我们加载我们之前保存的 FeatureSpace
,如果您训练了一个模型,但想在不同的时间(可能使用不同的设备或环境)进行推断,这可能非常方便。
loaded_feature_space = keras.saving.load_model("myfeaturespace.keras")
要构建推理模型,我们需要特征输入映射和预处理编码的 Keras 张量。
dict_inputs = loaded_feature_space.get_inputs()
encoded_features = loaded_feature_space.get_encoded_features()
print(encoded_features)
print(dict_inputs)
outputs = model(encoded_features)
inference_model = keras.Model(inputs=dict_inputs, outputs=outputs)
sample = {
"age": 30,
"job": "blue-collar",
"marital": "married",
"education": "basic.9y",
"default": "no",
"housing": "yes",
"loan": "no",
"contact": "cellular",
"month": "may",
"day_of_week": "fri",
"campaign": 2,
"pdays": 999,
"previous": 0,
"poutcome": "nonexistent",
"emp.var.rate": -1.8,
"cons.price.idx": 92.893,
"cons.conf.idx": -46.2,
"euribor3m": 1.313,
"nr.employed": 5099.1,
"previously_contacted": 0,
}
input_dict = {
name: keras.ops.convert_to_tensor([value]) for name, value in sample.items()
}
predictions = inference_model.predict(input_dict)
print(
f"This particular client has a {100 * predictions[0][0]:.2f}% probability "
"of subscribing a term deposit, as evaluated by our model."
)
<KerasTensor shape=(None, 77), dtype=float32, sparse=False, name=keras_tensor_99>
{'previously_contacted': <KerasTensor shape=(None, 1), dtype=int32, sparse=False, name=previously_contacted>, 'marital': <KerasTensor shape=(None, 1), dtype=string, sparse=False, name=marital>, 'education': <KerasTensor shape=(None, 1), dtype=string, sparse=False, name=education>, 'default': <KerasTensor shape=(None, 1), dtype=string, sparse=False, name=default>, 'housing': <KerasTensor shape=(None, 1), dtype=string, sparse=False, name=housing>, 'loan': <KerasTensor shape=(None, 1), dtype=string, sparse=False, name=loan>, 'contact': <KerasTensor shape=(None, 1), dtype=string, sparse=False, name=contact>, 'month': <KerasTensor shape=(None, 1), dtype=string, sparse=False, name=month>, 'day_of_week': <KerasTensor shape=(None, 1), dtype=string, sparse=False, name=day_of_week>, 'poutcome': <KerasTensor shape=(None, 1), dtype=string, sparse=False, name=poutcome>, 'job': <KerasTensor shape=(None, 1), dtype=string, sparse=False, name=job>, 'pdays': <KerasTensor shape=(None, 1), dtype=int32, sparse=False, name=pdays>, 'age': <KerasTensor shape=(None, 1), dtype=float32, sparse=False, name=age>, 'campaign': <KerasTensor shape=(None, 1), dtype=float32, sparse=False, name=campaign>, 'previous': <KerasTensor shape=(None, 1), dtype=float32, sparse=False, name=previous>, 'emp.var.rate': <KerasTensor shape=(None, 1), dtype=float32, sparse=False, name=emp.var.rate>, 'cons.price.idx': <KerasTensor shape=(None, 1), dtype=float32, sparse=False, name=cons.price.idx>, 'cons.conf.idx': <KerasTensor shape=(None, 1), dtype=float32, sparse=False, name=cons.conf.idx>, 'euribor3m': <KerasTensor shape=(None, 1), dtype=float32, sparse=False, name=euribor3m>, 'nr.employed': <KerasTensor shape=(None, 1), dtype=float32, sparse=False, name=nr.employed>}
/home/humbulani/tensorflow-env/env/lib/python3.11/site-packages/keras/src/models/functional.py:248: UserWarning: The structure of `inputs` doesn't match the expected structure.
Expected: {'age': 'age', 'campaign': 'campaign', 'cons.conf.idx': 'cons.conf.idx', 'cons.price.idx': 'cons.price.idx', 'contact': 'contact', 'day_of_week': 'day_of_week', 'default': 'default', 'education': 'education', 'emp.var.rate': 'emp.var.rate', 'euribor3m': 'euribor3m', 'housing': 'housing', 'job': 'job', 'loan': 'loan', 'marital': 'marital', 'month': 'month', 'nr.employed': 'nr.employed', 'pdays': 'pdays', 'poutcome': 'poutcome', 'previous': 'previous', 'previously_contacted': 'previously_contacted'}
Received: inputs={'age': 'Tensor(shape=(1,))', 'job': 'Tensor(shape=(1,))', 'marital': 'Tensor(shape=(1,))', 'education': 'Tensor(shape=(1,))', 'default': 'Tensor(shape=(1,))', 'housing': 'Tensor(shape=(1,))', 'loan': 'Tensor(shape=(1,))', 'contact': 'Tensor(shape=(1,))', 'month': 'Tensor(shape=(1,))', 'day_of_week': 'Tensor(shape=(1,))', 'campaign': 'Tensor(shape=(1,))', 'pdays': 'Tensor(shape=(1,))', 'previous': 'Tensor(shape=(1,))', 'poutcome': 'Tensor(shape=(1,))', 'emp.var.rate': 'Tensor(shape=(1,))', 'cons.price.idx': 'Tensor(shape=(1,))', 'cons.conf.idx': 'Tensor(shape=(1,))', 'euribor3m': 'Tensor(shape=(1,))', 'nr.employed': 'Tensor(shape=(1,))', 'previously_contacted': 'Tensor(shape=(1,))'}
warnings.warn(msg)
1/1 ━━━━━━━━━━━━━━━━━━━━ 0秒 1秒/步
1/1 ━━━━━━━━━━━━━━━━━━━━ 2秒 2秒/步
This particular client has a 10.85% probability of subscribing a term deposit, as evaluated by our model.