05_pytorch的Tensor操作

pytorch完整教程目录:https://www.cnblogs.com/nickchen121/p/14662511.html

一、引言

上一篇文章我们利用手写数字分类这个问题讲解了深度网络模型的架构以及来源,简单点说,深度网络模型就是多个分类的模型叠加在一起,而分类模型就是在回归模型上加了一个激活函数。

本次分享更多的是想让我们更好的利用torch帮我们解决更多的实际问题,而为了解决我们这第一个手写数字分类问题,首先让我们来先了解下torch的一些基础语法和基础方法。不得不再次申明,由于torch的数据类型和numpy的数据类型有异曲同工之妙,所以此处默认你有numpy的基础,如果你不是很了解numpy的用法,可以查看我的这篇博客https://www.cnblogs.com/nickchen121/p/10807564.html

二、tensor的基础操作

tensor,也可以叫做张量,学过线性代数的你,其实早就解除了张量,只不过我们一直把它叫做向量和矩阵,而向量就是一维张量、矩阵是二维张量,只不过张量还可以是三维的、四维的,只是维数高了之后,我们难以理解,因此统一把它都叫做张量。

在torch中,张量是一个数据类型,也就是tensor,它和numpy中的ndarray这个数据类型很像,以及和它的操作方法也很类似,其实你可以发现,ndarray不就是一维和二维张量吗?

如果你看过我的Python博客,可以发现我把python的所有的基础类型和其对应的操作方法都讲到了,那是因为任何框架的基础都是python,python的所有操作方法都学习全面了,你自己也可以造框架。而对于框架的各种操作方法,底层无非就是一堆python代码的堆叠,也就是有些操作方法你不学,你也可以自己造出来,所以对于torch的很多不常用的内容我们可能会一笔概之或者直接不讲。

而对于tensor的基础操作,我们可以从两个方面来讲。

如果从接口的角度,对tensor的操作可以分为两类:

  1. torch.function,如torch.save
  2. tensor.function,如tensor.view

注:对于这两种接口方法,大多数时候都是等价的,如torch.sum(a,b)a.sum(b)

如果从存储的角度讲,对tensor的操作也可以分为两类:

  1. a.add(b),不会修改a自身的数据,加法的结果会返回一个新的tensor
  2. a.add_(b),会修改a自身的数据,也就是说加法的结果存在a中

注:函数名以_结尾的都是修改调用者自身的数据。

2.1 创建tensor

此处我只列出表格,不给出详细介绍和代码打印结果,只给出一些细节上需要注意的东西,因为它除了支持多维,其他和numpy简直一模一样

函数 功能
Tensor(*size) 基础构造函数
ones(*sizes) 全1Tensor
zeros(*sizes) 全0Tensor
eye(*sizes) 对角矩阵(对角线为1,其他为0,不要求行列一致)
arrange(s,e,step) 从s到e,步长为step
linspace(s,e,steps) 从s到e,均匀分成steps份
rand/randn(*sizes) 均匀/标准分布
normal(mean,std)/uniform(from,tor) 正态分布/均匀分布
randperm(m) 随机排列
import torch as t

如果*size为列表,则按照列表的形状生成张量,否则传入的参数看作是张量的形状

a = t.Tensor(2, 3)  # 指定形状构建2*3维的张量
a
tensor([[ 0.0000e+00, -2.5244e-29,  0.0000e+00],
[-2.5244e-29, 6.7294e+22, 1.8037e+28]])
b = t.tensor([[1, 2, 3], [2, 3, 4]])  # 通过传入列表构建2*3维的张量
b
tensor([[1, 2, 3],
[2, 3, 4]])
b.tolist()  # 把b转化为列表,但是b的实际数据类型仍是tensor
[[1, 2, 3], [2, 3, 4]]
print(f'type(b): {type(b)}')
type(b): <class 'torch.Tensor'>
b.size()  # 返回b的大小,等价于b.shape()
torch.Size([2, 3])
b.numel()  # 计算b中的元素个数,等价于b.nelement()
6
c = t.Tensor(b.size())  # 创建一个和b一样形状的张量
c
tensor([[0.0000e+00, 3.6013e-43, 1.8754e+28],
[2.0592e+23, 1.3003e+22, 1.0072e-11]])

注:t.Tensor(*size)创建tensor时,系统不会马上分配空间,只有使用到tensor时才会分配内存,而其他操作都是在创建tensor后马上进行空间分配

2.2 常用tensor操作

2.2.1 调整tensor的形状

view()方法调整tensor的形状,但是必须得保证调整前后元素个数一致,但是view方法不会修改原tensor的形状和数据

a = t.arange(0, 6)
a
tensor([0, 1, 2, 3, 4, 5])
b = a.view(2, 3)
print(f'a: {a}\n\n b:{b}')
a: tensor([0, 1, 2, 3, 4, 5])

 b:tensor([[0, 1, 2],
[3, 4, 5]])
c = a.view(-1, 3)  # -1会自动计算大小。注:我已经知道你在想什么了,两个-1你就上天吧,鬼知道你想改成什么形状的
print(f'a: {a}\n\n b:{c}')
a: tensor([0, 1, 2, 3, 4, 5])

 b:tensor([[0, 1, 2],
[3, 4, 5]])
a[1] = 0  # view方法返回的tensor和原tensor共享内存,修改一个,另外一个也会修改
print(f'a: {a}\n\n b:{b}')
a: tensor([0, 0, 2, 3, 4, 5])

 b:tensor([[0, 0, 2],
[3, 4, 5]])

resize()是另一种用来调整size的方法,但是它相比较view,可以修改tensor的尺寸,如果尺寸超过了原尺寸,则会自动分配新的内存,反之,则会保留老数据

b.resize_(1, 3)
tensor([[0, 0, 2]])
b.resize_(3, 3)
tensor([[0, 0, 2],
[3, 4, 5],
[0, 0, 0]])
b.resize_(2, 3)
tensor([[0, 0, 2],
[3, 4, 5]])

2.2.2 添加或压缩tensor维度

unsqueeze()可以增加tensor的维度;squeeze()可以压缩tensor的维度

# 过于抽象,无法理解就跳过。
d = b.unsqueeze(
1) # 在第1维上增加“1”,也就是2*3的形状变成2*1*3。如果是b.unsqueeze(0)就是在第0维上增加1,形状变成1*2*3。
d, d.size()
(tensor([[[0, 0, 2]],

         [[3, 4, 5]]]), torch.Size([2, 1, 3]))
b.unsqueeze(-1)  # 在倒数第1维上增加“1”,也就是2*3的形状变成2*3*1。
tensor([[[0],
[0],
[2]], [[3],
[4],
[5]]])
e = b.view(1, 1, 2, 1, 3)
f = e.squeeze(0) # 压缩第0维的“1”,某一维度为“1”才能压缩,如果第0维的维度是“2”如(2,1,1,1,3)则无法亚索第0维
f, f.size()
(tensor([[[[0, 0, 2]],

          [[3, 4, 5]]]]), torch.Size([1, 2, 1, 3]))
e.squeeze()  # 把所有维度为“1”的压缩。
tensor([[0, 0, 2],
[3, 4, 5]])

2.3 索引操作

tensor的索引操作和ndarray的索引操作类似,并且索引出来的结果与原tensor共享内存。因此在这里普通的切片操作我们就不多介绍,我们只讲解tensor一些特有的选择函数。

函数 功能
index_select(input,dim,index) 在指定维度dim上选取,例如选取某些行、某些列
masked_select(inpu,mask) a[a>1] 等价于a.masked_select(a>1)
non_zero(input) 获取非0元素的下标
gather(input,dim,index) 根据index,在dim维度上选取数据,输出的size与index一样
import torch as t

对于上述选择函数,我们讲解一下比较难的gather函数,对于一个二维tensor,gather的输出如下所示:

  1. out[i][j] = input[index[i][j]][j] # dim=0
  2. out[i][j] = input[i][index[i][j]] # dim=1
a = t.arange(0, 16).view(4, 4)
a
tensor([[ 0,  1,  2,  3],
[ 4, 5, 6, 7],
[ 8, 9, 10, 11],
[12, 13, 14, 15]])
# 选取对角线的元素
index = t.LongTensor([[0, 1, 2, 3]])
print(f'index: {index}')
a.gather(0, index) # dim=0
index: tensor([[0, 1, 2, 3]])

tensor([[ 0,  5, 10, 15]])

对于上述实例,可以做出如下解释:

  1. i=0,j=0 -> index[0,0]=0 -> input[index[0,0]][0]=input[0][0] = 0
  2. i=0,j=1 -> index[0,1]=1 -> input[index[0,1]][1]=input[1][1] = 5
  3. i=0,j=2 -> index[0,2]=2 -> input[index[0,2]][2]=input[2][2] = 10
  4. i=0,j=3 -> index[0,3]=3 -> input[index[0,3]][3]=input[3][3] = 15

下述实例,自行判断。

# 选取反对角线上的元素
index = t.LongTensor([[3, 2, 1, 0]]).t() # .t()是转置
print(f'index: {index}')
a.gather(1, index)
index: tensor([[3],
[2],
[1],
[0]]) tensor([[ 3],
[ 6],
[ 9],
[12]])
# 选取反对角线上的元素
index = t.LongTensor([[3, 2, 1, 0]]) # .t()是转置
a.gather(0, index)
tensor([[12,  9,  6,  3]])
# 选取两个对角线上的元素
index = t.LongTensor([[0, 1, 2, 3], [3, 2, 1, 0]]).t() # .t()是转置
print(f'index: {index}')
b = a.gather(1, index)
b
index: tensor([[0, 3],
[1, 2],
[2, 1],
[3, 0]]) tensor([[ 0, 3],
[ 5, 6],
[10, 9],
[15, 12]])

与gather函数相应的逆操作则是scatter_,scatter_可以把gather取出的元素放回去。

out = input.gather(dim, index)
out = Tensor()
out.scatter_(dim, index)
# 把两个对角线元素放回到指定位置里
c = t.zeros(4, 4, dtype=t.int64)
c.scatter_(1, index, b)
tensor([[ 0,  0,  0,  3],
[ 0, 5, 6, 0],
[ 0, 9, 10, 0],
[12, 0, 0, 15]])

2.4 高级索引

torch的高级索引和numpy的高级索引也很类似,因此照例,只讲一些复杂的高级索引方法。

注:高级索引操作的结果和原tensor不共享内存

x = t.arange(0, 27).view(3, 3, 3)
x
tensor([[[ 0,  1,  2],
[ 3, 4, 5],
[ 6, 7, 8]], [[ 9, 10, 11],
[12, 13, 14],
[15, 16, 17]], [[18, 19, 20],
[21, 22, 23],
[24, 25, 26]]])
x[[1, 2], [1, 2], [2, 0]]  # x[1,1,2] 和 x[2,2,0]
tensor([14, 24])
x[[2, 1, 0], [0], [1]]  # x[2,0,1],x[1,0,1],x[0,0,1]
tensor([19, 10,  1])
x[[0, 2], ...]  # x[0] 和 x[2]
tensor([[[ 0,  1,  2],
[ 3, 4, 5],
[ 6, 7, 8]], [[18, 19, 20],
[21, 22, 23],
[24, 25, 26]]])

可以从上述三个例子看出高级索引的本质就是先循环第一个列表中的元素,然后与后面列表的元素配对,配对满足维数要求则停止,否则继续往后搜索。

对于第一个例子:

  1. 先从[1,2]中取出1
  2. 1和第二个列表[1,2]配对,满足三维要求,即[1,1,2],停止配对,循环步骤一取出2
  3. 2和第三个列表[2,0]配对,满足三维要求,即[2,2,0],停止配对

对于第二例子:

  1. 先从[2,1,0]中取出2
  2. 2和第二个列表[0]配对,不满足三维要求,继续往后搜索,和第三个列表[1]配对,满足三维要求,即[2,0,1]
  3. ……

2.5 Tensor类型

2.5.1 Tensor数据类型

数据类型 CPU tensor GPU tensor
32bit浮点 torch.FloatTensor torch.cuda.FloatTensor
64bit浮点 torch.DoubleTensor torch.cuda.DoubleTensor
16bit半精度浮点 torch.HalfTensor 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.IntTensor
64bit有符号整型 torch.LongTensor torch.cuda.LongTensor

上表中只有HalfTensor值得一提,它是gpu独有的数据类型,使用该数据类型,gpu在存储该类型数据时,内存占用会减少一半,可以解决gpu显存不足的问题,但是由于它所能表示的数值大小和精度有限,所以可能存在溢出问题。

2.5.2 数据类型转换

# 设置默认tensor,系统默认tensor是FloatTensor,也仅支持浮点数类型为默认数据类型,设置成IntTensor会报错
t.set_default_tensor_type('torch.DoubleTensor')
a = t.Tensor(2, 3)
a, a.type() # a现在是DoubleTensor
(tensor([[0., 0., 0.],
[0., 0., 0.]]), 'torch.DoubleTensor')
b = a.int()  # 可通过`float(), int(), double(), char(), long(), int()`更换数据类型
b.type()
'torch.IntTensor'
c = a.type_as(b)  # 对a进行数据类型转换
c, c.type()
(tensor([[0, 0, 0],
[0, 0, 0]], dtype=torch.int32), 'torch.IntTensor')
d = a.new(2, 3)  # 生成与a数据类型一致的tensor
d, d.type()
(tensor([[ 2.0000e+00,  2.0000e+00, 3.9525e-323],
[ 0.0000e+00, 0.0000e+00, 0.0000e+00]]), 'torch.DoubleTensor')
a.new??  # 查看new的源码
t.set_default_tensor_type('torch.FloatTensor')  # 恢复之前的默认设置

2.5.3 cpu和gpu间数据类型转换

cpu和gpu的数据类型通常有tensor.cpu()tensor.gpu()互相转换,由于我的电脑没有gpu,从网上摘抄一段供大家参考:

In [115]: a = t.ones(2,3)                                                                                                                                            

In [116]: a.type()
Out[116]: 'torch.FloatTensor' In [117]: a
Out[117]:
tensor([[1., 1., 1.],
[1., 1., 1.]]) In [118]: a.cuda()
Out[118]:
tensor([[1., 1., 1.],
[1., 1., 1.]], device='cuda:0') In [119]: b = a.cuda() In [120]: b
Out[120]:
tensor([[1., 1., 1.],
[1., 1., 1.]], device='cuda:0') In [121]: b.type()
Out[121]: 'torch.cuda.FloatTensor' In [122]: b.cpu()
Out[122]:
tensor([[1., 1., 1.],
[1., 1., 1.]]) In [123]: b.cpu().type()
Out[123]: 'torch.FloatTensor'

2.6 逐元素操作

通俗点讲,就是对tensor进行数学操作,只不过是对tensor的每个元素都进行相对应的操作,因此叫做逐元素操作,也因此该类操作的输出形状与原tensor形状一致。常见的逐元素操作如下表:

函数 功能
mul/abs/sqrt/exp/fmod/log/pow…… 乘法(*)/绝对值/平方根/除法(/)/指数/求余(%)/求幂(**)
cos/sin/asin/atan2/cosh 三角函数
ceil/round/floor/trunc 上取整/四舍五入/下去整/只保留整数部分
clamp(input,min,max) 超过min和max部分截断
sigmod/tanh/... 激活函数

针对上述一些运算符,torch实现了运算符重载,例如a**2等价于torch.pow(a,2)

针对clamp函数,它的输出满足下述公式:

\[y_i =
\begin{cases}
& min,\quad\text{if x_i < min} \\
& x_i,\quad\quad\text{if min}\leq\text{x_i}\leq\text{max}\\
& max,\quad\text{if x_i > max}
\end{cases}
\]
a = t.arange(0, 6).view(2, 3)
a
tensor([[0, 1, 2],
[3, 4, 5]])
a.clamp(min=3)
tensor([[3, 3, 3],
[3, 4, 5]])

2.7 归并操作

该类操作可以沿着某一维度进行指定操作,因此它们的输出形状一般小于元tensor形状。如加法sum,可以计算正整个tensor的和,也可以计算某一行或某一列的和。常用的归并操作如下表所示:

函数 功能
mean/sum/median/mode 均值/和/中位数/众数
norm/dist 范数/距离
std/var 标准差/方差
cunsum/cumprod 累加/累乘

以上函数大多都有一个dim参数(对应numpy中的axis参数),它的使用如下所示(假设输入的形状是(a,b,c)):

  1. 如果指定dim=0,输出形状是(1,b,c)或(b,c)
  2. 如果指定dim=1,输出形状是(a,1,c)或(a,c)
  3. 如果指定dim=2,输出形状是(a,b,1)或(a,b)

对于上述操作是否保留输出形状中的“1”,取决于参数keepdim,如果keepdim=True则保留,反之不保留。但是从torch0.2.0版本开始,统一不保留。虽然以上总结适用于大多数函数,但是对于cumsum函数,则不适用该规则。

b = t.ones(2, 3)
b.sum(dim=0), b.sum(dim=0, keepdim=True) # 前者输出形状是(3),后者输出形状是(1,3)
(tensor([2., 2., 2.]), tensor([[2., 2., 2.]]))
a = t.arange(0, 6).view(2, 3)
a
tensor([[0, 1, 2],
[3, 4, 5]])
a.cumsum(dim=1)  # 对第二个维度行的元素按照索引顺序进行累加
tensor([[ 0,  1,  3],
[ 3, 7, 12]])

2.8 比较

对于比较函数中,有些函数逐元素操作,有些函数则类似于归并不逐元素操作。常用的比较函数有:

函数 功能
gt/lt/le/eq/ne 大于(>)/小于(<)/大于等于(>=)/小于等于(<=)/等于)(=)/不等(!=)
topk 最大的k个数
sort 排序
max/min 比较两个tensor的最大值和最小值

其中max和min两个函数有点特殊,它们有以下三种情况:

  1. t.max(tensor):返回tensor中最大的一个数
  2. t.max(tensor,dim):指定维上最带的数,返回tensor和下标
  3. t.max(tensor1,tensor2):比较两个tensor相比较大的元素
a = t.linspace(0, 15, 6).view(2, 3)
a
tensor([[ 0.,  3.,  6.],
[ 9., 12., 15.]])
b = t.linspace(15, 0, 6).view(2, 3)
b
tensor([[15., 12.,  9.],
[ 6., 3., 0.]])
t.max(a)
tensor(15.)
t.max(a, 1)  # 返回第0行和第1行的最大的元素
torch.return_types.max(
values=tensor([ 6., 15.]),
indices=tensor([2, 2]))
t.max(a, b)
tensor([[15., 12.,  9.],
[ 9., 12., 15.]])

2.9 线性代数

常用的线性代数函数如下表所示:

函数 功能
trace 对角线元素之和(矩阵的迹)
diag 对角线元素
triu/tril 矩阵的上三角/下三角,可指定偏移量
mm/bmm 矩阵的乘法/batch的矩阵乘法
addmm/addbmm/addmv 矩阵运算
t 转置
dot/cross 内积/外积
inverse 求逆矩阵
svd 奇异值分解

其中矩阵的转置会导致存储空间不连续,需调用它的.contiguous方法让它连续

b = a.t()
b, b.is_contiguous()
(tensor([[ 0.,  9.],
[ 3., 12.],
[ 6., 15.]]), False)
b = b.contiguous()
b, b.is_contiguous()
(tensor([[ 0.,  9.],
[ 3., 12.],
[ 6., 15.]]), True)

三、Tensor和Numpy

由于tensor和ndarray具有很高的相似性,并且两者相互转化需要的开销很小。但是由于ndarray出现时间较早,相比较tensor有更多更简便的方法,因此在某些时候tensor无法实现某些功能,可以把tensor转换为ndarray格式进行处理后再转换为tensor格式。

3.1 tensor数据和ndarray数据相互转换

import numpy as np

a = np.ones([2, 3], dtype=np.float32)
a
array([[1., 1., 1.],
[1., 1., 1.]], dtype=float32)

b = t.from_numpy(a)  # 把ndarray数据转换为tensor数据
b
tensor([[1., 1., 1.],
[1., 1., 1.]])
b = t.Tensor(a)  # 把ndarray数据转换为tensor数据
b
tensor([[1., 1., 1.],
[1., 1., 1.]])
a[0, 1] = 100
b
tensor([[  1., 100.,   1.],
[ 1., 1., 1.]])
c = b.numpy()  # 把tensor数据转换为ndarray数据
c
array([[  1., 100.,   1.],
[ 1., 1., 1.]], dtype=float32)

3.2 广播法则

广播法则来源于numpy,它的定义如下:

  • 让所有输入数组都向其中shape最长的数组看齐,shape中不足部分通过在前面加1补齐
  • 两个数组要么在某一个维度的长度一致,要么其中一个为1,否则不能计算
  • 当输入数组的某个维度的长度为1时,计算时沿此维度复制扩充×一样的形状

torch当前支持自动广播法则,但更推荐使用以下两个方法进行手动广播,这样更直观,更不容出错:

  1. unsqueeze或view:为数据某一维的形状补1
  2. expand或expand_as:重复数组,实现当输入的数组的某个维度的长度为1时,计算时沿此维度复制扩充成一样的形状

注:repeat与expand功能相似,但是repeat会把相同数据复制多份,而expand不会占用额外空间,只会在需要的时候才扩充,可以极大地节省内存。

a = t.ones(3, 2)
b = t.zeros(2, 3, 1)

自动广播法则:

  1. a是二维,b是三维,所在现在较小的a前面补1(等价于a.unsqueeze(0),a的形状变成(0,2,3))
  2. 由于a和b在第一维和第三维的形状不一样,利用广播法则,两个形状都变成了(2,3,2)
a + b
tensor([[[1., 1.],
[1., 1.],
[1., 1.]], [[1., 1.],
[1., 1.],
[1., 1.]]])

对上述自动广播可以通过以下方法实现手动广播

a.unsqueeze(0).expand(2, 3, 2) + b.expand(
2, 3, 2) # 等价于a.view(1,3,2).expand(2,3,2) + b.expand(2,3,2)
tensor([[[1., 1.],
[1., 1.],
[1., 1.]], [[1., 1.],
[1., 1.],
[1., 1.]]])

四、Tensor内部存储结构

tensor的数据存储结构如上图所示,它分为信息区(Tensor)和存储区(Storage),信息区主要保存tensor的形状、数据类型等信息;而真正的数据则保存成连续数组存放在存储区。

一个tensor有着一个与之对应的storage,storage是在data之上封装的接口,便于使用。不同的tensor的头信息一般不同,但却有可能使用相同的storage。

a = t.Tensor([0, 1, 2, 3, 4, 5])
b = a.view(2, 3)
id(a.storage()), id(b.storage()), id(a.storage()) == id(b.storage())
(140397108640200, 140397108640200, True)
a[1] = 100  # a改变,b进而随之改变,因为它们共享内存
b
tensor([[  0., 100.,   2.],
[ 3., 4., 5.]])
c = a[2:]
# data_ptr返回tensor首元素的地址
c.data_ptr() - a.data_ptr() # 相差16,这是因为2*8=16相差两个元素,每个元素占8个字节
8
c[0] = -100  # c和a共享内存
a
tensor([   0.,  100., -100.,    3.,    4.,    5.])
c.storage()
 0.0
100.0
-100.0
3.0
4.0
5.0
[torch.FloatStorage of size 6]
d = t.Tensor(c.storage())  # 使用a的存储数据建立d
d[0] = 666
a
tensor([ 666.,  100., -100.,    3.,    4.,    5.])
id(a.storage()) == id(b.storage()) == id(c.storage()) == id(d.storage())
True
# storage_offset是数据在storage中的索引,a和d从sotrage的第一个元素开始找,c是从第三个元素开始查找
a.storage_offset(), c.storage_offset(), d.storage_offset()
(0, 2, 0)
e = b[::2, ::2]  # 从0开始,每隔2行/列取一个元素
e
tensor([[ 666., -100.]])
b
tensor([[ 666.,  100., -100.],
[ 3., 4., 5.]])
e.storage()
 666.0
100.0
-100.0
3.0
4.0
5.0
[torch.FloatStorage of size 6]
# stride是storage中对应于tensor的相邻维度间第一个索引的跨度
# 对于b,第一行第一个元素到第二行第一个元素的索引差距为3,第一列第一个元素到到第二列第一个元素的索引差距为1
# 对于e,第一行第一个元素到第二行第一个元素(空)的索引差距为6,第一列第一个元素到到第二列第一个元素的索引差距为2
b.stride(), e.stride()
((3, 1), (6, 2))
e.is_contiguous()
False
id(d.storage()), id(e.storage())
(140397108641736, 140397108641736)
e.contiguous()
id(e.storage())
140397108699912

从上可见大多数操作并不会修改tensor的数据,只是修改tensor的头信息,这种做法减少了内存的占用,并且更加节省了时间。但是有时候这种操作会导致tensor不连续,此时可以通过contiguous方法让其连续,但是这种方法会复制数据到新的内存空间,不再和原来的数据共享内存。

五、其他

5.1 持久化

和sklearn中的持久化一样,保存一个模型或者特有的数据为pkl数据。但是tensor在加载数据的时候还可以把gpu tensor映射到cpu上或者其他gpu上。

5.1.1 保存模型

if t.cuda.is_available():
a = a.cuda(1) # 把a转为gpu1上的tensor
t.save(a, 'a.pkl')

5.1.2 加载模型

# 加载为b,存储于gpu1上(因为保存时tensor就在gpu1上)
b = t.load('a.pkl')
# 加载为c,存储于cpu
c = t.load('a.pkl', map_location=lambda storage, loc: storage)
# 加载为d,存储于gpu0上
d = t.load('a.pkl', map_location={'cuda:1': 'cuda:0'})

5.2 向量化

向量化计算是一种特殊的并行计算方法,通常是对不同的数据执行同样的一个或一批指令。由于Python原生的for循环效率低下,因此可以尽可能的使用向量化的数值计算。

def for_loop_add(x, y):
result = []
for i, j in zip(x, y):
result.append(i + j)
return t.Tensor(result) x = t.zeros(100)
y = t.ones(100) %timeit -n 100 for_loop_add(x,y)
%timeit -n 100 x+y
566 µs ± 100 µs per loop (mean ± std. dev. of 7 runs, 100 loops each)
3.25 µs ± 1.63 µs per loop (mean ± std. dev. of 7 runs, 100 loops each)

从上面可以看见,如果自己写一个方法实现内建函数,运行时间相差200倍,因为内建函数底层大多由c/c++实现,能通过执行底层优化实现高效计算。所以平时在写代码时,应该养成向量化的思维习惯。

5.3 注意事项

除了上述讲的大多数内容,最后还有以下三点需要注意:

  1. 大多数t.function都有一个参数out,可以将其产生的结果保存在out指定的tensor之中
  2. t.set_num_threads可以设置torch进行cpu多线程并行计算时所占用的线程数,用来限制torch所占用的cpu数目
  3. t.set_printoptions可以用来设置打印tensor时的数值精度和格式
b = t.FloatTensor()
t.randn(2, 3, out=b)
b
tensor([[ 1.4754, -0.7392, -0.1900],
[-0.8091, 0.2227, 0.8951]])
t.set_printoptions(precision=10)
b
tensor([[ 1.4753551483, -0.7392477989, -0.1899909824],
[-0.8091416359, 0.2227495164, 0.8951155543]])

六、总结

这一篇章幅度较大,对于熟悉numpy的同学可能得心应手很多,如果对numpy不是特别熟悉的同学,建议先按照上述所给的教程学一遍numpy,再过来学习tensor这个数据类型,从一二维过渡到高维,也将更容易上手。

这篇文章内容虽多,但从实用的角度来说,相对而言也比较全面,其中内容不需要全部熟稔于心,但至少得对每个方法都大概有个印象,知道有这个东西,这个东西能干啥!

05_pytorch的Tensor操作的更多相关文章

  1. 常用的Tensor操作

    常用的Tensor操作 1.通过tensor.view方法可以调整tensor的形状,但必须保证调整去前后元素总数一致.view不会修改自身的数据,返回新的tensor与原tensor共享内存,即更改 ...

  2. pytorch从入门到放弃(目录)

    目录 前置基础 Pytorch从入门到放弃 推荐阅读 前置基础 Python从入门到放弃(目录) 人工智能(目录) Pytorch从入门到放弃 01_pytorch和tensorflow的区别 02_ ...

  3. 深度学习框架PyTorch一书的学习-第三章-Tensor和autograd-1-Tensor

    参考https://github.com/chenyuntc/pytorch-book/tree/v1.0 希望大家直接到上面的网址去查看代码,下面是本人的笔记 Tensor Tensor可以是一个数 ...

  4. tf.Variable和tensor的区别(转)

    刷课过程中思考到Variable和Tensor之间的区别,尝试发现在如下代码中: a = tf.Variable(tf.ones(1)) b = tf.add(a,tf.ones(1)) 1 2 a是 ...

  5. pytorch之Tensor

    #tensor和numpy import torch import numpy as np numpy_tensor = np.random.randn(3,4) print(numpy_tensor ...

  6. pytorch入坑一 | Tensor及其基本操作

    由于之前的草稿都没了,现在只有重写…. 我好痛苦 本章只是对pytorch的常规操作进行一个总结,大家看过有脑子里有印象就好,知道有这么个东西,需要的时候可以再去详细的看,另外也还是需要在实战中多运用 ...

  7. Pytorch | 详解Pytorch科学计算包——Tensor

    本文始发于个人公众号:TechFlow,原创不易,求个关注 今天是Pytorch专题的第二篇,我们继续来了解一下Pytorch中Tensor的用法. 上一篇文章当中我们简单介绍了一下如何创建一个Ten ...

  8. Tensor基本理论

    Tensor基本理论 深度学习框架使用Tensor来表示数据,在神经网络中传递的数据均为Tensor. Tensor可以将其理解为多维数组,其可以具有任意多的维度,不同Tensor可以有不同的数据类型 ...

  9. Tensor基础实践

    Tensor基础实践 飞桨(PaddlePaddle,以下简称Paddle)和其他深度学习框架一样,使用Tensor来表示数据,在神经网络中传递的数据均为Tensor. Tensor可以将其理解为多维 ...

随机推荐

  1. 利用 Java 操作 Jenkins API 实现对 Jenkins 的控制详解

    本文转载自利用 Java 操作 Jenkins API 实现对 Jenkins 的控制详解 导语 由于最近工作需要利用 Jenkins 远程 API 操作 Jenkins 来完成一些列操作,就抽空研究 ...

  2. iOS拍照之系统拍照

    拍照在App中使用频次高,入门级别直接调用系统拍照 思路: 系统拍照使用UIImagePickerController 1.设置下plist,否则没权限,报错 2.判断摄像头,获取权限,否则弹出界面黑 ...

  3. 读懂RESTful风格

    RESTful就是资源定位和资源操作的风格.不是标准也不是协议. REST即Representational State Transfer的缩写,可译为"表现层状态转化".REST ...

  4. Java基本概念:多态

    一.简介 描述: 多态性是面向对象编程中的一个重要特性,主要是用来实现动态联编的.换句话说,就是程序的最终状态只有在执行过程中才被决定,而非在编译期间就决定了.这对于大型系统来说能提高系统的灵活性和扩 ...

  5. Charles 抓取https 包

    1.  Recording Settings中 include 添加 host , port端口为443 2.  SSL Proxying Settings 选中 Enable SSL Proxyin ...

  6. 阿里云CentOS8.0服务器配置Django3.0+Python 3.7 环境

    ---恢复内容开始--- 1. 下载并安装python # 安装Python3.7.6 wget https://www.python.org/ftp/python/3.7.6/Python-3.7. ...

  7. 基于Hi3559AV100 RFCN实现细节解析-(2)RFCN数据流分析

    下面随笔系列将对Hi3559AV100 RFCN实现细节进行解析,整个过程涉及到VI.VDEC.VPSS.VGS.VO.NNIE,其中涉及的内容,大家可以参考之前我写的博客: Hi3559AV100的 ...

  8. 通过golang小案例,了解golang程序常见机制

    目录 代码理解及纠错 1.defer和panic执行先后顺序 2.for循环元素副本问题 3.slice追加元素问题 4.返回值命名问题 5.用new初始化内置类型问题 6.切片append另外一个切 ...

  9. 剑指 Offer 36. 二叉搜索树与双向链表 + 中序遍历 + 二叉排序树

    剑指 Offer 36. 二叉搜索树与双向链表 Offer_36 题目描述 题解分析 本题考查的是二叉树的中序遍历以及二叉排序树的特征(二叉排序树的中序遍历序列是升序序列) 利用排序二叉树中序遍历的性 ...

  10. IDEA中部署servlet

    配置和不是servlet 第一种方法:(两种方法不能混用,使用第一种方法后,尽量删除第二种方法的注解方式) 使用xml文件配置: name没有什么特别的,就是需要统一即可.url-pattern中的地 ...