原文連接:https://blog.csdn.net/MOU_IT/article/details/82717745
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、paraller_reader。
evalution:包含一些評估模型的例程。
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), 6 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提供了在搭建神經網絡模型時許多函數的標准實現
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, [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