该文由何向南教授团队于18年发表在IJCAI‘18,其核心思想在于使用元素外积来显式建模嵌入空间维度间的成对相关性。相较于用简单的连接或元素乘积结合<用户&项目>嵌入的模型相比,此举产生了一个更具表现力和语义合理性的二维交互图。且在此基础上,使用了卷积神经网络来学习嵌入维度间的高阶相关性。
Keywords: 协同过滤//卷积神经网络CNN//贝叶斯个性化排序BPR//隐式反馈
在当年的时代背景下,矩阵分解MF技术已经被充分研究,神经网络于推荐系统的应用正处于快速发展阶段。并且作者指出,MF模型设计中有一个本质缺陷(inherent limitaion),即其使用的交互函数是一个固定且与数据无关的函数(内积)。作者认为,MF的基础假设就存在缺陷,因为其假设嵌入维度彼此独立,且对所有数据点的预测贡献相等。这种假设并不符合实际,因为嵌入维度可以解释为项目的某种属性,这些属性并非独立的。因此,用MF来模拟丰富而复杂的真实世界反馈数据是次优的。
故,在这项工作中作者提出了一个新的NCF框架——ONCF架构,通过将嵌入维度之间的相关性集成到建模中。具体来说就是在嵌入层上使用外积运算,明确地捕获嵌入维度间的成对相关性。而由外积得到的相关矩阵称为相互作用图 (K × K) ,并使用卷积神经网络 (CNN) 来学习相互作用函数 (经验证,CNN比MLP更易概括和深入学习)。
实验部分主要回答三个问题:
实验设置
数据集:Yelp & Gowalla (pretreated)
预训练:使用 MF-BPR 预训练 ConvNCF 的嵌入层并允许将训练好的结果作为其初始化参数
评估方法: 该模型采用 leave-1-out 评估,其中评价指标为 HitRate & NDCG
基准方法:ItemPop & MF-BPR & MLP & JRL & NeuMF
通过实验,作者证明了使用外积来获得2D交互图的ONCF框架的作用,以及将CNN作为学习高阶相关性的优势。
源代码:Outer-Product based Neural Collabrative Filtering
该模型基于Pyhon2 & tensorflow1.7构建 (Linux),但Py2版本下的TF1.7已经无法从官方or镜像源获取,需要在Github自寻资源后本地安装。
FBI WARRNING: 经本人多次尝试后发现,Windows系统下无法同时安装Py2和TF1.7 (属于是踩了天坑),因此退而求其次安装了Python3.6 & TF1.7 (python & tensor匹配关系链接)。不可避免的,因为Py2与Py3底层代码的差异,我在跑程序的过程中遇到了各种报错和警告。下面,我将把改错的过程以及底层逻辑逐一列举,并展示修改后的代码。
Load data
# Series of loaddata functions
def load_negative_file(self, filename):
return negativeList
def load_raing_file_as_list(self, filename):
return ratingList
def load_raing_file_as_matrix(self, filename):
return uimatrix
def load_training_file_as_list(self, filename):
return trainList
MF_BPR Part
def shuffle(samples, batch_size, dataset, model):
# 此函数内会出现运行报错,主要原因来自于Py2和Py3的底层函数差异
···para define···
# before: _index = range(len(_user_input))
_index = list(range(len(_user_input))) # changed1
np.random.shuffle(_index) # 随机排序
num_batch = len(_user_input) // _batch_size
# before: res = pool.map(_get_train_batch, range(num_batch))
res = list(map(_get_train_batch, range(0, num_batch))) # changed2
···
return user_list,item_pos_list, user_dns_list, item_dns_list
'''1.numpy.random.shuffle函数在当前环境下的参数要求为list,而源代码中为range对象
2.map函数的用法差异[Py2: pool.map() return list/Py3: map() retrun map_object]'''
class GMF:
# 通过调用tf中模块化的函数构建一个BPR模型(详见源码)
······
def init_eval_model(model, dataset):
# 评估初始化模型
···para define···
# before: feed_dicts = pool.map(_evaluate_input, range(0, _dataset.num_users))
feed_dicts = list(map(_evaluate_input, range(0, _dataset.num_users))) # changed3
···
return feed_dicts
'''3.修改原因与2同,即map函数返回值类型有别'''
def _evaluate_input(user):
# 返回用于评估的用户和项目的输入
# before: item_input = dataset.testNegative[user]
item_input = _dataset.testNegative[user] # changed4
test_item = _dataset.testRating[user][1]
···
return user_input, item_input
'''4.源码漏掉_,包括NCF的源码在内,有一些非技术性错误,一度让我以为是故意的ovo?'''
ConvNCF Part
# 出问题的点与MF_BPR相同,此处不再赘述
def shuffle(samples, batch_size, dataset, model): line: 110
return user_list,item_pos_list, user_dns_list, item_dns_list
def init_eval_model(model, dataset): line: 460
return feed_dicts
def _evaluate_input(user): line: 480
return user_input, item_input
class ConvNCF:
# 核心部分是用用户项目嵌入的外积来创建交互图,并用CNN来提取信号
def _create_placeholders(self):
def _conv_weight(self, isz, osz):
return weight_variable([2, 2, isz, osz]), bias_variable([osz])
def _conv_layer(self, input, p):
return tf.nn.relu(conv+P[1])
def _create_variable(self):
'''以上三个函数控制卷积层的参数设置'''
def _create_inference(self, item_input):
with tf.name_scope("inference"):
# 分别得到P, Q的embedding
self.embedding_p = tf.nn.embedding_lookup(self.embedding_P, self.user_input)
self.embedding_q = tf.nn.embedding_lookup(self.embedding_Q, item_input)
# matmul(P_u, Q_i)取外积
self.relation = tf.matmul(tf.transpose(self.embedding_p, perm=[0, 2, 1]), self.embedding_q)
self.net_input = tf.expand_dims(self.relation, -1)
# CNN definition
self.layer = []
input = self.net_input
for p in self.P:
self.layer.append(self._conv_layer(input, p))
input = self.layer[-1]
# prediction
···
return self.embedding_p, self.embedding_q, self.output_layer
······
从文章理解的角度来说,其难点在于了解作者的如此构建模型的意图。文章的思想与NCF无二致,核心还是在于将矩阵分解与神经网络相结合,先利用嵌入外积构建交互图,并用CNN来学习它。若各位对NCF熟稔于心,那想必这篇文篇定会让你思如泉涌。
从代码部分来说,查阅多方资料后并修改源代码后,即可复现ConvNCF模型。但是该模型初始实验环境可能是基于Linux,因此在Windows下配置环境时会遇到较多困难。虽然 Py2 与 Py3 版本部分模块的差异性影响了实验进展,不过代码整体架构恰当,函数封装合理,结合文章叙述去理解代码相对顺利。
作者在文末提到,未来可能会引入更多先进的CNN模型,以此来提高ONCF框架下的实例模型的性能。此外,还会将ONCF拓展到基于内容的推荐中,比如项目信息除了ID外还有其他丰富的特征项。
在我看来,这个架构放在今天仍不失为一个优秀的框架。首先其模型结构清晰,有较强的理论支撑。再者,从作者提出的发展方向来看,可加入其它辅助信息来提高模型对用户项目交互的表达能力。