线性代数第三章:向量的高级话题

规范化

上篇中我们提到了向量的方向性,其实向量除了方向还有大小。

u=(3,4)\vec{u}=(3,4),问 u\vec u 的大小是多少?

在二维平面中,我们可以根据勾股定理得,u\vec u 的大小 u=32+42=5\|\vec u\|=\sqrt{3^2+4^2}=5,其中 u\|\vec u\| 也叫向量的

之所以模定义为双竖线,是与二范数相对应,所谓二范数就是平方开根号,三范数就是立方开根号,其中二范数的形式与欧拉距离是一样的。

再举个例子:

u=OP=(2,3,5)\vec{u}=\overrightarrow{O P}=(2,3,5)

u\vec{u} 的大小是多少?

OA=22+32\|\vec{O A}\|=\sqrt{2^{2}+3^{2}}

OP=OA2+AP2=22+32+52\|\vec{O P}\|=\sqrt{\|\vec{OA}\|^{2}+\|\vec{AP}\|^{2}}=\sqrt{2^{2}+3^{2}+5^{2}}

u=22+32+52\|\vec{u}\|=\sqrt{2^{2}+3^{2}+5^{2}}

同理,nn 维度向量:

u=(u1,u2,,un)T\vec{u}=\left(u_{1}, u_{2}, \ldots, u_{n}\right)^{T}

u=u12+u22++un2\|\vec{u}\|=\sqrt{u_{1}^{2}+u_{2}^{2}+\ldots+u_{n}^{2}}

单位向量

模长为 1 ,长度不再重要,只表示方向。

假设有 u\vec u

u=(u1,u2,,un)T\vec{u}=\left(u_{1}, u_{2}, \ldots, u_{n}\right)^{T}

其单位向量:

u^=1uu=(u1u,u2u,,unu)\hat{u}=\frac{1}{\|\vec{u}\|} \cdot \vec{u}=\left(\frac{u_{1}}{\|\vec{u}\|}, \frac{u_{2}}{\|\vec{u}\|}, \ldots, \frac{u_{n}}{\|\vec{u}\|}\right)

u^=1\|\hat{u}\|=1 只表示方向

根据 u\vec u 求出 u^\hat u 的过程,叫做归一化、规范化(normalize)

单位向量有无数个。

坐标轴上以 1 为半径的圆上的点都是单位向量。

其中,指向坐标轴正方向的,叫做标准单位向量

蓝色为标准单位向量

在二维空间中,有两个标准单位向量:

e1=(1,0)e2=(0,1)\vec{e_1}=(1,0)\\ \vec{e_2}=(0,1)

在三维空间中,有三个标准单位向量:

e1=(1,0,0)e2=(0,1,0)e3=(0,0,1)\vec{e_1}=(1,0,0)\\ \vec{e_2}=(0,1,0)\\ \vec{e_3}=(0,0,1)

nn 维空间中,有 nn 个标准单位向量:

e1=(1,0,,0)e2=(0,1,,0)en=(0,0,,1)\vec{e_1}=(1,0,\dots,0)\\ \vec{e_2}=(0,1,\dots,0)\\ \dots\\ \vec{e_n}=(0,0,\dots,1)

向量的模和单位向量的实现:

__init__.py

from ._globals import EPSILON

_globals.py

EPSILON = 1e-8

Vector.py

import math
from ._globals import EPSILON


class Vector:

def __init__(self, lst):
self._values = list(lst)

@classmethod
def zero(cls, dim):
"""返回一个dim维的零向量"""
return cls([0] * dim)

def __add__(self, another):
"""向量加法,返回结果向量"""
assert len(self) == len(another), \
"Error in adding. Length of vectors must be same."

return Vector([a + b for a, b in zip(self, another)])

def __sub__(self, another):
"""向量减法,返回结果向量"""
assert len(self) == len(another), \
"Error in subtracting. Length of vectors must be same."

return Vector([a - b for a, b in zip(self, another)])

def norm(self):
"""返回向量的模"""
return math.sqrt(sum(e**2 for e in self))

def normalize(self):
"""返回向量的单位向量"""
if self.norm() < EPSILON:
raise ZeroDivisionError("Normalize error! norm is zero.")
return Vector(self._values) / self.norm()
# return 1 / self.norm() * Vector(self._values)
# return Vector([e / self.norm() for e in self])

def __mul__(self, k):
"""返回数量乘法的结果向量:self * k"""
return Vector([k * e for e in self])

def __rmul__(self, k):
"""返回数量乘法的结果向量:k * self"""
return self * k

def __truediv__(self, k):
"""返回数量除法的结果向量:self / k"""
return (1 / k) * self

def __pos__(self):
"""返回向量取正的结果向量"""
return 1 * self

def __neg__(self):
"""返回向量取负的结果向量"""
return -1 * self

def __iter__(self):
"""返回向量的迭代器"""
return self._values.__iter__()

def __getitem__(self, index):
"""取向量的第index个元素"""
return self._values[index]

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

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

def __str__(self):
return "({})".format(", ".join(str(e) for e in self._values))

向量的点乘与几何意义

向量点乘的定义

前面我们学习了向量的加法和数量相乘,说到向量的点乘,可能会让人直观的想是不是这样的乘法:

uv=(u1u2un)(v1v2vn)=(u1v1u2v2unvn)\vec{u} \cdot \vec{v}= \begin{pmatrix} u_{1} \\ u_{2} \\ \cdots \\ u_{n} \end{pmatrix} \cdot \begin{pmatrix} v_{1} \\ v_{2} \\ \cdots \\ v_{n} \end{pmatrix} = \begin{pmatrix} u_{1} \cdot v_{1} \\ u_{2} \cdot v_{2} \\ \cdots \\ u_{n} \cdot v_{n} \end{pmatrix}

向量中的元素分别相乘,但这是错误的,我们后面会讲到在几何中的理解就更容易理解了。

正确的两个向量相乘的方法是:

uv=(u1u2un)(v1v2vn)=sum(u1v1u2v2unvn)=u1v1+u2v2++unvn\vec{u} \cdot \vec{v}= \begin{pmatrix} u_{1} \\ u_{2} \\ \cdots \\ u_{n} \end{pmatrix} \cdot \begin{pmatrix} v_{1} \\ v_{2} \\ \cdots \\ v_{n} \end{pmatrix} = \operatorname{sum} \begin{pmatrix} u_{1} \cdot v_{1} \\ u_{2} \cdot v_{2} \\ \cdots \\ u_{n} \cdot v_{n} \end{pmatrix} =u_{1} \cdot v_{1}+u_{2} \cdot v_{2}+\dots+u_{n} \cdot v_{n}

和错误的相比就多了一步,相乘的结果要相加。

两个向量相乘后的结果是一个数(标量),而不再是一个向量。

向量的点乘(dot product)又叫内积(inner product)。

在这一小结中给出这样的定义,后续再做解释。

点乘的几何意义

在几何上,

uv=(u1u2un)(v1v2vn)=u1v1+u2v2++unvn=uvcosθ\begin{aligned} \vec{u} \cdot \vec{v}=& \begin{pmatrix} u_{1} \\ u_{2} \\ \cdots \\ u_{n} \end{pmatrix} \cdot \begin{pmatrix} v_{1} \\ v_{2} \\ \cdots \\ v_{n} \end{pmatrix} =u_{1} \cdot v_{1}+u_{2} \cdot v_{2}+\dots+u_{n} \cdot v_{n}\\ =&\|\vec{u}\|\cdot{\|\vec{v}\|}\cdot{\cos{\theta}} \end{aligned}

向量相乘的结果得到的标量恰好是两个向量模的乘积再乘以夹角的余弦值。

证明:

在二维空间中:

u\vec uv\vec v,绿色箭头是 uv\vec u-\vec v ,其中 uv\vec u-\vec v 可以理解成 u+(v)\vec u+ (-\vec v),同一个箭头方向,同一个箭头大小,平移到上图所示即可。

uv2=u2+v22uvcosθ\|\vec{u}-\vec{v}\|^{2}=\|\vec u\|^{2}+\| \vec v\|^{2}-2 \cdot\| \vec{u}\| \cdot\| v\| \cdot \cos \theta

移项之后:

uvcosθ=12(u2+v2uv2)=12(x12+y12+x22+y22(x1x2)2(y1y2)2)=12(x12+y12+x22+y22x12+2x1x2x22y12+2y1y2y22)=x1x2+y1y2\begin{aligned} \| \vec{u}\| \cdot\| v\| \cdot \cos \theta &=\frac{1}{2}\left(\|\vec u\|^{2}+\| \vec v\|^{2}-\| \vec{u}-\vec{v}\|^{2}\right) \\ &=\frac{1}{2}\left(x_{1}^{2}+y_{1}^{2}+x_{2}^{2}+y_{2}^{2}-\left(x_{1}-x_{2}\right)^{2}-\left(y_{1}-y_{2}\right)^{2}\right) \\ &=\frac{1}{2}\left(x_{1}^{2}+y_{1}^{2}+x_{2}^{2}+y_{2}^{2}-x_{1}^{2}+2 x_{1} x_{2}-x_{2}^{2}-y_{1}^{2}+2 y_{1} y_{2}-y_{2}^{2}\right) \\ &=x_{1} x_{2}+y_{1} y_{2} \end{aligned}

即证。

向量点乘的直观理解

上一节我们得出了下面向量点乘的公式:

uv=uvcosθ\vec{u} \cdot \vec{v} =\|\vec{u}\|\cdot{\|\vec{v}\|}\cdot{\cos{\theta}}

将它做一个小小的变形:

uv=(ucosθ)v\vec{u} \cdot \vec{v} =(\|\vec{u}\|\cdot{\cos{\theta}})\cdot{\|\vec{v}\|}

就可以把 ucosθ\|\vec{u}\|\cdot{\cos{\theta}} 看做 u\vec uv\vec v 方向上的投影,也就是蓝色线。

当然,也可以将 u\vec uv\vec v 都投影到 xx 轴和 yy 轴上:

同方向的数值相乘,不同方向的乘积相加,就是点乘的结果。

为什么 x2x_2 不去乘以 y1y_1 呢,因为他们相互垂直,相互不构成任何贡献。

向量点乘的实现:

Vector.py:

import math
from ._globals import EPSILON


class Vector:

def __init__(self, lst):
self._values = list(lst)

@classmethod
def zero(cls, dim):
"""返回一个dim维的零向量"""
return cls([0] * dim)

def __add__(self, another):
"""向量加法,返回结果向量"""
assert len(self) == len(another), \
"Error in adding. Length of vectors must be same."

return Vector([a + b for a, b in zip(self, another)])

def __sub__(self, another):
"""向量减法,返回结果向量"""
assert len(self) == len(another), \
"Error in subtracting. Length of vectors must be same."

return Vector([a - b for a, b in zip(self, another)])

def norm(self):
"""返回向量的模"""
return math.sqrt(sum(e**2 for e in self))

def normalize(self):
"""返回向量的单位向量"""
if self.norm() < EPSILON:
raise ZeroDivisionError("Normalize error! norm is zero.")
return Vector(self._values) / self.norm()

def dot(self, another):
"""向量点乘,返回结果标量"""
assert len(self) == len(another), \
"Error in dot product. Length of vectors must be same."

return sum(a * b for a, b in zip(self, another))

def __mul__(self, k):
"""返回数量乘法的结果向量:self * k"""
return Vector([k * e for e in self])

def __rmul__(self, k):
"""返回数量乘法的结果向量:k * self"""
return self * k

def __truediv__(self, k):
"""返回数量除法的结果向量:self / k"""
return (1 / k) * self

def __pos__(self):
"""返回向量取正的结果向量"""
return 1 * self

def __neg__(self):
"""返回向量取负的结果向量"""
return -1 * self

def __iter__(self):
"""返回向量的迭代器"""
return self._values.__iter__()

def __getitem__(self, index):
"""取向量的第index个元素"""
return self._values[index]

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

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

def __str__(self):
return "({})".format(", ".join(str(e) for e in self._values))

向量点乘的应用

uv=uvcosθ\vec{u} \cdot \vec{v} =\|\vec{u}\|\cdot{\|\vec{v}\|}\cdot{\cos{\theta}}

可得

cosθ=uvuv\cos{\theta}=\frac{\vec u\cdot \vec v}{\|\vec u\|\cdot\|\vec v\|}

  • 如果 θ=90\theta=90^\circuv=0\vec u \cdot \vec v=0
  • 如果 uv=0\vec u \cdot \vec v = 0,两个向量方向垂直
  • 如果 uv>0\vec u \cdot \vec v > 0,两个向量夹角为锐角
  • 如果 uv<0\vec u \cdot \vec v < 0,两个向量夹角为钝角

应用于推荐系统

计算投影点的坐标

投影点的距离:

d=vcosθ=uvuˉ\quad d=\|\vec v\| \cos \theta=\frac{\vec{u} \cdot \vec{v}}{\|\bar{u}\|}

投影点的方向:

u^\hat{u}

投影点的坐标:

Pv=du^P_{v}=d \cdot \hat{u}

Numpy 的使用

import numpy as np

if __name__ == "__main__":

print(np.__version__)

# np.array 基础
lst = [1, 2, 3]
lst[0] = "Linear Algebra"
print(lst)

vec = np.array([1, 2, 3])
print(vec)
# vec[0] = "Linear Algebra"
# vec[0] = 666
# print(vec)

# np.array的创建
print(np.zeros(5))
print(np.ones(5))
print(np.full(5, 666))

# np.array的基本属性
print(vec)
print("size =", vec.size)
print("size =", len(vec))
print(vec[0])
print(vec[-1])
print(vec[0: 2])
print(type(vec[0: 2]))

# np.array的基本运算
vec2 = np.array([4, 5, 6])
print("{} + {} = {}".format(vec, vec2, vec + vec2))
print("{} - {} = {}".format(vec, vec2, vec - vec2))
print("{} * {} = {}".format(2, vec, 2 * vec))
print("{} * {} = {}".format(vec, vec2, vec * vec2))
print("{}.dot({}) = {}".format(vec, vec2, vec.dot(vec2)))

print(np.linalg.norm(vec))
print(vec / np.linalg.norm(vec))
print(np.linalg.norm(vec / np.linalg.norm(vec)))

# zero3 = np.zeros(3)
# print(zero3 / np.linalg.norm(zero3))


   转载规则


《线性代数第三章:向量的高级话题》 Harbor Zeng 采用 知识共享署名 4.0 国际许可协议 进行许可。
 上一篇
线性代数第四章:矩阵不仅仅是n×m个数 线性代数第四章:矩阵不仅仅是n×m个数
什么是矩阵 矩阵是对向量的扩展 (12345678910111213141516)\begin{pmatrix} 1 & 2 & 3 & 4 \\ 5 & 6 & 7 & 8 \\ 9 & 10 & 11 & 12 \\ 13 & 14 & 15 & 16 \end{pmatrix} ⎝⎜⎜⎛​15
2020-09-16
下一篇 
线性代数第二章:一切从向量开始 线性代数第二章:一切从向量开始
什么是向量 为什么线性代数很重要?线性代数使数学的研究从一个数扩展到一组数。 一组数就是向量(Vector) 研究一组数有什么用?最基本的出发点:表示方向。假如说 5km,我们不知道往哪个方向去的 5km,在二维平面中,至少需要两个数,才能确定方向, 向量是线性代数研究的基本元素 向量的起始点不重要,为方便起见,全部看做从原点出发 向量的顺序重要,不同的顺序是不同的
2020-09-04
  目录