參考:https://blog.csdn.net/u013733326/article/details/79971488
使用TensorFlow構建你的第一個神經網絡
我們將會使用TensorFlow構建一個神經網絡,需要記住的是實現模型需要做以下兩個步驟:
1. 創建計算圖
2. 運行計算圖
該神經網絡的目標是:判斷圖片表示的是什么數字(0-5)
- 訓練集:有從0到5的數字的1080張圖片(64x64像素),每個數字擁有180張圖片。
- 測試集:有從0到5的數字的120張圖片(64x64像素),每個數字擁有5張圖片。
建立的模型是:LINEAR -> RELU -> LINEAR -> RELU -> LINEAR -> SOFTMAX,SIGMOID輸出層已經轉換為SOFTMAX。
當有兩個以上的類時,一個SOFTMAX層將SIGMOID一般化,SIGMOID一般用於二分類中
這是一個三層神經網絡
1.加載數據
X_train_orig , Y_train_orig , X_test_orig , Y_test_orig , classes = tf_utils.load_dataset()
隨機查看加載的數據:
index = 11 plt.imshow(X_train_orig[index]) print("Y = " + str(np.squeeze(Y_train_orig[:,index])))
返回:
Y = 1
圖示:
index = 12 plt.imshow(X_train_orig[index]) print("Y = " + str(np.squeeze(Y_train_orig[:,index])))
返回:
Y = 5
圖示:
2.處理數據
1.首先將數據集進行扁平化
2.除以255進行歸一化,將像素值由(0,255)變為(0,1)
3.並將標簽Y轉成獨熱編碼
X_train_flatten = X_train_orig.reshape(X_train_orig.shape[0],-1).T #每一列就是一個樣本 X_test_flatten = X_test_orig.reshape(X_test_orig.shape[0],-1).T #歸一化數據 X_train = X_train_flatten / 255 X_test = X_test_flatten / 255 #轉換為獨熱矩陣 Y_train = tf_utils.convert_to_one_hot(Y_train_orig,6) Y_test = tf_utils.convert_to_one_hot(Y_test_orig,6) print("訓練集樣本數 = " + str(X_train.shape[1])) print("測試集樣本數 = " + str(X_test.shape[1])) print("X_train.shape: " + str(X_train.shape)) print("Y_train.shape: " + str(Y_train.shape)) print("X_test.shape: " + str(X_test.shape)) print("Y_test.shape: " + str(Y_test.shape))
返回:
訓練集樣本數 = 1080 測試集樣本數 = 120 X_train.shape: (12288, 1080) Y_train.shape: (6, 1080) X_test.shape: (12288, 120) Y_test.shape: (6, 120)
3.創建占位符 —— X,Y
這將允許我們稍后在運行會話時傳遞您的訓練數據
def create_placeholders(n_x,n_y): """ 為TensorFlow會話創建占位符 參數: n_x - 一個實數,圖片向量的大小(64*64*3 = 12288) n_y - 一個實數,分類數(從0到5,所以n_y = 6) 返回: X - 一個數據輸入的占位符,維度為[n_x, None],dtype = "float" Y - 一個對應輸入的標簽的占位符,維度為[n_Y,None],dtype = "float" 提示: 使用None,因為它讓我們可以靈活處理占位符提供的樣本數量。事實上,測試/訓練期間的樣本數量是不同的。 """ X = tf.placeholder(tf.float32, [n_x, None], name="X") Y = tf.placeholder(tf.float32, [n_y, None], name="Y") return X, Y
測試:
X, Y = create_placeholders(12288, 6) print("X = " + str(X)) print("Y = " + str(Y))
返回:
X = Tensor("X_2:0", shape=(12288, ?), dtype=float32) Y = Tensor("Y:0", shape=(6, ?), dtype=float32)
4.初始化參數
使用Xavier初始化權重w和用零來初始化偏差b,即:
W1 = tf.get_variable("W1", [25,12288], initializer = tf.contrib.layers.xavier_initializer(seed = 1)) b1 = tf.get_variable("b1", [25,1], initializer = tf.zeros_initializer())
⚠️這里使用get_variable()函數的原因:
tf.Variable()
每次都在創建新對象,對於get_variable()
來說,對於已經創建的變量對象,就把那個對象返回,如果沒有創建變量對象的話,就創建一個新的。
def initialize_parameters(): """ 初始化神經網絡的參數,參數的維度如下: W1 : [25, 12288] b1 : [25, 1] W2 : [12, 25] b2 : [12, 1] W3 : [6, 12] b3 : [6, 1] 返回: parameters - 包含了W和b的字典 """ tf.set_random_seed(1) #指定隨機種子 W1 = tf.get_variable("W1",[25,12288],initializer=tf.contrib.layers.xavier_initializer(seed=1)) b1 = tf.get_variable("b1",[25,1],initializer=tf.zeros_initializer()) W2 = tf.get_variable("W2", [12, 25], initializer = tf.contrib.layers.xavier_initializer(seed=1)) b2 = tf.get_variable("b2", [12, 1], initializer = tf.zeros_initializer()) W3 = tf.get_variable("W3", [6, 12], initializer = tf.contrib.layers.xavier_initializer(seed=1)) b3 = tf.get_variable("b3", [6, 1], initializer = tf.zeros_initializer()) parameters = {"W1": W1, "b1": b1, "W2": W2, "b2": b2, "W3": W3, "b3": b3} return parameters
測試:
tf.reset_default_graph() #用於清除默認圖形堆棧並重置全局默認圖形。 with tf.Session() as sess: parameters = initialize_parameters() print("W1 = " + str(parameters["W1"])) print("b1 = " + str(parameters["b1"])) print("W2 = " + str(parameters["W2"])) print("b2 = " + str(parameters["b2"])) print("W3 = " + str(parameters["W3"])) print("b3 = " + str(parameters["b3"]))
返回:
W1 = <tf.Variable 'W1:0' shape=(25, 12288) dtype=float32_ref> b1 = <tf.Variable 'b1:0' shape=(25, 1) dtype=float32_ref> W2 = <tf.Variable 'W2:0' shape=(12, 25) dtype=float32_ref> b2 = <tf.Variable 'b2:0' shape=(12, 1) dtype=float32_ref> W3 = <tf.Variable 'W3:0' shape=(6, 12) dtype=float32_ref> b3 = <tf.Variable 'b3:0' shape=(6, 1) dtype=float32_ref>
正如預期的那樣,這些參數只有物理空間,但是還沒有被賦值,這是因為沒有通過session執行。
5.前向傳播
在TensorFlow中實現前向傳播,該函數將接受一個字典參數並完成前向傳播,它會用到以下代碼:
- tf.add(…) :加法
- tf.matmul(… , …) :矩陣乘法
- tf.nn.relu(…) :Relu激活函數
我們要實現神經網絡的前向傳播,我們會拿numpy與TensorFlow實現的神經網絡的代碼作比較。最重要的是前向傳播要在Z3
處停止,因為在TensorFlow中最后的線性輸出層的輸出作為計算損失函數的輸入,所以不需要A3.
def forward_propagation(X,parameters): """ 實現一個模型的前向傳播,模型結構為LINEAR -> RELU -> LINEAR -> RELU -> LINEAR -> SOFTMAX 參數: X - 輸入數據的占位符,維度為(輸入節點數量,樣本數量) parameters - 包含了W和b的參數的字典 返回: Z3 - 最后一個LINEAR節點的輸出 """ W1 = parameters['W1'] b1 = parameters['b1'] W2 = parameters['W2'] b2 = parameters['b2'] W3 = parameters['W3'] b3 = parameters['b3'] Z1 = tf.add(tf.matmul(W1,X),b1) # Z1 = np.dot(W1, X) + b1 #Z1 = tf.matmul(W1,X) + b1 #也可以這樣寫 A1 = tf.nn.relu(Z1) # A1 = relu(Z1) Z2 = tf.add(tf.matmul(W2, A1), b2) # Z2 = np.dot(W2, a1) + b2 A2 = tf.nn.relu(Z2) # A2 = relu(Z2) Z3 = tf.add(tf.matmul(W3, A2), b3) # Z3 = np.dot(W3,Z2) + b3 return Z3
測試:
tf.reset_default_graph() #用於清除默認圖形堆棧並重置全局默認圖形。 with tf.Session() as sess: X,Y = create_placeholders(12288,6) parameters = initialize_parameters() Z3 = forward_propagation(X,parameters) print("Z3 = " + str(Z3))
返回:
Z3 = Tensor("Add_2:0", shape=(6, ?), dtype=float32)
可見到這里還是不能夠確定傳入的mini_batch_size的大小,所以是問號?
前向傳播不會輸出任何cache,當我們完成反向傳播的時候你就會明白了
6.計算成本
tf.reduce_mean(tf.nn.softmax_cross_entropy_with_logits(logits = ..., labels = ...))
函數為:
def compute_cost(Z3,Y): """ 計算成本 參數: Z3 - 前向傳播的結果 Y - 標簽,一個占位符,和Z3的維度相同 返回: cost - 成本值 """ logits = tf.transpose(Z3) #轉置 labels = tf.transpose(Y) #轉置 cost = tf.reduce_mean(tf.nn.softmax_cross_entropy_with_logits(logits=logits,labels=labels)) return cost
測試:
tf.reset_default_graph() with tf.Session() as sess: X,Y = create_placeholders(12288,6) parameters = initialize_parameters() Z3 = forward_propagation(X,parameters) cost = compute_cost(Z3,Y) print("cost = " + str(cost))
返回:
cost = Tensor("Mean:0", shape=(), dtype=float32)
這里有個警告:
WARNING:tensorflow:From <ipython-input-19-fa19d65a87cf>:17: softmax_cross_entropy_with_logits (from tensorflow.python.ops.nn_ops) is deprecated and will be removed in a future version. Instructions for updating: Future major versions of TensorFlow will allow gradients to flow into the labels input on backprop by default. See `tf.nn.softmax_cross_entropy_with_logits_v2`.
其實就是應該使用新的成本計算函數tf.nn.softmax_cross_entropy_with_logits_v2
改后:
def compute_cost(Z3,Y): """ 計算成本 參數: Z3 - 前向傳播的結果 Y - 標簽,一個占位符,和Z3的維度相同 返回: cost - 成本值 """ logits = tf.transpose(Z3) #轉置 labels = tf.transpose(Y) #轉置 cost = tf.reduce_mean(tf.nn.softmax_cross_entropy_with_logits_v2(logits=logits,labels=labels)) return cost
7.反向傳播和更新參數
得益於編程框架,所有反向傳播和參數更新都在1行代碼中處理。
計算成本函數后,將創建一個“optimizer”對象,定義使用的優化算法和學習率等參數。
運行tf.session時,必須將此對象與成本函數一起調用,當被調用時,它將使用所選擇的方法和學習速率對給定成本進行優化。
比如下面使用的是梯度下降法GD對成本進行優化:
optimizer = tf.train.GradientDescentOptimizer(learning_rate = learning_rate).minimize(cost)
要進行優化,應該這樣做:
_ , c = sess.run([optimizer,cost],feed_dict={X:mini_batch_X,Y:mini_batch_Y})
feed_dict為占位符傳入參數,其實就是指定一次迭代訓練/測試的照片的數量
編寫代碼時,我們經常使用 _
作為一次性變量來存儲我們稍后不需要使用的值。 這里,_
具有我們不需要的優化器的評估值(並且c取值為成本變量的值)
8.整合函數,建立模型
def model(X_train,Y_train,X_test,Y_test, learning_rate=0.0001,num_epochs=1500,minibatch_size=32, print_cost=True,is_plot=True): """ 實現一個三層的TensorFlow神經網絡:LINEAR->RELU->LINEAR->RELU->LINEAR->SOFTMAX 參數: X_train - 訓練集,維度為(輸入大小(輸入節點數量) = 12288, 樣本數量 = 1080) Y_train - 訓練集分類數量,維度為(輸出大小(輸出節點數量) = 6, 樣本數量 = 1080) X_test - 測試集,維度為(輸入大小(輸入節點數量) = 12288, 樣本數量 = 120) Y_test - 測試集分類數量,維度為(輸出大小(輸出節點數量) = 6, 樣本數量 = 120) learning_rate - 學習速率 num_epochs - 整個訓練集的遍歷次數 mini_batch_size - 每個小批量數據集的大小 print_cost - 是否打印成本,每100代打印一次 is_plot - 是否繪制曲線圖 返回: parameters - 學習后的參數 """ ops.reset_default_graph() #能夠重新運行模型而不覆蓋tf變量 tf.set_random_seed(1) seed = 3 (n_x , m) = X_train.shape #獲取輸入節點數量和樣本數 n_y = Y_train.shape[0] #獲取輸出節點數量 costs = [] #成本集 #給X和Y創建placeholder X,Y = create_placeholders(n_x,n_y) #初始化參數 parameters = initialize_parameters() #前向傳播 Z3 = forward_propagation(X,parameters) #計算成本 cost = compute_cost(Z3,Y) #反向傳播,使用Adam優化 optimizer = tf.train.AdamOptimizer(learning_rate=learning_rate).minimize(cost) #初始化所有的變量 init = tf.global_variables_initializer() #開始會話並計算 with tf.Session() as sess: #初始化 sess.run(init) #正常訓練的循環 for epoch in range(num_epochs): epoch_cost = 0 #每代的成本 num_minibatches = int(m / minibatch_size) #minibatch的總數量 seed = seed + 1 #打亂來為數據分批,seed的作用是保證我們打亂后的數據和作者的相同 minibatches = tf_utils.random_mini_batches(X_train,Y_train,minibatch_size,seed) for minibatch in minibatches: #選擇一個minibatch (minibatch_X,minibatch_Y) = minibatch #數據已經准備好了,開始運行session _ , minibatch_cost = sess.run([optimizer,cost],feed_dict={X:minibatch_X,Y:minibatch_Y}) #計算這個minibatch在這一代中所占的誤差 epoch_cost = epoch_cost + minibatch_cost / num_minibatches #記錄並打印成本 ## 記錄成本 if epoch % 5 == 0: costs.append(epoch_cost) #是否打印: if print_cost and epoch % 100 == 0: print("epoch = " + str(epoch) + " epoch_cost = " + str(epoch_cost)) #是否繪制圖譜 if is_plot: plt.plot(np.squeeze(costs)) plt.ylabel('cost') plt.xlabel('iterations (per tens)') plt.title("Learning rate =" + str(learning_rate)) plt.show() #保存學習后的參數 parameters = sess.run(parameters) print("參數已經保存到session。") #計算當前的預測結果 correct_prediction = tf.equal(tf.argmax(Z3),tf.argmax(Y)) #計算准確率 accuracy = tf.reduce_mean(tf.cast(correct_prediction,"float")) print("訓練集的准確率:", accuracy.eval({X: X_train, Y: Y_train})) print("測試集的准確率:", accuracy.eval({X: X_test, Y: Y_test})) return parameters
我們來正式運行一下模型,請注意,這次的運行時間大約在5-8分鍾左右,如果在epoch = 100
的時候,你的epoch_cost = 1.01645776539
的值和我相差過大,那么你就立即停止,回頭檢查一下哪里出了問題。
測試:
#開始時間 start_time = time.clock() #開始訓練 parameters = model(X_train, Y_train, X_test, Y_test) #結束時間 end_time = time.clock() #計算時差 print("CPU的執行時間 = " + str(end_time - start_time) + " 秒" )
在jupyter運行的過程中,遇見錯誤:
OMP: Error #15: Initializing libiomp5.dylib, but found libiomp5.dylib already initialized. OMP: Hint This means that multiple copies of the OpenMP runtime have been linked into the program. That is dangerous, since it can degrade performance or cause incorrect results. The best thing to do is to ensure that only a single OpenMP runtime is linked into the process, e.g. by avoiding static linking of the OpenMP runtime in any library. As an unsafe, unsupported, undocumented workaround you can set the environment variable KMP_DUPLICATE_LIB_OK=TRUE to allow the program to continue to execute, but that may cause crashes or silently produce incorrect results. For more information, please see http://www.intel.com/software/products/support/. [I 15:33:17.486 NotebookApp] KernelRestarter: restarting kernel (1/5), keep random ports kernel eb597a64-a8c0-49e5-82f2-a853f6c8b72a restarted
導致重啟,一直不能成功跑完,解決辦法是在運行之前先運行:
import os os.environ["KMP_DUPLICATE_LIB_OK"]="TRUE"
如果你沒有解決,更多的解決辦法可見:https://github.com/dmlc/xgboost/issues/1715
返回:
epoch = 0 epoch_cost = 1.8557019125331538 epoch = 100 epoch_cost = 1.0172552520578557 epoch = 200 epoch_cost = 0.7331836395191423 epoch = 300 epoch_cost = 0.5730706308827257 epoch = 400 epoch_cost = 0.46857346774953773 epoch = 500 epoch_cost = 0.38122756237333466 epoch = 600 epoch_cost = 0.313796519781604 epoch = 700 epoch_cost = 0.2537061707539992 epoch = 800 epoch_cost = 0.20388749196673892 epoch = 900 epoch_cost = 0.16644730599540652 epoch = 1000 epoch_cost = 0.1466932777654041 epoch = 1100 epoch_cost = 0.10728540858536052 epoch = 1200 epoch_cost = 0.08660263003724997 epoch = 1300 epoch_cost = 0.05934071975449723 epoch = 1400 epoch_cost = 0.052284905574086944 參數已經保存到session。 訓練集的准確率: 0.9990741 測試集的准確率: 0.725 CPU的執行時間 = 1185.838935 秒
圖示:
現在,我們的算法已經可以識別0-5的手勢符號了,准確率在72.5%。
我們的模型看起來足夠大了,可以適應訓練集,但是考慮到訓練與測試的差異,你也完全可以嘗試添加L2或者dropout來減少過擬合。將session視為一組代碼來訓練模型,在每個minibatch上運行會話時,都會訓練我們的參數,總的來說,你已經運行了很多次(1500代),直到你獲得訓練有素的參數。
9.使用訓練好的模型來測試
使用保存在session中訓練好的參數值parameters
import matplotlib.pyplot as plt # plt 用於顯示圖片 import matplotlib.image as mpimg # mpimg 用於讀取圖片 import numpy as np #這是博主自己拍的圖片 my_image1 = "5.png" #定義圖片名稱 fileName1 = "./datasets/fingers/" + my_image1 #圖片地址 image1 = mpimg.imread(fileName1) #讀取圖片 plt.imshow(image1) #顯示圖片 my_image1 = image1.reshape(1,64 * 64 * 3).T #重構圖片 my_image_prediction = tf_utils.predict(my_image1, parameters) #開始預測 print("預測結果: y = " + str(np.squeeze(my_image_prediction)))
返回:
預測結果: y = 5
圖示:
my_image1 = "4.png" #定義圖片名稱 fileName1 = "./datasets/fingers/" + my_image1 #圖片地址 image1 = mpimg.imread(fileName1) #讀取圖片 plt.imshow(image1) #顯示圖片 my_image1 = image1.reshape(1,64 * 64 * 3).T #重構圖片 my_image_prediction = tf_utils.predict(my_image1, parameters) #開始預測 print("預測結果: y = " + str(np.squeeze(my_image_prediction)))
返回:
預測結果: y = 2
圖示:
my_image1 = "3.png" #定義圖片名稱 fileName1 = "./datasets/fingers/" + my_image1 #圖片地址 image1 = mpimg.imread(fileName1) #讀取圖片 plt.imshow(image1) #顯示圖片 my_image1 = image1.reshape(1,64 * 64 * 3).T #重構圖片 my_image_prediction = tf_utils.predict(my_image1, parameters) #開始預測 print("預測結果: y = " + str(np.squeeze(my_image_prediction)))
返回:
預測結果: y = 2
圖示:
my_image1 = "2.png" #定義圖片名稱 fileName1 = "./datasets/fingers/" + my_image1 #圖片地址 image1 = mpimg.imread(fileName1) #讀取圖片 plt.imshow(image1) #顯示圖片 my_image1 = image1.reshape(1,64 * 64 * 3).T #重構圖片 my_image_prediction = tf_utils.predict(my_image1, parameters) #開始預測 print("預測結果: y = " + str(np.squeeze(my_image_prediction)))
返回:
預測結果: y = 1
圖示:
my_image1 = "1.png" #定義圖片名稱 fileName1 = "./datasets/fingers/" + my_image1 #圖片地址 image1 = mpimg.imread(fileName1) #讀取圖片 plt.imshow(image1) #顯示圖片 my_image1 = image1.reshape(1,64 * 64 * 3).T #重構圖片 my_image_prediction = tf_utils.predict(my_image1, parameters) #開始預測 print("預測結果: y = " + str(np.squeeze(my_image_prediction)))
返回:
預測結果: y = 1
圖示:
從上面可見測試的效果不是很好,之后優化下,使用dropout,感覺訓練結果過擬合