一、Tensor
1.1 基本概念
Tensor,又名张量,是pytorch中重要的一种数据结构,从工程的角度上来说,可以很简单将其认为是与numpy的nadarray类似的数组,用来保存数据支持高效的科学计算。但是PyTorch中的Tensor支持cuda用GPU加速。
1.2基本操作
从接口的角度来说,对tensor的操作可以分为两类:
(1)torch.function,如torch.save等。
(2)tensor.function,如tensor.view等。
实际上,这两种操作大部分时候等价,只是调用的API不同,做为初学者一般可以不予区分。
从储存的角度来说,对tensor的操作也可以分为两类:
(1)不修改自身的数据,如a.add(b),该操作不会修改a中数据,而是将加法结果返回一个新的tensor。
(2)修改自身的数据,如a.add_(b),该操作会将加法结果储存在a当中,将a原本的数据修改为加法的结果。
实际上,可观察到函数尾有_后缀的都是会修改自身的数据,应用时注意加以区分。
1.3创建Tensor
Tensor(*sizes) | 基础构建函数 |
ones(*sizes) | 构建一个全为1的Tensor |
zeros(*sizes) | 构建一个全为0的Tensor |
eye(*sizes) | 构建一个对角矩阵Tensor |
arange(s, e, step) | 构建一个从s到e,步长为step的Tensor |
linspace(s ,e, steps) | 从s到e,均匀切成steps份 |
rand/randn(*sizes) | 构建一个均匀/标准分布Tensor |
normal(mean, std)/uniform(from, to) | 构建一个正态/均匀分布Tensor |
randperm(m) | 构建一个随机排列Tensor |
下面举几个构建Tensor的例子:
#指定tensor的形状
a = torch.Tensor(2, 3)
#数值决定于当前内存空间的状态,会生成一个2x3大小的Tensor
#用list的数据创建tensor
b = torch.Tensor([1, 2, 3], [4, 5, 6])
#会生成一个2x3大小的Tensor,其中数据按照123456排序
如果要查看tensor的size,使用tensor.size()函数,如:
b = torch.tensor(2, 3)
b_size = b.size()#此函数返回torch.Size([2, 3])其中list[2, 3]表示b为2x3的Tensor
#注:虽然b.size()返回tuple子类数据,但是对其的操作方式与tuple有所区别
b_all_number = b.numel()#此函数可以统计b中所有元素的个数,相当于b.nelement()
下面说明torch.Size对象与tuple的不同。
#要创建一个大小与b相同的数组c
c = torch.Tensor(b.size())
#若使用tuple
d = torch.Tensor((2, 3))
#查看c,d的结果可以看到c是一个2x3大小的Tensor,而d会生成一个元素为2和3的2大小的Tensor
当然除了tensor.size()函数,tensor.shape()函数也可以直接查看torch的形状大小。
其他构建tensor的方法:
#构建大小为2x3,元素全为1的tensor
a = torch.ones(2, 3)
#构建大小为2x3,元素全为0的tensor
b = torch.zeros(2, 3)
#构建从1到6,步长为2的tensor
c = torch.arrage(1, 6, 2)
#将1到10分割平均分割成3份,得到size是3,元素为1.0000, 5.0000, 10.0000的tensor
d = torch.linspace(1, 10, 3)
#构建大小为2x3,元素为标准分布的tensor
e = torch.randn(2, 3)
#构建长度为5的随机排列tensor
f = torch.randperm(5)
#构建大小为2x3,对角线元素为1,其他全为0的tensor
g = torch.eyes(2, 3)
1.4常见tensor操作
1.4.1变形操作
通过tensor.view可以改变tenosr的形状,但是必须保证调整前后元素总数一致,如2x3只能变成1x6或者6x1或者3x2。同时veiw不会改变自身的数据,返回的新的tensor与原来的tensor共享内存。
#构建一个2x3的tensor
a = torch.arrange(0, 6).view(2, 3)
#当某一维是-1时,自动计算其大小
b = a.view(-1, 3)
#如果要再增加一维,使用unsqueeze()函数,如unsqueeze(1)在第一维(下标从0开始)上增加“1”
c = b.unqueeze(1)
在jupyter-notebook上运行unsqueeze()结果如下:
b.unsqueeze(0)表示在第0维上增加一维。
b.unsqueeze(1)表示在第1维上增加一维。
b.unsqueeze(2)表示在第2维上增加一维。
下面展示squeeze()的用法
#设置一个1x1x1x2x3形状的tensor
d = torch.arange(0, 6).view(1, 1, 1, 2, 3)
#压缩第0维的“1”
d.squeeze(0)
#压缩所有维度的“1”
d.squeeze()
在jupyter-notebook上运行结果如下图:
同时由于b由a.view(-1, 3)构建,二者共享内存,当改变a内元素时,b内元素随之改变:
a[1] = 100
#由于a,b共享内存,此时b[1]也应该等于100
在jupyter中查看结果:
可见尽管他们形状不同,但是由于b由a.view()生成,他们依旧共享内存。
另一种resize_()函数同样可以修改tensor的形状尺寸,但是前后的元素总量可变,当新尺寸大于旧尺寸的时候,会自动分配新的内存空间,如果小于,之前的数据依旧会被保存。
如下:
#将b变为1x3的tensor
b.resize_(1, 3)
#将b变为3x3的tensor
b.resize_(3, 3)
1.4.2索引操作
pytorch中对tensor的索引操作在语法上与numpy对ndarry类似。一般情况下,对tensor索引出来的结果与原tensor共享内存。基本操作可见numpy对索引的操作,下面将举例一些常用的选择函数:
index_select(input, dim, index) | 在指定维度dim上选取,例如选取某些行、某些列 |
masked_select(input, mask) | 使用ByteTensor进行选取,与a[a>1]类似 |
non_zero(input) | 选取非0元素的下标 |
gather(input, dim, index) | 根据index,在dim维度上选取数据,输出的size与index一致 |
其中gather操作是一个比较复杂的操作,对于一个Tensor,其输入如下所示:
#生成一个4x4的Tensor
a = torch.arange(0, 16).view(4, 4)
#生成一个1x1的LongTensor保存索引
index = torch.LongTensor([[0, 1, 2, 3]])
a.gather(0, index)
得到:
若要求反对角线的元素:
index_reverse = torch.LongTensor([[3, 2, 1, 0]]).torch()
a.gather(0, index_reverse).view(1, 4)
当然,如果想要两条对角线上面的元素,如下:
#选取正对角线的元素
a = torch.arange(0, 16).view(4, 4)
index = torch.LongTensor([[0, 1, 2, 3], [3, 2, 1, 0]])
a.gather(0, index)
如果不想要索引与原Tensor共享内存,使用Pytorch 0.2以上版本更新的高级索引操作,高级索引是Numpy风格,例:
#构建一个3x3x3的Tensor
a = torch().arange(0, 27).view(3, 3, 3)
#取出a[1, 1, 2]和a[2, 2, 0],即14和24
a[[1, 2], [1, 2], [2, 0]]
#取出a[2, 0, 1],a[1, 0, 1],a[0, 0, 1],即19,10,1
a[[2, 1, 0], [0], [1]]
#取出a[0],a[2],即第一维的第一个以及第三个
a[[0, 2]]
此时改变其中的元素:
#取出a[0, 0, 0]为100,如果共享内存,则原Tensor第一维第一行第一列那个元素为100
b = a[[0], [0], [0]]
b = 100
#查看a[0]
print(a[[0], [0], [0]])
可见不共享内存
1.5 Tensor的类型
Tensor有不同的类型,绝大多数类型都拥有CPU版本和GPU版本。默认的Tensor类型是FloatTensor, 可以通过torch.set_default_tensor_type修改默认的Tensor类型,但是此方法在pytorch更新之后仅支持设置默认类型为浮点类型。
以下是tensor的数据类型
数据类型 | CPU Tensor | GPU Tensor |
32bit浮点 | torch.FloatTensor | torch.cuda.FloatTensor |
64bit浮点 | torch.DoubleTensor | torch.cuda.DoubleTensor |
16bit半精度浮点 | N/A | torch.cuda.HalfTensor |
8bit无符号整型(0~255) | torch.ByteTensor | torch.cuda.ByteTensor |
8bit有符号整型(-128~127) | torch.CharTensor | torch.cuda.CharTensor |
16bit有符号整型 | torch.ShortTensor | torch.cuda.ShortTensor |
32bit有符号整型 | torch.IntTensor | torch.cuda.IntTenso |
64bit有符号整型 | torch.LongTensor | torch.cuda.LongTensor |
要进行类型的转化,type(new_type)是通用的做法,同时可以直接使用long,float,int等类numpy快捷方法。而CPU与GPU之间的转化,使用torch.cuda和torch.cpu来实现。
Tensor中还有一个new方法,用法与torch.Tensor一致。
同时还有type_as函数,用法如下:
1.6逐元素操作
该操作会对tensor中每个元素进行操作,输入与输出的形状一致。常见的操作如下:
abs/sqrt/div/exp/fmod/log/pow | 绝对值/平方根/除法/指数/求余/求对数/求幂 |
cos/sin/tan/asin/atan2/cosh | 三角函数 |
ceil/round/floor/trunc | 上取整/四舍五入/下取整/只保留整数部分 |
clamp(input, min , max) | 超出min和max的部分截断 |
sigmod/tanh | 激活函数 |
对于一般运算可以直接使用运算符来替代,如torch.pow(a, 2) == a ** 2,而对于clamp如下图:
如图,clamp用在比较大小的地方,取tensor中每一个元素与另一个数的最大值或最小值。
#构建一个2x3的Tensor
a = torch.arange(0, 6).view(2, 3)
#使用clamp函数找大的值
b = torch.clamp(a, max = 3)
print(b)
1.7归并操作
归并操作会使两个tensor合并,使得输出比输入小,也可以沿着某一维度单独操作。
mean/sum/median/mode | 均值/和/中位数/众数 |
norm/dist | 范数/距离 |
std/var | 标准差/方差 |
cumsum/cumprod | 累加/累乘 |
以上大多数函数都只有一个参数dim,用来指定这些操作是在哪个维度上执行。假设输入的形状是(m, n, k):
1.如果dim = 0,输出(1, n, k);
2.如果dim = 1,输出(m, 1, k);
3.如果dim = 2,输出(m, n, 1);
是否保留原维度1,取决于keepdim,keepdim = True会保留维度1,默认keepdim = Flase(一般情况)
#构建一个2x3的元素全是1的tensor
b = torch.ones(2, 3)
b.sum(dim = 0, keepdim = True)
#不保留维度“1”, 注意形状
b.sum(dim = 0, keepdim = False)
#对第二维度求和
b.sum(dim = 1)
#沿行累加
a = torch.arange(0, 6).view(2, 3)
a.cumsum(dim = 1)
1.8比较
比较函数中有一些是逐元素比较,操作类似于逐元素操作。
gt/lt/le/eq/ne | 大于/小于/大于等于/小于等于/等于/不等于 |
topk | 最大的k个数 |
sort | 排序 |
max/min | 比较两个tensor的最大值和最小值 |
以上几种函数,第一行的函数可以使用运算符来替代,返回ByteTensor,max/min操作比较特殊,有三种使用情况:
1.torch.max(tensor):返回tensor中最大的一个数;
2.torch.max(tensor, dim):指定维上的最大的数,返回tensor以及下标;
3.torch.max(tensor_1, tensor_2):比较两个tensor中较大的元素。
1.9线性代数
pytorch的线性函数中主要封装了Blas以及Lapack,常用线性代数函数如下:
trace | 对角线元素之和(矩阵的迹) |
diag | 对角线元素 |
triu/tril | 矩阵的上三角/下三角,可指定偏移量 |
mm/bmm | 矩阵乘法,batch的矩阵乘法 |
addmm/addbmm/addmv | 矩阵运算 |
t | 转置 |
dot/cross | 内积/外积 |
inverse | 求逆矩阵 |
svd | 奇异值分解 |
注:矩阵转置时可能会导致存储空间的不同,需要调用.contigous方法将其转为连续。
1.10Tensor与Numpy
Tensor与Numpy有极高的相似性,彼此之间可以简单高效的转化。当Tensor遇到不支持的操作时,可以转为numpy进行操作。
将numpy中的元素进行传入tensor时,可以使用torch.from_numpy函数进行操作,也可以直接使用torch.Tensor传入。由于是Tensor与Numpy的转化,所以在传入时Tensor和Numpy共享内存。
注:广播法则是科学运算中经常使用的一种技巧,可以快速实现向量化运算,有以下法则:
1.让所有输入数组都向其中shape最长的数组看齐,shape中不足的部分通过在前面加1补齐。
2.两个数组要么在某一个维度的长度一致,要么其中一个为1,否则不能计算。
3.当输入数组的某个维度的长度为1时,计算时沿此维度复制扩充成一样的形状。
#构建3x2的元素全为“1”的tensor,构建2x3x1的元素全是“0”的tensor
a = torch.ones(3, 2)
b = torch.zeros(2, 3, 1)
#自动广播法则
#第一步:a是二维,b是三维,所以先在较小的a前面补1
#即:a.unqueeze(0),a的形状变为(1, 3, 2), b的形状是(2, 3, 1)
#第二步:a和b在第一维和第三维的形状不一样,其中一个为1
#可以利用广播法则扩展,两个形状都变成了(2, 3, 2)
a + b
#手动广播法则
#或者a.view(1, 3, 2).expend(2, 3, 2) + b.expand(2, 3, 2)
a.unqueeze(0).expand(2, 3, 2) + b.expand(2, 3, 2)
#使用expand不会占用额外空间,只会在需要时才扩充,可极大地节省内存
1.11内部结构
tensor的数据结构如上图所示,分为头信息区(Tensor)和存储区(storage),头信息区主要保存数据的形状,步长,数据类型等信息,真正的数据储存在存储区,由于头信息区只存储tensor的性质等信息,而存储区存有大量数据,所以tensor的内存占用取决于tensor中元素的数目。
一般来说,一个tensor有相对应的storage,storage是在data上封装的接口,不同的tensor头信息不同但是可以使用相同的storage。
接下来给出部分函数(可用于查看地址等信息):
#一个对象的id值可以看作它在内存中的地址
id(a.storage())
#data_ptr返回tensor首元素的内存地址
a.data_ptr()
#storage_offset返回tensor中的第一个元素于storage中第一个元素的偏移量
a.storage_offset()
#stride()是在指定维度dim中从一个元素跳到下一个元素所必需的步长。当没有参数传入时,返回所有步长的元组。否则,将返回一个整数值作为特定维度dim中的步长。
a.stride()
#is_contiguous()是判断tensor存储是否连续
a.iscontiguous()
1.12Tensor的保存于加载
torch.save和torch.load时可以指定使用的pickle模块,在加载时还可以把GPU tensor映射到CPU或者其他GPU上
#保存与加载
if torch.cuda.is_available():
a = a.cuda(1) # 把a转为GPU1上的tensor
torch.save(a, 'a.pth')
#加载为b,存储于GPU1上(因为保存时tensor就在GPU1上)
b = torch.load('a.pth')
#加载为c,存储于CPU
c = torch.load('a.pth', map_location = lambda storage, loc: storage)
#加载为d,存储于GPU0上
d = torch.load('a.pth', map_location = {'cuda:1' : 'cuda:0'})
*1.13向量化计算
向量化是一种特殊的并行计算方式,可以在同一时间执行多个操作,极大地提高科学运算的效率。
使用pytorch中需要注意以下几点:
1.大多数torch.function都有一个参数out,此时结果将保存在out指定的tensor中;
2.torch.set_num_threads可以设置Pytorch进行CPU多线程并行计算时所占用的线程数;
3.torch.set_printoptions(precision = )可以用来设置打印tensor时的数值精度和格式,precision可以设置精度。
标签:dim,元素,tensor,torch,笔记,学习,Pytorch,构建,Tensor From: https://blog.csdn.net/AsukaRanyo/article/details/136819389