契机
传统DSSM双塔模型只有一个query塔和doc塔,这样导致的问题是不同质doc的特征不同,这样训练出的模型学习的东西很杂,没有对不同质doc进行不同的处理。这里MultiView-DNN解决的就是当前问题。
模型结构
MultiView-DNN将query-doc转换为推荐系统中的user-item,这样描述起来会更接地气一些。可以发现,对于不同质(view)的item,会输入到不同的塔中,并采用不同的DNN处理方式和激活函数,当数据输入某一个view时,其他view的输入为0,最终的目标如下所示,其中
Y
u
Y_u
Yu指的是User塔的输出embedding,
Y
a
,
j
Y_{a,j}
Ya,j指的是某一个item塔的输出embedding,分母下的数据是一个batch内随机采样的负样本。
p = a r g m a x W u , W 1 , . . . , W v ∑ j = 1 N e α a c o s ( Y u , Y a , j ) ∑ X ′ ∈ R d a e α c o s ( Y u , f a ( X ′ , W a ) ) p = argmax_{W_u, W_1, ..., W_v}\sum_{j=1}^N \frac{e^{\alpha_a cos(Y_u, Y_{a,j})}}{\sum_{X'\in R^{d_a}} e^{\alpha cos(Y_u, f_a(X', W_a))}} p=argmaxWu,W1,...,Wvj=1∑N∑X′∈Rdaeαcos(Yu,fa(X′,Wa))eαacos(Yu,Ya,j)
示例代码可以参见这里,其中核心代码如下所示:
with tf.name_scope('Make_Negative_Item'):
# 合并负样本,tile可选择是否扩展负样本。
# 判断激活哪一个view。
if active_view == 1:
item_y = tf.tile(view1_positive_y, [1, 1])
elif active_view == 2:
item_y = tf.tile(view2_positive_y, [1, 1])
else:
item_y = tf.tile(view3_positive_y, [1, 1])
item_y_temp = tf.tile(item_y, [1, 1])
# batch内随机负采样。
for i in range(NEG):
rand = int((random.random() + i) * user_BS / NEG)
item_y = tf.concat([item_y,
tf.slice(item_y_temp, [rand, 0], [user_BS - rand, -1]),
tf.slice(item_y_temp, [0, 0], [rand, -1])], 0)
with tf.name_scope('Cosine_Similarity'):
# Cosine similarity
# query_norm = sqrt(sum(each x^2))
query_norm = tf.tile(tf.sqrt(tf.reduce_sum(tf.square(user_y), 1, True)), [NEG + 1, 1])
# doc_norm = sqrt(sum(each x^2))
doc_norm = tf.sqrt(tf.reduce_sum(tf.square(item_y), 1, True))
# query * doc
prod = tf.reduce_sum(tf.multiply(tf.tile(user_y, [NEG + 1, 1]), item_y), 1, True)
# ||query|| * ||doc||
norm_prod = tf.multiply(query_norm, doc_norm)
# cos_sim_raw = query * doc / (||query|| * ||doc||)
cos_sim_raw = tf.truediv(prod, norm_prod)
# gamma = 20
# shape = [user_BS, NEG + 1],第一列是正样本cos相似度。
cos_sim = tf.transpose(tf.reshape(tf.transpose(cos_sim_raw), [NEG + 1, user_BS])) * 20