DMR (Deep Match to Rank) 网络介绍与源码浅析

DMR (Deep Match to Rank) 网络介绍与源码浅析

零. 前言 (与正文无关, 请忽略)

对自己之前分析过的文章做一个简单的总结:

广而告之

可以在微信中搜索 “珍妮的算法之路” 或者 “world4458” 关注我的微信公众号;另外可以看看知乎专栏 PoorMemory-机器学习, 以后文章也会发在知乎专栏中. CSDN 上的阅读体验会更好一些, 地址是: https://blog.csdn.net/eric_1993/category_9900024.html

一. 文章信息

核心观点

本文提出 DMR (Deep Match to Rank) 算法, 将 Matching 和 Ranking 网络进行联合训练. DMR 主要由 Item-to-item 子网络和 User-to-Item 子网络构成, 两个子网络均尝试捕获 user 和 target item 之间的相关性. 其中 Item-to-Item 子网络采用 Attention 机制计算 target item 和用户历史行为之间的相关性, 而 User-to-Item 子网络通过内积来建模用户和 target item 之间的相关性. 注意在训练 User-to-Item 子网络时, 只靠 click label 提供监督信息是不够的, 因此引入了辅助 Loss, 使用用户历史行为作为 label 来帮助 User-to-Item 网络的学习.

核心观点解读

DMR (Deep Match to Rank) 模型结构如下图:

其主要由 User-to-Item 以及 Item-to-Item 两个子网络组成, 两个子网络均尝试捕获 user 和 target item 之间的相关性.

User-to-Item 子网络

在 User-to-Item 子网络中, 采用 Attention 机制结合 position encoding 来学习用户侧表征, 之后再与 target item 的 embedding 进行内积.
这里需要注意 target item 在 User-to-Item 和 Item-to-Item 子网络中均有出现, 但是它们并不共享 embedding. 其中 Item-to-Item 网络中 target item 对应的 embedding 被称为 input representation, 而 User-to-Item 网络中 target item 对应的 embedding 被称为 output representation.

User-to-Item 子网络中, Attention 权重系数的生成方式为:

a t = z ⊤ tanh ⁡ ( W p p t + W e e t + b ) α t = exp ⁡ ( a t ) ∑ i = 1 T exp ⁡ ( a i ) \begin{aligned} a_{t} &=\boldsymbol{z}^{\top} \tanh \left(\boldsymbol{W}_{p} \boldsymbol{p}_{t}+\boldsymbol{W}_{e} \boldsymbol{e}_{t}+\boldsymbol{b}\right) \\ \alpha_{t} &=\frac{\exp \left(a_{t}\right)}{\sum_{i=1}^{T} \exp \left(a_{i}\right)} \end{aligned} atαt=ztanh(Wppt+Weet+b)=i=1Texp(ai)exp(at)

其中 p t ∈ R d p \boldsymbol{p}_{t}\in\mathbb{R}^{d_p} ptRdp 为第 t t t 个 position embedding, e t ∈ R d e \boldsymbol{e}_{t} \in \mathbb{R}^{d_{e}} etRde 为第 t t t 个用户行为对应的特征向量, z ∈ R d h \boldsymbol{z} \in \mathbb{R}^{d_{h}} zRdh 为可学习的参数. 最终的用户表达是对历史行为进行加权求和获得:

u = g ( ∑ t = 1 T ( α t e t ) ) = g ( ∑ t = 1 T ( h t ) ) \boldsymbol{u}=g\left(\sum_{t=1}^{T}\left(\alpha_{t} \boldsymbol{e}_{t}\right)\right)=g\left(\sum_{t=1}^{T}\left(\boldsymbol{h}_{t}\right)\right) u=g(t=1T(αtet))=g(t=1T(ht))

其中 u ∈ R d v \boldsymbol{u} \in \mathbb{R}^{d_{v}} uRdv 为用户 embedding, g ( ⋅ ) g(\cdot) g() 为非线性变换, 其输入维度大小为 d e d_e de, 输出维度大小为 d v d_v dv.

在 User-to-Item 网络中, target item 对应的 embedding 为 output representation, 使用 v ′ \boldsymbol{v}^\prime v 表示, 它与用户 embedding 的内积可以描述 user 和 item 之间的相关性:

r = u ⊤ v ′ r = \boldsymbol{u}^{\top} \boldsymbol{v}^{\prime} r=uv

r r r 值越大, 越能体现 user emb 和 item emb 之间的相关性, 这样能促进 prediction net 对 ctr 的预估. 但是只靠 click label 来提供监督信息是不够的, 因此引入了辅助网络, 使用用户行为作为 label 来帮助 u2i 网络的学习. 辅助网络的目标是使用前 T − 1 T - 1 T1 个行为来预测第 T T T 个行为, 那么该问题就转化为了一个 multi-classification 的分类问题. 前 T − 1 T - 1 T1 个行为 sum pooling 的结果表示为 u T − 1 ∈ R d v \boldsymbol{u}_{T-1} \in \mathbb{R}^{d_{v}} uT1Rdv, 那么用户下一次要点击 item j j j 的概率为:

p j = exp ⁡ ( u T − 1 ⊤ v j ′ ) ∑ i = 1 K exp ⁡ ( u T − 1 ⊤ v i ′ ) p_{j}=\frac{\exp \left(\boldsymbol{u}_{T-1}^{\top} \boldsymbol{v}_{j}^{\prime}\right)}{\sum_{i=1}^{K} \exp \left(\boldsymbol{u}_{T-1}^{\top} \boldsymbol{v}_{i}^{\prime}\right)} pj=i=1Kexp(uT1vi)exp(uT1vj)

Loss 函数可以设计为:

L a u x = − 1 N ∑ i = 1 N ∑ j = 1 K y j i log ⁡ ( p j i ) L_{aux}=-\frac{1}{N} \sum_{i=1}^{N} \sum_{j=1}^{K} y_{j}^{i} \log \left(p_{j}^{i}\right) Laux=N1i=1Nj=1Kyjilog(pji)

但显然这是一个计算成本巨大的任务, 因此作者借鉴 Word2Vec 中提出的负采样技术, 重新设计了辅助网络的 Loss 为:

L N S = − 1 N ∑ i = 1 N ( log ⁡ ( σ ( u T − 1 ⊤ v o ′ ) ) + ∑ j = 1 k log ⁡ ( σ ( − u T − 1 ⊤ v j ′ ) ) ) \begin{aligned} L_{N S}=&-\frac{1}{N} \sum_{i=1}^{N}\left(\log \left(\sigma\left(\boldsymbol{u}_{T-1}^{\top} \boldsymbol{v}_{o}^{\prime}\right)\right)\right.\\ &\left.+\sum_{j=1}^{k} \log \left(\sigma\left(-\boldsymbol{u}_{T-1}^{\top} \boldsymbol{v}_{j}^{\prime}\right)\right)\right) \end{aligned} LNS=N1i=1N(log(σ(uT1vo))+j=1klog(σ(uT1vj)))

Item-to-Item 子网络

Item-to-Item 子网络以一种不太直接的方式来建模 user 和 item 之间的相关性, 它采用 Attention 机制学习 target item 与用户行为的相关性, 公式如下:

a ^ t = z ^ ⊤ tanh ⁡ ( W ^ c e c + W ^ p p t + W ^ e e t + b ^ ) \hat{a}_{t}=\hat{\boldsymbol{z}}^{\top} \tanh \left(\hat{\boldsymbol{W}}_{c} \boldsymbol{e}_{c}+\hat{\boldsymbol{W}}_{p} \boldsymbol{p}_{t}+\hat{\boldsymbol{W}}_{e} \boldsymbol{e}_{t}+\hat{\boldsymbol{b}}\right) a^t=z^tanh(W^cec+W^ppt+W^eet+b^)

其中 e c ∈ R d e \boldsymbol{e}_{c} \in \mathbb{R}^{d_{e}} ecRde 为 target item 对应的 embedding, 注意它和 User-to-Item 子网络中的 embedding 不是共享的. 其他参数不再介绍, 一目了然.

另外, 在 Item-to-Item 网络中, 用户与 item 之间的相似性可以使用 target 与用户历史行为的相似程度之和来表示, 即:

r ^ = ∑ t = 1 T a ^ t \hat{r}=\sum_{t=1}^{T} \hat{a}_{t} r^=t=1Ta^t

而用户侧的 embedding 则采用对历史行为的加权求和表示:

α ^ t = exp ⁡ ( a ^ t ) ∑ i = 1 T exp ⁡ ( a ^ i ) u ^ = ∑ t = 1 T ( α ^ t e ^ t ) \begin{aligned} \hat{\alpha}_{t} &=\frac{\exp \left(\hat{a}_{t}\right)}{\sum_{i=1}^{T} \exp \left(\hat{a}_{i}\right)} \\ \hat{\boldsymbol{u}} &=\sum_{t=1}^{T}\left(\hat{\alpha}_{t} \hat{\boldsymbol{e}}_{t}\right) \end{aligned} α^tu^=i=1Texp(a^i)exp(a^t)=t=1T(α^te^t)

最后输入到 MLP 的 embedding 为:

c = [ x p , x t , x c , u ^ , r , r ^ ] \boldsymbol{c}=\left[\boldsymbol{x}_{p}, \boldsymbol{x}_{t}, \boldsymbol{x}_{c}, \hat{\boldsymbol{u}}, r, \hat{r}\right] c=[xp,xt,xc,u^,r,r^]

依次表示 User Profile, Target Item, Context, Item-to-Item 网络中的用户 embedding, User-to-Item 网络中用户和 item 的相似性, Item-to-Item 网络中用户和 item 的相似性

源码分析

源码位于: https://github.com/lvze92/DMR; 在下面分析代码时, 从名字就能看出含义的变量不多介绍.
数据集来自 https://tianchi.aliyun.com/dataset/dataDetail?dataId=56

其中用户历史行为使用:

self.btag_his = tf.cast(self.feature_ph[:, 0:50], tf.int32)
self.cate_his = tf.cast(self.feature_ph[:, 50:100], tf.int32)
self.brand_his = tf.cast(self.feature_ph[:, 100:150], tf.int32)
self.mask = tf.cast(self.feature_ph[:, 150:200], tf.int32)
self.match_mask = tf.cast(self.feature_ph[:, 200:250], tf.int32)

btag 表示用户行为类型, 包括浏览/加购/购买/喜欢.

行为中采用类目以及品牌, 行为个数截断为 50 个, 不足 50 个则采用补 0 操作, 设置 mask 来确定真实行为. 行为序列按照由远及近的顺序进行排序, 比如 mask 可能的值是 [0, 0, ..., 1, 1, 1], mask[-1] 表示最近的行为对应的 mask. 在大多数情况下 self.match_maskself.mask 的值是相同的, 但存在一小部分样本这两个值有 diff. 暂时没想明白 match_maskmask 的区别 (数据预处理代码没有提供), 因此假设 self.match_maskself.mask 完全相同, 忽略细节, 不影响对核心算法的理解.

Position Embedding 的生成

代码位于: https://github.com/lvze92/DMR/blob/master/model.py

## 用于 Item-to-Item 网络
self.position_his = tf.range(50)
self.position_embeddings_var = tf.get_variable("position_embeddings_var", [50, other_embedding_size])
self.position_his_eb = tf.nn.embedding_lookup(self.position_embeddings_var, self.position_his)  # [T, E']
self.position_his_eb = tf.tile(self.position_his_eb, [tf.shape(self.mid)[0], 1])  # [B*T, E']
self.position_his_eb = tf.reshape(self.position_his_eb, [tf.shape(self.mid)[0], -1, self.position_his_eb.get_shape().as_list()[1]])  # [B,T,E']

## 用于 User-to-Item 网络
self.dm_position_his = tf.range(50)
self.dm_position_embeddings_var = tf.get_variable("dm_position_embeddings_var", [50, other_embedding_size])
self.dm_position_his_eb = tf.nn.embedding_lookup(self.dm_position_embeddings_var, self.dm_position_his)  # [T, E']
self.dm_position_his_eb = tf.tile(self.dm_position_his_eb, [tf.shape(self.mid)[0], 1])  # [B*T, E']
self.dm_position_his_eb = tf.reshape(self.dm_position_his_eb, [tf.shape(self.mid)[0], -1, self.dm_position_his_eb.get_shape().as_list()[1]])  # [B,T,E']


## Position Embedding 最后要和用户行为类型 (btag 表示浏览/加购/购买/喜欢等行为)对应的 embedding 进行拼接
self.position_his_eb = tf.concat([self.position_his_eb, self.btag_his_batch_embedded], -1) ## 用于 Item-to-Item 网络, 大小为 [B, T, E3]
self.dm_position_his_eb = tf.concat([self.dm_position_his_eb, self.dm_btag_his_batch_embedded], -1) ## 用于 User-to-Item 网络, 大小为 [B, T, E3]

注意 Position Embedding 最后和用户行为类型对应的 embedding 进行拼接, 其中 self.position_his_eb 用于 Item-to-Item 网络, 而 self.dm_position_his_eb 用于 User-to-Item 网络.

User-to-Item 网络

https://github.com/lvze92/DMR/blob/master/model.py 中, 调用:

# User-to-Item Network
with tf.name_scope('u2i_net'):
	"""
	 + dm_item_vectors 为用户行为 embedding layer, 大小设置为 [K, E1]
	 + dm_item_biases 为 bias, 大小设置为 K
	 + 注意 K 是所有商品空间的大小
	 + deep_match() 函数中传入的参数含义:
		该函数参数比较多, 但总结下来可以认为: 前四个参数和求 Attention 系数有关, 而后面 5 个参数跟利用负采样来计算辅助 loss 有关.
		 - self.item_his_eb 为用户历史行为 emb, 大小为 [B, T, E2]
		 - self.dm_position_his_eb 为 position embedding, 大小为 [B, T, E3]
		 - self.mask 和 self.match_mask 大小相等, 均为 [B, T]
		 - self.cate_his: 用户历史行为, 这里只使用类目特征, 没有使用 brand 特征, 大小为 [B, T], 而上面 self.item_his_eb 则是对类目 emb 和 brand emb 进行了拼接;
		 			之所以只用 cate 而不同时使用 brand, 猜测是进行负采样的时候, 传入的是 dm_item_vectors (含义是用户行为 embedding layers, 里面只包含 cate)
		 - main_embedding_size: item embedding size, 等于注释中的 E1
		 - dm_item_vectors: 商品 embedding 空间, 大小为 [K, E1], 用于负采样
		 - dm_item_biases: 商品 bias 空间, 大小为 [K]
		 - cate_size: 商品空间大小, 表示 K
	 + deep_match() 函数有三个返回值:
	 	 - self.aux_loss: 表示辅助 loss
	 	 - dm_user_vector: User-to-Item 网络中最终的 user emb, 大小为 [B, E1]
	 	 - scores: Attention 权重系数, 大小为 [B, 1, T]
	 + self.cate_id: target item 对应的 id, 大小为 [B]
	 + dm_item_vec: target item 对应的 emb, 大小为 [B, E1]
	 + rel_u2i: 用户 emb 和 target item 对应的 emb 做内积, 得到大小为 [B, 1] 的结果
	"""
	dm_item_vectors = tf.get_variable("dm_item_vectors", [cate_size, main_embedding_size])
    dm_item_biases = tf.get_variable('dm_item_biases', [cate_size], initializer=tf.zeros_initializer(), trainable=False)
    # Auxiliary Match Network
    self.aux_loss, dm_user_vector, scores = deep_match(self.item_his_eb, self.dm_position_his_eb, self.mask, tf.cast(self.match_mask, tf.float32), self.cate_his, main_embedding_size, dm_item_vectors, dm_item_biases, cate_size)
    self.aux_loss *= 0.1
    dm_item_vec = tf.nn.embedding_lookup(dm_item_vectors, self.cate_id)
    rel_u2i = tf.reduce_sum(dm_user_vector * dm_item_vec, axis=-1, keep_dims=True)
    self.rel_u2i = rel_u2i

可以看到, deep_match() 函数返回用户 emb dm_user_vector 后, 和 target item 向量 dm_item_vec 进行内积, 得到相关性 rel_u2i. 再来看 deep_match 函数的定义, 代码位于: https://github.com/lvze92/DMR/blob/master/utils.py. 分成两个部分阅读, 首先是 position emb 作为 query

def deep_match(item_his_eb, context_his_eb, mask, match_mask, mid_his_batch, EMBEDDING_DIM, item_vectors, item_biases, n_mid):
	"""
	参数介绍: (前四个参数和求 Attention 系数有关, 而后面 5 个参数跟利用负采样来计算辅助 loss 有关.)
	+ item_hist_eb: 用户历史行为 emb, 大小为 [B, T, E2]
	+ context_his_eb: position embedding, 大小为 [B, T, E3]
	+ mask 和 match_mask: 大小相等, 均为 [B, T]
	+ mid_his_batch: (mid 表示 m id, 哈哈) 用户历史行为, 和 item_hist_eb 的区别是, 这里只使用类目特征, 没有使用 brand 特征, 大小为 [B, T]
			之所以只用 cate 而不同时使用 brand, 猜测是进行负采样的时候, 传入的 item_vectors 中只包含了 cate 特征 (没包含 brand 特征)
	+ EMBEDDING_DIM: item embedding size, 等于注释中的 E1 (看上面一大段代码中的注释)
	+ item_vectors: 商品 embedding 空间, 大小为 [K, E1], 用于负采样
	+ 商品 bias 空间, 大小为 [K]
	+ cate_size: 商品空间大小, 表示 K
	"""

	## 1. 首先 context_his_eb 表示 position emb, 它用作 query, 和用户行为一起做非线性变换来生成 Attention 权重系数 scores
    query = context_his_eb ## [B, T, E3]
    query = tf.layers.dense(query, item_his_eb.get_shape().as_list()[-1], activation=None, name='dm_align') ## [B, T, E2]
    query = prelu(query, scope='dm_prelu') ## [B, T, E2]
    inputs = tf.concat([query, item_his_eb, query-item_his_eb, query*item_his_eb], axis=-1) ## [B, T, E4]
    att_layer1 = tf.layers.dense(inputs, 80, activation=tf.nn.sigmoid, name='dm_att_1')
    att_layer2 = tf.layers.dense(att_layer1, 40, activation=tf.nn.sigmoid, name='dm_att_2')
    att_layer3 = tf.layers.dense(att_layer2, 1, activation=None, name='dm_att_3')  ## [B, T, 1]
    scores = tf.transpose(att_layer3, [0, 2, 1]) ## [B, 1, T], 生成权重系数

    ## 结合 mask, 因为后面要使用 softmax 来对权重系数进行归一化, 只有那些真实的历史行为会被用于计算, 而通过补 0 操作得到的行为不用于计算, 
    ## 因此通过设置一个极大的负值 -2**32+1, 来达到目的, 这样 softmax 的结果就约等于 0
    bool_mask = tf.equal(mask, tf.ones_like(mask))  # [B, T]
    key_masks = tf.expand_dims(bool_mask, 1)  # [B, 1, T]
    paddings = tf.ones_like(scores) * (-2 ** 32 + 1)
    scores = tf.where(key_masks, scores, paddings)

    """
	这一步是比较细节的操作, 从论文的公式中不太能看出来. 假设 scores 为: (大小为 [B, 1, T])

	[[[1, 2, ...]],
	 [[3, 4, ...]]]

	经过 tile + reshape 等操作后, 得到的结果是: (大小为 [B, T, T])

	[
		[
			[1, 2, ...],
	     	[1, 2, ...],
	     	....
	    ],

	    [
	    	[3, 4, ...],
	     	[3, 4, ...],
	     	....
	    ],
	]

	tril 为下三角矩阵:

	[
		[
			[1, 0, 0, ...],
			[1, 1, 0, ...],
			[1, 1, 1, ...]
		],
		[
			[1, 0, 0, ...],
			[1, 1, 0, ...],
			[1, 1, 1, ...]
		],
	]

	scores_tile 经过 softmax 对 Attention 系数进行归一化, 最后和 item_his_eb 进行矩阵相乘, 得到大小为 [B, T, E2] 的输出 att_dm_item_his_eb
	使用下三角函数的目的是, 每一次只计算前 i 个行为的加权求和结果. 比如对第 T 个行为, 它只和 T-1, T-2, ..., 0 个行为来计算 Attention 的结果.
	同时, 下一段将要分析的代码中,  user_vector = dnn_layer1[:, -1, :], 其表示最后输出的 user emb, 但它是从 dnn_layer1 中取最后一个结果.
    """
    scores_tile = tf.tile(tf.reduce_sum(scores, axis=1), [1, tf.shape(scores)[-1]]) # [B, T*T]
    scores_tile = tf.reshape(scores_tile, [-1, tf.shape(scores)[-1], tf.shape(scores)[-1]]) # [B, T, T]
    diag_vals = tf.ones_like(scores_tile)  # [B, T, T]
    # tril = tf.contrib.linalg.LinearOperatorTriL(diag_vals).to_dense()
    tril = tf.linalg.LinearOperatorLowerTriangular(diag_vals).to_dense()
    paddings = tf.ones_like(tril) * (-2 ** 32 + 1)
    scores_tile = tf.where(tf.equal(tril, 0), paddings, scores_tile)  # [B, T, T]
    scores_tile = tf.nn.softmax(scores_tile) # [B, T, T]
    att_dm_item_his_eb = tf.matmul(scores_tile, item_his_eb) # [B, T, E2]

上面代码阐述了如何利用 position emb 作为 query 来进行 Attention, 下面介绍如何产生 user emb 以及如何做负采样来进行辅助 Loss 的计算, 代码如下:

## Attention 后的结果还要进行非线性变换
dnn_layer1 = tf.layers.dense(att_dm_item_his_eb, EMBEDDING_DIM, activation=None, name='dm_fcn_1') ## [B, T, E1]
dnn_layer1 = prelu(dnn_layer1, 'dm_fcn_1') # [B, T, E1]

## 从 dnn_layer1 中选取最后一个结果作为用户 emb, 它已经对用户的 T 个行为进行了加权求和, 即
user_vector = dnn_layer1[:, -1, :]  ## [B, E1]

## 从 dnn_layer1 中选取倒数第二个结果作为 u_{T - 1} 作为辅助 Loss 的正样本 (看模型结构图中 Auxiliary Match Network Loss 中那部分)
## 这里的 match_mask 我没弄明白, 不过大多数情况下, 它和 mask 的值是一样的, 不过它的数值要么是 1, 要么是 0
user_vector2 = dnn_layer1[:, -2, :] * tf.reshape(match_mask, [-1, tf.shape(match_mask)[1], 1])[:, -2, :]

## 调用 tf.nn.sampled_softmax_loss 以及 tf.nn.learned_unigram_candidate_sampler 来进行辅助 Loss 的计算
## 其中 mid_his_batch[:, -1] 表示 e_{T}, 即最后一个行为 (看模型结构图, 图中的 e_{T} 就是 md_his_batch[:, -1], 同时它也是
## Auxiliary Match Network Loss 图中的 v_{o}), 用作辅助 Loss 的正样本
## 而这里还要采用 2000 个行为作为负样本, user_vector2 是辅助 Loss 的 u_{T - 1}.
num_sampled = 2000
loss = tf.reduce_mean(tf.nn.sampled_softmax_loss(weights=item_vectors,
                                                  biases=item_biases,
                                                  labels=tf.cast(tf.reshape(mid_his_batch[:, -1], [-1, 1]), tf.int64),
                                                  inputs=user_vector2,
                                                  num_sampled=num_sampled,
                                                  num_classes=n_mid,
                                                  sampled_values=tf.nn.learned_unigram_candidate_sampler(tf.cast(tf.reshape(mid_his_batch[:, -1], [-1, 1]), tf.int64), 1, num_sampled, True, n_mid)
                                                  ))
return loss, user_vector, scores

最后 deep_match 函数返回辅助 loss, 用户 emb, 以及 Attention 权重系数 scores.

Item-to-Item 网络

https://github.com/lvze92/DMR/blob/master/model.py 中, 调用:

# Item-to-Item Network
with tf.name_scope('i2i_net'):
	"""
	+ self.item_eb: target item 对应的 emb, 大小为 [B, E2], 拼接了类目和 brand embedding
	+ self.item_his_eb: 用户历史行为 emb, 大小为 [B, T, E2]
	+ self.position_his_eb: position embedding, 大小为 [B, T, E3]
	+ self.mask: 大小为 [B, T]

	+ att_outputs: target emb 与 position emb 作为 query, 与历史行为共同用于 Attention 系数的生成, 最后使用 Attention 系数对用户行为进行加权求和, 
		         得到大小为 [B, E2] 的向量
	+ alphas: 归一化之后的 Attention 系数, 大小为 [B, 1, T]
	+ scores_unnorm: 归一化之前的 Attention 系数, 大小为 [B, 1, T]
	+ rel_i2i: 表示 user和 item 相关性的结果, 对 Attention 系数进行累加 (文章公式说了), 大小为 [B, 1]
	"""
    att_outputs, alphas, scores_unnorm = dmr_fcn_attention(self.item_eb, self.item_his_eb, self.position_his_eb, self.mask)
    rel_i2i = tf.expand_dims(tf.reduce_sum(scores_unnorm, [1, 2]), -1)
    self.rel_i2i = rel_i2i
    self.scores = tf.reduce_sum(alphas, 1)

下面看 dmr_fcn_attention() 函数, 定义在: https://github.com/lvze92/DMR/blob/master/utils.py,

def dmr_fcn_attention(item_eb, item_his_eb, context_his_eb, mask, mode='SUM'):
	"""
	参数介绍:
	+ item_eb: target item 对应的 emb, 大小为 [B, E2], 拼接了类目和 brand embedding
	+ item_his_eb:  用户历史行为 emb, 大小为 [B, T, E2]
	+ context_his_eb: position embedding, 大小为 [B, T, E3]
	+ mask: 大小为 [B, T]
	"""
    mask = tf.equal(mask, tf.ones_like(mask))
    item_eb_tile = tf.tile(item_eb, [1, tf.shape(mask)[1]]) # [B, T*E2]
    item_eb_tile = tf.reshape(item_eb_tile, [-1, tf.shape(mask)[1], item_eb.shape[-1]]) # [B, T, E2]

    ## 当 position emb 不为空时, 它和目标商品的 embedding 进行拼接作为 query, 大小为 [B, T, E2+E3]
    if context_his_eb is None:
        query = item_eb_tile
    else:
        query = tf.concat([item_eb_tile, context_his_eb], axis=-1)

    ## query 经过非线性变换, 变换后的大小为 [B, T, E2]
    query = tf.layers.dense(query, item_his_eb.get_shape().as_list()[-1], activation=None, name='dmr_align') ## [B, T, E2]
    query = prelu(query, scope='dmr_prelu')

    ## query 和用户历史行为用于 Attention 系数的计算
    dmr_all = tf.concat([query, item_his_eb, query-item_his_eb, query*item_his_eb], axis=-1)
    att_layer_1 = tf.layers.dense(dmr_all, 80, activation=tf.nn.sigmoid, name='tg_att_1')
    att_layer_2 = tf.layers.dense(att_layer_1, 40, activation=tf.nn.sigmoid, name='tg_att_2')
    att_layer_3 = tf.layers.dense(att_layer_2, 1, activation=None, name='tg_att_3') # [B, T, 1]
    att_layer_3 = tf.reshape(att_layer_3, [-1, 1, tf.shape(item_his_eb)[1]]) # [B, 1, T]
    scores = att_layer_3

    ## 设置 Mask
    key_masks = tf.expand_dims(mask, 1)  # [B, 1, T]
    paddings = tf.ones_like(scores) * (-2 ** 32 + 1)
    paddings_no_softmax = tf.zeros_like(scores)
    scores = tf.where(key_masks, scores, paddings)  # [B, 1, T]
    scores_no_softmax = tf.where(key_masks, scores, paddings_no_softmax)

    ## 得到归一化后的 Attention 系数, 大小为 [B, 1, T]
    scores = tf.nn.softmax(scores)

    if mode == 'SUM':
    	## 使用  Attention 系数对历史行为进行加权求和
        output = tf.matmul(scores, item_his_eb)  # [B, 1, E2]
        output = tf.reduce_sum(output, axis=1)  # [B, E2]
    else:
    	## 相当于不进行加权求和
        scores = tf.reshape(scores, [-1, tf.shape(item_his_eb)[1]]) ## [B, T]
        output = item_his_eb * tf.expand_dims(scores, -1) ## [B, T, E2]
        output = tf.reshape(output, tf.shape(item_his_eb)) ## [B, T, E2]

    return output, scores, scores_no_softmax

MLP 网络

代码位于: https://github.com/lvze92/DMR/blob/master/model.py, 将各种 embedding 进行拼接 (包括 User-to-Item & Item-to-Item 网络的输出以及 Attention 系数的累加, User Profile 等):

"""
+ user_feat: 用户侧特征, 大小为 [B, C1]
+ item_feat: 目标商品侧特征, 大小为 [B, C2]
+ context_feat: 上下文特征, 大小为 [B, C3] 
+ item_his_eb_sum: 历史行为特征进行 sum, 大小为 [B, E2]
+ item_eb * item_hist_eb_sum: 目标商品和历史行为的交叉特征, 大小为 [B, E2]
+ rel_u2i: User-to-Item 网络的输出, 用户 emb 和 item emb 的内积, 大小为 [B, 1] 
+ rel_i2i: Item-to-Item 网络的输出, 表示目标商品与用户的相关性, 大小为 [B, 1]
+ att_outputs: Item-to-Item 网络中目标商品和历史行为的 Attention 结果, 大小为 [B, E2]
"""
inp = tf.concat([self.user_feat, self.item_feat, self.context_feat, self.item_his_eb_sum,self.item_eb * self.item_his_eb_sum, rel_u2i, rel_i2i, att_outputs], -1)
self.build_fcn_net(inp) ## MLP 的输出

总结

其实网络结构不是很复杂, 但写博客时发现要形式化描述, 得花很多时间… 深感没有必要, 谨记, 以后写博客尽量从简, 快速分析重点. 将论文中的内容重新说一遍实在没有必要.

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值