Tensorflow-Estimator-自定义估算器

这篇文章介绍自定义一个估算器(分类器)Estimator的完整流程。
请先参照鸢尾花iris案例并完成练习。


自定义Custom Estimator和预制Pre-made Estimator

在上面iris的案例中我们使用了tensorflow里面自带的深度神经网络分类器tf.estimator.DNNClassifie。这些tensorflow自带的estimator称为预制估算器Pre-made Estimator(预创建的Estimator)。

classifier = tf.estimator.DNNClassifier(
    feature_columns=feature_columns,
    hidden_units=[10, 10],
    n_classes=3,
    model_dir=models_path,
    config=ckpt_config) #

Tensorflow允许我们自己创建更加灵活的Custom Estimator。自定义Estimator是tf.estimator.Estimator()方法生成,能够像预制Estimator一样使用。


Tensorflow-Estimator-自定义估算器_第1张图片

结构概览

从表面看,我们的Estimator应该具有DNNClassifier一样的功能

  • 创建的时候接收一些参数,如feature_columns、hidden_units、n_classes等
  • 具有train()、evaluate()、predict()三个方法用来训练、评价、预测

如上所说,我们使用 tf.estimator.Estimator()方法来生成自定义Estimator,它的语法格式是

tf.estimator.Estimator(
    model_fn, #模型函数
    model_dir=None, #存储目录
    config=None, #设置参数对象
    params=None, #超参数,将传递给model_fn使用
    warm_start_from=None #热启动目录路径
)

模型函数model_fn是唯一没有默认值的参数,它也是自定义Estimator最关键的部分,包含了最核心的算法。model_fn需要一个能够进行运算的函数,它的样子应该长成这样

my_model(
  features, #输入的特征数据
  labels, #输入的标签数据
  mode, #train、evaluate或predict
  params #超参数,对应上面Estimator传来的参数
)

神经网络层Layers

model_fn应该怎么运作?下图展示了iris案例的情况


Tensorflow-Estimator-自定义估算器_第2张图片

从这个图中我没看到的结构:

  • 输入层Input Layer,数据从这里进入
  • 隐藏层Hidden Layer,2个,每层包含多个节点,数据流经这里,被推测规律
  • 输出层Output Layer,将推测的结果整理显示出来

我们并不需要手工实现隐藏层的算法和工作原理,Tensorflow已经为我们设计好。我们需要的只是创建这些神经网络层,并确保它们按照正常的顺序连接起来,至于其中如何推算演绎的魔法就完全交给tensorflow就可以了。

mode_fn需要完成的就是创建和组织这些神经层。


编写model_fn

对应我们创建Estimator时候的参数

classifier = tf.estimator.DNNClassifier(
    feature_columns=feature_columns,
    hidden_units=[10, 10],
    n_classes=3,
    model_dir=models_path,
    config=ckpt_config) 

这些参数都会被Estimator打包放在params超参数中,传递给model_fn,所以我们用下面的代码在model_fn内创建网络层

import tensorflow as tf

#自定义模型函数
def my_model_fn(features,labels,mode,params):
    #输入层,feature_columns对应Classifier(feature_columns=...)
    net = tf.feature_column.input_layer(features, params['feature_columns'])
    
    #隐藏层,hidden_units对应Classifier(unit=[10,10]),2个各含10节点的隐藏层
    for units in params['hidden_units']:
        net = tf.layers.dense(net, units=units, activation=tf.nn.relu)
    
    #输出层,n_classes对应3种鸢尾花
    logits = tf.layers.dense(net, params['n_classes'], activation=None)

输入层Input Layer

在上面代码中,我们使用这行代码创建输入层

    net = tf.feature_column.input_layer(features, params['feature_columns'])

如下图所示,Input Layer把输入的数据features填充到特征列params['feature_column']里面,稍后它会被继续传递到隐藏层hidden layer:


Tensorflow-Estimator-自定义估算器_第3张图片
输入层Input Layer

隐藏层Hidden Layer

我们使用循环为hidden_unit列表([10,10])创建了2个隐藏图层,每个图层的神经元节点unit都等于10.

    for units in params['hidden_units']:
        net = tf.layers.dense(net, units=units, activation=tf.nn.relu)

我们注意到上面的输入层叫做net(暂时叫net0),for循环里的隐藏层也叫net(暂叫net1)而且参数里还有net(net2),示意代码如下

#仅供示意
net0 = tf.feature_column.input_layer...
for units ...
        net1 = tf.layers.dense(net2, ...)

实际运行到隐藏层第一层(for循环第一次)的时候,我们创建隐藏层net1,并把net0作为参数输入到net1的,也就是隐藏第一层中关联了输入层:

input_net0=...#创建输入层
hidden_net1=tf.layers.dense(input_net0,...) #创建隐藏层1

然后for第二次循环的时候我们又关联了第一个隐藏层hidden_net1:

hidden_net2=tf.layers.dense(hidden_net1,...) #创建隐藏层2

这样逐层传递就形成了链条,数据沿着链条进行流动Flow和处理

intputLayer - hiddenLayer1 - hiddenLayer2 - ...
Tensorflow-Estimator-自定义估算器_第4张图片
隐藏层Hidden layer

输出层Output Layer

我们使用了这行代码创建输出层,请注意net!

    logits = tf.layers.dense(net, params['n_classes'], activation=None)

仍然是链条的延续!
但是activation这里改为了None,不再激活后续的部分,所以输出层就是链条的终点。


Tensorflow-Estimator-自定义估算器_第5张图片
输出层Output Layer

请注意这里的[-1.3,2.6,-0.9]表示了某朵花的测量数据分别属于三种分类的可能性,但是这里的数字很奇怪,甚至还有负数...稍后我们会对它们进行转化。


训练train、评价evaluate和预测predict

前面我们知道,自定义的估算分类器必须能够用来执行my_classifier.train()、my_classifier.evaluate()、my_classifier.predict()三个方法。

但实际上,它们都是model_fn这一个函数的分身!

上面出现的model_fn语法:

my_model(
  features, #输入的特征数据
  labels, #输入的标签数据
  mode, #train、evaluate或predict
  params #超参数,对应上面Estimator传来的参数
)

注意第三个参数mode,如果它等于"TRAIN"我们就执行训练:

#示意代码
my_model(..,..,"TRAIN",...)

如果是“EVAL”就执行评价,“PREDICT”就执行预测。

我们修改my_model代码来实现这三个功能:

def my_model_fn(features,labels,mode,params):
    #输入层,feature_columns对应Classifier(feature_columns=...)
    net = tf.feature_column.input_layer(features, params['feature_columns'])
    
    #隐藏层,hidden_units对应Classifier(unit=[10,10]),2个各含10节点的隐藏层
    for units in params['hidden_units']:
        net = tf.layers.dense(net, units=units, activation=tf.nn.relu)
    
    #输出层,n_classes对应3种鸢尾花
    logits = tf.layers.dense(net, params['n_classes'], activation=None)
   
    #预测
    predicted_classes = tf.argmax(logits, 1) #预测的结果中最大值即种类
    if mode == tf.estimator.ModeKeys.PREDICT:
        predictions = {
            'class_ids': predicted_classes[:, tf.newaxis], #拼成列表[[3],[2]]格式
            'probabilities': tf.nn.softmax(logits), #把[-1.3,2.6,-0.9]规则化到0~1范围,表示可能性
            'logits': logits,#[-1.3,2.6,-0.9]
        }
        return tf.estimator.EstimatorSpec(mode, predictions=predictions)
     
     
    #损失函数
    loss = tf.losses.sparse_softmax_cross_entropy(labels=labels, logits=logits)
    
    #训练
    if mode == tf.estimator.ModeKeys.TRAIN:
        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)      
    
    #评价
    accuracy = tf.metrics.accuracy(labels=labels,
                                   predictions=predicted_classes,
                                   name='acc_op') #计算精度
    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) 

如上面所示,请将预测Predict需要放在最先编写,否则可以引发后续错误。

下面我们分别详解三个方法的代码


预测Predict

因为预测最后我们需要返回花的种类label,还希望知道这个预测有多精确,所以在预测部分的代码里面,首先取到三种花可能性最大的一个predicted_classes即[-1.3,2.6,-0.9]中的2.6;然后把它转成列表格式[[2.6]];同时把logit得到的[-1.3,2.6,-0.9]转化为表示0~1可能性的小数[0.01926995 0.95198274 0.02874739]

predicted_classes = tf.argmax(logits, 1) #预测的结果中最大值即种类
    if mode == tf.estimator.ModeKeys.PREDICT:
        predictions = {
            'class_ids': predicted_classes[:, tf.newaxis], #拼成列表[[3],[2]]格式
            'probabilities': tf.nn.softmax(logits), #把[-1.3,2.6,-0.9]规则化到0~1范围,表示可能性
            'logits': logits,#[-1.3,2.6,-0.9]
        }
        return tf.estimator.EstimatorSpec(mode, predictions=predictions)

注意最后一句,我们返回return的是一个EstimatorSpec对象,下面的训练predict和评价evaluate也都返回EstimatorSpec形式的对象,但是参数不同,请留意。

我们可以使用以下代码在单独文件测试tf.newaxis和tf.nn.softmax对数据转化的作用

import tensorflow as tf

a=tf.constant([2.6],name='a')
b=a[:,tf.newaxis]

a2=tf.constant([-1.3,2.6,-0.9],name='a')
b2= tf.nn.softmax(a2)

with tf.Session() as session:   
    session.run(tf.global_variables_initializer())
    session.run(tf.tables_initializer())
    print(session.run(b))
    print(session.run(b2))

输出

[[2.6]]
[0.01926995 0.95198274 0.02874739]

损失函数Loss

损失函数是Tensorflow中神经网络的重要概念,简单说,它能够计算出我们模型的偏差程度,结果越大,我们的模型就偏差越大、离正确也远、也越不准确、越糟糕。

为了降低损失,我们可以使用更多更好的数据,还可以设计更好的优化方法,来优化改进模型,让损失变为最小。

训练神经网络模型的目标就是把偏差损失降为最小,机器学习就是一批一批数据反复分析计算反复尝试,不断的利用优化方法,想尽办法把Loss的值降到最小的过程。

优化方法设计的越好好,损失也就越少,精度也就越高。
    loss = tf.losses.sparse_softmax_cross_entropy(labels=labels, logits=logits)

这里我们使用了tensorflow提供的稀疏柔性最大交叉熵sparse_softmax_cross_entropy来计算损失程度,它对于分类问题很有效,DNNClassifier也使用了这个方法。


训练Train

我们在训练部分代码中,创建了优化器optimizer,然后使用它尝试将我们的损失函数loss变为最小minimize:

    if mode == tf.estimator.ModeKeys.TRAIN:
        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)      

评价Evaluate

我们使用下面的代码来评价预测结果prediction和test数据中植物学家标记的数据是否足够吻合:

    accuracy = tf.metrics.accuracy(labels=labels,
                                   predictions=predicted_classes,
                                   name='acc_op') #计算精度
    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.metrics.accuracy方法对比植物学家的标记labels和批量预测结果predicted_classes([[-1.3,2.6,-0.9],...]),


导入数据

相关文件可以从百度云这里下载 密码:y3id

经过上面的过程,我们创建了估算分类器的核心部分model_fn,接下来我们继续添加以下代码,导入数据备用。具体解释请参照鸢尾花iris案例

import os
import pandas as pd

FUTURES = ['SepalLength', 'SepalWidth','PetalLength', 'PetalWidth', 'Species']
SPECIES = ['Setosa', 'Versicolor', 'Virginica']

dir_path = os.path.dirname(os.path.realpath(__file__))
train_path=os.path.join(dir_path,'iris_training.csv')
test_path=os.path.join(dir_path,'iris_test.csv')

train = pd.read_csv(train_path, names=FUTURES, header=0)
train_x, train_y = train, train.pop('Species')

test = pd.read_csv(test_path, names=FUTURES, header=0)
test_x, test_y = test, test.pop('Species')

创建分类器

继续添加代码,使用model_fn来生成自定义分类器(请注意最后几行):

feature_columns = []
for key in train_x.keys():
    feature_columns.append(tf.feature_column.numeric_column(key=key))

#创建自定义分类器    
classifier = tf.estimator.Estimator(
        model_fn=my_model, #注意这里!
        params={
            'feature_columns': feature_columns,
            'hidden_units': [10, 10],
            'n_classes': 3,
        })

训练模型

添加下面代码开始训练模型

#针对训练的喂食函数
batch_size=100
def train_input_fn(features, labels, batch_size):
    dataset = tf.data.Dataset.from_tensor_slices((dict(features), labels))
    dataset = dataset.shuffle(1000).repeat().batch(batch_size) #每次随机调整数据顺序
    return dataset.make_one_shot_iterator().get_next()

#开始训练
classifier.train(
    input_fn=lambda:train_input_fn(train_x, train_y, 100),
    steps=1000)

评价模型

添加下面的代码可以对模型进行评价并打印出精度

#针对测试的喂食函数
def eval_input_fn(features, labels, batch_size):
    features=dict(features)
    inputs=(features,labels)
    dataset = tf.data.Dataset.from_tensor_slices(inputs)
    dataset = dataset.batch(batch_size)
#    return dataset
    return dataset.make_one_shot_iterator().get_next()

#评估我们训练出来的模型质量
eval_result = classifier.evaluate(
    input_fn=lambda:eval_input_fn(test_x, test_y,batch_size))

print(eval_result)

进行预测

添加以下代码让用我们的模型可以进行交互预测

#支持100次循环对新数据进行分类预测
for i in range(0,100):
    print('\nPlease enter features: SepalLength,SepalWidth,PetalLength,PetalWidth')
    a,b,c,d = map(float, input().split(',')) #捕获用户输入的数字
    predict_x = {
        'SepalLength': [a],
        'SepalWidth': [b],
        'PetalLength': [c],
        'PetalWidth': [d],
    }
    
    #进行预测
    predictions = classifier.predict(
        input_fn=lambda:eval_input_fn(predict_x,
                                      labels=[0,],
                                      batch_size=batch_size))    

    #预测结果是数组,尽管实际我们只有一个
    for pred_dict in predictions:
        class_id = pred_dict['class_ids'][0]
        probability = pred_dict['probabilities'][class_id]
        print(SPECIES[class_id],100 * probability)

模型的恢复与保存设置

修改创建估算分类器的代码设置model_dir模型保存与自动恢复,并设定日志打印

tf.logging.set_verbosity(tf.logging.INFO)
models_path=os.path.join(dir_path,'mymodels/')

#创建自定义分类器
classifier = tf.estimator.Estimator(
        model_fn=my_model_fn,
        model_dir=models_path,
        params={
            'feature_columns': feature_columns,
            'hidden_units': [10, 10],
            'n_classes': 3,
        })

TensorBoard信息板

打开新的命令行工具窗口,使用下面的命令启动信息板:

tensorboard --logdir=~/desktop/iris/mymodels

这里的~/desktop/iris/models应该和上面配置的model_dir=models_path完全一致,正常情况会输出很多信息,并在最后显示类似下面的提示

TensorBoard 1.6.0 at http://xxx-xxx-xxx.local:6006 (Press CTRL+C to quit)

把这段http://xxx-xxx-xxx.local:6006复制到浏览器窗口,或者复制http://localhost:6006/就可以打开TensorBoard信息板,这里包含了很多关于模型的性能质量等方面的图表:

Tensorflow-Estimator-自定义估算器_第6张图片

关于TensorBoard更多内容可以点右上角的问号打开Github上的项目详细说明。


上面的代码和相关文件可以从百度云这里下载 密码:y3id


探索人工智能的新边界

如果您发现文章错误,请不吝留言指正;
如果您觉得有用,请点喜欢;
如果您觉得很有用,感谢转发~


END

你可能感兴趣的:(Tensorflow-Estimator-自定义估算器)