2019 年 1 月 28 日 - 由 Sara Robinson 发表
您是否曾经开始构建一个 ML 模型,却发现您不确定哪个模型架构将产生最佳结果?输入基于 TensorFlow 的 AdaNet 框架。使用 AdaNet,您可以将多个模型馈送到 AdaNet 的算法中,它将在训练过程中找到所有模型的最佳组合。我最近一直在玩它,并且对集成与单个模型相比的准确性印象深刻。…
AutoEnsembleEstimator
构建一个集成网络。您可以使用 AdaNet 构建任何类型的网络(图像、文本、结构化数据等)。对于这个例子,我将构建一个文本分类模型,根据他们写的一些句子来预测作者。除了 AdaNet 之外,我们还将使用以下工具来构建此模型import adanet
import numpy as np
import pandas as pd
import tensorflow as tf
import tensorflow_hub as hub
import urllib
from sklearn.preprocessing import LabelEncoder
urllib
下载 CSV,将其转换为 Pandas DataFrame,对数据进行混洗并进行预览urllib.request.urlretrieve('https://storage.googleapis.com/authors-training-data/data.csv', 'data.csv')
data = pd.read_csv('data.csv')
data = data.sample(frac=1) # Shuffles the data
data.head()
然后我们将将其分成训练集和测试集,使用 80% 的数据进行训练train_size = int(len(data) * .8)
train_text = data['text'][:train_size]
train_authors = data['author'][:train_size]
test_text = data['text'][train_size:]
test_authors = data['author'][train_size:]
标签是每个作者的字符串,我已经使用 Scikit Learn LabelEncoder 实用程序将它们编码为独热向量。我们可以在几行代码中完成此操作encoder = LabelEncoder()
encoder.fit_transform(np.array(train_authors))
train_encoded = encoder.transform(train_authors)
test_encoded = encoder.transform(test_authors)
num_classes = len(encoder.classes_)
ndim_embeddings = hub.text_embedding_column(
"ndim",
module_spec="https://tfhub.dev/google/nnlm-en-dim128/1", trainable=False
)
encoder_embeddings = hub.text_embedding_column(
"encoder",
module_spec="https://tfhub.dev/google/universal-sentence-encoder/2", trainable=False)
现在我们可以定义我们将馈送到 AdaNet 模型的两个 Estimator。由于这是一个分类问题,因此我们将对两者都使用 DNNEstimator
estimator_ndim = tf.contrib.estimator.DNNEstimator(
head=multi_class_head,
hidden_units=[64,10],
feature_columns=[ndim_embeddings]
)
estimator_encoder = tf.contrib.estimator.DNNEstimator(
head=multi_class_head,
hidden_units=[64,10],
feature_columns=[encoder_embeddings]
)
这里发生了什么?hidden_units
告诉 TensorFlow 我们的网络在每一层中将有多少个神经元。对于这些中的每一个,它将在第一层中有 64 个,在第二层中有 10 个。feature_columns
是模型特征的列表。在这个例子中,我们只有一个(书的句子)。AutoEnsembleEstimator
,这使得它非常简单。它将接收我创建的两个估算器,并通过对每个模型的预测进行平均来增量地创建集成。有关更多自定义选项,请查看 adanet.subnetwork
Builder 和 Generator 类。使用 AutoEnsembleEstimator
,我们可以将我们上面定义的两个模型都馈送到 candidate_pool
参数中的集成中model_dir=os.path.join('/path/to/model/dir')
multi_class_head = tf.contrib.estimator.multi_class_head(
len(encoder.classes_),
loss_reduction=tf.losses.Reduction.SUM_OVER_BATCH_SIZE
)
estimator = adanet.AutoEnsembleEstimator(
head=multi_class_head,
candidate_pool=[
estimator_ndim,
estimator_encoder
],
config=tf.estimator.RunConfig(
save_summary_steps=1000,
save_checkpoints_steps=1000,
model_dir=model_dir
),
max_iteration_steps=5000
)
那里有很多事情,让我们分解一下head
是 tf.contrib.estimator.Head
的一个实例,它告诉我们的模型如何为每个可能的集成计算损失和评估指标。AdaNet 将这些潜在的集成网络称为“候选”。有许多不同类型的头部(用于回归、多类分类等)。这里我们使用 multi_class_head
,因为我们的模型中存在超过 2 个可能的标签类别。对于将多个标签分配给一个特定输入的模型,我们将使用 multi_label_head
。config
设置了一些用于运行训练作业的参数:我们想要保存模型摘要和检查点的频率,以及 TF 应该将它们保存到的目录。请记住,如果您在 Colab 中训练模型,过于频繁地保存检查点可能会占用您的可用磁盘空间。max_iteration_steps
告诉 AdaNet 在单个 *迭代* 中执行多少个训练步骤。迭代是指对一组候选进行训练,因此此数字(以及我们将稍后定义的总训练步骤)告诉 AdaNet 生成新的集成候选的频率。train_and_evaluate
函数,它将同时运行训练和评估。为了设置它,我们需要编写训练和评估输入函数。输入函数负责将数据馈送到我们的模型中。我们将在输入函数中使用 tf.data
API。即使我们有两个使用不同特征列的独立模型,我们也可以将两个特征放在同一个字典中,这样我们只需要编写一个输入函数train_features = {
"ndim": train_text,
"encoder": train_text
}
def input_fn_train():
dataset = tf.data.Dataset.from_tensor_slices((train_features, train_authors))
dataset = dataset.repeat().shuffle(100).batch(64)
iterator = dataset.make_one_shot_iterator()
data, labels = iterator.get_next()
return data, labels
我们的评估特征和输入函数看起来非常相似eval_features = {
"ndim": test_text,
"encoder": test_reviews
}
def input_fn_eval():
dataset = tf.data.Dataset.from_tensor_slices((eval_features, test_authors))
dataset = dataset.batch(64)
iterator = dataset.make_one_shot_iterator()
data, labels = iterator.get_next()
return data, labels
我们快完成了!在训练之前,我们需要做的最后一件事是创建或训练和评估规范。您可以将其视为将所有内容连接在一起 - 由于我们正在一次运行训练和评估,因此这些规范将告诉我们的估算器为每个作业运行哪个输入函数train_spec = tf.estimator.TrainSpec(
input_fn=input_fn_train,
max_steps=40000
)
eval_spec=tf.estimator.EvalSpec(
input_fn=input_fn_eval,
steps=None,
start_delay_secs=10,
throttle_secs=10
)
还记得我们在上面定义的 max_iteration_steps
吗?TrainSpec
中的 max_steps
参数是指要训练的总步数。这意味着我们将总共有 8 次迭代(8 组集成候选)。现在该运行训练和评估了tf.estimator.train_and_evaluate(estimator, train_spec, eval_spec)
setup.py
config.yaml
trainer/
model.py
__init__.py
您可以随意命名 trainer
目录 - 这是我们将使用我们的模型上传到 ML Engine 的 Python 包的名称。__init__.py
是一个空文件,model.py
包含上面所有代码。setup.py
包含我们包的名称和版本,以及我们用于创建模型的任何 Python 包依赖项。config.yaml
是您指定任何与云相关的训练参数的地方。这些包括您是否将使用 GPU 或 TPU 进行训练,以及您需要多少个工作人员才能完成训练作业。所有配置选项都列在 这里。
我们将使用 `LatestExporter` 类导出我们的模型。为了创建一个导出器,我们需要定义一个 服务输入函数。这最初让我感到困惑,但它与我们定义的其他输入函数并没有太大区别。它应该返回两件事:当模型被服务时,它应该期望的输入格式,以及服务器应该期望的输入格式。在我们的模型中,它们是相同的,但在某些情况下,你可能希望在输入被送入模型之前对它们进行一些预处理。因为我们的输入格式相同,所以服务输入函数非常简单。
def serving_input_fn():
feature_placeholders = {
'ndim' : tf.placeholder(tf.string, [None]),
'encoder' : tf.placeholder(tf.string, [None])
}
return tf.estimator.export.ServingInputReceiver(feature_placeholders, feature_placeholders)
如果我们使用的 TF Hub 模块不允许我们直接传递原始文本,而是要求将文本转换为整数,我们的输入函数将在此处返回两个不同的对象。有了这个函数,我们可以定义我们的导出器。exporter = tf.estimator.LatestExporter('exporter', serving_input_fn, exports_to_keep=None)
为了调用 `export()`,我们还需要模型的最后一个检查点和该检查点的评估结果。我们可以通过以下代码获取它们。latest_ckpt = tf.train.latest_checkpoint(model_dir)
last_eval = estimator.evaluate(
input_fn_eval,
checkpoint_path=latest_ckpt
)
exporter.export(estimator, model_dir, latest_ckpt, last_eval, is_the_final_export=True)
哇!当它在 ML Engine 中运行时,它将保存我们的最终模型。export JOB_ID=unique_job_name
export JOB_DIR=gs://your/gcs/bucket/path
export PACKAGE_PATH=trainer/
export MODULE=trainer.model
export REGION=your_cloud_project_region
用你项目中特定的变量替换上面的字符串。然后你就可以使用以下 gcloud 命令进行训练。gcloud ml-engine jobs submit training $JOB_ID --package-path $PACKAGE_PATH --module-name $MODULE --job-dir $JOB_DIR --region $REGION --runtime-version 1.12 --python-version 3.5 --config config.yaml
如果这个命令执行正确,你应该在控制台中看到一条消息,表示你的任务已排队。你可以从命令行流式传输日志,或者在云控制台的 **任务** 标签页中进行导航。运行以下命令将 TensorBoard 指向云存储上的日志目录。
tensorboard --logdir gs://your/gcs/checkpoint/path
然后将你的浏览器指向 `localhost:6006` 来查看训练进度,并导航到标量标签页: 我承认:我一直避免使用 TensorBoard 直到现在(这么多图表可能让人望而生畏!)。但正如你很快就会看到的那样,TensorBoard 使我们更容易理解模型的性能,对于 AdaNet 来说它尤其有用。我们只关注准确性和 `adanet_loss` 图表。让我们从准确性开始,看看 `adanet_weighted_ensemble` 图表: 请记住,我们的模型每迭代 5000 步,这意味着每 5000 步 AdaNet 就会生成新的候选集成(除了第一次迭代,它只包含单个网络)。如果你将鼠标悬停在图表上,你可以看到每条线指的是哪次迭代和哪一个集成: 我们可以看到,在训练的这个阶段,第 7 次迭代的第二个集成(`t6_DNNEstimator1/eval`)具有最佳的准确性。TensorBoard 真正向我们展示了 AdaNet 组合模型的力量——随着训练的继续,集成准确性提高,并且远远高于单个网络自身的准确性(上面图表中左边的粉红色和浅蓝色线)。损失(或误差)图表显示了类似的趋势:随着 AdaNet 生成和训练新的集成,误差稳步下降。
因为如果你在没有对训练好的模型进行任何预测的情况下就离开了,那就太可惜了,所以让我们使用 ML Engine 的 `local predict` 从命令行对训练好的模型进行本地预测。我们只需要创建一个换行符分隔的 JSON 文件,其中包含我们想要预测的输入,并遵循与服务输入函数相同的格式。
以下是一个例子
{"encoder": "A strange land indeed! Could it be one with his native New England? Did Congress assemble from the Antipodes?", "ndim": "A strange land indeed! Could it be one with his native New England? Did Congress assemble from the Antipodes?"}
然后我们可以运行以下命令gcloud ml-engine local predict --model-dir=gs://path/to/saved_model.pb --json-instances=path/to/test.json
这是响应CLASS_IDS CLASSES PROBABILITIES
[1] [u'1'] [0.0043347785249352455, 0.8382837176322937, 0.12185576558113098, 0.025106186047196388, 0.010419543832540512]
这意味着我们的模型预测,这篇文章有 83% 的概率是由与我们标签数组中第一个索引相对应的作者撰写的(我们可以通过在上面记录 `encoder.classes_` 来获取这个索引),也就是丘吉尔。这是正确的!
2019 年 1 月 28 日 — 发布者 Sara Robinson
你是否曾经开始构建一个 ML 模型,但后来意识到你不确定哪种模型架构将产生最佳结果?介绍基于 TensorFlow 的 AdaNet 框架。使用 AdaNet,你可以将多个模型馈送到 AdaNet 的算法中,它将在训练过程中找到所有模型的最佳组合。我最近一直在玩它,并且已经 bee…