NumPy 中 array 和 matrix 都能表示二维数据,但乘法运算符、转置、一维向量解释差异明显。实际开发中常见的 ValueError: shapes (3,3) and (1,3) not aligned,往往就是混用 ndarray 和 matrix 的 *、@ 导致。下面结合实测过程,把 array 与 matrix 的区别、矩阵乘法写法、维度排查方法整理出来。
一、array 与 matrix 的基本关系
NumPy 的主要对象是同种元素的多维数组 ndarray。维度叫轴,轴的个数叫秩,这同线性代数中的矩阵秩不是一个概念;要求矩阵秩,可以用 np.linalg.matrix_rank。matrix 是 array 的分支(子类),很多场景通用。官方建议如果两者都能用,优先 array:更灵活、更快,还能表示 3、4、5 维;matrix 的优势是运算符简单,两个矩阵相乘可以直接用 *,而 array 的 * 是逐元素乘法,普通矩阵乘法要用 dot 或 @。
- import numpy as np
- i = np.matrix(np.eye(4))
- print(type(i))
- print(np.linalg.matrix_rank(i))
- i = np.eye(4)
- print(type(i))
- print(np.linalg.matrix_rank(i))
复制代码
输出分别是 matrix 类型、4、ndarray 类型、4。说明两者都能被 matrix_rank 处理,但类型不同。
二、array 中的 @、* 与一维 shape
np.array([1,2,3]) 的 shape 是 (3,),它既可能被当作行向量,也可能被当作列向量,取决于放在矩阵乘法左边还是右边。@ 是矩阵乘法,* 或 np.multiply 是数量积/逐元素乘法。示例:
- import numpy as np
- a = np.array([[1, 0, 0], [0, 1, 0], [0, 0, 1]])
- b = np.array([1, 2, 3])
- c1 = a @ b
- c2 = b @ a
- print(c1)
- print(c2)
复制代码
c1 相当于 (3,3) @ (3,),得到 (3,);c2 相当于 (3,) @ (3,3),也得到 (3,)。这说明一维 ndarray 在 @ 两侧会按规则参与矩阵乘法,但不要把它当成真正固定形状的行向量或列向量。
再看逐元素乘法:
- import numpy as np
- a = np.array([[1, 0, 2], [0, 0, 1], [0, 0, 1]])
- b = np.array([1, 2, 1])
- c1 = a * b
- c2 = b * a
- print(c1)
- print(c2)
复制代码
这里 * 不会执行矩阵乘法,而是把 b 广播到 a 的每一列/每一行,结果仍是逐元素运算。
三、matrix 中 * 是矩阵乘法,multiply 才是逐元素乘法
matrix 与 ndarray 最大的差异在于运算符含义。matrix 中 * 直接表示矩阵乘法。例如:
- import numpy as np
- a = np.mat([[1, 0, 0], [0, 1, 0], [0, 0, 1]])
- b = np.mat([1, 2, 3])
- c = b * a
- print(c.shape)
- c2 = np.multiply(a, b)
- print(c2)
复制代码
b 被构造为 matrix 后,形状是 (1,3),即行向量。因此 b * a 是 (1,3)*(3,3),可以执行。若写 a * b,则等价于 (3,3)*(1,3),会报:
- ValueError: shapes (3,3) and (1,3) not aligned: 3 (dim 1) != 1 (dim 0)
复制代码
原因是 matrix 中 * 调用矩阵乘法,a 的列数 3 与 b 的行数 1 不匹配。若要让 a * b 成立,需要把 b 变成列向量,例如 b.reshape(3,1) 或 b.T。逐元素乘法在 matrix 中要用 np.multiply:
- c = np.multiply(a, b)
- print(c)
复制代码
输出为:
- [[1 0 0]
- [0 2 0]
- [0 0 3]]
复制代码
四、@ 在 matrix 中同样可用
实测 @ 在 matrix 中也可表示矩阵乘法,并且与 * 的结果一致:
- import numpy as np
- a = np.mat([[1], [1], [1]])
- b = np.mat([1, 1, 1])
- c1 = a @ b
- c2 = a * b
- print(c1 == c2)
复制代码
输出全为 True。也就是说,在 matrix 中 @ 和 * 都可表示叉乘/矩阵乘法;但在 array 中 @ 才是矩阵乘法,* 是逐元素乘法。
五、为什么 (1×2) 和 (1×3) 直接相乘会报错
如果要实现 (1×2) 与 (1×3) 相乘,直观做法是先把 (1×2) 转置成 (2×1),再与 (1×3) 相乘,得到 (2×3)。但直接用一维 array 会出问题:
- import numpy as np
- a = np.array([1, 1])
- b = np.array([1, 1, 1])
- c = a @ b
复制代码
报错:
- ValueError: shapes (2,) and (3,) not aligned: 2 (dim 0) != 3 (dim 0)
复制代码
这是因为一维 ndarray 的 @ 不是把 a 当 (1,2)、把 b 当 (3,1) 的二维矩阵乘法,而是要求参与运算的一维长度一致。解决办法是借助 matrix 转换为明确的二维矩阵:
- import numpy as np
- a = np.array([1, 1])
- b = np.array([1, 1, 1])
- c = (np.matrix(a).T) @ (np.matrix(b))
- print(c)
复制代码
输出:
这里 np.matrix(a).T 得到 (2,1),np.matrix(b) 得到 (1,3),相乘后就是 (2,3)。
六、.T 转置对一维 array 不起作用,对 matrix 起作用
二维 array 可以正常转置:
- import numpy as np
- a = np.array([[1, 1, 1], [1, 1, 1]])
- print(a.T.shape)
复制代码
输出 (3,2)。但一维 array 不行:
- import numpy as np
- a = np.array([1, 1, 1])
- print(a.shape)
- print(a.T.shape)
复制代码
输出都是 (3,),转置后形状不变。matrix 则可以:
- import numpy as np
- a = np.mat([[1, 1, 1]])
- print(a.shape)
- print(a.T.shape)
复制代码
输出 (1,3) 和 (3,1)。这也是上面用 np.matrix(a).T 能把一维数组变成列向量的原因。
七、实践建议
实际写 NumPy 矩阵运算时,建议每一步都打印 shape,尤其是在 @、*、dot、multiply 混用时。需要矩阵语义时,优先把一维数组 reshape 成明确二维形状,如 (n,1) 或 (1,n);或者使用 @ 配合二维 array。matrix 虽然让 * 直接表示矩阵乘法,但容易与 array 的广播、逐元素乘法混淆,官方也更推荐 array。只要明确 array 和 matrix 在 *、@、multiply、.T 上的差异,就能避免 shapes not aligned 一类错误。 |