《线性代数》1. 一切从向量开始

发布时间 2023-08-25 18:45:30作者: 古明地盆

什么是向量

我们在初等数学的时候,研究的都是一个数,而到线性代数,我们将从研究一个数拓展到研究一组数,而一组数的基本表示方法就是向量(Vector)。向量是线性代数研究的基本元素,它就是一组数,比如 \((1, 2, 3)\) 就是一个向量。那么问题来了,向量究竟有什么作用呢?或者说我们研究一组数有什么用呢?其实向量最基本的出发点就是表示方向,比如某个人从出发点走了 \(5\) 千米,那么你能判断最终这个人走到了哪个位置吗?显然是不能的,因为不知道方向。

因为真实的世界一定是多维度的,所以同时只研究一个数是不够的,那么此时向量便有了意义,我们举个例子。

如果用一个数来描述的话,那么就是这个人走了 \(5\) 千米。但如果用一组数来描述,以这人的初始位置为原点建立坐标系,那么就可以说这个人走到了坐标为 \((4, 3)\) 的位置,当然也可以是 \((3, 4)\) 等其它位置。可以看到,此时就可以精确定位某个人的位置了,如果这个人还可以在天上飞,那么就用三维坐标系。

所以使用向量最开始的目的就是为了体现方向的概念,比如向某个方向走了多少距离,因为只有距离没有方向是不够的。而在物理学中,位移、速度、加速度、力等等,都具有方向的概念,这是光有一个数所不足以描述的。

然后还需要注意,向量都是针对于原点来说的,比如一个人从 \((0, 0)\) 走到了 \((4, 3)\) 的位置,那么它和从 \((-1, -1)\) 走到了 \((3, 2)\) 的位置是一样的,都是在 \(X\) 轴方向上移动了 \(4\) 个单位,在 \(Y\) 轴上移动了 \(3\) 个单位。而在线性代数中,我们不关心起始点在什么位置,统一默认为原点。就像初中时学的数轴一样,说一个点的坐标是 \(6\),那么一定是它相对于 \(0\) 的位置是 \(6\)。所以起始点在哪儿,对于向量的运算来说是无关紧要的,因为它的长度和方向是一样的,只是我们统一以原点作为起始点。

当然,如果只是描述物理世界中的方向,那么三个维度就完全足够了,因为真实世界只有三个维度。但大部分时候,我们要描述的问题却远不止三个维度,比如一个房子的购买率。一个房子是否会有人买,取决于很多因素。

  • 面积是多大
  • 卧室多少个
  • 周围距离学校有多远
  • 周围距离医院有多远
  • 最近的地铁站有多远
  • 房子是否是大产权
  • 价格是多少

此时我们便可以使用一个 \(7\) 维向量来描述这个房子的特征,如果特征更多,那么就用更高维度的向量来描述,最终可以拓展到 \(n\) 维向量。而 \(n\) 维向量也可以看作是 \(n\) 维空间中的某一个点相对于原点的坐标,虽然我们无法想象维度大于 \(3\) 的空间是什么样子,但这不重要,直接类比三维或二维即可。所以向量就是一组数字,它的含义是由使用者来决定的。

向量的更多术语

目前我们已经了解了什么是向量,可以把它理解为一组数,或者空间中的一个点,下面再来补充一些关于向量的术语。

与向量相对应的是标量,标量是一个数,向量是一组数。那么问题来了,我要如何区分一个符号是标量还是向量呢?所以有一个约定,普通的符号依旧表示标量,而如果符号的上面出现了一个箭头,那么就表示向量。

  • \({v}\) :这是一个标量
  • \(\vec{v}\) :这是一个向量

然后我们说,在线性代数中不关心起始点的位置,统一默认为原点。但个别情况下(尤其是几何学),我们也会考虑向量的起始点,比如起点为 \(O\),终点为 \(A\),那么就用 $ \overrightarrow{OA} $ 来表示。这一点了解就好,我们这里不考虑起始点。

然后向量分为行向量和列向量,行向量就是将一组数排成一行,列向量就是把一组数排成一列。

  • 行向量:\((1, 3, 5)\)
  • 列向量:\(\begin{pmatrix} 1 \\ 3 \\ 5 \\ \end{pmatrix}\)

对于向量本身来说,行向量和列向量没啥区别,都是相同的一组数,我们是横着摆放还是竖着摆放都没啥区别。但当学到矩阵的时候,行向量和列向量就有区别了,至于具体区别等介绍矩阵的时候再说。另外对于学校的教材、论文来说,里面提到的向量,指的都是列向量。至于为什么更愿意用列向量来表示,我们还是等介绍矩阵的时候再说。

需要补充一点,由于书籍是横版印刷,所以虽然是列向量,但它依旧是按照一行显示的。因此为了避免误会,很多书籍会写成类似 \((1, 3, 5)^{T}\) 这种形式,也就是给行向量增加一个转置操作,来表示列向量。

使用 Python 实现向量

下面我们就来定义一个类,来实现向量。

class Vector:
    """
    向量
    """

    def __init__(self, values):
        self._values = values

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

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

    def __str__(self):
        return f"{tuple(self._values)}"

    def __getitem__(self, item):
        """基于索引获取向量的元素"""
        return self._values[item]

此时我们的向量就实现好了,来测试一下。

from vector import Vector

vec = Vector([11, 22, 33])
print(vec)  # (11, 22, 33)
print("%r" % vec)  # Vector([11, 22, 33])
print(len(vec))  # 3
print(vec[0], vec[1], vec[2])  # 11 22 33

结果没有问题,当然随着后续的学习,我们会给 Vector 这个类添加更多的方法。

向量的两个基本运算

标量支持各种各样的数学运算,那么向量都支持哪些呢?首先加法运算,向量是支持的。并且在高中的时候我们就接触过向量的加法,当时书中会告诉你,两个向量相加,等于这两个向量组成的平行四边形的对角线。

但是为什么会有这个结论,很多人却不明白,下面我们就来解释一下。以 \((3, 4)^{T} + (4, 3)^{T}\) 为例,它相当于先以原点作为起始点,走到了 \((3, 4)\) 的位置;然后再以 \((3, 4)\) 为起点,走到了 \((4, 3)\) 的位置。

所以 \((a, b)^{T} + (c, d)^{T}\) 的结果就相当于 \((a + c, b + d)^{T}\),两个向量相加等于每个分量分别相加。当然以上都是二维向量,至于 \(n\) 维向量也是同理。

\(\begin{pmatrix} v_{1} \\ v_{2} \\ v_{3} \\ ... \\ v_{n} \\ \end{pmatrix} + \begin{pmatrix} w_{1} \\ w_{2} \\ w_{3} \\ ... \\ w_{n} \\ \end{pmatrix} = \begin{pmatrix} v_{1} + w_{1} \\ v_{2} + w_{2} \\ v_{3} + w_{3} \\ ... \\ v_{n} + w_{n} \\ \end{pmatrix}\)

以上就是向量的加法,除了加法,向量还支持乘法。比如 \(3 * (4, 3)^{T}\),那么它的结果是什么呢?相信这个很简单,答案就是 \((12, 9)^{T}\),因为相当于 \(3\)\((4, 3)^{T}\) 相加。同样的,我们可以将这个结论推广至 \(n\) 维向量:

\(k * \begin{pmatrix} v_{1} \\ v_{2} \\ v_{3} \\ ... \\ v_{n} \\ \end{pmatrix} = \begin{pmatrix} k * v_{1} \\ k * v_{2} \\ k * v_{3} \\ ... \\ k * v_{n} \\ \end{pmatrix}\)

所以向量的加法是两个向量相加,而向量的乘法是一个向量乘以一个标量,最终的结果都是向量。

从原理上介绍完向量的运算之后,我们再来编程实现一下。

class Vector:
    """
    向量
    """

    def __init__(self, values):
        self._values = values

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

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

    def __str__(self):
        return f"{tuple(self._values)}"

    def __getitem__(self, item):
        """基于索引获取向量的元素"""
        return self._values[item]

    def __add__(self, other):
        if type(other) is not Vector:
            raise TypeError("Vector 只能和 Vector 相加")
        if len(self) != len(other):
            raise TypeError("相加的两个 Vector 的维度必须相同")
        return Vector(
            [self[i] + other[i] for i in range(len(self))]
        )

    def __mul__(self, other):
        if type(other) is not int:
            raise TypeError("Vector 只能和一个整数相乘")
        return Vector(
            [self[i] * other for i in range(len(self))]
        )

    __rmul__ = __mul__

测试一下:

from vector import Vector

vec1 = Vector([1, 3, 5])
vec2 = Vector([2, 4, 6])

print(vec1 + vec2)  # (3, 7, 11)
print(vec1 * 5)  # (5, 15, 25)
print(5 * vec2)  # (10, 20, 30)

结果没有问题。

向量运算的基本性质

回忆小时候学习的数的运算,我们要先知道数的运算的基本性质,然后才能进行复杂的运算,比如加法交换律、乘法交换律、加法结合律、乘法结合律等等。对于向量运算也是如此,那么向量运算的基本性质能有哪些呢?

  • 加法交换律:\(\vec{u} + \vec{w} = \vec{w} + \vec{u}\)
  • 加法结合律:\((\vec{u} + \vec{w}) + \vec{v} = \vec{u} + (\vec{w} + \vec{v})\)
  • 乘法分配律:\(k * (\vec{u} + \vec{w}) = k * \vec{u} + k * \vec{w}\),或者 \((k + c) * \vec{u} = k * \vec{u} + c * \vec{u}\)
  • 乘法结合律:\((k * c) * \vec{u} = k * (c * \vec{u})\),另外整数 \(1\) 乘以某个向量,还等于该向量本身

这些性质虽然比较简单,但它们都是可以通过严格的数学证明出来,比如 \(k * (\vec{u} + \vec{w}) = k * \vec{u} + k * \vec{w}\)。我们基于向量的两个基本运算来进行证明:

\(k * (\vec{u} + \vec{w}) = k * (\begin{pmatrix} u_{1} \\ u_{2} \\ u_{3} \\ ... \\ u_{n} \\ \end{pmatrix} + \begin{pmatrix} w_{1} \\ w_{2} \\ w_{3} \\ ... \\ w_{n} \\ \end{pmatrix}) = k * \begin{pmatrix}u_{1} + w_{1} \\ u_{2} + w_{2} \\ u_{3} + w_{3} \\ ... \\ u_{n} + w_{n} \\ \end{pmatrix} = \begin{pmatrix}k * u_{1} + k * u_{1} \\ k * u_{2} + k * u_{2} \\ k * u_{3} + k * w_{3} \\ ... \\ k * u_{n} + k * w_{n} \\ \end{pmatrix}\)

\(k * \vec{u} + k * \vec{w} = k * \begin{pmatrix} u_{1} \\ u_{2} \\ u_{3} \\ ... \\ u_{n} \\ \end{pmatrix} + k * \begin{pmatrix} w_{1} \\ w_{2} \\ w_{3} \\ ... \\ w_{n} \\ \end{pmatrix} = \begin{pmatrix}k * u_{1} \\ k * u_{2} \\ k * u_{3} \\ ... \\ k * u_{n} \\ \end{pmatrix} + \begin{pmatrix} k * w_{1} \\ k * w_{2} \\ k * w_{3} \\ ... \\ k * w_{n} \\ \end{pmatrix} = \begin{pmatrix}k * u_{1} + k * w_{1} \\ k * u_{2} + k * w_{2} \\ k * u_{3} + k * w_{3} \\ ... \\ k * u_{n} + k * w_{n} \\ \end{pmatrix}\)

以上我们就严格证明了这个性质是正确的,当然你也可以试着证明其它性质。

零向量

0 和任意一个数相加,结果都等于该数本身。那么是否也存在一个向量 \(\vec{O}\),和任意一个向量 \(\vec{u}\) 相加之后,结果还等于 \(\vec{u}\) 呢?如果存在,那么 \(\vec{O}\) 我们就称之为零向量。

毫无疑问是存在的,根据向量的加法运算,我们可以得出 \(\vec{O}\) 是一个所有维度都为 \(0\) 的向量。需要说明的是,零向量的上方应该是没有箭头的,也就是 \(O\)。因为零向量的所有维度都是 \(0\),它和原点是重合的,所以它仍然是一个点,而不是一个线段。我们称呼它为零向量,只是为了说法上的统一,但零向量是没有方向的概念的,它就是一个点,并且和坐标原点重合。

既然提到了零向量,那么我们就可以引出负向量。在标量中:\(u + -u = 0\),那么在向量中也有:\(\vec{u} + \vec{-u} = 0\)

\(\vec{u} + \vec{-u} = \begin{pmatrix} u_{1} \\ u_{2} \\ u_{3} \\ ... \\ u_{n} \\ \end{pmatrix} + \begin{pmatrix} -u_{1} \\ -u_{2} \\ -u_{3} \\ ... \\ -u_{n} \\ \end{pmatrix} = \begin{pmatrix} u_{1} + -u_{1} \\ u_{2} + -u_{2} \\ u_{3} + -u_{3} \\ ... \\ u_{n} + -u_{n} \\ \end{pmatrix} = \begin{pmatrix} 0 \\ 0 \\ 0 \\ ... \\ 0 \\ \end{pmatrix}\)

证明也很简单。然后我们来实现一下零向量,我们希望用户通过 Vector 这个类能够快速构建出一个指定维度的零向量。

class Vector:
    """
    向量
    """

    def __init__(self, values):
        self._values = values

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

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

    def __str__(self):
        return f"{tuple(self._values)}"

    def __getitem__(self, item):
        """基于索引获取向量的元素"""
        return self._values[item]

    def __add__(self, other):
        if type(other) is not Vector:
            raise TypeError("Vector 只能和 Vector 相加")
        if len(self) != len(other):
            raise TypeError("相加的两个 Vector 的维度必须相同")
        return Vector(
            [self[i] + other[i] for i in range(len(self))]
        )

    def __mul__(self, other):
        if type(other) is not int:
            raise TypeError("Vector 只能和一个整数相乘")
        return Vector(
            [self[i] * other for i in range(len(self))]
        )

    __rmul__ = __mul__

    @classmethod
    def zero(cls, dim: int):
        """构建一个零向量"""
        return cls([0] * dim)

    def __neg__(self):
        """构建一个负向量"""
        return Vector(
            [-self[i] for i in range(len(self))]
        )

    def __eq__(self, other):
        if type(other) is not Vector:
            raise TypeError("Vector 只能和 Vector 进行比较")
        if len(self) != len(other):
            return False
        return all([self[i] == other[i] for i in range(len(self))])

老规矩,我们还是测试一下。

from vector import Vector

vec = Vector([1, 3, 5])
zero = Vector.zero(3)
print(vec)  # (1, 3, 5)
print(zero)  # (0, 0, 0)
# vec 和 零向量相加还等于 vec 本身
print(vec + zero == vec)  # True
# 创建负向量
print(-vec)  # (-1, -3, -5)

# 正向量和对应的负向量相加,等于零向量
print(vec + -vec == zero)  # True

结果没有问题,以上我们就通过编程实现了零向量。