Pytorch中Tensor Variable Parameter register_buffer以及求导运算

在较新的版本中,Variable被弃用,将功能合并给Tensor,所以不用考虑

求导

重要理论

  1. 从头开始创建的Tensor(例如x = torch.tensor(1.))称为leaf Tensor(叶张量),依赖其他Tensor计算而来的(例如y = 2 * x)称为non-leaf Tensor(非叶张量)
  2. 若一Tensor的requires_grad=True,则依赖它的所有Tensor的requires_grad=True;若一Tensor的requires_grad=False,则依赖它的所有Tensor的requires_grad=False。
  3. loss的requires_grad=False则其不能调用backward;某non-leaf Tensor的requires_grad=False则其之前的参数由于梯度反向传播时被截断,所以不会得到更新;leaf Tensor的requires_grad=False,表示其不需要梯度,所以其也不能被更新。
  4. backward()只更新leaf的梯度,因为一般只优化leaf的值(在逻辑上模型可优化的参数是从头创建的leaf Tensor)。若想得到non-leaf的梯度,可以使用autogard()函数。
  5. 使用with torch.no_gard()可以使得其中Tensor的requires_grad=False,这表示他不需要传播梯度,从而节省显存,但注意如果在no_gard()中新建Tensor,其requires_grad的优先级比no_gard()高(详细见示例)。一般将测试过程放入with torch.no_grad()

autogard

# autogard.grad返回leaf tensor和non-leaf tensor的梯度,但tensor的grad属性值不更新

import torch
from torch import autograd

x = torch.tensor(1.)
a = torch.tensor(1., requires_grad=True)
b = torch.tensor(2., requires_grad=True)
c = torch.tensor(3., requires_grad=True)

# requires_grad=True
y = a**2 * x + b * x + c

# requires_grad=True
z = y**2 + 2*y

print('before:', a.grad, b.grad, c.grad, y.grad)
grads = autograd.grad(z, [a, b, c, y])
print('after :', grads[0], grads[1], grads[2], grads[3])
print('after :', a.grad, b.grad, c.grad, y.grad)

# before: None None None None
# after : tensor(28.) tensor(14.) tensor(14.) tensor(14.)
# after : None None None None

backward

# backward更新leaf tensor的grad属性值(在参数优化时常用backward而不是autogard.grad因为通常只需更新leaf tensor的值)

import torch

x = torch.tensor(1.)
a = torch.tensor(1., requires_grad=True)
b = torch.tensor(2., requires_grad=True)
c = torch.tensor(3., requires_grad=True)

# requires_grad=True
y = a**2 * x + b * x + c

# requires_grad=True
z = y**2 + 2*y

print('before:', a.grad, b.grad, c.grad, y.grad)
z.backward()
print('after :', a.grad, b.grad, c.grad, y.grad)

# before: None None None None
# after : tensor(28.) tensor(14.) tensor(14.) None

no_gard

常用的场景为测试过程,因为只有训练的过程需要梯度来更新参数,测试过程的不需要更新参数,所以将整个测试过程放到no_grad()下,以节省显存

with torch.no_grad():
	for data in test_dataloader:
		imgs, targets = data
		outputs = my_model(imgs)
		loss = loss_fn(outputs, targets)
		total_test_loss = total_test_loss + loss.item()
		accuracy = (outputs.argmax(1) == targets).sum()
		total_accuracy = total_accuracy + accuracy
# 使用with torch.no_gard()可以使得在其中的Tensor的requires_grad=False,
# 这表示他不需要传播梯度,从而节省显存
import torch
from torch.nn import Parameter

x = torch.tensor(1.)
a = torch.tensor(1., requires_grad=True)
b = torch.tensor(2., requires_grad=True)
c = torch.tensor(3., requires_grad=True)

y = a**2 * x + b * x + c

z = y**2 + 2*y

# True True True
print(c.requires_grad, y.requires_grad, z.requires_grad)

# y不需要梯度
with torch.no_grad():
    y = a**2 * x + b * x + c

# z依赖于y,也不需要梯度
z = y**2 + 2*y

# True False False
print(c.requires_grad, y.requires_grad, z.requires_grad)

# 此处调用z.backward()报错,z的requires_grad=False

# (注意)新建tensor且指定requires_grad=True,其优先级高于no_grad()即requires_grad=True
# 但其派生而来的y和z的requires_grad仍为False
with torch.no_grad():
    a = torch.tensor(1., requires_grad=True)
    b = torch.tensor(2., requires_grad=True)
    c = torch.tensor(3., requires_grad=True)

    y = a**2 * x + b * x + c

    z = y**2 + 2*y

print(c.requires_grad, y.requires_grad, z.requires_grad)
# True False False

# (注意)新建Parameter,其优先级高于no_grad()即requires_grad=True
# 但其派生而来的y和z的requires_grad仍为False
with torch.no_grad():
    a = Parameter(torch.tensor(1.))
    b = Parameter(torch.tensor(2.))
    c = Parameter(torch.tensor(3.))

    y = a**2 * x + b * x + c

    z = y**2 + 2*y

print(c.requires_grad, y.requires_grad, z.requires_grad)
# True False False

optimizer更新参数

# non-leaf不能被更新

import torch
from torch import optim

x = torch.tensor(1.)
a = torch.tensor(1., requires_grad=True)
b = torch.tensor(2., requires_grad=True)
c = torch.tensor(3., requires_grad=True)

y = a**2 * x + b * x + c

z = y**2 + 2*y

# 报错:can't optimize a non-leaf Tensor
# optimizer = optim.SGD([a, b, c, y], lr=0.01)

optimizer = optim.SGD([a, b, c], lr=0.01)
print('before:', a, b, c)
optimizer.zero_grad()
z.backward()
optimizer.step()
print('after :', a, b, c)

# before: tensor(1., requires_grad=True) tensor(2., requires_grad=True) tensor(3., requires_grad=True)
# after : tensor(0.7200, requires_grad=True) tensor(1.8600, requires_grad=True) tensor(2.8600, requires_grad=True)

Tensor vs Parameter vs. register_buffer

重要理论

  1. Parameter和register_buffer均为特殊的Tensor,其中Parameter设计目的为可以优化和保存的Tensor,register_buffer设计目的为不能优化但能保存的Tensor。
  2. Tensor默认requires_grad=False,不和模型绑定,net.parameters()访问不到,net.state_dict()访问不到
  3. Parameter默认requires_grad=True和模型绑定,net.parameters()可以访问,net.state_dict()可以访问
  4. register_buffer默认requires_grad=False,和模型绑定,net.parameters()访问不到,net.state_dict()可以访问
  5. 和模型绑定即表示 会随着模型移动到cuda而同时移动到cuda上;net.parameters()常作为优化器的参数,net.parameters()可以访问即表示 主观上想要进行优化的Tensor(指定requires_grad=True并显式的放到optim.SGD([…]中的 普通Tensor/register_buffer 也可以进行优化);net.state_dict()常作为torch.save的参数,net.state_dict()可以访问即表示主观上想要进行保存的Tensor(普通Tensor无法通过任何方式进行保存,即只能保存Parameter、register_buffer)。
import torch
from torch.nn.parameter import Parameter
from torch import nn
from torch.nn import functional as F


class Net(nn.Module):

    def __init__(self):
        super(Net, self).__init__()

        self.testTensor = torch.tensor([1.0, 1.0])

        self.testParameter = Parameter(torch.tensor([2.0, 2.0]))
		
        # 若指定参数persistent=False,则该register_buffer与普通Tensor相同
        self.register_buffer("testRegister", torch.tensor([3.0, 3.0]))

    def forward(self, x):
        x = F.relu(self.testTensor * x)
        x = F.relu(self.testParameter * x)
        x = F.relu(self.testRegister * x)
        return x


net = Net()

# net.parameters()内包含Parameter,不含Tensor和register_buffer
for name, param in net.named_parameters():
    print(f'{name} {param}')
# testParameter Parameter containing:tensor([2., 2.], requires_grad=True)

print('-------------------------------------------------')

# net.state_dict()内包含Parameter和register_buffer,不含Tensor
for state in net.state_dict():
    print(f'{state}')
# testParameter
# testRegister

print('-------------------------------------------------')

# Parameter和register_buffer和模型绑定,即会随着模型移动到cuda而同时移动到cuda上
print(f'{net.testTensor} {net.testParameter} {net.testRegister}')
# tensor([1., 1.]) Parameter containing:tensor([2., 2.], requires_grad=True) tensor([3., 3.])
net = net.cuda()
print(f'{net.testTensor} {net.testParameter} {net.testRegister}')
# tensor([1., 1.]) Parameter containing:tensor([2., 2.], device='cuda:0', requires_grad=True) tensor([3., 3.], device='cuda:0')
import torch
from torch.nn.parameter import Parameter
from torch import nn
from torch.nn import functional as F
from torch import optim


class Net(nn.Module):

    def __init__(self):
        super(Net, self).__init__()

        self.testTensor = torch.tensor([1.0, 1.0])

        self.testParameter = Parameter(torch.tensor([2.0, 2.0]))

        self.register_buffer("testRegister", torch.tensor([3.0, 3.0]))

    def forward(self, x):
        x = F.relu(self.testTensor * x)
        x = F.relu(self.testParameter * x)
        x = F.relu(self.testRegister * x)
        return x


# net.parameters()常作为优化器的参数,net.parameters()可以访问即表示主观上想要进行优化的Tensor
net = Net()
inp = torch.tensor([5.0, 5.0])
out = net(inp)
loss = F.mse_loss(out, torch.tensor([3.0, 3.0]))

optimizer = optim.SGD(net.parameters(), lr=0.01)
print(f'before: {net.testTensor} {net.testParameter} {net.testRegister}\n')
optimizer.zero_grad()
loss.backward()
optimizer.step()
print(f'after: {net.testTensor} {net.testParameter} {net.testRegister}\n')
# before: tensor([1., 1.]) Parameter containing:
# tensor([2., 2.], requires_grad=True) tensor([3., 3.])
# 
# after: tensor([1., 1.]) Parameter containing:
# tensor([-2.0500, -2.0500], requires_grad=True) tensor([3., 3.])

print('-------------------------------------------------')

# 指定requires_grad=True并显式的放到optim.SGD([...]中的 普通Tensor/register_buffer 也可以进行优化
net = Net()
net.testTensor.requires_grad = True
net.testRegister.requires_grad = True
inp = torch.tensor([5.0, 5.0])
out = net(inp)
loss = F.mse_loss(out, torch.tensor([3.0, 3.0]))

tmpParams = list(net.parameters())
tmpParams.extend([net.testTensor, net.testRegister])
optimizer = optim.SGD(tmpParams, lr=0.01)
print(f'before: {net.testTensor} {net.testParameter} {net.testRegister}\n')
optimizer.zero_grad()
loss.backward()
optimizer.step()
print(f'after: {net.testTensor} {net.testParameter} {net.testRegister}\n')
# before: tensor([1., 1.], requires_grad=True) Parameter containing:
# tensor([2., 2.], requires_grad=True) tensor([3., 3.], requires_grad=True)
# 
# after: tensor([-7.1000, -7.1000], requires_grad=True) Parameter containing:
# tensor([-2.0500, -2.0500], requires_grad=True) tensor([0.3000, 0.3000], requires_grad=True)
  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 打赏
    打赏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

MallocLu

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值