BT

如何利用碎片时间提升技术认知与能力? 点击获取答案

开源跨平台推荐算法框架LibRec:包含70余例推荐算法

| 作者 刘啸泽 关注 0 他的粉丝 发布于 2017年4月12日. 估计阅读时间: 15 分钟 | GMTC大前端的下一站,PWA、Web框架、Node等最新最热的大前端话题邀你一起共同探讨。

在著名的Netflix竞赛之后,推荐系统得到了很大的关注和发展,很多出色的工具库也随之出现。但是,绝大多数的工具库都存在跨平台难,内置算法数量少,更新停止等问题。推荐系统领域也存在因为数据集和实现方式不同而导致算法结果很难被复现的问题。因此,不论是工业界还是学术界,对于一个算法充分,结构精良,使用简单,扩展性强的算法工具库的需求越来越强烈。

为了满足以上需求,LibRec团队设计实现了基于Java的跨平台推荐框架,并以GPL3.0协议在GitHub上开源共享。基于此工具库,科研人员可以快速实现全新的推荐系统算法并以其他推荐算法为基线进行比较,而研发人员也可以基于当前工具库的比较结果来选择具体业务情景下合适的算法或直接将LibRec集成在系统平台中。

与其他机器学习系统类似,推荐系统本质上都是对于输入的数据分成两批进行再加工。其中一批数据用于算法模型的训练,另外一批用于算法模型的评估。最后,根据评估结果来判断算法模型的质量。然而在具体到推荐系统的设计时,要同时考虑到针对不同类型的数据源,不同需求的分割方式,以及推荐算法之间的差异和评估结果的不同。这就要求系统必须要有非常高的模型化设计,同时不同模块之间的耦合度要尽可能低。本文接下来着重介绍系统的结构实现,主要为数据结构层,数据模型层,推荐算法层,评估层与过滤层。在此基础上,介绍推荐程序的执行流程与简单示例。

数据结构层

在推荐系统领域内,user-item矩阵一般都非常稀疏。如果使用传统的数据保存方式,矩阵内会填充大量的0,从而产生严重的内存和计算浪费。因此,LibRec使用的基本数据结构为存储紧凑的稀疏向量,稀疏矩阵与稀疏张量。

稀疏矩阵的构建方式为将n*m的矩阵转换为2*r+n (CRS)或者2*r+m (CCS),其中r为矩阵中非零的个数。n和m分别是矩阵的宽和长。以CRS为例,在内部实现构建方式为向量val保存所有的数值,向量col_ind保存每一个数值在相应行中所处在的列的位置。向量row_ptr保存在col_ind索引中,换行的位置。因此,为获取坐标(i, j)的数值,可以首先在row_ptr中查询当前行的数值在col_ind与val的范围,然后与该范围内的col_ind进行匹配并获取到当前位置的数值。稀疏向量与稀疏张量的实现方式与该方式类似,不再展开。

数据模型层

数据模型层是指准备数据的所有过程,包括读取user-item数据,读取分割数据,读取关于user或者关于item的关系数据。这些功能分别由Convertor,Splitter,Appender模块实现。

Convertor负责不同类型和格式的数据读取。具体来说,Convertor实现了针对于user-item-rating或user-item-rating-date的数据形式与另外一种更加通用的数据保存形式:arff。第一种格式每一行分别保存一个三元组或三元组加上时间来表示一条记录。数据形式为3*n或4*n,其中n为数据的条目数量。Arff格式的文件需要首先在文件的头部定义关系名称,之后分别定义了每一列的数据类型和数据名称。Arff也是基于Java的著名开源算法包Weka中数据的保存格式。

Splitter模块负责对Convertor读取到的数据进行分割。在机器学习过程中,一般都需要将数据集分割为两份或三份。其中一份用来训练模型,另外一份用来验证分类效果的好坏。考虑到具体不同的实现需求,LibRec的数据分割包括以下几类。

  1. Ratio. 基于Ratio的分类方法为通过给定的比例来将数据分为两部分。这个分类过程可以在所有数据中进行随机分类,也可以在用户或者物品维度上进行分类。当有时间的特征时,可以根据时间顺序留出最后一定比例的数据来进行测试。

  2. LooCV. LooCV的分割方法为leave-one-user/item/rating-out,也就是随机选取每个user的任意一个item或者每个item的任意一个user作为测试数据,余下的数据来作为训练数据。在实现中实现了基于User和基于Item的多种分类方式。

  3. GivenN. GivenN分割方法是指为每个用户留出指定数目N的数据来作为测试用例,余下的样本作为训练数据。

  4. KCV. KCV即K折交叉验证。将数据分割为K份,在每次执行时选择其中一份作为测试数据,余下的数据作为训练数据,共执行K次。综合K次的训练结果来对推荐算法的性能进行评估。

  5. GivenTestSet. GivenTestSet是指由用户提供测试数据。与其他分割方式相比,使用GivenTestSet更方便比较基于不同平台实现的推荐算法。

部分推荐算法(如SocialRecommender)需要通过引入user和user或者item与item之间的关系数据。针对这一需求,LibRec设计了Appender模块来进行关系数据读取并返回读取数据之后的稀疏矩阵。

算法层

算法层是LibRec项目中最核心的部分,也是LibRec项目最具有竞争力的部分。目前LibRec中包含了70余例推荐算法,涵盖了基准算法,协同过滤算法,基于内容的算法,基于情景感知的算法,混合算法以及其他扩展算法。这些算法的时间跨度从NMF等经典的算法至TrustSVD等近两年提出的算法。目前所包含的算法如下图所示,所有算法对应的论文已经整理在文档中。

在数据预处理部分,数据读取与分割之后保存数据的类为DataModel,这同时也是Recommender类的构造器参数之一。而模型的训练则由evaluate方法完成。该方法被不同的算法重写,从而完成不同推荐系统模型的训练。

对于具体的算法实现,需要分别实现以下几个模块。模型的推断(Inference),模型的预测(Prediction),相似度的衡量(Similary)以及模型的持久化(Persistence)。模型的推断部分用于模型的训练,而模型的预测部分则被Evaluator类调用从而进行最终结果的评估。模型的持久化用于保存训练完成的算法,从而避免算法的重复执行。相似度的计算可以通过实例化相似度类进行计算,作为参数输入构造器;也可以使用Recommender类直接对DataModel计算相似度矩阵。LibRec中模型相似度的衡量主要涵盖了以下相似度计算方法。具体计算公式不再展开讲解。

评估层

输出层主要包含两部分,其中一部分为Evaluator,即对模型的评估。另外一部分为Filter,用于将结果输出时以给定条件进行过滤。评估过程为针对测试数据,根据预测出的结果与实际的结果对推荐算法进行评估。根据推荐算法的不同,最终的评估函数也分为ranking与rating两种类型。

目前LibRec包含的评估函数如下表所示:

Filter类需要通过set方法来写入需要过滤掉的user列表与item列表,并且可以在推荐算法的结果中进行过滤并手动输出到文件中。具体实现可以参考文档和程序中附带的实例程序。

在当前模块结构下,典型的算法实现流程如下图所示。

所有的实线为所有算法均需要执行的步骤,而虚线连接为部分算法需要执行的步骤。首先对于输入的数据,读取分割后送给推荐类进行训练。训练结束之后对测试数据进行预测并使用相应的评估器进行评估。同时对于预测之后的结果进行过滤并进行持久化保存。

得益于优良的模块化设计,在具体实现中只需要对相应的类(如推荐算法、评估器)进行实例化并进行调用即可,典型的代码调用如下所示。


public static void main(String[] args) throws Exception {
    // set id list of filter
    List userIdList = new ArrayList<>();
    List itemIdList = new ArrayList<>();
    userIdList.add("1");
    itemIdList.add("70");

    // build data model
    Configuration conf = new Configuration();
    conf.set("dfs.data.dir", "path/to/data/folder/");
    Randoms.seed(1);
    TextDataModel dataModel = new TextDataModel(conf);
    dataModel.buildDataModel();

    // build recommender context
    RecommenderContext context = new RecommenderContext(conf, dataModel);

    // build similarity
    conf.set("rec.recommender.similarity.key" ,"item");
    RecommenderSimilarity similarity = new PCCSimilarity();
    similarity.buildSimilarityMatrix(dataModel);
    context.setSimilarity(similarity);

    // build recommender
    conf.set("rec.neighbors.knn.number", "5");
    Recommender recommender = new ItemKNNRecommender();
    recommender.setContext(context);

    // run recommender algorithm
    recommender.recommend(context);

    // evaluate the recommended result
    RecommenderEvaluator evaluator = new RMSEEvaluator();
    System.out.println("RMSE:" + recommender.evaluate(evaluator));

    // filter the recommended result
    List recommendedItemList = recommender.getRecommendedList();
    GenericRecommendedFilter filter = new GenericRecommendedFilter();
    filter.setUserIdList(userIdList);
    filter.setItemIdList(itemIdList);
    recommendedItemList = filter.filter(recommendedItemList);

    // print filter result
    for (RecommendedItem recommendedItem : recommendedItemList) {
        System.out.println(
                "user:" + recommendedItem.getUserId() + " " +
                "item:" + recommendedItem.getItemId() + " " +
                "value:" + recommendedItem.getValue()
        );
    }
}

如果是仅使用一种推荐算法,也可以将所有的参数定义完整后使用LibRec提供的RecommenderJob类以及使用命令行来执行。具体使用方式请参阅LibRec文档。

小结

这篇文章细致的总结了LibRec项目中整个模块的规划以及每一个模块的功能构成,并最终演示了基于当前项目的模块进行推荐的完整代码。在整个模块的设计过程中,以易用,高内聚,低耦合为目标进行设计,力求使得LibRec项目可以成为一个高扩展的算法框架。从最终的执行结果来看,目前LibRec的结构完全满足了当初进行2.0版本研发时的所有需求。同时,在LibRec优良的模块化基础上,其内置的算法的丰富性,配置的灵活性,框架的易用性以及计算流程的清晰性使得LibRec成为推荐领域内不可多得的推荐平台。

评价本文

专业度
风格

您好,朋友!

您需要 注册一个InfoQ账号 或者 才能进行评论。在您完成注册后还需要进行一些设置。

获得来自InfoQ的更多体验。

告诉我们您的想法

允许的HTML标签: a,b,br,blockquote,i,li,pre,u,ul,p

当有人回复此评论时请E-mail通知我

初试牛刀 by Zhao Wenlong

librec看起来是有些强大,但是资料很少呢~

允许的HTML标签: a,b,br,blockquote,i,li,pre,u,ul,p

当有人回复此评论时请E-mail通知我

允许的HTML标签: a,b,br,blockquote,i,li,pre,u,ul,p

当有人回复此评论时请E-mail通知我

1 讨论

登陆InfoQ,与你最关心的话题互动。


找回密码....

Follow

关注你最喜爱的话题和作者

快速浏览网站内你所感兴趣话题的精选内容。

Like

内容自由定制

选择想要阅读的主题和喜爱的作者定制自己的新闻源。

Notifications

获取更新

设置通知机制以获取内容更新对您而言是否重要

BT