技术交流QQ群:1027579432,欢迎你的加入!

一、SVD的优缺点及应用场合

  • 1.优点:简化数据,去除噪声,提高算法的结果
  • 2.缺点:数据的转换可能难以理解
  • 3.适用场合:数值型数据

二、SVD算法的主要用途

  • SVD是矩阵分解的一种类型,而矩阵分解是将数据矩阵分解成多个独立部分的过程。
  • 1.隐性语义分析LSA
    • 最早的SVD应用之一是信息检索,称为利用SVD的方法为隐性语义分析。在LSA中,一个矩阵是由文档和词语组成的。当在此矩阵上应用SVD时,就会构建出多个奇异值,这些奇异值表示的是文档中的概念或主题,这一特点可以应用在更高的文档搜索中。
  • 2.推荐系统
    • SVD的另一个应用就是推荐系统。利用SVD从数据中构建一个主题空间,然后在该空间下计算其相似度。考虑下图中给出的矩阵,它是由菜品(列)和品菜师对这些菜的意见构成的(列)。品菜师可以采用1到5之间的任意一个整数对菜进行评价;如果品菜师没有尝过该菜,则评分为0。可以利用SVD将原先的5维/7维的矩阵降低到只有2维。


      餐馆的菜及评价的数据矩阵.jpg

三、矩阵分解

  • 矩阵分解就是将原始矩阵表示成新的易于处理的形式,这种新式是两个或多个矩阵的乘积。不同的矩阵分解技术具有不同的性质,其中有些更适合于某个应用,有些则更适合于其他的应用,最常见的矩阵分解技术是SVD,SVD将原始的数据集矩阵Data分解成三个矩阵U,Sigma,V.T。如果原始矩阵Data是m行n列,那个U,Sigma,V.T就分别是m*m,m*n,n*n, 如下面的公式所示:


    奇异值分解.jpg
  • 上面分解中会构建出一个矩阵Sigma,此矩阵只有对角元素,其他元素均为0.Sigma的对角元素一般是从大到小排列的。这些对角元素称为奇异值,它们对应了原始数据集矩阵Data的奇异值,设Data*Data.T矩阵的特征是是lambda,则奇异值就是sqrt(lambda)。
  • 在实际工程中,存在一个现象。在某个奇异值的数目(r)个后,其他的奇异值都置为0。这意味着数据集中仅有r个重要的特征,而其余特征则都是噪声或冗余特征。如何知道仅保留前几个的奇异值?确定要保留的奇异值数量有很多启发式的策略。主要有两种:
    • a.保留矩阵中90%的能量信息,为了计算总能量信息,将所有的奇异值求其平方和,于是可以将奇异值的平方累加到总值的90%为止。
    • b.当矩阵中有很多的奇异值时,那么就保留前面的2000或3000个奇异值,在实际中更容易实现。

四、利用numpy求矩阵的奇异值SVD

SVD示意图.jpg
from numpy import *
   from numpy import linalg as la


    def loadDataSet():
       return [[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]]
   dataMat = loadDataSet()
    print("----------奇异值计算-----------")
    U, Sigma, VT = la.svd(dataMat)
    print("Sigma:\n", Sigma)
    print("---------------重构原始矩阵dataMat------------")
    Sigma3 = mat([[Sigma[0], 0, 0], [0, Sigma[1], 0], [0, 0, Sigma[2]]])
    dataMatload = U[:, :3] * Sigma3 * VT[:3, :]
    print("重构的原始矩阵:\n", dataMatload)

五、基于协同过滤的推荐引擎

  • 协同过滤:通常是将用户和其他用户的数据进行对比来实现推荐的。下面的数据是采用餐馆的菜及评价的数据矩阵给出的。当数据采用这种方式给出时,就可以比较用户或物品之间的相似度。当知道两个用户或两个物品之间的相似度,就可以利用已有的数据来预测未知用户的喜好。唯一做的就是相似度的计算,计算方法通常有三种。
    相似度计算.jpg
    • 1.欧式距离
      计算手撕猪肉与烤牛肉、鳗鱼饭之间的相似度,使用欧式距离来表示。计算公式如下:
      欧式距离1.jpg

      欧式距离2.jpg

      在上面的距离中,手撕猪肉与烤牛肉之间的距离小于手撕猪肉与鳗鱼饭之间的距离,因此手撕猪肉与烤牛肉更相似。我们希望相似度值在0到1之间变化,并且两个物品之间月相似,相似度值越大。可以使用相似度=1/(1+欧式距离)这样的计算方法来计算相似度,当距离为0是时,相似度是1.
    • 2.皮尔逊相关系数
      相对于欧式距离的优势在于它对用户评级的量级并不敏感。例如,当某个极端主义者对所有物品的评分都是0或5,皮尔逊相关系数会认为这两个向量是相等的。在numpy中,皮尔逊相关系数由函数corrcoef()计算的。皮尔逊相关系数的取值范围从-1到+1,通过函数0.5+0.5*corrcoef()这个函数将取值范围归一化到0到1之间。
    • 3.余弦相似度
      计算两个向量之间夹角的余弦值,夹角是90度,相似度为0;两个向量同方向,则相似度为1。余弦相似度的范围也是-1到+1,因此也需要归一化至0到1之间。


      余弦相似度.jpg
      # 相似度计算函数
        # 欧式距离---列向量参与运算
        def ecludSim(inA, inB):
            return 1.0 / (1.0 + la.norm(inA - inB))  # la.norm是计算列向量的L2范数
      
      
        # 皮尔逊系数
        def pearSim(inA, inB):
              if (len(inA) < 3):
                  return 1.0
              else:
                  return 0.5 + 0.5 * corrcoef(inA, inB, rowvar=0)[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)

六、基于物品相似度的餐馆菜品推荐引擎实例

  • 计算两个餐馆菜品之间的距离,称为基于物品的相似度。行与行之间的比较是基于用户的相似度,列与列之间的比较是基于物品的相似度。使用哪种相似度,主要取决于用户或物品的数量。如果用户的数目很多,倾向于使用基于物品的相似度的计算方法。对大部分产品导向的推荐引擎而言,用户的数量往往大于物品的数量,即购买商品的用户会对于出售商品的种类。另外,对推荐引擎的评价采用交叉测试的方法。将某些已经知道的评分值去掉,然后对它们进行预测,最后计算预测值与实际值之间的差异。通常用于推荐引擎的评价指标是最小均方根误差RMSE。
#!/usr/bin/env python
# -*- coding: utf-8 -*-
# @Date    : 2019-05-20 10:34:35
# @Author  : cdl (1217096231@qq.com)
# @Link    : https://github.com/cdlwhm1217096231/python3_spider
# @Version : $Id$

from numpy import *
from numpy import linalg as la


def loadDataSet():
    return [[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]]


def loadDataSet2():
    return [[4, 4, 0, 2, 2],
            [4, 0, 0, 3, 3],
            [4, 0, 0, 1, 1],
            [1, 1, 1, 2, 0],
            [2, 2, 2, 0, 0],
            [1, 1, 1, 0, 0],
            [5, 5, 5, 0, 0]]


def loadExData():
    return[[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]]


# 相似度计算函数
# 欧式距离---列向量参与运算
def ecludSim(inA, inB):
    return 1.0 / (1.0 + la.norm(inA - inB))  # la.norm是计算列向量的L2范数


# 皮尔逊系数
def pearSim(inA, inB):
    if (len(inA) < 3):
        return 1.0
    else:
        return 0.5 + 0.5 * corrcoef(inA, inB, rowvar=0)[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)


# 基于物品(列)进行相似度的推荐引擎
def standEst(dataMat, user, simMeas, item):
    """
        dataMat:数据集
        user:用户
        simMeans:相似度衡量方式
        item:物品
    """
    n = shape(dataMat)[1]
    simTotal = 0.0
    ratSimTotal = 0.0
    for j in range(n):
        userRating = dataMat[user, j]
        if userRating == 0:
            continue
        overlap = nonzero(logical_and(
            dataMat[:, item].A > 0, dataMat[:, j].A > 0))[0]
        if len(overlap) == 0:
            similarity = 0.0
        else:
            similarity = simMeas(dataMat[overlap, item], dataMat[overlap, j])
            print("the %d and %d similarity is: %f" % (item, j, similarity))
            simTotal += similarity
            ratSimTotal += similarity * userRating
    if simTotal == 0.0:
        return 0
    else:
        return ratSimTotal / simTotal


# 基于SVD的评分估计
def svdEst(dataMat, user, simMeas, item):
    n = shape(dataMat)[1]
    simTotal = 0.0
    ratSimTotal = 0.0
    U, Sigma, VT = la.svd(dataMat)
    Sig4 = mat(eye(4) * Sigma[:4])  # 包含奇异值能量的90%
    xformedItems = dataMat.T * U[:, :4]
    for j in range(n):
        userRating = dataMat[user, j]
        if userRating == 0 or j == item:
            continue
        else:
            similarity = simMeas(xformedItems[item, :].T, xformedItems[j, :].T)
            print("the %d and %d similarity is: %f" % (item, j, similarity))
            simTotal += similarity
            ratSimTotal += similarity * userRating
    if simTotal == 0.0:
        return 0
    else:
        return ratSimTotal / simTotal


# 推荐函数
def recommend(dataMat, user, N=3, simMeas=cosSim, estMethod=standEst):
    unratedItems = nonzero(dataMat[user, :].A == 0)[1]  # 寻找未评分的物品
    if len(unratedItems) == 0.0:
        return "you rated everything"
    itemScores = []
    for item in unratedItems:
        estimatedScore = estMethod(dataMat, user, simMeas, item)
        itemScores.append((item, estimatedScore))
    return sorted(itemScores, key=lambda jj: jj[1], reverse=True)[:N]


if __name__ == "__main__":
    dataMat = loadDataSet()
    print("----------奇异值计算-----------")
    U, Sigma, VT = la.svd(dataMat)
    print("Sigma:\n", Sigma)
    print("---------------重构原始矩阵dataMat------------")
    Sigma3 = mat([[Sigma[0], 0, 0], [0, Sigma[1], 0], [0, 0, Sigma[2]]])
    dataMatload = U[:, :3] * Sigma3 * VT[:3, :]
    print("重构的原始矩阵:\n", dataMatload)
    print("----------------几种相似度计算方法-----------------")
    myData = mat(dataMat)
    print("欧式距离:\n", ecludSim(myData[:, 0], myData[:, 4]))
    print("欧式距离:\n", ecludSim(myData[:, 4], myData[:, 4]))
    print("皮尔逊系数:\n", pearSim(myData[:, 0], myData[:, 4]))
    print("皮尔逊系数:\n", pearSim(myData[:, 4], myData[:, 4]))
    print("余弦相似度:\n", cosSim(myData[:, 0], myData[:, 4]))
    print("余弦相似度:\n", cosSim(myData[:, 4], myData[:, 4]))
    print("------------基于物品相似度的推荐引擎-----------")
    dataMat2 = loadDataSet2()
    myData2 = mat(dataMat2)
    print("默认推荐结果:\n", recommend(myData2, user=2))
    print("使用欧式距离,推荐结果:\n", recommend(myData2, user=2, simMeas=ecludSim))
    print("使用皮尔逊系数,推荐结果:\n", recommend(myData2, user=2, simMeas=pearSim))
    print("------------基于SVD相似度的推荐引擎-----------")
    myData3 = mat(loadExData())
    U, Sigma, VT = la.svd(myData3)
    print("奇异值矩阵Sigma:\n", Sigma)
    Sigma2 = Sigma ** 2
    sumSigma = sum(Sigma2)
    print("总能量:", sumSigma)
    print("总能量的90%:", sumSigma * 0.9)
    print("前两个元素所包含的能量:", sum(Sigma2[:2]))
    print("前三个元素所包含的能量:", sum(Sigma2[:3]))  # 将11维矩阵降维到3维矩阵
    print("使用默认相似度的SVD进行评分:", recommend(myData3, user=1, estMethod=svdEst))
    print("使用皮尔逊系数的SVD进行评分:", recommend(myData3, user=1,
                                  estMethod=svdEst, simMeas=pearSim))