传统推荐算法(一)利用SVD进行推荐(4)tensorflo实战SVD推荐

1.SVD用于推荐

本文的SVD推荐不是FunkSVD,是利用我们在上篇文章中分析过的SVD分解进行推荐。怎么说呢?这才是真正的SVD推荐!

应用的思路是在基于物品的协同过滤的基础上,利用SVD将物品稀疏表示转化为低维的特征表示。

2.实战

这部分的代码改自机器学习实战的第14章,保证可读性不保证运行效率。

2.1 基于物品的协同过滤

计算过程:

对每个用户u未评分的物品item:
对每个u已评过分的物品i:
  计算item和i的相似度
  相似度乘以i的评分得到item的评分
  把评分加起来得到item的总评分
最后给用户推荐评分较高的k个物品

首先加载数据就不说了

from numpy import corrcoef, mat, shape, nonzero, logical_and
import numpy.linalg as la

def loadExtData():
    # mat A
    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]]

然后定义几个相似度计算的函数,用来计算物品的相似度:

# similarity calculation
def excludSim(inA,inB):
    '''
    use l2 norm to calculate similarity
    normalize -> (0,1]
    '''
    dis = 1.0/(1.0+la.norm(inA-inB))
    return dis

def pearsSim(inA,inB):
    '''
    user pearson coefficient to calculate similarity
    normalize -> (0,1]
    '''
    if(len(inA) < 3):return 1.0
    dis = 0.5+0.5*corrcoef(inA,inB,rowvar=0)[0][1]
    return dis

def cosSim(inA,inB):
    '''
    cosine similarity
    normalize -> (0,1]
    '''
    tmp = float(inA.T*inB)
    dis = 0.5+0.5*tmp/(la.norm(inA)*la.norm(inB))
    return dis

根据数据集上的信息,计算用户对物品的评分值:

def standEst(dataMat, user, simMean, item):
    '''
    calculate user's score for item
    simMean:similarity calculation method
    '''
    if(dataMat[user, item] != 0): return dataMat[user, item]
    n = shape(dataMat)[1]  # number of items
    simTotal = 0.0
    ratSimTotal = 0.0
    for i in range(n):
        userRating = dataMat[user, i]
        if(userRating == 0 or i == item): continue
        # search for users that ever rate two items
        overLap = nonzero(logical_and(dataMat[:,i].A>0, dataMat[:,item].A>0))[0]
        if(len(overLap) == 0): similarity = 0
        else: similarity = simMean(dataMat[overLap, i], dataMat[overLap, item])
        simTotal += similarity
        ratSimTotal += similarity * userRating
    if(simTotal == 0):return 0
    else: return ratSimTotal / simTotal # return user's score for item

这里总的评分ratSimTotal 除以总的 simTotal其实是对评分进行归一化,只不过不是归一化到(0,1)之间,而是归一化到评分上下限(0,5)之间。

这个也可以看成对物品的所有评分进行加权求和,每个评分的权重等于相似度除以总的相似度(反映这个评分的可靠性),权重之和为1。这个过程类似于我们求均值的过程,只不过求均值时每个值的权重都是1。

然后对某个用户返回评分最高的N个结果,默认是3个:

# 参数有评分矩阵,用户id,返回几个结果,相似度计算方法,计算用户对物品评分的方法
def recommend(dataMat, user, N=3, simMean=cosSim, estTestMean=standEst):
    '''
    recommend n items to user based on the specific dataMat
    simMean:similarity calculation method
    estTestMean:cal user score of item
    '''
    unRatedItem = nonzero(dataMat[user,:].A == 0)[1] # .A change matrix to array
    if(len(unRatedItem) == 0):print('There is nothing to recommend')
    retScores = [] # scores of unRatedItems
    for item in unRatedItem:
        itemScore=estTestMean(dataMat, user, simMean, item) # predicton of user for item
        retScores.append((item, itemScore))
    return sorted(retScores, key=lambda j:j[1], reverse=True)[:N] # return the top N high rated items

然后我们试验一下,比如对第2个用户进行推荐:

myData = mat(loadExtData())
ans = recommend(myData, 2)
print(ans)

2.2 使用SVD进行基于物品的协同过滤推荐

加上SVD到底有啥不一样呢?其实就是利用SVD分解对数据进行低秩近似,然后计算相似度时,物品也不再是高维的向量,而是转化为低维的特征进行计算。让我们一步一步来,先看看如何低维近似。首先对数据进行SVD分解:

dataMat = loadExtData()
U,S,V = la.svd(dataMat)
print(S,"\n")

然后我们取前3个特征值,进行还原:

S_3 = mat([[S[0],0,0],[0,S[1],0],[0,0,S[2]]])
restoreData = U[:,:3]*S_3*V[:3,:]
print(restoreData)

结果如下:

发现还是差不多的。然后我们重新加载一个更稀疏的数据,利用SVD进行推荐。和之前的区别就是在计算相似度的时候, 使用了物品的低维特征:

# recommender items based on svd
def loadExtData2():
    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],
            [1, 0, 0, 0, 0, 0, 0, 1, 2, 0, 0]]

def svdEst(dataMat, user, simMeas, item, k):
    if(dataMat[user, item] != 0): return dataMat[user, item]
    n = shape(dataMat)[1]   # n,11
    simTotal = 0.0;ratSimTotal = 0.0
    U, S, V = la.svd(dataMat)
    S3 = mat(eye(k) * S[:k]) # create a diagonal matrix to save 3 eigenvalues in S
    xformedItems = dataMat.T * U[:, :k] * S3.I # reduce dimensions of 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)
        simTotal += similarity
        ratSimTotal += similarity * userRating
    if(simTotal == 0):  return 0
    else:   return ratSimTotal / simTotal

def svdRecommend(dataMat, user, N=3, simMean=cosSim, estTestMean=svdEst, k=3):
    '''
    recommend n items to user based on the specific dataMat
    simMean:similarity calculation method
    estTestMean:cal user score of item
    k:k controls the number of eigenvalues
    '''
    unRatedItem = nonzero(dataMat[user,:].A == 0)[1] # .A change matrix to array
    if(len(unRatedItem) == 0):print('There is nothing to recommend')
    retScores = [] # scores of unRatedItems
    for item in unRatedItem:
        itemScore=estTestMean(dataMat, user, simMean, item, k=k) # predicton of user for item
        retScores.append((item, itemScore))
    return sorted(retScores, key=lambda j:j[1], reverse=True)[:N] # return the top N high rated items

然后我们就可以着手推荐了。 这里要先确定我们到底要降到几维。

奇异值的减少特别的快,在很多情况下,前10%甚至1%的奇异值的和就占了全部的奇异值之和的99%以上的比例。也就是说,我们也可以用最大的k个的奇异值和对应的左右奇异向量来近似描述矩阵。

计算前几个奇异值平方和的百分比来确定将数据降到多少维合适,这里将90%确定为阈值。也可以自己设一个,比如95%。

myData = mat(loadExtData2())
U, S, V = la.svd(myData)
S *= S
threshold = sum(S) * 0.9
k = 0

for i in range(S.shape[0]+1):
    if(sum(S[:i]) >= threshold):
        k = i
        break

然后我们对第3个用户进行推荐:

svdItems = svdRecommend(myData, user=3, estTestMean=svdEst, k=k)
print(svdItems)

3.优缺点分析

首先看下优点吧,我们需要多次计算物品间相似度,svd将物品的稀疏表示映射到低维空间后,计算量大大减小。

缺点太明显了,正如[1]中所说:

在大规模的数据上,SVD分解会降低程序的速度。SVD分解可以在程序调入时运行一次。在大型系统中,SVD每天运行一次或者其频率更低,并且还要离线运行。

这种降维的过程,有时候可以消去噪声,有时候即使能去噪也会损失很多信息得不偿失,所以k值需要谨慎选择。

另一个问题是,SVD分解方式比较单一,分解得到的特征表示是否一定是用户/物品的一种比较好的表示呢?从这个角度看,获取的特征的方式不够灵活,有时效果就会。。。如果可以自己训练出分解的特征那就完美了,FunkSVD就可以自己训练出特征,传统推荐算法(二)我们会介绍一下FunkSVD及其变种。

参考文献

[1] Peter Harrington. 机器学习实战[M]. 2013.

广告

所有传统推荐算法的代码在:https://github.com/wyl6/Recommender-Systems-Samples/tree/master/RecSys Traditional/MF/SVD

更多精彩内容请移步公众号:推荐算法工程师

感觉公众号内容不错点个关注呗

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值