TensorFlow-Slim使用方法說明


翻譯自:https://github.com/tensorflow/tensorflow/tree/master/tensorflow/contrib/slim

 

TensorFlow-Slim

 

TF-Slim是Tensorflow中一個輕量級的庫,用於定義、訓練和評估復雜的模型。TF-Slim中的組件可以與Tensorflow中原生的函數一起使用,與其他的框架,比如與tf.contrib.learn也可以一起使用。

 

Usage使用方法

import tensorflow.contrib.slim as slim

Why TF-Slim?

       TF-Slim可以使建立、訓練和評估神經網絡更加簡單。

l  允許用戶通過減少模板代碼使得模型更加簡潔。這個可以通過使用argument scoping和大量的高層layers、variables來實現;

l  通過使用常用的正則化( regularizers)使得建立模型更加簡單;

l  一些廣泛使用的計算機視覺相關的模型(比如VGG,AlexNet)已經在slim中定義好了,用戶可以很方便的使用;這些既可以當成黑盒使用,也可以被擴展使用,比如添加一些“multiple heads”到不同的內部的層;

l  Slim使得擴展復雜模型變得容易,可以使用已經存在的模型的checkpoints來開始訓練算法。

 

What are the various components of TF-Slim?

TF-Slim由幾個獨立存在的組件組成,主要包括以下幾個:

arg_scope:提供一個新的作用域(scope),稱為arg_scope,在該作用域(scope)中,用戶可以定義一些默認的參數,用於特定的操作;

data:包含TF-Slim的dataset定義,data providersparallel_reader,和 decoding utilities;

evaluation:包含用於模型評估的常規函數;

layers:包含用於建立模型的高級layers;

learning:包含一些用於訓練模型的常規函數;

losses:包含一些用於loss function的函數;

metrics:包含一些熱門的評價標准;

nets:包含一些熱門的網絡定義,如VGG,AlexNet等模型;

queues:提供一個內容管理者,使得可以很容易、很安全地啟動和關閉QueueRunners;

regularizers:包含權重正則化;

variables:提供一個方便的封裝,用於變量創建和使用。

 

Defining Models

       使用TF-Slim,結合variables, layers 和 scopes,模型可以很簡潔地被定義。這些元件定義如下。

 

Variables

       在原生的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)

Layers

       在原生的Tensorflow中,要定義一些層(比如說卷積層,全連接層,BatchNorm層等)是比較麻煩的。舉個例子,神經網絡中的卷積層由以下幾個步驟組成:

  1. 創建權重和偏置變量
  2. 將輸入與權重做卷積運算
  3. 將偏置加到第二步的卷積運算得到的結果中
  4. 使用一個激活函數

上面的步驟使用原始的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

slim.bias_add

BatchNorm

slim.batch_norm

Conv2d

slim.conv2d

Conv2dInPlane

slim.conv2d_in_plane

Conv2dTranspose (Deconv)

slim.conv2d_transpose

FullyConnected

slim.fully_connected

AvgPool2D

slim.avg_pool2d

Dropout

slim.dropout

Flatten

slim.flatten

MaxPool2D

slim.max_pool2d

OneHotEncoding

slim.one_hot_encoding

SeparableConv2

slim.separable_conv2d

UnitNorm

slim.unit_norm

 

       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')

Scopes

       除了Tensorflow中作用域(scope)之外(name_scopevariable_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起作用。

 

Working Example: Specifying the VGG16 Layers

       通過結合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

Training Models

       訓練Tensorflow模型要求一個模型、一個loss function、梯度計算和一個訓練的程序,用來迭代的根據loss計算模型權重的梯度和更新權重。TF-Slim提供了loss function和一些幫助函數,來運行訓練和評估。

 

Losses

       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。

 

Training Loop

       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。

 

 

Working Example: Training the VGG16 Model

       下面是訓練一個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)

 

Fine-Tuning Existing Models

Brief Recap on Restoring Variables from a Checkpoint

       當一個模型被訓練完畢之后,它可以從一個給定的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這兩個頁面。

 

Partially Restoring Models

       在一個新的數據集或者一個新的任務上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
  ...

Restoring models with different variable names

       當從一個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")

Fine-Tuning a Model on a different task

       考慮這么一種情況:我們有一個預訓練好的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)

 

Evaluating Models.

       一旦我們已經訓練好了一個模型(或者模型正在訓練之中),我們想要看看模型的實際表現能力。這個可以通過使用一些評估度量來實現,該度量可以對模型的表現能力評分。而評估代碼實際上是加載數據,做預測,將預測結果與真實值做比較,最后得到得分。這個步驟可以運行一次或者周期重復。

 

Metrics

       我們將度量定義為一個性能度量,它不是一個loss函數(losses是在訓練的時候直接最優化),但我們仍然感興趣的是評估模型的目的。比如說,我們想要最優化log loss,但是我們感興趣的度量可能是F1得分(test accuracy),或者是Intersection Over Union score(這是不可微的,因此不能作為損失使用)。

       TF-Slim提供了一些使得評估模型變得簡單的度量操作。計算度量的值可以分為以下三個步驟:

  1. 初始化(Initialization):初始化用於計算度量的變量
  2. 聚合(Aggregation):使用操作(比如求和操作)來計算度量
  3. 終止化(Finalization):(可選的)使用最終的操作來計算度量值,比如說計算均值,最小值,最大值等。

 

舉個例子,為了計算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),
})

Working example: Tracking Multiple Metrics

 

       將代碼全部放在一起:

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))

Evaluation Loop

       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)

 

Authors

Sergio Guadarrama and Nathan Silberman

 


免責聲明!

本站轉載的文章為個人學習借鑒使用,本站對版權不負任何法律責任。如果侵犯了您的隱私權益,請聯系本站郵箱yoyou2525@163.com刪除。



 
粵ICP備18138465號   © 2018-2025 CODEPRJ.COM