Tensorflow學習(8)-通過MNIST數字識別問題學習(2)-變量管理

Tensorflow學習(7)-通過MNIST數字識別問題學習(1)中,將計算神經網絡前向傳播結果的過程抽象成了一個函數。

def inference(input_tensor, avg_class, weights1, biases1, weights2, biases2):

在定義中可看到,這個函數的參數包括了神經網絡中的所有參數。然而,當網絡結構更復雜、參數更多時。就需要一個更好的方式來傳遞和管理神經網絡中的參數了。

TensorFlow提供了通過變量名稱來創建或者獲取一個變量的機制,通過這個機制,可以在不同的函數中直接通過變量的名字來使用變量,而不需要將變量通過參數的形式來傳遞。

主要是通過tf.get_variable和tf.variable_scope來實現的。下面介紹如何使用這兩個函數。

之前我們通過tf.Variable函數來創建一個變量。除了tf.Variable函數,TensorFlow還提供了tf.get_variable來創建或者獲取變量,當tf.get_variable用來創建變量時,和tf.Variable是基本等價的

# 下面兩個定義是等價的
v = tf.get_variable("v", shape=[1], initializer=tf.constant_initializer(1.0))
v = tf.Variable(tf.constant(1.0, shape=[1]), name="v")

它們兩個最大的區別就是tf.Variable函數中,變量名稱(name)是一個可選的參數,但是tf.get_variable函數,變量命名成是一個必填的參數。tf.get_variable會根據這個名字創建或者獲取變量。

獲取

如果需要通過tf.get_variable獲取一個已經創建的變量,需要通過tf.variable_scope函數來生成一個上下文管理器,並明確指明在這個上下文管理器中,tf.get_variable將直接獲取以生成的變量

import tensorflow as tf

# 在名字爲one的命名空間創建名字爲v的變量
with tf.variable_scope("one"):
    v = tf.get_variable("v", [1], initializer=tf.constant_initializer(1.0))

# 如果在命名空間中已經存在名字爲v的變量,代碼就會報錯
  with tf.variable_scope("one"):
      v = tf.get_variable("v", [1], initializer=tf.constant_initializer(1.0))

# 在生成上下文管理器時,將參數reuse設爲True。這樣tf.get_variable函數將直接獲取已經聲明的變量
with tf.variable_scope("one",reuse=True):
    v1 = tf.get_variable("v")
    print(v==v1)
print(v==v1)

實現方式

tf.variable_scope函數生成的上下文管理器也會創建一個TensorFlow中的命名空間,在命名空間內創建的變量名稱都會帶上命名空間名作爲前綴。

import tensorflow as tf

v1 = tf.get_variable("v", [1])
print(v1.name)  # v:0
with tf.variable_scope("one"):
    v2 = tf.get_variable("v", [1])
    print(v2.name)  # one/v:0
    with tf.variable_scope("two"):
        v3 = tf.get_variable("v", [1])
        print(v3.name)  # one/two/v:0

with tf.variable_scope("", reuse=True):
    v4 = tf.get_variable("one/v", [1])
    print(v4.name)  # one/v:0
    print(v4 == v2)  # True

with tf.variable_scope("one", reuse=True):
    v5 = tf.get_variable("two/v", [1])
    print(v5.name)  # one/two/v:0
    print(v5 == v3)  # True

通過所學對def inference(input_tensor, avg_class, weights1, biases1, weights2, biases2):進行改進

不必以參數的形式傳入每層的變量

def inference(input_tensor, avg_class=None, reuse=False):
    if(avg_class==None):
        with tf.variable_scope('layer1', reuse=reuse):
            weights = tf.get_variable("weights", [INPUT_NODE, LAYER1_NODE],
                                                        initializer=tf.truncated_normal_initializer(stddev=0.1))
            biases = tf.get_variable("biases", [1, LAYER1_NODE], initializer=tf.constant_initializer(0.0))
            layer1 = tf.nn.relu(tf.matmul(input_tensor, weights) + biases)
        with tf.variable_scope('layer2', reuse=reuse):
            weights = tf.get_variable("weights", [LAYER1_NODE, OUTPUT_NODE],
                                                        initializer=tf.truncated_normal_initializer(stddev=0.1))
            biases = tf.get_variable("biases", [1, OUTPUT_NODE], initializer=tf.constant_initializer(0.0))
            layer2 = tf.matmul(layer1, weights) + biases
        return layer2
    else:
        with tf.variable_scope('layer1', reuse=reuse):
            weights = avg_class.average(tf.get_variable("weights", [INPUT_NODE, LAYER1_NODE],
                                                        initializer=tf.truncated_normal_initializer(stddev=0.1)))
            biases = avg_class.average(
                tf.get_variable("biases", [1, LAYER1_NODE], initializer=tf.constant_initializer(0.0)))
            layer1 = tf.nn.relu(tf.matmul(input_tensor, weights) + biases)
        with tf.variable_scope('layer2', reuse=reuse):
            weights = avg_class.average(tf.get_variable("weights", [LAYER1_NODE, OUTPUT_NODE],
                                                        initializer=tf.truncated_normal_initializer(stddev=0.1)))
            biases = avg_class.average(
                tf.get_variable("biases", [1, OUTPUT_NODE], initializer=tf.constant_initializer(0.0)))
            layer2 = tf.matmul(layer1, weights) + biases
        return layer2

使用所學對Tensorflow學習(7)-通過MNIST數字識別問題學習(1)的代碼進行了重寫

其中inference_()和train_()是以前的版本。

import tensorflow as tf
from tensorflow.examples.tutorials.mnist import input_data

# MNIST數據集相關的參數
INPUT_NODE = 784  # 輸出層的節點數。也就是圖片的像素
OUTPUT_NODE = 10  # 輸出節點數,相當於類別

# 配置神經網絡的參數
LAYER1_NODE = 500  # 隱藏層節點數。這裏使用一個500個節點的隱藏層

LEARNING_RATE_BASE = 0.8  # 基礎的學習率
LEARNING_RATE_DECAY = 0.99  # 學習率的衰減率
REGULARIZATION_RATE = 0.0001  # 描述模型複雜度的正則化項在損失函數中的佔比係數

MOVING_AVERAGE_DECAY = 0.999  # 滑動平均衰減率

BATCH_SIZE = 100  # 數字越小越接近隨機梯度下降,越大越接近梯度下降
TRAINING_STEPS = 30000  # 訓練輪數

def new(reuse=False):
    with tf.variable_scope('layer1', reuse=reuse):
        tf.get_variable("weights", [INPUT_NODE, LAYER1_NODE],
                                                    initializer=tf.truncated_normal_initializer(stddev=0.1))
        tf.get_variable("biases", [1, LAYER1_NODE], initializer=tf.constant_initializer(0.0))

    with tf.variable_scope('layer2', reuse=reuse):
        tf.get_variable("weights", [LAYER1_NODE, OUTPUT_NODE],
                                                    initializer=tf.truncated_normal_initializer(stddev=0.1))
        tf.get_variable("biases", [1, OUTPUT_NODE], initializer=tf.constant_initializer(0.0))
def inference(input_tensor, avg_class=None, reuse=False):
    if(avg_class==None):
        with tf.variable_scope('layer1', reuse=reuse):
            weights = tf.get_variable("weights", [INPUT_NODE, LAYER1_NODE],
                                                        initializer=tf.truncated_normal_initializer(stddev=0.1))
            biases = tf.get_variable("biases", [1, LAYER1_NODE], initializer=tf.constant_initializer(0.0))
            layer1 = tf.nn.relu(tf.matmul(input_tensor, weights) + biases)
        with tf.variable_scope('layer2', reuse=reuse):
            weights = tf.get_variable("weights", [LAYER1_NODE, OUTPUT_NODE],
                                                        initializer=tf.truncated_normal_initializer(stddev=0.1))
            biases = tf.get_variable("biases", [1, OUTPUT_NODE], initializer=tf.constant_initializer(0.0))
            layer2 = tf.matmul(layer1, weights) + biases
        return layer2
    else:
        with tf.variable_scope('layer1', reuse=reuse):
            weights = avg_class.average(tf.get_variable("weights", [INPUT_NODE, LAYER1_NODE],
                                                        initializer=tf.truncated_normal_initializer(stddev=0.1)))
            biases = avg_class.average(
                tf.get_variable("biases", [1, LAYER1_NODE], initializer=tf.constant_initializer(0.0)))
            layer1 = tf.nn.relu(tf.matmul(input_tensor, weights) + biases)
        with tf.variable_scope('layer2', reuse=reuse):
            weights = avg_class.average(tf.get_variable("weights", [LAYER1_NODE, OUTPUT_NODE],
                                                        initializer=tf.truncated_normal_initializer(stddev=0.1)))
            biases = avg_class.average(
                tf.get_variable("biases", [1, OUTPUT_NODE], initializer=tf.constant_initializer(0.0)))
            layer2 = tf.matmul(layer1, weights) + biases
        return layer2


def train(mnist):
    x = tf.placeholder(tf.float32, [None, INPUT_NODE], name='x-input')
    new()
    y_ = tf.placeholder(tf.float32, [None, OUTPUT_NODE], name='y-input')
    y = inference(x,None,True)
    # 定義存儲訓練輪數的變量。這個變量不需要計算滑動平均值,
    # 所以這裏指定這個變量爲不可訓練的變量(trainable = Fasle)。
    global_step = tf.Variable(0, trainable=False)

    # 給定滑動平均衰減率和訓練輪數的變量
    # 這裏給定訓練輪數的變量就是我們在<Tensorflow學習(6)-神經網絡進一步優化(滑動平均模型)>中提到的:
    # ExponentialMovingAverage還提供了num_updates參數來動態設置decay大小,避免模型前期訓練的太慢
    variable_averages = tf.train.ExponentialMovingAverage(MOVING_AVERAGE_DECAY, num_updates=global_step)

    # 在所有代表神經網絡參數的變量上使用滑動平均。tf.trainable_variables返回的就是
    # 圖上集合GraphKeys.TRAINABLE_VARIABLESZ中的元素。這個集合的元素就是所有沒有指定trainable=False的參數。
    variable_averages_op = variable_averages.apply(tf.trainable_variables())
    print(tf.trainable_variables())
    a_y = inference(x, variable_averages, True)
    # 分類問題,計算交叉熵作爲刻畫預測和真是指之間差距的損失函數。這裏使用TensorFlow中提供的
    # sparse_softmax_cross_entropy_with_logits函數來計算交叉熵。當分類只有一個正確答案時
    # 可以使用這個函數來計算交叉熵損失。
    # 這個函數的第一個參數是神經網絡不包括softmax層的前向傳播結果,第二個是訓練數據的正確答案。
    # 因爲標準答案是一個長度爲10的一位數組,而該函數需要的是提供一個正確答案的數字,所以要使用tf.argmax
    # 函數來得到正確答案對應的編號
    # (2)tf.argmax()會在下面給出解釋,編號爲2
    cross_entropy = tf.nn.sparse_softmax_cross_entropy_with_logits(logits=y, labels=tf.argmax(y_, 1))
    # 計算在當前batch中所有樣例的交叉熵平均值
    # (3)tf.redunce_mean
    cross_entropy_mean = tf.reduce_mean(cross_entropy)

    # 計算L2正則化損失函數。
    regularizer = tf.contrib.layers.l2_regularizer(REGULARIZATION_RATE)
    # 計算模型的正則化損失。一般只計算權重的正則化損失,不計算偏置項
    with tf.variable_scope("layer1", reuse=True):
        weights1 = tf.get_variable("weights")
    with tf.variable_scope("layer2", reuse=True):
        weights2 = tf.get_variable("weights")
    regularization = regularizer(weights1) + regularizer(weights2)
    loss = cross_entropy_mean + regularization

    # 設置指數衰減的學習率。
    learning_rate = tf.train.exponential_decay(
        LEARNING_RATE_BASE,
        global_step,  # 當前的輪數
        mnist.train.num_examples / BATCH_SIZE,  # 過完所有訓練集需要的輪數
        # 如在加上參數staircase=True,上面兩個數就保證了
        # 學習率沒經過一次所有的訓練數據就就乘以一次衰減率
        LEARNING_RATE_DECAY)

    # 使用tf.train.GradientDescentOptimizer優化算法來優化損失函數
    train_step = tf.train.GradientDescentOptimizer(learning_rate). \
        minimize(loss, global_step=global_step)

    # 在訓練神經網絡模型時,每過一遍數據需要通過反向傳播來更新網絡中的參數
    # 又要更新每一個參數的滑動平均值。爲了一次完成多個操作,TensorFlow提供了
    # tf.group和tf.control_dependencies兩種機制。
    # 這樣寫與下面一行代碼是等價的
    # with tf.control_dependencies([train_step,variable_averages_op]):
    #     train_op = tf.no_op(name='train')
    # (感覺tf.group這種挺好記的,記錄上面這種就爲了以後看代碼的時候防止看不懂吧)
    train_op = tf.group(train_step, variable_averages_op)

    # 檢驗正確率。現在這些代碼看的明白,就不解釋了。但是可能下次看就不明白了。。
    # 不明白就看書吧,書上有
    correct_prediction_ = tf.equal(tf.argmax(a_y, 1), tf.argmax(y_, 1))
    accuracy_ = tf.reduce_mean(tf.cast(correct_prediction_, tf.float32))

    # 終於可以開始訓練了
    # 由於數據集比較小,所以沒有劃分爲更小的batch。
    # 關於如何使用batch可以參考之前的博客
    with tf.Session() as sess:
        # ??? 咋直接run()了
        tf.global_variables_initializer().run()
        # 準備驗證數據。這個一般都是用來大致判斷停止的條件。
        validata_feed = {x: mnist.validation.images, y_: mnist.validation.labels}
        # 準備測試數據
        test_feed = {x: mnist.test.images, y_: mnist.test.labels}

        # 迭代的訓練神經網絡
        for i in range(TRAINING_STEPS):
            # 每1000輪輸出一次在驗證集上的測試效果
            if i % 100 == 0:
                validata_acc_ = sess.run(accuracy_, feed_dict=validata_feed)
                print("%d training steps , validata_acc ,using average model is %g" % (i, validata_acc_))
            xs, ys = mnist.train.next_batch(BATCH_SIZE)
            sess.run(train_op, feed_dict={x: xs, y_: ys})
        # 訓練結束後,測試最終正確率
        test_acc_ = sess.run(accuracy_, feed_dict=test_feed)
        print("last acc not use %d", test_acc_)


# 定義一個輔助函數,給定神經網絡的輸入和所有參數,計算神經網絡的前向傳播結果。
# 這個函數也支持傳入用於計算滑動平均值的類,方便在測試時使用滑動平均值
def inference_(input_tensor, avg_class, weights1, biases1, weights2, biases2):  # inference有推斷,推理的意思
    # 當沒有提供滑動平均類時,直接使用參數當前的取值
    if avg_class is None:
        # 計算隱藏層的前向傳播結果,使用ReLU激活函數
        layer1 = tf.nn.relu(tf.matmul(input_tensor, weights1) + biases1)

        # 因爲在計算損失函數時會一併計算softmax函數,所以這裏不需要加入激活函數
        return tf.matmul(layer1, weights2) + biases2
    # 當提供滑動平均類時
    else:
        # 使用avg_class.average來取變量的滑動平均值,用它來計算相應神經網絡前向傳播的結果
        layer1 = tf.nn.relu(tf.matmul(input_tensor, avg_class.average(weights1)) + avg_class.average(biases1))

        return tf.matmul(layer1, avg_class.average(weights2)) + avg_class.average(biases2)


# 訓練模型的過程
def train_(mnist):
    x = tf.placeholder(tf.float32, [None, INPUT_NODE], name='x-input')
    y_ = tf.placeholder(tf.float32, [None, OUTPUT_NODE], name='y-input')
    # 生成隱藏層的參數
    # (1)tf.truncated_normal() 會在博客下方列出解釋
    weights1 = tf.Variable(tf.truncated_normal([INPUT_NODE, LAYER1_NODE], stddev=0.1))
    biases1 = tf.Variable(tf.constant(0.1, shape=[1, LAYER1_NODE]))
    # 生成輸出層的參數
    weights2 = tf.Variable(tf.truncated_normal([LAYER1_NODE, OUTPUT_NODE], stddev=0.1))
    biases2 = tf.Variable(tf.constant(0.1, shape=[1, OUTPUT_NODE]))

    # 使用自定義的函數inference()計算當前參數下神經網絡前向傳播的結果
    # 這裏不計算滑動平均值
    y = inference(x, None, weights1, biases1, weights2, biases2)

    # 定義存儲訓練輪數的變量。這個變量不需要計算滑動平均值,
    # 所以這裏指定這個變量爲不可訓練的變量(trainable = Fasle)。
    global_step = tf.Variable(0, trainable=False)

    # 給定滑動平均衰減率和訓練輪數的變量
    # 這裏給定訓練輪數的變量就是我們在<Tensorflow學習(6)-神經網絡進一步優化(滑動平均模型)>中提到的:
    # ExponentialMovingAverage還提供了num_updates參數來動態設置decay大小,避免模型前期訓練的太慢
    variable_averages = tf.train.ExponentialMovingAverage(MOVING_AVERAGE_DECAY, num_updates=global_step)

    # 在所有代表神經網絡參數的變量上使用滑動平均。tf.trainable_variables返回的就是
    # 圖上集合GraphKeys.TRAINABLE_VARIABLESZ中的元素。這個集合的元素就是所有沒有指定trainable=False的參數。
    variable_averages_op = variable_averages.apply(tf.trainable_variables())

    # 計算使用了滑動平均之後的前向傳播結果
    # 可以在inference函數中注意到,需要使用滑動平均值時,需要明確調用average函數。
    # 因爲滑動平均不會改變變量本身的取值,而是會維護一個影子變量來記錄其滑動平均值,
    # 當需要使用這個滑動平均值時,需要明確指明調用average函數
    average_y = inference(x, variable_averages, weights1, biases1, weights2, biases2)

    # 分類問題,計算交叉熵作爲刻畫預測和真是指之間差距的損失函數。這裏使用TensorFlow中提供的
    # sparse_softmax_cross_entropy_with_logits函數來計算交叉熵。當分類只有一個正確答案時
    # 可以使用這個函數來計算交叉熵損失。
    # 這個函數的第一個參數是神經網絡不包括softmax層的前向傳播結果,第二個是訓練數據的正確答案。
    # 因爲標準答案是一個長度爲10的一位數組,而該函數需要的是提供一個正確答案的數字,所以要使用tf.argmax
    # 函數來得到正確答案對應的編號
    # (2)tf.argmax()會在下面給出解釋,編號爲2
    cross_entropy = tf.nn.sparse_softmax_cross_entropy_with_logits(logits=y, labels=tf.argmax(y_, 1))
    # 計算在當前batch中所有樣例的交叉熵平均值
    # (3)tf.redunce_mean
    cross_entropy_mean = tf.reduce_mean(cross_entropy)

    # 計算L2正則化損失函數。
    regularizer = tf.contrib.layers.l2_regularizer(REGULARIZATION_RATE)
    # 計算模型的正則化損失。一般只計算權重的正則化損失,不計算偏置項
    regularization = regularizer(weights1) + regularizer(weights2)
    loss = cross_entropy_mean + regularization

    # 設置指數衰減的學習率。
    learning_rate = tf.train.exponential_decay(
        LEARNING_RATE_BASE,
        global_step,  # 當前的輪數
        mnist.train.num_examples / BATCH_SIZE,  # 過完所有訓練集需要的輪數
        # 如在加上參數staircase=True,上面兩個數就保證了
        # 學習率沒經過一次所有的訓練數據就就乘以一次衰減率
        LEARNING_RATE_DECAY)

    # 使用tf.train.GradientDescentOptimizer優化算法來優化損失函數
    train_step = tf.train.GradientDescentOptimizer(learning_rate). \
        minimize(loss, global_step=global_step)

    # 在訓練神經網絡模型時,每過一遍數據需要通過反向傳播來更新網絡中的參數
    # 又要更新每一個參數的滑動平均值。爲了一次完成多個操作,TensorFlow提供了
    # tf.group和tf.control_dependencies兩種機制。
    # 這樣寫與下面一行代碼是等價的
    # with tf.control_dependencies([train_step,variable_averages_op]):
    #     train_op = tf.no_op(name='train')
    # (感覺tf.group這種挺好記的,記錄上面這種就爲了以後看代碼的時候防止看不懂吧)
    train_op = tf.group(train_step, variable_averages_op)

    # 檢驗正確率。現在這些代碼看的明白,就不解釋了。但是可能下次看就不明白了。。
    # 不明白就看書吧,書上有
    correct_prediction = tf.equal(tf.argmax(average_y, 1), tf.argmax(y_, 1))
    accuracy = tf.reduce_mean(tf.cast(correct_prediction, tf.float32))
    correct_prediction_ = tf.equal(tf.argmax(y, 1), tf.argmax(y_, 1))
    accuracy_ = tf.reduce_mean(tf.cast(correct_prediction_, tf.float32))

    # 終於可以開始訓練了
    # 由於數據集比較小,所以沒有劃分爲更小的batch。
    # 關於如何使用batch可以參考之前的博客
    with tf.Session() as sess:
        # ??? 咋直接run()了
        tf.global_variables_initializer().run()
        # 準備驗證數據。這個一般都是用來大致判斷停止的條件。
        validata_feed = {x: mnist.validation.images, y_: mnist.validation.labels}
        # 準備測試數據
        test_feed = {x: mnist.test.images, y_: mnist.test.labels}

        # 迭代的訓練神經網絡
        for i in range(TRAINING_STEPS):
            # 每1000輪輸出一次在驗證集上的測試效果
            if i % 100 == 0:
                validata_acc = sess.run(accuracy, feed_dict=validata_feed)
                validata_acc_ = sess.run(accuracy_, feed_dict=validata_feed)
                print("%d training steps , validata_acc ,using average model is %g" % (i, validata_acc))
                print("%d training steps , validata_acc ,using average model is %g" % (i, validata_acc_))
            xs, ys = mnist.train.next_batch(BATCH_SIZE)
            sess.run(train_op, feed_dict={x: xs, y_: ys})
        # 訓練結束後,測試最終正確率
        test_acc = sess.run(accuracy, feed_dict=test_feed)
        test_acc_ = sess.run(accuracy_, feed_dict=test_feed)
        print("last acc %d", test_acc)
        print("last acc not use %d", test_acc_)


def main(argv=None):
    mnist = input_data.read_data_sets("path/", one_hot=True)
    train(mnist)


if __name__ == '__main__':
    tf.app.run()

其他

tf.variable是可以嵌套的,tf.get_variable_scope().reuse可以獲取當前上下文管理器中的reuse參數。

import tensorflow as tf

# 在名字爲one的命名空間創建名字爲v的變量
with tf.variable_scope("one"):
    print(tf.get_variable_scope().reuse) #輸出False
    with tf.variable_scope("two",reuse=True):
        print(tf.get_variable_scope().reuse) #輸出True

 

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