TensorFlow入門(1):求N元一次方程
導語 | 本文是 騰訊雲技術社區 作者 譚正中 首發在社區的系列文章,記錄了作者在初學神經網路和機器學習所遇到的坑,目前已經發布了3篇。鏈接如下:
TensorFlow入門(1):求N元一次方程
TensorFlow 入門(2):使用DNN分類器對數據進行分類
TensorFlow入門(3):使用神經網路擬合N元一次方程
背景
今年以來,人工智慧成為一個時代熱點,同時 TensorFlow 1.0 的發布後,我也想蹭蹭時代的熱點,初步學習一下神經網路和機器學習,在這裡把成果以初學者的方式記錄下來。 學習一個新東西,不可避免會遇到很多坑,很多教程都是一個有經驗,熟悉的人寫的,那樣其實並不是特別接地氣,因為很多坑在作者寫文章的時候都忘了,這篇文章也是記錄一下我掉的坑,做一個備忘的作用。 閱讀這一系列文章,你需要做好以下準備,或者有以下技能:
- 能夠在你的環境中安裝好 Python、TensorFlow
- 了解基本的 Python 語法和數據結構
- 有基礎的線性代數知識
- 英語水平能夠大概看懂TensorFlow 官方入門教程
我也只是一個剛接觸機器學習的初學者,所以你不用太過於擔心這篇文章難度問題,我會盡量用高中生能夠看懂的方式來敘述問題,希望能夠幫助到大部分初學者。TensorFlow 介紹性的話就不多說了,直奔主題。
解決什麼問題?
這篇文章是我正式使用 TensorFlow 第一天寫的,在這之前,我閱讀了一些關於機器學習的理論知識,在閱讀完官網的 入門教程後,我發現 TensorFlow 最基礎的應用可以用來擬合方程,即給出 N 個點(x,y),這些點符合一定規律,我們希望推導出其他符合這個規律的 x 對應的 y 值。
最簡單的情況是線性的,我們希望能夠使用一條直線擬合這幾個點,得到方程式完整的內容,即假設 y = a *x b,我們只需要求得 a 和 b 的值就好了,在初中數學中,只需要提供 2 組(x,y),即可通過消元法求得 a 和 b,這是一個很簡單的數學問題。但是如果想用 TensorFlow 的比較通用的方式解決這個問題,就不能教機器這樣解了,我們必須讓機器通過不斷嘗試的方式,來獲得 a 和 b 的值。
我們來實際操作一下,假設我們現在有一個方程 y = 5 * x 13,我們需要讓機器通過一些(x,y) 來推導出 a = 5,b = 13。 首先要使用 TensorFlow,需要 import tensorflow 和數學庫 numpy,TensorFlow 的數學計算是以 numpy 為基礎的(這點我還不是特別確認,可能也可以有其他的數學庫,不過他們之間關係緊密):
import tensorflow as tfimport numpy as np
我們要提供一系列已經存在的(x,y) 組合,這個叫做訓練集,我們先用代碼生成 5 組訓練集,先隨機生成 5 組 x 的值,命名為 t_x(t 為訓練 train 的意思),其中使用 np.random.random([5]) 來生成 0 到 1 之間的隨機數,將其乘以 10 可以獲得 0 到 10 之間的隨機數,最後使用 np.floor 函數對其進行取整,並且令數據類型為浮點數 np.float32 以便於計算:
t_x = np.floor(10 * np.random.random([5]),dtype=np.float32)print t_x
結果如下,其中隨機數每次執行會不一樣:
[ 4. 2. 3. 2. 9.]
然後根據公式求得 t_y 的值:
t_y = t_x * 3.0 8.0print t_y
結果為:
[ 20. 14. 17. 14. 35.]
這樣我們就能夠得到 5 組訓練集了,可以開始使用 TensorFlow 求解了,關於 TensorFlow 的基本用法我現在也沒辦法講的太清楚,可以簡單參考下 官網基礎教程,我以我的理解,重新簡單講解一下,TensorFlow 所有的執行流程會在一個 Session 中執行,可以把它暫時看做執行計算的一個載體。我們要在執行前,構造計算的規則,對於計算量的表示,目前只需要知道 2 種,一種是輸入量,在 TensorFlow 中以佔位符 placeholder 表示,另一種是變數,以 Variable 表示。我們的訓練集是在計算過程中以輸入表示,因此將其定義為佔位符,它的類型為浮點類型 tf.float32:
x = tf.placeholder(tf.float32)y = tf.placeholder(tf.float32)
而需要用於輸出的 a 和 b 的值,我們將其定義為變數,初始化為浮點數 0.0,這個初始值並不是特別重要,因為 TensorFlow 在訓練的過程中,會不斷調整這兩個值,這個後面會詳細說明:
a = tf.Variable(0.0)b = tf.Variable(0.0)
而在 TensorFlow 的 Session 內部,我們需要根據內部 x,a,b 的值,求得當前的 y 的值 curr_y,因此按照線性公式使用 x,a,b 定義 curr_y,這行語句執行時並不會進行真正的計算,只是僅僅描述他們的關係:
curr_y = x * a b
得到了當前的 y 值 curr_y,我們就要和我們提供的訓練集中對應的 y 值進行比較,使得差異最小,這個差異在機器學習中稱為損失函數(Loss function),當損失函數值最低時,就可以認為找到了一個比較好的值,當然實際應用中會有一些局部最小值,這個本例不涉及到,就不討論了。一般來說,可以使用他們的方差來描述損失函數,因為 TensorFlow 能夠很好的支持矩陣運算,而 curr_y 和 y 都可以看做是 1 行 5 列的一個矩陣,因此損失函數可以定義為矩陣各元素之差的平方之和:
loss = tf.reduce_sum(tf.square(curr_y - y)) #損失函數,實際輸出數據和訓練輸出數據的方差
重溫一下我們訓練的目的,是通過不斷調整變數 a 和 b 的值,來達到損失函數值最小的目的。而調整 a 和 b 的值的方法,我們採用一個叫做梯度下降(Gradient descent)的方法,簡單來說,可以看做做 loss = fun(a,b) 形成了一個三維曲面,大概如下圖所示(圖來源文末參考資料):
梯度下降可以看做是一個小球,沿著曲面滾動,它距離地面的面積,就是 loss 函數的值,當它滾動到最低點時,也就找到了損失函數最小的位置(關於局部最小值和梯度下降更深入的內容可以參考原文)。 TensorFlow 中能夠很方便地定義梯度下降的訓練方法以及描述求損失函數最小值的目的:
optimizer = tf.train.GradientDescentOptimizer(0.001)train = optimizer.minimize(loss) #訓練的結果是使得損失函數最小
其中梯度下降的參數 0.001 是我調出來的,我目前並不清楚這個值如何更好的調整,太大可能找不到局部最小點,太小會導致訓練過慢,也許這就是機器學習工程師有時候被戲稱為"調參工程師"的原因? 至此,我們的 TensorFlow 描述部分已經完成了,可以開始進入執行流程了,首先,我們要創建一個 Session 用於執行對於變數,我們需要進行初始化操作,內部會對變數進行內存的分配操作,這個內存會在 Session 關閉時被釋放:
sess = tf.Session()sess.run(tf.global_variables_initializer())
然後我們可以對數據開始訓練,第一個參數是訓練的內容 train,第二個參數是指定變數 x 和 y 對應的實際值:
sess.run(train, {x:t_x, y:t_y})
一般,訓練次數和準確度是有關係的,我通過"調參",確定訓練 10000 次,在每次訓練後,把當前的 a,b 和損失函數 loss 的值列印出來,需要注意的是,TensorFlow 中的值需要在 sess.run 中執行才能看到結果,如果需要得到多個值,可以將其放到一個數組 []中,因此列印 a,b 和 loss 的值需要放到 sess.run 中執行,同時也要將 t_x 和 t_y 傳入:
for i in range(10000): sess.run(train, {x:t_x, y:t_y}) print sess.run([a,b,loss],{x:t_x, y:t_y})
完整的 Python 代碼如下:
#!/usr/bin/python#coding=utf-8import tensorflow as tfimport numpy as nptf.logging.set_verbosity(tf.logging.ERROR) #日誌級別設置成 ERROR,避免干擾np.set_printoptions(threshold=nan) #列印內容不限制長度t_x = np.floor(10 * np.random.random([5]),dtype=np.float32)print t_xt_y = t_x * 3.0 8.0print t_yx = tf.placeholder(tf.float32)y = tf.placeholder(tf.float32)a = tf.Variable(0.0)b = tf.Variable(0.0)curr_y = x * a bloss = tf.reduce_sum(tf.square(curr_y - y)) #損失函數,實際輸出數據和訓練輸出數據的方差optimizer = tf.train.GradientDescentOptimizer(0.001)train = optimizer.minimize(loss) #訓練的結果是使得損失函數最小sess = tf.Session() #創建 Sessionsess.run(tf.global_variables_initializer()) #變數初始化for i in range(10000): sess.run(train, {x:t_x, y:t_y}) print sess.run([a,b,loss],{x:t_x, y:t_y})exit(0)
好了,現在可以執行一下,如果環境沒有問題的話,你應該能看到屏幕上不斷出現的值,我稍微截取頭尾一部分看看:
$ python ./test1.py [ 4. 2. 3. 2. 9.][ 20. 14. 17. 14. 35.][1.0040001, 0.2, 1381.1299][1.7710881, 0.35784, 839.84033][2.3569665, 0.48341811, 522.96967][2.8042414, 0.58430529, 337.39871][3.1455021, 0.66629255, 228.64702][3.4056759, 0.73380953, 164.84021][3.6038294, 0.7902444, 127.32993][3.7545466, 0.83818877, 105.2057][3.8689826, 0.87962502, 92.084335][3.9556696, 0.91606945, 84.231171][4.0211344, 0.94868195, 79.461243][4.0703683, 0.97834975, 76.496269][4.1071901, 1.0057515, 74.588219][4.1345205, 1.0314064, 73.299591]...[3.0000157, 7.9999132, 1.0950316e-08][3.0000157, 7.9999132, 1.0950316e-08][3.0000157, 7.9999132, 1.0950316e-08][3.0000157, 7.9999132, 1.0950316e-08][3.0000157, 7.9999132, 1.0950316e-08][3.0000157, 7.9999132, 1.0950316e-08][3.0000157, 7.9999132, 1.0950316e-08]
在剛開始執行的時候,代碼列印出訓練集 t_x 和 t_y 的值,然後開始進行訓練,a 和 b 的值快速增長,損失函數也在不斷減少,最後 a 的值停留在 3.0000157,b 的值停留在 7.9999132,損失函數則為 1.0950316e-08,可見與結果 a=3,b=8 已經非常接近了,如果要更加接近結果,可以嘗試降低梯度下降學習速率參數。這樣就達到了求 a 和 b 的值的目的。
再深入一點:多元一次方程
上面的例子如果能完成,結合官網的資料和其他博主的資料,我相信你已經算入了個門了,後面能不能通過修改上面的例子進行解決更加複雜的問題呢?再看看下一個問題,如果有一個值,它受到 N 個參數的影響,但是每個參數的權重我們並不清楚,我們希望能用剛剛學到的 TensorFlow 來解決這個問題。 首先建立一個模型,表示 N 組數據,具體點,先實現 5 個變數的求解,生成 10 個數據集,我們可以很容易聯想到使用大小為 [10,5]的矩陣表示 t_x,使用大小為 [5,1]的矩陣表示參數權重 t_w,使用大小為 [10,1]的矩陣表示結果 t_y,即 t_y = t_x * t_w。 當然,為了更加通用,變數的數量和數據集的數量可以使用常量來表示,矩陣的向量乘法在 numpy 庫中使用 dot 函數實現:
test_count = 10 #數據集數量param_count = 5 #變數數t_x = np.floor(1000 * np.random.random([test_count,param_count]),dtype=np.float32)#要求的值t_w = np.floor(1000 * np.random.random([param_count,1]),dtype=np.float32)#根據公式 t_y = t_x * t_w 算出值 t_yt_y = t_x.dot(t_w)print t_xprint t_wprint t_y
與上面的例子一樣,我們以 TensorFlow 佔位符形式定義輸入訓練集 x 和 y,矩陣大小可以使用 shape 參數來定義:
#x 是輸入量,對應 t_x,用於訓練輸入,在訓練過程中,由外部提供,因此是 placeholder 類型x = tf.placeholder(tf.float32,shape=[test_count,param_count])y = tf.placeholder(tf.float32,shape=[test_count,1])
以 TensorFlow 變數形式定義結果 w:
#w 是要求的各個參數的權重,是目標輸出,對應 t_ww = tf.Variable(np.zeros(param_count,dtype=np.float32).reshape((param_count,1)), tf.float32)
定義 TensorFlow 計算結果 y、損失函數 loss 和訓練方法:
curr_y = tf.matmul(x, w) #實際輸出數據loss = tf.reduce_sum(tf.square(t_y - curr_y)) #損失函數,實際輸出數據和訓練輸出數據的方差之和optimizer = tf.train.GradientDescentOptimizer(0.0000001)train = optimizer.minimize(loss) #訓練的結果是使得損失函數最小
針對訓練次數的問題,我們可以優化一下之前的方式,設定當 loss 函數值低於一定值或者不再變化的時候停止,因為 loss 函數需要在 Session 中使用,它需要使用 TensorFlow 的常量表示:
LOSS_MIN_VALUE = tf.constant(1e-5) #達到此精度的時候結束訓練
好了,模型已經建立完畢,開始訓練,我們使用變數 run_count 來記錄訓練的次數,以 last_loss 記錄上一次訓練的損失函數的值,初始值為 0。
sess = tf.Session()sess.run(tf.global_variables_initializer())run_count = 0last_loss = 0
訓練主循環,將當前的 loss 函數值保存在 curr_loss 中,與上一次相比,如果相同,則退出訓練,另外如果 loss 函數低於設定的精度 LOSS_MIN_VALUE,也會退出訓練:
while True: run_count = 1 sess.run(train, {x:t_x, y:t_y}) curr_loss,is_ok = sess.run([loss,loss < LOSS_MIN_VALUE],{x:t_x, y:t_y}) print "運行%d 次,loss=%s" % (run_count,curr_loss) if last_loss == curr_loss: break last_loss = curr_loss if is_ok: break
最後列印結果,由於我們知道 t_w 的值是整數,因此將得到的結果四捨五入的值 fix_w 也列印出來,再看看 fix_w 與 t_w 的差距 fix_w_loss 是多少:
curr_W, curr_loss = sess.run([w, loss], {x:t_x,y:t_y})print("t_w: %snw: %snfix_w: %snloss: %snfix_w_loss:%s" % (t_w, curr_W, np.round(curr_W), curr_loss, np.sum(np.square(t_w - np.round(curr_W)))))exit(0)
完整代碼如下:
#!/usr/bin/python#coding=utf-8import tensorflow as tfimport numpy as nptf.logging.set_verbosity(tf.logging.ERROR) #日誌級別設置成 ERROR,避免干擾np.set_printoptions(threshold=nan) #列印內容不限制長度test_count = 10 #數據集數量param_count = 5 #變數數t_x = np.floor(1000 * np.random.random([test_count,param_count]),dtype=np.float32)#要求的值t_w = np.floor(1000 * np.random.random([param_count,1]),dtype=np.float32)#根據公式 t_y = t_x * t_w 算出值 t_yt_y = t_x.dot(t_w)print t_xprint t_wprint t_y#x 是輸入量,對應 t_x,用於訓練輸入,在訓練過程中,由外部提供,因此是 placeholder 類型x = tf.placeholder(tf.float32,shape=[test_count,param_count])y = tf.placeholder(tf.float32,shape=[test_count,1])#w 是要求的各個參數的權重,是目標輸出,對應 t_ww = tf.Variable(np.zeros(param_count,dtype=np.float32).reshape((param_count,1)), tf.float32) curr_y = tf.matmul(x, w) #實際輸出數據loss = tf.reduce_sum(tf.square(t_y - curr_y)) #損失函數,實際輸出數據和訓練輸出數據的方差之和optimizer = tf.train.GradientDescentOptimizer(0.00000001)train = optimizer.minimize(loss) #訓練的結果是使得損失函數最小LOSS_MIN_VALUE = tf.constant(1e-5) #達到此精度的時候結束訓練sess = tf.Session()sess.run(tf.global_variables_initializer())run_count = 0last_loss = 0while True: run_count = 1 sess.run(train, {x:t_x, y:t_y}) curr_loss,is_ok = sess.run([loss,loss < LOSS_MIN_VALUE],{x:t_x, y:t_y}) print "運行%d 次,loss=%s" % (run_count,curr_loss) if last_loss == curr_loss: break last_loss = curr_loss if is_ok: breakcurr_W, curr_loss = sess.run([w, loss], {x:t_x,y:t_y})print("t_w: %snw: %snfix_w: %snloss: %snfix_w_loss:%s" % (t_w, curr_W, np.round(curr_W), curr_loss, np.sum(np.square(t_w - np.round(curr_W)))))exit(0)
運行一下,仍然把頭尾的部分記錄下來,中間部分太多就省略掉:
$ python ./test1.py [[ 842. 453. 586. 919. 91.] [ 867. 600. 156. 993. 558.] [ 795. 809. 146. 793. 118.] [ 202. 184. 125. 132. 450.] [ 214. 36. 436. 118. 290.] [ 207. 916. 757. 647. 670.] [ 679. 176. 872. 522. 927.] [ 552. 602. 981. 563. 937.] [ 31. 519. 718. 226. 178.] [ 571. 464. 289. 141. 769.]][[ 42.] [ 465.] [ 890.] [ 84.] [ 488.]][[ 889153.] [ 809970.] [ 663711.] [ 435982.] [ 565200.] [ 1489672.] [ 1382662.] [ 1680752.] [ 987505.] [ 884068.]]運行 1 次,loss=3.30516e 13運行 2 次,loss=1.02875e 14運行 3 次,loss=3.22531e 14運行 4 次,loss=1.01237e 15運行 5 次,loss=3.17825e 15運行 6 次,loss=9.97822e 15運行 7 次,loss=3.13272e 16運行 8 次,loss=9.83534e 16運行 9 次,loss=3.08786e 17運行 10 次,loss=9.69452e 17運行 11 次,loss=3.04365e 18運行 12 次,loss=9.55571e 18運行 13 次,loss=3.00007e 19運行 14 次,loss=9.41889e 19運行 15 次,loss=2.95712e 20...運行 2821 次,loss=6839.32運行 2822 次,loss=6780.68運行 2823 次,loss=6767.86運行 2824 次,loss=6735.09運行 2825 次,loss=6709.06運行 2826 次,loss=6662.66運行 2827 次,loss=6637.81運行 2828 次,loss=6637.81t_w: [[ 117.] [ 642.] [ 662.] [ 318.] [ 771.]]w: [[ 117.0872879 ] [ 641.80706787] [ 662.05078125] [ 318.10388184] [ 771.01501465]]fix_w: [[ 117.] [ 642.] [ 662.] [ 318.] [ 771.]]loss: 6637.81fix_loss:0.0
可見,這次在執行了 2828 次之後,loss 函數從 3.30516e 13 降低到 6637.81 後不再變動,看起來有點大,但是實際上我們的 y 值也是非常大的,最後求得的結果與實際值有大約不到千分之一的差距,要縮小這個差距,可以通過減少梯度下降學習速率,同時增加訓練次數來解決,而 fix_w 的值已經等於 t_w 的值了。 目前這個代碼也可以修改一下訓練集的數量以及變數的數量,然後通過調梯度下降學習速率參數來進行訓練,如果學習速率過大,可能就會得到 loss 函數為 inf 值,這樣就無法得到結果了,具體原因我還得繼續研究一下。
還能做什麼呢?
能夠解決這樣的問題,基本上就能夠初步使用機器學習的思維來解決問題了,比如預測股票下個交易日漲跌幅,參數可以是昨日開盤價,昨日收盤價,昨日漲跌幅,昨日成交量等。不過先別激動,股票的模型也不是簡單的線性模型,如果想建立股票預測模型,還需要使用更加複雜的方法才行,有興趣的讀者可以繼續深入研究,比如使用多元多次方程來進行數據的擬合,只要建立起這個思想,這篇文章的目的就達到了。
參考資料
- 機器學習入門:線性回歸及梯度下降
- Numpy Quickstart tutorial
- TensorFlow Getting Startted
- 官網系列教程翻譯:
- 一文入門谷歌深度學習框架 Tensorflow
- 【Tensorflow r1.0 文檔翻譯】入門教程
相關閱讀
機器學習庫初探之 TensorFlow
實習生的監控演算法: 利用機器學習方法進行曲線分類
深度學習基礎概念筆記
推薦閱讀:
※「命理「周易」的系統,是一種語言還是一種程序呢,在未來可否會被人工智慧代替?
※CES Asia旅遊和科技行業的全新跨界時代:聚焦人工智慧和大數據
※大家來預測一下吳恩達三個新項目之一deeplearning.ai之後,接下來的兩個項目將會是什麼項目?
※管住嘴、邁開腿,智能手環教你動起來
※以倒立擺為例學習自動控制(一):傳遞函數模型
TAG:TensorFlow | 机器学习 | 人工智能 |