1. 程式人生 > >Tensorflow學習--最佳的深度學習實踐案例

Tensorflow學習--最佳的深度學習實踐案例

2018-1-26Task——Chapter5吃透MNIST識別例子

目的:驗證神經網路的優化方法,使用MNIST手寫數字識別神經網路進行驗證。
目錄:
·5.1 Tensorflow處理MNIST手寫數字識別資料集
·5.2 對比神經網路設計和引數優化的不同方法
·5.3 介紹Tensorflow的變數重用問題和變數的名稱空間問題
·5.4 介紹神經網路模型的持久化問題—直接使用訓練好的模型
·5.5 完整的Tensorflow解決MNIST問題程式碼


5.1MNIST資料處理

MNIST資料集包含了60000張圖片作為訓練資料(55000張作為訓練資料,5000張作為cross-validation的資料),10000張作為測試資料。每一張圖片的大小均為28×28,且數字都會出現在圖片的正中間。以下是Tensorflow處理MNIST資料的程式碼:

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

#載入MNIST資料集,如果指定地址/path/to/MNIST_data 下沒有已經下好的資料,
#那麼Tensorflow會自動從表5-1的地址下載資料
mnist = input_data.read_data_sets("/path/to/MNIST_data/",one_hot=True)

#列印Training data size:55000
print("Training data size:",mnist.train.num_examples)

#列印Validation data siz:5000
print("Validation data size:",mnist.validation.num_examples)

#列印Testing data size. 10000
print("Testing data size:",mnist.test.num_examples)

#列印Example training data:
print("Example training data:\n",mnist.train.images[0])

#列印Example Training data label:
print("Example training data label:\n", mnist.train.labels[0])

input_data.read_data_sets函式生成的類會自動將MNIST的資料集劃分為train、validation、test三個資料集。對應的資料集分別有5.5w,5k,1w張圖片。處理後的每一張圖片是一個長度為28*28=784的一維陣列。陣列中的每一個元素對應畫素點中的每一個位置的數字(轉換為[0,1]之間的灰度值)。外,input_data.read_data_sets

函式生成的類還提供了mnist.train.next_batch函式可以從訓練資料中讀取一小部分作為一個訓練batch,便於使用批量(batch)梯度下降法。以下程式碼顯示了此功能:

batch_size = 100

xs,ys = mnist.train.next_batch(batch_size)
#從train集合中選取batch_size個數據
#其中X為測試資料集,Y為標籤集
print("X shape:",xs.shape)
#輸出 X shape:(100,784)  

print("Y shape:",ys.shape)
#輸出 Y Shape:(100,10)

5.2.1 Tensorflow訓練神經網路模型

主要運用到以下優化演算法:

①啟用函式實現神經網路模型的去線性化

②使用一個或多個隱藏層使得神經網路變得更深

③使用指數衰減法設定學習率

④使用L2正則化來避免過擬合

⑤使用滑動平均模型使得最終的模型更加健壯

以下是直接將以上演算法進行整合的程式碼(暫時不考慮模型儲存,名稱空間,變數重用問題)

Tensorflow的常用函式可以看這裡

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

#載入MNIST資料集,如果指定地址/path/to/MNIST_data 下沒有已經下好的資料,
#那麼Tensorflow會自動從表5-1的地址下載資料
# mnist = input_data.read_data_sets("/path/to/MNIST_data/",one_hot=True)

#列印Training data size:55000
# print("Training data size:",mnist.train.num_examples)

# #列印Validation data siz:5000
# print("Validation data size:",mnist.validation.num_examples)

# #列印Testing data size. 10000
# print("Testing data size:",mnist.test.num_examples)

# #列印Example training data:
# print("Example training data:\n",mnist.train.images[0])

# #列印Example Training data label:
# print("Example training data label:\n", mnist.train.labels[0])

# batch_size = 100
# xs,ys = mnist.train.next_batch(batch_size)
#從train集合中選取batch_size個數據
# print("X shape:",xs.shape)
# print("Y shape:",ys.shape)
#其中X為測試資料集,Y為標籤集

#MNIST資料集相關常數
INPUT_NODE = 784    #輸入層的節點數。對於MNIST資料集,這個就等於圖片的畫素
OUTPUT_NODE = 10   #輸出層的節點數。這個等於類別的數目,因為MNIST資料集中需要區分0~9這十個數字,所以這裡輸出層的節點數為10.

#配置神經網路的引數
LAYER1_NODE = 500   #隱藏層的節點數。這裡使用只有一個隱藏層的網路作為樣例,這個隱藏層有500個結點

BATCH_SIZE = 100    #一個訓練batch中的訓練資料個數。數字越小時,訓練過程越接近隨機梯度下降;數字越大,訓練越接近梯度下降

LEARNING_RATE_BASE = 0.8    #基礎的學習率
LEARNING_RATE_DECAY =0.99   #學習率的衰減率
REGULARIZATION_RATE =0.0001 #描述模型複雜度的正則化項在損失函式中的係數
TRAINING_STEPS =30000       #訓練的輪數
MOVING_AVERAGE_DECAY = 0.99 #滑動平均衰減率

#一個輔助函式,給定神經網路的輸入和所有引數,計算神經網路的前向傳播結果。在這裡
#定義了一個使用ReLU啟用函式的三層全連線神經網路。通過加入隱藏層實現了多層網路的結構
#通過ReLU啟用函式實現了去線性化。在這個函式中也支援傳入用於計算引數平均值的類,
#這樣方便在測試時使用滑動平均模型
def  inference(input_tensor,avg_class,weights1,biases1,weights2,biases2):
	#當沒有提供滑動平均類時,直接使用引數當前的取值
	if avg_class == None:
	#計算隱藏層的前向傳播結果
		layer1 = tf.nn.relu(tf.matmul(input_tensor,weights1)+biases1)

	#計算輸出層的前向傳播結果。因為在計算損失時會一併計算softmax函式
	#所以這裡不需要加入啟用函式。而且不加入softmax不影響預測結果。
	#因為預測時使用的是不同類別對應結點輸出值的不相對同大小,有沒有softmax層
	#對最後分類結果的計算沒有影響。於是在計算整個神經網路的前向傳播時可以
	#不加入最後的softmax層。
		return tf.matmul(layer1,weights2)+biases2

	else:
		#首先使用avg_class.average函式來計算得出變數W和b的滑動平均值,
		#然後在計算相應的神經網路前向傳播結果
		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(dtype=tf.float32,shape=[None,INPUT_NODE],name = 'x-input')
	y_ = tf.placeholder(dtype=tf.float32,shape=[None,OUTPUT_NODE],name = 'y-input')

	#生成隱藏層的引數
	#tf.truncated_mormal生成的隨機值服從正態分佈,且每個隨機值偏離均值不超過2個標        #準差,stddev=0.1,即標準差為0.1
	weights1 = tf.Variable(tf.truncated_normal([INPUT_NODE,LAYER1_NODE],stddev=0.1))
	biases1 = tf.Variable(tf.constant(0.1,shape=[1,LAYER1_NODE]))
	#注意此處的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]))

	#計算在當前引數下神經網路前向傳播的結果,這裡給出的用於計算滑動平均的類為None,
	#所以函式不會使用引數的滑動平均值
	y = inference(x,None,weights1,biases1,weights2,biases2)

	#定義儲存訓練輪數的變數。這個變數不需要計算滑動平均值,所以這裡指定這個變數為#   #不可訓練的變數(trainable=False)
	#Tensorflow訓練神經網路時,一般會將代表訓輪數的變臉指定為不可訓練的引數。

	#將代表訓練輪數的變數為不可變的引數
	global_step = tf.Variable(0,trainable=False)

	#給定_滑動平均衰減率_和_訓練輪數_的變數,初始化滑動平均類(見第4章)
	#給定訓練輪數的變數可以訓練早期變數的更新速度

	#variable_averages是一個滑動平均類的例項,global_step用於更新影子變數
	variable_averages = tf.train.ExponentialMovingAverage(MOVING_AVERAGE_DECAY,global_step)

	#在所有代表神經網路引數的變數上使用滑動平均,其他輔助變數就不需要了。
	#tf.trainable.variable返回的就是圖上儲存可訓練變數的集合collection
	variables_averages_op = variable_averages.apply(tf.trainable_variables())

	#計算使用滑動平均之後前向傳播的結果。滑動平均不會改變變數本身的取值,而會維護    #一個影子變數來記錄
	#其滑動平均值。所以當需要使用這個滑動平均值時,需要明確呼叫average()函式。
	average_y = inference(x,variable_averages,weights1,biases1,weights2,biases2)

	#計算交叉熵作為刻畫預測值和真實值之間差距的損失函式,這裡使用Tensorflow中提供
	#sparse_softmax_cross_entropy_with_logits函式來計算交叉熵。當分類問題只有一個   #正確答案時,可以使用這個函式來加速交叉熵的計算。MNIST問題的圖片中只包含了0~9   #中的一個數字,所以可以使用這一個函式來計算交叉損失。這個函式的第一個引數是神   #經網路不包括softmax的前向傳播結果第二個是訓練資料的正確答案。因為標準答案是一   #個長度為10的一維陣列,而該函式需要提供的是
	#一個正確答案的數字,所以需要使用tf.argmax(y_,1)函式來得到正確答案的編號
	cross_entropy = \ tf.nn.sparse_softmax_cross_entropy_with_logits(logits=y,labels=tf.argmax(y_,1))
	 
	#計算在當前batch中所有樣例的交叉熵平均值。
	cross_entropy_mean = tf.reduce_mean(cross_entropy)

	#定義計算L2正則化損失函式,REGULARIZATION_RATE表示模型複雜損失在總損失中的比例
	#正則化的思想,在損失函式中加入刻畫模型複雜程度的指標
	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, #過完所有訓練資料需要迭代的次數
		LEARNING_RATE_DECAY		     #學習率的衰減速度
		)

	#使用tf.train.GradientDescentOptimizer()函式優化演算法來優化損失函式。
	#這裡的損失函式包含了交叉熵損失和L2正則化損失
	train_step = tf.train.GradientDescentOptimizer(learning_rate).minimize(loss,global_step=global_step)
	#引數global_step代表全域性步數,比如在多少步該進行什麼操作,現在神經網路訓練到多   #少輪等等,類似於一個鐘錶。
	#損失函式優化器的minimize()中global_step=global_steps能夠提供global_step自動加   #一的操作。
	#從0開始,系統會自動更新這個值,逐次加一累加

	#在訓練神經網路模型時,每過一遍資料既需要通過反向傳播來更新神經網路中的引數,
	#又要更新每一個引數的滑動平均值。為了一次完成多個操作,Tensorflow提供了
	#tf.control_dependencies和tf.group兩種機制,下面兩行程式碼和
	#tf.control_dependencies([train_step,variable_averages_op])是等價的
	#tf.control_dependencies()控制計算流圖的,給圖中的某些計算指定順序
	with tf.control_dependencies([train_step,variables_averages_op]):
		train_op = tf.no_op(name='train')

	#檢測使用了滑動平靜模型的神經網路前向傳播是否正確。tf.argmax(average_y,1)
	#計算每一個樣例的預測答案。其中average_y是一個batch_size*10的二維陣列,每
	#一行表示案例向前傳播的結果。tf.argmax的第二個引數為1,表示選取最大值的
	#操作只在第一個維度上進行(x軸上),也就是說只在每一行選取最大值對應的下標
	#於是得到的結果是一個長度為batch的一維陣列,這個一維陣列中的值就表示了每
	#一個數字對應的樣例識別的結果.tf.equal()判斷每個Tensor的每一維度是否相同
	#如果相等返回True,否則返回False.
	correct_prrediction = tf.equal(tf.argmax(average_y,1),tf.argmax(y_,1))

	#這個運算首先將一個布林型的值轉換為實數型,然後計算平均值。這一個平均值
	#就代表模型在這一組資料上的正確率
	accuracy = tf.reduce_mean(tf.cast(correct_prrediction,tf.float32))

	#初始化會話,並開始訓練過程
	with tf.Session() as sess:
		sess.run(tf.initialize_all_variables())

		#準備驗證資料。一般在神經網路的訓練過程中會通過驗證資料大致判斷停止的     #條件和訓練的結果
		#驗證集
		validate_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):
			#每次取一個batch樣本出來訓練
			xs, ys = mnist.train.next_batch(BATCH_SIZE)
			#train_op相當於train_step優化器和variables_averages_op的集合, #將兩個關係依賴於train_op使得
			#反向傳播和平均滑動的引數一起在sess.run()的函式中一起更新。
			sess.run(train_op,feed_dict={x: xs,y_:ys})
			#每1000輪輸出一次在訓練集上的測試結果
			if i % 1000 == 0:
	#計算滑動平均模型在驗證資料上的結果。因為MNIST資料集比較小,所以一次
	#可以處理所有的驗證資料。為了計算方便。本程式沒有將資料劃分為更小的batch.
	#當神經網路模型比比較複雜或者驗證資料比較大時,太大的batch會導致計算時間過長
	#甚至導致內容溢位的錯誤。
				validate_acc =\  sess.run(accuracy,feed_dict=validate_feed)
				test_acc = sess.run(accuracy,feed_dict=test_feed)
				print("Aftrr %d training step(s),validation accuancy"\
					  " model is %g,test accuracy model is %g."%\(i,validate_acc,test_acc))

		#產生這一輪使用的一個batch訓練集,並執行訓練過程訓練結束之後,在測試數     #據上檢驗神經網路模型的最終正確率
		test_acc = sess.run(accuracy,feed_dict=test_feed)
	   print("After %d training strep(s),test accuracy using average model is %g"%(TRAINING_STEPS,test_acc))
		 

#主程式入口
def main(argv=None):
	#宣告處理MNIST資料集的類,這個類在初始化資料時會自動下載資料
	 mnist = input_data.read_data_sets("/path/to/MNIST_data/",one_hot=True)
	 train(mnist)

#Tensorflow提供一個主程式入口。tf.app.run()會呼叫上面定義的main函式
if __name__ == '__main__':
	#這句話的意思就是,當模組被直接執行時,以下程式碼塊將被執行,當模組是被匯入時,程式碼塊不被執行。
	tf.app.run()
	#tf.app.run處理flag解析,然後執行main函式 
	#Runs the program with an optional ‘main’ function and ‘argv’ list 
	#用主函式和命令列引數列表,來跑程式

執行結果如下圖所示:

【以上神經網路存在的不足之處:①訓練的資料是由tensorflow封裝的MNIST類進行處理,其過程過於簡便,然而許多模型的訓練中資料的處理並不會如此簡便②訓練完之後的模型沒有儲存起來,每次呼叫都得重新訓練一遍③聲明瞭太多的變數,變數過多,程式碼過於繁雜】

5.2.2 使用驗證資料集判斷模型的效果

採用cross-validation的方法需要的時間比較長,所以在海量資料的情況下,一般會更多的採用驗證資料集的形式來評測模型的效果。

5.2.3

對比不同模型在MNIST驗證集上的表現可以得出以下的結論:

①神經網路的結構(啟用函式、隱藏層)對模型有本質性的影響

②滑動平均模型、指數衰減、正則化所帶來的正確率的提升效果不是特別明顯。因為滑動平均模型和指數衰減的學習率在一定程度上都是限制神經網路引數更新的速度,而該模型的收斂速度很快,因此這兩種優化對模型最終的影響不大。迭代早期是否使用以上兩種優化方法對訓練結果的影響相對較小。然而,當問題更加複雜,迭代不會那麼快收斂時,使用該兩種優化演算法可以發揮更大的作用。

③使用正則化對模型的提升效果要比滑動平均模型和指數衰減的學習率更加顯著。

5.3變數管理

Tensorflow中提供了可以直接通過變數名稱來建立或獲取一個變數的機制。使得在不同函式中可以直接通過變數的名稱來使用變數,而不需要變數通過引數的形式到處傳遞。Tensorflow中除了tf.Variable()用於建立變數,還可以通過函式tf.get_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.get_variable 函式和tf.Variable函式最大的區別在於前者的變數名稱是一個必填的引數,而後者的變數名稱則是一個可選的引數,通過name="v"的形式給出。tf.get_variable()函式會根據這個名字去建立或者獲取變數。為了避免變數的重複建立程式會報錯。當需要通過tf.get_variable()來獲取一個已經建立的變數時,需要通過tf.variable_scope函式來生成一個上下文管理器(已建立的變數只能從其所在名稱空間內獲取),以下為相應的例子:

with tf.variable_scope("foo"):
	v = tf.get_variable("v",shape=[1],initializer=tf.constant_initializer(1.0))
with tf.variable_scope("foo",reuse=True):
	v1 = tf.get_variable("v",[1])
	print(v == v1)
而且在Tensorflow中tf.variable_scope函式是可以巢狀的:
with tf.variable_scope("root"):
	#可以通過tf.get_variable_scope().reuse來獲取當前上下文管理器中reuse引數的取值
	print (tf.get_variable_scope().reuse)
	#輸出False
	with tf.variable_scope("foo",reuse=True):
		print(tf.get_variable_scope().reuse)
		#輸出True
		with tf.variable_scope("bar"):
			print(tf.get_variable_scope().reuse)
			#輸出True

	print (tf.get_variable_scope().reuse)
	#輸出False
值得注意的是,在名稱空間內建立的變數的名稱都會帶上名稱空間名作為字首。所以tf.variable_scope函式除了可以控制tf.get_variable函式執行功能之外還提供了一個變數管理名稱空間的方式。

v1 = tf.get_variable("v",[1])
print(v1.name)#輸出v:0."v"為變數的名稱,“:0”表示這個變數是生成變數這個運算的第一個結果

with tf.variable_scope("foo"):
	v2 = tf.get_variable("v",[1])
	print(v2.name)#輸出f00/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
	v4 = tf.get_variable("v1",[1])
	print(v4.name)

with tf.variable_scope("",reuse=True):
	v5 = tf.get_variable("foo/bar/v",[1])
	#可以直接通過帶名稱空間名稱的變數名來獲取其他名稱空間下的變數

	print(v5 == v3)#True
	v6 = tf.get_variable("foo/v1",[1])
	print(v6 == v4)#True
以上的變數名稱空間以及命名管理方法將會應用到在最後的例項中

5.4Tensorflow模型的持久化
還記得之前給到的5.2的程式碼中存在的一個明顯的缺陷嗎,就是訓練完之後模型沒有儲存就直接退出程式了。為了讓訓練結果可以複用嗎,需要將訓練得到的神經網路的模型持久化。

5.4.1持久化程式碼實現

Tensorflow中提供了API tf.train.Saver類來儲存和還原一個神經網路模型。以下給出了儲存計算圖的方法:

import tensorflow as tf

#宣告兩個變數並計算他們的和
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

# init_op = tf.initialize_all_variables()
#宣告tf.train().Saver類,用於儲存模型
init_op = tf.initialize_all_variables()

saver = tf.train.Saver()

with tf.Session() as sess:
	sess.run(init_op)
	# saver.save(sess,"C:/Users/Jet Zhang/Desktop/Tensorflow實戰/path/model.ckpt")
	saver.save(sess,"C:/Users/Jet Zhang/Desktop/Tensorflow實戰/path/to/model/model.ckpt")

Tensorflow會將計算圖的結構和引數取值分開存放,因此在對應的路徑下會得到3個檔案,如下圖所示:


第一個檔案 model.ckpt.meta:用於儲存Tensorflow計算圖的結構,及神經網路的網路結構

第二個檔案 model.ckpt: 儲存了Tensorflow中每一個變數的取值

第三個檔案 checkpoint: 儲存了一個目錄下所有模型的檔案列表。格式(型別)為CheckpointState Protocol Buffer,包含以下屬性model_checkpoint_path:儲存最新的tensorflow模型的檔名;all_model_checkpoint_paths:列出還沒有被刪除的所有模型的檔名,checkpoint內容如下圖所示:


以下給出了載入載入以儲存的Tensorflow模型的方法:

import tensorflow as tf 

#使用很儲存模型中一樣的方式來宣告變數
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

saver  = tf.train.Saver()

with tf.Session() as sess:
	#載入以儲存的模型,並通過以儲存的模型中變數的值來計算加法
	saver.restore(sess,"C:/Users/Jet Zhang/Desktop/Tensorflow實戰/path/to/model/model.ckpt")
	print(result)   #輸出3
上述載入模型的程式碼和儲存的程式碼基本類似主要的區別為:載入模型的程式碼中不用執行初始化的過程,而是直接將變數的值通過以儲存的模型載入進來。如果不希望重複定義圖上的運算,也可以直接載入已經持久化的圖,樣例程式碼如下:
import tensorflow as tf 
#直接載入持久化圖
saver = tf.train.import_meta_graph(
	"path/to/model/model.ckpt.meta")
with tf.Session() as sess:
	saver.restore(sess,"path/to/model/model.ckpt")
	#通過Tensor的名稱來獲取張量
	print(sess.run(tf.get_default_graph().get_tensor_by_name("add:0")))
	#輸出[3.]

/************************************************************************************************************************

【此處補充關於"add:0"的知識】

import tensorflow as tf 
#tf.constant()是一個計算,其結果為一個張量
a = tf.constant([1.0,2.0],name = "a")
b = tf.constant([2.0,3.0],name = "b")
result = tf.add(a,b,name="add")
print(result)
#輸出Tensor("add:0", shape=(2,), dtype=float32)
Tensorflow中所有的資料都通過張量(Tensor)來表示,它只是對Tensorflow中運算結果的引用,並沒有真正地儲存數字,它儲存的是如何計算得到這些數字的過程。以上面的程式碼執行結果為例:

Tensorflow計算得到的是一個張量的結構:

一個張量包括以下3個屬性:名字(name)、維度(shape)、型別(type)

①名字是一個張量的唯一識別符號,它也可以給出這個張量是如何計算出來的。張量的命名通過“node:src_output”的形式給出。其中node為結點的名稱(計算圖上的每一個節點代表一個計算),src_output表示當前張量來自節點的第幾個輸出。比如“add:0”就表示節點“add”輸出的第一個結果(編號從0開始)。

②shape=(2,)說明了張量result是一個一維陣列,長度為出2。

③每一個張量會有唯一的型別,Tensorflow會對參與運算的所有張量作型別的檢查,當發現型別不匹配時就會報錯。

 *************************************************************************************************************************/

為了儲存或者載入部分變數,在宣告tf.train.Saver類時可以提供一個列表來制定需要儲存或載入的變數.比如通過saver=tf.train.Saver([v1])命令來構建tf.train.Saver類,那麼只有變數v1會被載入進來。

除了可以選取需要被載入的變數,tf.train.Saver類也支援在儲存或者載入時給變數重新命名。下面給出示例程式碼:


v1 = tf.Variable(tf.constant(1.0,shape=[1],name="other-v1"))
v2 = tf.Variable(tf.constant(2.0,shape=[1],name="other-v2"))
#直接使用tf.train.Saver()類載入模型會報變數找不到的錯誤

#這時只需要使用一個字典來重新命名變數就可以載入模型了,字典指定了原來名稱為v1的變
#量現在載入到變數v1中(名稱W為other-v1)
#名稱為v2的變數載入到變數v2中(名稱為other-v2)
savee = tf.train.Saver({"v1":v1,"v2":v2})

此處對變數v1和v2的名稱進行了修改,如果直接通過tf.train.Saver類預設的建構函式來載入儲存的模型則會報變數找不到的錯誤。Tensorflow中通過字典將儲存時的變數名和需要載入時的變數名聯絡起來。這樣做的主要目的之一是使用滑動平均值。使得神經網路模型變得更加健壯。在Tensorflow中,每一個變數的滑動平均值是通過一個影子變數來維護的,所以要獲取變數的滑動平均值實際上就是通過獲取這個影子變數的取值。如果載入模型時直接將影子變數對映到自身,那麼訓練好的模型就不在需要呼叫函式來獲取變數的滑動平均值了。大大方便了滑動平均模型的使用。以下為儲存滑動平均模型的樣例:

#儲存滑動平均模型的樣例
import tensorflow as tf
 
v = tf.Variable(0,dtype=tf.float32,name="v")

#在沒有宣告滑動平均模型的時候只有一個變數v,所以下面的語句會輸出"v:0"
for variables in tf.all_variables():
	print(variables.name)

ema = tf.train.ExponentialMovingAverage(0.99)
maintain_average_op = ema.apply(tf.all_variables())
#在申明滑動平均模型之後,Tensorflow會自動生成一個影子變數
#v/ExponentialMovaing Average.於是下面的語句會輸出
#"v:0"和"v/ExponentialMovingAverage:0"。
for variables in tf.all_variables():
	print(variables.name)

saver = tf.train.Saver()

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

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

	sess.run(maintain_average_op)

	#儲存時,Tensorflow會將v:0和v/ExponentialMovingAverage:0這兩個變數都存下來
	saver.save(sess,"/path/to/model/model.ckpt")
	print(sess.run([v,ema.average(v)]))

以下程式碼給出瞭如何通過變數重新命名直接讀取變數的滑動平均值
	v = tf.Variable(0,dtype=tf.float32,name="v1")
        #通過變數重新命名將要來變數v的滑動平均值直接賦給v
	saver = tf.train.Saver({"v/ExponentialMovingAverage":v})
	with tf.Session() as sess:
		saver.restore(sess,"/path/to/model/model.ckpt")
		print(sess.run(v1))
為了方便載入時重新命名滑動平均模型,tf.train.ExponentialMovingAverage類提供了variables_to_restore函式來生成tf.train.Saver類所需要的變數重新命名字典,以下程式碼為variables_to_restore函式的使用樣例: variables_to_restore函式
import tensorflow as tf 

v = tf.Variable(0,dtype=tf.float32,name="v")
ema = tf.train.ExponentialMovingAverage(0.99)

#通過使用variables_to_restore函式可以直接生成上面程式碼中提供的字典
#{"v/ExponentialMovingAverage":v}
#以下程式碼會輸出
#{'v/ExponentialMovingAverage':<tensorflow.python.ops.variables.Variable object at 0x7ff6454ddc10>}
#其中後面的Variable類就代表了變數v
print(ema.variables_to_restore())

saver = tf.train.Saver(ema.variables_to_restore())
with tf.Session() as sess:
	saver.restore(sess,"path/to.model/model.ckpt")
	print(sess.run(v))

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.initialize_all_variables())
	#匯出當前計算圖的GraphDef部分,只需要這一部分就可以完成輸入層到輸出層的計算過程
	graph_def = tf.get_default_graph().as_graph_def()

	#將圖中的變數及其取值轉化為常量,同時將圖中不必要的節點去掉。在下面一行程式碼中,最後一個引數'add'
	#給出了需要儲存的計算節點名稱.add節點是上面定義IDE兩個變數相加的操作。這裡的add是節點的名稱
	output_graph_def = graph_util.convert_variables_to_constants(sess,graph_def,['add'])
	#將匯出的模型存入檔案,Gfile以字串的形式將內容寫入檔案當中。
	with tf.gfile.GFile("/path/to/model/combined_model.pb","wb") as f:
		f.write(output_graph_def.SerializeToString())
通過下面的程式可以直接計算定義的加法運算的結果。當只需要得到計算圖的某個結點的取值時,這裡提供了一個更為簡便的方法,此方法將在後面應用於遷移學習當中:
import tensorflow as tf 
from tensorflow.python.platform import gfile
with tf.Session() as sess:
	model_filename = "/path/to/model/combined_model.pb"
	#讀取儲存的模型檔案,將檔案解析成對應的GraphDef Protocol Buffer.
	with gfile.FastGFile(model_filename,'rb') as f:
		graph_def = tf.GraphDef()
		graph_def.ParseFromString(f.read())

	#將graph_def中儲存的圖載入到當前圖中。return_elements=["add:0"]
	#給出了返回的張量名稱.在儲存的時候給出了計算圖結點的名稱,所以
	#為“add”.在載入的時候給出了張量的名稱,所以是add:0
	result = tf.import_graph_def(graph_def,return_elements=["add:0"])

	print(sess.run(result))#輸出[array([ 3.], dtype=float32)]

5.4.2持久化原理及資料格式

Tensorflow通過圖的形式來表示計算的程式設計系統。通過元圖(MetaGraphDef)來記錄計算圖中結點的資訊以及執行計算圖中結點所需的元資料。元圖由MetaGraphDef Protocol Buffer來定義。

【procotol buffer是用於處理結構化資料的工具。當要將結構化的資訊持久化或進行網路傳輸時,就需要先將它們序列化。序列化就是將結構化的資料變成資料流的格式,簡單說就是變成一個字串。Protocol Buffer解決的問題便是:將結構化的資料序列化並從序列化的資料流中還原出原來的結構化資料。】

MetaGraphDef中的內容就構成了Tensorflow持久化的第一個檔案。


MetaGraphDef的屬性詳見以下總結:

Tensorflow中提供了tf.train.NewCheckpointReader類來檢視model.ckpt文加中儲存的變數資訊。以下展示了該類的使用方法

import tensorflow as tf 
#tf.train.NewCheckpointReader可以讀取model.ckpt檔案中所儲存的所有變數
reader = tf.train.NewCheckpointReader('path/to/model/model.ckpt')

#獲取所有的變數列表。這個一個從變數名到變數維度的字典
all_variables = reader.get_variable_to_shape_map()
for variable_name in all_variables:
	#列印變數的名稱及其對應的維度
	print(variable_name,all_variables[variable_name])

print(reader.get_tensor("v1"))

# 輸出
# v1 [1]
# v2 [1]
# [ 1.]





5.5 神經網路基礎最佳實踐案例


mnist_inference.py

import tensorflow as tf

INPUT_NODE = 784
OUTPUT_NODE = 10
LAYER1_NODE = 512

def get_weight_variable(shape, regularizer):
    weights = tf.get_variable("weights", shape, initializer=tf.truncated_normal_initializer(stddev=0.1))
    #tf.get_variable(name,shape,initializer)
    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.0))
        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.0))
        layer2 = tf.matmul(layer1, weights) + biases

    #返回前向傳播的結果
    return layer2


mnist_train.py

import tensorflow as tf
from tensorflow.examples.tutorials.mnist import input_data
#載入mnist_inference中定義的常量和前行傳播的函式
import mnist_inference
import os

#配置神經網路的引數
BATCH_SIZE = 128
LEARNING_RATE_BASE = 0.9
LEARNING_RATE_DECAY = 0.99
REGULARIZATION_RATE = 0.0001
TRAINING_STEPS = 30000
MOVING_AVERAGE_DECAY = 0.99

#模型儲存的檔名和路徑
MODEL_SAVE_PATH="/path/to/model"
MODEL_NAME="mymodel.ckpt"


def train(mnist):
	#定義輸入輸出placeholder()
    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)
    y = mnist_inference.inference(x, regularizer)
    #將代表輪數的變數指定為不可訓練的引數
    global_step = tf.Variable(0, trainable=False)

    #定義損失函式、學習率、滑動平均操作以及訓練過程

    #給定滑動平均衰減率和訓練輪數的變數,初始化滑動平均類
    variable_averages = tf.train.ExponentialMovingAverage(MOVING_AVERAGE_DECAY, global_step)
    #在所有代表神經網路的變數上使用滑動平均。tf.trainable_variables返回的是需要訓練的變數列表
    #即GRAPHKets.TRAINABLE_VARIABLES中的元素,即沒有指定trainable=False的引數
    variables_averages_op = variable_averages.apply(tf.trainable_variables())
    #計算交叉熵,分類問題只有一個答案時使用sparse_softmax_cross_entropy_with_logits函式
    #para1:神經網路前向傳播的結果,para2:訓練資料的正確答案
    cross_entropy = tf.nn.sparse_softmax_cross_entropy_with_logits(logits=y, labels=tf.argmax(y_, 1))

    #計算在當前batch中所有交叉熵的平均值
    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,
        staircase=True)
    #優化損失函式
    train_step = tf.train.GradientDescentOptimizer(learning_rate).minimize(loss, global_step=global_step)
    #定義上下文的依賴關係
	#只有在 variables_averages_op被執行以後,上下文管理器中的操作才會被執行
    with tf.control_dependencies([train_step, variables_averages_op]):
        train_op = tf.no_op(name='train')

    saver = tf.train.Saver()
    with tf.Session() as sess:
        tf.global_variables_initializer().run()

        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:
            	#每訓練1000輪輸出一次損失函式的大小
                print("After %d training step(s), loss on training batch is %g." % (step, loss_value))
                #儲存當前的模型。這裡給出了global_step的引數,這樣可以讓每一個被儲存的模型
                #檔名的末尾加上訓練的輪數
                saver.save(sess, os.path.join(MODEL_SAVE_PATH, MODEL_NAME), global_step=global_step)

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

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


mnist.eval.py
# -*- coding: utf-8 -*-
import time
import tensorflow as tf
from tensorflow.examples.tutorials.mnist import input_data

# 載入mnist_inference.py和mnist_train.py中定義的常量和函式。
import mnist_inference
import mnist_train

# 每10秒載入一次最新的模型,並在測試資料上測試最新模型的正確率。
EVAL_INTERVAL_SECS = 15

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}

		# 直接通過呼叫封裝好的函式來計算前向傳播的結果。因為測試時不關注正則化損失的值, 
		# 所以這裡用於計算正則化損失的函式被設定為None。
		y = mnist_inference.inference(x, None)

		# 使用前向傳播的結果計算正確率。如果需要對未知的樣例進行分類,那麼使用
		# tf.argmax(y, 1)就可以得到輸入樣例的預測類別了。
		correct_prediction = tf.equal(tf.argmax(y, 1), tf.argmax(y_, 1))
		accuracy = tf.reduce_mean(tf.cast(correct_prediction, tf.float32))

		# 通過變數重新命名的方式來載入模型,這樣在前向傳播的過程中就不需要呼叫求滑動平均
		# 的函式來獲取平均值了。這使得我們可以完全共用mnist_inference.py中定義的
		# 前向傳播過程。
		variable_averages = tf.train.ExponentialMovingAverage(
				mnist_train.MOVING_AVERAGE_DECAY)
		variables_to_restore = variable_averages.variables_to_restore()
		saver = tf.train.Saver(variables_to_restore)

		# 每隔EVAL_INTERVAL_SECS秒呼叫一次計算正確率的過程以檢測訓練過程中正確率的# 變化。
		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:
				     # 載入模型。
				  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 step(s), 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("C:/path/to/MNIST_data", one_hot=True)
   evaluate(mnist)

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



Experiment階段(調參+記錄)
Version-1


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


INPUT_NODE = 784
OUTPUT_NODE = 10
LAYER1_NODE = 500


 


Version-2


BATCH_SIZE = 512
LEARNING_RATE_BASE = 0.9
LEARNING_RATE_DECAY = 0.99
REGULARIZATION_RATE = 0.0001
TRAINING_STEPS = 30000
MOVING_AVERAGE_DECAY = 0.99


INPUT_NODE = 784
OUTPUT_NODE = 10
LAYER1_NODE = 512


隨著batch的增大,反而使得訓練的時間多了3倍多。
 
 


隨後把batch調整為128,訓練的速度立即提了上來,但準確率變化卻不大。