tensorflow的動態圖和autograph的自動微分機制

在TensorFlow1.0時代,採用的是靜態計算圖,需要先使用TensorFlow的各種算子創建計算圖,然後再開啓一個會話Session,顯式執行計算圖。
而在TensorFlow2.0時代,採用的是動態計算圖,即每使用一個算子後,該算子會被動態加入到隱含的默認計算圖中立即執行得到結果,而無需開啓Session。
使用動態計算圖即Eager Excution的好處是方便調試程序,它會讓TensorFlow代碼的表現和Python原生代碼的表現一樣,寫起來就像寫numpy一樣,各種日誌打印,控制流全部都是可以使用的。
使用動態計算圖的缺點是運行效率相對會低一些。因爲使用動態圖會有許多次Python進程和TensorFlow的C++進程之間的通信。而靜態計算圖構建完成之後幾乎全部在TensorFlow內核上使用C++代碼執行,效率更高。此外靜態圖會對計算步驟進行一定的優化,剪去和結果無關的計算步驟。
如果需要在TensorFlow2.0中使用靜態圖,可以使用@tf.function裝飾器將普通Python函數轉換成對應的TensorFlow計算圖構建代碼。運行該函數就相當於在TensorFlow1.0中用Session執行代碼。使用tf.function構建靜態圖的方式叫做 Autograph.

下面介紹tensorflow使用動態圖和autograph的自動微分機制:
求:f(x) = a*x**2 + b*x + c的導數:
首先定義張量:

import tensorflow as tf
import numpy as np


x = tf.Variable(0.0, name='x', dtype=tf.float32)
a = tf.constant(1.0)
b = tf.constant(-2.0)
c = tf.constant(1.0)

使用tf.GradientTape()求導:

# 這裏創建一個GradientTape()對象,爲後面求出確定點的一階導數準備
with tf.GradientTape() as tape:
	# 將函數表示出來:
	y = a*x**2 + b*x + c
# 使用gradient()函數,相當於是求出了一階導數的表達式,
# 然後需要傳入x值才能得出在x這個點的一階導數值。
dy_dx = tape.gradient(y, x)
print(dy_dx)
tf.Tensor(-2.0, shape=(), dtype=float32)

對常量張量求導:需要增加watch:

with tf.GradientTape() as tape:
	tape.watch([a, b, c])
	y = a*x**2 + b*x + c

# 順序不能弄錯了:
dy_dx, dy_da, dy_db, dy_dc = tape.gradient(y, [x, a, b, c])
print(dy_da)
print(dy_dc)
tf.Tensor(0.0, shape=(), dtype=float32)
tf.Tensor(1.0, shape=(), dtype=float32)

因爲dy/da = x**2, x = 0,所以dy_da = 0

求二階導數

with tf.GradientTape() as tape1:
	with tf.GradientTape() as tape2:
		y = a * tf.pow(x, 2) + b * x + c
	dy_dx = tape2.gradient(y, x)
dy2_dx2 = tape1.gradient(dy_dx, x)
print(dy2_dx2)
tf.Tensor(2.0, shape=(), dtype=float32)

使用autograph實現:

@tf.function
def fun(x):
	a = tf.constant(1.0)
    b = tf.constant(-2.0)
    c = tf.constant(1.0)
    # 自變量轉換成tf.float32類型
    x = tf.cast(x, tf.float32)
	with tf.GradientTape as tape:
		# 因爲不添加watch的時候x必須是變量,但是傳進去的是常量
		tape.watch(x)
		y = a * tf.pow(x, 2) + b * x + c
	dy_dx = tape.gradient(y, x)
return (dy_dx, y)

tf.print(fun(tf.constant(0.0)))
(-2, 1)

求導數的最小值:

按照前面的思路嘗試一下:

定義變量:

import tensorflow as tf
import numpy as np


x = tf.Variable(0.0, name="x", dtype=tf.float32)
a = tf.constant(1.0)
b = tf.constant(-2.0)
c = tf.constant(1.0)
"""嘗試着往下寫,發現寫不下去了,因爲這種方法求的是給定一個x和abc值
求取當前的倒數值,無法求出最小值。"""
# @tf.function
# def min_value(a, b, c):
#
#     with tf.GradientTape() as tape:
#         y = a*x**2 + b*x + c
#     dy_dx = tape.gradient(y, x)

隨機梯度下降實現:

這裏介紹的是隨機梯度下降算法,因爲tensorflow的API封裝性好,不瞭解隨機梯度的人可能沒那麼容易看懂:

# 這裏首先構建一個優化器:SGD指的是隨機梯度下降
optimizer = tf.keras.optimizers.SGD(learning_rate=0.01)
for _ in range(1000):
    with tf.GradientTape() as tape:
        y = a * tf.pow(x, 2) + b * x + c
    dy_dx = tape.gradient(y, x)
    # 按照前面的例子,上一步求的是函數在x處的一階導數
    # 下面這一步的作用是利用梯度下降更新x的值:
    optimizer.apply_gradients(grads_and_vars=[(dy_dx, x)])

tf.print('y = ', y, '; x = ', x)
y =  0 ; x =  0.999998569

發現x的最終的值是無限接近於1,這裏再一次透露了梯度下降法求局部最小值是採用不斷逼近的思想。

使用optimizer.minimize()

optimizer.minimize()相當於先用tapegradient,再apply_gradient.
運行的時間和前面的方法差不多,只是用起來比較方便而已。

x = tf.Variable(0.0, name="x", dtype=tf.float32)

def f():
    a = tf.constant(1.0)
    b = tf.constant(-2.0)
    c = tf.constant(1.0)
    y = a * tf.pow(x, 2) + b * x + c
    return (y)


optimizer = tf.keras.optimizers.SGD(learning_rate=0.01)
for _ in range(1000):
	# 這一步相當於前面的with語句然後tape.gradient()
	# 其中傳進去的f是函數名,不能像上面一樣傳入y
    optimizer.minimize(f, [x])
tf.print('y = ', f(), '; x = ', x)
y =  0 ; x =  0.999998569

autograph中完成最小值求解

使用optimizer.apply_gradients

x = tf.Variable(0.0, name="x", dtype=tf.float32)
optimizer = tf.keras.optimizers.SGD(learning_rate=0.01)


@tf.function
def minimizef():
    a = tf.constant(1.0)
    b = tf.constant(-2.0)
    c = tf.constant(1.0)
	# 注意autograph時使用tf.range(1000)而不是range(1000)
    for _ in tf.range(1000):  
        with tf.GradientTape() as tape:
            y = a * tf.pow(x, 2) + b * x + c
        dy_dx = tape.gradient(y, x)
        optimizer.apply_gradients(grads_and_vars=[(dy_dx, x)])

    y = a * tf.pow(x, 2) + b * x + c
    return y


tf.print(minimizef())
tf.print(x)

上面的循環裏爲什麼強調要用tf.range()而不是range(),我測試了一下,如果使用tf.range()的話使用的時間是1.9秒,而換成range()之後花費的時間是37秒,根本起不到加速的作用,使用靜態圖的優點就是速度快。

這樣看來,使用autograph好像就是將所要實現的功能放在一個函數上,然後實現。其實這是必須的,因爲上面提到,要在tensorflow2.0使用靜態圖,就必須使用這樣的裝飾器的方式實現,所以創建函數是必然的。

使用optimizer.minimize

x = tf.Variable(0.0, name="x", dtype=tf.float32)
optimizer = tf.keras.optimizers.SGD(learning_rate=0.01)


@tf.function
def f():
    a = tf.constant(1.0)
    b = tf.constant(-2.0)
    c = tf.constant(1.0)
    y = a * tf.pow(x, 2) + b * x + c
    return (y)


@tf.function
def train(epoch):
    for _ in tf.range(epoch):
        optimizer.minimize(f, [x])
    return (f())


tf.print(train(1000))
tf.print(x)
發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章