TensorFlow slim(一) slim API使用方法說明


  TF-slim 模塊是TensorFLow中比較實用的API之一,是一個用於模型構建、訓練、評估復雜模型的輕量化庫。 其中引入的比較實用的函數包含arg_scope、model_variables、repeat、stack

  slim 模塊是在16年推出的,其主要功能是為了實現"代碼瘦身"

  該模塊已經成為很常用的模塊之一,在github上大部分TensorFLow的代碼中都會涉及到它,如果沒有涉及到,其網絡架構的實現可能會存在很多冗余,代碼不夠簡練,可讀性較低。

引言

  首先,來看一下運用slim模塊實現LeNet-5網絡架構的代碼:

 1 def lenet_architecture(self, is_trained=True):
 2     with slim.arg_scope([slim.conv2d], padding="valid",
 3                         weights_initializer=tf.truncated_normal_initializer(stddev=0.01),
 4                         weights_regularizer=slim.l2_regularizer(0.005)):
 5         # 由於Lenet中是32*32的輸入,而MNIST是28*28的圖片,所以第一層卷積需要使用SAME卷積
 6         net = slim.conv2d(self.input_image, 6, [5, 5], 1, padding="SAME", scope="conv1")  # 28*28*5
 7         net = slim.max_pool2d(net, [2, 2], 2, scope='pool_2')  # 14*14*6
 8         net = slim.conv2d(net, 16, [5, 5], 1, scope='conv3')   # 10*10*16
 9         net = slim.max_pool2d(net, [2, 2], 2, scope='pool_4')  # 5*5*16
10         net = slim.conv2d(net, 120, [1, 1], 1, scope='conv5')  # 通過1*1的方式代替全連接
11         net = slim.flatten(net, scope='flatten')  # 展平
12         net = slim.fully_connected(net, 84, scope='fc6')
13         net = slim.dropout(net, self.dropout, is_training=is_trained, scope='dropout')
14         digits = slim.fully_connected(net, 10, scope='fc7')
15     return digits        

  在上述代碼第6-14行,為LeNet網絡在處理MNIST手寫體識別時的網絡實現。可以看出,每一行即為一層網絡的實現。 尤其是每一層的卷積操作,並沒有按照先生成卷積核,再進行卷積操作,再添加正則化的操作進行實現。 而是很干練,一行直接包含了所有的內容。這都是源於arg_scope函數內允許用戶對scope內的操作定義默認參數,從而可以減少很多冗余的操作。

  可以初步的感受到,slim模塊可以使模型的構建、訓練評估變得更簡單。尤其是機器視覺領域的很多模型(LeNet-5, AlexNet, VGG等)。

  閑言少絮不用講,開始揭開slim神秘的面紗。

slim 模塊的基本使用

Slim模塊的導入

1 import tensorflow.contrib.slim as slim

  本文使用的環境是Python3.6,TensorFlow 1.12.0

  如果您的Python或者TF版本過高,可能會出現slim.沒有聯想輸入 或者 會報 ModuleNotFound Error: No module named 'tensorflow.contrib'的錯誤。  

使用slim構建模型詳解

slim 變量(Variables)

  模型的建立需要生成變量,首先來對比一下TensorFlow原生的變量生成方式和slim變量生成方式的區別

  原生的TensorFlow中創建變量的Variable函數中,需要設置預定義的值或者一個初始化的機制(隨機生成之類),其使用如下:

1 W = tf.Variable(tf.truncated_normal([10, 4], 0, 1), trainable=True, 
2                 name="weight", dtype=tf.float32)

  

  在slim中創建變量的variable函數中,提供了一系列wrapper函數。直觀上看,slim的變量生成函數將參數的設置都扁平化了,而且更加容易理解,例如生成一個變量,名字是什么,大小如何,使用什么方式進行初始化,使用什么方式進行正則化,存放在哪里等等。 除了扁平化的使用方式外,其還添加了一些額外的功能,像正則化、存放的設備等。

1 w = slim.variable('weight', shape=[10, 10, 3, 3], 
2                   initializer=tf.truncated_normal_initializer(stddev=0.1),
3                   regularizer=slim.l2_regularizer(0.5),
4                   device='/CPU:0')

  在slim中,同樣也對變量進行了進一步的區分,將變量定義為局部變量模型變量。顧名思義,模型變量是在訓練過程中需要訓練,進行微調的,並且在模型保存時會保存到.ckpt中,並用於推理過程的變量(Model variables are trained or fine-tuned during learning and are loaded from a checkpoint during evaluation or inference)。而局部變量只是訓練過程所使用的一些參數,不需要微調,也不會保存到模型中,當然,推理的過程也不需要使用的變量(諸如迭代次數、學習率等參數)。具體使用時,如下所示:

 1 # Model Variables 模型變量 使用model_variable()
 2 weights = slim.model_variable('weights',
 3                               shape=[10, 10, 3 , 3],
 4                               initializer=tf.truncated_normal_initializer(stddev=0.1),
 5                               regularizer=slim.l2_regularizer(0.05),
 6                               device='/CPU:0')
 7 model_variables = slim.get_model_variables()
 8 
 9 # Regular variables  # 局部變量,使用variable()
10 var = slim.variable('var',
11                        shape=[20, 1],
12                        initializer=tf.zeros_initializer())
13 regular_variables_and_model_variables = slim.get_variables()

slim 層(Layers)

  正如開篇LeNet示例所述,通過TensorFlow基礎函數建立一個卷積層必不可少的op包括:

  • 創建當前層卷積核和偏置變量
  • 通過卷積核對輸入進行卷積操作
  • 卷積結果添加偏置
  • 對結果添加激活函數

  其每一層的建立將會冗余成如下模樣:

1 # conv1
2 with tf.name_scope('conv1') as scope:
3     kernel = tf.Variable(tf.truncated_normal([11, 11, 3, 96], dtype=tf.float32,
4                                          stddev=1e-1), name='weights'
5     biases = tf.Variable(tf.constant(0.0, shape=[96], dtype=tf.float32),
6                          trainable=True, name='biases')
7     conv = tf.nn.conv2d(x, kernel, [1, 4, 4, 1], padding='SAME')
8     bias = tf.nn.bias_add(conv, biases)
9     conv1 = tf.nn.relu(bias, name=scope)

其中,3-6行是卷積核和偏置的初始化,7、8、9分別是卷積、偏置、激活函數的操作。對於深度、寬度都比較少的模型網絡(諸如LeNet),該操作還可行。但對於模型層數深或者寬度深的模型網絡(諸如Inception、ResNet等), 如果采用上述編寫方式,書寫繁瑣,也不便於維護。

  為了避免代碼的重復,slim提供了比較高級的Layers op,如下所示,slim版本的卷積操作。當然,該操作需要配合arg_scope()函數進行默認參數的設置,才會發揮其功效。

1 net = slim.conv2d(input, 128, [3, 3], scope='conv1_1')

  slim.arg_scope(), 對指定的函數設置默認參數,當然,如果其中有一兩個不符合默認參數的設置,可以在指定函數中使用關鍵字參數進行修改。將會在slim的作用域中詳細進行介紹

1 with slim.arg_scope([slim.conv2d], padding="valid",
2                         weights_initializer=tf.truncated_normal_initializer(stddev=0.01),
3                         weights_regularizer=slim.l2_regularizer(0.005)):

  另外,slim還提供了兩個meta-operations:repeat和stack,用於重復進行一些相同的操作。其應用場景為像VGG這種幾個卷積操作的堆疊后進行一個池化的模型網絡。如下述所示:

1 net = slim.conv2d(net, 256, [3, 3], scope='conv3_1')
2 net = slim.conv2d(net, 256, [3, 3], scope='conv3_2')
3 net = slim.conv2d(net, 256, [3, 3], scope='conv3_3')
4 net = slim.max_pool2d(net, [2, 2], scope='pool2')

其中,包含3個卷積操作。這3個卷積操作可以按照如上的方式進行編寫。也可以通過循環的方式進行:

1 for i in range(3):
2   net = slim.conv2d(net, 256, [3, 3], scope='conv3_%d' % (i+1))
3 net = slim.max_pool2d(net, [2, 2], scope='pool2')

  還可以使用slim中提供的repeat方法,可以使代碼更加簡明:

1 net = slim.repeat(net, 3, slim.conv2d, 256, [3, 3], scope='conv3')
2 net = slim.max_pool2d(net, [2, 2], scope='pool2')

  在repeat的過程中,會將scope的名稱依次命名為conv3_1,conv3_2,conv3_3。repeat函數允許重復參數相同的操作

  另外,slim中的stack方法允許操作不同參數的重復操作,好比上述卷積操作為卷積核大小、通道數量不一樣的卷積操作,或者是多個全連接網絡(一般每層神經元節點的個數都是不一樣的)。

1 # 全連接操作 之 冗長的方式
2 x = slim.fully_connected(x, 4096, scope='fc_1')
3 x = slim.fully_connected(x, 4096, scope='fc_2')
4 x = slim.fully_connected(x, 1000, scope='fc_3')

  可以看出,全連接操作中神經元節點個數不相同。stack的方式如下所示:

1 x = slim.stack(x, slim.fully_connected, [32, 64, 128], scope='fc')

  將不同的參數寫成一個列表即可,stack操作不標注堆疊的次數,因為每次參數不一樣。 

  除了全連接操作,實際上stack也可以處理卷積操作,對於下述3*3、 1*1的卷積操作:

1 x = slim.conv2d(x, 32, [3, 3], scope='conv_1')
2 x = slim.conv2d(x, 32, [1, 1], scope='conv_2')
3 x = slim.conv2d(x, 64, [3, 3], scope='conv_3')
4 x = slim.conv2d(x, 64, [1, 1], scope='conv_4')

  由於卷積核的大小和通道數量不盡相同,不能使用repeat操作,但可以通過stack的方式:

1 x = slim.stack(x, slim.conv2d, [(32, [3, 3]), (32, [1, 1]),
2                (64, [3, 3]), (64, [1, 1])], scope='conv')

  將不同的參數以元組的形式存在列表中。

slim作用域(scopes)

  TensorFlow中scope機制的幾種類型:

  • name_scope:限制op的作用域
  • variable_scope:變量的作用域

  slim中還新增了arg_scope的scope機制。該機制可以給一個或者多個op指定默認參數

  還用開篇的LeNet來舉例:如果沒有arg_scope(),LeNet的三個卷積操作的slim層的使用應該是這樣:

1 net = slim.conv2d(inputs, 6, [5, 5], 1, padding='SAME',
2                   weights_initializer=tf.truncated_normal_initializer(stddev=0.01),
3                   weights_regularizer=slim.l2_regularizer(0.005), scope='conv1')
4 net = slim.conv2d(net, 16, [5, 5], 1, padding='VALID',
5                   weights_initializer=tf.truncated_normal_initializer(stddev=0.01),
6                   weights_regularizer=slim.l2_regularizer(0.005), scope='conv2')
7 net = slim.conv2d(net, 120, [1, 1], 1, padding='SAME',
8                   weights_initializer=tf.truncated_normal_initializer(stddev=0.01),
9                   weights_regularizer=slim.l2_regularizer(0.005), scope='conv3')

  看起來真的很繁瑣,每個slim.conv2d()中有很多一樣的參數。但如果給其設置默認參數,使用arg_scope(),代碼將會得到簡化。

1  with slim.arg_scope([slim.conv2d], padding='SAME',
2                       weights_initializer=tf.truncated_normal_initializer(stddev=0.01)
3                       weights_regularizer=slim.l2_regularizer(0.0005)):
4     net = slim.conv2d(inputs, 64, [11, 11], scope='conv1')
5     net = slim.conv2d(net, 128, [11, 11], padding='VALID', scope='conv2')
6     net = slim.conv2d(net, 256, [11, 11], scope='conv3')

  在使用的時候,就是對各層找共性,共性越多,arg_scope()的使用便可以使代碼越簡潔。

  但一般而言不同類型的層的共性不多,因此,可以使用嵌套的方式進行制定:

 1 with slim.arg_scope([slim.conv2d, slim.fully_connected],
 2                       activation_fn=tf.nn.relu,
 3                       weights_initializer=tf.truncated_normal_initializer(stddev=0.01),
 4                       weights_regularizer=slim.l2_regularizer(0.0005)):
 5   with slim.arg_scope([slim.conv2d], stride=1, padding='SAME'):
 6     net = slim.conv2d(inputs, 64, [11, 11], 4, padding='VALID', scope='conv1')
 7     net = slim.conv2d(net, 256, [5, 5],
 8                       weights_initializer=tf.truncated_normal_initializer(stddev=0.03),
 9                       scope='conv2')
10     net = slim.fully_connected(net, 1000, activation_fn=None, scope='fc')

  在第一層arg_scope()中,對卷積層和全連接層的一些共性參數進行指定, 在第二層arg_scope()中,又對卷積層特有的參數進行指定。

使用slim訓練模型

  在模型建立后,模型的訓練需要添加損失函數loss function,梯度計算gradient computation

Slim 損失函數Losses

  據官方聲明,slim.losses模塊將被去除,請使用tf.losses模塊,因為二者功能完全一致

  損失函數是教導機器分辨對錯的量,也是要進行優化的參數。對於分類問題,通常采用交叉熵,對於回歸問題,一般采用MSE/SSE。從下述對比中,可以看出,slim.losses模塊和tf.losses模塊的使用完全一致:

1 slim.losses.softmax_cross_entropy(predictions, input_label, scope='loss')
2 tf.losses.softmax_cross_entropy(predictions, input_label, scope='loss')

  對於多任務需學習模型中,同一模型會存在多個損失函數,用於衡量不同功能的損失。例如,yolo v3中包含邊框坐標的損失、分類的損失和置信度的損失。對於多任務的損失,通常會求取多個損失的和,或者是加權的和。 如下所示:

1 total_loss = classification_loss + sum_of_squares_loss

  slim中也設計了相應函數get_total_loss(),會將通過slim生成的loss進行加和

1 total_loss = slim.losses.get_total_loss(add_regularization_losses=False)

  那如果有一些手動建立的loss,需要與slim建立的loss進行加和,手動建立的loss又該如何添加到slim當中呢?可以使用losses.add_loss()方法:

1 slim.losses.add_loss(my_loss)

  之后,再進行加和的運算:

1 total_loss = slim.losses.get_total()

slim訓練優化(Training Loop)

  在tf中,當完成模型、損失的建立之后,接下來將會生成優化器:

1 optimizer = tf.train.GradientDescentOptimizer(lr).minimize(loss)

  slim當中,訓練op的功能包含兩個操作,包含了:

  • 計算損失;
  • 進行梯度運算

  其使用模式如下所示:

 1 total_loss = slim.losses.get_total_loss()
 2 optimizer = tf.train.GradientDescentOptimizer(learning_rate)
 3 
 4 train_op = slim.learning.create_train_op(total_loss, optimizer)
 5 logdir = ... # Where checkpoints are stored.
 6 
 7 slim.learning.train(   # actually runs training
 8     train_op,
 9     logdir,
10     number_of_steps=1000,
11     save_summaries_secs=300,
12     save_interval_secs=600)
  • 損失
  • 優化器,此時不需要.minimize(loss)
  • 生成train_op , 損失和優化器一起; 個人感覺有些繁瑣,不如普通TF的方式。
  • slim.learning.train() # 這個訓練的機制倒是挺簡潔,不用開session 也不用寫循環
    • checkpoint 和 event的保存目錄
    • 訓練代數
    • 每多10分鍾保存一次

 


免責聲明!

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



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