PyTorch的张量tensor和自动求导autograd详解

 更新时间:2024年02月27日 10:04:56   作者:Vic·Tory  
这篇文章主要介绍了PyTorch的张量tensor和自动求导autograd,具有很好的参考价值,希望对大家有所帮助,如有错误或未考虑完全的地方,望不吝赐教

PyTorch是一个基于python的科学计算包,除了可以替代NumPy构建数组张量来利用GPU进行计算之外,还是一个高灵活性、速度快的深度学习平台

张量

张量(Tensor)实际上是一个多维数组,使用GPU可以加速张量的运算。

创建tensor:可以通过torch.tensor()将普通数组转化为tensor,torch.empty()rand()可以创建随机tensor,torch.zeros()ones()创建数据都是0或1的tensor。

t.clone()可以克隆一个张量,t.size()可以获取张量的形状。

import torch

t = torch.tensor([[5, 6], [7, 8]])  # 转化为tensor
t1 = torch.empty(5, 3)  # 创建随机5×3的tensor
t2 = torch.rand(5, 3)
t3 = torch.zeros(5, 3, dtype=torch.float16)  # 创建全为0的5×3数组
t4 = torch.ones(5, 3)	# 创建全为1的
t5 = torch.ones_like(t3)	# 创建形状和t3一样、全为1的数组
print(t5.size())  # 输出:torch.Size([5, 3])

操作tensor:tensor可以像Numpy一样直接对数组进行索引、截取、切片等操作。

  • view(-1,2)可以改变tensor的形状,类似于numpy.reshape(),这里修改为第一维长度为2,第二维-1代表自动计算维度。
  • permute()可以交换数组维度位置,类似于numpy.transpose()
  • squeeze()可以压缩张量中长度为1的维度,比如数组[[1,2,3]]形状为(1,3),经过压缩后变成长度为3的数组[1,2,3]。反之,unsqueeze()可以在指定位置将维度增加长度为1
  • expand()可以按原数据为元素扩大维度,即将原来的数据复制多份并一起组成更高维度,扩大tensor不需要分配新内存,只是仅仅新建一个tensor的视图。使用时注意保持新维度与原维度的统一。
t = torch.tensor([[1, 2, 3],
                  [4, 5, 6]])
print(t[0, :2])  # 选取首行前两个元素,输出tensor([1, 2])
print(t.view(-1, 2))	# 改变形状
'''
tensor([[1, 2],
        [3, 4],
        [5, 6]])
'''
t = torch.unsqueeze(t, dim=0)  # 给张量第一维增加一个维度
print(t)
'''
tensor([[[1, 2, 3],
         [4, 5, 6]]])
'''
print(t.permute(2, 1, 0))  # 交换维度位置
'''
tensor([[1, 3, 5],
        [2, 4, 6]])
'''
print(t.expand(2, 2, 3))    # 将(2,3)的tensor扩维成为(2,2,3)
'''
tensor([[[1, 2, 3],
         [4, 5, 6]],
        [[1, 2, 3],
         [4, 5, 6]]])
'''

通过tensor进行四则运算也十分方便,下面以加法为例记录几种使用的形式:

x = torch.tensor([[1, 2, 3],
                  [4, 5, 6]])
y = torch.tensor([[7, 8, 9],
                  [10, 11, 12]])
print(x + y)  # 用运算符
print(torch.add(x, y))  # 用add()函数
z = torch.empty(2, 3)
torch.add(x, y, out=z)  # 指明输出对象
print(z)
y.add_(x)  # 在原数组上操作
print(y)

值得注意的是,pytorch中在原张量上进行的操作函数后面都跟有一个_,例如其他的还有在原张量上复制x.copy_()。

和NumPy数组相互转化:通过tensor.numpy()可以将tensor转为numpy数组,但二者会共享一个内存空间,即对tensor进行的修改操作之后,输出numpy的结果也会随之改变。如果希望产生一个新的可以通过clone()函数。通过torch.from_numpy()可以从numpy数组创建tensor,同样地二者共享内存。

t = torch.ones(5)
print(t)
n1 = t.numpy()  # 转化为numpy
n2 = t.clone().numpy()
t.add_(1)  # 对原来的tensor进行操作
print(n1)
print(n2)

n = np.ones(5)	
t2 = torch.from_numpy(n)	# 从numpy创建tensor
'''
tensor([1., 1., 1., 1., 1.])	原来的t
[2. 2. 2. 2. 2.]				操作t后对应的n1也随之改变
[1. 1. 1. 1. 1.]				但是克隆产生的n2不会改变
'''

使用不同设备计算tensor,例如下面使用to()来将tensor移入和移出GPU

if torch.cuda.is_available():
    device = torch.device("cuda")  # a CUDA device object
    y = torch.ones_like(x, device=device)  # 直接在GPU上创建tensor
    x = x.to(device)  # 或者使用.to("cuda"),将tensor移入GPU
    z = x + y
    print(z)
    print(z.to("cpu", torch.double))  # 将tensor移出GPU,也能在移动时改变dtype
'''
tensor([2., 2., 2., 2., 2.], device='cuda:0')
tensor([2., 2., 2., 2., 2.], dtype=torch.float64)
'''

自动求导

PyTorch中,所有神经网络的核心是 autograd 包,它为张量上的所有操作提供了自动求导机制。

Pytorch通过Tensor来储存数据,我们可以把它看作一个节点,通过Function实现数据操作,即节点之间转换的路径。

如下所示为一个训练过程,输入张量x,经过一系列操作之后得到输出y。

如下所示为实现上面过程的代码,首先通过tensor定义张量,通过属性requires_grad指定是否需要自动求导。之后执行正向操作*w1、+b、*w2,最后求平均得到输出y。

接着通过backward()执行反向传播,就可以得到张量的grad值了。

# 定义张量
x = torch.ones(5, requires_grad=True)
w1 = torch.tensor(2.0, requires_grad=True)
w2 = torch.tensor(3.0, requires_grad=True)
b = torch.tensor(4.0, requires_grad=False)

# 执行正向操作
l1 = x * w1
l2 = l1 + b
l3 = l2 * w2
y = l3.mean()

# 反向传播
y.backward()

print(l1.data, l1.grad, l1.grad_fn)
# tensor([2., 2., 2., 2., 2.]) None <MulBackward0 object at 0x0000024D8E921BE0>
print(l2.data, l2.grad, l2.grad_fn)
# tensor([6., 6., 6., 6., 6.]) None <AddBackward0 object at 0x000001B960FC0F98>
print(y)
# tensor(18., grad_fn=<MeanBackward0>)
print(w1.grad, w2.grad)
# tensor(3.) tensor(6.)
print(x.grad)
# tensor([1.2000, 1.2000, 1.2000, 1.2000, 1.2000])

从上面的输出可以看到x一开始为[1,1,1,1,1],乘以w1后为[2., 2., 2., 2., 2.],+b之后为[6., 6., 6., 6., 6.],再乘以3为[18., 18., 18., 18., 18.],求均值得到y为18。

Tensor的grad_fn属性用于记录上一步是经过怎样的操作得到自己的,用于反向传播时进行求导。例如l1.grad_fn为MulBackward0代表其反向传播操作为乘法

反向传播后通过grade属性可以获得最后输出对本张量的导数值。例如上面的操作中输出为y,反向传播后,w2.grad获得的就是dy/dw2。由链式求导法则可得dy/dw2=dy/dl3 × dl3/dw2。由于y由l3求均值得到,即y=1/5(Σl3),所以dy/dl3=1/5,又l3=l2 * w2,所以dl3/dw2=l2,所以dy/dw2=1/5*l2=1/5(6., 6., 6., 6., 6.)=6。同理通过反向链式求导得到w1、x的grad

注意到l1、l2的grade值为None,这是由于张量l1、l2、l3都是中间计算结果,它们被称为非叶张量,相对地由用户创建的x、w1、w2被称为叶张量(leaf tensor)。pytorch为了节约内存并不会保存中间张量的导数值,只会用grad_fn来记录是通过什么操作产生的。如果我们希望查看的话,可以通过retain_grad()来保存非叶张量的导数值。

...
l1.retain_grad()
l2.retain_grad()
y.backward()

print(l1.data, l1.grad, l1.grad_fn)
# tensor([2., 2., 2., 2., 2.]) tensor([0.6000, 0.6000, 0.6000, 0.6000, 0.6000]) <MulBackward0 object at 0x000001DF52100F60>
print(l2.data, l2.grad, l2.grad_fn)
# tensor([6., 6., 6., 6., 6.]) tensor([0.6000, 0.6000, 0.6000, 0.6000, 0.6000]) <AddBackward0 object at 0x000001DF52111048>

如果我们设置了张量requires_grad,但在某个训练过程中不需要记录梯度grad,可以把代码块包裹在with torch.no_grad():中,这样里面的所有张量都不会保存grad

x = torch.ones(5, requires_grad=True)
print((x ** 2).requires_grad)   # 输出True,记录grad

with torch.no_grad():
    print((x ** 2).requires_grad)   # 输出False,不记录

总结

以上为个人经验,希望能给大家一个参考,也希望大家多多支持脚本之家。 

相关文章

  • python中wx将图标显示在右下角的脚本代码

    python中wx将图标显示在右下角的脚本代码

    python中wx将图标显示在右下脚的代码,此程序摘自wxdemo,不够完善,只供参考用
    2013-03-03
  • 使用Python绘制三种概率曲线详解

    使用Python绘制三种概率曲线详解

    这篇文章主要为大家分享了如何利用Python实现概率曲线的绘制,文中绘制了正态分布的曲线和指数分布的曲线,感兴趣的可以了解一下
    2022-03-03
  • 使用Pytest.main()运行时参数不生效问题解决

    使用Pytest.main()运行时参数不生效问题解决

    本文主要介绍了使用Pytest.main()运行时参数不生效问题解决,文中通过示例代码介绍的非常详细,对大家的学习或者工作具有一定的参考学习价值,需要的朋友们下面随着小编来一起学习学习吧
    2023-02-02
  • Python进阶之尾递归的用法实例

    Python进阶之尾递归的用法实例

    本篇文章主要介绍了Python进阶之尾递归的用法实例,小编觉得挺不错的,现在分享给大家,也给大家做个参考。一起跟随小编过来看看吧
    2018-01-01
  • Python延迟绑定问题原理及解决方案

    Python延迟绑定问题原理及解决方案

    这篇文章主要介绍了Python延迟绑定问题原理及解决方案,文中通过示例代码介绍的非常详细,对大家的学习或者工作具有一定的参考学习价值,需要的朋友可以参考下
    2020-08-08
  • 创建Django项目图文实例详解

    创建Django项目图文实例详解

    这篇文章主要介绍了创建Django项目,结合图文与实例形式详细分析了Django项目创建的具体步骤与相关操作技巧,需要的朋友可以参考下
    2019-06-06
  • Python多继承顺序实例分析

    Python多继承顺序实例分析

    这篇文章主要介绍了Python多继承顺序,结合实例形式分析了Python多继承情况下继承顺序对同名函数覆盖的影响,需要的朋友可以参考下
    2018-05-05
  • python面向对象值元类的声明周期详解

    python面向对象值元类的声明周期详解

    这篇文章主要介绍python的元类生命周期,我们可以和之前探讨类的生命中周期一样,我们写一个案例,使用print来输出一些信息,来判断如果基于元类而言,那么生命周期是怎么样的,文中有详细的代码示例,需要的朋友可以参考下
    2023-05-05
  • Python 实用技巧之利用Shell通配符做字符串匹配

    Python 实用技巧之利用Shell通配符做字符串匹配

    这篇文章主要介绍了Python 实用技巧之利用Shell通配符做字符串匹配的方法,本文给大家介绍的非常详细,具有一定的参考借鉴价值,需要的朋友可以参考下
    2019-08-08
  • python学习之基于Python的人脸识别技术学习

    python学习之基于Python的人脸识别技术学习

    面部识别技术的应用越来越广泛,它广泛应用于安全系统、人机交互、社交媒体、医疗保健等领域。本文介绍了基于Python的人脸识别技术,感兴趣的小伙伴可以参考阅读
    2023-03-03

最新评论