從頭學pytorch(二) 自動求梯度

PyTorch提供的autograd包可以根據輸⼊和前向傳播過程⾃動構建計算圖,並執⾏反向傳播。html

Tensor

Tensor的幾個重要屬性或方法python

  • .requires_grad 設爲true的話,tensor將開始追蹤在其上的全部操做
  • .backward()完成梯度計算
  • .grad屬性 計算的梯度累積到.grad屬性
  • .detach()解除對一個tensor上操做的追蹤,或者用with torch.no_grad()將不想被追蹤的操做代碼塊包裹起來.
  • .grad_fn屬性 該屬性即建立Tensor的Function類的類型,即該Tensor是由什麼運算得來的

幾個例子具體地解釋一下:函數

import torch
x = torch.ones(2, 2, requires_grad=True)
print(x)
print(x.grad_fn)

y = x+2
print(y)
print(y.grad_fn)

z = y*y*3
out=z.mean()
print(z,out)

輸出ui

tensor([[1., 1.],
        [1., 1.]], requires_grad=True)
None
tensor([[3., 3.],
        [3., 3.]], grad_fn=<AddBackward>)
<AddBackward object at 0x0000018752434B70>


tensor([[27., 27.],
        [27., 27.]], grad_fn=<MulBackward>) tensor(27., grad_fn=<MeanBackward1>)

y由加法獲得,因此y.grad_fn= ,x直接建立,其x.grad_fn=None. x這種直接建立的又稱爲葉子節點. spa

print(x.is_leaf, y.is_leaf) # True False

能夠用.requires_grad_()來用in-place的方式改變requires_grad屬性.code

a = torch.randn(2, 2) # 缺失狀況下默認 requires_grad = False
a = ((a * 3) / (a - 1))
print(a.requires_grad) # False
a.requires_grad_(True)
print(a.requires_grad) # True
b = (a * a).sum()
print(b.grad_fn)

輸出htm

False
True
<SumBackward0 object at 0x0000018752434D30>

梯度

所計算的梯度都是結果變量關於建立變量的梯度。
好比對:blog

x = torch.ones(2, 2, requires_grad=True)
print(x)
print(x.grad_fn)

y = x+2
print(y)
print(y.grad_fn)

z = y*3
z.backward(torch.ones_like(z))
print(y.grad) #None  
print(x.grad)

輸出element

None
tensor([[3., 3.],
        [3., 3.]])

上述代碼至關於建立了一個動態圖,其中x是咱們建立的變量,y和z都是由於x的改變會改變的結果變量. 因此在這個動態圖裏可以求的梯度只有\(\frac{\partial{z}}{\partial{x}}\),\(\frac{\partial{y}}{\partial{x}}\)文檔

爲何l.backward(gradient)須要傳入一個和l一樣形狀的gradient?
對於l.backward()而言,當l是標量時,能夠不傳參,至關於l.backward(torch.tensor(1.))
當l不是標量時,須要傳入一個和l同shape的gradient。

假設 x 通過一番計算獲得 y,那麼 y.backward(w) 求的不是 y 對 x 的導數,而是 l = torch.sum(y*w) 對 x 的導數。w 能夠視爲 y 的各份量的權重,也能夠視爲遙遠的損失函數 l 對 y 的偏導數(這正是函數說明文檔的含義)。特別地,若 y 爲標量,w 取默認值 1.0,纔是按照咱們一般理解的那樣,求 y 對 x 的導數

簡單地說就是,張量對張量無法求導,因此咱們須要人爲地定義一個w,把一個非標量的Tensor經過torch.sum(y*w)的形式轉換成標量。咱們本身定義的這個w的不一樣,固然最後獲得的梯度就不一樣.一般定義爲全1.也就是認爲Tensor y中的每個變量的重要性是等同的.

另外一個角度的理解就是,y是一個tensor,是一個向量,有N個標量,這每個標量都與x有關。對這N個標量咱們須要賦以不一樣的權重,以顯示y中每個標量受到x影響的程度.

好比對

import torch
x = torch.ones(2, 2, requires_grad=True)
print(x)
print(x.grad_fn)

y = x+2
print(y)
print(y.grad_fn)

z = y*3
print(z.shape)
w1=torch.Tensor([[1,2],[1,2]])
z.backward([w1])
print(x.grad)

x.grad.data.zero_()
w2=torch.Tensor([[1,1],[1,1]])
z.backward([w2])
print(x.grad)

輸出

tensor([[1., 1.],
        [1., 1.]], requires_grad=True)
None
tensor([[3., 3.],
        [3., 3.]], grad_fn=<AddBackward>)
<AddBackward object at 0x00000187524A6828>
torch.Size([2, 2])
tensor([[3., 6.],
        [3., 6.]])
tensor([[3., 3.],
        [3., 3.]])

對w1和w2而言,z.backward()之後x.grad是不一樣的。
注意:梯度是累加的,因此第二次計算以前咱們作了清零的操做:x.grad.data.zero_()

能夠參考:
https://zhuanlan.zhihu.com/p/29923090
http://www.javashuo.com/article/p-wgrlmkdc-kd.html

再來看看中斷梯度追蹤的例子:

x = torch.tensor(1.0, requires_grad=True)
y1 = x ** 2 
with torch.no_grad():
    y2 = x ** 3
y3 = y1 + y2
    
print(x.requires_grad)
print(y1, y1.requires_grad) # True
print(y2, y2.requires_grad) # False
print(y3, y3.requires_grad) # True

輸出:

True
tensor(1., grad_fn=<PowBackward0>) True
tensor(1.) False
tensor(2., grad_fn=<ThAddBackward>) True

反向傳播,求梯度

y3.backward()
print(x.grad)

輸出:

tensor(2.)

爲何是2呢?$ y_3 = y_1 + y_2 = x^2 + x^3$,當 \(x=1\)\(\frac {dy_3} {dx}\) 不該該是5嗎?事實上,因爲 \(y_2\) 的定義是被torch.no_grad():包裹的,因此與 \(y_2\) 有關的梯度是不會回傳的,只有與 \(y_1\) 有關的梯度纔會回傳,即 \(x^2\)\(x\) 的梯度。

上面提到,y2.requires_grad=False,因此不能調用 y2.backward(),會報錯:

RuntimeError: element 0 of tensors does not require grad and does not have a grad_fn

此外,若是咱們想要修改tensor的數值,可是又不但願被autograd記錄(即不會影響反向傳播),那麼我麼能夠對tensor.data進行操做。

x = torch.ones(1,requires_grad=True)

print(x.data) # 仍是一個tensor
print(x.data.requires_grad) # 可是已是獨立於計算圖以外

y = 2 * x
x.data *= 100 # 只改變了值,不會記錄在計算圖,因此不會影響梯度傳播

y.backward()
print(x) # 更改data的值也會影響tensor的值
print(x.grad)

輸出:

tensor([1.])
False
tensor([100.], requires_grad=True)
tensor([2.])
相關文章
相關標籤/搜索