TensorFlow张量的维度变换

在神经网络运算过程中,维度变换是最核心的张量操作,通过维度变换可以将数据任意地切换形式,满足不同场合的运算需求。

维度变换的一个例子:

Y = X@W + b

X 的 shape 为[2,4]
W 的 shape 为[4,3]
X@W的运算张量shape 为[2,3]
偏置b的张量为[3]

不同 shape 的 2 个张量怎么直接相加呢?

在这里插入图片描述

需要将shape为[3]的偏置b按样本数量复制一份,变成矩阵形式B,
在这里插入图片描述
这样就可以将偏置b和X@W相加了。

基本的维度变换包含了改变视图 reshape,插入新维度 expand_dims,删除维度squeeze,交换维度 transpose,复制数据 tile 等。

1.Reshape

import tensorflow as tf
x = tf.range(96)
x = tf.reshape(x, [2, 4, 4, 3])
x

<tf.Tensor: id=17, shape=(2, 4, 4, 3), dtype=int32, numpy=
array([[[[ 0,  1,  2],
         [ 3,  4,  5],
         [ 6,  7,  8],
         [ 9, 10, 11]],

        [[12, 13, 14],
         [15, 16, 17],
         [18, 19, 20],
         [21, 22, 23]],

        [[24, 25, 26],
         [27, 28, 29],
         [30, 31, 32],
         [33, 34, 35]],

        [[36, 37, 38],
         [39, 40, 41],
         [42, 43, 44],
         [45, 46, 47]]],


       [[[48, 49, 50],
         [51, 52, 53],
         [54, 55, 56],
         [57, 58, 59]],

        [[60, 61, 62],
         [63, 64, 65],
         [66, 67, 68],
         [69, 70, 71]],

        [[72, 73, 74],
         [75, 76, 77],
         [78, 79, 80],
         [81, 82, 83]],

        [[84, 85, 86],
         [87, 88, 89],
         [90, 91, 92],
         [93, 94, 95]]]])>

在存储数据时,内存并不支持这个维度层级概念,只能以平铺方式按序写入内存,数据在创建时按着初始的维度顺序写入,改变张量的视图仅仅是改变了张量的理解方式,并不会改变张量的存储顺序,这在一定程度上是从计算效率考虑的。

在 TensorFlow 中,可以通过张量的 ndim 和 shape 成员属性获得张量的维度数和形状:

x.ndim, x.shape

(4, TensorShape([2, 4, 4, 3]))

通过 tf.reshape(x, new_shape),可以将张量的视图进行任意的合法的改变:

tf.reshape(x, [2, -1])

<tf.Tensor: id=25, shape=(2, 48), dtype=int32, numpy=
array([[ 0,  1,  2,  3,  4,  5,  6,  7,  8,  9, 10, 11, 12, 13, 14, 15,
        16, 17, 18, 19, 20, 21, 22, 23, 24, 25, 26, 27, 28, 29, 30, 31,
        32, 33, 34, 35, 36, 37, 38, 39, 40, 41, 42, 43, 44, 45, 46, 47],
       [48, 49, 50, 51, 52, 53, 54, 55, 56, 57, 58, 59, 60, 61, 62, 63,
        64, 65, 66, 67, 68, 69, 70, 71, 72, 73, 74, 75, 76, 77, 78, 79,
        80, 81, 82, 83, 84, 85, 86, 87, 88, 89, 90, 91, 92, 93, 94, 95]])>

其中的参数-1 表示当前轴上长度需要根据视图总元素不变的法则自动推导,从而方便用户书写。

尽管我们可以有很多种方式去任意合法地改变维度,但是需要意识到,张量的存储顺序始终没有改变,数据在内存中仍然是按着初始写入的顺序0,1,2, … ,95保存的。

2.增删维度

增加维度 增加一个长度为 1 的维度相当于给原有的数据增加一个新维度的概念,维度长度为 1,故数据并不需要改变,仅仅是改变数据的理解方式。

x = tf.random.uniform([28,28],maxval=10,dtype=tf.int32)
x

<tf.Tensor: id=61, shape=(28, 28), dtype=int32, numpy=
array([[4, 2, 2, 9, 1, 7, 8, 7, 2, 3, 8, 2, 9, 9, 1, 3, 2, 0, 4, 1, 0, 2,

通过 tf.expand_dims(x, axis)可在指定的 axis 轴前可以插入一个新的维度:

x = tf.expand_dims(x,axis=2)
x

<tf.Tensor: id=63, shape=(28, 28, 1), dtype=int32, numpy=
array([[[4],
        [2],
        [2],
        [9],
        [1],

tf.expand_dims 的 axis 为正时,表示在当前维度之前插入一个新维度;为负时,表示当前维度之后插入一个新的维度。

删除维度 是增加维度的逆操作,与增加维度一样,删除维度只能删除长度为 1 的维
度,也不会改变张量的存储。

axis 参数为待删除的维度的索引号。

x = tf.expand_dims(x,axis=2)
x

<tf.Tensor: id=73, shape=(28, 28, 1), dtype=int32, numpy=
array([[[1],
        [8],
        [4],

x = tf.squeeze(x, axis=2)
x

<tf.Tensor: id=71, shape=(28, 28), dtype=int32, numpy=
array([[1, 8, 4, 2, 1, 5, 1, 9, 0, 4, 5, 6, 7, 9, 8, 0, 5, 2, 7, 0, 3, 9,

如果不指定维度参数 axis,即 tf.squeeze(x),那么他会默认删除所有长度为 1 的维度。

3.交换维度

交换维度操作是非常常见的,比如在 TensorFlow 中,图片张量的默认存储格式是通道后行格式:[b, h, w, c],但是部分库的图片格式是通道先行:[b, c, h, w],因此需要完成[b, h, w, c]到[b, c, h, w]维度交换运算。

比如一张图片地shape是[2, 32, 32, 3] 图片数量,行,列,通道数的维度索引分别为0, 1, 2, 3, 如果需要交换为[2, 3, 32, 32], 则新维度的排序为图片数量,通道数, 行,列, 对应的索引号为[0, 3, 1, 2]。

x = tf.random.normal([2, 32, 32, 3])
x

<tf.Tensor: id=85, shape=(2, 32, 32, 3), dtype=float32, numpy=
array([[[[ 0.9281655 , -0.87817407, -0.7339403 ],
         [-1.0697994 ,  1.8129319 , -0.25105947],
         [ 1.0635214 ,  0.26829538, -0.578875  ],
         ...,

tf.transpose(x, perm=[0, 3, 1, 2])

<tf.Tensor: id=87, shape=(2, 3, 32, 32), dtype=float32, numpy=
array([[[[ 0.9281655 , -1.0697994 ,  1.0635214 , ...,  0.04531553,

需要注意的是,通过 tf.transpose 完成维度交换后,张量的存储顺序已经改变,视图也随之改变,后续的所有操作必须基于新的存续顺序进行。

4.数据复制

可以通过tf.tile(x, multiples)函数完成数据在指定维度上的复制操作,multiples 分别指定了每个维度上面的复制倍数,对应位置为 1 表明不复制,为 2 表明新长度为原来的长度的 2 倍,即数据复制一份,以此类推。

import tensorflow as tf
x = tf.range(4)
x = tf.reshape(x, [2, 2])
x

<tf.Tensor: id=125, shape=(2, 2), dtype=int32, numpy=
array([[0, 1],
       [2, 3]])>

# 在列维度复制1份数据 2就是列维度大小扩大为原来两倍 1表示行维度不变
x = tf.tile(x, multiples=[1, 2])
x

<tf.Tensor: id=143, shape=(2, 4), dtype=int32, numpy=
array([[0, 1, 0, 1],
       [2, 3, 2, 3]])>

# 然后在行维度复制一份 2就是行维度大小扩大为原来两倍 1表示列维度不变
x = tf.tile(x, multiples=[2, 1])
x

<tf.Tensor: id=145, shape=(4, 4), dtype=int32, numpy=
array([[0, 1, 0, 1],
       [2, 3, 2, 3],
       [0, 1, 0, 1],
       [2, 3, 2, 3]])>

再举一个例子:

import tensorflow as tf
x = tf.range(4)
x = tf.reshape(x, [1, 4])
x

<tf.Tensor: id=231, shape=(1, 4), dtype=int32, numpy=array([[0, 1, 2, 3]])>

x = tf.tile(x, multiples=[4, 1])
x

<tf.Tensor: id=233, shape=(4, 4), dtype=int32, numpy=
array([[0, 1, 2, 3],
       [0, 1, 2, 3],
       [0, 1, 2, 3],
       [0, 1, 2, 3]])>

x = tf.range(4)
x = tf.reshape(x, [1, 4])
x

<tf.Tensor: id=231, shape=(1, 4), dtype=int32, numpy=array([[0, 1, 2, 3]])>

x = tf.tile(x, multiples=[1, 4])
x

<tf.Tensor: id=235, shape=(4, 16), dtype=int32, numpy=
array([[0, 1, 2, 3, 0, 1, 2, 3, 0, 1, 2, 3, 0, 1, 2, 3],
       [0, 1, 2, 3, 0, 1, 2, 3, 0, 1, 2, 3, 0, 1, 2, 3],
       [0, 1, 2, 3, 0, 1, 2, 3, 0, 1, 2, 3, 0, 1, 2, 3],
       [0, 1, 2, 3, 0, 1, 2, 3, 0, 1, 2, 3, 0, 1, 2, 3]])>

需要注意的是,tf.tile 会创建一个新的张量来保存复制后的张量,由于复制操作涉及到大量数据的读写 IO 运算,计算代价相对较高。

你可能感兴趣的:(张量,维度变换,TensorFlow,入门)