推薦未嘗過的菜肴-基于SVD的評分估計
實際上數據集要比我們上一篇展示的myMat要稀疏的多。
from numpy import linalg as la from numpy import *
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]]
一、計算一下到底有多少個奇異值能達到總能量的90%(下一篇我們將用一個函數實現該功能)
U, Sigma, VT = la.svd(mat(loadExData2()))
Sigma
array([15.77075346, 11.40670395, 11.03044558, 4.84639758, 3.09292055,2.58097379, 1.00413543, 0.72817072, 0.43800353, 0.22082113,0.07367823])
總能量:
Sig2 = Sigma ** 2
sum(Sig2)
541.9999999999995
總能量的90%:
sum(Sig2) * 0.9
487.7999999999996
計算前兩個元素所包含的能量:
sum(Sig2[:2])
378.8295595113579
該值低于總能量的90%,計算前三個元素所包含的能量:
sum(Sig2[:3])
500.5002891275793
該值高于總能量的90%,我們將一個11維的矩陣轉換成一個三維的矩陣,下面對轉換后的三維空間構造出一個相似度計算函數
二、相似度計算(歐式距離、皮爾遜相關系數、余弦相似度)
# 相似度計算 # 計算歐式距離 def ecludSim(inA, inB):return 1.0 / (1.0 + la.norm(inA - inB))# pearsim()函數會檢查是否存在3個或更多的點 # corrcoef直接計算皮爾遜相關系數,范圍[-1, 1],歸一化后[0, 1] def pearsSim(inA, inB):# 如果不存在,該函數返回1.0,此時兩個向量完全相關if len(inA) < 3:return 1.0return 0.5 + 0.5 * corrcoef(inA, inB, rowvar=0)[0][1]# 計算余弦相似度,如果夾角為90度,相似度為0;如果兩個向量的方向相同,相似度為1.0 def cosSim(inA, inB): num = float(inA.T * inB)denom = la.norm(inA) * la.norm(inB)return 0.5 + 0.5 * (num / denom)
三、基于SVD的評分估計
# 基于SVD的評分估計 # 在recommend()中,這個函數用于替換對standEst()的調用,該函數對給定用戶、給定物品構建了一個評分估計值 def svdEst(dataMat, user, simMeas, item):"""svdEst()Args:dataMat 訓練數據集user 用戶編號simMeas 相似度計算方法item 未評分的物品編號Returns:ratSimTotal / simTotal 評分(0~5之間的值)"""# 物品數目n = shape(dataMat)[1]# 對數據集進行SVD分解simTotal = 0.0ratSimTotal = 0.0# 奇異值分解# 在SVD分解之后,我們只利用包含了90%能量值的奇異值,這些奇異值會以Numpy數組的形式得以保存U, Sigma, VT = la.svd(dataMat)# 如果要進行矩陣運算,就必須要用這些奇異值構建出一個對角矩陣Sig4 = mat(eye(4) * Sigma[: 4])# 利用U矩陣將物品轉換到低維空間中,構建轉換后的物品xformedItems = dataMat.T * U[:, :4] * Sig4.I# 對于給定的用戶,for循環在用戶對應行的元素上進行遍歷# 這和standEst()函數中的for循環的目的一樣,只不過這里的相似度計算是在低維空間下進行的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 * userRatingif simTotal == 0:return 0else:# 計算估計評分return ratSimTotal/simTotal
四、排序獲取最后的推薦結果
# recommend()函數,就是推薦引擎,它默認調用 svdEst()函數,產生了最高的N個推薦結果 # 如果不指定N的大小,則默認值為3,該函數另外的參數該包括相似度計算方法和估計方法 def recommend(dataMat, user, N=3, simMeas=cosSim, estMethod=svdEst):"""recommend()Args:dataMat 訓練數據集user 用戶編號simMeas 相似度計算方法estMethod 使用的推薦算法Returns:返回最終N個推薦結果"""# 尋找未評級的物品# 對給定用戶建立一個未評分的物品列表unratedItems = nonzero(dataMat[user, :].A == 0)[1]# 如果不存在未評分物品,那么就退出函數if len(unratedItems) == 0:return 'you rated everything'# 物品的編號和評分值itemScores = []for item in unratedItems:# 獲取 item 該物品的評分estimatedScore = estMethod(dataMat, user, simMeas, item)itemScores.append((item, estimatedScore))# 按照評分得分,進行逆排序,獲取前N個未評級物品進行推薦return sorted(itemScores, key=lambda jj: jj[1], reverse=True)[: N]
myMat = mat(loadExData2())
recommend(myMat, 1, simMeas=pearsSim)
[(4, 3.346952186702173), (9, 3.33537965732747), (6, 3.3071930278130366)]
這表明用戶1(從0開始計數,對應是矩陣第2行),對物品4的預測評分為3.34,對物品9預測評分為3.33,對物品6預測評分為3.30
試試另一種相似度
recommend(myMat, 1, simMeas=cosSim)
[(4, 3.344714938469228), (7, 3.3294020724526967), (9, 3.3281008763900686)]
?