机器学习-推荐系统-协同过滤

协同过滤(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()

机器学习-推荐系统-协同过滤(基于用户、物品的协同过滤、SVD原理及使用)_协同过滤

 

 

 

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")

原图

机器学习-推荐系统-协同过滤(基于用户、物品的协同过滤、SVD原理及使用)_协同过滤_02

 

分别取前60、120、180个奇异值复原的图片如下

机器学习-推荐系统-协同过滤(基于用户、物品的协同过滤、SVD原理及使用)_协同过滤_03