機器學習-推薦系統-協同過濾
協同過濾(Collaborative Filtering, CF)
基於協同過濾的推薦,它的原理很簡單,就是根據用戶對物品或者信息的偏好,發現物品或者內容本身的相關性,或者發現用戶的相關性,然后再基於這些相關性進行推薦。基於協同過濾的推薦可以分為兩個簡單的子類:基於用戶的推薦(User-based Recommendation)和基於項目的推薦(Item-based Recommendation)、
大量的事實告訴我們,我們的興趣雖然不盡相同,但它們往往遵循某種相同的模式。一方面,人們喜歡的東西往往是類似的。另一方面,人們也傾向於喜歡與他們類似的人所喜歡的東西。
協同過濾通過用戶和產品及用戶的偏好信息產生推薦的策略,最基本的有兩種:一種是找到具有類似品味的人所喜歡的物品;另一種是從一個人喜歡的物品中找出類似的物品。這就是兩個最知名的類別推薦技術:基於用戶的推薦技術和基於物品的推薦技術,它們被稱為協同過濾。
協同過濾可以利用用戶和物品的信息來預測用戶的好惡,並發現新的用戶還不知道的東西,形成促銷策略。這就完成了推薦系統的核心思想。協同過濾一般是在海量的用戶中發掘出一小部分和你品味比較類似的,根據他們喜歡的其他東西組織成一個排序的目錄作為推薦給你。
如何確定這個用戶簇?使用聚類(kmeans)。
from numpy import * from sklearn.cluster import KMeans import matplotlib.pyplot as plt def file2matrix(filePath, lineSplit): dataSet = [] fr = open(filePath, 'r') content = fr.read() for line in content.splitlines(): dataSet.append([line.split('\t')[0], line.split('\t')[1]]) fr.close() return dataSet def drawScatter(plt, dataMat, size, color, mrkr): X = dataMat[:,0] Y = dataMat[:,-1] plt.scatter(X.tolist(), Y.tolist(), c=color, marker=mrkr, s=size) plt.xlabel("x") plt.ylabel("y") plt.title("Kmeans") plt.legend() k = 4 dataSet = file2matrix("/Users/FengZhen/Desktop/accumulate/機器學習/推薦系統/kmeans聚類測試集.txt", "\t") dataMat = mat(dataSet) print(dataMat) # 執行kmeans算法 kmeans = KMeans(init='k-means++', n_clusters=k) kmeans.fit(dataMat) print(kmeans.cluster_centers_) #繪制計算結果 drawScatter(plt, dataMat, size=20, color='b', mrkr='.') drawScatter(plt, kmeans.cluster_centers_, size=20, color='red', mrkr='D') plt.show()

User CF原理(基於用戶的協同過濾)
基於用戶的協同過濾算法在1992年被提出,首先應用於郵件過濾系統,到1994年用於新聞過濾,此算法是推薦系統最著名的算法。
步驟:
1.基於用戶對物品的偏好划分用戶類型(聚類算法)
2.找到最近鄰的用戶(kNN近鄰算法)
3.然后將同類用戶和相鄰用戶所喜歡的推薦給當前用戶。
計算上,就是將一個用戶對所有物品的偏好作為一個向量來計算用戶之間的相似度,找到K鄰居后,根據鄰居的相似度權重及他們對物品的偏好,預測當前用戶沒有偏好的未涉及物品,計算得到一個排序的物品列表作為推薦。
計算用戶之間的相似度可以使用機器學習的夾角余弦距離公式。
Item CF原理(基於物品的協同過濾)
基於物品的協同過濾 (Item-based Collaborative Filtering)算法是目前業界應用最多的算法。
基於物品的CF原理和基於用戶的CF原理類似,只是在計算時采用物品之間的相似度,而不是從用戶的角度,即
1.基於用戶對物品的偏好划分物品類型(聚類算法)
2.找到物品的近鄰物品(kNN算法)
3.將同類物品或相似物品推薦給當前用戶。
從計算角度看,就是將所有用戶對某個物品的偏好作為一個向量來計算物品之間的相似度,得到物品的相似物品后,根據用戶歷史的偏好預測當前用戶還沒有表示偏好的物品,計算到一個排序的物品列表作為推薦。
KNN算法如下
from numpy import * import operator # 夾角余弦距離公式 def consdist(vector1, vector2): print("vecotr1:",vector1) print("vector2:",vector2) result = dot(vector1, vector2) / (linalg.norm(vector1) * linalg.norm(vector2)) print("result:", result) return result # KNN分類器 # testData:測試集 trainSet:訓練集 listClasses:類別標簽 k:k個鄰居 def classify(testData, trainSet, listClasses, k): dataSetSize = trainSet.shape[0] # 返回樣本的行數 distances = array(zeros(dataSetSize)) print("dataSetSize:", dataSetSize) for indx in range(dataSetSize): # 計算測試集和訓練集之間的距離:余弦夾角 distances[indx] = consdist(testData, trainSet[indx]) # 根據生成的余弦夾角按從大到小排序,結果為索引號 sortedDistIndics = argsort(-distances) classCount = {} for i in range(k): # 獲得角度最小的前K項作為參考項 # 按排序順序返回樣本集對應的類別標簽 voteIlabel = listClasses[sortedDistIndics[i]] # 為字典classCount賦值,相同key,其中value加1 classCount[voteIlabel] = classCount.get(voteIlabel, 0) + 1 # 獲得voteIlabel的value值,沒有則默認為0 # 對分類字典classCount按value重新排序 # sorted(data.items(), key = operator.itemgetter(1), reverse = True) # 該句是按字典排序的固定用法 # classCount.items() # 字典迭代器函數 # key:排序參數 operator.itemgetter(1):多級排序 sortedClassCount = sorted(classCount.items(), key=operator.itemgetter(1), reverse=True) return sortedClassCount[0][0] # 返回序最高的一項 dataArr = np.array([[0.238, 0, 0.1905, 0.1905, 0.1905, 0.1905], [0, 0.177, 0, 0.294, 0.235, 0.294], [0.2, 0.16, 0.12, 0.12, 0.2, 0.2]]) dataMat = mat(dataArr) print("dataMat:", dataMat.shape) print("dataMat type:", type(dataMat)) testSet = [0.2174, 0.2174, 0.1304, 0, 0.2174, 0.2174] testMat = mat(testSet) classLabel = [0, 1, 2] k = 3 print("result:",classify(testSet, dataArr, classLabel, k))
SVD原理與計算
隱語義模型,數學上稱為SVD,奇異值分解。該算法最早在文本挖掘領域被提出,用於找到文章的隱含主題,也被稱為主題模型。
隱語義模型的核心思想是通過隱含特征(Latent Factor)計算用戶和物品的相似性。
SVD是將矩陣A分解成以下形式
A=U∑VT
其中U和V均為單位正交陣,UUT=E, VVT=E, U稱為左奇異矩陣,V稱為右奇異矩陣,∑僅在對角線上有值,我們稱之為奇異值,其他值均為0.
維度:U:m*m V:n*n ∑:m*n
關於相似性的計算存在一些問題
1.物品的人為分類對推薦算法造成影響。分類是人為指定的,不同的分類標准對不同的用戶帶來預測精度的問題
2.類中物品的相似度是一個消費行為的問題,需要針對不同的用戶確定不同的權重,這樣做的可能性不大
3.即使能夠構建權重和分類,也不能完全確定某個用戶對某類產品感興趣的程度。
因此,我們需要一種針對每類用戶的不同消費行為來計算對不同物品的相似度的算法。
假設有一個新用戶E,對物品的評分為[5,5,0,0,0,5].現在在現有矩陣中找出與其最相似的用戶,並把這個用戶感興趣的物品推薦給新用戶。
使用夾角余弦求用戶之間的相似度,即選中與新用戶之間夾角最小(余弦值)最大的那個。
from numpy import * import numpy as np # 夾角余弦距離公式 def cosSim(inA, inB): denom = linalg.norm(inA) * linalg.norm(inB) return float(inA * inB.T) / (denom + eps) A = mat([[5, 5, 3, 0, 5, 5], [5, 0, 4, 0, 4, 4], [0, 3, 0, 5, 4, 5], [5, 4, 3, 3, 5, 5]]) # 其中s是對矩陣a的奇異值分解。s除了對角元素不為0,其他元素都為0,並且對角元素從大到小排列。s中有n個奇異值,一般排在后面的比較接近0,所以僅保留比較大的r個奇異值。 U, S, VT = linalg.svd(A) print(S) # 避免除0 eps = 1.0e-6 # 新數據 new = mat([[5,5,0,0,0,5]]) U,S,VT = linalg.svd(A.T) V = VT.T # 對角矩陣(diagonal matrix)是一個主對角線之外的元素皆為0的矩陣,常寫為diag Sigma = diag(S) r = 2 # 取前兩個奇異值 # 得到金絲猴的U\S\V值 Ur = U[:,:r] Sr = Sigma[:r,:r] Vr = V[:,:r] newresult = new*Ur*linalg.inv(Sr) # 計算User E的坐標值 print(newresult) maxv = 0 # 最大的余弦值 maxi = 0 # 最大值的下標 indx = 0 for vi in Vr: temp = cosSim(newresult, vi) if temp > maxv: maxv = temp maxi = indx indx += 1 print(maxv,maxi)
[[-0.37752201 -0.08020351]]
0.9867908785596432 0
計算后得知,與之相似的用戶坐標為0,即[5,5,3,0,5,5].此時可將該用戶的第三、五個物品推薦給新用戶
SVD取前部分奇異值復原圖片
import matplotlib
import matplotlib.pyplot as plt
import matplotlib.image as mpimg
import numpy as np
# 讀取圖片
# img_eg = mpimg.imread("/Users/FengZhen/Desktop/image/bing/road.jpeg")
img_eg = mpimg.imread("/Users/FengZhen/Desktop/image/timg.jpeg")
print(img_eg.shape)
# 奇異值分解
# 我們先將圖片變成500×2250,再做奇異值分解。從svd函數中得到的奇異值sigma它是從大到小排列的。
img_temp = img_eg.reshape(500, 750 * 3)
U,Sigma,VT = np.linalg.svd(img_temp)
# 取前部分奇異值重構圖片
# 取前60個奇異值
sval_nums = 60
img_restruct1 = (U[:, 0:sval_nums]).dot(np.diag(Sigma[0:sval_nums])).dot(VT[0:sval_nums, :])
img_restruct1 = img_restruct1.reshape(500, 750, 3)
# 取前120個奇異值
sval_nums = 120
img_restruct2 = (U[:, 0:sval_nums]).dot(np.diag(Sigma[0:sval_nums])).dot(VT[0:sval_nums, :])
img_restruct2 = img_restruct2.reshape(500, 750, 3)
# 取前180個奇異值
sval_nums = 180
img_restruct3 = (U[:, 0:sval_nums]).dot(np.diag(Sigma[0:sval_nums])).dot(VT[0:sval_nums, :])
img_restruct3 = img_restruct3.reshape(500, 750, 3)
fig, ax = plt.subplots(1, 4, figsize=(24, 32))
ax[0].imshow(img_eg)
ax[0].set(title="src")
ax[1].imshow(img_restruct1.astype(np.uint8))
ax[1].set(title="nums of sigma = 60")
ax[2].imshow(img_restruct2.astype(np.uint8))
ax[2].set(title="nums of sigma = 120")
ax[3].imshow(img_restruct3.astype(np.uint8))
ax[3].set(title="nums of sigma = 180")
原圖

分別取前60、120、180個奇異值復原的圖片如下

