线性代数学习之矩阵不只是m*n个数字
什么是矩阵:
继续接着上一次https://www.cnblogs.com/webor2006/p/14257300.html的线性代数往下学习,前两次的学习都是跟Vector相关的,也就是学习线性代数这里是从Vector开始,而实际上人们一谈到线性代数第一想到的不是向量,而是矩阵【Matrix】。对于矩阵的样子应该都比较熟悉:
也就是将数排成m行,n列。那它的由来其实可以这样理解:向量是对数的拓展,一个向量表示一组数;而矩阵是对向量的拓展,一个矩阵表示一组向量。那么问题来了,既然一个矩阵是表示多个向量,那对于上图的这个矩阵到底表示几个向量?这些向量分别是谁?这里可以分2个视角来进行看待:
也就是横着来看,当然另一个视角就是竖着来看喽:
那到底怎么来看待这个矩阵呢?这个待之后慢慢再来体会,目前先了解矩阵的一些基本概念。对于上图的这个矩阵就可以称之为“4 * 4矩阵,行数是4,列数是4”,而还有可能是这种矩阵,行列不一样的:
它是3 * 4矩阵,行数是3,列数是4。另外对于行列相等的矩阵,又有一个名词出现了:
它也叫“方阵”,对于方阵它是有很多的特殊的性质的,或者某些特殊的矩阵一定是方阵,关于这块在后续再来体会,目前先了解该名词既可。
那既然咱们学的是代数,那就需要用字母来代替数对吧,所以对于矩阵而言它的表示法通常是用一个大写的字母,如下:
其中每一个元素都是由行列下标来表示,所以形式为:
它跟计算机中的二维数组的表示其实是一样的,所以理解起来也非常之自然,下面举个实际用矩阵表示数据的例子:
其中上图就是典型的一个矩阵,其中行向量来看就是表示某一个人各个科目的成绩,而列向量则表示某一个科目不同人的成绩。
实现属于我们自己的矩阵类:
跟向量的学习一样,接下来则编程实现一下自己的矩阵类,先新建一个文件:
__init__:定义构造
class Matrix: def __init__(self, list2d): # 遍历二维列表,然后拷贝生成一个新的列表 self._values = [row[:] for row in list2d]
__repr__:矩阵类输出
如之前封装Vector所示,还有另一个输出的方法,这里简单起见直接让其等于它:
下面简单调用一下:
shape():返回矩阵的形状
标红的其实是python中的逗号表达式,有了它,另外再提供两个便捷的方法,就是返回矩阵的行数和列数,如下:
下面来调用试一下:
size():返回矩阵元素的个数:
测试一下:
__len__:
对于矩阵的大小其实返回它的行数既可,所以可以简单定义为:
调用一下:
__getitem__:获取某个位置的元素
调用一下:
row_vector、col_vector:获得行向量:
由于看待矩阵的向量有两个视角,所以也对外提供相应的方法,如下:
下面调用一下:
矩阵的基本运算:
跟学向量一样的,对于矩阵也存在基本运算,这里先来回忆一下向量的基本运算有两个:
而对于矩阵的基本运算也是两个:
也就是矩阵的加法和数量乘法,下面具体来学习一下。
矩阵加法:
这块比较好理解,直接给出结论:
矩阵数量乘法:
这里也直接给出结论,跟向量的数量乘差不多:
拿现实举个例子:
要想算出2学期的平均分,就需要使用矩阵的加法,如下:
既要用到矩阵的加法,又要用到矩阵的数量乘法。
为了进一步加深对于矩阵的数量乘法的理解,下面回到二维平面的视角,既然矩形里面是包含多个向量,而每个向量可以看作是空间中的一个点,下面对于这样一个矩阵:
这里以行向量的视角来看,每行对应二维平面的一个坐标,如下:
而如果这样进行矩阵的数量乘:
此时结果为:
而对应平面上的点如蓝色所示:
有木有直观感受到对于矩阵的数量乘就是把原来的矩阵进行了一个扩大?
矩阵的运算性质:
矩阵加法:
交换率:
结合率:
-A唯一:
矩阵乘法:
结合率:
分配率:
举例论证:
同样的对于上面的每个结论都是可以通过严谨的证明的,以它为例,看一下论证过程:
其中可以看到等式确实是成立的。
实现矩阵的基本运算:
接下来则来回到编程的世界给咱们的矩阵加入基本运算。
__add__:矩阵加法
接下来处理i,它则是遍历总行数,如下:
有木有发现python的遍历真的好强大 ,当然感觉可读性不太好的样子,跟自己不太熟悉Python有关,慢慢习惯这种写法,下面调用运行一下:
__sub__:两矩阵相减
实现了两矩阵相加之后,相减就非常easy啦:
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())])
运行:
__mul__:矩阵的数量乘
def __mul__(self, k): """返回矩阵的数量乘结果:self * k""" return Matrix([[e * k for e in self.row_vector(i)] for i in range(self.row_num())])
运行:
依然跟向量一样,如果将数字提到前面其运行也是会报错的:
这是因为得覆盖另一个魔法函数才行,如下:
然后再运行就好了。
__truediv__:矩阵的数量除
这里直接利用数量乘的逻辑来实现:
def __truediv__(self, k): """返回数量除法的结果矩阵:self / k""" return (1 / k) * self
正负矩阵:
比较简单,直接定义了:
def __pos__(self): """返回矩阵取正的结果""" return 1 * self def __neg__(self): """返回矩阵取负的结果""" return -1 * self
下面调用一下:
zero:零矩阵:
调用一下:
把矩阵看作是对系统的描述:
在学习了矩阵的基本运算之后,接下来则该跟向量一样学习一下矩阵x矩阵了,不过在学习它之前,先来从另一个视角来审视下矩阵~~之前我们的例子中矩阵是一个数据表格:
其实矩阵还可以表示一个系统,下面从多个维度来看一下:
经济系统:
需要对IT,电子,矿产,房产进行投入预算,这里分别用这四个字母来表示:
首先看一下IT行业,专家预估需要这么多亿:
其中电子,矿产、房产等都需要IT的成本,并非只有IT需要成本,所以可以看到另外三个行业也乘上了成本的系数。同样的对于其它行业的成本预算如下:
而上面四个方程式组合起来就形成了如下方程组:
这样就用了一个方程组描述了一个系统。
网络中:
这里以抽象的角度来描述一个网络,如下:
而把这个可以想象成一个交通网络,其中有四个住在不同小区的人都需要去某个地方上班,比如:
其中每个小区都是100个人,现在可能交通部门想要观测整个道路交通网络中从这四个小区一直到最终的某个地方各个路径相应的网络情况,进而可以决定下次修路时哪些道路是需要扩宽一些,或者哪些地方可以增加一些公交站或地铁站等等,换言之就是交通部门需要知道各个节点的网络流量是多少,所以可以将各个道路流量假设成:
此时在这个网络中就可以列出很多的等式了,如下:
可以看到也是用了一个方程组来描述了一个网络系统。
电路系统中:
同样地在电路系统中也是可以用方程组来描述的,先看一下简单的电路图:
关于这块基本上也忘得差不多了,不过没关系,感受一下系统的概念既可。先来看一下左侧的电压,每个回路都是可以看成一个方程的:
稍加解释一下,从10v的电压出发,就有正的10;然后遇到R1的电阻则需要减掉,而由于电流是I2,所以是R1*I2;然后再遇到5v正的电压;最后再遇到电阻R2电压就需要减掉,它的电流是I1,所以需要减掉的电压是R2i1。这是左回路的电压方程描述,同样的对于右回路的电压也可以描述为:
最后电流I1在A这个点分流成了I2和I3,所以又有如下等式:
这样就可以通过这个方程组可以计算出来要布多大的布阻比较合适之类的,可见通过方程组又描述了一个电力系统。
通过上面的举例可以看出:线性方程组在各个领域,都有着非常重要的应用,而在线性代数中,将这样的方程组称之为“线性系统【之后还会再学】”,而这里多次在强调“系统”的概念,那它跟矩阵有啥关系呢?这里以上述网络系统的方程组为例,其实是可以做转换的,如下:
其中可以看到等号右侧的向量是用的列向量来表示的,因为刚好跟矩阵的行匹配上,这也是为啥一般向量要用列向量表达的原因之所在,所以可以看出对于系统的求解完成是可以转换成了对于矩阵的求解对么?所以说用矩阵就可以来表达一个系统,而一旦将矩阵用这样的方式看成系统之后,对于我们接下来要研究的矩阵的乘法就会变得很简单了,关于线性系统如何求解待之后学到时再说。
矩阵和向量的乘法:
要想知道矩阵x矩阵,则需要先明白矩阵x向量,所以这里还是以上面提到的网络系统转成矩阵的那个图为例进行阐述,抽象起来看的话,其实就是向量的数量乘【再一次体会到数学的严谨,基本上所有复杂的公式全是由之前简单的结论所能推演过来的】,看一下:
好,接下来就有一系列的变化了,会有点绕,但是又是理解矩阵和矩阵乘法的关键,对于上面这个式子其实又可以推导成这样:
所以就有如下等式了:
下面再来看一下对于等式右侧的向量中的每一个元素都是怎么计算出来的,对于第一项其实是这样:
而对于它其实就是向量的点乘运算,还记得向量点乘的式子么?可以回忆一下https://www.cnblogs.com/webor2006/p/14257300.html,
以此类推,对于结果向量的第二个元素的计算如下:
第三个元素:
第四个元素:
而总结一下可以发现:
好,接下来有一个非常非常关键的点来了,对于向量的点乘有一点非常重要的限制就是其向量的元素个数是必须一样的对不?看一下咱们之前所实现的Vector中的点乘实现就能知道:
那么回到咱们目前的矩阵和向量的乘法操作是不是可以得出这么一个结论:
矩阵A的列数必须和向量u的元素个数一致!另外矩阵A的行数是没有限制的。这里标红加粗了,因为太重要了,其原因就是由于向量的点乘其要求元数的个数必须一样。
下面来切换一下视角以行视角的方式来看待一下矩阵x向量其实就可以抽象成这样:
这样就更加容易理解。
再看向量的点乘:
在上面介绍矩阵x向量时:
其中提到对于矩阵的行数是木有限制的,可以是无数个,那假如矩阵的行数是1呢?此时就变成了这样:
此时矩阵又变成了向量,又回到了向量的点乘了,而依然也能推算出此时的结果就是向量的点乘:
矩阵是向量的函数:
最后还有一个非常重要的结论,就是对于矩阵和向量的乘法用字母来表示的话:
从另一个角度来看,可以看到矩阵T实际上是将向量a转换成了向量b了,是不是可以把矩阵理解成是一个向量的函数?理解这个视角对于后续空间等更加高级的内容时是非常重要的,先在大脑里记住它。
矩阵在图形变换中的应用:
矩阵在向量中的转换其实最典型的应用就是在图形学中,在之前学习矩阵的数量乘时举过这样一个例子:
它在二维平面坐标系的样子是这样的:
基于此,假如向量不是一个具体的值,而是抽象的,那么也就是这样:
也就是想将向量的x元素扩大1.5倍,而y扩大2倍,要想达到这样的目的,此时肯定需要加一个系数了:
而T这个矩阵的形态是有要求的,如之前矩阵*向量的规则所述,该矩阵是要求列数必须跟向量的行数一致才可以,所以该矩阵的列数应该为2,而根据等号右侧的向量结果来看,可以看出该矩阵的行数也是2,所以T是2x2的矩阵,抽象定义如下:
此时就可以很容易求解出此时T的值为:
也就是:
上面是对一个坐标点的转换操作,但是!!!【此转折点就是要推出矩阵x矩阵公式的关键啦!!】可能图形中不止一个点呀,比如:
有三个坐标点,那用这个矩阵对每一个点去做乘法不就可以么?是的,这样木问题,但是感觉有点麻烦,有木有更加批量的做法呢?其实有,就是将所有的点集合成一个矩阵,此时就如下:
那不就是矩阵x矩阵了么?嗯,是的,那它跟矩阵x向量有啥关系呢?回忆一下:
那是不是也可以将标红处的这个向量看成是一个2x1的矩阵【其中2代表每个点坐标有2个维度x,y,而1表示当前只有一个坐标点】对不?而回到咱们目前探讨的3个点的情况,那不就是多组矩阵x列向量的情况么?所以其结果为:
二维坐标系中也就对应:
矩阵和矩阵的乘法:
基于上面的推导,其实对于矩阵和矩阵的乘法操作就已经得出它的计算公式了,其实是这样的:
用抽象一点的方式来看,就是这样:
其中跟“矩阵x向量”的限制一样,也需要满足“矩阵A的列数必须和矩阵B的行数一致!【这个一定得要记住!!】”
下面用一个抽象的方式再来理解下:
假设矩形AB,其中B用一个列向量的形式来表示,此时就可以变为:
其相乘的结果每列都是列向量,而矩阵A看它的每行【为啥不看每列呢?因为根据矩阵Ax矩阵B的规则矩阵A的列数必须跟矩阵B的行数一致,是固定的,不固定的只有矩阵A的行,所以抽象的角度对于矩阵A当然是看每行的行向量,仔细体会一下~~】,也就是每行是一个行向量对不?那相应的咱们又可以变化为:
而它展开则为:
其中:
另外根据上面的表示再来总结一下矩阵与矩阵的相乘【为啥要不断总结,因为这块如果理解不透,将直接影响后续更加高级的线性代数的知识的学习,所以再啰嗦都不为过】:结果矩阵中的第i行第j列的元素其实就是A矩阵中的第i行向量和B矩阵的第j列向量进行点乘的结果,到图上拿结果矩阵中的第一行来理解就是:
另外还可以总结一个规律:
根据矩阵A的列数必须和矩阵B的行数一致这个规则,假设:A是m * k的矩阵;B是k * n的矩阵,则结果矩阵为m * n的矩阵。而正因为矩阵和矩阵相乘是需要有一定的限制的,所以很显然对于矩阵的乘法是不遵守交换律的:
因为很有可能根本是不能相乘的,记住这是我们所学的第一个不遵守交换律的乘法,关于这块的证明待下面用python来实现时再来验证一下是否是这样的。
实现矩阵的乘法:
矩阵x向量:
下面则回到编程的世界中来对于上面所说的矩阵和向量的乘法、矩阵和矩阵的乘法实现一下。
这里分为两种情况,也有像java的instance机制,下面先来实现矩阵和向量的乘法:
接下来就具体实现一下,其实就是矩形的行向量和向量的点乘,而对于Vector点乘咱们在之前已经实现过了,回忆一下:
所以实现起来就比较简单了:
矩阵x矩阵:
而有了矩阵x向量的实现,那实现矩阵x矩阵也很简单了,因为本身内部它就是矩阵x向量嘛,再回忆一下那个抽象图:
同样它也有需要有前置条件,并非所有的矩阵都满足相乘,所以:
而实现如下:
调用:
接下来看一下矩阵x矩阵:
最后再来验证一下在上面抛出的一个结论:“矩阵的乘法是不遵循交换率的”,下面来看一下:
矩阵乘法的性质:
接下来则来审视一下矩阵乘法的性质:
1、矩阵乘法不遵守交换律!
之前已经强调过了,这里再强调一下:
2、遵守规则:
但是呢,其实它也有一些性质是遵守的,如下:
- 乘法的结合率:
- 乘法的分配率:
同样的反过来: - 零矩阵:
跟向量一样,零矩阵也是相关的特性:
矩阵的幂:
学习了矩阵的乘法之后,很自然的就可以来学习矩阵的幂的概念了,如算数系统中,对于一个矩阵A的k次方可以表示为:
但是!!!由于矩阵的乘法是有限制的,这个限制再拎出来复习一下:“矩阵A的列数必须和矩阵B的行数一致!”,而要想让同一个矩阵能够相乘就只能是该矩阵是一个方阵【也就是行和列是一样的】,这一点需要知道。
既然已经知道了矩阵的幂的概念之后,那可能出现如下特殊的幂:
那它们是如何表示呢?这个待未来再来阐述。下面再来看一个结论:
上面这个式子对于算数是成立的,但是对于矩阵是不成立的哟,下面简单来证明如下:
记住结论就成,其证明过程了解既可。
矩阵的转置:
概述:
还记得咱们在阐述一个矩阵跟多个点的相乘推出矩阵与矩阵的相乘那块的东东么?回忆一下:
但是!!!实际中可能习惯按行的视角来表示P矩阵的多个点,也就是:
也就是每一行代表一个坐标上的点,而每一列则代表是二维平面中某一个维度的值(x轴或y轴),但是!!!很显然如果将P进行了行视角的看待之后,没法跟T矩阵相乘了呀,因为两矩阵相乘必须要满足矩阵A的列数要等于矩阵B的行数对吧?而目前看上图,T是2列的,而P是3行的,不满足相乘的要求了呀,咋办?此时矩阵的转置概述就诞生了,咱们将这个P转置一下就满足矩阵的相乘啦,如下:
其中转置表式是在字母P上加上一个T,上面可以看到其实就是将P的列的元素依次往左倒就成了它的转置了对不?
简言之:矩阵的转置就是行变成列、列变成行。用更加抽象一点的来表示就是:
知道了矩阵的转置之后,这里回忆一下之前学习向量时也提到过向量的转置,如下:
这样就串起来了。
性质:
接下来又来探讨矩阵转置的一些特性了,了解这些特性有助于咱们更好的去使用它们。
1、转置再转置就是本身
2、矩阵的加法转置:
3、矩阵的数量乘转置:
4、矩阵和矩阵的相乘转置:
这里需要特别注意等号右侧,是B的转置*A的转置,需要注意顺序哟,下面简单论证一下:
注意看一定是先把B的转置提到前面再乘以A的转置哟~~顺序一定要注意!!!
实现矩阵的转置:
接下来编程来实现一下矩阵的转置:
调用一下:
使用Numpy实现矩阵:
接下来使用Numpy数学库来实现矩阵:
矩阵的创建:
也就是在numpy中对于向量和矩阵封装在同一个函数当中了。
矩阵的属性:
获取矩阵的元素:
矩阵的基本运算:
接下来看一下矩阵与矩阵之间的乘法:
而要真正实现两矩阵的相乘,则需要调用它:
接下来看一下矩阵x向量:
因为在numpy中对于高维的数组(比如2维矩阵)都可以跟低维的数组(比如一维的向量)进行运算的,比如直接可以跟一个数字进行运算:
在numpy中像这种高维的数组可以和低维的数组进行运算有一个专业的名词叫广播,但是!!!在数学领域需要慎用它,因为会给人制造错觉,为啥一个矩阵可以跟一个向量进行加法操作呢?这里稍加提一下,下面来看一下矩阵和向量的乘法: