天天看点

深入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的时候更快地找到问题所在。

继续阅读