PyTorch是一个Python包,用于将数据封装成张量(Tensor)来进行运算。张量是向量和矩阵的推广。PyTorch中的张量就是元素为同一种数据类型的多维矩阵。
目录
- 1.定义张量的方法
- 2.生成随机值张量
- 3.张量的操作
- 4.在CPU和GPU控制的内存中定义张量
- 5.张量间的数据操作
- 5.1用reshape()函数实现数据维度的变换
- 5.2用squeeze()函数实现数据的压缩
- 5.3实现张量数据的矩阵转置
- 5.4view()方法与contiguous()方法
- 5.5用torch.cat()函数实现数据连接
- 5.6用torch.chunk()函数实现数据均匀分割
- 5.7用torch.split()函数实现数据不均匀分割
- 5.8用torch.gather()函数对张量数据进行检索
- 5.9按照指定的阈值对张量进行过滤
- 5.10用torch.nonzero()函数找出张量中的非0值索引
- 5.11用torch.where()函数根据条件对多个张量取值
- 5.12用torch.clamp()函数根据阈值进行数据截断
- 5.13获取数据中最大值、最小值的索引
1.定义张量的方法
1.1将已有数值转换成张量
函数torch.tensor()可以将传入的对象转换成张量。该函数不仅支持Python中的原生类型,还支持Numpy类型。
import torch
import numpy as np
a = torch.tensor(5)
print(a) # 输出:tensor(5)
anp = np.asarray([4])
a = torch.tensor(anp)
print(a) # 输出:tensor([4]. dtype=torch.int32)
1.2根据指定形状、类型生成张量
张量类型 | 函数 | dtype |
---|---|---|
系统的默认类型 | torch.Tensor() | torch.float32 or torch.float |
浮点型 | torch.FloatTensor() | torch.float32 or torch.float |
整型 | torch.IntTensor() | torch.int32 or torch.int |
双精度浮点型 | torch.DoubleTensor() | torch.float64 or torch.double |
长整型 | torch.LongTensor() | torch.int64 or torch.long |
字节型 | torch.ByteTensor() | torch.uint8 |
字符型 | torch.CharTensor() | torch.int8 |
短整型 | torch.ShortTensor() | torch.int16 or torch.short |
如果没有特殊要求,则直接用函数torch.Tensor()定义的张量是32位浮点型。这与调用torch.FloatTensor()函数定义张量的效果是一样的。
张量类型是由PyTorch中的默认类型来控制的。也可以修改用torch.Tensor生成的张量类型
import torch
print(torch.get_default_dtype()) # 输出默认类型:torch.float32
print(torch.Tensor([1, 3]).dtype) # 输出torch.Tensor()函数返回的类型:torch.float32
torch.set_default_dtype(torch.float64)
print(torch.get_default_dtype()) # 输出默认类型:torch.float64
print(torch.Tensor([1, 3]).dtype) # 输出torch.Tensor()函数返回的类型:torch.float64
在使用上表中的函数定义张量时,可以指定张量的形状,也可以指定张量的内容。以torch.Tensor()函数为例
a = torch.Tensor(2) # 定义一个指定形状的张量
print(a) # 输出:tensor([1.1210e-43, 4.7265e-01])
b = torch.Tensor(1, 2) # 定义一个指定形状的张量
print(b) # 输出:tensor([-1.4754e+04, 4.5909e-41])
c = torch.Tensor([2]) # 定义一个指定内容的张量
print(c) # 输出:tensor([2.])
d = torch.Tensor([1, 2]) # 定义一个指定内容的张量
print(d) # 输出:tensor([1., 2.])
注意:在以指定形状的方式调用torch.Tensor()函数时,得到的张量是没有初始化的。
1.3根据指定形状生成固定值的张量
- torch.ones()函数生成指定形状、值为1的张量数组
- torch.zeros()函数生成指定形状、值为0的张量数组
- torch.ones_like()函数生成与目标张量形状相同、值为1的张量数组
- torch.zeros_like()函数生成与目标张量形状相同、值为0的张量数组
- torch.eye()函数生成对角矩阵的张量
- torch.full()函数生成全为1的矩阵的张量
2.生成随机值张量
2.1设置随机值种子
所有的随机数都是基于种子参数进行生成的。使用torch.initial_seed()函数可以查看当前系统中的随机数种子,使用torch.manual_seed()函数可以设置随机数种子
torch.initial_seed() # 输出:1
torch.manual_seed(2)
torch.initial_seed() # 输出:2
2.2通过指定形状生成随机值
- torch.rand()函数生成指定形状的随机值,随机区间[0,1)
- torch.randn()函数生成指定形状的随机值,随机值的分布式为均值为0,方差为1
- torch.randint(low, high, size)函数生成指定形状的随机值,随机区间[low, high)
2.3生成线性空间的随机值
print(torch.arange(1, 10, step=2)) # 输出:tensor([1, 3, 5, 7, 9])
print(torch.linspace(1, 9, steps=5)) # 输出:tensor([1, 3, 5, 7, 9])
- arange():取值范围只包括起始值,不包括结束值,通过步长来控制取值的个数
- linespace():取值范围既包括起始值,又包括结束值,可以直接指定取值的个数
2.4生成对数空间的随机值
torch.logspace()函数
print(torch.logspace(1, 9, steps=5)) # 输出:rensor([1.0000e+01, 1.0000e+03, 1.0000e+05, 1.0000e+07, 1.0000e+09])
2.5生成未初始化的矩阵
torch.empty()函数
print(torch.empty(1, 2)) # 输出:tensor([[6.9518e-310, 0.0000e+00]])
3.张量的操作
3.1获得张量中元素的个数
torch.numel()函数
a = torch.Tensor(2)
print(torch.numel(a)) # 获得a中元素的个数,输出:2
3.2张量的判断
torch.is_tensor()函数
a = torch.Tensor(2)
print(torch.is_tensor(a)) # 判断a是否为张量,输出:True
3.3张量的类型转换
type()方法
a = tensor.FloatTensor([4])
print(a.type(torch.IntTensor)) # 输出:tensor([4], dtype=torch.int32)
print(a.type(torch.DoubleTensor)) # 输出:tensor([4.], dtype=torch.float64)
3.4张量类中的重载操作符函数
张量之间的运算于Python的基本运算完全一样,除此之外,在张量类中还有很多其他的运算函数。
a = torch.FloatTensor([4])
b = torch.add(a, a)
print(b) # 输出:tensor([8.])
torch.add(a, a, out=b)
print(b) # 输出:tensor([8.])
注意:在以指定输出的方式调用运算函数时,需要确保输出变量已经定义,否则会报错
3.5张量类中的自变化运算符
自变化运算函数是指,在变量本身的基础上做运算,其结果直接作用在变量自身。
a.add_(b)
print(b) # 输出:tensor([12.])
注意:在PyTorch中,所有的自变化运算都会带有一个下划线
3.6张量与Numpy间的相互转换
a = torch.FloatTensor([4])
print(a.numpy()) # 将张量转换成Numpy类型的对象,输出:[4.]
anp = np.asarray([4])
# 将Numpy类型的对象转换成张量,两种方法
print(torch.from_numpy(anp))
print(torch.tensor(anp)) # 输出:tensor([4], dtype=torch.int32)
张量与Numpy类型数据的转换是基于零复制技术实现的。在转换过程中,张量与Numpy数值对象共享同一个内存区域,张量会保留一个指向内部Numpy数组的指针,而不是直接复制Numpy的值。
3.7张量与Numpy各自的形状获取
x = torch.rand(2, 1)
print(x.shape) # 打印张量形状,输出:torch.Size([2 1])
print(x.size()) # 打印张量大小,输出:torch.Size([2 1])
anp = np.asarray([4, 2])
print(anp.shape, anp.size) # 打印Numpy变量的形状和大小,输出:(2,) 2
二者也都可以通过reshape()属性函数进行变形
print(x.reshape([1, 2]).shape) # 输出:torch.Size([1 2])
print(anp.reshape([1, 2]).shape) # 输出:(1 2)
3.8张量与Numpy各自的切片操作
张量与Numpy的切片操作几乎完全一样
x = torch.rand(2, 1)
print(x[:]) # 输出:tensor([[0.1273], [0.3797]])
anp = np.asarray([4, 2])
print(anp[:]) # 输出:[4 2]
3.9张量与Numpy相互转换的陷阱
在将Numpy转换成向量时,只是简单地给指针赋值,并不会发生复制现象。然而这种快捷的方式会带来安全隐患:由于两个变量共享一块内存,所以一旦修改了其中一个变量,则会影响另一个变量的值。
其实PyTorch考虑到了这一点,在Numpy被转成张量后,如果对张量进行修改,则其内部会触发复制机制,额外开辟一块内存并将值复制过去,不会影响原来Numpy的值。
但是在将Numpy转换成张量后,如果对Numpy进行修改,则结果就不一样了,因为Numpy没有PyTorch这种共享内存的设置,这会导致在对Numpy修改时使得张量的值偷偷发生了变化
nparray = np.array([1, 1])
x = torch.from_numpy(nparray)
print(x) # 输出:tensor([1, 1], dtype=toech.int32)
nparray += 1
print(x) # 输出:tensor([2, 2], dtype=toech.int32)
需要又替换内存的运算操作
nparray = np.array([1, 1])
x = torch.from_numpy(nparray)
print(x) # 输出:tensor([1, 1], dtype=toech.int32)
nparray = nparray + 1
print(x) # 输出:tensor([1, 1], dtype=toech.int32)
因为nparray = nparray + 1表示系统会额外复制一份内存将nparray + 1的结果赋值给nparray变量,并没有在nparray的原有内存中进行改变。
4.在CPU和GPU控制的内存中定义张量
Pytorch默认将张量定义在CPU控制的内存中
4.1将CPU内存中的张量复制到GPU内存中
cuda()方法
a = torch.FloatTensor([4])
b = a.cuda()
print(b) # 输出:tensor([4.], device='cuda:0')
同样,如果要将GPU中的张量创建到CPU中,需要cpu()方法
print(b.cpu()) # 输出:tensor([4.])
4.2直接在GPU内存中定义张量
通过调用函数torch.tensor()并指定device参数为cuda
a = torch.tensor([4], device="cuda")
print(q) # 输出:tensor([4], device='cuda:0')
4.3使用to()方法来指定设备
将张量的cpu()和cuda()两种方法合并到一起,通过张量的to()方法来实现
a = torch.FloatTensor([4])
print(a) # 输出:tensor([4.])
print(a.to("cuda:0")) # 输出:tensor([4.], device='cuda:0')
在计算机中,多块GPU卡的编号是从0开始的。"cuda:0"是指使用计算机的第一块GPU卡
4.4使用环境变量CUDA_VISIBLE_DEVICES来指定设备
使用环境变量CUDA_VISIBLE_DEVICES来为代码指定所运行的设备,时最常见的方式。它不需要对代码中的各个变量依次设置,只需要在运行python程序时统一设置依次即可。
在命令行输入如下启动命令:
CUDA_VISIBLE_DEVICES=0 python 自己的代码.py
也可以在代码的最前端加入如下语句:
import os
os.environ["CUDA_VISIBLE_DEVICES"] = "0"
5.张量间的数据操作
5.1用reshape()函数实现数据维度的变换
reshape()函数可以在保证张量矩阵数据不变的前提下改变数据的维度,使其转换成指定的形状。在神经网络的上下层连接时,经常会用到reshape()函数,用于调节数据的形状,使其与下层网络的输入相匹配。
a = torch.tensor([[1, 2], [3, 4]])
print(torch.reshape(a, (1, -1))) # 将其转为只有一行数据的张量,输出:tensor([[1, 2, 3, 4]])
在使用reshape函数时,要求指定的目标形状必须与原有的输入张量的元素个数一致,否则会报错。在指定形状过程中,可以使用-1来代表该维度由系统自动计算。
另外,还可以调用张量的reshape()或view()方法
print(a.reshape((1, -1))) # 将其转为只有一行数据的张量,输出:tensor([[1, 2, 3, 4]])
print(a.view((1, -1))) # 将其转为只有一行数据的张量,输出:tensor([[1, 2, 3, 4]])
5.2用squeeze()函数实现数据的压缩
a = torch.tensor([[1, 2], [3, 4]])
print(torch.squeeze(torch.reshape(a, (1, -1)))) # 输出:tensor([1, 2, 3, 4])
squeeze()函数默认在变形过程中,将输入张量中所有值为1的维度去掉。如果一个张量中值为1的维度有很多,但又不想全部去掉,则可以在函数中通过设定dim参数去掉某一个维度(dim参数所指定的维度必须满足值为1)
如果要删掉一个不为1的维度,则可以使用torch.unbind()函数。
还有一个与torch.squeeze()函数功能相反的函数-----torch.unsqueeze(),它可以为输入张量增加一个值为1的维度。函数定义如下:
torch.unsqueeze(input, dim, out=None)
其中,dim参数用于指定要增加维度的位置,其默认值为1,即在索引维度为1的位置加1
5.3实现张量数据的矩阵转置
torch.t()和torch.transpose()函数都可以实现张量的矩阵转置运算,其中torch.t()函数使用起来比较简单,而torch.transpose()函数功能更为强大,但使用起来较为复杂
b = torch.tensor([[5, 6, 7], [2, 8, 0]])
print(torch.t(b)) # 输出:tensor([[5, 2], [6, 8], [7, 0]])
print(torch.transpose(b, dim0=1, dim1=0)) # 输出:tensor([[5, 2], [6, 8], [7, 0]])
可以看到,torch.transpose()函数接收两个参数----dim0和dim1,分别用于指定原始的维度和转换后的目标维度。
另外,还可以使用张量的permute()方法实现转置:
b.permute(1, 0)
注意:permute可以处理超过二维
5.4view()方法与contiguous()方法
view()方法比reshape()方法更底层,也更不智能,只能作用于整块内存中的张量。
在PyTorch中,有些张量是由不同的数据块组成的,它们并没有分布在相同的整块内存中。view()方法无法对这样的张量数据进行变形处理。同样,view()方法也无法对已经用过transpose()、permute()等方法改变形状后的张量进行变形处理。通过张量的is_contiguous()方法可以判断该张量的内存是否连续。
如果要使用view()方法,则最好和contiguous()方法一起使用。contiguous()方法可以把张量复制到连续的整块内存中。
b = torch.tensor([[5, 6, 7], [2, 8, 0]])
print(b.is_contiguous()) # 输出:True
c = b.transpose(1, 0)
print(c.is_contiguous()) # 输出:False
print(c.contiguous().is_contiguous()) # 输出:True
print(c.contiguous().view(-1)) # 输出:tensor([5, 2, 6, 8, 7, 0])
5.5用torch.cat()函数实现数据连接
用torch.cat()函数可以将两个张量数据按照指定的维度连接起来。
a = torch.tensor([[1, 2], [3, 4]])
b = torch.tensor([[5, 6], [7, 8]])
print(torch.cat([a, b], dim=0)) # 输出:tensor([[1, 2], [3, 4], [5, 6], [7, 8]])
print(torch.cat([a, b], dim=1)) # 输出:tensor([[1, 2, 5, 6], [3, 4, 7, 8]])
还可以使用torch.stack()函数对列表中的多个元素进行合并。该函数于cat()的作用非常相似,只不过要求列表中的张量元素的维度必须一致。
5.6用torch.chunk()函数实现数据均匀分割
用torch.chunk()函数可以将一个多维张量按照指定的维度和拆分个数进行分割。chunks
参数用于指定拆分的个数,dim参数用于指定连接的维度,返回值是一个元组类型。
a = torch.tensor([[1, 2], [3, 4]])
print(torch.chunk(a, chunks=2, dim=0)) # 输出:(tensor([[1, 2]]), tensor([[3, 4]]))
print(torch.chunk(a, chunks=2, dim=1)) # 输出:(tensor([[1], [3]]), tensor([[2], [4]]))
5.7用torch.split()函数实现数据不均匀分割
b = torch.tensor([[5, 6, 7], [2, 8, 0]])
print(torch.split(b, split_size_or_sections=(1, 2), dim=1)) # 输出:(tensor([[5], [2]]), tensor([[6, 7], [8, 0]]))
注意:split_size_or_sections参数为一个具体的数值,代表系统将按照指定的元素个数对张量数据进行拆分。在分割过程中,“不满足指定个数的剩余数据”将被作为分割数据的最后一部分。例如:
torch.split(b, split_size_or_sections=2, dim=1) # 输出:(tensor([[5, 6], [2, 8]]), tensor([[7], [0]]))
5.8用torch.gather()函数对张量数据进行检索
torch.gather()函数与TensorFlow中的gather()函数意义相近,但用法截然不同。该函数的作用是,使张量数据中的值按照指定的索引和顺序进行排列。
b = torch.tensor([[5, 6, 7], [2, 8, 0]])
print(torch.gather(b, dim=1, index=torch.tensor([[1, 0], [1, 2]]))) # 输出:tensor([[6, 5], [8, 0]])
print(torch.gather(b, dim=0, index=torch.tensor([[1, 0, 0]]))) # 输出:tensor([[2, 6, 7]])
在torch.gather()函数中,index参数必须是张量类型,而且要与输入的维度相同。index参数中的值是输入数据中的索引。
如果要从多维张量中取出整行或整列的数据,则可以使用torch.index_select()函数。具体如下:
torch.index_select(b, dim=0, index=torch.tensor(1)) # 输出:tensor([[2, 8, 0]])
其中,index参数可以是一个一维数组,代表所选取数据的索引值。
5.9按照指定的阈值对张量进行过滤
可以通过PyTorch中的逻辑比较函数和掩码取值函数(torch.masked_select())来实现。
a = torch.tensor([[1, 2], [3, 4]])
mask = a.ge(2) # 找出大于或等于2的数
print(mask) # 输出掩码,输出:tensor([[0, 1], [1, 1]], dtype=torch.unit8)
print(torch.masked_select(a, mask)) # 按照掩码取值,输出:tensor([2, 3, 4])
常见的逻辑比较函数有:大于(gt())、大于或等于(ge())、等于(eq())、小于(lt())、小于或等于(le())、不等(ne())
5.10用torch.nonzero()函数找出张量中的非0值索引
eye = torch.eye(3)
print(eye) # 输出:tensor([[1., 0., 0.], [0., 1., 0.], [0., 0., 1.]])
print(torch.nonzero(eye)) # 输出:tensor([[0, 0], [1, 1], [2, 2]])
5.11用torch.where()函数根据条件对多个张量取值
b = torch.tensor([[5, 6, 7], [2, 8, 0]])
c = torch.ones_like(b)
print(c) # 输出:tensor([[1, 1, 1], [1, 1, 1]])
print(torch.where(b>5, b, c)) # 将b中值大于5的取出,否则取c中的值
5.12用torch.clamp()函数根据阈值进行数据截断
该功能常用在梯度的计算过程中:通过一个固定的阈值限制梯度的变化,从而避免出现训练过程中的“梯度爆炸”(模型每次训练的调整至都变得很大,从而导致最终训练过程难以收敛)现象。
a = torch.tensor([[1, 2, 3], [4, 5, 6]])
print(torch.clamp(a, min=2, max=5)) # 输出:tensor([[2, 2, 3], [4, 5, 5]])
5.13获取数据中最大值、最小值的索引
torch.argmax()函数用于返回最大值的索引,torch.argmin()函数用于返回最小值索引。
a = torch.tensor([[1, 2], [3, 4]])
print(torch.argmax(a, dim=0)) # 输出:tensor([1, 1])
print(torch.argmin(a, dim=1)) # 输出:tensor([0 ,0])
还可以使用功能更为强大的torch.max()和torch.min()函数,这两个函数在输出张量数据中最大值、最小值的同时,还会输出其对应的索引。
a = torch.tensor([[1, 2], [3, 4]])
print(torch.max(a, dim=0)) # 输出:(tensor([3, 4]), tensor([1, 1]))
print(torch.min(a, dim=1)) # 输出:(tensor([1, 3]), tensor([0 ,0]))