数据中shape变换会用到的函数

前言:在处理数据的时候,经常需要存储、读取、变换等等操作,其中一个很重要的方面就是对数据进行升维和降维,如何正确的、按照我们自己的处理思路完成数据的操作非常重要,在本文中我们简单了解一些经常使用的函数。

concatenate

沿着现有的轴连接一系列数组。无论是numpy中、还是pytorch、tensorflow中,concatenate的用法都是相同的,知只是一些参数名字表达不同。在这里我们以numpy为例,结合官方文档做一些通俗易懂的解释。

numpy.concatenate((a1a2...)axis=0out=Nonedtype=Nonecasting="same_kind")

  - (a1a2...):可以同时concat多个,shape要一致,除了要连接的axis那一维度(当axis参数为0,形如(m,k)和形如(n,k),在现有轴上concat后是(m+n,k)不是吗?对于本来就要连接的那一维度形状是不限制的,但是为了保证concat后的形状仍然是统一的,所以shape要保持一致)

  - axis:整数,可选,默认是0。如果特别设置axis=None,会把所有数据先展平再连接。

  - out:可选,指定一个已经存在的数组来存储 concatenate 操作的结果。这可以避免分配新的内存,从而在某些情况下提高性能。对新手来说还有一个功能就是用于验证concat后的shape结果是否是自己所想要达到的结果。

  - dtype:可选,指定数组数据的数据类型。注意1:不能和out参数结合使用,因为out已经指定了一个数据类型。注意2:如果数组中原本的数据类型各不相同,那么指定数据类型后会区分情况,如str的内容是数值,转换到int是可以的,否则会报错,也就是说dtype会对所有的数据进行指定。

  - casting:可选{‘no’, ‘equiv’, ‘safe’, ‘same_kind’, ‘unsafe’},默认为same_kind,一般和dtype配合使用,制定了在对数据进行类型转换时的规则。

(大多数场景下仅指定(a1, a2, ...)和axis参数即可)

# 简单例子

a = np.array([[1, 2], [3, 4]])
b = np.array([[5, 6]])
np.concatenate((a, b), axis=0)
array([[1, 2],
       [3, 4],
       [5, 6]])
np.concatenate((a, b.T), axis=1)
array([[1, 2, 5],
       [3, 4, 6]])
np.concatenate((a, b), axis=None)
array([1, 2, 3, 4, 5, 6])
# out参数的使用方法

a = np.array([1, 2, 3])
b = np.array([4, 5, 6])

# 创建一个足够大的输出数组
out_array = np.empty((6,), dtype=int)  # 确保有足够的空间和适当的数据类型

# 使用 out 参数进行合并
np.concatenate((a, b), out=out_array)

最后,当使用 np.concatenate 来合并两个或多个 MaskedArray 时,输出的数组将不再是 MaskedArray,并且合并后的数组不会继承任何输入数组的掩码。换句话说,即使输入数组中的某些数据被掩蔽,输出数组也会将这些数据视为普通数据,不再标记为掩蔽。如果你的数据中包含 MaskedArray 并且你希望保留掩码信息,你需要在使用 np.concatenate 后手动处理或恢复这些掩码。这通常意味着你需要重新创建一个 MaskedArray 并应用原始的掩码或根据需要生成一个新的掩码。

# 创建两个 MaskedArray,其中包含一些掩蔽元素
a = ma.array([1, 2, 3], mask=[0, 1, 0])
b = ma.array([4, 5, 6], mask=[1, 0, 0])

# 使用 np.concatenate 合并这两个数组
result = np.concatenate((a, b))

# 查看合并后的结果
print("合并后的数组:", result)
print("数组的掩码:", ma.getmaskarray(result))  # 这将显示没有掩码

# 重新应用掩码
new_mask = np.concatenate((a.mask, b.mask))
result_masked = ma.array(result, mask=new_mask)

print("重新掩蔽后的数组:", result_masked)

最后对于使用concat的一点快速思考的trick,axis设置的那个维度是长度可以不一致的,axis所在的那个维度是各数据长度的和,其它的长度不变,如axis=1,(2,3,3)和(2,4,3)->(2,3+4,3)==(2,7,3)


 

stack

stack的的含义是在一个新的轴(维度)上连接一系列张量,无论是numpy中、还是pytorch、tensorflow中,stack的用法都是相同的,只是一些参数名字表达不同。

numpy.stack(arraysaxis=0out=None*dtype=Nonecasting='same_kind')

  - arrays:多个shape相同的数组,值得注意的是,这里的每个数组shape一定要一致

  axis:可选,默认为0,新添加轴所在的位置

  - out: 可选,和上一个concatenate函数参数意义一致,不再赘述

  - dtype:可选,指定堆叠后的数据类型

  - castring: {‘no’, ‘equiv’, ‘safe’, ‘same_kind’, ‘unsafe’}, optional

 与concatenate最大的不同就是,stack会产生一个新轴,新轴的长度就是堆叠前数组的个数。在pytorch中该函数的实现是基于调用concat函数,是在指定维度上unsqueeze然后再在该维度上进行concat。

 所以我更觉得,在这里要理解stack后数据的组织形式,即是如何对数据进行切分和粘接的

# 从1维到2维
a = np.array([1, 2, 3]) b = np.array([4, 5, 6]) np.stack((a, b)) array([[1, 2, 3], [4, 5, 6]]) np.stack((a, b), axis=-1) array([[1, 4], [2, 5], [3, 6]])
# 从二维到三维
arrays = [np.random.randn(3, 4) for _ in range(10)]
np.stack(arrays, axis=0).shape
(10, 3, 4)
np.stack(arrays, axis=1).shape
(3, 10, 4)
np.stack(arrays, axis=2).shape
(3, 4, 10)

 

column_stack

numpy.column_stack(tup)

  - tup: 一些1-D维或者2-D的数组,1-D和2-D混合也可

当输入为多维数组时,hstackcolumn_stack 的行为是一致的。但当输入数组为有且只有一维数组时,hstack 会将它们简单地并排放置,就像concat一维数组一样,而不会将其视为列向量。而column_stack会把所有的1-D数组先转换为列向量(或者简单的说是转置也一样),然后再按照列进行拼接

a = np.array((1,2,3))
b = np.array((2,3,4))
np.column_stack((a,b))
array([[1, 2],
       [2, 3],
       [3, 4]])
z = np.ones((3,3))
x = np.zeros(3)
print(np.column_stack((z,x)))

[[1. 1. 1. 0.]
 [1. 1. 1. 0.]
 [1. 1. 1. 0.]]

上述第一个例子如果把column_stack 替换为hstack,结果会是[1,2,3,2,3,4]

上述第二个例子如果把column_stack 替换为hstack就会报错,因为两个数组的shape不一致(不符合规则)。


 

row_stack

又名vstack,等同于concatenate指定了沿着第一列进行拼接

numpy.row_stack(tup*dtype=Nonecasting='same_kind')

  - tup:除了第一维外的形状必须相同;当是一位数组时,各个数组的的长度必须相同
  - dtype:与上述所有的参数要求一致(有趣的是,在官方文档中也说明了不能用out参数公用,但是官方文档中其实并没有out参数)
  - castring: {‘no’, ‘equiv’, ‘safe’, ‘same_kind’, ‘unsafe’}, optional
  - return:至少是2-D的数组,所以该函数会把1维的数组升维到2维,但是在高维(2维以上又不会添加新轴,等同于concatenate-axis=0)
# 1维长度相同,并返回2-D数组
a = np.array([1, 2, 3]) b = np.array([4, 5, 6]) np.vstack((a,b)) array([[1, 2, 3], [4, 5, 6]])
# 高维数组等同于concatenate指定axis为0,两个(3,1)stack后变为(6,1)
a = np.array([[1], [2], [3]]) b = np.array([[4], [5], [6]]) np.vstack((a,b)) array([[1], [2], [3], [4], [5], [6]])

 

expand_dims/unsqueeze

在现有的数据维度上增加一个新的维度,正确理解维度的添加方式是理解stack的的第一步、

numpy.expand_dims(aaxis)

  - array_like: 输入n维数组

  - axis: 整数型or元组,新增轴所在位置

# 从1维到2维
x = np.array([1, 2]) x.shape (2,)
# The following
is equivalent to x[np.newaxis, :] or x[np.newaxis]: y = np.expand_dims(x, axis=0) y array([[1, 2]]) y.shape (1, 2)
# The following
is equivalent to x[:, np.newaxis]: y = np.expand_dims(x, axis=1) y array([[1], [2]]) y.shape (2, 1)

axis 参数是一个元组时,函数会按照元组中指定的每个轴的顺序依次添加新维度。这样,你可以一次性扩展数组的多个维度,而不需要多次调用 expand_dims

当原始数组为(3,4):

axis=(0, 2)

  1. 首先在原数组最前面(最外层)添加一个新维度,数组形状从 (3, 4) 变为 (1, 3, 4)
  2. 然后在新的第三维添加一个新维度,数组形状从 (1, 3, 4) 变为 (1, 3, 1, 4)

axis=(2, 0)

  1. 首先在原数组的第三个轴位置(实际上是在最后,因为原数组只有两维)添加一个新维度,数组形状从 (3, 4) 变为 (3, 4, 1)
  2. 然后在最外层(现在是新的第一维)添加一个新维度,数组形状从 (3, 4, 1) 变为 (1, 3, 4, 1)

 

newaxis

numpy.newaxis

在 Python 的 NumPy 库中,np.newaxis 是一个非常有用的工具,用于增加数组的维度。它通常用在索引操作中,来改变现有数组的形状,从而添加一个新的轴(维度)。他和上述的expand_dims/unsequzzez的效果是一致的,我的感受是这样更显式一些。

a = np.array([1, 2, 3])
# 在数组前面添加一个新的轴,变成1x3的二维数组
row_vec = a[np.newaxis, :]
>>>
[[1,2,3]]
# 在数组后面添加一个新的轴,变成3x1的二维数组 col_vec = a[:, np.newaxis]
>>>
[[1],
[2],
[3]]

 有趣的是,numpy.newaxis还能作为一个None的常量/别名

print(newaxis is None)
True
print(np.newaxis)
None

 

squeeze

维度压缩是expand_dims/unsqueeze的反操作,顾名思义就是在现有维度的层数上再减少一维

numpy.squeeze(aaxis=None)

  - array_like:Input data.
  - axisNone or int or tuple of ints, optional,默认参数是None
当使用expand_dims时,新添加的轴的长度是1,同理,去压缩的维度也一定得是1,当axis=None时,所有长度为1的维度都会被移除,官方文档中给出如果一个ndarray的所有维度长度都为1,那么不指定维度后会退化为数组:0 d array
x = np.array([[[0], [1], [2]]])
x.shape
(1, 3, 1)
# axis=None
np.squeeze(x).shape
(3,)
# axis=0/1/2
np.squeeze(x, axis=0).shape
(3, 1)
np.squeeze(x, axis=1).shape
Traceback (most recent call last):
...
ValueError: cannot select an axis to squeeze out which has size not equal to one
np.squeeze(x, axis=2).shape
(1, 3)
# 所有的维度都为1
x = np.array([[1234]])
x.shape
(1, 1)
np.squeeze(x)
array(1234)  # 0d array
np.squeeze(x).shape
()
np.squeeze(x)[()]
1234

 

reshape

在不更改数组数据的情况下为数组提供新形状。这个函数有点像是上述两个函数的升级款,如果想做,也是能做到上述两个函数做的事。

numpy.reshape(anewshapeorder='C')

   - a:array_like
  - newshape:整数或者整数型元组。当是整数时,意味着把数据规整为1维,此时参数可以指定为-1,意味着由函数自动推理总元素数,或者直接指定整数为元素个数,其它整数会报错。当是元组时,就意味着转换为指定的shape,其中也可以设置某一维度为-1,这意味着由函数推算该维的的长度。
  - order: 可选,{‘C’, ‘F’, ‘A’},默认为’C‘。这里指定了数组重塑操作中的对元素的读取顺序,’C‘意味着按照行展开读取,即更容易理解的哪一种顺序,’F‘是按照列的形式展开读取。 ‘C’ means to read / write the elements using C-like index order, with the last axis index changing fastest, back to the first axis index changing slowest. ‘F’ means to read / write the elements using Fortran-like index order, with the first index changing fastest, and the last index changing slowest.
np.reshape(a, (2, 3)) # C-like index ordering
array([[0, 1, 2],
       [3, 4, 5]])
np.reshape(np.ravel(a), (2, 3)) # equivalent to C ravel then C reshape
array([[0, 1, 2],
       [3, 4, 5]])
np.reshape(a, (2, 3), order='F') # Fortran-like index ordering
array([[0, 4, 3],
       [2, 1, 5]])
np.reshape(np.ravel(a, order='F'), (2, 3), order='F')
array([[0, 4, 3],
       [2, 1, 5]])
# order选择的区别
a = np.array([[1,2,3], [4,5,6]]) np.reshape(a, 6) array([1, 2, 3, 4, 5, 6]) np.reshape(a, 6, order='F') array([1, 4, 2, 5, 3, 6])
# 自动推算维度长度
np.reshape(a, (3,-1))       # the unspecified value is inferred to be 2
array([[1, 2],
       [3, 4],
       [5, 6]])

 

flatten

该函数用法直观简单且单一,就是无论什么维度的数组都展成1维的数组

ndarray.flatten(order='C')

  - order: {‘C’, ‘F’, ‘A’, ‘K’}, optional

  • 'C':C风格,按行的顺序展平数组(默认值)。
  • 'F':Fortran风格,按列的顺序展平数组。
  • 'A':如果数组是在Fortran连续内存中,按列顺序展平,否则按行顺序。
  • 'K':按照元素在内存中的出现顺序展平,不考虑特定的风格。
a = np.array([[1,2], [3,4]])
a.flatten()
>>>
array([1, 2, 3, 4])
a.flatten('F')
>>>
array([1, 3, 2, 4])

 

总结

  • concat是在现有的轴上进行连接,元素总数肯定是两个分离数组的和,维度层数不变,只有连接的那个轴的长度发生变化,且变化后的长度是变化前一些列数组的在那一维度的和。
  • stack是在新建的轴上进行的连接,所以维度层数发生了变化,元素总是也是一系列被链接数组元素的和。特别地与concat不同又相同的是,只有新建的轴的长度发生变化且其它轴的shape要完全一样。
  • expand_dims和squeeze都是针对某一轴长度为1的操作,不过是一个指定在那个维度层数增加一个长度为1的轴,另一个是决定将哪个长度为1的轴给删除。
  • reshape也是对单个数组进行的shape重塑,reshape、expand_dims和squeeze这三者操作前后的总元素个数是不发生任何变化的。

参考链接:

chatGPT4

numpy官方文档:https://numpy.org/doc/stable/index.html

posted @ 2024-05-05 17:18  半度墨水  阅读(44)  评论(0编辑  收藏  举报
Live2D