前言
最近做算法相关的工作时,写了一些线性回归的代码,其中用了比较多的矩阵计算,由于已经毕业好多年,大学线性代数学的又不怎么样,所以本篇文章就简单回顾一下矩阵相关的知识。
正文
什么是矩阵,矩阵的本质就是一张数表,可以看成一种表示线性变换的工具。
矩阵是什么样子的
矩阵是一个由多个数字组合起来的方阵,比如下面这个样子:
147258369
这里我们看出基本特征:
- “矩”的意思就是矩形,由数字组成的矩形;
- “阵”的意思整齐,这些数字排列起来非常整齐;
- 矩阵中,横向的数字是行,竖向的数字是列;
- 矩阵中,通过"第几行、第几列"这种简单的方式来确定某个数字的具体位置。
在上面的矩阵中,它是一个3x3
的矩阵,数字8在第三行第二列,这里注意在矩阵的描述中,行和列都是从1开始算起,而不是编程中习惯地从0算起。知道了矩阵长这个样子后,有个疑问,把数字弄成这个样子有什么用?
矩阵的由来
数学之所以越来越复杂,有一个关键点是在于代数的发明,也就是用x
这样的未知数来代替计算。比如有个小学问题:小明在市场买一些鸭子和小狗,鸭子3块钱一只,小狗7块钱一只,一共花了59块钱;小明又数了一下,鸭子和小狗一共44条腿,问小明买了多少只鸭子和多少只小狗?
这种问题我们用代数就非常容易解决,设鸭子的数量为x,小狗的数量为y,所以可以列出下面方程:
3x + 7y = 59
2x + 4y = 44
一般人看到这里就不会继续再思考了,因为问题已经解决了。但是数学家看到上面的二元一次方程的时候,会联想到一元一次方程,我们列个一元方程:
2x = 10
这时会想,有没有什么办法,可以把二元一次方程表示为和一元一次方程一样简单呢?然后解起来也非常方便呢?这个就是矩阵的雏形。
数学家发现,这个二元一次方程可以分为3个部分:
- 第一部分,就是代数x、y左边的系数;
- 第二部分,是代数x、y本身;
- 第三部分,是等号右边的数字。
然后一元一次方程也是3个部分:
- 第一部分,代数x左边的系数;
- 第二部分,代数x本身;
- 第三部分,是等号右边的数字
这时我们就可以把第一部分按照方程原先的顺序提取出来,然后是代数部分,他们是上下组合的,再加上最后一部分,所以科学家类比一元一次方程,把这3个部分给组合起来就得到:
[3274][xy]=[5944]
这差不多就是矩阵被创造出来的大致过程了。
探究矩阵乘法
按照类似一元一次的方程是写出来了,但是怎么计算呢?我们还是来观察一下刚刚创造出来的矩阵和原来的方程有什么相通的地方,这是矩阵:
[3274][xy]=[5944]
这是方程:
3x+7y=592x+4y=44
这里我们可以把二元一次方程的左边代入矩阵的结果,即可得到:
[3274][xy]=[3x+7y2x+4y]
所以这里的乘法规则就很容易看出来了。第一个矩阵的第一行和第二个矩阵的第一列数字分别相乘相加,就得到了结果的第一行的值;第一个矩阵的第二行和第二个矩阵的第一列数字分别相乘相加,就得到了第二行的值。
这里我们就可以总结出2个关于矩阵相乘的规律:
-
第一个矩阵的列数必须和第二个矩阵的行数相等,才可以相乘。即只有mxn
的矩阵可以和nxl
的矩阵相乘,最后得到一个mxl
的矩阵。
-
矩阵相乘不满足交换律。这里理解起来非常容易,比如矩阵A是4x2
的矩阵,矩阵B是2x3
的矩阵,AxB
满足上面要求,但是BxA
就不可以了。
这里给出一个矩阵乘法的公式:
a11a21a31a12a22a32×[b11b21b12b22b13b23]=a11b11+a12b21a21b11+a22b21a31b11+a32b21a11b12+a12b22a21b12+a22b22a31b12+a32b22a11b13+a12b23a21b13+a22b23a31b13+a32b23
单位矩阵
在一元一次方程中,有个特殊的系数是1,数字1有个特点:就是和任何数相乘都不会改变他们的值,相似的矩阵中有没有这个"1"呢,和其他矩阵相乘不会改变其他矩阵呢?经过研究是有这种矩阵的,这种矩阵叫做单位矩阵,那单位矩阵长什么样子呢?我们可以简单推导出来,还是使用上面的例子:
[3274][xy]=[3x+7y2x+4y]
这里我们如何让等式右边还等于[xy]呢?非常简单,我们把3改成1,7改成0,2改成0,4改成1,即变成了[1001]了就可以,这个矩阵就是一个单位矩阵。
经过研究,所有单位矩阵都有如下2个特点:
-
是一个正方形矩阵。
-
除了对角线上的数字是1之外,剩下的数字都是0,单位矩阵用大写的粗体I来表示。

矩阵的转置
矩阵的转置是一种操作,它将矩阵的行和列互换位置得到一个新的矩阵。假设有一个mxn
的矩阵A,其转置矩阵记作AT,即将矩阵A的第i行变成AT的第i列,将矩阵A的第j列变成矩阵AT的第j行。
具体来说,如果A=[aij]是一个mxn
的矩阵,则其转置矩阵AT=[bij]是一个nxm
的矩阵,其中bij=aji,即矩阵A的第i行第j列元素变成了转置矩阵AT的第j行第i列元素。
比如现在有矩阵:
A=[142536]
那么其转置矩阵为:
AT=123456
矩阵转置有如下几条性质:
- (AT)T =A,即转置的转置等于原矩阵。
- (A+B)T=AT+BT,即矩阵相加后再转置等于分别转置后再相加。
- (kA)T=k(AT),其中k为常数。
- (AB)T=BTAT,即2个矩阵相乘后转置等于分别转置再相乘(乘法的顺序颠倒)。
初等行变换
接下来我们在介绍逆矩阵相关知识点前,我们来看一个基本概念,叫做初等行变换。
在求解线性方程组时,可以对方程组进行下列同解变形:
- 交互2个方程的位置;
- 用一非零数乘以某一个方程;
- 把某个方程乘以一个常数加到另一个方程上。
比如有线性方程组:
7x + 8y + 11z = -3
5x + y - 3z = -4
x + 2y + 3z = 1
将方程式(1)与方程式(3)进行交换:
x + 2y + 3z = 1
5x + y - 3z = -4
7x + 8y + 11z = -3
将方程式(1)x(-5)加到方程式(2)上,将方程式(1)x(-7)加到方程式(3)上,得到如下:
x + 2y + 3z = 1
-9y - 18z = -9
-6y - 10z = -10
将方程式(2)x(-1/9),将方程式(3)x(-1/2),得到如下:
x + 2y + 3z = 1
y + 2z = 1
3y + 5z = 5
再将方程式(2)x(-3)加到方程式(3)上,得到如下:
x + 2y + 3z = 1
y + 2z = 1
-z = 2
再将方程式(3)x(3)加到方程式(1),将方程式(3)x(2)加到方程式(2),得到如下:
x + 2y = 7
y = 5
-z = 2
最后将方程式(2)x(-2)加到方程式(1),将方程式(3)x(-1),就可以得到方程组的解:
x = -3
y = 5
z = -2
上面流程就是我们使用消元法求解方程组的过程,最后算出值。而我们知道矩阵的本质就是多元方程组的一种简写,所以这里类似可以给出矩阵的初等行变换定义:
- 互换矩阵的第i,j俩行,记作ri↔rj,简称换行;
- 将矩阵第i行各元素乘以非零常数k,记作kri,简称为数乘;
- 将矩阵第j行各元素数乘k后加到第i行的对应元素,记作ri+krj,简称为倍加;
这样上面的方程组
7x + 8y + 11z = -3
5x + y - 3z = -4
x + 2y + 3z = 1
我们使用曾广矩阵来表示即为:
75181211−33−3−41
而把消元法的步骤对应到矩阵上来,最后得到的矩阵是:
100010001−35−2
可以看到最终的矩阵中,左边是一个单位矩阵,右边就是我们要求解的未知数的值。
矩阵的逆矩阵
逆矩阵的概念和和单位矩阵相关,就比如整数5 * 1/5 = 1
这种情况类似,假如有单位矩阵,我们记为I,对于矩阵A,如果存在A−1,使得A X A−1=A−1XA=I,那么就称A−1为矩阵A的逆矩阵。
从定义上来看,逆矩阵就类似于数字的倒数,从一元一次方程来看确实如此,这里的单位矩阵I就类似于1。明确了逆矩阵的定义,我们就需要知道如何求解逆矩阵,这里我们使用初等行变换的方式来求解逆矩阵。
我们直接来说思路:假设我们需要求A的逆矩阵,我们可以对矩阵A进行连续的初等行变化的操作,直到将矩阵A转行为单位矩阵I。与这些初等行变化操作同步进行的是一个对同阶单位矩阵I进行初等行变换操作,当矩阵A通过不断变化为单位矩阵后,这时对I的操作将会把I变成矩阵A的逆矩阵。
通过前面的初等矩阵变化操作,我们可以预测这个操作非常复杂,我们也来看个例子,最开始有2个矩阵,一个是待求解其逆的矩阵,一个是单位矩阵:
A=243436618I=100010001
进行r1∗1/2:
A=143236318I=1/200010001
进行r2+−4∗r1和r3+−3∗r1:
A=1002−503−11−1I=1/2−2−3/2010001
进行r3∗−1:
A=1002−503−111I=1/2−23/201000−1
进行r2∗2/5+r1:
A=1000−50−7/5−111I=−3/10−23/22/51000−1
进行r3∗7/5+r1:
A=1000−500−111I=9/5−23/22/510−7/50−1
进行r2∗−1/5:
A=100010011/51I=9/52/53/22/5−1/50−7/50−1
进行r3∗−11/5+r2:
A=100010001I=9/5−29/103/22/5−1/50−7/511/5−1
这时矩阵A变成了单位矩阵,原单位矩阵就是逆矩阵A−1。
这个化为单位矩阵的过程并不唯一,基本思路就是先将第一列除第一行外的元素化为0,再将第二列除第二行外的元素化为0,然后操作第三列,这个过程非常复杂,容易出错,我们使用代码来验证一下:
Eigen::MatrixXf A(3,3);
A << 2,4,6,
4,3,1,
3,6,8;
std::cout << "------ A ------" << std::endl << A << std::endl;
std::cout << "------ A.inverse ------" << std::endl << A.inverse() << std::endl;
------ A ------
2 4 6
4 3 1
3 6 8
------ A.inverse ------
1.8 0.4 -1.4
-2.9 -0.2 2.2
1.5 2.23517e-008 -1
可以发现结果是正确的,为什么这里的0会变成一个非常小的数,是因为矩阵的数据类型是float
类型。
总结
本篇文章主要简单介绍了矩阵相关概念,以及最后使用Eigen
库来验证我们的推导,这些是算法的基础,必须要理解。
更多算法相关总结移步专栏: juejin.cn/column/7415…