簡 介: 初步分析了求解梯度的部分。詳細的過程可以參見以下文獻: pytorch自動求梯度—詳解 關鍵詞
: 自動求導,Paddle
01 自動微分
PaddlePaddle
的神經網絡核心是自動微分,本篇文章主要為你介紹如何使用飛槳的自動微分,以及飛槳的自動微分機制,幫助你更好的使用飛槳進行訓練。
一、背景
神經網絡是由節點和節點間的互相連接配接組成的。網絡中每層的每個節點代表一種特定的函數,來對輸入進行計算。每個函數都是由不同參數(權重
w
和偏置
b
)組成。神經網絡訓練的過程,就是不斷讓這些函數的參數進行學習、優化,以能夠更好的處理後面輸入的過程。
為了讓神經網絡的判斷更加準确,首先需要有衡量效果的工具,于是損失函數應運而生。如果你想要神經網絡的效果好,那麼就要讓損失函數盡可能的小,于是深度學習引入了能夠有效計算函數最小值的算法–梯度下降等優化算法,以及參數優化更新過程–反向傳播。
- 前向傳播是輸入通過每一層節點計算後得到每層輸出,上層輸出又作為下一層的輸入,最終達到輸出層。然後通過損失函數計算得到
值。loss
- 反向傳播是通過
值來指導前向節點中的函數參數如何改變,并更新每層中每個節點的參數,來讓整個神經網絡達到更小的loss
loss
自動微分機制就是讓你隻關注組網中的前向傳播過程,然後飛槳架構來自動完成反向傳播過程,進而來讓你從繁瑣的求導、求梯度的過程中解放出來。
二、應用Paddle自動微分機制
本文通過一個比較簡單的模型來還原飛槳的自動微分過程。 本示例基于
Paddle2.0
編寫。
import paddle
from paddle.vision.models import vgg11
import paddle.nn.functional as F
import numpy as np
print(paddle.__version__)
2.2.1
1、定義模型
本案例首先定義網絡。因為本示例着重展示如何使用飛槳進行自動微分,故組網部分不過多展開,直接使用高層
API
中封裝好的模型
vgg11
。
然後随機初始化一個輸入
x
,和對應标簽
label
model = vgg11()
x = paddle.rand([1,3,224,224])
label = paddle.randint(0,1000)
label 取值為一個 0 - 1000之間的随機值:
Tensor(shape=[1], dtype=int64, place=CPUPlace, stop_gradient=True,
[578])
2、前向傳播
然後将輸入傳入到模型中,進行前向傳播過程。
predicts = model(x)
print(predicts)
Tensor(shape=[1, 1000], dtype=float32, place=CPUPlace, stop_gradient=False,
[[-5.86728096, 4.47268057, -0.95992291, 0.23020555, 1.00721347,
2.99545169, -2.75388002, -2.63786125, -3.75683165, 0.73450726,
0.85474956, -0.75945181, -5.11652756, 0.88256705, -4.56009960,
.....
1.58102369, -1.69987798, -1.06336939, -0.56181955, 0.71741229,
-3.75649190, 0.65584004, 0.91495293, -0.22558716, 0.40234384,
1.70077145, 1.04505992, -3.24514723, -0.23471458, -0.45503062]])
前向傳播結束後,你就得到模型的預測結果
predicts
,這時可以使用飛槳中的對應損失函數
API
進行損失函數的計算。該例子中使用
cross_entropy
來計算損失函數,來衡量模型的預測情況。
3、計算損失與反向傳播
loss = F.cross_entropy(predicts, label)
print(loss)
Tensor(shape=[1], dtype=float32, place=CPUPlace, stop_gradient=False,
[12.34226704])
随後進行反向傳播,在飛槳中你隻需要調用
backward()
即可自動化展開反向傳播過程。各梯度儲存在
grad
屬性中。
loss.backward()
4、優化器
然後來定義優化器,本例子中使用
Adam
優化器,設定
learning_rate
為
0.001
,并把該模型的所有參數傳入優化器中。
optim = paddle.optimizer.Adam(learning_rate=0.001, parameters=model.parameters())
最後通過
step
來開始執行優化器,并進行模型參數的更新
optim.step()
通過以上步驟,你已經完成了一個神經網絡前向傳播、反向傳播的所有過程。快自己動手試試吧!
三、自動微分使用說明
此章主要介紹飛槳中所有自動微分過程中會使用到的方法、屬性等。屬于第二部分的擴充閱讀。
1、stop grandient屬性
飛槳中的
Tensor
有
stop_gradient
屬性,這個屬性可以檢視一個
Tensor
是否計算并傳播梯度。
- 如果為
,則該True
不會計算梯度,并會阻絕Tensor
的梯度傳播。Autograd
- 反之,則會計算梯度并傳播梯度。使用者自行建立的的
,預設Tensor
stop_gradient
,即預設不計算梯度;模型參數的True
預設都為stop_gradient
,即預設計算梯度。False
import paddle
a = paddle.to_tensor([1.0, 2.0, 3.0])
b = paddle.to_tensor([1.0, 2.0, 3.0], stop_gradient=False) # 将b設定為需要計算梯度的屬性
print(a.stop_gradient)
print(b.stop_gradient)
True
False
a.stop_gradient = False
print(a.stop_gradient)
False
2、反向計算
接下來,本文用一個簡單的計算圖來了解如何調用
backward()
函數。開始從目前
Tensor
開始計算反向的神經網絡,傳導并計算計算圖中
Tensor
的梯度。
import paddle
x = paddle.to_tensor([1.0, 2.0, 3.0], stop_gradient=False)
y = paddle.to_tensor([4.0, 5.0, 6.0], stop_gradient=False)
z = x ** 2 + 4 * y
假設上面建立的
x
和
y
分别是神經網絡中的參數,
z
為神經網絡的損失值
loss
$${{\partial z} \over {\partial x}} = 2x,\space \space \space {{\partial z} \over {\partial y}} = 4$$
對
z
調用
backward()
,飛槳即可以自動計算
x
y
的梯度,并且将他們存進
grad
z.backward()
print("Tensor x's grad is: {}".format(x.grad))
print("Tensor y's grad is: {}".format(y.grad))
Tensor x's grad is: Tensor(shape=[3], dtype=float32, place=CPUPlace, stop_gradient=False,
[2., 4., 6.])
Tensor y's grad is: Tensor(shape=[3], dtype=float32, place=CPUPlace, stop_gradient=False,
[4., 4., 4.])
此外,飛槳預設會釋放反向計算圖。如果在
backward()
之後繼續添加
OP
,需要将
backward()
中的
retain_graph
參數設定為
True
,此時之前的反向計算圖會保留。
溫馨小提示:将其設定為
False
會更加節省記憶體。因為他的預設值是
False
,是以也可以直接不設定此參數。
import paddle
x = paddle.to_tensor([1.0, 2.0, 3.0], stop_gradient=False)
y = x + 3
y.backward(retain_graph=True) # 設定retain_graph為True,保留反向計算圖
print("Tensor x's grad is: {}".format(x.grad))
Tensor x's grad is: Tensor(shape=[3], dtype=float32, place=CPUPlace, stop_gradient=False,
[1., 1., 1.])
3、梯度清除
因為
backward()
會累積梯度,是以飛槳還提供了
clear_grad()
函數來清除目前
Tensor
import paddle
import numpy as np
x = np.ones([2, 2], np.float32)
inputs2 = []
for _ in range(10):
tmp = paddle.to_tensor(x)
tmp.stop_gradient = False
inputs2.append(tmp)
ret2 = paddle.add_n(inputs2)
loss2 = paddle.sum(ret2)
loss2.backward()
print("Before clear {}".format(loss2.gradient()))
loss2.clear_grad()
print("After clear {}".format(loss2.gradient()))
Before clear [1.]
After clear [0.]
(1)說明
如果不貂絨
backward()
則無法産生
gradient()
:
print("Before clear {}".format(loss2.gradient()))
print(ret2.gradient())
loss2.clear_grad()
ret2.clear_grad()
print("After clear {}".format(loss2.gradient()))
print(ret2.gradient())
隻有貂絨 backward(),才可以獲得對應的梯度:
Before clear [1.]
[[1. 1.]
[1. 1.]]
After clear [0.]
[[0. 0.]
[0. 0.]]
四、自動微分運作機制
本章主要介紹飛槳在實作反向傳播進行自動微分計算時,内部是如何運作工作的。此部分為選讀部分,更多是介紹飛槳内部實作機制,可以選擇跳過,跳過不會影響你的正常使用。
1、自動微分
飛槳的自動微分是通過
trace
的方式,記錄前向
OP
的執行,并自動建立反向
var
和添加相應的反向
OP
,然後來實作反向梯度計算的。
▲ 圖1.4.1 自動微分的流程圖
2、舉例
下面本文用一些的例子,來模拟這個過程。
例子一:首先用一個比較簡單的例子來讓你了解整個過程。
(1)測試代碼
import paddle
a = paddle.to_tensor(2.0, stop_gradient=False)
b = paddle.to_tensor(5.0, stop_gradient=True)
c = a * b
c.backward()
print("Tensor a's grad is: {}".format(a.grad))
print("Tensor b's grad is: {}".format(b.grad))
Tensor a's grad is: Tensor(shape=[1], dtype=float32, place=CPUPlace, stop_gradient=False,
[5.])
Tensor b's grad is: None
(2)分析
在上面代碼中
c.backward()
執行前,你可以了解整個計算圖是這樣的:
▲ 圖1.4.2 PaddlePaddle梯度求解
當建立
Tensor
,
Tensor
的
stop_grad=False
時,會自動為此
Tensor
建立一個反向
Tensor
。在此例子中,
a
的反向
Tensor
就是
a_grad
。在
a_grad
中,會記錄他的反向
OP
,因為
a
沒有作為任何反向
op
的輸入,是以它的
grad_op
None
當執行
OP
時,會自動建立反向
OP
,不同的
OP
建立反向
OP
的方法不同,傳的内容也不同。本文以這個乘法
OP
為例:
- -乘法OP的反向OP,即MulBackward的輸入是,正向OP的兩個輸入,以及正向OP的輸出Tensor的反向Tensor。在此例子中就是,a、b、c_grad
- -乘法OP的反向OP,即MulBackward的輸出是,正向OP的兩個輸入的反向Tensor(如果輸入是stop_gradient=True,則即為None)。在此例子中就是,a_grad、None(b_grad)
- -乘法OP的反向OP,即MulBackward的grad_pending_ops是自動建構反向網絡的時候,讓這個反向op知道它下一個可以執行的反向op是哪一個,可以了解為反向網絡中,一個反向op指向下一個反向op的邊。
當
c
通過乘法
OP
被建立後,
c
會建立一個反向
Tensor
c_grad,
他的
grad_op
為該乘法
OP
OP
,即
MulBackward
調用
backward()
後,正式開始進行反向傳播過程,開始自動計算微分。
▲ 圖1.4.3 反向計算梯度過程
例子二:用一個稍微複雜一點的例子讓你深入了解這個過程。
import paddle
a = paddle.to_tensor(2.0, stop_gradient=False)
b = paddle.to_tensor(5.0, stop_gradient=False)
c = a * b
d = paddle.to_tensor(4.0, stop_gradient=False)
e = c * d
e.backward()
print("Tensor a's grad is: {}".format(a.grad))
print("Tensor b's grad is: {}".format(b.grad))
print("Tensor c's grad is: {}".format(c.grad))
print("Tensor d's grad is: {}".format(d.grad))
Tensor a's grad is: Tensor(shape=[1], dtype=float32, place=CPUPlace, stop_gradient=False,
[20.])
Tensor b's grad is: Tensor(shape=[1], dtype=float32, place=CPUPlace, stop_gradient=False,
[8.])
Tensor c's grad is: Tensor(shape=[1], dtype=float32, place=CPUPlace, stop_gradient=False,
[4.])
Tensor d's grad is: Tensor(shape=[1], dtype=float32, place=CPUPlace, stop_gradient=False,
[10.])
該例子的正向和反向圖建構過程即:
▲ 圖1.4.4 例子2計算梯度過程
※ 總 結 ※
初步分析了求解梯度的部分。詳細的過程可以參見以下文獻:
■ 相關文獻連結:
■
● 相關圖表連結:
★ 本文來自于CSDN文獻:<
關鍵詞
PaddlePaddle
w
b
-
loss
-
loss
loss
Paddle2.0
import paddle
from paddle.vision.models import vgg11
import paddle.nn.functional as F
import numpy as np
print(paddle.__version__)
2.2.1
API
vgg11
x
label
model = vgg11()
x = paddle.rand([1,3,224,224])
label = paddle.randint(0,1000)
Tensor(shape=[1], dtype=int64, place=CPUPlace, stop_gradient=True,
[578])
predicts = model(x)
print(predicts)
Tensor(shape=[1, 1000], dtype=float32, place=CPUPlace, stop_gradient=False,
[[-5.86728096, 4.47268057, -0.95992291, 0.23020555, 1.00721347,
2.99545169, -2.75388002, -2.63786125, -3.75683165, 0.73450726,
0.85474956, -0.75945181, -5.11652756, 0.88256705, -4.56009960,
.....
1.58102369, -1.69987798, -1.06336939, -0.56181955, 0.71741229,
-3.75649190, 0.65584004, 0.91495293, -0.22558716, 0.40234384,
1.70077145, 1.04505992, -3.24514723, -0.23471458, -0.45503062]])
predicts
API
cross_entropy
loss = F.cross_entropy(predicts, label)
print(loss)
Tensor(shape=[1], dtype=float32, place=CPUPlace, stop_gradient=False,
[12.34226704])
backward()
grad
loss.backward()
Adam
learning_rate
0.001
optim = paddle.optimizer.Adam(learning_rate=0.001, parameters=model.parameters())
step
optim.step()
Tensor
stop_gradient
Tensor
-
True
Tensor
Autograd
-
Tensor
stop_gradient
True
stop_gradient
False
import paddle
a = paddle.to_tensor([1.0, 2.0, 3.0])
b = paddle.to_tensor([1.0, 2.0, 3.0], stop_gradient=False) # 将b設定為需要計算梯度的屬性
print(a.stop_gradient)
print(b.stop_gradient)
True
False
a.stop_gradient = False
print(a.stop_gradient)
False
backward()
Tensor
Tensor
import paddle
x = paddle.to_tensor([1.0, 2.0, 3.0], stop_gradient=False)
y = paddle.to_tensor([4.0, 5.0, 6.0], stop_gradient=False)
z = x ** 2 + 4 * y
x
y
z
loss
z
backward()
x
y
grad
z.backward()
print("Tensor x's grad is: {}".format(x.grad))
print("Tensor y's grad is: {}".format(y.grad))
Tensor x's grad is: Tensor(shape=[3], dtype=float32, place=CPUPlace, stop_gradient=False,
[2., 4., 6.])
Tensor y's grad is: Tensor(shape=[3], dtype=float32, place=CPUPlace, stop_gradient=False,
[4., 4., 4.])
backward()
OP
backward()
retain_graph
True
False
False
import paddle
x = paddle.to_tensor([1.0, 2.0, 3.0], stop_gradient=False)
y = x + 3
y.backward(retain_graph=True) # 設定retain_graph為True,保留反向計算圖
print("Tensor x's grad is: {}".format(x.grad))
Tensor x's grad is: Tensor(shape=[3], dtype=float32, place=CPUPlace, stop_gradient=False,
[1., 1., 1.])
backward()
clear_grad()
Tensor
import paddle
import numpy as np
x = np.ones([2, 2], np.float32)
inputs2 = []
for _ in range(10):
tmp = paddle.to_tensor(x)
tmp.stop_gradient = False
inputs2.append(tmp)
ret2 = paddle.add_n(inputs2)
loss2 = paddle.sum(ret2)
loss2.backward()
print("Before clear {}".format(loss2.gradient()))
loss2.clear_grad()
print("After clear {}".format(loss2.gradient()))
Before clear [1.]
After clear [0.]
backward()
gradient()
print("Before clear {}".format(loss2.gradient()))
print(ret2.gradient())
loss2.clear_grad()
ret2.clear_grad()
print("After clear {}".format(loss2.gradient()))
print(ret2.gradient())
Before clear [1.]
[[1. 1.]
[1. 1.]]
After clear [0.]
[[0. 0.]
[0. 0.]]
trace
OP
var
OP
import paddle
a = paddle.to_tensor(2.0, stop_gradient=False)
b = paddle.to_tensor(5.0, stop_gradient=True)
c = a * b
c.backward()
print("Tensor a's grad is: {}".format(a.grad))
print("Tensor b's grad is: {}".format(b.grad))
Tensor a's grad is: Tensor(shape=[1], dtype=float32, place=CPUPlace, stop_gradient=False,
[5.])
Tensor b's grad is: None
c.backward()
Tensor
Tensor
stop_grad=False
Tensor
Tensor
a
Tensor
a_grad
a_grad
OP
a
op
grad_op
None
OP
OP
OP
OP
OP
c
OP
c
Tensor
c_grad,
grad_op
OP
OP
MulBackward
backward()
import paddle
a = paddle.to_tensor(2.0, stop_gradient=False)
b = paddle.to_tensor(5.0, stop_gradient=False)
c = a * b
d = paddle.to_tensor(4.0, stop_gradient=False)
e = c * d
e.backward()
print("Tensor a's grad is: {}".format(a.grad))
print("Tensor b's grad is: {}".format(b.grad))
print("Tensor c's grad is: {}".format(c.grad))
print("Tensor d's grad is: {}".format(d.grad))
Tensor a's grad is: Tensor(shape=[1], dtype=float32, place=CPUPlace, stop_gradient=False,
[20.])
Tensor b's grad is: Tensor(shape=[1], dtype=float32, place=CPUPlace, stop_gradient=False,
[8.])
Tensor c's grad is: Tensor(shape=[1], dtype=float32, place=CPUPlace, stop_gradient=False,
[4.])
Tensor d's grad is: Tensor(shape=[1], dtype=float32, place=CPUPlace, stop_gradient=False,
[10.])