SVD算法以及改进后的隐义模型(LFM)在推荐系统中的应用
1、写在前面
推荐系统中,协同过滤推荐是当前应用最广泛、最成功的个性化推荐系统,其算法有很多,比如:基于用户、物品的协同过滤推荐算法,以及基于模型的协同过滤推荐算法,其中基于模型的,如将高维评分矩阵降维的SVD算法,以及SVD采取梯度下降法改进的LFM算法,隐义模型等,在此注重讲解下当前备受关注的SVD算法以及其改进后的LFM算法在推荐系统中的应用,同时结合MovieLens—1M(数据源,密码:es9r)这个数据集进行算法应用。这里要提一下,在推荐系统中处理的数据源问题,是建立一个用户与商品的矩阵,称之为偏好矩阵,其矩阵元素就是用户对商品的喜爱程度,偏好的计算过程很复杂,不同的推荐系统有自己的计算方式,比如,对某一物品用户的评分,投票,转发等行为,对不同的行为乘以各自的影响权值再累加,最后就可以转化成一个具体数值,而本博客为了简单叙述,直接将用户对物品的评分[0-5]作为偏好的计算方式。以下是书本上对推荐系统框架的描述,分为了数据源、推荐算法、以及最后的推荐结果。
推荐框架
2、SVD算法
这里直接引用一篇专注SVD算法在推荐系统中的研究论文的内容来叙述:
以上的最小整数值K要留意,在下一节LFM算法中它是作为物品种类数的初始值的
但是针对其需要先填充为稠密矩阵,以及运算速度慢的缺点,一直只停留在了理论阶段。
3、LFM算法
LFM隐义模型是利用梯度下降法改进SVD算法的,主要优势是并不需要填充稀疏矩阵,大大降低了数据内存。梯度下降算法是一种最优化算法,在
各大博客上也讲解的很
详细了,
而引用在SVD上,是想使得公式
取得最小值,为了防止过度拟合问题,在上面的公式后加入了一正则项,得到用于梯度下降的损失函数
即推荐系统评判标准
RMSE的值达到最优,那么梯度算法实则就是计算p,q矩阵
,使得
RMSE的值达到最优。而求导以及步长a问题和正则化参数来求得最快下
降值。公式如下:
具体的梯度下降算法过程将在代码中的函数latenFactorModel中实现。
4、代码实现
在此本人总结了下LFM的流程:
(1)读取数据源ratings.csv文件 (开头以及给出下载链接了)
(2)直接求解最终的p,q矩阵,latenFactorModel函数形参,classcount就是SVD奇异值分解的正整数k,这里就初始为5。
a.先要初始p,q矩阵以及得到用户对物品偏好的一种数据结构userItem,它是将用户对物品的评价值[0-5]转为了[0-1]的。采取随机数初始化p,q矩阵,维数是
用户列表以及
物品列表来确定,userItem的结构:[{用户1:{物品1:1,物品2:1,物品3:0.........物品n:0或1}},
{用户2:{物品1:1,物品2:1,物品3:0.........物品n
:0或1}},,,,,
],注意的是对
未评价过得物品的偏好怎么得到的,代码中采取的是将当前用户未评价的物品,将其计算其他用多少用户评价过,那么就会
得到一个类似未评价物品A:6(有6个其他用户偏好的)
物品B:7......的panda.series结构,这样得到一个类似统计物品被其他用户偏好的一种热门度数据,那么
就将最热门的n个未被用户评价的物品的偏好值设为0,如在userItem结构形式为(物品3:0)。
b.得到初始化p,q以及userItem结构后,就进入具体的梯度下降算法
,在遍历
userItem结构后,有
eui=rui-lfmPredict(p,q,userID,itemID)这一步,就是将
userItem结构中当前
用户对物品的偏好值rui减去lfm当前得到的p,q矩阵预测
到的偏好值。
c.接着就是更改p,q,使之往损失函数梯度下降的方向运动,最终在循环迭代次数后,保证p,q最优化,是的损失函数值最小,那么用这个p,q
矩阵就可以预测每一个用
户对某一物品的偏好,即填充偏好矩阵。
具体代码如下,其中还有些细节处理,都相继的注释了。
# 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)
运行结果图:
5、写在最后
梯度算法的部分并没有细节介绍,是因为梯度寻优的问题本人将在下一博客细说。针对SVD算法以及LFM模型在推荐系统的介绍就到此。写在这里,就当是一个长期的笔记
,
在闲暇翻看时,可以唤起学习当中的理解,从而掌握这个算法。