14-矩阵相乘及其运算法则

矩阵与向量的乘法

在这一篇文章中我们就将基于上一篇重新审视矩阵的这个视点来理解矩阵的乘法,那么在这一篇,我们主要来看一下矩阵和向量的乘法。这里这个线性方程组是上一小节给大家举的模拟的一个非常简单的小型经济系统的例子,我们可以把这个经济系统其实本质就是一个线性方程组,这个线性方程组的左侧表示成这样的一个矩阵 ( 1 − 0.2 0.1 0.5 − 0.5 − 1 0.2 0.1 0 − 0.4 − 1 0.3 − 0.2 0 0 1 ) ( x i t x e x m x h ) = ( 100 50 20 666 ) \begin{array}{l}\left(\begin{array}{cccc}1 & -0.2 & 0.1 & 0.5 \\-0.5 & -1 & 0.2 & 0.1 \\0 & -0.4 & -1 & 0.3 \\-0.2 & 0 & 0 & 1\end{array}\right)\left(\begin{array}{c}x_{i t} \\x_{e} \\x_{m} \\x_{h}\end{array}\right)=\left(\begin{array}{c}100 \\50 \\20 \\666\end{array}\right)\\ \end{array} 10.500.20.210.400.10.2100.50.10.31 xitxexmxh = 1005020666

可以理解为 A ⋅ x ⃗ = b ⃗ A \cdot \vec{x}=\vec{b} Ax =b

  • 有的时候,我们也管它叫做系数矩阵,因为这个矩阵中的所有的元素都表示的是某一个未知数所对应的系数,而在这里,最右侧的列向量则是这个线性方程组中每一个方程等号右侧对应的值组成的列向量, 那么大家应该就可以看出来了,对于这个线性方程组来说,除了这个系数矩阵以及每一个方程等号右侧的元素组成的这个列向量之外, 还缺少一部分,那么缺少的这部分就是我们要表达这个线性方程组中的每一个未知数,那么在这里对于我们的这个线性方程组一共有四个未知数,那么很显然,这四个未知数也就排成了一个向量的形式。
  • 在这里,我们等号右侧的这些数值都排成了列向量,为了方便起见,也是为了统一起见, 我们的所有这些未知数也就排成了一个列向量。实际上,在我们的线性代数中,大多数情况当我们提到向量的时候,就都是这样使用列向量的表达方式。大家可以看出来,这种表达方式在这种线性系统中表示是非常方便的,那么这样一来,大家可以看有一个系数矩阵,有一个表达未知数的列向量,
  • 右侧还有一个表达方程等号右侧结果的列项量, 这个形式其实和我们学习的普通的数字的方程的形式是非常一致的,我们就把上面的这样的一个线性方程组使用矩阵的形式表达成是这个系数矩阵乘以这个表达未知数的列向量,就等于等号右侧的这些结果所组成的列向量。那么,通常如果我把这些矩阵和向量用符号来表示的话,我把这个矩阵当做是A这个表达未知数的向量给当做是 x ⃗ \vec{x} x ,注意它是一个向量,相应的,另外一个向量,管它叫做 b ⃗ \vec{b} b 的话,这个式子其实就是 A ∗ x ⃗ = b ⃗ A*\vec{x}=\vec{b} Ax =b ,这种表示方法,其实和我们在初中的代数中所学习的一个系数乘以一个未知数等于另外一个数字的表示方法本质是一样的,只不过现在在我们线性代数的世界中,A表示的是一个矩阵, x ⃗ , b ⃗ \vec{x}, \vec{b} x b 表示的是向量而已, 它们之间存在这样的一种对应关系,那么现在大家就知道了矩阵乘以这样的一个未知数的向量,等于这样的一个向量。

经济系统的等式左侧的矩阵乘以向量的部分和我们之前的那个线性方程组是等价的,我们的线性方程组其实就是四个方程,这四个方程右侧的结果已经被这个向量所表示了,那么左侧所对应的方程我们也可以把它这样的列成一个向量 ( 1 − 0.2 0.1 0.5 − 0.5 − 1 0.2 0.1 0 − 0.4 − 1 0.3 − 0.2 0 0 1 ) ( x i t x e x m x h ) = ( x i t − 0.2 x e + 0.1 x m + 0.5 x h − 0.5 x i t − x e + 0.2 x m + 0.1 x h − 0.4 x e − x m + 0.3 x h − 0.2 x i t + x h ) \left(\begin{array}{cccc}1 & -0.2 & 0.1 & 0.5 \\-0.5 & -1 & 0.2 & 0.1 \\0 & -0.4 & -1 & 0.3 \\-0.2 & 0 & 0 & 1\end{array}\right)\left(\begin{array}{c}x_{i t} \\x_{e} \\x_{m} \\x_{h}\end{array}\right)=\left(\begin{array}{c}x_{i t}-0.2 x_{e}+0.1 x_{m}+0.5 x_{h} \\-0.5 x_{i t}-x_{e}+0.2 x_{m}+0.1 x_{h} \\-0.4 x_{e}-x_{m}+0.3 x_{h} \\-0.2 x_{i t}+x_{h}\end{array}\right) 10.500.20.210.400.10.2100.50.10.31 xitxexmxh = xit0.2xe+0.1xm+0.5xh0.5xitxe+0.2xm+0.1xh0.4xexm+0.3xh0.2xit+xh 这本来就是我们那个方程组所表达的意思,只不过现在使用向量的形式来连接这个等号而已,而我们又定义了这个矩阵乘以包含所有未知数的向量得到的结果也等于右侧的这个向量,那么就说明这个矩阵乘以这个未知数的向量,它其实等于右侧这种形式。大家可以看这个形式相当于我们就得到了矩阵和向量到底是怎样相乘的, 这个等号的左边就是一个矩阵乘以一个向量,而这个等号的右边结果还是一个向量,这个向量中的元素结合了我们的这个常数矩阵和这个未知数的向量中的所有的元素,那么这里其实描述的就是矩阵和向量如何相乘的,大家可以简单的看一下这个乘法到底是怎样进行的,其实非常的简单,

因为对于右侧的这个向量中相应的每一个内容,其实我们已经都非常熟悉了, 就是我们原来线性方程组中每一个方程左侧的那部分相乘的结果,这里面每一个元素其实就是每一个未知数的系数再乘以这个未知数,然后全部加起来,而现在我们的这个等号的左侧矩阵就是每一个系数,而左侧的向量就是每一个未知数,那么相应的大家就可以想象,我们右侧中这第一个元素是怎么得到的呢?就是我们左侧的这个系数矩阵中的第一行元素和我们的这个未知数向量中的每一个元素对应的的元素相乘再相加

最终的结果就是我们右侧矩阵中的这个第一个元素,当然了,我在这里说的是相乘再相加,实际上我们可以把这个矩阵中的第一行的元素看成是一个行向量,而这个未知数向量是一个列向量,这个相乘再相加的过程。大家回忆一下,在上一章向大家介绍了这向量的乘法的定义。换句话说,我们右侧的这个结果向量的第一个元素,其实就是我们矩阵的第一个行向量和未知数组成的列向量进行点乘的结果。这个规律同理大家可以看到,

我们拿出这个矩阵的第二行,这个行向量和我们的这个包含未知数的列向量进行点乘, 点乘的结果就是我们的这个结果向量中的第二个元素,以此类推,我们拿出这个矩阵中的第三行的行向量和包含未知数的这个列向量进行点乘, 点乘的结果就是我们的结果矩阵中第三个元素所对应的值, 在这里,大家注意我们的这个矩阵中的第三行第一个元素,这个系数是零,那么零乘以这个 x i t x_{it} xit就等于零,所以对应的在我们的这个结果向量中第三个元素中并不包含 x i t x_{it} xit这一项,我们继续来看,最后矩阵中拿出第四行的行向量和我们的这个未知数向量进行点乘,结果就是我们的结果向量中的第四个元素,那么对于我们矩阵中第四个行向量,第2、3个元素都为零,对应的就是 x e x_{e} xe x m x_{m} xm和它们相乘的那个系数为零,所以大家可以看在我们的这个结果向量中第四个元素。并不包含 x e x_{e} xe x m x_{m} xm这两项,那么这就是矩阵和向量相乘的法则

用一张图来说明运算规则
14-矩阵相乘及其运算法则_第1张图片

经济系统中的矩阵乘以一个向量其实就和原来的方程组等价
( 1 − 0.2 0.1 0.5 − 0.5 − 1 0.2 0.1 0 − 0.4 − 1 0.3 − 0.2 0 0 1 ) ( x i t x e x m x h ) = ( x i t − 0.2 x e + 0.1 x m + 0.5 x h − 0.5 x i t − x e + 0.2 x m + 0.1 x h − 0.4 x e − x m + 0.3 x h − 0.2 x i t + x h ) \left(\begin{array}{cccc}1 & -0.2 & 0.1 & 0.5 \\-0.5 & -1 & 0.2 & 0.1 \\0 & -0.4 & -1 & 0.3 \\-0.2 & 0 & 0 & 1\end{array}\right)\left(\begin{array}{c}x_{i t} \\x_{e} \\x_{m} \\x_{h}\end{array}\right)=\left(\begin{array}{c}x_{i t}-0.2 x_{e}+0.1 x_{m}+0.5 x_{h} \\-0.5 x_{i t}-x_{e}+0.2 x_{m}+0.1 x_{h} \\-0.4 x_{e}-x_{m}+0.3 x_{h} \\-0.2 x_{i t}+x_{h}\end{array}\right) 10.500.20.210.400.10.2100.50.10.31 xitxexmxh = xit0.2xe+0.1xm+0.5xh0.5xitxe+0.2xm+0.1xh0.4xexm+0.3xh0.2xit+xh

所以矩阵和向量相乘的通式就是如此:
( a 11 a 12 ⋯ a 1 n a 21 a 12 ⋯ a 2 n … … … a m 1 a 12 ⋯ a m n ) ∗ ( u 1 u 2 … u n ) = ( a 11 u 1 + a 12 u 2 + ⋯ + a 1 n u n a 21 u 1 + a 21 u 2 + ⋯ + a 2 n u n … a m 1 u 1 + a m 2 u 2 + ⋯ + a m n u n ) \begin{pmatrix} a_{11}& a_{12} & \cdots & a_{1n}\\ a_{21}& a_{12} & \cdots & a_{2n}\\ \dots& \dots & & \dots \\ a_{m1}& a_{12} & \cdots & a_{mn}\end{pmatrix} * \begin{pmatrix} u_{1}\\ u_{2}\\ \dots\\ u_{n}\end{pmatrix} = \begin{pmatrix} a_{11}u_{1} + a_{12}u_{2} + \cdots + a_{1n}u_{n}\\ a_{21}u_{1} + a_{21}u_{2} + \cdots + a_{2n}u_{n}\\ \dots \\ a_{m1}u_{1} + a_{m2}u_{2} + \cdots + a_{mn}u_{n}\end{pmatrix} a11a21am1a12a12a12a1na2namn u1u2un = a11u1+a12u2++a1nuna21u1+a21u2++a2nunam1u1+am2u2++amnun

注意, 这里说的是矩阵和向量相乘, 不是向量和矩阵相乘, 它们不满足交换律

  • 矩阵A的列数必须和向量u的元素个数(行数)一致!
  • 矩阵A的行数没有限制

在矩阵乘以向量时, 矩阵相当由一行行的行向量组成的, 所以我们也可以把表达式写成这种形式, 这里使用的是行视角
( − − r ⃗ 1 − − − − r ⃗ 2 − − … − − r m ⃗ − − ) ⋅ u ⃗ = ( r 1 → ⋅ u ⃗ r 2 → ⋅ u ⃗ … r m → ⋅ u ⃗ ) \left(\begin{array}{c}--\vec{r}_{1}-- \\--\vec{r}_{2}-- \\\ldots \\--\vec{r_{m}}--\end{array}\right) \cdot \vec{u}=\left(\begin{array}{c}\overrightarrow{r_{1}} \cdot \vec{u} \\\overrightarrow{r_{2}} \cdot \vec{u} \\\ldots \\\overrightarrow{r_{m}} \cdot \vec{u}\end{array}\right) r 1r 2rm u = r1 u r2 u rm u

当矩阵的行数为1的话, 此时矩阵就相当于变为了一个向量, 此时向量的点乘就符合矩阵相乘的法则的, 换句话说,矩阵乘以这个列向量,我们每次从这个矩阵中拿出一个行向量和这个列向量进行点乘,就是对应元素相乘再相加, 最终得到的结果就是得到的结果向量中的对应元素,但是现在我们只能取出唯一的一个行向量,那么结果向量元素中相应的也就有唯一的一个元素。那么用这种方式,我们就可以又换了一个角度来理解向量的点乘, 向量的点乘可以理解成是一个只有一行的矩阵和一个向量相乘的结果。最终得到的结果,我们依然是可以推出来两个向量点乘的结果就等于各个分量相乘再相加。

矩阵在图形变换中的应用及矩阵相乘

在上一章中,我们介绍了矩阵和向量的乘法定义,这一章我们进一步拓展来看一下矩阵和矩阵的乘法是怎样定义的, 在上一篇的最后,我们看到了对于矩阵和向量的乘法,我们有一个非常重要的看待它的视角——把矩阵看作是关于向量的一个函数,对于任意一个m×n的矩阵。如果我有一个n个元素的向量,这个矩阵都能把这个向量通过这种乘法的方式转换成一个有m个元素的向量。

使用矩阵做这种向量的转换,那么最典型的应用是在图形学中。在之前向大家介绍矩阵的数量乘的时,曾经举了一个例子, 那么,对于一个矩阵,我管它叫做P,那么这个矩阵中每一行如果都代表二维平面中的一个点的话,我对这个P矩阵进行一个数量乘乘以2得到的结果其实就相当于是对其中的每一个点的x轴坐标和y轴坐标都扩大了两倍,最终我们把这些点绘制到二维坐标系中,如果我们的这些点表示的是一个三角形的话那么可视化出的效果就是这个样子

14-矩阵相乘及其运算法则_第2张图片

我们的三角形扩大了两倍,但如果我并不是对于这三角形中的每一个顶点的横纵坐标都扩大两倍,而是横坐标扩大1.5倍,纵坐标扩大2倍的话,那么对于这个问题,是典型的需要把一个向量转变成另外的一个向量,相应的这样的一个需求相当于对于每一个点坐标,

14-矩阵相乘及其运算法则_第3张图片

我把它写成这样的列向量x、y的话,需要通过一种转换,把它转换成是1.5x和2y这样的一个坐标,那么此时矩阵本身可以作为向量的一个函数相应的这个用途就体现了出来,相当于我要找到一个函数,对于这个函数来说,它传进去x、y输出的是1.5x和2y,由于对于我们的向量来说,矩阵就是向量的函数, 所以我们是要找到一个变化矩阵T,满足 T ⋅ ( x y ) = ( 1.5 x 2 y ) T\cdot \begin{pmatrix} x\\y\end{pmatrix} = \begin{pmatrix}1.5x\\2y\end{pmatrix} T(xy)=(1.5x2y)那么对于T它的形状是怎样的呢?首先对于我们的这个T,它要可以和x、y这样一个有两个元素的向量相乘,那么这个T的列数一定是2,这是我们的矩阵和向量能够进行合法的乘法所需要的,其次,由于我们得到的这个结果向量,它有两个维度,所以对于矩阵T来说,它的行数也应该为2

经过分析,我们要找到的矩阵T是一个2×2的矩阵,不妨把这四个元素设置成是abcd, 然后我们就得到了下图中的式子
14-矩阵相乘及其运算法则_第4张图片

所以最终我们的这个矩阵T就已经求出来了,通过这样的一个简单的分析,我们就明白了,如果要满足我们的这个要求,只需要对我们的向量经过T这个矩阵的变换。这里所谓的变换,其实就是使用T矩阵乘以我们的任意一个点的坐标所表示出的。

现在问题来了,在这个图形中是有很多点的,如果我把每一个点都画成一个向量,用这个矩阵T和每一个点去进行这个乘法操作的话,固然是可以的,不过有更简单的、更批量化的做法,我们只需要把所有的点坐标合在一起,集合成一个矩阵就可以了

14-矩阵相乘及其运算法则_第5张图片

接着我们进行矩阵和矩阵的乘法操作

14-矩阵相乘及其运算法则_第6张图片

在这里其实就相当于我们定义了两个矩阵进行运算,我们是基于矩阵和向量的乘法运算的定义,定义出了矩阵和矩阵的乘法运算的定义。

14-矩阵相乘及其运算法则_第7张图片

那么,这个定义方法就是一个矩阵a和一个矩阵b相乘,我们可以把b这个矩阵拆成一个一个的列向量,拆完后,让矩阵a分别去和矩阵b中的每一个列向量进行矩阵和向量的乘法,两个矩阵相乘后,最终得到的结果依然是一个矩阵,这个矩阵中的每一列是原先的矩阵a的行向量和矩阵b中相应的列向量相乘的结果,这就是矩阵和矩阵之间如何进行乘法运算,在这里大家一定也就看得出来, 由于我们的每一步操作都要让矩阵a和矩阵b中的列向量进行乘法操作,而矩阵和向量的乘法操作要求这个矩阵的列数和这个向量中的元素的数量必须一致

那么放到矩阵的环境中,就要求矩阵a的列数必须和矩阵b的行数一致, 大家想象一下,在这里,矩阵b的行数其实就是指我们从矩阵b中随便拿出一个列向量,这个列向量中的元素个数,这个元素个数必须和矩阵a的列数是一致的,才可以进行这种矩阵间的乘法运算,那么在这里再稍微形式化的表示一下矩阵a乘以矩阵b,那么矩阵和矩阵的乘法形式就是这样式的, 就相当于矩阵分别乘以B矩阵中的每个列向量
A ⋅ B = A ⋅ ( ∣ ∣ ∣ c 1 → c 2 → … c n ‾ ∣ ∣ ∣ ) = ( ∣ ∣ ∣ A ⋅ c 1 → A ⋅ c 2 → … A ⋅ c n → ∣ ∣ ∣ ) A \cdot B=A \cdot\left(\begin{array}{cccc}\mid & \mid & & \mid \\\overrightarrow{c_{1}} & \overrightarrow{c_{2}} & \ldots & \overline{c_{n}} \\\mid & \mid & & \mid\end{array}\right)=\left(\begin{array}{cccc}\mid & \mid & & \mid \\A \cdot \overrightarrow{c_{1}} & A \cdot \overrightarrow{c_{2}} & \ldots & A \cdot \overrightarrow{c_{n}} \\\mid & \mid & & \mid\end{array}\right) AB=A c1 c2 cn = Ac1 Ac2 Acn
再结合我们矩阵与向量的乘法所讲到的知识, 最终可以转化为这种形式(矩阵的乘法和一般的乘法是不太一样的 它是把第一个矩阵的每一行,和第二个矩阵的每一列拿过来做内积得到结果
( − − r ⃗ 1 − − − − r ⃗ 2 − − … − − r m ⃗ − − ) ⋅ ( ∣ ∣ ∣ c 1 → c 2 → … c n ‾ ∣ ∣ ∣ ) = ( r 1 → ⋅ c 1 → r 1 → ⋅ c 2 → … r 1 → ⋅ c n → r 2 → ⋅ c 1 → r 2 → ⋅ c 2 → … r 2 → ⋅ c n → … … … r m → ⋅ c 1 → r m → ⋅ c 2 → … r m → ⋅ c n → ) \left(\begin{array}{c}--\vec{r}_{1}-- \\--\vec{r}_{2}-- \\\ldots \\--\vec{r_{m}}--\end{array}\right)\cdot\left(\begin{array}{cccc}\mid & \mid & & \mid \\\overrightarrow{c_{1}} & \overrightarrow{c_{2}} & \ldots & \overline{c_{n}} \\\mid & \mid & & \mid\end{array}\right)=\left(\begin{array}{cccc}\overrightarrow{r_{1}} \cdot \overrightarrow{c_{1}} & \overrightarrow{r_{1}} \cdot \overrightarrow{c_{2}} & \ldots & \overrightarrow{r_{1}} \cdot \overrightarrow{c_{n}} \\\overrightarrow{r_{2}} \cdot \overrightarrow{c_{1}} & \overrightarrow{r_{2}} \cdot \overrightarrow{c_{2}} & \ldots & \overrightarrow{r_{2}} \cdot \overrightarrow{c_{n}} \\\ldots & \ldots & & \ldots \\\overrightarrow{r_{m}} \cdot \overrightarrow{c_{1}} & \overrightarrow{r_{m}} \cdot \overrightarrow{c_{2}} & \ldots & \overrightarrow{r_{m}} \cdot \overrightarrow{c_{n}}\end{array}\right) r 1r 2rm c1 c2 cn = r1 c1 r2 c1 rm c1 r1 c2 r2 c2 rm c2 r1 cn r2 cn rm cn

  • 注意:矩阵A的列数一定要和矩阵B的行数保持一致, 因为矩阵A乘以矩阵B就是矩阵A中的行向量转置为列向量后乘以矩阵B的列向量, 而两个向量做点乘, 它们的元素个数必须保持一致,最后得出的结果向量的行数就等于矩阵A的行数、列数就等于矩阵B的列数, 也就得出了这样一个概念——A是m*k的矩阵;B是k*n的矩阵,则结果矩阵为m*n的矩阵
  • 为什么k消失了?
    这是因为每一个r向量和每一个c向量,它们的元素个数都为k,那么这样的两个向量相乘之后,得到的结果是一个数,所以k就没有了,最终我们得到了m*n的矩阵.

矩阵A和矩阵B相乘时, 矩阵A的列数就必须和矩阵B的行数保持一致, 至于m、n无要求

最后我们回到矩阵的乘法中,我们得到了这么一个概念——A是m*k的矩阵;B是k*n的矩阵,则结果矩阵为m*n的矩阵, 进而我们揭示了矩阵的乘法必须要满足这样的条件才能相乘, 那么这就意味着矩阵的乘法是不遵守交换律的, 也就是a*b不一定等于b*a

这非常好理解,这是因为我们将两个矩阵交换了之后,很有可能就不能相乘了,比如说我a矩阵是一个3×2的矩阵, b矩阵是一个2×8的矩阵,那么a是可以乘以b的,但是2×8的矩阵是不能乘以一个3×2的矩阵的。所以连这个乘法本身都不合法了,更不要提它们的结果相等。

在这里可能有一些同志发现了并不是在所有的情况下b×a都是不合法的,那么比如说在两个方阵相乘的情况下a是2阶的方阵, 矩阵b也是2阶的方阵,那么a×b是合法的b×a也是合法的,不过在大多数情况下,我们这样做相乘的结果也会不一样。

14-矩阵相乘及其运算法则_第8张图片

m行的数组①的a行与k列的数组②的b列的向量的内积, 在m行k列的新数组中的元素位置为a行b列, 也就是(a-1, b-1)这个位置

满足分配律,结合律,不满足交换律
A + B + C = A + ( B + C ) A+B+C=A+(B+C) A+B+C=A+(B+C) 加法肯定是满足,重点看乘法

  • 首先乘法是满足结合律的 ( A B ) C = A ( B C ) (AB)C=A(BC) (AB)C=A(BC)
  • 满足分配律的,这里是左分配律,和右分配律 :
    • A ( B + C ) = A B + A C A(B+C)=AB+AC A(B+C)=AB+AC
    • ( B + C ) A = B A + C A (B+C)A=BA+CA (B+C)A=BA+CA ,
    • 因为矩阵相乘不满足交换律的, 所以 A ⋅ ( B + C ) ≠ ( B + C ) ⋅ A A\cdot(B+C)≠(B+C)\cdot A A(B+C)=(B+C)A
  • 特别强调的是矩阵是不满足交换律的,不一定相等,甚至 AB 的尺寸和 BA 的尺寸是不同的 A B ≠ B A AB≠BA AB=BA 还有一个特殊的转置的公式 ( A B ) T = B T A T (AB)^T = B^TA^T (AB)T=BTAT
  • 对任意 r ∗ c r*c rc的矩阵A,存在c*x的矩阵O,满足: A ⋅ O c x = O r x A\cdot O_{cx}=O_{rx} AOcx=Orx(下标表示零矩阵的形状)
  • 对任意 r ∗ c r*c rc的矩阵A,存在x*r的矩阵O,满足: O x r ⋅ A = O x c O_{xr}\cdot A=O_{xc} OxrA=Oxc(下标表示零矩阵的形状)

矩阵相乘有可能是很多同志学习数学计算,学习了那么多基本元素,所看到的第一个不遵守交换律的一个乘法运算,我们上学时所学的,无论是实数有理数、复数,或者是我们之前学习的向量之间的乘法,它们都是遵守交换律的,但是矩阵的乘法是不遵守的。这一点大家一定要了解好。

矩阵的幂运算

14-矩阵相乘及其运算法则_第9张图片

至于 A 0 、 A − 1 、 A − 2 A^0、A^{-1}、A^{-2} A0A1A2我们则会在后面矩阵的逆矩阵的讲解中详细展开说明

在这里我们拓展一条知识点:

  • ( A + B ) 2 ≠ A 2 + 2 A B + B 2 (A+B)^{2}≠A^2 + 2AB + B^2 (A+B)2=A2+2AB+B2 因为矩阵是不满足交换律的, 接下来我们证明一下这条结论
  • 因为矩阵是满足分配律的所以我们可以把等式左边拆成 ( A + B ) 2 = ( A + B ) ( A + B ) (A+B)^2=(A+B)(A+B) (A+B)2=(A+B)(A+B), 紧接着我们再用一次分配律得到 ( A + B ) ( A + B ) = A ⋅ ( A + B ) + B ⋅ ( A + B ) (A+B)(A+B)=A·(A+B)+B·(A+B) (A+B)(A+B)=A(A+B)+B(A+B), 然后再用一次分配律我们就拆成了 A ・ ( A + B ) + B ⋅ ( A + B ) = A ・ A + A ・ B + B ・ A + B ・ B A・(A+B)+B·(A+B)=A・A+A・B+B・A+B・B A(A+B)+B(A+B)=AA+AB+BA+BB, 根据矩阵的幂运算, A ∗ A = A 2 、 B ∗ B = B 2 A*A=A^2、B*B=B^2 AA=A2BB=B2, 我们就可以继续转化为这种形式: A ・ A + A ・ B + B ⋅ A + B ⋅ B = A 2 + A ⋅ B + B ⋅ A + B 2 A・A+A・B+B·A+B·B=A^2+A·B+B·A+B^2 AA+AB+BA+BB=A2+AB+BA+B2, 相信在这里大家就能够看出来中间的那两项 A ⋅ B + B ⋅ A A·B+B·A AB+BA是不等的, 所以我们不能合并同类项, 进而这条结论也就是不成立的
    ( A + B ) 2 = ( A + B ) ( A + B ) = A ⋅ ( A + B ) + B ⋅ ( A + B ) = A ・ A + A ・ B + B ・ A + B ・ B = A 2 + A ⋅ B + B ⋅ A + B 2 \begin{matrix}(A+B)^2=(A+B)(A+B) \\=A·(A+B)+B·(A+B)\\ =A・A+A・B+B・A+B・B\\ =A^2+A·B+B·A+B^2\\\end{matrix} (A+B)2=(A+B)(A+B)=A(A+B)+B(A+B)=AA+AB+BA+BB=A2+AB+BA+B2

dot方法在矩阵中的应用–矩阵相乘

numpy.dot方法用于计算两个数组的矩阵乘法(点积)。在矩阵运算中,矩阵乘法是一种常见的操作,它涉及到两个矩阵的元素相乘和求和的过程。

函数签名:

numpy.dot(a, b, out=None)

参数:

  • a:要进行矩阵乘法的数组或矩阵。
  • b:要进行矩阵乘法的数组或矩阵。
  • out:可选参数,指定输出结果的数组。如果不指定,结果将会创建一个新的数组。

返回值:

  • 返回两个数组的矩阵乘法结果。

注意事项:

  1. 两个数组的维度必须满足矩阵乘法的条件,即第一个数组的列数要与第二个数组的行数相等。注意顺序, 矩阵相乘时, 它只会采用dot方法的参数括弧中的第一个数组的行, 和第二个数组的列
  2. 如果两个数组是一维的,那么dot函数将计算它们的点积(内积)。
  3. 如果两个数组是二维的,那么dot函数将计算它们的矩阵乘法。

示例:

import numpy as np

# 一维数组的点积(内积)
a = np.array([1, 2, 3])
b = np.array([4, 5, 6])
dot_product = np.dot(a, b)
print(dot_product)  # 输出:32

# 二维数组的矩阵乘法
matrix_a = np.array([[1, 2], [3, 4]])
matrix_b = np.array([[5, 6], [7, 8]])
matrix_product = np.dot(matrix_a, matrix_b)
print(matrix_product)
# 输出:
# [[19 22]
#  [43 50]]

numpy.dot函数在线性代数和矩阵运算中非常常用,特别是在神经网络等领域中,矩阵乘法是一种基本的运算操作。

在前面我们知道矩阵相乘是指两个矩阵的乘法运算。两个矩阵相乘的条件是,第一个矩阵的列数必须等于第二个矩阵的行数。如果两个矩阵A和B满足条件,它们的乘积C将是一个新的矩阵,其行数等于A的行数,列数等于B的列数。

矩阵相乘的运算可以使用numpy库的dot()函数来实现。例如,假设有两个矩阵A和B:

import numpy as np

# 定义两个矩阵A和B
A = np.array([[1, 2], [3, 4]])
B = np.array([[5, 6], [7, 8]])

# 矩阵相乘
C = np.dot(A, B)

print(C)

输出结果将是新的矩阵C:

[[19 22]
 [43 50]]

注意,矩阵相乘的顺序很重要,即A * B与B * A的结果一般是不相同的。

  • 在这一章的最后,我想提一个在我看来非常重要的一个内容,大家再回忆一下,我们在这一章中所讲的矩阵和向量的乘法,那么对于一个矩阵来说, 假设它是一个m×n的矩阵,那么它乘以一个有n个元素的向量,最终的结果,就是一个有m个元素的向量,那么如果我把这个矩阵写成是T的话,我把这个相乘的列向量写成是 a ⃗ \vec{a} a 的话,

  • 得到的结果就是一个 b ⃗ \vec{b} b ,我们可以换另外一个角度来理解这个乘法,这个过程相当于是我们通过某种方式把 a ⃗ \vec{a} a 转换成了 b ⃗ \vec{b} b ,或者说是把 a ⃗ \vec{a} a 映射成了 b ⃗ \vec{b} b ,那么这个转换的方法或者映射的方法就是由这个矩阵T来定义的, 那么通过这样的一个视角来理解的话,实际上我们的矩阵T可以理解成是一个向量的函数,在这里大家回忆一下函数的概念,什么叫函数?就是将一个数映射成或者转换成另外的一个数,比如说f(x)=x^2,那么它就可以把2转换成4, 3转换成9,以此类推…而对于矩阵来说,对于每一个m×n的矩阵,我们都可以给它一个有n个元素的向量, 通过这个矩阵就转成了一个有m个元素的向量,所以我们就可以把矩阵理解成是向量的函数,那么这个视角是一个非常重要的视角,我们在后续还会继续拓展这个视角。甚至我个人认为,在后续线性代数空间相关的更加高级的学习中,深刻的理解这个视角是非常重要的。

实战1–采用Numpy

# coding: utf-8

import numpy as np

n1 = np.arange(6).reshape((2, 3))
n2 = np.copy(n1)
n3 = n1 * n2  # '*'注意它不是矩阵相乘, 它是矩阵的对应位置相乘


"""
①:矩阵相乘有一个前提:第一个数组的列数要与第二个数组的行数相等, 
不相等的话则会报错。此处我们直接使用数组和它转置后的对象
②:一定要注意好dot方法参数括弧中的数组对象的传入顺序, 因为矩阵相乘会采用第一个数组的行和第二个数组的列
顺序不一致则生成的新数组也不相同
"""
n4 = np.dot(n1, n1.T)
print(n4)
"""
result:
[[ 5 14]
 [14 50]]
"""

# 我们再看一下调换顺序后的结果
n4 = np.dot(n1.T, n1)
print(n4)
"""
result:
[[ 9 12 15]
 [12 17 22]
 [15 22 29]]
"""

n1 = np.arange(6).reshape((2, 3))
n2 = np.copy(n1)

c = np.dot(n1, n2.T)
"""
c:
[[ 5 14]
 [14 50]]
"""
c = c.T  # 对称矩阵转置后不会改变原始矩阵的形状和值

A = np.arange(6).reshape(2, 3)
"""
A:[[0 1 2]
 [3 4 5]]
"""

B = np.arange(10, 16).reshape(3, 2)
"""
B:[[10 11]
 [12 13]
 [14 15]]
"""
result1 = np.dot(A, B)
"""
result1:
[[ 40  43]
 [148 160]]
"""

result2 = np.dot(B, A)
"""
result2:
[[ 33  54  75]
 [ 39  64  89]
 [ 45  74 103]]
"""

# 接下来我们将会验证(AB)^T==(B^T)(A^T)是否成立
result1_T = result1.T

# 在此之前我们先来尝试一下(A^T)(B^T)
result3 = np.dot(A.T, B.T)
"""
这是(A^T)(B^T), A(作为第一个数组)、 B(作为第二个数组)矩阵分别转置后然后再相乘后的结果
result3:
[[ 33  39  45]
 [ 54  64  74]
 [ 75  89 103]]
"""

"""
这是(AB)^T, A、B矩阵相乘然后再转置后的结果
result1_T:
[[ 40 148]
 [ 43 160]]
"""

# 从上面可以看到(AB)^T != (A^T)(B^T)
# 我们再来验证一下(AB)^T==(A^T)(B^T), 因为矩阵是不满足交换律的, 在这里(A^T)(B^T)、(A^T)(B^T)的值是不相等的
result4 = np.dot(B.T, A.T)
"""
这是(B^T)(A^T), A(作为第二个数组)、 B(作为第一个数组)矩阵分别转置后然后再相乘后的结果
result4:
[[ 40 148]
 [ 43 160]]

在这里我们看到(B^T)(A^T)和(AB)^T的值是相等的
所以(AB)^T==(B^T)(A^T)是成立的
"""

实战2–手动创建Matrix类和Vector类

项目目录结构:

├───Linear_Algebra
│   │   main_matrix.py
│   │   
│   │           
│   ├───playLA
│   │   │   Matrix.py
│   │   │   Vector.py
│   │   │   _globals.py
│   │   │   __init__.py

Matrix.py :

# coding: utf-8

from .Vector import Vector


class Matrix:

    def __init__(self, list2d):
        self._values = [row[:] for row in list2d]

    @classmethod
    def zero(cls, r, c):
        """返回一个r行c列的零矩阵"""
        return cls([[0] * c for _ in range(r)])


    def __add__(self, another):
        """返回两个矩阵的加法结果"""
        assert self.shape() == another.shape(), \
            "Error in adding, shape of matrix must be same."
        return Matrix([[a + b for a, b in zip(self.row_vector(i), another.row_vector(i))]
                       for i in range(self.row_num())])
    def __sub__(self, another):
        """返回两个矩阵的减法的结果"""
        assert self.shape() == another.shape(), \
            "Error in subtracting, Shape of matrix must be same."
        return Matrix([[a - b for a, b in zip(self.row_vector(i), another.row_vector(i))]
                       for i in range(self.row_num())])
    def dot(self, another):
        """返回矩阵乘法的结果"""
        if isinstance(another, Vector):
            """矩阵和向量的乘法"""
            assert self.col_num() == len(another), \
                "Error in Matrix-Vector Multiplication"
            return Vector([self.row_vector(i).dot(another) for i in range(self.row_num())])
        if isinstance(another, Matrix):
            """矩阵和矩阵的乘法"""
            assert self.col_num() == another.row_num(), \
                "Error in Matrix-Matrix Multiplication"
            return Matrix([[(self.row_vector(r)).dot(another.col_vector(c))
                            for c in range(another.col_num())] for r in range(self.row_num())])

    def __mul__(self, k):
        """返回矩阵的数乘结果self * k"""
        return Matrix([[k * e for e in self.row_vector(i)] for i in range(self.row_num())])

    def __rmul__(self, k):
        """返回矩阵的数乘结果 k * self"""
        return self * k  # 交换律

    def __truediv__(self, k):
        return (1 / k) * self

    def __pos__(self):
        """返回矩阵取正的结果"""
        return 1 * self

    def __neg__(self):
        """返回矩阵取负的结果"""
        return -1 * self

    def row_vector(self, index):
        """返回矩阵的第index个行向量"""
        return Vector(self._values[index])

    def col_vector(self, index):
        """返回矩阵的第index个列向量"""
        return Vector([row[index] for row in self._values])

    def __getitem__(self, pos):
        """返回矩阵pos位置的元素"""
        r, c = pos
        return self._values[r][c]

    def size(self):
        """返回矩阵的元素个数"""
        r, c = self.shape()
        return r * c

    def row_num(self):
        """返回矩阵的行数"""
        return self.shape()[0]

    __len__ = row_num

    def col_num(self):
        """返回矩阵的列数"""
        return self.shape()[1]

    def shape(self):
        return len(self._values), len(self._values[0])

    def __repr__(self):
        return f"Matrix{self._values}"

    __str__ = __repr__

Vector.py :

import math
from ._globals import EPSILON


class Vector:

    def __init__(self, lst):
        self._values = list(lst)

    @classmethod
    def zero(cls, dim):
        """返回一个dim维的零向量"""
        return cls([0] * dim)

    def __add__(self, another):
        """向量加法,返回结果向量"""
        assert len(self) == len(another), \
            "Error in adding. Length of vectors must be same."

        return Vector([a + b for a, b in zip(self, another)])

    def __sub__(self, another):
        """向量减法,返回结果向量"""
        assert len(self) == len(another), \
            "Error in subtracting. Length of vectors must be same."

        return Vector([a - b for a, b in zip(self, another)])

    def norm(self):
        """返回向量的模"""
        return math.sqrt(sum(e**2 for e in self))

    def normalize(self):
        """返回向量的单位向量"""
        if self.norm() < EPSILON:
            raise ZeroDivisionError("Normalize error! norm is zero.")
        return Vector(self._values) / self.norm()

    def dot(self, another):
        """向量点乘,返回结果标量"""
        assert len(self) == len(another), \
            "Error in dot product. Length of vectors must be same."

        return sum(a * b for a, b in zip(self, another))

    def __mul__(self, k):
        """返回数量乘法的结果向量:self * k"""
        return Vector([k * e for e in self])

    def __rmul__(self, k):
        """返回数量乘法的结果向量:k * self"""
        return self * k

    def __truediv__(self, k):
        """返回数量除法的结果向量:self / k"""
        return (1 / k) * self

    def __pos__(self):
        """返回向量取正的结果向量"""
        return 1 * self

    def __neg__(self):
        """返回向量取负的结果向量"""
        return -1 * self

    def __iter__(self):
        """返回向量的迭代器"""
        return self._values.__iter__()

    def __getitem__(self, index):
        """取向量的第index个元素"""
        return self._values[index]

    def __len__(self):
        """返回向量长度(有多少个元素)"""
        return len(self._values)

    def __repr__(self):
        return "Vector({})".format(self._values)

    def __str__(self):
        return "({})".format(", ".join(str(e) for e in self._values))

_globals.py :

EPSILON = 1e-8

EPSILON = 1e-8表示的是一个及其微小的误差范围,用于更好的去判定向量的模是否相等或者等于某一个值。详细点的说明在这里拓展:EPSILON = 1e-8

__init__.py :

from ._globals import EPSILON

main_matrix.py :

# coding:utf-8

from playLA.Matrix import Matrix
from playLA.Vector import Vector

if __name__ == '__main__':
    matrix = Matrix([[1, 2], [3, 4]])
    print(matrix)
    print('matrix.shape=', matrix.shape())
    print("matrix.size=", matrix.size())
    print("len(matrix)=", len(matrix))
    print("matrix[0][0]=", matrix[0, 0])

    matrix2 = Matrix([[5, 6], [7, 8]])
    print(matrix + matrix2)
    print(matrix - matrix2)
    print(matrix * 2)
    print(2 * matrix)
    print(Matrix.zero(2, 3))

    T = Matrix([[1.5, 0], [0, 2]])
    p = Vector([5, 3])
    print(T.dot(p))

    P = Matrix([[0, 4, 5], [0, 0, 3]])
    print(T.dot(P))

    print(matrix.dot(matrix2), matrix2.dot(matrix))  # 验证矩阵相乘是否遵循交换律

你可能感兴趣的:(人工智能高等数学知识强化,矩阵,线性代数)