使用Tensorflow訓練一元線性模型

個人原文:www.hijerry.cn/p/26959.htm…html

引言

這是一次使用python進行機器學習的實驗。python

一是總結本身學過的各類python包,二是瞭解一下使用python進行機器學習大概是什麼樣的,不過此次使用主要的目的仍是熟悉Tensorflow的使用。git

本次實驗使用到的python包及其版本:程序員

  • Tensorflow 1.8.0
  • Numpy 1.14.3
  • Pandas 0.22.0
  • Matplotlib 2.2.2

機器環境是:macOS 10.13github

Tensoflow基礎知識

這裏只介紹本次實驗所用到的Tensorflow的關鍵知識、概念,想了解詳情能夠參考官方文檔:https://tensorflow.google.cn/programmers_guide/low_level_intro 。web

張量

TensorFlow 中的核心數據單位是張量。一個張量由一組造成陣列(任意維數)的原始值組成。張量的是它的維數,而它的形狀是一個整數元組,指定了陣列每一個維度的長度。如下是張量值的一些示例:shell

3. # 0階張量;也叫標量;形狀是[]
[1., 2., 3.] # 1階張量;也叫向量;形狀是[3]
[[1., 2., 3.], [4., 5., 6.]] # 2階張量;也叫矩陣;形狀是[2, 3]
[[[1., 2., 3.]], [[7., 8., 9.]]] # 3階張量;形狀是[2, 1, 3]
複製代碼

TensorFlow 使用 numpy 陣列來表示張量編程

您能夠將 TensorFlow Core 程序看做由兩個互相獨立的部分組成:api

  1. 構建計算圖 (tf.Graph)。
  2. 運行計算圖 (tf.Session)。

計算圖是排列成一個圖的一系列 TensorFlow 指令。圖由兩種類型的對象組成。數組

  • 指令(或「op"):圖的節點。 指令說明的是消耗和生成張量的計算。
  • 張量:圖的邊。它們表明將流經圖的值。大多數 TensorFlow 函數會返回 tf.Tensors

重要提示tf.Tensors 不具備值,它們只是計算圖中元素的手柄。

咱們來構建一個簡單的計算圖。最基本的指令是一個常量。構建指令的 Python 函數將一個張量值做爲輸入值。生成的指令不須要輸入值。它在運行時輸出的是被傳遞給構造函數的值。咱們能夠建立以下所示的兩個浮點數常量 ab

a = tf.constant(3.0, dtype=tf.float32)
b = tf.constant(4.0) # 默認dtype=tf.float32
total = a + b
print(a)
print(b)
print(total)
複製代碼

打印語句會生成:

Tensor("Const:0", shape=(), dtype=float32)
Tensor("Const_1:0", shape=(), dtype=float32)
Tensor("add:0", shape=(), dtype=float32)
複製代碼

請注意,打印張量並不會如您可能預期的那樣輸出值 3.04.07.0。上述語句只會構建計算圖。這些 tf.Tensor 對象僅表明將要運行的指令的結果。

圖中的每一個指令都擁有惟一的名稱。這個名稱不一樣於使用 Python 分配給相應對象的名稱。張量是根據生成它們的指令命名的,後面跟着輸出索引,如上文的 "add:0" 所示。

會話

要評估張量,您須要實例化一個 tf.Session 對象(一般被稱爲會話)。會話會封裝 TensorFlow 運行時的狀態,並運行 TensorFlow 指令。若是說 tf.Graph 像一個 .py 文件,那麼 tf.Session 就像一個可執行的 python

下面的代碼會建立一個 tf.Session 對象,而後調用其 run 方法來評估咱們在上文中建立的 total 張量:

sess = tf.Session()
print(sess.run(total))
複製代碼

當您使用 Session.run 請求輸出節點時,TensorFlow 會回溯整個圖,並流經提供了所請求的輸出節點對應的輸入值的全部節點。所以此指令會打印預期的值 7.0:

7.0
複製代碼

您能夠將多個張量傳遞給 tf.Session.runrun 方法以透明方式處理元組或字典的任何組合,以下例所示:

print(sess.run({'ab':(a, b), 'total':total}))
複製代碼

它返回的結果擁有相同的佈局結構:

{'total': 7.0, 'ab': (3.0, 4.0)}
複製代碼

在調用 tf.Session.run 期間,任何 tf.Tensor 都只有單個值。例如,如下代碼調用 tf.random_uniform 來生成一個 tf.Tensor,後者會生成隨機的三元素矢量(值位於 [0,1)):

vec = tf.random_uniform(shape=(3,))
out1 = vec + 1
out2 = vec + 2
print(sess.run(vec))
print(sess.run(vec))
print(sess.run((out1, out2)))
複製代碼

每次調用 run 時,結果都會顯示不一樣的隨機值,但在單個 run 期間(out1out2 接收到相同的隨機輸入值),結果顯示的值是一致的:

[ 0.52917576  0.64076328  0.68353939]
[ 0.66192627  0.89126778  0.06254101]
(
  array([ 1.88408756,  1.87149239,  1.84057522], dtype=float32),
  array([ 2.88408756,  2.87149239,  2.84057522], dtype=float32)
)
複製代碼

部分 TensorFlow 函數會返回 tf.Operations,而不是 tf.Tensors。對指令調用 run 的結果是 None。您運行指令是爲了產生反作用,而不是爲了檢索一個值。這方面的例子包括稍後將演示的[初始化](https://tensorflow.google.cn/programmers_guide/low_level_intro#Initializing Layers)和訓練指令。

佔位符

目前來說,這個圖不是特別有趣,由於它老是生成一個常量結果。圖能夠參數化以便接受外部輸入,也稱爲佔位符佔位符表示承諾在稍後提供值,它就像函數參數。

x = tf.placeholder(tf.float32)
y = tf.placeholder(tf.float32)
z = x + y
複製代碼

前面三行有點像函數。咱們定義了這個函數的兩個輸入參數(xy),而後對它們運行指令。咱們可使用 run 方法feed_dict 參數來爲佔位符提供真正的值,從而經過多個輸入值來評估這個圖:

print(sess.run(z, feed_dict={x: 3, y: 4.5}))
print(sess.run(z, feed_dict={x: [1, 3], y: [2, 4]}))
複製代碼

上述操做的結果是輸出如下內容:

7.5
[ 3.  7.]
複製代碼

另請注意,feed_dict 參數可用於覆蓋圖中的任何張量。佔位符和其餘 tf.Tensors 的惟一不一樣之處在於若是沒有提供值給它們,那麼佔位符會顯示錯誤。

Estimator

它是一種可極大地簡化機器學習編程的高階 TensorFlow API。Estimator 會封裝下列操做:

  • 訓練
  • 評估
  • 預測
  • 導出以供使用

爲了更好的理解它是個啥,請看後面的 Premade EstimatorCustom Estimator 章節。

理論基礎

直接看線性模型的目標是什麼吧,如下圖爲例:

模型的目標是找到一條直線(圖紅色直線),讓每個藍色點到與直線的y距離最小。

下面來更數學化一點的介紹:

給定一個大小爲n的點集 S = \{ (x_1,y_1), (x_2,y_2), … (x_n,y_n)\}

線性模型的目標就是尋找一組 Wb 構成的直線 y = Wx + b

使得全部點的損失值 loss = \sum_i^n (Wx_i + b - y_i) ^2 越小越好。

由於若是咱們找到了這麼一組 Wb ,咱們就能夠預測某一個 x_my_m 值。

這裏我想多說幾句,線性模型在實際應用中不必定能很好的預測 y_m 的值,這是由於實際的數據分佈也許不是線性的,多是二次、三次、圓形甚至無規則,因此判斷何時能用線性模型很重要。一個比較好的實踐方法是,先用matplotlib畫出數據分佈,觀察一下看看,就比如上面的藍色點,一看就知道是線性分佈,因此能夠用線性模型來作。可是這種方法在大部分狀況下也不能用,由於數據不少狀況下有多個特徵(多元),一元、二元都還好,能看出來,到了三元、四元數據,可能連圖都畫不出來。。這時候又怎麼辦呢?也很簡單,用線性模型和其餘模型一塊兒套一下,評估、對比看看結果如何。

那麼如今問題是,怎麼讓 loss 最小呢?請接着往下看。

基礎版本

模型

廢話很少說,直接上寫好的代碼:

def fit_linear_model(data, num_steps, alpha):
    """ train with the machine learning :param data: training data :param num_steps: training steps :param alpha: learning rate :return: W and b of trained linear model """
    # variables
    W = tf.Variable(1, dtype=tf.float64)
    b = tf.Variable(1, dtype=tf.float64)
    x = tf.placeholder(tf.float64)
    y = tf.placeholder(tf.float64)

    # predict
    pred = W * x + b

    # loss
    loss = tf.reduce_sum(tf.square(pred - y))

    # optimizer
    optimizer = tf.train.GradientDescentOptimizer(alpha)

    # train
    train = optimizer.minimize(loss)

    train_set, test_set = split_test_set(data, frac=0.3, random=True)

    sess = tf.Session()
    sess.run(tf.global_variables_initializer())
    for i in range(num_steps):
        sess.run(train, {x: train_set['x'], y: train_set['y']})

    final_W, final_b = sess.run([W, b], {x: train_set['x'], y: train_set['y']})

    # evaluate
    final_loss, evaluate_loss = evaluate(train_set, test_set, final_W, final_b)

    print('W: {}, b: {}, final loss: {}, evaluate loss: {}'.format(final_W, final_b, final_loss, evaluate_loss))

    return final_W, final_b
複製代碼

下面一步一步講解代碼:

  • 11~14行:定義須要用到的張量,其中 Wb 是變量,而且都給了初始值 1x, y 是佔位符用於接收數據。
  • 17行:計算預測值
  • 20行:計算損失值 loss
  • 2三、26行:使用 GradientDescentOptimizer 來優化模型,減少loss ,這個類的原理是梯度降低,能夠看到咱們傳遞了學習速率 alphaα 。能夠好奇的是,咱們沒有計算梯度,而是調用了minimize 方法,這個方法分兩步進行,第一步是使用 compute_gradients 計算梯度,第二步是使用 apply_gradients 更新參數值,憑藉經驗能夠知道,第一步其實就是在計算偏導數,那麼tensorflow是怎麼作到,能夠計算任意元線性模型的偏導數的呢,我大概掃描了一下源碼,猜想應該是用了計算圖
  • 28行:使用自定義方法 split_test_set 將數據集劃分爲 訓練集:測試集= 7:3,即有30%的數據用做測試集。
  • 30~33行:訓練模型。我比較好奇 global_variables_initializer 是個神魔戀,幾乎tensorflow應用都要執行這個東西,看看文檔就知道它會建立初始化程序時圖中就存在的變量好比代碼中的 Wb,其實就是 variables_initializer(global_variables()) 的縮寫。
  • 35行:獲取最終模型,即 Wb ,這裏須要轉變如下思惟, Wb 是張量(即圖中的一個節點),因此須要經過 run 方法獲取到。講道理應該能夠經過相似 get_variable 的方法拿到值,看了下貌似沒有這個方法。
  • 38行。使用自定義方法 evaluate 評估模型,計算模型在訓練集、測試集上的損失值。
  • 40行。打印訓練結果。
  • 42行。返回模型。

這是個封裝好的函數,只要傳入數據集、訓練步數、學習率就能夠獲得訓練好的模型了(即 Wb)。

須要提醒一下的是,第3三、35行的 {x: train_set['x'], y: train_set['y']} 不能把 key 寫成單引號的 {'x': train_set['x'], 'y': train_set['y']}

數據

那麼下面是須要拿到數據,這裏我用的是隨機生成的數據:

def linear_data(data_size, devi_degree):
    """ Make random linear data :param data_size: data size :param devi_degree: degree of deviation :return: linear data with x and y """
    # standard linear function
    x = np.array(range(data_size), dtype=np.float64)
    y = 3 * x + 0.6

    # make deviation
    y += np.random.randn(data_size) * devi_degree

    data = pd.DataFrame({'x': x, 'y': y})

    return data
複製代碼

作法是:

  • 先拿到大小爲data_size 的標準一元一次函數點集
  • 在此基礎上將每一個點在y軸方向上隨機偏移一段距離,偏離程度是 devi_degree

再來劃分訓練集和測試集:

def split_test_set(df, frac=0.3, random=True):
    """ Split DataFrame to train set and test set :param df: :param frac: :param random: :return: """
    test_size = int(len(df) * min(frac, 1))
    if random:
        df = df.sample(frac=1).reset_index(drop=True)
    return df[test_size:].reset_index(drop=True), df[:test_size].reset_index(drop=True)
複製代碼

評估

def evaluate(train_set, test_set, W, b):
    """ Evaluate the model's loss :param train_set: :param test_set: :param W: :param b: :return: train_loss, evaluate_loss """
    x = tf.placeholder(tf.float64)
    y = tf.placeholder(tf.float64)

    # predict
    pred = W * x + b

    # loss
    loss = tf.reduce_sum(tf.square(pred - y))

    sess = tf.Session()
    sess.run(tf.global_variables_initializer())

    train_loss = sess.run(loss, {x: train_set['x'], y: train_set['y']})
    evaluate_loss = sess.run(loss, {x: test_set['x'], y: test_set['y']})

    sess.close()

    return train_loss, evaluate_loss
複製代碼

這裏的loss評估是使用預測值-實際值平方再求和,與 理論基礎 章節描述的 loss 同樣。

繪圖

這部分就簡單了,把點畫一下,把直線畫一下就能夠了,注意直線最好和點不同的顏色,因此標紅。

def print_linear_model(data, W, b):
    """ print the data and the predictions of linear model :param data: :param W: W of linear model :param b: b of linear model """
    x = np.array(data['x'])
    y = np.array(data['y'])

    pred = np.array(W * x + b)

    plt.scatter(x, y, linewidths=1)
    plt.plot(x, pred, color='red')

    plt.show()
複製代碼

主程序

每一部分的函數寫完啦,如今要作的是將這幾個函數組合起來用,上代碼。

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

parser = argparse.ArgumentParser()
parser.add_argument('--data_size', default=50, type=int, help='data size')
parser.add_argument('--num_steps', default=1000, type=int, help='number of trannig steps')
parser.add_argument('--devi_degree', default=10, type=int, help='Degree of deviation in stand linear data')
parser.add_argument('--alpha', default=0.00001, type=float, help='learning rate of gradient decent')

def main(argv):
    args = parser.parse_args(argv[1:])

    data = linear_data(args.data_size, args.devi_degree)

    W, b = fit_linear_model(data, args.num_steps, args.alpha)

    print_linear_model(data, W, b)

if __name__ == '__main__':
    tf.logging.set_verbosity(tf.logging.INFO)
    tf.app.run(main)

複製代碼

這裏用到了 argparse 包來處理傳入的參數

把我上面給的全部代碼,都複製到tf-linear.py 文件裏,接着運行:

python tf-linear.py --data_size=100 --alpha=0.000001 --devi_degree=30 --num_steps=5000
複製代碼

便可看到以前那個圖啦。

這裏須要注意一下,alpha 參數不要設太大了。。否則會梯度爆炸。。程序員運行不出結果(Wb 都會變成 nan

Premade Estimator

模型

若是你看懂了基礎版本的代碼。。。那這部分就簡單的很了,仍是同樣,直接上代碼:

def fit_estimator(data, num_steps):
    """ train with estimator :param data: :param num_steps: :return: """
    feature_columns = [
        tf.feature_column.numeric_column('x')
    ]

    estimator = tf.estimator.LinearRegressor(feature_columns=feature_columns)

    train_set, test_set = split_test_set(data, frac=0.3, random=True)

    input_fn = tf.estimator.inputs.numpy_input_fn(
        {'x': train_set['x']}, train_set['y'], batch_size=4, num_epochs=None, shuffle=True
    )

    estimator.train(input_fn=input_fn, steps=num_steps)

    W = estimator.get_variable_value('linear/linear_model/x/weights')
    b = estimator.get_variable_value('linear/linear_model/bias_weights')
    final_W, final_b = float(W), float(b)

    final_loss, evaluate_loss = evaluate(train_set, test_set, final_W, final_b)

    print('W: {}, b: {}, final loss: {}, evaluate loss: {}'.format(final_W, final_b, final_loss, evaluate_loss))

    return final_W, final_b
複製代碼

仍是按步驟來解釋吧:

  • 9~11行:定義特徵列
  • 13行:建立 線性迴歸器 ,也就是 estimator
  • 15行:使用自定義方法 split_test_set 將數據集劃分爲 訓練集:測試集= 7:3,即有30%的數據用做測試集。
  • 17~21行:使用訓練集訓練模型,也就是訓練線性迴歸器
  • 23~24行:獲取最終模型,即 Wb 。能夠好奇的是,爲何 Wb 的獲取方式這麼奇葩。。。其實我是經過dir 命令才知道,能夠用這種方法獲取到參數,若是有其餘優雅的方法能夠告訴我哦。
  • 27行。使用自定義方法 evaluate 評估模型,計算模型在訓練集、測試集上的損失值。
  • 29行。打印訓練結果。
  • 31行。返回模型。

能夠看到,比我們本身手擼模型要簡單多了。首先,預測函數不用本身寫了,優化器也不用本身建立了,甚至連for-loop也不用了,直接調用 train 方法就萬事大吉了。

這裏須要注意一下,LinearRegressor 有個 weight_column 參數,若是不給的話,初始值是1,形狀是 (1,),能夠理解爲是 np.array([1.]),因此才能把他轉成 float ,若是大於1階,估計會轉換失敗。

主程序

數據、繪圖都不變,只須要修改 main 方法爲以下便可:

def main(argv):
    args = parser.parse_args(argv[1:])

    data = linear_data(args.data_size, args.devi_degree)

    # W, b = fit_linear_model(data, args.num_steps, args.alpha)

    W, b = fit_estimator(data, args.num_steps)

    print_linear_model(data, W, b)
複製代碼

接着仍是運行:

python tf-linear.py --data_size=100 --alpha=0.000001 --devi_degree=30 --num_steps=5000
複製代碼

而後能夠看到和以前那個圖差很少的樣子啦。

Custom Estimator

模型

依然先上代碼:

def fit_custom_estimator(data, num_steps, alpha):
    """ train with custom estimator :param data: :param num_steps: :param alpha: :return: """

    def model_fn(features, labels, mode):
        W = tf.get_variable('W', 1., dtype=tf.float64)
        b = tf.get_variable('b', 1., dtype=tf.float64)

        # predict
        pred = W * tf.cast(features['x'], dtype=tf.float64) + b

        # loss
        loss = tf.reduce_sum(tf.square(pred - labels))

        # optimizer
        optimizer = tf.train.GradientDescentOptimizer(alpha)

        # global step
        global_step = tf.train.get_global_step()

        # train
        train = tf.group(
            optimizer.minimize(loss),
            tf.assign_add(global_step, 1)
        )

        return tf.estimator.EstimatorSpec(
            mode=mode,
            predictions=pred,
            loss=loss,
            train_op=train
        )

    feature_columns = [
        tf.feature_column.numeric_column('x')
    ]

    estimator = tf.estimator.Estimator(
        model_fn=model_fn
    )

    train_set, test_set = split_test_set(data, frac=0.3, random=True)

    input_fn = tf.estimator.inputs.numpy_input_fn(
        {'x': train_set['x']}, train_set['y'], batch_size=4, num_epochs=None, shuffle=True
    )

    estimator.train(input_fn=input_fn, steps=num_steps)

    W = estimator.get_variable_value('W')
    b = estimator.get_variable_value('b')
    final_W, final_b = float(W), float(b)

    final_loss, evaluate_loss = evaluate(train_set, test_set, final_W, final_b)

    print('W: {}, b: {}, final loss: {}, evaluate loss: {}'.format(final_W, final_b, final_loss, evaluate_loss))

    return final_W, final_b
複製代碼

能夠分爲兩部分,第一部分是編寫 model_fn ,第二部分是調用自定義的estimator。

先看第一部分吧,也就是11行~38行。

  • 12~13行:獲取當前session中的 Wb 變量,若是不存在就設置他們爲 1
  • 16行:計算預測值。
  • 19行:計算損失值 loss
  • 22行:實例化 GradientDescentOptimizer 類,用於優化模型。
  • 25行:拿到 global_step 即當前步數(這個步數是全局的)。
  • 28~31行:定義訓練節點要作的操做,即 使用 GradientDescentOptimizer 優化模型,而且把 global_step 加一。group 方法表示把多個操做放在一個節點裏,這個方法沒有返回值。
  • 33~38行。關鍵行,實例化 Estimator 類,並把一些必要參數傳給它。

第二部分調用自定義的estimator大部分代碼和調用預約義的estimator同樣,就是在第44~46行,咱們把本身寫的 model_fn 做爲參數傳入到實例化的 Estimator 裏。

在咱們調用estimator的 train 方法時,tensorflow就會在內部調用咱們寫的 model_fn 方法來計算loss、prediction以及訓練模型。

model_fnmode 參數是啥?它其實有三個值分別是:ModeKeys.TRAIN、ModeKeys.EVAL、ModeKeys.PREDICT,用來指示本次調用的目的是訓練、評估仍是預測。不過在本次實驗中,咱們寫了本身的evaluate 方法,因此就不區分這三種狀況了。

主程序

def main(argv):
    args = parser.parse_args(argv[1:])

    data = linear_data(args.data_size, args.devi_degree)

    # W, b = fit_linear_model(data, args.num_steps, args.alpha)

    # W, b = fit_estimator(data, args.num_steps)
    
    W, b = fit_custom_estimator(data, args.num_steps, args.alpha)

    print_linear_model(data, W, b)
複製代碼

* 擴展閱讀

Tensorboard

在使用 Estimator 訓練模型的時候,tensorflow會自動記錄訓練過程當中某些數據的變化,好比 loss 值,拿premade estimator來講,在訓練完成後,控制會打印出如圖所示的一些信息:

能夠看到有一些數據被存到了:

/var/folders/4c/14xc6rkj1ndgw2x5kw5sw8hr0000gn/T/tmpo5gg328l/model.ckpt.
複製代碼

查看這個目錄發現裏面還有不少其餘文件

下面運行Tensorboard,來查看此次訓練中的數據變化:

tensorboard --logdir /var/folders/4c/14xc6rkj1ndgw2x5kw5sw8hr0000gn/T/tmpo5gg328l
複製代碼

這會啓動一個web服務,點擊終端提示的網址便可打開,個人是在 6006 端口。

源碼和參考資料

本次實驗的開源github地址:https://github.com/JerryCheese/tensorflow-study

參考的文檔:

[1] TensorFlow 完整的TensorFlow入門教程, https://blog.csdn.net/lengguoxing/article/details/78456279

[2] TensorFlow 使用入門, https://tensorflow.google.cn/get_started/premade_estimators

相關文章
相關標籤/搜索