作者 | 上杉翔二   责编 | 张文

头图 | CSDN 下载自视觉中国

今天来看看『推荐系统 + 知识图谱』,又会有哪些有趣的玩意儿呢。

Knowledge Graph

知识图谱是一种语义图,其结点(node)代表实体(entity)或者概念(concept),边(edge)代表实体/概念之间的各种语义关系(relation)。

一个知识图谱由若干个三元组(h、r、t)组成,其中 h 和 t 代表一条关系的头结点和尾节点,r 代表关系。

引入知识图谱进入推荐系统领域的优点在于:

  • 「精确性」:为物品 item 引入了更多的语义关系,可以深层次地发现用户兴趣

  • 「多样性」:提供了不同的关系连接种类,有利于推荐结果的发散,避免推荐结果局限于单一类型

  • 「可解释性」:连接用户的历史记录和推荐结果,从而提高用户对推荐结果的满意度和接受度,增强用户对推荐系统的信任。

但是知识图谱难以与神经网络直接结合,所以引出了「knowledge representation learning」,通过学习 entity 和 relation 的 embedding 之后,再嵌入到神经网络中。

embedding 方法主要可以分为 translational distance 方法和 semantic matching 方法两种,前者是学习从头实体到尾实体的空间关系变换(如 TransE 等系列),后者则是直接用神经网络对语义相似度进行计算

将其结合到推荐里面比较困难的地方仍然有:

  • 「图简化」 如何处理 KG 带来的多种实体和关系,按需要简化虽然可能会损失部分信息但对效率是必要的,如只对 user-user 或者 item-item 关系简化子图。

  • 「多关系传播」KG 的特点就是多关系,不过现有可以用 attention 来区分不同关系的重要性,为邻居加权。

  • 「用户整合」将角色引入图结构,由于 KG 是外部信号,但是否也可以将用户也融入为一种实体变成内在产物呢?

一般使用知识图谱有三种模式,如上图:

  • 依次学习:使用知识图谱特征学习得到实体向量和关系向量,然后将这些低维向量(TransR方法等),引入推荐系统再做后面的处理。即只把知识图谱作为一个 side info,多一维特征的处理方式。

  • 联合学习:将知识图谱特征学习和推荐算法的目标函数结合,使用端到端(end-to-end)的方法进行联合学习。即把知识图谱的损失也纳入到最后的损失函数联合训练。

  • 交替学习:将知识图谱特征学习和推荐算法视为两个分离但又相关的任务,使用多任务学习(multi-task learning)的框架进行交替学习。这样可以让 KG 和 RC 在某种程度上融合的更加深入。

在介绍论文之前,先简要看看一般学习知识图谱的方法,一般有几种如下的处理方式:

  • TransE:即使其满足 h + r ≈ t,尾实体是头实体通过关系平移(翻译)得到的,但它不适合多对一和多对多,所以导致 TransE 在复杂关系上的表现差。公式如下:


















  • TransH 模型:即将实体投影到由关系构成的超平面上。值得注意的是它是非对称映射

  • TransR 模型:该模型则认为实体和关系存在语义差异,它们应该在不同的语义空间。此外,不同的关系应该构成不同的语义空间,因此 TransR 通过关系投影矩阵,将实体空间转换到相应的关系空间。




























  • TransD 模型:该模型认为头尾实体的属性通常有比较大的差异,因此它们应该拥有不同的关系投影矩阵。此外还考虑矩阵运算比较耗时,TransD 将矩阵乘法改成了向量乘法,从而提升了运算速度。

  • NTN 模型:将每一个实体用其实体名称的词向量平均值来表示,可以共享相似实体名称中的文本信息。

接下来主要整理 2 篇论文,CKE 和 RippleNet。

CKE

论文:Collaborative Knowledge base Embedding

地址:https://www.kdd.org/kdd2016/papers/files/adf0066-zhangA.pdf

发自 16 年 KDD,将 KG 与 CF 融合做联合训练。

首先为了使用知识库,作者设计了三个组件分别从结构化知识,文本知识和视觉知识中提取语义特征,如上图中的右半部分,知识库的处理分别为:

结构化知识

知识库中的实体以及实体的联系。使用 TransR 提取物品的结构化信息(同时考虑 nodes 和 relations),它的结构如下图,对于每个元组(h,r,t),首先将实体空间中的实体向关系 r 投影得到到















,然后使














,能够使得头/尾实体在这个关系 r 下靠近彼此,使得不具有此关系r的实体彼此远离。

文本知识

对实体的文字性描述。用多层降噪自编码器提取文本表达(SDAE),图中写的是 Bayesian SDAE,意思就是让权重,偏置,输出层符合特定的正态分布,对视觉的处理也是一样的。

视觉知识

对实体的图片描述如海报等。用多层卷积自编码提取物品视觉表达(SCAE)

最后得到的 item 的表示为 offset 向量以及结构化知识,文本知识,图片知识的向量:

然后从知识库中提取的特征融合到 collabrative filtering 中去,即与左边的用户反馈结合起来一起做 CF 进行训练就可以了,训练损失函数会用 pair-wise 的偏序优化。

#TransR
def projection_transR_pytorch(original, proj_matrix):ent_embedding_size = original.shape[1]rel_embedding_size = proj_matrix.shape[1] // ent_embedding_sizeoriginal = original.view(-1, ent_embedding_size, 1)#借助一个投影矩阵就行proj_matrix = proj_matrix.view(-1, rel_embedding_size, ent_embedding_size)return torch.matmul(proj_matrix, original).view(-1, rel_embedding_size)

RippleNet

论文:RippleNet: Propagating User Preferences on the Knowledge Graph for Recommender Systems

地址:https://arxiv.org/abs/1803.03467

向来不同技术之间如果能融合的更深入,自然是能得到更好的信息。

Ripple Network 模拟了用户兴趣在知识图谱上的传播过程,整个过程类似于水波的传播,如上图从实体 Forrest Gump 开始一跳 hop1,二跳 hop2 做传播,同时权重递减。

模型图如下图,对于给定的用户 u 和物品 v,如何模拟用户兴趣在 KG 上的传播呢?

作者提出的方法就是将知识图谱中的每一个实体(h,r,t)都用用户历史的物品进行相似度计算:

v 是物品向量,r 是关系,h 是头节点,三者计算相似度(得到了图片中 Rh 后面的绿色方格)。然后用这个权重对该实体中的尾节点 t 加权就得到了第一跳/扩散的结果:

所有跳最后的用户特征为所有跳的总和,需要注意的是,Ripple Network 中没有对用户直接使用向量进行刻画,而是用用户点击过的物品的向量集合作为其特征(代码中也可以只使用最后的 o):




























实际上求和得到的结果可以视为 v 在 u 的一跳相关实体中的一个响应。该过程可以重复在 u 的二跳、三跳相关实体中进行,如此,v 在知识图谱上便以 V 为中心逐层向外扩散。最后再用用户特征计算对物品的相似度得到预测结果:















然后来看一下模型类的代码:这部分的代码分为:数据 input,得到嵌入特征,依次计算每一跳的结果并更新(按照公式依次计算),预测。最后是损失函数(由三部分组成)和训练、测试函数。

class RippleNet(object):    def __init__(self, args, n_entity, n_relation):        self._parse_args(args, n_entity, n_relation)        self._build_inputs()        self._build_embeddings()        self._build_model()        self._build_loss()        self._build_train()def _parse_args(self, args, n_entity, n_relation):        self.n_entity = n_entity        self.n_relation = n_relation        self.dim = args.dim        self.n_hop = args.n_hop        self.kge_weight = args.kge_weight        self.l2_weight = args.l2_weight        self.lr = args.lr        self.n_memory = args.n_memory        self.item_update_mode = args.item_update_mode        self.using_all_hops = args.using_all_hopsdef _build_inputs(self):        #输入有items id,labels和用户每一跳的ripple set记录        self.items = tf.placeholder(dtype=tf.int32, shape=[None], name="items")        self.labels = tf.placeholder(dtype=tf.float64, shape=[None], name="labels")        self.memories_h = []        self.memories_r = []        self.memories_t = []for hop in range(self.n_hop):#每一跳的结果            self.memories_h.append(                tf.placeholder(dtype=tf.int32, shape=[None, self.n_memory], name="memories_h_" + str(hop)))            self.memories_r.append(                tf.placeholder(dtype=tf.int32, shape=[None, self.n_memory], name="memories_r_" + str(hop)))            self.memories_t.append(                tf.placeholder(dtype=tf.int32, shape=[None, self.n_memory], name="memories_t_" + str(hop)))def _build_embeddings(self):#得到嵌入        self.entity_emb_matrix = tf.get_variable(name="entity_emb_matrix", dtype=tf.float64,                                                 shape=[self.n_entity, self.dim],                                                 initializer=tf.contrib.layers.xavier_initializer())        #relation连接head和tail所以维度是self.dim*self.dim        self.relation_emb_matrix = tf.get_variable(name="relation_emb_matrix", dtype=tf.float64,                                                   shape=[self.n_relation, self.dim, self.dim],                                                   initializer=tf.contrib.layers.xavier_initializer())def _build_model(self):        # transformation matrix for updating item embeddings at the end of each hop        # 更新item嵌入的转换矩阵,这个不一定是必要的,可以使用直接替换或者加和策略。        self.transform_matrix = tf.get_variable(name="transform_matrix", shape=[self.dim, self.dim], dtype=tf.float64,                                                initializer=tf.contrib.layers.xavier_initializer())# [batch size, dim],得到item的嵌入        self.item_embeddings = tf.nn.embedding_lookup(self.entity_emb_matrix, self.items)self.h_emb_list = []        self.r_emb_list = []        self.t_emb_list = []        for i in range(self.n_hop):#得到每一跳的实体,关系嵌入list            # [batch size, n_memory, dim]            self.h_emb_list.append(tf.nn.embedding_lookup(self.entity_emb_matrix, self.memories_h[i]))# [batch size, n_memory, dim, dim]            self.r_emb_list.append(tf.nn.embedding_lookup(self.relation_emb_matrix, self.memories_r[i]))# [batch size, n_memory, dim]            self.t_emb_list.append(tf.nn.embedding_lookup(self.entity_emb_matrix, self.memories_t[i]))#按公式计算每一跳的结果        o_list = self._key_addressing()#得到分数        self.scores = tf.squeeze(self.predict(self.item_embeddings, o_list))        self.scores_normalized = tf.sigmoid(self.scores)def _key_addressing(self):#得到olist        o_list = []        for hop in range(self.n_hop):#依次计算每一跳            # [batch_size, n_memory, dim, 1]            h_expanded = tf.expand_dims(self.h_emb_list[hop], axis=3)# [batch_size, n_memory, dim],计算Rh,使用matmul函数            Rh = tf.squeeze(tf.matmul(self.r_emb_list[hop], h_expanded), axis=3)# [batch_size, dim, 1]            v = tf.expand_dims(self.item_embeddings, axis=2)# [batch_size, n_memory],然后和v内积计算相似度            probs = tf.squeeze(tf.matmul(Rh, v), axis=2)# [batch_size, n_memory],softmax输出分数            probs_normalized = tf.nn.softmax(probs)# [batch_size, n_memory, 1]            probs_expanded = tf.expand_dims(probs_normalized, axis=2)# [batch_size, dim],然后分配分数给尾节点得到o            o = tf.reduce_sum(self.t_emb_list[hop] * probs_expanded, axis=1)#更新Embedding表,并且存好o            self.item_embeddings = self.update_item_embedding(self.item_embeddings, o)            o_list.append(o)        return o_listdef update_item_embedding(self, item_embeddings, o):        #计算完hop之后,更新item的Embedding操作,可以有多种策略        if self.item_update_mode == "replace":#直接换            item_embeddings = o        elif self.item_update_mode == "plus":#加到一起            item_embeddings = item_embeddings + o        elif self.item_update_mode == "replace_transform":#用前面的转换矩阵            item_embeddings = tf.matmul(o, self.transform_matrix)        elif self.item_update_mode == "plus_transform":#用矩阵而且再加到一起            item_embeddings = tf.matmul(item_embeddings + o, self.transform_matrix)        else:            raise Exception("Unknown item updating mode: " + self.item_update_mode)        return item_embeddingsdef predict(self, item_embeddings, o_list):        y = o_list[-1]#1只用olist的最后一个向量        if self.using_all_hops:#2或者使用所有向量的相加来代表user            for i in range(self.n_hop - 1):                y += o_list[i]# [batch_size],user和item算内积得到预测值        scores = tf.reduce_sum(item_embeddings * y, axis=1)        return scoresdef _build_loss(self):#损失函数有三部分        #1用于推荐的对数损失函数        self.base_loss = tf.reduce_mean(tf.nn.sigmoid_cross_entropy_with_logits(labels=self.labels, logits=self.scores))#2知识图谱表示的损失函数        self.kge_loss = 0        for hop in range(self.n_hop):            h_expanded = tf.expand_dims(self.h_emb_list[hop], axis=2)            t_expanded = tf.expand_dims(self.t_emb_list[hop], axis=3)            hRt = tf.squeeze(tf.matmul(tf.matmul(h_expanded, self.r_emb_list[hop]), t_expanded))            self.kge_loss += tf.reduce_mean(tf.sigmoid(hRt))#为hRt的表示是否得当        self.kge_loss = -self.kge_weight * self.kge_loss#3正则化损失        self.l2_loss = 0        for hop in range(self.n_hop):            self.l2_loss += tf.reduce_mean(tf.reduce_sum(self.h_emb_list[hop] * self.h_emb_list[hop]))            self.l2_loss += tf.reduce_mean(tf.reduce_sum(self.t_emb_list[hop] * self.t_emb_list[hop]))            self.l2_loss += tf.reduce_mean(tf.reduce_sum(self.r_emb_list[hop] * self.r_emb_list[hop]))            if self.item_update_mode == "replace nonlinear" or self.item_update_mode == "plus nonlinear":                self.l2_loss += tf.nn.l2_loss(self.transform_matrix)        self.l2_loss = self.l2_weight * self.l2_lossself.loss = self.base_loss + self.kge_loss + self.l2_loss #三者相加def _build_train(self):#使用adam优化        self.optimizer = tf.train.AdamOptimizer(self.lr).minimize(self.loss)        '''        optimizer = tf.train.AdamOptimizer(self.lr)        gradients, variables = zip(*optimizer.compute_gradients(self.loss))        gradients = [None if gradient is None else tf.clip_by_norm(gradient, clip_norm=5)                     for gradient in gradients]        self.optimizer = optimizer.apply_gradients(zip(gradients, variables))        '''def train(self, sess, feed_dict):#开始训练        return sess.run([self.optimizer, self.loss], feed_dict)def eval(self, sess, feed_dict):#开始测试        labels, scores = sess.run([self.labels, self.scores_normalized], feed_dict)        #计算auc和acc        auc = roc_auc_score(y_true=labels, y_score=scores)        predictions = [1 if i >= 0.5 else 0 for i in scores]        acc = np.mean(np.equal(predictions, labels))        return auc, acc

完整的逐行中文注释笔记在:https://github.com/nakaizura/Source-Code-Notebook/tree/master/RippleNet

关于多跳的实现

博主在读文章的时候,始终不明白多跳是怎么实现的,下面我们看看代码是怎么写:

#ripple多跳时,每跳的结果集def get_ripple_set(args, kg, user_history_dict):    print('constructing ripple set ...')# user -> [(hop_0_heads, hop_0_relations, hop_0_tails), (hop_1_heads, hop_1_relations, hop_1_tails), ...]    ripple_set = collections.defaultdict(list)for user in user_history_dict:#对于每个用户        for h in range(args.n_hop):#该用户的兴趣在KG多跳hop中            memories_h = []            memories_r = []            memories_t = []if h == 0:#如果不传播,上一跳的结果就直接是该用户的历史记录                tails_of_last_hop = user_history_dict[user]            else:#去除上一跳的记录                tails_of_last_hop = ripple_set[user][-1][2]#去除上一跳的三元组特征            for entity in tails_of_last_hop:                for tail_and_relation in kg[entity]:                    memories_h.append(entity)                    memories_r.append(tail_and_relation[1])                    memories_t.append(tail_and_relation[0])# if the current ripple set of the given user is empty, we simply copy the ripple set of the last hop here            # this won't happen for h = 0, because only the items that appear in the KG have been selected            # this only happens on 154 users in Book-Crossing dataset (since both BX dataset and the KG are sparse)            if len(memories_h) == 0:                ripple_set[user].append(ripple_set[user][-1])            else:                #为每个用户采样固定大小的邻居                replace = len(memories_h) < args.n_memory                indices = np.random.choice(len(memories_h), size=args.n_memory, replace=replace)                memories_h = [memories_h[i] for i in indices]                memories_r = [memories_r[i] for i in indices]                memories_t = [memories_t[i] for i in indices]                ripple_set[user].append((memories_h, memories_r, memories_t))return ripple_set

即创造了一个 ripple_set,这个 set 相当于就得到整个多跳应该访问到的节点,在每一跳里面都会为每个用户采样固定大小的邻居,然后存如到 set 中。所以在 model 模型的部分,可以直接遍历多跳计算。

程序员如何避免陷入“内卷”、选择什么技术最有前景,中国开发者现状与技术趋势究竟是什么样?快来参与「2020 中国开发者大调查」,更有丰富奖品送不停!

☞百度推出开发者搜索 Beta;雷军手机使用时长曝光;苹果败诉,电脑上可以模拟 iOS 系统 | 极客头条☞我们差点就用不上 Java 了!
☞印度永久封禁了微信、百度、TikTok 等 59 款中国 App……

当推荐系统遇见知识图谱会发生什么?相关推荐

  1. 深度融合 | 当推荐系统遇见知识图谱(三)

    NewBeeNLP原创出品 公众号专栏作者@上杉翔二 悠闲会 · 信息检索 本篇文章继续整理『推荐系统+知识图谱』这个lineup的后续,主要是2020年SIGIR,CIKM,RecSys的相关文章. ...

  2. 知识图谱论文阅读(九)【转】推荐系统遇到知识图谱之MKR模型

    文哥的学习笔记: https://www.jianshu.com/p/af5226c7fbbb 知识图谱特征学习在推荐系统中的应用步骤大致有以下三种方式: 依次训练的方法主要有:Deep knowle ...

  3. 论文浅尝 | 打通推荐系统与知识图谱: 第一个公开的大规模链接数据集合

    本文转载自:RUC智能情报站,知乎专栏链接:https://zhuanlan.zhihu.com/RucAIBox 前言:近年来,知识图谱(KB)被广泛应用于推荐系统(RS),但尚未有公开将推荐系统物 ...

  4. 推荐系统与知识图谱(2)

    推荐系统与知识图谱(2) Ref:如何将知识图谱特征学习应用到推荐系统? 将知识图谱作为辅助信息引入到推荐系统中可以有效地解决传统推荐系统存在的稀疏性和冷启动问题,近几年有很多研究人员在做相关的工作. ...

  5. 【十】推荐系统遇到知识图谱RippleNet

    RippleNet: Propagating User Preferences on the Knowledge Graph for Recommender Systems 代码: https://g ...

  6. 推荐系统和知识图谱论文推荐

    KDD2020 Graph-based Recommendation(基于图,不是知识图谱) 1. A Framework for Recommending Accurate and Diverse ...

  7. “直男变暖男”—— 当推荐系统遇上知识图谱

    本文转载自文旅记,详情可以扫描下方二维码: 作为一个文科生,深知文化娱乐.旅游休闲.市场营销等领域正在迎接推荐算法的洗礼,传统的分析.内容生产,势必会发生改变.因此笔者一直关注技术领域,斥巨资(狗头保 ...

  8. 如何将知识图谱特征学习应用到推荐系统?

    来源 | 微软研究院AI头条 将知识图谱作为辅助信息引入到推荐系统中可以有效地解决传统推荐系统存在的稀疏性和冷启动问题,近几年有很多研究人员在做相关的工作.目前,将知识图谱特征学习应用到推荐系统中主要 ...

  9. 领域应用 | 如何将知识图谱特征学习应用到推荐系统?

    本文转载自公众号:微软研究院AI头条. 编者按:在上周发表的"推荐算法不够精准?让知识图谱来解决"一文中,我们为大家介绍了日常生活中几乎每天都会用到的推荐系统,以及用来提高推荐系统 ...

最新文章

  1. 腾讯优图+厦门大学发布!2021十大人工智能趋势
  2. [react native] navigator过渡卡顿问题
  3. 小米2s屏幕_米家空气净化器3VS小米空气净化器2 成熟设计的优势
  4. 安装ArchLinux BIOS
  5. web flash rtmp_基于RTMP和WebRTC开发大规模低延迟(1000毫秒内)直播系统
  6. PHP数组的排序函数
  7. sql中字段名中包含特殊字符的查询方法
  8. 揭晓新版《Windows Sysinternals实战指南》读书积赞活动
  9. jqgrid mvc_jqGrid,REST,AJAX和Spring MVC集成
  10. 【Python】Python中打开文件那些事
  11. SpringBoot 3.0最低版本要求的JDK 17,这几个新特性不能不知道!
  12. 自动化集成:Pipeline整合Docker+K8S
  13. AD采样的平均值滤波
  14. opesnstack四部曲(1)---keystone
  15. 火影忍者服务器维护时间,火影手游12月12日更新:仙鸣增加破霸体,小自来也变蛙时间减半!...
  16. 进程状态-Linux ps命令详细参数
  17. Java文件上传数据库(并保存本地)、word转pdf并进行页面预览
  18. 原生js完成的五子棋人机大战
  19. 移动App | 个人隐私信息合规检测浅析及方法
  20. 云端服务器上传本地项目

热门文章

  1. 敏捷开发绩效管理之二:用中医理论管理团队及其绩效(绩效考核,团队管理,自组织团队)...
  2. 简单干净的C#方法设计案例:SFCUI.AjaxValue()之二
  3. python netsnmp与pysnmp
  4. JAVA基础知识(四):final关键字
  5. CocoaPods 基础知识--------安装 及 使用第三方库
  6. [C++对象模型][10]类型转化
  7. 主从模式在不同场景下的解释
  8. 下载m3u8加密视频的一些经验和小细节
  9. linux数据,Linux数据
  10. js 自动分配金额_(2.4w字,建议收藏)??原生JS灵魂之问(下), 冲刺??进阶最后一公里(附个人成长经验分享)