翻譯自:https://github.com/tensorflow/tensorflow/tree/master/tensorflow/contrib/slimhtml
TF-Slim是Tensorflow中一個輕量級的庫,用於定義、訓練和評估複雜的模型。TF-Slim中的組件能夠與Tensorflow中原生的函數一塊兒使用,與其餘的框架,好比與tf.contrib.learn也能夠一塊兒使用。python
import tensorflow.contrib.slim as slim
TF-Slim可使創建、訓練和評估神經網絡更加簡單。git
l 容許用戶經過減小模板代碼使得模型更加簡潔。這個能夠經過使用argument scoping和大量的高層layers、variables來實現;github
l 經過使用經常使用的正則化( regularizers)使得創建模型更加簡單;算法
l 一些普遍使用的計算機視覺相關的模型(好比VGG,AlexNet)已經在slim中定義好了,用戶能夠很方便的使用;這些既能夠當成黑盒使用,也能夠被擴展使用,好比添加一些「multiple heads」到不一樣的內部的層;api
l Slim使得擴展複雜模型變得容易,可使用已經存在的模型的checkpoints來開始訓練算法。安全
TF-Slim由幾個獨立存在的組件組成,主要包括如下幾個:網絡
l arg_scope:提供一個新的做用域(scope),稱爲arg_scope,在該做用域(scope)中,用戶能夠定義一些默認的參數,用於特定的操做;session
l data:包含TF-Slim的dataset定義,data providers,parallel_reader,和 decoding utilities;app
l evaluation:包含用於模型評估的常規函數;
l layers:包含用於創建模型的高級layers;
l learning:包含一些用於訓練模型的常規函數;
l losses:包含一些用於loss function的函數;
l metrics:包含一些熱門的評價標準;
l nets:包含一些熱門的網絡定義,如VGG,AlexNet等模型;
l queues:提供一個內容管理者,使得能夠很容易、很安全地啓動和關閉QueueRunners;
l regularizers:包含權重正則化;
l variables:提供一個方便的封裝,用於變量建立和使用。
使用TF-Slim,結合variables, layers 和 scopes,模型能夠很簡潔地被定義。這些元件定義以下。
在原生的Tensorflow中,建立Variable須要一個預約義的值或者一種初始化機制(好比從一個高斯分佈中隨機採樣)。此外,若是一個變量須要在一個特定的設備上(如GPU)建立,那麼必須被明確說明。爲了減小變量建立所需的代碼,TF-Slim提供了一些封裝函數(定義在variables.py中),可使得用戶定義變量變得簡單。
舉個例子,定義一個權重(weight)變量,使用一個截斷的正態分佈來初始化,使用l2 loss正則化,並將該變量放置在CPU中,咱們只須要聲明以下:
weights = slim.variable('weights', shape=[10, 10, 3 , 3], initializer=tf.truncated_normal_initializer(stddev=0.1), regularizer=slim.l2_regularizer(0.05), device='/CPU:0')
注意到,在原生的Tensorflow中,有兩種類型的variables,regular variables 和 local (transient) variables。絕大部分變量是regular variables,一旦被建立,可使用saver來將這些變量保存到磁盤中;Local variables是那些僅僅存在於一個session內,並不會被保存到磁盤中。
TF-Slim經過定義model variables來進一步區別變量,這些是表示一個模型參數的變量。Model variables在學習期間被訓練或者fine-tuned,在評估或者推斷期間能夠從一個checkpoint中加載。模型變量包括使用slim.fully_connected 或者 slim.conv2d建立的變量等。非模型變量(Non-model variables)指的是那些在學習或者評估階段使用可是在實際的inference中不須要用到的變量。好比說,global_step在學習和評估階段會用到的變量,可是實際上並非模型的一部分。相似的,moving average variables也是非模型變量。
model variables和regular variables在TF-Slim中很容易地被建立和恢復:
# Model Variables weights = slim.model_variable('weights', shape=[10, 10, 3 , 3], initializer=tf.truncated_normal_initializer(stddev=0.1), regularizer=slim.l2_regularizer(0.05), device='/CPU:0') model_variables = slim.get_model_variables() # Regular variables my_var = slim.variable('my_var', shape=[20, 1], initializer=tf.zeros_initializer()) regular_variables_and_model_variables = slim.get_variables()
這是如何工做的呢?當你經過TF-Slim的layer或者直接經過slim.model_variable函數建立一個模型的變量時,TF-Slim將變量添加到tf.GraphKeys.MODEL_VARIABLES集合中。若是你想擁有本身定製化的layers或者variables建立機制,可是仍然想利用TF-Slim來管理你的變量,此時,TF-Slim提供一個方便的函數,用於添加模型的變量到集合中:
my_model_variable = CreateViaCustomCode() # Letting TF-Slim know about the additional variable. slim.add_model_variable(my_model_variable)
在原生的Tensorflow中,要定義一些層(好比說卷積層,全鏈接層,BatchNorm層等)是比較麻煩的。舉個例子,神經網絡中的卷積層由如下幾個步驟組成:
上面的步驟使用原始的Tensorflow代碼,實現以下:
input = ... with tf.name_scope('conv1_1') as scope: kernel = tf.Variable(tf.truncated_normal([3, 3, 64, 128], dtype=tf.float32, stddev=1e-1), name='weights') conv = tf.nn.conv2d(input, kernel, [1, 1, 1, 1], padding='SAME') biases = tf.Variable(tf.constant(0.0, shape=[128], dtype=tf.float32), trainable=True, name='biases') bias = tf.nn.bias_add(conv, biases) conv1 = tf.nn.relu(bias, name=scope)
爲了減小重複代碼,TF-Slim提供了一些方便高級別更抽象的神經網絡層。好比說,卷積層實現以下:
input = ... net = slim.conv2d(input, 128, [3, 3], scope='conv1_1')
TF-Slim提供了大量的標準的實現,用於創建神經網絡。包括以下函數:
Layer |
TF-Slim |
BiasAdd |
|
BatchNorm |
|
Conv2d |
|
Conv2dInPlane |
|
Conv2dTranspose (Deconv) |
|
FullyConnected |
|
AvgPool2D |
|
Dropout |
|
Flatten |
|
MaxPool2D |
|
OneHotEncoding |
|
SeparableConv2 |
|
UnitNorm |
TF-Slim也兩個操做符,稱爲repeat
和 stack
,容許用戶重複執行相同的操做。好比說,下面幾個卷積層加一個池化層是VGG網絡的一部分,
net = ... net = slim.conv2d(net, 256, [3, 3], scope='conv3_1') net = slim.conv2d(net, 256, [3, 3], scope='conv3_2') net = slim.conv2d(net, 256, [3, 3], scope='conv3_3') net = slim.max_pool2d(net, [2, 2], scope='pool2')
減小重複代碼的其中一種方法是利用for循環,以下:
net = ... for i in range(3): net = slim.conv2d(net, 256, [3, 3], scope='conv3_%d' % (i+1)) net = slim.max_pool2d(net, [2, 2], scope='pool2')
另外一種方式是,使用TF-Slim中的repeat操做:
net = slim.repeat(net, 3, slim.conv2d, 256, [3, 3], scope='conv3') net = slim.max_pool2d(net, [2, 2], scope='pool2')
上面例子中,slim.repeat會自動給每個卷積層的scopes命名爲'conv3/conv3_1', 'conv3/conv3_2' 和 'conv3/conv3_3'。
另外,TF-Slim的 slim.stack
操做容許用戶用不一樣的參數重複調用同一種操做。slim.stack
也爲每個被建立的操做建立一個新的tf.variable_scope。好比說,下面是一種簡單的方式來建立多層感知器(Multi-Layer Perceptron (MLP)):
# Verbose way: x = slim.fully_connected(x, 32, scope='fc/fc_1') x = slim.fully_connected(x, 64, scope='fc/fc_2') x = slim.fully_connected(x, 128, scope='fc/fc_3') # Equivalent, TF-Slim way using slim.stack: slim.stack(x, slim.fully_connected, [32, 64, 128], scope='fc')
在上面的例子中,slim.stack調用了slim.fully_connected三次。相似的,咱們可使用stack來簡化多層的卷積層。
# Verbose way: x = slim.conv2d(x, 32, [3, 3], scope='core/core_1') x = slim.conv2d(x, 32, [1, 1], scope='core/core_2') x = slim.conv2d(x, 64, [3, 3], scope='core/core_3') x = slim.conv2d(x, 64, [1, 1], scope='core/core_4') # Using stack: slim.stack(x, slim.conv2d, [(32, [3, 3]), (32, [1, 1]), (64, [3, 3]), (64, [1, 1])], scope='core')
除了Tensorflow中做用域(scope)以外(name_scope, variable_scope),TF-Slim增長了新的做用域機制,稱爲arg_scope。這個新的做用域容許使用者明確一個或者多個操做和一些參數,這些定義好的操做或者參數會傳遞給arg_scope內部的每個操做。下面舉例說明。先看以下代碼片斷:
net = slim.conv2d(inputs, 64, [11, 11], 4, padding='SAME', weights_initializer=tf.truncated_normal_initializer(stddev=0.01), weights_regularizer=slim.l2_regularizer(0.0005), scope='conv1') net = slim.conv2d(net, 128, [11, 11], padding='VALID', weights_initializer=tf.truncated_normal_initializer(stddev=0.01), weights_regularizer=slim.l2_regularizer(0.0005), scope='conv2') net = slim.conv2d(net, 256, [11, 11], padding='SAME', weights_initializer=tf.truncated_normal_initializer(stddev=0.01), weights_regularizer=slim.l2_regularizer(0.0005), scope='conv3')
從上面的代碼中能夠清楚的看出來,有3層卷積層,其中不少超參數都是同樣的。兩個卷積層有相同的padding,全部三個卷積層有相同的weights_initializer和weight_regularizer。上面的代碼包含了大量重複的值,其中一種解決方法是使用變量來講明一些默認的值:
padding = 'SAME' initializer = tf.truncated_normal_initializer(stddev=0.01) regularizer = slim.l2_regularizer(0.0005) net = slim.conv2d(inputs, 64, [11, 11], 4, padding=padding, weights_initializer=initializer, weights_regularizer=regularizer, scope='conv1') net = slim.conv2d(net, 128, [11, 11], padding='VALID', weights_initializer=initializer, weights_regularizer=regularizer, scope='conv2') net = slim.conv2d(net, 256, [11, 11], padding=padding, weights_initializer=initializer, weights_regularizer=regularizer, scope='conv3')
上面的解決方案其實並無減小代碼的混亂程度。經過使用arg_scope,咱們能夠既能夠保證每一層使用相同的值,也能夠簡化代碼:
with slim.arg_scope([slim.conv2d], padding='SAME', weights_initializer=tf.truncated_normal_initializer(stddev=0.01) weights_regularizer=slim.l2_regularizer(0.0005)): net = slim.conv2d(inputs, 64, [11, 11], scope='conv1') net = slim.conv2d(net, 128, [11, 11], padding='VALID', scope='conv2') net = slim.conv2d(net, 256, [11, 11], scope='conv3')
上面的例子代表,使用arg_scope可使得代碼變得更整潔、更乾淨而且更加容易維護。注意到,在arg_scope中規定的參數值,它們能夠被局部覆蓋。好比說,上面的padding參數被設置成‘SAME’,可是在第二個卷積層中用‘VALID’覆蓋了這個參數。
咱們也能夠嵌套使用arg_scope,在相同的做用域內使用多個操做。舉例以下:
with slim.arg_scope([slim.conv2d, slim.fully_connected], activation_fn=tf.nn.relu, weights_initializer=tf.truncated_normal_initializer(stddev=0.01), weights_regularizer=slim.l2_regularizer(0.0005)): with slim.arg_scope([slim.conv2d], stride=1, padding='SAME'): net = slim.conv2d(inputs, 64, [11, 11], 4, padding='VALID', scope='conv1') net = slim.conv2d(net, 256, [5, 5], weights_initializer=tf.truncated_normal_initializer(stddev=0.03), scope='conv2') net = slim.fully_connected(net, 1000, activation_fn=None, scope='fc')
在上面的例子中,在第一個arg_scope中,卷積層和全鏈接層被應用於相同的權重初始化和權重正則化;在第二個arg_scope中,額外的參數僅僅對卷積層conv2d起做用。
經過結合TF-Slim的Variables, Operations 和 scopes,咱們可使用比較少的代碼來實現一個比較複雜的網絡。好比說,整個VGG網絡定義以下:
def vgg16(inputs): with slim.arg_scope([slim.conv2d, slim.fully_connected], activation_fn=tf.nn.relu, weights_initializer=tf.truncated_normal_initializer(0.0, 0.01), weights_regularizer=slim.l2_regularizer(0.0005)): net = slim.repeat(inputs, 2, slim.conv2d, 64, [3, 3], scope='conv1') net = slim.max_pool2d(net, [2, 2], scope='pool1') net = slim.repeat(net, 2, slim.conv2d, 128, [3, 3], scope='conv2') net = slim.max_pool2d(net, [2, 2], scope='pool2') net = slim.repeat(net, 3, slim.conv2d, 256, [3, 3], scope='conv3') net = slim.max_pool2d(net, [2, 2], scope='pool3') net = slim.repeat(net, 3, slim.conv2d, 512, [3, 3], scope='conv4') net = slim.max_pool2d(net, [2, 2], scope='pool4') net = slim.repeat(net, 3, slim.conv2d, 512, [3, 3], scope='conv5') net = slim.max_pool2d(net, [2, 2], scope='pool5') net = slim.fully_connected(net, 4096, scope='fc6') net = slim.dropout(net, 0.5, scope='dropout6') net = slim.fully_connected(net, 4096, scope='fc7') net = slim.dropout(net, 0.5, scope='dropout7') net = slim.fully_connected(net, 1000, activation_fn=None, scope='fc8') return net
訓練Tensorflow模型要求一個模型、一個loss function、梯度計算和一個訓練的程序,用來迭代的根據loss計算模型權重的梯度和更新權重。TF-Slim提供了loss function和一些幫助函數,來運行訓練和評估。
Loss function定義了一個咱們須要最小化的量。對於分類問題,主要是計算真正的分佈與預測的機率分佈之間的交叉熵。對於迴歸問題,主要是計算預測值與真實值均方偏差。
特定的模型,好比說多任務學習模型,要求同時使用多個loss function;換句話說,最終被最小化的loss function是多個其餘的loss function之和。好比說,一個同時預測圖像中場景的類型和深度的模型,該模型的loss function就是分類loss和深度預測loss之和(the sum of the classification loss and depth prediction loss)。
TF-Slim經過losses模塊爲用戶提供了一種機制,使得定義loss function變得簡單。好比說,下面的是咱們想要訓練VGG網絡的簡單示例:
import tensorflow as tf import tensorflow.contrib.slim.nets as nets vgg = nets.vgg # Load the images and labels. images, labels = ... # Create the model. predictions, _ = vgg.vgg_16(images) # Define the loss functions and get the total loss. loss = slim.losses.softmax_cross_entropy(predictions, labels)
在上面這個例子中,咱們首先建立一個模型(利用TF-Slim的VGG實現),而後增長了標準的分類loss。如今,讓咱們看看當咱們有一個多個輸出的多任務模型的狀況:
# Load the images and labels. images, scene_labels, depth_labels = ... # Create the model. scene_predictions, depth_predictions = CreateMultiTaskModel(images) # Define the loss functions and get the total loss. classification_loss = slim.losses.softmax_cross_entropy(scene_predictions, scene_labels) sum_of_squares_loss = slim.losses.sum_of_squares(depth_predictions, depth_labels) # The following two lines have the same effect: total_loss = classification_loss + sum_of_squares_loss total_loss = slim.losses.get_total_loss(add_regularization_losses=False)
在這個例子中,咱們有2個loss,是經過調用slim.losses.softmax_cross_entropy
和 slim.losses.sum_of_squares
獲得。咱們能夠將這兩個loss加在一塊兒或者調用slim.losses.get_total_loss()來獲得所有的loss(total_loss)。這是如何工做的?當你經過TF-Slim建立一個loss時,TF-Slim將loss加到一個特殊的TensorFlow collection of loss functions。這使得你既能夠手動得管理所有的loss,也可讓TF-Slim來替你管理它們。
若是你想讓TF-Slim爲你管理losses可是你有一個本身實現的loss該怎麼辦?loss_ops.py 也有一個函數能夠將你本身實現的loss加到 TF-Slims collection中。舉例以下:
# Load the images and labels. images, scene_labels, depth_labels, pose_labels = ... # Create the model. scene_predictions, depth_predictions, pose_predictions = CreateMultiTaskModel(images) # Define the loss functions and get the total loss. classification_loss = slim.losses.softmax_cross_entropy(scene_predictions, scene_labels) sum_of_squares_loss = slim.losses.sum_of_squares(depth_predictions, depth_labels) pose_loss = MyCustomLossFunction(pose_predictions, pose_labels) slim.losses.add_loss(pose_loss) # Letting TF-Slim know about the additional loss. # The following two ways to compute the total loss are equivalent: regularization_loss = tf.add_n(slim.losses.get_regularization_losses()) total_loss1 = classification_loss + sum_of_squares_loss + pose_loss + regularization_loss # (Regularization Loss is included in the total loss by default). total_loss2 = slim.losses.get_total_loss()
在這個例子中,咱們既能夠手動的計算的出所有的loss function,也可讓TF-Slim知道這個額外的loss而後讓TF-Slim處理這個loss。
TF-Slim提供了一個簡單可是很強的用於訓練模型的工具(在 learning.py)。其中包括一個能夠重複測量loss,計算梯度和將模型保存到磁盤的訓練函數。舉個例子,一旦咱們定義好了模型,loss function和最優化方法,咱們能夠調用slim.learning.create_train_op
和 slim.learning.train
來實現優化。
g = tf.Graph() # Create the model and specify the losses... ... total_loss = slim.losses.get_total_loss() optimizer = tf.train.GradientDescentOptimizer(learning_rate) # create_train_op ensures that each time we ask for the loss, the update_ops # are run and the gradients being computed are applied too. train_op = slim.learning.create_train_op(total_loss, optimizer) logdir = ... # Where checkpoints are stored. slim.learning.train( train_op, logdir, number_of_steps=1000, save_summaries_secs=300, save_interval_secs=600):
在這個例子中,提供給slim.learning.train的參數有1)train_op,用於計算loss和梯度,2)logdir用於聲明checkpoints和event文件保存的路徑。咱們能夠用number_of_steps參數來限制梯度降低的步數;save_summaries_secs=300代表咱們每5分鐘計算一次summaries,save_interval_secs=600代表咱們每10分鐘保存一次模型的checkpoint。
下面是訓練一個VGG網絡的例子。
import tensorflow as tf import tensorflow.contrib.slim.nets as nets slim = tf.contrib.slim vgg = nets.vgg ... train_log_dir = ... if not tf.gfile.Exists(train_log_dir): tf.gfile.MakeDirs(train_log_dir) with tf.Graph().as_default(): # Set up the data loading: images, labels = ... # Define the model: predictions = vgg.vgg_16(images, is_training=True) # Specify the loss function: slim.losses.softmax_cross_entropy(predictions, labels) total_loss = slim.losses.get_total_loss() tf.summary.scalar('losses/total_loss', total_loss) # Specify the optimization scheme: optimizer = tf.train.GradientDescentOptimizer(learning_rate=.001) # create_train_op that ensures that when we evaluate it to get the loss, # the update_ops are done and the gradient updates are computed. train_tensor = slim.learning.create_train_op(total_loss, optimizer) # Actually runs training. slim.learning.train(train_tensor, train_log_dir)
當一個模型被訓練完畢以後,它能夠從一個給定的checkpoint中使用tf.train.Saver()來恢復變量。在不少狀況下,tf.train.Saver()提供一個簡答的機制來恢復全部變量或者一部分變量。
# Create some variables. v1 = tf.Variable(..., name="v1") v2 = tf.Variable(..., name="v2") ... # Add ops to restore all the variables. restorer = tf.train.Saver() # Add ops to restore some variables. restorer = tf.train.Saver([v1, v2]) # Later, launch the model, use the saver to restore variables from disk, and # do some work with the model. with tf.Session() as sess: # Restore variables from disk. restorer.restore(sess, "/tmp/model.ckpt") print("Model restored.") # Do some work with the model ...
詳細的信息能夠查看 Restoring Variables 和 Choosing which Variables to Save and Restore這兩個頁面。
在一個新的數據集或者一個新的任務上fine-tune一個預訓練的模型一般是比較受歡迎的。咱們可使用TF-Slim的helper函數來選擇想要恢復的一部分變量:
# Create some variables. v1 = slim.variable(name="v1", ...) v2 = slim.variable(name="nested/v2", ...) ... # Get list of variables to restore (which contains only 'v2'). These are all # equivalent methods: variables_to_restore = slim.get_variables_by_name("v2") # or variables_to_restore = slim.get_variables_by_suffix("2") # or variables_to_restore = slim.get_variables(scope="nested") # or variables_to_restore = slim.get_variables_to_restore(include=["nested"]) # or variables_to_restore = slim.get_variables_to_restore(exclude=["v1"]) # Create the saver which will be used to restore the variables. restorer = tf.train.Saver(variables_to_restore) with tf.Session() as sess: # Restore variables from disk. restorer.restore(sess, "/tmp/model.ckpt") print("Model restored.") # Do some work with the model ...
當從一個checkpoint中恢復變量時,Saver定位在checkpoint文件中變量的名字,而且將它們映射到當前圖(graph)的變量中去。上面,咱們經過傳遞給saver一個變量列表來建立一個saver。在這種狀況下,在checkpoint文件中定位的變量名隱式地從每一個提供的變量的var. op. name中得到。
當checkpoint文件中的變量名與graph匹配時,將會工做良好。然而,有時候,咱們想要從一個與當前的graph不一樣變量名的checkpoint中恢復變量,那麼在這種狀況下,咱們必須給Saver提供一個字典,該字典將每一個checkpoint中變量名映射到每一個graph的變量。下面的例子是,經過一個簡單的函數得到checkpoint中的變量的名字。
# Assuming than 'conv1/weights' should be restored from 'vgg16/conv1/weights' def name_in_checkpoint(var): return 'vgg16/' + var.op.name # Assuming than 'conv1/weights' and 'conv1/bias' should be restored from 'conv1/params1' and 'conv1/params2' def name_in_checkpoint(var): if "weights" in var.op.name: return var.op.name.replace("weights", "params1") if "bias" in var.op.name: return var.op.name.replace("bias", "params2") variables_to_restore = slim.get_model_variables() variables_to_restore = {name_in_checkpoint(var):var for var in variables_to_restore} restorer = tf.train.Saver(variables_to_restore) with tf.Session() as sess: # Restore variables from disk. restorer.restore(sess, "/tmp/model.ckpt")
考慮這麼一種狀況:咱們有一個預訓練好的VGG16模型,該模型是在ImageNet數據集上訓練好的,有1000類。然而,咱們想要將其應用到只有20類的Pascal VOC數據集上。爲了實現這個,咱們可使用不包括最後一層的預訓練模型來初始化咱們的新模型。
# Load the Pascal VOC data image, label = MyPascalVocDataLoader(...) images, labels = tf.train.batch([image, label], batch_size=32) # Create the model predictions = vgg.vgg_16(images) train_op = slim.learning.create_train_op(...) # Specify where the Model, trained on ImageNet, was saved. model_path = '/path/to/pre_trained_on_imagenet.checkpoint' # Specify where the new model will live: log_dir = '/path/to/my_pascal_model_dir/' # Restore only the convolutional layers: variables_to_restore = slim.get_variables_to_restore(exclude=['fc6', 'fc7', 'fc8']) init_fn = assign_from_checkpoint_fn(model_path, variables_to_restore) # Start training. slim.learning.train(train_op, log_dir, init_fn=init_fn)
一旦咱們已經訓練好了一個模型(或者模型正在訓練之中),咱們想要看看模型的實際表現能力。這個能夠經過使用一些評估度量來實現,該度量能夠對模型的表現能力評分。而評估代碼其實是加載數據,作預測,將預測結果與真實值作比較,最後獲得得分。這個步驟能夠運行一次或者週期重複。
咱們將度量定義爲一個性能度量,它不是一個loss函數(losses是在訓練的時候直接最優化),但咱們仍然感興趣的是評估模型的目的。好比說,咱們想要最優化log loss,可是咱們感興趣的度量多是F1得分(test accuracy),或者是Intersection Over Union score(這是不可微的,所以不能做爲損失使用)。
TF-Slim提供了一些使得評估模型變得簡單的度量操做。計算度量的值能夠分爲如下三個步驟:
舉個例子,爲了計算mean_absolute_error,2個變量,count
和 total
變量被初始化爲0。在聚合期間,咱們觀測到一些預測值和標籤值,計算它們的絕對差值而後加到total中。每一次咱們觀測到新的一個數據,咱們增長count。最後,在Finalization期間,total除以count來得到均值mean。
下面的示例演示了聲明度量標準的API。因爲度量常常在測試集上進行評估,所以咱們假設使用的是測試集。
images, labels = LoadTestData(...) predictions = MyModel(images) mae_value_op, mae_update_op = slim.metrics.streaming_mean_absolute_error(predictions, labels) mre_value_op, mre_update_op = slim.metrics.streaming_mean_relative_error(predictions, labels) pl_value_op, pl_update_op = slim.metrics.percentage_less(mean_relative_errors, 0.3)
如示例所示,一個度量的建立返回兩個值:value_op和update_op。value_op是一個冪等操做,它返回度量的當前值。update_op是執行上面提到的聚合步驟的操做,以及返回度量的值。
跟蹤每一個value_op和update_op是很費力的。爲了解決這個問題,TF-Slim提供了兩個便利功能:
# Aggregates the value and update ops in two lists: value_ops, update_ops = slim.metrics.aggregate_metrics( slim.metrics.streaming_mean_absolute_error(predictions, labels), slim.metrics.streaming_mean_squared_error(predictions, labels)) # Aggregates the value and update ops in two dictionaries: names_to_values, names_to_updates = slim.metrics.aggregate_metric_map({ "eval/mean_absolute_error": slim.metrics.streaming_mean_absolute_error(predictions, labels), "eval/mean_squared_error": slim.metrics.streaming_mean_squared_error(predictions, labels), })
將代碼所有放在一塊兒:
import tensorflow as tf import tensorflow.contrib.slim.nets as nets slim = tf.contrib.slim vgg = nets.vgg # Load the data images, labels = load_data(...) # Define the network predictions = vgg.vgg_16(images) # Choose the metrics to compute: names_to_values, names_to_updates = slim.metrics.aggregate_metric_map({ "eval/mean_absolute_error": slim.metrics.streaming_mean_absolute_error(predictions, labels), "eval/mean_squared_error": slim.metrics.streaming_mean_squared_error(predictions, labels), }) # Evaluate the model using 1000 batches of data: num_batches = 1000 with tf.Session() as sess: sess.run(tf.global_variables_initializer()) sess.run(tf.local_variables_initializer()) for batch_id in range(num_batches): sess.run(names_to_updates.values()) metric_values = sess.run(names_to_values.values()) for metric, value in zip(names_to_values.keys(), metric_values): print('Metric %s has value: %f' % (metric, value))
TF-Slim提供了一個評估模塊(evaluation.py),它包含了使用來自 metric_ops.py 模塊編寫模型評估腳本的輔助函數。這些功能包括按期運行評估、對數據批量進行評估、打印和彙總度量結果的功能。
import tensorflow as tf slim = tf.contrib.slim # Load the data images, labels = load_data(...) # Define the network predictions = MyModel(images) # Choose the metrics to compute: names_to_values, names_to_updates = slim.metrics.aggregate_metric_map({ 'accuracy': slim.metrics.accuracy(predictions, labels), 'precision': slim.metrics.precision(predictions, labels), 'recall': slim.metrics.recall(mean_relative_errors, 0.3), }) # Create the summary ops such that they also print out to std output: summary_ops = [] for metric_name, metric_value in names_to_values.iteritems(): op = tf.summary.scalar(metric_name, metric_value) op = tf.Print(op, [metric_value], metric_name) summary_ops.append(op) num_examples = 10000 batch_size = 32 num_batches = math.ceil(num_examples / float(batch_size)) # Setup the global step. slim.get_or_create_global_step() output_dir = ... # Where the summaries are stored. eval_interval_secs = ... # How often to run the evaluation. slim.evaluation.evaluation_loop( 'local', checkpoint_dir, log_dir, num_evals=num_batches, eval_op=names_to_updates.values(), summary_op=tf.summary.merge(summary_ops), eval_interval_secs=eval_interval_secs)
Sergio Guadarrama and Nathan Silberman