正文
发布人:TensorFlow 团队
欢迎阅读介绍 TensorFlow 数据集和估算器的博客系列的第 3 部分。
第 1 部分
重点介绍了预制估算器,
第 2 部分
讨论了特征列。在今天的第
3 部分中,您将了解如何创建自定义估算器。需要特别注意的是,我们将演示在解决鸢尾花问题时,如何创建模仿
DNNClassifier
行为的自定义估算器。
如果您感觉不耐烦,尽请对比下面的完整程序:
-
此处
是通过预制 DNNClassifier 估算器实现的鸢尾花预测源代码。
-
此处
是通过自定义估算器实现的鸢尾花预测源代码。
预制与自定义对比
如图 1 所示,预制估算器是
tf.estimator.Estimator
基类的子类,而自定义估算器是
tf.estimator.Estimator:
的实例化
图 1.预制和自定义估算器都是估算器。
预制估算器
是全成品。但有时您需要更多地控制估算器的行为。自定义估算器应运而生。
您可以创建自定义估算器来仅执行某些操作。例如,如果需要以某种不同寻常的方式连接的隐藏层,则可以编写自定义估算器。如果想要计算模型的独特指标,也可以编写自定义估算器。从根本上讲,如果您需要针对特定问题优化的估算器,都可以编写自定义估算器。
模型函数
(
model_fn
) 可以实现您的模型。使用预制估算器与自定义估算器的唯一差别在于:
-
使用预制估算器时,已经有人为您写好了模型函数。
-
而使用自定义估算器,您必须自己编写模型函数。
您的模型函数可以实现多种算法,定义各种隐藏层和指标。像输入函数一样,所有模型函数都必须接受一组标准输入参数,并返回一组标准输出值。输入参数可以利用 Dataset API,模型函数可以利用 Layers API 和 Metrics API。
以预制估算器形式实现鸢尾花:快速温习
在演示如何以自定义估算器形式实现鸢尾花之前,请回想一下,我们在本系列的
第 1 部分
是如何以预制估算器形式实现鸢尾花的。在第 1 部分,我们为鸢尾花数据集简单地创建了一个完全连接的
深度
神经网络,方式是将
预制估算器
实例化,如下所示:
# Instantiate a deep neural network classifier.
classifier = tf.estimator.DNNClassifier(
feature_columns=feature_columns, # The input features to our model.
hidden_units=[10, 10], # Two layers, each with 10 neurons.
n_classes=3, # The number of output classes (three Iris species).
model_dir=PATH) # Pathname of directory where checkpoints, etc. are stored.
上述代码会创建一个具有下列特征的深度神经网络:
-
特征列的列表。(上面的代码段中未显示特征列的定义。)对于鸢尾花,特征列是四个输入特征的数字表示。
-
两个完全连接的层,每个都有 10 个神经元。
完全连接的层
(也称为
密集层
)连接到后继层中的每个神经元。
-
输出层包括一个三元素列表。该列表中的元素都是浮点值;这些值的总和必须为 1.0(这是一种概率分布)。
-
将用于存储训练的模型和各个检查点的目录 (
PATH
)。
图 2 显示了鸢尾花模型的输入层、隐藏层和输出层。为清楚起见,我们在每个隐藏层中只绘制了 4 个节点。
图 2.我们的鸢尾花实现包含四个特征、两个隐藏层和一个 logits 输出层。
下面我们来看看如何使用自定义估算器解决同样的鸢尾花问题。
输入函数
估算器框架的一项最大优势在于,不需要改变数据管道就可以试验不同的算法。因此,我们将重复利用
第 1 部分
中的大量输入函数:
def my_input_fn(file_path, repeat_count=1, shuffle_count=1):
def decode_csv(line):
parsed_line = tf.decode_csv(line, [[0.], [0.], [0.], [0.], [0]])
label = parsed_line[-1] # Last element is the label
del parsed_line[-1] # Delete last element
features = parsed_line # Everything but last elements are the features
d = dict(zip(feature_names, features)), label
return d
dataset = (tf.data.TextLineDataset(file_path) # Read text file
.skip(1) # Skip header row
.map(decode_csv, num_parallel_calls=4) # Decode each line
.cache() # Warning: Caches entire dataset, can cause out of memory
.shuffle(shuffle_count) # Randomize elems (1 == no operation)
.repeat(repeat_count) # Repeats dataset this # times
.batch(32)
.prefetch(1) # Make sure you always have 1 batch ready to serve
)
iterator = dataset.make_one_shot_iterator()
batch_features, batch_labels = iterator.get_next()
return batch_features, batch_labels
请注意,输入函数会返回下面两个值:
-
batch_features
,是一本字典。此字典的键是特征名称,值是特征的值。
-
batch_labels
,是一个用于批处理的标签值列表。
如需了解有关输入函数的完整详细信息,请参阅
第 1 部分
。
创建特征列
如本系列
第 2 部分
详述,您必须定义模型的特征列,以便指定每个特征的表示形式。无论是使用预制估算器还是自定义估算器,特征列的定义方式都一样。例如,以下代码会创建表示鸢尾花数据集中四个特征(全数值)的特征列:
feature_columns = [
tf.feature_column.numeric_column(feature_names[0]),
tf.feature_column.numeric_column(feature_names[1]),
tf.feature_column.numeric_column(feature_names[2]),
tf.feature_column.numeric_column(feature_names[3])
]
编写模型函数
我们现在准备为自定义估算器编写
model_fn
。首先是函数声明:
def my_model_fn(
features, # This is batch_features from input_fn
labels, # This is batch_labels from input_fn
mode): # Instance of tf.estimator.ModeKeys, see below
前两个参数是从输入函数返回的特征和标签;也就是说,
features
和
labels
是模型要使用的数据句柄。
mode
参数指明调用方是在请求训练、预测还是评估。
要实现典型的模型函数,必须执行以下操作:
定义模型的层
如果您的自定义估算器生成深度神经网络,那么您必须定义以下三个层:
使用 Layers API (
tf.layers
) 定义隐藏层和输出层。
如果自定义估算器生成线性模型,则只需生成一个层,我们将在下一部分介绍。
定义输入层
调用
tf.feature_column.input_layer
,为深度神经网络定义输入层。例如:
# Create the layer of input
input_layer = tf.feature_column.input_layer(features, feature_columns)
上面一行代码会创建输入层,通过输入函数读取
features
,并通过之前定义的
feature_columns
对它们进行筛选。要详细了解通过特征列表示数据的各种方法,请参阅
第 2 部分
。
要为线性模型创建输入层,请调用
tf.feature_column.linear_model
,而不是
tf.feature_column.input_layer
。由于线性模型没有隐藏层,因此,从
tf.feature_column.linear_model
返回的值将用作输入层和输出层。也就是说,此函数返回的值
是
预测。
建立隐藏层
如果要创建深度神经网络,您必须定义一个或多个隐藏层。Layers API 具有丰富的函数,可以定义所有类型的隐藏层,包括卷积层、池化层和退出层。对于鸢尾花,我们只需调用两次
tf.layers.Dense
来创建两个密集的隐藏层,每个层有 10 个神经元。“密集”的意思是第一个隐藏层中的每个神经元都连接到第二个隐藏层中的每个神经元。下面是相关代码:
# Definition of hidden layer: h1
# (Dense returns a Callable so we can provide input_layer as argument to it)
h1 = tf.layers.Dense(10, activation=tf.nn.relu)(input_layer)
# Definition of hidden layer: h2
# (Dense returns a Callable so we can provide h1 as argument to it)
h2 = tf.layers.Dense(10, activation=tf.nn.relu)(h1)
tf.layers.Dense
的
inputs
参数标识
前置
层。位于
h1
之前的层是输入层。
图 3.输入层馈入隐藏层 1。
h2
的前置层是
h1
。因此,这些层的串联看起来就像下图所示:
图 4.隐藏层 1 馈入隐藏层 2。
tf.layers.Dense
的第一个参数定义其输出神经元数量 - 这里是 10。
activation
参数定义激活函数 - 这里是
Relu
。
请注意,
tf.layers.Dense
提供许多其他功能,包括设置多个正则化参数。但为简便起见,我们简单地接受其他参数的默认值。另外,在查看
tf.layers
时,您可能会发现小写版本(如
tf.layers.dense
)。作为一般规则,您应使用以大写字母开头的类版本 (
tf.layers.Dense
)。
输出层
我们再次调用
tf.layers.Dense
来定义输出层:
# Output 'logits' layer is three numbers = probability distribution
# (Dense returns a Callable so we can provide h2 as argument to it)
logits = tf.layers.Dense(3)(h2)
请注意,输出层从
h2
接收其输入。因此,全部层现在按照下面所示方式连接:
图 5.隐藏层 2 馈入输出层。
在定义输出层时,
units
参数指定可能的输出值数量。因此,将
units
设置为
3
后,
tf.layers.Dense
函数会建立一个三元素的
logits 矢量。logits 矢量的每个单元都包含鸢尾花分别为山鸢尾、变色鸢尾或维吉尼亚鸢尾的可能性。
由于输出层是最后一个层,因此,对
tf.layers.Dense
的调用会忽略可选的
activation
参数。
实现训练、评估和预测
创建模型函数的最后一步是编写实现预测、评估和训练的分支代码。