隐语义模型为推荐系统中很常见的使用机器学习的思想构建的模型,本文章参考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
如需转载,请说明出处。
网友评论