【notes】pytorch学习笔记1-Tensor部分

Tensor和autograd

每个深度学习框架的设计核心是张量和计算图,在pytorch里体现为张量系统(Tensor)和自动微分系统(atutograd)。

Tensor

  • 中文译为张量,可以简单看作一个数组。
  • 与numpy里的ndarrays类似,但tensor支持GPU加速。

基础操作

接口角度:

  1. torch.function
  2. tensor.function

存储角度:

  1. 不会修改自身数据,如a.add(b),返回一个值为加法结果的新的tensor。
  2. 会修改自身数据,如a.add_(b),加法的值储存在a中了。
创建Tensor

在pytorch中常见的新建tensor的方法:

类别 特点 函数 功能
第一类:基础方法 最灵活 Tensor(*sizes) 基础构造函数
第二类:根据sizes建立 常数型 ones(*sizes) 全1Tensor
常数型 zeros(*sizes) 全0Tensor
常数型 eyes(*sizes) 对角线为1,其他为0
概率分布型 rand/randn(*sizes) 均匀/标准分布
第三类:在一定范围内建立 等差数列型 arange(s,e,step) 从s到e,步长为step
等差数列型 linspace(s,e,steps) 从s到e,均匀切分成steps份
概率分布型 normal(mean,std)/uniform(from,to) 正态分布/均匀分布
概率分布型 randperm(m) 随机分布
  • 其中使用Tensor函数新建tensor是最复杂多变的,它既可以接受一个list,并根据list的数据新建tensor,也可根据指定的形状新建tensor,还能传入其他的tensor。
1
2
3
# 引入必要的包
import torch as t
from torch.autograd import Variable as V
1
2
# 指定tensor的形状
a = t.Tensor(2, 3);a
tensor([[7.2443e+22, 4.2016e+30, 9.9708e+17],
        [7.2296e+31, 5.6015e-02, 4.4721e+21]])
1
2
# 用list的数据创建tensor
b = t.Tensor([[1,2,3],[4,5,6]]);b
tensor([[1., 2., 3.],
        [4., 5., 6.]])
1
b.tolist(),type(b.tolist()) # 把tensor转为list
([[1.0, 2.0, 3.0], [4.0, 5.0, 6.0]], list)

tensor.size()返回torch.Size()对象,它是tuple的子类,但其使用方式与tuple略有不同。

1
b_size = b.size();b_size
torch.Size([2, 3])
1
b.numel() # numelements前五个字母,b中元素总个数,等价于b.nelement()
6
1
2
3
4
5
# 创建一个和b形状一样的tensor
c = t.Tensor(b_size)
# 创建一个元素为2和3的tensor
d = t.Tensor((2, 3))
c, d # 输出结果不同,明显看出torch.Size()对象和tuple的不同
(tensor([[5.8959e-35, 4.5636e-41, 1.0257e-36],
         [0.0000e+00, 5.0000e+00, 6.0000e+00]]), tensor([2., 3.]))

tensor.shape等价于tensor.size()

1
c.shape
torch.Size([2, 3])

* 需要注意:t.Tensor(*sizes)创建tensor时,系统不会马上分配空间,只会计算内存是否够用,使用到tensor时才会分配,而其他方法是创建后会立马分配空间。 *

1
t.ones(2, 3)
tensor([[1., 1., 1.],
        [1., 1., 1.]])
1
t.zeros(2, 3)
tensor([[0., 0., 0.],
        [0., 0., 0.]])
1
t.linspace(1, 10 ,3)
tensor([ 1.0000,  5.5000, 10.0000])
1
t.randn(2, 3)
tensor([[-0.4864,  0.5022, -0.4059],
        [ 0.4138,  1.1588, -1.1650]])
1
t.randperm
<function _VariableFunctions.randperm>
1
2
3
# 0到n-1随机排列后的数列
n = 10
t.randperm(n)
tensor([2, 5, 8, 3, 4, 1, 0, 7, 9, 6])
1
t.eye(2, 3) # 不要求行列数一致
tensor([[1., 0., 0.],
        [0., 1., 0.]])
1
t.normal(t.Tensor([0]),t.Tensor([1]))
tensor([-0.5517])
常用Tensor操作
  • tensor.view方法可以改变tensor的形状,但要保证前后元素总数一致。前后保持数据一致,返回的新tensor与源tensor共享内存。

  • 在实际应用中可能经常需要增加或减少某个维度,这是squeeze和unsqueeze两个函数排上用场。

1
2
a = t.arange(0, 6)
a.view(2, 3)
tensor([[0, 1, 2],
        [3, 4, 5]])
1
2
b = a.view(-1, 3) # 当某一维为-1时,会自动计算它的大小
b
tensor([[0, 1, 2],
        [3, 4, 5]])
1
b.shape, b.unsqueeze(1).shape # 注意形状,在第1维上增加“1”
(torch.Size([2, 3]), torch.Size([2, 1, 3]))
1
b.unsqueeze(-2) # -2表示倒数第二个维度
tensor([[[0, 1, 2]],

        [[3, 4, 5]]])
1
2
c = b.view(1, 1, 1, 2, 3)
c, c.squeeze(0) # 压缩第0维的1
(tensor([[[[[0, 1, 2],
            [3, 4, 5]]]]]), tensor([[[[0, 1, 2],
           [3, 4, 5]]]]))
1
c.squeeze() # 压缩所有的“1”的维度
tensor([[0, 1, 2],
        [3, 4, 5]])
1
2
a[1] = 100
b # a和b共享内存,修改了a,b也变了
tensor([[  0, 100,   2],
        [  3,   4,   5]])

resize是另一种改变size的方法,和view不同的地方是resize可以改变尺寸,可以有不同数量的元素。如果新尺寸超过了旧尺寸,会自动分配空间,如果新尺寸小于旧尺寸,之前的数据依旧会保存。

1
2
b.resize_(1, 3)
b
tensor([[  0, 100,   2]])
1
2
b.resize_(3, 3) # 旧的数据依旧被保存,多出的数据会分配新空间。
b
tensor([[                  0,                 100,                   2],
        [                  3,                   4,                   5],
        [7881702260482471202, 8319104481852400229, 7075192647680159593]])
索引操作

Tensor支持和numpy.ndarray类似的索引操作,语法上也类似。

如无特殊说明,索引出来的结果与原tensor共享内存

1
a = t.randn(3,4);a
tensor([[ 0.8865, -0.8832, -1.0883, -0.2804],
        [-0.9056,  0.0635,  0.5528, -0.0222],
        [ 1.4919, -1.0480, -1.7623,  0.8558]])
1
a[0] # 第0行
tensor([ 0.8865, -0.8832, -1.0883, -0.2804])
1
a[:, 0] # 第0列
tensor([ 0.8865, -0.9056,  1.4919])
1
a[0][2] # 第0行第2个元素,等价于a[0,2]
tensor(-1.0883)
1
a[0, -1] # 第0行最后一个元素
tensor(-0.2804)
1
a[:2] # 前两行
tensor([[ 0.8865, -0.8832, -1.0883, -0.2804],
        [-0.9056,  0.0635,  0.5528, -0.0222]])
1
a[:2, 0:2] # 前两行,第0,1列
tensor([[ 0.8865, -0.8832],
        [-0.9056,  0.0635]])
1
a[0:1, :2].shape, a[0, :2].shape # 注意两者的区别是形状不同,但是值是一样的
(torch.Size([1, 2]), torch.Size([2]))
1
2
3
a[a > 1] # 等价于a.masked_select(a>1)

# 选择结果与原tensor不共享内存空间
tensor([1.4919])
1
a[t.LongTensor([0,1])] # 第0行和第1行
tensor([[ 0.8865, -0.8832, -1.0883, -0.2804],
        [-0.9056,  0.0635,  0.5528, -0.0222]])

常用的选择函数:

函数 功能
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是一个比较复杂的操作,对于一个二维的tensor,输出的每个元素如下:

1
2
out[i][j] = input[index[i][j]][j] # dim = 0
out[i][j] = input[i][index[i][j]] # dim = 1
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]])
1
2
3
# 选取对角线上的元素
index = t.LongTensor([[0,1,2,3]])
a.gather(0, index)
tensor([[ 0,  5, 10, 15]])
1
2
3
# 选取反对角线上的元素
index = t.LongTensor([[3, 2, 1, 0]]).t()
a.gather(1,index)
tensor([[ 3],
        [ 6],
        [ 9],
        [12]])
1
2
3
# 选取反对角线上的元素,注意与上面不同
index = t.LongTensor([[3, 2, 1, 0]])
a.gather(0, index)
tensor([[12,  9,  6,  3]])
1
2
3
# 选取两个对角线上的元素
index = t.LongTensor([[0, 1, 2, 3], [3, 2, 1, 0]]).t()
b = a.gather(1, index);b
tensor([[ 0,  3],
        [ 5,  6],
        [10,  9],
        [15, 12]])

gather的逆操作是scatter_, gather把数据从input中按index取出,而scatter_是把取出的数据再放回去。注意scatter_函数是inplace操作。

1
2
3
4
out = input.gather(dim, index)
-->近似逆操作
out = Tensor()
out.scatter_(dim, index)
1
2
3
4
# 把两个对角线元素放回到指定位置

c = t.zeros(4, 4)
c.scatter_(1, index, b.float())
tensor([[ 0.,  0.,  0.,  3.],
        [ 0.,  5.,  6.,  0.],
        [ 0.,  9., 10.,  0.],
        [12.,  0.,  0., 15.]])
高级索引

高级索引可以看成是普通索引的扩展,但是高级索引操作的结果一般不和原Tensor共享内存。

1
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]]])
1
x[[1, 2], [1, 2], [2, 0]] # 元素的个数是列表的长度  元素为x[1,1,2]和x[2,2,0]
tensor([14, 24])
1
x[[2,1,0],[0],[1]] # 元素为最长列表的长度 x[2,0,1] x[1,0,1] x[0,0,1]
tensor([19, 10,  1])
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]]])
Tensor类型

默认的Tensor类型为FloatTensor,可通过t.get_default_tensor_type修改默认类型(如果默认类型为GPU tensor,在所有操作都在GPU上进行)。

HalfTensor是专门为GPU版本设计的,同样的元素个数,显存占用只有FloatTensor的一半,所以可以极大地缓解GPU显存不足的问题,但是由于HalfTensor所能表示的数值大小和精度有限,所以可能出现溢出等问题。

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

各数据类型之间可以互相转换,type(new_type)是通用的做法,同时还有float、long、half等快捷方法。CPU tensor与GPUtensor之间的互相装换通过tensor.cuda和tensor.cpu的方法实现。Tensor还有一个new方法,用法与t.Tensor一样,会调用该tensor对应类型的构造函数,生成与当前tensor类型一致的tensor。

1
2
3
4
5
6
# 设置默认tensor类型, 注意参数是字符串

# t.set_default_tensor_type('torch.IntTensor') 会报错
# TypeError: only floating-point types are supported as the default type
# t.get_default_dtype() 返回 torch.float32
# t.set_default_dtype(t.int) 报错 TypeError: only floating-point types are supported as the default type
1
a = t.Tensor(2, 3);a
tensor([[1.8609e+34, 1.8179e+31, 1.8524e+28],
        [9.6647e+35, 2.0076e+29, 7.3185e+28]])
1
b = a.int();b
tensor([[-2147483648, -2147483648, -2147483648],
        [-2147483648, -2147483648, -2147483648]], dtype=torch.int32)
1
c = a.type_as(b);c
tensor([[-2147483648, -2147483648, -2147483648],
        [-2147483648, -2147483648, -2147483648]], dtype=torch.int32)
1
d = b.new(2, 3);d
tensor([[         0,  775041082,  960062260],
        [1697986359,  926101553,  895706424]], dtype=torch.int32)
1
2
3
# 查看函数new的源码

a.new??
逐元素操作

这部分操作会对tensor的每个元素进行操作,输入和输出的形状相同。

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

对于很多基本的运算,比如加减乘除求余等运算pytorch都实现了运算符重载,可以直接使用运算符。
其中camp(x, min, max)的输出满足一个分段函数:

$$
y_i=
\begin{cases}
min, & {x_i < min}\\
x_i, & {min \leq x_i \leq max}\\
max, & {x_i > max}
\end{cases}
$$

1
2
a = t.arange(0, 6).view(2, 3).float() # 注意要转换一下类型,否则会报错
t.cos(a)
tensor([[ 1.0000,  0.5403, -0.4161],
        [-0.9900, -0.6536,  0.2837]])
1
a % 3 # 等价于t.fmod(a, 5)
tensor([[0., 1., 2.],
        [0., 1., 2.]])
1
a ** 2# 等价于t.power(a, 2)
tensor([[ 0.,  1.,  4.],
        [ 9., 16., 25.]])
1
2
3
4
# a中每个元素与3相比取较大的那一个

print(a)
t.clamp(a, min = 3)
tensor([[0., 1., 2.],
        [3., 4., 5.]])





tensor([[3., 3., 3.],
        [3., 4., 5.]])
归并操作

这类操作会使输入形状小于输出形状,并可以沿着某一维度进行制定操作。

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

几乎每个函数都有一个dim参数,用来制定在那个维度上执行。
假设输入的形状是(m, n, k):

  • 如果指定dim = 0,输出的形状为(1, n, k)或者(n, k)
  • 如果指定dim = 1,输出的形状为(m, 1, k)或者(m, k)
  • 如果指定dim = 2,输出的形状为(m, n, 1)或者(m, n)

也就是dim指定哪个维度,那个维度就会变成1,size中是否有1取决于keepdim,keepdim=True会保留1,keepdim默认为False。但是并非总是这样,比如cumsum。

归并运算就是对其他维度取值相同且该维度取值不同元素进行操作。

1
2
b = t.ones(2, 3)
b.sum(dim = 0, keepdim = True)
tensor([[2., 2., 2.]])
1
b.sum(dim = 0) #keepdim = False
tensor([2., 2., 2.])
1
b.sum(dim = 1)
tensor([3., 3.])
1
2
3
a = t.arange(0, 6).view(2, 3)
print(a)
a.cumsum(dim = 1) #沿着行累加
tensor([[0, 1, 2],
        [3, 4, 5]])





tensor([[ 0,  1,  3],
        [ 3,  7, 12]])

cumsum可以理解为以dim这个维度上索引取值相同的看作一个整体,比如dim=0每一行就是一个整体,cumsum运算相当于dim这个维度上取值为n的值加上取值为n-1的值(这个n-1已经进行过前面的运算,不是初始的值)。

比较

比较函数有的是逐元素操作,有的是归并操作。

函数 功能
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
a = t.linspace(0, 15, 6).view(2, 3);a
tensor([[ 0.,  3.,  6.],
        [ 9., 12., 15.]])
1
b = t.linspace(15, 0, 6).view(2, 3);b
tensor([[15., 12.,  9.],
        [ 6.,  3.,  0.]])
1
a > b
tensor([[False, False, False],
        [ True,  True,  True]])
1
a[a > b]
tensor([ 9., 12., 15.])
1
t.max(a)
tensor(15.)
1
t.max(a, dim = 1)
torch.return_types.max(
values=tensor([ 6., 15.]),
indices=tensor([2, 2]))
1
t.max(a, b)
tensor([[15., 12.,  9.],
        [ 9., 12., 15.]])
1
2
3
# 比较a和10较大的元素

t.clamp(a, min=10)
tensor([[10., 10., 10.],
        [10., 12., 15.]])
线性代数

pytorch的线性函数封装了Blas和Lapack。

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

需要注意矩阵装置会导致储存空间不连续,需调用它的.contiguous方法将其转为连续。

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

Tensor和Numpy

tensor和numpy数组之间具有很高的相似性,彼此之间相互操作也十分高效。需要注意,numpy和tensor共享内存。当遇到tensor不支持的操作时,可先转成Numpy数组,处理后再装回tensor,其转换开销很小。

广播法则是科学运算中经常使用的一个技巧,它在快速执行向量化的同时不会占用额外的内存、显存。Numpy的广播法则定义如下:

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

pytorch当前已经支持了自动广播法则,但建议可以手动通过函数实现广播法则,更直观不易出错。

  • unsqueeze或者view:为数据的某一维的形状补1,实现法则1。
  • expand或者expand_as,重复数组,实现法则3;该操作不会复制数组,所以不会占用额外的空间。

注意:repeat实现有expand类似,但是repeat会把相同数据复制多份,因此会占用额外空间。

1
2
a = t.ones(3, 2)
b = t.zeros(2, 3, 1)
1
2
3
4
5
6
7
# 自动广播法则
# 第一步:a是二维,b是三维,所以先在较小的a前面补1,
# 即:a.unsqueeze(0), a的形状变成(1, 3, 2), b的形状是(2, 3, 1),
# 第二步:a和b在第一维和第三维的形状不一样,其中一个为1
# 可以利用广播法则扩展,两个形状都变成了(2, 3, 2)

(a + b).shape,a + b
(torch.Size([2, 3, 2]), tensor([[[1., 1.],
          [1., 1.],
          [1., 1.]],

         [[1., 1.],
          [1., 1.],
          [1., 1.]]]))
1
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
import numpy as np
a = np.ones([2, 3], dtype = np.float32)
a
array([[1., 1., 1.],
       [1., 1., 1.]], dtype=float32)
1
b = t.from_numpy(a);b
tensor([[1., 1., 1.],
        [1., 1., 1.]])
1
2
b = t.Tensor(a) # 也可以直接讲numpy对象传入Tensor,这种情况下若numpy类型不是Float32会新建。
b
tensor([[1., 1., 1.],
        [1., 1., 1.]])
1
2
c = b.numpy() # a, b, c三个对象共享内存
c
array([[1., 1., 1.],
       [1., 1., 1.]], dtype=float32)
1
2
# expand不会占用额外空间,只会在需要时才扩充,可极大地节省内存。
e = t.Tensor(a).unsqueeze(0).expand(1000000000000, 2, 3)

内部结构

tensor分为头信息区(Tensor)和存储区(Storage),信息区主要保存着tensor的形状(size),步长(stride)、数据类型(type)等信息,而真正的数据则保存成连续的数组。

            
            graph LR;
A[Tensor A: *size *stride * dimention...] --> C[Storage:*data *size ...];
B[Tensor B: *size *stride * dimention....] --> C[Storage:*data *size ...];
          

一般来说,一个tensor有着与之对应的storage,storage是在data之上封装的接口,便于使用。不同的tensor的头信息一般不同,但却可能使用相同的storage。下面我们来看两个例子。

1
2
a = t.arange(0, 6)
a.storage()
 0
 1
 2
 3
 4
 5
[torch.LongStorage of size 6]
1
2
b = a.view(2, 3)
b.storage()
 0
 1
 2
 3
 4
 5
[torch.LongStorage of size 6]
1
2
3
# 一个对象的id值可以看作它在内存中的地址
# a和b storage的内存地址一样,即是同一个storage
id(b.storage()) == id(a.storage())
True
1
2
3
4
# a改变,b也随之改变,因为它们共享storage

a[1] = 100
b
tensor([[  0, 100,   2],
        [  3,   4,   5]])
1
2
c = a[2:]
c.storage()
 0
 100
 2
 3
 4
 5
[torch.LongStorage of size 6]
1
2
c.data_ptr(), a.data_ptr(), c.dtype # data_ptr返回tensor的首元素的内存地址
# 可以看出相差16,这是因为2x8相差两个元素,每个元素占8个字节
(61509136, 61509120, torch.int64)
1
2
c[0] = -100 # c[0]的内存地址对应a[2]内存地址
a
tensor([   0,  100, -100,    3,    4,    5])
1
2
3
d = t.Tensor(c.float().storage())
d[0] = 6666
b
tensor([[   0,  100, -100],
        [   3,    4,    5]])
1
2
3
# 下面4个共享storage

id(a.storage()) == id(b.storage()) == id(c.storage()) == id(d.storage())
True
1
a.storage_offset(), c.storage_offset(), a[3:].storage_offset()
(0, 2, 3)
1
2
e = b[::2, ::2] # 隔2行/列取一个元素
id(e.storage()) == id(a.storage())
True
1
b.stride(), e.stride()
((3, 1), (6, 2))
1
e.is_contiguous()
False
  • 可见绝大多数操作并不修改tensor的数据,只是修改头信息。这样更节省内存,同时提升了处理的速度。但是,有些操作会导致tensor不连续,这时需调用tensor.contiguous方法将他们变成连续数据,该方法复制数据到新的内存,不再与原来的数据共享storage。

  • 另外高级索引一般不共享内存,而普通索引共享storage。

其他有关Tensor的话题

持久化

tensor的保存和加载十分简单,使用t.save和t.load即可完成相应功能。在save/load时可以指定使用的pickle模块,在load时还可以将GPU tensor映射到CPU或其他GPU上。

1
2
3
4
5
6
7
8
9
if t.cuda.is_available():
a = a.cuda(1)
t.save(a, 'a.pth')
# 加载为b,储存于GPU1上(因为保存时就在GPU1上)
b = t.load('a.pth')
# 加载为c,储存在CPU
c = t.load('a.pth', map_location = lambda storage,loc:storage)
# 加载为d,储存于GPU0上
d = t.load('a.pth', map_location = {'cuda:1':'cuda:0'})

向量化

向量化计算是一种特殊的并行计算方式,通常是对不同的数据执行同样的一个或一批指令。向量化可极大第提高科学运算的效率。Python有许多操作很低效,尤其是for循环。在科学计算中要极力避免使用Python原生的for循环,尽量使用向量化的数值计算。

1
2
3
4
5
def for_loop_add(x, y):
result = []
for i, j in zip(x, y):
result.append(i + j)
return t.Tensor(result)
1
2
x = t.zeros(100)
y = t.ones(100)
1
2
%timeit -n 10 for_loop_add(x, y)
%timeit -n 10 x + y
729 µs ± 414 µs per loop (mean ± std. dev. of 7 runs, 10 loops each)
The slowest run took 4.81 times longer than the fastest. This could mean that an intermediate result is being cached.
3.5 µs ± 2.69 µs per loop (mean ± std. dev. of 7 runs, 10 loops each)

可见有好几百倍的速度差距,因此在实际使用中应尽量调用内建函数,这些函数底层由C/C++实现,能通过执行底层优化实现高效计算。

此为还需要注意几点:

  • 大多数t.function都有一个参数out,这时产生的结果将保存在out指定的tensor之中
  • t.set_num_threads可以设置pytorch进行CPU多线程并行计算时所占用的线程数,来限制pytorch所占用的CPU数目。
  • t.set_printoptions可以用来设置打印tensor时的数值精度和格式。
1
a = t.randn(2, 3); a
tensor([[-0.1227, -0.0569, -0.6876],
        [ 1.6025,  0.6995,  0.1694]])
1
t.set_printoptions(precision = 10);a
tensor([[-0.1226951405, -0.0568769276, -0.6875813603],
        [ 1.6024936438,  0.6995284557,  0.1693879962]])

———————————————感谢阅读———————————————

欢迎收藏访问我的博客 知乎 掘金 简书 知乎

贰三 wechat
欢迎扫描二维码订阅我的公众号!