Python3 tensorflow.slim()用法詳解

因本人剛開始寫博客,學識經驗有限,如有不正之處望讀者指正,不勝感激;也望藉此平臺留下學習筆記以溫故而知新。這是關於tensorflow 中slim(TF-Slim)用法的詳解。

可能很多tensorflow的老版本玩家沒見過這個東西,slim這個模塊是在16年新推出的,其主要目的是來做所謂的“代碼瘦身”。

但事實上它已經成爲我比較喜歡,甚至是比較常用的模塊,github上面大部分tensorflow的工程都會涉及到它,不得不說,撇開Keras,TensorLayer,tfLearn這些個高級庫不談,光用tensorflow能不能寫出簡潔的代碼?當然行,有slim就夠了!

惟一的缺點是slim這玩意的中文的文檔幾乎絕跡。所以國內還是Keras,tensorLayer這些官方文檔比較完備的高級庫的天下。


一.簡介

slim被放在tensorflow.contrib這個庫下面,導入的方法如下:

import tensorflow.contrib.slim as slim

這樣我們就可以使用slim了,既然說到了,先來扒一扒tensorflow.contrib這個庫,tensorflow官方對它的描述是:此目錄中的任何代碼未經官方支持,可能會隨時更改或刪除。每個目錄下都有指定的所有者。它旨在包含額外功能和貢獻,最終會合併到核心TensorFlow中,但其接口可能仍然會發生變化,或者需要進行一些測試,看是否可以獲得更廣泛的接受。所以slim依然不屬於原生tensorflow。

那麼什麼是slim?slim到底有什麼用?

slim是一個使構建,訓練,評估神經網絡變得簡單的庫。它可以消除原生tensorflow裏面很多重複的模板性的代碼,讓代碼更緊湊,更具備可讀性。另外slim提供了很多計算機視覺方面的著名模型(VGG, AlexNet等),我們不僅可以直接使用,甚至能以各種方式進行擴展。


slim的子模塊及功能介紹:

arg_scope: provides a new scope named arg_scope that allows a user to define default arguments for specific operations within that scope.

除了基本的namescope,variabelscope外,又加了argscope,它是用來控制每一層的默認超參數的。(後面會詳細說)

data: contains TF-slim's dataset definition, data providers, parallel_reader, and decoding utilities.

貌似slim裏面還有一套自己的數據定義,這個跳過,我們用的不多。

evaluation: contains routines for evaluating models.

評估模型的一些方法,用的也不多

layers: contains high level layers for building models using tensorflow.

這個比較重要,slim的核心和精髓,一些複雜層的定義

learning: contains routines for training models.

一些訓練規則

losses: contains commonly used loss functions.

一些loss

metrics: contains popular evaluation metrics.

評估模型的度量標準

nets: contains popular network definitions such as VGG and AlexNet models.

包含一些經典網絡,VGG等,用的也比較多

queues: provides a context manager for easily and safely starting and closing QueueRunners.

文本隊列管理,比較有用。

regularizers: contains weight regularizers.

包含一些正則規則

variables: provides convenience wrappers for variable creation and manipulation.

這個比較有用,我很喜歡slim管理變量的機制

具體子庫就這麼多拉,接下來乾貨時間!


二.slim定義模型

slim中定義一個變量的示例:

  1. # Model Variables
  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. # Regular variables
  9. my_var = slim.variable('my_var',
  10. shape=[20, 1],
  11. initializer=tf.zeros_initializer())
  12. regular_variables_and_model_variables = slim.get_variables()

如上,變量分爲兩類:模型變量和局部變量。局部變量是不作爲模型參數保存的,而模型變量會再save的時候保存下來。這個玩過tensorflow的人都會明白,諸如global_step之類的就是局部變量。slim中可以寫明變量存放的設備,正則和初始化規則。還有獲取變量的函數也需要注意一下,get_variables是返回所有的變量。


slim中實現一個層:

首先讓我們看看tensorflow怎麼實現一個層,例如卷積層:

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

然後slim的實現:

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

但這個不是重要的,因爲tenorflow目前也有大部分層的簡單實現,這裏比較吸引人的是slim中的repeat和stack操作:

假設定義三個相同的卷積層:

  1. net = ...
  2. net = slim.conv2d(net, 256, [3, 3], scope='conv3_1')
  3. net = slim.conv2d(net, 256, [3, 3], scope='conv3_2')
  4. net = slim.conv2d(net, 256, [3, 3], scope='conv3_3')
  5. 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')

而stack是處理卷積核或者輸出不一樣的情況:

假設定義三層FC:

  1. # Verbose way:
  2. x = slim.fully_connected(x, 32, scope='fc/fc_1')
  3. x = slim.fully_connected(x, 64, scope='fc/fc_2')
  4. x = slim.fully_connected(x, 128, scope='fc/fc_3')

使用stack操作:

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

同理卷積層也一樣:

  1. # 普通方法:
  2. x = slim.conv2d(x, 32, [3, 3], scope='core/core_1')
  3. x = slim.conv2d(x, 32, [1, 1], scope='core/core_2')
  4. x = slim.conv2d(x, 64, [3, 3], scope='core/core_3')
  5. x = slim.conv2d(x, 64, [1, 1], scope='core/core_4')
  6. # 簡便方法:
  7. slim.stack(x, slim.conv2d, [(32, [3, 3]), (32, [1, 1]), (64, [3, 3]), (64, [1, 1])], scope='core')

slim中的argscope:

如果你的網絡有大量相同的參數,如下:

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


然後我們用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就行了。

採用如上方法,定義一個VGG也就十幾行代碼的事了。

  1. def vgg16(inputs):
  2. with slim.arg_scope([slim.conv2d, slim.fully_connected],
  3. activation_fn=tf.nn.relu,
  4. weights_initializer=tf.truncated_normal_initializer(0.0, 0.01),
  5. weights_regularizer=slim.l2_regularizer(0.0005)):
  6. net = slim.repeat(inputs, 2, slim.conv2d, 64, [3, 3], scope='conv1')
  7. net = slim.max_pool2d(net, [2, 2], scope='pool1')
  8. net = slim.repeat(net, 2, slim.conv2d, 128, [3, 3], scope='conv2')
  9. net = slim.max_pool2d(net, [2, 2], scope='pool2')
  10. net = slim.repeat(net, 3, slim.conv2d, 256, [3, 3], scope='conv3')
  11. net = slim.max_pool2d(net, [2, 2], scope='pool3')
  12. net = slim.repeat(net, 3, slim.conv2d, 512, [3, 3], scope='conv4')
  13. net = slim.max_pool2d(net, [2, 2], scope='pool4')
  14. net = slim.repeat(net, 3, slim.conv2d, 512, [3, 3], scope='conv5')
  15. net = slim.max_pool2d(net, [2, 2], scope='pool5')
  16. net = slim.fully_connected(net, 4096, scope='fc6')
  17. net = slim.dropout(net, 0.5, scope='dropout6')
  18. net = slim.fully_connected(net, 4096, scope='fc7')
  19. net = slim.dropout(net, 0.5, scope='dropout7')
  20. net = slim.fully_connected(net, 1000, activation_fn=None, scope='fc8')
  21. return net

三.訓練模型

這個沒什麼好說的,說一下直接拿經典網絡來訓練吧。

  1. import tensorflow as tf
  2. vgg = tf.contrib.slim.nets.vgg
  3. # Load the images and labels.
  4. images, labels = ...
  5. # Create the model.
  6. predictions, _ = vgg.vgg_16(images)
  7. # Define the loss functions and get the total loss.
  8. loss = slim.losses.softmax_cross_entropy(predictions, labels)

是不是超級簡單?

關於loss,要說一下定義自己的loss的方法,以及注意不要忘記加入到slim中讓slim看到你的loss。

還有正則項也是需要手動添加進loss當中的,不然最後計算的時候就不優化正則目標了。

  1. # Load the images and labels.
  2. images, scene_labels, depth_labels, pose_labels = ...
  3. # Create the model.
  4. scene_predictions, depth_predictions, pose_predictions = CreateMultiTaskModel(images)
  5. # Define the loss functions and get the total loss.
  6. classification_loss = slim.losses.softmax_cross_entropy(scene_predictions, scene_labels)
  7. sum_of_squares_loss = slim.losses.sum_of_squares(depth_predictions, depth_labels)
  8. pose_loss = MyCustomLossFunction(pose_predictions, pose_labels)
  9. slim.losses.add_loss(pose_loss) # Letting TF-Slim know about the additional loss.
  10. # The following two ways to compute the total loss are equivalent:
  11. regularization_loss = tf.add_n(slim.losses.get_regularization_losses())
  12. total_loss1 = classification_loss + sum_of_squares_loss + pose_loss + regularization_loss
  13. # (Regularization Loss is included in the total loss by default).
  14. total_loss2 = slim.losses.get_total_loss()

四.讀取保存模型變量

通過以下功能我們可以載入模型的部分變量:

  1. # Create some variables.
  2. v1 = slim.variable(name="v1", ...)
  3. v2 = slim.variable(name="nested/v2", ...)
  4. ...
  5. # Get list of variables to restore (which contains only 'v2').
  6. variables_to_restore = slim.get_variables_by_name("v2")
  7. # Create the saver which will be used to restore the variables.
  8. restorer = tf.train.Saver(variables_to_restore)
  9. with tf.Session() as sess:
  10. # Restore variables from disk.
  11. restorer.restore(sess, "/tmp/model.ckpt")
  12. print("Model restored.")

除了這種部分變量加載的方法外,我們甚至還能加載到不同名字的變量中。

假設我們定義的網絡變量是conv1/weights,而從VGG加載的變量名爲vgg16/conv1/weights,正常load肯定會報錯(找不到變量名),但是可以這樣:

  1. def name_in_checkpoint(var):
  2. return 'vgg16/' + var.op.name
  3. variables_to_restore = slim.get_model_variables()
  4. variables_to_restore = {name_in_checkpoint(var):var for var in variables_to_restore}
  5. restorer = tf.train.Saver(variables_to_restore)
  6. with tf.Session() as sess:
  7. # Restore variables from disk.
  8. restorer.restore(sess, "/tmp/model.ckpt")

通過這種方式我們可以加載不同變量名的變量!!


發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章