傳統推薦算法(一)利用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

更多精彩內容請移步公衆號:推薦算法工程師

感覺公衆號內容不錯點個關注唄

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章