很久没看推荐系统相关的论文了,最近发现一篇2017年的论文,感觉不错。

原始论文 https://arxiv.org/pdf/1708.05031.pdf

网上有翻译了 https://www.cnblogs.com/HolyShine/p/6728999.html

git项目 https://github.com/hexiangnan/neural_collaborative_filtering

项目的主题框架如下:

代码是使用keras来实现的深度学习,其中GMF.py是传统的Matrix Factorization算法,关键代码分为两部分:

def get_model(num_users, num_items, latent_dim, regs=[0,0]):
# Input variables
user_input = Input(shape=(1,), dtype='int32', name = 'user_input')
item_input = Input(shape=(1,), dtype='int32', name = 'item_input') MF_Embedding_User = Embedding(input_dim = num_users, output_dim = latent_dim, name = 'user_embedding',
init = init_normal, W_regularizer = l2(regs[0]), input_length=1)
MF_Embedding_Item = Embedding(input_dim = num_items, output_dim = latent_dim, name = 'item_embedding',
init = init_normal, W_regularizer = l2(regs[1]), input_length=1) # Crucial to flatten an embedding vector!
user_latent = Flatten()(MF_Embedding_User(user_input))
item_latent = Flatten()(MF_Embedding_Item(item_input)) # Element-wise product of user and item embeddings
predict_vector = merge([user_latent, item_latent], mode = 'mul') # Final prediction layer
#prediction = Lambda(lambda x: K.sigmoid(K.sum(x)), output_shape=(1,))(predict_vector)
prediction = Dense(1, activation='sigmoid', init='lecun_uniform', name = 'prediction')(predict_vector) model = Model(input=[user_input, item_input],
output=prediction) return model

上述代码是构建模型结构,首先定义Input为一维多列的数据,然后是Embedding层,Embedding主要是为了降维,就是起到了look up的作用,然后是Merge层,将用户和物品的张量进行了内积相乘(latent_dim 表示两者的潜在降维的维度是相同的,因此可以做内积),紧接着是一个全连接层,激活函数为sigmoid。


下面是MLP.py的源码:

def get_model(num_users, num_items, layers = [20,10], reg_layers=[0,0]):
assert len(layers) == len(reg_layers)
num_layer = len(layers) #Number of layers in the MLP
# Input variables
user_input = Input(shape=(1,), dtype='int32', name = 'user_input')
item_input = Input(shape=(1,), dtype='int32', name = 'item_input') MLP_Embedding_User = Embedding(input_dim = num_users, output_dim = layers[0]/2, name = 'user_embedding',
init = init_normal, W_regularizer = l2(reg_layers[0]), input_length=1)
MLP_Embedding_Item = Embedding(input_dim = num_items, output_dim = layers[0]/2, name = 'item_embedding',
init = init_normal, W_regularizer = l2(reg_layers[0]), input_length=1) # Crucial to flatten an embedding vector!
user_latent = Flatten()(MLP_Embedding_User(user_input))
item_latent = Flatten()(MLP_Embedding_Item(item_input)) # The 0-th layer is the concatenation of embedding layers
vector = merge([user_latent, item_latent], mode = 'concat') # MLP layers
for idx in xrange(1, num_layer):
layer = Dense(layers[idx], W_regularizer= l2(reg_layers[idx]), activation='relu', name = 'layer%d' %idx)
vector = layer(vector) # Final prediction layer
prediction = Dense(1, activation='sigmoid', init='lecun_uniform', name = 'prediction')(vector) model = Model(input=[user_input, item_input],
output=prediction) return model

最重要的也是构建模型的部分,与GMF不同的有两个部分,首先是user_latent和item_latent的merge的部分,不再采用内积的形式,而是contract拼接的方式;再者就是for循环构建深层全连接神经网络,内部Layer的激活函数是relu,最后一层的激活函数仍然是sigmoid。


接下来是NeuMF.py,将MLP和GMF进行了融合,模型构建代码如下

def get_model(num_users, num_items, mf_dim=10, layers=[10], reg_layers=[0], reg_mf=0):
assert len(layers) == len(reg_layers)
num_layer = len(layers) #Number of layers in the MLP
# Input variables
user_input = Input(shape=(1,), dtype='int32', name = 'user_input')
item_input = Input(shape=(1,), dtype='int32', name = 'item_input') # Embedding layer
MF_Embedding_User = Embedding(input_dim = num_users, output_dim = mf_dim, name = 'mf_embedding_user',
init = init_normal, W_regularizer = l2(reg_mf), input_length=1)
MF_Embedding_Item = Embedding(input_dim = num_items, output_dim = mf_dim, name = 'mf_embedding_item',
init = init_normal, W_regularizer = l2(reg_mf), input_length=1) MLP_Embedding_User = Embedding(input_dim = num_users, output_dim = layers[0]/2, name = "mlp_embedding_user",
init = init_normal, W_regularizer = l2(reg_layers[0]), input_length=1)
MLP_Embedding_Item = Embedding(input_dim = num_items, output_dim = layers[0]/2, name = 'mlp_embedding_item',
init = init_normal, W_regularizer = l2(reg_layers[0]), input_length=1) # MF part
mf_user_latent = Flatten()(MF_Embedding_User(user_input))
mf_item_latent = Flatten()(MF_Embedding_Item(item_input))
mf_vector = merge([mf_user_latent, mf_item_latent], mode = 'mul') # element-wise multiply # MLP part
mlp_user_latent = Flatten()(MLP_Embedding_User(user_input))
mlp_item_latent = Flatten()(MLP_Embedding_Item(item_input))
mlp_vector = merge([mlp_user_latent, mlp_item_latent], mode = 'concat')
for idx in xrange(1, num_layer):
layer = Dense(layers[idx], W_regularizer= l2(reg_layers[idx]), activation='relu', name="layer%d" %idx)
mlp_vector = layer(mlp_vector) # Concatenate MF and MLP parts
#mf_vector = Lambda(lambda x: x * alpha)(mf_vector)
#mlp_vector = Lambda(lambda x : x * (1-alpha))(mlp_vector)
predict_vector = merge([mf_vector, mlp_vector], mode = 'concat') # Final prediction layer
prediction = Dense(1, activation='sigmoid', init='lecun_uniform', name = "prediction")(predict_vector) model = Model(input=[user_input, item_input],
output=prediction) return model

代码的前半部分分别是GMFe和MLP的内部layer构建过程,在 predict_vector = merge([mf_vector, mlp_vector], mode = 'concat')这一行开始对两者的输出进行了merge,方式为concat。最后包了一层的sigmoid。


 看完了构建模型的代码,下面关注几个细节

  1. 训练样本的正负比例如何设定?

    def get_train_instances(train, num_negatives):
    user_input, item_input, labels = [],[],[]
    num_users = train.shape[0]
    for (u, i) in train.keys():
    # positive instance
    user_input.append(u)
    item_input.append(i)
    labels.append(1)
    # negative instances
    for t in xrange(num_negatives):
    j = np.random.randint(num_items)
    while train.has_key((u, j)):
    j = np.random.randint(num_items)
    user_input.append(u)
    item_input.append(j)
    labels.append(0)
    return user_input, item_input, labels

    该函数是获取用户和物品的训练数据,其中num_negatives控制着正负样本的比例,负样本的获取方法也简单粗暴,直接随机选取用户没有选择的其余的物品。

  2. 保存了训练的模型,该怎么对数据进行预测?我们从evalute.py中的源码中可以得到答案
    def eval_one_rating(idx):
    rating = _testRatings[idx]
    items = _testNegatives[idx]
    u = rating[0]
    gtItem = rating[1]
    items.append(gtItem)
    # Get prediction scores
    map_item_score = {}
    users = np.full(len(items), u, dtype = 'int32')
    predictions = _model.predict([users, np.array(items)],
    batch_size=100, verbose=0)
    for i in xrange(len(items)):
    item = items[i]
    map_item_score[item] = predictions[i]
    items.pop() # Evaluate top rank list
    ranklist = heapq.nlargest(_K, map_item_score, key=map_item_score.get)
    hr = getHitRatio(ranklist, gtItem)
    ndcg = getNDCG(ranklist, gtItem)
    return (hr, ndcg)

    输入只要保证和训练的时候的格式一样即可,这里作者事先构建了negative的数据,也就是说对negative的物品和测试集合中的某一个物品进行了预测,最终选取topK的,来评测是否在其中(注getHitRatio函数不是最终结果,只是0/1) eval_one_rating 函数只是对测试集合中的某个用户的某个物品,以及和事先划分好的负样本组合在一起进行预测,最终输出该测试物品是否在topK中。

  3. Embedding 层的物品的latent_dim和用户的latent_dim是一致的,如果不一致是否可以?在实际中未必两者的维度是一致的,这里受限于keras的merge函数的参数要求,输入的数据的shape必须是一致的,所以必须是一致的。以及Merge中的mode参数,至于什么时候选择contact,什么时候选择mul,我觉得依赖于模型效果,在实际工程中选择使得最优的方式。
  4. python MLP.py --dataset ml-1m --epochs 20 --batch_size 256 --layers [64,32,16,8] 这是运行MLP的参数,layers的参数在逐渐减小,这也是深度神经网络的潜在设置,一般意义上越深的layer是对前面的更高层次的抽象。

【推荐系统】neural_collaborative_filtering(源码解析)的更多相关文章

  1. Java生鲜电商平台-电商中海量搜索ElasticSearch架构设计实战与源码解析

    Java生鲜电商平台-电商中海量搜索ElasticSearch架构设计实战与源码解析 生鲜电商搜索引擎的特点 众所周知,标准的搜索引擎主要分成三个大的部分,第一步是爬虫系统,第二步是数据分析,第三步才 ...

  2. [源码解析] 深度学习流水线并行Gpipe(1)---流水线基本实现

    [源码解析] 深度学习流水线并行Gpipe(1)---流水线基本实现 目录 [源码解析] 深度学习流水线并行Gpipe(1)---流水线基本实现 0x00 摘要 0x01 概述 1.1 什么是GPip ...

  3. [源码解析] NVIDIA HugeCTR,GPU 版本参数服务器 --(1)

    [源码解析] NVIDIA HugeCTR,GPU版本参数服务器 --(1) 目录 [源码解析] NVIDIA HugeCTR,GPU版本参数服务器 --(1) 0x00 摘要 0x01 背景 1.1 ...

  4. [源码解析] NVIDIA HugeCTR,GPU版本参数服务器--- (2)

    [源码解析] NVIDIA HugeCTR,GPU版本参数服务器--- (2) 目录 [源码解析] NVIDIA HugeCTR,GPU版本参数服务器--- (2) 0x00 摘要 0x01 总体流程 ...

  5. [源码解析] NVIDIA HugeCTR,GPU版本参数服务器---(3)

    [源码解析] NVIDIA HugeCTR,GPU版本参数服务器---(3) 目录 [源码解析] NVIDIA HugeCTR,GPU版本参数服务器---(3) 0x00 摘要 0x01 回顾 0x0 ...

  6. [源码解析] NVIDIA HugeCTR,GPU版本参数服务器--- (4)

    [源码解析] NVIDIA HugeCTR,GPU版本参数服务器--- (4) 目录 [源码解析] NVIDIA HugeCTR,GPU版本参数服务器--- (4) 0x00 摘要 0x01 总体流程 ...

  7. [源码解析] NVIDIA HugeCTR,GPU版本参数服务器--- (5) 嵌入式hash表

    [源码解析] NVIDIA HugeCTR,GPU版本参数服务器--- (5) 嵌入式hash表 目录 [源码解析] NVIDIA HugeCTR,GPU版本参数服务器--- (5) 嵌入式hash表 ...

  8. [源码解析] NVIDIA HugeCTR,GPU版本参数服务器--- (6) --- Distributed hash表

    [源码解析] NVIDIA HugeCTR,GPU版本参数服务器--- (6) --- Distributed hash表 目录 [源码解析] NVIDIA HugeCTR,GPU版本参数服务器--- ...

  9. [源码解析] NVIDIA HugeCTR,GPU 版本参数服务器---(7) ---Distributed Hash之前向传播

    [源码解析] NVIDIA HugeCTR,GPU 版本参数服务器---(7) ---Distributed Hash之前向传播 目录 [源码解析] NVIDIA HugeCTR,GPU 版本参数服务 ...

  10. [源码解析] NVIDIA HugeCTR,GPU 版本参数服务器---(8) ---Distributed Hash之后向传播

    [源码解析] NVIDIA HugeCTR,GPU 版本参数服务器---(8) ---Distributed Hash之后向传播 目录 [源码解析] NVIDIA HugeCTR,GPU 版本参数服务 ...

随机推荐

  1. [USACO08NOV]Cheering up the Cow

    嘟嘟嘟 这道题删完边后是一棵树,那么一定和最小生成树有关啦. 考虑最后的生成树,无论从哪一个点出发,每一条边都会访问两次,而且在看一看样例,会发现走第w条边(u, v)的代价是L[w] * 2 + c ...

  2. Python:numpy.newaxis

    x1[:,np.newaxis]:增维,转置 从字面上是插入新的维度的意思 demo1: 针对一维的情况 >>> b = np.array([1, 2, 3, 4, 5, 6]) & ...

  3. oracle 基本知识点

    //创建临时表空间create temporary tablespace test_temp tempfile 'E:\oracle\product\10.2.0\oradata\testserver ...

  4. 关于iframe的父页面调取子页面里的事件(父往子里传)

    在body里打开一个iframe页面,想要调取打开的这个页面里的函数: $('iframe')[0].contentWindow.sonFn(canshu); [0]:比如在index.html页面里 ...

  5. break,continue,return和exit的区别

    1.break break语句的使用场合主要是switch语句和循环结构. 在循环结构中使用break语句,就退出循环,接着执行循环结构下面的第一条语句. 如果在多重嵌套循环中使用break语句,当执 ...

  6. 升序排列的数组中是否存在A[i]=i

    #include<stdio.h> void equal(int a[],int N) { int i; ;i<N;i++) { if(i<a[i]) { printf(&qu ...

  7. ThinkPHP5.1中数据查询使用field方法数组参数起别名时遇到的问题

    首先数据库基本查询是没有问题的 <?php namespace app\index\controller; use think\Db; class Demo5 { //1.单条查询 public ...

  8. CTF-i春秋网鼎杯第二场misc部分writeup

    CTF-i春秋网鼎杯第二场misc部分writeup 套娃 下载下来是六张图片 直接看并没有什么信息 一个一个查看属性 没有找到有用信息 到winhexv里看一下 都是标准的png图片,而且没有fla ...

  9. python 集合总结

    ''' 集合:1:他是无序的,他是不重复的. 2,他里面的元素必须是可哈希的. int str bool ()但是它本身是不可哈希的. 3,集合不能更改里面的元素. 4,集合可以求交集,并集,差集,反 ...

  10. c++ 双向链表 的查找和删除

    #include <stdio.h> #include <stdlib.h> #include <string.h> #include <ctype.h> ...