引言
前序學習進程中,已經對pytorch基本運算中的求導進行了基礎討論,相關文章鏈接為:
導數運算pytorch基本運算-導數和f-string-CSDN博客
實際上,求導是微分的進一步計算,要想求導的前一步其實是計算微分:
導數表達式:
f ′ ( x ) 或 d y d x f^{'}(x) 或 \frac{dy}{dx} f′(x)或dxdy?
?微分表達式:
f ′ ( x ) d x 或 d y = f ′ ( x ) d x f^{'}(x) dx 或 {dy=f^{'}(x) dx} f′(x)dx或dy=f′(x)dx
導數是某一點處的變化率,微分是某一點附近的變化量。
如果一個函數在多個點進行導數求解,或者說子安多維度上進行導數計算,實際上就是在求梯度。
pytorch自動微分獲取梯度
為完整展示pytorch的梯度計算功能,將測試分為以下部分。
初始定義
首先是引入模塊,完成變量定義:
# 導入模塊
import torch
# 定義變量
x=torch.arange(3.0)
print('x=',x)
這里的輸出結果是:
x= tensor([0., 1., 2.])
需要說明的是,因為pytorch默認對浮點數進行求導,所以定義變量的時候,pyorch.arange()使用了3.0而不是整數3。
緊接著,需要對變量執行梯度運算。
梯度運算標定
梯度運算標定的目的是,聲明要對x進行梯度運算。任何沒有經過提前標定的量,都不能正常執行梯度運算。
# 標記需要對x進行梯度計算
z=x.requires_grad_(True)
print('z=',z)
梯度標定使用requires_grad_(True),就像對話一樣,需要求梯度_(需要)。
代碼運行的效果為:
z= tensor([0., 1., 2.], requires_grad=True)
下一步是定義一個函數。
函數定義
這里定義一個簡單函數:
f ( x ) = 2 x 2 f(x)=2x^{2} f(x)=2x2
具體定義代碼為:
# 點乘定義
m=2*torch.dot(x,x)
print('m=',m)
計算微分對函數開展才有意義,所以必須定義函數,這里只是一個示例,也可以是其他函數。
torch.dot()函數的計算規則為:對位相乘然后求和。
代碼運行效果為:
m= tensor(10., grad_fn=)
這里輸出了兩個部分:
第一部分是10,就是元素對位相乘后求和的效果(2X0X0+2X1X1+2X2X2=10)。
第二部分是grad_fn=,grad_fn的意思是grad_function,就是求導函數的意思,后面的MulBackward0是對求導函數的具體定義。
MulBackward0 表示這是一個乘法操作的梯度函數,具體拆開來:multiplication-backward,字面意思解釋:乘法-反向傳播。
這就是pytorch自動微分的核心機制:它可以自動測算求導函數的類型,比如這是一個自變量相乘的函數,并且指出要用哪種方法,比如這里要用反向傳播法。
到這一步還無法計算微分,只是通過輸出效果知道用反向傳播方法計算微分,然后就是正式使用反向傳播方法計算微分。
梯度計算
微分計算使用的代碼為:
# 執行梯度運算
n=m.backward()
k=x.grad
print('n=',n)
print('k=',k)
這里用了兩步,第一步是定義對函數m調用backward方法求倒數,然后具體是對x求導數,所獲得計算結果為:
n= None
k= tensor([0., 4., 8.])
n對應的其實是方法定義,k才是具體的對x的求導效果。
實際上到這一步,如何用pytorch直接計算導數已經非常清晰:先要標定梯度計算的變量,然后要對函數聲明梯度計算的方法,最后直接計算梯度。完整代碼為:
# 導入模塊
import torch
# 定義變量
x=torch.arange(3.0)
print('x=',x)
# 標記需要對x進行梯度計算
z=x.requires_grad_(True)
print('z=',z)
# 點乘定義
m=2*torch.dot(x,x)
print('m=',m)
# 執行梯度運算
n=m.backward()
k=x.grad
print('n=',n)
print('k=',k)
新的函數
未計算對新函數進行求導運算,需要提前將梯度清零,避免梯度計算效果彼此疊加,出現預料之外的效果。
梯度清零
代碼為:
# 梯度清零
kk=x.grad.zero_()
print('kk=',kk)
代碼運行效果為:
kk= tensor([0., 0., 0.])
定義新函數
代碼為:
# 定義新函數
hh=x.sum()
print('hh=',hh)
這里使用了求和函數sim(),代碼運行效果為:
hh= tensor(3., grad_fn=)
這里也輸出了兩個部分:
第一部分是3,就是元素求和的效果(0+1+2=3)。
第二部分是grad_fn=,grad_fn的意思是grad_function,就是求導函數的意思,后面的SumBackward0是對求導函數的具體定義。
SumBackward0 表示這是一個加法操作的梯度函數,具體拆開來:Sum-backward,字面意思解釋:加法-反向傳播。
導數計算
此時可以直接計算導數,代碼為:
# 定義用backward方法計算導數
nn=hh.backward()
print('nn=',nn)
# 導數計算
tt=x.grad
print('tt=',tt)
代碼運行效果為:
nn= None
tt= tensor([1., 1., 1.])
因為是各個變量直接疊加,所以每個變量前的系數都是1,所以導數運算的結果是[1.0,1.0,1.0].
此時的完整代碼為:
# 導入模塊
import torch
# 定義變量
x=torch.arange(3.0)
print('x=',x)
# 標記需要對x進行梯度計算
z=x.requires_grad_(True)
print('z=',z)
# 點乘定義
m=2*torch.dot(x,x)
print('m=',m)
# 執行梯度運算
n=m.backward()
k=x.grad
print('n=',n)
print('k=',k)
# 梯度清零
kk=x.grad.zero_()
print('kk=',kk)
# 定義新函數
hh=x.sum()
print('hh=',hh)
# 定義用backward方法計算導數
nn=hh.backward()
print('nn=',nn)
# 導數計算
tt=x.grad
print('tt=',tt)
完整的輸出效果為:
x= tensor([0., 1., 2.])
z= tensor([0., 1., 2.], requires_grad=True)
m= tensor(10., grad_fn=)
n= None
k= tensor([0., 4., 8.])
kk= tensor([0., 0., 0.])
hh= tensor(3., grad_fn=)
nn= None
tt= tensor([1., 1., 1.])
梯度清零操作的討論
前述有一個梯隊清零的操作,如果沒有這步操作,輸出效果會如何變化,這里直接給出完整代碼來測試。給出完整代碼為:
# 導入模塊
import torch
# 定義變量
x=torch.arange(3.0)
print('x=',x)
# 標記需要對x進行梯度計算
z=x.requires_grad_(True)
print('z=',z)
# 點乘定義
m=2*torch.dot(x,x)
print('m=',m)
# 執行梯度運算
n=m.backward()
k=x.grad
print('n=',n)
print('k=',k)
# 梯度清零
#kk=x.grad.zero_()
#print('kk=',kk)
# 定義新函數
hh=x.sum()
print('hh=',hh)
# 定義用backward方法計算導數
nn=hh.backward()
print('nn=',nn)
# 導數計算
tt=x.grad
print('tt=',tt)
此時的輸出效果為:
x= tensor([0., 1., 2.])
z= tensor([0., 1., 2.], requires_grad=True)
m= tensor(10., grad_fn=)
n= None
k= tensor([0., 4., 8.])
hh= tensor(3., grad_fn=)
nn= None
tt= tensor([1., 5., 9.])
這里可以看到sum()函數的梯度輸出為:[1.,5.,9.],這個結果的來源其實是:[0., 4., 8.]+[1., 1., 1.]=[1., 5., 9.]。
此處可見,及時將梯度清零很有必要。
總結
掌握了通過python+pytorch執行梯度運算的基本技巧。