如题,在推荐系统中我们在推荐给用户的商品中一定是需要先后顺序的,即我们需要关心的是用户将会更喜欢我们所推荐的商品,从而得到–个性化排序。但是没错,前几篇所整理的方法目的也是为了预测用户喜好,但往往我们只能通过观察到的正例去估计暗含着负例与缺失值的“?”中,而实际填充也如上图一样,一般用0做填充。然后基于此计算出得分,从某种意义上也可以得到用户优先级的排序,所以首先同样的我们需要解决的问题还是:
对于用户集U和物品集I的对应的 U × I 的预测排序矩阵 X,避免矩阵分解所需要的稠密性,同样采取分解为两个矩阵,即:
X
‾
=
W
H
T
\overline{X} = WH^T
X=WHT然后同样需要寻找最好的W和H,使其与真正X的误差最小,但是与先前的SVD不同,不是尝试对项目打分再排序,而且从整个思想上进行优化,所以作为先验与后验的桥梁----贝叶斯的思想。
**首先为了排序,引入三元组的概念:即如果用户u在同时有物品 i 和 j 的时候点击了 i,那么定义三元组<u,i,j><u,i,j>,即对用户u来说,i 的排序要比 j 靠前。如上图中,在左侧显示了观测数据和一些未知的“?”数据,三元组处理后,变成在右侧的,“+” 表示用户更喜欢项 i 大于 j 项;“-” 表示更喜欢j而不是i。
基于用户的全序关系的贝叶斯就变成:
P
(
θ
∣
>
u
)
=
P
(
>
u
∣
θ
)
P
(
θ
)
P
(
>
u
)
P(\theta|>_u) = \frac{P(>_u|\theta)P(\theta)}{P(>_u)}
P(θ∣>u)=P(>u)P(>u∣θ)P(θ)
其中W和H用
θ
\theta
θ表示,为了得到最好的W,H,即得到最好的
θ
\theta
θ,需要优化后面的这一堆,同样的分母(某用户的全序,对所有的都是物品一样)一样,可以先舍去不考虑。那么对于分子第一项可以有最大似然估计:
∏
u
∈
U
P
(
>
u
∣
θ
)
=
∏
(
u
,
i
,
j
)
∈
(
U
×
I
×
I
)
P
(
i
>
u
j
∣
θ
)
δ
(
(
u
,
i
,
j
)
∈
D
)
(
1
−
P
(
i
>
u
j
∣
θ
)
)
δ
(
(
u
,
j
,
i
)
∉
D
)
\prod_{u \in U}P(>_u|\theta) = \prod_{(u,i,j) \in (U \times I \times I)}P(i >_u j|\theta)^{\delta((u,i,j) \in D)}(1-P(i >_u j|\theta))^{\delta((u,j,i) \not\in D) }
u∈U∏P(>u∣θ)=(u,i,j)∈(U×I×I)∏P(i>uj∣θ)δ((u,i,j)∈D)(1−P(i>uj∣θ))δ((u,j,i)∈D)其中
δ
(
b
)
=
{
1
i
f
b
i
s
t
r
u
e
0
e
l
s
e
\delta(b)= \begin{cases} 1& {if\; b\; is \;true}\\ 0& {else} \end{cases}
δ(b)={10ifbistrueelse
P(i >u j | θ)即<u,i,j>,i 排名高于 j。
但是其实,既然是排序–定义使 i 大于 j,那么就使P(i >u j|θ)出现的概率越大越好就行了。那么
P
(
i
>
u
j
∣
θ
)
=
σ
(
x
‾
u
i
j
(
θ
)
)
,
其
中
σ
函
数
用
于
优
化
计
算
P(i >_u j|\theta) = \sigma(\overline{x}_{uij}(\theta)),其中\sigma函数用于优化计算
P(i>uj∣θ)=σ(xuij(θ)),其中σ函数用于优化计算而且如果要是一方的概率越大,那么另一方就小就好,那么自然期望他们之间的差异就越大即:
x
‾
u
i
j
=
x
‾
u
i
−
x
‾
u
j
\overline{x}_{uij} = \overline{x}_{ui} - \overline{x}_{uj}
xuij=xui−xuj而且这就已经直接是预测矩阵X中的对应位置的值了。即分子第一项变为:
∏
u
∈
U
P
(
>
u
∣
θ
)
=
∏
(
u
,
i
,
j
)
∈
D
σ
(
x
‾
u
i
−
x
‾
u
j
)
\prod_{u \in U}P(>_u|\theta) = \prod_{(u,i,j) \in D} \sigma(\overline{x}_{ui} - \overline{x}_{uj})
u∈U∏P(>u∣θ)=(u,i,j)∈D∏σ(xui−xuj)
然后分子第二项
P
(
θ
)
P(\theta)
P(θ),直接使用贝叶斯假设,即
θ
\theta
θ 符合正太分布,而总所周知,正太分布的对数形式是跟
∣
∣
θ
∣
∣
2
||\theta||^2
∣∣θ∣∣2成比的!(除了优化式子外,居然还能曲线救国,漂亮的完成了正则化的作用??所以直接假设使用参数符合正太分布的操作设计很巧妙呀)那么整个下来的的式子就直接变成了:
l
n
P
(
θ
∣
>
u
)
∝
l
n
P
(
>
u
∣
θ
)
P
(
θ
)
=
l
n
∏
(
u
,
i
,
j
)
∈
D
σ
(
x
‾
u
i
−
x
‾
u
j
)
+
l
n
P
(
θ
)
=
∑
(
u
,
i
,
j
)
∈
D
l
n
σ
(
x
‾
u
i
−
x
‾
u
j
)
+
λ
∣
∣
θ
∣
∣
2
ln\;P(\theta|>_u) \propto ln\;P(>_u|\theta)P(\theta) = ln\;\prod\limits_{(u,i,j) \in D} \sigma(\overline{x}_{ui} - \overline{x}_{uj}) + ln P(\theta) = \sum\limits_{(u,i,j) \in D}ln\sigma(\overline{x}_{ui} - \overline{x}_{uj}) + \lambda||\theta||^2\;
lnP(θ∣>u)∝lnP(>u∣θ)P(θ)=ln(u,i,j)∈D∏σ(xui−xuj)+lnP(θ)=(u,i,j)∈D∑lnσ(xui−xuj)+λ∣∣θ∣∣2
然后同样让我们求导,让我们使用梯度。(不过这里需要使用梯度上升法,因为为了求最大值,即求“上坡”)
∂
l
n
P
(
θ
∣
>
u
)
∂
θ
∝
∑
(
u
,
i
,
j
)
∈
D
1
1
+
e
x
‾
u
i
−
x
‾
u
j
∂
(
x
‾
u
i
−
x
‾
u
j
)
∂
θ
+
λ
θ
\frac{\partial ln\;P(\theta|>_u)}{\partial \theta} \propto \sum\limits_{(u,i,j) \in D} \frac{1}{1+e^{\overline{x}_{ui} - \overline{x}_{uj}}}\frac{\partial (\overline{x}_{ui} - \overline{x}_{uj})}{\partial \theta} + \lambda \theta
∂θ∂lnP(θ∣>u)∝(u,i,j)∈D∑1+exui−xuj1∂θ∂(xui−xuj)+λθ
x
‾
u
i
−
x
‾
u
j
=
∑
f
=
1
k
w
u
f
h
i
f
−
∑
f
=
1
k
w
u
f
h
j
f
\overline{x}_{ui} - \overline{x}_{uj} = \sum\limits_{f=1}^kw_{uf}h_{if} - \sum\limits_{f=1}^kw_{uf}h_{jf}
xui−xuj=f=1∑kwufhif−f=1∑kwufhjf
所以最后的训练算法为:
def bpr_mf(user_count, item_count, hidden_dim):
u = tf.placeholder(tf.int32, [None])
i = tf.placeholder(tf.int32, [None])
j = tf.placeholder(tf.int32, [None])
with tf.device("/cpu:0"):
user_emb_w = tf.get_variable("user_emb_w", [user_count+1, hidden_dim],
initializer=tf.random_normal_initializer(0, 0.1))
item_emb_w = tf.get_variable("item_emb_w", [item_count+1, hidden_dim],
initializer=tf.random_normal_initializer(0, 0.1))
#矩阵的分解可以理解为embedding的转化
u_emb = tf.nn.embedding_lookup(user_emb_w, u)
i_emb = tf.nn.embedding_lookup(item_emb_w, i)
j_emb = tf.nn.embedding_lookup(item_emb_w, j)
#第一部分的i 和 j的差值计算
x = tf.reduce_sum(tf.multiply(u_emb, (i_emb - j_emb)), 1, keep_dims=True)
mf_auc = tf.reduce_mean(tf.to_float(x > 0))
#第二部分完美的正则项
l2_norm = tf.add_n([
tf.reduce_sum(tf.multiply(u_emb, u_emb)),
tf.reduce_sum(tf.multiply(i_emb, i_emb)),
tf.reduce_sum(tf.multiply(j_emb, j_emb))
])
#整个loss
regulation_rate = 0.0001
bprloss = regulation_rate * l2_norm - tf.reduce_mean(tf.log(tf.sigmoid(x)))
#梯度上升
train_op = tf.train.GradientDescentOptimizer(0.01).minimize(bprloss)
return u, i, j, mf_auc, bprloss, train_op
完整代码的逐行源码阅读笔记在: https://github.com/nakaizura/Source-Code-Notebook/tree/master/BPR