簡易微分
假設 $(f(x)=0.6*x^2+5)$,微分函數為 $(f'(x)=0.6*2x )$。當 x = 2 時,$(f'(2)= 0.6 *2 * 2 = 2.4 )$,也就是說,x = 2 時的斜率是 2.4。
因為要對 x 微分,所以設定 x 變數時需加上 requires_grad = True,告知這是要進行微分的變數,然後用 y.backward() 開始計算微分,最後再由 x.grad 取出斜率。
請特別注意,x 值必需是小數才能計算微分,整數是無法計算的。
import torch
x=torch.tensor(2., requires_grad = True)
y=0.6 * x.square() + 5
y.backward()
print(x.grad)
計算多個 x 位置的微分
上述公式中,如果要計算 x 分別在 [1, 2, 3, 4] 這 4 處的斜率,張量宣告如下。
x = torch.tensor([1., 2., 3., 4.], requires_grad = True)
微分後 f'(x) 會產生 4 個值,分別為 [1.2000, 2.4000, 3.6000, 4.8000],結果是陣列而非純量,所以在 y.backward() 時需設定 gradient(斜率) 給定 4 個位置來儲存微分後的值,指定 4 個存放位置的方式如下。
y = x.square() y.backward(gradient=torch.ones_like(y)) 或 y.backward(gradient=torch.ones([x.shape[0]]))
4 個存放位置的初始值必需為 1,因為微分後的結果會跟存放的值相乘,所以要用 torch.ones(),不能使用 torch.zeros()。
torch.ones_like(y) 是建立一個跟 y 一模一樣形狀的 tensor。另外 x.shape 是二維的,取其大小必需是 x.shape[0]。
微分後的結果為陣列,而 y.backward() 又沒有指定存放的位置,則會出現只允許標量輸出的錯誤。
RuntimeError: grad can be implicitly created only for scalar outputs
完整代碼如下
import torch x = torch.tensor([1.,2.,3.,4.], requires_grad=True) y = x.square() y.backward(torch.ones_like(y)) print(x.grad) 結果 : tensor([2., 4., 6., 8.])
$(\sum)$微分
假設 $(f(x)=\sum_{i=1}^{4}a_ix^2+b_i )$,a 是 [0.1, 0.2, 0.3, 0.4] 的集合,b 是 [1, 2, 3, 4] 的集合。
$(f'(x)=\sum_{i=1}^{4}2a_ix = 2(0.1+0.2+0.3+0.4)x=2x)$,所以當 x = 2 時的微分值是 2*2 = 4,相關代碼如下
import torch a=torch.tensor([0.1, 0.2, 0.3, 0.4]) b=torch.tensor([1., 2., 3., 4.]) x=torch.tensor([2.], requires_grad=True) y=(a*x.square()+b).sum() y.backward() print(x.grad) 結果: tensor([4.])
偏微分
偏微分其實只考慮單一層面的影響。比如我是賣便當的,影響我淨利的有菜價、人工成本、電價、瓦斯價、房屋租金。我現在只想知道,電價上升一塊錢,會影響我多少收益,這稱為斜率,也就是電價對淨利的偏微分。
假設公式為 $(f(x,y)=\sum_{i=1}^{n}a_ix^2+b_iy^2)$,a 是 [0.1, 0.2, 0.3, 0.4] 的集合,b 是 [1, 2, 3, 4] 的集合。
對 x 的偏微分為 $(\frac{f(x, y)}{\sigma x}=\sum_{i=1}^{4}2a_ix)$,其意是 x 升 1 單位,z 升多少。
對 y 的偏微分為 $(\frac{f(x, y)}{\sigma y}=\sum_{i=1}^{4}2b_iy)$,其意是 y 升 1 單位,z 升多少。
import torch
a=torch.tensor([0.1, 0.2, 0.3, 0.4])
b=torch.tensor([1., 2., 3., 4.])
x=torch.tensor(2., requires_grad=True)
y=torch.tensor(3., requires_grad=True)
f=(a*x.square()+b*y.square()).sum()
f.backward()
print("x偏微階 :", x.grad)
print("y偏微階 :", y.grad)
結果:
x偏微階 : tensor(4.)
y偏微階 : tensor(60.)
梯度下降
假設要使用梯度下降計算 $(y=x^2)$ 的極小值,x 一開始由 -5 開始逼近,經由基礎梯度下降的說明,每次逼近的公式為$(x_{t+1}=x_{t} – f'(x_t) * lr)$
先定義 x 張量,切記一定要寫 5. ,因為只有小數才能進行微分。
x=torch.tensor([-5.], requires_grad=True)
定義公式 $(y = x^2)$,這個公式在每次 x 變更時都需重新定義,所以一定要寫在迴圈中,然後用 y.backward() 進行微分。
因為 x 是標量,所以 y.backward() 不需給存放的位置
for i in range(epochs):
y=x.square()
y.backward()
接下來逼近 x ,但請注意 x 值只要一改變,就會被自動追蹤計算的過程,所以手動更改 x 值時,一定要放在 with torch.no_grad() 區塊中,這個目的是在告知 pytorch,區塊中的計算請不要追蹤。
如果不放在區塊中,只能重定 x = torch.tensor([ x – x.grad * lr], requires_grad = True),但這會影響效能。
另外在區塊中,只能寫 x -= …. ,如果寫成 x = x – x.grad * lr,是會出現錯誤的。
with torch.no_grad():
x -= x.grad * lr#不可以寫成 x = x - x.grad *0.01
最後一定要清除 x.grad,如果不清除,前後的結果會累加進去
x.grad.zero_()
完整代碼如下
import torch
epochs=1000
lr=0.01
x=torch.tensor([-5.], requires_grad=True)
for i in range(epochs):
y=x.square()
y.backward()
with torch.no_grad():#torch會自動追蹤x的任何計算,所以要手動調整x時,要用 no_grad 取消追蹤
x -= x.grad * lr#不可以寫成 x = x - x.grad *0.01
x.grad.zero_()#最後一定要清除 x.grad, 不然會累加上去
print(x)
結果 :
........
tensor([-9.1231e-09], requires_grad=True)
tensor([-8.9406e-09], requires_grad=True)
tensor([-8.7618e-09], requires_grad=True)
tensor([-8.5866e-09], requires_grad=True)
tensor([-8.4148e-09], requires_grad=True)
detach
torch 的張量模式有時需轉成 numpy 格式進行計算或繪圖,此時可以用 .numpy() 進行轉換。
但張量若宣告為 requires_grad = True 時,在這狀態下處於追蹤模式,是不能轉成 numpy 格式的,必需使用 detach() 暫時分離追蹤模式。
如下代碼,x 宣告為追蹤模式,y 也隨之變成追蹤模式,所以都要先 detach 後才能轉成 numpy 格式,z 就不用分離了。
import torch
x=torch.tensor([1.,2.,3.,4.], requires_grad=True)
y=x.square()
z=torch.tensor([1.,2.,3.,4.])
print(x.detach().numpy())
print(y.detach().numpy())
print(z.numpy())
結果 :
[1. 2. 3. 4.]
[ 1. 4. 9. 16.]
[1. 2. 3. 4.]
torch.no_grad區塊
在 torch.no_grad 區塊是屬於分離狀態的,所以可以直接使用 numpy() 轉換,但如果脫離了區塊範圍,就要加上 detach() 方法
底下代碼藍色部份,分別是區塊內及區塊外的使用方法。
import torch
epochs=1000
lr=0.01
x=torch.tensor([-5.], requires_grad=True)
for i in range(epochs):
y=torch.square(x)
y.backward()
with torch.no_grad():
x -= x.grad * lr#不可以寫成 x = x - x.grad *0.01
x.grad.zero_()#最後一定要清除 x.grad, 不然會累加上去
print('區塊內 : ', x.numpy()[0])
print('區塊外 : ', x.detach().numpy()[0])
結果 :
區塊內 : -8.7618e-09
區塊外 : -8.7618e-09
區塊內 : -8.586564e-09
區塊外 : -8.586564e-09
區塊內 : -8.414833e-09
區塊外 : -8.414833e-09
GPU運算
如果交由 GPU運算,要將 GPU 裏的變數轉成 numpy,需使用 .cpu() 將變數 copy 到主機版的 Ram, 後再轉成 numpy()。底下代碼藍色部份,分別是區塊內及區塊外的使用方法。
import torch epochs=1000 lr=0.01 #x=torch.tensor([-5.], requires_grad=True, device="cuda") x=torch.tensor([-5.], requires_grad=True) for i in range(epochs): y=torch.square(x) y.backward() with torch.no_grad(): x -= x.grad * lr#不可以寫成 x = x - x.grad *0.01 x.grad.zero_()#最後一定要清除 x.grad, 不然會累加上去 print('區塊內 : ', x.cpu().numpy()[0]) print('區塊外 : ', x.cpu().detach().numpy()[0]) 結果 : 區塊內 : -8.7618e-09 區塊外 : -8.7618e-09 區塊內 : -8.586564e-09 區塊外 : -8.586564e-09 區塊內 : -8.414833e-09 區塊外 : -8.414833e-09
