SVD算法以及改进后的LFM模型在推荐系统的应用

版权声明:本文为博主原创文章,未经博主允许不得转载。 https://blog.csdn.net/qq_34533544/article/details/78034469

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模型在推荐系统的介绍就到此。写在这里,就当是一个长期的笔记
在闲暇翻看时,可以唤起学习当中的理解,从而掌握这个算法。

猜你喜欢

转载自blog.csdn.net/qq_34533544/article/details/78034469