『PyTorch』第五彈_深入理解autograd_上:Variable屬性方法


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

  • autograd根據用戶對variable的操作構建其計算圖。對變量的操作抽象為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高。
  • 多次反向傳播時,梯度是累加的。反向傳播的中間緩存會被清空,為進行多次反向傳播需指定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

  • .volatile  是否處於推理模式

 

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
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>

 

模擬一個簡單的反向傳播:

def f(x):
    """x^2 * e^x"""
    y = x**2 * t.exp(x)
    return y

def gradf(x):
    """2*x*e^x + x^2*e^x"""
    dx = 2*x*t.exp(x) + x**2*t.exp(x)
    return dx

x = V(t.randn(3,4), requires_grad=True)
y = f(x)
y.backward(t.ones(y.size()))
print(x.grad)
print(gradf(x))
Variable containing:
 -0.3315   3.5068  -0.1079  -0.4308
 -0.1202  -0.4529  -0.1873   0.6514
  0.2343   0.1050   0.1223  15.9192
[torch.FloatTensor of size 3x4]

Variable containing:
 -0.3315   3.5068  -0.1079  -0.4308
 -0.1202  -0.4529  -0.1873   0.6514
  0.2343   0.1050   0.1223  15.9192
[torch.FloatTensor of size 3x4]

 結果一致。

 

.grad_fn.next_functions 

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)
z = y.add(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))
((<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
# 節省顯存提高效用的參數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

附錄、Variable類源碼簡介

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

 


免責聲明!

本站轉載的文章為個人學習借鑒使用,本站對版權不負任何法律責任。如果侵犯了您的隱私權益,請聯系本站郵箱yoyou2525@163.com刪除。



 
粵ICP備18138465號   © 2018-2025 CODEPRJ.COM