专栏首页相约机器人结合知识图谱实现基于电影的推荐系统

结合知识图谱实现基于电影的推荐系统

知识图谱(Knowledge Graph,KG)可以理解成一个知识库,用来存储实体与实体之间的关系。知识图谱可以为机器学习算法提供更多的信息,帮助模型更好地完成任务。

在推荐算法中融入电影的知识图谱,能够将没有任何历史数据的新电影精准地推荐给目标用户。

实例描述

现有一个电影评分数据集和一个电影相关的知识图谱。电影评分数据集里包含用户、电影及评分;电影相关的知识图谱中包含电影的类型、导演等属性。

要求:从知识图谱中找出电影间的潜在特征,并借助该特征及电影评分数据集,实现基于电影的推荐系统。

本实例使用了一个多任务学习的端到端框架MKR。该框架能够将两个不同任务的低层特征抽取出来,并融合在一起实现联合训练,从而达到最优的结果。有关MKR的更多介绍可以参考以下链接:

https://arxiv.org/pdf/1901.08907.pdf

一、准备数据集

在1901.08907.pdf的相关代码链接中有3个数据集:图书数据集、电影数据集和音乐数据集。本例使用电影数据集,具体链接如下:

https://github.com/hwwang55/MKR/tree/master/data/movie

该数据集中一共有3个文件。

  • item_index2entity_id.txt:电影的ID与序号。具体内容如图1所示,第1列是电影ID,第2列是序号。
  • kg.txt:电影的知识图谱。图2中显示了知识图谱的SPO三元组(Subject-Predicate-Object),第1列是电影ID,第2列是关系,第3列是目标实体。
  • ratings.dat:用户的评分数据集。具体内容如图3所示,列与列之间用“::”符号进行分割,第1列是用户ID,第2列是电影ID,第3列是电影评分,第4列是评分时间(可以忽略)。

二、预处理数据

数据预处理主要是对原始数据集中的有用数据进行提取、转化。该过程会生成两个文件。

  • kg_final.txt:转化后的知识图谱文件。将文件kg.txt中的字符串类型数据转成序列索引类型数据,如图4所示。
  • ratings_final.txt:转化后的用户评分数据集。第1列将ratings.dat中的用户ID变成序列索引。第2列没有变化。第3列将ratings.dat中的评分按照阈值5进行转化,如果评分大于等于5,则标注为1,表明用户对该电影感兴趣。否则标注为0,表明用户对该电影不感兴趣。具体内容如图5所示。

三、搭建MKR模型

MKR模型由3个子模型组成,完整结构如图6所示。具体描述如下。

  • 推荐算法模型:如图6的左侧部分所示,将用户和电影作为输入,模型的预测结果为用户对该电影的喜好分数,数值为0~1。
  • 交叉压缩单元模型:如图6的中间部分,在低层将左右两个模型桥接起来。将电影评分数据集中的电影向量与知识图谱中的电影向量特征融合起来,再分别放回各自的模型中,进行监督训练。
  • 知识图谱词嵌入(Knowledge Graph Embedding,KGE)模型:如图6的右侧部分,将知识图谱三元组中的前2个(电影ID和关系实体)作为输入,预测出第3个(目标实体)。

图6 MKR框架

在3个子模型中,最关键的是交叉压缩单元模型。下面就先从该模型开始一步一步地实现MKR框架。

1.交叉压缩单元模型

交叉压缩单元模型可以被当作一个网络层叠加使用。如图7所示的是交叉压缩单元在第l层到第l+1层的结构。图7中,最下面一行为该单元的输入,左侧的是用户评论电影数据集中的电影向量,右侧的是知识图谱中的电影向量。

图7 交叉压缩单元模型的结构

交叉压缩单元模型的具体处理过程如下:

(1)将与进行矩阵相乘得到。

(2)将复制一份,并进行转置得到。实现特征交叉融合。

(3)将经过权重矩阵进行线性变化(与矩阵相乘)。

(4)将经过权重矩阵进行线性变化。

(5)将(3)与(4)的结果相加,再与偏置参数相加,得到。将用于推荐算法模型的后续计算。

(6)按照第(3)、(4)、(5)步的做法,同理可以得到。将用于知识图谱词嵌入模型的后续计算。

用tf.layer接口实现交叉压缩单元模型,具体代码如下。

代码7-14 MKR

    import numpy as np
    import tensorflow as tf
    from sklearn.metrics import roc_auc_score
    from tensorflow.python.layers import base

    class CrossCompressUnit(base.Layer):                        #定义交叉压缩单元模型类
        def __init__(self, dim, name=None):
           super(CrossCompressUnit, self).__init__(name)
           self.dim = dim
           self.f_vv = tf.layers.Dense(1, use_bias = False) #构建权重矩阵
           self.f_ev = tf.layers.Dense(1, use_bias = False)
           self.f_ve = tf.layers.Dense(1, use_bias = False)
           self.f_ee = tf.layers.Dense(1, use_bias = False)
           self.bias_v = self.add_weight(name='bias_v',         #构建偏置权重
                                               shape=dim, 
                                               initializer=tf.zeros_initializer())         self.bias_e = self.add_weight(name='bias_e', 
                                               shape=dim, 
                                               initializer=tf.zeros_initializer())

        def _call(self, inputs):
            v, e = inputs                   #v和e的形状为[batch_size, dim]
            v = tf.expand_dims(v, dim=2)    #v的形状为 [batch_size, dim, 1]
            e = tf.expand_dims(e, dim=1)    #e的形状为 [batch_size, 1, dim]

            c_matrix = tf.matmul(v, e)#c_matrix的形状为 [batch_size, dim, dim]
            c_matrix_transpose = tf.transpose(c_matrix, perm=[0, 2, 1])
            #c_matrix的形状为[batch_size * dim, dim]
            c_matrix = tf.reshape(c_matrix, [-1, self.dim])
            c_matrix_transpose = tf.reshape(c_matrix_transpose, [-1, self.dim])

            #v_output的形状为[batch_size, dim]
            v_output = tf.reshape( 
                              self.f_vv(c_matrix) + self.f_ev(c_matrix_transpose),
                              [-1, self.dim]
                                     ) + self.bias_v

            e_output = tf.reshape(
                              self.f_ve(c_matrix) + self.f_ee(c_matrix_transpose),
                              [-1, self.dim]
                                      ) + self.bias_e
        #返回结果
        return v_output, e_output

代码第10行,用tf.layers.Dense方法定义了不带偏置的全连接层,并在代码第34行,将该全连接层作用于交叉后的特征向量,实现压缩的过程。

2.将交叉压缩单元模型集成到MKR框架中

在MKR框架中,推荐算法模型和知识图谱词嵌入模型的处理流程几乎一样。可以进行同步处理。在实现时,将整个处理过程横向拆开,分为低层和高层两部分。

—低层:将所有的输入映射成词嵌入向量,将需要融合的向量(图6中的v和h)输入交叉压缩单元,不需要融合的向量(图6中的u和r)进行同步的全连接层处理。

—高层:推荐算法模型和知识图谱词嵌入模型分别将低层的传上来的特征连接在一起,通过全连接层回归到各自的目标结果。

具体实现的代码如下。

代码7-14 MKR(续)

    class MKR(object):
        def __init__(self, args, n_users, n_items, n_entities, n_relations):
            self._parse_args(n_users, n_items, n_entities, n_relations)
            self._build_inputs()
            self._build_low_layers(args)    #构建低层模型
            self._build_high_layers(args)   #构建高层模型
            self._build_loss(args)
            self._build_train(args)

        def _parse_args(self, n_users, n_items, n_entities, n_relations):
            self.n_user = n_users
            self.n_item = n_items
            self.n_entity = n_entities
            self.n_relation = n_relations

            #收集训练参数,用于计算l2损失
            self.vars_rs = []
            self.vars_kge = []

        def _build_inputs(self):
            self.user_indices=tf.placeholder(tf.int32, [None], 'userInd')
            self.item_indices=tf.placeholder(tf.int32, [None],'itemInd')
            self.labels = tf.placeholder(tf.float32, [None], 'labels')
            self.head_indices =tf.placeholder(tf.int32, [None],'headInd')
            self.tail_indices =tf.placeholder(tf.int32, [None], 'tail_indices')
            self.relation_indices=tf.placeholder(tf.int32, [None], 'relInd')
        def _build_model(self, args): 
            self._build_low_layers(args)
            self._build_high_layers(args)

        def _build_low_layers(self, args):
            #生成词嵌入向量
            self.user_emb_matrix = tf.get_variable('user_emb_matrix',
                                                           [self.n_user, args.dim])
            self.item_emb_matrix = tf.get_variable('item_emb_matrix',
                                                           [self.n_item, args.dim])
            self.entity_emb_matrix = tf.get_variable('entity_emb_matrix',
                                                           [self.n_entity, args.dim])
            self.relation_emb_matrix = tf.get_variable('relation_emb_matrix',
                                                          [self.n_relation, args.dim])

            #获取指定输入对应的词嵌入向量,形状为[batch_size, dim]
            self.user_embeddings = tf.nn.embedding_lookup(
                                          self.user_emb_matrix, self.user_indices)
            self.item_embeddings = tf.nn.embedding_lookup(
                                          self.item_emb_matrix, self.item_indices)
            self.head_embeddings = tf.nn.embedding_lookup(
                                         self.entity_emb_matrix, self.head_indices)
            self.relation_embeddings = tf.nn.embedding_lookup(
                                  self.relation_emb_matrix, self.relation_indices)
            self.tail_embeddings = tf.nn.embedding_lookup(
                                       self.entity_emb_matrix, self.tail_indices)

            for _ in range(args.L):#按指定参数构建多层MKR结构
                #定义全连接层
                user_mlp = tf.layers.Dense(args.dim, activation=tf.nn.relu)
                tail_mlp = tf.layers.Dense(args.dim, activation=tf.nn.relu)
                cc_unit = CrossCompressUnit(args.dim)#定义CrossCompress单元
                #实现MKR结构的正向处理
                self.user_embeddings = user_mlp(self.user_embeddings)
                self.tail_embeddings = tail_mlp(self.tail_embeddings)
                self.item_embeddings, self.head_embeddings = cc_unit(
                                   [self.item_embeddings, self.head_embeddings])
                #收集训练参数
                self.vars_rs.extend(user_mlp.variables)
                self.vars_kge.extend(tail_mlp.variables) 
                self.vars_rs.extend(cc_unit.variables)
                self.vars_kge.extend(cc_unit.variables)

        def _build_high_layers(self, args):
            #推荐算法模型
            use_inner_product = True        #指定相似度分数计算的方式
            if use_inner_product:           #内积方式
                #self.scores的形状为[batch_size]
                self.scores = tf.reduce_sum(self.user_embeddings * self.item_embeddings, axis=1)
            else:
                #self.user_item_concat的形状为[batch_size, dim * 2]
                self.user_item_concat = tf.concat(
                           [self.user_embeddings, self.item_embeddings], axis=1)
                for _ in range(args.H - 1):
                    rs_mlp = tf.layers.Dense(args.dim * 2, activation=tf.nn.relu)
                    #self.user_item_concat的形状为[batch_size, dim * 2]
                    self.user_item_concat = rs_mlp(self.user_item_concat)
                    self.vars_rs.extend(rs_mlp.variables)
                #定义全连接层
                rs_pred_mlp = tf.layers.Dense(1, activation=tf.nn.relu)
                #self.scores的形状为[batch_size]
                self.scores = tf.squeeze(rs_pred_mlp(self.user_item_concat))
                self.vars_rs.extend(rs_pred_mlp.variables)  #收集参数
            self.scores_normalized = tf.nn.sigmoid(self.scores)

            #知识图谱词嵌入模型
            self.head_relation_concat = tf.concat(  #形状为[batch_size, dim * 2]
                      [self.head_embeddings, self.relation_embeddings], axis=1)
            for _ in range(args.H - 1):
                kge_mlp = tf.layers.Dense(args.dim * 2, activation=tf.nn.relu)
                #self.head_relation_concat的形状为[batch_size, dim* 2]
                self.head_relation_concat = kge_mlp(self.head_relation_concat)
                self.vars_kge.extend(kge_mlp.variables)

            kge_pred_mlp = tf.layers.Dense(args.dim, activation=tf.nn.relu)
            #self.tail_pred的形状为[batch_size, args.dim]
            self.tail_pred = kge_pred_mlp(self.head_relation_concat)
            self.vars_kge.extend(kge_pred_mlp.variables)
            self.tail_pred = tf.nn.sigmoid(self.tail_pred)

            self.scores_kge = tf.nn.sigmoid(tf.reduce_sum(self.tail_embeddings * self.tail_pred, axis=1))
            self.rmse = tf.reduce_mean(
                tf.sqrt(tf.reduce_sum(tf.square(self.tail_embeddings - self.tail_pred), axis=1) / args.dim))

代码第115~132行是推荐算法模型的高层处理部分,该部分有两种处理方式:

  • 使用内积的方式,计算用户向量和电影向量的相似度。有关相似度的更多知识,可以参考8.1.10小节的注意力机制。
  • 将用户向量和电影向量连接起来,再通过全连接层处理计算出用户对电影的喜好分值。

代码第132行,通过激活函数sigmoid对分值结果scores进行非线性变化,将模型的最终结果映射到标签的值域中。

代码第136~152行是知识图谱词嵌入模型的高层处理部分。具体步骤如下:

(1)将电影向量和知识图谱中的关系向量连接起来。

(2)将第(1)步的结果通过全连接层处理,得到知识图谱三元组中的目标实体向量。

(3)将生成的目标实体向量与真实的目标实体向量矩阵相乘,得到相似度分值。

(4)对第(3)步的结果进行激活函数sigmoid计算,将值域映射到0~1中。

3.实现MKR框架的反向结构

MKR框架的反向结构主要是loss值的计算,其loss值一共分为3部分:推荐算法模型模型的loss值、知识图谱词嵌入模型的loss值和参数权重的正则项。具体实现的代码如下。

代码7-14 MKR(续)

        def _build_loss(self, args):
            #计算推荐算法模型的loss值
            self.base_loss_rs = tf.reduce_mean(
                tf.nn.sigmoid_cross_entropy_with_logits(labels=self.labels, logits=self.scores))
            self.l2_loss_rs = tf.nn.l2_loss(self.user_embeddings) + tf.nn.l2_loss (self.item_embeddings)
            for var in self.vars_rs:
                self.l2_loss_rs += tf.nn.l2_loss(var)
            self.loss_rs = self.base_loss_rs + self.l2_loss_rs * args.l2_weight

            #计算知识图谱词嵌入模型的loss值
            self.base_loss_kge = -self.scores_kge
            self.l2_loss_kge = tf.nn.l2_loss(self.head_embeddings) + tf.nn.l2_loss (self.tail_embeddings)
            for var in self.vars_kge:   #计算L2正则
                self.l2_loss_kge += tf.nn.l2_loss(var)
            self.loss_kge = self.base_loss_kge + self.l2_loss_kge * args.l2_weight

        def _build_train(self, args):   #定义优化器
            self.optimizer_rs = tf.train.AdamOptimizer(args.lr_rs).minimize(self.loss_rs)
            self.optimizer_kge = tf.train.AdamOptimizer(args.lr_kge). minimize(self. loss_kge)

        def train_rs(self, sess, feed_dict):        #训练推荐算法模型
            return sess.run([self.optimizer_rs, self.loss_rs], feed_dict)

        def train_kge(self, sess, feed_dict):       #训练知识图谱词嵌入模型
            return sess.run([self.optimizer_kge, self.rmse], feed_dict)

        def eval(self, sess, feed_dict):            #评估模型
            labels, scores = sess.run([self.labels, self.scores_normalized], feed_dict)
            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

        def get_scores(self, sess, feed_dict):
            return sess.run([self.item_indices, self.scores_normalized], feed_dict)

代码第173、176行, 分别是训练推荐算法模型和训练知识图谱词嵌入模型的方法。因为在训练的过程中,两个子模型需要交替的进行独立训练,所以将其分开定义。

四、训练模型并输出结果

训练模型的代码在本书配套的“7-15 train.py”文件中,读者可以自行参考。代码运行后输出以下结果:

……
epoch 9    train auc: 0.9540  acc: 0.8817    eval auc: 0.9158  acc: 0.8407    test auc: 0.9155  acc: 0.8399

在输出的结果中,分别显示了模型在训练、评估、测试环境下的分值。

本文分享自微信公众号 - 相约机器人(xiangyuejiqiren),作者:代码医生

原文出处及转载信息见文内详细说明,如有侵权,请联系 yunjia_community@tencent.com 删除。

原始发表时间:2019-08-19

本文参与腾讯云自媒体分享计划,欢迎正在阅读的你也加入,一起分享。

我来说两句

0 条评论
登录 后参与评论

相关文章

  • 使用PyTorch进行表格数据的深度学习

    使用表格数据进行深度学习的最简单方法是通过fast-ai库,它可以提供非常好的结果,但是对于试图了解幕后实际情况的人来说,它可能有点抽象。因此在本文中,介绍了如...

    代码医生工作室
  • 创建自动滑雪模拟器

    关于自治代理,它们的应用和改进,有很多研究。所以在考虑自动驾驶汽车,它可以在没有任何碰撞的情况下在雪地上行驶。不幸的是,没有足够的资源和时间来构建一个真正的机器...

    代码医生工作室
  • 如何构建PyTorch项目

    自从开始训练深度神经网络以来,一直在想所有Python代码的结构是什么。理想情况下,良好的结构应支持对该模型进行广泛的试验,允许在一个紧凑的框架中实现各种不同的...

    代码医生工作室
  • python pyqt5弹出子窗体

    from PyQt5 import QtCore, QtGui, QtWidgets import sys from PyQt5.QtWidgets imp...

    用户5760343
  • 用Q-learning算法实现自动走迷宫机器人

    强化学习作为机器学习算法的一种,其模式也是让智能体在“训练”中学到“经验”,以实现给定的任务。但不同于监督学习与非监督学习,在强化学习的框架中,我们更侧重通过智...

    宜信技术学院
  • Objectiv-c - UICollectionViewLayout自定义布局-瀑布流Demo地址

    公开变量以及代理. 公开的变量是可以进行调用时设置,一般就为这些,delegate用来实现动态的高度设置

    gwk_iOS
  • CNN预测股票走势基于Tensorflow(思路+程序)

    前言 我们希望找出跟随价格上涨的模式。通过每日收盘价,MA,KD,RSI,yearAvgPrice 本次推文研究只是展示深入学习的一个例子。 结果估计不是很好。...

    量化投资与机器学习微信公众号
  • 用 Python 实现植物大战僵尸代码

    导读:以前很火的植物大战僵尸游戏,本想在网上找个Python版本游戏学习下,无奈没有发现比较完整的,那就自己来写一个吧。

    华章科技
  • Python《植物大战僵尸》代码实现:植物卡片选择和种植

    最近一直在给这个植物大战僵尸游戏添加新的植物和僵尸, 因为网上的图片资源有限,能加的植物和僵尸比较少, 目前进展如下。

    AI科技大本营
  • Python 还能实现图片去雾?FFA 去雾算法、暗通道去雾算法用起来! | 附代码

    Pytorch模块用来模型训练和网络层建立;其底层和Torch框架一样,但是使用Python重新写了很多内容,不仅更加灵活,支持动态图,而且提供了Python接...

    AI科技大本营

扫码关注云+社区

领取腾讯云代金券