深度學習框架PyTorch一書的學習-第三章-Tensor和autograd-2-autograd


參考https://github.com/chenyuntc/pytorch-book/tree/v1.0

希望大家直接到上面的網址去查看代碼,下面是本人的筆記

 

torch.autograd就是為了方便用戶使用,專門開發的一套自動求導引擎,她能夠根據輸入和前向傳播過程自動構建計算圖,並執行反向傳播

1.Variable

深度學習算法的本質是通過反向函數求導數,pytorch的Autograd模塊實現了此功能。在Tensor上的所有操作,Autograd都能夠為他們自動提供微分,避免手動計算的復雜過程

其中Autograd.Variable是Autograd的核心類。Tensor被封裝成Variable后,可以調用它的.backward實現反向傳播,自動計算所有梯度

 

  • data : 保存Variable所包含的Tensor
  • grad : 保存data對應的梯度,grad也是一個Variable(即也有data\grad\grad_fn三個值),而不是Tensor,它和data的形狀一樣
  • grad_fn : 指向一個Function對象,這個Function用來反向傳播計算輸入的梯度,記錄variable的操作歷史,即它是什么操作的輸出,用來構建計算圖。如果某一個變量是由用戶創建的,則它為葉子節點,對應的grad_fn等於None

 

Variable的構造函數需要傳入tensor,同時有兩個可選參數:

  • requires_grad(bool):是否需要對該variable進行求導
  • volatile(bool):設置為True,構建在該variable之上的圖都不會求導,專為推理階段設計

Variable支持大多數tensor支持的函數,但其不支持部分inplace函數,因為這些函數會修改tensor自身,而在反向傳播中,variable需要緩存原來的tensor來計算梯度

如果想要計算各個Variable的梯度,只需要調用根節點variable的backward方法,autograd會自動沿着計算圖反向傳播,計算每一個葉子節點的梯度

 

variable.backward(grad_variables=None, retain_graph=None, create_graph=None)的參數:

  • grad_variables:形狀與variable一致,對於y.backward(),其相當於鏈式法則。grad_variables也可以是tensor或序列                                                                                    
  • retain_graph:反向傳播 需要緩存一些中間結果,反向傳播后,這些緩存就被清空,可以通過指定這個參數不清空緩存用來進行多次反向傳播
  • create_graph:對反向傳播過程再次構建計算圖,可通過backward of backward實現求高階導數

舉例:

from __future__ import print_function
import torch as t
from torch.autograd import Variable as V

生成a:

#從tensor中創建variable,指定需要求導
a = V(t.ones(3,4), requires_grad=True)
a

返回:

tensor([[1., 1., 1., 1.],
        [1., 1., 1., 1.],
        [1., 1., 1., 1.]], requires_grad=True)

生成b:

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

返回:

tensor([[0., 0., 0., 0.],
        [0., 0., 0., 0.],
        [0., 0., 0., 0.]])

生成c,實現對a,b的操作:

#函數的使用與tensor一致
#也可以寫成c = a+b
c = a.add(b)
c

返回:

tensor([[1., 1., 1., 1.],
        [1., 1., 1., 1.],
        [1., 1., 1., 1.]], grad_fn=<AddBackward0>)

反向:

d = c.sum()
d.backward()#反向傳播

⚠️

#注意tensor和variable兩者操作的區別
#前者在取data后變成tensor,從tensor計算sum得到float
#后者計算sum后仍然是Variable
c.data.sum(), c.sum()

返回:

(tensor(12.), tensor(12., grad_fn=<SumBackward0>))

查看梯度:

#反向傳播后就能夠查看變量的梯度了
a.grad

返回:

tensor([[1., 1., 1., 1.],
        [1., 1., 1., 1.],
        [1., 1., 1., 1.]])

說明:

#此處雖然沒有指定c需要求導,但c依賴於a,而a需要求導
#因此c的requires_grad屬性會自動設置為True
a.requires_grad, b.requires_grad, c.requires_grad

返回:

(True, False, True)

 

#由用戶創建的variable是葉子節點,其對應的grad_fn是None
a.is_leaf, b.is_leaf, c.is_leaf

返回:

(True, True, False)

 

#c.grad是None,c不是葉子節點,它的梯度是用來計算a的梯度的
#雖然c.requires_grad為True,但是其梯度在計算完后會被釋放
c.grad is None #返回True

 

接下來我們看看autograd計算的導數和我們手動推導的區別 

def f(x) :
    '''計算y'''
    y = x**2 * t.exp(x)
    return y

def gradf(x):
    '''手動自動求導'''
    dx = 2*x*t.exp(x) + x**2*t.exp(x)
    return dx

生成x開始計算y:

x = V(t.randn(3,4), requires_grad=True)
y = f(x)
y

返回:

tensor([[0.0090, 0.3115, 1.2975, 0.4315],
        [0.5321, 0.3219, 0.1713, 0.3801],
        [0.1355, 0.0842, 0.1229, 0.4506]], grad_fn=<MulBackward0>)

反向傳播:

y.backward(t.ones(y.size())) #grad_variables形狀與y一致
x.grad

返回:

tensor([[-0.1716,  1.7068,  4.6518, -0.2922],
        [-0.0763,  1.7446,  1.1561, -0.3552],
        [ 0.9972, -0.4043, -0.4409,  2.1901]])

grad_variables為什么這樣設置,可見:pytorch的backward

#autograd的計算結果與利用公式的計算結果一致
gradf(x)

返回:

tensor([[-0.1716,  1.7068,  4.6518, -0.2922],
        [-0.0763,  1.7446,  1.1561, -0.3552],
        [ 0.9972, -0.4043, -0.4409,  2.1901]], grad_fn=<AddBackward0>)

 

2.計算圖

pytorch中autograd的底層采用了計算圖,其是一種有向無環圖(DAG),用於計算算子和變量之間的關系

如表達式z = wx +b可分解成y = wx, z = y+b,其計算圖如下所示:

一般用矩形表示算子,橢圓表示變量,即圖中的MUL和ADD都是算子,w,x,b為變量 

如這個有向無環圖,X和b是葉子節點,這些節點通常由用戶自己創建,不依賴於其他變量。z稱為根節點,是計算圖的最終目標。

利用鏈式法則很容易求得各個葉子節點的梯度:

有了計算圖,上述鏈式求導即可利用計算圖的反向傳播自動完成,其過程如下:

在pytorch實現中,autograd會隨着用戶的操作,記錄生成當前variable的所有操作,由此建立一個有向無環圖,每一個變量在圖中的位置可以通過其grad_fn屬性在圖中的位置推測得到

用戶每進行一次 操作,相應的計算圖就會發生變化

每一個前向傳播操作的函數都有與之對應的反向傳播函數用來計算輸入的各個variable的梯度,這些函數的函數名通常以Backward結尾,代碼實現:

x = V(t.ones(1))
b = V(t.rand(1),requires_grad=True)
w = V(t.rand(1),requires_grad=True)
y = w * x #等價於 y = w.mul(x)
z = y + b #等價於 z = y.add(b)

查看requires_grad:

x.requires_grad,b.requires_grad,w.requires_grad

返回:

(False, True, True)

 

#雖然未指定y.requires_grad為True,但由於y依賴於需要求導的w
#故而y.requires_grad自動設置為True
y.requires_grad #返回True

查看葉子節點:

x.is_leaf,b.is_leaf,w.is_leaf

返回:

(True, True, True)

 

y.is_leaf, z.is_leaf

返回:

(False, False)

查看grad_fn:

#grad_fn可以查看這個variable的反向傳播函數
#z是add函數的輸出,所以它的反向傳播函數是AddBackward
z.grad_fn

返回:

<AddBackward0 at 0x116776240>

next_functions:

#next_functions保存grad_fn的輸入,grad_fn的輸入是一個tuple
#第一個是y,它是乘法mul的輸出,所以對應的反向傳播函數y.grad_fn是MulBackward
#第二個是b,它是葉子節點,由用戶創建,grad_fn為None,但是有
z.grad_fn.next_functions

返回:

((<MulBackward0 at 0x116776c50>, 0), (<AccumulateGrad at 0x116776828>, 0))

 

#Variable的grad_fn對應着圖中的function
z.grad_fn.next_functions[0][0] == y.grad_fn #返回True

 

#第一個是w,葉子節點,需要求導,梯度是累加的
#第二個是x,葉子節點,不需要求導,所以為None
y.grad_fn.next_functions

返回:

((<AccumulateGrad at 0x116776ba8>, 0), (None, 0))

 

#葉子節點的grad_fn是None
w.grad_fn, x.grad_fn

返回:

(None, None)

 

計算w的梯度時需要用到x的數值(因為),這些數值在前向過程中會保存下來成buffer,在計算完梯度后會自動清空。

為了能夠多次反向傳播,需要指定retain_graph來保留這些buffer

y.grad_fn.saved_variables

返回錯誤:

AttributeError: 'MulBackward0' object has no attribute 'saved_variables'

原因確實是版本問題,PyTorch0.3 中把許多python的操作轉移到了C++中,saved_variables 現在是一個c++的對象,無法通過python訪問。(https://github.com/chenyuntc/pytorch-book/issues/7)

可以查看這里進行學習https://github.com/chenyuntc/pytorch-book/blob/0.3/chapter3-Tensor和autograd/Autograd.ipynb,省掉上面的操作:

#使用retain_graph來保存buffer
z.backward(retain_graph=True)
w.grad

返回:

tensor([1.])

 

#多次反向傳播,梯度累加,這就是w中AccumulateGrad標識的含義
z.backward()
w.grad

返回:

tensor([2.])

 

PyTorch使用的是動態圖,它的計算圖在每次前向傳播時都是從頭開始構建,所以它能夠使用Python控制語句(如for、if等)根據需求創建計算圖。這點在自然語言處理領域中很有用,它意味着你不需要事先構建所有可能用到的圖的路徑,圖在運行時才構建

def abs(x):
    if x.data[0] > 0:return x
    else: return -x
x = V(t.ones(1),requires_grad=True)
y = abs(x)
y.backward()
x.grad

返回:

tensor([1.])

 

x = V(-1*t.ones(1),requires_grad=True)
y = abs(x)
y.backward()
x.grad

返回:

tensor([-1.])

 

如果不使用.float(),報錯:

RuntimeError: Only Tensors of floating point dtype can require gradients

如果使用data[0],報錯:

IndexError: invalid index of a 0-dim tensor. Use tensor.item() to convert a 0-dim tensor to a Python number

最終正確版:

def f(x):
    result = 1
    for ii in x:
        if ii.data > 0:result = ii*result #這里如果使用data[0]會報錯
    return result
x = V(t.arange(-2, 4).float(),requires_grad=True) #這里如果不使用.float()會報錯
y = f(x) # y = x[3]*x[4]*x[5]
y.backward()
x.grad

返回:

tensor([0., 0., 0., 6., 3., 2.])

變量的requires_grad屬性默認為False,如果某一個節點requires_grad被設置為True,那么所有依賴它的節點requires_grad都是True。這其實很好理解,對於$ \textbf{x}\to \textbf{y} \to \textbf{z}$,x.requires_grad = True,當需要計算$\partial z \over \partial x$時,根據鏈式法則,$\frac{\partial z}{\partial x} = \frac{\partial z}{\partial y} \frac{\partial y}{\partial x}$,自然也需要求$ \frac{\partial z}{\partial y}$,所以y.requires_grad會被自動標為True.

 

volatile=True是另外一個很重要的標識,它能夠將所有依賴於它的節點全部都設為volatile=True(默認為True)其優先級比requires_grad=Truevolatile=True的節點不會求導,即使requires_grad=True,也無法進行反向傳播。對於不需要反向傳播的情景(如inference,即測試推理時),該參數可實現一定程度的速度提升,並節省約一半顯存,因其不需要分配空間計算梯度。

x = V(t.ones(1))
w = V(t.rand(1),requires_grad=True)
y = x*w
#y依賴於x,w,但x.volatile = True,w.requires_grad = True
x.requires_grad, w.requires_grad, y.requires_grad

返回:

(False, True, True)

 

x.volatile, w.volatile, y.volatile

報錯:

/anaconda2/envs/deeplearning3/lib/python3.6/site-packages/ipykernel_launcher.py:1: UserWarning: volatile was removed (Variable.volatile is always False)
  """Entry point for launching an IPython kernel.

返回與預期不符:

(False, False, False)

該屬性已經在0.4版本中被移除了(我使用的是pytorch-1.0.1),可以使用with torch.no_grad()代替該功能,函數可以使用裝飾器@torch.no_grad(),即:

>>> x = torch.tensor([1], requires_grad=True)
>>> with torch.no_grad():
...   y = x * 2
>>> y.requires_grad
False
>>> @torch.no_grad()
... def doubler(x):
...     return x * 2
>>> z = doubler(x)
>>> z.requires_grad
False

詳細內容可見https://pytorch.org/docs/master/autograd.html#locally-disable-grad

所以現在一般只使用requires_grad來查看,不使用volatile了

 

在反向傳播過程中非葉子節點的導數計算完之后即被清空。若想查看這些變量的梯度,有兩種方法:

  • 使用autograd.grad函數
  • 使用hook

autograd.gradhook方法都是很強大的工具,更詳細的用法參考官方api文檔,這里舉例說明基礎的使用。推薦使用hook方法,但是在實際使用中應盡量避免修改grad的值。

x = V(t.ones(3),requires_grad=True)
y = V(t.rand(3),requires_grad=True)
y = x*w
# y依賴於w,而w.requires_grad = True
z = y.sum()
x.requires_grad, w.requires_grad, y.requires_grad

返回:

(True, True, True)

 

#非葉子節點grad計算完后自動清空,所以y.grad為None
z.backward()
x.grad, w.grad, y.grad

返回:

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

第一種方法:

#第一種方法:使用grad獲得中間變量的梯度
x = V(t.ones(3), requires_grad=True)
w = V(t.rand(3), requires_grad=True)
y = x * w
z = y.sum()
#Z對y的梯度,隱式調用backward()
t.autograd.grad(z, y)

返回:

(tensor([1., 1., 1.]),)

第二種方法:

#第二種方法:使用hook
#hook是一個函數,輸入是梯度,不應該有返回值
def varaible_hook(grad):
    print('y的梯度:\r\n',grad)
x = V(t.ones(3), requires_grad=True)
w = V(t.rand(3), requires_grad=True)
y = x * w
#注冊hook
hook_handle = y.register_hook(varaible_hook) #存儲的是那個值的梯度就對那個值進行注冊hook
z = y.sum()
z.backward()

#除非你每次都要用hook,否則用完后記得移除hook
hook_handle.remove()

返回:

y的梯度:
 tensor([1., 1., 1.])

 

最后再來看看variable中grad屬性和backward函數grad_variables參數的含義,這里直接下結論:

  • variable $\textbf{x}$的梯度是目標函數${f(x)} $$\textbf{x}$的梯度,$\frac{df(x)}{dx} = (\frac {df(x)}{dx_0},\frac {df(x)}{dx_1},...,\frac {df(x)}{dx_N})$,形狀和$\textbf{x}$一致。
  • 對於y.backward(grad_variables)中的grad_variables相當於鏈式求導法則中的$\frac{\partial z}{\partial x} = \frac{\partial z}{\partial y} \frac{\partial y}{\partial x}$中的$\frac{\partial z}{\partial y}$。z是目標函數,一般是一個標量,故而$\frac{\partial z}{\partial y}$的形狀與variable $\textbf{y}$的形狀一致。z.backward()在一定程度上等價於y.backward(grad_y)。z.backward()省略了grad_variables參數,是因為$z$是一個標量,而$\frac{\partial z}{\partial z} = 1$

查看pytorch的backward

x = V(t.arange(0,3).float(),requires_grad=True)
y = x**2 + x*2
z = y.sum()
z.backward() #z是標量,即一個數,所以可以省略參數
x.grad

返回:

tensor([2., 4., 6.])

 

x = V(t.arange(0,3).float(), requires_grad=True)
y = x**2 + x*2
z = y.sum()
y_grad_variables = V(t.Tensor([1,1,1])) # dz/dy,因為y不是標量,即一個數
y.backward(y_grad_variables) #從y開始反向傳播
x.grad

返回:

tensor([2., 4., 6.])

 

另外值得注意的是,只有對variable的操作才能使用autograd,如果對variable的data直接進行操作,將無法使用反向傳播。除了對參數初始化,一般我們不會修改variable.data的值。

 

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

  • autograd根據用戶對variable的操作構建其計算圖。對變量的操作抽象為Function
  • 對於那些不是任何函數(Function)的輸出,由用戶創建的節點稱為葉子節點,葉子節點的grad_fn為None。葉子節點中需要求導的variable,具有AccumulateGrad標識,因其梯度是累加的
  • variable默認是不需要求導的,即requires_grad屬性默認為False,如果某一個節點requires_grad被設置為True,那么所有依賴它的節點requires_grad都為True。
  • (deprecated)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采用動態圖設計,可以很方便地查看中間層的輸出,動態的設計計算圖結構。
 

3.擴展autograd

目前絕大多數函數都可以使用autograd實現反向求導,但如果需要自己寫一個復雜的函數,不支持自動反向求導怎么辦? 寫一個Function,實現它的前向傳播和反向傳播代碼,Function對應於計算圖中的矩形, 它接收參數,計算並返回結果。下面給出一個例子。

class Mul(Function):
    @staticmethod
    def forward(ctx, w, x, b, x_requires_grad=True):
        ctx.x_requires_grad = x_requires_grad
        ctx.save_for_backward(w, x)#存儲用來反向傳播的參數
        output = w*x +b
        return output
    
    @staticmethod
    def backward(ctx, grad_output):
        w, x = ctx.save_variables
        grad_w = grad_output * x
        if ctx.x_requires_grad:
            grad_x = grad_output * w
        else:
            grad_x = None
        grad_b = grad_output * 1
        return grad_w, grad_x, grad_b, None

上面這種寫法是有問題的:

NameError: name 'Function' is not defined

分析如下:

  • 自定義的Function需要繼承autograd.Function,沒有構造函數__init__,forward和backward函數都是靜態方法
  • forward函數的輸入和輸出都是Tensor,backward函數的輸入和輸出都是Variable
  • backward函數的輸出和forward函數的輸入一一對應,backward函數的輸入和forward函數的輸出一一對應
  • backward函數的grad_output參數即t.autograd.backward中的grad_variables
  • 如果某一個輸入不需要求導,直接返回None,如forward中的輸入參數x_requires_grad顯然無法對它求導,直接返回None即可
  • 反向傳播可能需要利用前向傳播的某些中間結果,需要進行保存,否則前向傳播結束后這些對象即被釋放

Function的使用利用Function.apply(variable)

from torch.autograd import Function
class MultiplyAdd(Function):
    
    @staticmethod
    def forward(ctx, w, x, b):
        print('type in forward', type(x))
        ctx.save_for_backward(w, x)#存儲用來反向傳播的參數
        output = w*x +b
        return output
    
    @staticmethod
    def backward(ctx, grad_output):
        w, x = ctx.saved_variables #deprecated,現在使用saved_tensors
        print('type in backward',type(x))
        grad_w = grad_output * x
        grad_x = grad_output * w
        grad_b = grad_output * 1
        return grad_w, grad_x, grad_b

調用方法一:

類名.apply(參數)

輸出變量.backward()

x = V(t.ones(1))
w = V(t.rand(1),requires_grad=True)
b = V(t.rand(1),requires_grad=True)
print('開始前向傳播')
z = MultiplyAdd.apply(w, x, b)
print('開始反向傳播')
z.backward()

# x不需要求導,中間過程還是會計算它的導數,但隨后被清空
x.grad, w.grad, b.grad

警告:

/anaconda2/envs/deeplearning3/lib/python3.6/site-packages/ipykernel_launcher.py:13: DeprecationWarning: 'saved_variables' is deprecated; use 'saved_tensors'
  del sys.path[0]

返回:

開始前向傳播
type in forward <class 'torch.Tensor'>
開始反向傳播
type in backward <class 'torch.Tensor'>
(None, tensor([1.]), tensor([1.]))

 

 

調用方法二

類名.apply(參數)

輸出變量.grad_fn.apply()

x = V(t.ones(1))
w = V(t.rand(1),requires_grad=True)
b = V(t.rand(1),requires_grad=True)
print('開始前向傳播')
z = MultiplyAdd.apply(w, x, b)
print('開始反向傳播')

# 調用MultiplyAdd.backward
# 會自動輸出grad_w, grad_x, grad_b
z.grad_fn.apply(V(t.ones(1)))

返回:

開始前向傳播
type in forward <class 'torch.Tensor'>
開始反向傳播
type in backward <class 'torch.Tensor'>
Out[68]:
(tensor([1.]), tensor([0.8597], grad_fn=<MulBackward0>), tensor([1.]))

 

之所以forward函數的輸入是tensor,而backward函數的輸入是variable,是為了實現高階求導。backward函數的輸入輸出雖然是variable,但在實際使用時autograd.Function會將輸入variable提取為tensor,並將計算結果的tensor封裝成variable返回。在backward函數中,之所以也要對variable進行操作,是為了能夠計算梯度的梯度(backward of backward)。下面舉例說明,有關torch.autograd.grad的更詳細使用請參照文檔。

x = V(t.Tensor([5]),requires_grad=True)
y = x**2
grad_x = t.autograd.grad(y,x,create_graph=True)
grad_x # dy/dx = 2 * x

返回:

(tensor([10.], grad_fn=<MulBackward0>),)

 

grad_grad_x = t.autograd.grad(grad_x[0],x)
grad_grad_x #二階導數 d(2x)/dx = 2

返回:

(tensor([2.]),)

這種設計雖然能讓autograd具有高階求導功能,但其也限制了Tensor的使用,因autograd中反向傳播的函數只能利用當前已經有的Variable操作。這個設計是在0.2版本新加入的,為了更好的靈活性,也為了兼容舊版本的代碼,PyTorch還提供了另外一種擴展autograd的方法。

PyTorch提供了一個裝飾器@once_differentiable,能夠在backward函數中自動將輸入的variable提取成tensor,把計算結果的tensor自動封裝成variable。有了這個特性我們就能夠很方便的使用numpy/scipy中的函數,操作不再局限於variable所支持的操作。但是這種做法正如名字中所暗示的那樣只能求導一次,它打斷了反向傳播圖,不再支持高階求導。

上面所描述的都是新式Function,還有個legacy Function,可以帶有__init__方法,forwardbackwad函數也不需要聲明為@staticmethod,但隨着版本更迭,此類Function將越來越少遇到,在此不做更多介紹。

上面這些都是比較老的寫法了,現在都不使用了,現在的聲明都類似:

class StyleLoss(nn.Module):

    def __init__(self, target_feature):
        super(StyleLoss, self).__init__()
        self.target = gram_matrix(target_feature).detach()

    def forward(self, input):
        G = gram_matrix(input)
        self.loss = F.mse_loss(G, self.target)
        return input

backward方法都不自己寫了,都使用autograd

此外在實現了自己的Function之后,還可以使用gradcheck函數來檢測實現是否正確。gradcheck通過數值逼近來計算梯度,可能具有一定的誤差,通過控制eps的大小可以控制容忍的誤差。

 

下面舉例說明如何利用Function實現sigmoid Function

class Sigmoid(Function):
    @staticmethod
    def forward(ctx, x):
        output = 1 / (1 + t.exp(-x))
        ctx.save_for_backward(output)
        return output
    
    @staticmethod
    def backward(ctx, grad_output):
        output, = ctx.saved_tensors
        grad_x = output * (1 - output) *grad_output
        return grad_x
                    

 

#采用數值逼近方式檢驗計算梯度的公式對不對
test_input =V(t.randn(3,4), requires_grad=True)
t.autograd.gradcheck(Sigmoid.apply, (test_input,), eps=1e-3) #返回True

 

def f_sigmoid(x):
    y = Sigmoid.apply(x)
    y.backward(t.ones(x.size()))
    
def f_naive(x):
    y = 1/(1 + t.exp(-x))
    y.backward(t.ones(x.size()))
    
def f_th(x):
    y = t.sigmoid(x)
    y.backward(t.ones(x.size()))        

測試:

import time
x = V(t.randn(100, 100), requires_grad=True)
start_sigmoid = time.time()
for i in range(100):
    f_sigmoid(x)
end_sigmoid = time.time()

print(end_sigmoid-start_sigmoid)
start_naive = time.time()
for i in range(100):
    f_naive(x)
end_naive = time.time()
print(end_naive-start_naive)

start_th = time.time()
for i in range(100):
    f_th(x)
end_th = time.time()
print(end_th-start_th)

返回:

0.01591801643371582
0.02293086051940918
0.010257244110107422


顯然f_sigmoid要比單純利用autograd加減和乘方操作實現的函數快不少,因為f_sigmoid的backward優化了反向傳播的過程。另外可以看出系統實現的buildin接口(t.sigmoid)更快。

 

4.小試牛刀——用Variable實現線性回歸

import torch as t
from torch.autograd import Variable as V
from matplotlib import pyplot as plt
from IPython import display

 

#設置隨機數種子,為了在不同人電腦上運行時下面的輸入一致
t.manual_seed(1000)
def get_fake_data(batch_size=8):
    ''' 產生隨機數據:y = x*2 + 3,加上了一些噪聲'''
    x = t.rand(batch_size,1) * 20
    y = x * 2 + (1 + t.randn(batch_size, 1))*3
    return x, y

 

# 來看看產生x-y分布是什么樣的
x, y = get_fake_data()
plt.scatter(x.squeeze().numpy(), y.squeeze().numpy())

圖示: 

 

#隨機初始化參數
w = V(t.rand(1,1), requires_grad=True)
b = V(t.zeros(1,1), requires_grad=True)

lr = 0.001 #學習率

for ii in range(8000):#8000次迭代
    x, y = get_fake_data()
    x, y  = V(x), V(y)
    
    #forward:計算loss
    y_pred = x.mm(w) + b.expand_as(y)
    loss = 0.5 * (y_pred - y) ** 2
    loss = loss.sum()
    
    # backward:手動計算梯度
    loss.backward()
    
    # 更新參數
    w.data.sub_(lr * w.grad.data)
    b.data.sub_(lr * b.grad.data)
    
    #梯度清零
    w.grad.data.zero_()    
    b.grad.data.zero_()
    
    if ii%1000 == 0:
        # 畫圖
        display.clear_output(wait=True)
        x = t.arange(0, 20).float().view(-1, 1)
        y = x.mm(w.data) + b.data.expand_as(x)
        plt.plot(x.numpy(), y.numpy()) # predicted
        
        x2, y2 = get_fake_data(batch_size=20) 
        plt.scatter(x2.numpy(), y2.numpy()) # true data
        
        plt.xlim(0,20)
        plt.ylim(0,41)   
        plt.show()
        plt.pause(0.5)
        
print(w.data.squeeze(), b.data.squeeze())

返回:

tensor(2.0516) tensor(2.9800)

圖示: 

 


免責聲明!

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



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