奇异值分解(SVD)学习笔记(python)

 

一.原理

 

 SVD的性质:

SVD计算举例:

二.

SVD(奇异值分解):

优点:简化数据,去除噪声点,提高算法的结果;
缺点:数据的转换可能难以理解;
适用于数据类型:数值型。
通过SVD对数据的处理,我们可以使用小得多的数据集来表示原始数据集,从有噪声的数据中抽取相关特征,这样做实际上是去除了噪声和冗余信息,以此达到了优化数据、提高结果的目的。

最早的SVD应用之一是信息检索。我们称利用SVD的方法为隐性语义检索(LSI)或隐形语义分析(LSA)。
在LSI中,一个矩阵是由文档和词语组成的,当我们在该矩阵上应用SVD时,就会构建出多个奇异值。这些奇异值代表了文档中的概念或主题,这一特点可以用于更高效的文档搜索。如果我们从上千篇相似的文档中抽取出概念,那么同义词就会映射为同一概念。

SVD的另一个应用就是推荐系统。简单版本的推荐系统能够计算项或者相似度。更先进的方法则先利用SVD从数据中构建一个主题空间,然后再在该空间下计算其相似度。

SVD也可用于特征压缩(数据降维)。将SVD用于PCA主成分分析,来做数据压缩和降噪。

三.SVD的库实现

SCV实现的相关线性代数在Numpy中有一个称为线性代数linalg的线性代数工具箱。下面演示其用法对于一个简单的矩阵:

from numpy import *
from numpy import linalg as la
 
df = mat(array([[1,1],[1,7]]))
U,Sigma,VT = la.svd(df)
print(U)
print(Sigma)
print(VT)

-------------------------------

# [[ 0.16018224  0.98708746]
#  [ 0.98708746 -0.16018224]]

# [7.16227766 0.83772234]

# [[ 0.16018224  0.98708746]
#  [ 0.98708746 -0.16018224]]

四.SVD实战应用-基于物品相似度推荐算法

原理见:https://www.jianshu.com/p/e871e741b560

代码:

from numpy import *
from numpy import linalg as la
 
# (用户x商品)    # 为0表示该用户未评价此商品,即可以作为推荐商品
def loadExData():
    return [[0, 0, 0, 2, 2],
            [0, 0, 0, 3, 3],
            [0, 0, 0, 1, 1],
            [1, 1, 1, 0, 0],
            [2, 2, 2, 0, 0],
            [5, 0, 5, 0, 0],
            [1, 1, 1, 0, 0]]
 
# 假定导入数据都为列向量
 
# 欧几里德距离 这里返回结果已处理 0,1   0最大相似,1最小相似   欧氏距离转换为2范数计算
def ecludSim(inA,inB):
    return 1.0 / (1.0 + la.norm(inA-inB))
 
# 皮尔逊相关系数 numpy的corrcoef函数计算
def pearsSim(inA,inB):
    if(len(inA) < 3):
        return 1.0
    return 0.5 + 0.5*corrcoef(inA,inB,rowvar=0)[0][1]   # 使用0.5+0.5*x 将-1,1 转为 0,1
 
# 余玄相似度 根据公式带入即可,其中分母为2范数计算,linalg的norm可计算范数
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
 
 
# 对物品评分  (数据集 用户行号 计算误差函数 推荐商品列号)
def standEst(dataMat, user, simMeas, 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]   # 获得相比较的两列同时都不为0的数据行号
        if len(overLap) == 0:
            similarity = 0
        else:
            # 求两列的相似度
            similarity = simMeas(dataMat[overLap,item],dataMat[overLap,j])  # 利用上面求得的两列同时不为0的行的列向量 计算距离
        # print('%d 和 %d 的相似度是: %f' % (item, j, similarity))
        simTotal += similarity  # 计算总的相似度
        ratSimTotal += similarity * userRating  # 不仅仅使用相似度,而是将评分当权值*相似度 = 贡献度
    if simTotal == 0:   # 若该推荐物品与所有列都未比较则评分为0
        return 0
    else:
        return ratSimTotal/simTotal # 归一化评分 使其处于0-5(评级)之间
 
# 给出推荐商品评分
def recommend(dataMat, user, N=3, simMeas=cosSim, estMethod=standEst):
    unratedItems = nonzero(dataMat[user,:].A==0)[1]  # 找到该行所有为0的位置(即此用户未评价的商品,才做推荐)
    if len(unratedItems) == 0:
        return '所有物品都已评价...'
    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]   # 将推荐商品排序
 
# 结果测试如下:
myMat = mat(loadExData())
myMat[0,1] = myMat[0,0] = myMat[1,0] = myMat[2,0] = 4   # 将数据某些值替换,增加效果
myMat[3,3] = 2
result1 = recommend(myMat,2)        # 余玄相似度
print(result1)
result2 = recommend(myMat,2,simMeas=ecludSim)   # 欧氏距离
print(result2)
result3 = recommend(myMat,2,simMeas=pearsSim)   # 皮尔逊相关度
print(result3)

 

上面用了三种计算距离的函数,使用其中一种便可以了,下面使用SVD来做基于物品协同过滤。

SVD方法,用下面函数(svdEst)来替换上面的物品评价函数(standEst)即可,并且这里使用更复杂的数据集:

def loadExData2():
    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]]
 
# 替代上面的standEst(功能) 该函数用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])  #将奇异值向量转换为奇异值矩阵
    xformedItems = dataMat.T * U[:,:4] * Sig4.I  # 降维方法 通过U矩阵将物品转换到低维空间中 (商品数行x选用奇异值列)Sig4.I求Sig4的逆矩阵
    for j in range(n):
        userRating = dataMat[user,j]
        if userRating == 0 or j == item:
            continue
        # 这里需要说明:由于降维后的矩阵与原矩阵代表数据不同(行由用户变为了商品),所以在比较两件商品时应当取【该行所有列】 再转置为列向量传参
        similarity = simMeas(xformedItems[item,:].T,xformedItems[j,:].T)
        # print('%d 和 %d 的相似度是: %f' % (item, j, similarity))
        simTotal += similarity
        ratSimTotal += similarity * userRating
    if simTotal == 0:
        return 0
    else:
        return ratSimTotal/simTotal
 
# 结果测试如下:
myMat = mat(loadExData2())
result1 = recommend(myMat,1,estMethod=svdEst)   # 需要传参改变默认函数
print(result1)
result2 = recommend(myMat,1,estMethod=svdEst,simMeas=pearsSim)
print(result2)

 

上面的之所以使用4这个数字,是因为通过预先计算得到能满足90%的奇异值能量的前N个奇异值。判断计算如下:

# 选出奇异值能量大于90%的所有奇异值
myMat = mat(loadExData2())
U,sigma,VT = linalg.svd(myMat)
sigma = sigma**2    # 对奇异值求平方
cnt = sum(sigma)    # 所有奇异值的和
print(cnt)
value = cnt*0.9     # 90%奇异值能量
print(value)
cnt2 = sum(sigma[:3])   # 2小于90%,前3个则大于90%,所以这里选择前三个奇异值
print(cnt2)
 
# 541.9999999999995
# 487.7999999999996
# 500.5002891275793

在函数svdEst中使用SVD方法,将数据集映射到低纬度的空间中,再做运算。其中的xformedItems = dataMat.T*U[:,:4]*Sig4.I可能不是很好理解,它就是SVD的降维步骤,通过U矩阵和Sig4逆矩阵将商品转换到低维空间(得到 商品行,选用奇异值列)。

以上是SVD的一个示例,但是对此有几个问题:

我们不必在每次评分是都做SVD分解,大规模数据上可能降低效率,可以在程序调用时运行一次,在大型系统中每天运行一次或频率不高,还要离线运行;
矩阵中有很多0,实际系统中0更多,可以通过只存储非0元素来节省空间和计算开销;
计算资源浪费来自于相似度的计算,每次一个推荐时都需要计算多个物品评分(即相似度),在需要时此记录可以被用户重复使用。实际中,一个普遍的做法是离线计算并保存相似度得分

参考资料:

https://blog.csdn.net/qq_36523839/article/details/82347332

https://www.jianshu.com/p/e871e741b560

https://blog.csdn.net/qq_36657751/article/details/82734617

https://www.jianshu.com/p/4fdd0e8e272b

 

 

 

  • 0
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值