美文网首页
SVD解析以及用其实现推荐算法

SVD解析以及用其实现推荐算法

作者: PerfectDemoT | 来源:发表于2018-05-04 16:55 被阅读0次

    SVD解析以及用其实现推荐算法

    标签:推荐算法


    [TOC]

    首先介绍一下SVD,是对一个$mn$规模矩阵进行奇异值分解,最后得到的为:
    $$A = U∑V^T$$
    其中$V$是$n
    n$的正交矩阵,$U$是$mm$的正交矩阵,$∑$是$mn$的对角矩阵

    特征值分解和奇异值分解两者有着很紧密的关系,特征值分解和奇异值分解的目的都是一样,就是提取出一个矩阵最重要的特征。先谈谈特征值分解吧

    1. 特征值分解

    如果说一个向量v是方阵A的特征向量,将一定可以表示成下面的形式:
    $$Av = \lambda v$$

    这时候λ就被称为特征向量v对应的特征值,一个矩阵的一组特征向量是一组正交向量。特征值分解是将一个矩阵分解成下面的形式:
    $$A = Q∑Q^{-1}$$
    其中Q是这个矩阵A的特征向量组成的矩阵,Σ是一个对角阵,每一个对角线上的元素就是一个特征值。首先,要明确的是,一个矩阵其实就是一个线性变换,因为一个矩阵乘以一个向量后得到的向量,其实就相当于将这个向量进行了线性变换。
    分解得到的Σ矩阵是一个对角阵,里面的特征值是由大到小排列的,这些特征值所对应的特征向量就是描述这个矩阵变化方向(从主要的变化到次要的变化排列)
    当矩阵是高维的情况下,那么这个矩阵就是高维空间下的一个线性变换,这个线性变化可能没法通过图片来表示,但是可以想象,这个变换也同样有很多的变换方向,我们通过特征值分解得到的前N个特征向量,那么就对应了这个矩阵最主要的N个变化方向。我们利用这前N个变化方向,就可以近似这个矩阵(变换)。也就是之前说的:提取这个矩阵最重要的特征。

    总结一下,特征值分解可以得到特征值与特征向量,特征值表示的是这个特征到底有多重要,而特征向量表示这个特征是什么,可以将每一个特征向量理解为一个线性的子空间,我们可以利用这些线性的子空间干很多的事情.不过,特征值分解也有很多的局限,比如说变换的矩阵必须是方阵。


    2. 奇异值分解

    下面重点谈谈奇异值分解。特征值分解是一个提取矩阵特征很不错的方法,但是它只是对方阵而言的,在现实的世界中,我们看到的大部分矩阵都不是方阵,比如说有N个学生,每个学生有M科成绩,这样形成的一个N * M的矩阵就不可能是方阵,我们怎样才能描述这样普通的矩阵呢的重要特征呢?奇异值分解可以用来干这个事情,奇异值分解是一个能适用于任意的矩阵的一种分解的方法:
    $$A=U∑V^T$$
    假设A是一个M * N的矩阵,那么得到的U是一个M * M的方阵(里面的向量是正交的,U里面的向量称为左奇异向量),Σ是一个M * N的矩阵(除了对角线的元素都是0,对角线上的元素称为奇异值),V’(V的转置)是一个N * N的矩阵,里面的向量也是正交的,V里面的向量称为右奇异向量),如下:
    $$A_{MN}=U_{MM}∑{M*N}V{N*N}$$

    那么奇异值和特征值是怎么对应起来的呢?首先,我们将一个矩阵A的转置 * A,将会得到一个方阵,我们用这个方阵求特征值可以得到:
    $$(A^TA)v_i=\lambda_iv_i$$
    这里得到的v,就是我们上面的右奇异向量。此外我们还可以得到:
    $$\sigma_i=\sqrt{\lambda_i}$$
    $$u_i=\frac1\sigma_iAv_i$$
    这里的σ就是上面说的奇异值,u就是上面说的左奇异向量。奇异值σ跟特征值类似,在矩阵Σ中也是从大到小排列,而且σ的减少特别的快,在很多情况下,前10%甚至1%的奇异值的和就占了全部的奇异值之和的99%以上了。也就是说,我们也可以用前r大的奇异值来近似描述矩阵,这里定义一下部分奇异值分解:
    $$A_{mn}≈U_{mr}∑{r*r}V^T{rn}$$
    r是一个远小于m、n的数,这样矩阵的乘法看起来像是下面的样子:
    $$A_{m
    n}=U_{mr}∑_{rr}V^T_{r*n}$$

    右边的三个矩阵相乘的结果将会是一个接近于A的矩阵,在这儿,r越接近于n,则相乘的结果越接近于A。而这三个矩阵的面积之和(在存储观点来说,矩阵面积越小,存储量就越小)要远远小于原始的矩阵A,我们如果想要压缩空间来表示原矩阵A,我们存下这里的三个矩阵:U、Σ、V就好了。

    最后,我再举了例子:


    矩阵奇异值分解

    3. 将SVD应用于推荐系统

    数据集中行代表用户user,列代表物品item,其中的值代表用户对物品的打分。基于SVD的优势在于:用户的评分数据是稀疏矩阵,可以用SVD将原始数据映射到低维空间中,然后计算物品item之间的相似度,可以节省计算资源。

    整体思路:先找到用户没有评分的物品,然后再经过SVD“压缩”后的低维空间中,计算未评分物品与其他物品的相似性,得到一个预测打分,再对这些物品的评分从高到低进行排序,返回前N个物品推荐给用户。

    具体代码如下,主要分为5部分:

    第1部分:加载测试数据集;

    第2部分:定义三种计算相似度的方法;

    第3部分:通过计算奇异值平方和的百分比来确定将数据降到多少维才合适,返回需要降到的维度;

    第4部分:在已经降维的数据中,基于SVD对用户未打分的物品进行评分预测,返回未打分物品的预测评分值;

    第5部分:产生前N个评分值高的物品,返回物品编号以及预测评分值。

    优势在于:用户的评分数据是稀疏矩阵,可以用SVD将数据映射到低维空间,然后计算低维空间中的item之间的相似度,对用户未评分的item进行评分预测,最后将预测评分高的item推荐给用户。

    这里是代码:

    # coding=utf-8
    from numpy import *
    from numpy import linalg as la
    
    '''加载测试数据集'''
    
    
    def loadExData():
        return mat([[0, 0, 0, 0, 0, 4, 0, 0, 0, 0, 5],
                    [0, 0, 0, 3, 0, 4, 0, 0, 0, 0, 3],
                    [0, 0, 0, 0, 4, 0, 0, 1, 0, 4, 0],
                    [3, 3, 4, 0, 0, 0, 0, 2, 2, 0, 0],
                    [5, 4, 5, 0, 0, 0, 0, 5, 5, 0, 0],
                    [0, 0, 0, 0, 5, 0, 1, 0, 0, 5, 0],
                    [4, 3, 4, 0, 0, 0, 0, 5, 5, 0, 1],
                    [0, 0, 0, 4, 0, 4, 0, 0, 0, 0, 4],
                    [0, 0, 0, 2, 0, 2, 5, 0, 0, 1, 2],
                    [0, 0, 0, 0, 5, 0, 0, 0, 0, 4, 0],
                    [1, 0, 0, 0, 0, 0, 0, 1, 2, 0, 0]])
    
    
    '''以下是三种计算相似度的算法,分别是欧式距离、皮尔逊相关系数和余弦相似度,
    注意三种计算方式的参数inA和inB都是列向量'''
    #这段代码在机器学习实战书中P259
    #(注意传入的inA,inB都是列向量,行向量会报错)
    
    def ecludSim(inA, inB):
        return 1.0 / (1.0 + la.norm(inA - inB))  # 范数的计算方法linalg.norm(),这里的1/(1+距离)表示将相似度的范围放在0与1之间
    
    
    def pearsSim(inA, inB):
        if len(inA) < 3: return 1.0
        return 0.5 + 0.5 * corrcoef(inA, inB, rowvar=0)[0][
            1]  # 皮尔逊相关系数的计算方法corrcoef(),参数rowvar=0表示对列求相似度,这里的0.5+0.5*corrcoef()是为了将范围归一化放到0和1之间
    
    
    def cosSim(inA, inB):
        num = float(inA.T * inB)
        denom = la.norm(inA) * la.norm(inB)
        return 0.5 + 0.5 * (num / denom)  # 将相似度归一到0与1之间
    
    
    '''按照前k个奇异值的平方和占总奇异值的平方和的百分比percentage来确定k的值,
    后续计算SVD时需要将原始矩阵转换到k维空间'''
    
    
    def sigmaPct(sigma, percentage):
        sigma2 = sigma ** 2  # 对sigma求平方
        sumsgm2 = sum(sigma2)  # 求所有奇异值sigma的平方和
        sumsgm3 = 0  # sumsgm3是前k个奇异值的平方和
        k = 0
        for i in sigma:
            sumsgm3 += i ** 2
            k += 1
            if sumsgm3 >= sumsgm2 * percentage:
                return k
    
    
    '''函数svdEst()的参数包含:数据矩阵、用户编号、物品编号和奇异值占比的阈值,
    数据矩阵的行对应用户,列对应物品,函数的作用是基于item的相似性对用户未评过分的物品进行预测评分'''
    
    
    def svdEst(dataMat, user, simMeas, item, percentage):
        n = shape(dataMat)[1]
        simTotal = 0.0;
        ratSimTotal = 0.0
        u, sigma, vt = la.svd(dataMat)
        k = sigmaPct(sigma, percentage)  # 确定了k的值
        sigmaK = mat(eye(k) * sigma[:k])  # 构建对角矩阵
        xformedItems = dataMat.T * u[:, :k] * sigmaK.I  # 根据k的值将原始数据转换到k维空间(低维),xformedItems表示物品(item)在k维空间转换后的值
        for j in range(n):
            userRating = dataMat[user, j]
            if userRating == 0 or j == item: continue
            similarity = simMeas(xformedItems[item, :].T, xformedItems[j, :].T)  # 计算物品item与物品j之间的相似度
            simTotal += similarity  # 对所有相似度求和
            ratSimTotal += similarity * userRating  # 用"物品item和物品j的相似度"乘以"用户对物品j的评分",并求和
        if simTotal == 0:
            return 0
        else:
            return ratSimTotal / simTotal  # 得到对物品item的预测评分
    
    
    '''函数recommend()产生预测评分最高的N个推荐结果,默认返回5个;
    参数包括:数据矩阵、用户编号、相似度衡量的方法、预测评分的方法、以及奇异值占比的阈值;
    数据矩阵的行对应用户,列对应物品,函数的作用是基于item的相似性对用户未评过分的物品进行预测评分;
    相似度衡量的方法默认用余弦相似度
    '''
    
    
    def recommend(dataMat, user, N=5, simMeas=cosSim, estMethod=svdEst, percentage=0.9):
        unratedItems = nonzero(dataMat[user, :].A == 0)[1]  # 建立一个用户未评分item的列表
        if len(unratedItems) == 0: return 'you rated everything'  # 如果都已经评过分,则退出
        itemScores = []
        for item in unratedItems:  # 对于每个未评分的item,都计算其预测评分
            estimatedScore = estMethod(dataMat, user, simMeas, item, percentage)
            itemScores.append((item, estimatedScore))
        itemScores = sorted(itemScores, key=lambda x: x[1], reverse=True)  # 按照item的得分进行从大到小排序
    
        return itemScores[:N]  # 返回前N大评分值的item名,及其预测评分值
    
    #下面来调用一下:
    testdata = loadExData()
    top = recommend(testdata, 1, N=3, percentage=0.8) # 对编号为1的用户推荐评分较高的3件商品
    for Top in top :
        item , estimatedScore = Top
        print(item  , estimatedScore)
    
    

    最后,强烈推荐去看机器学习实战这本书上有关SVD的解析,讲的很清楚(本文代码框架来源于该书)

    (注:本文是我在博客上学习是所记的笔记,这里感谢一下两位博主并贴上两位博主文章链接:
    第一位
    第二位

    相关文章

      网友评论

          本文标题:SVD解析以及用其实现推荐算法

          本文链接:https://www.haomeiwen.com/subject/hszdrftx.html