1、簡介
對於tensorflow.contrib這個庫,tensorflow官方對它的描述是:此目錄中的任何代碼未經官方支持,可能會隨時更改或刪除。每個目錄下都有指定的所有者。它旨在包含額外功能和貢獻,最終會合並到核心TensorFlow中,但其接口可能仍然會發生變化,或者需要進行一些測試,看是否可以獲得更廣泛的接受。所以slim依然不屬於原生tensorflow。那么什么是slim?slim到底有什么用?
slim是一個使構建,訓練,評估神經網絡變得簡單的庫。它可以消除原生tensorflow里面很多重復的模板性的代碼,讓代碼更緊湊,更具備可讀性。另外slim提供了很多計算機視覺方面的著名模型(VGG, AlexNet等),我們不僅可以直接使用,甚至能以各種方式進行擴展。
Slim由幾個獨立存在的部分組成,以下為主要的模塊:
arg_scope: 提供了一個新的scope,它允許用戶定義在這個scope內的許多特殊操作(比如卷積、池化等)的默認參數。
data: 這個模塊包含data_decoder、prefetch_queue、dataset_data_provider、tfexample_decoder、dataset、data_provider、parallel_reader。
evaluation: 包含一些評估模型的例程。
layers: 包含使用TensorFlow搭建模型的一些high level layers 。
learning: 包含訓練模型的一些例程。
losses: 包含常用的損失函數。
metrics: 包含一些常用的評估指標。
nets: 包含一些常用的網絡模型的定義,比如VGG和 AlexNet。
queues: 提供一個上下文管理器,使得開啟和關閉一個QueueRunners.更加簡單和安全。
regularizers: 包含權重正則化器。
variables: 為變量的創建和操作提供了比較方面的包裝器。
2、定義模型
通過組合slim中變量(variables)、網絡層(layer)、前綴名(scope),模型可以被簡潔的定義。
(1)變量(Variables)定義
在原始的TensorFlow中,創建變量時,要么需要預定義的值,要么需要一個初始化機制(比如從高斯分布中隨機采樣)。此外,如果需要在一個特定的特備(比如GPU)上創建一個變量,這個變量必須被顯式的創建。為了減少創建變量的代碼量,slim提供了一系列包裝器函數允許調用者輕易的創建變量。
例如,為了創建一個權重變量,它使用截斷正太分布初始化、使用L2的正則化損失並且把這個變量放到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中,有兩種類型的變量:常規(regular)變量和局部(local)變量。大部分的變量都是常規變量,它們一旦被創建,它們就會被保存到磁盤。而局部變量只存在於一個Session的運行期間,它們並不會被保存到磁盤。在Slim中,模型變量代表一個模型中的各種參數,Slim通過定義模型變量,進一步把各種變量區分開來。模型變量在訓練過程中不斷被訓練和調參,在評估和預測時可以從checkpoint文件中加載進來,例如被slim.fully_connected() 或 slim.conv2d()網絡層創建的變量。而非模型變量是指那些在訓練和評估中用到的但是在預測階段沒有用到的變量,例如global_step變量在訓練和評估中用到,但是它並不是一個模型變量。同樣,移動平均變量可能反映模型變量,但移動平均值本身不是模型變量。模型變量和常規變量可以被Slim很容易的創建如下:
# 模型變量
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()
# 常規變量
my_var = slim.variable('my_var',
shape=[20, 1],
initializer=tf.zeros_initializer())
regular_variables_and_model_variables = slim.get_variables()
那這是怎么工作的呢?當你通過Slim的網絡層或者直接通過slim.model_variable()創建模型變量時,Slim把模型變量加入到tf.GraphKeys.MODEL_VARIABLES 的collection中。那如果你有屬於自己的自定義網絡層或者變量創建例程,但是你仍然想要Slim來幫你管理,這時要怎么辦呢?Slim提供了一個便利的函數把模型變量加入到它的collection中。
my_model_variable = CreateViaCustomCode()
# 讓Slim知道有額外的變量
slim.add_model_variable(my_model_variable)
(2)網絡層(layers)定義
雖然TensorFlow操作集非常廣泛,但神經網絡的開發人員通常會根據更高級別的概念來考慮模型,例如“層”,“損失”,“度量”和“網絡”。一個網絡層,比如一個卷積層、一個全連接層或者一個BatchNorm層相對於一個簡單的TensorFlow操作而言是非常的抽象的,而且一個網絡層通常會包含多個TensorFlow的操作。此外,不像TensorFlow許多原生的操作一樣,一個網絡層通常(但不總是)通常有與之相關聯的變量。例如,神經網絡中的一個卷積層通常由以下幾個low-level的操作組成:
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)
為了減輕這種重復碼代碼的工作量,Slim提供了許多定義在網絡層(layer)層次的操作,這些操作使得創建模型更加方便。比如,使用Slim中的函數來創建一個和上面類似網絡層如下:
input = ...
net = slim.conv2d(input, 128, [3, 3], scope='conv1_1')
Slim提供了在搭建神經網絡模型時許多函數的標准實現,包括如下(以下函數的實現參考源碼):
Layer TF-Slim
BiasAdd(加上偏置) slim.bias_add
BatchNorm(歸一化) slim.batch_norm
Conv2d(2維卷積) slim.conv2d
Conv2dInPlane slim.conv2d_in_plane
Conv2dTranspose (反卷積) slim.conv2d_transpose
FullyConnected(全連接) slim.fully_connected
AvgPool2D(2維平均池化) slim.avg_pool2d
Dropout slim.dropout
Flatten(展為一維) slim.flatten
MaxPool2D(2維最大池化) slim.max_pool2d
OneHotEncoding(onehot編碼) slim.one_hot_encoding
SeparableConv2(可分離卷積) slim.separable_conv2d
UnitNorm(單位歸一化) slim.unit_norm
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')
一種減少這種代碼重復的方式是使用循環,例如:
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')
而使用Slim提供的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()操作不僅僅可以應用相同的參數,它還可以為操作加上scope,因此,被賦予每個后續slim.conv2d()操作的scope都被附加上下划線和編號。具體來說,在上例中的scope將會被命名為:'conv3/conv3_1', 'conv3/conv3_2' 和 'conv3/conv3_3'。
此外,Slim的slim.stack()操作允許調用者使用不同的參數來調用相同的操作,從而建立一個堆棧式(stack)或者塔式(Tower)的網絡層。slim.stack()同樣也為每個操作創建了一個新的tf.variable_scope()。比如,創建多層感知機(MLP)的簡單的方式如下:
# 常規方式
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')
# 使用slim.stack()方式
slim.stack(x, slim.fully_connected, [32, 64, 128], scope='fc')
在這個例子中,slim.stack()三次調用slim.fully_connected(),把每次函數調用的輸出傳遞給下一次的調用,而每次調用的隱層的單元數從32到64到128,。同樣的,我們也可以用slim.stack()來簡化多個卷積操作:
# 常規方式
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')
# 使用Slim.stack():
slim.stack(x, slim.conv2d, [(32, [3, 3]), (32, [1, 1]), (64, [3, 3]), (64, [1, 1])], scope='core')
(3)Scopes定義
除了TensorFlow中的scope機制(name_scope, variable_scope),Slim添加了一種新的稱為arg_scope的機制。這種新的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')
可以看到,這三個卷積共享某些相同的超參數。有兩個有相同的padding方式,三個都有相同的權重初始化器和權重正則化器。這種代碼閱讀性很差而且包含很多可以被分解出去的重復值,一個可行的解決方案是指定變量的默認值。
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’,但是第二個卷積的的padding可以被重寫為‘VALID’。
我們也可以嵌套使用arg_scope,在相同的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對slim.conv2d()和slim.fully_connected()采用相同的權重初始化器和權重正則化器參數。在第二個arg_scope中,只針對slim.conv2d的附加的默認參數被具體制定。
接下來我們定義VGG16網絡,通過組合Slim的變量、操作和Scope,我們可以用很少的幾行代碼寫一個常規上來講非常復雜的網絡,整個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
3、訓練模型
訓練TensorFlow的模型需要一個模型(model)、一個損失函數(loss)、梯度計算和一個訓練過程,訓練過程迭代地計算損失函數相對於權重的梯度並且根據梯度值來更新權重值。在訓練和評估過程中,Slim提供了一些通用的損失函數和一系列幫助(helper)函數
(1)損失函數(Losses)
損失函數定義了一個我們需要優化的數量值。對於分類問題而言,損失函數通常是類別真實分布和預測分布之間的交叉熵。對於回歸問題而言,損失函數通常是真實值和預測值之間差值的平方和。在一些模型中,比如多任務的學習模型,就需要同時使用多個損失函數。換句話說,最終被優化的損失函數是其它損失函數的總和。比如,考慮一個這樣的模型,它需要同時預測圖像中場景的類別和每個像素的相機深度值(the depth from the camera of each pixel),這個模型的損失函數將是分類損失和深度值預測損失之和。
Slim的損失函數模塊提供了一個簡單易用的機制,可以定義和跟蹤損失函數,考慮一個訓練VGG16網絡的簡單例子:
import tensorflow as tf
import tensorflow.contrib.slim.nets as nets
vgg = nets.vgg
# 加載圖像和標簽
images, labels = ...
# 創建模型
predictions, _ = vgg.vgg_16(images)
# 定義損失函數和各種算是求和
loss = slim.losses.softmax_cross_entropy(predictions, labels)
在這個例子中,我們首先創建模型(使用Slim中的VGG實現),然后添加標准的分類損失。現在,讓我看一個有多任務的模型,這個模型會產生多個輸出。
# 加載圖像和標簽
images, scene_labels, depth_labels = ...
# 創建模型
scene_predictions, depth_predictions = CreateMultiTaskModel(images)
# 定義損失函數和獲取總的損失
classification_loss = slim.losses.softmax_cross_entropy(scene_predictions, scene_labels)
sum_of_squares_loss = slim.losses.sum_of_squares(depth_predictions, depth_labels)
# 下面兩行代碼的效果相同
total_loss = classification_loss + sum_of_squares_loss
total_loss = slim.losses.get_total_loss(add_regularization_losses=False)
在這個例子中,我們有兩個損失,分別是通過調用slim.losses.softmax_cross_entropy() 和 slim.losses.sum_of_squares()得到的,通過把它們加在一起或者調用slim.losses.sum_of_squares()函數我們可以獲得總的損失。這是怎么工作的呢?當你通過Slim創建一個損失函數的時候,Slim把這個損失添加到一個特殊的TensorFlow collection中,這就使得你可以手動管理總的損失,也可以讓Slim幫你管理總的損失。
那如果你想要Slim幫你管理一個你自定義的損失函數怎么辦呢?loss.py中有一個函數可以把你自定義的損失添加到Slim collection中,比如:
# 加載圖像和標簽
images, scene_labels, depth_labels, pose_labels = ...
# 創建模型
scene_predictions, depth_predictions, pose_predictions = CreateMultiTaskModel(images)
# 定義損失函數和獲取總的損失
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知道你自定義的附加損失
slim.losses.add_loss(pose_loss)
# 下列兩種計算總的損失的方式是等價的
regularization_loss = tf.add_n(slim.losses.get_regularization_losses())
total_loss1 = classification_loss + sum_of_squares_loss + pose_loss + regularization_loss
# (默認情況下,正則化損失是包含在總的損失之內的)
total_loss2 = slim.losses.get_total_loss()
在這個例子中,我們既可以手動管理總的損失函數,也可以讓Slim知道我們自定義的附加損失讓后讓Slim幫我們管理。
(2)循環訓練(Training Loop)
在learning.py文件中,Slim提供了一系列簡單強大的訓練模型的工具。其中就包含了一個訓練函數,它重復的測量損失、計算梯度值並且保存模型到磁盤中,此外也包含了手動計算梯度的幾個方便的函數。比如,一旦我們確定了模型、損失函數和優化器,我們就可以調用slim.learning.create_train_op() 和 slim.learning.train()來執行優化操作
g = tf.Graph()
# 創建模型和確定損失函數
...
total_loss = slim.losses.get_total_loss()
optimizer = tf.train.GradientDescentOptimizer(learning_rate)
# 創建訓練操作確保我們每次請求計算損失、運行權重更新操作和計算梯度值
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()提供了一個訓練操作,它有兩個作用:第一個是計算損失,第二個是計算梯度值,logdir指定了保存checkpoint文件和event文件的目錄。我們可以指定梯度更新次數為任意數字(也就是總的迭代次數),在這個例子中,我們要求執行1000次迭代。最后,save_summaries_secs=300表示我們每5分鍾計算一次summaries,save_interval_secs=600 表示我們每10分鍾保存一次checkpoint文件。
訓練VGG16的模型如下:
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():
# 開啟數據加載
images, labels = ...
# 定義模型
predictions = vgg.vgg_16(images, is_training=True)
# 指定損失函數
slim.losses.softmax_cross_entropy(predictions, labels)
total_loss = slim.losses.get_total_loss()
tf.summary.scalar('losses/total_loss', total_loss)
# 指定優化器
optimizer = tf.train.GradientDescentOptimizer(learning_rate=.001)
# 創建訓練操作,確保當我們獲取損失用於評估時,權重跟新和梯度值已經被計算
train_tensor = slim.learning.create_train_op(total_loss, optimizer)
# 開始訓練
slim.learning.train(train_tensor, train_log_dir)
4、已存在模型的調參(fine-tuning)
(1)簡要回顧從checkpoint文件中恢復變量
在一個模型被訓練之后,我們可以使用tf.train.Saver()函數從checkpoint文件中恢復變量和模型。在很多情況下, tf.train.Saver()提供了一個簡單的機制來恢復所有或一部分變量。
# 創建一些變量
v1 = tf.Variable(..., name="v1")
v2 = tf.Variable(..., name="v2")
...
# 對恢復的所有變量添加一些操作
restorer = tf.train.Saver()
# 對恢復的一些變量添加一些操作
restorer = tf.train.Saver([v1, v2])
# 接下來,我們啟動模型,使用saver來恢復保存在磁盤上的變量,並且對模型做一些操作
with tf.Session() as sess:
# 從磁盤恢復變量
restorer.restore(sess, "/tmp/model.ckpt")
print("Model restored.")
# 對模型做一些操作
...
See Restoring Variables and Choosing which Variables to Save and Restore sections of the Variables page for more details.
(2)有選擇地恢復模型
有時我們更想要在一個預訓練模型上用一個全新的數據集或者完成一個新任務對預訓練模型進行調參,在這種情況下,我們可以使用Slim的幫助函數來恢復模型的一部分變量。
# 創建一些變量
v1 = slim.variable(name="v1", ...)
v2 = slim.variable(name="nested/v2", ...)
...
# 獲取需要恢復的變量列表 (只包含'v2'變量). 一些方法都是等價的
variables_to_restore = slim.get_variables_by_name("v2")
# 或者
variables_to_restore = slim.get_variables_by_suffix("2")
# 或者
variables_to_restore = slim.get_variables(scope="nested")
# 或者
variables_to_restore = slim.get_variables_to_restore(include=["nested"])
# 或者
variables_to_restore = slim.get_variables_to_restore(exclude=["v1"])
# 創建saver用來恢復變量
restorer = tf.train.Saver(variables_to_restore)
with tf.Session() as sess:
# 從磁盤恢復變量
restorer.restore(sess, "/tmp/model.ckpt")
print("Model restored.")
# 對模型做一些操作
...
(3)用不同的變量名恢復模型
當從一個checkpoint文件恢復變量時,Saver在checkpoint文件中找到變量名並把它們映射為當前計算圖中的變量。在上述例子中,我們傳遞一個變量列表給Saver並創建它,此時,Saver在checkpoint文件中查找時使用的變量名是通過傳入的每個變量的var.op.name隱式獲取的。當checkpoint中的變量名和計算圖中的變量名匹配時這種方式很OK。然而,在有些情況下,我們想要從一個checkpoint文件中恢復一個模型,但是它的變量名和當前計算圖中的變量名不同。在這種情況下,我們必須提供給Saver一個字典,把checkpoint文件中的變量名映射為計算圖中的變量名。考慮下面的例子,checkpoint的查找時使用的變量名是通過一個簡單函數得到的:
# 假設 'conv1/weights' 應該從 'vgg16/conv1/weights'中恢復
def name_in_checkpoint(var):
return 'vgg16/' + var.op.name
# 假設'conv1/weights'和'conv1/bias' 應該從 'conv1/params1'和'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:
# 從磁盤恢復變量
restorer.restore(sess, "/tmp/model.ckpt")
(4)在一個模型上Fine-Tuning完成一個不同的任務
考慮這種情況,我們已經用ImageNet數據集(數據集有1000個類別)訓練好了VGG16的網絡,然而我們想要把這個網絡用於Pascal VOC(這個數據集只有20個類別),為了完成這個任務,我們可以用預訓練模型(去除最后一層)來初始化我們新的模型。
# 加載Pascal VOC數據集
image, label = MyPascalVocDataLoader(...)
images, labels = tf.train.batch([image, label], batch_size=32)
# 創建新的模型
predictions = vgg.vgg_16(images)
train_op = slim.learning.create_train_op(...)
# 指明用ImageNet預訓練的VGG16模型的路徑
model_path = '/path/to/pre_trained_on_imagenet.checkpoint'
# 指明新模型保存的路徑
log_dir = '/path/to/my_pascal_model_dir/'
# 只回復預訓練模型的卷積層
variables_to_restore = slim.get_variables_to_restore(exclude=['fc6', 'fc7', 'fc8'])
init_fn = assign_from_checkpoint_fn(model_path, variables_to_restore)
# 開始訓練
slim.learning.train(train_op, log_dir, init_fn=init_fn)
5、評估模型
當我們已經訓練好一個模型之后(或者模型正在訓練時),我們可能想要知道這個模型的實際效果到底如何。這可以用一些評估指標來評估,評估時將給模型的表現性能打分。評估代碼將加載數據、執行預測、比較預測結果和真實結果並記錄下評估分數。這些操作可以被單次實行或者被周期的重復執行。
(1)評價指標(Metrics)
盡管我們定義的評價指標是一個非損失函數的性能度量,但是在評估我們的模型時我們仍然對評價指標很感興趣。比如,我們可能想要優化的是對數損失函數,但我們感興趣的指標可能是F1分數,或者IoU分數(IoU不可微分,因此不能用來做損失)
Slim提供了一系列評估操作可以使我們輕易的評估模型。概念上,計算評估指標值可以被分為三個部分如下:
1)初始化:初始化用於計算評估指標的變量
2)累加:執行用於計算評價指標的操作(如sums等)
3)最終:(可選)執行任何計算評價指標的最終操作。例如,計算均值、最小值、最大值等。
例如,為了計算平均絕對誤差,兩個變量(count 和total)需要被初始化為0。在累加階段,我們得到一些預測值和標簽值,計算它們的絕對誤差,然后把總和加到total中去。每次我們獲取到一個值時,count加1。最終,total除以count得到均值。
下面的例子解釋了聲明評估指標的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非常繁瑣,因此Slim提供了兩個方便的函數:
# 在兩個列表中累加值和更新操作
value_ops, update_ops = slim.metrics.aggregate_metrics(
slim.metrics.streaming_mean_absolute_error(predictions, labels),
slim.metrics.streaming_mean_squared_error(predictions, labels))
# 在兩個字典中累加值和更新操作
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
# 加載數據集
images, labels = load_data(...)
# 定義網絡模型
predictions = vgg.vgg_16(images)
# 選擇需要計算的評價指標
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),
})
#使用1000個batchs來評估模型
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))
注意: metric_ops.py 可以被單獨的使用,而不用依賴於 layers.py 或者 loss_ops.py
(2)周期性評估(Evaluation Loop)
Slim也提供了一個評估模塊(evaluation.py),它包含一些幫助函數,這些函數使用metric_ops.py中評價指標來寫模型評估腳本。其中就有包括一個函數,它周期的運行評估操作、計算數據batch的評估值、打印輸出、summarizeing評估結果。比如:
import tensorflow as tf
slim = tf.contrib.slim
# 加載數據
images, labels = load_data(...)
# 定義網絡
predictions = MyModel(images)
# 選擇需要計算的評估指標
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),
})
# 創建一個summary操作
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))
# 啟動全局步驟
slim.get_or_create_global_step()
# summaries保存的路徑
output_dir = ...
# 多久運行一次評估操作
eval_interval_secs = ...
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)
參考:https://github.com/tensorflow/tensorflow/tree/master/tensorflow/contrib/slim
原文鏈接:https://blog.csdn.net/MOU_IT/article/details/82717745