企业🤖AI Agent构建引擎,智能编排和调试,一键部署,支持私有化部署方案 广告
# 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) ~~~