《28天玩轉TensorFlow2》第3天:張量的自動求導機制

第3天:張量的自動求導機制

import tensorflow as tf
tf.print('tensorflow的版本:{}'.format(tf.__version__))
tensorflow的版本:2.1.0

1、自動求導機制

所謂的自動求導機制,就是對於屬性爲變量的張量,tensorflow會自動的將該變量加入到它的求導記錄器tf.GradientTape() 中,實現自動求導。對於屬性爲常量的張量而言,需要將該常量手工加入,涉及的函數就是watch,具體參見下面給出的示例。

1.1、一元函數求導

一元函數,也就是函數中變量的個數爲1。引入一個例子,y=3x34x22x+2y = 3x^{3} - 4x^{2} - 2x + 2,計算函數yyx=3x=3處的一階導數y1y1,二階導數y2y2的值。
因爲dydx=9x28x2\frac{\mathrm{dy}}{\mathrm{d}x} = 9x^{2} - 8x-2

d2ydx2=18x8\frac{\mathrm{d^{2}y}}{\mathrm{d}x^{2}} = 18x-8

所以y1=9×328×32=55y2=18×38=28y1=9\times 3^{2} - 8\times 3-2=55,y2=18\times 3 - 8=28

# 注意類型必須均爲浮點型,並且一致
# 常量
a = tf.constant(3.)
b = tf.constant(4.)
c = tf.constant(2.)
d = tf.constant(2,dtype=tf.float32)
# 變量
x = tf.Variable(3, dtype=tf.float32, name='x')

with tf.GradientTape() as g: # 自動求導機制
    y = a * tf.pow(x, 3) - b * tf.square(x) - c * x + d  # 函數
y1 = g.gradient(y, x) # 一階導數
print('函數y在x=3處的一階導數:y1=', y1.numpy(), sep='')

函數y在x=3處的一階導數:y1=55.0
# 注意類型必須均爲浮點性,並且一致
# 常量
a = tf.constant(3.)
b = tf.constant(4.)
c = tf.constant(2.)
d = tf.constant(2,dtype=tf.float32)
# 變量
x = tf.Variable(3, dtype=tf.float32, name='x')

with tf.GradientTape() as g: # 自動求導機制
    g.watch([a, b]) # 手動的將常量的a,b加入到求導的記錄器中
    y = a * tf.pow(x, 3) - b * tf.square(x) - c * x + d  # 函數
y1_x, y1_a, y1_b = g.gradient(y, [x, a, b]) # 一階導數

print('函數y在x=3處的關於a的一階導數:y1_a=', y1_a.numpy(), sep='')
print('函數y在x=3處的關於b的一階導數:y1_a=', y1_b.numpy(), sep='')
函數y在x=3處的關於a的一階導數:y1_a=27.0
函數y在x=3處的關於b的一階導數:y1_a=-9.0

tf.GradientTape() 下,只會保留計算一次的導數,因此如果需要多次調用 .gradient,參數persistent 需要設置爲True,該值默認爲False

# 注意類型必須均爲浮點型,並且一致
x = tf.Variable(2, dtype=tf.float32, name='x')

with tf.GradientTape(persistent=True) as g:
    y = 3 * tf.pow(x, 3) - 4 * tf.square(x) - 2 * x + 2
    z = 4 * tf.pow(x, 3) - 2 * tf.square(x) + 3 * x - 1
y1 = g.gradient(y, x) 
z1 = g.gradient(z, x) 
print('函數y在x=2處的一階導數:y1=', y1.numpy(), sep='')
print('函數z1在x=2處的一階導數:z1=', z1.numpy(), sep='')
函數y在x=2處的一階導數:y1=18.0
函數z1在x=2處的一階導數:z1=43.0

計算函數的二階導數,是利用嵌套來完成的,高階亦如此。示例如下:

# 注意類型必須均爲浮點型,並且一致
x = tf.Variable(2, dtype=tf.float32, name='x')

with tf.GradientTape() as g2:
    with tf.GradientTape() as g1:
        y = 3 * tf.pow(x, 3) - 4 * tf.square(x) - 2 * x + 2
    y1 = g1.gradient(y, x) 
y2 = g2.gradient(y1, x) 
print('函數y在x=2處的二階導數:y2=', y2.numpy(), sep='')
函數y在x=2處的二階導數:y2=28.0

1.2 多元函數求導

多元就是函數中包含多個變量,下面引入一個例子:y=6x12x22+4x1x25x2y = 6x_1^{2} - x_2^{2} + 4x_1x_2-5x_2
則函數yy關於x1,x2x_1,x_2的一階導數分別爲:
dydx1=12x1+4x2\frac{\mathrm{dy}}{\mathrm{d}x_1} = 12x_1 + 4x_2

dydx2=2x2+4x15\frac{\mathrm{dy}}{\mathrm{d}x_2} = -2x_2+4x_1-5

# 注意類型必須均爲浮點型,並且一致
# 變量
x1 = tf.Variable(3, dtype=tf.float32, name='x_1')
x2 = tf.Variable(1, dtype=tf.float32, name='x_2')

with tf.GradientTape() as g: # 自動求導機制
    y = 6 * tf.pow(x1, 2) - tf.pow(x2, 2) + 4 * x1 * x2 - 5 * x2 
dx1, dx2 = g.gradient(y, [x1, x2]) # 一階導數
print('函數y在x_1=3, x_2=1處的關於x_1的一階導數:dx1=', dx1.numpy(), ',關於x_2的一階導數:dx2=', dx2.numpy(),sep='')
函數y在x_1=3, x_2=1處的關於x_1的一階導數:dx1=40.0,關於x_2的一階導數:dx2=5.0

1.3 向量求導

向量求導就是把向量作爲變量,就是下面引入一個例子:Y=AXY=AX
其中XX是一個2行一列的向量,A=[364657]A = \begin{bmatrix} -3 & 6 \\ 4 & 6 \\ 5 & 7 \end{bmatrix}
則函數YY關於XX的一階導數爲:
YX=[345667]\frac{\partial Y}{\partial X} = \begin{bmatrix} -3 & 4 &5 \\ 6 & 6 &7\\ \end{bmatrix}

# 注意類型必須均爲浮點型,並且一致
# 變量
X = tf.Variable([[1], [1]], dtype=tf.float32, name='X')
A = tf.constant([[-3, 6],[4, 6], [5, 7]], dtype=tf.float32)
with tf.GradientTape(persistent=True) as g: # 
    Y = tf.matmul(A, X)
dX = g.jacobian(Y, X) # 一階導數,注意此處不是梯度
print('函數y關於X的一階導數:dX=', dX.numpy(),sep='')

gX = g.gradient(Y, X) # 一階導數,注意此處不是梯度
print('函數y關於X的梯度:gX=', gX.numpy(),sep='') # 梯度的維度一定和變量的維度是一模一樣的,這樣才能利用梯度下降法更改參數
函數y關於X的一階導數:dX=[[[[-3.]
   [ 6.]]]


 [[[ 4.]
   [ 6.]]]


 [[[ 5.]
   [ 7.]]]]
函數y關於X的梯度:gX=[[ 6.]
 [19.]]

1.4 矩陣求導

矩陣求導就是把矩陣作爲變量,這種情況在機器學習中經常出現。下面引入一個例子:Y=XTAXY= X^{T}AX
其中XX是一個3行2列的矩陣,A=[364463579]A = \begin{bmatrix} -3 & 6 & 4\\ 4 & 6 & 3\\ 5 & 7 & 9 \end{bmatrix}

# 注意類型必須均爲浮點型,並且一致
# 變量
X = tf.Variable(tf.ones((3, 2)), dtype=tf.float32, name='X')
A = tf.constant([[-3, 6, 4],[4, 6, 3], [5, 7, 9]], dtype=tf.float32)
with tf.GradientTape(persistent=True) as g: # 
    Y = tf.matmul(tf.matmul(tf.transpose(X), A), X)
dX = g.jacobian(Y, X) # 一階導數,注意此處不是梯度
print('函數y關於X的一階導數:dX=', dX.numpy(),sep='')

gX = g.gradient(Y, X) # 一階導數,注意此處不是梯度
print('函數y關於X的梯度:gX=', gX.numpy(),sep='') # 梯度的維度一定和變量的維度是一模一樣的,這樣才能利用梯度下降法更改參數
函數y關於X的一階導數:dX=[[[[13.  0.]
   [32.  0.]
   [37.  0.]]

  [[ 7.  6.]
   [13. 19.]
   [21. 16.]]]


 [[[ 6.  7.]
   [19. 13.]
   [16. 21.]]

  [[ 0. 13.]
   [ 0. 32.]
   [ 0. 37.]]]]
函數y關於X的梯度:gX=[[26. 26.]
 [64. 64.]
 [74. 74.]]

2、結合優化器計算函數最小值

下面展示計算最小值的幾種實現方式

  • optimizer.apply_gradients
  • optimizer.apply_gradients+函數形式
  • autograph
  • autograph+函數形式

下面給出計算函數最小值的示例:

首先引入一個示例:y=2x26x+5y = 2x^{2} - 6x + 5,計算函數的最小值。
因爲y=2x26x+5=2(x1.5)2+0.5y = 2x^{2} - 6x + 5 = 2(x-1.5)^{2}+0.5,所以當x=1.5x=1.5時,yy取得最小值ymin=0.5ymin = 0.5

# optimizer.apply_gradients
x = tf.Variable(2., name="x")
# 優化器,有很多優化器可供選擇
# 優化器可以獲得最小值的原理,就是變量x沿着梯度的相反的方向以learning_rate大小的步長變化,
# 也就是x=x-learning_rate*dy_dx,這樣持續一定的步數,可以取得比較滿意的最小值。
optimizer = tf.keras.optimizers.Adam(learning_rate=0.02)
for _ in range(1280):  # 持續1280步
    with tf.GradientTape() as tape:
        y = 2 * tf.square(x) - 6 * x + 5  # 需要獲得最小值的函數
    dy_dx = tape.gradient(y, x) # 計算一階導數
    optimizer.apply_gradients(grads_and_vars=[(dy_dx, x)])  # 應用一階導數
print('最小值ymin={}'.format(y.numpy()), '此時x={}'.format(x.numpy()))
最小值ymin=0.5 此時x=1.5
# optimizer.apply_gradients+函數, 機器學習中比較常用的形式
x1 = tf.Variable(1.)
x2 = tf.Variable(1.)

#注意func()無參數
def func():    
    y = 2 * tf.square(x1) - 6 * x1 + 5 + tf.square(x2) + 4 * x2 - 8
    return y
# 優化器
optimizer = tf.keras.optimizers.Adam(learning_rate=0.02)   
for _ in range(1280):
    optimizer.minimize(func, [x1, x2]) # 最小值   
y = func()  # 此時x1,x2已經是使得y取得最小值的數值
print('最小值ymin={}'.format(y.numpy()), '此時x1={}'.format(x1.numpy()), '此時x2={}'.format(x2.numpy()))
最小值ymin=-11.5 此時x1=1.5 此時x2=-1.999998927116394
# autograph 動態計算圖
x1 = tf.Variable(2.)
x2 = tf.Variable(2.)
optimizer = tf.keras.optimizers.Adam(learning_rate=0.08)

@tf.function
def targetfunc():
    for _ in tf.range(1280): # 使用tf.range(1280)
        with tf.GradientTape() as g:
            y = 2 * tf.square(x1) - 6 * x1 + 5 + tf.square(x2) + 4 * x2 - 8
        dy_dx = g.gradient(y, [x1, x2])
        optimizer.apply_gradients(grads_and_vars=zip(dy_dx, [x1, x2]))
        
    y =  2 * tf.square(x1) - 6 * x1 + 5 + tf.square(x2) + 4 * x2 - 8 # 根據得到的x值計算y的最小值
    return y
y = targetfunc()
print('最小值ymin={}'.format(y.numpy()), '此時x1={}'.format(x1.numpy()), '此時x2={}'.format(x2.numpy()))
最小值ymin=-11.5 此時x1=1.5 此時x2=-2.000000476837158
# autograph+函數形式
x = tf.Variable(0.2)
optimizer = tf.keras.optimizers.SGD(learning_rate=0.32)   

@tf.function
def func():   
    return 2 * tf.square(x) - 6 * x + 5

@tf.function
def train(epoch):  
    for _ in tf.range(epoch):  
        optimizer.minimize(func, [x])# 最小值
    return func()

y = train(1280)
print('最小值ymin={}'.format(y), '此時x={}'.format(x.numpy()))
最小值ymin=0.5 此時x=1.5

3、線性迴歸示例

對於線性迴歸模型Y=WX+BY=WX+B
也就是計算使得最小二乘誤差cost=j=1mi=1n(yjiy~ji)2cost =\sum_{j=1}^{m} \sum_{i=1}^{n}(y_{ji}-\tilde{y}_{ji})^{2}
得到最小值的W,BW,B

其中mm是一條數據中因變量的個數,nn是數據條數,yjiy_{ji}ii條數據的因變量jj的值,y~ji\tilde{y}_{ji}是對應的模型輸出值。
假設自變量矩陣的維度爲[d,n][d, n],也就是一條數據有dd個自變量;因變量的維度爲[m,n][m, n]。也就是一條數據有mm個因變量,則WW的維度就是[m,d][m, d]BB的維度就是[m,1][m, 1]。當d等於1時,WW就可看作一個標量;當m等於1時,BB也可以看成一個標量。

3.1 簡單線性迴歸

簡單線性迴歸就是隻有一個自變量x,一個因變量y的模型,模型可以寫成:
y=ax+by=ax+b

下面給出一組數據:在這裏插入圖片描述
計算出a,ba, b的值。

# 3.1 代碼實現
x = tf.constant([3, 5, 8, 9, 1, 15, 17, 26], dtype=tf.float32)
y = tf.constant([7, 11, 17, 19, 3, 31, 35, 53], dtype=tf.float32)
a = tf.Variable(0.)
b = tf.Variable(0.)
# 優化器
optimizer = tf.keras.optimizers.Adam(learning_rate=0.6)
for _ in range(4001):  # 迭代的次數
    with tf.GradientTape() as t:
        y_modelout = a * x + b # 模型的輸出值
        cost = tf.reduce_sum(tf.square(y - y_modelout))  # 定義最小二乘法誤差
        if _ % 1000 == 0: # 打印誤差
            print('誤差:{}'.format(cost))
    d_cost = t.gradient(cost, [a, b]) # 計算一階導數
    optimizer.apply_gradients(grads_and_vars=zip(d_cost, [a, b]))  # 應用一階導數
    
print('最終最小二乘誤差cost={}'.format(cost.numpy()), '此時模型參數a={},'.format(a.numpy()), 'b={}'.format(b.numpy())) 
print('模型輸出值:{}'.format(y_modelout.numpy()))
誤差:5824.0
誤差:5.684341886080802e-14
誤差:5.684341886080802e-14
誤差:0.0
誤差:0.0
最終最小二乘誤差cost=0.0 此時模型參數a=2.0, b=1.0000001192092896
模型輸出值:[ 7. 11. 17. 19.  3. 31. 35. 53.]

3.2 多重線性迴歸

又稱多因素線性迴歸,也就是有多個自變量,一個因變量y的模型,模型可以寫成:
y=AX+by=AX+b
在這裏插入圖片描述
計算出A,bA, b的值。

上面的數據中有8條數據,每條數據有3個自變量,所有自變量構成的數據矩陣就是3行8列的,因變量矩陣是1行8列的。所以參數矩陣A就是1行3列的,參數矩陣b就是一個標量。

# 3.2 代碼實現
x = tf.constant([[3, 6, 8, 10, 2, 16, 17, 26],[3, 5, 8, 9, 1, 14, 17, 26],[5, 9, 14, 16, 3, 27, 31, 48]], dtype=tf.float32)
y = tf.constant([4, 7, 9, 11, 2, 17, 20, 28], dtype=tf.float32)
A = tf.Variable(tf.zeros((1, 3)), dtype=tf.float32, name='A') # 1行3列
b = tf.Variable(0., name='b')

# 模型輸出函數
def modelout():
    y_modelout = tf.matmul(A, x) + b
    return y_modelout
# 計算誤差的函數    
def costfunc():    
    cost = tf.reduce_sum(tf.square(y - modelout())) 
    return cost
# 優化器
optimizer = tf.keras.optimizers.Adam(learning_rate=0.01)   
for _ in range(8001):
    optimizer.minimize(costfunc, [A, b]) # 最小值   
    if _ % 2000 == 0:
        print('誤差:{}'.format(costfunc()))  # 誤差的變化
        
cost, y_modelout = costfunc(), modelout()   
print('最小二乘誤差cost={}'.format(cost.numpy()), '此時A={},'.format(A.numpy()), 'b={}'.format(b.numpy())) 
print('模型輸出值:{}'.format(y_modelout.numpy()))
誤差:1625.72119140625
誤差:2.1829843521118164
誤差:1.9039669036865234
誤差:1.6688652038574219
誤差:1.6241304874420166
最小二乘誤差cost=1.6241304874420166 此時A=[[ 0.79583377  0.80701506 -0.28584385]], b=0.5898457169532776
模型輸出值:[[ 3.969173   6.8273287  9.410822  11.237818   2.1309967 16.903612
  18.977118  28.54341  ]]

3.3 多重多元線性迴歸

多元指的是有多個因變量的模型。多重多元線性迴歸的模型,可以寫成:
Y=AX+BY=AX+B
在這裏插入圖片描述
計算出A,BA, B的值。

上面的數據中有8條數據,每條數據有3個自變量,所有自變量構成的數據矩陣就是3行8列的;有2個因變量,因變量矩陣是2行8列的。所以參數矩陣A就是2行3列的,參數矩陣b就是2行一列的。

# 3.3 代碼實現
X = tf.constant([[3, 6, 8, 10, 2, 16, 17, 26],[3, 5, 8, 9, 1, 14, 17, 26],[5, 9, 14, 16, 3, 27, 31, 48]], dtype=tf.float32)
Y = tf.constant([[4, 7, 9, 11, 2, 17, 20, 28],[-3, -7, -13, -14, -2, -25, -28, -44]], dtype=tf.float32)
A = tf.Variable(tf.zeros((2, 3)), dtype=tf.float32, name='A')  # 2行3列
B = tf.Variable(tf.ones((2, 1)), dtype=tf.float32, name='B')  # 2行一列
# 優化器
optimizer = tf.keras.optimizers.SGD(learning_rate=0.000022)   

@tf.function  
def modelout():
    y_modelout = tf.add(tf.matmul(A, X), B)
    return y_modelout

@tf.function    
def costfunc():    
    cost = tf.reduce_sum(tf.square(Y - modelout())) 
    return cost

@tf.function
def train(epoch):  
    for _ in tf.range(epoch):  
        optimizer.minimize(costfunc, [A, B]) # 最小值
    return costfunc()

cost = train(20000)
y_modelout =  modelout()   
print('最終的最小二乘誤差cost={}'.format(cost.numpy()), '此時A={},'.format(A.numpy()), 'B={}'.format(B.numpy())) 
print('模型輸出值:{}'.format(y_modelout.numpy()))
最終的最小二乘誤差cost=3.6218783855438232 此時A=[[ 0.5145932   0.4480298   0.05619257]
 [-0.23077865  0.02671658 -0.8352975 ]], B=[[0.88975173]
 [1.2307721 ]]
模型輸出值:[[  4.0585837   6.723193    9.377432   10.967032    2.5355456  16.912859
   18.996311   28.615192 ]
 [ -3.5579019  -7.5379944 -12.09589   -14.201325   -1.709961  -24.640688
  -28.132507  -44.16912  ]]

點擊獲得更多項目源碼。歡迎Follow,感謝Star!!! 掃描關注微信公衆號pythonfan,獲取更多。
在這裏插入圖片描述
image

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