Estimator是Tensorflow提供的一种高阶API,在数据输入输出、模型构建、模型配置、模型保存等方面都进行了封装,隐藏了传统静态图的运行流程和底层细节,便于研究人员更专注于模型本身,本文即对Estimator的用法进行简要介绍。

Estimators的好处

  • 模型内部有并行优化,因此无需修改代码就可以直接将运行在本地的代码部署到服务器/GPU集群/TPU上;
  • Estimators对模型各部分进行了封装,因此便于代码重用;
  • 代码可读性更好,隐藏了tensorflow底层的实现细节。

如何使用Pre-made Estimators

1、构造数据输入函数input_fn

  • input_fn的任务:将从文件中读入的feature和label封装起来,转换为dataset的形式(或者是dictionary & label 的形式,本质是一样的,dataset就是对其的封装),以此来作为Estimator的输入数据。
  • 通常训练、评估和测试集各构造一个函数:train_input_fn, eval_input_fn, test_input_fn,也可选择直接在函数内部读取文件,返回一个dataset即可。
  • 对于feature_column+dataset这种情况,其实相当于是feature为axis0, example为axis1,和传统的形式相反。
    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    # dataset的形式
    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.
    return dataset.shuffle(1000).repeat().batch(batch_size)

    # dictionary & label的形式
    def input_fn(dataset):
    # manipulate dataset, extracting the feature dict and the label
    return feature_dict, label
    # 输出1:训练/测试数据(dictionary),key(str): feature name, value(tensor): feature data;
    # 输出2:标签label(tensor):包含所有数据的标签。

2、构造feature columns

  • feature columns是tensorflow的一种描述数据的格式,包含了数据/feature的名称、类型和一些预处理操作,Estimator会根据feature columns的描述来决定如何处理传入的数据(dataset)。
  • feature columns类型:list,里面元素类型:tf.feature_column
    1
    2
    3
    4
    # Feature columns describe how to use the input.
    my_feature_columns = []
    for feature_key in train_features.keys():
    my_feature_columns.append(tf.feature_column.numeric_column(key=feature_key))

3、初始化Pre-made Estimator

  • 这一步主要是传入feature columns、以及必要的模型配置信息。
    1
    2
    3
    4
    5
    6
    7
    # Build a DNN with 2 hidden layers and 10 nodes in each hidden layer.
    classifier = tf.estimator.DNNClassifier(
    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)

4、模型训练、评估和预测

  • 配置完estimator后,之后就是调用train/eval/predict方法来进行训练、评估和预测模型。
  • input_fn:这是train/eval/predict三个方法都要传入的参数,要传入输入函数的闭包形式。
  • 闭包函数的参数没有具体要求,但返回值要求是dataset类型,或者(dict, label) tuple类型。
  • 此外对于input_fn的输入参数,如果指定了paramsconfig这两个参数,则会自动接收Estimator构造函数传入的paramsconfig(或Estimator内部构造的paramsconfig实例),可以在函数内部直接使用。

    训练模型

  • input_fn参数要求传入是无参的函数句柄,因此使用lambdab表达式来构造;
  • steps规定了函数训练多少步停止(可以由数据大小、epoch数和batch size算出),在eval过程中不用指定steps。
    1
    2
    3
    classifier.train(
    input_fn=lambda:train_input_fn(features, labels, batch_size),
    steps=train_steps)

评估模型

  • The eval_result dictionary contains the average_loss (mean loss per sample), the loss (mean loss per mini-batch) and the value of the estimator’s global_step (the number of training iterations it underwent).
    1
    2
    3
    4
    5
    6
    eval_result = classifier.evaluate(
    input_fn=lambda:iris_data.eval_input_fn(test_x, test_y, args.batch_size))

    print('\nTest set accuracy: {accuracy:0.3f}\n'.format(**eval_result))
    # output
    # Test set accuracy: 0.967

使用模型预测

  • Estimator.predict()returns a Python iterable, yielding a dictionary of prediction results for each example.
  • 而prediction results具体是什么结构则要看模型(model_fn)中是怎么定义的。本例中即为dict类型,key包括class_ids, probabilities等,具体情况具体分析。
    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    19
    20
    21
    22
    23
    24
    25
    expected = ['Setosa', 'Versicolor', 'Virginica']
    predict_x = {
    'SepalLength': [5.1, 5.9, 6.9],
    'SepalWidth': [3.3, 3.0, 3.1],
    'PetalLength': [1.7, 4.2, 5.4],
    'PetalWidth': [0.5, 1.5, 2.1],
    }

    predictions = classifier.predict(
    input_fn=lambda:eval_input_fn(predict_x, batch_size=args.batch_size))

    template = ('\nPrediction is "{}" ({:.1f}%), expected "{}"')

    for pred_dict, expec in zip(predictions, expected):
    class_id = pred_dict['class_ids'][0]
    probability = pred_dict['probabilities'][class_id]

    print(template.format(iris_data.SPECIES[class_id],
    100 * probability, expec))
    # output
    # Prediction is "Setosa" (99.6%), expected "Setosa"

    # Prediction is "Versicolor" (99.8%), expected "Versicolor"

    # Prediction is "Virginica" (97.9%), expected "Virginica"

如何自己构造Estimators

  • 自己构造Estimator和Pre-made Estimators的不同就在于:1. model function(其定义了模型的结构和参数);2. metrics & loss 函数(用于评价模型并对模型进行优化)。
  • 使用Pre-made Estimators时别人已经帮你实现好了,而自己构造Estimators则需要先自己实现model function,然后再传入Estimator中即可(loss函数一般在model function中实现)。
  • model function中要实现训练、分析、预测三个部分的代码(可通过if-else分支来实现),然后每个分支都要返回一个tf.estimator.EstimatorSpec实例,其是模型的一种评价指标,用来训练模型、分析模型结果。train/eval/predict三个过程对应的EstimatorSpec也不一样,下面来分别分析。

    初始化Estimator

    1
    2
    3
    4
    5
    6
    7
    tf.estimator.Estimator.__init__(
    model_fn,
    model_dir=None,
    config=None,
    params=None,
    warm_start_from=None
    )
  • model_fn:Estimator的核心逻辑部分,定义了模型的结构、train/eval/test流程等,model_fn需要传入函数的闭包,且函数的输入输出接口必须遵循固定格式(Estimator内部要与其相互):
    model_fn(feature, labels, mode, params, config),return:tf.estimator.EstimatorSpec。具体解释见下文。

  • config:模型配置信息(如多少步打印一次、gou/tpu设置、),类型为tf.estimator.RunConfig。对于一些特定的Estimator类型(如TPUEstimator),其内部会自己使用这个对象,因此相当于直接传入了模型的配置信息;一般地会直接传入model_fn中,用户可以自己使用。
  • params:模型的一些超参数设置,没有特定结构,用户可完全自己定义,只用定义成dict形式即可,key为参数名,value为参数的值(python内置类型)。
  • model_dir:指定了训练模型的保存目录,也被用来从目录中加载模型到estimator中以继续训练。因为还可通过config.model_dir来设置,所以一般不用管。
  • 对于model_fn的参数,都是estimatior内部自动传入的,我们只用了解其含义和如何使用即可。
    • feature:This is batch_features from input_fn
    • labels:This is batch_labels from input_fn
    • mode:An instance of tf.estimator.ModeKeys
    • params:直接从Estimator的构造函数中传入的。
    • config:直接从Estimator的构造函数中传入的。
      可以直接把所有参数都固定写上?,这样就算Estimator中没有用到也只是传入了None,没什么影响。
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
def my_model_fn(features, labels, mode, params):
if mode == tf.estimator.ModeKeys.PREDICT:
pass
elif mode == tf.estimator.ModeKeys.EVAL:
pass
elif mode == tf.estimator.ModeKeys.TARAIN:
pass

# 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,
},
config=model_config
)

Estimator的返回值

  • model function中要实现train、eval、predict三个部分的代码,一般通过if-else分支来实现,每个分支都要返回一个tf.estimator.EstimatorSpec对象,其为模型的评价指标,用来训练模型、分析模型结果。train/eval/predict三个过程对应的EstimatorSpec也不一样,下面来分别分析。
  • 何为Spec:Spec是specification的简写,可理解为模型的规范/结果,即定义了模型运行的结果/返回值,通常包含了模型的loss或者metric,以及进一步要做的优化操作(train_op)。

prediction过程的Estimatorspec

  • 对于pred过程,EstimatorSpec要设置的参数有:
    mode:直接返回model_fn的输入的mode参数即可
    predictiondict类型,内部是用户自己构造的评估模型的键值对,只用来后期用户自己分析,因此可根据需求随意构造。

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    if mode == tf.estimator.ModeKeys.PREDICT:
    pred_dict = {
    'class_ids': predicted_classes[:, tf.newaxis],
    'probabilities': tf.nn.softmax(logits),
    'logits': logits,
    }
    return tf.estimator.EstimatorSpec(mode, predictions=pred_dict)

    # 调用estimator预测函数,会生成一个迭代器
    result = estimator.predict(input_fn=predict_input_fn, yield_single_examples=True)

    for (i, prediction) in enumerate(result):
    # ...
    pass
  • result迭代的是单个元素的值,不带batch,因为predict函数的yield_single_examples参数默认为True,即把axis0维拆成batch_size个对象,如果axis0不是batch_size的话就会出错(需要改成False)。

  • batch_siza是在哪传入的?在RunConfig或直接在Estimator中传入的。

eval过程的Estimatorspec

对于train和eval过程,都要包含loss函数。

  • 对于eval过程,EstimatorSpec要设置的参数有:
    mode:直接返回model_fn的输入的mode参数即可;
    loss:计算出的loss值(标量形式),返回loss值的原因:存储模型训练数据,便于之后查看模型训练效果;
    eval_metric_ops参数:dict,为str-metrics的键值对,metrics是tf.metrics.accuracy()/mean()等函数的返回值。
  • 注:eval_metric_ops中的metrics必须是根据tf.metrics.accuracy()/mean()等函数返回/计算出的对象,因为metrics其实是一个tensorupdate_op的tuple,具体见tensorflow官方文档:tf.metrics.accuracy
1
2
3
4
5
6
7
8
if mode == tf.estimator.ModeKeys.EVAL:
# ...
# 完成loss的计算
accuracy = tf.metrics.accuracy(labels=labels,
predictions=predicted_classes, name='acc_op')
metrics = {'accuracy': accuracy}
return tf.estimator.EstimatorSpec(mode, loss=loss, eval_metric_ops=metrics)
# 每次只计算一个batch的accruacy,estimator内部会自动更新总的accuracy值。

train的Estimatorspec

对于train和eval过程,都要包含loss函数。

  • 对于train过程,EstimatorSpec要设置的参数有:
    mode:直接返回model_fn的输入的mode参数即可;
    loss:计算出的loss值(标量形式);
    train_op:训练操作,estimator会根据其来训练、优化模型。
    1
    2
    3
    4
    5
    6
    if mode == tf.estimator.ModeKeys.TARAIN:
    # ...
    # 完成loss的计算
    optimizer = tf.train.AdagradOptimizer(learning_rate=0.1)
    train_op = optimizer.minimize(loss, global_step=tf.train.get_global_step())
    return tf.estimator.EstimatorSpec(mode, loss=loss, train_op=train_op)

Feature Columns介绍

  • feature columns是tensorflow的一种描述数据的格式,包含了数据/feature的名称、类型和一些预处理操作,Estimator会根据feature columns的描述来决定如何处理传入的数据(dataset)。
  • feature columns类型:list,里面的元素类型:tf.feature_column
  • tf.feature_column总共有两大类:Dense Column(数值数据)和Categorical Column(类别数据),以及bucketized column(兼具两者的特点)。
类别(前面都有tf.feature_column.) 解释
numeric_column 数值数据类型, here
bucketized_column 将数值先分段,然后映射为独热编码
categorical_column_with_identity 将连续的整数数值映射为独热编码
indicator_column 将单词映射为独热编码
embedding_column 将单词映射为embedding向量

tf.feature_column.numeric_column()

1
2
3
4
5
6
7
8
9
10
11
12
13
tf.feature_column.numeric_column(
key, # 数据命名
shape=(1,), # 数据元的shape
default_value=None,
dtype=tf.dtypes.float32, # 数值类型,默认float32
normalizer_fn=None
)

# Represent a 10-element vector in which each cell contains a tf.float32.
vector_feature_column = tf.feature_column.numeric_column(key="Bowling", shape=10)

# Represent a 10x5 matrix in which each cell contains a tf.float32.
matrix_feature_column = tf.feature_column.numeric_column(key="MyMatrix", shape=[10,5])

tf.feature_column.bucketized_column

  • 分段数值类型,其将numeric_column的数据划分为一定的区间,再按照独热编码的形式表示。
  • 如年份数据,按照<1960, 1960~1980, 1980~2000, >2000分为四个区间:
年份范围 编码
<1960 [1, 0, 0, 0]
[1960, 1980) [0, 1, 0, 0]
[1980, 2000)] [0, 0, 1, 0]
>=2000 [0, 0, 0, 1]
  • example code
    1
    2
    3
    4
    5
    6
    7
    # First, convert the raw input to a numeric column.
    numeric_feature_column = tf.feature_column.numeric_column("Year")

    # Then, bucketize the numeric column on the years 1960, 1980, and 2000.
    bucketized_feature_column = tf.feature_column.bucketized_column(
    source_column = numeric_feature_column,
    boundaries = [1960, 1980, 2000])

tf.feature_column.categorical_column_with_identity

1
2
3
4
5
6
7
8
9
10
11
12
# Create categorical output for an integer feature named "my_feature_b",
# The values of my_feature_b must be >= 0 and < num_buckets
identity_feature_column = tf.feature_column.categorical_column_with_identity(
key='my_feature',
num_buckets=4) # Values [0, 4)

# In order for the preceding call to work, the input_fn() must return
# a dictionary containing 'my_feature_b' as a key. Furthermore, the values
# assigned to 'my_feature_b' must belong to the set [0, 4).
def input_fn():
...
return ({'my_feature_b':[3, 1, 2, 2] }, [Label_values])

模型保存 checkpoint

  • Estimator会自动保存两类文件:checkpointevent file,前者是保存模型的结构、参数等信息,后者是保存训练期间的变量信息(如loss、global_step等)。
  • 构建Estimator时通过传入model_dir参数来指定模型保存的路径。
  • 模型重新训练时会自动加载最近的一次checkpoint文件,因此可以实现模型的继续训练。

Post Date: 2019-07-21

版权声明: 本文为原创文章,转载请注明出处