天天看點

TensorFlow學習筆記(4) 變量管理及模型持久化變量管理模型持久化TensorFlow最佳實踐樣例程式

目錄

變量管理

模型持久化

儲存模型

讀取模型

儲存為可移植單個檔案

持久化原理及資料格式

TensorFlow最佳實踐樣例程式

mnist_inference.py

mnist_train.py

mnist_eval.py

變量管理

随着神經網絡的結構更加複雜,參數更多時,需要一個更好的方式來傳遞和管理變量。在TF中提供了通過變量的名字來建立或者擷取一個變量的機制,通過這個機制不同函數可以直接通過變量的名字來直接使用變量。這機制主要是通過tf.get_variable和tf.variable_scope實作的。

  • 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和tf.get_variable最大的差別在于指定變量名稱的參數。tf.Variable的變量名稱是一個可選的參數,通過name='v'給出。但是tf.get_variable的變量名稱是一個必填的參數,tf.get_variable會根據這個名字去建立參數,是以不能建立一個已經存在的名字,否則就會報錯。

  • tf.variable_scope

如果需要通過tf.get_variable來擷取一個已經建立的變量,則需要通過tf.variable_scope來生成一個上下文管理器來控制tf.get_variable函數擷取已建立的變量:

#在名字為foo的命名空間中建立名字為v的變量
with tf.variable_scope('foo'):
    v = tf.get_variable('v',[1],initializer=tf.constant_initializer(1.0))

#由于已經存在v,是以以下代碼将會報錯
with tf.variable_scope('foo'):
    v = tf.get_variable('v',[1])
#Variable foo/v already exists, disallowed. Did you mean to set reuse=True in VarScope?

#在生成上下文管理器時,将reuse設定為True,即可擷取變量
with tf.variable_scope('foo', reuse=True):
    v = tf.get_variable('v',[1])
           

tf.variable_scope函數會建立一個TF的命名空間,在命名空間内建立的變量名稱都會帶上這個空間名字作為字首,是以這個函數也提供了一個管理變量命名空間的方式:

v1= tf.get_variable('v', [1])
print(v1.name)
#v:0
#0表示是這個變量運算的第一個結果

with tf.variable_scope('foo'):
    v2 = tf.get_variable('v', [1])
    print(v2.name)
#foo/v:0

with tf.variable_scope('foo'):
    with tf.variable_scope('bar'):
        v3 = tf.get_variable('v', [1])
        print(v3.name)
#foo/bar/v:0
           

 tf.variable_scope的參數reuse=True時,tf.get_variable将直接且隻能擷取已經存在的變量,若變量不存在,就會報錯。

with tf.variable_scope('foo'):
    v = tf.get_variable('v', [1])
    print(v.name)
#foo/v:0

with tf.variable_scope('foo',reuse=True):
    v1 = tf.get_variable('v', [1])
    print(v = v1)#因為之前已經建立了foo/v 是以v=v1
    #True
    v1 = tf.get_variable('v1', [1]) # 這句會報錯,因為并沒有叫v1的變量

with tf.variable_scope('',reuse=True):
    v2 = tf.get_variable('foo/v', [1]) #另外也可以直接在一個空的空間直接擷取其他空間的變量
    print(v2 = v) #因為之前已經建立了foo/v 是以v=v2
           

TensorFlow的7種不同的初始化函數

初始化函數 功能 主要參數
tf.constant_initializer 将變量初始化為給定常量 常量的取值
tf.random_normal_initializer 将變量初始化為滿足正太分布的随機值 正太分布的均值和标準差
tf.truncated_normal_initializer 将變量初始化為滿足正太分布的随機值,但若随機出來的值偏離平均值超過兩個标準差,那麼這個數将會被重新随機 正太分布的均值和标準差
tf.random_uniform_initializer 将變量初始化為滿足平均分布的随機值 最大,最小值
tf.uniform_unit_scaling_initializer 将變量初始化為滿足平均分布但不影響輸出數量級的随機值 factor(産生随機值時乘以的系數)
tf.zeros_initializer 将變量設定為全為0 變量次元
tf.ones_initializer 将變量設定為全為1 變量次元

使用變量管理對(三)中的TF模型進行改進,這種方法可以提高複雜程式的可讀性:

def inference(input_tensor,avg_class,regularizer):
    if avg_class==None:
        with tf.variable_scope('layer1'):
            weights = tf.get_variable('weights', [INPUT_NODE, LAYER1_NODE], initializer=tf.truncated_normal_initializer(stddev=0.1))
            biases = tf.get_variable("biases", [LAYER1_NODE], initializer=tf.constant_initializer(0.0))
            layer1 = tf.nn.relu(tf.matmul(input_tensor, weights) + biases)


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


        with tf.variable_scope('layer2',reuse=True):
            weights = tf.get_variable('weights', [LAYER1_NODE, OUT_NODE], initializer=tf.truncated_normal_initializer(stddev=0.1))
            biases = tf.get_variable("biases", [OUT_NODE], initializer=tf.constant_initializer(0.0))
            layer2 = tf.matmul(layer1,avg_class.average(weights))+avg_class.average(biases)
        return layer2

x = tf.placeholder(tf.float32, [None, INPUT_NODE], name='x-input')
y = inference(x)

#若需要使用訓練好的神經網絡進行推導時
new_x = ...
new_y = inference(new_x, True)
           

模型持久化

TF提供了一個簡單的API來儲存和還原一個神經網絡模型。這個API就是tf.train.Saver類。

儲存模型

下面即為儲存TensorFlow計算圖的方法(tf.train.Saver.save()):

import tensorflow as tf

v1 = tf.Variable(tf.constant(1.0, shape=[1], name='v1'))
v2 = tf.Variable(tf.constant(1.0, shape=[1], name='v2'))
result = v1 + v2

#聲明tf.train.Saver類用于儲存模型
saver = tf.train.Saver()

with tf.Session() as sess:
    sess.run(tf.initialize_all_variables())

    #将模型儲存到model/model.ckpt檔案
    saver.save(sess, 'model/model.ckpt')
           

這樣就實作了持久化一個簡單的TF模型的功能,通過saver.save函數将TF模型儲存到model.ckpt中。雖然隻指定了一個檔案路徑,但是在該路徑下會出現三個檔案:

model.ckpt.meta 儲存了TF計算圖的結構
model.ckpt 儲存了TF程式中每個變量的取值
checkpoint 儲存了一個目錄下所有的模型檔案清單

讀取模型

在儲存了TF模型後,下面是使用saver.restore()加載該模型:

import tensorflow as tf

v1 = tf.Variable(tf.constant(1.0, shape=[1], name='v1'))
v2 = tf.Variable(tf.constant(1.0, shape=[1], name='v2'))
result = v1 + v2

#聲明tf.train.Saver
saver = tf.train.Saver()

with tf.Session() as sess:
    #加載已經儲存的模型,并通過已經儲存的模型中的變量來計算
    saver.restore(sess, ''model/model.ckpt'')
    print(sess.run(result))
           

注意加載模型的代碼沒有初始化變量,而是通過已經儲存的模型加載進來。如果不想重複定義模型的結構,也可以直接将模型的結構加載出來:

import tensorflow as tf
#加載模型結構
saver = tf.train.import_meta_graph('model/model.ckpt.meta')

with tf.Session() as sess:
    saver.restore(sess, 'model/model.ckpt')
    #通過張量的名稱來擷取張量
    print(sess.run(tf.get_default_graph().get_tensor_by_name('add:0')))
    #[2.]
           

為了儲存和加載部分變量,在聲明tf.train.Saver類時可以提供一個清單來指定儲存或者加載的變量,如tf.train.Saver([v1]),這時就隻有變量v1會被加載進來。除了可以指定被加載的變量,tf.train.Saver類也支援在儲存或加載時給變量重命名:

import tensorflow as tf

v1 = tf.Variable(tf.constant(2.0, shape=[1], name='other-v1'))
v2 = tf.Variable(tf.constant(3.0, shape=[1], name='other-v2'))

#這裡如果直接使用tf.train.Saver()來加載模型會報錯

#使用一個字典來重命名變量就可以加載原來的模型了
#這個字典指定了原名稱為v1的變量現在加到v1變量中
saver = tf.train.Saver({'v1': v1, 'v2': v2})
           

這種方式友善使用變量的滑動平均值,在加載模型時将影子變量映射到變量自身,那麼在訓練好的模型中就不需要再調用函數獲得變量的滑動平均了:

import tensorflow as tf

v = tf.Variable(0, dtype=tf.float32, name='v')

for variables in tf.global_variables():
    print(variables.name)
    #v:0

ema = tf.train.ExponentialMovingAverage(0.99)
maintain_averages_op = ema.apply(tf.global_variables())

for variables in tf.global_variables():
    print(variables.name)
    #v:0
    #v/ExponentialMovingAverage:0

saver = tf.train.Saver()

with tf.Session() as sess:
    sess.run(tf.initialize_all_variables())

    sess.run(tf.assign(v, 10))
    sess.run(maintain_averages_op)

    saver.save(sess, '/model1/model.ckpt')
    print(sess.run([v, ema.average(v)]))
    #[10.0, 0.099999905]
           

 加載滑動平均值

import tensorflow as tf

v = tf.Variable(0, dtype=tf.float32, name='v')
saver = tf.train.Saver({'v/ExponentialMovingAverage': v})

with tf.Session() as sess:
    saver.restore(sess, '/model1/model.ckpt')
    print(sess.run(v))
    #0.099999905
           

為了友善加載時重命名滑動變量,tf.train.ExponentialMovingAverage類提供了variables_to_restore()來生成重命名所需要的字典,即{'v/ExponentialMovingAverage': v},是以上面的代碼也可以改為

saver = tf.train.Saver(ema.variables_to_restore())
           

儲存為可移植單個檔案

使用tf.train.Saver會儲存運作Tensorflow程式所需要的全部資訊,然後有時并不需要某些資訊。比如在測試或者離線預測時,隻需要知道如何從神經網絡的輸入層經過前向傳播計算得到輸出層即可,而不需要類似于變量初始化,模型儲存等輔助節點的資訊。而且,将變量取值和計算圖結構分成不同的檔案存儲有時候也不友善,于是Tensorflow提供了convert_variables_to_constants函數,通過這個函數可以将計算圖中的變量及其取值通過常量的方式儲存,這樣整個Tensorflow計算圖可以統一存放在一個檔案中。如下:

import tensorflow as tf
from tensorflow.python.framework import graph_util

v1 = tf.Variable(tf.constant(1.0, shape=[1], name='v1'))
v2 = tf.Variable(tf.constant(2.0, shape=[1], name='v2'))
result = v1 + v2

with tf.Session() as sess:
    sess.run(tf.global_variables_initializer())
    #導出目前計算圖GraphDef部分,隻需這一層就可以完成從輸入層到輸出層的計算
    graph_def = tf.get_default_graph().as_graph_def()
    #将導出的計算圖中的變量轉化為常量
    output_graph_def = graph_util.convert_variables_to_constants(sess, graph_def, ['add'])

    with tf.gfile.GFile('model/combined_model.pb', 'wb') as f:
        f.write(output_graph_def.SerializeToString())
           

在儲存之後,當隻需要得到計算圖中某個節點的取值時,就會有一個更友善的方法:

import tensorflow as tf
from tensorflow.python.framework import graph_util

with tf.Session() as sess:
    model_filename = 'model/combined_model.pb'
    #讀取檔案并解析成對應的GraphDef Protocol Buffer
    with gfile.FastGfile(model_filename, 'rb') as f:
        graph_def = tf.GraphDef()
        graph_def.ParseFromString(f.read())
        #将儲存的圖加載到目前圖中,并給定傳回張量的名稱
        result = tf.import_graph_def(graph_def, return_elements=['add:0'])#add為張量的名稱
        print(sess.run(result))
           

持久化原理及資料格式

Tensorflow通過元圖(MetaGraph)來記錄計算圖中節點的資訊以及運作計算圖節點所需要的中繼資料。MetaGraphDef中的内容就構成了Tensorflow持久化時的第一個檔案(model.ckpt.meta)。元圖中主要記錄了五類資訊:

  • meta_info_def:記錄了計算圖中的中繼資料以及程式中所使用到的運算方法的資訊。其中計算中繼資料包含了計算圖的版本号(meta_graph_version)以及使用者指定的标簽(tags)。stripped_op_list屬性記錄了計算圖上使用到的所有運算方法的資訊。
  • graph_def:主要記錄了計算圖上的節點資訊,該屬性隻關注運算的連接配接結構。其中version屬性主要存儲了Tensorflow的版本号,其他的主要資訊都存在Nnode屬性中,記錄了計算圖上的節點資訊,有name、input、device等。
  • saver_def:記錄了持久化模型時需要用到的一些參數,比如儲存的檔案名、儲存操作和加載操作的名稱、儲存頻率、清理曆史記錄等。
  • collection_def:記錄了從集合名稱到集合内容的映射。
  • signature_def:

tensorflow提供了export_meta_graph來擷取以上資訊:

v1 = tf.Variable(tf.constant(1.0,shape=[1]),name='v1')
v2 = tf.Variable(tf.constant(1.0,shape=[1]),name='v2')
result = v1+v2
saver = tf.train.Saver()
saver.export_meta_graph('path/model.ckpt.meta.json',as_text=True)
           

第二個檔案model.ckpt儲存了模型中變量的取值。Tensorflow提供了tf.train.NewCheckpointReader來檢視該檔案儲存的變量資訊。

reader = tf.train.NewCheckpointReader('path/model.ckpt')

# 擷取所有變量清單,并将其轉化為變量名到變量次元的字典
all_variables = reader.get_variable_to_shape_map()

for variable_name in all_variables:
    print(variable_name ,all_variables[variable_name])
    # v1 [1]
    # v2 [1]

print(reader.get_tensor('v1'))
#[1.]
           

checkpoint記錄了儲存的模型的檔案名,其中第一行mode_checkpoint_path屬性為最新儲存的檔案名,下面的為all_model_checkpoint_paths屬性,儲存了所有已儲存的模型的檔案名。

TensorFlow最佳實踐樣例程式

上一篇中MNIST程式是不完整的,這裡利用變量管理和模型持久化對其進行完善。優化重構之後的程式分為三個:

  • 第一個是mnist_inference.py,定義前向傳播的過程及參數;
  • 第二個是mnist_train.py,定義神經網絡的訓練過程;
  • 第三個是mnist_eval.py,定義神經網絡的測試過程。

mnist_inference.py

import tensorflow as tf
 
#定義神經網絡結構相關的參數
INPUT_NODE = 784
OUTPUT_NODE = 10
LAYER1_NODE = 500
 
#定義函數建立或者加載變量,并生成正則化損失加入損失集合
def get_weight_variable(shape, regularizer):
    weights = tf.get_variable('weights', shape, initializer=tf.truncated_normal_initializer(stddev=0.1))
    if regularizer != None:
        tf.add_to_collection('losses', regularizer(weights))
    return weights
 
#定義神經網絡的前向傳播過程
def inference(input_tensor, regularizer):
    with tf.variable_scope('layer1'):
        weights = get_weight_variable([INPUT_NODE, LAYER1_NODE], regularizer)
        biases = tf.get_variable('biases', [LAYER1_NODE], initializer=tf.constant_initializer(0.1))
        layer1 = tf.nn.relu(tf.matmul(input_tensor, weights) + biases)
 
    with tf.variable_scope('layer2'):
        weights = get_weight_variable([LAYER1_NODE, OUTPUT_NODE], regularizer)
        biases = tf.get_variable('biases', [OUTPUT_NODE], initializer=tf.constant_initializer(0.1))
        layer2 = tf.matmul(layer1, weights) + biases
 
    return layer2
           

mnist_train.py

import os
import tensorflow as tf
from tensorflow.examples.tutorials.mnist import input_data
import mnist_inference
 
 
# 配置神經網絡的參數
BATCH_SIZE = 100
LEARNING_RATE_BASE = 0.8
LEARNING_RATE_DECAY = 0.99
REGULARIZATION_RATE = 0.0001 #描述模型複雜度的正則化項在損失函數中的系數
TRAINING_STEPS = 30000
MOVING_AVERAGE_DECAY = 0.99 #滑動平均衰減率
 
#模型儲存的路徑和檔案名
MODEL_SAVE_PATH = '/mnist_model/'
MODEL_SAVE_NAME = 'mnist_model.ckpt'
 
def train(mnist):
    x = tf.placeholder(tf.float32, [None, mnist_inference.INPUT_NODE], name='x-input')
    y_ = tf.placeholder(tf.float32, [None, mnist_inference.OUTPUT_NODE], name='y-input')
    regularizer = tf.contrib.layers.l2_regularizer(REGULARIZATION_RATE)
 
    #直接使用mnist_inference.py中定義的前向傳播結果
    y = mnist_inference.inference(x, regularizer)
    global_step = tf.Variable(0, trainable=False)
 
    # 生成一個滑動平均的類,并在所有變量上使用滑動平均
    variables_averages = tf.train.ExponentialMovingAverage(MOVING_AVERAGE_DECAY, global_step)
    variables_averages_op = variables_averages.apply(tf.trainable_variables())
 
    # 計算交叉熵及目前barch中的所有樣例的交叉熵平均值,并求出損失函數
    cross_entropy = tf.nn.sparse_softmax_cross_entropy_with_logits(logits=y, labels=tf.argmax(y_, 1))
    cross_entropy_mean = tf.reduce_mean(cross_entropy)
    loss = cross_entropy_mean + tf.add_n(tf.get_collection('losses'))
 
    # 定義指數衰減式的學習率以及訓練過程
    learning_rate = tf.train.exponential_decay(LEARNING_RATE_BASE, global_step, mnist.train.num_examples / BATCH_SIZE, LEARNING_RATE_DECAY)
    train_step = tf.train.GradientDescentOptimizer(learning_rate).minimize(loss, global_step=global_step)
    train_op = tf.group(train_step, variables_averages_op)  # 打包
    with tf.control_dependencies([train_step, variables_averages_op]):
        train_op = tf.no_op(name='train')
 
    #初始化TF持久化類
    saver = tf.train.Saver()
 
    with tf.Session() as sess:
        sess.run(tf.global_variables_initializer())
 
        for i in range(TRAINING_STEPS):
            xs, ys = mnist.train.next_batch(BATCH_SIZE)
            _, loss_value, step = sess.run([train_op, loss, global_step], feed_dict={x: xs, y_: ys})
            if i%1000 == 0:
                print('After %d training steps, loss on training batch is %g'% (step, loss_value))
                saver.save(sess, os.path.join(MODEL_SAVE_PATH, MODEL_SAVE_NAME), global_step=global_step)
 
def main(argv=None):
    mnist = input_data.read_data_sets("MNIST_data", one_hot=True)
    train(mnist)
 
if __name__ =='__main__':
    tf.app.run()
           

mnist_eval.py

import time
import tensorflow as tf
from tensorflow.examples.tutorials.mnist import input_data
import mnist_inference
import mnist_train
 
EVAL_INTERVAL_SECS = 10
 
def evaluate(mnist):
    with tf.Graph().as_default() as g:
        x = tf.placeholder(tf.float32, [None, mnist_inference.INPUT_NODE], name='x-input')
        y_ = tf.placeholder(tf.float32, [None, mnist_inference.OUTPUT_NODE], name='y-input')
 
        validate_feed = {x: mnist.validation.images, y_: mnist.validation.labels}
 
        y = mnist_inference.inference(x, None)
 
        correct_prediction = tf.equal(tf.argmax(y, 1), tf.argmax(y_, 1))  # 判斷兩張量的每一維是否相等,相等傳回True,不等傳回False
        accuracy = tf.reduce_mean(tf.cast(correct_prediction, tf.float32))  # cast将布爾值轉化為float32 求均值即得正确率
 
        #通過變量重命名的方式來加載模型,這樣就不需要調動滑動平均的函數來求平均值
        variables_averages = tf.train.ExponentialMovingAverage(mnist_train.MOVING_AVERAGE_DECAY)
        variables_to_restore = variables_averages.variables_to_restore()
 
        saver = tf.train.Saver(variables_to_restore)
 
        #每隔10s調用一次計算正确率的過程以檢測訓練過程中正确率的變化
        while True:
            with tf.Session() as sess:
                #tf.train.get_checkpoint_state通過checkpoint檔案自動找到目錄中最新模型的檔案名
                ckpt = tf.train.get_checkpoint_state(mnist_train.MODEL_SAVE_PATH)
                if ckpt and ckpt.model_checkpoint_path:
                    #ckpt.model_checkpoint_path:表示模型存儲的位置,不需要提供模型的名字,它會去檢視checkpoint檔案,看看最新的是誰,叫做什麼。
                    saver.restore(sess, ckpt.model_checkpoint_path)
                    global_step = ckpt.model_checkpoint_path.split('/')[-1].split('-')[-1]
                    accuracy_score = sess.run(accuracy, feed_dict=validate_feed)
                    print('After %s training steps, validation accuracy = %g' % (global_step, accuracy_score))
                else:
                    print('No checkpoint file found')
                    return
            time.sleep(EVAL_INTERVAL_SECS)
 
def main(argv=None):
    mnist = input_data.read_data_sets("MNIST_data", one_hot=True)
    evaluate(mnist)
 
if __name__ == '__main__':
    tf.app.run()
           

繼續閱讀