Latent Factor Model(LFM)隐语义模型

隐语义模型为推荐系统中很常见的使用机器学习的思想构建的模型,本文章参考http://blog.csdn.net/sinat_33741547/article/details/52976391以及项亮的《推荐系统实战》中部分理论知识。
干货奉上,简单的讲隐语义模型就是构建一个物品与用户关系的超大矩阵,如下图1所示:

图1

上图中R为用户与物品的历史关系矩阵,其中R11···R34等等为用户对物品的兴趣度。

对于隐性反馈数据,用户只要浏览了该产品,我们就可以简单的认为用户对该产品的兴趣度为1,也就是Rxx为1,否则为0。

对于显性反馈数据,比如用户对物品的评分为1-5分,那么Rxx的取值范围为1-5,对于显性数据,也可以将其归一化后进行计算。

由上述分析,R矩阵即为机器学习中所说label(结果集)。如图1所示P,Q矩阵的乘积得到了R,那么我们需要设置P、Q中的class个数。设置了class个数为n,那么矩阵P、Q的大小就是已知的,我们可以随机初始化P、Q的值。由图一可知,Rxy = Pxn * Qny(矩阵相乘),对于Rxy,我们是已知的,那么我们训练的目标就是要得到一个合适的P、Q。使得他们的乘积结果等于Rxy,其实这就类似一个简单的线性回归的模型。对于隐形数据来说,初始化的P、Q矩阵相乘得到的结果带入sigmod函数计算出结果在0-1之间,肯定与R有差异,那么我们就通过梯度下降来优化模型参数,加入正则化参数来避免过拟合。

对于如何筛选训练数据,可以参考文章开头所提及的文章。简单的讲正样本就是用户产生过兴趣或行为的物品,负样本就是用户未产生兴趣或行为的物品。

通过上述内容我们可以确定初始化模型我们需要设置以下参数:正负样本比例、隐特征个数(class个数)、学习率、正则化参数、迭代次数。其中学习率要根据迭代的次数的增加而适当的降低,代码中已经有所体现,简单的讲就是更新参数时步子迈得不要太大,应当适时的降低。
如下为LFM模型代码,代码中不考虑效率问题:

# -*- coding: utf-8 -*-
"""
Created on Thu Aug 03 16:09:09 2017

@author: hdx
"""

import pandas as pd
import numpy as np
import random


class LatentFactorModel(object):
    def __init__(self, ratio, f, learning_rate, c, iteration_counts):
        """
        :param ratio: 正负样本比例
        :param f: 隐特征个数
        :param learning_rate: 学习率
        :param c: 正则化参数
        :param iteration_counts : 迭代次数
        """
        self._ratio = ratio
        self._f = f
        self._learning_rate = learning_rate
        self._c = c
        self._iteration_counts = iteration_counts

    def _getData(self):
        """
        data中每个tuple的第三项数据不用管,也就是pandas中dataframe的status列不用理会
        :return: 商品浏览记录总数据
        """
        data = [('a',101,1),('a',111,1),('a',141,0), 
                ('b',111,0),('b',151,1),('b',131,0), 
                ('c',121,1),('c',161,0),('c',141,0), 
                ('d',111,1),('d',161,1),('d',141,0),('d',121,0), 
                ('e',131,1),('e',151,0),('e',171,0),
                ('f',181,0),('f',191,1),
                ('g',101,1),('g',201,0)]
        data = pd.DataFrame(np.array(data))
        data.columns = ['openid', 'productid', 'status']
        return data

    def _getUserPositiveItem(self, data, openid):
        """
        :param data: 总数据
        :param openid: 用户id
        :return: 正反馈物品
        """
        positiveItemList = data[data['openid'] == openid]['productid'].unique().tolist()
        return positiveItemList

    def _getUserNegativeItem(self, data, openid):
        """
        :param data: 总数据
        :param openid: 用户id
        :return: 未产生行为的物品
        """
        otherItemList = list(set(data['productid'].unique().tolist()) - set(self._getUserPositiveItem(data, openid)))
        negativeItemList = random.sample(otherItemList, self._ratio * len(self._getUserPositiveItem(data, openid)))
        return negativeItemList

    def _initUserItem(self, data):
        userItem = {}
        for openid in data['openid'].unique():
            positiveItem = self._getUserPositiveItem(data, openid)
            negativeItem = self._getUserNegativeItem(data, openid)
            itemDict = {}
            for item in positiveItem: itemDict[item] = 1
            for item in negativeItem: itemDict[item] = 0
            userItem[openid] = itemDict
        return userItem


    def _initParams(self, data):
        """
        初始化参数
        :param data: 总数据
        :return: p\q参数
        """
        p = np.random.rand(len(data['openid'].unique().tolist()), self._f)
        q = np.random.rand(self._f, len(data['productid'].unique().tolist()))
        userItem = self._initUserItem(data)
        p = pd.DataFrame(p, columns=range(0, self._f), index=data['openid'].unique().tolist())
        q = pd.DataFrame(q, columns=data['productid'].unique().tolist(), index=range(0, self._f))
        return p, q, userItem

    def sigmod(self, x):
        """
        单位阶跃函数,将兴趣度限定在[0,1]范围内
        :param x: 兴趣度
        :return: 兴趣度
        """
        y = 1.0 / (1 + np.exp(-x))
        return y

    def predict(self, p, q, openid, productid):
        """
        利用参数p, q预测目标用户对目标物品的兴趣度
        """
        p = np.mat(p.ix[openid].values)
        q = np.mat(q[productid].values).T
        r = (p * q).sum()
        r = self.sigmod(r)
        return r

    def train(self):
        data = self._getData()
        p, q, userItem = self._initParams(data)
        for step in xrange(1, self._iteration_counts+1):
            for openid, samples in userItem.items():
                for productid, r in samples.items():
                    loss = r - self.predict(p, q, openid, productid)
                    for f in xrange(0, self._f):
                        print('step %d openid %s class %d loss %f' % (step, openid, f, np.abs(loss)))
                        p[f][openid] += self._learning_rate * (loss * q[productid][f] - self._c * p[f][openid])
                        q[productid][f] += self._learning_rate * (loss * p[f][openid] - self._c * q[productid][f])
            if step % 5 == 0:
                self._learning_rate *= 0.9
        return p, q, data

    def recommend(self, data, p, q):
        rank = []
        for openid in data['openid'].unique():
            for productid in data['productid'].unique():
                rank.append((openid, productid, self.predict(p, q, openid, productid)))
        return rank

上述代码构建了一个LFM模型,下面为创建模型,训练模型,使用模型。

lfm = LatentFactorModel(1,5,0.5,0.01,200)
p,q,data = lfm.train()
lfm.recommend(data,p,q)

以上代码还可以优化性能,进行大工程计算时还是得使用矢量化计算提高效率。
最后贴一个本人的机器学习github地址 https://github.com/huangdaoxu/Machine_Learning

如需转载,请说明出处。

你可能感兴趣的:(Latent Factor Model(LFM)隐语义模型)