美文网首页
理解科学计算(numpy,pytorch)中的dim参数

理解科学计算(numpy,pytorch)中的dim参数

作者: 我只要喝点果粒橙 | 来源:发表于2020-12-05 14:16 被阅读0次

    理解numpy中array和pytorch中tensor的操作是开始科学运算的第一步!

    首先明白维度的感念:

    维度

    我们通常能听到的都是2D, 3D,其实这边的D就是dimension的含义即维度。2D,我们通常理解为是平面,如我们最熟悉的直角坐标系就是平面坐标系,还有极坐标系。而3D呢,就是在平面的基础上增加了一维——高度,从而使平面的物体立起来了,同样3D也有耳熟能详的坐标系——3维坐标系。

    更官方的解释呢:维度(Dimension),又称为维数,是数学中独立参数的数目。在物理学哲学的领域内,指独立的时空坐标的数目。0维是一个无限小的点,没有长度。1维是一条无限长的线,只有长度。2维是一个平面,是由长度和宽度(或部分曲线)组成面积。3维是2维加上高度组成体积。4维分为时间上和空间上的4维,人们说的4维通常是指关于物体在时间线上的转移。(4维准确来说有两种。1.四维时空,是指三维空间加一维时间。2.四维空间,只指四个维度的空间。)四维运动产生了五维。

    从哲学角度看,人们观察、思考与表述某事物的“<u>思维角度</u>”,简称“维度”。例如,人们观察与思考“月亮”这个事物,可以从月亮的“内容、时间、空间”三个思维角度去描述;也可以从月亮的“载体、能量、信息”三个思维角度去描述。这边的维度其实也可以理解为角度,从不同的方面去看待、确定一个事物。

    所以代数上来说,维度其实是数学里在表示方面的一个重要的概念,它反映的是一个空间的本质性质。

    科学计算中维度的概念

    从二维点位置->编程中的坐标系

    维度的考量主要集中在矩阵的运算上。首先我们来看一个元素:4,其实它就是一个点,可以被认为是0维的。但往往我们不会只有一个元素。我们最常见的是编程中的数组,如[1,2,3,4],这个是由多个元素构成的,它的维度就是一维的,这个我们也比较好理解。

    而二维是什么呢?我们能直观理解的二维是平面坐标系的那种:(1,3), (4,5)...即给一个x,一个y,那么在平面中就可以在直角坐标系下确定这个点(物)。现在我们规整下这些坐标点[ (1, 3), (4, 5) ],从这个角度上离我们的矩阵,或是数组好像还是有点远。那么我们继续变形。

    如果我们需要画出坐标系中有哪些点的话, 1.第一种做法就是跟上述一样, 把点都存一个vector中[ (1, 3), (4, 5) ],然后遍历,再在坐标系中点出。2.第二种呢,就是在坐标系中把所有的位置都列出来,如果有点存在就把它标出来,即跟我们列出迷宫地图一样,先把地图画出来,然后再把宝藏标出来。所以上述的两个点可以理解为。在给出了map[20][20]的地图上,(1, 3)和(4, 5)位置为true, 即map[1][3] = 1, map[4][5] = 1,其他位置map[x][y] = 0,所以这样我们就从[(1, 3), (4, 5)]==> 用map形式表现出了这两个点,两者成功在二维上进行了转换。接下来我们就来分析这个二维的map。

    数组,在编程中,我们都不陌生,如int arr[50][50],虽然可以通过这个二维的数组,根据val的不同来表示三维的量,但是我们这边不把它这么理解,仅是当做bool arr[50][50]来理解维度上的概念。===>同样,面对numpy中的array我们也是这么个理解。

    import numpy as np
    import torch 
    x = np.random.randint(2, size = (2,3))
    print(x)
    
    y = torch.randint(2, size = (2, 3))
    print(y)
    '''
    [[0 1 1]
     [1 0 1]]
    tensor([[1, 0, 1],
            [0, 0, 1]])
    '''
    

    我们从numpy的x上理解,这边是创建了一个2*3的矩阵,其中x[0][1], x[0][2], x[1][0], x[0][2]全1,其余为0,输入x.shape得到的结果是(2, 3),有两项,跟我们从map的理解上是一致的,这个地图map拥有长和宽两个维度。

    然后我们从编程中观察这个2*3的矩阵或是叫数组,可以发现x[a][b]第一个[]中的内容a范围是从0-1的,第一个[]中的内容b范围是从0-2的,0的话学编程的人都能很快的理解,而第二个的范围却不太那么肯定。为什么呢?因为它跟我们普通认知的直角坐标系不一致。下面我们把x画出来(不改变输出显示的形式,而是让坐标系去适应这种表现形式)。

    坐标系.png

    为什么是这样画的呢?首先明确的原则是,不改变输出显示的形式,而是让坐标系去适应这种输出形式,因此输出长啥样,我们坐标系只能去适应。由于我们碰到有x,有y的时候,习惯上把第一个出现的当作x,第二个当作y,所以就有了第一个[]为x,第二个[]为y。

    好了,现在我们确定好坐标系长什么样了。接下来就是具体理解dim的含义了

    编程中坐标系->科学计算中array的dim

    想必大家在学习numpy或者torch的时候都被各种函数方法中的dim参数折磨过,感觉怎么理解都有问题,不敢自己使用。因此,这边就是解决,这些函数中的dim到底是怎么确定的

    比如我们创建一个高维的array

    A = torch.randint(2, size = (1,2,3,4))
    '''
    tensor([[[[0, 0, 1, 0],
              [1, 1, 0, 1],
              [0, 0, 0, 0]],
             [[0, 0, 1, 1],
              [1, 1, 1, 0],
              [0, 1, 0, 0]]]])
    '''
    

    举个我自己最初理解dim的笨方法:硬记x为第一维(dim = 0), y为第二维(dim = 1)

    实际上这种记法是比较低效的,最好的方法是我们怎么定义这个array就怎么记,比如我们这边创建的是一个size=(1, 2, 3, 4),输出len(A.shape)为4,可以看到这就是个4维的tensor,那么我们顺理成章地就把把各个维度依次定义出来了。如dim = 0地指的就是size = 1的那层,dim = 1就是指size = 2的那层,依次类推。这样说可能有点抽象,因此我们回归简单的。

    B = torch.randint(2, size = (3, 2))
    print(B)
    '''
    tensor([[1, 0],
            [1, 1],
            [0, 0]])
    '''
    

    按照我们刚的定义,dim=0就是size=3的这一层,也就是我们坐标系中的X轴orX面。

    好了,想必大家这个时候还不知道我在说什么。接下来就带大家来测试函数。

    测试dim在函数参数中的定义

    提前指出把:要注意函数介绍中dim指的是"沿着dim这个维度"or"删除、增加....dim这个维度(在dim这个维度上进行维度修改)"

    规约计算

    一般是指分组聚合计算,表现结果就是会进行维度压缩

    sum

    沿着dim累加元素

    C = torch.randint(5, size = (2, 5))
    print(C)
    '''
    tensor([[2, 3, 3, 4, 0],
            [1, 0, 2, 4, 4]])
    '''
    print(C.sum(dim = 0))
    print(C.sum(dim = 1))
    '''
    tensor([3, 3, 5, 8, 4])
    tensor([12, 11])
    '''
    

    可以看到sum就是比较典型的"沿着dim"的例子,当dim = 0时就沿着dim = 0即x轴进行累加,由于sum这个函数会进行维度的压缩,所以最后的结果为tensor([3, 3, 5, 8, 4])

    cumprod

    通过dim指定沿着某个维度计算累积

    其他的函数还有cumsum、prod、sum,实际上两者是相同的,还有mean、median、var、std、min、max

    #
    x = torch.Tensor([
        [2,3,4,5,6],
        [9,8,7,6,5,]
    ])
    print(torch.cumprod(x, dim = 0))
    print(torch.cumprod(x, dim = 1))
    '''
    tensor([[ 2.,  3.,  4.,  5.,  6.],
            [18., 24., 28., 30., 30.]])
    tensor([[2.0000e+00, 6.0000e+00, 2.4000e+01, 1.2000e+02, 7.2000e+02],
            [9.0000e+00, 7.2000e+01, 5.0400e+02, 3.0240e+03, 1.5120e+04]])
    '''
    
    # min
    x = torch.Tensor([
        [2,3,4,5,6],
        [9,8,7,6,5,]
    ])
    print(torch.min(x, dim = 0))
    print(torch.min(x, dim = 1))
    '''
    torch.return_types.min(
        values=tensor([2., 3., 4., 5., 5.]),
        indices=tensor([0, 0, 0, 0, 1]) )
    torch.return_types.min(
        values=tensor([2., 5.]),
        indices=tensor([0, 4]) )
    '''
    
    # mean
    x = torch.Tensor([
        [2,3,4,5,6],
        [9,8,7,6,5,]
    ])
    print(torch.mean(x, dim = 0))
    print(torch.mean(x, dim = 1))
    '''
    tensor([5.5000, 5.5000, 5.5000, 5.5000, 5.5000])
    tensor([4., 7.])
    '''
    
    索引、切片、连接
    squeeze,unsqueeze

    unsqueeze关键字:参数dim指定在第几个维度增加"[]",以提升维度

    squeeze: unsqueeze的逆操作,删除dim指定的维度

    unsqueeze

    D = torch.Tensor( [1, 2, 3, 4, 5] )
    y = D.unsqueeze(dim = 0)
    print(y, y.shape)
    '''
    tensor([[1., 2., 3., 4., 5.]]) torch.Size([1, 5])
    '''
    y = D.unsqueeze(dim = 1)
    print(y.shape)
    '''
    tensor([[1.],
            [2.],
            [3.],
            [4.],
            [5.]])  torch.Size([5, 1])
    '''
    

    可以看到的是dim = 0的时候就是在dim = 0 维度上增加了一维, 使得变成了[1, 5]。第二个是在dim=1的位置加了一维变成了[5, 1] (这也就是为什么很多书上会说其实就是在dim维度上加了1)

    ▲这个典型就是要区分: 在dim维度上沿着dim维度

    squeeze

    F = torch.Tensor( [ [0, 2, 3, 4], 
                        [22, 33, 44 ,55]
                      ])
    y = torch.squeeze(F, dim = 0)
    print(y, y.shape)
    '''
    tensor([[ 0.,  2.,  3.,  4.],
            [22., 33., 44., 55.]]) torch.Size([2, 4])
    '''
    y = torch.squeeze(F, dim = 1)
    print(y, y.shape)
    '''
    tensor([[ 0.,  2.,  3.,  4.],
            [22., 33., 44., 55.]]) torch.Size([2, 4])
    '''
    

    这边变换不大的原因是因为dim上没有size=1可以删除

    split

    按(沿着)dim维度将tensor分成n个部分

    x = torch.Tensor([
        [1, 2, 3, 4, 5, 6, 7, 8, 9, 10],
        [10, 9, 8, 7, 6, 5, 4, 3, 2, 1]
    ])
    print(x)
    print(torch.split(x, 5, dim = 1))
    # 指定划分列表,表示依次有1,2,3,4个长度 (总和得跟dim维度上元素个数相同)
    print(torch.split(x, [1,2,3,4], dim = 1))
    '''
    
    tensor([[ 1.,  2.,  3.,  4.,  5.,  6.,  7.,  8.,  9., 10.],
            [10.,  9.,  8.,  7.,  6.,  5.,  4.,  3.,  2.,  1.]])
            
    (tensor([                               # 可以看到这个是在x[:][6]的地方将tensor切成了两个
            [ 1.,  2.,  3.,  4.,  5.],
            [10.,  9.,  8.,  7.,  6.]
            ]), 
    tensor([
            [ 6.,  7.,  8.,  9., 10.],
            [ 5.,  4.,  3.,  2.,  1.]
            ]))
            
    (tensor([[ 1.],
            [10.]]), tensor([[2., 3.],
            [9., 8.]]), tensor([[4., 5., 6.],
            [7., 6., 5.]]), tensor([[ 7.,  8.,  9., 10.],
            [ 4.,  3.,  2.,  1.]]))
    '''
    
    unbind

    删除某个维度后,返回所有切片组成的元组

    
    x = torch.rand(1,2,3)
    # x = torch.rand(size=(1,2,3))
    print(x, x.shape)
    out = torch.unbind(x, dim = 1)
    print(out, len(out))
    '''
    tensor([
            [
                [0.3631, 0.6672, 0.9489],
                [0.4944, 0.1606, 0.6122]
            ]
             ]) torch.Size([1, 2, 3])
             
    (
        tensor([[0.3631, 0.6672, 0.9489]]),         torch.Size([1, 3])
        tensor([[0.4944, 0.1606, 0.6122]])          torch.Size([1, 3])
    )      2
    '''
        
        
    x = torch.Tensor([
            [[1,2,3,4,],
            [5,6,7,8],
            [9,10,11,12]],
        
            [[13,14,15,16],
            [17,18,19,20],
            [21,22,23,24]]
    ])
    print(x, x.shape)
    out = torch.unbind(x, dim = 1)
    print(out, len(out))
    print(out[0].shape)
    '''
    tensor([
            [[1,2,3,4,],
            [5,6,7,8],
            [9,10,11,12]],
        
            [[13,14,15,16],
            [17,18,19,20],
            [21,22,23,24]]
    ])                                              torch.Size([2, 3, 4])
    (tensor([
            [ 1.,  2.,  3.,  4.],
            [13., 14., 15., 16.]]), 
    tensor([
            [ 5.,  6.,  7.,  8.],
            [17., 18., 19., 20.]]), 
    tensor([
            [ 9., 10., 11., 12.],
            [21., 22., 23., 24.]])) 
    删除dim = 1, 把size[1] = 3的tensor拆成了3个tensor
        不要记这个: 因为dim0为z轴, dim1为x轴, dim2为y轴,所以删除dim1就是删除x轴,最后得到的就是yOz平面
    '''
    
    cat、stack

    通过关键字dim指定按哪个维度拼接

    x = torch.randint(1, 100, size=(2,3))
    print(x)
    y = torch.randint(1, 100, size=(2,3))
    print(y)
    res = torch.cat((x, y), dim = 1)
    print(res)
    '''
    
    tensor([[71, 56, 44],
            [64, 30, 87]])
    tensor([[39, 56, 63],
            [68, 28, 65]])
    tensor([[71, 56, 44, 39, 56, 63],
            [64, 30, 87, 68, 28, 65]])
    '''
    
    # 加强高维理解
    x = torch.randint(1, 100, size=(2,3,4))
    print(x)
    y = torch.randint(1, 100, size=(2,3,4))
    print(y)
    res = torch.cat((x, y), dim = 1)
    print(res)
    '''
    tensor([[[81, 79, 10,  8],
             [47, 30, 48, 35],
             [10, 57, 68, 88]],
    
            [[33, 51, 60, 97],
             [27, 14, 83, 51],
             [51, 54, 79, 65]]])
    tensor([[[85,  9, 95, 95],
             [29, 99, 12,  8],
             [32,  8,  3, 84]],
    
            [[13, 24, 46, 20],
             [86, 83, 72, 10],
             [76, 33, 79, 48]]])
    tensor([[[81, 79, 10,  8],
             [47, 30, 48, 35],
             [10, 57, 68, 88],
             [85,  9, 95, 95],
             [29, 99, 12,  8],
             [32,  8,  3, 84]],
    
            [[33, 51, 60, 97],
             [27, 14, 83, 51],
             [51, 54, 79, 65],
             [13, 24, 46, 20],
             [86, 83, 72, 10],
             [76, 33, 79, 48]]]) torch.Size([2, 6, 4])
    dim=1即沿元素为3的方向上延伸,所以结果变成了6
        不要记:也可以理解为沿x轴方向
    '''
    

    官方文档: https://pytorch.org/docs/stable/generated/torch.cat.html?highlight=cat#torch.cat

    总结

    正确理解姿势

    dim是指tensor在shape上的顺序(可以这么理解),如x的shape是2x3x4,也就是[2, 3, 4]。故可以这样一一对应来。
    比如dim = 1就是按具有3个元素的那个轴操作,从而不用死记硬背那些dim = 0是对列操作还是对行操作了。

    强记三维

    3维.png

    但还是不提倡强记,因为一旦高维就理解不了了。

    附:

    关于size的设置

    在ones、rand等函数上,size = (2,3,4),我们在C++数组中int arr[x][y][z]的理解是2*3然后z为4, 但实际上在科学运算中size = (2,3,4)的矩阵是有4个3*4的矩阵叠加而成,这边是要区分的

    >>> import numpy as np
    >>> a = np.random.randint(1, 100, [2, 3, 4])
    >>> a
    array([[[26, 36, 31, 21],
            [74, 59, 79, 32],
            [77, 94, 81, 32]],
    
           [[72, 76, 85, 93],
            [66, 34, 80, 12],
            [99, 17, 98, 23]]])
    
    x = torch.randint(1, 100, size=(2,3,4))
    print(x)
    print(x[1][2][3])       # 高度索引为1的, 在x = 2, y = 3的元素就是76
    '''
    tensor([[[63, 54, 57, 17],
             [78, 64, 76, 44],
             [96,  3, 59, 37]],
    
            [[86,  3, 92, 84],
             [89, 36,  8, 79],
             [10, 87, 15, 76]]])
    tensor(76)
    '''
    
    x = torch.randint(1, 100, size=(2,3,4,2))
    print(x)
    '''
    tensor([[[[29, 50],
              [50, 69],
              [95, 70],
              [21, 35]],
    
             [[58, 65],
              [15, 53],
              [96, 25],
              [11, 75]],
    
             [[12, 71],
              [36, 12],
              [71, 92],
              [87, 47]]],
    
    
            [[[43, 89],
              [88, 22],
              [61, 56],
              [47, 97]],
    
             [[71,  7],
              [44, 88],
              [54, 32],
              [15, 65]],
    
             [[96, 22],
              [90, 78],
              [30, 85],
              [65, 57]]]])
    '''
    

    pytorchAPI:

    https://pytorch.org/docs/stable/torch.html#torch.arange

    相关文章

      网友评论

          本文标题:理解科学计算(numpy,pytorch)中的dim参数

          本文链接:https://www.haomeiwen.com/subject/qfdgwktx.html