Pytorch-自動求導(dǎo)

構(gòu)建深度學(xué)習(xí)模型的基本流程就是:搭建計算圖,求得預(yù)測值,進(jìn)而得到損失,然后計算損失對模型參數(shù)的導(dǎo)數(shù),再利用梯度下降法等方法來更新參數(shù)。

搭建計算圖的過程,稱為“正向傳播”,這個是需要我們自己動手的,因為我們需要設(shè)計我們模型的結(jié)構(gòu)。由損失函數(shù)求導(dǎo)的過程,稱為“反向傳播”,求導(dǎo)是件辛苦事兒,所以自動求導(dǎo)基本上是各種深度學(xué)習(xí)框架的基本功能和最重要的功能之一,PyTorch也不例外。

我們今天來體驗一下PyTorch的自動求導(dǎo)吧,好為后面的搭建模型做準(zhǔn)備。

1、設(shè)置Tensor的自動求導(dǎo)屬性

1) 所有的tensor都有.requires_grad屬性,都可以設(shè)置成自動求導(dǎo)。具體方法就是在定義tensor的時候,讓這個屬性為True,例如:

[1]: import torch
[2]: x = torch.ones(2, 3, requires_grad=True)
[3]: print('x:', x)
x: tensor([[1., 1., 1.],
           [1., 1., 1.]], requires_grad=True)

2)只要這樣設(shè)置了之后,后面由x經(jīng)過運(yùn)算得到的其他tensor,就都有requires_grad=True 屬性。可以通過x.requires_grad來查看這個屬性。例如:

[4]: y = x + 1
[5]: print(y); print(y.requires_grad)
y: tensor([[2., 2., 2.],
        [2., 2., 2.]], grad_fn=<AddBackward0>)
grad: True

3)如果想改變這個屬性,就調(diào)用x.requires_grad_()方法:

[6]: x.requires_grad_(False)
[7]: print(x.requires_grad); print(y.requires_grad)
False
True

注意區(qū)別:x.requires_gradx.requires_grad_()兩個東西,前面是調(diào)用變量的屬性值,后者是調(diào)用內(nèi)置的函數(shù),來改變屬性。

2、來求導(dǎo)吧

下面我們來試試自動求導(dǎo)到底怎么樣。

我們首先定義一個計算圖(計算的步驟):

[1]: import torch

[2]: x = torch.tensor([[1., 2., 3.], [4., 5., 6.]], requires_grad=True)
[3]: y = x + 1
[4]: z = 2 * y * y
[5]: J = torch.mean(z)

注意:

  • 要想使x支持求導(dǎo),必須讓x為浮點類型,否則會報錯:RuntimeError: Only Tensors of floating point dtype can require gradients;
  • 求導(dǎo),只能是【標(biāo)量】對標(biāo)量,或者【標(biāo)量】對向量/矩陣求導(dǎo),針對這一點的具體分析如下:

x、y、z都是tensor,但是size為(2,3)的矩陣。但是J是對z的每一個元素加起來求平均,所以J是標(biāo)量。

試圖z對x求導(dǎo):

[6]: z.backward()

# 報錯:
---------------------------------------------------------------------------
RuntimeError                              Traceback (most recent call last)
<ipython-input-38-40c0c9b0bbab> in <module>
----> 1 z.backward()
...
...

~/anaconda2/envs/py3/lib/python3.6/site-packages/torch/autograd/__init__.py in _make_grads(outputs, grads)
     32             if out.requires_grad:
     33                 if out.numel() != 1:
---> 34                     raise RuntimeError("grad can be implicitly created only for scalar outputs")
     35                 new_grads.append(torch.ones_like(out))
     36             else:

RuntimeError: grad can be implicitly created only for scalar outputs

正確的應(yīng)該是J對x求導(dǎo):

  • PyTorch里面,求導(dǎo)是調(diào)用.backward()方法。直接調(diào)用backward()方法,會計算對計算圖葉節(jié)點(允許求導(dǎo))的導(dǎo)數(shù)。
  • 獲取求得的導(dǎo)數(shù),用.grad方法。
[7]: J.backward()
[8]: x.grad
tensor([[1.3333, 2.0000, 2.6667],
        [3.3333, 4.0000, 4.6667]])

總結(jié)上述過程,構(gòu)建計算圖(正向傳播,F(xiàn)orward Propagation)和求導(dǎo)(反向傳播,Backward Propagation)的過程就是:


3、關(guān)于backward函數(shù)的一些關(guān)鍵問題

3.1 一個計算圖只能backward一次

一個計算圖在進(jìn)行反向求導(dǎo)之后,為了節(jié)省內(nèi)存,這個計算圖就銷毀了。如果你想再次求導(dǎo),就會報錯。
例如:

[1]: import torch
[2]: x = torch.tensor([[1., 2., 3.], [4., 5., 6.]], requires_grad=True)
[3]: y = x + 1
[4]: z = 2 * y * y
[5]: J = torch.mean(z)
[6]: J.backward()  # 正常運(yùn)行
[7]: x.grad
tensor([[1.3333, 2.0000, 2.6667],
        [3.3333, 4.0000, 4.6667]])
[8]: J.backward()    # 報錯
RuntimeError: Trying to backward through the graph a second time, but the buffers have already been freed. Specify retain_graph=True when calling backward the first time.

那么,我還想再次求導(dǎo),該怎么辦呢?

遇到這種問題,一般兩種情況:

  • 你的實際計算,確實需要保留計算圖,不讓子圖釋放

那么,就更改你的backward函數(shù),添加參數(shù)retain_graph=True,重新進(jìn)行backward,這個時候你的計算圖就被保留了,不會報錯。但是這樣會吃內(nèi)存!尤其是,你在大量迭代進(jìn)行參數(shù)更新的時候,很快就會內(nèi)存不足,memory out了。

[1]: import torch
[2]: x = torch.tensor([[1., 2., 3.], [4., 5., 6.]], requires_grad=True)
[3]: y = x + 1
[4]: z = 2 * y * y
[5]: J = torch.mean(z)
[6]: J.backward(retain_graph=True)   # 保留圖
[7]: x.grad
tensor([[1.3333, 2.0000, 2.6667],
        [3.3333, 4.0000, 4.6667]])
[8]: J.backward()    # 正常運(yùn)行

也就是說第8行([8]:J.backward() )想要正常運(yùn)行,則需要在第6行增加retain_graph=True,即第6行改為J.backward(retain_graph=True);換句話說,想要再次求導(dǎo)成功,則需要前一次求導(dǎo)中保留圖。

  • 你實際根本沒必要對一個計算圖backward多次,而你不小心多跑了一次backward函數(shù)。

這種情況在Jupyter中的比較常見,粗暴的解決辦法是:重啟Jupyter核,重運(yùn)行一遍所有代碼cell。

3.2 不是標(biāo)量也可以用backward()函數(shù)來求導(dǎo)

確實是,不一定只有標(biāo)量能求導(dǎo),這里面的玄機(jī)在哪里呢?文檔中有這么一個例子就不是標(biāo)量求導(dǎo):

其中,y是向量,可以對x求導(dǎo),但同時發(fā)現(xiàn)需要傳遞參數(shù)gradients。

那么gradients是什么呢?

從說明中我們可以了解到:

  • 如果你要求導(dǎo)的是一個標(biāo)量,那么gradients默認(rèn)為None,所以前面可以直接調(diào)用J.backward()就行了
  • 如果你要求導(dǎo)的是一個張量,那么gradients應(yīng)該傳入一個Tensor。那么這個時候是什么意思呢?

在StackOverflow有一個解釋很好:

大意就是說,我們有時候需要讓loss(loss=[loss1,loss2,loss3])的各個分量分別對x求導(dǎo),這個時候就采用loss.backward(torch.tensor([[1.0,1.0,1.0,1.0]])),其中各個分量的權(quán)重都為1;

還有一種情況,如果你想讓不同的分量有不同的權(quán)重,那么就賦予gradients不一樣的值即可,比如:loss.backward(torch.tensor([[0.1,1.0,10.0,0.001]]))。

這樣就使得backward()操作更加靈活。

4 具體實例

4.1 均方誤差(MSE)的求導(dǎo)

損失函數(shù)公式:
\text{loss} = \sum [y - f_{w} (x)]^2,f_{w}(x) = x * w + b

求導(dǎo)公式:
\frac{\triangledown loss}{\triangledown w} = 2 \sum [y - f_{w}(x)] * \frac{(-1) * \triangledown f_{w}(x)}{\triangledown w}

x = 1, w = 2, b = 0, y = 1,那么lossw的偏導(dǎo)數(shù)計算如下:
\frac{\triangledown loss}{\triangledown w} = 2 * [y - (x * w + b)] * (-1) * x = 2 * [1 - (1 * 2 + 0)] * (-1) = 2.

import torch
import torch.nn.functional as F

x = torch.ones(1)
w = torch.full([1], 2, requires_grad=True)  # 允許求導(dǎo)
b = torch.zeros(1)
y = torch.ones(1)

mse = F.mse_loss(y, x * w + b)
torch.autograd.grad(mse, [w])

# =========================== # 
OUT:
(tensor([2.]),)

其中求導(dǎo)方式使用torch.autograd.grad(loss, [w1, w2, ..., ])

4.2 Softmax求導(dǎo)

  • Softmax過程:
  • 求導(dǎo)過程:

1)當(dāng)i=j

2)當(dāng)i != j

\frac{\partial p_i}{\partial {a_j}} = \left\{\begin{matrix} p_i (1 - p_j)& if \; i = j\\ -p_j p_i& if \; i \neq j \end{matrix}\right.

如果另
\delta{ij} = \left\{\begin{matrix} 1& if \; i = j\\ 0& if \; i \neq j \end{matrix}\right.

則有,
\frac{\partial p_i}{\partial {a_j}} = p_i (\delta{ij} - p_j)

4.3 Pytorch求解梯度的兩個API

  • torch.autograd.grad(loss, [w1, w2, ...])
    提供哪些變量,就對哪些變量求導(dǎo),當(dāng)然這些變量可求導(dǎo),返回[w1.grad, w2.grad, ...]
  • loss.backward()
    返回所有的可求導(dǎo)變量的導(dǎo)數(shù)。

參考文獻(xiàn)

PyTorch簡明筆記[2]-Tensor的自動求導(dǎo)(AoutoGrad)

最后編輯于
?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請聯(lián)系作者
【社區(qū)內(nèi)容提示】社區(qū)部分內(nèi)容疑似由AI輔助生成,瀏覽時請結(jié)合常識與多方信息審慎甄別。
平臺聲明:文章內(nèi)容(如有圖片或視頻亦包括在內(nèi))由作者上傳并發(fā)布,文章內(nèi)容僅代表作者本人觀點,簡書系信息發(fā)布平臺,僅提供信息存儲服務(wù)。

相關(guān)閱讀更多精彩內(nèi)容

  • ??自動求導(dǎo)應(yīng)該是Torch、Tensorflow等基礎(chǔ)框架最核心的部分,屬于任督二脈性質(zhì)的,一通百通;本主題主要...
    楊強(qiáng)AT南京閱讀 2,966評論 0 3
  • 原版英文鏈接:Edward Z. Yang's PyTorch internals : Inside 245-5D...
    _soaroc_閱讀 1,044評論 0 0
  • 概述 在新版本中,PyTorch引入了許多令人興奮的新特性,主要的更新在于 Variable和Tensor的合并 ...
    古de莫寧閱讀 6,274評論 0 1
  • 閑翻史書,看到很多地名,需要一本歷史地圖。為什么這些史書不帶地圖呢。 在網(wǎng)上搜了下,找到不少春秋時的地圖,上面這個...
    小島毅閱讀 580評論 0 0
  • 這本是放飛風(fēng)箏的季節(jié) 今日卻無風(fēng) 刮起多年的愿望 并一心一意地牽引 童年扎的風(fēng)箏 在角落里數(shù)著灰塵 ...
    紅塵之土閱讀 163評論 0 0

友情鏈接更多精彩內(nèi)容