深度学习之pytorch张量与张量运算

1. pytorch张量

PyTorch最基本的操作对象是张量,张量是PyTorch中重要的数据结构,可认为是一个高维数组。

张量类似NumPy的数组(ndarray),与ndarray不同的是,张量可以在GPU上使用以加速计算。

事实上,张量和NumPy的数组通常可以共享相同的底层内存,无须复制数据。

一般的,标量(scalar)是只有大小没有方向的量,如1、2、3等;

向量(vector)是有大小和方向的量,如[1, 2, 3];

矩阵(matrix)是由多个向量组成的,如[[1, 2, 3], [4, 5, 6]]。

张量是基于向量和矩阵的推广,我们可以将标量视为零阶张量,向量可以视为一阶张量,矩阵就是二阶张量。

总之,张量是支持高效的科学计算的数组,它可以是一个数(标量)、一维数组(向量)、二维数组(矩阵)和更高维的数组(高阶数据)

1.1 初始化张量

我们可以使用多种形式初始化张量,如可直接从Python数据创建张量,无须指定类型,PyTorch会自动推荐其类型,可通过张量的dtype属性查看其类型。

import torch

t = torch .tensor([1, 2]) #创建一个张量
print(t) #输出tensor([1,2])
print(t.dtype) #输出torch.int64

在创建张量时,如果想直接创建为float类型,可使用torch.FloatTensor()方法;如果需要明确地创建为int类型,可使用torch.LongTensor()方法。

这两种类型是PyTorch中使用最多、最常见的两种类型。代码如下:

t = torch.FloatTensor( [1, 2])
print(t) # 输出tensor([1., 2.])
print(t.dtype) # torch.float32
t=torch.LongTensor([1, 2])
print(t) #输出tensor([1, 2])
print(t.dtype) # torch.int64

也可以使用torch.from_numpy()方法从NumPy数组ndarray创建张量。

np_array = np.array([[1,2],[3,4]]) # 创建一个ndarray
t_np = torch.from_numpy(np_array) #从ndarray 创建张量
print(t_np)
'''
tensor([[1, 2],
        [3, 4]], dtype=torch.int32)
'''

1.2 张量类型

与ndarray类型类似,张量的基础数据类型主要包含以下几种。

  1. 32位浮点型:torch.float32/torch.float。

  2. 64位浮点型:torch.float64。

  3. 16位浮点型:torch.float16。

  4. 64位整型:torch.int64/torch.long。

  5. 32位整型:torch.int32。

  6. 16位整型:torch.int16。

  7. 8位整型:torch.int8。

其中32位浮点型和64位整型是最常用的类型,这两种类型也常常被表示为torch.float和torch.long,也就是说,torch.float32等价于torch.float,torch.int64等价于torch.long,这两种类型正好对应上面的两种创建张量的方法torch.FloatTensor()和torch.LongTensor()。我们可在构造张量时使用dtype明确其类型。

t = torch. tensor ([1,2],dtype=torch.float32)
print(t)# 输出tensor([1., 2.])
print (t.dtype)#输出torch.float32

#也可以使用torch.float作为dtype的参数
t= torch.tensor ([1,2], dtype=torch.float)
print(t.dtype)#输出torch.float32
print(torch.float == torch.float32)#返回True
print(torch.long == torch.int64)#返回True

以上代码中直接将torch.float与torch.float32做相等判断,判断后发现返回结果为True,说明这两个写法是完全等价的。PyTorch针对torch.float32和torch.int64类型有专门这样的简写形式是因为,这两种类型特别重要,模型的输入类型一般都是torch.float32,而模型分类问题的标签类型一般为torch.int64。

t = torch.tensor([1,2], dtype=torch.float32)
print(t.dtype) # 输出torch.float32
#type ()方法转换类型
t = t.type(torch.int64)
print(t.dtype) #输出torch.int64

编写代码过程中,由于转换为torch.float32和torch.int64两个类型最常见,框架提供了两个快捷的实例转换方法,即float()方法和long()方法。代码如下。

t = torch.tensor([1, 2], dtype=torch.float32)
t = t.long()
print(t.dtype)#输出torch.int64
t = t.float()
print(t.dtype)#输出torch.float32

这两个写法很简洁,在以后涉及类型转换时很常见。

1.3 创建随机值张量

可使用torch.rand()方法创建0~1均匀分布的随机数,使用torch.randn()方法创建标准正态分布随机数,使用torch.zeros()和torch.ones()方法创建全0和全1的张量,代码如下。

t = torch.rand(2,3)
print(t)
'''
tensor([[0.5632, 0.5258, 0.8128],
        [0.1759, 0.5175, 0.8469]])
'''



t = torch.randn(2,3)
print(t)
'''
tensor([[ 1.9799, -1.5323, -0.4381],
        [ 0.0467, -0.2860,  0.9599]])
'''


t = torch.zeros(3)
print(t)
'''
tensor([0., 0., 0.])
'''


t = torch.ones(3,2)
print(t)

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

还可以从另一个张量创建新的张量,除非明确覆盖,否则新的张量保留原来张量的属性(形状、数据类型),代码如下。

t = torch.ones(3,2)
print(t)
x = torch.zeros_like(t) #类似的还有torch.ones_like ()
print(x) #输出形状和类型与上面创建的张量t相同的全0张量
x = torch.rand_like(t)
print(x)#输出形状和类型与上面创建的张量t相同的0~1均匀分布的张量

'''
tensor([[1., 1.],
        [1., 1.],
        [1., 1.]])
tensor([[0., 0.],
        [0., 0.],
        [0., 0.]])
tensor([[0.9392, 0.9222],
        [0.1343, 0.1274],
        [0.6039, 0.5655]])
'''

1.4 张量属性

tensor.shape属性可返回张量的形状,它与tensor.size()方法等价,后者更灵活,可以通过给定参数返回某一个维度的形状;

tensor.dtype属性可返回当前张量的类型,代码如下。

t = torch.ones(2,3,dtype=torch.float64)

print(t.shape) #输出torch.Size([2, 3])
print(t.size())  #输出torch.Size([2, 3])
print(t.size(1)) # 输出第一维度大小3
print(t.dtype) # 输出torch.float64
print(t.device) # 输出cpu

我们可通过使用tensor.device属性查看当前张量所在的设备(device)。直接创建的张量都在内存中,所以显示的device是CPU,如果是显存中的张量,则显示为CUDA。

1.5 将张量移动到显存

张量可进行算术运算、线性代数、矩阵操作等计算,这些计算既可以在CPU上运行,也可在GPU上运行,在GPU上的运算速度通常高于CPU。

默认情况下是在CPU上创建张量。如果有可用的GPU,可以使用tensor.to()方法明确地将张量移动到GPU,代码如下:

t = torch.ones(2,3,dtype=torch.float64)
#如果GPU 可用,将张量移动到显存
if torch.cuda.is_available ( ) :
    t = t.to('cuda')
print(t.device) #如果 GPU可用,输出类似 cuda:0

在编码过程中,一般可用如下代码获取当前可用设备:

device = torch.device('cuda' if torch.cuda.is_available() else 'cpu')
print(device) # cuda
t = t.to(device)
print(t.device) # cuda:0

这样设置要比直接使用t.to(‘cuda’)更稳妥,可以确保代码中无论有无GPU都能正常运行,并且如果GPU可用就用GPU。

2. 张量运算

张量的运算规则、切片索引规则与NumPy类似,运算中遵循广播原则和同形状相同位置元素对齐运算的原则,代码如下:

t1 = torch.randn(2,3 )
t2 = torch.ones(2,3)
print('===t1=====')
print(t1)
print('===t1=====')
print('-------t2----------')
print(t2)
print('-------t2----------')
print(t1 + 3) # t1中每一个元素都加3
print(t1 + t2) # t1与t2中每一个相同位置的元素相加
print(t1.add(t2)) #等价于t1+t2,结果与上面相同
print('*******当前t1*******')
print(t1) #输出当前t1值
print('*******当前t1*******')
t1.add_(t2) #运行add这个计算方法,add_方法中的下画线代表就尤地改变原值
print(t1) #查看运算结果

'''
===t1=====
tensor([[ 0.5889, -0.3709, -0.1448],
        [-0.0836,  1.3802, -1.3291]])
===t1=====
-------t2----------
tensor([[1., 1., 1.],
        [1., 1., 1.]])
-------t2----------
tensor([[3.5889, 2.6291, 2.8552],
        [2.9164, 4.3802, 1.6709]])
tensor([[ 1.5889,  0.6291,  0.8552],
        [ 0.9164,  2.3802, -0.3291]])
tensor([[ 1.5889,  0.6291,  0.8552],
        [ 0.9164,  2.3802, -0.3291]])
*******当前t1*******
tensor([[ 0.5889, -0.3709, -0.1448],
        [-0.0836,  1.3802, -1.3291]])
*******当前t1*******
tensor([[ 1.5889,  0.6291,  0.8552],
        [ 0.9164,  2.3802, -0.3291]])
'''

在PyTorch中,如果一个运算方法后面加上下画线,代表就地改变原值,即t1.add_(t2)会直接将运算结果保存为t1,这样做可以节省内存,但是缺点是会直接改变t1原值,因此在使用的时候需要谨慎。

张量可以进行常见的算术运算,如abs(绝对值)、cunsum(累加)、divide(除)、floor_divide(整除)、mean(均值)、min(最小值)、max(最大值)、multiply(乘)等,这里不再一一演示。在深度学习中,矩阵运算常用到转置(tensor.T)和矩阵乘法(matmul或@),代码如下:

t1 = torch.randn(2,3 )
t2 = torch.ones(2,3)
print('===t1=====')
print(t1)
print('===t1=====')
print('-------t2----------')
print(t2)
print('-------t2----------')
print(t1.matmul(t2.T))   # t1与t2的转置进行矩阵乘法
print(t1@(t2.T))       # 与上一行运算等价,t1与t2的转置进行矩阵乘法

'''
===t1=====
tensor([[ 0.9426, -1.2947,  1.6743],
        [-0.8599, -0.1431,  0.4690]])
===t1=====
-------t2----------
tensor([[1., 1., 1.],
        [1., 1., 1.]])
-------t2----------
tensor([[ 1.3222,  1.3222],
        [-0.5339, -0.5339]])
tensor([[ 1.3222,  1.3222],
        [-0.5339, -0.5339]])
'''

对于只有一个元素的张量,可以使用tensor.item()方法将其转换为标量,也就是Python的基本类型。

t1 = torch.ones(2,3)
print(t1)
t3 = t1.sum()
print(t3)# t3是只有一个元素的张量,输出tensor(6.)
print(t3.item( ))#输出一个Python浮点数,6.0

以上代码中,首先将张量中所有的元素求和,得到只有一个元素的张量,然后使用tensor.item()方法将其转为标量,这种转换在我们希望打印模型正确率和损失值的时候很常见。

2.1 与NumPy数据类型的转换

上面已经演示过可使用torch.from_numpy()方法将ndarray转为张量,张量也可以使用tensor.numpy()方法得到它对应的ndarray数组,它们共用相同的内存。

a = np.random.randn(2,3) #创建一个形状为(2,3)的ndarray
print(a) #输出此ndarray
t = torch.from_numpy(a) #使用此ndarray创建一个张量
print(t) #输出创建的张量
print(t.numpy()) #使用tensor.numpy()方法获得张量对应的ndarray

'''
[[ 0.06039446 -1.77297262  0.85665315]
 [ 1.18995835 -1.60017137 -0.10981338]]
tensor([[ 0.0604, -1.7730,  0.8567],
        [ 1.1900, -1.6002, -0.1098]], dtype=torch.float64)
[[ 0.06039446 -1.77297262  0.85665315]
 [ 1.18995835 -1.60017137 -0.10981338]]
'''

2.2 张量的变形

tensor.size()方法和tensor.shape属性可以返回张量的形状。

当需要改变张量的形状时,可以通过tensor.view()方法,这个方法相当于NumPy中的reshape方法,用于改变张量的形状。

需要注意的是,在转换过程中要确保元素数量一致。代码如下:

t = torch.randn(4, 6)#创建一个形状为(4,6)的张量
print(t.shape)# 输出t的形状为torch.Size([4, 6])
t1 = t.view(3, 8)#调整成(3,8)形状,这里元素数量是相等的
print(t1.shape)#输出t1的形状为torch.size([3,8])

#现在我们需要将t展平为最后一个维度为1的张量
#第二个参数1代表第二维长度为1,参数-1表示根据元素个数自动计算第一维
t1 = t.view(-1, 1)
print(t1.shape) #输出t1的形状为torch.Size([24, 1])

#也可以使用view增加维度,当然元素个数是不变的
t1 = t.view(1, 4, 6) #调整成三维的,其中第一维的长度为1
print(t1.shape)#输出t1的形状为torch.Size([1, 4, 6])

对于维度长度为1的张量,可以使用torch.squeeze()方法去掉长度为1的维度,相应的也有一个增加长度为1维度的方法,即torch.unsqueeze(),代码如下:

t = torch.randn(4, 6) #创建一个形状为(4,6)的张量
t1 = t.view(1, 4, 6) #调整成三维的,其中第一维的长度为1
print(t1.shape) #输出t1的形状为torch.Size([1, 4, 6])
t2 = torch.squeeze(t1)
print(t2.shape) #输出t2的形状为torch.Size([4, 6])
t3 = torch.unsqueeze(t2, 0) #参数0表示指定在第一个维度上增加维度
print(t3.shape) #输出t3的形状为torch.Size([1, 4, 6])

2.3 张量的自动微分

在PyTorch中,张量有一个requires_grad属性,可以在创建张量时指定此属性为True。

如果requires_grad属性被设置为True,PyTorch将开始跟踪对此张量的所有计算,完成计算后,可以对计算结果调用backward()方法,PyTorch将自动计算所有梯度。该张量的梯度将累加到张量的grad属性中。张量的grad_fn属性则指向运算生成此张量的方法。

简单地说,张量的requires_grad属性用来明确是否跟踪张量的梯度,grad属性表示计算得到的梯度,grad_fn属性表示运算得到生成此张量的方法。

代码演示如下,注意查看代码中的注释:

t = torch.ones(2, 2, requires_grad=True) #这里设置requires_grad为True
print(t.requires_grad) # 输出是否跟踪计算张量梯度,输出 True
print(t.grad) # tensor.grad输出张量的梯度,输出为None,表示目前t没有梯度
print(t.grad_fn) # 指向运算生成此张量的方法tensor.grad_fn,这里为None
#进行张量运算,得到y
y = t + 5
print(y) #tensor([[6., 6.],[6., 6.]], grad_fn=<AddBackward0>)

#由于y是运算而创建的,因此grad_fn属性不是空
print(y.grad_fn) # 输出类似<AddBackward0 object at 0x0000016BDB1E06C8>

#进行更多运算
z = y * 2
out = z.mean()#求张量的均值,实例方法
print(out) #输出tensor(12., grad_fn=<MeanBackward0>)

上面代码中,首先创建了张量,并指定requires_grad属性为True,因为这是一个新创建的张量,它的grad和grad_fn属性均为空。然后经过加法、乘法和取均值运算,我们得到了out这个最终结果。注意:现在out只有单个元素,它是一个标量值。

下面在out上执行自动微分运算,并输出t的梯度(d(out)/d(x)微分运算的结果),代码如下:

out.backward()
#自动微分运算,注意out是标量值
#下面输出t的梯度,也就是d(out)/d(x)微分运算的结果
print(t.grad)
#输出tensor([[0.5000, 0.5000],[0.5000, 0.5000]])

当张量的requires_grad属性为True时,PyTorch一直跟踪记录此张量的运算;当不需要跟踪计算时,可以通过将代码块包装在with torch.no_grad():上下文中,防止PyTorch继续跟踪此张量的运算,代码如下:

print(t.requires_grad) #输出True,PyTorch 跟踪此张量的运算
print((t + 2).requires_grad) # 输出True,PyTorch 跟踪此张量的运算
# 将代码块包装在with torch.no_grad () :上下文中
with torch.no_grad():
    print((t + 2).requires_grad) # 输出False,PyTorch没有继续跟踪此张量的运算

也可使用tensor.detach()方法获得具有相同内容但不需要跟踪运算的新张量,可以认为是获取张量的值,代码如下:

print(out.requires_grad) # 输出True
s = out.detach() # 获取out的值,也可以使用out.data()方法
print(s.requires_grad) # 输出False

可使用requires_grad_()方法就地改变张量的这个属性,当我们希望模型的参数不再随着训练变化时,可以使用此方法,代码如下:

print(t.requires_grad)#输出True
t.requires_grad_(False)# 就地改变requires_grad为False
print(t.requires_grad)# 输出False
  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 打赏
    打赏
  • 0
    评论

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

打赏作者

冰履踏青云

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

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

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

打赏作者

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

抵扣说明:

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

余额充值