note
文章目录
- note
- 一、EGES图算法
- 1.0 回顾GNN
- 1.1 基本定义和数据预处理
- 1.2 GES: GNN with side info
- 1.3 EGES: enhanced版本
- 二、Framework of EGES
- 三、代码实现
- 四、Experiments
- 4.1 offline evaluation
- 4.2 online A/B test
- 五、系统部署和Operation
- 六、离线评估
- 七、EGES训练慢的解决办法
- Reference
一、EGES图算法
1.0 回顾GNN
在商品推荐系统中普遍存在三大问题:可扩展性、稀疏性和冷启动问题。GNN类算法就能很好地解决推荐系统中的稀疏性问题,因为RS中有很多图结构数据,如二部图,序列图、社交关系图、知识语义图等。
回顾word2vec:基于节点的 ID 把每个节点映射成一个 embedding,这种方式的缺点在于只能处理出现频次大于一定阈值的节点,但是在例如淘宝的场景中,大量的商品(特别是新商品)被消费的次数很少,因此无法为这些商品生成 embedding。
- 某宝在19年提出的EGES模型,是加入side information的graph embedding(如item的品牌、类别、价格、描述、海报等)方法,解决冷启动问题。核心任务在于基于用户行为计算所有项目之间的成对相似性。大致步骤为基于用户历史行为构造一个图,然后利用 Node2Vec 的方法来学习 Item 的 Embedding 向量。这样便可以根据向量的内积计算节点间的相似度来生成候选集。
- 为了解决冷启动,阿里的GNN迭代了三次:BGE、GES 和 EGES。
- 推荐系统中存在很多的图结构,如二部图,序列图,社交关系图,知识语义图等。GNN比random walk等算法效果更好。
1.1 基本定义和数据预处理
利用滑动窗口选取用户历史行为序列,同时也有降噪处理,如点击少于1s的大概率为无意点击(需要剔除)、过度活跃用户(短时间购买几千件商品可能为刷的,需要剔除)等等。
根据用户行为构建加权图(如上图b中所示),然后通过我们最熟悉的deepwalk学习G图中每个节点的embedding。
random walk的转移概率定义如下:
其中几个定义:
- 图,V表示节点(商品)集合,表示加权有向边的集合,这里的边权,即从节点
i
到j
的边权表示所有用户,先消费商品item i
再消费item j
的频率 - 的邻接矩阵为M,并且为节点到节点的边权重
- 表示节点的所有邻居节点集合
通过random walk得到一系列的1.1(b)中的序列,再通过skip gram学习节点的embedding,训练目标是最大化我们在random walk得到序列中的节点共现概率,即优化问题为:
其中为滑动窗口的大小,基于独立假设,有
利用负采样优化这里的损失函数(减少分类softmax的目标标签数),将问题转为:
其中:
- 是的负采样样本,经验上看,该负采样数值越大越好
- 是sigmoid函数
1.2 GES: GNN with side info
GBE模型存在严重的冷启动问题,在BGE基础上提出GES,即为图中每个节点增加side information。定义矩阵 表示物品及其side information的embedding向量表示, 表示物品 自身的向 量表示, 表示物品 第s 类side information的向量表示。为了合并item v的所有side info向量,做一层平均池化(average-pooling):
实际上就是word2vec的多个embedding的版本,我们传统的word2vec的结构中单个input 对应一个词,而这里,单个input对应一个向量,以item2vec为例,单个input就是一个item,而这里单个input包括了[item ,item的价格,item的商品类型,item所在的shop…]并且为每一个属性都建立了一个input embedding,然后这些input embeddings做一个mean pooling就重新变成了一个常规的单个item的input embedding的形式,原论文中对price category shop这些的embedding的size取的大小和item embedding相同,这样才可以直接mean pooling,否则维度不同只能concat了
1.3 EGES: enhanced版本
- 定义一个矩阵 表示的是图中所有节点的数量,
- 表示第
i
个item的第j
类side information的权重; - 表示物品 的第
融入side info的加权层定义:
其中上面有个trick,使用代替确保每个side info值是大于0的,分母是对每个side information进行归一化标准处理。
EGES的目标函数:
其中:
- :表示在训练集中,节点v的上下文节点u的embedding,即item u的output vector(在softmax层中的embedding)
- 看成是
- label 表示 是否 是
二、Framework of EGES
为了得到采样序列,先随机选择一个起始节点,然后使用alias method进行采样确定下一个节点。
alias method:加权随机采样. 胡诚. https://guyuecanhui.github.io/2020/12/05/weighted-sample/
node2vec 算法 [9] 还提出了一种有偏的随机游走策略,通过引入两个超参数来决定游走是偏向于向远离初始点的方向探索,还是围绕初始点附近进行探索,因此更加灵活,但是初始化的效率比较低。
其中:
- : Skip-Gram算法窗口大小(Skip-Gram window size
其中加权skip-gram的伪代码如下,目标是最大化所有序列中节点共现的概率。
三、代码实现
import torch as th
class EGES(th.nn.Module):
def __init__(self, dim, num_nodes, num_brands, num_shops, num_cates):
super(EGES, self).__init__()
self.dim = dim
# embeddings for nodes
base_embeds = th.nn.Embedding(num_nodes, dim)
brand_embeds = th.nn.Embedding(num_brands, dim)
shop_embeds = th.nn.Embedding(num_shops, dim)
cate_embeds = th.nn.Embedding(num_cates, dim)
# concat four embedding
self.embeds = [base_embeds, brand_embeds, shop_embeds, cate_embeds]
# weights for each node's side information
self.side_info_weights = th.nn.Embedding(num_nodes, 4)
#
def forward(self, srcs, dsts):
# srcs: sku_id, brand_id, shop_id, cate_id
srcs = self.query_node_embed(srcs)
dsts = self.query_node_embed(dsts)
return srcs, dsts
def query_node_embed(self, nodes):
"""
@nodes: tensor of shape (batch_size, num_side_info)
"""
batch_size = nodes.shape[0]
# query side info weights, (batch_size, 4)
side_info_weights = th.exp(self.side_info_weights(nodes[:, 0]))
# merge all embeddings
side_info_weighted_embeds_sum = []
side_info_weights_sum = []
# four embeddings
for i in range(4):
# weights for i-th side info, (batch_size, ) -> (batch_size, 1)
i_th_side_info_weights = side_info_weights[:, i].view((batch_size, 1))
# batch of i-th side info embedding * its weight, (batch_size, dim)
side_info_weighted_embeds_sum.append(i_th_side_info_weights * self.embeds[i](nodes[:, i]))
side_info_weights_sum.append(i_th_side_info_weights)
# stack: (batch_size, 4, dim), sum: (batch_size, dim)
side_info_weighted_embeds_sum = th.sum(th.stack(side_info_weighted_embeds_sum, axis=1), axis=1)
# stack: (batch_size, 4), sum: (batch_size, )
side_info_weights_sum = th.sum(th.stack(side_info_weights_sum, axis=1), axis=1)
# (batch_size, dim)
H = side_info_weighted_embeds_sum / side_info_weights_sum
return H
def loss(self, srcs, dsts, labels):
dots = th.sigmoid(th.sum(srcs * dsts, axis=1))
dots = th.clamp(dots, min=1e-7, max=1 - 1e-7)
return th.mean(- (labels * th.log(dots) + (1 - labels) * th.log(1 - dots)))
四、Experiments
4.1 offline evaluation
4.2 online A/B test
五、系统部署和Operation
六、离线评估
Embedding 的离线评估是很困难的。原文提出一种连接预测的方式,也就是把图中随机选一些有向边作为正例,随机构造一些不存在的有向边作为负例来测试模型。除了这种方式,还可以通过选择一些典型的 case 来将各维度的 embedding 进行可视化,或者召回相似列表,看看是否满足主观预期,但是这种方式可以用来理解和优化,不适合用于评估。
七、EGES训练慢的解决办法
- 第一条是我们可以把商品 Embedding 进行预训练,再跟其他 side information 特征一起输入 EGES,不用直接在 EGES 中加 Embedding 层进行 End2End 训练。
- 第二条是我们可以把商品进行聚类后再输入 EGES 网络,比如非常类似的商品,可以用一个商品聚类 id 替代,当作一个商品来处理。事实上,这种方法往往可以大幅减少商品数量的量级,AirBnb 就曾经非常成功地应用了该方法,用一些特征的组合来代替一类商品或用户,不仅大幅加快训练速度,而且推荐效果也没有受到影响。