赞
踩
在深度学习中,我们通常会频繁地对数据进行操作。作为动手学深度学习的基础,本节将介绍如何对内存中的数据进行操作。
在PyTorch中,torch.Tensor
是存储和变换数据的主要工具。如果你之前用过NumPy,你会发现Tensor
和NumPy的多维数组非常类似。然而,Tensor
提供GPU计算和自动求梯度等更多功能,这些使Tensor
更加适合深度学习。
"tensor"这个单词一般可译作“张量”,张量可以看作是一个多维数组。标量可以看作是0维张量,向量可以看作1维张量,矩阵可以看作是二维张量。
我们先介绍Tensor
的最基本功能,即Tensor
的创建。
首先导入PyTorch:
然后我们创建一个5x3的未初始化的Tensor
:
输出:
创建一个5x3的随机初始化的Tensor
:
输出:
创建一个5x3的long型全0的Tensor
:
输出:
还可以直接根据数据创建:
输出:
还可以通过现有的Tensor
来创建,此方法会默认重用输入Tensor
的一些属性,例如数据类型,除非自定义数据类型。
输出:
我们可以通过shape
或者size()
来获取Tensor
的形状:
输出:
注意:返回的torch.Size其实就是一个tuple, 支持所有tuple的操作。
还有很多函数可以创建Tensor
,去翻翻官方API就知道了,下表给了一些常用的作参考。
函数 | 功能 |
---|---|
Tensor(*sizes) | 基础构造函数 |
tensor(data,) | 类似np.array的构造函数 |
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) | 随机排列 |
这些创建方法都可以在创建的时候指定数据类型dtype和存放device(cpu/gpu)。
本小节介绍Tensor
的各种操作。
在PyTorch中,同一种操作可能有很多种形式,下面用加法作为例子。
加法形式一
加法形式二
还可指定输出:
加法形式三、inplace
注:PyTorch操作inplace版本都有后缀
_
, 例如x.copy_(y), x.t_()
以上几种形式的输出均为:
我们还可以使用类似NumPy的索引操作来访问Tensor
的一部分,需要注意的是:索引出来的结果与原数据共享内存,也即修改一个,另一个会跟着修改。
输出:
除了常用的索引选择数据之外,PyTorch还提供了一些高级的选择函数:
函数 | 功能 |
---|---|
index_select(input, dim, index) | 在指定维度dim上选取,比如选取某些行、某些列 |
masked_select(input, mask) | 例子如上,a[a>0],使用ByteTensor进行选取 |
nonzero(input) | 非0元素的下标 |
gather(input, dim, index) | 根据index,在dim维度上选取数据,输出的size与index一样 |
这里不详细介绍,用到了再查官方文档。
用view()
来改变Tensor
的形状:
输出:
注意view()
返回的新Tensor
与源Tensor
虽然可能有不同的size
,但是是共享data
的,也即更改其中的一个,另外一个也会跟着改变。(顾名思义,view仅仅是改变了对这个张量的观察角度,内部数据并未改变)
输出:
所以如果我们想返回一个真正新的副本(即不共享data内存)该怎么办呢?Pytorch还提供了一个reshape()
可以改变形状,但是此函数并不能保证返回的是其拷贝,所以不推荐使用。推荐先用clone
创造一个副本然后再使用view
。参考此处
输出:
使用
clone
还有一个好处是会被记录在计算图中,即梯度回传到副本时也会传到源Tensor
。
另外一个常用的函数就是item()
, 它可以将一个标量Tensor
转换成一个Python number:
输出:
另外,PyTorch还支持一些线性函数,这里提一下,免得用起来的时候自己造轮子,具体用法参考官方文档。如下表所示:
函数 | 功能 |
---|---|
trace | 对角线元素之和(矩阵的迹) |
diag | 对角线元素 |
triu/tril | 矩阵的上三角/下三角,可指定偏移量 |
mm/bmm | 矩阵乘法,batch的矩阵乘法 |
addmm/addbmm/addmv/addr/baddbmm… | 矩阵运算 |
t | 转置 |
dot/cross | 内积/外积 |
inverse | 求逆矩阵 |
svd | 奇异值分解 |
PyTorch中的Tensor
支持超过一百种操作,包括转置、索引、切片、数学运算、线性代数、随机数等等,可参考官方文档。
前面我们看到如何对两个形状相同的Tensor
做按元素运算。当对两个形状不同的Tensor
按元素运算时,可能会触发广播(broadcasting)机制:先适当复制元素使这两个Tensor
形状相同后再按元素运算。例如:
输出:
由于x
和y
分别是1行2列和3行1列的矩阵,如果要计算x + y
,那么x
中第一行的2个元素被广播(复制)到了第二行和第三行,而y
中第一列的3个元素被广播(复制)到了第二列。如此,就可以对2个3行2列的矩阵按元素相加。
前面说了,索引操作是不会开辟新内存的,而像y = x + y
这样的运算是会新开内存的,然后将y
指向新内存。为了演示这一点,我们可以使用Python自带的id
函数:如果两个实例的ID一致,那么它们所对应的内存地址相同;反之则不同。
如果想指定结果到原来的y
的内存,我们可以使用前面介绍的索引来进行替换操作。在下面的例子中,我们把x + y
的结果通过[:]
写进y
对应的内存中。
我们还可以使用运算符全名函数中的out
参数或者自加运算符+=
(也即add_()
)达到上述效果,例如torch.add(x, y, out=y)
和y += x
(y.add_(x)
)。
注:虽然
view
返回的Tensor
与源Tensor
是共享data
的,但是依然是一个新的Tensor
(因为Tensor
除了包含data
外还有一些其他属性),二者id(内存地址)并不一致。
我们很容易用numpy()
和from_numpy()
将Tensor
和NumPy中的数组相互转换。但是需要注意的一点是:
这两个函数所产生的的Tensor
和NumPy中的数组共享相同的内存(所以他们之间的转换很快),改变其中一个时另一个也会改变!!!
还有一个常用的将NumPy中的array转换成
Tensor
的方法就是torch.tensor()
, 需要注意的是,此方法总是会进行数据拷贝(就会消耗更多的时间和空间),所以返回的Tensor
和原来的数据不再共享内存。
使用numpy()
将Tensor
转换成NumPy数组:
输出:
使用from_numpy()
将NumPy数组转换成Tensor
:
输出:
所有在CPU上的Tensor
(除了CharTensor
)都支持与NumPy数组相互转换。
此外上面提到还有一个常用的方法就是直接用torch.tensor()
将NumPy数组转换成Tensor
,需要注意的是该方法总是会进行数据拷贝,返回的Tensor
和原来的数据不再共享内存。
输出
用方法to()
可以将Tensor
在CPU和GPU(需要硬件支持)之间相互移动。
Copyright © 2003-2013 www.wpsshop.cn 版权所有,并保留所有权利。