1. 程式人生 > >【120】TensorFlow 從CSV檔案中讀取資料並訓練線性迴歸模型(面向新手)

【120】TensorFlow 從CSV檔案中讀取資料並訓練線性迴歸模型(面向新手)

正文開始。

學習 TensorFlow 讓我的思維發生了變化。

計算機本質上是一種數學的工具,而我在學習程式設計的時候,思維也不可避免地收到了影響。傳統的程式設計思想,常常認為程式就應該像數學定理或者數學函式一樣,給出一個確定的結果。這是一種基於邏輯推導的思維習慣。

然而,做實驗的科學家們的思維卻不像數學家一樣。實驗科學家通過做實驗收集資料,再根據資料推測其中蘊含的某種規律。

這篇文章以胡克定律為例子,向新手介紹如何使用TF。胡克定律是由物理學家胡克發現。簡單地說,這個定律指彈簧長度的增量和彈簧受力大小呈正比關係。(注:這並不是胡克定律的嚴格定義,只是為了方便讀者理解而作的簡化說明。這個說法更像中學物理對胡克定律的說明。)

現在假設你是一個實驗物理學家,要探索彈簧長度增量和受力大小之間的關係。你對一個彈簧施加了不同的力,並記錄下了彈簧的增量(資料都是我瞎編的),儲存到一個CSV檔案中。檔名是 HookeLaw.csv

HookeLaw.csv檔案內容

"force","length_variation"
8.0,3.9
4.3,2.2
5.7,3
7.2,4.1
10,5.2
21,9.0
14.5,7.1
11.3,5.8
12.3,6.1
21.3,10.5
18.2,9.8
21.3,10.8
21.5,10
19.1,9.8
32.3,15.9
28.3,14.0
33.3,16.2
30.3,15.5
31.7
,17.1 29.9,14.6 30.5,14.9 34.5,17 9.9,10 35.5,18 29.9,15.0

這個樣是看不出什麼有價值的資訊。你需要畫出一個圖表來進行判斷。如果是在胡克那個時代,只能用手畫出圖表。現在只需用Python就可以了。我把 HookeLaw.csv 檔案放到了 nginx 伺服器上,方便 Python 讀取。

import tensorflow as tf
import numpy as np
import matplotlib.pyplot as plt
import pandas as pd

# 從CSV檔案中讀取資料,並返回2個數組。分別是自變數x和因變數y。方便TF計算模型。
def zc_read_csv(): zc_dataframe = pd.read_csv("http://yoursize.com/HookeLaw.csv", sep=",") x = [] y = [] for zc_index in zc_dataframe.index: zc_row = zc_dataframe.loc[zc_index] x.append(zc_row["force"]) y.append(zc_row["length_variation"]) return (x,y) x, y = zc_read_csv() # 獲得畫圖物件。 fig = plt.figure() fig.set_size_inches(10, 4) # 整個繪圖區域的寬度10和高度4 ax = fig.add_subplot(1, 2, 1) # 整個繪圖區分成一行兩列,當前圖是第一個。 # 畫出原始資料的散點圖。 ax.set_title("Hooke's Law") ax.set_xlabel("force") ax.set_ylabel("length_variation") ax.scatter(x, y) plt.show()

程式執行結果:

120_2.png

排除掉明顯的誤差資料,散點圖看上去是一條直線。設彈簧長度增量是 y,受力是x,有理由推測 y = kx + b。在沒有計算機的時代,需要手動畫一條直線,並保持直線和各個點之間的距離最小,最後計算直線的 k 和 b。

因為有了計算機,我們可以用計算機來完成這些繁瑣的工作。利用線性迴歸,讓程式一點一點找到合適的b值和k值。在機器學習裡面b和k叫做權重,用 w0 和 w1 表示。公式可以寫成 y = w0 + w1x 。

下面的程式中,zc_x4tf 相當於是一個矩陣,在TF會話中是input:

3.png

zc_y4tf 相當於是一個列向量,在TF會話中是weights:

4.png

yhat = tf.matmul(input, weights) 可以理解成矩陣乘法,結果是預測值。

5.png

L2損失用於衡量每個實際值和預測值之間的偏差。對應概率論中的方差。

下面的程式給出了一個完整的實現。

import tensorflow as tf
import numpy as np
import matplotlib.pyplot as plt
import pandas as pd

# 從CSV檔案中讀取資料,並返回2個數組。分別是自變數x和因變數y。方便TF計算模型。
def zc_read_csv():
    zc_dataframe = pd.read_csv("http://yoursize.com/HookeLaw.csv", sep=",")
    x = []
    y = []
    for zc_index in zc_dataframe.index:
        zc_row = zc_dataframe.loc[zc_index]
        x.append(zc_row["force"])
        y.append(zc_row["length_variation"])
    return (x,y)

x, y = zc_read_csv()

zc_x = []
for item in x:
    zc_x.append([1., item])
zc_x4tf = np.array(zc_x).astype(np.float32)

zc_y = []
for item in y:
    zc_y.append([item])
zc_y4tf = np.array(zc_y).astype(np.float32)

# 存放 L2 損失的陣列
loss_arr = []
# 訓練的步數。即訓練的迭代次數。
training_steps = 55
# 在梯度下降演算法中,控制梯度步長的大小。
learning_rate = 0.01

# 開啟TF會話,在TF 會話的上下文中進行 TF 的操作。
with tf.Session() as sess:
    # 設定 tf 張量(tensor)。注意:TF會話中的註釋裡面提到的常量和變數是針對TF設定而言,不是python語法。

    # 因為在TF運算過程中,x作為特徵值,y作為標籤
    # 是不會改變的,所以分別設定成input 和 target 兩個常量。
    # 這是 x 取值的張量。設一共有m條資料,可以把input理解成是一個m行2列的矩陣。矩陣第一列都是1,第二列是x取值。
    input = tf.constant(zc_x4tf)
    # 設定 y 取值的張量。target可以被理解成是一個m行1列的矩陣。 有些文章稱target為標籤。
    target = tf.constant(zc_y4tf)

    # 設定權重變數。因為在每次訓練中,都要改變權重,來尋找L2損失最小的權重,所以權重是變數。
    # 可以把權重理解成一個2行1列的矩陣。初始值是隨機的。[2,1] 表示2行1列。
    weights = tf.Variable(tf.random_normal([2, 1], 0, 0.1))

    # 初始化上面所有的 TF 常量和變數。
    tf.global_variables_initializer().run()
    # input 作為特徵值和權重做矩陣乘法。m行2列矩陣乘以2行1列矩陣,得到m行1列矩陣。
    # yhat是新矩陣,yhat中的每個數 yhat' = w0 * 1 + w1 * x。 
    # yhat是預測值,隨著每次TF調整權重,yhat都會變化。
    yhat = tf.matmul(input, weights)
    # tf.subtract計算兩個張量相減,當然兩個張量必須形狀一樣。 即 yhat - target。
    yerror = tf.subtract(yhat, target)
    # 計算L2損失,也就是方差。
    loss = tf.nn.l2_loss(yerror)
    # 梯度下降演算法。
    zc_optimizer = tf.train.GradientDescentOptimizer(learning_rate)
    # 注意:為了安全起見,我們還會通過 clip_gradients_by_norm 將梯度裁剪應用到我們的優化器。
    # 梯度裁剪可確保梯度大小在訓練期間不會變得過大,梯度過大會導致梯度下降法失敗。
    zc_optimizer = tf.contrib.estimator.clip_gradients_by_norm(zc_optimizer, 5.0)
    zc_optimizer = zc_optimizer.minimize(loss)
    for _ in range(training_steps):
        # 重複執行梯度下降演算法,更新權重數值,找到最合適的權重數值。
        sess.run(zc_optimizer)
#         print(weights.eval())
        # 每次迴圈都記錄下損失loss的值,病放到陣列loss_arr中。
        loss_arr.append(loss.eval())
    zc_weight_arr = weights.eval()
    zc_yhat = yhat.eval()

print("weights", zc_weight_arr)


# 畫出原始資料的散點圖和數學模型的直線圖。
def paint_module(fig):
    ax = fig.add_subplot(1, 2, 1)  # 整個繪圖區分成一行兩列,當前圖是第一個。
    # 畫出原始資料的散點圖。
    ax.set_title("Hooke's Law")
    ax.set_xlabel("force")
    ax.set_ylabel("length_variation")
    ax.scatter(x, y)
    # 畫出預測值的散點圖。
    p_yhat = [a[0] for a in zc_yhat]
    ax.scatter(x, p_yhat, c="red", alpha=.6)
    # 畫出線性迴歸計算出的直線模型。
    line_x_arr = [1, 40]
    line_y_arr = []
    for item in line_x_arr:
        line_y_arr.append(zc_weight_arr[0] + zc_weight_arr[1] * item)
    ax.plot(line_x_arr, line_y_arr, "g", alpha=0.6)

# 畫出訓練過程中的損失變化
def paint_loss(fig):
    print("loss", loss_arr)
    ax = fig.add_subplot(1, 2, 2)  # 整個繪圖區分成一行兩列,當前圖是第二個。
    ax.plot(range(0, training_steps), loss_arr)


# 獲得畫圖物件。
fig = plt.figure()
fig.set_size_inches(10, 4)   # 整個繪圖區域的寬度10和高度4
paint_module(fig)
paint_loss(fig)

plt.show()

1.png

有結果可知 y = 0.5x + 0.2 變形得 x = 2y - 0.4
而胡克定律公式是 F = kL (F是力,k是彈性係數,L是彈簧長度增量)。可以把常數項 -0.4 看作系統誤差,則彈簧彈性係數是2 。