TensorFlow從0到1 - 5 - TensorFlow輕松搞定線性回歸

TensorFlow從0到1系列回顧

上一篇 第一個(gè)機(jī)器學(xué)習(xí)問題 其實(shí)是一個(gè)線性回歸問題(Linear Regression),呈現(xiàn)了用數(shù)據(jù)來(lái)訓(xùn)練模型的具體方式。本篇從平行世界返回,利用TensorFlow,重新解決一遍該問題。

TensorFlow的API有低級(jí)和高級(jí)之分。

底層的API基于TensorFlow內(nèi)核,它主要用于研究或需要對(duì)模型進(jìn)行完全控制的場(chǎng)合。如果你想使用TF來(lái)輔助實(shí)現(xiàn)某個(gè)特定算法、呈現(xiàn)和控制算法的每個(gè)細(xì)節(jié),那么就該使用低級(jí)的API。

高級(jí)API基于TensorFlow內(nèi)核構(gòu)建,屏蔽了繁雜的細(xì)節(jié),適合大多數(shù)場(chǎng)景下使用。如果你有一個(gè)想法要驗(yàn)證并快速獲得結(jié)果,那么TF的高級(jí)API就是高效的構(gòu)建工具。

本篇使用TF的低級(jí)API來(lái)呈現(xiàn)線性回歸的每一個(gè)步驟。

線性回歸

第一個(gè)機(jī)器學(xué)習(xí)的TF實(shí)現(xiàn)

TensorFlow的計(jì)算分為兩個(gè)階段:

  • 構(gòu)建計(jì)算圖;
  • 執(zhí)行計(jì)算圖。

先給出“平行世界”版本,(a, b)初始值為(-1, 50),第二次嘗試(-1, 40)。

import tensorflow as tf

# model parameters
a = tf.Variable([-1.], tf.float32)
b = tf.Variable([50.], tf.float32)

# model input and output
x = tf.placeholder(tf.float32)
linear_model = a * x + b
y = tf.placeholder(tf.float32)

# loss
loss = tf.reduce_sum(tf.square(linear_model - y)) / 8

# training data
x_train = [22, 25, 28, 30]
y_train = [18, 15, 12, 10]

# training loop
init = tf.global_variables_initializer()
sess = tf.Session()
sess.run(init)  # 1st

print("loss: %s" % (sess.run(loss, {x: x_train, y: y_train})))

# 2nd
fixa = tf.assign(a, [-1.])
fixb = tf.assign(b, [40.])
sess.run([fixa, fixb])

print("loss: %s" % (sess.run(loss, {x: x_train, y: y_train})))

程序輸出

loss: 50.0
loss: 0.0

下載 tf_5_manual.py

上面的python代碼利用了在2 TensorFlow內(nèi)核基礎(chǔ) 介紹的基本API實(shí)現(xiàn)了“第一個(gè)機(jī)器學(xué)習(xí)問題”。代碼通過一步步構(gòu)造計(jì)算圖,最后得到了loss節(jié)點(diǎn)。loss即4 第一個(gè)機(jī)器學(xué)習(xí)問題中定義過的損失函數(shù),這里再次給出其定義:

B-P-F-1 損失函數(shù)

構(gòu)建好計(jì)算圖,接下來(lái)開始執(zhí)行。執(zhí)行l(wèi)oss節(jié)點(diǎn)(同時(shí)提供基于tf.placeholder的訓(xùn)練數(shù)據(jù)),得到loss的值為50。然后開始第二次訓(xùn)練,修改基于tf.Variable的a和b的值,再次執(zhí)行l(wèi)oss節(jié)點(diǎn),loss的值為0,降到了最低。此時(shí)的a和b就是最佳的模型參數(shù)了。

還記得那個(gè)神秘力量嗎?到底是什么讓機(jī)器在第二次訓(xùn)練中將模型參數(shù)(a, b)的值從初始的隨機(jī)值(-1, 50)遷移到最優(yōu)的(-1, 40)?如果不靠運(yùn)氣的話,機(jī)器如何能自動(dòng)的找到最優(yōu)解呢?

梯度下降算法

在此之前,或許你已經(jīng)想到了隨機(jī)窮舉的辦法,因?yàn)闄C(jī)器不怕累。這的確是個(gè)辦法,但面臨的挑戰(zhàn)也不可接受:不可控。因?yàn)榧幢闶侵挥?個(gè)參數(shù)的模型訓(xùn)練,其枚舉域也是無(wú)限大的,這和靠運(yùn)氣沒有分別。運(yùn)氣差的話,等個(gè)幾百年也說不定。

不繞圈子,那個(gè)神秘力量就是:梯度下降算法(gradient descent)。雖然它也是讓機(jī)器一小步一小步的去嘗試不同的(a, b)的組合,但是它能指導(dǎo)每次前進(jìn)的方向,使得每嘗試一組新的值,loss就能變小一點(diǎn)點(diǎn),直到趨于穩(wěn)定。

而這一切TF已經(jīng)把它封裝好了。 本篇先把它當(dāng)個(gè)黑盒子使用。

tf.train API

import tensorflow as tf

# model parameters
a = tf.Variable([-1.], tf.float32)
b = tf.Variable([50.], tf.float32)

# model input and output
x = tf.placeholder(tf.float32)
linear_model = a * x + b
y = tf.placeholder(tf.float32)

# loss
loss = tf.reduce_sum(tf.square(linear_model - y)) / 8   # sum of the squares

# training data
x_train = [22, 25, 28, 30]
y_train = [18, 15, 12, 10]

# optimizer
optimizer = tf.train.GradientDescentOptimizer(0.01)
train = optimizer.minimize(loss)

# training loop
init = tf.global_variables_initializer()
sess = tf.Session()
sess.run(init)
for i in range(1000):
    sess.run(train, {x: x_train, y: y_train})

# evaluate training accuracy
curr_a, curr_b, curr_loss = sess.run([a, b, loss], {x: x_train, y: y_train})
print("a: %s b: %s loss: %s" % (curr_a, curr_b, curr_loss))

代碼幾乎和TensorFlow Get Started官方代碼一致,主要區(qū)別在于訓(xùn)練數(shù)據(jù)不同,以及初始值不同。

  • TF官方的訓(xùn)練數(shù)據(jù)是x_train = [1, 2, 3, 4],y_train = [0, -1, -2, -3],而我們的訓(xùn)練數(shù)據(jù)是“平行世界”的觀察記錄x_train = [22, 25, 28, 30],y_train = [18, 15, 12, 10]。
  • TF官方的(a, b)初始值是(.3, -.3), 我們的是(-1., 50.)。
  • 或許你還發(fā)現(xiàn)在官方版本的loss函數(shù)末尾沒有/ 8,是因?yàn)槲沂褂镁讲畹木壒剩?由4x2得到(4個(gè)訓(xùn)練數(shù)據(jù))。

重點(diǎn)說下tf.train API。tf.train.GradientDescentOptimizer即封裝了梯度下降算法。梯度下降在數(shù)學(xué)上屬于最優(yōu)化領(lǐng)域,從其名字Optimizater也可體現(xiàn)出。其參數(shù)就是“學(xué)習(xí)率”(learning rate),先記住這個(gè)名詞,暫不展開,其基本的效用是決定待調(diào)整參數(shù)的調(diào)整幅度。學(xué)習(xí)率越大,調(diào)整幅度越大,學(xué)習(xí)的越快。反之亦然。可也并不是越大越好,是相對(duì)來(lái)說的。先取0.01。

另一個(gè)需要輸入給梯度下降算法的就是loss,它是求最優(yōu)化解的主體,通過optimizer.minimize(loss)傳入,并返回train節(jié)點(diǎn)。接下來(lái)在循環(huán)中執(zhí)行train節(jié)點(diǎn)即可,循環(huán)的次數(shù),即訓(xùn)練的步數(shù)。

執(zhí)行計(jì)算圖,程序輸出:

a: [ nan] b: [-inf] loss: nan

這個(gè)結(jié)果令人崩潰,僅僅換了下TF官方get started中例子中模型的訓(xùn)練數(shù)據(jù)和初始值,它就不工作了。

先來(lái)看看問題在哪。一個(gè)調(diào)試的小技巧就是打印每次訓(xùn)練的情況,并調(diào)整loop的次數(shù)。

for i in range(49):
    sess.run(train, {x: x_train, y: y_train})
    curr_a, curr_b, curr_loss = sess.run([a, b, loss], {x: x_train, y: y_train})
    print("a: %s b: %s loss: %s" % (curr_a, curr_b, curr_loss))

程序輸出:

overflow

TF實(shí)際是工作的,并沒有撂挑子。只是它訓(xùn)練時(shí)每次調(diào)整(a, b)都幅度很大,接下來(lái)又矯枉過正且幅度越來(lái)越大,導(dǎo)致最終承載a和b的tf.float32溢出而產(chǎn)生了nan。這不是TF的一個(gè)bug,而是算法本身、訓(xùn)練數(shù)據(jù)、學(xué)習(xí)率、訓(xùn)練次數(shù)共同導(dǎo)致的(它們有個(gè)共同的名字:超參數(shù)。)。可見,訓(xùn)練是一門藝術(shù)

直覺上,初始值或許有優(yōu)劣之分,或許是離最優(yōu)值越近的初始值越容易找到。可是訓(xùn)練數(shù)據(jù)則應(yīng)該是無(wú)差別的吧?實(shí)則不然。但是現(xiàn)在我還不打算把它解釋清楚,等后面分析完梯度下降算法后再回來(lái)看這個(gè)問題。

遇到該問題的也不再少數(shù),Stack Overflow上已經(jīng)很好的回答了。我們先通過調(diào)整學(xué)習(xí)率和訓(xùn)練次數(shù)來(lái)得到一個(gè)完美的Ending。

把學(xué)習(xí)率從0.01調(diào)制0.0028,然后將訓(xùn)練次數(shù)從1000調(diào)整至70000。

程序輸出:

a: [-1.02855277] b: [ 40.75948715] loss: 0.00379487

最終代碼如下:

import tensorflow as tf

# model parameters
a = tf.Variable([-1.], tf.float32)
b = tf.Variable([50.], tf.float32)

# model input and output
x = tf.placeholder(tf.float32)
linear_model = a * x + b
y = tf.placeholder(tf.float32)

# loss
loss = tf.reduce_sum(tf.square(linear_model - y)) / 8   # sum of the squares

# training data
x_train = [22, 25, 28, 30]
y_train = [18, 15, 12, 10]

# optimizer
optimizer = tf.train.GradientDescentOptimizer(0.0028)
train = optimizer.minimize(loss)

# training loop
init = tf.global_variables_initializer()
sess = tf.Session()
sess.run(init)
for i in range(70000):
    sess.run(train, {x: x_train, y: y_train})

# evaluate training accuracy
curr_a, curr_b, curr_loss = sess.run([a, b, loss], {x: x_train, y: y_train})
print("a: %s b: %s loss: %s" % (curr_a, curr_b, curr_loss))

下載 tf_5_tf.train.py

TensorBoard

TF的另一個(gè)強(qiáng)大之處就是可視化算法的TensorBoard,把構(gòu)造的計(jì)算圖顯示出來(lái)。圖中顯示,每一個(gè)基本運(yùn)算都被獨(dú)立成了一個(gè)節(jié)點(diǎn)。除了圖中我標(biāo)注的Rank節(jié)點(diǎn)、range節(jié)點(diǎn),start節(jié)點(diǎn)、delta節(jié)點(diǎn)外,其他節(jié)點(diǎn)都是由所寫代碼構(gòu)建出來(lái)的。

TensorBoard

詞匯表

  • derivative; 導(dǎo)數(shù);
  • estimator: 估計(jì);
  • gradient descent: 梯度下降;
  • inference: 推理;
  • linear regression:線性回歸;
  • loss function: 損失函數(shù);
  • magnitude: 量;
  • optimal: 最優(yōu)的;
  • optimizers: 優(yōu)化器;

上一篇 4 第一個(gè)機(jī)器學(xué)習(xí)問題
下一篇 6 解鎖梯度下降算法


共享協(xié)議:署名-非商業(yè)性使用-禁止演繹(CC BY-NC-ND 3.0 CN)
轉(zhuǎn)載請(qǐng)注明:作者黑猿大叔(簡(jiǎn)書)

最后編輯于
?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請(qǐng)聯(lián)系作者
平臺(tái)聲明:文章內(nèi)容(如有圖片或視頻亦包括在內(nèi))由作者上傳并發(fā)布,文章內(nèi)容僅代表作者本人觀點(diǎn),簡(jiǎn)書系信息發(fā)布平臺(tái),僅提供信息存儲(chǔ)服務(wù)。

推薦閱讀更多精彩內(nèi)容