隐语义模型(LFM)
通过隐含特征(latent factor)联系用户兴趣和物品。比如给用户推荐图书的场景,隐含特征可以是图书的类别。通过降维的方法补全评分矩阵。
算法理论
用户
u
u
u对物品
i
i
i的兴趣
r
^
u
i
=
p
u
T
q
i
=
∑
k
=
1
K
p
u
,
k
q
i
,
k
\hat{r}_{ui}=p^T_u q_i=\sum\limits_{k=1}^K p_{u,k}q_{i,k}
r^ui=puTqi=k=1∑Kpu,kqi,k
其中
p
u
,
k
p_{u,k}
pu,k和
q
i
,
k
q_{i,k}
qi,k是模型的参数,
p
u
,
k
p_{u,k}
pu,k度量用户
u
u
u的兴趣和第
k
k
k个隐类的关系,
q
i
,
k
q_{i,k}
qi,k度量第
k
k
k个隐类和物品
i
i
i的关系。
优化目标
C
=
∑
u
,
i
[
(
r
u
i
−
r
^
u
i
)
2
+
λ
1
∣
∣
p
u
∣
∣
2
+
λ
2
∣
∣
q
i
∣
∣
2
]
=
∑
u
,
i
[
(
r
u
i
−
∑
k
=
1
K
p
u
,
k
q
i
,
k
)
2
+
λ
1
∣
∣
p
u
∣
∣
2
+
λ
2
∣
∣
q
i
∣
∣
2
]
C=\sum\limits_{u,i}\Big[(r_{ui}-\hat{r}_{ui})^2+\lambda_1 ||p_u||^2+\lambda_2 ||q_i||^2\Big] \\=\sum\limits_{u,i}\Big[(r_{ui}-\sum\limits_{k=1}^K p_{u,k}q_{i,k})^2+\lambda_1 ||p_u||^2+\lambda_2 ||q_i||^2\Big]
C=u,i∑[(rui−r^ui)2+λ1∣∣pu∣∣2+λ2∣∣qi∣∣2]=u,i∑[(rui−k=1∑Kpu,kqi,k)2+λ1∣∣pu∣∣2+λ2∣∣qi∣∣2]
利用梯度下降法迭代求解,得梯度如下
∂
C
∂
p
u
k
=
−
2
q
i
k
⋅
(
r
u
i
−
∑
k
=
1
K
p
u
,
k
q
i
,
k
)
+
2
λ
1
p
u
k
\frac{\partial C}{\partial p_{uk}}=-2q_{ik}\cdot (r_{ui}-\sum\limits_{k=1}^K p_{u,k}q_{i,k})+2\lambda_1 p_{uk}
∂puk∂C=−2qik⋅(rui−k=1∑Kpu,kqi,k)+2λ1puk
∂
C
∂
q
i
k
=
−
2
p
u
k
⋅
(
r
u
i
−
∑
k
=
1
K
p
u
,
k
q
i
,
k
)
+
2
λ
2
q
i
k
\frac{\partial C}{\partial q_{ik}}=-2p_{uk}\cdot (r_{ui}-\sum\limits_{k=1}^K p_{u,k}q_{i,k})+2\lambda_2 q_{ik}
∂qik∂C=−2puk⋅(rui−k=1∑Kpu,kqi,k)+2λ2qik
进而,有迭代更新公式
p
u
k
←
p
u
k
+
α
(
q
i
k
⋅
(
r
u
i
−
∑
k
=
1
K
p
u
,
k
q
i
,
k
)
−
λ
1
p
u
k
)
p_{uk} \leftarrow p_{uk}+\alpha(q_{ik}\cdot (r_{ui}-\sum\limits_{k=1}^K p_{u,k}q_{i,k}) - \lambda_1 p_{uk})
puk←puk+α(qik⋅(rui−k=1∑Kpu,kqi,k)−λ1puk)
q
i
k
←
q
i
k
+
α
(
p
u
k
⋅
(
r
u
i
−
∑
k
=
1
K
p
u
,
k
q
i
,
k
)
−
λ
2
q
i
k
)
q_{ik} \leftarrow q_{ik}+\alpha(p_{uk}\cdot (r_{ui}-\sum\limits_{k=1}^K p_{u,k}q_{i,k}) - \lambda_2 q_{ik})
qik←qik+α(puk⋅(rui−k=1∑Kpu,kqi,k)−λ2qik)
其中
α
\alpha
α是学习率。
LFM模型的一些特性
(1)当数据集非常稀疏时,LFM的性能会下降,甚至不如user-based CF和item-based CF的性能。
(2)负正样本比例:对于隐性反馈数据集,即只有正样本(user like which item),没有负样本(user unlike which item),要想LFM解决TonN推荐,需要为每个用户生成负样本。一种比较好的做法是在一个用户没有过行为的物品中(热门却没用户行为),采样出一些物品作为负样本,采样时保证每个用户的正负样本数相当。
LFM模型的改进版本
r
^
u
i
=
μ
+
b
u
+
b
i
+
p
u
T
⋅
q
i
\hat{r}_{ui}=\mu+b_u+b_i+p^T_u\cdot q_i
r^ui=μ+bu+bi+puT⋅qi
其中
μ
\mu
μ是训练集中所有记录评分的全剧平均数,
b
u
b_u
bu是用户偏置项(用户的评分习惯和物品没有关系),
b
i
b_i
bi是物品偏置项(物品接受的评分中和用户没有什么关系的因素)。以上三个参数,只有
b
u
b_u
bu和
b
i
b_i
bi是模型需要学习的参数。
import random
import math
def InitLFM(datas, F):
#初始化每个用户和每个物品的隐含层向量
"""
:param datas: user-item matrix
:param F: 隐语义层类别数
:return: 初始化的user-item matrix隐语义分解的 user-隐语义矩阵 item-隐语义矩阵
"""
p = dict() #{用户id : 该用户对应的隐含层向量}
q = dict()
for u, i, rui in datas:
if u not in p:
p[u] = [random.random()/math.sqrt(F) for x in range(0,F)]
if i not in q:
q[i] = [random.random()/math.sqrt(F) for x in range(0,F)]
return (p, q)
#test:
user_item = [('U_A','I_a',4.0),
('U_B','I_b',3.0),
('U_C','I_c',5.0),
('U_D','I_d',4.0),
('U_E','I_e',3.0)]
lfm_decompose = InitLFM(user_item, 3)
print(lfm_decompose)
#Output:
({'U_A': [0.0711747331462215, 0.2616227297093711, 0.4899385151289309],
'U_B': [0.555968449196184, 0.21855559641436223, 0.041509712538012535],
'U_C': [0.1052411630108121, 0.13275955981411028, 0.479097718394469],
'U_D': [0.15802706877290149, 0.017726990993899946, 0.36606267043759183],
'U_E': [0.3321503428981605, 0.15044863198207514, 0.3359742839454751]},
{'I_a': [0.14004807384500936, 0.5378836451538328, 0.4501529686622375],
'I_b': [0.1654725947133352, 0.09187293849635998, 0.3309355309148931],
'I_c': [0.22693588447564933, 0.3399964380090505, 0.468304320741949],
'I_d': [0.467540151256175, 0.4681798818717969, 0.0863588872230948],
'I_e': [0.31334966195966957, 0.5064351119551628, 0.060277549670346375]})
import numpy as np
def Predict(u, i, p, q):
#根据 user-隐语义矩阵 item-隐语义矩阵 还原user对item的预测分
"""
:param u: user-id
:param i: item-id
:param p: user-隐语义矩阵
:param q: item-隐语义矩阵
:return: user对item的预测分
"""
return np.dot(p[u], q[i])
#test:
all_compose=[]
for i in user_item:
lfm_compose = Predict(i[0], i[1], lfm_decompose[0], lfm_decompose[1])
all_compose.append(lfm_compose)
print(all_compose)
#Output:
[0.09034022111346175, 0.25724014106241055, 0.18898141649285463, 0.038779828218872214, 0.09510803928688424]
def LearningLFM(datas, F, n, alpha, lam):
#LFM优化求 user-隐语义矩阵 item-隐语义矩阵 的过程
"""
:param datas: user-item matrix
:param F: 隐含层类别数
:param n: 梯度更新迭代轮次
:param alpha: 迭代步长
:param lam: 正则化项系数
:return: 优化后的分解矩阵
"""
(p, q) = InitLFM(datas, F)
for step in range(0, n):
totalerror = 0.0
for u, i, rui in datas: #(用户id, 物品id, 得分)
pui = Predict(u, i, p, q) #LFM预测分
eui = rui - pui
totalerror += np.abs(eui)
for k in range(F):
p[u][k] += alpha * (q[i][k] * eui - lam * p[u][k])
q[i][k] += alpha * (p[u][k] * eui - lam * q[i][k])
#print(totalerror)
alpha *= 0.9
return (p, q)
#test:
LFM_matrix = LearningLFM(user_item, 2, 20, 0.25, 0.5)
print(LFM_matrix)
#Output:
({'U_A': [1.4556948719620124, 1.0808822320643687],
'U_B': [0.34805247328789335, 1.506941524269859],
'U_C': [1.4004162596465355, 1.0323917498743957],
'U_D': [1.264866114649551, 1.3305433780548566],
'U_E': [1.1725088890202162, 1.021494392658505]},
{'I_a': [1.5405697795989084, 1.164920748249187],
'I_b': [0.36288140741053504, 1.575764055732583],
'I_c': [2.1107317430824186, 1.5407904238923547],
'I_d': [1.3090775380908621, 1.3865900804605573],
'I_e': [1.2029532096219022, 1.067316130809409]})
def Recommend(user, p, q):
"""
:param user: user-id
:param p: user-隐语义矩阵
:param q: item-隐语义矩阵
:return: 对user-id的LFM推荐排序列表
"""
rank = dict()
puf = p[user]
for i, qif in q.items():
if i not in rank:
rank[i] = 0
rank[i] += np.dot(puf, qif)
return rank
#test:
print(Recommend('U_A', LFM_matrix[0], LFM_matrix[1]))
#Output:
{'I_a': 3.5010760116000625,
'I_b': 2.855107643198207,
'I_c': 3.3870812532757943,
'I_d': 3.1630603878656465,
'I_e': 2.8016187845189524}