从近视宅男买早餐到彭罗斯逆矩阵(1):矩阵乘法|N文粗通线性代数

学术   2024-11-02 00:01   加拿大  





「N文粗通线性代数」开篇辞:


本来想模仿网红爆款文,写篇《一文读懂线性代数》。但实际一写,发现很难。别说让读者读懂,光是把一些概念粗浅地写明白,也起码要写两篇,于是把标题改成《两文粗通线性代数》。写着写着,发现两篇也不够,最后只好改成《N文粗通线性代数》。


学习数学,从来都是一分耕耘,一分收获,来不得半点自欺与浮躁,任何投机取巧都注定枉然。但刻苦用功不等于死记硬背、机械刷题。事实上,书本上抽象的数学原理,很多都是从自然现象与实际生活中归纳总结出来的。只要善于联系数学原理与实际生活,你会发现掌握线性代数并不难。


——吴进远


撰文 吴进远

在全世界的大学里,“线性代数”这个名字给人的印象是学起来很难,很容易挂科,而且对找工作没什么用。其实这门课的应用并不窄。有个笑话说,有位教授开了线性代数课,选修者寥寥。于是下个学期他把课名改为“人工智能工程基础”,结果选修者趋之若鹜。至于学起来难不难,要看能不能找到合适的学习方法,方法对了就不难了。

书归正传,话说某近视宅男,某日下楼到早点铺买早餐。眼镜忘在家里,看不清黑板上写的价目。宅男心算能力超强,碰巧服务员小妹口算能力也超强,而且唱收唱付声音清亮。


于是,宅男就一边排队,一边听着前边顾客买早点的品种数量和服务员小妹报的总价,据此计算各种早点的单价。

(1)先乘再累加

为了简化问题,我们假定早点铺只卖三种早点:

1)油饼,单价3元;

2)茶叶蛋,单价4元;

3)豆腐脑,单价7元。

当然这几个单价对宅男来说是未知的。

第一个顾客买了一个油饼、一个茶叶蛋、一碗豆腐脑,服务员小妹报价14元;

第二个顾客买了两个油饼、四个茶叶蛋、四碗豆腐脑,服务员小妹报价50元;

第三个顾客买了三个油饼、五个茶叶蛋、八碗豆腐脑,服务员小妹报价85元;

第四个顾客买了三个油饼、一个茶叶蛋、一碗豆腐脑,服务员小妹报价20元。

这几笔交易可以列成下面一个表:


服务员小妹是怎样算出总价的呢?很简单,把顾客购买每种食品的数量乘以它们各自的单价,再把各个乘积加和起来,就得到了总价。这个计算方法,可以用下图表示:


在这里,我们用x1、x2、x3分别代替油饼、茶叶蛋、豆腐脑的单价,y1到y4分别表示顾客1到4付出的总价,而每位顾客购买的不同食品的数目,则由带两个下标的字母a代替。

(2)矩阵与向量相乘

上面这个算法,可以用一个更简洁的矩阵乘法公式写出来:


有的时候,只有一个下标的矩阵可以称为向量。或者说,向量有时可以看成是一种特殊的矩阵。因此,上面这个公式显示了矩阵乘法的一种特殊情况,即一个矩阵与一个向量相乘,得到另一个向量。不过注意,这两个向量的意义不一定一样,它们的维度也不一定一样。

向量与矩阵之间的乘法是按照下面的公式进行的


在上面的计算中,我们把左边矩阵中一行里j=1到3的元素,与右边矩阵(或向量)一列中j=1到3的元素一对对相乘然后累加,就得到新向量的一个元素。大家是不是觉得这话听着像绕口令?现在我告诉你我是怎么记住这个算法的。


我们可以把矩阵乘法中左边的矩阵想象成一串串横挂的羊肉串,把右边的矩阵(或向量)看成是竖着插的糖葫芦。二者相乘的时候,把糖葫芦举高,再横过来。


然后把糖葫芦与第一串羊肉串怼到一起,把一个个糖葫芦球与对应的一片片羊肉一对一对地撸下来,打烂搅匀(即一对对地做乘法),再把一对对混合物捏在一起(即累加)。这样就形成了新串烧(新向量)的第一个球。


用同样的糖葫芦与第二羊肉串做相同操作,就得到新串烧的第二球。对左边矩阵的所有行重复这个运算,最终就得到了一个新的向量。总之,只要始终把左边的矩阵看成横挂的串烧,右边的看成竖插的,就不会记混。

(3)矩阵与矩阵相乘

我们可以把上面买早餐的问题再扩展一下,比如这个餐厅的老板鼓励同学早起,规定早上七点以前有早起优惠,这样就有了两组不同的单价。而同样的顾客购买同样品种数量的食品,早起或睡懒觉就会有不同的总价。这种情况下,我们就有了一个公式:一个4行3列矩阵,乘以一个3行2列矩阵,得到一个4行2列矩阵。


在这个公式中,我们特意把食品的品种下标写成油、蛋、豆,而价格的下标写成平(常)、早(起)。这样写的目的,是强调不同的下标表示的意义可能是不同的。尽管我们平时都用1,2,3,4等下标,但不同下标即便使用相同的数字,它们代表的意义也可能相差十万八千里,千万不可以傻傻分不清。

上面公式中保留的1,2,3,4代表了不同的顾客,如果不嫌烦琐,我们也完全可以把这些顾客用他们的自然姓名标注,比如张三、李四、陈老大、王二麻子等等。不过,代数代数,妙就妙在这个“代”字上。数学中一个非常重要的技巧,就是把一些繁杂的事物,以及对这些繁杂事物的处理方法,打包起来,用一个比较简单的符号代替。通过这样一种层层迭代,人们就可以认识与处理远远超过人脑处理能力的复杂事物。比如前面谈到的矩阵乘法,就可以用一个很简单的公式写出来:


更进一步,我们甚至可以把这个运算直接写成 y = Ax 。

(4)爱因斯坦求和约定

大家都知道爱因斯坦,他脑子比咱们大家都好使,上面这个公式对他应该是小菜一碟。可是架不住他天天做这样的推导运算,整天“西格玛”“西格玛”地写,也够烦的,于是他老人家就提出了一个著名的爱因斯坦求和约定。这个约定最主要的一件事,就是遇到类似的矩阵乘法可以不写“西格玛”。两个数怼到一起,如果有相同的下标或上标,就表示对这个下标(或上标)的所有成员一对对相乘然后求和。于是上面那个公式就简化为:


这个约定看似只做了很少的简化,但可以想象一下,如果讨论的问题涉及很多个矩阵连续相乘,我们就需要写上很多层连加号。这种情况下,使用爱因斯坦求和约定带来的好处就非常明显了。

爱因斯坦求和约定还涉及一些细节,比如下标与上标的使用规范。又比如用拉丁字母做上下标代表三维空间中1,2,3这三个维度,而用希腊字母做上下标时表示时间加空间,即0,1,2,3这四个维度。这些大家读到具体的文献时就会接触到。

(5)硬件相乘累加运算单元

这种两组数一对一对地相乘再累加的运算,在数学的很多领域都有非常广泛的应用。除了矩阵乘法,我们学过加权平均,两个矢量的内积(点积)都是这样的形式。由于这种运算使用得太频繁了,在现代的微处理器、数字信号处理器以及现场可编程门阵列(FPGA)等逻辑芯片中,人们经常会把相乘累加运算单元直接设计进去。


上图就是一个典型的相乘累加运算单元。输入的数据从ai和xi这两个端口送进去,每个时钟周期放一对数。这一对数相乘之后,其乘积送到累加器。每次得到的新的乘积都与留存在寄存器中的部分和y相加。等到ai与xi这两个向量中所有的成员都刷过一遍之后,寄存器y就累加出了这两个向量的内积。

(6)矩阵乘法与图片旋转

大家可能会问,这种矩阵或者向量的乘法运算,除了买早餐算账,还有什么用呢?我们现在讨论一个和吃没有关系的例子。


外出旅游,拍了一张很好看的风景照,可惜手机没有端平,地平线拍歪了。这样一张照片,怎么才能把地平线调平呢?

照片是由纵横排列的像素构成的,要想把地平线调平,只要把所有像素围绕一个原点旋转一定角度就可以了。具体讲,是把横坐标为x1,纵坐标为x2的像素,按照下面这个公式,移动到横坐标y1,纵坐标y2的位置。或者说,我们要对照片每个像素的坐标做线性变换。(注意我们这里分别用x和y代表转动前后的坐标,而用下标1、2代表横坐标与纵坐标。这是为了和我们前面的用法统一形式。)



把所有像素的坐标都按照这个公式算出来,再把各个像素移动到新的位置,我们就可以看到整个照片旋转了一个角度。当然实际操作时,新图片中可能会有一些像素被漏掉,这就需要根据周围像素显示的色彩亮度数据来做插值,把这些漏掉的像素填上。

(7)美颜磨皮

如今照相,美颜已成为标配功能。所谓美颜,其实就是对照片中的像素做一系列的修改。这些像素修改,很多时候要用到矩阵乘法。磨皮是美颜中的一个重要功能。比如下面这位笑脸帅哥,不满意自己皮肤,尤其是眼睛下方皮肤的松弛感,就给自己的照片做了磨皮。


原始照片有很多瑕疵,皮肤上各种斑点非常多。经过磨皮,这些缺陷被抹掉了。

磨皮有非常多的算法,上网搜一搜甚至能找到不少学术会议论文。磨皮算法中最简单的一种,是把一个小范围内的像素做加权平均,然后生成一个新的像素。用公式写出来是这样的:


其中,q1到q9是九宫格排列的九个像素的亮度值,w1到w9是各个像素对应的权重,而p5是生成的像素亮度值。如下图所示。


这个算法可以写成矩阵乘法的形式:


这里请读者注意,公式中所有权重因子的和等于1。新生成图片的每个像素都是原始图片对应点附近九个像素的加权平均。这九个像素对新像素的贡献是不同的,其中中心像素的贡献为0.2,比周边像素的贡献(0.1)要高。经过这个运算,原始图片中相邻像素亮度(或颜色)突然跳变的情形就被平均掉了,图片看起来就显得更加平滑。

对于大多数彩色数字图片,每个像素包括红绿蓝三种颜色,q1到q9是这九个像素中某一种颜色的亮度值,上面的运算要对所有三种颜色重复进行。这些亮度值通常是整数,范围是0到255,用8个比特来表述。

在处理器硬件中,整数运算通常要比浮点运算快,功耗也更低。此外,乘法的功耗要比普通的加减法高。只是我们平时编程的时候,运算操作会交给编译器调用的事先写好的二进制代码,我们完全感觉不到底层处理过程,但所有这些运算消耗的电能是实打实的。要想节约能耗,我们就应该尽量减少浮点运算,减少乘法,于是上面这个矩阵乘法可以写成:


这个公式中,由于大部分权重为1,所以不需要做乘法,直接把整数亮度值累加起来就可以了。中间权重为2,相当于把对应的整数q5加两次,也相当于把q5所有的二进制比特向左移动一位。

最后累加出来的结果,我们把它乘以0.1,而不是除以10。这是因为除法是四则运算中最麻烦的——不仅仅是笔算时麻烦,在计算机中也同样如此。

有时候,我们会把节约功耗的想法在算法设计的过程中就体现出来,比如像下面这个公式里的情形。


这个公式中,整数亮度值与权重值的乘法,完全可以通过移位来实现,乘以2或4分别相当于向左移动1位或2位。最后累加结果需要除以16,可以用向右移动4位实现。

对大多数码农来说,这类节约资源与功耗的技巧可有可无,因为现代处理器都比较快,像磨皮这种偶尔用一次的子程序,上述技巧带来的好处不很明显。不过,如果是FPGA等资源有限的硬件环境,计算量又非常大的情况下,这种设计与实现算法的技巧就会有用武之地了。

各位读者可能会问,宅男算出食品单价了没有?别急,我和您说过一篇文章写不完,欲知后事如何,且听下回分解。





微信实行乱序推送,常点“在看”,可防失联

哲学园
哲学是爱智慧, 爱智慧乃是对心灵的驯化。 这里是理念的在场、诗意的栖居地。 关注哲学园,认识你自己。
 最新文章