Q1
论文试图解决什么问题?
先前的推荐系统中只把用户建模为一个嵌入表示,不能很好的反应用户的多种兴趣关系。
Q2
这是否是一个新的问题?
是一个新的问题
Q3
这篇文章要验证一个什么科学假设?
用户的兴趣是分散的,精细化建模每个用户的不同兴趣对推荐系统有帮助
Q4
有哪些相关研究?如何归类?谁是这一课题在领域内值得关注的研究员?
大领域属于序列推荐,小类为多兴趣推荐,Yitong Pang, Yiming Zhang
Q5
论文中提到的解决方案之关键是什么?
动态路由算法提取用户的兴趣,生成一组向量
Q6
论文中的实验是如何设计的?
线下:Train: Test = 19:1 评估HitRate,线上AB测试评估CTR
Q7
用于定量评估的数据集是什么?代码有没有开源?
Amazon Books 和Tmall,https://github.com/alibaba/easyrec
Q8
论文中的实验及结果有没有很好地支持需要验证的科学假设?
线上线下都是SOTA,而且建模的兴趣数量越多,性能越好。
Q9
这篇论文到底有什么贡献?
建模了用户的多种兴趣
Q10
下一步呢?有什么工作可以继续深入?
作者想加入更多信息:例如序列时间等进行建模,以及优化动态路由的初始化方案。此外感觉还可以优化兴趣的数量,每个用户拥有的兴趣数量可能不一样。
胶囊网络学习
传统ANN处理标量,胶囊网络处理向量,对应论文中的动态路由模块。
def forward(self, item_eb, mask):
if self.bilinear_type == 0: # MIND
item_eb_hat = self.linear(item_eb) # [b, s, h]
item_eb_hat = paddle.repeat_interleave(item_eb_hat, self.interest_num, 2) # [b, s, h*in]
elif self.bilinear_type == 1:
item_eb_hat = self.linear(item_eb)
else: # ComiRec_DR
u = paddle.unsqueeze(item_eb, 2) # shape=(batch_size, maxlen, 1, embedding_dim)
item_eb_hat = paddle.sum(self.w[:, :self.seq_len, :, :] * u,
3) # shape=(batch_size, maxlen, hidden_size*interest_num)
item_eb_hat = paddle.reshape(item_eb_hat, (-1, self.seq_len, self.interest_num, self.hidden_size))
item_eb_hat = paddle.transpose(item_eb_hat, perm=[0,2,1,3])
# item_eb_hat = paddle.reshape(item_eb_hat, (-1, self.interest_num, self.seq_len, self.hidden_size))
# [b, in, s, h]
if self.stop_grad: # 截断反向传播,item_emb_hat不计入梯度计算中
item_eb_hat_iter = item_eb_hat.detach()
else:
item_eb_hat_iter = item_eb_hat
# b的shape=(b, in, s)
if self.bilinear_type > 0: # b初始化为0(一般的胶囊网络算法)
capsule_weight = paddle.zeros((item_eb_hat.shape[0], self.interest_num, self.seq_len))
else: # MIND使用高斯分布随机初始化b
capsule_weight = paddle.randn((item_eb_hat.shape[0], self.interest_num, self.seq_len))
for i in range(self.routing_times): # 动态路由传播3次
atten_mask = paddle.repeat_interleave(paddle.unsqueeze(mask, 1), self.interest_num, 1) # [b, in, s]
paddings = paddle.zeros_like(atten_mask)
# 计算c,进行mask,最后shape=[b, in, 1, s]
capsule_softmax_weight = F.softmax(capsule_weight, axis=-1)
capsule_softmax_weight = paddle.where(atten_mask==0, paddings, capsule_softmax_weight) # mask
capsule_softmax_weight = paddle.unsqueeze(capsule_softmax_weight, 2)
if i < 2:
# s=c*u_hat , (batch_size, interest_num, 1, seq_len) * (batch_size, interest_num, seq_len, hidden_size)
interest_capsule = paddle.matmul(capsule_softmax_weight,
item_eb_hat_iter) # shape=(batch_size, interest_num, 1, hidden_size)
cap_norm = paddle.sum(paddle.square(interest_capsule), -1, keepdim=True) # shape=(batch_size, interest_num, 1, 1)
scalar_factor = cap_norm / (1 + cap_norm) / paddle.sqrt(cap_norm + 1e-9) # shape同上
interest_capsule = scalar_factor * interest_capsule # squash(s)->v,shape=(batch_size, interest_num, 1, hidden_size)
# 更新b
delta_weight = paddle.matmul(item_eb_hat_iter, # shape=(batch_size, interest_num, seq_len, hidden_size)
paddle.transpose(interest_capsule, perm=[0,1,3,2])
# shape=(batch_size, interest_num, hidden_size, 1)
) # u_hat*v, shape=(batch_size, interest_num, seq_len, 1)
delta_weight = paddle.reshape(delta_weight, (
-1, self.interest_num, self.seq_len)) # shape=(batch_size, interest_num, seq_len)
capsule_weight = capsule_weight + delta_weight # 更新b
else:
interest_capsule = paddle.matmul(capsule_softmax_weight, item_eb_hat)
cap_norm = paddle.sum(paddle.square(interest_capsule), -1, keepdim=True)
scalar_factor = cap_norm / (1 + cap_norm) / paddle.sqrt(cap_norm + 1e-9)
interest_capsule = scalar_factor * interest_capsule
interest_capsule = paddle.reshape(interest_capsule, (-1, self.interest_num, self.hidden_size))
if self.relu_layer: # MIND模型使用book数据库时,使用relu_layer
interest_capsule = self.relu(interest_capsule)
return interest_capsule
多兴趣模型评估
ni = user_embs.shape[1] # num_interest
user_embs = np.reshape(user_embs,
[-1, user_embs.shape[-1]]) # shape=(batch_size*num_interest, embedding_dim)
user_embs = normalize(user_embs, norm='l2').astype('float32')
D, I = gpu_index.search(user_embs, topN) # Inner Product近邻搜索,D为distance,I是index
# D,I = faiss.knn(user_embs, item_embs, topN,metric=faiss.METRIC_INNER_PRODUCT)
for i, iid_list in enumerate(targets): # 每个用户的label列表,此处item_id为一个二维list,验证和测试是多label的
recall = 0
dcg = 0.0
item_list_set = []
# 将num_interest个兴趣向量的所有topN近邻物品(num_interest*topN个物品)集合起来按照距离重新排序
item_list = list(
zip(np.reshape(I[i * ni:(i + 1) * ni], -1), np.reshape(D[i * ni:(i + 1) * ni], -1)))
item_list.sort(key=lambda x: x[1], reverse=True) # 降序排序,内积越大,向量越近
for j in range(len(item_list)): # 按距离由近到远遍历推荐物品列表,最后选出最近的topN个物品作为最终的推荐物品
if item_list[j][0] not in item_list_set and item_list[j][0] != 0:
item_list_set.append(item_list[j][0])
if len(item_list_set) >= topN:
break
test_gd[user_id] = iid_list
preds[user_id] = item_list_set
user_id +=1
Datawhale推荐项目
MIND