📊 第6章 图解数组计算模块 NumPy
📄 一句话概括

A * B 是逐元素乘,A @ B 才是矩阵乘。两个结果毫无关系。 形状 (m, n) 乘 (n, p) 得 (m, p),中间那个 n 必须相等。 行列式为 0 就没有逆矩阵,这是「方程无唯一解」的信号。

📝 本章和前面几章的关系

这一篇是第 6 章最容易出错的一篇:星号和 @ 长得像,算的完全不是一回事。 矩阵乘、转置、逆、解方程、按轴求和,五件事覆盖了九成矩阵操作。

一、三种乘法,结果完全不同

A 的第一行是 1 和 2,第二行是 3 和 4;B 的第一行是 5 和 6,第二行是 7 和 8。 A * B 给 5、12、21、32,位置对位置各乘各的,排成两行两列。 A @ B 给 19、22、43、50,这是行乘列再相加。

两个结果完全不同。星号是逐元素,@ 是矩阵乘,没有默认哪个更常用。 np.dot(A, B) 和 A @ B 一样,@ 是后来加的短写法,建议用 @。

二、矩阵乘到底怎么算

A 的第 1 行是 [1, 2],B 的第 1 列是 [5, 7]。 1 乘 5 加 2 乘 7 等于 19,这就是结果的第一行第一列。 A 的第 1 行乘 B 的第 2 列 [6, 8],1 乘 6 加 2 乘 8 等于 22。

行乘列、逐个乘、再加起来。八个字把矩阵乘说完了。 形状规则:(m, n) 乘 (n, p) 得 (m, p)。中间那个 n 是「对消」的那一维。 两边对不上就报错。所以做矩阵乘之前先打 shape,比 debug 半天快。

三、转置、行列式、逆

转置把行变列、列变行,二维矩阵 .T 一下就翻转。 行列式 np.linalg.det(A) 这次给 -2.0。它是一个数,衡量这个矩阵「缩放了多大面积」。 逆矩阵 np.linalg.inv(A) 给 -2.0、1.0、1.5、-0.5,同样是两行两列。

验算:A 乘它的逆,得到单位矩阵,对角 1 其余 0。这就是逆的定义。 行列式为 0 就没有逆矩阵,inv 会抛 LinAlgError。 行列式 0 的意思是「这个变换把平面压成了一条线」,压扁了就没法还原。

四、解线性方程组

方程组 1x 加 2y 等于 5,3x 加 4y 等于 11。 系数写成矩阵 A,右边写成向量 b,np.linalg.solve(A, b) 直接给解。 这次解是 x 等于 1.00,y 等于 2.00。验算 A 乘 x 得 [5, 11],对上了。

解方程用 solve,不要先求逆再乘。solve 更快,数值上也更稳。 先求逆再乘相当于多做一次不必要的近似,误差会被放大。

五、矩阵的常用量

要什么 写法 这次的值
形状 A.shape (2, 3)
元素个数 A.size 6
维度 A.ndim 2
转置后的形状 A.T.shape (3, 2)
所有元素和 A.sum() 21
按列求和 A.sum(axis=0) [5, 7, 9]

axis=0 是压掉行,也就是顺着行往下走,每列得一个数。 这个方向最容易记反。别记「横还是竖」,记「压掉哪一维」,一次就记住了。

六、动手 Demo:矩阵的基本操作

先并排打 A 乘 B 和 A 乘 B 的矩阵乘,看两个结果差多远。 然后转置、行列式、逆、解方程,最后把常用量列一遍。

import numpy as np

A = np.array([[1, 2], [3, 4]])
B = np.array([[5, 6], [7, 8]])
print("A * B 逐元素乘:")
print(A * B)
print("A @ B 矩阵乘:")
print(A @ B)

print("1 乘 5 加 2 乘 7 = %d" % (1 * 5 + 2 * 7))
print("1 乘 6 加 2 乘 8 = %d" % (1 * 6 + 2 * 8))

F = np.array([[1.0, 2.0], [3.0, 4.0]])
print("转置:")
print(F.T)
print("行列式 %.1f" % np.linalg.det(F))
print("逆矩阵:")
print(np.round(np.linalg.inv(F), 2))
print("A 乘逆矩阵:")
print(np.round(F @ np.linalg.inv(F), 10))

b = np.array([5.0, 11.0])
x = np.linalg.solve(F, b)
print("方程解:x = %.2f,y = %.2f" % (x[0], x[1]))
print("验算:%s" % np.round(F @ x, 10))

T = np.arange(1, 7).reshape(2, 3)
print("形状 %s,元素 %d,维度 %d" % (T.shape, T.size, T.ndim))
print("转置形状 %s" % (T.T.shape,))
print("全部和 %d,按列 %s" % (T.sum(), T.sum(axis=0).tolist()))

实跑输出:

--------------------------------------------------------------------
矩阵的基本操作
--------------------------------------------------------------------

  ① 三种乘法,结果完全不同
  --------------------------------------------------------------------
  A * B 是逐元素乘:
  [5, 12]
  [21, 32]
  A @ B 是矩阵乘(行乘列再相加):
  [19, 22]
  [43, 50]
  星号逐元素、@ 是矩阵乘。这两个搞混,结果是完全不同的两个矩阵。
  ! np.dot(A, B) 和 A @ B 一样。@ 是更短的新写法,建议用 @。

  ② 矩阵乘怎么算的
  --------------------------------------------------------------------
  A 的第 1 行 [1, 2] 乘 B 的第 1 列 [5, 7]:1×5 + 2×7 = 19。
  A 的第 1 行乘 B 的第 2 列 [6, 8]:1×6 + 2×8 = 22。
  所以结果的第一行是 [19, 22]。行乘列、逐个乘、再加起来。
  ! 形状规则:(m, n) 乘 (n, p) 得 (m, p)。中间那个 n 必须相等,否则报错。

  ③ 转置、逆、行列式
  --------------------------------------------------------------------
  转置:
  [1.0, 3.0]
  [2.0, 4.0]
  行列式 -2.0
  逆矩阵:
  [-2.0, 1.0]
  [1.5, -0.5]
  验算 A 乘 A 的逆,应该是单位矩阵:
  [1.0, 0.0]
  [0.0, 1.0]
  ! 行列式为 0 就没有逆矩阵,inv 会报 LinAlgError。这是「方程无唯一解」的信号。

  ④ 解线性方程组
  --------------------------------------------------------------------
  方程组 1x + 2y = 5,3x + 4y = 11。
  solve 给解:x = 1.00,y = 2.00。
  验算 A @ x:[ 5. 11.],等于右边的 [5, 11]。
  ! 解方程用 solve,不要先求逆再乘。solve 更快也更准。

  ⑤ 矩阵的常用量
  --------------------------------------------------------------------

| 要什么 | 写法 | 这次的值 |
|---|---|---|
| 形状 | A.shape | (2, 3) |
| 元素个数 | A.size | 6 |
| 维度 | A.ndim | 2 |
| 转置 | A.T | (3, 2) |
| 所有元素和 | A.sum() | 21 |
| 按列求和 | A.sum(axis=0) | [5, 7, 9] |

  axis=0 是顺着行走(压掉行),得到每列一个数。这个方向最容易记反。

np.round(..., 10) 是为了把浮点误差抹掉:逆矩阵理论上是整数,算出来可能带 -1e-17。 验算那一步别省。A 乘逆矩阵得到单位矩阵,才说明逆求对了。 行列式 -2.0 是负数,说明这个变换把平面翻了个面,跟「有没有逆」无关。

30 秒实验

  1. 把 A @ B 换成 A * B,再把结果拿去解方程,看会不会报错。
  2. 造一个第一行是 1 和 2、第二行是 2 和 4 的矩阵,求它的行列式,看是不是 0,再试 inv。
  3. 想一想:为什么 solve 比「先求逆再乘」更好?多做一步会多出什么误差?

常见误区

  1. 以为 * 是矩阵乘:它是逐元素乘,矩阵乘要写 @。
  2. 矩阵乘两边形状不对就去调参数:先打 shape,(2,3) 和 (2,3) 是乘不了的。
  3. 行列式 0 还去求逆:抛 LinAlgError,因为方程没有唯一解。
  4. 记「axis=0 是竖着算」:记「压掉行」,方向自然就出来了。
自测三问(点开对答案) 1. `A * B` 和 `A @ B` 有什么区别? —— 前者逐元素相乘,形状不变;后者行乘列,形状按 (m,n) 乘 (n,p) 得 (m,p)。 2. (3, 4) 的矩阵能乘 (2, 5) 的矩阵吗? —— 不能。中间要相等,也就是第一个的列数 4 得等于第二个的行数 2,这里对不上。 3. 行列式为 0 意味着什么? —— 矩阵不可逆,对应的线性方程组没有唯一解,inv 会报 LinAlgError。

矩阵会算了,最后一篇收个尾:统计函数,还有两个一次算完多列的案例。

相关笔记