天天看點

深入Pytorch中的Tensor,梯度以及權重

前言

我們在使用pytorch搭模組化型的時候,一般隻關注模型的forward前向傳播,backward後向傳播過程中,模型梯度的計算以及參數的更新都是pytorch架構背景自動進行計算的,那麼有的時候就會思考,pytorch到底是怎麼進行計算的,産生的這些梯度以及參數是如何儲存的,而我們想要通路又可以怎樣來通路。帶着這些疑問,我們來一一解開這些神秘的面紗。

tensor的grad屬性

首先我們從tensor入手,pytorch中建立的張量,也就是tensor,預設情況下是沒有grad梯度屬性的。檢查一個tensor是否有grad屬性可以通過tensor.requires_grad來檢視,如果tensor中有grad屬性,則傳回Ture,反之傳回False。

那麼我們如何在建立tensor的時候就讓tensor具有grad屬性呢?那就是要在建立tensor的時候将tensor的requires_grad屬性設定為True。舉個例子:

import torch
import torch.nn as nn

input = torch.tensor([1., 2., 3., 4.,])
print(input.requires_grad)
#False

input = torch.tensor([1., 2., 3., 4.,], requires_grad=True)
print(input.requires_grad)
#True
           

我們知道pytorch模型在訓練過程中都是以tensor的資料結構進行輸入輸出計算的,那麼tensor具有了grad屬性是不是就意味着模型在訓練的過程中,計算出來的grad都儲存在相應的tensor中呢?我們可以使用tensor.grad檢視tensor的梯度,接着上面的例子

input = torch.tensor([1., 2., 3., 4.,], requires_grad=True)
print(input.grad)
#None
           

輸出為空,因為tensor還沒有進行計算,雖然具有grad屬性,但是暫時并沒有梯度值

模型中的梯度grad

在模型運作過程中,所有tensor之間的運算,在所有的輸入中,若有一個輸入需要求導,那麼輸出一定會需要求導。

舉個例子,我們在訓練模型的過程中,Dataloader傳回的mini-batch作為輸入資料,我們并沒有指定需要求導,預設情況下的requires_grad屬性也是False。Ground Truth資料同理,預設情況下也不需要求導,那麼都不需要求導,那我們的模型在求出loss值之後還怎麼進行後向傳播計算梯度呢?答案就是上面的規則,其實模型中的weight參數預設情況下是requires_grad屬性為True,是需要求導的。是以,雖然輸入的資料預設是不需要求導的,但是,模型中的參數是需要求導的,這樣一來,隻要有一個需要求導,那麼模型的輸出就是需要求導的。驗證一下。

input = torch.randn(4, 3, 256, 256)
print(input.requires_grad)
#False

#初始化一個網絡模型
model = nn.Sequential(
		nn.Conv2d(3, 8, 3, 1, 1),
		nn.Conv2d(8, 16, 3, 1, 1)
)
#輸出模型中的參數是否有梯度
for param in model.named_parameters():
	print(param[0], param[1].requires_grad)
# 0.weight True
# 0.bias True
# 1.weight True
# 1.bias True

output = model(input)
print(output.requires_grad)
#True
           

果然輸入的時候沒有梯度,經過模型計算之後的輸出便有了梯度。

通過上面的例子是不是同樣也發現了什麼?pytorch中的tensor都可以指定requires_grad屬性,這是不是就說明了模型中經過後向傳播計算出來的梯度值都儲存在tensor中,我們來做個後向傳播來驗證一下

import torch
import torch.nn.functional as F

net1 = torch.nn.Linear(1, 1)   
loss_fcn = torch.nn.BCELoss()

x = torch.zeros((1,1))
y = F.sigmoid(net1(x))

loss = loss_fcn(y, x)
loss.backward()

for param in net1.named_parameters():
    print(param[0], param[1].grad)
#weight tensor([[0.]])
#bias tensor([0.2995])
           

我們可以看到,通過調用tensor的grad屬性就可以擷取tensor的梯度值,是以,反向傳播過程中計算的梯度值都儲存在模型參數或者中間變量中,通過grad屬性就可以調用。

這裡需要強調的一點是,由于pytorch的記憶體管理機制,中間變量在計算過程中産生的梯度值在使用過後就被釋放了,是以如果要擷取中間變量的梯度值,需要通過

retain_grad()

函數事先聲明保留變量的梯度值,這樣在變量使用完後還會儲存其梯度值,我們接着上面的例子

net1 = torch.nn.Linear(1, 1)

loss_fcn = torch.nn.BCELoss()
x = torch.zeros((1,1))
y = F.sigmoid(net1(x))
loss = loss_fcn(y, x)
loss.backward()
print(y.grad)
#None

y = F.sigmoid(net1(x))
y.retain_grad()
loss = loss_fcn(y, x)
loss.backward()
print(y.grad)
#tensor([[1.9580]])
           

模型中的參數weight

其實通過上面的一些例子我們就已經可以看出,模型在訓練過程中的參數是儲存在模型中的,通過

model.parameters()

或者

model.named_parameters()

就可以獲得,還是通過上面的例子來看:

import torch
import torch.nn.functional as F

net = torch.nn.Linear(1, 1)   
for param in net.named_parameters():
    print(param[0], param[1].data)
#weight tensor([[-0.4321]])
#bias tensor([0.6777])

for param in net.parameters():
    print(param.data)
#tensor([[-0.4321]])
#tensor([0.6777])
           

通過上面的例子也可以看出,模型在聲明的時候,就會自動初始化參數值。

總結

通過深挖pytorch中的tensor,梯度以及權重,了解了梯度、權重的存放位置,擷取方式以及傳播規律,這樣我們就可以在以後的模型訓練中更好地了解模型内在的運作規律,同時也可以在模型代碼出bug的時候更快地找到問題所在。

繼續閱讀