矩阵分解: SVD-PCA

矩阵分解

矩阵分解(Decomposition Factorization)是将矩阵拆解为若干个矩阵的相乘的过程。在数值分析中,常常被用来实现一些矩阵运算的快速算法,在机器学习领域有非常重要的作用。有的推荐系统采用SVD算法来实现整套系统中的矩阵分解过程。

SVD算法即为奇异值分解法,相对于矩阵的特征值分解法,它可以对非方阵形式的矩阵进行分解,将一个矩阵A分解为如下形式:

<math xmlns="http://www.w3.org/1998/Math/MathML"> A = U Σ V T A=UΣV^T </math>A=UΣVT

其中:

  • A代表需要被分解的矩阵,设其维度是 <math xmlns="http://www.w3.org/1998/Math/MathML"> m × n m×n </math>m×n
  • U矩阵是被分解为的3个矩阵之一,它是一个 <math xmlns="http://www.w3.org/1998/Math/MathML"> m × m m×m </math>m×m的方阵,构成这个矩阵的向量是正交的,被称为左奇异向量
  • Σ是一个 <math xmlns="http://www.w3.org/1998/Math/MathML"> m × n m×n </math>m×n的向量,它的特点是除了对角线中的元素外,其余元素都为0
  • V是一个 <math xmlns="http://www.w3.org/1998/Math/MathML"> n × n n×n </math>n×n的方阵,它的转置也是一个方阵,与U矩阵类似,构成这个矩阵的向量也是正交的,被称为右奇异向量

基于Numpy实现SVD

python 复制代码
import numpy as np

matrix = np.array([[1, 2], [3, 4]])

another_matrix = np.dot(matrix, matrix.T)

U, s, V = np.linalg.svd(another_matrix)  # 使用奇异值分解法将矩阵进行分解,得到3个子矩阵U,s,V

# 在s矩阵的基础上,生成S矩阵为
S = np.array([[s[0], 0], [0, s[1]]])

# 利用生成的USV三个矩阵,可以重建回原来的矩阵another_matrix
np.dot(U, np.dot(S, V))

其中得到的结果如下:

lua 复制代码
s: array([29.86606875,  0.13393125])
S: array([[29.86606875,  0.        ],
       [ 0.        ,  0.13393125]])
       
U:array([[-0.40455358, -0.9145143 ],
       [-0.9145143 ,  0.40455358]])

V:array([[-0.40455358, -0.9145143 ],
       [-0.9145143 ,  0.40455358]])
       
重建:array([[ 5., 11.],
       [11., 25.]])

在上面的代码片段中,s向量表示的是分解后的Σ矩阵中对角线上的元素 ,所以在这里面引入了一个S矩阵,将s向量中的元素放置在这个矩阵中,用以验证分解后的矩阵重建回原先的矩阵A的过程。

基于SVD实现PCA算法

python 复制代码
# 零均值预处理
def zero_centered(data):
    matrix_mean = np.mean(data, axis=0)
    return data - matrix_mean

def pca_eig(data, n):
    new_data = zero_centered(data)
    cov_mat = np.dot(new_data.T, new_data)
    eig_values, eig_vectors = np.linalg.eig(np.mat(cov_mat))
    value_indices = np.argsort(eig_values)  # 特征值从小到大排序
    n_vectors = eig_vectors[:, value_indices[-1: -(n+1): -1]]  # 最大的n个特征值对应的特征向量
    return new_data * n_vectors  # 返回低维空间的数据 x * v

def pca_svd(data, n):
    new_data = zero_centered(data)
    cov_mat = np.dot(new_data.T, new_data)  # 协方差矩阵
    U, s, V = np.linalg.svd(cov_mat)
    pc = np.dot(new_data, U)  # 返回矩阵的第1个列向量即是降维后的结果 
    return pc[:, 0]

def unit_test():
    data = np.array(
        [[2.5, 2.4], [0.5, 0.7], [2.2, 2.9], [1.9, 2.2], [3.1, 3.0], [2.3, 2.7],
        [2, 1.6], [1, 1.1], [1.5, 1.6], [1.1, 0.9]]
    )
    result_eig = pca_eig(data, 1)  # 使用常规的特征矩阵分解,将二维数据降到一维
    print(result_eig)
    result_svd = pca_svd(data, 1)  # 使用奇异值分解法将协方差矩阵分解,得到降维结果
    print(result_svd)
    

if __name__ == '__main__':
    unit_test()
css 复制代码
[[-0.82797019]
 [ 1.77758033]
 [-0.99219749]
 [-0.27421042]
 [-1.67580142]
 [-0.9129491 ]
 [ 0.09910944]
 [ 1.14457216]
 [ 0.43804614]
 [ 1.22382056]]
 
[-0.82797019  1.77758033 -0.99219749 -0.27421042 -1.67580142 -0.9129491  0.09910944  1.14457216  0.43804614  1.22382056]

可以看到,数据已经从二维变为一维了,这两个PCA算法的计算结果是相同的。其中pca_eig()函数使用常规的特征值分解方法来求解,读者可以参照前面讲述的PCA算法过程来理解这段代码。pca_svd()函数是使用奇异值分解法来求解的。

下面简要阐述一下PCA算法中奇异值分解的步骤:

  • 第一步,PCA算法中得到样本的协方差矩阵是经过零均值化处理的

    <math xmlns="http://www.w3.org/1998/Math/MathML"> C = X T X C=X^T X </math>C=XTX

    其中,X是经过中心化处理后的样本矩阵,一个矩阵与其转置矩阵相乘的结果是一个对称矩阵,所以C是对称矩阵,将其进行奇异值分解后可以表示为:

    <math xmlns="http://www.w3.org/1998/Math/MathML"> C = U Σ V T C=UΣV^T </math>C=UΣVT

  • 第二步,将经过中心化的样本矩阵X进行奇异值分解,可以得到:

​ <math xmlns="http://www.w3.org/1998/Math/MathML"> X = U Σ V T X=UΣV^T </math>X=UΣVT

​ <math xmlns="http://www.w3.org/1998/Math/MathML"> X T X = ( U Σ V T ) T ( U Σ V T ) = V Σ T U T U Σ V T = V Σ 2 V T X^TX \\ = (UΣV^T) ^T (UΣV^T) \\ = VΣ^T U^T UΣV^T \\ = VΣ^2 V^T </math>XTX=(UΣVT)T(UΣVT)=VΣTUTUΣVT=VΣ2VT

奇异矩阵V中的列对应着PCA算法主成分中的主方向 ,因此可以得到主成分为:

<math xmlns="http://www.w3.org/1998/Math/MathML"> X V = U Σ V T V = U Σ XV=UΣV^TV =UΣ </math>XV=UΣVTV=UΣ

SVD与PCA等价,所以PCA问题可以转化为SVD问题求解,那转化为SVD问题有什么好处?

有三点:

  • 一般 <math xmlns="http://www.w3.org/1998/Math/MathML"> X X </math>X的维度很高, <math xmlns="http://www.w3.org/1998/Math/MathML"> X T X X^TX </math>XTX的计算量很大
  • 方阵的特征值分解计算效率不高
  • SVD除了特征值分解这种求解方式外,还有更高效且更准确的迭代求解法,避免了 <math xmlns="http://www.w3.org/1998/Math/MathML"> X T X X^TX </math>XTX的计算

其实,PCA只与SVD的右奇异向量的压缩效果相同:

  • 如果取 <math xmlns="http://www.w3.org/1998/Math/MathML"> V V </math>V的前 <math xmlns="http://www.w3.org/1998/Math/MathML"> k k </math>k行作为变换矩阵 <math xmlns="http://www.w3.org/1998/Math/MathML"> P k × n P_{k×n} </math>Pk×n ,则 <math xmlns="http://www.w3.org/1998/Math/MathML"> Y k × m = P k × n X n × m Y_{k×m}=P_{k×n}X_{n×m} </math>Yk×m=Pk×nXn×m ,起到压缩行即降维的效果
  • 如果取 <math xmlns="http://www.w3.org/1998/Math/MathML"> U U </math>U的前 <math xmlns="http://www.w3.org/1998/Math/MathML"> d d </math>d行作为变换矩阵 <math xmlns="http://www.w3.org/1998/Math/MathML"> P d × m P_{d×m} </math>Pd×m ,则 <math xmlns="http://www.w3.org/1998/Math/MathML"> Y n × d = P n × m X m × d Y_{n×d}=P_{n×m}X_{m×d} </math>Yn×d=Pn×mXm×d ,起到压缩列即去除冗余样本的效果

参考

相关推荐
努力学习编程的伍大侠12 分钟前
基础排序算法
数据结构·c++·算法
XiaoLeisj40 分钟前
【递归,搜索与回溯算法 & 综合练习】深入理解暴搜决策树:递归,搜索与回溯算法综合小专题(二)
数据结构·算法·leetcode·决策树·深度优先·剪枝
Jasmine_llq1 小时前
《 火星人 》
算法·青少年编程·c#
闻缺陷则喜何志丹1 小时前
【C++动态规划 图论】3243. 新增道路查询后的最短距离 I|1567
c++·算法·动态规划·力扣·图论·最短路·路径
Lenyiin1 小时前
01.02、判定是否互为字符重排
算法·leetcode
鸽鸽程序猿2 小时前
【算法】【优选算法】宽搜(BFS)中队列的使用
算法·宽度优先·队列
Jackey_Song_Odd2 小时前
C语言 单向链表反转问题
c语言·数据结构·算法·链表
Watermelo6172 小时前
详解js柯里化原理及用法,探究柯里化在Redux Selector 的场景模拟、构建复杂的数据流管道、优化深度嵌套函数中的精妙应用
开发语言·前端·javascript·算法·数据挖掘·数据分析·ecmascript
乐之者v2 小时前
leetCode43.字符串相乘
java·数据结构·算法