python 实现协同过滤算法并应用奇异值分解(SVD)优化

★ 协同过滤算法:

     协同过滤算法可以分为基于用户的协同过滤算法和基于物品的协同过滤算法。

     (1.)  基于物品的协同过滤算法:

     它是计算物品之间的相似度,并根据物品之间的相似度给目标用户未评分项进行预测。即列与列之间的比较。

     ✿ 目标用户 u 对未评分项 i 的预测公式为:

                                 p(u,i)=\sum_{j\in I(u)}w_{i,j}\cdot \gamma _{u,j}                        ①

     其中:I(u) 表示用户 u 所有评分过的物品的集合,​w_{i,j} 表示 物品i (i列)和物品j (j列)之间的相似度,\gamma _{u,j} 表示目标用户 u 对物品 j 的评分。

     (2.) 基于用户的协同过滤算法:

     它是计算用户之间的相似度,并根据用户之间的相似度给目标用户未评分项进行预测。即行与行之间的比较。

    ✿ 目标用户 u 对未评分项 i 的预测公式为:

                                 p(u,i)=\sum_{v\in N(i)}w_{u,v}\cdot \gamma _{v,i}                       ②

      其中:N(i)表示对商品i打过分的用户的集合,w_{u,v}表示用户u (u行)与用户v (v行)的相似度,\gamma _{v,i}表示用户v对商品i的打分。

     那到底选那一种方法计算相似度来进行预测推荐呢?这取决于用户或物品的数目,如果用户的数目很多,我们更倾向与使用基于物品相似度的计算方法。

★ 相似度的三种计算方式:

    (1.) 欧式距离计算相似度:

     X、Y表示数据集矩阵中的任意两列或两行:

                  similarity = \frac{1}{||X-Y||_{2}}                      ③

    相似度取值范围是:(0,1]。当距离为0时,相似度为1;当距离为非常大时,相似度趋近于0。

    (2.) 皮尔逊相关系数计算相似度:

     Corr(X、Y)是X与Y的相关系数。

                 similarity=0.5+0.5*Corr(X,Y)               ④

    相似度范围是:[0,1]。当X、Y不相关时(Corr(X,Y)=0),相似度为0.5;当X、Y强正相关时(Corr(X,Y)=1),相似度为1;当X、Y强负相关时(Corr(X,Y)=-1),相似度为0。

     (3.) 余弦相似度:

                similarity=0.5+0.5*\frac{\vec{X}\cdot \vec{Y}}{||X||\cdot ||Y||}                  ⑤

     相似度范围是:[0,1]。当向量X、Y夹角为90度的时候,相似度为0.5;当夹角为0度时,相似度为1;当夹角为180度时,相似度为0。

★ 基于物品相似度的推荐算法:

    ✿ 代码流程:

     (1.) 给定用户user,遍历所有物品,找到该用户未评分的物品组成一个集合。遍历这个集合no_rated,如物品i未评分。
     (2.) 遍历所有物品,找到该用户评过分的物品再组成一个集合rated,遍历这个集合,如物品j评过分。

     (3.) 找出既对物品i评过分的,又对物品j评过分的所有用户,计算这些用户对物品i的评分并组成列向量,再计算这些用户对物品j评分并组成向量,计算这两个向量的相似度(通过上述方法的任何一种)。

     (4.) 遍历集合rated,按(3.)的方法把所有相似度都计算出来。然后把所有相似度的和保存到simTotal变量中,预测评分根据公式为:rated集合中的所有物品×该物品的相似度的累加和,保存到变量ratSimTotal中。对预测评分归一化,ratSimTotal / simTotal

     (5.) 继续遍历no_rate集合的下一个为评分元素,重复(2)、(3)、(4),遍历完后,对预测物品按预测评分倒序排列。选前3个物品推荐给用户。

from numpy import *

def euclidSim(inA, inB):  # 欧式距离计算相似度
    return 1.0/(1.0 + linalg.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 = linalg.norm(inA)*linalg.norm(inB)
    return 0.5+0.5*(num/denom)  # 公式

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

def standEst(dataMat,user,simMeas,item):  # 计算每个未评分的物品的预测评分,user是给定的要推荐给她物品的用户,item是未评分的物品
    n = shape(dataMat)[1] # 物品总数
    simTotal = 0.0   # 总的相似度
    ratSimTotal = 0.0  # 该物品的预测评分
    for j in range(n):  # 遍历所有元素
        userRating = dataMat[user,j]
        if userRating == 0:  # 找到该用户评过分的物品j
            continue
        overLap = nonzero(logical_and(dataMat[:,item].A > 0,dataMat[:,j].A > 0))[0] # 找到既对评过分的物品j和未评分的物品item的用户的行坐标
        if len(overLap) == 0: # 如果没有符合这种要求的用户
            similarity = 0    # 相似度置为0
        else: # 有符合这种要求的用户
            similarity = simMeas(dataMat[overLap,item],dataMat[overLap,j]) # 根据公式计算相似度
        simTotal += similarity
        ratSimTotal += similarity * userRating
    if simTotal == 0: # 如果未评分的这个物品,大家都没评过分
        return 0  # 则不推荐
    else:
        return ratSimTotal / simTotal # 对预测评分归一化

def recommend(dataMat, user, N=3, simMeas=cosSim,estMethod=standEst): # user是给定的用户,即我们要向他推荐物品的用户,N是为该用户推荐的物品的个数
    unratedItems = nonzero(dataMat[user,:].A==0)[1]   # 找出该用户未评分的物品,即得到列下标
    if len(unratedItems) == 0: # 如果为0,说明该用户对这些物品都评过分了,不要推荐了
        return 'you rated everything'
    itemScores = []
    for item in unratedItems: # 遍历这些待推荐的物品
        estimateScore = estMethod(dataMat,user,simMeas,item)
        itemScores.append((item,estimateScore))
    return sorted(itemScores,key=lambda jj:jj[1],reverse=True)[:N] # 根据预测评分倒序排列,选前N个待推荐的物品

if __name__ == '__main__':
    dataset = loadDataSet()
    recommend_goods = recommend(dataset,2) # 要推荐的物品
    print('要推荐的物品有:\n',recommend_goods)

    ✿ 运行效果:

     我们对用户2,首先推荐物品2,对物品2的预测评分是2.5;再推荐物品1,对物品1的预测评分是2.024。

★ 利用SVD提高推荐效果:

     我们得到的用户物品数据集实际上会是稀疏的多的矩阵(0特别多),我们有必要对其进行降维,SVD是一种不错的方法,我们知道,任何一个矩阵都可以进行奇异值分解:

                                        M_{m\times n}=U_{m\times m}*\Sigma _{m\times n}*(V_{n\times n})^{T}

     其中:U是m×m的酉矩阵,∑是m×n的对角矩阵,V是n×n的酉矩阵。∑对角线上是M的奇异值(也是M*M^{T}的特征值的开方),奇异值越大,对应的投影的信息量就越大,一般来说,我们只需要选出包含信息量超过总信息量的90﹪的几个奇异值就可以了,即前r个奇异值就满足这个要求的话,∑就可以只保留前r列、前r行,即m×n维变成r×r维,而U选取前r列,V^{T}选取前r行,因此M就可以分解为:

                                       M_{m\times n}\approx U_{m\times r}*\Sigma _{r\times r}*(V^{T})_{r\times n}

   这样M就可以用占空间更小的三个矩阵U、∑、V来存贮。SVD可用来对数据集进行压缩。

   比如压缩列:

                   M_{m\times n}*V_{n\times r}\approx U_{m\times r}*\Sigma _{r\times r}*(V^{T})_{r\times n}*V_{n\times r}=U_{m\times r}*\Sigma _{r\times r}

   压缩行:

                 (U^{T})_{r\times m}*M_{m\times n}\approx (U^{T})_{r\times m}*U_{m\times r}*\Sigma _{r\times r}*(V^{T})_{r\times n}=\Sigma _{r\times r}*(V^{T})_{r\times n}

★ 代码

  ✿ 代码疑问:请路过的大佬解答一下!

    (1.) 对降维公式的疑问:

   书中对原数据集采用SVD降维,降维后的数据采用公式:

                     M^{new}=M^{T}*U_{m\times r}*(\Sigma _{r\times r})^{-1}

    代码是:

                 xformedItems = dataMat.T * U[:,:dim_num+1] * Sig4.I

    我们看出,降维后数据集变成n×r维。而且根据这个公式求出来的新的数据集Mnew,其实就是V。

   (2.)  疑问2:

     书中,利用上述公式求出的新的数据集Mnew,为何直接用于了相似度的求取。这两个疑问的关键就是:这么降维后的数据集应该怎么理解?这正好也是SVD降维的缺点,即转换后的数据难以理解。

def svdEst(dataMat, user, simMeas, item):  # SVD降维,并计算相似度,求出推荐产品
    n = shape(dataset)[1]
    simTotal = 0.0
    ratSimTotal = 0.0
    U,Sigma,VT = linalg.svd(dataMat) # 奇异值分解
    dim_num = calcDim(Sigma)
    # Sig4 = mat(eye(dim_num+1)*Sigma[:dim_num+1])  # 把前4个奇异值构造成4×4维对角阵
    Sig4 = mat(diag(Sigma[:dim_num+1])) # 跟上一个语句一个意思
    xformedItems = dataMat.T * U[:,:dim_num+1] * Sig4.I  # 不理解
    # print('新数据=',xformedItems)
    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 {0} and {1} similarity is : {2}'.format(item,j,similarity))
        simTotal += similarity
        ratSimTotal += similarity * userRating
    if simTotal == 0:
        return 0
    else:
        return ratSimTotal/simTotal

def calcDim(sigma):  # 选择合适的sigma维数
    sigma2 = sigma**2
    for i in range(len(sigma)):
        if sum(sigma2[:i])/sum(sigma2) > 0.9:
            return i

 

★ 总的SVD推荐算法代码

from numpy import *

def euclidSim(inA, inB):  # 欧式距离计算相似度
    return 1.0/(1.0 + linalg.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 = linalg.norm(inA)*linalg.norm(inB)
    return 0.5+0.5*(num/denom)  # 公式

def loadDataSet():
    # dataset = mat([[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]])
    # dataset = mat([[2,0,0,4,4,0,0,0,0,0,0],
    #                [0,0,0,0,0,0,0,0,0,0,5],
    #                [0,0,0,0,0,0,0,1,0,4,0],
    #                [3,3,4,0,3,0,0,2,2,0,0],
    #                [5,5,5,0,0,0,0,0,0,0,0],
    #                [0,0,0,0,0,0,5,0,0,5,0],
    #                [4,0,4,0,0,0,0,0,0,0,5],
    #                [0,0,0,0,0,4,0,0,0,0,4],
    #                [0,0,0,0,0,0,5,0,0,5,0],
    #                [0,0,0,3,0,0,0,0,4,5,0],
    #                [1,1,2,1,1,2,1,0,4,5,0]
    #                ])
    dataset = 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]])

    return dataset

def standEst(dataMat,user,simMeas,item):  # 计算每个未评分的物品的预测评分,user是给定的要推荐给她物品的用户,item是未评分的物品
    n = shape(dataMat)[1] # 物品总数
    simTotal = 0.0   # 总的相似度
    ratSimTotal = 0.0  # 该物品的预测评分
    for j in range(n):  # 遍历所有元素
        userRating = dataMat[user,j]
        if userRating == 0:  # 找到该用户评过分的物品j
            continue
        overLap = nonzero(logical_and(dataMat[:,item].A > 0,dataMat[:,j].A > 0))[0] # 找到既对评过分的物品j和未评分的物品item的用户的行坐标
        if len(overLap) == 0: # 如果没有符合这种要求的用户
            similarity = 0    # 相似度置为0
        else: # 有符合这种要求的用户
            similarity = simMeas(dataMat[overLap,item],dataMat[overLap,j]) # 根据公式计算相似度
        simTotal += similarity
        ratSimTotal += similarity * userRating
    if simTotal == 0: # 如果未评分的这个物品,大家都没评过分
        return 0  # 则不推荐
    else:
        return ratSimTotal / simTotal # 对预测评分归一化

def recommend(dataMat, user, N=3, simMeas=cosSim,estMethod=standEst): # user是给定的用户,即我们要向他推荐物品的用户,N是为该用户推荐的物品的个数
    unratedItems = nonzero(dataMat[user,:].A==0)[1]   # 找出该用户未评分的物品,即得到列下标
    if len(unratedItems) == 0: # 如果为0,说明该用户对这些物品都评过分了,不要推荐了
        return 'you rated everything'
    itemScores = []
    for item in unratedItems: # 遍历这些待推荐的物品
        estimateScore = estMethod(dataMat,user,simMeas,item)
        itemScores.append((item,estimateScore))
    return sorted(itemScores,key=lambda jj:jj[1],reverse=True)[:N] # 根据预测评分倒序排列,选前N个待推荐的物品

def svdEst(dataMat, user, simMeas, item):
    n = shape(dataset)[1]
    simTotal = 0.0
    ratSimTotal = 0.0
    U,Sigma,VT = linalg.svd(dataMat)
    dim_num = calcDim(Sigma)
    # Sig4 = mat(eye(dim_num+1)*Sigma[:dim_num+1])  # 把前4个奇异值构造成4×4维对角阵
    Sig4 = mat(diag(Sigma[:dim_num+1]))
    xformedItems = dataMat.T * U[:,:dim_num+1] * Sig4.I
    # print('新数据=',xformedItems)
    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 {0} and {1} similarity is : {2}'.format(item,j,similarity))
        simTotal += similarity
        ratSimTotal += similarity * userRating
    if simTotal == 0:
        return 0
    else:
        return ratSimTotal/simTotal

def calcDim(sigma):  # 选择合适的sigma维数
    sigma2 = sigma**2
    for i in range(len(sigma)):
        if sum(sigma2[:i])/sum(sigma2) > 0.9:
            return i

if __name__ == '__main__':
    dataset = loadDataSet()
    # recommend_goods = recommend(dataset,2) # 要推荐的物品
    # print('要推荐的物品有:\n',recommend_goods)
    u,s,vt = linalg.svd(dataset)
    oo = recommend(dataset,1,estMethod=svdEst)
    print(oo)

 ★ 代码效果:

★ 不错的SVD链接:

    1. https://blog.csdn.net/qq_32742009/article/details/82286434

你可能感兴趣的:(大数据)