1. 程式人生 > >PyTorch基礎系列(三)——深入理解autograd:Variable屬性方法【最新已經和tensor合併為一類】

PyTorch基礎系列(三)——深入理解autograd:Variable屬性方法【最新已經和tensor合併為一類】

torch.autograd.backward(variables, grad_variables, retain_variables=False)

當前Variable對leaf variable求偏導。

計算圖可以通過鏈式法則求導。如果Variable是 非標量(non-scalar)的,且requires_grad=True。那麼此函式需要指定gradient,它的形狀應該和Variable的長度匹配,裡面儲存了Variable的梯度。

此函式累積leaf variable的梯度。你可能需要在呼叫此函式之前將Variable的梯度置零。

引數說明:

  • variables (variable 列表) – 被求微分的葉子節點,即 ys

  • grad_variables (Tensor 列表) – 對應variable的梯度。僅當variable不是標量且需要求梯度的時候使用。見下例中的c.backward(torch.ones(a.size()))

  • retain_variables (bool) – True,計算梯度時所需要的buffer在計算完梯度後不會被釋放。如果想對一個子圖多次求微分的話,需要設定為True

# retain_variables用法

x = V(t.ones(3))

w = V(t.rand(3),requires_grad=True)

y = w.mul(x)

z = y.sum()

z.backward(retain_graph=True)

print(w.grad)

z.backward()#保留了上一次的結果,才能再一次backward()否則圖也解散第二次backward()會報錯: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 當然你也可以選擇再構造一次圖 y =

w.mul(x)  z = y.sum()然後再backward()也可以

print(w.grad)

Variable containing:
 1
 1
 1

Variable containing:【第二次結果是進行了累加第一次】
 2
 2
 2

在PyTorch中計算圖的特點可總結如下:


葉子節點物件【使用者自己創造的物件】是一個 AccumulateGrad Object 表示梯度累加物件。通過grad_fn.next_functions可得。
                                                 非葉子節點的梯度計算完之後即被清空,grad為None 用 retain_grad可保留中間梯度

 

 

a = V(t.ones(3,4),requires_grad=True)        葉子節點 grad_fn為None       backward後grad

b = V(t.zeros(3,4))requires_grad=False        葉子節點 grad_fn為None       backward後grad無

c = a.add(b) 所有依賴a的節點requires_grad=True  grad_fn為AddBackward1   backward後grad無

d = c.sum()   所有依賴a的節點 requires_grad=True  grad_fnsumBackward1   backward後grad無


d.backward()  這時候d是標量12,所以可以省略backward的grad_variable引數,預設(torch.FloatTensor([1]))

c.backward(torch.ones(a.size())) c 是向量,需定義backward的grad_variable引數,表示每一個都是1倍梯度。


 

  • autograd根據使用者對variable的操作Function構建其計算圖。對變數的操作抽象為Function
  • 對於那些不是任何函式(Function)的輸出,由使用者建立的節點稱為葉子節點,葉子節點的grad_fn為None。葉子節點中需要求導的variable,具有AccumulateGrad標識,因其梯度是累加的。
  • variable預設是不需要求導的,即requires_grad屬性預設為False,如果某一個節點requires_grad被設定為True,那麼所有依賴它的節點requires_grad都為True。
  • variable的volatile屬性預設為False,如果某一個variable的volatile屬性被設為True,那麼所有依賴它的節點volatile屬性都為True。volatile屬性為True的節點不會求導,volatile的優先順序比requires_grad高。已經被torch.no_grad()替代
  • 多次反向傳播時,梯度是累加的。反向傳播的中間快取會被清空,為進行多次反向傳播需指定retain_graph=True來儲存這些快取。
  • 非葉子節點的梯度計算完之後即被清空,可以使用autograd.gradhook技術獲取非葉子節點的值。
  • variable的grad與data形狀一致,應避免直接修改variable.data,因為對data的直接操作無法利用autograd進行反向傳播
  • 反向傳播函式backward的引數grad_variables可以看成鏈式求導的中間結果,如果是標量,可以省略,預設為1
  • PyTorch採用動態圖設計,可以很方便地檢視中間層的輸出,動態的設計計算圖結構。

 

Variable類和計算圖

簡單的建立一個計算圖,便於理解幾個相關知識點:

  • requires_grad  是否要求導數,預設False,葉節點指定True後,依賴節點都被置為True

  • .backward()  根Variable的方法會反向求解葉Variable的梯度

  • .backward()方法grad_variable引數  形狀與根Variable一致,非標量Variable反向傳播方向指定

  • 葉節點  由使用者建立的計算圖Variable物件,反向傳播後會保留梯度grad數值,非葉子節點Variable會清空為None

  • grad_fn  指向建立Tensor的Function,如果某一個物件由使用者建立葉子節點,則指向None

  •  .is_leaf  是否是葉節點

  • .grad_fn.next_functions  本節點接收的上級節點的grad_fn,# grad_fn.next_functions代表了本節點的輸入節點資訊grad_fn表示了本節點的輸出資訊

  • .volatile  是否處於推理模式 作用於依賴路徑全部的Variable。已經被torch.no_grad(), torch.set_grad_enabled(grad_mode)替代,在0.4版本中。【具體案例見下面volatile部分】

1

2

3

4

5

6

7

8

9

10

11

12

13

14

15

import torch as t

from torch.autograd import Variable as V

 

a = V(t.ones(3,4),requires_grad=True)

b = V(t.zeros(3,4))

c = a.add(b)

d = c.sum()

d.backward()

 

# 雖然沒有要求cd的梯度,但是cd依賴於a,所以a要求求導則cd梯度屬性會被預設置為True

print(a.requires_grad, b.requires_grad, c.requires_grad,d.requires_grad)

# 葉節點(由使用者建立)的grad_fn指向None

print(a.is_leaf, b.is_leaf, c.is_leaf,d.is_leaf)

# 中間節點雖然要求求梯度,但是由於不是葉節點,其梯度不會保留,所以仍然是None

print(a.grad,b.grad,c.grad,d.grad)

True False True True
True True False False
Variable containing:
 1  1  1  1
 1  1  1  1
 1  1  1  1
[torch.FloatTensor of size 3x4]
 None None None

1

print('\n',a.grad_fn,'\n',b.grad_fn,'\n',c.grad_fn,'\n',d.grad_fn)

None 
None 
<AddBackward1 object at 0x000002A2F3D2EBA8> 
<SumBackward0 object at 0x000002A2F3D2ECC0>

 

.grad_fn.next_functions

# grad_fn.next_functions代表了本節點的輸入節點資訊,grad_fn表示了本節點的輸出資訊

1

2

3

4

5

6

7

8

9

10

11

12

13

14

15

16

17

18

x = V(t.ones(1))

w = V(t.rand(1),requires_grad=True)

b = V(t.rand(1),requires_grad=True)

 

y = w.mul(x)   #((<AccumulateGrad object at 0x000002A2F57F5630>, 0), (None, 0)) :(w,x)

z = y.add(b)  #((<MulBackward1 object at 0x000002A2F57F57B8>, 0), (<AccumulateGrad object at 0x000002A2F57F57F0>, 0)) : (y,b)

 

print(x.is_leaf,w.is_leaf,b.is_leaf,y.is_leaf,z.is_leaf)

print(x.requires_grad,w.requires_grad,b.requires_grad,y.requires_grad,z.requires_grad)

print(x.grad_fn,w.grad_fn,b.grad_fn,y.grad_fn,z.grad_fn)

 

# grad_fn.next_functions

# grad_fn.next_functions代表了本節點的輸入節點資訊,grad_fn表示了本節點的輸出資訊

# 葉子結點grad_fn為None,沒有next_functions,但是間接查詢到AccumulateGrad object表示該葉子節點

# 接受梯度更新,查詢到None表示不接受更新

print(y.grad_fn.next_functions,z.grad_fn.next_functions)

print(z.grad_fn.next_functions[0][0]==y.grad_fn)

print(z.grad_fn.next_functions[0][0],y.grad_fn)

.is_leaf
True True True False False

.requires_grad
False True True True True

.grad_fn
None None None <MulBackward1 object at 0x000002A2F57F5710> <AddBackward1 object at 0x000002A2F57F5630>

.grad_fn.next_functions
((<AccumulateGrad object at 0x000002A2F57F5630>, 0), (None, 0))第一個引數是w,使用者自己創造的葉子節點,梯度累加行AccumulateGrad,第二是x 
((<MulBackward1 object at 0x000002A2F57F57B8>, 0), 
(<AccumulateGrad object at 0x000002A2F57F57F0>, 0))
z.grad_fn.next_functions[0][0]==y.grad_fn
True 
z.grad_fn.next_functions[0][0],y.grad_fn
<MulBackward1 object at 0x000002A2F57F57F0> <MulBackward1 object at 0x000002A2F57F57F0>

.volatile

volatile was removed and now has no effect. Use `with torch.no_grad():` instead

 

1

2

3

4

5

6

7

8

9

# volatile

# 節省視訊記憶體提高效用的引數volatile,也會作用於依賴路徑全部的Variable上,且優先順序高於requires_grad,

# 這樣我們在實際設計網路時不必修改其他葉子結點的requires_grad屬性,只要將輸入葉子volatile=True即可

 

x = V(t.ones(1),volatile=True)

w = V(t.rand(1),requires_grad=True)

y = w.mul(x)

print(x.requires_grad,w.requires_grad,y.requires_grad)

print(x.volatile,w.volatile,y.volatile)

False True False 
True False True

.volatile已經被orch.no_grad(), torch.set_grad_enabled(grad_mode)替代,在0.4版本中。

>>> x = torch.zeros(1, requires_grad=True)
>>> with torch.no_grad():
...     y = x * 2
>>> y.requires_grad
False
>>>
>>> is_train = False
>>> with torch.set_grad_enabled(is_train):
...     y = x * 2#這樣形成的y不求導
>>> y.requires_grad
False
>>> torch.set_grad_enabled(True)  # this can also be used as a function
>>> y = x * 2
>>> y.requires_grad
True
>>> torch.set_grad_enabled(False)
>>> y = x * 2
>>> y.requires_grad
False

附錄、Variable類原始碼簡介

1

2

3

4

5

6

7

8

9

10

11

12

13

14

15

16

17

18

19

20

21

22

23

24

25

26

27

28

29

30

31

32

33

34

35

36

37

38

39

40

41

42

43

44

45

46

47

48

49

50

51

52

53

54

55

56

57

58

59

60

61

62

63

64

65

66

67

68

69

70

71

72

73

74

75

76

77

78

79

80

81

82

83

84

85

86

87

88

89

90

91

92

93

94

95

96

97

98

99

100

101

102

103

104

105

106

107

108

109

110

111

112

113

114

115

116

117

118

class Variable(_C._VariableBase):

 

    """

    Attributes:

        data: 任意型別的封裝好的張量。

        grad: 儲存與data型別和位置相匹配的梯度,此屬性難以分配並且不能重新分配。

        requires_grad: 標記變數是否已經由一個需要呼叫到此變數的子圖建立的bool值。只能在葉子變數上進行修改。

        volatile: 標記變數是否能在推理模式下應用(如不儲存歷史記錄)的bool值。只能在葉變數上更改。

        is_leaf: 標記變數是否是圖葉子(如由使用者建立的變數)的bool值.

        grad_fn: Gradient function graph trace.

 

    Parameters:

        data (any tensor class): 要包裝的張量.

        requires_grad (bool): bool型的標記值. **Keyword only.**

        volatile (bool): bool型的標記值. **Keyword only.**

    """

 

    def backward(self, gradient=None, retain_graph=None, create_graph=None, retain_variables=None):

        """計算關於當前圖葉子變數的梯度,圖使用鏈式法則導致分化

        如果Variable是一個標量(例如它包含一個單元素資料),你無需對backward()指定任何引數

        如果變數不是標量(包含多個元素資料的向量)且需要梯度,函式需要額外的梯度;

        需要指定一個和tensor的形狀匹配的grad_output引數(y在指定方向投影對x的導數);

        可以是一個型別和位置相匹配且包含與自身相關的不同函式梯度的張量。

        函式在葉子上累積梯度,呼叫前需要對該葉子進行清零。

 

        Arguments:

            grad_variables (Tensor, Variable or None):

                           變數的梯度,如果是一個張量,除非“create_graph”是True,否則會自動轉換成volatile型的變數。

                           可以為標量變數或不需要grad的值指定None值。如果None值可接受,則此引數可選。

            retain_graph (bool, optional): 如果為False,用來計算梯度的圖將被釋放。

                                           在幾乎所有情況下,將此選項設定為True不是必需的,通常可以以更有效的方式解決。

                                           預設值為create_graph的值。

            create_graph (bool, optional): 為True時,會構造一個導數的圖,用來計算出更高階導數結果。

                                           預設為False,除非``gradient``是一個volatile變數。

        """

        torch.autograd.backward(self, gradient, retain_graph, create_graph, retain_variables)

 

 

    def register_hook(self, hook):

        """Registers a backward hook.

 

        每當與variable相關的梯度被計算時呼叫hook,hook的申明:hook(grad)->Variable or None

        不能對hook的引數進行修改,但可以選擇性地返回一個新的梯度以用在`grad`的相應位置。

 

        函式返回一個handle,其``handle.remove()``方法用於將hook從模組中移除。

 

        Example:

            >>> v = Variable(torch.Tensor([0, 0, 0]), requires_grad=True)

            >>> h = v.register_hook(lambda grad: grad * 2)  # double the gradient

            >>> v.backward(torch.Tensor([1, 1, 1]))

            >>> v.grad.data

             2

             2

             2

            [torch.FloatTensor of size 3]

            >>> h.remove()  # removes the hook

        """

        if self.volatile:

            raise RuntimeError("cannot register a hook on a volatile variable")

        if not self.requires_grad:

            raise RuntimeError("cannot register a hook on a variable that "

                               "doesn't require gradient")

        if self._backward_hooks is None:

            self._backward_hooks = OrderedDict()

            if self.grad_fn is not None:

                self.grad_fn._register_hook_dict(self)

        handle = hooks.RemovableHandle(self._backward_hooks)

        self._backward_hooks[handle.id] = hook

        return handle

 

    def reinforce(self, reward):

        """Registers a reward obtained as a result of a stochastic process.

        區分隨機節點需要為他們提供reward值。如果圖表中包含任何的隨機操作,都應該在其輸出上呼叫此函式,否則會出現錯誤。

        Parameters:

            reward(Tensor): 帶有每個元素獎賞的張量,必須與Variable資料的裝置位置和形狀相匹配。

        """

        if not isinstance(self.grad_fn, StochasticFunction):

            raise RuntimeError("reinforce() can be only called on outputs "

                               "of stochastic functions")

        self.grad_fn._reinforce(reward)

 

    def detach(self):

        """返回一個從當前圖分離出來的新變數。

        結果不需要梯度,如果輸入是volatile,則輸出也是volatile。

 

        .. 注意::

          返回變數使用與原始變數相同的資料張量,並且可以看到其中任何一個的就地修改,並且可能會觸發正確性檢查中的錯誤。

        """

        result = NoGrad()(self# this is needed, because it merges version counters

        result._grad_fn = None

        return result

 

    def detach_(self):

        """從建立它的圖中分離出變數並作為該圖的一個葉子"""

        self._grad_fn = None

        self.requires_grad = False

 

    def retain_grad(self):

        """Enables .grad attribute for non-leaf Variables."""

        if self.grad_fn is None# no-op for leaves

            return

        if not self.requires_grad:

            raise RuntimeError("can't retain_grad on Variable that has requires_grad=False")

        if hasattr(self, 'retains_grad'):

            return

        weak_self = weakref.ref(self)

 

        def retain_grad_hook(grad):

            var = weak_self()

            if var is None:

                return

            if var._grad is None:

                var._grad = grad.clone()

            else:

                var._grad = var._grad + grad

 

        self.register_hook(retain_grad_hook)

        self.retains_grad = True