Pytorch 微分

      在〈Pytorch 微分〉中尚無留言

標量-Scalar

標量 (scalar) 就是只有一個數字,不屬於陣列,又稱為 0 維數組,也就是所謂的純量,請查看如下執行結果。

沒有 “[]” 包含的,就是標量。

import torch as tc
print('0 維       : 標量 ',tc.tensor(5.))
print('1 維 0 元素 : 非標量',tc.tensor([]))
print('1 維 1 元素 : 非標量',tc.tensor([5.]))
print('1 維 2 元素 : 非標量', tc.tensor([5., 6.]))

結果:
0維        : 標量  tensor(5.)
1維 0 元素 : 非標量 tensor([])
1維 1 元素 : 非標量 tensor([5.])
1維 2 元素 : 非標量 tensor([5., 6.])

簡易微分

假設 $(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 是跟 torch 說這是要進行追蹤(微分)的變數,然後用 y.backward() 開始計算微分,最後再由 x.grad 取出斜率。

請特別注意,x 值必需是小數才能計算微分,整數是無法計算的。

import torch as tc
x=tc.tensor(2., requires_grad = True)
y=0.6 * tc.square(x) + 5
y.backward()
print(x.grad)

計算多個 x 位置的微分

上述公式中,如果要計算 x 分別在 [1, 2, 3, 4] 這 4 處的斜率,張量宣告如下。

x = tc.tensor([1., 2., 3., 4.], requires_grad = True)

微分後 f'(x) 會產生 4 個值,分別為 [1.2000, 2.4000, 3.6000, 4.8000],結果是 “非標量”,所以在 y.backward() 時需給定 4 個位置來儲存微分後的值,指定 4 個存放位置的方式如下。

y = tc.square(x)
y.backward(gradient=tc.ones([x.shape[0]]))

4 個存放位置的初始值必需為 1,因為微分後的結果會跟存放的值相乘,所以要用 tc.ones(),不能使用 tc.zeros()。

另請注意,x.shape 是二維的,要取得其大小,必需是 x.shape[0]

微分後的結果若不是標量,而 y.backward() 又沒有指定存放的位置,則會出現只允許標量(scalar)輸出的錯誤。

網路上一些教學使用 from torch.autograd import Variable,把非標量放在 Variable 中,這個方法已經被捨棄掉了,請不要用。

RuntimeError: grad can be implicitly created only for scalar outputs

完整代碼如下

import torch as tc
x = tc.tensor([1.,2.,3.,4.], requires_grad=True)
y = tc.square(x)
y.backward(tc.ones(x.shape[0]))
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 as tc
a=tc.tensor([0.1, 0.2, 0.3, 0.4])
b=tc.tensor([1., 2., 3., 4.])

x=tc.tensor([2.], requires_grad=True)
y=tc.sum(a*tc.square(x)+b)
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)$

對 y 的偏微分為 $(\frac{f(x, y)}{\sigma y}=\sum_{i=1}^{4}2b_ix)$

import torch as tc
a=tc.tensor([0.1, 0.2, 0.3, 0.4])
b=tc.tensor([1., 2., 3., 4.])

x=tc.tensor(2., requires_grad=True)
y=tc.tensor(3., requires_grad=True)
z=tc.sum(a*tc.square(x)+b*tc.square(y))
z.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=tc.tensor([-5.], requires_grad=True)

定義公式 $(y = x^2)$,這個公式在每次 x 變更時都需重新定義,所以一定要寫在迴圈中,然後用 y.backward() 進行微分。

因為 x 是標量,所以 y.backward() 不需給存放的位置

for i in range(epochs): 
    y=tc.square(x) 
    y.backward()

接下來逼近 x ,但請注意 x 值只要一改變,就會被自動追蹤計算的過程,所以手動更改 x  值時,一定要放在 with tc.no_grad() 區塊中,這個目的是在告知 pytorch,區塊中的計算請不要追蹤。

如果不放在區塊中,只能重定 x = tc.tensor([ x – x.grad * lr], requires_grad = True),但這會影響效能。

另外在區塊中,只能寫 x -= …. ,如果寫成 x = x – x.grad * lr,是會出現錯誤的。

with tc.no_grad():
    x -= x.grad * lr#不可以寫成 x = x - x.grad *0.01

最後一定要清除 x.grad,如果不清除,前後的結果會累加進去

    x.grad.zero_()

完整代碼如下

import torch as tc
epochs=1000
lr=0.01
x=tc.tensor([-5.], requires_grad=True)
for i in range(epochs):
    y=tc.square(x)
    y.backward()
    with tc.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 as tc
x=tc.tensor([1.,2.,3.,4.], requires_grad=True)
y=tc.square(x)
z=tc.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 as tr
epochs=1000
lr=0.01
#x=tr.tensor([-5.], requires_grad=True)
x=tr.tensor([-5.], requires_grad=True)
for i in range(epochs):
    y=tr.square(x)
    y.backward()
    with tr.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 as tr
epochs=1000
lr=0.01
#x=tr.tensor([-5.], requires_grad=True, device="cuda")
x=tr.tensor([-5.], requires_grad=True)
for i in range(epochs):
    y=tr.square(x)
    y.backward()
    with tr.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

發佈留言

發佈留言必須填寫的電子郵件地址不會公開。 必填欄位標示為 *