# Estimator簡要介紹
上一節我們實現了DNN,你一定注意到了我們使用了tensorflow自定義的Estimator——DNNClassifier。
你可能會問Estimator是什么?
按照我的理解其實Estimator就是tensorflow一個高級API。
然后它有什么作用呢?
可極大地簡化機器學習編程的高階 TensorFlow API
Estimator 會封裝下列操作:
* 訓練
* 評估
* 預測
* 導出以供使用
我們可以使用提供的預創建的 Estimator,也可以編寫自定義 Estimator。所有 Estimator(無論是預創建還是自定義)都是基于 tf.estimator.Estimator 類的類。
:-: 
:-: 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 為了了解一下其中涉及的概念,以防下面忽然出現一個概念你就蒙了。
我們還是去手動實現官網上的例子手寫數字的識別。
## 描述問題
:-: 
識別手寫數字-------就是判別手寫的數字是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,如下圖:

讓我們首先建立我們的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參數變小一點,這樣程序會很快執行完成。但是會影響模型的準確性。
你會看到控制臺上的代碼如下(由于我的網絡出現了問題沒有出來結果,所以目前直接用的官網上的圖):
:-: 
這里我們實現了在測試數據上 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
- 第六章 機器學習在實際項目中的應用