1. 存储方式
tensor的存储方式是行主序,这决定了reshape()
的工作方式与matlab不同
tensor是有确定维数的,因此tensor(1)
,tensor([1])
,tensor([[1]])
是不同的,不能像matlab一样用一维索引遍历。
>> a=1
a =
1
>> a(1,1,1)
ans =
1
可以使用unsqueeze()
升维;上述三个向量都可以通过.item()
方法取出唯一存储的标量(非唯一有方法.tolist
)
2. 构造与生成
2.1. 等距生成
对于matlab代码:
K>> 1:2:5
ans =
1 3 5
K>> 1:2:6
ans =
1 3 5
同样输入pytorch函数arange()
,得到的东西完全不一样!因为python的最后一位是不取的。
>>> y=torch.arange(1,6,2)
>>> y
tensor([1, 3, 5])
>>> y=torch.arange(1,5,2)
>>> y
tensor([1, 3])
所以,一般的对于start:step:end
,可以转换成torch.arange(start,end+1,step)
3. 索引、查找与赋值
matlab索引从1开始,正常的计算机语言从0开始,想必就不用多说了。
3.1. 查找元素索引
stackoverflow:How Pytorch Tensor get the index of specific value
不像matlab可以直接使用find()
,pytorch没有办法做到查找元素返回索引,于是可以将tensor做逻辑运算编程boolTensor,在进行布尔运算后,通过nonzero()返回非零索引
>>> import torch
>>> xg = torch.linspace(0,1,20)
>>> xg
tensor([0.0000, 0.0526, 0.1053, 0.1579, 0.2105, 0.2632, 0.3158, 0.3684, 0.4211,
0.4737, 0.5263, 0.5789, 0.6316, 0.6842, 0.7368, 0.7895, 0.8421, 0.8947,
0.9474, 1.0000])
>>> xg == xg[3]
tensor([False, False, False, True, False, False, False, False, False, False,
False, False, False, False, False, False, False, False, False, False])
>>> (xg == xg[3]).nonzero()
tensor([[3]])
>>> (xg > 0.3)&(xg < 0.7)
tensor([False, False, False, False, False, False, True, True, True, True,
True, True, True, True, False, False, False, False, False, False])
>>> ((xg > 0.3)&(xg < 0.7)).nonzero()
tensor([[ 6],
[ 7],
[ 8],
[ 9],
[10],
[11],
[12],
[13]])
3.2. 多索引访问
# 一维的情况下:
>>> y=torch.arange(20)
>>> y[torch.tensor([6,7,8])]
tensor([6, 7, 8])
# 多维的情况下:
>>> y=y.reshape(4,5)
>>> y
tensor([[ 0, 1, 2, 3, 4],
[ 5, 6, 7, 8, 9],
[10, 11, 12, 13, 14],
[15, 16, 17, 18, 19]])
>>> y[torch.tensor([2,3])]
tensor([[10, 11, 12, 13, 14],
[15, 16, 17, 18, 19]])
>>> y[(torch.tensor([2,3]),torch.tensor([0,1]))]
tensor([10, 16])
# 这就很变态了,tuple里每个tensor不是坐标,而是每个坐标的在该维度的分量
# 换句话说[2,3],[0,1]代表着坐标(2,0),(3,1)
3.3. 按索引赋值
matlab可以方便的在索引内用矩阵对分立的元素一次性赋值(这得益于matlab可以用一维索引访遍所有元素,以及和find()
的配合),但pytorch不能搜索,就很恶心。
# 上一节的index
>>> index
tensor([[ 6],
[ 7],
[ 8],
[ 9],
[10],
[11],
[12],
[13]])
>>> index.squeeze()
tensor([ 6, 7, 8, 9, 10, 11, 12, 13])
>>> y=torch.arange(20)
# 直接传向量是不可行的
>>> y.index_put_(index.squeeze(),torch.ones(len(index_tuple)))
Traceback (most recent call last):
File "<stdin>", line 1, in <module>
TypeError: index_put_(): argument 'indices' (position 1) must be tuple of Tensors, not Tensor
# 但是你他妈可以通过向量访问到元素
>>> y[torch.tensor([2,3])]=0
>>> y
tensor([ 0, 1, 0, 0, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15, 16, 17,
18, 19])
# 注意看这个逗号,折腾了很久这是关键,否则不会识别成tuple
>>> y.index_put_((index.squeeze(),),torch.ones(len(index_tuple),dtype=torch.long))
tensor([ 0, 1, 0, 0, 4, 5, 1, 1, 1, 1, 1, 1, 1, 1, 14, 15, 16, 17,
18, 19])
3.4. 查找并替换
https://blog.csdn.net/qq_34372112/article/details/106219482
终于!可以摆脱判断后接index_put_()
的逼操作了:
>>> a=torch.tensor([[1,1,4,5,1,4],[1,9,torch.nan,1,9,torch.nan]])
>>> a
tensor([[1., 1., 4., 5., 1., 4.],
[1., 9., nan, 1., 9., nan]])
>>> torch.where(a.isnan(),torch.zeros_like(a),a)
tensor([[1., 1., 4., 5., 1., 4.],
[1., 9., 0., 1., 9., 0.]])
4. 拼接、拓展与维度交换
4.1. 快速拼接
我们都知道matlab的[]
运算符可以做到快速拼接矩阵,对于二维矩阵的拼接比较好理解,在更高维的情况下有:
K>> a = ones(2,3,4);
K>> b = ones(2,3,4);
K>> size([a b])% 实际上调用了horzcat
ans =
2 6 4
K>> size([a,b])
ans =
2 6 4
K>> size([a;b])% 实际上调用了vertcat
ans =
4 3 4
则对于pytorch,cat
或者concat
函数在指定维度上连接(并增长尺寸)
horzcat(a,b) <=> cat((a,b),1)
vertcat(a,b) <=> cat((a,b),0)
4.2. 拼接并拓展维度
在matlab里面[a;b]
还能做到将两个一维行向量接成两行矩阵,但是cat()
做不到这一点,因为pytorch不认为存在一个新维度,但办法也是有的,stack()
可以拓展再拼接:
>>> x = torch.randn(3, 4)
>>> torch.stack((x, x), 0).size()
torch.Size([2, 3, 4])
>>> torch.stack((x, x), 1).size()
torch.Size([3, 2, 4])
>>> torch.cat((x, x), 2).size()
Traceback (most recent call last):
File "<stdin>", line 1, in <module>
IndexError: Dimension out of range (expected to be in range of [-2, 1], but got 2)
4.3. 维度的拓展与复制
在维度与拓展方向相等的情况下,pytorch的tile()
与matlab的repmat()
行为是一致的,但是在维度不同的情况下,它们的行为是不一致的,在matlab下面:
>> x=rand(1,10);
>> size(x)
ans =
1 10
>> xn = repmat(x,[1,1,2]);
>> size(xn)
ans =
1 10 2
对于pytorch:
>>> x=torch.rand(1,10)
>>> x.size()
torch.Size([1, 10])
>>> xn=x.tile(1,1,2)
>>> xn.size()
torch.Size([1, 1, 20])
这他妈就非常恶心了,matlab的repmat()
在这方面更符合直觉,将输入矩阵(1,10)拓展为(1,10,1),迎合拓展尺寸;而pytorch将前面作为更高维,因此拓展为(1,1,10)。也就是说,新维度诞生的位置是不一样的。
在实操的时候一般遇到这个问题的时候是不同维数的矩阵相乘,在matlab中自动广播的机制做的比较好,但到了pytorch里面相对而言就比较拉跨,下面是实操中的例子:
# brdx refers to broadcast
# x_cor.shape = torch.Size([12, 9, 1])
# xn.shape = torch.Size([1, 1, 229])
# nnterm.shape = torch.Size([1, 1, 1, 42])
x_cor_brdx = (
x_cor[i * seg_bound :, :, :]
.unsqueeze(3) # 先手拓展到四维(在3位置处增加一维)
.tile(1, 1, xn.size(2), nnterm.size(3)) # 适应其他维度的尺寸,即广播
)
xn_brdx = xn.unsqueeze(3).tile(
x_cor_brdx.size(0), x_cor_brdx.size(1), 1, nnterm.size(3)
)
terms = (
(-1) ** nnterm # nnterm的前三维不用广播也能顺利运算,但需要广播其他的张量的第四维
* torch.sin((nnterm - 0.5) * pi * (x_cor_brdx - xn_brdx)) # 广播前三维就是为了这个减法
* torch.exp(-((nnterm - 0.5) ** 2) * pi * pi * t)
).sum(3)
terms_out = torch.cat((terms_out, terms), 0)
4.4. 维度的交换
repmat()
函数至今没有遇到使用问题,看来两者行为比较一致。唯一需要注意的是4.3. 维度的拓展与复制中tile()
的行为带来的维数打乱的问题。
4.5. 映射与变换
类似于map
的,pytorch给了两个语焉不详的方法:TORCH.TENSOR.APPLY_和TORCH.TENSOR.MAP_,看起来apply是一参数的,map是两参数的。注意他们是作用于自身的方法,如果是f:x->y的使用并且不想失去x,记得用clone()
方法新开张量。