numpy中axis参数如何理解

这也是我一直没有搞懂的点,记录一下目前的理解。

axis=i 之所以难懂,就是不同函数有不同的效果,让人一头雾水,而且看到的文档资料感觉都没有说得很清楚。

axis 本身定义

参考官网网址1,axis 本身定义为axis 编号 = shape 元组的下标(从左到右) 。

因此,二维数组里,axis 0 对应行,axis 1 对应列 。

在更高维度下,情况有所不同。NumPy 会将高维向量显示为按行和列排列的构建块的重复,例如这个三维向量:

1
2
a = np.arange(12).reshape(2,2,3)
a

输出

1
2
3
4
5
array([[[ 0,  1,  2],
[ 3, 4, 5]],

[[ 6, 7, 8],
[ 9, 10, 11]]])

a 被表示为一个由两个元素组成的数组,其元素是 2×3 的向量。从这个角度来看,行和列分别对应任意形状下的最后两个轴。

该规则可帮助你预测向量的打印方式,反之亦然,可以找到任何打印元素的索引。例如,在示例中,8 的索引的最后两个值必须是 0 和 2 (对应第一行第二列)。由于 8 出现在两个 2x3 矩阵中的第二个位置,因此第一个索引必须是 1:

1
a[1,0,2]

输出

1
8

在打印的向量中,一个方便的方法是统计开括号后的 [ 符号数量,从而确定数组的维度(dim)。

沿着轴 n 的定义(along an axis)

参考官网网址1,数组 a 沿轴 n (along axis n)的操作,其行为如同将参数视为 a 的一个切片数组,其中每个切片均是沿轴 n 的连续的索引。再翻译一下就是, 固定其他维度,让第 n 维的索引依次变化,对每个切片做操作。

例如,如果 a 是一个 3×N 的数组,沿轴 0 执行操作时,其行为就如同输入的参数是一个包含每行切片的数组:

1
np.array((a[0,:], a[1,:], a[2,:])) 

但是我感觉这个定义不能覆盖 numpy 的全部情况,下面会有我的理解。

np.flip

为了具体说明,我们可以选择数组反转函数 numpy.flip,该函数接受一个轴参数。我们构造一个 3×4 的数组 a:

1
2
a = np.arange(12).reshape(3,4)
a

输出

1
2
3
array([[ 0,  1,  2,  3],
[ 4, 5, 6, 7],
[ 8, 9, 10, 11]])

沿轴0反转后得到

1
np.flip(a,axis=0)

输出

1
2
3
array([[ 8,  9, 10, 11],
[ 4, 5, 6, 7],
[ 0, 1, 2, 3]])

回顾沿轴的定义,沿轴0翻转是将它的参数当作

1
np.array((a[0,:], a[1,:], a[2,:]))

而 np.flip(a, axis=0) 的结果是反转切片:

1
np.array((a[2,:],a[1,:],a[0,:]))

就得到了上面的输出结果,也就是翻转了行的顺序。

np.sort

np.sort 只负责把指定方向上的数字按从小到大(默认)重新排队,不删除任何维度。

1
2
3
4
5
6
7
import numpy as np
arr = np.array([[3, 1, 4],
[6, 5, 2]])
print(arr)
# 原始数据:
# [[3, 1, 4],
# [6, 5, 2]]

场景一:np.sort(arr, axis=0) —— 垂直排序(跨行比较)

  • 逻辑:固定每一列(纵向)。在这一列里,把上面的数和下面的数比较,按从小到大重新排列。

  • 计算过程:

    • 第1列(3和6)→ 排序后变成 [3, 6](不动)
    • 第2列(1和5)→ 排序后变成 [1, 5](不动)
    • 第3列(4和2)→ 排序后变成 [2, 4](4和2互换位置,小的上移)
  • 结果:

    python

    1
    2
    3
    np.sort(arr, axis=0)
    # [[3, 1, 2], <- 只有第三列的数字换了个上下位置
    # [6, 5, 4]]

np.mean:相反效果

但是,“along an axis”这一个统一规则,在不同类型的函数上呈现出不同的效果。

比如如果采用求均值等涉及轴消除的函数,效果完全相反,例如

1
np.mean(a, axis=0)

输出为每列的均值

1
array([4., 5., 6., 7.])

关键在于:mean 是个归约函数(reduction),其计算过程为(这是 AI 的解释):

  1. 沿 axis=0 取出切片集合:[a[0,:], a[1,:], a[2,:]](和术语表定义一致)
  2. 对每个切片集合做归约:把这三个切片”压缩”成一个——具体是对它们逐元素求平均
  3. axis=0 这一维被消除——因为已经没有”多行”可言了,只剩”一列值”

按照两类函数(非归约函数和归约函数),说明如下

💡 “along axis=n” 始终表示:把数组看作一串以 axis n 为索引的切片。至于操作之后 axis n 是否还存在,取决于具体操作的性质:

  • 非归约操作(flip、sort、apply_along_axis):axis n 保留,只是切片被重新排列或各自被处理
  • 归约操作(sum、mean、max、min):axis n 被消除,因为该方向上的多个值被”压缩”成了一个

我的理解

我看了官方文档、AI回答、及其整理的资料,我觉得都不算完全对,或者说没有概括清楚所有的情况。下面说一下我的看法,不一定全对,但是是我归纳得到的结论。

我的理解是,指定 axis=n 时,表示操作将沿着第 n 个维度进行遍历,或者说沿着轴 n 滚动。但是具体怎么个遍历方法不同函数有区别。

下面都以 axis = 0 为例。

这里其实分为3层含义(自身理解,不一定对,但是是我自己从所有 axis=n 整理的规律 ):

  1. 非归约操作的第一种:遍历的是整个切片,就是上面的官方定义,代表函数是 np.flip 和 np.concatenate,二维数组 axis = 0 就是遍历所有行的切片,三维数组 axis = 0 就是遍历所有的二维矩阵的切片,然后改变顺序或整合

  2. 非归约操作的第二种:遍历的是单个元素,也就是沿着 0 轴遍历,提取其他轴某个固定位置的元素,得到这样一组元素的切片,然后改变顺序,代表函数就是 np.sort 。二维数组 axis = 0 ,那就是先提取列号为 0 的所有行的元素,改变顺序,然后列号为1,一直到最后一列。三维数组 axis = 0 也一样,就是先提取第二个轴和第三个轴的坐标均为0的所有行的元素。

    重新整理一下,怎么提取元素的过程,这样好理解:

    1. 固定其他轴:对于输出结果的每一个位置,它先固定除了 axis=i 之外的所有轴。

    2. 沿轴迭代:然后,它沿着 axis=i(即 axis=0,行方向)进行迭代,也就是“沿着”行的方向,从上到下取出所有元素。

  3. 归约操作:同第二种操作,也是遍历的单个元素,只是这是这类函数提取到一组单个元素的切片后,会将其计算为单个的值,因此就将 axis = 0 的形状变成了1,也就是消除了这个维度。代表函数就是 np.mean 。

这里 2 和 3 逻辑是一样的,只不过返回的数组维度不变还是降维而已。但是 1 和 2/3 从效果来说就是相反的,这就是容易让人混淆的。

另一种解释

如果一定要按照那个官方定义来解释所有函数的话,就只能这么理解了。

  1. 非归约操作的第一种:沿 axis=0 取出切片集合,对这个切片集合进行改变顺序或整合
  2. 非归约操作的第二种:沿 axis=0 取出切片集合后,提取所有切片相同位置的元素,然后在所有切片上重新组合
  3. 归约操作:沿 axis=0 取出切片集合后,将所有切片”压缩“成单个切片。做法也是提取所有切片相同位置的元素整合成单个值。

这样理解也可以。注意的点就是所有操作就是针对切片集合的处理,而不是对单个切片的处理。比如求均值,是对切片集合相同位置元素求均值,而不是对单个切片的所有元素求均值。

下面是 AI 给出的归约函数的做法的解释,AI 只给出了这类函数的解释。

归约函数怎么理解

归约操作(Reduction Operation),在 NumPy、PyTorch 等科学计算库中,指的是将一组数据通过某种聚合规则,压缩成更少的数据(通常是单个值或更小维度的张量)。

先把坐标系建立起来

对于一个 shape = (4, 3) 的二维数组:

  • axis=0(第 0 维):长度是 4,对应行的方向——沿着这一维走,你会遍历 4 行
  • axis=1(第 1 维):长度是 3,对应列的方向——沿着这一维走,你会遍历 3 列
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
import numpy as np
a = np.array([[ 0.0, 0.0, 0.0],
[10.0, 10.0, 10.0],
[20.0, 20.0, 20.0],
[30.0, 30.0, 30.0]]) # shape (4, 3)
# axis=1 →
# ┌───┬───┬───┐
# │ 0 │ 0 │ 0 │
# ├───┼───┼───┤
# │10 │10 │10 │
# ├───┼───┼───┤ axis=0 ↓
# │20 │20 │20 │
# ├───┼───┼───┤
# │30 │30 │30 │
# └───┴───┴───┘

核心心法:axis=k 意味着”把第 k 维干掉”

当你对一个数组执行 np.sum(arr, axis=k) 这类归约操作时,第 k 维会从结果里消失——因为这一维上的所有元素被”汇总”成一个值了。

操作 输入 shape 输出 shape 含义
a.sum(axis=0) (4, 3) (3,) 把行这一维消掉 → 得到每列的总和
a.sum(axis=1) (4, 3) (4,) 把列这一维消掉 → 得到每行的总和
a.sum() (4, 3) () 标量 把所有维度都消掉 → 总和

实跑:

1
2
3
print(a.sum(axis=0))   # [60. 60. 60.]  ← shape (3,),每列求和
print(a.sum(axis=1)) # [0. 30. 60. 90.] ← shape (4,),每行求和
print(a.sum()) # 180.0 ← 标量

验证”维度消除”:输入 (4, 3),消掉 axis=0(长度为 4 的那维)→ 剩下 (3,);消掉 axis=1(长度为 3 的那维)→ 剩下 (4,)。结果的 shape 就是”原 shape 去掉 axis 指定的那一位”。

用”固定其他维,沿 axis 滚动”来理解

如果你觉得”消除”太抽象,可以用另一种等价视角:axis=k 表示”沿着第 k 维的方向做运算”,也就是固定其他所有维度,只让第 k 维的索引变化。

以 a.sum(axis=0) 为例: - 固定列号 j(j 可以是 0, 1, 2) - 让行号 i 从 0 变到 3,把这 4 个数加起来 - 所以结果是一个长度为 3 的数组,每个元素是”某一列上所有行的和”

1
2
3
4
# a.sum(axis=0) 的第 j 个元素 = sum over i of a[i, j]
print(a[:, 0].sum()) # 0+10+20+30 = 60
print(a[:, 1].sum()) # 60
print(a[:, 2].sum()) # 60

以 a.sum(axis=1) 为例: - 固定行号 i(i 可以是 0, 1, 2, 3) - 让列号 j 从 0 变到 2,把这 3 个数加起来 - 结果是一个长度为 4 的数组,每个元素是”某一行上所有列的和”

1
2
3
4
5
# a.sum(axis=1) 的第 i 个元素 = sum over j of a[i, j]
print(a[0, :].sum()) # 0+0+0 = 0
print(a[1, :].sum()) # 30
print(a[2, :].sum()) # 60
print(a[3, :].sum()) # 90

推广到三维:用你的形状推 axis

这套逻辑对 N 维数组通用。假设有个 shape = (2, 4, 3) 的三维数组:

  • axis=0:长度 2,沿着”最外层”方向 → 归约后 shape 变成 (4, 3)
  • axis=1:长度 4,沿着”中间层”方向 → 归约后 shape 变成 (2, 3)
  • axis=2:长度 3,沿着”最内层”方向 → 归约后 shape 变成 (2, 4)

记住这条规律:result.shape = 原 shape 去掉 axis 对应的那一维。

1
2
3
4
arr = np.ones((2, 4, 3))
print(arr.sum(axis=0).shape) # (4, 3)
print(arr.sum(axis=1).shape) # (2, 3)
print(arr.sum(axis=2).shape) # (2, 4)

几个常见的 axis 操作场景

函数 axis=0 axis=1
np.sum 各列求和 各行求和
np.mean 各列平均值 各行平均值
np.max 各列最大值 各行最大值
np.concatenate 按行拼接(上下叠) 按列拼接(左右拼)
np.delete 删除某些行 删除某些列

注意 np.concatenate 是个特例——它不”消除”维度,而是”沿着 axis 方向连接”。但 axis 编号的含义不变:axis=0 仍是行方向,axis=1 仍是列方向。

1
2
3
b = np.ones((2, 3))
print(np.concatenate([a, b], axis=0).shape) # (6, 3) ← 行数增加
print(np.concatenate([a, b], axis=1).shape) # (4, 6) ← 列数增加

参考资料

  1. https://numpy.org/doc/stable/glossary.html#term-axis
  • 版权声明: 本博客所有文章除特别声明外,著作权归作者所有。转载请注明出处!
  • Copyrights © 2019-2026 Vincere Zhou
  • 访问人数: | 浏览次数:

请我喝杯茶吧~

支付宝
微信