推荐系统中,协同过滤推荐是当前应用最广泛、最成功的个性化推荐系统,其算法有很多,比如:基于用户、物品的协同过滤推荐算法,以及基于模型的协同过滤推荐算法,其中基于模型的,如将高维评分矩阵降维的SVD算法,以及SVD采取梯度下降法改进的LFM算法,隐义模型等,在此注重讲解下当前备受关注的SVD算法以及其改进后的LFM算法在推荐系统中的应用,同时结合MovieLens—1M(数据源,密码:es9r)这个数据集进行算法应用。这里要提一下,在推荐系统中处理的数据源问题,是建立一个用户与商品的矩阵,称之为偏好矩阵,其矩阵元素就是用户对商品的喜爱程度,偏好的计算过程很复杂,不同的推荐系统有自己的计算方式,比如,对某一物品用户的评分,投票,转发等行为,对不同的行为乘以各自的影响权值再累加,最后就可以转化成一个具体数值,而本博客为了简单叙述,直接将用户对物品的评分[0-5]作为偏好的计算方式。以下是书本上对推荐系统框架的描述,分为了数据源、推荐算法、以及最后的推荐结果。
推荐框架
# coding=gbk
from multiprocessing import Pool, Manager
from math import exp
import pandas as pd
import numpy as np
import pickle
import time
def getResource(csvPath):
'''
获取原始数据
:param csvPath: csv原始数据路径
:return: frame
'''
frame = pd.read_csv(csvPath)
return frame
def getUserNegativeItem(frame, userID):
'''
获取用户负反馈物品:热门但是用户没有进行过评分 与正反馈数量相等
:param frame: ratings数据
:param userID:用户ID
:return: 负反馈物品
'''
userItemlist = list(set(frame[frame['UserID'] == userID]['MovieID']).values) #用户评分过的物品
otherItemList = [item for item in set(frame['MovieID'].values) if item not in userItemlist] #用户没有评分的物品
itemCount = [len(frame[frame['MovieID'] == item]['UserID']) for item in otherItemList] #物品热门程度
series = pd.Series(itemCount, index=otherItemList)
series = series.sort_values(ascending=False)[:len(userItemlist)] #获取正反馈物品数量的负反馈物品
negativeItemList = list(series.index)
return negativeItemList
def getUserPositiveItem(frame, userID):
'''
获取用户正反馈物品:用户评分过的物品
:param frame: ratings数据
:param userID: 用户ID
:return: 正反馈物品
'''
series = frame[frame['UserID'] == userID]['MovieID']#用户userID,MovieID栏下的值
positiveItemList = list(series.values)
return positiveItemList
def initUserItem(frame, userID=1):
'''
初始化用户正负反馈物品,正反馈标签为1,负反馈为0
:param frame: ratings数据
:param userID: 用户ID
:return: 正负反馈物品字典
'''
positiveItem = getUserPositiveItem(frame, userID)
negativeItem = getUserNegativeItem(frame, userID)
itemDict = {}
for item in positiveItem: itemDict[item] = 1
for item in negativeItem: itemDict[item] = 0
return itemDict
def initPara(userID, itemID, classCount):
'''
初始化参数q,p矩阵, 随机
:param userCount:用户ID
:param itemCount:物品ID
:param classCount: 隐类数量
:return: 参数p,q
'''
arrayp = np.random.rand(len(userID), classCount)
arrayq = np.random.rand(classCount, len(itemID))
p = pd.DataFrame(arrayp, columns=range(0,classCount), index=userID)
q = pd.DataFrame(arrayq, columns=itemID, index=range(0,classCount))
return p,q
def work(id, queue):
'''
多进程slave函数
:param id: 用户ID
:param queue: 队列
'''
#print(id)
itemDict = initUserItem(frame, userID=id)
queue.put({id:itemDict})
def initUserItemPool(userID):
'''
初始化目标用户样本
:param userID:目标用户
:return:
'''
pool = Pool()#进程池
userItem = []
queue = Manager().Queue()
for id in userID: pool.apply_async(work, args=(id,queue))
pool.close()
pool.join()
while not queue.empty(): userItem.append(queue.get())
return userItem
def initModel(frame, classCount):
'''
初始化模型:参数p,q,样本数据
:param frame: 源数据
:param classCount: 隐类数量
:return:
'''
userID = list(set(frame['UserID'].values))
itemID = list(set(frame['MovieID'].values))
p, q = initPara(userID, itemID, classCount)
userItem = initUserItemPool(userID)
return p, q, userItem
def sigmod(x):
'''
单位阶跃函数,将兴趣度限定在[0,1]范围内
:param x: 兴趣度
:return: 兴趣度
'''
y = 1.0/(1+exp(-x))
return y
def lfmPredict(p, q, userID, itemID):
'''
利用参数p,q预测目标用户对目标物品的兴趣度
:param p: 用户兴趣和隐类的关系
:param q: 隐类和物品的关系
:param userID: 目标用户
:param itemID: 目标物品
:return: 预测兴趣度
'''
p = np.mat(p.ix[userID].values)
q = np.mat(q[itemID].values).T
r = (p * q).sum()
r = sigmod(r)
return r
def latenFactorModel(frame, classCount, iterCount, alpha, lamda):
'''
隐语义模型计算参数p,q
:param frame: 源数据
:param classCount: 隐类数量
:param iterCount: 迭代次数
:param alpha: 步长
:param lamda: 正则化参数
:return: 参数p,q
'''
p, q, userItem = initModel(frame, classCount)
for step in range(0, iterCount):
for user in userItem:
for userID, samples in user.items():
for itemID, rui in samples.items():
eui = rui - lfmPredict(p, q, userID, itemID)
for f in range(0, classCount):
#print('step %d user %d class %d' % (step, userID, f))p[f][userID]是遍历p的f列userID行
p[f][userID] += alpha * (eui * q[itemID][f] - lamda * p[f][userID])
q[itemID][f] += alpha * (eui * p[f][userID] - lamda * q[itemID][f])
alpha *= 0.9
return p, q
def recommend(frame, userID, p, q, TopN=10):
'''
推荐TopN个物品给目标用户
:param frame: 源数据
:param userID: 目标用户
:param p: 用户兴趣和隐类的关系
:param q: 隐类和物品的关系
:param TopN: 推荐数量
:return: 推荐物品
'''
userItemlist = list(set(frame[frame['UserID'] == userID]['MovieID']))
otherItemList = [item for item in set(frame['MovieID'].values) if item not in userItemlist]
predictList = [lfmPredict(p, q, userID, itemID) for itemID in otherItemList]
series = pd.Series(predictList, index=otherItemList)
series = series.sort_values(ascending=False)[:TopN]
return series
if __name__ == '__main__':
frame = getResource('ratings.csv')
p, q = latenFactorModel(frame, 5, 10, 0.02, 0.01)
l = recommend(frame, 1, p, q)
print(l)
运行结果图: