1. 程式人生 > >TensorFlow-實戰Google深度學習框架 筆記(上)

TensorFlow-實戰Google深度學習框架 筆記(上)

當我 日誌 不一定 rain 如何 validate .config 存儲 構建

TensorFlow

TensorFlow 是一種采用數據流圖(data flow graphs),用於數值計算的開源軟件庫。在 Tensorflow 中,所有不同的變量和運算都是儲存在計算圖,所以在我們構建完模型所需要的圖之後,還需要打開一個會話(Session)來運行整個計算圖
通常使用import tensorflow as tf來載入TensorFlow
在TensorFlow程序中,系統會自動維護一個默認的計算圖,通過tf.get_default_graph函數可以獲取當前默認的計算圖。除了使用默認的計算圖,可以使用tf.Graph函數來生成新的計算圖,不同計算圖上的張量和運算不會共享
在TensorFlow程序中,所有數據都通過張量的形式表示,張量可以簡單的理解為多維數組,而張量在TensorFlow中的實現並不是直接采用數組的形式,它只是對TensorFlow中運算結果的引用。即在張量中沒有真正保存數字,而是如何得到這些數字的計算過程
如果對變量進行賦值的時候不指定類型,TensorFlow會給出默認的類型,同時在進行運算的時候,不會進行自動類型轉換
會話(session)擁有並管理TensorFlow程序運行時的所有資源,所有計算完成之後需要關閉會話來幫助系統回收資源,否則可能會出現資源泄漏問題
一個簡單的計算過程:

import tensorflow as  tf
a = tf.constant([1, 2], name=‘a‘)
b = tf.constant([3, 4], name=‘b‘)
c = a + b
with tf.Session() as sess:
    print(sess.run(c))

可以通過ConfigProto Protocol Buffer來配置需要生成的會話:

config = tf.ConfigProto(allow_soft_placement=True, log_device_placement=True)
sess1 = tf.InteractiveSession(config=config)
sess2 = tf.Session(config=config)

通過ConfigProto可以配置類似並行的線程數、GPU分配策略、運算超時時間等參數。當allow_soft_placement設置為True時,當出現以下情況,GPU上的運算可以放到CPU上進行:

  1. 運算無法在GPU上執行
  2. 沒有GPU資源
  3. 運算輸入包含對CPU計算結果的引用

當log_device_placement設置為True時,日誌中將會記錄每個節點被安排在哪個設備上以方便調試
使用神經網絡解決分類問題主要分為以下4個步驟:

  1. 提取問題中實體的特征向量作為神經網絡的輸入
  2. 定義神經網絡的結構,並定義如何從神經網絡的輸入得到輸出
  3. 通過訓練數據來調整神經網絡中參數的取值
  4. 使用訓練好的神經網絡來預測未知的數據

TensorFlow中矩陣乘法的使用:

tf.matmul(x, w)

tf.random_normal函數可以產生一個矩陣,矩陣中的元素是均值為0,標準差為指定數的隨機數,TensorFlow中,一個變量在被初始化之前,該變量的初始化過程需要被明確地調用:

a = tf.Variable(tf.random_normal([2, 3], stddev=2))
b = tf.Variable(tf.random_normal([2, 3], stddev=2))
c = tf.matmul(a, b)
with tf.Session() as sess:
    sess.run(a.initializer)
    sess.run(b.initializer)
    print(sess.run(c))

一個簡化初始化過程的函數,可以實現初始化所有變量的過程:

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

通過tf.global_variables()函數可以拿到當前計算圖上的所有變量,有助於持久化整個計算圖的運行狀態
TensorFlow中,變量的類型是不可改變的,而緯度是可以更改的,只是需要設置validate_shape=False
TensorFlow提供了placeholder機制用於提供輸入數據。placeholder相當於定義了一個位置,這個位置中的數據在程序運行時再指定。這樣在程序中就不需要生成大量常量來提供輸入數據,而只需要將數據通過placeholder傳入TensorFlow計算圖。在placeholder定義時,這個位置的數據類型需要指定,緯度信息可以推導出來,所以不一定要給出

a = tf.Variable(tf.random_normal([2, 3], stddev=2))
x = tf.placeholder(tf.float32, shape=(1, 2), name=‘input‘)
v1 = tf.matmul(x, a)
with tf.Session() as sess:
    sess.run(a.initializer)
    print(sess.run(v1, feed_dict={x : [[6, 7]]}))

在shape的一個緯度上使用None可以方便使用不同的batch大小shape(None, 3)
假定損失函數為loss = (...),一次訓練過程為sess.run(loss, feed_dict{x: X, ...})

TensorFlow提供了7種激活函數,其中常用的有:tf.nn.relutf.sigmoidtf.tanh
一個簡單的實現兩層神經網絡的前向傳播算法:

a = tf.nn.relu(tf.matmul(x, w1) + biases1)  
y = tf.nn.relu(tf.matmul(a, w2) + biases2)  

其中biases為偏向

w為了判斷一個輸出向量和期望的向量的接近程度,可以使用交叉熵
交叉熵公式: 技術分享圖片

交叉熵定義了兩個概率分布之間的距離,即可以表示通過概率分布q來表達概率分布p的困難程度,該值越小說明擬合越好

TensorFlow支持的交叉熵的Demo:

cross_entropy = -tf.reduce_mean(y_ * tf.log(tf.clip_by_value(y, le-10, 1.0)))

其中, y_為正確結果,y為預測值,tf.clip_by_value(y, a, b)將y的值限制在a、b之間,reduce_mean取均值
softmax函數用於將原始網絡的輸出當作置信度生成新的輸出
softmax: 技術分享圖片

交叉熵一般與softmax回歸一起使用

cross_entropy = tf.nn.softmax_cross_entropy_with_logits(labels=y_, logits=y)

y表示原始神經網路輸出結果,y_給出了標準答案

對於回歸問題最常用的損失函數是均方誤差(MSE),即方差取均值
TensorFlow支持為:

mse = tf.reduce_mean(tf.square(y_ - y))  

tf.greatertf.where可以用來實現選擇操作。tf.greater輸入兩個張量,比較大小,然後返回比較結果,tf.where輸入三個參數,第一個為條件參數,當其為True時,該函數會選擇第二個參數的值,否則選擇第三個值

v1 = tf.constant([1, 2, 3, 4])
v2 = tf.constant([4, 3, 2, 1])
sess = tf.InteractiveSession()
print(tf.greater(v1, v2).eval())  # [False False  True  True]
print(tf.where(tf.greater(v1, v2), v1, v2).eval())  # [4 3 3 4]
sess.close()

自定義損失函數的TensorFlow例子:

import tensorflow as  tf
from numpy.random import RandomState


batch_size = 8
x = tf.placeholder(tf.float32, shape=(None, 2), name=‘x-input‘)
y_ = tf.placeholder(tf.float32, shape=(None, 1), name=‘y-input‘)
w1 = tf.Variable(tf.random_normal([2, 1], stddev=1, seed=1))
y = tf.matmul(x, w1)
# 損失函數,且預測少了的損失更大
loss_less = 10
loss_more = 1
loss = tf.reduce_sum(tf.where(tf.greater(y, y_), (y - y_) * loss_more, (y_ - y) * loss_less))
train_step = tf.train.AdamOptimizer(0.001).minimize(loss)
# 生成模擬數據集
rdm = RandomState(1)
dataset_size = 128
X = rdm.rand(dataset_size, 2)
# 設置正確預測的值,並加上 -0.05 ~ 0.05的噪音
Y = [[x1 + x2 + rdm.rand() / 10.0 - 0.05] for (x1, x2) in X]

with tf.Session() as sess:
    init_op = tf.global_variables_initializer()
    sess.run(init_op)
    STEPS = 5000
    for i in range(STEPS):
        start = (i * batch_size) % dataset_size
        end = min(start + batch_size, dataset_size)
        sess.run(train_step, feed_dict={x: X[start: end], y_ : Y[start: end]})
        print(sess.run(w1))
        

梯度下降算法主要用於優化單個參數的取值,而反向傳播算法給出了一個高效的方式在所有參數上使用梯度下降算法,從而使神經網絡模型在訓練數據上的損失函數盡可能小
梯度下降法公式:
技術分享圖片

其中n為學習率

梯度下降方法存在的問題有:

  1. 不一定能得到全局最優解
  2. 計算時間過長

因為損失函數是所有訓練數據上的損失和,所以時間過長,可以使用隨機梯度下降算法來加速過程。該算法在每一輪叠代中隨機優化某一條訓練數據上的損失函數,當然這樣可以進行加速,但是存在更大的不能得到最優解的問題
為了折中這兩種算法,可以每次計算一小部分訓練數據的損失函數,這一小部分稱之為一個batch

已知當我們設置學習率的時候,我們應該首先選取一個較大的學習率,然後在訓練的過程中逐漸進行衰減。TensorFlow提供了一種靈活的學習率的設置方法--指數衰減法。tf.train.exponential_decay函數實現了指數衰減學習率,其減小的方法為:

decayed_learning_rate = learning_rate * decay_rate ^ (global_step / decay_steps)

decayed_learning_rate為每一輪優化時使用的學習率,learning_rate為事先設定的初始學習率,decay_rate為衰減系數,decay_steps為衰減速度
decay_steps通常代表完整地使用一遍訓練數據所需要的叠代輪數,即總樣本數除以每一個batch中的訓練樣本數,如此,就可以每完整的過完一遍訓練數據,學習率就減小一次
使用方法:

global_step = tf.Variable(0)

learning_rate = tf.train.exponential_decay(0.1, global_step, 100, 0.96, staircase=True)

learning_step = tf.train.GradientDescentOptimizer(learning_rate).minimize(loss=loss, global_step=global_step)

0.01為初始學習率,因設置starcase,所以每訓練100輪,學習率乘以0.96

當出現過擬合的情況的時候,可以使用正則化的方法來優化,此時不再直接優化
$$
J(\theta)
$$
而是優化
$$
J(\theta) + \lambda R(\omega)
$$
其中,
$$
R(\omega)
$$
刻畫了模型的復雜程度
$$
\lambda
$$
表示模型復雜損失在總損失中的比例
正則化範式:
技術分享圖片

L1正則化會將參數變得更稀疏,而L2不會

TensorFlow對正則化的支持:

loss = tf.reduce_mean(tf.square(y_ - y) + tf.contrib.layers.l2_regularizer(lambda)(w))

TensorFlow會將L2的正則化損失值除以2使得求導得到的結果更簡潔

使用滑動平均可以使得模型更具健壯性,TensorFlow提供的方法為tf.train.ExponentialMovingAverage,使用時需要提供一個衰減率,其會維護一個影子變量(shadow variable),其更新方法為:
shadow_variable = decay * shadow_variable + (1 - decay) * variable
其中,decay為衰減率,decay越大,模型越穩定,通常將decay設置為接近1的數,variable為待更新的變量
如果該函數提供了num_updates參數來動態設置decay的大小,那麽衰減率將變為:
$$
min( decay, \frac{1 + num_updates} {10 + num_updates})
$$
滑動平均樣例:

v1 = tf.Variable(0, dtype=tf.float32)
# step控制衰減率
step = tf.Variable(0, trainable=False)

ema = tf.train.ExponentialMovingAverage(0.99, step)
# 定義一個更新變量滑動平均的操作,每次執行這個操作時,列表中的變量就會被更新
maintain_averages_op = ema.apply([v1])

with tf.Session() as sess:
    init_op = tf.global_variables_initializer()
    sess.run(init_op)
    # 通過ema.average(v1)獲取滑動平均之後變量的取值
    print(sess.run([v1, ema.average(v1)])) # [0.0 0.0]
    # 賦值
    sess.run(tf.assign(v1, 5))
    # 更新v1的滑動取值
    sess.run(maintain_averages_op)
    print(sess.run([v1, ema.average(v1)])) # [5.0, 4.5]

    sess.run(tf.assign(step, 10000))
    sess.run(tf.assign(v1, 10))
    sess.run(maintain_averages_op)
    print(sess.run([v1, ema.average(v1)])) # [10.0, 4.555]

    sess.run(maintain_averages_op)
    print(sess.run([v1, ema.average(v1)])) # [10.0, 4.60945]

一個mnist數字識別的簡單例子,使用TensorFlow完成

import tensorflow as  tf
from tensorflow.examples.tutorials.mnist import input_data


# mnist = input_data.read_data_sets("/home/fan/dataset/mnist/", one_hot=True)
# print("mnist data info")
# print("Training data size: ", mnist.train.num_examples)
# print("Validating data size: ", mnist.validation.num_examples)
# print("Testing data size: ", mnist.test.num_examples)
# print("Example training data: ", mnist.train.images[0])
# print("Example training data label: ", mnist.train.labels[0])

INPUT_NODE = 784  # 輸入層節點數  28 * 28
OUTPUT_NODE = 10  # 區分 0-10

LAYER1_NODE = 500  # 一個有500個節點的隱藏層
BATCH_SIZE = 100  # 一批訓練數據個數
LEARNING_RATE_BASE = 0.8
LEARNING_RATE_DECAY = 0.99
REGULARIZATION_RATE = 0.0001  # 正則化項在損失函數中的系數
TRAINING_STEPS = 30000  # 訓練輪數
MOVING_AVERAGE_DECAY = 0.99


def inference(input_tensor, avg_class, weights1, biases1, weights2, biases2):
    # 當沒有提供滑動平均類時,直接使用參數當前的取值
    if avg_class is None:
        layer1 = tf.nn.relu(tf.matmul(input_tensor, weights1) + biases1)
        return  tf.matmul(layer1, weights2) + biases2
    else:
        layer1 = tf.nn.relu(tf.matmul(input_tensor, avg_class.average(weights1)) + avg_class.average(biases1))
        return tf.matmul(layer1, avg_class.average(weights2)) + avg_class.average(biases2)


def train(mnist):
    x = tf.placeholder(tf.float32, [None, INPUT_NODE], name=‘x-input‘)
    y_ = tf.placeholder(tf.float32, [None, OUTPUT_NODE], name=‘y-input‘)
    # 隱藏層參數
    weights1 = tf.Variable(tf.truncated_normal([INPUT_NODE, LAYER1_NODE], stddev=0.1))
    biases1 = tf.Variable(tf.constant(0.1, shape=[LAYER1_NODE]))
    # 輸出層參數
    weights2 = tf.Variable(tf.truncated_normal([LAYER1_NODE, OUTPUT_NODE], stddev=0.1))
    biases2 = tf.Variable(tf.constant(0.1, shape=[OUTPUT_NODE]))
    y = inference(x, None, weights1, biases1, weights2, biases2)
    global_step = tf.Variable(0, trainable=False)
    variable_averages = tf.train.ExponentialMovingAverage(MOVING_AVERAGE_DECAY, global_step)
    variables_averages_op = variable_averages.apply(tf.trainable_variables())
    average_y = inference(x, variable_averages, weights1, biases1, weights2, biases2)
    cross_entropy = tf.nn.sparse_softmax_cross_entropy_with_logits(logits=y, labels=tf.argmax(y_, 1))
    cross_entropy_mean = tf.reduce_mean(cross_entropy)
    regularizer = tf.contrib.layers.l2_regularizer(REGULARIZATION_RATE)
    regularization = regularizer(weights1) + regularizer(weights2)
    loss = cross_entropy_mean + regularization

    learning_rate = tf.train.exponential_decay(LEARNING_RATE_BASE, global_step, mnist.train.num_examples / BATCH_SIZE,
                                               LEARNING_RATE_DECAY)
    train_step = tf.train.GradientDescentOptimizer(learning_rate).minimize(loss, global_step=global_step)
    # 每過一遍數據既需要通過反向傳播來更新神經網絡參數,又要更新每一個參數的滑動平均值,為了一次完成多個操作,
    # TensorFlow提供了tf.control_dependencies和tf.group兩種機制
    with tf.control_dependencies([train_step, variables_averages_op]):
        train_op = tf.no_op(name=‘train‘)
    correct_prediction = tf.equal(tf.argmax(average_y, 1), tf.argmax(y_, 1))
    accuracy = tf.reduce_mean(tf.cast(correct_prediction, tf.float32))
    with tf.Session() as sess:
        tf.global_variables_initializer().run()
        validate_feed = {x: mnist.validation.images, y_: mnist.validation.labels}
        test_feed = {x: mnist.test.images, y_: mnist.test.labels}
        for i in range(TRAINING_STEPS):
            if i % 1000 == 0:
                validate_acc = sess.run(accuracy, feed_dict=validate_feed)
                print("After %d training step(s), validation accuracy using average model is %g" % (i, validate_acc))
            xs, ys = mnist.train.next_batch(BATCH_SIZE)
            sess.run(train_op, feed_dict={x: xs, y_: ys})
        test_acc = sess.run(accuracy, feed_dict=test_feed)
        print("After %d training step(s), test accuracy using average model is %g" %(TRAINING_STEPS, test_acc))


def main(argv = None):
    # 該語句會自動將數據集下載到相應目錄
    mnist = input_data.read_data_sets("/home/fan/dataset/mnist/", one_hot=True)
    train(mnist)


if __name__ == "__main__":
    tf.app.run()
    

可見最後調用的時候,使用的是tf.app.run(),其意思為通過處理flag解析,然後執行main函數,如果你的函數入口不為main(),假設為test(),那麽此處應該為tf.app.run(test()),當然,也等同於直接調用入口函數的效果(此處存疑,不過在上面的代碼中是可以的)
在選擇優化的時候,我們現在選擇的是優化總損失,也可以選擇優化交叉熵,但是,如果優化交叉熵的話,會使得模型過擬合從而在面臨未知數據的時候表現的沒有優化總損失好

變量管理

# 以下二者創建變量的方式等價
v = tf.get_variable("v", shape=[1], initializer=tf.constant_initializer(1.0))
v1 = tf.Variable(tf.constant(1.0, shape=[1]), name="v")

TensorFlow中的變量初始化函數

初始化函數 功能
tf.constant_initializer 將變量初始化為給定常量
tf.random_normal_initializer 將變量初始化為滿足正態分布的隨機值
tf.truncated_normal_initializer 將變量初始化為滿足正態分布的隨機值,但如果隨機出來的值偏離平均值超過2個標準差,那麽這個數將被重新隨機
tf.random_uniform_initializer 將變量初始化為滿足平均分布的隨機值
tf.uniform_unit_scaling_initializer 將變量初始化為滿足平均分布但不影響輸出數量級的隨機值
tf.zeros_initializer 將變量設置為全0
tf.ones_initializer 將變量設置為全1

如果要獲取一個已經創建的變量,需要通過tf.variable_scope函數來生成一個上下文管理器,並明確指明在這個上下文管理器中,tf.variable將直接獲取已經生成的變量

with tf.variable_scope("a"):
    v = tf.get_variable("v", shape=[1], initializer=tf.constant_initializer(1.0))
with tf.variable_scope("a", reuse=True):
    v1 = tf.get_variable("v", [1])
    print(v == v1)  # True

如上,當我們想要復用變量的時候,要設置reuse為True,否則則是另外創建
當上下文管理器嵌套的時候,如果內部的上下文管理器不設置reuse值,那麽其將會保持和外層值一致
也可以在名稱為空的命名空間中直接通過代命名空間名稱的變量名來獲取其他命名空間下的變量

with tf.variable_scope("", reuse=True):
    v1 = tf.get_variable("a/v", [1])
    print(v == v1) # True

TensorFlow模型持久化
TensorFlow提供了tf.train.Saver類來保存和還原網絡模型

import tensorflow as  tf
import os


v1 = tf.Variable(tf.constant(1.0, shape=[1], name=‘v1‘))
v2 = tf.Variable(tf.constant(2.0, shape=[1], name=‘v2‘))
result = v1 + v2
init_op = tf.global_variables_initializer()
saver = tf.train.Saver()

with tf.Session() as sess:
    sess.run(init_op)
    if not os.path.exists("saveModel"):
        os.mkdir("saveModel")
    saver.save(sess, ‘saveModel/saveTest.ckpt‘)

如上,saver.save函數將把TensorFlow模型保存到saveModel/saveTest.ckpt文件中,但同時會在saveModel文件下出現額外的三個文件:checkpoint文件保存了一個目錄下所有的模型文件列表,.meta文件保存了TensorFlow計算圖的結構,.ckpt文件保存了TensorFlow程序中每個變量的取值
加載模型的方法:

v1 = tf.Variable(tf.constant(1.0, shape=[1], name=‘v1‘))
v2 = tf.Variable(tf.constant(2.0, shape=[1], name=‘v2‘))
result = v1 + v2
saver = tf.train.Saver()
with tf.Session() as sess:
    saver.restore(sess, "saveModel/saveTest.ckpt")
    print(sess.run(result))
    

以上,通過加載模型而不用初始化變量,從而使得變量變為模型中存儲的值
也可以直接加載已經持久化的圖:

import tensorflow as  tf

saver = tf.train.import_meta_graph("saveModel/saveTest.ckpt.meta")
with tf.Session() as sess:
    saver.restore(sess, "saveModel/saveTest.ckpt")
    print(sess.run(tf.get_default_graph().get_tensor_by_name("add:0")))

以上程序默認保存和加載了TensorFlow計算圖上定義的全部變量
可以提供一個列表給tf.train.Saver來指定需要保存或者加載的變量,如:saver = tf.train.Saver([v1]),如此將只會加載v1變量
可以在保存或加載時給變量重命名

v1 = tf.Variable(tf.constant(1.0, [1]), name="o-v1")
v2 = tf.Variable(tf.constant(2.0, [1]), name="o-v2")
saver = tf.train.Saver({"v1": v1, "v2": v2})

TensorFlow-實戰Google深度學習框架 筆記(上)