第一部分是PyTorch中有关Tensor的一些基本用法,因为之前并没有系统学习过PyTorch,所以现在看书的同时慢慢学习PyTorch的知识
第二部分是原书的知识和一些自己的理解
张量 Tensor
张量包含了一个数据集合,这个数据集合就是原始值变形而来的,它可以是一个任何维度的数据。tensor的rank就是其维度。
Rank本意是矩阵的秩,不过Tensor Rank和Matrix Rank的意义不太一样,这里就还叫Rank。Tensor Rank的意义看起来更像是维度,比如Rank =1就是向量,Rank=2 就是矩阵了,Rank = 0 就是一个值。
在PyTorch中,**torch.Tensor
**是存储和变换数据的主要工具。Tensor
和 NumPy
的多维数组非常类似。
首先导入PyTorch
import torch
创建一个5x3的未初始化的 Tensor
x = torch.empty(5, 3)
print(x)
tensor([[5.4880e+23, 4.5886e-41, 2.7434e-24],
[3.0915e-41, 4.4842e-44, 0.0000e+00],
[4.4842e-44, 0.0000e+00, 2.7450e-24],
[3.0915e-41, 5.4880e+23, 4.5886e-41],
[4.2039e-45, 0.0000e+00, 4.6243e-44]])
创建一个5x3的随机初始化的 Tensor
x = torch.rand(5, 3) # 这里rand的用法后面会讲到
print(x)
tensor([[0.7787, 0.8019, 0.3431],
[0.1335, 0.3062, 0.2305],
[0.6151, 0.5777, 0.2794],
[0.4701, 0.6086, 0.9624],
[0.6524, 0.6794, 0.8206]])
创建一个5x3的long类型全0的 Tensor
x = torch.zeros(5, 3, dtype=torch.long)
print(x)
tensor([[0, 0, 0],
[0, 0, 0],
[0, 0, 0],
[0, 0, 0],
[0, 0, 0]])
直接根据数据创建
x = torch.tensor([5.5, 3])
print(x)
tensor([5.5000, 3.0000])
通过现有的 Tensor
来创建,此方法会默认重用输入 Tensor
的一些属性,例如数据类型,除非自定义数据类型。
x = x.new_ones(5, 3, dtype=torch.float64) # new_ones 返回一个与size大小相同的用1填充的张量,默认具有相同的torch.dtype和torch.device
print(x)
x = torch.randn_like(x, dtype=torch.float) # randn_like形状与输入的张量相同,指定新的数据类型
print(x)
tensor([[1., 1., 1.],
[1., 1., 1.],
[1., 1., 1.],
[1., 1., 1.],
[1., 1., 1.]], dtype=torch.float64)
tensor([[-0.9532, 0.4367, -0.1972],
[ 2.1078, 0.3750, -0.2939],
[-0.3682, 1.3246, -0.7197],
[-0.4119, 0.2093, -0.3431],
[-1.7094, 0.0638, -0.4597]])
通过 shape
或者size()
来获取 Tensor
的形状
print(x.size())
print(x.shape)
torch.Size([5, 3])
torch.Size([5, 3])
✨ 注意:返回的torch.Size其实就是一个tuple, 支持所有tuple的操作。
此变量用于生成数据类型为浮点型的 Tensor
,传递给 torch.FloatTensor
的参数可以是一个列表,也可以是一个维度值。
a = torch.FloatTensor(2, 3) # 两行三列
print(a)
tensor([[5.4880e+23, 4.5886e-41, 5.4880e+23],
[4.5886e-41, 1.4584e-19, 7.8458e+17]])
b = torch.FloatTensor([[2, 3], [4, 5]])
print(b, b.shape, b.dtype)
tensor([[2., 3.],
[4., 5.]]) torch.Size([2, 2]) torch.float32
用于生成数据类型为整型的 Tensor
,传递给 torch.IntTensor
的参数可以是一个列表,也可以是一个维度值。
a = torch.IntTensor(2, 3)
print(a)
tensor([[1726508320, 32745, 407958368],
[ 22062, 1953384789, 1701869908]], dtype=torch.int32)
b = torch.IntTensor([[2, 3], [4, 5]])
print(b, b.dtype)
tensor([[2, 3],
[4, 5]], dtype=torch.int32) torch.int32
用于生成数据类型为浮点型且维度指定的随机 Tensor
,和在 Numpy
中使用 numpy.rand
生成随机数的方法类似,随机生成的浮点数据在 0~1区间均匀分布。
a = torch.rand(2, 3)
print(a, a.dtype)
tensor([[0.8055, 0.3392, 0.5802],
[0.3333, 0.7156, 0.3415]]) torch.float32
用于生成数据类型为浮点型且维度指定的随机 Tensor
,和在 Numpy
中使用 numpy.randn
生成随机数的方法类似,随机生成的浮点数的取值满足均值为0,方差为1的正态分布。
a = torch.randn(2, 3)
print(a, a.dtype)
tensor([[ 0.4737, 0.3686, -1.1102],
[ 0.9147, -0.3446, -0.7511]]) torch.float32
用于生成数据类型为浮点型且自定义其实范围和结束范围的 Tensor
,所以传递给 torch.range
的参数有三个,分别是范围的起始值,范围的结束值和步长,其中,步长用于指定从起始值到结束值的每步的数据间隔。
a = torch.range(2, 8, 3)
print(a, a.dtype)
tensor([2., 5., 8.]) torch.float32
/opt/conda/lib/python3.7/site-packages/ipykernel_launcher.py:2: UserWarning: torch.range is deprecated in favor of torch.arange and will be removed in 0.5. Note that arange generates values in [start; end), not [start; end].
用于生成数据类型为浮点型且维度指定的 Tensor
,不过这个浮点型的 Tensor
中的元素值全部为0
a = torch.zeros(3, 4)
print(a, a.dtype)
tensor([[0., 0., 0., 0.],
[0., 0., 0., 0.],
[0., 0., 0., 0.]]) torch.float32
这里通常对 Tensor
数据类型的变量进行运算,来组合一些简单或者复杂的算法,常用的 Tensor
运算如下:
将参数传递到 torch.abs
后返回输入参数的绝对值作为输出,输出参数必须是一个 Tensor
数据类型的变量
a = torch.randn(2, 3)
print(a)
b = torch.abs(a)
print(b)
tensor([[-1.5257, 0.1174, -0.2927],
[ 0.4662, 0.7019, 0.2605]])
tensor([[1.5257, 0.1174, 0.2927],
[0.4662, 0.7019, 0.2605]])
将参数传递到 torch.add
后返回输入参数的求和结果作为输出,输入参数既可以全部是 Tensor
数据类型的变量,也可以是一个 Tensor
数据类型的变量,另一个是标量。
a = torch.randn(2, 3)
print(a)
b = torch.randn(2, 3)
print(b)
c = torch.add(a, b)
print(c)
d = torch.randn(2, 3)
print(d)
e = torch.add(d, 10)
print(e)
tensor([[-1.5090, -1.1659, -0.7795],
[ 0.8453, -0.0334, 0.2251]])
tensor([[-1.5168, -1.2602, 0.8775],
[ 1.8206, -0.0880, -1.1371]])
tensor([[-3.0258, -2.4261, 0.0980],
[ 2.6659, -0.1213, -0.9120]])
tensor([[0.2818, 1.4852, 2.0287],
[1.1209, 1.6720, 1.0154]])
tensor([[10.2818, 11.4852, 12.0287],
[11.1209, 11.6720, 11.0154]])
可以指定输出
result = torch.empty(2, 3)
torch.add(a, b, out=result)
print(result)
tensor([[-3.0258, -2.4261, 0.0980],
[ 2.6659, -0.1213, -0.9120]])
关于加法还有两种方式:
✨ 注:PyTorch操作inplace版本都有后缀_, 例如x.copy_(y), x.t_()
print(a+b)
tensor([[-3.0258, -2.4261, 0.0980],
[ 2.6659, -0.1213, -0.9120]])
b.add_(a)
print(b)
tensor([[-3.0258, -2.4261, 0.0980],
[ 2.6659, -0.1213, -0.9120]])
对输入参数按照自定义的范围进行裁剪,最后将参数裁剪的结果作为输出。所以输入参数一共有三个,分别是需要进行裁剪的Tensor数据类型的变量、裁剪的上边界和裁剪的下边界,
具体的裁剪过程是:使用变量中的每个元素分别和裁剪的上边界及裁剪的下边界的值进行比较,如果元素的值小于裁剪的下边界的值,该元素就被重写成裁剪的下边界的值;
同理,如果元素的值大于裁剪的上边界的值,该元素就被重写成裁剪的上边界的值。
a = torch.randn(2, 3)
print(a)
b = torch.clamp(a, -0.1, 0.1)
print(b)
tensor([[ 0.5965, 2.1073, -1.2866],
[-0.1101, -1.6736, -2.2357]])
tensor([[ 0.1000, 0.1000, -0.1000],
[-0.1000, -0.1000, -0.1000]])
将参数传递到 torch.div
后返回输入参数的求商结果作为输出,同样,参与运算的参数可以全部是 Tensor
数据类型的变量,也可以是 Tensor
数据类型的变量和标量的组合。
a = torch.randn(2,3)
print(a)
b = torch.randn(2,3)
print(b)
c = torch.div(a,b)
print(c)
d = torch.randn(2,3)
print(d)
e = torch.div(d,10)
print(e)
tensor([[ 0.4518, 0.1334, 1.7579],
[ 0.0349, -0.2346, 1.6790]])
tensor([[ 1.2516, -1.1198, 1.1351],
[-0.6222, -0.6472, -0.0758]])
tensor([[ 0.3610, -0.1191, 1.5486],
[ -0.0561, 0.3624, -22.1492]])
tensor([[ 0.2908, 0.0664, -1.4821],
[ 0.4358, 0.3226, 1.0338]])
tensor([[ 0.0291, 0.0066, -0.1482],
[ 0.0436, 0.0323, 0.1034]])
将参数传递到 torch.mul
后返回输入参数求积的结果作为输出,参与运算的参数可以全部是 Tensor
数据类型的变量,也可以是 Tensor
数据类型的变量和标量的组合。
a = torch.randn(2, 3)
print(a)
b = torch.randn(2, 3)
print(b)
c = torch.mul(a, b)
print(c)
d = torch.randn(2, 3)
print(d)
e = torch.mul(d, 10)
print(e)
tensor([[ 0.5851, 0.2113, 0.6891],
[ 1.1177, -0.0177, 1.5595]])
tensor([[ 0.9094, -0.0707, -0.3900],
[ 0.2990, -0.9827, 0.7165]])
tensor([[ 0.5321, -0.0149, -0.2687],
[ 0.3342, 0.0174, 1.1174]])
tensor([[-0.7012, 1.2348, 1.6156],
[ 0.5412, 0.2345, -0.5753]])
tensor([[-7.0115, 12.3478, 16.1558],
[ 5.4116, 2.3447, -5.7526]])
将参数传递到 torch.pow
后返回输入参数的求幂结果作为输出,参与运算的参数可以全部是 Tensor
数据类型的变量,也可以是 Tensor
数据类型的变量和标量的组合。
a = torch.randn(2, 3)
print(a)
b = torch.pow(a, 2)
print(b)
tensor([[-0.9387, 1.0499, -1.6718],
[-0.3190, -1.1677, -0.0666]])
tensor([[0.8812, 1.1024, 2.7948],
[0.1018, 1.3635, 0.0044]])
将参数传递到 torch.mm
后返回输入参数的求积结果作为输出,不过这个求积的方式和之前的 torch.mul
运算方式不太样,
torch.mm
运用矩阵之间的乘法规则进行计算,所以被传入的参数会被当作矩阵进行处理,参数的维度自然也要满足矩阵乘法的前提条件,
即前一个矩阵的行数必须和后一个矩阵的列数相等,否则不能进行计算。
a = torch.randn(2, 3)
print(a)
b = torch.randn(3, 2)
print(b)
b = torch.mm(a, b)
print(b)
tensor([[ 0.1701, 0.9539, -0.3128],
[-0.2466, 2.4600, -1.6023]])
tensor([[-1.0573, -1.0292],
[-0.2707, 0.2992],
[-1.0913, -3.1058]])
tensor([[-0.0967, 1.0818],
[ 1.3436, 5.9664]])
将参数传递到 torch.mv
后返回输入参数的求积结果作为输出,torch.mv
运用矩阵与向量之间的乘法规则进行计算,被传入的参数中的第1个参数代表矩阵,第2个参数代表向量,顺序不能颠倒。
a = torch.randn(2, 3)
print(a)
b = torch.randn(3)
print(b)
c = torch.mv(a, b)
print(c)
tensor([[ 1.7745, 0.8665, -0.5622],
[-0.6072, 0.5540, -1.0647]])
tensor([ 0.0553, -0.5526, -1.0924])
tensor([0.2335, 0.8233])
部分操作已经在前面提及
索引出来的结果与原数据共享内存,也即修改一个,另一个会跟着修改。
x = torch.randn(2, 4)
print(x)
y = x[:, :3]
print(y)
y += 1
print(y)
print(x[:, :3]) # 源tensor也被改了
print(x)
tensor([[ 0.5706, 0.3683, 1.4869, 1.2791],
[-0.1592, -1.7226, -1.1192, -0.9729]])
tensor([[ 0.5706, 0.3683, 1.4869],
[-0.1592, -1.7226, -1.1192]])
tensor([[ 1.5706, 1.3683, 2.4869],
[ 0.8408, -0.7226, -0.1192]])
tensor([[ 1.5706, 1.3683, 2.4869],
[ 0.8408, -0.7226, -0.1192]])
tensor([[ 1.5706, 1.3683, 2.4869, 1.2791],
[ 0.8408, -0.7226, -0.1192, -0.9729]])
除了常用的索引选择数据之外,PyTorch还提供了一些高级的选择函数:
函数 | 功能 |
---|---|
index_select(input, dim, index) | 在指定维度dim上选取,比如选取某些行、某些列 |
masked_select(input, mask,out=None) | 根据布尔掩码 (boolean mask) 索引输入张量的 1D 张量 |
nonzero(input) | 非0元素的下标 |
gather(input, dim, index) | 根据index,在dim维度上选取数据,输出的size与index一样 |
index_select
index_select(
input,
dim,
index
)
参数:
print(torch.index_select(x,0,torch.tensor([0, 1])))
print(torch.index_select(x,1,torch.tensor([0, 1])))
tensor([[ 1.5706, 1.3683, 2.4869, 1.2791],
[ 0.8408, -0.7226, -0.1192, -0.9729]])
tensor([[ 1.5706, 1.3683],
[ 0.8408, -0.7226]])
masked_select
masked_select(
input,
mask,
out
)
参数:
⚠️ 注意:「 masked_select 函数最关键的参数就是布尔掩码 mask,
传入 mask 参数的布尔张量通过 True 和 False (或 1 和 0) 来决定输入张量对应位置的元素是否保留,
既然是一一对应的关系,这就需要传入 mask 中的布尔张量和传入 input 中的输入张量形状要相同。」
mask = x.ge(0.5)
print(mask)
print(torch.masked_select(x, mask))
tensor([[ True, True, True, True],
[ True, False, False, False]])
tensor([1.5706, 1.3683, 2.4869, 1.2791, 0.8408])
print(x)
print(torch.nonzero(x))
tensor([[ 1.5706, 1.3683, 2.4869, 1.2791],
[ 0.8408, -0.7226, -0.1192, -0.9729]])
tensor([[0, 0],
[0, 1],
[0, 2],
[0, 3],
[1, 0],
[1, 1],
[1, 2],
[1, 3]])
gather
gather(
input,
dim,
index
)
参数:
print(torch.gather(x, dim=1, index=torch.LongTensor([[0, 1],[0, 0]]))) # dim为1说明按列索引,[0, 1]表示第一行的第0列和第1列,就是1.5706和1.3683,同理[0, 0]是0.8408和0.8408
print(torch.gather(x, dim=0, index=torch.LongTensor([[0,1,1,0],[0,0,0,0]]))) # dim为0说明按行索引,[0, 1, 1, 0]表示第0行,第1行,第1行,第0行
tensor([[1.5706, 1.3683],
[0.8408, 0.8408]])
tensor([[ 1.5706, -0.7226, -0.1192, 1.2791],
[ 1.5706, 1.3683, 2.4869, 1.2791]])
用 view()
来改变 Tensor
的形状:
⚠️ 需要注意的是:-1所指的维度可以根据其他维度的值推出来,这个用法在很多地方都见过,应该要记住
y = x.view(8)
z = x.view(-1, 2) # -1所指的维度可以根据其他维度的值推出来
print(x.size(), y.size(), z.size())
torch.Size([2, 4]) torch.Size([8]) torch.Size([4, 2])
view()
返回的新 Tensor
与源 Tensor
虽然可能有不同的 size
,但是是共享 data
的,也即更改其中的一个,另外一个也会跟着改变。(顾名思义,view
仅仅是改变了对这个张量的观察角度,内部数据并未改变)
x += 1
print(x)
print(y)
tensor([[2.5706, 2.3683, 3.4869, 2.2791],
[1.8408, 0.2774, 0.8808, 0.0271]])
tensor([2.5706, 2.3683, 3.4869, 2.2791, 1.8408, 0.2774, 0.8808, 0.0271])
所以如果我们想返回一个真正新的副本(即不共享data内存)该怎么办呢?
Pytorch还提供了一个 reshape()
可以改变形状,但是此函数并不能保证返回的是其拷贝,所以不推荐使用。推荐先用 clone
创造一个副本然后再使用 view
。
x_cp = x.clone().view(8)
x -= 1
print(x)
print(x_cp)
tensor([[ 1.5706, 1.3683, 2.4869, 1.2791],
[ 0.8408, -0.7226, -0.1192, -0.9729]])
tensor([2.5706, 2.3683, 3.4869, 2.2791, 1.8408, 0.2774, 0.8808, 0.0271])
✨ 使用 clone
还有一个好处是会被记录在计算图中,即梯度回传到副本时也会传到源 Tensor
另外一个常用的函数就是 item()
, 它可以将一个标量 Tensor
转换成一个Python number:
x = torch.randn(1)
print(x)
print(x.item())
tensor([1.0600])
1.059958815574646
另外,PyTorch还支持一些线性函数,这里提一下,免得用起来的时候自己造轮子,具体用法参考官方文档。如下表所示:
函数 | 功能 |
---|---|
trace | 对角线元素之和(矩阵的迹) |
diag | 对角线元素 |
triu/tril | 矩阵的上三角/下三角,可指定偏移量 |
mm/bmm | 矩阵乘法,batch的矩阵乘法 |
addmm/addbmm/addmv/addr/baddbmm… | 矩阵运算 |
t | 转置 |
dot/cross | 内积/外积 |
inverse | 求逆矩阵 |
svd | 奇异值分解 |
当对两个形状不同的 Tensor
按元素运算时,可能会触发广播(broadcasting)机制:先适当复制元素使这两个 Tensor
形状相同后再按元素运算。
x = torch.arange(1, 3).view(1, 2)
print(x)
y = torch.arange(1, 4).view(3, 1)
print(y)
print(x + y)
tensor([[1, 2]])
tensor([[1],
[2],
[3]])
tensor([[2, 3],
[3, 4],
[4, 5]])
由于 x
和 y
分别是1行2列和3行1列的矩阵,如果要计算 x + y
,那么 x
中第一行的2个元素被广播(复制)到了第二行和第三行,
而 y
中第一列的3个元素被广播(复制)到了第二列。如此,就可以对2个3行2列的矩阵按元素相加。
前面说了,索引操作是不会开辟新内存的,而像y = x + y
这样的运算是会新开内存的,然后将y指向新内存。
为了演示这一点,我们可以使用Python自带的 id
函数:如果两个实例的ID一致,那么它们所对应的内存地址相同;反之则不同。
x = torch.tensor([1, 2])
y = torch.tensor([3, 4])
id_before = id(y)
y = y + x
print(id(y) == id_before) # False
False
如果想指定结果到原来的 y
的内存,我们可以使用前面介绍的索引来进行替换操作。在下面的例子中,我们把x + y
的结果通过 [:]
写进 y
对应的内存中。
x = torch.tensor([1, 2])
y = torch.tensor([3, 4])
id_before = id(y)
y[:] = y + x
print(id(y) == id_before) # True
True
我们还可以使用运算符全名函数中的 out
参数或者自加运算符 +=
(也即 add_()
)达到上述效果,例如 torch.add(x, y, out=y)
和 y += x(y.add_(x))
。
x = torch.tensor([1, 2])
y = torch.tensor([3, 4])
id_before = id(y)
torch.add(x, y, out=y) # y += x, y.add_(x)
print(id(y) == id_before) # True
True
✨ 注:虽然 view
返回的 Tensor
与源 Tensor
是共享data的,但是依然是一个新的 Tensor
(因为 Tensor
除了包含data外还有一些其他属性),二者id(内存地址)并不一致。
我们很容易用 numpy()
和 from_numpy()
将 Tensor
和 NumPy
中的数组相互转换。
但是需要注意的一点是: 这两个函数所产生的的 Tensor
和 NumPy
中的数组共享相同的内存(所以他们之间的转换很快),改变其中一个时另一个也会改变!!!
✨ 还有一个常用的将NumPy中的array转换成Tensor的方法就是torch.tensor(), 需要注意的是,此方法总是会进行数据拷贝(就会消耗更多的时间和空间),所以返回的Tensor和原来的数据不再共享内存。
Tensor转NumPy
使用 numpy()
将 Tensor
转换成 NumPy
数组:
a = torch.ones(5)
b = a.numpy()
print(a, b)
a += 1
print(a, b)
b += 1
print(a, b)
tensor([1., 1., 1., 1., 1.]) [1. 1. 1. 1. 1.]
tensor([2., 2., 2., 2., 2.]) [2. 2. 2. 2. 2.]
tensor([3., 3., 3., 3., 3.]) [3. 3. 3. 3. 3.]
NumPy数组转Tensor
使用from_numpy()
将 NumPy
数组转换成 Tensor
:
import numpy as np
a = np.ones(5)
b = torch.from_numpy(a)
print(a, b)
a += 1
print(a, b)
b += 1
print(a, b)
[1. 1. 1. 1. 1.] tensor([1., 1., 1., 1., 1.], dtype=torch.float64)
[2. 2. 2. 2. 2.] tensor([2., 2., 2., 2., 2.], dtype=torch.float64)
[3. 3. 3. 3. 3.] tensor([3., 3., 3., 3., 3.], dtype=torch.float64)
所有在CPU上的 Tensor
(除了 CharTensor
)都支持与 NumPy
数组相互转换。
此外上面提到还有一个常用的方法就是直接用 torch.tensor()
将 NumPy
数组转换成 Tensor
,
需要注意的是该方法总是会进行数据拷贝,返回的 Tensor
和原来的数据不再共享内存。
c = torch.tensor(a)
a += 1
print(a, c)
[4. 4. 4. 4. 4.] tensor([3., 3., 3., 3., 3.], dtype=torch.float64)
用方法 to()
可以将 Tensor
在CPU和GPU(需要硬件支持)之间相互移动。
# 以下代码只有在PyTorch GPU版本上才会执行
if torch.cuda.is_available():
device = torch.device("cuda") # GPU
y = torch.ones_like(x, device=device) # 直接创建一个在GPU上的Tensor
x = x.to(device) # 等价于 .to("cuda")
z = x + y
print(z)
print(z.to("cpu", torch.double)) # to()还可以同时更改数据类型