首先,附上Github链接

LakeSoul:https://github.com/meta-soul/LakeSoul

导读

推荐系统是当前互联网产品中非常重要的组成部分。对于互联网平台来说,一个好的推荐场景不但可以快速提升点击率、互动率等短期业务指标,同时对于DAU、用户满意度、回访率等偏长期用户指标及用户体验都有好的助益。那么,你是否也想DIY一个类似的信息流推荐系统呢?

这篇文章会帮助读者基于MetaSpore平台一步一步构建一个属于自己的电影推荐系统,同时,整个系统具有很强的扩展性。我们实现了近年来在工业界经典的排序算法和召回算法,并且软件接口设计统一,无论在线还是离线相关算法只需要少量代码和配置修改就可以应用到实际的业务中。

关于MetaSpore

MetaSpore是由数元灵出品的开源一站式机器学习开发平台,提供从数据预处理、模型训练、离线实验、在线推理、在线应用框架的全流程框架和开发接口。我们希望用户可以在MetaSpore的通用组件的基础上,通过低代码的方式就可以快速创建集分布式机器学习训练、高性能模型推理、高可用AB实验框架等能力于一身的工业级AI系统。

MetaSpore 具有如下几个特点:

  • 一站式端到端开发,从离线模型训练到在线预测和分桶实验调试,全链路统一的开发体验;

  • 完善的深度学习训练框架,兼容 PyTorch 用户生态,支持分布式大规模稀疏特征学习

  • 训练框架与 PySpark 打通,无缝读取数据湖和数仓上的训练数据;

  • 高性能在线推理服务,支持神经网络、决策树、Spark ML、SKLearn 等多种模型;支持异构计算推理加速;

  • 在离线统一特征抽取框架,自动生成线上特征读取逻辑,统一特征抽取逻辑;

  • 在线算法应用框架,提供模型预测、实验分桶切流、参数动态热加载和可视化的 Debug 功能;

  • 丰富的行业算法示例和端到端完整链路解决方案。

同时,MetaSpore也是遵守Apache License 2.0的开源项目,我们在GitHub 项目中提供了标准案例与代码教程,项目的新功能和使用文档也正在持续丰富中。

1.典型推荐系统架构

一般来说,典型推荐系统会包括在线和离线两部分,如下图所示:

图中上半部分为系统的在线部分,包括用户建模、召回、排序、重排、取摘要等几个部分。这个部分一般使用Java、C++或者Python系统开发,好的系统设计能让算法服务高效且易扩展。当用户从手机端APP发起请求后,服务会经历这些部分的处理,最终给出展示的结果。

图中下半部分为系统的离线部分,一般来说这个部分主要是系统不同阶段的排序模型,比如:在召回阶段可能是用协同过滤或者基于图论的方法,甚至是基于神经网络的方法,做用户与物料之间或者候选物料之间中的相似度的计算与排序;而在排序、重排阶段,则一般是通过复杂机器学习模型方法(如超大规模在线学习的深度模型)建模最终业务指标并进行排序。大量日常优化工作会集中在离线的模型迭代上。

接下来的内容,我们会以MovieLens-1M数据集作为案例,围绕着:MetaSpore环境安装、数据处理与样本生成、召回算法、排序算法、在线系统与实验框架等5个部分来向读者展开一个工业级推荐系统的搭建过程一些重点的部分我们会有代码和图示,并且会在文章的最后附带我们在github代码链接。

那么我们一起开始探索推荐系统之旅吧!

2.MetaSpore环境安装

首先,可以到我们的代码仓库:MetaSpore项目(https://github.com/meta-soul/MetaSpore)中找到离线训练环境安装说明并安装和配置好模型训练和服务的基础环境。

其次,在这个Demo的项目里,我们使用MoiveLens-1M这个数据集来演示。这份数据集在推荐领域非常著名,可以从给出的官网链接(https://grouplens.org/datasets/movielens/1m/)中下载,并存储在您的云端/本地存储上。

最后,在MovieLens Demo项目offline目录中找到离线训练模型的使用说明,并训练好模型。最后需要在MovieLens Demo项目online目录中找到在线Java环境的安装说明,并运行相应的配置脚本。后面我们将在这一章的基础上一步一步搭建我们的系统,在您移步到其他章节之前,请您确保以上的环境已经在您的服务器上已经配置完成。

3.数据处理与样本生成

一般来说数据方面的工作,包括:日志清洗、样本处理、特征挖掘、实验效果追踪与分析等方面的工作,可能会占一个典型应用算法工程师70%的时间,甚至更多。而一个算法项目最终是否能取得线上业务指标的收益,数据方面的工作至关重要。我们这里是个Demo的原型系统,数据分析部分我们会针对MovieLens 1M的数据集做简要的分析,而特征方面的工作,由于和模型更紧密,我们会和对应的模型来一起说明,这里主要说明一下数据基础操作和样本生成的过程。

3.1数据分析

针对MovieLens 1M的数据,这个数据集中一共包含了6040个用户对3883部电影进行评价。我们在这里简要的做下数据分析:

首先,分析一下电影的评分的分布情况,通过下面的饼图可以看到,相对来说,用户其实是更倾向于给电影打高分的(评分>3),原因可能是用户更可能给自己喜欢电影进行点评;

其次,我们可以使用增强箱图来分析一下不同类型电影的流行度分布,横轴是电影的类型,纵轴是评价电影的用户数量;

最后,我们可以看一下用户观看电影的数量分布,可以发现大部分用评价的电影数量在100以内,但是也有不少用户评价电影的数量在100-200之间,整体来看用户行为并不是非常稀疏。

当然,真实场景中的数据分析要比上述示范的要复杂很多,并且每次迭代实验,还有不少实验结果归因的工作,这里就不一一展开了。

3.2数据集划分

对于一个机器学习系统来说,数据集划分是一个系统在设计最开始就要解决的问题,不同的划分方式,可能最终会带来不同的结果。一般来说,我们会有训练集/验证集/测试集的划分,对于线上的推荐系统而言,我们通常采用时间的划分方式,比如抽取前[-N, -2]天作为训练集,最后1天的数据随机抽取出测试集和验证集。

由于我们这里采用的是一个中等规模的电影数据集,数据量并不大,为了处理方便,针对召回、排序的过程并没有特殊的trick,我们这里采用Next-Item的方式:即对一个用户而言,前[-N, -2]个交互序列作为训练集,最后一个交互的电影数据作为测试集:

需要强调的是,样本划分的方式需要根据自己的业务和数据情况来确定,比如同样是对MovieLens-1M数据集进行划分,有的工作采用的随机划分,那么最终模型评估结果可能会有差异。

3.3正负样本划分

在我们进行模型训练之前,需要对数据样本进行划分,这里针对不同类型的模型,我们采用不同的划分方式。

3.3.1CTR模型

我们知道MovieLens数据只有用户评分,并没有典型的推荐场景那种(样本, 曝光,点击)这样的用户反馈数据。在这里,我们参考[1]中,对数据集进行划分的方式:

经过以上的数据划分,我们就可以对CTR模型的正负样本进行划分。以上处理方式较为简单,训练出的模型相对于真实场景来说预测较为容易,因为一般观看电影其实已经是用户对电影比较感兴趣了。

3.3.2双塔模型的样本生成

针对于双塔模型,参考YouTube[2],我们需要对样本进行负采样。对样本进行负采样的过程也非常tricky,比如近些年G家的文章在Youtube[3]和Google Play[4]上一直在迭代负采样的策略,重点是解决Batch内负采样的过程,这对于超大规模数据集或者在线学习的方式是很重要的。

由于MovieLens-1M的数据集较小,我们这里采用较为简单的全局负采样的方法。因而我们的采样过程如下:用户观看过的电影作为正样本,用户未观看过的电影作为负样本做全局采样,全局负采样的的采样概率使用的经验公式为

\frac{f(movie_i)^{\frac{3}{4}}}{\sum_{j}f(movie_j)^{\frac{3}{4}}}

其中f(movie_i)是电影moive_i在样本集合中出现的概率。有了采样概率,我们就可以使用PySpark的RDD编程API,通过Map-Reduce的方式来操作采样过程negative_sampling():

def negative_sampling(spark, dataset, user_column='user_id', item_column='movie_id', time_column='timestamp', \negative_item_column='trigger_item_id', negative_sample=3):        # sampling distributionitem_weight, _, _ = gen_sample_prob(dataset, item_column)items_dist = item_weight.select('movie_id', 'sampling_prob')\.rdd.map(lambda x: (x[0], x[1])).collect()zipped_dist = [list(t) for t in zip(*items_dist)]item_list, dist_list = zipped_dist[0], zipped_dist[1]dist_list = np.asarray(dist_list).astype('float64')dist_list = list(dist_list / np.sum(dist_list))## broadcastdist_list_br = spark.sparkContext.broadcast(dist_list)# generate sampling dataframesampling_df=dataset.rdd\.map(lambda x: (x[user_column], [x[item_column]]))\.reduceByKey(lambda x, y: x + y)\.map(lambda x: (x[0], sample(x[0], x[1], item_list, dist_list_br.value, negative_sample)))\.flatMapValues(lambda x: x)\.map(lambda x: (x[0], x[1][0], x[1][1]))\.toDF([user_column, negative_item_column, item_column])return sampling_df

以上示例代码主要可以大致分为以下几步:

  • 首先,通过一个标准的MapReduce过程,将每个用户曾经观看过的电影列表汇聚起来;

  • 其次,在一个Map的过程中,调用sample()函数,使用numpy.choice()的API,对负样本按照前面生成的采样概率进行采样,与用户曾经观看过的电影集合取差集;

  • 最后,经过flatMapValues、map的操作,将上述采样结果,以(userId,positiveMovie, negativeMoive)这样方式组织起来。

需要说明的是,由于电影的数据集并不大,我们这里使用了broadcast,这样每个电影的采样概率会传送到每个计算节点上,减少了不必要的通信成本。

4.召回算法的实现

召回算法是一个推荐系统的起始点,一般说来我们服务器的资源永远是有限的,同时用户对系统响应时长的耐心也是有限的,而召回系统就是从几百万~几十亿这个量级的物料集合中,快速淘汰那些与用户需求并不匹配的物料,而让剩余相关的物料进入到系统的之后处理流程中。这个部分我们会在本章中详细介绍,虽然这些模型可能已经出现了很长一段时间了,但在文献[5]的对比测试中发现,这些方法依然是非常强的baseline模型,在实际的工业级推荐系统中也是不容忽视的存在。

此外,近些年随着深度网络的推陈出新,使用神经网路做召回的工作也不断涌现出来,我们这里也设计一个双塔模型,方便大家一起了解里面的原理。当然还有很多其他的召回方式,比如全局热门、基于人口属性、基于地理位置、基于重定向等,但是这些方法一般来说技术复杂度不高,这里就不花时间介绍了。在这一章中,我们分别会对ItemCF、Swing、双塔等三类模型模型进行介绍。

Item CF

Item CF的方法是最经典的协同过过滤的算法之一,在[6]中被提出之后,由于设计简单、易于并行计算、效果较好等特点,在推荐场景中,特别是电商推荐场景中一直扮演重要的角色,堪称推荐领域的AK47自动步枪。这里采用[7]中给出的商品相似度计算公式:

w_{i, j} = \frac{\sum_{u \in U_i \cap U_j} w^2_u}{\sqrt{\sum_{u \in U_i} w^2_u} \sqrt{\sum_{v \in U_j} w^2_v}},w_u = \frac{1}{\sqrt { | I_u|}}, w_v = \frac{1}{\sqrt{|I_v|}}

\text{where } |I_u| \text{ is the product of user clicks/purchases}

针对于有过点击、加购、收藏、购买行为的商品之间,采用这个公式计算sim(i, j)。当然,CF的计算方法被研究的很多,即使计算相似度,也有多种方式,包括:Vector Cosine Similarity,Jaccard Coefficients,Correlation-Based Similarity等几种方式,这里不一一赘述。

使用PySpark,对上述的计算公式可以通过以下代码进行计算:

def item_cf_transform(self, dataset):# compute user weight follow w_u = 1 / \sqrt{|I_u|} user_weight = self._cf_compute_user_weight(dataset)# compute crossing weight matrix for all users that click/purchase item_i and item_jcrossing_weight = self._cf_compute_crossing_weight(dataset, user_weight)       # compute item l2 norm follow l2_norm = \sqrt{\sum_{u \in U_i} w_u^2}item_l2_norm = self._cf_compute_item_l2_norm(dataset, user_weight)# rename column for spark join sqlt2 = item_l2_norm.withColumnRenamed('weight', 'normal_weight_i')t3 = item_l2_norm.withColumnRenamed('weight', 'normal_weight_j')## sparse inner product inner_product = crossing_weight.groupby('item_id_i', 'item_id_j') \.agg(F.sum(F.col('weight') * F.col('weight')).alias('weight_sum')) ## penalized by the l2 normcossine_similarity = inner_product.alias('t1')\.join(t2.alias('t2'), on=(F.col('t1.item_id_i')==F.col('t2.item_id'))) \.join(t3.alias('t3'), on=(F.col('t1.item_id_j')==F.col('t3.item_id'))) \.withColumn('weight', F.col('t1.weight_sum')/(F.col('t2.normal_weight_i') * F.col('t3.normal_weight_j')))## collect the top k listresult = cossine_similarity.withColumn("rn", F.row_number().over(Window.partitionBy('item_id_i').orderBy(F.desc('weight')))) \.filter(f"rn <= %d"%self.max_recommendation_count)  \.groupBy('item_id_i') \.agg(F.collect_list(F.struct(F.col('item_id_j').alias('_1'), F.col('weight').alias('_2'))).alias(self.value_column_name)) \.withColumnRenamed('item_id_i', self.key_column_name)return result

上述代码分为基本是对上述计算公式拆解:

  • 首先,计算每个用户对行为数量,并根据每个用户交互行为数量,对用户权重进行discounting:w_u = 1 / \sqrt{|I_u|}

  • 其次,对于任意一个物品ij,我们计算用户w_u和在他们相似度分子上的贡献  ;

  • 最后,使用上述两步计算的结果,通过Join->Groupby->Sum->Select Top K等一系列的Map-Reduce操作,计算物品之间的相似度。

在ItemCF I2I的相似度矩阵计算完成后,我们会推送到高速缓存中,这里MetaSpore选用的是MongoDB,当然在实际的应用中,也可以根据自己的需要选用Redis或者Cassandra。

Swing

作为一个特殊的Item CF的算法,Swing算法在[7]中被提出,在淘宝电商场景中,根据[7]中给出的实际业务对比结果,相比于前面所述的ItemCF的方法而言,在Precision、Recall、MAP等3种不同的衡量指标下都有大幅提升。当然,具体效果,有场景的数据有关,在淘宝的场景中,用户行为是非常稀疏的,因而这种基于二部图的算法显得非常具有优势。具体的计算公式:

s(i, j) = \sum_{u \in U_i \cap U_j} \sum_{v \in U_i \cap Uj} w_u \cdot w_v \cdot \frac{1}{\alpha + \vert I_u \cap I_v \vert}

\text{where } w_u = \frac{1}{\sqrt{\vert I_u \vert}} \text{, } w_v = \frac{1}{\sqrt{\vert I_v \vert}}

在Spark中的实现过程,如下图所示。由于Swing算法的计算量较大,为了提升运行效率,这里我们采用Scala对算法进行实现:

  • 首先,通过一个Map-Reduce的过程,将用户和之前交互过的物料组织起来成:(u_i: i_{i1}, i_{i2}, ...)这样的数据形式;

  • 其次,再通过一个Mapper,将数据整理成(i_{11}: u_1, i_{1,2}, ...i_{1n})这样的数据形式,方便后续Reducer的计算过程s(i, j)

  • 最后,再通过一个Reducer,按照上述数据按照的Swing的公式进行计算,根据用户和商品交互数量计算出用户的权重,最终计算得到。

Swing I2I的相似度矩阵的处理过程和Item CF,我们会在计算完成后将结果推送到MongoDB中。

双塔模型介绍

双塔模型,在[2]中被应用于推荐领域之后,就一直在推荐场中作为一个非常重要的召回方式,除此之外,也有将双塔模型作为粗排的模型来使用。它的工作原理,如下图所示:

  • 首先,User的特征特征通过几层NN+ReLU的结构,形成Query Embeddingm向量u,这部分网络结构可以称为User塔;

  • 其次,Item侧的特征经过几层NN+ReLU结构形成的Item Embedding向量v,这部分网络结构可以称为Item塔;

  • 再次,双塔的计算结果uv经过内积操作,并可以选取不同的Loss对模型进行训练,比如我们这里会使用SimpleX模型[8]中一个特殊的Loss函数;

  • 最后,User塔的模型会加载到MetaSpore Serving中,而Item塔在离线进行推理,结果Embeding会加载到Milvus或者Faiss等向量检索引擎中。

在这里,我们使用[8]中提出的一个CCL(Cosine Contrastive Loss):

\mathcal{L}_{CCL} (u, i) = (1 - \hat{y}_{ui}) + \frac{w}{\mathcal{\vert N \vert}} \sum_{j \in \mathcal{N}} \max(0, \hat{y}_{uj} - m)

其中,y_{ui}是时正样本中User塔和Item塔的Embedding的内积,y_{ui}则是负样本的两塔内积操作的结果,\mathcal{N}则是负样本采样的集合,wm是可以调整的超参数。

SimpleX模型结构简单,只考虑了User和Item交互的序列,而不考虑其他的特征,非常方便演示MetaSpore在双塔模型上的应用。而模型在实际的实现中,参考文章中的网络结构,可以将正样本和负样本分别拆开计算,而不影响上述公式的计算。模型的网络结构较为简单,关于如何在MetaSpore中定义网络结构我们在排序一章中有详细定义,这里重点说明一下基于MetaSpore的实现的CCL ( Cosine Contrastive Loss ):

class SimpleXAgent(ms.PyTorchAgent):def cosine_contrastive_loss(yhat, y, nsc, w, m):z = yhat-mz[z<0] = 0loss_vector = y*(1-yhat) + (1-y)*(w/nsc*z)return torch.sum(loss_vector)def compute_loss(self, predictions, labels):loss = self.cosine_contrastive_loss(predictions, labels, self._negative_sample_count, self._w, self._m)\/ labels.shape[0] \* (1+self._negative_sample_count)return loss...

此外,在MetaSpore中,我们需要对数据集的特征列、交叉特征组合进行简单定义。比如,以我们的MovieLens-1M中,特征列定义如下:

最左侧是我们的特征列的说明,右侧作为模型的交叉特征,我们这里有三组,一组是User特征、一组是用户与Item交互特征,一组是Item特征,由于我们使用的是SimpleX模型,即只使用Id特征,所以这里定义的较为简单,在实际的应用中,可以还会有各种的上下文特征以及组合特征。

模型完成训练后,用户塔的模型会自动导出成ONNX格式到云存储上。线上应用时,在线上根据用户的特征实时对用户塔做推理,然后根据推理得到的User Embedding到向量检索引擎中进行搜索,得到最终的召回结果。

5.排序算法的实现

这个部分正是大量应用各种复杂模型的部分,这个部分也是各类信息检索、推荐系统相关顶级刊物上受关注的最多的领域之一。特别是近些年以来,由于神经网络模型的大行其道,所以排序的工作近些年也涌现出了不少相关的算法,比如在[9]中,就对这些模型在Criteo、MovieLens等典型数据集上做了广泛的对比。常见的神经网络排序模型有Wide & Deep[10]、DeepFM[11]、xDeepFM[12]、Deep & Cross[13][14]等,这里就不一一列举了。与此同时,经典的排序模型,比如基于Logistic Regreesion、LightGBM[15]、XGBoost[16]的排序算法,依然是很多公司中比较强的baseline。此外,大型的互联网公司中,遇到流量洪峰的时候,往往会对模型进行降级,而这些传统的基于统计学习的模型在这个时候则是一种非常好的选择。

Wide & Deep模型

在这里,我们是使用CTR来建模排序问题,所以使用Cross Entropy作为模型的Loss。在MetaSpore平台中,针对于这类模型,我们只需要使用原生的PyTorch代码,对网络结构进行简单定义,在调用的时候,我们只需要对特征、学习率、训练轮数等一些超参数进行定义,就可以进行模型训练了。这里,我们根据[10]中对Wide & Deep模型的描述,我们可以如下定义我们的神经模型的结构:


class WideDeep(torch.nn.Module):def __init__(self,use_wide=True,wide_embedding_dim=10,deep_embedding_dim=10,wide_column_name_path=None,wide_combine_schema_path=None,deep_column_name_path=None,deep_combine_schema_path=None,dnn_hidden_units=[1024,512,1],**kwargs):super().__init__()self.use_wide = use_wideif self.use_wide:self.lr_sparse = ms.EmbeddingSumConcat(wide_embedding_dim,wide_column_name_path,wide_combine_schema_path)self.dnn_sparse = ms.EmbeddingSumConcat(deep_embedding_dim,deep_column_name_path,deep_combine_schema_path)self.dnn_sparse.updater = ms.FTRLTensorUpdater()self.dnn_sparse.initializer = ms.NormalTensorInitializer(var=0.01)self.dnn = MLPLayer(dnn_hidden_units, deep_embedding_dim, self.dnn_sparse.feature_count)self.bias = torch.nn.Parameter(torch.zeros(1), requires_grad=True)self.final_activation = torch.nn.Sigmoid()def forward(self, x):if self.use_wide:      wide_out = self.lr_sparse(x)wide_out = torch.sum(wide_out, dim=1, keepdim=True)dnn_out = self.dnn_sparse(x)dnn_out = self.dnn(dnn_out)final_out = torch.add(wide_out, dnn_out) if self.use_wide else dnn_out + self.biasreturn self.final_activation(final_out)class MLPLayer(torch.nn.Module):def __init__(self,hidden_lay_unit,embedding_size,feature_dim):super().__init__()dense_layers=[]dnn_linear_num=len(hidden_lay_unit)dense_layers.append(ms.nn.Normalization(feature_dim*embedding_size))dense_layers.append(torch.nn.Linear(feature_dim*embedding_size, hidden_lay_unit[0]))dense_layers.append(torch.nn.ReLU())for i in range(dnn_linear_num - 2):dense_layers.append(torch.nn.Linear(hidden_lay_unit[i], hidden_lay_unit[i + 1]))dense_layers.append(torch.nn.ReLU())dense_layers.append(torch.nn.Linear(hidden_lay_unit[-2], hidden_lay_unit[-1]))self.dnn = torch.nn.Sequential(*dense_layers)def forward(self,inputs):return self.dnn(inputs)

MetaSpore的优势在于可以非常灵活的定义神经网络的结果,但是这里有几点需要说明一下:

  • 我们使用metaspore.EmbeddingSumConcat来定义系稀疏特征的Embedding,一般来说大规模稀疏特征需要使用到类似于Parameter Server的分布式训练架构,而在MetaSpore中,只需要一行对对稀疏特征的操作就拥有这种能力;

  • 这里定义了两个稀疏的Embeeding变量,分别是lr_sparse和dnn_sparse,这两个变量需要特殊的关注,在进行线上推理的时候,需要将这两个变量中所使用的特征显式的定义赋值;

  • 此外,我们也需要对数据集的特征列、交叉特征组合进行简单定义,类似于前文中的SimpleX模型中的定义,这里就不赘述。

在训练过程结束之后,我们会将模型自动以ONNX格式导出到云存储上,当导出结束后,MetaSpore Serving会对模型进行发现并加载到内存中等待线上的服务请求。

LightGBM模型

关于LightGBM模型如何做CTR预测,互联网上能找到的资料较多。MetaSpore并没有对LightGBM等经典的机器学习模型做封装,使用原生的LightGBM或者基于Spark环境的LightGBM。训练完模型之后,需要手动导出为ONNX格式的模型,MetaSpore可以很方便的对其提供Real time inference服务


def convert_model(lgbm_model: LGBMClassifier or Booster, input_size: int) -> bytes:initial_types = [("input", FloatTensorType([-1, input_size]))]onnx_model = convert_lightgbm(lgbm_model, initial_types=initial_types, target_opset = 9)return onnx_mode

重排与多样性

一般排序的部分可能建模的是点击或者转化,亦或者浏览时长等单一的指标。但是,在推荐系统迭代到一定时间之后,业务推进会进入深水区,就不能只看单一指标来论成败,往往面临并不是单一的业务指标,可能是多个业务指标需要提升的压力,比如:多样性,新颖性,冷启动,同时也可能是要考虑到用户体验到问题。所以,这个地方也是近些年一个研究和探索的热点,也有不少工作是结合了深度强化学习来对这些问题建模和求解。由于本文是面临初阶使用者,是帮助读者重点了解整个推荐体系,并借助MetaSpore快速支撑推荐场景业务,所以在这个版本的文档中,对这个领域涉及较少。在线上处理的部分增加了两个多样性的算子,包括一种基于MMR(Maximal Marginal Relevance)[17],同时考虑相关性和多样性的情况进行重排。线上可以根据需要在系统的实验框架中通过参数配置指定不同的打散算子。

6.在线系统与实验框架

截至目前,我们介绍的内容还没有脱离数据和算法的范畴,但是推荐系统内涵远不止这些,也包括我们如何让这个系统效率更高、更易扩展、设计更优美,更包括我们如何设计实验、快速进行实验部署,在这一章会对这部分内容进行说明。

6.1算法Pipeline

在前面的章节里,我们基于MetaSpore平台离线训练了一系列的离线模型。在这里,我们会基于之前已经建立好的离线算法,建立起一套处理的算法框架,就是推荐系统架构图中展示的UserModel->Match->Rank->ReRank->Summary这个标准的处理范式。我们这里会涉及到一些在线的算法策略,包括:用户建模中的用户侧特征组织,多路召回结果的融合,排序和重排的融合,同时也会支撑各类算法模型的一些基础服务,包括:用于缓存离线结果的MongoDB,用于做向量检索的Milvus,用于各类模型线上推理的MetaSpore Serving。整体这套在线的工程框架是基于SpringBoot+K8S开发的,也方便大家部署到自己的服务器上,这些工作会在这一章中进行阐述。

用户画像

推荐系统接收到前端请求之后,会根据前端的userId或者sessionId到高速缓存服务中查找用户特征,包括:age、gender、purchase level、LBS、preference、realtime behaviors等信息,构建用户画像。后续召回、排序等各个模型会截取自己需要的用户特征,作为模型的输入参数。

在MovieLens-1M数据集中,用户特征可以使用的较为有限,也没有实时的用户反馈,这里只是将userId、gender、age等信息从Mongo中取出,并将用户近期点击的movie_id作为序列特征处理。

多路召回

召回部分主要考虑的是在海量的数据中,如何快速的找到和用户相关的推荐结果。以我们的电影推荐系统为例,由于不同模型做召回时,所倚重侧重点不同。所以单独使用一种模型做召回时,往往得到的召回电影类型不够全面。这个时候就要并行使用多种模型进行召回,称之为多路召回。但是不同召回模型的评分标准不同,不能直接混合在一起排序,这个时候就需要对多路召回的结果进行融合。

我们采用的方法是为每一路召回模型设定一个 ALGO_LEVEL,不同的模型 ALGO_LEVEL 可以相同。对于某个召回模型的一次召回结果,我们对召回的电影列表做 归一化,然后用归一化得分(0~1之间) 加上该模型的 ALGO_LEVEL 作为该item最终召回分数。

最后根据最终分数,对多路召回结果进行统一排序、截断等操作,传入到下游的 Rank 层。可以看出,使用这种融合策略,ALGO_LEVEL大的模型召回的电影最终一定会排在 ALGO_LEVEL 小的模型之前,而相同ALGO_LEVEL的模型召回的结果会彼此交错融合。

排序因子的融合

排序的融合是指,对于某个排序模型的一次调用MetaSpore Serving行为之后,会返回一个带有CTR模型rankScore的电影列表,而对这个列表的最终排序,参考的不仅仅是CTR的得分,还可能参考它上游模型的得分。举例来说:

  • 策略一,是仅使用CTR模型进行排序;

  • 策略二,是使用召回阶段得分和CTR模型得分加权之后的结果进行排序:

(1 + \text{matchScore})^\alpha \times (1 + \text{ctrScore})^\beta

这里的排序策略,以及 alpha,beta 等权重,我们可以很容易在实验框架中进行配置。

8.2实验配置说明

在MetaSpore中,我们基于SpringBoot的技术方案,打造了一套Experiment Pipeline的基础框架,方便大家做实验的分层、分桶、实验参数热加载,这个对于一个真正的推荐系统来说至关重要,以为在实际的业务场景中,可能同时运行着10~100个分桶在进行不同的AB实验。除了实验的切流之外,我们这里还会帮助大家介绍,对于一个已经发布的线上应用来说,如何通过一个debug参数,可以快速定位到有问题的算法pipeline。最后,我们会让大家了解,如何进行服务降级,这对对于应对流量洪峰而言是必不可少的。

实验的配置方法

简单来说,我们的 Experiment Pipeline 是靠 Consul 下的一个 YAML 文件配置的。YAML 文件中描述了三个实体:

  • Scene: 表示一个场景,例如推荐系统中的“猜你喜欢”。

  • Layer:一个 Scene 是有多个 Layer 构成的一个 Pipeline,例如 UserModel->Match->Rank->ReRank->Summary,是一个由5层 Layer 构成的 Scene。

  • Experiment:每层 Layer 可以有多个 Experiment。一个前端的 request 经过每个 Layer 时,根据切流算法以及切流比率,只会被发送到当前 Layer 的某一个 Experiment 中做处理。

这里有一个简单的YAML配置案例:Experiment YAML Demo,可以帮助大家快速了解实验的配置方法。

切流算法说明

在这里,我们实现了两种切流算法,可以供用户使用:

  • 随机切流:请求到来后,会根据切流比率,随机的被分配到某一个 Experiment 中,在这种分流情况下,同一个用户,在不同的请求中可能会被分配到不同 Experiment 中;

  • 哈希切流:请求到来后,会根据用户的userId做一次加密哈希,再根据哈希结果以及切流比率,分配到某一个 Experiment 中。同一个用户,在不同的请求中保证一定会被分配到同一个 Experiment 中。

用户可以根据自己的实际需要选择切流的方式,切流的方式可以在实验的YAML文件中配置。

Debug模式

Debug 模式由单独的Controller处理,需要发送 POST 请求到 Debug 专属的 URL。JSON Body 中,可以指定当前请求跳过切流算法,强制走某个 Layer 中的某一个 Experiment。比如,一个典型的Debug的POST参数可以如下配置:

算法服务降级

面对流量洪峰时,我们需要在服务响应速度和精准度之间做出权衡,必要时牺牲精准度,增加响应速度和吞吐量,这就是服务降级。我们实现的方式,是在YAML 配置文件的每个 Layer 层加入一个基础 Experiment,该Experiment为当前层最快速(牺牲精准度)的解决方案,举例来说:

  • Match layer:在这里,只用了 Item CF 模型(统计类模型),只需要从高速缓存中读出结果即可;

  • Rank layer:在这里,直接把上一层(Match层)的结果作为当前层的结果。

当流量洪峰来临时,我们可以视具体情况把流量切到 Base Experiment 上。

启动Demo测试

当这一切准备就绪之后,一个真实的电影推荐原型系统就将呈现在大家的眼前!那么让我们来运行我们的电影推荐系统吧:

结束语

本篇文章虽然是以MovieLens-1M数据集为例,但是在少量代码和配置的改动之后,已经可以构造出利于电商、新闻的推荐系统雏形。相信经过您对本篇文章的阅读,已经具备构建一个真实的工业级推荐系统的能力。这篇文章还有不少坑需要填,比如:在线学习、多模态建模、多目标优化、基于大规模图神经网络的推荐等主题,未来我们会再做一些系列文章讨论这些较为前沿的算法主题。需要说明的是,这篇文章主要面向的是对推荐系统感兴趣及有一定相关机器学习算法基础的同学,如果您已经是推荐领域较为资深的同学,也希望您可以帮忙指出文章中不严谨或者错误之处。最后,附上我们系统的代码链接:

  • MetaSpore一站式机器学习开发平台:

    https://github.com/meta-soul/MetaSpore

  • MovieLens-1M电影推荐系统离线模型代码:

    https://github.com/metasoul/MetaSpore/tree/main/demo/movielens/offline

  • MovieLens-1M电影推荐系统在线系统代码:

    https://github.com/metasoul/MetaSpore/tree/main/demo/movielens/online

欢迎大家试测并提出宝贵意见,谢谢大家~

参考文献:

1.AutoInt: Automatic Feature Interaction Learning via Self-Attentive Neural Networks

2.Deep Neural Networks for YouTube Recommendations

3.Sampling-Bias-Corrected Neural Modeling for Large Corpus Item Recommendations

4.Mixed Negative Sampling for Learning Two-tower Neural Networks in Recommendations

5.Are We Really Making Much Progress A Worrying Analysis of Recent Neural Recommendation Approaches

6.Amazon.com recommendations: item-to-item collaborative filtering

7.Large Scale Product Graph Construction for Recommendation in E-commerce

8.SimpleX: A Simple and Strong Baseline for Collaborative Filtering

9.Open Benchmarking for Click-Through Rate Prediction

10.Wide & Deep Learning for Recommender Systems

11.DeepFM: A Factorization-Machine based Neural Network for CTR Prediction

12.xDeepFM: Combining Explicit and Implicit Feature Interactions for Recommender Systems

13.Deep & Cross Network for Ad Click Predictions

14.DCN V2: Improved Deep & Cross Network and Practical Lessons for Web-scale Learning to Rank Systems

15.LightGBM: A Highly Efficient Gradient Boosting Decision Tree

16.XGBoost: A Scalable Tree Boosting System

17.The Use of MMR, Diversity-Based Reranking for Reordering Documents and Producing Summaries

官方资料

GitHub: 

LakeSoul:https://github.com/meta-soul/LakeSoul

MetaSpore:https://github.com/meta-soul/MetaSpore

AlphaIDE:https://registry-alphaide.dmetasoul.com/login

官网: https://www.dmetasoul.com

官方交流群: 微信群:关注公众号“元灵数智”,点击“了解我们-用户交流”即可获取二维码

Slack: https://join.slack.com/t/dmetasoul-user/shared_invite/zt-1681xagg3-4YouyW0Y4wfhPnvji~OwFg

基于新一代MetaSpore平台快速搭建工业级推荐系统相关推荐

  1. 【Microsoft Azure 的1024种玩法】五十九.基于Azure云平台快速搭建GitLab应用实现代码托管

    [简介] GitLab是由GitLab Inc.开发,一款基于Git的完全整合的软体开发平台,以 Git 作为代码管理工具并实现自托管的 Git 项目仓库,本篇文章主要介绍如何在Azure Virtu ...

  2. 【Microsoft Azure 的1024种玩法】四十八.基于Azure Virtual Machines快速搭建SQL Server应用

    [简介] SQL Server系列软件是Microsoft 公司推出的关系型数据库管理系统,本文的主要内容是围绕着Azure Virtual Machines 来快速搭建SQL Server应用,使我 ...

  3. 如何基于模型训练平台快速打造AI能力

    课程概要 近年来,随着大数据的积累.理论算法的革新.计算能力的提升,人工智能人工智能再次受到学术界和产业界的广泛关注,并在很多应用领域取得了突破性进展.不过,定制模型往往需要 AI算法科学家们搭建深度 ...

  4. Springboot毕设项目基于大数据平台的个性化图书推荐系统02tt9java+VUE+Mybatis+Maven+Mysql+sprnig)

    Springboot毕设项目基于大数据平台的个性化图书推荐系统02tt9java+VUE+Mybatis+Maven+Mysql+sprnig) 项目运行 环境配置: Jdk1.8 + Tomcat8 ...

  5. 利用建站快速软件包:XAMPP,构建基于winodws平台快速搭建PHP的数据库应用- kimai - 团队时间记录

    前言: 用php编写的web应用程序,需运行在php的web容器中,其中apache server是一个针对php web容器,它是apache下的开源项目. 通常要运行一个web程序,我们还需要安装 ...

  6. 学信网:研究生云复试平台快速搭建上线

    通过覆盖全球的音视频通信服务,支撑学信网视频面试稳定运行和效率提升. 案例简介 研究生复试工作碰到疫情,各大院校先后发布复试流程调整通知,将复试工作从线下搬到了线上,这也是历史上的第一次.要在短期内完 ...

  7. 基于HT for Web 快速搭建3D机房设备面板

    以真实设备为模型,搭建出设备面板,并实时获取设备运行参数,显示在设备面板上,这相比于纯数值的设备监控系统显得更加生动直观.今天我们就在HT for Web的3D技术上完成设备面板的搭建. 我们今天模拟 ...

  8. 【Microsoft Azure 的1024种玩法】六十八.基于Azure云平台使用Azure Virtual machines快速搭建Docker容器

    [简介] Docker 是一个开放源代码软件,主要应用于开发应用.交付应用.运行应用,Docker 可以将应用程序及其依赖项打包到可以在任何 Linux.Windows 或 macOS 计算机上运行的 ...

  9. 【Microsoft Azure 的1024种玩法】七十一.基于Azure Virtual Machines快速上手搭建Typecho博客系统

    [简介] Typecho 是基于 PHP5 构建的开源跨平台博客系统,Typecho开源跨平台博客系统相较于wordpress .hexo有一定的性能优势,是我们记录文章内容的最佳首选博客,那么本篇文 ...

最新文章

  1. “围剿”杀人机器人,周志华、Hinton等57位学界大牛出手了
  2. python简单代码画图-Python科学画图代码分享
  3. 我的C++学习历程(old)
  4. Oralce数据库之存储过程、存储函数、触发器和数据字典
  5. sql over函数_SQL 高级函数
  6. 日本第四次产业革命瞄准物联网
  7. PHP环境配置遇到的问题与解决
  8. oracle里面查找重复项,Oracle数据库查询重复数据及删除重复数据方法
  9. 有些参考文献找不到页码和期号怎么解决?
  10. 前端性能优化之gzip 1
  11. java中void是什么意思_JAVA里VOID是什么意思
  12. 计算机项目管理缩写,项目管理英文缩写!!!
  13. φ(1)=1 [Sdoi2008]沙拉公主的困惑
  14. mysql capi函数详解_CAPI函数描述(G-N)
  15. 学习Java可以做些什么?
  16. 微信公众平台的基础对接
  17. NOI2014魔法森林--LCT
  18. 买房后为何有装修公司找你?个人信息就卖5毛钱
  19. 怎么用C++编个网站
  20. php用Imagick扩展合并多张图片为PDF

热门文章

  1. VSCode中预览markdown和修改预览样式
  2. 大厂裁员,小厂倒闭,程序员如何应对?
  3. MIMIC数据读取和MATLAB显示
  4. 基于JSP实现网上招聘系统
  5. 推荐两个高仿抖音 GitHub 开源项目( iOS 和 Android)
  6. Emqx3.4.4(企业版试用) web端 js实现消息的发布接收
  7. Thinkpad MORFFHL鼠标滑鼠接收器配对 thinkpad laser wireless mouse
  8. 【电子硬件】FPC、FFC和插座的常识
  9. 企业征信报告解析(授信机构版)
  10. 简单聊聊微信接听逻辑