This commit is contained in:
louzefeng
2024-07-09 18:38:56 +00:00
parent 8bafaef34d
commit bf99793fd0
6071 changed files with 1017944 additions and 0 deletions

View File

@@ -0,0 +1,120 @@
<audio id="audio" title="33 | 线性代数:线性代数到底都讲了些什么?" controls="" preload="none"><source id="mp3" src="https://static001.geekbang.org/resource/audio/ac/3c/acff92ddbb7ef15c383ff65e29b0413c.mp3"></audio>
你好,我是黄申。
通过第二模块的学习,我想你对概率统计在编程领域,特别是机器学习算法中的应用,已经有了一定理解。概率统计关注的是随机变量及其概率分布,以及如何通过观测数据来推断这些分布。可是,在解决很多问题的时候,我们不仅要关心单个变量之间的关系,还要进一步研究多个变量之间的关系,最典型的例子就是基于多个特征的信息检索和机器学习。
在信息检索中,我们需要考虑多个关键词特征对最终相关性的影响,而在机器学习中,无论是监督式还是非监督式学习,我们都需要考虑多个特征对模型拟合的影响。在研究多个变量之间关系的时候,线性代数成为了解决这类问题的有力工具。
另一方面在我们日常生活和工作中很多问题都可以线性化小到计算两个地点之间的距离大到计算互联网中全部网页的PageRank。所以为了使用编程来解决相应的问题我们也必须掌握一些必要的线性代数基础知识。因此我会从线性代数的基本概念出发结合信息检索和机器学习领域的知识详细讲解线性代数的运用。
关于线性代数,究竟都需要掌握哪些方面的知识呢?我们今天就来看一看,让你对之后一段时间所要学习的知识有个大体的了解。
## 向量和向量空间
我们之前所谈到的变量都属于**标量**Scalar。它只是一个单独的数字而且不能表示方向。从计算机数据结构的角度来看标量就是编程中最基本的变量。这个很好理解你可以回想一下刚开始学习编程时接触到的标量类型的变量。
和标量对应的概念,就是线性代数中最常用、也最重要的概念,**向量**Vector也可以叫作矢量。它代表一组数字并且这些数字是有序排列的。我们用数据结构的视角来看向量可以用数组或者链表来表达。
后面的文章里,我会用加粗的小写字母表示一个向量,例如$x$,而$x_{1}x_{2}x_{3}x_{n}$等等来表示向量中的每个元素这里面的n就是向量的维。
<img src="https://static001.geekbang.org/resource/image/0e/82/0e03179f68d39d5a26ad18acd4bbda82.png" alt="">
向量和标量最大的区别在于,向量除了拥有数值的大小,还拥有方向。向量或者矢量中的“向”和“矢”这两个字,都表明它们是有方向的。你可能会问,为什么这一串数字能表示方向呢?
这是因为,如果我们把某个向量中的元素看作坐标轴上的坐标,那么这个向量就可以看作空间中的一个点。以原点为起点,以向量代表的点为终点,就能形成一条有向直线。而这样的处理其实已经给向量赋予了代数的含义,使得计算的过程中更加直观。在后面讨论向量空间、向量夹角、矩阵特征值等概念的时候,我会进一步展示给你看。
由于一个向量包含了很多个元素,因此我们自然地就可以把它运用在机器学习的领域。上一个模块,我讲过如何把自然界里物体的属性,转换为能够用数字表达的特征。由于特征有很多维,因此我们可以使用向量来表示某个物体的特征。其中,向量的每个元素就代表一维特征,而元素的值代表了相应特征的值,我们称这类向量为**特征向量**Feature Vector
需要注意的是,这个特征向量和**矩阵的特征向量**Eigenvector是两码事。那么矩阵的特征向量是什么意思呢矩阵的几何意义是坐标的变换。如果一个矩阵存在特征向量和特征值那么这个矩阵的特征向量就表示了它在空间中最主要的运动方向。如果你对这几个概念还不太理解也不用担心在介绍矩阵的时候我会详细说说什么是矩阵的特征向量。
## 向量的运算
标量和向量之间可以进行运算,比如标量和向量相加或者相乘时,我们直接把标量和向量中的每个元素相加或者相乘就行了,这个很好理解。可是,向量和向量之间的加法或乘法应该如何进行呢?我们需要先定义向量空间。向量空间理论上的定义比较繁琐,不过二维或者三维的坐标空间可以很好地帮助你来理解。这些空间主要有几个特性:
<li>
空间由无穷多个的位置点组成;
</li>
<li>
这些点之间存在相对的关系;
</li>
<li>
可以在空间中定义任意两点之间的长度,以及任意两个向量之间的角度;
</li>
<li>
这个空间的点可以进行移动。
</li>
有了这些特点,我们就可以定义向量之间的加法、乘法(或点乘)、距离和夹角等等。
两个向量之间的加法,首先它们需要维度相同,然后是对应的元素相加。
<img src="https://static001.geekbang.org/resource/image/ce/0b/ceff73e9e2f4f8bc3bc8d72e5cc9d80b.png" alt="">
所以说,向量的加法实际上就是把几何问题转化成了代数问题,然后用代数的方法实现了几何的运算。我下面画了一张图,来解释二维空间里,两个向量的相加,看完你就能理解了。
<img src="https://static001.geekbang.org/resource/image/bf/9e/bf3780041ecb702bd3ae130aa4b4119e.png" alt="">
在这张图中有两个向量x和y它们的长度分别是x和y它们的相加结果是x+y这个结果所对应的点相当于x向量沿着y向量的方向移动y或者是y向量沿着x向量的方向移动x
向量之间的乘法默认是点乘向量x和y的点乘是这么定义的
<img src="https://static001.geekbang.org/resource/image/90/b2/907f5f302897d2ca31444d6f2144cdb2.png" alt="">
点乘的作用是把相乘的两个向量转换成了标量它有具体的几何含义。我们会用点乘来计算向量的长度以及两个向量间的夹角所以一般情况下我们会默认向量间的乘法是点乘。至于向量之间的夹角和距离它们在向量空间模型Vector Space Model中发挥了重要的作用。信息检索和机器学习等领域充分利用了向量空间模型计算不同对象之间的相似程度。在之后的专栏里我会通过向量空间模型详细介绍向量点乘以及向量间夹角和距离的计算。
## 矩阵的运算
矩阵由多个长度相等的向量组成其中的每列或者每行就是一个向量。因此我们把向量延伸一下就能得到矩阵Matrix
从数据结构的角度看向量是一维数组那矩阵就是一个二维数组。如果二维数组里绝大多数元素都是0或者不存在的值那么我们就称这个矩阵很稀疏Sparse。对于稀疏矩阵我们可以使用哈希表的链地址法来表示。所以矩阵中的每个元素有两个索引。
我用加粗的斜体大写字母表示一个矩阵,例如$X$,而$X_{12}X_{22}X_{nm}$等等表示矩阵中的每个元素而这里面的n和m分别表示矩阵的行维数和列维数。
我们换个角度来看向量其实也是一种特殊的矩阵。如果一个矩阵是n × m维那么一个n × 1的矩阵也可以称作一个n维列向量而一个1 × m矩阵也称为一个m维行向量。
同样,我们也可以定义标量和矩阵之间的加法和乘法,我们只需要把标量和矩阵中的每个元素相加或相乘就可以了。剩下的问题就是,矩阵和矩阵之间是如何进行加法和乘法的呢?矩阵加法比较简单,只要保证参与操作的两个矩阵具有相同的行维度和列维度,我们就可以把对应的元素两两相加。而乘法略微繁琐一些,如果写成公式就是这种形式:
<img src="https://static001.geekbang.org/resource/image/64/85/6466b4df93b4dc7bfd7d73ca258f0185.png" alt="">
其中,矩阵$Z$为矩阵$X$和$Y$的乘积,$X$是形状为i x k的矩阵而$Y$是形状为k × j的矩阵。$X$的列数k必须和$Y$的行数k相等两者才可以进行这样的乘法。
我们可以把这个过程看作矩阵$X$的行向量和矩阵$Y$的列向量两两进行点乘,我这里画了张图,你理解了这张图就不难记住这个公式了。
<img src="https://static001.geekbang.org/resource/image/d7/cf/d718c22f06c9250028867c74e5daedcf.png" alt="">
两个矩阵中对应元素进行相乘,这种操作也是存在的,我们称它为元素**对应乘积**或者Hadamard乘积。但是这种乘法咱们用得比较少所以你只要知道有这个概念就可以了。
除了加法和乘法,矩阵还有一些其他重要的操作,包括转置、求逆矩阵、求特征值和求奇异值等等。
**转置**Transposition是指矩阵内的元素行索引和纵索引互换例如$X_{ij}$就变为$X_{ji}$相应的矩阵的形状由转置前的n × m变为转置后的m × n。从几何的角度来说矩阵的转置就是原矩阵以对角线为轴进行翻转后的结果。下面这张图展示了矩阵$X$转置之后的矩阵$X$
<img src="https://static001.geekbang.org/resource/image/a0/c1/a024d2504f9a6351b5b815ff251a7bc1.png" alt="">
除了转置矩阵另一个重要的概念是逆矩阵。为了理解逆矩阵或矩阵逆Matrix Inversion我们首先要理解单位矩阵Identity Matrix。单位矩阵中所有沿主对角线的元素都是1而其他位置的所有元素都是0。通常我们只考虑单位矩阵为方阵的情况也就是行数和列数相等我们把它记做$I_{n}$$n$表示维数。我这里给出一个$I_{5}$的示例。
<img src="https://static001.geekbang.org/resource/image/56/00/56451a7a823444abc5791f4bc7ea6800.png" alt="">
如果有矩阵$X$,我们把它的逆矩阵记做$X^{-1}$,两者相乘的结果是单位矩阵,写成公式就是这种形式:
<img src="https://static001.geekbang.org/resource/image/51/65/51adb30f4c592f3a10f2490f75913f65.png" alt="">
特征值和奇异值的概念以及求解比较复杂了,从大体上来理解,它们可以帮助我们找到矩阵最主要的特点。通过这些操作,我们就可以在机器学习算法中降低特征向量的维度,达到特征选择和变换的目的。我会在后面的专栏,结合案例给你详细讲解。
## 总结
相对于概率统计,线性代数中的基本概念和知识点可能没有那么多。但是对于刚入门的初学者,这些内容理解起来会比较费力。在这一节里,我进行了大致的梳理,帮助你学习。
标量和向量的区别,标量只是单独的一个数,而向量是一组数。矩阵是向量的扩展,就是一个二维数组。我们可以使用哈希表的链地址法表示稀疏矩阵。
标量和向量或矩阵的加法、乘法比较简单,就是把这个标量和向量或矩阵中所有的元素轮流进行相加或相乘。向量之间的加法和矩阵之间的加法,是把两者对应的元素相加。向量之间的相乘分为叉乘和点乘,我在专栏里默认向量乘法为点乘。而矩阵的乘法默认为左矩阵的行向量和右矩阵的列向量两两点乘。
说到这里,你可能还是不太理解线性代数对于编程有什么用处。在这个模块之后的内容中,我会详细介绍向量空间模型、线性方程组、矩阵求特征值和奇异值分解等,在信息检索和机器学习领域中,有怎样的应用场景。
## 思考题
之前你对线性代数的认识是什么样的呢?对这块内容,你觉得最难的是什么?
欢迎留言和我分享,也欢迎你在留言区写下今天的学习笔记。你可以点击“请朋友读”,把今天的内容分享给你的好友,和他一起精进。

View File

@@ -0,0 +1,155 @@
<audio id="audio" title="34 | 向量空间模型:如何让计算机理解现实事物之间的关系?" controls="" preload="none"><source id="mp3" src="https://static001.geekbang.org/resource/audio/23/14/2368461cd0126aa0457233a20132b214.mp3"></audio>
你好,我是黄申。
之前我们讲过如何让计算机理解现实世界中的事物,方法是把事物的各种特性转为机器所能理解的数据字段。而这些数据字段,在机器学习里通常被称为特征。有了特征,我们不仅可以刻画事物本身,还能刻画不同事物之间的关系。
上一个模块我们只是了解了监督式学习,重点考察了特征和分类标签之间的关系。但是在信息检索和非监督式学习中,我们更关注的是不同事物之间的相似程度。这就需要用到线性代数中的向量空间模型了。
提到向量空间模型,你可能对其中的概念有点陌生,所以我会从向量空间的基本概念开始说起,讲到向量空间模型的相关知识,最后再讲讲它是如何应用在不同的编程中的。
## 什么是向量空间?
上一节,我讲到了向量和向量空间的一些基本概念。为了帮助你更好地理解向量空间模型,我这里给出向量和向量空间的严格定义。
首先假设有一个数的集合$F$,它满足“$F$中任意两个数的加减乘除法(除数不为零)的结果仍然在这个$F$中”,我们就可以称$F$为一个“域”。我们处理的数据通常都是实数,所以这里我只考虑实数域。而如果域$F$里的元素都为实数,那么$F$就是实数域。
如果$x_{1}x_{2}……x_{n}∈F$,那么$F$上的$n$维向量就是:
<img src="https://static001.geekbang.org/resource/image/d9/8b/d93900b041abe4a77690635691662e8b.png" alt="">
或者写成转置的形式:
<img src="https://static001.geekbang.org/resource/image/96/db/961ed40b59d1c2664d688a9b10ab96db.png" alt="">
向量中第$i$个元素,也称为第$i$个分量。$F_{n}$是由$F$上所有$n$维向量构成的集合。
我们已经介绍过向量之间的加法,以及标量和向量的乘法。这里我们使用这两个操作来定义向量空间。
假设$V$是$F_{n}$的非零子集,如果对任意的向量$x$、向量$y∈V$,都有$(x+y)∈V$,我们称为$V$对向量的加法封闭;对任意的标量$k∈V$,向量$x∈V$,都有$kx$属于$V$,我们称$V$对标量与向量的乘法封闭。
如果$V$满足向量的加法和乘法封闭性,我们就称$V$是$F$上的向量空间。向量空间除了满足这两个封闭性,还满足基本运算法则,比如交换律、结合律、分配律等等。这里介绍的定义和法则有点多,不过你可以不用都死记硬背下来。只要用的时候,知道有这些东西就可以了。
## 向量空间的几个重要概念
有了刚才的铺垫,接下来我们来看几个重要的概念:向量的长度、向量之间的距离和夹角。
### 向量之间的距离
有了向量空间我们就可以定义向量之间的各种距离。我们之前说过可以把一个向量想象为n维空间中的一个点。而向量空间中两个向量的距离就是这两个向量所对应的点之间的距离。距离通常都是大于0的这里我介绍几种常用的距离包括曼哈顿距离、欧氏距离、切比雪夫距离和闵可夫斯基距离。
- **曼哈顿距离**Manhattan Distance
这个距离度量的名字由来非常有趣。你可以想象一下,在美国人口稠密的曼哈顿地区,从一个十字路口开车到另外一个十字路口,驾驶距离是多少呢?当然不是两点之间的直线距离,因为你无法穿越挡在其中的高楼大厦。你只能驾车绕过这些建筑物,实际的驾驶距离就叫作曼哈顿距离。由于这些建筑物的排列都是规整划一的,形成了一个个的街区,所以我们也可以形象地称把它为“城市街区”距离。我这里画了张图方便你理解这种距离。
<img src="https://static001.geekbang.org/resource/image/3c/f2/3c07f6da33999ebc41e53be90ba35af2.png" alt="">
从图中可以看出从A点到B点有多条路径但是无论哪条曼哈顿距离都是一样的。
在二维空间中,两个点(实际上就是二维向量)$x(x_{1},x_{2})$与$y(y_{1},y_{2})$间的曼哈顿距离是:
<img src="https://static001.geekbang.org/resource/image/9b/c0/9bb6b5eb558d5582456d19be80b300c0.png" alt="">
推广到$n$维空间,曼哈顿距离的计算公式为:
<img src="https://static001.geekbang.org/resource/image/f8/e9/f81a89126f0a8adb7bb578fef17409e9.png" alt="">
其中$n$表示向量维度,$x_{i}$表示第一个向量的第$i$维元素的值,$y_{i}$表示第二个向量的第$i$维元素的值。
- **欧氏距离**Euclidean Distance
欧氏距离其实就是欧几里得距离。欧氏距离是一个常用的距离定义指在n维空间中两个点之间的真实距离在二维空间中两个点$x(x_{1},x_{2})$与$y(y_{1},y_{2})$间的欧氏距离是:
<img src="https://static001.geekbang.org/resource/image/51/29/5182e68ae521212464a0c8a1dd26d829.png" alt="">
推广到n维空间欧氏距离的计算公式为
<img src="https://static001.geekbang.org/resource/image/3b/2b/3b4b64fec423d02bb6b511ee37c4412b.png" alt="">
- **切比雪夫距离**Chebyshev Distance
切比雪夫其实是在模拟国际象棋里国王的走法。国王可以走临近8个格子里的任何一个那么国王从格子$(x_{1},x_{2})$走到格子$(y_{1},y_{2})$最少需要多少步呢?其实就是二维空间里的切比雪夫距离。
一开始为了走尽量少的步数国王走的一定是斜线所以横轴和纵轴方向都会减1直到国王的位置和目标位置在某个轴上没有差距这个时候就改为沿另一个轴每次减1。所以国王走的最少格子数是$|x_{1}-y_{1}|$和$|x_{2}-y_{2}|$这两者的较大者。所以,在二维空间中,两个点$x(x_{1},x_{2})$与$y(y_{1},y_{2})$间的切比雪夫距离是:
<img src="https://static001.geekbang.org/resource/image/19/85/196f8f04a75ad0c48f0f425a6e13aa85.png" alt="">
推广到n维空间切比雪夫距离的计算公式为
<img src="https://static001.geekbang.org/resource/image/ba/f6/ba2ddcb9a07dfe8bff595abf1fa68bf6.png" alt="">
上述三种距离,都可以用一种通用的形式表示,那就是闵可夫斯基距离,也叫闵氏距离。在二维空间中,两个点$x(x_{1},x_{2})$与$y(y_{1},y_{2})$间的闵氏距离是:
<img src="https://static001.geekbang.org/resource/image/0f/db/0fdcaa63c585cb2b530f03e69dcab1db.png" alt="">
两个$n$维变量$x(x_{1},x_{2},…,x_{n})$与$y(y_{1},y_{2},…,y_{n})$间的闵氏距离的定义为:
<img src="https://static001.geekbang.org/resource/image/6f/8f/6f201ca2a097b7763ae4bfbf545e9c8f.png" alt="">
其中$p$是一个变参数尝试不同的p取值你就会发现
<li>
当$p=1$时,就是曼哈顿距离;
</li>
<li>
当$p=2$时,就是欧氏距离;
</li>
<li>
当$p$趋近于无穷大的时候,就是切比雪夫距离。这是因为当$p$趋近于无穷大的时候,最大的$|x_{i}-y_{i}|$会占到全部的权重。
</li>
距离可以描述不同向量在向量空间中的差异,所以可以用于描述向量所代表的事物之差异(或相似)程度。
### 向量的长度
有了向量距离的定义,向量的长度就很容易理解了。向量的长度,也叫向量的模,是向量所对应的点到空间原点的距离。通常我们使用欧氏距离来表示向量的长度。
当然,我们也可以使用其他类型的距离。说到这里,我也提一下“范数”的概念。范数满足非负性、齐次性、和三角不等式。你可以不用深究这三点的含义,不过你需要知道范数常常被用来衡量某个向量空间中向量的大小或者长度。
$L_{1}$范数$||x||$ ,它是为$x$向量各个元素绝对值之和,对应于向量$x$和原点之间的曼哈顿距离。
$L_{2}$范数$||x||_{2}$ ,它是$x$向量各个元素平方和的$\frac{1}{2}$次方,对应于向量$x$和原点之间的欧氏距离。
$L_{p}$范数$||x||_{p}$ ,为$x$向量各个元素绝对值$p$次方和的1/p次方对应于向量$x$和原点之间的闵氏距离。
$L_{∞}$范数$||x||_{∞}$ ,为$x$向量各个元素绝对值最大那个元素的绝对值,对应于向量$x$和原点之间的切比雪夫距离。
所以在讨论向量的长度时我们需要弄清楚是L几范数。
### 向量之间的夹角
在理解了向量间的距离和向量的长度之后,我们就可以引出向量夹角的余弦,它计算了空间中两个向量所形成夹角的余弦值,具体的计算公式我列在了下面:
<img src="https://static001.geekbang.org/resource/image/47/58/47a3eb754bc98caab86dc554b4cf7558.png" alt="">
从公式可以看出分子是两个向量的点乘而分母是两者长度或L2范数的乘积而L2范数可以使用向量点乘自身的转置来实现。夹角余弦的取值范围在[-1,1]当两个向量的方向重合时夹角余弦取最大值1当两个向量的方向完全相反夹角余弦取最小值-1。值越大说明夹角越小两点相距就越近值越小说明夹角越大两点相距就越远。
## 向量空间模型
理解了向量间距离和夹角余弦这两个概念,你再来看**向量空间模型**Vector Space Model就不难了。
向量空间模型假设所有的对象都可以转化为向量,然后使用向量间的距离(通常是欧氏距离)或者是向量间的夹角余弦来表示两个对象之间的相似程度。我使用下图来展示空间中向量之间的距离和夹角。
<img src="https://static001.geekbang.org/resource/image/2b/ec/2b2fb28b3bfb8fc43831469f537068ec.png" alt="">
由于夹角余弦的取值范围已经在-1到1之间而且越大表示越相似所以可以直接作为相似度的取值。相对于夹角余弦欧氏距离ED的取值范围可能很大而且和相似度呈现反比关系所以通常要进行1/(ED+1)这种归一化。
当ED为0的时候变化后的值就是1表示相似度为1完全相同。当ED趋向于无穷大的时候变化后的值就是0表示相似度为0完全不同。所以这个变化后的值取值范围是0到1之间而且和相似度呈现正比关系。
早在上世纪的70年代人们把向量空间模型运用于信息检索领域。由于向量空间可以很形象地表示数据点之间的相似程度因此现在我们也常常把这个模型运用在基于相似度的一些机器学习算法中例如K近邻KNN分类、K均值K-Means)聚类等等。
## 总结
为了让计算机理解现实世界中的事物,我们会把事物的特点转换成为数据,并使用多维度的特征来表示某个具体的对象。多个维度的特征很容易构成向量,因此我们就可以充分利用向量和向量空间,来刻画事物以及它们之间的关系。
我们可以在向量空间中定义多种类型的向量长度和向量间距离,用于衡量向量之间的差异或者说相似程度。此外,夹角余弦也是常用的相似度衡量指标。和距离相比,夹角余弦的取值已经控制在[-1, 1]的范围内,不会因为异常点所产生的过大距离而受到干扰。
向量空间模型充分利用了空间中向量的距离和夹角特性,来描述文档和查询之间的相似程度,或者说相关性。虽然向量空间模型来自信息检索领域,但是也被用广泛运用在机器学习领域中。在接下来的文章里,我会结合具体的案例,分别来说说如何在这些领域使用向量空间模型。
## 思考题
假设在三维空间中有两个点,它们的坐标分别是(3, -1, 8)和(-2, 3, -6),请计算这两个点之间的欧氏距离和夹角余弦。
欢迎留言和我分享,也欢迎你在留言区写下今天的学习笔记。你可以点击“请朋友读”,把今天的内容分享给你的好友,和他一起精进。

View File

@@ -0,0 +1,111 @@
<audio id="audio" title="35 | 文本检索:如何让计算机处理自然语言?" controls="" preload="none"><source id="mp3" src="https://static001.geekbang.org/resource/audio/54/6c/54f319503c11df36ccdd91a1513fa56c.mp3"></audio>
你好,我是黄申。
上一节,我详细解释了向量空间和向量空间模型。你也许觉得理论上的内容还是过于抽象,不太好理解。别急,今天我就来具体演示一下如何使用这个模型。由于学者们最初是在信息检索领域使用这个模型的,所以我会结合文本信息检索领域的知识,阐述如何在这个领域使用向量空间模型。
## 什么是信息检索?
首先,我们先来看一下,什么是信息检索,以及最基本的排序模型有哪些。这样,你就能理解为什么我们需要使用向量空间模型了。
现在的信息检索技术已经相当成熟,并影响我们日常生活的方方面面。搜索引擎就是这项技术的最佳体现,人们输入一个查询,然后系统就能返回相关的信息。
笼统地说,**信息检索就是让计算机根据用户信息需求,从大规模、非结构化的数据中,找出相关的资料。**这里的“非结构化”其实是针对经典的关系型数据库Relation Database而言的比如DB2、Oracle DB、MySQL等等。
数据库里的记录都有严格的字段定义Schema是“结构化”数据的典型代表。相反“非结构化”没有这种严格的定义互联网世界里所存储的海量文本就是“非结构化“数据的典型代表。因为这些文章如果没有经过我们的分析对于其描述的主题、写作日期、作者等信息我们是一无所知的。自然我们也就无法将其中的内容和已经定义好的数据库字段进行匹配所以这也是数据库在处理非结构化数据时非常乏力的原因。这时候就需要采用信息检索的技术来帮助我们。
在信息检索中,相关性是个永恒的话题。“这篇文章是否和体育相关?”当被问及这个问题,我们要大致看一下文章的内容,才能做出正确的判断。可是,迄今为止,计算机尚无法真正懂得人类的语言,它们该如何判定呢?好在科学家们设计了很多模型,帮助计算机处理基于文本的相关性。
最简单的模型是布尔模型它借助了逻辑布尔代数的基本思想。如果我想看一篇文章是否关于体育最简单的方法莫过于看看其中是否提到和体育相关的关键词比如“足球”“NBA”“奥运会”等等。如果有就相当于返回值为“真”我就认为这篇文章就是相关的。如果没有就相当于返回值为“假”我就认为这篇文章不相关。这就是布尔模型的核心思想。
这里我列出了要求全部关键词都出现的查询条件。
<img src="https://static001.geekbang.org/resource/image/81/3d/8105ba571514b27e93b500462090603d.png" alt="">
当然我们可以根据具体的需求在查询条件中加入“OR”允许进行部分关键词的匹配。
和布尔模型相比向量空间模型更为复杂也更为合理。如我之前介绍的此模型的重点是将文档转换为向量然后比较向量之间的距离或者相似程度。在转换的时候我们通常会使用词包Bag Of Word的方式忽略了单词在文章中出现的顺序简化计算复杂度。类似地这个模型也会把用户输入的查询转换为向量。如此一来相关性问题就转化为计算查询向量和文档向量之间的距离或者相似度了。距离越小或者说相似度越高那么我们就认为相关度越高。
相对于标准的布尔数学模型,向量空间模型的主要优势在于,允许文档和查询之间的部分匹配
连续的相似程度、以及基于这些的的排序。结果不再局限于布尔模型的“真”“假”值。此外,单词或词组的权重可以不再是二元的,而是可以使用例如[tf-idf](https://zh.wikipedia.org/wiki/Tf-idf)term frequencyinverse document frequency的机制。
上面我简要地说明了为什么在信息检索领域,向量空间模型相比布尔模型更具优势。接下来,我来详细讲解如何在一个文档集合上,使用向量空间模型,查找和给定查询相关的文档。
## 信息检索中的向量空间模型
整个方法从大体上来说,可以分为四个主要步骤。
第一步,把文档集合都转换成向量的形式。
第二步,把用户输入的查询转换成向量的形式,然后把这个查询的向量和所有文档的向量,进行比对,计算出基于距离或者夹角余弦的相似度。
第三步,根据查询和每个文档的相似度,找出相似度最高的文档,认为它们是和指定查询最相关的。
第四步,评估查询结果的相关性。
这一节,我主要侧重讲解和向量空间模型最相关的前两步。
### 把文档转为特征向量
任何向量都有两个主要的构成要素:维度和取值。这里的维度表示向量有多少维分量、每个分量的含义是什么,而取值表示每个分量的数值是多少。而原始的文本和向量差别很大,我们需要经过若干预处理的步骤。
我们首先来看看如何为文本创建向量的维度。简单地说,我们要把文章中唯一的单词或者词组,作为向量的一个维度。
在概率统计的模块中我说过如何基于词包Bag of Word的方式来预处理文本包括针对中文等语系的分词操作、针对英文等拉丁语系的词干Stemming和归一化Normalization处理以及所有语言都会碰到的停用词Stopword、同义词和扩展词处理。完成了前面这些预处理我们就可以获得每篇文档出现的单词和词组。而通过对所有文档中的单词和词组进行去重我们就可以构建整个文档集合的词典Vocabulary。向量空间模型把词典中的每个词条作为向量的一个维度。
有了向量的维度我们再来考虑每个维度需要取什么值。最简单的方法是用“1”表示这个词条出现在文档中“0”表示没有出现。不过这种方法没有考虑每个词的权重。有些词经常出现它更能表达文章的主要思想对于计算机的分析能起到更大的作用。对于这点有两种常见的改进方法分别是使用词频和词频x逆文档频率来实现的。
我们先来看基于词频的方法。假设我们有一个文档集合cd表示c中的一个文档t表示一个单词那么我们使用tf表示词频Term Frequency也就是一个词t在文档d中出现的次数。这种方法的假设是如果某个词在文档中的tf越高那么这个词对于这个文档来说就越重要。
另一种改进方法不仅考虑了tf还考虑了idf。这里idf表示逆文档频率Inverse Document Frequency
首先df表示文档频率Document Frequency也就是文档集合c中出现某个词t的文档数量。一般的假设是某个词t在文档集合c中出现在越多的文档中那么其重要性越低反之则越高。刚开始可能感觉有点困惑但是仔细想想不难理解。
在讨论体育的文档集合中“体育”一词可能会出现在上万篇文章中它的出现并不能使得某篇文档变得和“体育”这个主题更相关。相反如果只有3篇文章讨论到中国足球那么这3篇文章和中国足球的相关性就远远高于其他文章。“中国足球”这个词组在文档集合中就应该拥有更高的权重用户检索“中国足球”时这3篇文档应该排在更前面。所以我们通常用df的反比例指标idf来表示这种重要程度基本公式如下
<img src="https://static001.geekbang.org/resource/image/3e/58/3e72f94ec09ad6f73f0428141811d658.png" alt="">
其中N是整个文档集合中文章数量log是为了确保idf分值不要远远高于tf而埋没tf的贡献。这样一来单词t的df越低其idf越高t的重要性越高。那么综合起来tf-idf的基本公式表示如下
<img src="https://static001.geekbang.org/resource/image/86/78/86a0881bc6518898640bb368f78be878.png" alt="">
一旦完成了从原始文档到向量的转换我们就可以接受用户的查询Query
### 查询和文档的匹配
在计算查询和文档的相似度之前,我们还需要把查询转换成向量。由于用户的查询也是由自然语言组成,所以这个转换的流程和文档的转换流程是基本一致的。不过,查询也有它的特殊性,因此需要注意下面几个问题。
第一查询和文档长度不一致。人们输入的查询通常都很短甚至都不是一个句子而只是几个关键词。这种情况下你可能会觉得两个向量的维度不同无法计算它们之间的距离或夹角余弦。对于这种情况我们可以使用文档字典中所有的词条来构建向量。如果某维分量所对应的词条出现在文档或者查询中就取1、tf或tf-idf值如果没有就取0。这样文档向量和查询向量的维度就相同了只是查询向量更稀疏、拥有多维度的0。
第二,查询里出现了文档集合里没有的词。简单的做法是直接去除这维分量,也可以使用相对于其他维度来说极小的一个数值,这和分类中的平滑技术类似。
第三查询里词条的idf该如何计算。如果我们使用tf-idf机制来计算向量中每个维度的取值那么就要考虑这个问题。由于查询本身并不存在文档集合的概念所以也就不存在df和idf。对于这种情况我们可以借用文档集合里对应词条的idf。
把查询转换成向量之后,我们就可以把这个查询的向量和所有文档的向量依次对比,看看查询和哪些文档更相似。我们可以结合上一节所说的,计算向量之间的距离或者夹角余弦。由于夹角余弦不用进行归一化,所以这种方法更为流行。需要注意的是,信息检索里,夹角余弦的取值范围通常是[0,1],而不再是[-1,1]。这是因为在进行文本处理的时候我们根据单词的出现与否设置0、1/tf/tf-idf因此向量每个分量的取值都是正的。
在概率统计模块中,我介绍过特征选择和特征值的转换。由于文本向量往往是非常稀疏的,我们也可能需要对转换后的文档和查询向量,进行这两项操作。
### 排序和评估
完成了前两步,后面的排序和评估就很直观了。我们按照和输入查询的相似程度,对所有文档进行相似度由高到低的排序,然后取出前面的若干个文档,作为相关的信息返回。当然,这里你需要注意,这里所说的“相关性”是从向量空间模型的角度出发,不代表所返回的信息一定满足用户的需求。因此,我们还需要设计各种离线或者在线的评估,来衡量向量空间模型的效果。由于这些内容不是线性代数的关注点,我就不展开了。如果你有兴趣,可以自己去研究一下。
## 总结
今天我从文本的信息检索出发,介绍如何使用向量空间模型。在使用这个模型之前,很重要的处理步骤,就是要把原始数据转换成向量。这里所说的数据类型是文本,所以我们要进行分词等操作,然后构建文档的字典,并使用字典的词条来构建向量。如果是其他类型的数据,我们则需要提取相应的特征,并利用这些特征来构建向量。
如果我们把查询也转换成向量,那么就可以计算查询向量和文档向量之间的相似度。通过这种相似度,我们就能对所有的文档进行排序,找出向量空间模型认为“最相关”的文章。
不过,我今天介绍的计算相似度并排序的过程,只是最基本的实现,而这种实现并没有考虑效率的问题。我们这里可以简单分析一下时间复杂度。
假设查询的平均长度或词条数量远远小于文档的平均长度我们把查询的平均长度记做m那么对于每次计算查询向量和文档向量的相似度时间复杂度都是O(m)。假设文档集中文档的数量平均是n那么根据时间复杂度的四则运算法则把查询和所有文档比较的时间复杂度是O(m*n)。
其实在第17讲我曾经提到过了倒排索引的案例我们可以把倒排索引和向量空间模型相结合。倒排索引可以快速找到包含查询词的候选文档这样就避免了不必要的向量计算。更多具体的内容我会在之后的实战模块为你详细讲解。
## 思考题
假设你使用了tf-idf的机制来构造向量那么当文档集合中新增了文档之后你是不是只需要为新增文档构建向量原有文档的向量是否需要更新
欢迎留言和我分享,也欢迎你在留言区写下今天的学习笔记。你可以点击“请朋友读”,把今天的内容分享给你的好友,和他一起精进。

View File

@@ -0,0 +1,181 @@
<audio id="audio" title="36 | 文本聚类:如何过滤冗余的新闻?" controls="" preload="none"><source id="mp3" src="https://static001.geekbang.org/resource/audio/74/74/74db4f5a980744960b6025741a3bc274.mp3"></audio>
你好,我是黄申。
前两节,我讲了向量空间模型,以及如何在信息检索领域中运用向量空间模型。向量空间模型提供了衡量向量之间的距离或者相似度的机制,而这种机制可以衡量查询和被查询数据之间的相似程度,而对于文本检索来说,查询和文档之间的相似程度可作为文档的相关性。
实际上,除了文档的相关性,距离或者相似度还可以用在机器学习的算法中。今天,我们就来聊聊如何在聚类算法中使用向量空间模型,并最终实现过滤重复文章。
## 聚类算法
在概率统计模块中我们介绍了分类Classification/Categorization和回归Regression这两种监督式学习Supervised Learning。监督式学习通过训练资料学习并建立一个模型并依此模型对新的实例进行预测。
不过在实际场景中我们常常会遇到另一种更为复杂的情况。这时候不存在任何关于样本的先验知识而是需要机器在没人指导的情形下去将很多东西进行归类。由于缺乏训练样本这种学习被称为“非监督学习”Unsupervised Learning也就是我们通常所说的聚类Clustering。在这种学习体系中系统必须通过一种有效的方法发现样本的内在相似性并把数据对象以群组Cluster的形式进行划分。
谈到相似性,你可能已经想到了利用特征向量和向量空间模型,这确实是可行的方法。不过,为了让你全面了解在整个非监督式学习中,如何运用向量空间,让我先从一个具体的聚类算法开始。
这个算法的名称是K均值K-Means聚类算法它让我们可以在一个任意多的数据上得到一个事先定好群组数量K的聚类结果。这种算法的中心思想是尽量最大化总的群组内相似度同时尽量最小化群组之间的相似度。群组内或群组间的相似度是通过各个成员和群组质心相比较来确定的。想法很简单但是在样本数量达到一定规模后希望通过排列组合所有的群组划分来找到最大总群组内的相似度几乎是不可能的。于是人们提出如下的求近似解的方法。
<li>
从N个数据对象中随机选取k个对象作为质心这里每个群组的质心定义是群组内所有成员对象的平均值。因为是第一轮所以第i个群组的质心就是第i个对象而且这时候我们只有这一个组员。
</li>
<li>
对剩余的对象,测量它和每个质心的相似度,并把它归到最近的质心所属的群组。这里我们可以说距离,也可以说相似度,只是两者呈现反比关系。
</li>
<li>
重新计算已经得到的各个群组的质心。这里质心的计算是关键,如果使用特征向量来表示的数据对象,那么最基本的方法是取群组内成员的特征向量,将它们的平均值作为质心的向量表示。
</li>
<li>
迭代上面的第2步和第3步直至新的质心与原质心相等或相差之值小于指定阈值算法结束。
</li>
我以二维空间为例子,画张图来展示一下数据对象聚类的过程。
<img src="https://static001.geekbang.org/resource/image/56/0e/5642c36e8780997ee36ed53380ae880e.png" alt="">
在这张图中,( a )、( b )、( c )三步分别展示了质心和群组逐步调整的过称。我们一一来看。(a)步骤是选择初始质心质心用不同颜色的x表示( b )步骤开始进行聚类,把点分配到最近的质心所在的组;( c )步骤重新计算每个群组的质心你会发现x的位置发生了改变。之后就是如此重复进入下一轮聚类。
总的来说K均值算法是通过不断迭代、调整K个聚类质心的算法。而质心或者群组的中心点是通过求群组所包含的成员之平均值来计算的。
## 使用向量空间进行聚类
明白了K均值聚类算法的核心思想再来理解向量空间模型在其中的运用就不难了。我还是以文本聚类为例讲讲如何使用向量空间模型和聚类算法去除重复的新闻。
我们在看新闻的时候一般都希望不断看到新的内容。可是由于现在的报道渠道非常丰富经常会出现热点新闻霸占版面的情况。假如我们不想总是看到重复的新闻应该怎么办呢有一种做法就是对新闻进行聚类那么内容非常类似的文章就会被聚到同一个分组然后对每个分组我们只选择1到2篇显示就够了。
基本思路确定后,我们可以把整个方法分为三个主要步骤。
第一步,把文档集合都转换成向量的形式。这块我上一节讲过了,你要是不记得了,可以自己回去复习一下。
第二步使用K均值算法对文档集合进行聚类。这个算法的关键是如何确定数据对象和分组质心之间的相似度。针对这点我们有两个点需要关注。
<li>
使用向量空间中的距离或者夹角余弦度量,计算两个向量的相似度。
</li>
<li>
计算质心的向量。K均值里质心是分组里成员的平均值。所以我们需要求分组里所有文档向量的平均值。求法非常直观就是分别为每维分量求平均值我把具体的计算公式列在这里
</li>
<img src="https://static001.geekbang.org/resource/image/7f/43/7f47f218d42e6d33b4af132c6d536543.png" alt="">
其中,$x_{i}$表示向量的第i个分量$x_{ij}$表示第j格向量的第$i$个分量,而$j=1,2,…,n$表示属于某个分组的所有向量。
第三步,在每个分类中,选出和质心最接近的几篇文章作为代表。而其他的文章作为冗余的内容过滤掉。
下面我使用Python里的sklearn库来展示使用欧氏距离的K均值算法。
## Python中的K均值算法
在尝试下面的代码之前你需要看看自己的机器上是不是已经安装了scikit-learn。Scikit-learn是Python常用的机器学习库它提供了大量的机器学习算法的实现和相关的文档甚至还内置了一些公开数据集是我们实践机器学习算法的好帮手。
首先我使用sklearn库中的CountVectorizer对一个测试的文档集合构建特征也就是词典。这个测试集合有7句话2句关于篮球2句关于电影还有3句关于游戏。具体代码如下
```
from sklearn.feature_extraction.text import CountVectorizer
#模拟文档集合
corpus = ['I like great basketball game',
'This video game is the best action game I have ever played',
'I really really like basketball',
'How about this movie? Is the plot great?',
'Do you like RPG game?',
'You can try this FPS game',
'The movie is really great, so great! I enjoy the plot']
#把文本中的词语转换为词典和相应的向量
vectorizer = CountVectorizer()
vectors = vectorizer.fit_transform(corpus)
#输出所有的词条(所有维度的特征)
print('所有的词条(所有维度的特征)')
print(vectorizer.get_feature_names())
print('\n')
#输出(文章ID, 词条ID) 词频
print('(文章ID, 词条ID) 词频')
print(vectors)
print('\n')
```
从运行的结果中,你可以看到,整个词典里包含了哪些词,以及每个词在每个文档里的词频。
这里我们希望使用比词频tf更好的tf-idf机制TfidfTransformer可以帮助我们做到这点代码和注释如下
```
from sklearn.feature_extraction.text import TfidfTransformer
#构建tfidf的值
transformer = TfidfTransformer()
tfidf = transformer.fit_transform(vectorizer.fit_transform(corpus))
# 输出每个文档的向量
tfidf_array = tfidf.toarray()
words = vectorizer.get_feature_names()
for i in range(len(tfidf_array)):
print (&quot;*********第&quot;, i + 1, &quot;个文档中所有词语的tf-idf*********&quot;)
# 输出向量中每个维度的取值
for j in range(len(words)):
print(words[j], ' ', tfidf_array[i][j])
print('\n')
```
运行的结果展示了每个文档中每个词的tfidf权重你可以自己手动验算一下。
最后我们就可以进行K均值聚类了。由于有篮球、电影和游戏3个类别我选择的K是3并在KMeans的构造函数中设置n_clusters为3。
```
from sklearn.cluster import KMeans
#进行聚类,在我这个版本里默认使用的是欧氏距离
clusters = KMeans(n_clusters=3)
s = clusters.fit(tfidf_array)
#输出所有质心点,可以看到质心点的向量是组内成员向量的平均值
print('所有质心点的向量')
print(clusters.cluster_centers_)
print('\n')
#输出每个文档所属的分组
print('每个文档所属的分组')
print(clusters.labels_)
#输出每个分组内的文档
dict = {}
for i in range(len(clusters.labels_)):
label = clusters.labels_[i]
if label not in dict.keys():
dict[label] = []
dict[label].append(corpus[i])
else:
dict[label].append(corpus[i])
print(dict)
```
为了帮助你的理解我输出了每个群组的质心也就是其中成员向量的平均值。最后我也输出了3个群组中所包含的句子。在我机器上的运行结果显示系统可以把属于3个话题的句子区分开来。如下所示
```
{2: ['I like great basketball game', 'I really really like basketball'], 0: ['This video game is the best action game I have ever played', 'Do you like RPG game?', 'You can try this FPS game'], 1: ['How about this movie? Is the plot great?', 'The movie is really great, so great! I enjoy the plot']}
```
不过由于KMeans具体的实现可能不一样而且初始质心的选择也有一定随机性所以你看到的结果可能稍有不同。
## 总结
这一节,我介绍了如何在机器学习的聚类算法中,使用向量空间模型。在聚类中,数据对象之间的相似度时很关键的。如果我们把样本转换为向量,然后使用向量空间中的距离或者夹角余弦,就很自然的能获得这种相似度,所以向量空间模型和聚类算法可以很容易的结合在一起。
为了给你加深印象我介绍了一个具体的K均值算法以及向量空间模型在其中所起到的作用并通过Python的sklearn代码演示了几个关键的步骤。
向量空间模型和K均值算法的结合虽然简单易懂但是一开始怎样选择这个群组的数量是个关键问题。我今天演示的测试数据很小而且主题划分的也非常明显。所以我选择聚类的数量为3。
可是在实际项目中对于一个新的数据集合选择多少比较合适呢如果这个K值取得太大群组可能切分太细每个之间区别不大。如果K值取得太小群组的粒度又太粗造成群组内差异比较明显。对非监督式的学习来说这个参数确实难以得到准确预估。我们可以事先在一个较小的数据集合上进行尝试然后根据结果和应用场景确定一个经验值。
## 思考题
今天我使用的是sklearn里的KMeans包它使用了向量间的欧氏距离来进行聚类。你可以尝试实现自己的K均值聚类并使用向量间的夹角余弦作为相似度的度量。
欢迎留言和我分享,也欢迎你在留言区写下今天的学习笔记。你可以点击“请朋友读”,把今天的内容分享给你的好友,和他一起精进。

View File

@@ -0,0 +1,154 @@
<audio id="audio" title="37 | 矩阵如何使用矩阵操作进行PageRank计算" controls="" preload="none"><source id="mp3" src="https://static001.geekbang.org/resource/audio/95/b3/95cd7ba649c3fee7e7e1199b6adc36b3.mp3"></audio>
你好,我是黄申。今天我来说说矩阵。
前面我说过,矩阵由多个长度相等的向量组成,其中的每列或者每行就是一个向量。从数据结构的角度来看,我们可以把向量看作一维数组,把矩阵看作二维数组。
具有了二维数组的特性矩阵就可以表达二元关系了例如图中结点的邻接关系或者是用户对物品的评分关系。而通过矩阵上的各种运算操作我们就可以挖掘这些二元关系在不同的应用场景下达到不同的目的。今天我就从图的邻接矩阵出发展示如何使用矩阵计算来实现PageRank算法。
## 回顾PageRank链接分析算法
在讲马尔科夫模型的时候我已经介绍了PageRank链接分析算法。所以在展示这个算法和矩阵操作的关系之前我们快速回顾一下它的核心思想。
PageRank是基于马尔科夫链的。它假设了一个“随机冲浪者”模型冲浪者从某张网页出发根据Web图中的链接关系随机访问。在每个步骤中冲浪者都会从当前网页的链出网页中随机选取一张作为下一步访问的目标。此外PageRank还引入了随机的跳转操作这意味着冲浪者不是按Web图的拓扑结构走下去只是随机挑选了一张网页进行跳转。
基于之前的假设PageRank的公式定义如下
<img src="https://static001.geekbang.org/resource/image/55/6d/553f1e841d71ac34db7161cb9974e56d.png" alt="">
其中,$p_{i}$表示第$i$张网页,$M_{i}$是$p_{i}$的入链接集合,$p_{j}$是$M_{i}$集合中的第$j$张网页。$PR_{(p_{j})}$表示网页$p_{j}$的PageRank得分$L_{(p_{j})}$表示网页$p_{j}$的出链接数量,$\frac{1}{L_{(p_{j})}}$就表示从网页$p_{j}$跳转到$p_{i}$的概率。$α$是用户不进行随机跳转的概率,$N$表示所有网页的数量。
PageRank的计算是采样迭代法实现的一开始所有网页结点的初始PageRank值都可以设置为某个相同的数例如1然后我们通过上面这个公式得到每个结点新的PageRank值。每当一张网页的PageRank发生了改变它也会影响它的出链接所指向的网页因此我们可以再次使用这个公式循环地修正每个网页结点的值。由于这是一个马尔科夫过程所以我们能从理论上证明所有网页的PageRank最终会达到一个稳定的数值。整个证明过程很复杂这里我们只需要知道这个迭代计算的过程就行了。
## 简化PageRank公式
那么这个计算公式和矩阵操作又有什么联系呢为了把问题简化我们暂时不考虑随机跳转的情况而只考虑用户按照网页间链接进行随机冲浪。那么PageRank的公式就简化为
<img src="https://static001.geekbang.org/resource/image/b6/05/b6f8fc1f6e8b144e3d9e6a0d99da1c05.png" alt="">
这个公式只包含了原公式中的$Σ\frac{PR_{(p_{j})}}{L_{(p_{j})})}$部分。我们再来对比看看矩阵点乘的计算公式。
<img src="https://static001.geekbang.org/resource/image/5b/fc/5bb501ed32b7f9882fd71baf3d4f92fc.png" alt="">
以上两个公式在形式上是基本一致的。因此,我们可以把$Σ\frac{PR_{(p_{j})}}{L_{(p_{j})})}$的计算分解为两个矩阵的点乘。一个矩阵是当前每张网页的PageRank得分另一个矩阵就是邻接矩阵。所谓邻接矩阵其实就是表示图结点相邻关系的矩阵。
假设$x_{i,j}$是矩阵中第$i$行、第$j$列的元素,那么我们就可以使用$x_{i,j}$表示从结点$i$到结点$j$的连接放到PageRank的应用场景$x_{i,j}$就表示网页$p_{i}$到网页$p_{j}$的链接。最原始的邻接矩阵所包含的元素是0或10表示没有链接而1表示有链接。
考虑到PageRank里乘积是$\frac{1}{L_{(p_{j})}}$我们可以对邻接矩阵的每一行进行归一化用原始的值0或1除以$L_{(p_{j})}$,而$L_{(p_{j})}$表示有某张网页$p_{j}$的出链接,正好是矩阵中$p_{j}$这一行的和。所以,我们可以对原始的邻接矩阵,进行基于行的归一化,这样就能得到每个元素为$\frac{1}{L_{(p_{j})}}$的矩阵,其中$j$表示矩阵的第$j$行。注意这里的归一化是指让所有元素加起来的和为1。
为了方便你理解,我用下面这个拓扑图作为例子给你详细解释。
<img src="https://static001.geekbang.org/resource/image/df/27/df77f2aa727b5c8dba6a5276e5a25627.png" alt="">
基于上面这个图,原始矩阵为:
<img src="https://static001.geekbang.org/resource/image/08/09/08cb860669c99a1a8cdb0666373c6e09.png" alt="">
其中第i行、第j列的元素值表示从结点i到j是不是存在链接。如果是那么这个值为1否则就为0。
按照每一行的和,分别对每一行进行归一化之后的矩阵就变为:
<img src="https://static001.geekbang.org/resource/image/b1/f5/b16cace172cb8e3ff7a4981cc53504f5.png" alt="">
有了上述这个邻接矩阵我们就可以开始最简单的PageRank计算。PageRank的计算是采样迭代法实现的。这里我把初始值都设为1并把第一次计算的结果列在这里。
<img src="https://static001.geekbang.org/resource/image/fb/0c/fbc67543c3113496bfcf4e39bf375c0c.png" alt="">
好了,我们已经成功迈出了第一步,但是还需要考虑随机跳转的可能性。
## 考虑随机跳转
经过上面的步骤,我们已经求得$Σ\frac{PR_{(p_{j})}}{L_{(p_{j})})}$部分。不过PageRank引入了随机跳转的机制。这一部分其实也是可以通过矩阵的点乘来实现的。我们把$Σ\frac{PR_{(p_{j})}}{L_{(p_{j})})}$部分用$A$表示那么完整的PageRank公式就可以表示为
于是,我们可以把上述公式分解为如下两个矩阵的点乘:
<img src="https://static001.geekbang.org/resource/image/ea/af/eaf0b4fb41e70cc39dc534a457c2a9af.png" alt="">
我们仍然使用前面的例子来看看经过随机跳转之后PageRank值变成了多少。这里$α$取0.9。
<img src="https://static001.geekbang.org/resource/image/83/71/831c5970c794231fec8bca1a38e58271.png" alt="">
我们前面提到PageRank算法需要迭代式计算。为了避免计算后的数值越来越大甚至溢出我们可以进行归一化处理保证所有结点的数值之和为1。经过这个处理之后我们得到第一轮的PageRank数值也就是下面这个行向量
[0.37027027 0.24864865 0.37027027 0.00540541 0.00540541]
接下来,我们只需要再重复之前的步骤,直到每个结点的值趋于稳定就可以了。
## 使用Python进行实现
说到这里我已经把如何把整个PageRank的计算转换成多个矩阵的点乘这个过程讲完了。这样一来我们就可以利用Python等科学计算语言提供的库来完成基于PageRank的链接分析。为了展示具体的代码我以之前的拓扑图为例给你详细讲述每一步。
首先,我们要进行一些初始化工作,包括设置结点数量、确定随机跳转概率的$α$、代表拓扑图的邻接矩阵以及存放所有结点PageRank值的数组。下面是一段示例代码在代码中我提供了注释供你参考。
```
import numpy as np
# 设置确定随机跳转概率的alpha、网页结点数
alpha = 0.9
N = 5
# 初始化随机跳转概率的矩阵
jump = np.full([2,1], [[alpha], [1-alpha]], dtype=float)
# 邻接矩阵的构建
adj = np.full([N,N], [[0,0,1,0,0],[1,0,1,0,0],[1,0,0,0,0],[0,0,0,0,0],[0,1,0,0,0]], dtype=float)
# 对邻接矩阵进行归一化
row_sums = adj.sum(axis=1) # 对每一行求和
row_sums[row_sums == 0] = 0.1 # 防止由于分母出现0而导致的Nan
adj = adj / row_sums[:, np.newaxis] # 除以每行之和的归一化
# 初始的PageRank值通常是设置所有值为1.0
pr = np.full([1,N], 1, dtype=float)
```
之后我们就能采用迭代法来计算PageRank值。一般我们通过比较每个结点最近两次计算的值是否足够接近来确定数值是不是已经稳定以及是不是需要结束迭代。这里为简便起见我使用了固定次数的循环来实现。如果你的拓扑图比较复杂需要更多次迭代我把示例代码和注释列在这里。
```
# PageRank算法本身是采样迭代方式进行的当最终的取值趋于稳定后结束。
for i in range(0, 20):
# 进行点乘,计算Σ(PR(pj)/L(pj))
pr = np.dot(pr, adj)
# 转置保存Σ(PR(pj)/L(pj))结果的矩阵并增加长度为N的列向量其中每个元素的值为1/N便于下一步的点乘。
pr_jump = np.full([N, 2], [[0, 1/N]])
pr_jump[:,:-1] = pr.transpose()
# 进行点乘,计算α(Σ(PR(pj)/L(pj))) + (1-α)/N)
pr = np.dot(pr_jump, jump)
# 归一化PageRank得分
pr = pr.transpose()
pr = pr / pr.sum()
print(&quot;round&quot;, i + 1, pr)
```
如果成功运行了上述两段代码你就能看到每个结点最终获得的PageRank分数是多少。
Python中还有一些很不错的库提供了直接构建拓扑图和计算PageRank的功能例如networkx[https://networkx.github.io/](https://networkx.github.io/)。你可以尝试使用这种库构建样例拓扑图并计算每个结点的PageRank得分最后和上述代码所计算的PageRank得分进行比较验证一下上述代码的结果是不是合理。
## 总结
我们可以把向量看作一维数组,把矩阵看作二维数组。矩阵的点乘,是由若干个向量的点乘组成的,所以我们可以通过矩阵的点乘操作,挖掘多组向量两两之间的关系。
今天我们讲了矩阵的点乘操作在PageRank算法中的应用。通过表示网页的邻接二元关系我们可以使用矩阵来计算PageRank的得分。在这个应用场景下矩阵点乘体现了多个马尔科夫过程中的状态转移。
矩阵点乘和其他运算操作还可以运用在很多其他的领域。例如我在上一节介绍K均值聚类算法时就提到了需要计算某个数据点向量、其他数据点向量之间的距离或者相似度以及使用多个数据点向量的平均值来获得质心点的向量这些都可以通过矩阵操作来完成。
另外,在协同过滤的推荐中,我们可以使用矩阵点乘,来实现多个用户或者物品之间的相似程度,以及聚集后的相似程度所导致的最终推荐结果。下一节,我会使用矩阵来表示用户和物品的二元关系,并通过矩阵来计算协同过滤的结果。
## 思考题
在介绍PageRank算法时我提到了它的计算是一个迭代的过程。这一节我使用了固定次数的循环来实现这一点。请尝试使用计算前后两次PageRank数值的差来判断是否需要结束迭代。提示你可以使用矩阵元素对应的减法以及在第3讲和加餐2中提到的相对误差。
欢迎留言和我分享,也欢迎你在留言区写下今天的学习笔记。你可以点击“请朋友读”,把今天的内容分享给你的好友,和他一起精进。

View File

@@ -0,0 +1,123 @@
<audio id="audio" title="38 | 矩阵(下):如何使用矩阵操作进行协同过滤推荐?" controls="" preload="none"><source id="mp3" src="https://static001.geekbang.org/resource/audio/25/20/256678b42d5cb6409de6d7ba1b038720.mp3"></audio>
你好,我是黄申。今天我们来聊聊矩阵操作和推荐算法的关系。
我这里说的推荐,是指为用户提供可靠的建议、并协助用户挑选物品的一种技术。一个好的推荐系统需要建立在海量数据挖掘基础之上,并根据用户所处的情景和兴趣特点,向用户推荐可能感兴趣的信息和商品。
协同过滤Collaborative Filtering是经典的推荐算法之一它充分利用了用户和物品之间已知的关系为用户提供新的推荐内容。我会从这种二元关系出发给你讲讲如何使用矩阵计算来实现协同过滤推荐算法。
## 用矩阵实现推荐系统的核心思想
矩阵中的二维关系,除了可以表达图的邻接关系,还可以表达推荐系统中用户和物品的关系。如果你不懂推荐系统,不用急,我这里先给你简单讲讲它的核心思想。
简单地理解就是推荐系统会根据用户所处的场景和个人喜好推荐他们可能感兴趣的信息和商品。比如你在阅读一部电影的影评时系统给你推荐了其他“你可能也感兴趣的电影”。可以看出来推荐系统中至少有2个重要的角色用户和物品。用户是系统的使用者物品就是将要被推荐的候选对象。
例如,亚马逊网站的顾客就是用户,网站所销售的商品就是物品。需要注意的是,除了用户角色都是现实中的自然人,某些场景下被推荐的物品可能也是现实中的自然人。例如,一个招聘网站会给企业雇主推荐合适的人才,这时候应聘者承担的是物品角色。
而一个好的推荐算法,需要充分挖掘用户和物品之间的关系。我们可以通过矩阵来表示这种二元关系。我这里有一个例子,我们用矩阵$X$来表示用户对物品喜好程度。
<img src="https://static001.geekbang.org/resource/image/e5/13/e58feab91a926fae9201fbf49f749113.png" alt="">
其中第$i$行是第$i$个用户的数据而第j列是用户对第j格物品的喜好程度。我们用$x_{i,j}$表示这个数值。这里的喜好程度可以是用户购买商品的次数、对书籍的评分等等。
假设我们用一个0到1之间的小数表示。有了这种矩阵我们就可以通过矩阵的操作充分挖掘用户和物品之间的关系。下面我会使用经典的协同过滤算法来讲解矩阵在其中的运用。
在此之前,我们先来看什么是协同过滤。你可以把它理解为最直观的“口口相传”。假设我们愿意接受他人的建议,尤其是很多人都向你建议的时候。其主要思路就是利用已有用户群过去的行为或意见,预测当前用户最可能喜欢哪些东西。根据推荐依据和传播的路径,又可以进一步细分为基于用户的过滤和基于物品的过滤。
## 基于用户的过滤
首先,我们来看基于用户的协同过滤。它是指给定一个用户访问(我们假设有访问就表示有兴趣)物品的数据集合,找出和当前用户历史行为有相似偏好的其他用户,将这些用户组成“近邻”,对于当前用户没有访问过的物品,利用其近邻的访问记录来预测。我画了一张图方便你理解。
<img src="https://static001.geekbang.org/resource/image/25/b5/25cde6fc74cd86892b41ce66de4be4b5.png" alt="">
根据这张图的访问关系来看用户A访问了物品A和C用户B访问了物品B用户C访问了物品AC和D。我们计算出来用户C是A的近邻而B不是。因此系统会更多地向用户A推荐用户C访问的物品D。
理解了这个算法的基本概念我们来看看如何使用公式来表述它。假设有m个用户n个物品那么我们就能使用一个m×n维的矩阵$X$来表示用户对物品喜好的二元关系。基于这个二元关系,我们可以列出下面这两个公式:
<img src="https://static001.geekbang.org/resource/image/1b/de/1bb5c0d0f0d77e7e77b2dbdc56ae27de.png" alt="">
其中,第一个公式比较容易理解,它的核心思想是计算用户和用户之间的相似度。完成了这一步我们就能找到给定用户的“近邻”。
我们可以使用向量空间模型中的距离或者是夹角余弦来处理,在这里我使用了夹角余弦,其中$us_{i1}$,$i2$表示用户$i1$和$i2$的相似度,而$X_{i1}$,表示矩阵中第$i1$行的行向量,$X_{i2}$,表示矩阵中第$i2$行的行向量。分子是两个表示用户的行向量之点乘,而分母是这两个行向量$L2$范数的乘积。
第二个公式利用第一个公式所计算的用户间相似度,以及用户对物品的喜好度,预测任一个用户对任一个物品的喜好度。其中$p_{i,j}$表示第$i$用户对第$j$个物品的喜好度,$us_{i,k}$表示用户$i$和$k$之间的相似度,$x_{k,j}$表示用户$k$对物品$j$的喜好度。注意这里最终需要除以$Σus_{i,k}$,是为了进行归一化。
从这个公式可以看出,如果$us_{i,k}$越大,$x_{k,j}$对最终$p_{i,j}$的影响越大,反之如果$us_{i,k}$越小,$x_{k,j}$对最终$p_{i,j}$的影响越小,充分体现了“基于相似用户”的推荐。
如果你无法理解如何把这两个公式对应为矩阵操作,没关系,我下面会通过之前介绍的喜好度矩阵$X$的示例,把这两个公式逐步拆解,并对应到矩阵上的操作,你一看就能明白了。
首先,我们来看第一个关于夹角余弦的公式。
<img src="https://static001.geekbang.org/resource/image/3c/2b/3c04082d6f87f9268eceb65cb624892b.png" alt="">
在介绍向量空间模型的时候,我提到夹角余弦可以通过向量的点乘来实现。这对矩阵同样适用,我们可以采用矩阵点乘自身的转置来实现,也就是$XX$。矩阵$X$的每一行是某个用户的行向量,每个分量表示用户对某个物品的喜好程度。而矩阵$X$的每一列是某个用户的列向量,每个分量表示用户对某个物品的喜好程度。
我们假设$XX$的结果为矩阵$Y$,那么$y_{i,j}$就表示用户$i$和用户$j$这两者喜好度向量的点乘结果,它就是夹角余弦公式中的分子。如果$i$等于$j$,那么这个计算值也是夹角余弦公式分母的一部分。从矩阵的角度来看,$Y$中任何一个元素都可能用于夹角余弦公式的分子,而对角线上的值会用于夹角余弦公式的分母。这里我们仍然使用之前的喜好度矩阵示例,来计算矩阵$Y$和矩阵$US$。
首先我们来看$Y$的计算。
<img src="https://static001.geekbang.org/resource/image/79/2b/79e7065dade1b1a4d38639bb9d2cea2b.png" alt="">
然后我们使用$Y$来计算$US$。我用下面这张图表示矩阵中的元素和夹角余弦计算的对应关系。
<img src="https://static001.geekbang.org/resource/image/9d/42/9ddfe8b7874d9d708fa367ccca967942.png" alt="">
明白了上面这个对应关系,我们就可以利用矩阵$Y$获得任意两个用户之间的相似度并得到一个m×m维的相似度矩阵$US$。矩阵$US$中$us_{i,j}$的取值为第$i$个用户与第$j$个用户的相似度。这个矩阵是一个沿对角线对称的矩阵。根据夹角余弦的定义,$us_{i,j}$和$us_{j,i}$是相等的。通过示例的矩阵$Y$,我们可以计算矩阵$US$。我把相应的结果列在了下方。
<img src="https://static001.geekbang.org/resource/image/1e/1e/1e02d4f61487b6e5fd6178ef8e143b1e.png" alt="">
接下来,我们再来看第二个公式。
<img src="https://static001.geekbang.org/resource/image/80/6b/80fc4fd72d8f4f114cd0fa7f868f576b.png" alt="">
从矩阵的角度来看,现在我们已经得到用户相似度矩阵$US$,再加上用户对物品的喜好度矩阵$X$,现在需要计算任意用户对任意物品的喜好度推荐矩阵$P$。
为了实现上面这个公式的分子部分,我们可以使用$US$和$X$的点乘。我们假设点乘后的结果矩阵为$USP$。这里我列出了根据示例计算得到的矩阵$USP$。
<img src="https://static001.geekbang.org/resource/image/65/1f/65ff214ce18ccc12193bf17cd1ec201f.png" alt="">
分母部分可以使用$US$矩阵的按行求和来实现。我们假设按行求和的矩阵为$USR$。根据示例计算就可以得到$USR$。
<img src="https://static001.geekbang.org/resource/image/28/0e/28c5469d3fcacda96604f66dfc883a0e.png" alt="">
最终,我们使用$USP$和*$USR$的元素对应除法,就可以求得矩阵$P$。
<img src="https://static001.geekbang.org/resource/image/eb/c8/eba06795db612c7be96118b9fa93cfc8.png" alt="">
既然已经有$X$这个喜好度矩阵了,为什么还要计算$P$这个喜好度矩阵呢?实际上,$X$是已知的、有限的喜好度。例如用户已经看过的、购买过的、或评过分的物品。而$P$是我们使用推荐算法预测出来的喜好度。
即使一个用户对某个物品从未看过、买过、或评过分,我们依然可以通过矩阵$P$,知道这位用户对这个物品大致的喜好程度,从而根据这个预估的分数进行物品的推荐,这也是协同过滤的基本思想。从根据示例计算的结果也可以看出这点,在原始矩阵$X$中第1个用户对第3个物品的喜好度为0。可是在最终的喜好度推荐矩阵P中第1个用户对第3个物品的喜好度为0.278已经明显大于0了因此我们就可以把物品3推荐给用户1。
上面这种基于用户的协同过滤有个问题,那就是没有考虑到用户的喜好程度是不是具有可比性。假设用户的喜好是根据对商品的评分来决定的,有些用户比较宽容,给所有的商品都打了很高的分,而有些用户比较严苛,给所有商品的打分都很低。分数没有可比性,这就会影响相似用户查找的效果,最终影响推荐结果。这个时候我们可以采用之前介绍的特征值变化,对于原始的喜好度矩阵,按照用户的维度对用户所有的喜好度进行归一化或者标准化处理,然后再进行基于用户的协同过滤。
## 基于物品的过滤
基于物品的协同过滤是指利用物品相似度,而不是用户间的相似度来计算预测值。我同样用图来帮助你理解。
<img src="https://static001.geekbang.org/resource/image/5b/00/5be6626c3c5bf8a6dcdb0c29f032c500.png" alt="">
在这张图中物品A和C因为都被用户A和B同时访问因此它们被认为相似度更高。当用户C访问过物品A后系统会更多地向用户推荐物品C而不是其他物品。
基于物品的协同过滤同样有两个公式,你可以看一下。
<img src="https://static001.geekbang.org/resource/image/d7/21/d7d584a2b1f85463c5028fffb76cce21.png" alt="">
如果你弄明白了基于用户的过滤,那么这两个公式也就不难理解了。第一个公式的核心思想是计算物品和物品之间的相似度,在这里我仍然使用夹角余弦。其中$is_{j1}$,$j2$表示物品$j1$和$j2$的相似度,而$X_{j1}$表示了$X$中第$j1$列的列向量,而$X_{j2}$表示了$X$中第$j2$列的列向量。分子是两个表示物品的列向量之点乘,而分母是这两个列向量$L2$范数的乘积。
第二个公式利用第一个公式所计算的物品间相似度,和用户对物品的喜好度,预测任一个用户对任一个物品的喜好度。其中$p_{i,j}$表示第$i$用户对第$j$个物品的喜好度,$x_{i,k}$表示用户$i$对物品$k$的喜好度,$is_{k,j}$表示物品$k$和$j$之间的相似度,注意这里除以$Σis_{k,j}$是为了进行归一化。从这个公式可以看出,如果$is_{k,j}$越大,$x_{i,k}$对最终$p_{i,j}$的影响越大,反之如果$is_{k,j}$越小,$x_{i,k}$对最终$p_{i,j}$的影响越小,充分体现了“基于相似物品”的推荐。
类似地,用户喜好程度的不一致性,同样会影响相似物品查找的效果,并最终影响推荐结果。我们也需要对于原始的喜好度矩阵,按照用户的维度对用户的所有喜好度,进行归一化或者标准化处理。
## 总结
今天我首先简要地介绍了推荐系统的概念和主要思想。为了给用户提供可靠的结果,推荐系统需要充分挖掘历史数据中,用户和物品之间的关系。协同过滤的推荐算法就很好地体现了这一点。
一旦涉及用户和物品的这种二元关系,矩阵就有用武之地了。我通过矩阵来表示用户和物品的关系,并通过矩阵计算来获得协同过滤的结果。协同过滤分为基于用户的过滤和基于物品的过滤两种,它们的核心思想都是相同的,因此矩阵操作也是类似的。在这两个应用场景下,矩阵点乘体现了多个用户或者物品之间的相似程度,以及聚集后的相似程度所导致的最终推荐结果。
当然,基于用户和物品间关系的推荐算法有很多,对矩阵的操作也远远不止点乘、按行求和、元素对应乘除法。我后面会介绍如何使用矩阵的主成分分析或奇异值分解,来进行物品的推荐。
## 思考题
我在介绍推荐算法时,提到了基于物品的协同过滤。请参照基于用户的协同过滤,写出相应的矩阵操作步骤。
欢迎留言和我分享,也欢迎你在留言区写下今天的学习笔记。你可以点击“请朋友读”,把今天的内容分享给你的好友,和他一起精进。

View File

@@ -0,0 +1,202 @@
<audio id="audio" title="39 | 线性回归(上):如何使用高斯消元求解线性方程组?" controls="" preload="none"><source id="mp3" src="https://static001.geekbang.org/resource/audio/7b/b1/7b6e350c627d4b1e9a15b7e5debacab1.mp3"></audio>
你好,我是黄申。
之前我使用Boston Housing的数据阐述了如何使用多元线性回归。可是计算机系统究竟是如何根据观测到的数据来拟合线性回归模型呢这两节我就从最简单的线性方程组出发来说说如何求解线性回归的问题。
在第29讲中我讲过机器学习中两类很重要的方法回归分析以及线性回归。回归分析属于监督式学习算法主要研究一个或多个随机变量$y_1$$y_2$,…,$y_i$与另一些变量$x_{1}$$x_{2}$,…,$x_{k}$之间的关系。其中,我们将$y_{1}y_{2}、…y_{i}$称为因变量,$x_1x_2x_k$称为自变量。按照不同的维度,我们可以把回归分为三种。
<li>
按照自变量数量,当自变量$x$的个数大于1时就是多元回归。
</li>
<li>
按照因变量数量,当因变量$y$个数大于1时就是多重回归。
</li>
<li>
按照模型种类,如果因变量和自变量为线性关系时,就是线性回归模型;如果因变量和自变量为非线性关系时时,就是非线性回归分析模型。
</li>
## 高斯消元法
对于回归分析来说,最简单的情形是只有一个自变量和一个因变量,且它们大体上是有线性关系的,这就是一元线性回归。对应的模型很简单,就是$Y=a+bX+ε$。这里的$X$是自变量,$Y$是因变量,$a$是截距b是自变量的系数。前面这些你估计都很熟悉最后还有个$ε$,这表示随机误差,只不过我们通常假定随机误差的均值为$0$。进一步来说如果我们暂时不考虑a和ε把它扩展为多元的形式那么就可以得到类似下面这种形式的方程
$b_1·x_1+b_2·x_2+...+b_{n-1}·x_{n-1} +b_n·x_n=y$
假设我们有多个这样的方程,就能构成线性方程组,我这里列出一个例子。
$2x_1+x_2+x_3=0$<br>
$4x_1+2x_2+x_3=56$<br>
$2x_1-x_2+4x_3=4$
对于上面这个方程组,如果存在至少一组$x_1、x_2$和$x_3$使得三个方程都成立,那么就叫方程有解;如果没有,那么我们就说方程无解。如果方程有解,那么解可能是唯一,也可能是多个。我们通常关心的是,方程组是不是有解,以及$x_1$一直到$x_n$分别是多少。
为了实现这个目的,人们想了很多方法来求解方程组,这些方法看起来多种多样,其实主要就是两大类,直接法和迭代法。
直接法就是通过有限次的算术运算计算精确解。而迭代法我们在第3讲就提到过它是一种不断用变量的旧值递推新值的过程。我们可以用迭代法不断地逼近方程的精确解。
这里,我就从上面这个方程组的例子出发,阐述最常见的高斯消元法,以及如何使用矩阵操作来实现它。
高斯消元法主要分为两步,**消元**Forward Elimination和**回代**Back Substitution。所谓消元就是要减少某些方程中元的数量。如果某个方程中的元只剩一个了$x_m$了,那么这个自变量$x_m$的解就能知道了。所谓的回代,就是把已知的解$x_m$代入到方程式中,求出其他未知的解。
我们先从消元开始,来看这个方程组。
$2x_1+x_2+x_3=0$<br>
$4x_1+2x_2+x_3=56$<br>
$2x_1-x_2+4x_3=4$
首先保持第一个方程不变,然后消除第二个和第三个方程中的$x_1$。对于第二个方程,方法是让第二个方程式减去第一个方程式的两倍,方程的左侧为:
$(4x_1+2x_2+x_3)-2(2x_1+x_2+x_3)=-x_3$
方程的右侧变为:
$56-2·0=56$
所以第二个方程变为:
$-x_3=56$
这样三个方程式就变为:
$2x_1+x_2+x_3=0$<br>
$-x_3=56$<br>
$2x_1-x_2+4x_3=4$
对于第三个方程同样如此,我们需要去掉其中的$x_1$。方法是让第三个方程减去第一个方程,之后三个方程式变为:
$2x_1+x_2+x_3=0$<br>
$-x_3=56$<br>
$-2x_2+3x_3=4$
至此,我们使用第一个方程式作为参照,消除了第二个和第三个方程式中的$x_1$,我们称这里的第一个方程式为“主元行”。
接下来,我们要把第二个方程式作为“主元行”,来消除第三个方程中的$x_2$。你应该能发现,第二个方程中的$x_2$已经没有了,失去了参照,这个时候我们需要把第二个方程和第三个方程互换,变为:
$2x_1+x_2+x_3=0$<br>
$-2x_2+3x_3=4$<br>
$-x_3=56$
到了这个时候,由于第三个方程以及没有$x_2$了,所以无需再消元。如果还有$x_2$,那么就需要参照第二个方程式来消除第三个方程中的$x_2$。
观察一下现在的方程组第一个方程有3个自变量第二个方程有2个自变量第三个方程只有1个自变量。这个时候我们就可以从第三个方程开始开始回代的过程了。通过第三个方程显然我们可以得到$x_3=-56$,然后把这个值代入第二个方程,就可以得到$x_2 = -86$。最后把$x_2$和$x_3$的值代入第一个方程式,我们可以得到$x_1=71$。
## 使用矩阵实现高斯消元法
如果方程和元的数量很小,那么高斯消元法并不难理解。可是如果方程和元的数量很多,整个过程就变得比较繁琐了。实际上,我们可以把高斯消元法转为矩阵的操作,便于自己的理解和记忆。
为了进行矩阵操作,首先我们要把方程中的系数$b_i$转成矩阵,我们把这个矩阵记作$B$。对于上面的方程组示例,系数矩阵为:
<img src="https://static001.geekbang.org/resource/image/f5/27/f503789bf7c86ed71833714ef2ec7d27.png" alt="">
那么最终我们通过消元把系数矩阵B变为
<img src="https://static001.geekbang.org/resource/image/4b/83/4b22614e3ee87cc6f05e83f149577583.png" alt="">
从此可以看出,消元的过程就是把原始的系数矩阵变为上三角矩阵。这里的上三角矩阵表示,矩阵中只有主对角线以及主对角线以上的三角部分里有数字。我们用$U$表示上三角矩阵。
而回代呢,我们最终得到的结果是:
$x_1=71$<br>
$x_2=-86$<br>
$x_3=-56$
我们可以把这几个结果看作:
$1·x_1+0·x_2+0·x_3=71$<br>
$0·x_1+1·x_2+0·x_3=-86$<br>
$0·x_1+0·x_2+1·x_3=-56$
再把系数写成矩阵的形式,就是:
<img src="https://static001.geekbang.org/resource/image/30/cc/30000d4fa09611c433b1bf4c830f5dcc.png" alt="">
发现没?这其实就是单位矩阵。所以说,回代的过程是把上三角矩阵变为单位矩阵的过程。
为了便于后面的回代计算,我们也可以把方程式等号右边的值加入到系数矩阵,我们称这个新的矩阵为**增广矩阵**,我把这个矩阵记为$A$。
好,现在让我们来观察一下这个增广矩阵$A$。
<img src="https://static001.geekbang.org/resource/image/22/eb/22fedea5f06d766d4ff2cfa7319ddceb.png" alt="">
对于这个矩阵,我们的最终目标是,把除了最后一列之外的部分,变成单位矩阵,而此时最后一列中的每个值,就是每个自变量所对应的解了。
之前我已经讲过矩阵相乘在向量空间模型、PageRank算法和协同过滤推荐中的应用。这里我们同样可以使用这种操作来进行消元。为了方便你理解我们可以遵循之前消元的步骤一步步来看。
还记得这个方程组消元的第一步吗?对,首先保持第一个方程不变,然后消除第二个和第三个方程中的$x_1$。这就意味着要把$A_{2,1}$和$A_{3,1}$变为$0$。
对于第一个方程式,如果要保持它不变,我们可以让向量$[1, 0, 0]$左乘$A$。对于第二个方程,具体操作是让第二个方程式减去第一个方程式的两倍,达到消除$x_1$的目的。我们可以让向量$[-2, 1, 0]$左乘$A$。对于第三个方程式,具体操作是让第三个方程式减去第一个方程式,达到消除$x_1$的目的。我们可以让向量$[-1, 0, 1]$左乘$A$。我们使用这三个行向量组成一个矩阵$E1$。
<img src="https://static001.geekbang.org/resource/image/a1/e0/a1d1ed2a3d2fad6612419778cefb71e0.png" alt="">
因此,我们可是用下面这个矩阵$E1$和$A$的点乘,来实现消除第二个和第三个方程式中$x_1$的目的。
<img src="https://static001.geekbang.org/resource/image/51/4e/51740abc1698f91b5baac900b42b8d4e.png" alt="">
你会发现,由于使用了增广矩阵,矩阵中最右边的一列,也就是方程等号右边的数值也会随之发生改变。
下一步是消除第三个方程中的$x_2$。依照之前的经验,我们要把第二个方程式作为“主元行”,来消除第三个方程中的$x_2$。可是第二个方程中的$x_2$已经没有了,失去了参照,这个时候我们需要把第二个方程和第三个方程互换。这种互换的操作如何使用矩阵来实现呢?其实不难,例如使用下面这个矩阵$E2$左乘增广矩阵$A$。
<img src="https://static001.geekbang.org/resource/image/7a/c3/7a5989f252595581c5a0e47107269bc3.png" alt="">
上面这个矩阵第一行$[1 0 0]$的意思就是我们只取第一行的方程,而第二行$[0 0 1]$的意思是只取第三个方程,而第三行$[0 1 0]$表示只取第二个方程。
我们先让$E1$左乘$A$,然后再让$E2$左乘$E1A$的结果,就能得到消元后的系数矩阵。
<img src="https://static001.geekbang.org/resource/image/de/00/decf0462a60b9dcb7defd521d4b19500.png" alt="">
我们把$E1$点乘$E2$的结果记作$E3$,并把$E3$称为消元矩阵。
<img src="https://static001.geekbang.org/resource/image/2e/df/2ea3b5f4e5eed00d635e208c1f7b0cdf.png" alt=""><img src="https://static001.geekbang.org/resource/image/0b/86/0b2a2499dc78c7dd8f32db062ce3b586.png" alt="">
对于目前的结果矩阵来说,除了最后一列,它已经变成了一个上三角矩阵,也就是说消元步骤完成。接下来,我们要使得最后一列之外的部分变成一个单位矩阵,就能得到最终的方程组解。和消元不同的是,我们将从最后一行开始。对于最后一个方程,我们只需要把所有系数取反就行了,所以会使用下面这个矩阵$S1$实现。
<img src="https://static001.geekbang.org/resource/image/2a/bc/2a502f387af1227ff309faf921268ebc.png" alt=""><img src="https://static001.geekbang.org/resource/image/12/46/1266ac238659c01af69bbf37d4335f46.png" alt="">
接下来要去掉第二个方程中的$x_3$我们要把第二个方程减去3倍的第三个方程然后除以-2。首先是减去3倍的第三个方程。
<img src="https://static001.geekbang.org/resource/image/bf/e0/bfd6bce21ada11993249d3d9728cf6e0.png" alt="">
然后把第二个方程除以-2。
<img src="https://static001.geekbang.org/resource/image/31/ca/31e6d5d4e71cb304eee7e3b78b0c9bca.png" alt="">
最后对于第一个方程我们要把第一个方程减去第二个和第三个方程最后除以2我把这几步合并了并列在下方。
<img src="https://static001.geekbang.org/resource/image/24/1b/249f6c4cf7b5ff373f23ac3e1167ae1b.png" alt="">
最终,结果矩阵的最后一列就是方程组的解。我们把回代部分的矩阵,都点乘起来。
<img src="https://static001.geekbang.org/resource/image/ba/c1/ba81f9fed08ce1c10f6dbc98196602c1.png" alt="">
而消元矩阵$E3$为:
<img src="https://static001.geekbang.org/resource/image/0b/86/0b2a2499dc78c7dd8f32db062ce3b586.png" alt="">
我们可以让矩阵$S$左乘矩阵$E3$,就会得到下面的结果。
<img src="https://static001.geekbang.org/resource/image/8d/b0/8da5e9b9ac5ca050c10bee764067bab0.png" alt="">
我们把这个矩阵记作$SE$,把乘以最初的系数矩阵$B$,就得到了一个单位矩阵。根据逆矩阵的定义,$SE$就是$B$的逆矩阵。换个角度来思考,使用消元法进行线性方程组求解的过程,就是在找系数矩阵的逆矩阵的过程。
## 总结
今天我们一起探讨了求解线性方程组最常见的方法之一,高斯消元法。这个方法主要包含了消元和回代两个步骤。这些步骤都可以使用矩阵的操作来进行。从矩阵的角度来说,消元就是把系数矩阵变为上三角矩阵,而回代是把这个上三角矩阵变为单位矩阵。我们可以直接把用于消元和回代的矩阵,用于由系数和因变量值组成的增广矩阵,并获得最终的方程解。
线性方程组的概念,也是线性回归分析的基础。在线性回归时,我们也能获得由很多观测数据值所组成的方程组。但是,在进行线性回归分析时,方程组的处理方式和普通的方程组求解有一些不同。其中有两个最主要的区别。
第一个区别是,在线性回归分析中,样本数据会告诉我们自变量和因变量的值,要求的是系数。而在线性方程组中,我们已知系数和因变量的值,要求的是自变量的值。
第二个区别是在线性回归分析中方程的数量要远远大于自变量的数量而且我们不要求每个方程式都是完全成立。这里不要求完全成立的意思是拟合出来的因变量值可以和样本数据给定的因变量值存在差异也就允许模型拟合存在误差。模型拟合的概念我在上一模块的总结篇中重点讲解了所以你应该能理解模型的拟合不可能100%完美,这和我们求解线性方程组精确解的概念是不同的。
正是因为这两点差异,我们无法直接使用消元法来求解线性回归。下一节,我会来详细解释,如何使用最小二乘法来解决线性回归的问题。
## 思考题
请分别写出下面这个方程组的消元矩阵和回代矩阵,并求出最终的解。
$x_1-2x_2+x_3-4x_4=4$<br>
$x_2-x_3+x_4=-3$<br>
$x_1+3x_2+x_4=1$<br>
$-7x_2+3x_3+x_4=-3$
欢迎留言和我分享,也欢迎你在留言区写下今天的学习笔记。你可以点击“请朋友读”,把今天的内容分享给你的好友,和他一起精进。

View File

@@ -0,0 +1,202 @@
<audio id="audio" title="40 | 线性回归(中):如何使用最小二乘法进行直线拟合?" controls="" preload="none"><source id="mp3" src="https://static001.geekbang.org/resource/audio/ea/0e/eaf4289b03597a9e9e26b35635ac5e0e.mp3"></audio>
你好,我是黄申。
上一节,我提到了,求解线性回归和普通的线性方程组最大的不同在于误差ε。在求解线性方程组的时候,我们并不考虑误差的存在,因此存在无解的可能。而线性回归允许误差ε的存在,我们要做的就是尽量把ε最小化,并控制在一定范围之内。这样我们就可以求方程的近似解。而这种近似解对于海量的大数据分析来说是非常重要的。
但是现实中的数据一定存在由于各种各样原因所导致的误差因此即使自变量和因变量之间存在线性关系也基本上不可能完美符合这种线性关系。总的来说线性回归分析并不一定需要100%精确,而误差ε的存在可以帮助我们降低对精度的要求。通常,多元线性回归会写作:
$y=b_0+b_1·x_1+b_2·x_2+…+$<br>
$b_{n-1}·x_{n-1}+b_n·x_n+ε$
这里的$x_1x_2x_n$是自变量,$y$是因变量,$b_0$是截距,$b_1$$b_2$,…,$b_n$是自变量的系数,$ε$是随机误差。
在线性回归中,为了实现最小化$ε$的目标,我们可以使用最小二乘法进行直线的拟合。最小二乘法通过最小化误差的平方和,来寻找和观测数据匹配的最佳函数。由于这些内容有些抽象,下面我会结合一些例子来解释最小二乘法的核心思想,以及如何使用这种方法进行求解。
## 使用观测值拟合
在详细阐述最小二乘法之前我们先来回顾一下第32讲介绍的模型拟合。在监督式学习中拟合模型其实是指通过模型的假设和训练样本推导出具体参数的过程。有了这些参数我们就能对新的数据进行预测。而在线性回归中我们需要找到观测数据之间的线性关系。
假设我们有两个观测数据,对应于二维空间中的两个点,这两个点可以确定唯一的一条直线,两者呈现线性关系。你可以参考下面这张图。
<img src="https://static001.geekbang.org/resource/image/0d/96/0dc299d455a9010bf4c64f8d12248e96.png" alt="">
之后,我们又加入了一个点。这个点不在原来的那条直线上。
<img src="https://static001.geekbang.org/resource/image/51/7c/51d4747efbae8c38eb1cc8ebc41c5e7c.png" alt="">
这个时候,从线性方程的角度来看,就不存在精确解了。因为没有哪条直线能同时穿过这三个点。这张图片也体现了线性回归分析和求解线性方程组是不一样的,线性回归并不需要求精确解。
如果我们加入更多的观察点,就更是如此了。比如下面这张图。
<img src="https://static001.geekbang.org/resource/image/c4/2e/c499346f631f1decd062335a45f41f2e.png" alt="">
从上图中你应该可以看出,这根直线不是完全精准地穿过这些点,而只是经过了其中两个,大部分点和这根直线有一定距离。这个时候,线性回归就有用武之地了。
由于我们假设ε的存在,因此在线性回归中,我们允许某条直线只穿过其中少量的点。不过,既然我们允许这种情况发生,那么就存在无穷多这样的直线。比如下面我随便画了几条,都是可以的。
<img src="https://static001.geekbang.org/resource/image/e5/e6/e5bae0f3a457f75f20f16c02512dcbe6.png" alt="">
当然,我们从直觉出发,一定不会选取那些远离这些点的直线,而是会选取尽可能靠近这些点的那些线。比如下面这张图里展示的这两条。
<img src="https://static001.geekbang.org/resource/image/f1/f3/f17c54e4d9018d3a9f58e9711e2f00f3.png" alt="">
好了,即然这样,我们就需要定义哪根线是最优的,以及在给出了最优的定义之后,如何能求解出这条最优的直线呢?最小二乘法可以回答这两个问题,下面我们具体来看。
## 最小二乘法
最小二乘法的主要思想就是求解未知参数,使得理论值与观测值之差(即误差,或者说残差)的平方和达到最小。我们可以使用下面这个公式来描述。
<img src="https://static001.geekbang.org/resource/image/82/f8/82c94c629f2cb09dff9a8014186b84f8.png" alt="">
其中,$y_i$表示来自数据样本的观测值,而$y$^是假设的函数的理论值,$ε$就是我们之前提到的误差,在机器学习中也常被称为损失函数,它是观测值和真实值之差的平方和。最小二乘法里的“二乘”就是指的平方操作。有了这个公式,我们的目标就很清楚了,就是要发现使ε最小化时候的参数。
那么最小二乘法是如何利用最小化$ε$的这个条件来求解的呢?让我们从矩阵的角度出发来理解整个过程。
有了上面的定义之后,我们就可以写出最小二乘问题的矩阵形式。
$min||XB-Y||_{2}^{2}$
其中$B$为系数矩阵,$X$为自变量矩阵,$Y$为因变量矩阵。换句话说,我们要在向量空间中,找到一个$B$,使向量$XB$与$Y$之间欧氏距离的平方数最小的$B$。
结合之前所讲的矩阵点乘知识,我们把上述式子改写为:
$||XB-Y||_{2}^{2}=tr((XB-Y)(XB-Y))$
其中$(XB-Y)$表示矩阵$(XB-Y)$的转置。而$tr()$函数表示取对角线上所有元素的和,对于某个矩阵$A$来说,$tr(A)$的值计算如下:
<img src="https://static001.geekbang.org/resource/image/ca/35/ca47283036c28ac72e281fb21a105735.png" alt="">
进一步,根据矩阵的运算法则,我们有:
$tr((XB-Y)(XB-Y))$<br>
$=tr(BX-Y)(XB-Y)$<br>
$=tr(BXXB-BXY-YXB+YY)$
因此我们可以得到:
$||XB-Y||_{2}^{2}$<br>
$=tr((XB-Y)(XB-Y))$<br>
$=tr(BX-Y)(XB-Y)$<br>
$=tr(BXXB-BXY-YXB+YY)$
我们知道求最极值问题直接对应的就是导数为0因此我对上述的矩阵形式进行求导得到如下的式子
$\frac{d||XB-Y||_{2}^{2}}{dB}$<br>
$=\frac{d(tr(BXXB-BXY-YXB+YY))}{dB}$<br>
$=XXB+XXB-XY-XY$<br>
$=2XXB-2XY$
如果要$||XB-Y||_{2}^{2}$最小,就要满足两个条件。
第一个条件是$\frac{d||XB-Y||_{2}^{2}}{dB}$为0也就是$2XXB-2XY=0$。
第二个条件是$\frac{d(2XXB-2XY)}{dB}&gt;0$。
由于$\frac{d(2XXB-2XY)}{dB}=2XX&gt;0$,所以,第二个条件是满足的。只要$2XXB=2XY$。
我们就能获得$ε$的最小值。从这个条件出发,我们就能求出矩阵$B$
$2XXB=2XY$<br>
$XXB=XY$<br>
$(XX)^{-1}XXB=(XX)^{-1}XY$<br>
$IB=(XX)^{-1}XY$<br>
$B=(XX)^{-1}XY$
其中$I$为单位矩阵。而$(XX)^{-1}$表示$XX$的逆矩阵。所以,最终系数矩阵为:
$B=(XX)^{-1}XY$
## 补充证明和解释
为了保持推导的连贯性,在上述的推导过程中,我跳过了几个步骤的证明。下面我会给出详细的解释,供你更深入的学习和研究。
### 步骤a
$(XB)=BX$
### 证明:
对于$XB$中的每个元素$xb_{i,j}$,有:
<img src="https://static001.geekbang.org/resource/image/5f/1d/5fb0b4cae0208e738c50f158b3ecd31d.png" alt="">
而对于$(XB)$中的每个元素$xb_{i,j}$,有:
<img src="https://static001.geekbang.org/resource/image/27/99/27ecb20e94b05529217503a16cfe2399.png" alt="">
对于$B$中的每个元素有:
$b**{i,k}=b**{k,i}$
$X$中的每个元素有:
$x**{k,j}=x**{j,k}$
那么,对于$BX$中的每个元素$bx_{i,j}$,就有:
<img src="https://static001.geekbang.org/resource/image/73/be/736be0c61a9234300bd1c9394e90fdbe.png" alt="">
所以有$(XB) = BX$。
### 步骤b
$(XB-Y)=BX-Y$
### 证明:
和步骤a类似对于$XB-Y$中的每个元素 $xb-y_{i,j}$有:
### 步骤c
$\frac{d(tr(BXY))}{dB}=XY$
### 证明:
<img src="https://static001.geekbang.org/resource/image/bd/57/bd0d66ab7fee4a37a0fac13b677d7f57.png" alt="">
同理,可以证明:
$\frac{d(tr(YXB))}{dB}=(YX)=XY$
### 步骤d
$\frac{d(tr(BXXB))}{dB}=2XXB$
### 证明:
$\frac{d(tr(BXXB))}{dB}$<br>
$=\frac{d(tr(B(XXB)))}{dB}+\frac{d(tr((BXX)B))}{dB}$<br>
$=(XXB)+(BXX)$<br>
$=XXB+XXB$<br>
$=2XXB$
### 步骤e
常量对于变量求导为0例如
$\frac{d(YY)}{dB}=0$
好了,弄明白了这些细节上的证明,你就能更好地理解最小二乘法中的推导步骤。不过,你可能还是会奇怪,为什么最终要对矩阵求导数来求ε的最小值。最后,我们就聊聊如何使用求导获取极小值。
极值是一个函数的极大值或极小值。如果一个函数在一点的某个邻域内每个地方都有确定的值,而以该点所对应的值是最大(小)的,那么这函数在该点的值就是一个极大(小)值。而函数的极值可以通过它的一阶和二阶导数来确定。
对于一元可微函数$f(x)$,它在某点$x_0$有极值的充分必要条件是$f(x)$在$x_0$的邻域上一阶可导,在$x_0$处二阶可导,且一阶导数$f(x_0)=0$,二阶导数$f(x_0)≠0$。其中$f$和$f$分别表示一阶导数和二阶导数。
在一阶导数$f(x0)=0$的情况下,如果$f(x0)&lt;0$,则$f$在$x_0$取得极大值;如果$f(x0)&gt;0$,则$f$在$x_0$取得极小值。这就是为什么在求矩阵$B$的时候,我们要求$2XXB-2XY$为$0$,并且$2XXB-2XY$的导数要大于$0$,这样我们才能确保求得极小值。
## 总结
今天我们探讨了为什么简单的线性方程组无法满足线性函数拟合的需求,最主要的原因就是现实的观测数据往往不是精确的线性关系,存在一定的误差。我们所要做的就是,在允许一定范围的误差前提下,找到一种线性关系,尽量的满足观察数据,使得我们所定义的误差最小。
最小二乘法通过向量空间的欧氏距离之平方,定义了预测值和真实值之间的误差。在给定自变量和因变量的观测值之后,最小二乘法可以帮助我们推导出所有自变量的系数,并最小化误差。我使用矩阵的形式,为你推导了整个过程。
不过到目前为止我们都只是从理论上理解最小二乘法可能你还没有太深的感触。下一节我会通过一个具体的例子来逐步进行演算并使用Python代码对最终的结果进行验证。
## 思考题
还记得在29讲的线性回归案例吗我们使用了Boston Housing的数据拟合出了十多个自变量的系数。请使用这些系数计算train.csv中所有样本因变量预测值和真实值之间的误差。你可以使用Python代码来实现一下。
欢迎留言和我分享,也欢迎你在留言区写下今天的学习笔记。你可以点击“请朋友读”,把今天的内容分享给你的好友,和他一起精进。

View File

@@ -0,0 +1,198 @@
<audio id="audio" title="41 | 线性回归(下):如何使用最小二乘法进行效果验证?" controls="" preload="none"><source id="mp3" src="https://static001.geekbang.org/resource/audio/b5/36/b570e73c9233b0378c2d614a52d06236.mp3"></audio>
你好,我是黄申。
上一节我们已经解释了最小二乘法的核心思想和具体推导过程。今天我们就用实际的数据操练一下,这样你的印象就会更加深刻。我会使用几个具体的例子,演示一下如何使用最小二乘法的结论,通过观测到的自变量和因变量值,来推算系数,并使用这个系数来进行新的预测。
## 基于最小二乘法的求解
假想我们手头上有一个数据集里面有3条数据记录。每条数据记录有2维特征也就是2个自变量和1个因变量。
<img src="https://static001.geekbang.org/resource/image/94/b4/9427dc10b0745cb5680e911e0d0d15b4.png" alt="">
如果我们假设这些自变量和因变量都是线性的关系,那么我们就可以使用如下这种线性方程,来表示数据集中的样本:
$b_1·0+b_2·1=1.5$<br>
$b_1·1-b_2·1=-0.5$<br>
$b_1·2+b_2·8=14$
也就是说,我们通过观察数据已知了自变量$x_1$、$x_2$和因变量$y$的值,而要求解的是$b_1$和$b_2$这两个系数。如果我们能求出$b_1$和$b_2$,那么在处理新数据的时候,就能根据新的自变量$x_1$和$x_2$的取值,来预测$y$的值。
可是我们说过,由实际项目中的数据集所构成的这类方程组,在绝大多数情况下,都没有精确解。所以这个时候我们没法使用之前介绍的高斯消元法,而是要考虑最小二乘法。根据上一节的结论,我们知道对于系数矩阵$B$,有:
$B=(XX)^{-1}XY$
既然有了这个公式,要求$B$就不难了,让我们从最基本的几个矩阵开始。
<img src="https://static001.geekbang.org/resource/image/f2/9d/f2f70be34564a2e64071cd623d0e3f9d.png" alt="">
矩阵$(XX)^{-1}$的求解稍微繁琐一点。逆矩阵的求法我还没讲解过,之前我们说过线性方程组之中,高斯消元和回代的过程,就是把系数矩阵变为单位矩阵的过程。我们可以利用这点,来求解$X^{-1}$。我们把原始的系数矩阵$X$列在左边,然后把单位矩阵列在右边,像$[X | I]$这种形式,
其中$I$表示单位矩阵。
然后我们对左侧的矩阵进行高斯消元和回代把左边矩阵X变为单位矩阵。同时我们也把这个相应的矩阵操作运用在右侧。这样当左侧变为单位矩阵之后那么右侧的矩阵就是原始矩阵$X$的逆矩阵$X^{-1}$,具体证明如下:
$[X | I]$<br>
$[X^{-1}X | X^{-1}I]$<br>
$[I | X^{-1}I]$<br>
$[I | X^{-1}]$
好了,给定下面的$XX$矩阵之后,我们使用上述方法来求$(XX)^{-1}$ 。我把具体的推导过程列在了这里。
<img src="https://static001.geekbang.org/resource/image/1a/ad/1a6f3815a9895fc80f54124a1dae7cad.png" alt="">
求出$(XX)^{-1}$之后,我们就可以使用$B=(XX)^{-1}XY$来计算矩阵B。
<img src="https://static001.geekbang.org/resource/image/fe/3d/fe4c0b674649e5a18b5992c71316253d.png" alt="">
最终,我们求出系数矩阵为$[1 1.5]$,也就是说$b_1 = 1$, $b_2 = 1.5$。实际上,这两个数值是精确解。我们用高斯消元也是能获得同样结果的。接下来,让我稍微修改一下$y$值,让这个方程组没有精确解。
$b_1·0+b_2·1=1.4$<br>
$b_1·1-b_2·1=-0.48$<br>
$b_1·2+b_2·8=13.2$
你可以尝试高斯消元法对这个方程组求解,你会发现只要两个方程就能求出解,但是无论是哪两个方程求出的解,都无法满足第三个方程。
那么通过最小二乘法我们能不能求导一个近似解保证_ε_足够小呢下面让我们遵循之前求解$(XX)^{-1}XY$的过程,来计算$B$。
<img src="https://static001.geekbang.org/resource/image/f3/10/f3653f2fd1220936834777b62cced310.png" alt="">
计算完毕之后,你会发现两个系数的值分别变为$b_1 = 0.938, b_2 = 1.415$。由于这不是精确解,所以让我们看看有了这系数矩阵$B$之后,原有的观测数据中,真实值和预测值的差别。
首先我们通过系数矩阵$B$和自变量矩阵$X$计算出来预测值。
<img src="https://static001.geekbang.org/resource/image/a3/7a/a3cf56dc09e8e53304060a418ea9707a.png" alt="">
然后是样本数据中的观测值。这里我们假设这些值是真实值。
<img src="https://static001.geekbang.org/resource/image/a6/d9/a693f3f03a81c01d7f34e615ce473cd9.png" alt="">
根据误差$ε$的定义,我们可以得到:
<img src="https://static001.geekbang.org/resource/image/94/20/947a02d7580305c8f3a6e19d64dbf120.png" alt="">
说到这里,你可能会怀疑,通过最小二乘法所求得的系数$b_1 = 0.949$和$b_2 = 1.415$,是不是能让$ε$最小呢?这里,我们随机的修改一下这两个系数,变为$b_1 = 0.95$和$b_2 = 1.42$,然后我们再次计算预测的$y$值和$ε$。
<img src="https://static001.geekbang.org/resource/image/36/0e/36d8fb93ae5967f2e75cabd4da43890e.png" alt="">
很明显0.064是大于之前的0.0158。
这两次计算预测值_y_的过程其实也是我们使用线性回归对新的数据进行预测的过程。简短地总结一下线性回归模型根据大量的训练样本推算出系数矩阵$B$,然后根据新数据的自变量$X$向量或者矩阵,计算出因变量的值,作为新数据的预测。
## Python代码实现
这一部分我们使用Python的代码来验证一下之前的推算结果是不是正确并看看最小二乘法和Python sklearn库中的线性回归这两种结果的对比。
首先我们使用Python numpy库中的矩阵操作来实现最小二乘法。主要的函数操作涉及矩阵的转置、点乘和求逆。具体的代码和注释我列在了下方。
```
from numpy import *
x = mat([[0,1],[1,-1],[2,8]])
y = mat([[1.4],[-0.48],[13.2]])
# 分别求出矩阵X'、X'X、(X'X)的逆
# 注意这里的I表示逆矩阵而不是单位矩阵
print(&quot;X矩阵的转置X'\n&quot;, x.transpose())
print(&quot;\nX'点乘X\n&quot;, x.transpose().dot(x))
print(&quot;\nX'X矩阵的逆\n&quot;, (x.transpose().dot(x)).I)
print(&quot;\nX'X矩阵的逆点乘X'\n&quot;, (x.transpose().dot(x)).I.dot(x.transpose()))
print(&quot;\n系数矩阵B\n&quot;, (x.transpose().dot(x)).I.dot(x.transpose()).dot(y))
```
通过上述代码,你可以看到每一步的结果,以及最终的矩阵$B$。你可以把输出结果和之前手动推算的结果进行对比,看看是不是一致。
除此之外我们还可把最小二乘法的线性拟合结果和sklearn库中的LinearRegression().fit()函数的结果相比较,具体的代码和注释我也放在了这里。
```
import pandas as pd
from sklearn.linear_model import LinearRegression
df = pd.read_csv(&quot;/Users/shenhuang/Data/test.csv&quot;)
df_features = df.drop(['y'], axis=1) #Dataframe中除了最后一列其余列都是特征或者说自变量
df_targets = df['y'] #Dataframe最后一列是目标变量或者说因变量
print(df_features, df_targets)
regression = LinearRegression().fit(df_features, df_targets) #使用特征和目标数据,拟合线性回归模型
print(regression.score(df_features, df_targets)) #拟合程度的好坏
print(regression.intercept_)
print(regression.coef_) #各个特征所对应的系数
```
其中test.csv文件的内容我也列在了这里。
$x_1,x_2,y$<br>
$0,1,1.4$<br>
$1,-1,-0.48$<br>
$2,8,13.2$
这样写是为了方便我们使用pandas读取csv文件并加载为dataframe。
在最终的结果中1.0表示拟合程度非常好,而-0.014545454545452863表示一个截距,[0.94909091 1.41454545]表示系数$b_1$和$b_2$的值。这个结果和我们最小二乘法的结果有所差别主要原因是LinearRegression().fit()默认考虑了有线性函数存在截距的情况。那么我们使用最小二乘法是不是也可以考虑有截距的情况呢?答案是肯定的,不过我们首先要略微修改一下方程组和矩阵$X$。如果我们假设有截距存在,那么线性回归方程就要改写为:
$b_0+b_1·x_1+b_2·x_2+…+b_{n-1}·x_{n-1}+b_n·x_n=y$
其中,$b_0$表示截距,而我们这里的方程组用例就要改写为:
$b_0+b_1·0+b_2·1=1.4$<br>
$b_0+b_1·1-b_2·1=-0.48$<br>
$b_0+b_1·2+b_2·8=13.2$
而矩阵$X$要改写为:
<img src="https://static001.geekbang.org/resource/image/41/fb/417cf726043be38e7cf0a46a9eea3bfb.png" alt="">
然后我们再执行下面这段代码。
```
from numpy import *
x = mat([[1,0,1],[1,1,-1],[1,2,8]])
y = mat([[1.4],[-0.48],[13.2]])
print(&quot;\n系数矩阵B\n&quot;, (x.transpose().dot(x)).I.dot(x.transpose()).dot(y))
```
你就会得到:
```
系数矩阵B
[[-0.01454545]
[ 0.94909091]
[ 1.41454545]]
```
这个结果和LinearRegression().fit()的结果就一致了。
需要注意的是,使用线性回归的时候,我们都有一个前提假设,那就是数据的自变量和因变量之间现线性关系。如果不是线性关系,那么使用线性模型来拟合的效果一定不好。比如,之前在解释欠拟合的时候,我用过下面这个例子。
<img src="https://static001.geekbang.org/resource/image/0b/a5/0b9f8c88a846626c74a803ee645bc1a5.png" alt="">
上面这张图的数据分布并没有表达线性关系,所以我们需要对原始的数据进行非线性的变换,或者是使用非线性的模型来拟合。
那么我们如何判断一个数据集是不是能用线性模型表示呢在线性回归中我们可以使用决定系数R2。这个统计指标使用了回归平方和与总平方和之比是反映模型拟合度的重要指标。它的取值在0到1之间越接近于1表示拟合的程度越好、数据分布越接近线性关系。随着自变量个数的增加R2将不断增大因此我们还需要考虑方程所包含的自变量个数对R2的影响这个时候可使用校正的决定系数Rc2。所以在使用各种科学计算库进行线性回归时你需要关注R2或者Rc2来看看是不是一个好的线性拟合。在之前的代码实践中我们提到的regression.score函数其实就是返回了线性回归的R2。
## 总结
今天我们使用了具体的案例来推导最小二乘法的计算过程并用Python代码进行了验证。通过最近3节的讲解相信你对线性方程组求精确解、求近似解、以及如何在线性回归中运用这些方法有了更加深入的理解。
实际上,从广义上来说,最小二乘法不仅可以用于线性回归,还可以用于非线性的回归。其主要思想还是要确保误差ε最小,但是由于现在的函数是非线性的,所以不能使用求多元方程求解的办法来得到参数估计值,而需要采用迭代的优化算法来求解,比如梯度下降法、随机梯度下降法和牛顿法。
## 思考题
我这里给出一个新的方程组,请通过最小二乘法推算出系数的近似解,并使用你熟悉的语言进行验证。
$b_1+b_2·3+b_3·(-7)=-7.5$<br>
$b_1·2+b_2·5+b_3·4=5.2$<br>
$b_1·(-3)+b_2·(-7)+b_3·(-2)=-7.5$<br>
$b_1·1+b_2·4+b_3·(-12)=-15$
欢迎留言和我分享,也欢迎你在留言区写下今天的学习笔记。你可以点击“请朋友读”,把今天的内容分享给你的好友,和他一起精进。

View File

@@ -0,0 +1,140 @@
<audio id="audio" title="42 | PCA主成分分析如何利用协方差矩阵来降维" controls="" preload="none"><source id="mp3" src="https://static001.geekbang.org/resource/audio/6d/06/6d069e84c3d081776283e9d1e4403106.mp3"></audio>
你好,我是黄申。
在概率统计模块,我详细讲解了如何使用各种统计指标来进行特征的选择,降低用于监督式学习的特征之维度。接下来的几节,我会阐述两种针对数值型特征,更为通用的降维方法,它们是**主成分分析PCA**Principal Component Analysis和**奇异值分解SVD**Singular Value Decomposition。这两种方法是从矩阵分析的角度出发找出数据分布之间的关系从而达到降低维度的目的因此并不需要监督式学习中样本标签和特征之间的关系。
## PCA分析法的主要步骤
我们先从主成分分析PCA开始看。
在解释这个方法之前,我先带你快速回顾一下什么是特征的降维。在机器学习领域中,我们要进行大量的特征工程,把物品的特征转换成计算机所能处理的各种数据。通常,我们增加物品的特征,就有可能提升机器学习的效果。可是,随着特征数量不断的增加,特征向量的维度也会不断上升。这不仅会加大机器学习的难度,还会影响最终的准确度。针对这种情形,我们需要过滤掉一些不重要的特征,或者是把某些相关的特征合并起来,最终达到在减少特征维度的同时,尽量保留原始数据所包含的信息。
了解了这些我们再来看今天要讲解的PCA方法。它的主要步骤其实并不复杂我一说你就能明白但是为什么要这么做你可能并不理解。咱们学习一个概念或者方法不仅要知道它是什么还要明白是怎么来的这样你就能知其然知其所以然明白背后的逻辑达到灵活运用。因此我先从它的运算步骤入手给你讲清楚每一步然后再解释方法背后的核心思想。
和线性回归的案例一样我们使用一个矩阵来表示数据集。我们假设数据集中有m个样本、n维特征而这些特征都是数值型的那么这个集合可以按照如下的方式来展示。
<img src="https://static001.geekbang.org/resource/image/10/e2/10cf8973fd0f94e778c808bdda2881e2.png" alt="">
那么这个样本集的矩阵形式就是这样的:
<img src="https://static001.geekbang.org/resource/image/fb/20/fb10462182f9cc58b389771316f40720.png" alt="">
这个矩阵是m×n维的其中每一行表示一个样本而每一列表示一维特征。让我们把这个矩阵称作样本矩阵现在我们的问题是能不能通过某种方法找到一种变换可以降低这个矩阵的列数也就是特征的维数并且尽可能的保留原始数据中有用的信息
针对这个问题PCA分析法提出了一种可行的解决方案。它包括了下面这样几个主要的步骤
<li>
标准化样本矩阵中的原始数据;
</li>
<li>
获取标准化数据的协方差矩阵;
</li>
<li>
计算协方差矩阵的特征值和特征向量;
</li>
<li>
依照特征值的大小,挑选主要的特征向量;
</li>
<li>
生成新的特征。
</li>
下面,我们一步步来看。
### 1.标准化原始数据
之前我们已经介绍过特征标准化,这里我们需要进行同样的处理,才能让每维特征的重要性具有可比性。为了便于你回顾,我把标准化的公式列在了这里。
$x=\frac{x-μ}{σ}$
其中$x$为原始值,$u$为均值,$σ$为标准差,$x$是变换后的值。需要注意的是,这里标准化的数据是针对同一种特征,也是在同一个特征维度之内。不同维度的特征不能放在一起进行标准化。
### 2.获取协方差矩阵
首先我们来看一下什么是协方差Covariance以及协方差矩阵。协方差是用于衡量两个变量的总体误差。假设两个变量分别是$x$和$y$,而它们的采样数量都是$m$,那么协方差的计算公式就是如下这种形式:
<img src="https://static001.geekbang.org/resource/image/27/c2/2732d3255408c3bb4e01f6c2bd4499c2.png" alt="">
其中$x_k$表示变量$x$的第$k$个采样数据,$\bar{x}$表示这$k$个采样的平均值。而当两个变量是相同时,协方差就变成了方差。
那么,这里的协方差矩阵又是什么呢?我们刚刚提到了样本矩阵,假设$X_{,1}$表示样本矩阵$X$的第$1$列,$X_{,2}$表示样本矩阵$X$的第$2$列,依次类推。而$cov(X_{,1},X_{,1})$表示第1列向量和自己的协方差而$cov(X_{,1},X_{,2})$表示第1列向量和第2列向量之间的协方差。结合之前协方差的定义我们可以得知
<img src="https://static001.geekbang.org/resource/image/a3/a0/a3664cc303c7473df6dae33c6a0fbca0.png" alt="">
其中,$x_{k,i}$表示矩阵中第$k$行,第$i$列的元素。 $\bar{X_{,i}}$表示第$i$列的平均值。
有了这些符号表示,我们就可以生成下面这种协方差矩阵。
<img src="https://static001.geekbang.org/resource/image/ff/6c/ffe746718b2dde0a76051066326d226c.png" alt="">
从协方差的定义可以看出,$cov(X_{,i},X_{,j})=cov(X_{,j},X_{,i})$,所以$COV$是个对称矩阵。另外,我们刚刚提到,对于$cov(X_{,i},X_{,j})$,如果$i=j$,那么$cov(X_{,i},X_{,j})$也就是$X_{,j}$这组数的方差。所以这个对称矩阵的主对角线上的值就是各维特征的方差。
### 3.计算协方差矩阵的特征值和特征向量
需要注意的是这里所说的矩阵的特征向量和机器学习中的特征向量Feature Vector完全是两回事。矩阵的特征值和特征向量是线性代数中两个非常重要的概念。对于一个矩阵$X$,如果能找到向量$v$和标量$λ$,使得下面这个式子成立。
$Xv=λv$
那么,我们就说$v$是矩阵$X$的特征向量,而$λ$是矩阵$X$的特征值。矩阵的特征向量和特征值可能不止一个。说到这里,你可能会好奇,特征向量和特征值表示什么意思呢?我们为什么要关心这两个概念呢?简单的来说,我们可以把向量$v$左乘一个矩阵$X$看做对$v$进行旋转或拉伸,而这种旋转和拉伸都是由于左乘矩阵$X$后,所产生的“运动”所导致的。特征向量$v$表示了矩阵$X$运动的方向,特征值$λ$表示了运动的幅度,这两者结合就能描述左乘矩阵$X$所带来的效果因此被看作矩阵的“特征”。在PCA中的主成分就是指特征向量而对应的特征值的大小就表示这个特征向量或者说主成分的重要程度。特征值越大重要程度越高我们要优先现在这个主成分并利用这个主成分对原始数据进行变换。
如果你还是有些困惑我会在下面一节讲解更多的细节。现在让我们先来看看给定一个矩阵如何计算它的特征值和特征向量并完成PCA分析的剩余步骤。我在下面列出了计算特征值的推导过程
$Xv=λv$<br>
$Xv-λv=0$<br>
$Xv-λIv=0$<br>
$(X-λI)v=0$
其中I是单位矩阵。对于上面推导中的最后一步我们需要计算矩阵的行列式。
<img src="https://static001.geekbang.org/resource/image/37/d0/373722f933c8a6afc97052c5f3686ed0.png" alt="">
$(x_{1,1}-λ)(x_{2,2}-λ)…(x_{n,n}-λ)+x_{1,2}x_{2,3}…x_{n-1,n}x_{n,1}+…)-(x_{n,1}x_{n-1,2}…x_{2,n-1}x_{1,n})=0$
最后,通过解这个方程式,我们就能求得各种λ的解,而这些解就是特征值。计算完特征值,我们可以把不同的λ值代入$λE-A$,来获取特征向量。
<img src="https://static001.geekbang.org/resource/image/17/cb/1776be34cd7d73453a33e7259abbe0cb.png" alt="">
### 4.挑选主要的特征向量,转换原始数据
假设我们获得了k个特征值和对应的特征向量那么我们就有
$Xv_1=λ_1v_1$<br>
$Xv_2=λ_2v_2$<br>
$…$<br>
$Xv_k=λ_kv_k$
按照所对应的λ数值的大小对这k组的v排序。排名靠前的v就是最重要的特征向量。
假设我们只取前k1个最重要的特征那么我们使用这k1个特征向量组成一个n×k1维的矩阵D。
把包含原始数据的m×n维矩阵X左乘矩阵D就能重新获得一个m×k1维的矩阵达到了降维的目的。
有的时候我们无法确定k1取多少合适。一种常见的做法是看前k1个特征值的和占所有特征值总和的百分比。假设一共有10个特征值总和是100最大的特征值是80那么第一大特征值占整个特征值之和的80%我们认为它能表示80%的信息量还不够多。那我们就继续看第二大的特征值它是15前两个特征值之和有95占比达到了95%如果我们认为足够了那么就可以只选前两大特征值把原始数据的特征维度从10维降到2维。
## 小结
这一节我首先简要地重温了为什么有时候需要进行特征的降维和基于分类标签的特征选择。随后我引出了和特征选择不同的另一种方法基于矩阵操作的PCA主成分分析。这种方法的几个主要步骤包括标准化原始数据、获得不同特征的协方差矩阵、计算协方差矩阵的特征值和特征向量、选择最重要的主成分以及通过所选择的主成分来转换原始的数据集。
要理解PCA分析法是有一定难度的主要是因为两点原因第一计算的步骤有些复杂。第二这个方法的核心思路有些抽象。这两点可能会让刚刚接触PCA的学习者感到无从下手。
为了帮助你更好的理解下一节我会使用一个示例的矩阵进行详细的推算并用两种Python代码进行结果的验证。除此之外我还会分析几个要点包括PCA为什么使用协方差矩阵这个矩阵的特征值和特征向量又表示什么为什么特征值最大的主成分涵盖最多的信息量明白了这些你就能深入理解为什么PCA分析法要有这些步骤以及每一步都代表什么含义。
## 思考题
给定这样一个矩阵:
<img src="https://static001.geekbang.org/resource/image/96/9f/962b0abb078974d1d964627e43081f9f.png" alt="">
假设这个矩阵的每一列表示一个特征的维度,每一行表示一个样本。请完成
<li>
按照列(也就是同一个特征维度)进行标准化。
</li>
<li>
生成这个矩阵的协方差矩阵。
</li>
欢迎留言和我分享,也欢迎你在留言区写下今天的学习笔记。你可以点击“请朋友读”,把今天的内容分享给你的好友,和他一起精进。

View File

@@ -0,0 +1,212 @@
<audio id="audio" title="43 | PCA主成分分析为什么要计算协方差矩阵的特征值和特征向量" controls="" preload="none"><source id="mp3" src="https://static001.geekbang.org/resource/audio/e7/5f/e7263c041cac42252155130600a99d5f.mp3"></audio>
你好我是黄申今天我们继续来聊PCA主成分分析的下半部分。
上一节我们讲解了一种特征降维的方法PCA主成分分析。这个方法主要是利用不同维度特征之间的协方差构造一个协方差矩阵然后获取这个矩阵的特征值和特征向量。根据特征值的大小我们可以选取那些更为重要的特征向量或者说主成分。最终根据这些主成分我们就可以对原始的数据矩阵进行降维。
PCA方法的操作步骤有些繁琐并且背后的理论支持也不是很直观因此对于初学者来说并不好理解。考虑到这些我今天会使用一个具体的矩阵示例详细讲解每一步操作的过程和结果并辅以基于Python的核心代码进行分析验证。除此之外我还会从多个角度出发分析PCA方法背后的理论帮助你进一步的理解和记忆。
## 基于Python的案例分析
这么说可能有一些抽象让我使用一个具体的案例来帮你理解。假设我们有一个样本集合包含了3个样本每个样本有3维特征$x_1$$x_2$和$x_3$。
<img src="https://static001.geekbang.org/resource/image/96/9f/962b0abb078974d1d964627e43081f9f.png" alt="">
在标准化的时候需要注意的是我们的分母都使用m而不是m-1这是为了和之后Python中sklearn库的默认实现保持一致。
首先需要获取标准化之后的数据。
第一维特征的数据是12-3。平均值是0方差是
$\sqrt{\frac{1+4+9}{3}}≈2.16$
标准化之后第一维特征的数据是1/2.16=0.4632/2.16=0.926-3/2.16=-1.389。以此类推,我们可以获得第二个维度和第三个维度标准化之后的数据。
当然全部手动计算工作量不小这时可以让计算机做它擅长的事情重复性计算。下面的Python代码展示了如何对样本矩阵的数据进行标准化。
```
from numpy import *
from numpy import linalg as LA
from sklearn.preprocessing import scale
# 原始数据包含了3个样本和3个特征每一行表示一个样本每一列表示一维特征
x = mat([[1,3,-7],[2,5,-14],[-3,-7,2]])
# 矩阵按列进行标准化
x_s = scale(x, with_mean=True, with_std=True, axis=0)
print(&quot;标准化后的矩阵:&quot;, x_s)
```
其中scale函数使用了axis=0表示对列进行标准化因为目前的矩阵排列中每一列代表一个特征维度这点需要注意。如果矩阵排列中每一行代表一个特征维度那么可以使用axis=1对行进行标准化。
最终标准化之后的矩阵是这样的:
<img src="https://static001.geekbang.org/resource/image/94/2e/94b28f8127ced76a795e8ef1a38a532e.png" alt="">
接下来是协方差的计算。对于第1维向量的方差
$\frac{0.463^2 +0.926^2+(-1.389^2)}{2}≈1.5$
第1维和第2维向量之间的协方差是
$\frac{0.463×0.508+0.926×0.889+(-1.389)×(-1.397)}{2}≈1.5$
以此类推我们就可以获得完整的协方差矩阵。同样的为了减少推算的工作量我们可以使用Python代码获得协方差矩阵。
```
# 计算协方差矩阵,注意这里需要先进行转置,因为这里的函数是看行与行之间的协方差
x_cov = cov(x_s.transpose())
# 输出协方差矩阵
print(&quot;协方差矩阵:\n&quot;, x_cov, &quot;\n&quot;)
```
和sklearn中的标准化函数scale有所不同numpy中的协方差函数cov除以的是(m-1)而不是m。最终完整的协方差矩阵是
<img src="https://static001.geekbang.org/resource/image/58/31/585248158037b17596803624dd708d31.png" alt="">
然后,我们要求解协方差矩阵的特征值和特征向量。
<img src="https://static001.geekbang.org/resource/image/d7/38/d7344d083edfddad1cf64e8faf52f038.png" alt="">
最后化简为:
$-λ^3+4.5λ^2=0.343λ=0$<br>
$λ(0.0777-λ)(λ-4.4223)=0$
所以$λ$有3个近似解分别是0、0.0777和4.4223。
特征向量的求解过程如果手动推算比较繁琐我们还是利用Python语言直接求出特征值和对应的特征向量。
```
# 求协方差矩阵的特征值和特征向量
eigVals,eigVects = LA.eig(x_cov)
print(&quot;协方差矩阵的特征值:&quot;, eigVals)
print(&quot;协方差的特征向量(主成分):\n&quot;, eigVects, &quot;\n&quot;)
```
我们可以得到三个特征值及它们对应的特征向量。
<img src="https://static001.geekbang.org/resource/image/ad/94/ad822f51bc6d1f741717d2abe6f3b694.png" alt="">
需要注意Python代码输出的特征向量是列向量而我表格中列出的是行向量。
我使用下面的这段代码,找出特征值最大的特征向量,也就是最重要的主成分,然后利用这个主成分,对原始的样本矩阵进行变换。
```
# 找到最大的特征值,及其对应的特征向量
max_eigVal = -1
max_eigVal_index = -1
for i in range(0, eigVals.size):
if (eigVals[i] &gt; max_eigVal):
max_eigVal = eigVals[i]
max_eigVal_index = i
eigVect_with_max_eigVal = eigVects[:,max_eigVal_index]
# 输出最大的特征值及其对应的特征向量,也就是第一个主成分
print(&quot;最大的特征值:&quot;, max_eigVal)
print(&quot;最大特征值所对应的特征向量:&quot;, eigVect_with_max_eigVal)
# 输出变换后的数据矩阵。注意这里的三个值是表示三个样本而特征从3维变为1维了。
print(&quot;变换后的数据矩阵:&quot;, x_s.dot(eigVect_with_max_eigVal), &quot;\n&quot;)
```
很明显最大的特征值是4.422311507725755,对应的特征向量是[-0.58077228 -0.57896098 0.57228292]。变换后的样本矩阵是:
<img src="https://static001.geekbang.org/resource/image/48/ce/486d7bd7918b92fc7a1ef3798ecfd2ce.png" alt=""><br>
它从原来的3个特征维度降维1个特征维度了。
Python的sklearn库也实现了PCA我们可以通过下面的代码来尝试一下。
```
from sklearn.decomposition import PCA
# 挑选前2个主成分
pca = PCA(n_components=2)
# 进行PCA分析
pca.fit(x_s)
# 输出变换后的数据矩阵。注意这里的三个值是表示三个样本而特征从3维变为1维了。
print(&quot;方差(特征值): &quot;, pca.explained_variance_)
print(&quot;主成分(特征向量)&quot;, pca.components_)
print(&quot;变换后的样本矩阵:&quot;, pca.transform(x_s))
print(&quot;信息量: &quot;, pca.explained_variance_ratio_)
```
这段代码中我把输出的主成分设置为2也就是说挑出前2个最重要的主成分。相应的变化后的样本矩阵有2个特征维度。
<img src="https://static001.geekbang.org/resource/image/11/5f/11271b41ece4f3cfc27a6aee14e4f05f.png" alt="">
除了输出主成分和变换后的矩阵sklearn的PCA分析还提供了信息量的数据。
```
信息量: [0.98273589 0.01726411]
```
它是各个主成分的方差所占的比例表示第一个主成分包含了原始样本矩阵中的98.27%的信息而第二个主成分包含了原始样本矩阵中的1.73%的信息可想而知最后一个主成分提供的信息量基本为0了我们可以忽略不计了。如果我们觉得95%以上的信息量就足够了那么就可以只保留第一个主成分把原始的样本矩阵的特征维度降到1维。
当然学习的更高境界不是仅仅“知其然”还要做到“知其所以然”。即使现在你对PCA的操作步骤了如指掌可能还是有不少疑惑比如为什么我们要使用协方差矩阵这个矩阵的特征值和特征向量又表示什么为什么选择特征值最大的主成分就能涵盖最多的信息量呢不用着急接下来我会给你做出更透彻的解释让你不仅明白如何进行PCA分析同时还明白为什么要这么做。
## PCA背后的核心思想
### 为什么要使用协方差矩阵?
首先要回答的第一个问题是为什么我们要使用样本数据中各个维度之间的协方差来构建一个新的协方差矩阵要弄清楚这一点首先要回到PCA最终的目标降维。降维就是要去除那些表达信息量少或者冗余的维度。
我们首先来看如何定义维度的信息量大小。这里我们认为样本在某个特征上的差异就越大,那么这个特征包含的信息量就越大,就越重要。相反,信息量就越小,需要被过滤掉。很自然,我们就能想到使用某维特征的方差来定义样本在这个特征维度上的差异。
另一方面我们要看如何发现冗余的信息。如果两种特征是有很高的相关性那我们可以从一个维度的值推算出另一个维度的值所表达的信息就是重复的。在概率和统计模块我介绍过多个变量间的相关性而在实际运用中我们可以使用皮尔森Pearson相关系数来描述两个变量之间的线性相关程度。这个系数的取值范围是$[-1,1]$,绝对值越大,说明相关性越高,正数表示正相关,负数表示负相关。
我使用下面这张图,来表示正相关和负相关的涵义。左侧$X$曲线和$Y$曲线有非常近似的变化趋势,当$X$上升$Y$往往也是上升的,$X$下降$Y$往往也下降,这表示两者有较强的正相关性。右侧$X$和$Y$两者相反,当$X$上升的时候,$Y$往往是下降的,$X$下降的时候,$Y$往往是上升,这表示两者有较强的负相关性。
<img src="https://static001.geekbang.org/resource/image/1f/cf/1fb2ca1b83f4d01c9b1e4cd7bc6ef7cf.png" alt="">
皮尔森系数计算公式如下:
<img src="https://static001.geekbang.org/resource/image/75/17/75f30694d8ef651299d322361848a117.png" alt="">
其中$n$表示向量维度,$x_{k,i}$和$x_{k,j}$分别为两个特征维度$i$和$j$在第$k$个采样上的数值。 $\bar{x_{,i}}$和$\bar{x_{,j}}$分别表示两个特征维度上所有样本的均值,$σx$和$σy$分别表示两个特征维度上所有样本的标准差。
我把皮尔森系数的公式稍加变化,你来观察一下皮尔森系数和协方差之间的关系。
<img src="https://static001.geekbang.org/resource/image/52/51/52128104b8b2cacbffd8c2ef720cba51.png" alt="">
你看,变换后的分子不就是协方差吗?而分母类似于标准化数据中的分母。所以在本质上,皮尔森相关系数和数据标准化后的协方差是一致的。
考虑到协方差既可以衡量信息量的大小也可以衡量不同维度之间的相关性因此我们就使用各个维度之间的协方差所构成的矩阵作为PCA分析的对象。就如前面说讲述的这个协方差矩阵主对角线上的元素是各维度上的方差也就体现了信息量而其他元素是两两维度间的协方差也就体现了相关性。
既然协方差矩阵提供了我们所需要的方差和相关性,那么下一步,我们就要考虑对这个矩阵进行怎样的操作了。
### 为什么要计算协方差矩阵的特征值和特征向量?
关于这点,我们可以从两个角度来理解。
第一个角度是对角矩阵。所谓对角矩阵就是说只有矩阵主对角线之上的元素有非0值而其他元素的值都为0。我们刚刚解释了协方差矩阵的主对角线上都是表示信息量的方差而其他元素都是表示相关性的协方差。既然我们希望尽可能保留大信息量的维度而去除相关的维度那么就意味着我们希望对协方差进行对角化尽可能地使得矩阵只有主对角线上有非0元素。
假如我们确实可以把矩阵尽可能的对角化,那么对角化之后的矩阵,它的主对角线上元素就是、或者接近矩阵的特征值,而特征值本身又表示了转换后的方差,也就是信息量。而此时,对应的各个特征向量之间是基本正交的,也就是相关性极低甚至没有相关性。
第二个角度是特征值和特征向量的几何意义。在向量空间中,对某个向量左乘一个矩阵,实际上是对这个向量进行了一次变换。在这个变换的过程中,被左乘的向量主要发生旋转和伸缩这两种变化。如果左乘矩阵对某一个向量或某些向量只发生伸缩变换,不对这些向量产生旋转的效果,那么这些向量就称为这个矩阵的特征向量,而伸缩的比例就是特征值。换句话来说,某个矩阵的特征向量表示了这个矩阵在空间中的变换方向,这些方向都是趋于正交的,而特征值表示每个方向上伸缩的比例。
如果一个特征值很大,那么说明在对应的特征向量所表示的方向上,伸缩幅度很大。这也是为什么,我们需要使用原始的数据去左乘这个特征向量,来获取降维后的新数据。因为这样做可以帮助我们找到一个方向,让它最大程度地包含原有的信息。需要注意的是,这个新的方向,往往不代表原始的特征,而是多个原始特征的组合和缩放。
## 小结
这两节我详细讲解了PCA主成分分析法它是一种针对数值型特征、较为通用的降维方法。和特征选择不同它并不需要监督式学习中的样本标签而是从不同维度特征之间的关系出发进行了一系列的操作和分析。主要步骤包括标准化原始的数据矩阵、构建协方差矩阵、计算这种协方差矩阵的特征值和特征向量、挑选较大特征值所对应的特征向量、进行原始特征数据的转换。如果排名靠前的特征向量或者说主成分已经包括了足够的信息量那么我们就可以通过选择较少的主成分对原始的样本矩阵进行转换从而达到降维的目的。
PCA方法一开始不是很好理解其主要的原因之一是它背后的核心思想并不是很直观。为此我详细解释了为什么PCA会从标准化和协方差入手来构建协方差矩阵。对于同类的特征来说标准化之后的协方差就是方差表示了这一维特征所包含的信息量。而对于不同类的特征来说标准化之后的协方差体现了这两维特征的相关性。鉴于这两个特性我们需要求解协方差矩阵的特征值和特征向量。如果你弄清楚了这几个关键点那么PCA方法也就不难理解了。
## 思考题
到目前为止我们讲解了两种特征降维的方法。第一在监督式学习中基于分类标签的特征选择第二基于特征协方差矩阵的PCA主成分分析。请尝试从你自己的理解来说说这两种降维方法各自的优缺点。
欢迎留言和我分享,也欢迎你在留言区写下今天的学习笔记。你可以点击“请朋友读”,把今天的内容分享给你的好友,和他一起精进。

View File

@@ -0,0 +1,115 @@
<audio id="audio" title="44 | 奇异值分解:如何挖掘潜在的语义关系?" controls="" preload="none"><source id="mp3" src="https://static001.geekbang.org/resource/audio/50/ad/50bf4ea68429ec8f9a0d498a9c1c80ad.mp3"></audio>
你好,我是黄申。
今天,我们来聊另一种降维的方法,**SVD奇异值分解**Singular Value Decomposition。它的核心思路和PCA不同。PCA是通过分析不同维度特征之间的协方差找到包含最多信息量的特征向量从而实现降维。而SVD这种方法试图通过样本矩阵本身的分解找到一些“潜在的因素”然后通过把原始的特征维度映射到较少的潜在因素之上达到降维的目的。
这个方法的思想和步骤有些复杂,它的核心是矩阵分解,首先,让我们从方阵的矩阵分解开始。
## 方阵的特征分解
在解释方阵的分解时,我们会用到两个你可能不太熟悉的概念:方阵和酉矩阵。为了让你更顺畅的理解整个分解的过程,我先给你解释下这两个概念。
**方阵**Square Matrix是一种特殊的矩阵它的行数和列数相等。如果一个矩阵的行数和列数都是n那么我们把它称作n阶方阵。
如果一个矩阵和其转置矩阵相乘得到的是单位矩阵,那么它就是一个**酉矩阵**Unitary Matrix
$XX=I$
其中X表示X的转置I表示单位矩阵。换句话说矩阵X为酉矩阵的充分必要条件是X的转置矩阵和X的逆矩阵相等。
$X=X^{-1}$
理解这两个概念之后让我们来观察矩阵的特征值和特征向量。前两节我们介绍了对于一个n×n维的矩阵$X$$n$维向量$v$,标量$λ$,如果有$Xv=λv$。
那么我们就说$λ$是$X$的特征值,$v$是$X$的特征向量,并对应于特征值$λ$。
之前我们说过特征向量表示了矩阵变化的方向而特征值表示了变化的幅度。实际上通过特征值和特征矩阵我们还可以把矩阵X进行**特征分解**Eigendecomposition。这里矩阵的特征分解是指把矩阵分解为由其特征值和特征向量表示的矩阵之积的方法。如果我们求出了矩阵$X$的$k$个特征值$λ1λ2λn$,以及这$n$个特征值所对应的特征向量$v1v2vn$,那么就有$XV=VΣ$。
其中,$V$是这$n$个特征向量所张成的n×n维矩阵而Σ为这n个特征值为主对角线的n×n维矩阵。进一步推导我们可以得到
$XVV^{-1}=VΣV^{-1}$<br>
$XI=VΣV^{-1}$<br>
$X=VΣV^{-1}$
如果我们会把$V$的这$n$个特征向量进行标准化处理,那么对于每个特征向量$V_i$,就有$||V_i||_2=1$,而这表示$V_iV_i=1$此时V的n个特征向量为标准正交基满足$VV=I$ 也就是说V为酉矩阵有$V=V^{-1}$ 。这样一来,我们就可以把特征分解表达式写作$X=VΣV$。
我们以介绍PCA分析时所用的矩阵为例验证矩阵的特征分解。当时我们有一个
<img src="https://static001.geekbang.org/resource/image/33/0e/3384840ca067f7d9564de1ff74130e0e.png" alt="">
下面我们需要证明$X=VΣV$成立,我把推算的过程写在下面了。
<img src="https://static001.geekbang.org/resource/image/83/14/83a16135267b7c38c26fccc0d8a41314.png" alt="">
讲到这里,相信你对矩阵的特征分解有了一定程度的认识。可是,矩阵$X$必须为对称方阵才能进行有实数解的特征分解。那么如果$X$不是方阵那么应该如何进行矩阵的分解呢这个时候就需要用到奇异值分解SVD了。
## 矩阵的奇异值分解
SVD分解和特征分解相比在形式上是类似的。假设矩阵$X$是一个m×n维的矩阵那么$X$的SVD为$X=UΣV$。
不同的地方在于SVD并不要求要分解的矩阵为方阵所以这里的$U$和$V$并不是互为逆矩阵。其中$U$是一个m×m维的矩阵$V$是一个n×n维的矩阵。而$Σ$是一个m×n维的矩阵对于$Σ$来说,只有主对角线之上的元素可以为非$0$,其他元素都是$0$,而主对角线上的每个元素就称为**奇异值**。$U$和$V$都是酉矩阵,即满足$UU=I,VV=I$。
现在问题来了我们应该如何求出用于SVD分解的$U,Σ和V$这三个矩阵呢之所以不能使用有实数解的特征分解是因为此时矩阵X不是对称的方阵。我们可以把$X$的转置$X$和$X$做矩阵乘法得到一个n×n维的对称方阵$XX$。这个时候,我们就能对$XX$这个对称方阵进行特征分解了,得到的特征值和特征向量满足$(XX)v_i=λ_iv_i$。
这样一来,我们就得到了矩阵$XX$的$n$个特征值和对应的$n$个特征向量$v$。通过$XX$的所有特征向量构造一个n×n维的矩阵$V$这就是上述SVD公式里面的$V$矩阵了。通常我们把$V$中的每个特征向量叫作$X$的**右奇异向量**。
同样的道理如果我们把X和X做矩阵乘法那么会得到一个m×m维的方阵XX。由于XX也是方阵因此我们同样可以对它进行特征分解得到的特征值和特征向量满足$(XX)u_i=λ_iu_i$。
类似地,我们得到了矩阵$XX$的m个特征值和对应的m个特征向量$u$。通过XX的所有特征向量构造一个m×m的矩阵$U$。这就是上述SVD公式里面的$U$矩阵了。通常我们把U中的每个特征向量叫作X的**左奇异向量**。
现在,包含左右奇异向量的$U$和$V$都求解出来了,只剩下奇异值矩阵$Σ$了。之前我提到,$Σ$除了对角线上是奇异值之外,其他位置的元素都是$0$,所以我们只需要求出每个奇异值$σ$就可以了。这个解可以通过下面的公式推导求得:
$X=UΣV$<br>
$XV=UΣVV$
由于$V$是酉矩阵,所以$VV=I$,就有:
$XV=UΣI$<br>
$XV=UΣ$<br>
$Xv_i=σ_iu_i$<br>
$σ_i=\frac{Xv_i}{u_i}$
其中$v_i$和$u_i$都是列向量。一旦我们求出了每个奇异值$σ$,那么就能得到奇异值矩阵$Σ$。
通过上述几个步骤我们就能把一个mxn维的实数矩阵分解成$X=UΣV$的形式。说到这里你可能会疑惑把矩阵分解成这个形式有什么用呢实际上在不同的应用中这种分解表示了不同的含义。下面我会使用潜在语义分析的案例带你看看在发掘语义关系的时候SVD分解起到了怎样的关键作用。
## 潜在语义分析和SVD
在讲向量空间模型的时候,我解释了文档和词条所组成的矩阵。对于一个大的文档集合,我们首先要构造字典,然后根据字典构造每篇文档的向量,最后通过所有文档的向量构造矩阵。矩阵的行和列分别表示文档和词条。基于这个矩阵、向量空间中的距离、余弦夹角等度量,我们就可以进行基于相似度的信息检索或文档聚类。
不过最简单的向量空间模型采用的是精确的词条匹配它没有办法处理词条形态的变化、同义词、近义词等情况。我们需要使用拉丁语系的取词根Stemming操作并手动建立同义词、近义词词典。这些处理方式都需要人类的语义知识也非常依赖人工的干预。另外有些词语并不是同义词或者近义词但是相互之间也是有语义关系的。例如“学生”“老师”“学校”“课程”等等。
那么,我们有没有什么模型,可以自动地挖掘在语义层面的信息呢?当然,目前的计算机还没有办法真正理解人类的自然语言,它们需要通过大量的数据,来找到词语之间的关系。下面我们就来看看**潜在语义分析LSA**Latent Semantic Analysis或者叫潜在语义索引LSILatent Semantic Index这种方法是如何做到这点的。
和一般的向量空间模型有所不同LSA通过词条和文档所组成的矩阵发掘词和词之间的语义关系并过滤掉原始向量空间中存在的一些“噪音”最终提高信息检索和机器学习算法的精确度。LSA主要包括以下这些步骤。
第一步,分析文档集合,建立表示文档和词条关系的矩阵。
第二步,对文档-词条矩阵进行SVD奇异值分解。在LSA的应用场景下分解之后所得到的奇异值σ对应了一个语义上的“概念”而$σ$值的大小表示这个概念在整个文档集合中的重要程度。$U$中的左奇异值向量表示了每个文档和这些语义“概念”的关系强弱,$V$中的右奇异值向量表示每个词条和这些语义“概念”的关系强弱。所以说SVD分解把原来的词条-文档关系,转换成了词条-语义概念-文档关系。
我画了一张图帮助你理解这个过程。
<img src="https://static001.geekbang.org/resource/image/95/c1/95c4ef346ab87eafcfa6e7236dc0cdc1.png" alt="">
在这种图中我们有一个7×5维的矩阵$X$表示7个文档和5个单词。经过SVD分解之后我们得到了两个主要的语义概念一个概念描述了计算机领域另一个概念描述了医学领域。矩阵U描述文档和这两个概念之间的关系而矩阵$V$描述了各个词语和这两个概念之间的关系。如果要对文档进行检索,我们可以使用$U$这个降维之后的矩阵找到哪些文档和计算机领域相关。同样对于聚类算法我们也可以使用U来判断哪些文档属于同一个类。
第三步对SVD分解后的矩阵进行降维这个操作和PCA主成分分析的降维操作是类似的。
第四步,使用降维后的矩阵重新构建概念-文档矩阵,新矩阵中的元素不再表示词条是不是出现在文档中,而是表示某个概念是不是出现在文档中。
总的来说LSA的分解不仅可以帮助我们找到词条之间的语义关系还可以降低向量空间的维度。在这个基础之上在运行其他的信息检索或者机器学习算法就更加有效。
## 总结
之前介绍的PCA主成分分析要求矩阵必须是对称的方阵因此只适用于刻画特征之间关系的协方差矩阵。但是有的时候我们需要挖掘的是样本和特征之间的关系例如文档和词条。这个时候矩阵并不是对称的方阵因此无法直接使用PCA分析。
为此SVD奇异值分解提供了一种可行的方案。它巧妙地运用了矩阵X和自己的转置相乘生成了两种对称的方阵并通过这两者的特征分解获得了SVD中的左奇异向量所组成的矩阵U和右奇异向量所组成的矩阵V并最终推导出奇异值矩阵Σ。这样SVD就可以对原始的数据矩阵进行分解并运用最终的奇异向量进行降维。
我们可以把SVD运用在很多场合中在不同的应用场景下$UV$和$Σ$代表了不同的含义。例如在LSA分析中通过对词条和文档矩阵的SVD分解我们可以利用Σ获得代表潜在语义的一些概念。而矩阵$U$表示了这些概念和文档之间的关系,矩阵$V$表示了这些概念和单个词语之间的关系。
## 思考题
请使用你自己熟悉的语言实现SVD分解。提示如果使用Python等科学计算语言你可以参考本节所讲述的矩阵分解步骤也可以使用一些现成的科学计算库。
欢迎留言和我分享,也欢迎你在留言区写下今天的学习笔记。你可以点击“请朋友读”,把今天的内容分享给你的好友,和他一起精进。

View File

@@ -0,0 +1,95 @@
<audio id="audio" title="45 | 线性代数篇答疑和总结:矩阵乘法的几何意义是什么?" controls="" preload="none"><source id="mp3" src="https://static001.geekbang.org/resource/audio/89/78/89b8520d889c80c46e72a6d7ad098a78.mp3"></audio>
你好,我是黄申。今天是线性代数的答疑和总结。
在这个模块中我们讲了不少向量、矩阵、线性方程相关的内容。看到大家在留言区的问题今天我重点说说矩阵乘法的几何意义以及为什么SVD中$X'X$的特征向量组成了$V$矩阵,而$XX'$的特征向量组成了$U$矩阵。最后,我会对整个线性代数的模块做一个总结。
## 矩阵乘法的几何意义
首先,我们来说说矩阵乘法所代表的几何意义。
在阐述PCA主成分分析的时候我们聊过为什么这个方法要研究协方差矩阵的特征值和特征向量。其中我提到对某个向量左乘一个矩阵实际上是对这个向量进行了一次变换。某个矩阵的特征向量表示了这个矩阵在空间中的变换方向这些方向都是正交或者趋于正交的而特征值表示每个方向上伸缩的比例。今天我会继续深入这个话题结合实例给出更详细地解释。
多维的向量空间很难理解所以我们还是从最简单的二维空间开始。首先我们需要明白什么是二维空间中的正交向量。正交向量的定义非常简单只要两个向量的点乘结果为0那么它们就是正交的。在酉矩阵之中矩阵和矩阵的转置相乘为单位矩阵只有向量自己点乘自己值为1而不同向量之间点乘值为0所以不同的向量之间是正交的。
理解了正交向量之后,我们来定义一个二维空间,这个空间的横坐标为$x$,纵坐标为$y$,空间中的一个点坐标为$(1,2)$,对于这个点,我们可以把从原点到它的直线投影到$x$轴和$y$轴,这个直线在$x$轴上投影的长度为1在y轴上投影的长度为2。我使用下图来表示。
<img src="https://static001.geekbang.org/resource/image/67/af/67b0b2634a6c53339b41579bf34f80af.png" alt="">
对于这个点,我们使用一个矩阵$X_1$左乘这个点的坐标,你可以看看会发生什么。
<img src="https://static001.geekbang.org/resource/image/42/ba/423463beaff43e69429cc6b4f17910ba.png" alt="">
我们把结果转成坐标系里的点,它的坐标是$(3, 4)$,把从原点到$(1,2)$的直线,和从原点到$(3,4)$的直线进行比较,你会发现直线发生了旋转,而且长度也发生了变化,这就是矩阵左乘所对应的几何意义。我们还可以对这个矩阵$X_1$分析一下,看看它到底表示了什么含义,以及为什么它会导致直线的旋转和长度发生变化。
之前我讲过,要看一个矩阵的特征,需要分析它的特征向量和特征值。由于矩阵$X_1$是一个对角矩阵所以特征值很容易求解分别是3和2。而对应的特征向量是$[1, 0]$和$[0, 1]$。在二维坐标中,坐标[1, 0]实际上表示的是$x$轴的方向,而[0, 1]实际上表示的是$y$轴的方向。特征值3对应特征向量[1, 0]就表明在$x$轴方向拉伸为原来的3倍特征值2对应特征向量[0, 1]就表明在$y$轴方向拉伸2倍。所以矩阵$X_1$的左乘,就表示把原有向量在$x$轴上拉伸为原来的3倍而在$y$轴上拉伸为原来的2倍。我用下面这张图来展示。
<img src="https://static001.geekbang.org/resource/image/8e/80/8e42e5eace66c585d78dfa32226ec780.png" alt="">
我们还可以从另一个角度来验证这点,把从原点到$(3, 4)$的直线进行分解,我们会发现这个直线在$x$轴上投影的长度为3为原来的3倍而在$y$轴上投影的长度为4为原来的2倍。
当然,矩阵的特征向量不一定是$x$轴和$y$轴,它们可以是二维空间中任何相互正交的向量。下面,我们再来看一个稍微复杂一点的例子。这次我们从两个正交的向量开始。
<img src="https://static001.geekbang.org/resource/image/32/f3/32bfec48931dcc52354db9624a0d9bf3.png" alt="">
我使用下面这张图展示了这两个向量在空间的方向。
<img src="https://static001.geekbang.org/resource/image/07/04/0795164dbc78540fc5c0fc56713ae504.png" alt="">
然后我用这两个向量构建一个矩阵$V$。
<img src="https://static001.geekbang.org/resource/image/9a/de/9a02adc8acb0d8f0b40bdc85eaea9cde.png" alt="">
之所以使用这样一个例子,是因为$V$是一个酉矩阵,也就是说$VV'=I$,所以我们可以使用它,外加一个特征值组成的对角矩阵$Σ$,来构建另一个用于测试的矩阵$X_2$。我在SVD的那一讲介绍过对称方阵可以进行特征值分解所以我们可以通过$V$和$Σ$,获得一个对称方阵$X_2=VΣV'$。
我们假设两个特征值分别是0.5和2所以有
<img src="https://static001.geekbang.org/resource/image/26/6f/2606792083d27b6333428d03918e606f.png" alt="">
根据我们之间的解释,如果让这个矩阵$X_2$左乘任何一个向量,就是让向量沿$[\frac{1}{\sqrt{2}} \frac{1}{\sqrt{2}}]$方向压缩一半,而在$[\frac{1}{\sqrt{2}} \frac{-1}{\sqrt{2}}]$方向增加两倍。为了验证这一点,我们让$X_2$左乘向量$(1, 2)$,获得新向量:
<img src="https://static001.geekbang.org/resource/image/2d/e3/2dd3d37508be334af83f9d6ec2a17fe3.png" alt="">
把这个新的坐标$(-0.25, 1.75)$和原坐标$(1,2)$都放到二维坐标系中,并让它们分别在$[\frac{1}{\sqrt{2}} \frac{1}{\sqrt{2}}]$和$[\frac{1}{\sqrt{2}} \frac{-1}{\sqrt{2}}]$这两个方向进行投影,然后比较一下投影的长度,你就会发现伸缩的变化了。我使用下面这张图来帮你理解。
<img src="https://static001.geekbang.org/resource/image/be/a0/be3adac33d5359d8962ffa55f42ba6a0.png" alt="">
弄清楚了矩阵左乘向量的几何意义,那么矩阵左乘矩阵的几何意义也就不难理解了。假设我们让矩阵$X$左乘矩阵$Y$,那么可以把右矩阵$Y$看作一堆列向量的集合,而左乘矩阵$X$就是对每个$Y$中的列向量进行变换。另外,如果二维空间理解了,那么三维、四维直到$n$维空间就可以以此类推了。
## SVD分解中的$U$和$V$矩阵
在讲解SVD奇异值分解的时候我们解释了$X'X$的特征向量组成了SVD中的$V$矩阵,而$XX'$的特征向量组成了SVD中的$U$矩阵。不过,我们还没有证明这两点。今天我来说说如何证明它们。首先,我们来看看$V$矩阵的证明。
$X=UΣV'$<br>
$X'=VΣ'U'$<br>
$X'X=(VΣ'U)(UΣV')=VΣ'(U'U)Σ'V'=VΣ^2V')$
其中,$(UΣV')'=VΣ'U'$的证明,我们在最小二乘法的讲解过程中证明过。另外,$U$是酉矩阵,所以$U'U=I$。$Σ$是对角矩阵,所以$Σ'Σ=Σ2$,而且$Σ2$仍然是对角矩阵。
由于$Σ2$是对角矩阵,所以通过$X'X=VΣ2V'$,我们可以看出$V$中的向量就是$X'X$的特征向量,而特征值是$Σ2$对角线上的值。
同理,我们也可以证明$U$中的向量就是$XX'$的特征向量。
$X=UΣV'$<br>
$X'=VΣ'U'$<br>
$XX'=(UΣV')(VΣ'U')=UΣ(V'V)Σ'U'=UΣ^2U')$
从这个证明的过程我们也发现了XX'或者X'X特征值矩阵等于奇异值矩阵的平方也就是说我们可以通过求出X'X特征值的平方根来求奇异值。
## 总结
回答完两个问题之后,我来总结一下线性代数这个模块。
线性代数最基本的概念包括了向量、矩阵以及对应的操作。向量表示了一组数的概念非常适合表示一个对象的多维特征因此被广泛的运用在信息检索和机器学习的领域中。而矩阵又包含了多个向量所以适合表示多个数据对象的集合。同时矩阵也可以用于表达二维关系例如网页的邻接矩阵用户对物品的喜好程度关键词在文档中的tf-idf等等。
由于向量和矩阵的特性我们可以把它们运用在很多算法和模型之中。向量空间模型定义了向量之间的距离或者余弦夹角我们可以利用这些指标来衡量数据对象之间的相似程度并把这种相似程度用于定义查询和文档之间的相关性或者是文档聚类时的归属关系。矩阵的运算体现了对多个向量同时进行的操作比如最常见的左乘就可以用在计算PageRank值协同过滤中的用户或者物品相似度等等。
当然矩阵的运用还不止计算数据对象之间的关系。最小二乘法的实现、PCA主成分的分析、SVD奇异值的分解也可以基于矩阵的运算。这些都可以帮助我们发现不同维度特征之间的关系并利用这些关系找到哪些特征更为重要选择或者创建更为重要的特征。
有的时候,线性代数涉及的公式和推导比较繁琐。在思考的过程中,我们可以把矩阵的操作简化为向量之间的操作,而把向量之间的操作简化为多个变量之间的运算。另外,我们可以多结合实际的案例,结合几何空间、动手推算,甚至可以编程实现某些关键的模块,这些都有利于理解和记忆。
## 思考题
我想听你说说,学习完了编程领域中常用的线性代数知识,你有哪些收获和心得?
欢迎留言和我分享,也欢迎你在留言区写下今天的学习笔记。你可以点击“请朋友读”,把今天的内容分享给你的好友,和他一起精进。