奇异值分解(SVD)意义及具体实现

作者:金良([email protected]) csdn博客:http://blog.csdn.net/u012176591

奇异值分解(SVD)意义及具体实现_第1张图片

1、SVD优缺点

优点:不需要像余弦距离方法一样一次次地迭代,所以速度较快。

能够简化数据规模。

缺点:分类结果往往比较粗糙。

应用方式:实际工作中,先进行奇异值分解,得到粗分类结果,再利用计算向量余弦的方法,在粗分类的基础上,进行几次迭代,得到比较精细的结果。

适用数据类型:数值型数据

2.SVD的意义

新闻分类乃至各种分类其实是一个聚类问题,关键是计算两篇新闻的相似程度。为了完成这个过程,我们要将新闻变成代表他们内容的实词,然后再变成对应的一组数,即向量,最后求这两个向量的夹角。当这两个向量的夹角很小时,新闻就相关;当它们垂直或者说正交时,新闻就无关。这种方法需要对所有新闻做两两计算,而且要进行多次迭代,因此耗时特别长,尤其是当新闻的数量很大,同时此表也很大的时候。我们希望有一个办法,依次就能把所有新闻相关性计算出来。这个一步到位的办法是利用矩阵运算中的奇异值分解(Singular Value Decomposition,简称SVD)。

举个例子,有M篇文章,每篇文章都有一个与N个词相对应的向量,所以就组成如下所示的的矩阵:

奇异值分解(SVD)意义及具体实现_第2张图片





奇异值分解(SVD)意义及具体实现_第3张图片


3.SVD的实现

我们要分解的矩阵:
    myMat = [[1,1,1,0,0],[2,2,2,0,0],[1,1,1,0,0],[5,5,5,0,0],[1,1,0,2,2],[0,0,0,3,3],[0,0,0,1,1]]
对其进行奇异值分解:
    U,Sigma,VT = la.svd(myMat)
分解得到的U,Sigma和VT如下:
U:    
[[ -1.77939726e-01  -1.64228493e-02   1.80501685e-02   9.48215967e-01   -2.43879623e-01   5.88803098e-02   7.53939908e-02]
 [ -3.55879451e-01  -3.28456986e-02   3.61003369e-02  -2.42667431e-02   -1.30579552e-01  -8.77959841e-01  -2.87253136e-01]
 [ -1.77939726e-01  -1.64228493e-02   1.80501685e-02  -2.86917521e-01   -9.17843690e-01   1.99699755e-01   5.57067394e-02]
 [ -8.89698628e-01  -8.21142464e-02   9.02508423e-02  -1.22552992e-01    2.84576484e-01   2.99467924e-01   8.86811085e-02]
 [ -1.33954753e-01   5.33527340e-01  -8.35107599e-01   4.16333634e-16   -8.60422844e-16   2.37657116e-16   3.05311332e-16]
 [ -2.15749771e-02   7.97677135e-01   5.13074760e-01   1.71950731e-02   -2.25601962e-03   9.80604897e-02  -3.00138935e-01]
 [ -7.19165903e-03   2.65892378e-01   1.71024920e-01  -5.15852193e-02    6.76805885e-03  -2.94181469e-01   9.00416804e-01]]


Sigma:    
[  9.72140007e+00   5.29397912e+00   6.84226362e-01   1.50962387e-15   1.15387192e-31]


VT:    
[[ -5.81200877e-01  -5.81200877e-01  -5.67421508e-01  -3.49564973e-02   -3.49564973e-02]
 [  4.61260083e-03   4.61260083e-03  -9.61674228e-02   7.03814349e-01    7.03814349e-01]
 [ -4.02721076e-01  -4.02721076e-01   8.17792552e-01   5.85098794e-02    5.85098794e-02]
 [ -7.07106781e-01   7.07106781e-01  -4.44089210e-16  -6.93889390e-18   -6.24500451e-17]
 [  0.00000000e+00   2.38253479e-17  -1.69766851e-17  -7.07106781e-01    7.07106781e-01]]
[[  1.00000000e+00   1.00000000e+00   1.00000000e+00  -1.14491749e-16   -1.21430643e-16]
 [  2.00000000e+00   2.00000000e+00   2.00000000e+00   2.81675724e-16    2.67797937e-16]
 [  1.00000000e+00   1.00000000e+00   1.00000000e+00   3.25802753e-16    3.18863859e-16]
 [  5.00000000e+00   5.00000000e+00   5.00000000e+00  -3.59955121e-17   -9.15066634e-17]
 [  1.00000000e+00   1.00000000e+00  -2.77555756e-16   2.00000000e+00    2.00000000e+00]
 [ -5.55111512e-17   1.08246745e-15  -1.22124533e-15   3.00000000e+00    3.00000000e+00]
 [ -7.63278329e-17   3.05311332e-16  -2.91433544e-16   1.00000000e+00    1.00000000e+00]]

我们首先考察Sigma的元素的特征:Sigma中的元素是上一节的矩阵的对角线上的元素,这是因为是对角矩阵,这样表示比较方便。可以看到Sigma中的元素(即奇异值)依次递减,并且后面两个元素相比于前边的三个元素要小得多得多。我们要达到降维的目的,就要仅保留Sigma中较大的奇异值,扔掉后面那些小得多的奇异值。我们如何确定要保留前几个奇异值呢?一个典型的做法就是保留矩阵中90%的能量信息。为了计算总能量信息,我们将所有的奇异值求其平方和。于是可以讲奇异值的平方和累加到总值得90%为止。
确定要保留的奇异值的个数的函数代码如下:
def getdim(Sigma,threshold):
    a = 0
    for i in range(len(Sigma)):
        a += Sigma[i]**2
    b = []
    b.append(Sigma[0]**2)
    for i in range(1,len(Sigma)):
        b.append(b[i-1]+Sigma[i]**2)
    b = array(b)/b[len(b)-1]
    for i in range(len(b)):
        if b[i] > threshold:
            break#里外的循环都停止了
    return (i+1)
上述函数有两个参数,一个就是奇异值分解得到的Sigma,另一个参数threshold是保留的能量信息的比率,可以使0.90、0.95、0.99等,返回值是要保留的奇异值的个数。
得到要保留的奇异值的个数之后,我们就要对奇异值分解得到的三个矩阵进行处理,以适应这种变化,如下图所示。
具体地说,假如要保留的奇异值个数是n,那么矩阵U只保留其前n列元素,保留其左上角的n个对角元素(就是前n个奇异值),保留其前n行的元素。


 SVD示意图。矩阵Data被分解。浅灰色区域是原始数据,深灰色区域是矩阵近似计算仅需要的数据。
该过程的代码如下:
def dimReduce(U,Sigma,VT,dim):
    dim0 = len(Sigma)
    U = array(U)[:,0:dim]
    Sigma = array(Sigma)[0:dim]
    VT = array(VT)[0:dim,:]
    return U,Sigma,VT
从上图可以看出,删除部分行或列元素的三个矩阵仍满足矩阵相乘的条件,并能生成和最初的 Data矩阵相同行列数的矩阵,而且这个新生成的矩阵保留了原来矩阵的绝大部分信息。三个处理后的矩阵相乘的代码如下:
    myMat2 = dot(dot(U,diag(Sigma)),VT)
    print myMat2
将新生成的矩阵打印:
[[  1.00000000e+00   1.00000000e+00   1.00000000e+00   2.17382536e-16    2.04155269e-16]
 [  2.00000000e+00   2.00000000e+00   2.00000000e+00   7.95804395e-17    3.92481186e-17]
 [  1.00000000e+00   1.00000000e+00   1.00000000e+00  -5.80481843e-16   -6.00648004e-16]
 [  5.00000000e+00   5.00000000e+00   5.00000000e+00   1.25333771e-16    1.77809156e-17]
 [  1.00000000e+00   1.00000000e+00  -2.22044605e-16   2.00000000e+00    2.00000000e+00]
 [  5.55111512e-17  -5.55111512e-17  -1.11022302e-16   3.00000000e+00    3.00000000e+00]
 [ -2.77555756e-17  -6.24500451e-17   2.77555756e-17   1.00000000e+00    1.00000000e+00]]
可以看到该矩阵与最初的矩阵的元素大小相差无几。
上面的结果是保留了0.999的矩阵能量,我们减小此比例,令其保留0.99,生成的矩阵如下:
[[  1.00497377e+00   1.00497377e+00   9.89899934e-01  -7.22620478e-04   -7.22620478e-04]
 [  2.00994753e+00   2.00994753e+00   1.97979987e+00  -1.44524096e-03   -1.44524096e-03]
 [  1.00497377e+00   1.00497377e+00   9.89899934e-01  -7.22620478e-04   -7.22620478e-04]
 [  5.02486883e+00   5.02486883e+00   4.94949967e+00  -3.61310239e-03   -3.61310239e-03]
 [  7.69884117e-01   7.69884117e-01   4.67288818e-01   2.03343270e+00    2.03343270e+00]
 [  1.41378969e-01   1.41378969e-01  -2.87093661e-01   2.97945956e+00    2.97945956e+00]
 [  4.71263231e-02   4.71263231e-02  -9.56978871e-02   9.93153188e-01    9.93153188e-01]]
可以看到误差更大了些,不过仍然与最初的矩阵相差不大。
需要提到的是,保留0.999的能量信息时,有三个奇异值被保留;而保留0.99的能量信息时,只有2个奇异值被保留,所以才有了上述两个生成矩阵的差异。

程序全部的代码如下:
#encoding=UTF-8   
''''' 
Created on 2014��6��21�� 
 
@author: jin 
'''
from pylab import * 
from numpy import linalg as la
import copy
import math

def getdim(Sigma,threshold):
    a = 0
    for i in range(len(Sigma)):
        a += Sigma[i]**2
    b = []
    b.append(Sigma[0]**2)
    for i in range(1,len(Sigma)):
        b.append(b[i-1]+Sigma[i]**2)
    b = array(b)/b[len(b)-1]
    for i in range(len(b)):
        if b[i] > threshold:
            break#里外的循环都停止了
    return (i+1)
def dimReduce(U,Sigma,VT,dim):
    dim0 = len(Sigma)
    U = array(U)[:,0:dim]
    Sigma = array(Sigma)[0:dim]
    VT = array(VT)[0:dim,:]
    return U,Sigma,VT
def Main():
    threshold = 0.99
    myMat = [[1,1,1,0,0],[2,2,2,0,0],[1,1,1,0,0],[5,5,5,0,0],[1,1,0,2,2],[0,0,0,3,3],[0,0,0,1,1]]
    U,Sigma,VT = la.svd(myMat)
    dim = getdim(Sigma,threshold)
    U,Sigma,VT = dimReduce(U,Sigma,VT,dim)
    myMat2 = dot(dot(U,diag(Sigma)),VT)
    print myMat2
Main()



你可能感兴趣的:(数据挖掘,python,降维,SVD,奇异值分解)