tf官网创建用户自定义estimator
如下图所示,预定义的Estimator是tf.estimator.Estimator类的子类,而自定义Estimators是tf.estimator.Estimator的一个实例:
Figure1. Pre-made and custom Estimators are all Estimators.
预定义Estimators是完全成熟的。 但有时候,你需要更多地控制Estimator的行为。 这就是自定义Estimators的用武之地。你可以创建一个自定义Estimator来做任何事情,如果您想以某种不寻常的方式连接隐藏层,请编写自定义Estimator。 如果想要为模型计算唯一metric,请编写自定义Estimator。 基本上,如果您想要针对特定问题优化Estimator,请编写自定义Estimator。
模型函数(或model_fn)实现ML算法。 使用预定义Estimator和自定义Estimator的唯一区别是:
你的模型函数可以实现各种算法,定义各种隐藏层和metrics。 与输入函数(input functions)一样,所有模型函数都必须接受一组标准输入参数并返回一组标准输出值。 正如输入函数可以利用dataset API一样,模型函数也可以利用Layers API和Metrics API。
让我们看看如何使用自定义Estimator解决Iris问题。 快速提醒 - 这是我们试图模仿的Iris模型的组织结构:
Figure2. Our implementation of Iris contains four features, two hidden layers, and a logits output layer.
我们的自定义Estimator实现使用与iris_data.py中预定义Estimator实现中相同的输入函数。即:
def train_input_fn(features, labels, batch_size):
"""An input function for training"""
# Convert the inputs to a Dataset.
dataset = tf.data.Dataset.from_tensor_slices((dict(features), labels))
# Shuffle, repeat, and batch the examples.
dataset = dataset.shuffle(1000).repeat().batch(batch_size)
# Return the read end of the pipeline.
return dataset.make_one_shot_iterator().get_next()
此输入函数构建一个输入pipeline,该管道可生成(yield)多个(features,labels)对,其中features是字典类型特征。
As detailed in the Premade Estimators and Feature Columns chapters, you must define your model’s feature columns to specify how the model should use each feature. Whether working with pre-made Estimators or custom Estimators, you define feature columns in the same fashion.
如预定义Estimators和Feature Columns章节中所述,您必须定义模型的feature columns,以指定模型应如何使用每个特征。 无论是使用预定义Estimators还是自定义Estimators,都可以以相同的方式定义feature columns。
以下代码为每个输入特征创建一个简单的numeric_column,指示输入特征的值应直接用作模型的输入:
# Feature columns describe how to use the input.
my_feature_columns = []
for key in train_x.keys():
my_feature_columns.append(tf.feature_column.numeric_column(key=key))
我们将使用的模型函数具有以下调用签名:
def my_model_fn(
features, # This is batch_features from input_fn
labels, # This is batch_labels from input_fn
mode, # An instance of tf.estimator.ModeKeys
params): # Additional configuration
前两个参数是从input function返回的批量特征和标签; 也就是说,features和labels是模型将使用的数据的句柄。 mode参数表明调用者是否正在请求训练(training),预测(predicting)或评估(evaluation)。
调用者可以将params传递给Estimator的构造函数。 传递给构造函数的任何参数依次传递给model_fn。 在custom_estimator.py中,以下行创建Estimator并设置params以配置模型。 此配置步骤与我们在Premade Estimators中配置tf.estimator.DNNClassifier的方式类似。
classifier = tf.estimator.Estimator(
model_fn=my_model_fn,
params={
'feature_columns': my_feature_columns,
# Two hidden layers of 10 nodes each.
'hidden_units': [10, 10],
# The model must choose between 3 classes.
'n_classes': 3,
})
要实现典型的模型函数,必须执行以下操作:
基本的深度神经网络模型必须定义以下三个部分:
输入层
一个或多个隐藏层
输出层
model_fn的第一行调用tf.feature_column.input_layer将特征字典和feature_columns转换为模型的输入,如下所示:
# Use `input_layer` to apply the feature columns.
net = tf.feature_column.input_layer(features, params['feature_columns'])
The preceding line applies the transformations defined by your feature columns, creating the model’s input layer.
上一行应用由features columns中定义的转换,从而创建模型的输入图层,如下:
Figure3. Input Layer
如果要创建深度神经网络,则必须定义一个或多个隐藏层。 Layers API提供了一组丰富的函数来定义所有类型的隐藏层,包括卷积层、池化和dropout层。 对于Iris,我们只需要调用tf.layers.dense来创建隐藏层,其维度由params [‘hidden_layers’]定义。 在dense层中,每个节点连接到前一层中的每个节点。 这是相关的代码:
# Build the hidden layers, sized according to the 'hidden_units' param.
for units in params['hidden_units']:
net = tf.layers.dense(net, units=units, activation=tf.nn.relu)
这里的变量net表示网络的当前顶层。 在第一次迭代期间,net表示输入层。 在每次循环迭代中,tf.layers.dense创建一个新层,使用变量net将前一层的输出作为其输入。
创建两个隐藏层后,我们的网络如下所示。 为简单起见,该图未显示每层中的所有单元。
请注意,tf.layers.dense提供了许多其他功能,包括设置多个正则化参数的功能。 但是,为了简单起见,我们将简单地接受其他参数的默认值。
我们将通过再次调用tf.layers.dense来定义输出层,这次没有激活函数:
# Compute logits (1 per class).
logits = tf.layers.dense(net, params['n_classes'], activation=None)
这里,net表示最后的隐藏层。 因此,完整的图层集现在连接如下:
The final hidden layer feeds into the output layer.
定义输出层时,units参数指定输出的神经元数量。 因此,通过将units设置为params [‘n_classes’],模型会为每个分类生成一个输出值。 输出向量的每个元素将包含分别针对Iris的相关类别计算的分数或“logit”:Setosa,Versicolor或Virginica。
稍后,这些logits将通过tf.nn.softmax函数转换为概率。
创建模型函数的最后一步是编写实现预测prediction,评估evaluation和训练training的分支代码。
只要有人调用Estimator的train,evaluate或predict方法,就会调用模型函数。 回想一下,模型函数的签名如下所示:
def my_model_fn(
features, # This is batch_features from input_fn
labels, # This is batch_labels from input_fn
mode, # An instance of tf.estimator.ModeKeys, see below
params): # Additional configuration
专注于第三个参数,mode。 如下表所示,当有人调用train,evaluate或predict时,Estimator框架使用如下设置的mode参数调用模型函数,如下所示:
Estimator method | Estimator Mode |
---|---|
tf.estimator.Estimator.train | tf.estimator.ModeKeys.TRAIN |
tf.estimator.Estimator.evaluate | tf.estimator.ModeKeys.EVAL |
tf.estimator.Estimator.predict | tf.estimator.ModeKeys.PREDICT |
例如,假设你实例化自定义Estimator以生成名为classifier的对象。 然后,您进行以下调用:
classifier = tf.estimator.Estimator(...)
classifier.train(input_fn=lambda: my_input_fn(FILE_TRAIN, True, 500))
Estimator框架然后调用模型函数,并将模式设置为ModeKeys.TRAIN。
您的模型函数必须提供代码来处理所有三个模式值。 对于每个mode,你的代码必须返回tf.estimator.EstimatorSpec的实例,其中包含调用者所需的信息。 我们来看看每种模式。
当调用Estimator的predict方法时,model_fn接收mode = ModeKeys.PREDICT。 在这种情况下,模型函数必须返回包含预测的tf.estimator.EstimatorSpec。
在进行预测之前,必须对模型进行训练。 训练后的模型存储在实例化Estimator时建立的model_dir目录中的磁盘上。
生成此模型预测的代码如下所示:
# Compute predictions.
predicted_classes = tf.argmax(logits, 1)
if mode == tf.estimator.ModeKeys.PREDICT:
predictions = {
'class_ids': predicted_classes[:, tf.newaxis],
'probabilities': tf.nn.softmax(logits),
'logits': logits,
}
return tf.estimator.EstimatorSpec(mode, predictions=predictions)
predictions字典包含模型在预测模式下运行时返回的所有内容。
预测包含以下三个键/值对:
我们通过tf.estimator.EstimatorSpec的predictions参数将该字典返回给调用者。 Estimator的tf.estimator.Estimator.predict方法将产生(yield)这些词典。
对于训练和评估,我们需要计算模型的损失,这是将进行优化的目标。
我们可以通过调用tf.losses.sparse_softmax_cross_entropy来计算损失。 当正确分类(在索引label处)的概率接近1.0时,此函数返回的值将最低约为0。 随着正确类别的概率降低,返回的损失值逐渐增大。
此函数返回整个批次的平均值。
# Compute loss.
loss = tf.losses.sparse_softmax_cross_entropy(labels=labels, logits=logits)
当调用Estimator的evaluate方法时,model_fn接收mode = ModeKeys.EVAL。 在这种情况下,模型函数必须返回包含模型损失以及可选的一个或多个指标metric的tf.estimator.EstimatorSpec。
虽然返回metrics是可选的,但大多数自定义Estimators确实至少返回一个指标。 TensorFlow提供Metrics模块tf.metrics来计算常用指标。为简洁起见,我们只返回准确性accuracy。 tf.metrics.accuracy函数将我们的预测与真实值进行比较,即与输入函数提供的标签进行比较。 tf.metrics.accuracy函数要求标签和预测具有相同的shape。 这是对tf.metrics.accuracy的调用:
# Compute evaluation metrics.
accuracy = tf.metrics.accuracy(labels=labels,
predictions=predicted_classes,
name='acc_op')
返回用于评估的tf.estimator.EstimatorSpec通常包含以下信息:
因此,我们将创建一个包含我们唯一指标的字典。 如果我们计算了其他指标,我们会将它们作为额外的键/值对添加到同一个字典中。 然后,我们将在tf.estimator.EstimatorSpec的eval_metric_ops参数中传递该字典。 这是代码:
metrics = {'accuracy': accuracy}
tf.summary.scalar('accuracy', accuracy[1])
if mode == tf.estimator.ModeKeys.EVAL:
return tf.estimator.EstimatorSpec(
mode, loss=loss, eval_metric_ops=metrics)
tf.summary.scalar将在TRAIN和EVAL模式下使TensorBoard可用。 (稍后会详细介绍)。
调用Estimator的train方法时,将使用mode = ModeKeys.TRAIN调用model_fn。 在这种情况下,模型函数必须返回包含loss和训练operation的EstimatorSpec。
构建训练operation需要优化器optimizer。 我们将使用tf.train.AdagradOptimizer,因为我们正在模仿DNNClassifier,它默认使用Adagrad。 tf.train包提供了许多其他优化器 - 可以随意尝试它们。
以下是构建优化器的代码:
optimizer = tf.train.AdagradOptimizer(learning_rate=0.1)
接下来,我们使用优化器的tf.train.Optimizer.minimize方法对我们之前计算的损失构建训练operation。
minimize方法也采用global_step参数。 TensorFlow使用此参数计算已处理的训练步骤数(以了解何时结束训练运行)。 此外,global_step对于TensorBoard图表正常工作至关重要。 只需调用tf.train.get_global_step并将结果传递给minimize的global_step参数即可。
这是训练模型的代码:
train_op = optimizer.minimize(loss, global_step=tf.train.get_global_step())
返回训练的tf.estimator.EstimatorSpec必须设置以下字段:
这是我们调用EstimatorSpec的代码:
return tf.estimator.EstimatorSpec(mode, loss=loss, train_op=train_op)
模型函数model function现在已完成。
通过Estimator基类实例化自定义Estimator方式如下所示:
# Build 2 hidden layer DNN with 10, 10 units respectively.
classifier = tf.estimator.Estimator(
model_fn=my_model_fn,
params={
'feature_columns': my_feature_columns,
# Two hidden layers of 10 nodes each.
'hidden_units': [10, 10],
# The model must choose between 3 classes.
'n_classes': 3,
})
这里params字典的作用与DNNClassifier的关键字参数相同; 也就是说,params字典允许你配置Estimator而无需修改model_fn中的代码。
使用我们的Estimator训练,评估和生成预测的其余代码与Premade Estimators章节中的相同。 例如,以下行将训练模型:
# Train the Model.
classifier.train(
input_fn=lambda:iris_data.train_input_fn(train_x, train_y, args.batch_size),
steps=args.train_steps)