从零开始-Machine Learning学习笔记(38)-基于SVD的推荐系统

0. 前言

  本文总结于Peter Harrington的《Machine Learning in Action》的第14章-利用SVD简化数据。

1. 关于SVD与特征值分解

  SVD(Singular Value Decomposition, SVD)奇异值分解。在说及SVD之前,我们需要了解一下特征值分解。学过线性代数都知道,如果一个方阵可以拆成如下的形式:
A v = λ v Av = \lambda v Av=λv
λ \lambda λ称为特征值,而v称为特征向量,如果这n个特征向量线性无关,那么矩阵就可以分解为如下的形式:
A = W Σ W − 1 A = W \Sigma W^{-1} A=WΣW1
其中,W是由n个特征向量组成的矩阵, Σ \Sigma Σ是由特征值组成的对角阵。通常我们会将W的n个向量进行标准化,即 ∣ ∣ W i ∣ ∣ 2 = 1 或 者 W T W = 1 ||W_i||_2 = 1或者W^T W = 1 Wi2=1WTW=1,此时的n个特征向量为标准正交基,满足 W T W = I W^T W = I WTW=I, 于是矩阵A就可以分解为:
A = W Σ W T A = W \Sigma W^T A=WΣWT
这就是矩阵的特征值分解。
  这里我们注意到,特征值分解用于方阵,但是实际问题中,往往不是方阵,所以为了对非方阵的矩阵进行分解,就需要我们的SVD。这里我们假设矩阵A是一个m*n的矩阵。

  1. 如果 A A T AA^T AAT,则 A A T AA^T AAT是一个m*m的方阵,对这个方阵进行特征值分解有:
    ( A A T ) u i = λ i u i (AA^T)u_i = \lambda_i u_i (AAT)ui=λiui
    会得到m个特征值和m个特征向量,这m个特征向量组成一个矩阵U,我们称之为左奇异矩阵
  2. 同理,如果 A T A A^T A ATA,则 A T A A^T A ATA是一个n*n的方阵,对这个方阵进行特征值分解有:
    ( A T A ) v i = λ i v i (A^T A)v_i = \lambda_i v_i (ATA)vi=λivi
    会得到n个特征值和n个特征向量,这n个特征向量组成一个矩阵V,我们称之为右奇异矩阵
  3. 将右奇异值矩阵对应的特征值进行开方,得到的结果称为奇异值,将这些奇异值组成对角矩阵 Σ \Sigma Σ

于是,一个非方阵 A m ∗ n A_{m*n} Amn,可以分解为:
A m ∗ n = U m ∗ m Σ m ∗ n V n ∗ n T A_{m*n} = U_{m*m}\Sigma_{m*n}V_{n*n}^T Amn=UmmΣmnVnnT
这就是奇异值分解的原理。

2. 基于SVD的推荐系统

  假如我们要对某个用户喜欢的电影进行预测,推荐系统发现一部电影该用户还没有看过,它就会计算该电影与用户看过的电影之间的相似度,如果相似度高,那么系统就会认为该用户也喜欢这部电影。所以,基于SVD的推荐系统的第一件事就是定义相似度。在推荐系统的中,相似度的定义一般有三种方式:欧式距离、皮尔逊相关系数和余弦距离。

2. 1 相似度

(1) 基于欧式距离的相似度:
S i m i l a r i t y = 1 1 + d i s t a n c e Similarity = \frac{1}{1 + distance} Similarity=1+distance1
如果距离越远,则相似度越小;距离越近,相似度越高。用python可以写为:

from numpy import linalg as la
def ecludSim(inA,inB):
    # 计算基于欧式距离的相似度
    return 1.0/(1.0 + la.norm(inA - inB))

其中la.norm()计算范数,默认为2范数。

(2) 基于皮尔逊相关系数的相似度:
S i m i l a r i t y = 0.5 + 0.5 ∗ c o r r c o e f ( ) Similarity = 0.5 + 0.5 *corrcoef() Similarity=0.5+0.5corrcoef()

def pearsSim(inA,inB):
    # 计算基于皮尔逊相关系数的相似度
    if len(inA) < 3 : return 1.0
    return 0.5+0.5*corrcoef(inA, inB, rowvar = 0)[0][1]

(3) 基于余弦距离的相似度“:
S i m i l a r i t y = 0.5 + 0.5 ∗ c o s ( θ ) c o s ( θ ) = A ∗ B ∣ ∣ A ∣ ∣ ∣ ∣ B ∣ ∣ Similarity = 0.5 + 0.5 * cos(\theta) \\ cos(\theta) = \frac{A*B}{||A|| ||B||} Similarity=0.5+0.5cos(θ)cos(θ)=ABAB

def cosSim(inA,inB):
    # 计算基于余弦cos距离的相似度
    num = float(inA.T*inB)
    denom = la.norm(inA)*la.norm(inB)
    return 0.5+0.5*(num/denom)
2.2 使用SVD对未打分的物品进行打分

先看代码:

def svdEst(dataMat, user, simMeas, item):
    '''
    采用SVD奇异值分解的评价函数
    :param dataMat:     数据矩阵
    :param user:        用户【行数】
    :param simMeas:     相似度计算函数
    :param item:        待评价的物品
    :return:
    '''
    # 获取物品的数量
    n = shape(dataMat)[1]
    simTotal = 0.0; ratSimTotal = 0.0
    # 奇异值分解
    U,Sigma,VT = la.svd(dataMat)
    # 选取包含90%能量的奇异值,在这里是前4个Sigma,构成对角矩阵
    Sig4 = mat(eye(4)*Sigma[:4]) #arrange Sig4 into a diagonal matrix
    # 将原数据转化到低维空间中
    xformedItems = dataMat.T * U[:,:5] * Sig4.I  #create transformed items

    for j in range(n):
        # 取出user对物品j的打分
        userRating = dataMat[user,j]
        # 如果打分为0或者j等于待打分物品 ,就跳过
        if userRating == 0 or j==item: continue
        # 计算物品j与物品item在低维空间中的相似度
        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
    # 返回关于物品item的打分
    else: return ratSimTotal/simTotal

(1)首先获取物品的数量,然后进行奇异值分解,选取包含90%能量以上的奇异值。即计算 Σ \Sigma Σ中前多少个奇异值之和能达到所有奇异值和的90%。这里选择的是5,这个5是基于测试数据:

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]]
# 对其进行奇异值分解有:
dataMat = mat(loadExData2())
U, Sigma, VT = la.svd(dataMat)
print('Sigma = ',Sigma)
print('Ratio = ',sum( Sigma[:5]) / sum(Sigma))

Sigma = [15.77075346 11.40670395 11.03044558 4.84639758 3.09292055 2.58097379
1.00413543 0.72817072 0.43800353 0.22082113 0.07367823]
Ratio = 0.9014360861521987

(2)然后将原数据矩阵转化到低维空间中:

xformedItems = dataMat.T * U[:,:5] * Sig4.I 

这句话很多人都不理解,为什么是这个样子的。我后来在机器学习实战的P264中代码对应的公式推导中找到了答案,其实就是一个很简单的线性变换:
A m ∗ n ≃ U m ∗ k Σ k ∗ n V n ∗ k T A_{m*n}\simeq U_{m*k}\Sigma_{k*n}V_{n*k}^T AmnUmkΣknVnkT
其中 V n ∗ k V_{n*k} Vnk就是A在低维空间中的映射。现在只需要反解出来就可以了:
U m ∗ k − 1 A m ∗ n ≃ U m ∗ k − 1 U m ∗ k Σ k ∗ n V n ∗ k T U k ∗ m A m ∗ n ≃ U k ∗ m U m ∗ k Σ k ∗ n V n ∗ k T 由 于 U k ∗ m 是 正 交 阵 , 有 : U k ∗ m A m ∗ n ≃ E k ∗ k Σ k ∗ n V n ∗ k T Σ k ∗ n − 1 U k ∗ m A m ∗ n ≃ Σ k ∗ n − 1 Σ k ∗ n V n ∗ k T 于 是 : V n ∗ k T = Σ k ∗ n − 1 U k ∗ m A m ∗ n 两 边 同 时 转 置 : V n ∗ k = ( Σ k ∗ n − 1 U k ∗ m A m ∗ n ) T = A m ∗ n T U m ∗ k Σ k ∗ n − 1 U_{m*k}^{-1}A_{m*n}\simeq U_{m*k}^{-1}U_{m*k}\Sigma_{k*n}V_{n*k}^T \\ U_{k*m}A_{m*n}\simeq U_{k*m}U_{m*k}\Sigma_{k*n}V_{n*k}^T \\ 由于U_{k*m}是正交阵,有:\\ U_{k*m}A_{m*n}\simeq E_{k*k}\Sigma_{k*n}V_{n*k}^T \\ \Sigma_{k*n}^{-1}U_{k*m}A_{m*n}\simeq \Sigma_{k*n}^{-1}\Sigma_{k*n}V_{n*k}^T \\ 于是: V_{n*k}^T = \Sigma_{k*n}^{-1}U_{k*m}A_{m*n} \\ 两边同时转置: V_{n*k} = (\Sigma_{k*n}^{-1}U_{k*m}A_{m*n})^T=A_{m*n}^TU_{m*k}\Sigma_{k*n}^{-1} Umk1AmnUmk1UmkΣknVnkTUkmAmnUkmUmkΣknVnkTUkmUkmAmnEkkΣknVnkTΣkn1UkmAmnΣkn1ΣknVnkTVnkT=Σkn1UkmAmnVnk=(Σkn1UkmAmn)T=AmnTUmkΣkn1

(3)对于每一个物品,计算与物品item在低维空间的中的相似度,遇到user打分为0或者j==item时就跳过。将这些相似度进行累加,对相似度与rating的乘积也进行累加,最后返回两者的比值:
R a t i o p r e d = r a t S i m T o t a l s i m T o t a l = ∑ i s i R i ∑ i s i Ratio_pred = \frac{ratSimTotal}{simTotal} \\ = \frac{\sum_i s_iR_i}{\sum_i s_i} Ratiopred=simTotalratSimTotal=isiisiRi
这个比值其实就是预测用户user在物品item上的打分,这个结果就是将用户对于其他物品打分的分值进行加权平均。而这个权值就是相似度。

2.3 基于SVD的推荐函数

  有了上述的打分函数,我们只需要遍历某个user没有打分的物品,使用打分函数 s v d E s t ( ) svdEst() svdEst()进行打分,最后返回得分最高的N个物品,推荐给用户user。

def recommend(dataMat, user, N=3, simMeas=cosSim, estMethod=standEst):
    '''
    推荐函数
    :param dataMat:     数据集
    :param user:        待推荐用户
    :param N:           推荐的个数
    :param simMeas:     相似度函数
    :param estMethod:   打分函数
    :return:            返回N个推荐的物品及其预测的得分
    '''
    # 找到用户user没有打分的物品
    unratedItems = nonzero(dataMat[user, :].A == 0)[1] #find unrated items
    # 如果没有未评价的物品,直接返回
    if len(unratedItems) == 0: return 'you rated everything'
    itemScores = []
    # 对于用户user没有评价的每个物品
    for item in unratedItems:
        # 计算该物品的得分
        estimatedScore = estMethod(dataMat, user, simMeas, item)
        # 把该物品及其得分加入到list中
        itemScores.append((item, estimatedScore))
    # 按照得分进行排序,并返回前N个物品
    return sorted(itemScores, key=lambda jj: jj[1], reverse=True)[:N]

3. 基于SVD的图像压缩

  SVD除了在推荐系统上有着很好的应用,也能应用在图像压缩上。将图像矩阵进行奇异值分解SVD,然后提取能使能量大于90%的前N个奇异值极其对应的左右奇异值矩阵。原来很大的图像,就可以用简单的左奇异矩阵U和右奇异矩阵V以及奇异值 Σ \Sigma Σ来表示,最后通过三者的乘积就可以复原图片。

4. SVD的评价

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

参考资料

奇异值分解(SVD)原理与在降维中的应用
奇异值分解(SVD)原理详解
机器学习实战的P264中代码对应的公式推导
机器学习实战——SVD(奇异值分解)

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

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值