# Estimator简要介绍
上一节我们实现了DNN,你一定注意到了我们使用了tensorflow自定义的Estimator——DNNClassifier。
你可能会问Estimator是什么?
按照我的理解其实Estimator就是tensorflow一个高级API。
然后它有什么作用呢?
可极大地简化机器学习编程的高阶 TensorFlow API
Estimator 会封装下列操作:
* 训练
* 评估
* 预测
* 导出以供使用
我们可以使用提供的预创建的 Estimator,也可以编写自定义 Estimator。所有 Estimator(无论是预创建还是自定义)都是基于 tf.estimator.Estimator 类的类。
:-: ![](https://box.kancloud.cn/a506cb0e53c510149635a27caeb8ef84_1104x465.png)
:-: api层次结构
> 既然是API那我们使用时,就应该按照api定义的规则去使用,通过上一节DNN我们知道了预定义的Estimator都需要什么格式的输入和初始化。其实就是按照api定义的格式我们去生成所要求的的格式而已——条用DatasetsAPI去生成输入。
> 如果想要深入了解可以看官网:https://www.tensorflow.org/programmers_guide/estimators
# CNN实例。
这一节我们将自己定义 Estimator来实现CNN网络。首先我建议你首先看一下官网自定义Estimator这一节(中文的):https://www.tensorflow.org/get_started/custom_estimators 为了了解一下其中涉及的概念,以防下面忽然出现一个概念你就蒙了。
我们还是去手动实现官网上的例子手写数字的识别。
## 描述问题
:-: ![](https://box.kancloud.cn/80b7dc4e89d7b43cf469fd7228a0c2e5_330x137.png)
识别手写数字-------就是判别手写的数字是0、1、2、3、4、5、6、7、8、9中的哪一个。
## 已有的条件
MINIST数据集——有60000张训练图片10000张测试图片,每张图片是格式化好的28* 28的单色图片。
## 解决问题的方法
我们自己构建卷积神经网络,用上述的MINIST数据集进行训练模型,然后进行测试效果。
我们使用的网络结构如下:
1. 输入层
2. 卷积层32个 5x5 filters ReLU激活函数
3. 最大池化层 2*2 filter 步长为2
4. 卷积层64 个 5x5 filters ReLU激活函数
5. 最大池化层 2*2 filter 步长为2、
6. 全连接层1,024 神经元, with dropout regularization rate of 0.4
7. 全连接层 10 神经元, one for each digit target class (0–9).
## 解决问题过程
### 前期准备工作
首先我们用上一节的虚拟环境tensorflow1建立一个项目cnn_minist,如下图:
![](https://box.kancloud.cn/d1f6b251c98a7b114dde38b37a6344ac_783x488.png)
让我们首先建立我们的tensorflow程序的架子。新建一个cnn_mnist.py文件,然后添加上以下代码:
~~~
from __future__ import absolute_import
from __future__ import division
from __future__ import print_function
# 导入所需要的包
import numpy as np
import tensorflow as tf
tf.logging.set_verbosity(tf.logging.INFO)
# 我们应用的逻辑将添加到这里
if __name__ == "__main__":
tf.app.run()
~~~
经过我们下面的进行,我们将添加构建网络,训练网络、评估网络的代码到该项目。当然了你首先应该知道卷积神经网络里面每个层的作用和具体操作。如果你还不是太了解那么我强烈建议您去浏览一下上面CNN介绍一节。
还记得我们上一节写的输入函数用的datasetAPI吗?这一节我们使用tf.layersAPI它包含创建以上三层的接口。如果你在思考的多一点,那么一定会猜到Estimator就是在此api上再次抽象出来的。
我们先来介绍一下tf.layersAPI中我们要使用的函数。
* conv2():创建2维的卷积层以卷积核的数量、卷积核的大小、填充方式、激活函数为参数。
* max_pooling2d():用最大池化算法创建二维的池化层以池化核大小和步长作为参数
* dense():创建一个全连接层。以神经元的个数和激活函数为参数
以上的这些方法接受一个tensor对象为输入,最后返回一个转变的tensor.这样会使层与层直接连接更加容易:仅仅把一层的输出作为下一层的输入。
> tensor对象是tensorflow低阶的API,我们后期如果用estimitor实现不了时,就可以使用更低阶的api来做,灵活性更大但是难度也更大。
### 开始编写Estimator的model函数
打开cnn_mnist.py文件然后添加下面cnn_model_fn方法——符合TensorFlow's Estimator API 的接口定义。cnn_model_fn以MINIST特征数据,标签数据模型模式(TRAIN, EVAL, PREDICT)作为参数,配置CNN,然后返回预测,损失和训练操作
> 如果你看过定义Estimator这一节,就知道预创建的estimator与自定义的estimitor唯一的区别就是model_fn函数是创建好的还是自己写的。
#### 输入层
在cnn_model_fn方法中添加输入层代码:
~~~
def cnn_model_fn(features, labels, mode):
"""Model function for CNN"""
# 输入层
input_layer = tf.reshape(features["x"], [-1, 28, 28, 1])
~~~
解释:你是不是感觉有点蒙呀怎么上来就给出的输入层是这个样子呀,别慌!我马上解释。首先我们要知道tf.layer生成卷积层和池化层。需要输入的默认张量是[batch_size, image_height, image_width, channels](batch_size:批量数量, image_height:图片的高,image_width:图片的宽,channels:通道的数量:RGB图像是3灰度图像是1, 当然你可以改变张量的格式那就必须制定data_format 的参数)。所以我们要给他们提供这种张量。而 tf.reshape可以实现这个功能你可以去参考:https://blog.csdn.net/m0_37592397/article/details/78695318 的用法。这里需要知道的一点是-1像是一个占位符它是根据具体传入的图片数量动态变化的。
**输入张量大小是:[batch_size, 28, 28, 1]**
#### 卷积层
接着在上面cnn_model_fn的方法中添加下面输入第一层卷积层代码:
~~~
# 第一层卷积层
conv1 = tf.layers.conv2d(
inputs=input_layer,
filters=32,
kernel_size=[5,5],
padding="same",
activation=tf.nn.relu
)
~~~
解释:可以看到该卷积层是32个5* 5卷积核,使用tf.nn.relu激活函数,并且与输入层的张量[batch_size, image_height, image_width, channels]连接上了。但是padding = "same"你可能不太理解,那我们就解释一下————首先padding可以在“same”和“valid”两个枚举值直接选择一个默认是“valid”,作用是:指示是否是经过填充是输入张量和输出张量一样大小。我们这里选择“same”表示在输入张量边缘添加0使输出张量也保持跟输入张量的大小一样。如果不填充的化,输出张量将变成24 * 24 (28-5 +1),从侧面也表明了tf.layers.conv2d(),的步长是1,不能修改。
**到现在经过第一层卷积层输出的张量大小为:[batch_size, 28, 28, 32]**
#### 池化层
接着在上面cnn_model_fn的方法中添加下面第一个max_pool的代码:
~~~
#第一个池化层
pool1 = tf.layers.max_pooling2d(inputs=conv1, pool_size=[2,2], strides=2)
~~~
解释:这个很容易理解,我们就不解释代码了。但是需要注意一点strides是在两个witch和high两个方向都是2,你可以设置在两个方向设置不同的值,如:[3, 6]
**到现在输出的张量的大小是:[batch_size, 14, 14, 32]**
* * * * *
现在我们再在上面的cnn_model_fn的函数中添加第二个卷积层和第二个池化层,代码如下:
~~~
#第二个卷积层
conv2 = tf.layers.conv2d(
inputs=pool1,
filters=64,
kernel_size=[5,5],
padding="same",
activation=tf.nn.relu
)
#第二个池化层
tf.layers.max_pooling2d(
inputs=tf.layers.max_pooling2d(inputs=conv2, pool_size=[2,2], strides=2)
)
~~~
**经过第二个卷积层我们的张量大小为:[batch_size, 14, 14, 64]
经过第二个池化层后我们的张量大小为:[batch_size, 7, 7, 64]**
* * * * *
#### 全连接层
我们接着在cnn_model_fn的函数上添加全连接层代码如下:
~~~
#全连接层
pool2_flat = tf.reshape(pool2, [-1, 7*7*64])
dense = tf.layers.dense(inputs=pool2_flat, units=1024, activation=tf.nn.relu)
~~~
解释:
因为全连接是每个神经元都加上一个权重然后生成下一个神经元。所以如果改变一个张量的维数将有助于计算(自己猜测)。
所以加上一个tf.reshape(pool2, [-1, 7*7*64])变成two dimensions张量。
**输出的张量大小:[batch_size, 1024]**
* * * * *
**dropout正则化**
为了是模型最后的结果效果好,同时防止过拟合,我们添加dropout正则化。可以参考下面网站了解:https://blog.csdn.net/sinat_29819401/article/details/60885679
代码如下:
~~~
#dropout正则化
dropout = tf.layer.dropout(
inputs=dense,
rate=0.4,
training=mode == tf.estimator.ModeKeys.TRAIN
)
~~~
解释:当我们训练该模型时我们才dropout正则化,并且以40%的神经元的值被随机丢弃。而且只有训练时我们才去正则化。
**输出的张量大小:[batch_size, 1024]**
* * * * *
#### 逻辑层
其实就是有特殊功能的全连接层。代码如下:
~~~
#逻辑层
logits = tf.layers.dense(inputs=dropout, units=10)
~~~
解释:因为最后的分类是10个,所以我们选择10个神经元,也就是生成10个数。
**输出的张量大小:[batch_size, 10]**
网络最后输出的10个数的含义是什么呢,我们应该怎么使用呢?下面我们就来说一说!
* * * * *
我们网络的逻辑层会以原始的数据返回预测在[batch_size, 10]张量里面。让我们转变原始数据成两个不同的格式。
* The predicted class for each example: a digit from 0–9.
* The probabilities for each possible target class for each example: the probability that the example is a 0, is a 1, is a 2, etc.
有时候英文比中文说的准确。所以我不吝啬的上了两句英文。
好了废话少说,我们先了解该句话的含义(不添加到函数里):
~~~
tf.argmax(input=logits,axis=1)
~~~
解释:因为我们所预测的类是每一行中最大的值所对应的类别。我们能用上面的函数去找到每一行最大数的索引值。(从0开始编码)可以参考这个:https://blog.csdn.net/u011597050/article/details/80581461
我们添加下一个格式,代码如下(同样不添加到函数里):
~~~
tf.nn.softmax(logits, name=softmax_tensor)
~~~
解释:我们可以通过使用tf.nn.softmax应用softmax激活从我们的logits层中得出概率。把10个数变成变成每个类对应的概率。可以去查一下softmax函数的用法吧。可以参考:https://blog.csdn.net/l691899397/article/details/52291909
> 我们使用name参数来明确命名这个操作softmax_tensor,所以我们可以在稍后引用它。
>
* * * * *
#### 预测模块
这一小部分我们写estimator预测模块。我们首先要知道estimator无论是预测还是训练还是评估最后都返回一个EstimatorSpec对象。
我们先把代码写下来:(同样是接着上面的写)
~~~
#预测模块
predictions = {
"classes": tf.argmax(input=logits, axis=1),
"probabilities": tf.nn.softmax(logits, name="softmax_tensor")
}
if mode == tf.estimator.ModeKeys.PREDICT:
return tf.estimator.EstimatorSpec(mode=mode, predictions=predictions)
~~~
解释:固定格式不做解释。
#### 训练模块与评估模块
当要训练和评估时,我们首先要定义损失函数。对于多类别分类问题我们尝试用交叉熵函数作为损失函数。如果不理解什么是交叉熵函数的可以参考这个网址:https://blog.csdn.net/allenlzcoder/article/details/78591535
损失函数添加代码如下:
~~~
#训练模块与评估模块
#损失函数
onehot_labels = tf.one_hot(indice=tf.cast(labels, tf.int32), depth=10)
loss = tf.losses.softmax_cross_entropy(
onehot_labels = onehot_labels, logits=logits
)
~~~
解释:让我们看看上面代码都发生了什么。例如我们的labels = [1, 9, ...],为了计算交叉熵,我们首先要变成【0,1,0.。。。】的形式。所以经过tf.one_hot()后我们的labels变成了[[0, 1, 0, 0, 0, 0, 0, 0, 0, 0], [0, 0, 0, 0, 0, 0, 0, 0, 0, 1],...]——one-hot tensor。tf.one_hot()函数不理解可以参考:https://www.w3cschool.cn/tensorflow_python/tensorflow_python-fh1b2fsm.html 接下来,我们计算onehot_labels和来自logits层的预测的softmax的交叉熵。 tf.losses.softmax_cross_entropy()将onehot_labels和logits作为参数,对logits执行softmax激活,计算交叉熵,并将我们的损失作为标量张量返回。
**配置训练操作**
前面我们已经通过标签和logits layer层配置了交叉熵损失函数,下面让我们通过训练优化这个损失函数,并优化这个model. 我们设置学习率为0.001,并使用随机梯度下降法来优化损失函数。
代码如下:
~~~
#配置训练
if mode == tf.estimator.ModeKeys.TRAIN:
optimizer = tf.train.GradientDescentOptimizer(learning_rate=0.001)
train_op = optimizer.minimize(
loss=loss,
global_step=tf.train.get_global_step()
)
return tf.estimator.EstimatorSpec(mode=mode, loss=loss, train_op=train_op)
~~~
解释:固定写法,我们必须返回一个EstimatorSpec的对象。minimize 方法还具有 global_step 参数。TensorFlow 使用此参数来计算已经处理过的训练步数(以了解何时结束训练)。此外,global_step 对于 TensorBoard 图能否正常运行至关重要。只需调用 tf.train.get_global_step 并将结果传递给 minimize 的 global_step 参数即可
**添加评价操作**
为了添加准确的评价指标到模型中去,我们定义字典类型eval_metric_ops
代码如下:
~~~
eval_metric_ops = {
"accuracy": tf.metrics.accuracy(
labels=labels, predictions=predictions["classes"])}
return tf.estimator.EstimatorSpec(
mode=mode, loss=loss, eval_metric_ops=eval_metric_ops)
~~~
解释:固定写法不做解释。
**训练和评估这个模型**
我们已经写好我们的 MNIST CNN model函数,现在已经做好训练和评估的准备了。
1. 载入训练与测试数据
代码如下:
~~~
def main(unused_argv):
# Load training and eval data
mnist = tf.contrib.learn.datasets.load_dataset("mnist")
train_data = mnist.train.images # Returns np.array
train_labels = np.asarray(mnist.train.labels, dtype=np.int32)
eval_data = mnist.test.images # Returns np.array
eval_labels = np.asarray(mnist.test.labels, dtype=np.int32)
~~~
解释:我们把训练数据和训练标签存成为numpy arrays.训练数据也是这个数据结构
**创建estimator**
接着写上面的main函数
下面我们用自己写的model去新建一个Estimator(a TensorFlow class for performing high-level model training, evaluation, and inference)
添加下面的代码到main函数:
~~~
#创建Estimator对象
mnist_classifier = tf.estimator.Estimator(
model_fn=cnn_model_fn,
model_dir="/tmp/mnist_convnet_model"
)
~~~
解释:model_fn参数指明了使用哪个模型函数来进行训练和评估和预测。这个model_dir参数model数据(checkpoint)讲被存放的位置你可以选择自己想要保存的位置。
**设置记录钩**
因为卷积神经网络需要花费一些是时间来进行训练,所以让我们设置一些日志来跟踪训练过程。我们能用Tensorflow的tf.train.SessionRunHook去构建一个tf.train.LoggingTensorHook 它可以记录来自 softmax layer的概率值
添加下面的代码到main函数:
~~~
#为预测设置日志
tensor_to_log = {"probabilities":"softmax_tensor"}
logging_hook = tf.train.LoggingTensorHook(
tensors=tensor_to_log,every_n_iter=50
)
~~~
解释:
我们存设置一个我们想记录一些tensor字典类型tensor_to_log,该字典中每一个标签都出打印到log窗口,而标签值是一个tensor(如:softmax_tensor,上面我们在in cnn_model_fn定义过了)。
> If you don't explicitly assign a name to an operation via the name argument, TensorFlow will assign a default name. A couple easy ways to discover the names applied to operations are to visualize your graph on TensorBoard) or to enable the TensorFlow Debugger (tfdbg).
> 还是英文写的清楚,谁让自己语言表达能力不强呢。应该能看懂得,看不懂也没关系。以后会解释。
>
接下来我们创建LoggingTensorHook对象,传递tensors_to_log 给tensors 参数,我们设置every_n_iter=50.指明训练每50次记录一次。
**训练模型**
现在我们可以去训练我们的模型了,我们通过创建train_input_fn和调用mnist_classifier的main函数。添加下面的代码到main函数:
~~~
#训练该模型
train_input_fn = tf.estimator.inputs.numpy_input_fn(
x={"x": train_data},
y=train_labels,
batch_size=100,
num_epochs=None, #设置他将训练模型,直到达到指定的步数
shuffle=True #随机拖拽训练数据
)
mnist_classifier.train(
input_fn=train_input_fn,
steps=2000, #训练步数
hooks=[logging_hook] #设置日志钩子,以在训练过程中去记录重要的信息
)
~~~
解释:注释写的很明白了,这里不做太多解释。
**评估模型**
一旦训练完成,我们就像看看该卷积神经网络在Mnist数据集上的准确率。我们调用evaluate 方法,该方法将评估我们指定的指标(在model_fn的参数eval_metric_ops指明的)。
添加下面的代码到main函数:
~~~
#评价该模型
#评价模型并打印结果
eval_input_fn = tf.estimator.numpy_input_fn(
x={"x": eval_data},
y=eval_labels,
num_epochs=1, #设置模型在一个时期的数据上评估度量并返回结果。
shuffle=False #按顺序遍历数据
)
eval_results = mnist_classifier.evaluate(
input_fn=eval_input_fn
)
print(eval_results)
~~~
解释:不做解释,注释写的很明白
**运行该模型**
到了最激动人心的时刻了,是不是有点小激动。我们下面就看看卷积神经网络的神奇魅力吧。
我们编写了CNN model方法,并创建了Estimator,还有训练和评估的逻辑,现在让我们看看效果吧,直接运行cnn_mnist.py。如果你觉得时间太长,你可以修改train的step参数变小一点,这样程序会很快执行完成。但是会影响模型的准确性。
你会看到控制台上的代码如下(由于我的网络出现了问题没有出来结果,所以目前直接用的官网上的图):
:-: ![](https://box.kancloud.cn/bcd2457ba419ed8aadc48b46af45da99_948x306.png)
这里我们实现了在测试数据上 97.3% 的准确率
* * * * *
你可能会好奇我没有下载数据怎么就获得了呢?
其实是调用了tensorflow中的api来获取的数据。也就是下面这段代码起到的作用
~~~
tf.contrib.learn.datasets.load_dataset("mnist")
~~~
可以参考该网址了解:https://www.tensorflow.org/api_docs/python/tf/contrib/learn/datasets?hl=zh-cn
* * * * *
这里我们给出完整的代码:
cnn_mnist.py
~~~
from __future__ import absolute_import
from __future__ import division
from __future__ import print_function
# 导入所需要的包
import numpy as np
import tensorflow as tf
tf.logging.set_verbosity(tf.logging.INFO)
def cnn_model_fn(features, labels, mode):
"""Model function for CNN"""
# 输入层
input_layer = tf.reshape(features["x"], [-1, 28, 28, 1])
# 第一层卷积层
conv1 = tf.layers.conv2d(
inputs=input_layer,
filters=32,
kernel_size=[5, 5],
padding="same",
activation=tf.nn.relu
)
# 第一个池化层
pool1 = tf.layers.max_pooling2d(inputs=conv1, pool_size=[2, 2], strides=2)
# 第二个卷积层
conv2 = tf.layers.conv2d(
inputs=pool1,
filters=64,
kernel_size=[5, 5],
padding="same",
activation=tf.nn.relu
)
# 第二个池化层
tf.layers.max_pooling2d(
inputs=tf.layers.max_pooling2d(inputs=conv2, pool_size=[2, 2], strides=2)
)
# 全连接层
pool2_flat = tf.reshape(pool2, [-1, 7 * 7 * 64])
dense = tf.layers.dense(inputs=pool2_flat, units=1024, activation=tf.nn.relu)
# dropout正则化
dropout = tf.layer.dropout(
inputs=dense,
rate=0.4,
training=mode == tf.estimator.ModeKeys.TRAIN
)
# 逻辑层
logits = tf.layers.dense(inputs=dropout, units=10)
if mode == tf.estimator.ModeKeys.PREDICT:
return tf.estimator.EstimatorSpec(mode=mode, predictions=predictions)
#训练模块与评估模块
#损失函数
onehot_labels = tf.one_hot(indice=tf.cast(labels, tf.int32), depth=10)
loss = tf.losses.softmax_cross_entropy(
onehot_labels=onehot_labels, logits=logits
)
#配置训练
if mode == tf.estimator.ModeKeys.TRAIN:
optimizer = tf.train.GradientDescentOptimizer(learning_rate=0.001)
train_op = optimizer.minimize(
loss=loss,
global_step=tf.train.get_global_step()
)
return tf.estimator.EstimatorSpec(mode=mode, loss=loss, train_op=train_op)
# 预测模块
predictions = {
"classes": tf.argmax(input=logits, axis=1),
"probabilities": tf.nn.softmax(logits, name="softmax_tensor")
}
eval_metric_ops = {
"accuracy": tf.metrics.accuracy(
labels=labels,
predictions=predictions["classes"]
)
}
return tf.estimator.EstimatorSpec(
mode=mode, loss=loss, eval_metric_ops=eval_metric_ops
)
def main(unused_argv):
#加载训练数据和评估数据
mnist = tf.contrib.learn.datasets.load_dataset("mnist")
train_data = mnist.train.images #返回np.array数组
train_labels = np.asarray(mnist.train.labels, dtype=np.int32)
eval_data = mnist.test.images #返回np.array数组
eval_labels = np.asarray(mnist.test.labels, dtype=np.int32)
#创建Estimator对象
mnist_classifier = tf.estimator.Estimator(
model_fn=cnn_model_fn,
model_dir="/tmp/mnist_convnet_model"
)
#为预测设置日志
tensor_to_log = {"probabilities":"softmax_tensor"}
logging_hook = tf.train.LoggingTensorHook(
tensors=tensor_to_log,every_n_iter=50
)
#训练该模型
train_input_fn = tf.estimator.inputs.numpy_input_fn(
x={"x": train_data},
y=train_labels,
batch_size=100,
num_epochs=None,
shuffle=True
)
mnist_classifier.train(
input_fn=train_input_fn,
steps=2000,
hooks=[logging_hook]
)
#评价该模型
#评价模型并打印结果
eval_input_fn = tf.estimator.numpy_input_fn(
x={"x": eval_data},
y=eval_labels,
num_epochs=1, #设置模型在一个时期的数据上评估度量并返回结果。
shuffle=False #按顺序遍历数据
)
eval_results = mnist_classifier.evaluate(
input_fn=eval_input_fn
)
print(eval_results)
if __name__ == "__main__":
tf.app.run(main)
~~~
- 序言
- 第一章 机器学习概述
- 第二章 机器学习环境搭建
- 环境搭建
- 第三章 机器学习之基础算法
- 第一节:基础知识
- 第二节:k近邻算法
- 第三节:决策树算法
- 第四节:朴素贝叶斯
- 第五节:逻辑斯蒂回归
- 第六节:支持向量机
- 第四章 机器学习之深度学习算法
- 第一节: CNN
- 4.1.1 CNN介绍
- 4.1.2 CNN反向传播
- 4.1.3 DNN实例
- 4.1.4 CNN实例
- 第五章 机器学习论文与实践
- 第一节: 语义分割
- 5.1 FCN
- 5.1.1 FCN--------实现FCN16S
- 5.1.2 FCN--------优化FCN16S
- 5.2 DeepLab
- 5.2.1 DeepLabv2
- 第六章 机器学习在实际项目中的应用