机器学习与线性代数
自打我开始学习机器学习的相关知识以来,线性代数就一直是制约我读懂算法的最大短板。尽管经过大概两个月的学习,我的代数知识积累似乎已经足以让我不害怕任何数学推导了,但是毕竟是将来要赖以生存的本领,如果有一天忘记了它们我会很沮丧的。所以这里我还是决定整理一下与机器学习相关的所有数学知识,方便随时查阅,杜绝忘却。
笔记将以花书《深度学习》为思维导向,从底到上梳理各种可能用到的数学知识。
代数的运算律
和标量类似的运算律有分配律、结合律,而一般不满足交换律,因为代数是有维度的。
此外代数可以转置,也就是沿对角线反转矩阵。代数乘积的转置满足
迹
只看这个定义看不出什么,体验它的作用还是在实践中。它有着非常好用的交换律,即使两个矩阵一个是nxn一个是mxm
范数
范数可以把向量映射为非负标量
范数常用来评估高维空间中的距离,经典的应用就是正则化和聚类。常用的范数有L1范数,即曼哈顿距离,L2范数即欧几里得距离,有时还会使用Linf范数,即计算向量中最大幅值元素的绝对值。
矩阵逆
单位矩阵一般写成,是对角线元素为1其他为0的方阵。为此我们有矩阵的逆矩阵的定义
做题和考试时求矩阵逆我们可能会采取手算的方法,就是通过多次基本矩阵变换把A变成I,这是同样把这些操作施加於单位矩阵上,就得到逆矩阵。矩阵可逆的条件是矩阵正定,即矩阵中不存在线性相关项。同时,单位正交矩阵的逆矩阵就是它自己的转置。
特征值分解
应某些问题需要,我们经常会用到矩阵对角化的运算。对一个方阵B,我们希望得到
其中A是对角矩阵,即对角线上的元素非零其他为零。而P是单位正交矩阵,单位正交矩阵的每一列对应的列向量都是单位向量,且两两正交。也就是满足
这样的矩阵分解方法也可以写成多个向量外积的加和
这种分解得到的对角矩阵中的标量称为特征值,每个特征值都会对应一个特征向量。一般的矩阵不一定能对角化,但是对称矩阵一定可以对角化。正定矩阵的特征值都是非负的。
行列式
计算大家都懂,它会得到矩阵所有特征值的乘积。一个有用的性质是, 如果方阵线性相关, 行列式的值为0.
SVD分解
我们不能对一般的矩阵(不一定是方阵)进行对角化,但是我们可以用一些运算构造出能分解的对称矩阵。
为对称正定矩阵,因为
很容易看出,M的转置仍然是自己,且不论是用怎样的特征向量,得到的特征值都是非负数。这样我们就能对这个进行SVD分解
两个矩阵分解得到的中间的对角矩阵,也就是得到的特征值相同。
这样就能对任意的矩阵进行奇异值的分解。得到的对角矩阵中的标量称为奇异值,左侧矩阵的向量称为左奇异向量,右侧称为右奇异向量。
SVD分解和特征值分解都常被用作矩阵的压缩保存,我们可以不保存所有特征向量,只保存一部分特征向量来重构原矩阵。
应用1:线性回归
我们在学习线性代数时知道用矩阵方法求解线性方程组,事实上对于一个线性方程组,我们完全可以用来计算线性方程组的解。当然A可逆的条件是A为方阵且A中的每个行向量线性无关,也就是A正定。但是实际使用中我们并不会有那么好的运气,在做线性回归时,我们的数据集的有效数据个数rank和数据维度m不总是相等的。当rank大于m时线性方程组无解,当rank小于m时方程组有无穷个解。解决这个问题的简单方法是使用伪逆。
我们将不追求得到尽善尽美的解,而是追求最小化均方误差。这种凸优化问题,我们只需要对均方误差求导就能得到最小化误差的解。
这时得到的解是
当A矩阵像上面的列数m多于行数n,则上面的方法并不能得到好的解,因为这时A的秩(最大线性无关行的个数)不会大于n,而n<m,对mxm的方阵我们是无法求逆的。这时我们需要引入L2正则化项让A可逆,我们把上面的损失函数改成
这时就可以求逆了,可以直观地理解,我们在每个向量的第m维加上了一点点的增量,这样即使原本线性相关的向量也会被变成线性无关。这样,我们就能对任意的A求解一个线性方程组近似解,也就是线性回归解。
上面的这个又叫伪逆,我们一般会用奇异值分解帮助我们计算伪逆。即,对角矩阵D的伪逆就是对D的非零元素取倒数再转置。如果我们用伪逆在数据集上进行线性回归,得到的解会让欧几里得距离,也就是上面的损失函数最小。同时如果引入正则化项,还能在m多于rank时,从无穷个解中找到最小范数的解x。
实例:小车初速度与加速度测定
我们在物理实验中会让小车在光滑平面上运动, 通过记录时间和路程构成的多个数据点并做数据分析, 得到小车实际的初速度和加速度. 这个过程实际上是一个线性回归, 我们知道路程-时间公式可以写成
那么问题实际上是一个三元的线性回归, 变量分别是, , , 我们把三元数据和路程做线性回归, 就能得到一个权值向量x, 它的三个维度的意义就是上面的.
import numpy as np
# 小车斜面下落实验, 加速度为4.9, 初速度2, 初始路程1
t = np.linspace(0, 5, 20)
s = 1 + 2*t + 4.9*t**2
# 加一些噪声,可以理解为实验中的测量误差
s += np.random.randn(*s.shape)*0.01
# 构造数据的三元矩阵
A = np.concatenate([np.ones(t.shape).reshape(1,-1),
t.reshape(1,-1),
(t**2).reshape(1,-1)]).T
# 计算线性回归的权值向量x
x = np.linalg.pinv(A).dot(s)
print("Displacement: %.4f"%(x[0]))
print("Initial velocity: %.4f"%(x[1]))
print("Acceleration: %.4f"%(x[2]))
Displacement: 0.9971
Initial velocity: 1.9972
Acceleration: 4.9009
应用2:主成分分析(PCA)
我们希望通过一个线性变换,把现有的数据集通过变换矩阵变换到d维,从而实现数据降维的需要。这个W要满足正交基的性质,即,是单位正交矩阵。
为了实现"变换后的数据丢失尽可能少的信息",我们会定义一个损失函数,让这些降维后的数据点分得尽可能开。这个损失函数就设定为变换后数据集的方差,我们希望最大化方差。为了处理起来简便,我们在一开始就把X标准化处理,让它的均值为零,那么降维后的均值。
这个损失函数可以用上面迹的定义写成非常好看的矩阵乘法形式
加上上面的W单位正交条件,这个问题是一个约束优化问题,最优解一定满足KKT条件。构造出拉格朗日函数,设$\lambda $是拉格朗日乘子的d维行向量。我们有
这说明W中的每个向量都满足的条件,也就是,W中的每个向量都是的特征向量。但是特征向量可能有很多,我们只需要d个,该选择哪d个呢?让我们回到上面的误差函数E
我们会发现我们想最大化的误差E就是w特征向量对应的特征值的乘积。如果我们要最大化这个误差,只需要找最大的d个特征值即可。
综上,算法的流程为,对X进行标准化,然后对进行特征值分解,并取出前d个最大特征值对应的特征向量拼接成W,返回,算法结束。
实例:数据可视化
在数据科学中, 我们要处理的数据往往超过三个维度, 几十维甚至上百维的数据是很常见的. 为了直观的观察数据的分布与它的特征之间的关系, 我们常常要用降维手段把它降到2维平面, 再plot出来观察. PCA可以胜任这个工作.
这里我们用经典的乳腺癌数据集(30维特征)做降维到2维, 并观察数据的分布和类别的关系. 在此之上判断我们下一步该用什么机器学习方法解决问题.
def PCA(X, dim):
'''
X, size(n,m), n个m维的数据点构成的数据集矩阵
dim, 目标降维维度, dim<m
'''
# 归一化处理
means = np.mean(X,axis = 0)
X = X.copy()-means
# 计算要特征值分解的协方差矩阵
Covs = X.T.dot(X)
# 特征值分解
lamda,V = np.linalg.eigh(Covs)
index = np.argsort(-lamda)[:dim]
W = V[:,index]
return X.dot(W)
# 导入数据集
from sklearn import datasets
X,y = datasets.load_breast_cancer(return_X_y = True)
X_2dim = PCA(X, 2)
import matplotlib.pyplot as plt
import matplotlib
plt.figure(figsize=(8,6))
ec_list = ['b' if n==0 else 'r' for n in y]
plt.scatter(X_2dim[:,0],X_2dim[:,1],c = 'w', edgecolor = ec_list)
plt.show()
可以看见的是良性肿瘤(红色)的分布较为集中, 表现出高斯簇的形状, 而恶性肿瘤(兰色)则比较发散
所以我们可以把这个问题用二分类模型来解, 也可以用无监督的多维高斯模型构造异常检测系统
应用3:PageRank
page rank是google在世纪初使用的搜索引擎技术, 它是一种结合了线性代数, 图论和数值计算的高效机器学习算法. 但究其原理其实并不复杂, 我们先构思一个图模型, 它的每个顶点是一个独立的网页. 这些网页之间被超链接互联, 从而让这个图模型是一个有向图. 这时我们就能给出一个假设, 网络中的用户流在顶点V时, 有等概率流向它的所有其他子顶点. 这样的假设建立了网页间的影响力关系线性方程组, 举个例子, 下面的网页图模型.
也就是, 我们把每个顶点的分流因子1平均赋值到所有子结点, 形成一个nxn的矩阵M. 所有顶点的影响力满足线性方程组
解一个这样的线性方程组就能得到解x, 它就是所有网页的影响力值, 可以通过排序被用在网页的推荐上. 但是这样的解法是有一定问题的, 一个是效率的问题, 我们的网页数目通常会很多, 100k以上都是比较少的了, 这样的线性方程组的系统不适合直接求解, 而且即使直接解, 也不能保证解是唯一的. 因此我们一般使用带约束的迭代解法, 一般如下面的流程.
- 初始化x为一个的向量
- 迭代计算
- x不再变化时, 停止迭代
这是不动点迭代的思想, 因为M的每一列的所有元素和为1, 迭代计算可以保证
始终成立, 而这样的迭代计算会让x向真正的解不断靠近直至收敛. 这个迭代次数一般不会太多, 所以这种解法能适用于有一定特殊性的本问题, 而且效率比RREF的方法高效得多
这样的解法可行, 但是我们仍然会面临两个很重要的问题, 它们会让解变得无效化.
- 等级泄露(Rank Leak):如果一个网页没有出链,就像是一个黑洞一样,吸收了其他网页的影响力而不释放,最终会导致其他网页的 PR 值为 0。
- 等级沉没(Rank Sink):如果一个网页只有出链,没有入链(如下图所示),计算的过程迭代下来,会导致这个网页的 PR 值为 0
为此我们可以引入一个合理的另一个假设, 我们假设在浏览网页时, 用户不会老老实实跟着超链接走下去, 很有可能看到一半感觉无聊了, 就随机跳到了一个另外的界面. 也就是, 我们引入这样的随机浏览因素, 把原来的M矩阵写成新的PR矩阵
这样的变化会让我们的计算收敛到一个有意义的解, 避免了无效解的产生
实例: 网页推荐
我们按照下面的网页连接状况进行建模, 并计算出网络影响力, 以此作为排序标准进行网页推荐.
hyperlinks = [
(3, 0),
(3, 1),
(4, 3),
(4, 1),
(1, 2),
(2, 1),
(5, 1),
(4, 5),
(5, 4),
(6, 1),
(6, 4),
(7, 1),
(7, 4),
(8, 1),
(8, 4),
(9, 4),
(10, 4)
]
n = 11
hyperlinks = np.array(hyperlinks)
M = np.zeros((n,n))
M[hyperlinks[:,1],hyperlinks[:,0]] = 1
for i in range(n):
if np.sum(M[:,i])>0:
M[:,i] /= np.sum(M[:,i])
d = 0.175
PR = (1-d)*M + d*np.ones((n,n))/n
x = np.ones((n,1))/n
for iter in range(50):
x = PR.dot(x)
x /= np.sum(x)
x = x[:,0]
x *= 100
sorted_indices = np.argsort(-x)
for i in sorted_indices:
name = chr(ord('A')+i)
print("Page "+name+": Score: %5.3f"%(x[i]))
Page B: Score: 38.322
Page C: Score: 34.143
Page E: Score: 8.163
Page D: Score: 3.943
Page F: Score: 3.943
Page A: Score: 3.308
Page G: Score: 1.636
Page H: Score: 1.636
Page I: Score: 1.636
Page J: Score: 1.636
Page K: Score: 1.636
小结
线性代数和概率论给我的感觉是, 它们就是机器学习和深度学习的基石, 比起最优化理论还要重要. 早知如此大一就不听我们的废柴老师讲的线代了, 自己看名校网课不是美滋滋? 我学线代时完全不知道这东西有什么用, 直到自己开始接触高级一点的机器学习问题或者物理建模, 才知道没学好这东西是一个多大的短板.