版本对应:
本网页讨论的机器学习内容,为特定的机器学习内容(并未与CFD结合)。在《无痛苦NS方程笔记》中, 特定的将CFD与机器学习互相结合起来,无普适性机器学习内容。
ML: libtorch张量(二)
在上一篇内容中介绍了张量的基本定义。本文讨论张量的基本操作。libtorch自带的张量的基本操作有特别多,本文会持续更新常用的基本操作。
Warning
CPUFloatType{2, 4, 3}
中的{2, 4, 3}表示形状。在下文中,将会省略CPUFloatType
,也即形状{2, 4, 3}。
基本声明
double dx = 0.2;
auto x = torch::arange(0, 1, dx);
上述arange()
代码生成0,0.2,0.4,0.6,0.8一系列的矢量,其形状为{5}。
auto test = torch::full({2,3}, 1.0);
上述full()
代码形状为{2,3}的矩阵,其值全部为1。
auto x = torch::linspace(0, 100, 11);
上述linspace()
表示生成从0到100个均匀间隔的11个数字,为一个一维张量。其代码形状为{11}的矩阵,其值为0,10,11,…,100。
auto x = torch::arange(0, 100, 2);
上述arange()
表示生成从0到100,生成间隔为2的一个一维张量。其值为0,2,…
假定a
是一个形状为{10}的张量,item()
可以获得具体的值,比如:
double t = mesh.index({0}).item();
item()
里面也可以加参数,比如item<double>()
,使用形式为l.item<double>()
类似。如果不加参数,那本身张量是什么类型就返回什么类型。
sizes()与size()
sizes()
可以获得张量的形状。有张量x
,x.sizes()
就可以输出张量的形状,例如其可能为{1,2,3}。更进一步的x.size(0)
将返回1,x.size(1)
将返回2,x.size(2)
将返回3。
reshape
reshape可以用来改变张量的形状。
auto b = torch::tensor({1.0, 2.0, 3.0, 4.0});
auto c = b.reshape({4,1});
在这个代码中,b是一个形状为{4}的矢量(一维张量,具有4个元素)。经过b.reshape({4,-1});
将其改变为形状为{4,1}的矢量。也就是变成4行,每一行只有1个元素的\(4\times 1\)矩阵。结果如下:
a:
1
2
3
4
[ CPUFloatType{4} ]
b:
1
2
3
4
[ CPUFloatType{4,1} ]
auto b =
torch::tensor
(
{
{1.0, 2.0, 3.0},
{4.0, 5.0, 6.0}
}
);
其中b是形状为{2,3}的矩阵(可以理解为2行3列矩阵,或2组矢量)。在这里可以进行好多种reshape操作。重要的是,reshape()操作的元素数要与b的元素数相符。通过下面的例子理解会更好理解:
auto c1 = b.reshape({6});//处理成矢量,存在6个元素
auto c3 = b.reshape({6, 1});//处理成矩阵,6行1列
auto c5 = b.reshape({1, 2, 3});
auto c4 = b.reshape({2, 1, 3});
auto c6 = b.reshape({1, 2, 1, 3});
先看reshape({3,2})
,reshape把所有元素展开,然后按照行的方向去处理成3行2列矩阵,那就是:
1 2
3 4
5 6
针对reshape({1, 2, 3});
,reshape把所有元素展开,首先看最后两个数2和3,因为相乘是6,所以reshape会把其排列成:
1 2 3
4 5 6
然后把他们当做一个整体,单独拎出来一个当做一个批次。输出就变成了:
(1,.,.) =
1 2 3
4 5 6
[ CPUFloatType{1,2,3} ]
针对reshape({2, 1, 3});
,reshape把所有元素展开,首先看最后两个数1和3,因为相乘不是6,reshape在目前这一步无法操作。继续看2,1,3,因为\(2\times 1\times3=6\),因此reshape会把其排列成2个批次,每个批次里面是一个1行3列的矩阵。其中1、2、3进入第一个批次,4、5、6进入第二个批次。最终输出为:
(1,.,.) =
1 2 3
(2,.,.) =
4 5 6
[ CPUFloatType{2,1,3} ]
下面看b.reshape({1, 2, 1, 3});
,因为后面3个数2、1、3的乘积是6,因此其会形成一个形状为{2,1,3}的张量。然后单独将其作为一个批次。最终的结果是:
(1,.,.,.) =
(1,.,.) =
1 2 3
(2,.,.) =
4 5 6
[ CPUFloatType{1,2,1,3} ]
Warning
能否做b.reshape({2, 2, 1, 3});
?做不了,因为这几个数相乘是12,不是6。
总之,reshape最重要的原则是,{}
里面的数的相乘,要与元素数量一致,同时,reshape在处理的时候,按照行的方式来进行。
另外,有的时候,reshape里面可以输入-1,比如给定一个形状为{3,4}的矩阵,其一共12个元素,reshape可以写为reshape({2,-1})
,或者reshape({4,-1})
或者reshape({6,-1})
,在这种情况下,-1会自动计算出来,比如
reshape({2,-1})
对应reshape({2,6})
reshape({4,-1})
对应reshape({4,3})
reshape({6,-1})
对应reshape({6,2})
Warning
reshape({-1,-1})
这样写是不行的。不能全部都是-1。reshape不能存在多个-1
unsqueeze
unsqueeze(i)
可以在第i个维度上插入一个新的维度。例如:
auto t = torch::full({2,3,4}, 5.0); //t的形状为{2,3,4}
有:
t.unsqueeze(0)形状为{1,2,3,4}
t.unsqueeze(1)形状为{2,1,3,4}
t.unsqueeze(2)形状为{2,3,1,4}
t.unsqueeze(3)形状为{2,3,4,1}
同时,unsqueeze也可以穿入多个参数。比如unsqueeze(2,3)则表示在第2、3个维度上插入一个新维度。unsqueeze的功能通过reshape也可以实现。
transpose
transpose()可以用来处理转置,通常transpose()里穿入2个参数,比如:
transpose(0,1)表示形状为{a,b,c,d,e,…}的张量,对第a组与b组进行操作;
transpose(0,2)表示形状为{a,b,c,d,e,…}的张量,对第a组与c组进行操作;
transpose(2,3)表示形状为{a,b,c,d,e,…}的张量,对第c组与d组进行操作;
同时,transpose(0,1)与transpose(1,0)相同,transpose(2,3)与transpose(3,2)相同。
Warning
网上一些资料说transpose(0,1)与transpose(1,0)不同。经过实际编程测试,并没有发现区别。
对于一个矩阵,如
1 2 3
4 5 6
对其进行transpose(0,1)之后就变成了
1 4
2 5
3 6
这是非常好理解的。
index
index主要用于从张量中抽取一部分元素。给定b(一个2行3列的矩阵,也可以理解为2组矢量,每个矢量包含3个元素):
auto b =
torch::tensor
(
{
{1.0, 2.0, 3.0},
{4.0, 5.0, 6.0}
}
);
其形状为{2,3}。b.index({0})
返回第1组矢量,形状为{3},即:
1 2 3
同理,b.index({1})
返回第2组矢量,形状为{3},即:
4 5 6
现在将b进行rshape({2,1,3}),为:
(1,.,.) =
1 2 3
(2,.,.) =
4 5 6
[ CPUFloatType{2,1,3} ]
在这种情况下,b.index({0})
返回第一个形状为{1,3}的矩阵,即:
1 2 3
[ CPUFloatType{1,3} ]
b.index({1})
返回第一个形状为{1,3}的矩阵,即:
4 5 6
[ CPUFloatType{1,3} ]
因此,给定形状为{a,b,c,d,e,…}的张量,index({i})取a里面的形状为{b,c,d,e,…}的第i个张量。
考虑更复杂的形状为{2,1,3,2}的张量:
(1,1,.,.) =
1 2
3 4
5 6
(2,1,.,.) =
7 8
9 10
11 12
index({0})
表示取{2,1,3,2}中第一个2里面的第0个张量,也就是会形成一个形状为{1,3,2}的张量,其为:
(1,1,.,.) =
1 2
3 4
5 6
同理,index({1})
表示取{2,1,3,2}中第一个2里面的第1个张量,也就是会形成一个形状为{1,3,2}的张量,其为:
(2,1,.,.) =
7 8
9 10
11 12
index()
还可以输入多个数字,比如index({0,0})
,其会形成一个{3,2}形状的张量,即输出
1 2
3 4
5 6
同理,index({1,0})
,其会形成一个{3,2}形状的张量,即输出
7 8
9 10
11 12
Warning
在这里index({0,1})将会报错。因为形状为{2,1,3,2}的张量,其中的1表示只有1组张量。index({0,1})中最后的1表示取第2组张量。事实上并没有这个东西存在。
以此类推,index({1,0,1})
会形成一个{2}形状的张量,输出:
9 10
回到更简单的情况,考虑一个形状为{5,4}的矩阵:
1 2 3 4
5 6 7 8
9 10 11 12
13 14 15 16
17 18 19 20
[ CPULongType{5,4} ]
index()
可以用来选取其中的列元素。例如index({torch::indexing::Slice(),0})
可以选取其中的第一列元素,形成一个矢量:
1
5
9
13
17
[ CPULongType{5} ]
注意,这里的形状是{5},而不是{5,1}。同理,index({torch::indexing::Slice(),i})
会选取第i列元素。类似的,index({i,torch::indexing::Slice()})
会选取第i行元素。同样的,会形成一个矢量,而不是矩阵。
如果要选取第一列元素的第2-3个元素(我们认为存在第0个元素),可以这样来进行:index({torch::indexing::Slice(2,4),i})
,其中的2表示第2个元素,其中的4表示第4个元素之前。也就是说,Slice(a,b)里面指定的是从第a个元素开始,到第b个元素不包括b。
cat
拼接。例如:
torch::cat({mesh,x,y}, 1);
将mesh,x,y在列方向进行拼接(第一列放mesh,第二列放x,以此类推),如果
mesh=
1 2
3 4
x =
5 6
7 8
y =
9 10
11 12
拼接后有:
1 2 5 6 9 10
3 4 7 8 11 12
下面的代码表示按行拼接(第一行放mesh,第二行放x,以此类推)
torch::cat({mesh,x,y}, 0);
待更新…