TensorFlow入門(七) 充分理解 name / variable_scope

歡迎轉載,但請務必註明原文出處及作者信息。

@author: huangyongye
@creat_date: 2017-04-26

前言: 本例子主要介紹 name_scope 和 variable_scope 的正確使用方式,學習並理解本例之後,你就能夠真正讀懂 TensorFlow 的很多代碼並能夠清晰地理解模型結構了。

之前寫過一個例子了: TensorFlow入門(四) name / variable_scope 的使用 但是當時其實還對 name / variable_scope 不是非常理解。所以又學習了一番,攢了這篇博客。學習本例子不需要看上一篇,但是咱們還是從上一篇說起:

* 起因:在運行 RNN LSTM 實例代碼的時候出現 ValueError。 *
在 TensorFlow 中,經常會看到這 name_scope 和 variable_scope 兩個東東出現,這到底是什麼鬼,到底系做咩噶!!! 在做 LSTM 的時候遇到了下面的錯誤:

ValueError: Variable rnn/basic_lstm_cell/weights already exists, disallowed.

然後谷歌百度都查了一遍,結果也不知是咋回事。我是在 jupyter notebook 運行的示例程序,第一次運行的時候沒錯,然後就總是出現上面的錯誤。後來才知道是 get_variable() 和 variable_scope() 搞的鬼。

=========================================================

1. 先說結論

要理解 name_scope 和 variable_scope, 首先必須明確二者的使用目的。我們都知道,和普通模型相比,神經網絡的節點非常多,節點節點之間的連接(權值矩陣)也非常多。所以我們費盡心思,準備搭建一個網絡,然後有了圖1的網絡,WTF! 因爲變量太多,我們構造完網絡之後,一看,什麼鬼,這個變量到底是哪層的??

fig1. 引入命名空間之前 fig2. 引入命名空間之後

爲了解決這個問題,我們引入了 name_scope 和 variable_scope, 二者又分別承擔着不同的責任:

  • * name_scope: * 爲了更好地管理變量的命名空間而提出的。比如在 tensorboard 中,因爲引入了 name_scope, 我們的 Graph 看起來才井然有序。
  • * variable_scope: * 大大大部分情況下,跟 tf.get_variable() 配合使用,實現變量共享的功能。

下面通過兩組實驗來探索 TensorFlow 的命名機制。

2. (實驗一)三種方式創建變量: tf.placeholder, tf.Variable, tf.get_variable

2.1 實驗目的:探索三種方式定義的變量之間的區別

import tensorflow as tf
# 設置GPU按需增長
config = tf.ConfigProto()
config.gpu_options.allow_growth = True
sess = tf.Session(config=config)
# 1.placeholder 
v1 = tf.placeholder(tf.float32, shape=[2,3,4])
print v1.name
v1 = tf.placeholder(tf.float32, shape=[2,3,4], name='ph')
print v1.name
v1 = tf.placeholder(tf.float32, shape=[2,3,4], name='ph')
print v1.name
print type(v1)
print v1
Placeholder:0
ph:0
ph_1:0
<class 'tensorflow.python.framework.ops.Tensor'>
Tensor("ph_1:0", shape=(2, 3, 4), dtype=float32)

# 2. tf.Variable()
v2 = tf.Variable([1,2], dtype=tf.float32)
print v2.name
v2 = tf.Variable([1,2], dtype=tf.float32, name='V')
print v2.name
v2 = tf.Variable([1,2], dtype=tf.float32, name='V')
print v2.name
print type(v2)
print v2
Variable:0
V:0
V_1:0
<class 'tensorflow.python.ops.variables.Variable'>
Tensor("V_1/read:0", shape=(2,), dtype=float32)

# 3.tf.get_variable() 創建變量的時候必須要提供 name
v3 = tf.get_variable(name='gv', shape=[])  
print v3.name
v4 = tf.get_variable(name='gv', shape=[2])
print v4.name
gv:0
---------------------------------------------------------------------------

ValueError                                Traceback (most recent call last)

<ipython-input-7-29efaac2d76c> in <module>()
      2 v3 = tf.get_variable(name='gv', shape=[])
      3 print v3.name
----> 4 v4 = tf.get_variable(name='gv', shape=[2])
      5 print v4.name
此處還有一堆錯誤信息。。。
ValueError: Variable gv already exists, disallowed. Did you mean to set reuse=True in VarScope? Originally defined at:
...
print type(v3)
print v3
<class 'tensorflow.python.ops.variables.Variable'>
Tensor("gv/read:0", shape=(), dtype=float32)

還記得有這麼個函數嗎? tf.trainable_variables(), 它能夠將我們定義的所有的 trainable=True 的所有變量以一個list的形式返回。 very good, 現在要派上用場了。

vs = tf.trainable_variables()
print len(vs)
for v in vs:
    print v
4
Tensor("Variable/read:0", shape=(2,), dtype=float32)
Tensor("V/read:0", shape=(2,), dtype=float32)
Tensor("V_1/read:0", shape=(2,), dtype=float32)
Tensor("gv/read:0", shape=(), dtype=float32)

2.2 實驗1結論

從上面的實驗結果來看,這三種方式所定義的變量具有相同的類型。而且只有 tf.get_variable() 創建的變量之間會發生命名衝突。在實際使用中,三種創建變量方式的用途也是分工非常明確的。其中

  • tf.placeholder() 佔位符。* trainable==False *
  • tf.Variable() 一般變量用這種方式定義。 * 可以選擇 trainable 類型 *
  • tf.get_variable() 一般都是和 tf.variable_scope() 配合使用,從而實現變量共享的功能。 * 可以選擇 trainable 類型 *

3. (實驗二) 探索 name_scope 和 variable_scope

3.1 實驗二目的:熟悉兩種命名空間的應用情景。

import tensorflow as tf
# 設置GPU按需增長
config = tf.ConfigProto()
config.gpu_options.allow_growth = True
sess = tf.Session(config=config)
with tf.name_scope('nsc1'):
    v1 = tf.Variable([1], name='v1')
    with tf.variable_scope('vsc1'):
        v2 = tf.Variable([1], name='v2')
        v3 = tf.get_variable(name='v3', shape=[])
print 'v1.name: ', v1.name
print 'v2.name: ', v2.name
print 'v3.name: ', v3.name
v1.name:  nsc1/v1:0
v2.name:  nsc1/vsc1/v2:0
v3.name:  vsc1/v3:0
with tf.name_scope('nsc1'):
    v4 = tf.Variable([1], name='v4')
print 'v4.name: ', v4.name
v4.name:  nsc1_1/v4:0

tf.name_scope() 並不會對 tf.get_variable() 創建的變量有任何影響。
tf.name_scope() 主要是用來管理命名空間的,這樣子讓我們的整個模型更加有條理。而 tf.variable_scope() 的作用是爲了實現變量共享,它和 tf.get_variable() 來完成變量共享的功能。

1.第一組,用 tf.Variable() 的方式來定義。

import tensorflow as tf
# 設置GPU按需增長
config = tf.ConfigProto()
config.gpu_options.allow_growth = True
sess = tf.Session(config=config)

# 拿官方的例子改動一下
def my_image_filter():
    conv1_weights = tf.Variable(tf.random_normal([5, 5, 32, 32]),
        name="conv1_weights")
    conv1_biases = tf.Variable(tf.zeros([32]), name="conv1_biases")
    conv2_weights = tf.Variable(tf.random_normal([5, 5, 32, 32]),
        name="conv2_weights")
    conv2_biases = tf.Variable(tf.zeros([32]), name="conv2_biases")
    return None

# First call creates one set of 4 variables.
result1 = my_image_filter()
# Another set of 4 variables is created in the second call.
result2 = my_image_filter()
# 獲取所有的可訓練變量
vs = tf.trainable_variables()
print 'There are %d train_able_variables in the Graph: ' % len(vs)
for v in vs:
    print v
There are 8 train_able_variables in the Graph: 
Tensor("conv1_weights/read:0", shape=(5, 5, 32, 32), dtype=float32)
Tensor("conv1_biases/read:0", shape=(32,), dtype=float32)
Tensor("conv2_weights/read:0", shape=(5, 5, 32, 32), dtype=float32)
Tensor("conv2_biases/read:0", shape=(32,), dtype=float32)
Tensor("conv1_weights_1/read:0", shape=(5, 5, 32, 32), dtype=float32)
Tensor("conv1_biases_1/read:0", shape=(32,), dtype=float32)
Tensor("conv2_weights_1/read:0", shape=(5, 5, 32, 32), dtype=float32)
Tensor("conv2_biases_1/read:0", shape=(32,), dtype=float32)

2.第二種方式,用 tf.get_variable() 的方式

import tensorflow as tf
# 設置GPU按需增長
config = tf.ConfigProto()
config.gpu_options.allow_growth = True
sess = tf.Session(config=config)

# 下面是定義一個卷積層的通用方式
def conv_relu(kernel_shape, bias_shape):
    # Create variable named "weights".
    weights = tf.get_variable("weights", kernel_shape, initializer=tf.random_normal_initializer())
    # Create variable named "biases".
    biases = tf.get_variable("biases", bias_shape, initializer=tf.constant_initializer(0.0))
    return None


def my_image_filter():
    # 按照下面的方式定義卷積層,非常直觀,而且富有層次感
    with tf.variable_scope("conv1"):
        # Variables created here will be named "conv1/weights", "conv1/biases".
        relu1 = conv_relu([5, 5, 32, 32], [32])
    with tf.variable_scope("conv2"):
        # Variables created here will be named "conv2/weights", "conv2/biases".
        return conv_relu( [5, 5, 32, 32], [32])


with tf.variable_scope("image_filters") as scope:
    # 下面我們兩次調用 my_image_filter 函數,但是由於引入了 變量共享機制
    # 可以看到我們只是創建了一遍網絡結構。
    result1 = my_image_filter()
    scope.reuse_variables()
    result2 = my_image_filter()


# 看看下面,完美地實現了變量共享!!!
vs = tf.trainable_variables()
print 'There are %d train_able_variables in the Graph: ' % len(vs)
for v in vs:
    print v
There are 4 train_able_variables in the Graph: 
Tensor("image_filters/conv1/weights/read:0", shape=(5, 5, 32, 32), dtype=float32)
Tensor("image_filters/conv1/biases/read:0", shape=(32,), dtype=float32)
Tensor("image_filters/conv2/weights/read:0", shape=(5, 5, 32, 32), dtype=float32)
Tensor("image_filters/conv2/biases/read:0", shape=(32,), dtype=float32)

3.2 實驗 2 結論

首先我們要確立一種 Graph 的思想。在 TensorFlow 中,我們定義一個變量,相當於往 Graph 中添加了一個節點。和普通的 python 函數不一樣,在一般的函數中,我們對輸入進行處理,然後返回一個結果,而函數裏邊定義的一些局部變量我們就不管了。但是在 TensorFlow 中,我們在函數裏邊創建了一個變量,就是往 Graph 中添加了一個節點。出了這個函數後,這個節點還是存在於 Graph 中的。

4. 優雅示例

在深度學習中,通常說到 變量共享 我們都會想到 RNN 。下面我找了兩個源碼中非常漂亮的例子,可以參考學習學習。

# 例1:MultiRNNCell(RNNCell) 中這樣來創建 各層 Cell
# https://github.com/tensorflow/tensorflow/blob/master/tensorflow/contrib/rnn/python/ops/core_rnn_cell_impl.py
for i, cell in enumerate(self._cells):
    with vs.variable_scope("cell_%d" % i):
        if self._state_is_tuple:
            ...
# 例2:tf.contrib.rnn.static_bidirectional_rnn 雙端 LSTM 
with vs.variable_scope(scope or "bidirectional_rnn"):
    # Forward direction
    with vs.variable_scope("fw") as fw_scope:
        output_fw, output_state_fw = static_rnn(
        cell_fw, inputs, initial_state_fw, dtype,
        sequence_length, scope=fw_scope)

    # Backward direction
    with vs.variable_scope("bw") as bw_scope:
        reversed_inputs = _reverse_seq(inputs, sequence_length)
        tmp, output_state_bw = static_rnn(
              cell_bw, reversed_inputs, initial_state_bw,
              dtype, sequence_length, scope=bw_scope)

本文代碼:https://github.com/yongyehuang/Tensorflow-Tutorial

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