隐语义模型(LFM)属于机器学习中的推荐算法,其中包含隐式因子部分,类似于深度学习网络的隐藏层(单层神经网络),所以很难解释隐含因子与模型最终输出结果的直接关系;但这并不妨碍我们使用该算法进行模型训练 ,因为目标最优才是我们衡量的效果。
LFM算法理解
- userCF的思路是找到相似的用户,将其他用户喜欢的物品推荐给当前用户。
- itemCF的思路找到与当前用户喜欢物品的相似物品推荐给他。见推荐算法入门
- contentCF的思路是通过物品本身属性特征计算物品间的相似性,不考虑用户对物品的行为。
- swing的思路是当同时购买物品A和物品B的用户里购买其它相同物品的比例越低,那么物品A和B越相近。见通过SQL实现阿里swing推荐算法
- LFM的思路是在用户与物品间构建隐含‘兴趣‘分类,通过寻找与当前用户偏好分类的相近的物品推荐给当前用户。
计算兴趣度公式:
参数数说明:
- P(u,k): 用户u和第k个隐类的关系,即用户u对k的偏好程度
- Q(i,k): 第i个商品在第k个隐类的权重
- F:隐类的个数
- R(u,i): 用户u对商品i的实际评分
举例:
假如有三类电影,分别是[武打片,喜剧片,爱情片],用户A非常喜欢武打片,这里用户A的P矩阵就是[1, 0, 0];有电影如下:
- 《战狼2》为武打片,那么Q的列向量表示Q(i,k)为[1, 0, 0]
- 《你好李焕英》的Q向量为[0, 1, 0]
那么用户A对两部电影的偏好计算为:
R(A,战狼2) = P(u,k) * Q(k,i) = [1, 0, 0] * [1, 0, 0] = 1
R(A,你好李焕英) = P(u,k) * Q(k,i) = [1, 0, 0] * [, 1, 0] = 0
根据公式不难发现,其实LFM是在寻找合适的P&Q矩阵,需要注意的是这里的分类不需要用户提前指定,用户只需要设置好K的个数,分类特征是模型在训练的时候通过随机梯度分解得到的
LFM的缺点
- 因P&Q矩阵需要提前算出,所有无法解决新用户与新商品冷启动的问题
- 需要遍历全集才能计算R的关系,计算量大故无法应用于实时推荐场景
代码实现
import random
import pickle
import pandas as pd
import numpy as np
from math import exp
import time
class LFM:
def __init__(self):
self.class_count = 25 #K的个数
self.iter_count = 10 #迭代次数
self.lr = 0.02 #学习率
self.lam = 0.01 #惩罚因子
self._init_model()
"""
构建P&Q矩阵,初始化参数
randn: 从标准正态分布中返回n个值
pd.DataFrame: columns 指定列顺序,index 指定索引
"""
def _init_model(self):
file_path = '03O2O优惠券/trains.csv'
self.uiscores = pd.read_csv(file_path)
self.user_ids = set(self.uiscores['user_id'].values) # 6040
self.item_ids = set(self.uiscores['product_id'].values) # 3706
#self.items_dict = pickle.load(open(pos_neg_path,'rb'))
self.user_items = {}
for _, row in self.uiscores.iterrows():
self.user_items.setdefault(row['user_id'],{})
self.user_items[row['user_id']][row['product_id']] = row['score']
array_p = np.random.randn(len(self.user_ids), self.class_count)
array_q = np.random.randn(len(self.item_ids), self.class_count)
self.p = pd.DataFrame(array_p, columns=range(0, self.class_count), index=list(self.user_ids))
self.q = pd.DataFrame(array_q, columns=range(0, self.class_count), index=list(self.item_ids))
# 使用误差平方和(SSE)作为损失函数
def _loss(self, user_id, item_id, y, step):
e = y - self._predict(user_id, item_id)
# print('Step: {}, user_id: {}, item_id: {}, y: {}, loss: {}'.format(step, user_id, item_id, y, e))
return e
"""
计算用户 user_id 对 item_id的兴趣度
p: 用户对每个类别的兴趣度
q: 物品属于每个类别的概率
"""
def _predict(self, user_id, item_id):
# p = np.mat(self.p.loc[user_id].values)
# q = np.mat(self.q.loc[item_id].values).T
# r = (p * q).sum()
# # 借助sigmoid函数,转化为是否感兴趣
# loss = 1.0 / (1 + exp(-r))
user_vector = self.p.loc[user_id].values
item_vector = self.q.loc[item_id].values
loss = np.dot(user_vector,item_vector)/(np.linalg.norm(user_vector)*np.linalg.norm(item_vector))
return loss
# 训练模型,每次迭代都要降低学习率,刚开始由于离最优值相差较远,因此下降较快,当到达一定程度后,就要减小学习率
def train(self):
for step in range(0, self.iter_count):
for user_id, item_dict in self.user_items.items():
#print('Step: {}, user_id: {}'.format(step, user_id))
item_ids = list(item_dict.keys())
random.shuffle(item_ids)
for item_id in item_ids:
e = self._loss(user_id, item_id, item_dict[item_id], step)
self._optimize(user_id, item_id, e)
self.lr *= 0.9
print('train end...')
self.save()
def _optimize(self, user_id, item_id, e):
gradient_p = -e * self.q.loc[item_id].values
l2_p = self.lam * self.p.loc[user_id].values
delta_p = self.lr * (gradient_p + l2_p)
gradient_q = -e * self.p.loc[user_id].values
l2_q = self.lam * self.q.loc[item_id].values
delta_q = self.lr * (gradient_q + l2_q)
self.p.loc[user_id] -= delta_p
self.q.loc[item_id] -= delta_q
# 计算用户未评分过的电影,并取top N返回给用户
def predict(self, user_id, top_n=10):
self.load()
user_item_ids = set(self.uiscores[self.uiscores['user_id'] == user_id]['product_id'])
other_item_ids = self.item_ids ^ user_item_ids # 交集与并集的差集
interest_list = [self._predict(user_id, item_id) for item_id in other_item_ids]
candidates = sorted(zip(list(other_item_ids), interest_list), key=lambda x: x[1], reverse=True)
return candidates[:top_n]
# 保存模型
def save(self):
f = open('data/lfm.model', 'wb')
pickle.dump((self.p, self.q), f)
f.close()
# 加载模型
def load(self):
f = open('data/lfm.model', 'rb')
self.p, self.q = pickle.load(f)
f.close()
# 模型效果评估,从所有user中随机选取10个用户进行评估,评估方法为:绝对误差(AE)
def evaluate(self):
self.load()
users=random.sample(self.user_ids,10)
user_dict={}
for user in users:
user_item_ids = set(self.uiscores[self.uiscores['user_id'] == user]['product_id'])
_sum=0.0
for item_id in user_item_ids:
_r = self._predict(user, item_id)
r=self.uiscores[(self.uiscores['user_id'] == user)
& (self.uiscores['product_id']==item_id)]["score"].values[0]
_sum+=abs(r-_r)
user_dict[user] = _sum/len(user_item_ids)
print("userID:{},AE:{}".format(user,user_dict[user]))
return sum(user_dict.values())/len(user_dict.keys())
if __name__=="__main__":
lfm=LFM()
lfm.train()
print(lfm.predict('0000c9e938104b5d9ef2e16bbfb7489b',10))
print(lfm.evaluate())
- 该模型既可以用于推荐系统评分预测,也可以用在离线(CTR)点击预估
网友评论