推荐系统实践 0x08 隐语义模型LFM
隐语义模型(LFM)
LFM(latent factor model)隐语义模型是前几年比较火的模型,它的核心思想是通过隐含特征来联系用户兴趣和物品。我们先给出LFM通过公式计算用户\(u\)对物品\(i\)的兴趣:
这个公式中\(p_{u,k}\)和\(q_{i,k}\)是模型的参数,其中\(p_{u,k}\)度量了用户\(u\)的兴趣和第\(k\)个隐类的关系,而\(q_{i,k}\)度量了第\(k\)个隐类和物品\(i\)之间的关系。这两个参数需要在优化目标函数的过程中拟合出来。LFM在显性反馈数据(评分)上可以达到很好的精度,而在目前的隐性反馈数据(只有正样本,没有负样本)还需要进一步探索。我们先聊聊怎么在这种数据集上获取负样本。
负样本采集
我们认为,用户没有过行为的物品可以视作为负样本。一般认为,很热门而用户却没有行为更加代表用户对这个物品不感兴趣。因为对于冷门的物品,用户可能是压根没在网站中发现这个物品,所以谈不上是否感兴趣。在每个训练批次上的正负样本的比例对结果也会产生很大影响,这里的负采样就按照流行度进行采样。
def RandomSelectNegativeSample(self, items):
ret = dict()
for i in items.keys():
ret[i] = 1
n = 0
for i in range(0, len(items) * 3):
item = items_pool[random.randint(0, len(items_pool) - 1)]
# items_pool为候选物品列表,物品出现的次数与物品流行度成正比
if item in ret:
continue
ret[item] = 0
n += 1
if n > len(items):
break
return ret
目标函数
所需要优化的目标函数
后面的正则化项是为了防止训练的过拟合,可以使用随机梯度下降法(SGD)来优化上述的目标函数。
首先对它们分别求偏导数
对模型参数进行更新
def LFM(train, ratio, K, lr, step, lmbda, N):
'''
:params: train, 训练数据
:params: ratio, 负采样的正负比例
:params: K, 隐语义个数
:params: lr, 初始学习率
:params: step, 迭代次数
:params: lmbda, 正则化系数
:params: N, 推荐TopN物品的个数
:return: GetRecommendation, 获取推荐结果的接口
'''
all_items = {}
for user in train:
for item in train[user]:
if item not in all_items:
all_items[item] = 0
all_items[item] += 1
all_items = list(all_items.items())
items = [x[0] for x in all_items]
pops = [x[1] for x in all_items]
# 负采样函数(注意!!!要按照流行度进行采样)
def nSample(data, ratio):
new_data = {}
# 正样本
for user in data:
if user not in new_data:
new_data[user] = {}
for item in data[user]:
new_data[user][item] = 1
# 负样本
for user in new_data:
seen = set(new_data[user])
pos_num = len(seen)
item = np.random.choice(items, int(pos_num * ratio * 3), pops)
item = [x for x in item if x not in seen][:int(pos_num * ratio)]
new_data[user].update({x: 0 for x in item})
return new_data
# 训练
P, Q = {}, {}
for user in train:
P[user] = np.random.random(K)
for item in items:
Q[item] = np.random.random(K)
for s in trange(step):
data = nSample(train, ratio)
for user in data:
for item in data[user]:
eui = data[user][item] - (P[user] * Q[item]).sum()
P[user] += lr * (Q[item] * eui - lmbda * P[user])
Q[item] += lr * (P[user] * eui - lmbda * Q[item])
lr *= 0.9 # 调整学习率
# 获取接口函数
def GetRecommendation(user):
seen_items = set(train[user])
recs = {}
for item in items:
if item not in seen_items:
recs[item] = (P[user] * Q[item]).sum()
recs = list(sorted(recs.items(), key=lambda x: x[1], reverse=True))[:N]
return recs
return GetRecommendation
目前的算法有这么几个超参数:
- 隐特征的个数\(F\)
- 学习速率\(\alpha\)
- 正则化参数\(\lambda\)
- 负样本正样本比例ratio
通过实验发现,ratio参数对LFM的性能影响最大。
小结
LFM模型在实际使用中有一个困难,那就是它很难实现实时的推荐。经典的LFM模型 每次训练时都需要扫描所有的用户行为记录,这样才能计算出用户隐类向量(\(p_u\))和物品隐类向量(\(q_i\))。所以LFM的冷启动问题也十分明显,如在新闻类推荐系统中就会遇到这种问题。对于yahoo的新闻推荐系统来说,他们为了解决冷启动问题,将解决方案分为两部分。首先,他们利用新闻链接的内容属性(关键词、类别等)得到链接\(i\)的内容特征向量\(y_i\)。其次,他们会实时地收集用户对链接的行为,并且用这些数据得到链接\(i\)的隐特征向量\(q_i\)。然后,他们会利用如下公式预测用户\(u\)是否会单击链接:
参考
文章转载请注明出处,喜欢文章的话请我喝一杯咖啡吧!在右边赞助里面哦!谢谢! NoMornings.