1. 程式人生 > >學習筆記TF049:TensorFlow 模型儲存載入、佇列執行緒、載入資料、自定義操作

學習筆記TF049:TensorFlow 模型儲存載入、佇列執行緒、載入資料、自定義操作

生成檢查點檔案(chekpoint file),副檔名.ckpt,tf.train.Saver物件呼叫Saver.save()生成。包含權重和其他程式定義變數,不包含圖結構。另一程式使用,需要重新建立圖形結構,告訴TensorFlow如何處理權重。
生成圖協議檔案(graph proto file),二進位制檔案,副檔名.pb,tf.tran.write_graph()儲存,只包含圖形結構,不包含權重,tf.import_graph_def載入圖形。

模型儲存,建立一個tf.train.Saver()儲存變數,指定儲存位置,副檔名.ckpt。

神經網路,兩個全連線層和一個輸出層,訓練MNIST資料集,儲存訓練好的模型。
載入資料、定義模型:

#載入資料
mnist = input_data.read_data_sets("MNIST_data/",one_hot=True)
trX,trY,teX,teY = mnist.train.images,mnist.train.labels,mnist.test.images,mnist.test.labels
X = tf.placeholder("float",[None,784])
Y = tf.placeholder("float",[None,10])
#初始化權重引數
w_h = init_weights([784,625])
w_h2 = init_weights([625,625])
w_o = int_weights([625,10])

#定義權重函式
def init_weights(shape):
    return tf.Variable(tf.random_normal(shape,stddev=0.01))

#定義模型
def model(X,w_h,w_h2,w_o,p_keep_input,p_keep_hidden):
    #第一個全連線層
    X = tf.nn.dropout(X,p_keep_input)
    h = tf.nn.relu(tf.matmul(X,w_h))

    h = tf.nn.dropout(h,p_keep,hidden)
    #第二個全連線層
    h2 = tf.nn.relu(tf.matmul(h,w_h2))
    h2 = tf.nn.dropout(h2,p_keep_hidden)

    return tf.matmul(h2,w_o)#輸出預測值

#定義損失函式
cost = tf.reduce_mean(tf.nn.softmax_cross_entropy_with_logits(py_x,Y))
train_op = tf.train.RMSPropOptimizer(0.001,0.9).minimize(cost)
predict_op = tf.argmax(py_x,1)

訓練模型及儲存模型:

#定義儲存路徑
ckpt_dir = "./ckpt_dir"
if not os.path.exists(ckpt_dir):
    os.makedirs(ckpt_dir)

定義計數器,為訓練輪數計數:

#計數器變數,設定它的trainable=False,不需要被訓練
global_step = tf.Variable(0,name='global_step',trainable=False)

定義完所有變數,tf.train.Saver()儲存、提取變數,後面定義變數不被儲存:

#宣告完所有變數,調tf.train.Saver
saver = tf.train.Saver()
#之後變數不被儲存
non_storable_variable = tf.Variable(777)

訓練模型並存儲:

with tf.Session() as sess:
    tf.initialize_all_variables().run()
    start = global_step.eval() #得到global_stepp初始值
    print("Start from:",start)
    for i in range(start,100):
        #128 batch_size
        for start,end in zip(range(0,len(trX),128),range(128,len(trX)+1,128)):
            sess.run(train_op,feed_dict={X:trX[start:end],Y:trY[start:end],p_keep_input:0.8, p_keep_hidden:0.5})
        global_step.assign(i).eval() #更新計數器
        saver.save(sess,ckpt_dir+"/model.ckpt",global_step=global_step) #儲存模型

訓練過程,ckpt_dir出現16個檔案,5個model.ckpt-{n}.data00000-of-00001檔案,儲存的模型。5個model.ckpt-{n}.meta檔案,儲存的元資料。TensorFlow預設只儲存最近5個模型和元資料,刪除前面沒用模型和元資料。5個model.ckpt-{n}.index檔案。{n}代表迭代次數。1個檢查點文字檔案,儲存當前模型和最近5個模型。

將之前訓練引數儲存下來,在出現意外狀競接著上一次地方開始訓練。每個固定輪數在檢查點儲存一個模型(.ckpt檔案),隨時將模型拿出來預測。

載入模型。
saver.restore載入模型:

with tf.Session() as sess:
    tf.initialize_all_variables().run()
    ckpt = tf.train.get_checkpoint_state(ckpt_dir)
    if ckpt and ckpt.model_checkpoint_path:
        print(ckpt.model_checkpoint_path)
        saver.restore(sess,ckpt.model_checkpoint_path) #載入所有引數

圖儲存載入。
僅儲存圖模型,圖寫入二進位制協議檔案:

v = tf.Variable(0,name='my_variable')
sess = tf.Session()
tf.train.write_graph(sess.graph_def,'/tmp/tfmodel','train.pbtxt')

讀取:

with tf.Session() as _sess:
    with grile.FastGFile("/tmp/tfmodel/train.pbtxt",'rb') as f:
        graph_def = tf.GraphDef()
        graph_def.ParseFromString(f.read())
        _sess.graph.as_default()
        tf.import_graph_def(graph_def,name='tfgraph')

佇列、執行緒。
佇列(queue),圖節點,有狀態節點。其他節點(入隊節點enqueue,出隊節點depueue),修改它內容。入隊節點把新元素插到佇列末尾,出隊節點把佇列前面元素刪除。
FIFOQueue、RandomShuffleQueue,原始碼在tensorflow-1.0.0/tensorflow/python/ops/data_flow_ops.py 。
FIFOQueue,建立先入先出佇列。迴圈神經網路結構,讀入訓練樣本需要有序。
建立含佇列圖:

import tensorflow as tf
#建立先入先出佇列,初始化佇列插入0.1、0.2、0.3數字
q = tf.FIFOQueue(3,"float")
init = q.enqueue_many(([0.1,0.2,0.3]))
#定義出隊、+1、入隊操作
x = q.dequeue()
y = x +1
q_inc = q.enqueue([y])

開戶會話,執行2次q_inc操作,檢視佇列內容:

with tf.Session() as sess:
    sess.run(init)
    quelen = sess.run(q.size())
    for i in range(2):
        sess.run(q_inc) #執行2次操作,佇列值變0.3,1.1,1.2
    quelen = sess.run(q.size())
    for i in range(quelen):
        print(sess.run(q.dequeue())) #輸出佇列值

RandomShuffleQueue,建立隨機佇列。出佇列,以隨機順序產生元素。訓練影象樣本,CNN網路結構,無序讀入訓練樣本,每次隨機產生一個訓練樣本。
非同步計算時非常重要。TensorFlow會話支援多執行緒,在主執行緒訓練操作,RandomShuffleQueue作訓練輸入,開多個執行緒準備訓練樣本。樣本壓入佇列,主執行緒從佇列每次取出mini-batch樣本訓練。
建立隨機佇列,最大長度10,出隊後最小長度2:

q = tf.RandomShuffleQueue(capacity=10,min_after_dequeue=2,dtype="float")

開戶會話,執行10次入隊操作,8次出隊操作:

sess = tf.Session()
for i in range(0,10): #10次入隊
    sess.run(q.enqueue(i))
for i in range(0,8): #8次出隊
    print(sess.run(q.dequeue()))

阻斷,佇列長度等於最小值,執行出隊操作;佇列長度等於最大值,執行入隊操作。
設定繪畫執行時等待時間解除阻斷:

run_iptions = tf.RunOptions(timeout_in_ms = 10000) #等待10秒
try:
    sess.run(q.dequeue(),options=run_options)
except tf.errors.DeadlineExceededError:
    print('out of range')

會話主執行緒入隊操作,資料量很大時,入隊操作從硬碟讀取資料,放入記憶體,主執行緒需要等待入隊操作完成,才能進行訓練操作。會話執行多個執行緒,執行緒管理器QueueRunner建立一系列新執行緒進行入隊操作,主執行緒繼續使用資料,訓練網張和讀取資料是非同步的,主執行緒訓練網路,另一執行緒將資料從硬碟讀入記憶體。

佇列管理器。
建立含佇列圖:

q = tf.FIFOQueue(1000,"float")
counter = tf.Variable(0.0) #計數器
increment_op = tf.assign_add(counter,tf.constant(1.0)) #操作:給計算器加1
enqueue_op = q.enqueue(counter) #操作:計數器值加入佇列

建立佇列管理器QueueRunner,兩個操作向佇列q新增元素。只用一個執行緒:

qr = tf.train.QueueRunner(q,enqueue_ops=[increment_op,enqueue_op] * 1)

啟動會話,從佇列管理器qr建立執行緒:

#主執行緒
with tf.Session() as sess:
    sess.run(tf.global_variables_initializer())
    enqueue_threads = qr.create_threads(sess,start=True) #啟動入隊執行緒
    #主執行緒
    for i in range(10):
        print(sess.run(q.dequeue()))

不是自然數列,執行緒被陰斷。加1操作和入隊操作不同步。加1操作執行多次後,才進行一次入隊操作。主執行緒訓練(出隊操作)和讀取資料執行緒的訓練(入隊操作)非同步,主執行緒一直等待資料送入。
入隊執行緒自顧執行,出隊操作完成,程式無法結束。tf.train.Coordinator實現執行緒同步,終止其他執行緒。

執行緒、協調器。
協調器(coordinator)管理執行緒。

#主執行緒
sess = tf.Session()
sess.run(tf.global_variables_initializer())
#Coordinator:協調器,協調執行緒間關係可以視為一種訊號量,用來做同步
coord = tf.train.Coordinator()
#啟動入隊執行緒,協調器是執行緒的引數
enqueue_threads = qr.create_threads(sess,coord = coord,start=True)
#主執行緒
for i in range(0,10):
    print(sess.run(q.dequeue()))
coord.request_stop() #通知其他執行緒關閉
coord.join(enqueue_threads) #join操作等待其他執行緒結束,其他所有執行緒關閉之後,函式才能返回

關閉佇列執行緒,執行出隊操作,丟擲tf.errors.OutOfRange錯誤。coord.request_stop()和主執行緒出隊操作q.dequeue()調換位置。

coord.request_stop()
#主執行緒
for i in range(0,10):
    print(sess.run(q.dequeue()))
coord.join(enqueue_threads)
tf.errors.OutOfRangeError捕捉錯誤:
coord.request_stop()
#主執行緒
for i in range(0,10):
    try:
        print(sess.run(q.dequeue()))
    except tf.errors.OutOfRangeError:
        break
coord.join(enqueue_threads)

所有佇列管理器預設加在圖tf.GraphKeys.QUEUE_RENNERS集合。

載入資料。
預載入資料(preloaded data),在TensorFlow圖中定義常量或變數儲存所有資料。填充資料(feeding),Python產生資料,資料填充後端。從檔案讀取資料(reading from file),佇列管理器從檔案中讀取資料。

預載入資料。資料直接嵌在資料流圖中,訓練資料大時,很消耗記憶體。

x1 = tf.constant([2,3,4])
x2 = tf.constant([4,0,1])
y = tf.add(x1,x2)

填充資料。sess.run()中的feed_dict引數,Python產生資料填充後端。資料量大、消耗記憶體,資料型別轉換等中間環節增加開銷。

import tensorflow as tf
#設計圖
a1 = tf.placeholder(tf.int16)
a2 = tf.placeholder(tf.int16)
b = tf.add(x1,x2)
#用Python產生資料
li1 = [2,3,4]
li2 = [4,0,1]
#開啟會話,資料填充後端
with tf.Session() as sess:
    print sess.run(b,feed_dict={a1:li1,a2:li2})

從檔案讀取資料。圖中定義好檔案讀取方法,TensorFlow從檔案讀取資料,解碼成可使用樣本集。
把樣本資料寫入TFRecords二進位制檔案。再從佇列讀取。
TFRecords二進位制檔案,更好利用記憶體,方便複製和移動,不需要單獨標記檔案。tensorflow-1.1.0/tensorflow/examples/how_tos/reading_data/convert_to_records.py。
生成TFRecords檔案。定義主函式,給訓練、驗證、測試資料集做轉換。獲取資料。編碼uint8。資料轉換為tf.train.Example型別,寫入TFRecords檔案。轉換函式convert_to,資料填入tf.train.Example協議緩衝區(protocol buffer),協議組衝區序列轉為字串,tf.python_io.TFRecordWriter寫入TFRecords檔案。55000個訓練資料,5000個驗證資料,10000個測試資料。黑白影象,單通道。寫入協議緩衝區,height、width、depth、label編碼int64型別,image_raw編碼成二進位制。序列化為字串。執行結束,在/tmp/data生成檔案train.tfrecords、validation.tfrecords、test.tfrecords。
從佇列讀取。建立張量,從二進位制檔案讀取一個樣本。建立張量,從二進位制檔案隨機讀取一個mini-batch。把每一批張量傳入網路作為輸入節點。程式碼 tensorflow-1.1.0/tensorflow/examples/how_tos/reading_data/fully_connected_reader.py。首先定義從檔案中讀取並解析一個樣本。輸入檔名佇列。解析example。寫明features裡key名稱。圖片 string型別。標記 int64型別。BytesList 重新解碼,把string型別0維Tensor變成unit8型別一維Tensor。image張量形狀Tensor(“input/sub:0”,shape=(784,),dtype=float32)。把標記從uint8型別轉int32型別。label張量形狀Tensor(“input/Cast_1:0”,shape=(),dtype=int32)。
tf.train.shuffle_batch樣本隨機化,獲得最小批次張量。輸入引數,train 選擇輸入訓練資料/驗證資料,batch_size 訓練每批樣本數,num_epochs 過幾遍資料 設定0/None表示永遠訓練下去。返回結果,images,型別float, 形狀[batch_size,mnist.IMAGE_PIXELS],範圍[-0.5,0.5]。labels,型別int32,形狀[batch_size],範圍[0,mnist.NUM_CLASSES]。tf.train.QueueRunner用tf.train.start_queue_runners()啟動執行緒。獲取檔案路徑,/tmp/data/train.tfrecords, /tmp/data/validation.records。tf.train.string_input_producer返回一個QueueRunner,裡面有一個FIFOQueue。如果樣本量很大,分成若干檔案,檔名列表傳入。隨機化example,規整成batch_size大小。留下部分佇列,保證每次有足夠資料做隨機打亂。
生成batch張量作網路輸入,訓練。輸入images、labels。構建從揄模型預測資料的圖。定義損失函式。加入圖操作,訓練模型。初始化引數,string_input_producer內療建立一個epoch計數變數,歸入tf.GraphKeys.LOCAL_VARIABLES集合,單獨用initialize_local_variables()初始化。開啟輸入入階執行緒。進入永久迴圈。每100次訓練輸出一次結果。通知其他執行緒關閉。資料集大小55000,2輪訓練,110000個數據,batch_size大小100,訓練次數1100次,每100次訓練輸出一次結果,輸出11次結果。
TensorFlow使用TFRecords檔案訓練樣本步驟:在生成檔名佇列中,設定epoch數量。訓練時,設定為無窮迴圈。在讀取資料時,如果捕捉到錯誤,終止。

實現自定義操作。
需要熟練掌握C++語言。對張量流動和前向傳播、反向傳播有深理解。
步驟。在C++檔案(*_ops.cc檔案)中註冊新操作。定義操作功能介面規範,操作名稱、輸入、輸出、屬性等。在C++檔案(*_kenels.cc檔案)實現操作,可以實現在CPU、GPU多個核心上。測試操作,編譯操作庫檔案(*_ops.so檔案),Python使用操作。
最佳實踐。詞嵌入。原始碼 https://github.com/tensorflow/models/blob/master/tutorials/embedding/word2vec_optimized.py
第一步,建立word2vec_ops.cc註冊兩個操作,SkipgramWord2vec、NegTrainWord2vec。
第二步,兩個操作在CPU裝置實現,生成word2vec_kernels.cc檔案。
第三步,編譯操作類檔案,測試。在特定標頭檔案目錄下編譯,Python提供get_include獲取標頭檔案目錄,C++編譯器把操作編譯成動態庫。TensorFlow Python API提供tf.load_op_library函式載入動態庫,向TensorFlow 框架註冊操作。load_op_library返回包含操作和核心Python模組。

參考資料:
《TensorFlow技術解析與實戰》

歡迎付費諮詢(150元每小時),我的微信:qingxingfengzi