机器学习之奇异值分解算法

544 阅读4分钟
  参与拿奖:本文已参与「新人创作礼」活动,一起开启掘金创作之路

1.相关概念

SVD:Singular Value Decomposition

奇异值分解:将一个矩阵划分为多个矩阵的乘积。

A=UxSigmaxV.T,其中Sigma矩阵只有对角元素,这些对角元素称为奇异值,对应了原始数据的奇异值。

奇异值分解优点:简化数据、去除噪声、提高算法结果;

奇异值分解缺点:数据的转换难以理解;

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

推荐系统:简单版本的推荐系统能够计算item与person之间的相似度;更先进的则使用SVD。

2.案例

2.1 简单demo

from numpy import *
from numpy import linalg as la
print("SVD分解一个矩阵")
testMat=mat([[1,1],[7,7]])
U,Sigma,VT=la.svd(testMat)
# Sigma只返回对角上的元素
print("U:",U)
print("Sigma:",Sigma)
print("V转置:",VT)

image.png

2.2 分解之后重构

data=[[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(data)
Sig3=eye(3)
for i in range(3):
    Sig3[i,i]=Sigma[i]
    
# print(Sigma[0])
mat(U[:,:3])*mat(Sig3)*mat(VT[:3,:])

image.png

对于具体保留多少个奇异值的方法,有两种方法:

1)保留矩阵中90%的能量信息,总的能量信息等于奇异值平方求和,通过奇异值累加到90%即可。

2)对于较多(上万)奇异值时,可以采用保留前2000~3000个奇异值。

2.3 推荐领域

2.3.1 相关概念

通过将用户信息与其他用户进行比对来实现推荐:

相似度计算方法:第一种是欧氏距离,就是通过做差再平方求和之后开方,可以1/(1+k)将其固定在(0,1]。

第二种:皮尔逊相关系数:度量两个向量之间的相似度,其对用户的某些差异较大的数据不敏感,取值为[-1,1]。

第三种:余弦相似度:计算两个向量之间夹角的余弦值,如果两向量垂直,则相似度为0,方向相同则相似度为1。

from numpy import *
from numpy import linalg as la


def ecludSim(inA,inB):
    return 1.0/(1.0 + la.norm(inA - inB))

def pearsSim(inA,inB):
    if len(inA) < 3 :
        return 1.0
    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 loadExData():
    dataMat=[[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, 5, 5, 0, 0],
           [1, 1, 1, 0, 0]]
    return dataMat

def test():
    dataMat=mat(loadExData())
    fishRice=dataMat[:,0]
    pigMeat=dataMat[:,4]
    print("欧式距离计算相似度:", ecludSim(fishRice, pigMeat))
    print("皮尔逊系数计算相似度:", pearsSim(fishRice, pigMeat))
    print('向量余弦计算相似度:',cosSim(fishRice, pigMeat))
    
test()

image.png

2.3.2 基于物品相似度的推荐

基于物品的相似度与基于用户的相似度,倾向于计算更少数据的相似度。

评价指标:RMSE(最小均方根误差)

推荐N个未品尝过的菜肴
1,寻找用户未评级的菜品
2,在未评级菜品中对于每一个物品预计一个评级分数,预测用户对其的打分
3,对这些评级物品排序,返回前N个菜品

from numpy import *
from numpy import linalg as la

# 参数分别为:数据矩阵、用户编号、相似度计算方法、物品编号
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])
        # print('the %d and %d similarity is: %f' % (item, j, similarity))
        # 对相似度求和
        simTotal += similarity
        ratSimTotal += similarity * userRating
    if simTotal == 0:
        return 0
    else:
        return ratSimTotal/simTotal
    

def recommend(dataMat, user, N=3, simMeas=cosSim, estMethod=standEst):
    unratedItems = nonzero(dataMat[user,:].A==0)[1]#find unrated items 
    if len(unratedItems) == 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]

def test():
    myMat=mat(loadExData())
    myMat[0,1]=myMat[0,0]=myMat[1,0]=myMat[2,0]=4
    myMat[3,3]=2
    for i in range(shape(myMat)[0]):
        # ['ecludSim','pearsSim','cosSim']:
        info=recommend(myMat,i,simMeas=ecludSim)
        for k,v in info:
            print("用户: %d 可能对菜品:%d 的评级为:%.2f"%(i,k,v))
    
    
    
test()
    

image.png

2.3.3 利用SVD提升推荐效果

### 基于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]) #arrange Sig4 into a diagonal matrix
    # 构建转换后的矩阵
    xformedItems = dataMat.T * U[:,:4] * Sig4.I  #create transformed items
    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('the %d and %d similarity is: %f' % (item, j, similarity))
        simTotal += similarity
        ratSimTotal += similarity * userRating
    if simTotal == 0:
        return 0
    else: 
        return ratSimTotal/simTotal
    
def test():
    myMat=mat(loadExData())
    myMat[0,1]=myMat[0,0]=myMat[1,0]=myMat[2,0]=4
    myMat[3,3]=2
    for i in range(shape(myMat)[0]):
        # ['ecludSim','pearsSim','cosSim']:
        info=recommend(myMat,i,estMethod=svdEst)
        for k,v in info:
            print("用户: %d 可能对菜品:%d 的评级为:%.2f"%(i,k,v))
    
test()

image.png

构建推荐引擎的挑战
一个挑战是在做SVD分解时,会降低程序运行速度。
矩阵的表示方法、以及冷启动问题(缺乏足够的数据时)。
冷启动问题的解决方案就是将推荐看成搜索问题。
可以为物品贴标签,将这些标签属性作为相似度计算所需要的数据(基于内容的推荐)。
另外在新增物品时,在协同过滤场景下,由于缺乏用户对新物品的使用以及评价信息,无法判断用户对其喜好,无法利用该商品。

3.小结

SVD是一种强大的降维工具,可以利用SVD来逼近矩阵并提取重要特征。

通过保留矩阵80%~90%的能量,就可以获得重要特征,并去掉噪声。

协同过滤是一种基于用户喜好或行为数据的推荐算法,其核心是相似度计算方法。

在大规模数据集上,可以采用离线的方式来进行SVD分解和相似度计算。

参考资料

[1] 机器学习实战

[2] 书籍源码

[3] jupyter版本

[4] 本节代码