版本对应:

本网页讨论的机器学习内容,为特定的机器学习内容(并未与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()可以获得张量的形状。有张量xx.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);

待更新…