《线性代数》3. 矩阵,线性代数中最重要的概念

什么是矩阵

前面我们介绍了向量,它是线性代数中最基本的元素,但提到线性代数,估计更多人第一时间想到的是矩阵(Matrix)。

\(\begin{Bmatrix} 1 & 2 & 3 & 4 \\ 5 & 6 & 7 & 8 \\ 9 & 10 & 11 & 12 \end{Bmatrix}\)

如果说向量是对数的拓展,一个向量表示一组数,那么矩阵就是对向量的拓展,一个矩阵表示一组向量。比如上面那个矩阵,如果横着看,那么它由三个向量组成(每个向量维度为 4);如果竖着看,那么它由四个向量组成(每个向量维度为 3)。

而在描述一个矩阵的时候,我们会用几行几列来描述,比如上面的矩阵就是一个 3 x 4 的矩阵(有 3 行 4 列)。如果一个矩阵的行数和列数相等,那么我们也称这样的矩阵为方阵。

\(A = \begin{Bmatrix} a_{11} & a_{12} & a_{13} & a_{14} \\ a_{21} & a_{22} & a_{23} & a_{24} \\ a_{31} & a_{32} & a_{33} & a_{34} \end{Bmatrix}\)

另外在数学体系中,我们使用大写符号来代表矩阵,对应的小写符号代表矩阵的元素。而且每个小写符号都有一个下标,比如 \(a_{mn}\),表示第 \(m\) 行、第 \(n\) 列。

了解完什么是矩阵之后,我们通过 Python 来实现一个矩阵,在计算机中,矩阵可以看成一个二维数组。

矩阵的基本运算

回忆一下向量的基本运算,分别是向量的加法:\(\vec{u} + \vec{w}\),向量的数量乘:\(k * \vec{u}\)。那么矩阵也是同理,矩阵也有加法:\(A + B\),和乘法 \(k * A\)

假设有两个矩阵 A 和 B,它们的值分别如下:

\(A = \begin{Bmatrix} a_{11} & a_{12} & a_{13} & ··· & a_{1n} \\ a_{21} & a_{22} & a_{23} & ··· & a_{2n} \\ a_{31} & a_{32} & a_{33} & ··· & a_{3n} \\ ··· & ··· & ··· & ··· & ··· \\ a_{m1} & a_{m2} & a_{m3} & ··· & a_{mn} \end{Bmatrix}\)\(B = \begin{Bmatrix} b_{11} & b_{12} & b_{13} & ··· & b_{1n} \\ b_{21} & b_{22} & b_{23} & ··· & b_{2n} \\ b_{31} & b_{32} & b_{33} & ··· & b_{3n} \\ ··· & ··· & ··· & ··· & ··· \\ b_{m1} & b_{m2} & b_{m3} & ··· & b_{mn} \end{Bmatrix}\)

那么 \(A + B\)\(k · A\) 的结果就为:

\(A + B = \begin{Bmatrix} a_{11} + b_{11} & a_{12} + b_{12} & a_{13} + b_{13} & ··· & a_{1n} + b_{1n} \\ a_{21} + b_{21} & a_{22} + b_{22} & a_{23} + b_{23} & ··· & a_{2n} + b_{2n} \\ a_{31} + b_{31} & a_{32} + b_{32} & a_{33} + b_{33} & ··· & a_{3n} + b_{3n} \\ ··· & ··· & ··· & ··· & ··· \\ a_{m1} + b_{m1} & a_{m2} + b_{m2} & a_{m3} + b_{m3} & ··· & a_{mn} + b_{mn} \end{Bmatrix}\)\(k · A = \begin{Bmatrix} k · a_{11} & k · a_{12} & k · a_{13} & ··· & k · a_{1n} \\ k · a_{21} & k · a_{22} & k · a_{23} & ··· & k · a_{2n} \\ k · a_{31} & k · a_{32} & k · a_{33} & ··· & k · a_{3n} \\ ··· & ··· & ··· & ··· & ··· \\ k · a_{m1} & k · a_{m2} & k · a_{m3} & ··· & k · a_{mn} \end{Bmatrix}\)

以上就是矩阵的基本运算,然后我们在此基础上再给出几个运算性质:

  • 矩阵满足加法交换律:\(A + B = B + A\)
  • 矩阵满足加法结合律律:\((A + B) + C = A + (B + C)\)
  • 和零矩阵相加,结果还是原矩阵:\(A + O = A\)
  • 和对应的负矩阵相加,结果是零矩阵:\(A + -A = O\)\(-A\) 是唯一的,并且等于 \(-1 * A\)
  • 数量乘也遵循结合律:\((c·k)·A = c·(k·A)\)
  • 分配率:\(k · (A + B) = k · A + k · B\),同理还有 \((c + k ) · A = c · A + k · A\)

矩阵和向量的乘法

假设有一个方程组:

\(\begin{cases} 3x + 2y + 4z + 10m = 12 \\ 6y + 4z + 8m = 17 \\ 4x + 10y + z + 2m = 9 \\ y + 5z + 6m = 11 \end{cases}\)

该方程组是我随便写的,很明显我们可以将未知数的系数提取成一个矩阵,至于未知数和等号右边的值则可以是一个列向量:

\(\begin{Bmatrix} 3 & 2 & 4 & 10 \\ 0 & 6 & 4 & 8 \\ 4 & 10 & 1 & 2 \\ 0 & 1 & 5 & 6 \end{Bmatrix} · \begin{pmatrix} x \\ y \\ z \\ m \end{pmatrix} = \begin{pmatrix} 12 \\ 17 \\ 9 \\ 11 \end{pmatrix}\)

如果让矩阵的每一行(行向量)都和未知数组成的向量做点乘,那么结果不就是上面的方程组吗。

因此任意一个方程组,我们都可以抽象成 \(A · \vec{x} = \vec{b}\) 的形式。而矩阵和一个向量相乘,等于矩阵的每一行都和向量做点乘,就像下面这样:

\(\begin{Bmatrix} a_{11} & a_{12} & a_{13} & ··· & a_{1n} \\ a_{21} & a_{22} & a_{23} & ··· & a_{2n} \\ a_{31} & a_{32} & a_{33} & ··· & a_{3n} \\ ··· & ··· & ··· & ··· & ··· \\ a_{m1} & a_{m2} & a_{m3} & ··· & a_{mn} \end{Bmatrix} · \begin{pmatrix} u_{1} \\ u_{2} \\ u_{3} \\ ··· \\ u_{n} \end{pmatrix} = \begin{pmatrix} a_{11}u_{1} + a_{12}u_{2} + a_{13}u_{3} + ··· + a_{1n}u_{n} \\ a_{21}u_{1} + a_{22}u_{2} + a_{23}u_{3} + ··· + a_{2n}u_{n} \\ a_{31}u_{1} + a_{32}u_{2} + a_{33}u_{3} + ··· + a_{3n}u_{n} \\ ··· \\ a_{m1}u_{1} + a_{m2}u_{2} + a_{m3}u_{3} + ··· + a_{mn}u_{n}\end{pmatrix}\)

所以矩阵如果要和向量相乘,那么矩阵的列数必须和向量的元素个数相同,这样矩阵的每一行才能和向量做点乘。至于矩阵有多少行则无所谓,并且矩阵的行数就是结果向量的元素个数。另外矩阵实际上是多个向量的组合,如果矩阵只有一行,那么它也可以看作是一个向量,此时再和向量点乘,得到的就是一个标量。

最后我们再来看一下 \(T · \vec{a} = \vec{b}\),这个式子可以解读为将向量 \(\vec{a}\) 转换成了向量 \(\vec{b}\),而转换的方式则由矩阵 \(T\) 来定义。从这个角度来理解,可以把矩阵 \(T\) 看成是向量的函数。

矩阵和矩阵的乘法

矩阵相乘在图形学上非常有用,比如有一个点 \(\begin{pmatrix}x \\ y \end{pmatrix}\),我们希望它的横坐标放大 \(1.5\) 倍,纵坐标放大 \(2\) 倍,那么怎么做呢?显然要找到一个矩阵 \(T\),使得:

\(T · \begin{pmatrix}x \\ y \end{pmatrix} = \begin{pmatrix}1.5x \\ 2y \end{pmatrix}\)

首先和 \(T\) 相乘的向量的长度是 \(2\),所以 \(T\) 的列数必须是 \(2\),否则两者无法相乘。然后结果向量的长度是 \(2\),因此 \(T\) 的行数也必须是 \(2\),所以 \(T\) 是一个 \(2 × 2\) 的矩阵。

\(\begin{Bmatrix} a & b \\ c & d \end{Bmatrix} · \begin{pmatrix}x \\ y \end{pmatrix} = \begin{Bmatrix} a·x + b·y \\ c·x + d·y \end{Bmatrix} = \begin{pmatrix}1.5x \\ 2y \end{pmatrix}\)

根据式子,不难得出:

\(T = \begin{Bmatrix} 1.5 & 0 \\ 0 & 2 \end{Bmatrix}\)

经过矩阵 \(T\),我们就可以将一个点(向量)进行映射,使得横坐标扩大 1.5 倍,纵坐标扩大 2 倍。但是问题来了,如果有很多的向量该怎么办?很简单, 将这些向量组合成一个矩阵即可。

\(\begin{Bmatrix} 1.5 & 0 \\ 0 & 2 \end{Bmatrix} · \begin{Bmatrix}x_{1} & x_{2} & x_{3} & x_{4} \\ y_{1} & y_{2} & y_{3} & y_{4}\end{Bmatrix} = \begin{Bmatrix} 1.5x_{1} & 1.5x_{2} & 1.5x_{3} & 1.5x_{4}\\ 2y_{1} & 2y_{2} & 2y_{3} & 2y_{4} \end{Bmatrix}\)

比如我们将 4 个点映射成矩阵,需要注意:这 4 个点是以列的形式组织的。所以从这个式子你也能看出矩阵乘法是怎么一回事了,假设有两个矩阵:\(A\)\(B\),如果 \(A\)\(B\) 能够相乘,那么必须满足矩阵 \(A\) 的列数等于矩阵 \(B\) 的行数,其它则没有要求。

回顾一下矩阵和向量相乘,需要满足矩阵的列数等于向量的长度,如果向量看成是只有一列的矩阵呢?所以结论是相似的。

然后让矩阵 \(A\) 的每一行去和矩阵 \(B\) 每一列相乘,得到的结果 \(C\) 就是一个行数和 \(A\) 保持一致、列数和 \(B\) 保持一致的矩阵。比如 \(A\)\(m\)\(k\) 列,\(B\)\(k\)\(n\) 列,那么矩阵 \(C\) 就是 \(m\)\(n\) 列。

\(A = \begin{Bmatrix}\vec{r_{1}} \\ \vec{r_{2}} \\ \vec{r_{3}} \\ ... \\ \vec{r_{m}} \end{Bmatrix}\)\(B = \begin{Bmatrix} \vec{c_{1}} & \vec{c_{2}} & \vec{c_{3}} & ... & \vec{c_{n}} \end{Bmatrix}\)

为了描述方便,我们用 \(\vec{r}\) 代表矩阵 \(A\) 的每一行,可以想象一行有很多元素,\(\vec{c}\) 代表矩阵 \(B\) 的每一列,可以想象一列有很多元素。但 \(A\)\(B\) 如果相乘,那必须满足 \(\vec{r}\) 和长度 \(\vec{c}\) 的长度是一致的,也就是 \(A\) 和列数要等于 \(B\) 的行数。

\(C = A · B = \begin{Bmatrix}\vec{r_{1}} · \vec{c_{1}} & \vec{r_{1}} · \vec{c_{2}} & \vec{r_{1}} · \vec{c_{3}} & ··· & \vec{r_{1}} · \vec{c_{n}}\\ \vec{r_{2}} · \vec{c_{1}} & \vec{r_{2}} · \vec{c_{2}} & \vec{r_{2}} · \vec{c_{3}} & ··· & \vec{r_{2}} · \vec{c_{n}}\\\vec{r_{3}} · \vec{c_{1}} & \vec{r_{3}} · \vec{c_{2}} & \vec{r_{3}} · \vec{c_{3}} & ··· & \vec{r_{3}} · \vec{c_{n}}\\ ··· & ··· & ··· & ··· & ···\\\vec{r_{m}} · \vec{c_{1}} & \vec{r_{m}} · \vec{c_{2}} & \vec{r_{m}} · \vec{c_{3}} & ··· & \vec{r_{m}} · \vec{c_{n}} \end{Bmatrix}\)

相乘的时候,让矩阵 \(A\) 的每一行去点乘矩阵 \(B\) 的每一列即可。并且 \(C\) 的行数等于 \(A\) 的行数,\(C\) 的列数等于 \(B\) 的列数,所以 \(C_{ij}\) 就是 \(A\) 的第 \(i\) 行和 \(B\) 的第 \(j\) 列点乘的结果。

矩阵乘法不遵循交换律。

矩阵乘法的性质

我们说矩阵乘法不遵循交换律,即 \(A · B ≠ B · A\),而且 \(A\)\(B\) 要能相乘,必须满足 \(A\) 的列数等于 \(B\) 的行数,但 \(A\) 的行数和 \(B\) 的列数之间的关系则没有要求,这就使得 \(B · A\) 可能压根就不合法。如果 \(A ·B\) 和 $B · A $ 都合法,那么要求 \(A\)\(B\) 必须都是方阵,但即便如此,它们的结果大部分情况也是不一样的。

那么矩阵乘法都遵循哪些性质呢?

  • \((A · B) · C = A · (B · C)\)
  • \(A · (B + C) = A · B + A · C\)
  • \((B + C) · A = B · A + C · A\)

注意:第二条和第三条本身是没有问题的,但它们两者是没有关系的。

然后对于任意的 \(r × c\) 的矩阵 \(A\),存在 \(c × m\) 的零矩阵 \(O\),使得 \(A · O_{cm} = O_{rm}\),这个很简单,任何矩阵和零矩阵相乘,结果还是零矩阵,只是维度的变了。

同理对于任意的 \(r × c\) 的矩阵 \(A\),存在 \(m × r\) 的零矩阵 \(O\),使得 \(O_{mr} · A = O_{mc}\)

然后矩阵也可以进行幂运算,比如 \(A^{k}\) 就是 \(k\)\(A\) 相乘,但很明显,如果矩阵想要幂运算,那么矩阵必须是方阵。

最后再补充一点:\((A + B)^{2} = A^{2} + A·B + B·A + B^{2}\),但不等于 \(A^{2} + 2AB + B^{2}\)

矩阵的转置

回顾一下在介绍矩阵乘法时,举的一个例子,我们假设有 4 个点。

\(\begin{Bmatrix}x_{1} & x_{2} & x_{3} & x_{4} \\ y_{1} & y_{2} & y_{3} & y_{4}\end{Bmatrix}\)

但这种排列方式多少有点别扭,我们更习惯这么排列:

\(\begin{Bmatrix}x_{1} & y_{1}\\x_{2} & y_{2}\\x_{3} & y_{3}\\x_{4} & y_{4}\end{Bmatrix}\)

但做乘法的时候,第二种矩阵是不能直接乘的,我们需要变成第一种矩阵的形式。而第二种矩阵只要将它的每一行单独的拿出来,并以列的方式进行组合,就得到了第一种矩阵,这个过程就叫做矩阵的转置。

矩阵 \(A\) 转置之后的矩阵,我们一般叫做 \(A^{T}\),如果 \(A\)\(m · n\) 矩阵,那么 \(A^{T}\) 就是 \(n · m\) 矩阵。

  • 其中 \(A\) 的第 \(i\) 行变成了 \(A^{T}\) 的第 \(i\)
  • \(A_{ij} = A^{T}_{ji}\)

然后是之前提到的向量,如果不做特殊说明,那么向量指的是列向量。但书籍是横版印刷,所以为了表示这是个列向量,于是便使用行向量加转置的方式,比如 \((3, 5)^{T}\)

了解完矩阵的转置之后,再来看看它有哪些性质。

  • \((A^{T})^{T} = A\)
  • \((A + B)^{T} = A ^{T} + B^{T}\)
  • \((k · A)^{T} = k · A^{T}\)
  • \((A · B)^{T} = B^{T} · A^{T}\)

有兴趣可以自己证明一下。

Numpy 中的矩阵

然后看一下如何在 Numpy 中操作矩阵。

import numpy as np

# 矩阵依旧可以通过数组来实现,只不过是二维数组
mat = np.array(
    [
        [1, 2, 3, 4],
        [5, 6, 7, 8],
        [9, 10, 11, 12]
    ]
)
print(mat)
"""
[[ 1  2  3  4]
 [ 5  6  7  8]
 [ 9 10 11 12]]
"""
# 3 行 4 列
print(mat.shape)
"""
(3, 4)
"""
# 元素个数
print(mat.size)
"""
12
"""
# 获取转置矩阵
print(mat.T)
"""
[[ 1  5  9]
 [ 2  6 10]
 [ 3  7 11]
 [ 4  8 12]]
"""
# 获取第 2 行第 3 列的元素
print(mat[1, 2])
"""
7
"""
# 也可以对矩阵进行截取,截取矩阵的第一行、第二行和第二列、第三列
print(mat[0: 2, 1: 3])
"""
[[2 3]
 [6 7]]
"""
# 获取矩阵的某一行,比如获取第 2 行,至于列则全部截取
# 也可以写成 mat[1],列默认全部截取
print(mat[1, :])
"""
[5 6 7 8]
"""
# 获取矩阵的某一列,比如获取第 3 列,此时必须写成 mat[:, 2]
print(mat[:, 2])
"""
[ 3  7 11]
"""

比较简单,再来看看矩阵的运算。

import numpy as np

# 矩阵依旧可以通过数组来实现,只不过是二维数组
mat1 = np.array(
    [[1, 2, 3], [4, 5, 6]]
)
mat2 = np.array(
    [[11, 22, 33], [44, 55, 66]]
)
# 矩阵相加
print(mat1 + mat2)
"""
[[12 24 36]
 [48 60 72]]
"""
# 矩阵相减
print(mat1 - mat2)
"""
[[-10 -20 -30]
 [-40 -50 -60]]
"""
# 和标量相乘
print(mat1 * 3)
"""
[[ 3  6  9]
 [12 15 18]]
"""
# 矩阵的相乘,如果直接用 * 号,那么表示对应的元素相乘
# 因为这里创建的其实是数组,本质上是用数组来模拟矩阵
# 如果希望实现数学上的矩阵相乘,那么应该使用 @
print(mat1 * mat2)
"""
[[ 11  44  99]
 [176 275 396]]
"""
try:
    mat1 @ mat2
except ValueError as e:
    print(e)
"""
matmul: Input operand 1 has a mismatch in its core dimension 0, 
        with gufunc signature (n?,k),(k,m?)->(n?,m?) (size 2 is different from 3)
"""
# 我们看到矩阵的相乘报错了,因为 mat1 的列数和 mat2 的行数不相等
# 提示给的也很明显:不满足 (n?,k),(k,m?)->(n?,m?)

# 我们将 mat2 转置一下即可,但 mat1 * mat2.T 是不对的,因为形状不一样,而 * 是对应元素依次相乘
print(mat1 @ mat2.T)
"""
[[154 352]
 [352 847]]
"""

# 矩阵和向量相乘
vec1 = np.array([1, 2, 3])  # 行向量
vec2 = np.array([[1], [2], [3]])  # 列向量
print(mat1 @ vec1)
"""
[14 32]
"""
print(mat1 @ vec2)
"""
[[14]
 [32]]
"""

非常简单,以上就是矩阵相关的操作。

posted @ 2023-08-28 17:55  古明地盆  阅读(81)  评论(0编辑  收藏  举报