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
更多精彩內容請移步公衆號:推薦算法工程師
感覺公衆號內容不錯點個關注唄