Tensor 从接口的角度来讲,对tensor的操作可分为两类:
torch.function
,如torch.save
等。
另一类是tensor.function
,如tensor.view
等。
而从存储的角度来讲,对tensor的操作又可分为两类:
不会修改自身的数据,如 a.add(b)
, 加法的结果会返回一个新的tensor。
会修改自身的数据,如 a.add_(b)
, 加法的结果仍存储在a中,a被修改了。
表3-1: 常见新建tensor的方法
|函数|功能|
|:—:|:—:|
|Tensor(*sizes)|基础构造函数|
|ones(*sizes)|全1Tensor|
|zeros(*sizes)|全0Tensor|
|eye(*sizes)|对角线为1,其他为0|
|arange(s,e,step|从s到e,步长为step|
|linspace(s,e,steps)|从s到e,均匀切分成steps份|
|rand/randn(*sizes)|均匀/标准分布|
|normal(mean,std)/uniform(from,to)|正态分布/均匀分布|
|randperm(m)|随机排列|
其中使用Tensor
函数新建tensor是最复杂多变的方式,它既可以接收一个list,并根据list的数据新建tensor,也能根据指定的形状新建tensor,还能传入其他的tensor.
b.tolist() 把 tensor 转为 list
b.numel() b 中元素总数,等价于 b.nelement()
torch.Tensor(b.size()) 创建和 b 一样的 tensor
除了tensor.size()
,还可以利用tensor.shape
直接查看tensor的形状,tensor.shape
等价于tensor.size()
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 b = torch.Tensor([[1 ,2 ,3 ],[4 ,5 ,6 ]]) print (b)print (b.tolist())print (b.numel())c = torch.Tensor(b.size()) print (c)d = torch.Tensor((2 , 3 )) print (d)
tensor([[ 1., 2., 3.],
[ 4., 5., 6.]])
[[1.0, 2.0, 3.0], [4.0, 5.0, 6.0]]
6
tensor(1.00000e-15 *
[[-3.4942, 0.0000, 0.0000],
[ 0.0000, 0.0000, 0.0000]])
tensor([ 2., 3.])
常用Tensor操作 view
, squeeze
, unsqueeze
, resize
通过tensor.view
方法可以调整tensor的形状,但必须保证调整前后元素总数一致。view
不会修改自身的数据,返回的新tensor与源tensor共享内存,也即更改其中的一个,另外一个也会跟着改变。在实际应用中可能经常需要添加或减少某一维度,这时候squeeze
和unsqueeze
两个函数就派上用场了。
tensorflow
里面是 tf.expand_dim
和 tf.squeeze
.
resize
是另一种可用来调整size
的方法,但与view
不同,它可以修改tensor的大小。如果新大小超过了原大小,会自动分配新的内存空间,而如果新大小小于原大小,则之前的数据依旧会被保存,看一个例子。
1 2 3 4 5 a = torch.arange(0 , 6 ) a.view(2 , 3 )
tensor([[ 0., 1., 2.],
[ 3., 4., 5.]])
tensor([[ 0., 1., 2.],
[ 3., 4., 5.]])
tensor([[[ 0., 1., 2.]],
[[ 3., 4., 5.]]])
tensor([[[ 0., 1., 2.]],
[[ 3., 4., 5.]]])
1 2 3 4 5 c = b.view(1 , 1 , 1 , 2 , 3 ) c.squeeze(0 )
tensor([[[[ 0., 1., 2.],
[ 3., 4., 5.]]]])
tensor([[ 0., 1., 2.],
[ 3., 4., 5.]])
tensor([[ 0., 100., 2.],
[ 3., 4., 5.]])
tensor([[ 0., 100., 2.]])
tensor([[ 0.0000, 100.0000, 2.0000],
[ 3.0000, 4.0000, 5.0000],
[ -0.0000, 0.0000, 0.0000]])
索引操作 Tensor支持与numpy.ndarray类似的索引操作,语法上也类似,下面通过一些例子,讲解常用的索引操作。如无特殊说明,索引出来的结果与原tensor共享内存,也即修改一个,另一个会跟着修改。
其它常用的选择函数如表3-2所示。
表3-2常用的选择函数
函数|功能|
:—:|:—:|
index_select(input, dim, index)|在指定维度dim上选取,比如选取某些行、某些列
masked_select(input, mask)|例子如上,a[a>0],使用ByteTensor进行选取
non_zero(input)|非0元素的下标
gather(input, dim, index)|根据index,在dim维度上选取数据,输出的size与index一样
gather
是一个比较复杂的操作,对一个2维tensor,输出的每个元素如下:
1 2 3 4 5 out[i][j] = input [index[i][j]][j] out[i][j] = input [i][index[i][j]]
三维tensor的gather
操作同理,下面举几个例子。
1 2 3 4 5 6 7 a = torch.randn(3 , 4 ) print (a)print (a[0 ,1 ])
tensor([[ 0.5948, -0.5760, 1.3726, -0.9664],
[ 0.5705, 1.0374, -1.1780, 0.0635],
[-0.1195, 0.6657, 0.9583, -1.8952]])
tensor(-0.5760)
返回行的四种方式 1 2 3 print (a[torch.LongTensor([1 ,2 ])])
tensor([[ 0.5705, 1.0374, -1.1780, 0.0635],
[-0.1195, 0.6657, 0.9583, -1.8952]])
1 2 3 4 5 index = torch.LongTensor([1 ,2 ]) a.index_select(dim=0 , index=index)
tensor([[ 0.5705, 1.0374, -1.1780, 0.0635],
[-0.1195, 0.6657, 0.9583, -1.8952]])
tensor([[ 0.5705, 1.0374, -1.1780, 0.0635],
[-0.1195, 0.6657, 0.9583, -1.8952]])
1 2 3 print (a[torch.LongTensor([[1 ],[2 ]])])
tensor([[[ 0.5705, 1.0374, -1.1780, 0.0635]],
[[-0.1195, 0.6657, 0.9583, -1.8952]]])
返回列的两种方式 1 2 3 a.index_select(dim=1 , index=index)
tensor([[-0.5760, 1.3726],
[ 1.0374, -1.1780],
[ 0.6657, 0.9583]])
tensor([[-0.5760, 1.3726],
[ 1.0374, -1.1780],
[ 0.6657, 0.9583]])
mask is ByteTensor, 类似于 a[a>1]
1 2 3 4 5 6 7 8 9 a = torch.randn(3 , 4 ) print (a)print (a[a>0 ])a.masked_select(a>0 )
tensor([[ 0.3464, 1.4499, 0.7417, -1.9551],
[-0.0042, -0.0141, 1.2861, 0.0691],
[ 0.5843, 1.6635, -1.2771, -1.4623]])
tensor([ 0.3464, 1.4499, 0.7417, 1.2861, 0.0691, 0.5843, 1.6635])
tensor([ 0.3464, 1.4499, 0.7417, 1.2861, 0.0691, 0.5843, 1.6635])
tensor([[ 1, 1, 1, 0],
[ 0, 0, 1, 1],
[ 1, 1, 0, 0]], dtype=torch.uint8)
1 2 3 4 5 b = torch.ByteTensor(3 ,4 ) b
tensor([[ 80, 235, 127, 167],
[ 199, 85, 0, 0],
[ 0, 0, 0, 0]], dtype=torch.uint8)
tensor([ 0.3464, 1.4499, 0.7417, -1.9551, -0.0042, -0.0141])
1 2 3 4 5 6 7 8 9 a = torch.arange(0 , 20 ).view(4 ,5 ) print (a)index = torch.LongTensor([[0 ,1 ,2 ,1 ,3 ]]) print (index, index.shape)
tensor([[ 0., 1., 2., 3., 4.],
[ 5., 6., 7., 8., 9.],
[ 10., 11., 12., 13., 14.],
[ 15., 16., 17., 18., 19.]])
tensor([[ 0, 1, 2, 1, 3]]) torch.Size([1, 5])
1 2 3 a.gather(dim=0 , index=index)
tensor([[ 0., 6., 12., 8., 19.]])
所以 gather 就是 index 与 input 中某一个维度一致,比如这里 input.size()=[4,5].
那么 dim=0, index.size()=[1,5]. 然后在每列对应的 index 选取对应的数据。最后输出 size 与 index 一致。
1 2 3 4 5 index2 = torch.LongTensor([[1 ],[2 ],[3 ],[4 ]]) print (index2.shape)
torch.Size([4, 1])
1 2 3 a.gather(dim=1 , index=index2)
tensor([[ 1.],
[ 7.],
[ 13.],
[ 19.]])
list 转换成 one-hot 向量 1 2 3 4 5 6 7 8 9 10 11 label = [1 , 2 , 3 , 4 , 5 ] label = torch.LongTensor(label).view(-1 , 1 ) one_hot = torch.zeros(5 , 10 ).scatter_(dim=1 , index=label, value=1 ) one_hot
tensor([[0., 1., 0., 0., 0., 0., 0., 0., 0., 0.],
[0., 0., 1., 0., 0., 0., 0., 0., 0., 0.],
[0., 0., 0., 1., 0., 0., 0., 0., 0., 0.],
[0., 0., 0., 0., 1., 0., 0., 0., 0., 0.],
[0., 0., 0., 0., 0., 1., 0., 0., 0., 0.]])
Tensor 类型 Tensor有不同的数据类型,如表3-3所示,每种类型分别对应有CPU和GPU版本(HalfTensor除外)。默认的tensor是FloatTensor,可通过t.set_default_tensor_type
来修改默认tensor类型(如果默认类型为GPU tensor,则所有操作都将在GPU上进行)。Tensor的类型对分析内存占用很有帮助。例如对于一个size为(1000, 1000, 1000)的FloatTensor,它有1000*1000*1000=10^9
个元素,每个元素占32bit/8 = 4Byte内存,所以共占大约4GB内存/显存。HalfTensor是专门为GPU版本设计的,同样的元素个数,显存占用只有FloatTensor的一半,所以可以极大缓解GPU显存不足的问题,但由于HalfTensor所能表示的数值大小和精度有限[^2],所以可能出现溢出等问题。
^2: https://stackoverflow.com/questions/872544/what-range-of-numbers-can-be-represented-in-a-16-32-and-64-bit-ieee-754-syste
表3-3: tensor数据类型
数据类型| CPU tensor |GPU tensor|
:—:|:—:|:–:|
32-bit 浮点| torch.FloatTensor |torch.cuda.FloatTensor
64-bit 浮点| torch.DoubleTensor| torch.cuda.DoubleTensor
16-bit 半精度浮点| N/A |torch.cuda.HalfTensor
8-bit 无符号整形(0~255)| torch.ByteTensor| torch.cuda.ByteTensor
8-bit 有符号整形(-128~127)| torch.CharTensor |torch.cuda.CharTensor
16-bit 有符号整形 | torch.ShortTensor| torch.cuda.ShortTensor
32-bit 有符号整形 |torch.IntTensor |torch.cuda.IntTensor
64-bit 有符号整形 |torch.LongTensor |torch.cuda.LongTensor
各数据类型之间可以互相转换,type(new_type)
是通用的做法,同时还有float
、long
、half
等快捷方法。CPU tensor与GPU tensor之间的互相转换通过tensor.cuda
和tensor.cpu
方法实现。Tensor还有一个new
方法,用法与t.Tensor
一样,会调用该tensor对应类型的构造函数,生成与当前tensor类型一致的tensor。
torch.set_sefault_tensor_type(‘torch.IntTensor)
逐元素操作 这部分操作会对tensor的每一个元素(point-wise,又名element-wise)进行操作,此类操作的输入与输出形状一致。常用的操作如表3-4所示。
表3-4: 常见的逐元素操作
|函数|功能|
|:–:|:–:|
|abs/sqrt/div/exp/fmod/log/pow..|绝对值/平方根/除法/指数/求余/求幂..|
|cos/sin/asin/atan2/cosh..|相关三角函数|
|ceil/round/floor/trunc| 上取整/四舍五入/下取整/只保留整数部分|
|clamp(input, min, max)|超过min和max部分截断|
|sigmod/tanh..|激活函数
对于很多操作,例如div、mul、pow、fmod等,PyTorch都实现了运算符重载,所以可以直接使用运算符。如a ** 2
等价于torch.pow(a,2)
, a * 2
等价于torch.mul(a,2)
。
其中clamp(x, min, max)
的输出满足以下公式:
$$
y_i =
\begin{cases}
min, & \text{if } x_i \lt min \
x_i, & \text{if } min \le x_i \le max \
max, & \text{if } x_i \gt max\
\end{cases}
$$
clamp
常用在某些需要比较大小的地方,如取一个tensor的每个元素与另一个数的较大值。
1 2 3 4 5 6 7 8 9 import torcha = torch.arange(0 ,6 ).view(2 ,3 ) print (a)torch.clamp(a, min =3 , max =5 )
tensor([[ 0., 1., 2.],
[ 3., 4., 5.]])
tensor([[ 3., 3., 3.],
[ 3., 4., 5.]])
归并操作 此类操作会使输出形状小于输入形状,并可以沿着某一维度进行指定操作。如加法sum
,既可以计算整个tensor的和,也可以计算tensor中每一行或每一列的和。常用的归并操作如表3-5所示。
表3-5: 常用归并操作
|函数|功能|
|:—:|:—:|
|mean/sum/median/mode|均值/和/中位数/众数|
|norm/dist|范数/距离|
|std/var|标准差/方差|
|cumsum/cumprod|累加/累乘|
以上大多数函数都有一个参数 **dim
**,用来指定这些操作是在哪个维度上执行的。关于dim(对应于Numpy中的axis)的解释众说纷纭,这里提供一个简单的记忆方式:
假设输入的形状是(m, n, k)
如果指定dim=0,输出的形状就是(1, n, k)或者(n, k)
如果指定dim=1,输出的形状就是(m, 1, k)或者(m, k)
如果指定dim=2,输出的形状就是(m, n, 1)或者(m, n)
size中是否有”1”,取决于参数keepdim
,keepdim=True
会保留维度1
。注意,以上只是经验总结,并非所有函数都符合这种形状变化方式,如cumsum
。
1 2 3 4 5 a = torch.arange(0 , 6 ).view(2 ,3 ) a
tensor([[ 0., 1., 2.],
[ 3., 4., 5.]])
1 2 3 a.norm(dim=0 , p=1 ), a.norm(dim=0 , p=2 ), a.norm(dim=0 , p=3 )
(tensor([ 3., 5., 7.]),
tensor([ 3.0000, 4.1231, 5.3852]),
tensor([ 3.0000, 4.0207, 5.1045]))
$||x||{p} = \sqrt[p]{x {1}^{p} + x_{2}^{p} + \ldots + x_{N}^{p}}$
torch.dist?? dist(input, other, p=2) -> Tensor
Returns the p-norm of (:attr:input
- :attr:other
)
1 2 3 torch.dist(torch.ones(4 ), torch.zeros(4 ), 2 )
tensor(2.)
1 2 3 torch.var(torch.randn(10 ,3 ), dim=0 )
tensor([ 0.7617, 1.0060, 1.6778])
比较 比较函数中有一些是逐元素比较,操作类似于逐元素操作,还有一些则类似于归并操作。常用比较函数如表3-6所示。
表3-6: 常用比较函数
|函数|功能|
|:–:|:–:|
|gt/lt/ge/le/eq/ne|大于/小于/大于等于/小于等于/等于/不等|
|topk|最大的k个数|
|sort|排序|
|max/min|比较两个tensor最大最小值|
表中第一行的比较操作已经实现了运算符重载,因此可以使用a>=b
、a>b
、a!=b
、a==b
,其返回结果是一个ByteTensor
,可用来选取元素。max/min这两个操作比较特殊,以max来说,它有以下三种使用情况:
t.max(tensor):返回tensor中最大的一个数
t.max(tensor,dim):指定维上最大的数,返回tensor和下标
t.max(tensor1, tensor2): 比较两个tensor相比较大的元素
至于比较一个tensor和一个数,可以使用clamp函数。下面举例说明。
1 2 3 4 5 a = torch.rand(3 ,4 ) a
tensor([[ 0.1845, 0.4101, 0.1470, 0.0083],
[ 0.7520, 0.8871, 0.9494, 0.2504],
[ 0.3879, 0.4554, 0.4080, 0.1703]])
(tensor([ 0.7326, 0.6784, 0.9791, 0.9011]), tensor([ 1, 2, 1, 1]))
(tensor([[ 0.1424, 0.5681, 0.1833, 0.1654],
[ 0.4556, 0.6418, 0.3242, 0.5120],
[ 0.7326, 0.6784, 0.9791, 0.9011]]), tensor([[ 2, 0, 0, 2],
[ 0, 1, 2, 0],
[ 1, 2, 1, 1]]))
(tensor([[ 0.7326, 0.6784, 0.9791, 0.9011],
[ 0.4556, 0.6418, 0.3242, 0.5120]]), tensor([[ 1, 2, 1, 1],
[ 0, 1, 2, 0]]))
线性代数 PyTorch的线性函数主要封装了Blas和Lapack,其用法和接口都与之类似。常用的线性代数函数如表3-7所示。
表3-7: 常用的线性代数函数
|函数|功能|
|:—:|:—:|
|trace|对角线元素之和(矩阵的迹)|
|diag|对角线元素|
|triu/tril|矩阵的上三角/下三角,可指定偏移量|
|mm/bmm|矩阵乘法,batch的矩阵乘法|
|addmm/addbmm/addmv/addr/badbmm..|矩阵运算
|t|转置|
|dot/cross|内积/外积
|inverse|求逆矩阵
|svd|奇异值分解
具体使用说明请参见官方文档^3 ,需要注意的是,矩阵的转置会导致存储空间不连续,需调用它的.contiguous
方法将其转为连续。
1 2 3 4 5 6 7 b.contiguous(), b.size() b.contiguous().is_contiguous() print (a.matmul(b.contiguous()))
tensor([[ 0.8260, 1.3392, 0.5944],
[ 1.3392, 2.7192, 1.0062],
[ 0.5944, 1.0062, 0.6130]])
1 2 3 4 5 6 7 8 9 b = a.t() print (a.size(), b.shape)print (a.mm(b))b.is_contiguous()
torch.Size([3, 4]) torch.Size([4, 3])
tensor([[ 0.8260, 1.3392, 0.5944],
[ 1.3392, 2.7192, 1.0062],
[ 0.5944, 1.0062, 0.6130]])
False
(tensor([[ 0.4556, 0.7326, 0.1424],
[ 0.5681, 0.6418, 0.6784],
[ 0.1833, 0.9791, 0.3242],
[ 0.5120, 0.9011, 0.1654]]), tensor([ 0.4556, 0.6418, 0.3242]))
1 2 3 4 5 a = torch.randn(5 ,5 ) a.triu(1 )
tensor([[ 0.0000, 1.5959, -0.2253, 0.2349, -0.5151],
[ 0.0000, 0.0000, -0.0366, -0.0867, 0.2737],
[ 0.0000, 0.0000, 0.0000, 0.9904, -1.4889],
[ 0.0000, 0.0000, 0.0000, 0.0000, -1.1053],
[ 0.0000, 0.0000, 0.0000, 0.0000, 0.0000]])
Tensor和Numpy Tensor和Numpy数组之间具有很高的相似性,彼此之间的互操作也非常简单高效。需要注意的是,Numpy和Tensor共享内存。由于Numpy历史悠久,支持丰富的操作,所以当遇到Tensor不支持的操作时,可先转成Numpy数组,处理后再转回tensor,其转换开销很小。
注意 : 当numpy的数据类型和Tensor的类型不一样的时候,数据会被复制,不会共享内存。
1 2 3 4 5 6 7 8 9 import numpy as npa = np.ones([2 ,3 ]) print (a.dtype)a
float64
array([[1., 1., 1.],
[1., 1., 1.]])
1 2 3 4 5 6 7 b = torch.Tensor(a) print (b.type ())b
torch.FloatTensor
tensor([[ 1., 100., 1.],
[ 1., 1., 1.]])
1 2 3 4 5 6 7 c = torch.from_numpy(a) print (c.type ())c
torch.DoubleTensor
tensor([[ 1., 1., 1.],
[ 1., 1., 1.]], dtype=torch.float64)
tensor([[ 1., 1., 1.],
[ 1., 1., 1.]])
tensor([[ 1., 100., 1.],
[ 1., 1., 1.]], dtype=torch.float64)
BroadCast 广播法则(broadcast)是科学运算中经常使用的一个技巧,它在快速执行向量化的同时不会占用额外的内存/显存。
Numpy的广播法则定义如下:
让所有输入数组都向其中shape最长的数组看齐,shape中不足的部分通过在前面加1补齐
两个数组要么在某一个维度的长度一致,要么其中一个为1,否则不能计算
当输入数组的某个维度的长度为1时,计算时沿此维度复制扩充成一样的形状
PyTorch当前已经支持了自动广播法则,但是笔者还是建议读者通过以下两个函数的组合手动实现广播法则,这样更直观,更不易出错:
注意,repeat实现与expand相类似的功能,但是repeat会把相同数据复制多份,因此会占用额外的空间。
1 2 3 4 5 a = torch.ones(3 , 2 ) b = torch.zeros(2 , 3 , 1 )
tensor([[[ 1., 1.],
[ 1., 1.],
[ 1., 1.]],
[[ 1., 1.],
[ 1., 1.],
[ 1., 1.]]])
1 2 3 a.unsqueeze(0 ).expand(2 ,3 ,2 ) + b.expand(2 ,3 ,2 )
tensor([[[ 1., 1.],
[ 1., 1.],
[ 1., 1.]],
[[ 1., 1.],
[ 1., 1.],
[ 1., 1.]]])
1 2 3 4 5 e = a.unsqueeze(0 ).expand(10000000000000 , 3 ,2 )
内部结构 tensor的数据结构如图3-1所示。tensor分为头信息区(Tensor)和存储区(Storage),信息区主要保存着tensor的形状(size)、步长(stride)、数据类型(type)等信息,而真正的数据则保存成连续数组。由于数据动辄成千上万,因此信息区元素占用内存较少,主要内存占用则取决于tensor中元素的数目,也即存储区的大小。
一般来说一个tensor有着与之相对应的storage, storage是在data之上封装的接口,便于使用,而不同tensor的头信息一般不同,但却可能使用相同的数据。下面看两个例子。
1 2 3 4 5 a = torch.arange(0 ,6 ) a
tensor([ 0., 1., 2., 3., 4., 5.])
0.0
1.0
2.0
3.0
4.0
5.0
[torch.FloatStorage of size 6]
1 2 3 4 5 b = a.view(2 , 3 ) b.storage()
0.0
1.0
2.0
3.0
4.0
5.0
[torch.FloatStorage of size 6]
1 2 3 4 5 6 7 id (b.storage()) == id (a.storage())
True
1 2 3 4 5 c = torch.arange(0 , 6 ) c.storage()
0.0
1.0
2.0
3.0
4.0
5.0
[torch.FloatStorage of size 6]
1 2 3 4 5 6 7 id (c.storage()) == id (a.storage())
True
(tensor([[ 0., 100., 2.],
[ 3., 4., 5.]]), tensor([ 0., 1., 2., 3., 4., 5.]))
1 2 3 4 5 6 7 id (c[1 ].storage()), id (c.storage())
(139719200619016, 139719200619016)
1 2 3 4 5 6 7 c = a[2 :] print (c)c.storage()
tensor([ 2., 3., 4., 5.])
0.0
100.0
2.0
3.0
4.0
5.0
[torch.FloatStorage of size 6]
1 2 3 4 5 c.data_ptr(), a.data_ptr()
(94551854283064, 94551854283056)
tensor([ 0., 100., -100., 3., 4., 5.])
1 2 3 4 5 6 7 d = torch.Tensor(c.storage()) d[0 ] = 6666 b
tensor([[ 6666., 100., -100.],
[ 3., 4., 5.]])
1 2 3 4 5 id (a.storage()) == id (b.storage()) == id (c.storage()) == id (d.storage())
True
1 2 3 a.storage_offset(), c.storage_offset(), d.storage_offset()
(0, 2, 0)
1 2 3 4 5 e = b[::2 , ::2 ] id (e.storage()) == id (a.storage())
True
False
可见绝大多数操作并不修改tensor的数据,而只是修改了tensor的头信息。这种做法更节省内存,同时提升了处理速度。在使用中需要注意。
此外有些操作会导致tensor不连续,这时需调用tensor.contiguous
方法将它们变成连续的数据,该方法会使数据复制一份,不再与原来的数据共享storage。
另外读者可以思考一下,之前说过的高级索引一般不共享stroage,而普通索引共享storage,这是为什么?(提示:普通索引可以通过只修改tensor的offset,stride和size,而不修改storage来实现)。
持久化 Tensor的保存和加载十分的简单,使用t.save和t.load即可完成相应的功能。在save/load时可指定使用的pickle
模块,在load时还可将GPU tensor映射到CPU或其它GPU上。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 if torch.cuda.is_available(): a = a.cuda() torch.save(a,'a.pth' ) b = torch.load('a.pth' ) c = torch.load('a.pth' , map_location=lambda storage, loc: storage) d = torch.load('a.pth' , map_location={'cuda:1' :'cuda:0' })
1 2 3 4 5 a = torch.load("a.pth" ) print (a)
tensor([ 6666., 100., -100., 3., 4., 5.], device='cuda:0')
向量化 向量化计算是一种特殊的并行计算方式,相对于一般程序在同一时间只执行一个操作的方式,它可在同一时间执行多个操作,通常是对不同的数据执行同样的一个或一批指令,或者说把指令应用于一个数组/向量上。向量化可极大提高科学运算的效率,Python本身是一门高级语言,使用很方便,但这也意味着很多操作很低效,尤其是for
循环。在科学计算程序中应当极力避免使用Python原生的for循环
。
1 2 3 4 5 6 7 8 9 10 11 def for_loop_add (x, y ): result = [] for i,j in zip (x, y): result.append(i + j) return torch.Tensor(result)
1 2 3 4 5 6 7 8 9 x = torch.zeros(100 ) y = torch.ones(100 ) %timeit -n 10 for_loop_add(x, y) %timeit -n 10 x + y
351 µs ± 9.1 µs per loop (mean ± std. dev. of 7 runs, 10 loops each)
The slowest run took 16.46 times longer than the fastest. This could mean that an intermediate result is being cached.
4.24 µs ± 7.12 µs per loop (mean ± std. dev. of 7 runs, 10 loops each)
可见二者有超过40倍的速度差距,因此在实际使用中应尽量调用内建函数(buildin-function),这些函数底层由C/C++实现,能通过执行底层优化实现高效计算。因此在平时写代码时,就应养成向量化的思维习惯。
此外还有以下几点需要注意:
大多数torch.function
都有一个参数out
,这时候产生的结果将保存在out指定tensor之中。
torch.set_num_threads
可以设置PyTorch进行CPU多线程并行计算时候所占用的线程数,这个可以用来限制PyTorch所占用的CPU数目。
torch.set_printoptions
可以用来设置打印tensor时的数值精度和格式。
下面举例说明。
1 2 3 4 5 6 7 a = torch.randn(2 ,3 ) torch.set_printoptions(precision=10 ) a
tensor([[-0.3306640089, -0.0507176071, -0.4223535955],
[-0.8678948879, -0.0437202156, 0.0183448847]])
线性回归 线性回归是机器学习入门知识,应用十分广泛。线性回归利用数理统计中回归分析,来确定两种或两种以上变量间相互依赖的定量关系的,其表达形式为$y = wx+b+e$,$e$为误差服从均值为0的正态分布。首先让我们来确认线性回归的损失函数:
$$
loss = \sum_i^N \frac 1 2 ({y_i-(wx_i+b)})^2
$$
然后利用随机梯度下降法更新参数$\textbf{w}$和$\textbf{b}$来最小化损失函数,最终学得$\textbf{w}$和$\textbf{b}$的数值。
1 2 3 4 5 6 7 8 9 import torch as t%matplotlib inline from matplotlib import pyplot as pltfrom IPython import display
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 t.manual_seed(1000 ) def get_fake_data (batch_size=8 ): ''' 产生随机数据:y=x*2+3,加上了一些噪声''' x = t.rand(batch_size, 1 ) * 20 y = x * 2 + (1 + t.randn(batch_size, 1 ))*3 return x, y x, y = get_fake_data() plt.scatter(x.squeeze().numpy(), y.squeeze().numpy())
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 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 w = torch.randn(1 ,1 ) b = torch.zeros(1 ,1 ) lr = 0.001 for epoch in range (20000 ): x, y = get_fake_data(batch_size=8 ) y_pred = x.mm(w) + b.expand_as(y) loss = 0.5 * (y_pred - y) ** 2 loss = loss.sum () dloss = 1 dy_pred = dloss * (y_pred - y) dw = x.t().contiguous().mm(dy_pred) db = dy_pred.sum () w.sub_(lr * dw) b.sub_(lr * db) if epoch % 1000 == 0 : print ("epoch:{}, loss:{}" .format (epoch, loss)) display.clear_output(wait=True ) x = torch.arange(0 , 20 ).view(-1 , 1 ) y = x.mm(w) + b.expand_as(x) plt.plot(x.numpy(), y.numpy()) x2, y2 = get_fake_data(batch_size=20 ) plt.scatter(x2.numpy(), y2.numpy()) plt.xlim(0 ,20 ) plt.ylim(0 ,41 ) plt.show() plt.pause(0.5 ) print (w.squeeze()[0 ], b.squeeze()[0 ])
tensor(2.0264241695) tensor(2.9323694706)