程序员数学:用Python学透线性代数和微积分
上QQ阅读APP看书,第一时间看更新

4.2 线性变换

下面要重点介绍一种良态(well-behaved)的向量变换,称为线性变换。除了向量,线性变换也是线性代数的一个主要研究对象。线性变换是一种向量运算在变换前后看起来一样的特殊变换。下面通过一些图例来说明其含义。

4.2.1 向量运算的不变性

向量加法和标量乘法是向量算术运算中最重要的两个。回到能够反映这些运算的二维图片,看看对它们应用变换前后的样子。

把两个向量的和想象成将它们头尾相接放置时得出的新向量,或者指向它们所张成平行四边形顶点的向量。例如,图4-22展示了向量和

图4-22 向量和的几何表示

我们想问的问题是:如果对图中的三个向量应用同样的向量变换,三个向量的关系是否会保持不变?下面尝试一种关于原点做逆时针旋转的向量变换。图4-23显示通过变换旋转了相同的角度。

图4-23 将旋转同样的角度,其关系仍然不变

旋转后的图例表示向量。只要,那么对三个向量中的每个向量应用同样的旋转变换依然成立。为了描述这个特性,我们说旋转保持(preserve)了向量和。

同样,旋转也会保持标量乘积。如果是一个向量,乘以标量,那么指向与相同的方向,只是被按照系数进行了缩放。如果对做同样的旋转就是与相同系数的标量乘积(见图4-24)。

图4-24 旋转保持了标量乘积

同样,这只是一个直观的示例而不是证明,但对于任意向量、标量和旋转,图中都保持了相同的特性。旋转或其他任意保持向量和与标量乘积的向量变换被称为线性变换

线性变换

线性变换是保持向量和与标量乘积的向量变换。也就是说,对于任意输入向量,有:

而对于任意一对标量和向量,有:

请务必停下来消化并理解这个定义。线性变换非常重要,以至于整个线性代数学科都以它命名。为了帮助你在看到线性变换时认出它们,我们再看几个示例。

4.2.2 图解线性变换

首先看一个反例:一个非线性的向量变换。示例变换接收向量并输出一个坐标被平方后的向量:。举一个例子,的和是(2, 3) + (1, -1) = (3, 2)。这个向量加法如图4-25所示。

图4-25 图解向量的和

现在把应用到每个向量上:。图4-26明显表明,不一致。

图4-26 没有保持向量和,相差甚远

作为练习,你可以试着找到一个反例来证明也不保持标量乘积。现在,我们来研究另一个变换。是按系数2对输入向量进行缩放的向量变换,换句话说,。它确实保持了向量和:如果,那么也成立。图4-27提供了一个直观的示例。

图4-27 将向量的长度增加1倍,可以保持它们的和:如果,那么

同样,也保持了标量乘积。这有些难画,但可以从代数上看出,对于任意标量

那么平移呢?假设将任意输入向量按照(7, 0)平移。令人惊讶的是,这不是线性变换。图4-28提供了一个直观的反例,其中,但不同。

图4-28 因为不等于,所以平移变换不保持向量和

事实证明,只有不移动原点的变换才能是线性的(在后面的练习中可以看到原因)。任何使用非零向量的平移都会将原点变换到不同的点上,所以它不可能是线性的。

其他线性变换的例子包括镜像、投影、剪切以及前面这些线性变换的任何三维类推。练习部分定义了这些变换,你应该通过几个示例来让自己相信,这些变换中的每一种都保持了向量和与标量乘积。通过练习,你可以识别哪些变换是线性的、哪些不是。接下来将介绍线性变换的特殊性质有什么用。

4.2.3 为什么要做线性变换

因为线性变换保持了向量和与标量乘积,所以也保持了一类更广泛的向量算术运算。最常规的运算称为线性组合。一个向量集合的线性组合是它们的标量乘积之和。例如,是向量的线性组合。给定三个向量,表达式是它们的线性组合。因为线性变换保持了向量和与标量乘积,所以也保持了线性组合。

用代数方式重新描述:如果有一个包含个向量()的集合,以及任意个标量(),则线性变换可以保持线性组合。

我们之前见过一个很容易绘制的线性组合:的组合,它相当于。图4-29显示,两个向量的这种线性组合可以让我们得到连接它们的线段的中点。

图4-29 两个向量的头部之间的中点可以用线性组合求得

这意味着线性变换能将一些中点变换为其他中点。例如,如图4-30所示,就是连接的线段的中点。

图4-30 因为两个向量之间的中点是向量的线性组合,所以线性变换之间的中点设为的中点

虽然不太明显,但像这样的线性组合也位于之间的线段上(见图4-31)。具体来说,是从路径上75%处的点,同样,路径上40%处的点,以此类推。

图4-31 点位于连接的线段上的75%处。你可以用具体的示例观察,比如当时的情况

事实上,两个向量之间线段上的每个点都是形如的“加权平均值”,其中介于0和1之间。为了证明这一点,图4-32显示了对于的向量组合,分别展示了10个和100个介于0和1之间的值。

图4-32 用0和1之间的10个值(左)和100个值(右)绘制(-1, 1)和(3, 4)的各种加权平均值

这里的关键思想是,连接两个向量的线段上的每一个点都是加权平均值,因此也是点的线性组合。考虑到这一点,我们可以思考线性变换对整个线段的作用。

因为连接的线段上的任意点都是的加权平均值,所以对于某个值,点的形式是。线性变换变换成新的向量。线段上的点被转化为某个新的点 。这又是的加权平均值,所以如图4-33所示,它是位于连接的线段上的一点。

图4-33 线性变换的加权平均值转化为的加权平均值。原加权平均值位于连接的线段上,而变换后的加权平均值位于连接的线段上

正因如此,线性变换把连接的线段上的每一个点都转移到连接的线段上的一个点。这是线性变换的一个关键性质:它们将每一条现有的线段都转移到一条新的线段上。因为我们的三维模型是由多边形组成的,而多边形是由线段勾勒出来的,所以可以预期线性变换会在一定程度上保持三维模型的结构(见图4-34)。

图4-34 对构成三角形的点进行线性变换(旋转60°),结果是一个(向左)旋转的三角形

相反,如果使用非线性变换转移到,可以看到线段是扭曲的。这意味着由向量定义的三角形并没有真正被转移到另一个由定义的三角形,如图4-35所示。

图4-35 应用非线性变换不能保持三角形边的直线性

总而言之,线性变换遵循向量的代数性质,保持了向量和、标量乘积和线性组合。它们还遵循向量集合的几何性质,将向量定义的线段和多边形转移到由变换后的向量定义的新线段和多边形上。接下来,我们将看到线性变换不仅从几何学的角度看特殊,还很容易计算。

4.2.4 计算线性变换

第2章和第3章介绍了如何将二维和三维向量分解为分量。例如,向量(4, 3, 5)可以分解为(4, 0, 0) + (0, 3, 0) + (0, 0, 5)。这样就很容易想象出向量在三维空间中每一个维度上延伸的距离。这可以进一步分解为线性组合(见图4-36)。

图4-36 三维向量(4, 3, 5)为(1, 0, 0)、(0, 1, 0)和(0, 0, 1)的线性组合

这似乎是一个简单的事实,但又是能从线性代数中得到的深刻见解之一:任何三维向量都可以被分解为(1, 0, 0)、(0, 1, 0)和(0, 0, 1)这三个向量的线性组合。这种分解中出现的向量的标量正是的坐标。

(1, 0, 0)、(0, 1, 0)和(0, 0, 1)这三个向量被称为三维空间的标准基(standard basis),分别表示为。因此,前面的线性组合可以写成。在二维空间中,。例如,(见图4-37)。(当我们说时,可能是指(1, 0)或(1, 0, 0),但一旦确定了是在二维还是三维空间中,通常就可以清楚地知道指的是哪一个。)

图4-37 标准基向量线性组合成的二维向量(7, -4)

这里只是用稍微不同的方式表示了相同的向量,但事实证明,这种视角的改变使得计算线性变换变得很容易。因为线性变换保持了线性组合,所以在计算线性变换时只需知道它如何影响标准基向量即可。

来看一个直观的示例,如图4-38所示。假设已知二维向量变换是线性的,并且知道是什么,其他未知。

图4-38 当线性变换作用于两个二维标准基向量时,会得到两个新的向量作为结果

对于其他任意向量,我们都会自动知道的终点。假如,那么可以做如下断言。

如图4-39所示,因为的位置已知,所以可以找到

图4-39 对于任意向量,可以将计算为的线性组合

为了更具体地说明这个问题,我们来完成一个完整的三维示例。假设是一个线性变换,我们只知道。如果,那么是什么?首先,可以把展开为三个标准基向量的线性组合。因为,可以代入得到:

接下来,利用是线性的并且保持线性组合的事实:

最后,将已知的的值代入,化简得到:

为了证明我们真的知道如何运作,把它应用到茶壶上。

Ae1 = (1,1,1)      ←---- 将A应用于标准基向量的结果已知
Ae2 = (1,0,-1)
Ae3 = (0,1,1)

def apply_A(v):      ←---- 构建函数apply_A(v),返回将A作用于输入向量v的结果
    return add(      ←---- 结果应该是这些向量的线性组合,其中标量是目标向量v的坐标
        scale(v[0], Ae1),
        scale(v[1], Ae2),
        scale(v[2], Ae3)
    )

draw_model(polygon_map(apply_A, load_triangles()))      ←---- 使用polygon_map将A应用到茶壶中每个三角形的每个向量上

图4-40显示了转换的结果。

图4-40 在旋转、扭曲下,可以看到茶壶是没有底的

这里的启示是,二维线性变换完全由的值来定义,也就是总共2个向量或4个数。同样,三维线性变换完全由的值来定义,也就是总共3个向量或9个数。在任意维中,线性变换的行为由一个向量列表或数组阵列来规定。这类包含数组的阵列称为矩阵,我们将在下一章中看到如何使用矩阵。

4.2.5 练习

练习4.10:再考虑对所有坐标执行二次方运算的向量变换,用代数方法证明并不是对所有标量和二维向量都成立。

:令,则。对于大多数和向量来说, ,并不等于。一个具体的反例是,其中,但是。这个反例证明不是线性变换。

 

练习4.11:假设是一个向量变换,且,其中代表所有坐标都等于零的向量。根据定义,为什么是非线性的?

:对于任意向量保持向量加法,应满足。因为,这就要求。鉴于情况并非如此,不可能是线性的。

 

练习4.12恒等变换是返回向量与接收向量相同的向量变换,用大写的表示。因此,对于所有向量,其定义写成。为什么是一个线性变换?

:对于任意向量;对于任意标量。这些等价性表明,恒等变换保持了向量和与标量乘积。

 

练习4.13:(5, 3)和(-2, 1)之间的中点是什么?把这三个点都画出来,看看你的做法是否正确。

:中点是1/2(5, 3) + 1/2(-2, 1)或(5/2, 3/2) + (-1, 1/2),等于(3/2, 2)。可以按比例画出来看看正确性,如图4-41所示。

图4-41 连接(5, 3)和(−2, 1)的线段的中点是(3/2, 2)

 

练习4.14:再考虑把转移到的非线性变换。用第2章的绘图代码将整数坐标为0~5的36个向量全部绘制成点,然后分别绘制它们的。在的作用下,向量在几何上会发生什么?

:开始时,点与点之间的空间是均匀的,但在变换后的图片中,随着坐标和坐标的增大,点与点之间在水平和垂直方向上的间距也分别增大了(见图4-42)。

图4-42 网格中的点间距最初是均匀的,但在应用变换后,点与点之间的间距是不同的,甚至同一条直线上的点间距也不同

 

练习4.15(小项目)基于属性的测试是一种单元测试,涉及为程序创造任意输入数据,然后检查输出是否满足所需条件。一些流行的Python库,如Hypothesis(可通过pip获得),可以很容易地配置它。使用你选择的库,实现基于属性的测试来检查向量变换是否是线性的。

具体来说,给定一个以Python函数形式实现的向量变换,生成大量随机向量对,并对所有这些向量断言,会保持它们的和。然后,对每组标量和向量做同样的事情,来确定保持了标量乘积。应该可以发现,像rotate_x_by(pi/2)这样的线性变换可以通过测试,但是像坐标-平方变换这样的非线性变换不能通过。

 

练习4.16:二维向量变换是相对于轴的镜像,这种变换接收一个向量并返回其相对于轴的镜像向量。它应该保持坐标不变,改变坐标符号。将这种变换称为,图4-43展示了向量和变换后的向量

图4-43 向量及其相对于轴的镜像(3, -2)

画出这两个向量、它们的和,以及这三个向量的镜像,来证明这种变换保持了向量和。再画出另一张图,同样证明这种变换保持了标量乘积,从而证明线性的两个标准。

:图4-44是一个相对于轴镜像的示例,它保持了向量和。

图4-44 对于如图所示的,在轴上的镜像保持了向量和

图4-45中的示例显示镜像保持了标量乘积:位于的预期位置。

图4-45 相对于轴的镜像保持了标量乘积

证明是线性的,需要证明可以为每一个向量和与标量乘积画出类似的图像。这些图像有无限多,所以最好用代数方法证明。(你能想出如何用代数方法证明这两个事实吗?)

 

练习4.17(小项目):假设都是线性变换。解释为什么的组合也是线性的。

:如果对于任意向量和,有,而且对于任意标量乘积,有,则组合是线性的。这只是一个必须被满足的定义声明。

现在来看它为什么为真。首先,假设对于任意给定输入向量,有。那么由于是线性的,亦知。因为此向量和是成立的,所以的线性告诉我们,它在下被保持了:。这意味着保持了向量和。

同样,对于任意标量乘积的线性告诉我们。根据的线性,也是如此。这意味着保持了标量乘积,因此满足前面所说的线性的全部定义。可以得出结论,两个线性变换的组合是线性的。

 

练习4.18:设是Python函数rotate_x_by(pi/2)所做的线性变换,那么分别是什么?

:相对于坐标轴的任意旋转都不会使轴上的点受到影响,所以由于轴上,。在平面内逆时针旋转,把此向量从轴正方向上1个单位处移到轴正方向上1个单位处,所以。同样,轴正方向逆时针旋转到轴负方向上。在这个方向上的长度仍为1,所以它是或(0, -1, 0)。

图4-46 在平面内沿逆时针方向转1/4圈,将转移到,将转移到

 

练习4.19:实现函数linear_combination(scalars, *vectors),接收一个标量列表和相同数量的向量,并返回一个向量。例如,linear_combination([1,2,3], (1,0,0), (0,1,0), (0,0, 1))应该返回,即(1, 2, 3)。

from vectors import *
def linear_combination(scalars,*vectors):
    scaled = [scale(s,v) for s,v in zip(scalars,vectors)]
    return add(*scaled)

可以确认,这样做能得到如前所述的预期结果。

>>> linear_combination([1,2,3], (1,0,0), (0,1,0), (0,0,1))
(1, 2, 3)

 

练习4.20:编写函数transform_standard_basis(transform),将一个三维向量变换作为输入,并输出它对标准基的影响。它应该输出一个由3个向量组成的元组,这些向量是transform分别作用于的结果。

:按照建议,我们只需要对每个标准基向量应用transform

def transform_standard_basis(transform):
    return transform((1,0,0)), transform((0,1,0)), transform((0,0,1))

打印rotate_x_by(pi/2)输出的地方(在浮点误差范围内)证实了我们关于前一个练习的解决方法。

>>> from math import *
>>> transform_standard_basis(rotate_x_by(pi/2))
((1, 0.0, 0.0), (0, 6.123233995736766e-17, 1.0), (0, -1.0,
    1.2246467991473532e-16))

这些向量大概是(1, 0, 0)、(0, 0, 1)和(0, -1, 0)。

 

练习4.21:假设是一个线性变换,满足是什么?

:因为,所以。因为是线性的,所以它保持了这种线性组合:。现在有了所有需要的信息:

 

练习4.22:假设都是线性变换,而且。那么是什么?

是将应用于。已知,所以是将应用于。这是的线性组合,标量为(2, 1, 0):

最后,是将应用于。这就是线性组合

请注意,现在知道了对于所有标准基向量的组合结果,所以可以计算关于任意向量了。

线性变换是良态的且容易计算,因为可以用很少的数据来指定它。下一章在用矩阵符号计算线性变换时将进一步探讨这个问题。