前往小程序,Get更优阅读体验!
立即前往
首页
学习
活动
专区
工具
TVP
发布
社区首页 >专栏 >干货 | 去哪儿酒店算法服务平台

干货 | 去哪儿酒店算法服务平台

作者头像
携程技术
发布2018-03-16 11:54:39
1.3K0
发布2018-03-16 11:54:39
举报
文章被收录于专栏:携程技术携程技术

作者简介

张中原,2011年加入去哪儿网,先后从事交易系统、酒店数据、公司基础平台与组件、存储和监控等相关工作,曾长期担任应届生技术培训和指导。

最近几年时间,机器学习算法相关的话题异常火爆,各个具有一定规模的公司都开始成立算法组开展相应的工作,从"对大数据的分析观察"逐渐转变为"用数据和算法驱动业务的发展"。

对于机器学习技术本身互联网上有足够的学习资料和案例,甚至已经有将机器学习做成服务的AI云出现,这其实也反映出行业对实施机器学习相关配套系统的迫切需求。

我们团队在做这件事情的过程中,也遇到过一些问题和解决思路,在此跟大家做个分享。

算法平台

整体上我们的算法平台建设分为三个阶段:

1、优先解决算法部署的问题,降低跨团队沟通障碍;

2、有了一定的积累之后,再完善一套较为完整的特征集,避免每个模型重复提取数据以及实时特征收集;

3、最后打造模型训练系统,以快速的针对多种算法结果进行评测,选取最优结果。

目前第一阶段基本工作基本结束,第二阶段正在进行中,本文主要介绍第一阶段有关的内容。

部署的问题

通常实施一个算法需要进行数据收集、特征处理、模型训练和模型发布这几个步骤,其中前三个步骤初期都可以离线执行,团队内部并行即可,而最后一步需要和其他团队合作发布,这其中有较多排期、协调、沟通等障碍,算法的发布模型迭代周期较长,主要面临的问题有:

1、没有标准规范,管理较为混乱,模型工具五花八门;

2、模型训练时使用Python进行特征转换,而发布是交易系统都是Java体系,转换逻辑需要重新开发,且极易引起逻辑不一致的情况;

3、每个模型的生成和发布过程大致相同,之间有很多重复性的工作无法复用;

4、算法通常不可能一次成型,都有一个逐步迭代的过程,这就要引入版本的概念,考虑每次变化对业务系统的影响,是否需要排期调整等;

5、由于初期模型都是直接交给业务系统加载使用,出现异常案例时,跟踪调试较为复杂,且算法团队无法对模型运行状况进行监控和在线调试,问题排查困难。

所以我们急切的需要一个系统能够将部分重复性工作自动化执行,统一调用接口减少与业务组的沟通成本,同时进行模型的跟踪与调试便于问题排查。

算法描述

平台构建初期,需要对算法有一个明确的定义,一个算法主要有特征处理和模型运算两部分构成。

首先,算法需要有版本,这很容易理解;其次,跟具体的业务场景有关,比如我们面向的是旅游行业相关的,有地域性差异,每套数据训练出的模型文件不同,所以需要考虑模型的细分粒度,我们使用一个filter作为城市标注;然后需要兼容多种类型的模型;最后还要统一特征转换,使一套转换逻辑可以应用到所有场景。这里有几个主要的接口定义:

interface Algorithm <R> { StringgetApp(); StringgetVersion(); FeatureResolver getFeatureResolver(); EvaluatorgetEvaluator(); ListenableFuture<ResultValue<R>> eval(Request request); } interface AlgorithmFactory { AlgorithmgetOrCreate(String app, String version, String filter); }

模型定义

在考虑使用何种模型文件时,我们做过一些对比。

早期的几个模型选用PMML文件的方式,其本身包含完整的特征预处理、模型预测和后处理的描述,但后来发现我们其实只需要模型预测的功能,而更希望将特征转换独立出来使用;

之后的几个模型选取的是Vowpal Wabbit,主要原因是经过工业应用验证,且有着更好的性能;然而VowpalWabbit官方主要使用C++实现,针对Java只提供了JNI的包装,所以我们根据源码自己实现了模型预测逻辑的Java版本,将vw的模型文件直接加载来用。

此外还有一种历史遗留下的XML文件描述的模型DataProc,其思想与PMML类似,也需要兼容。除了可描述的模型,还有可能出现多个模型协同运算的场景,目前我们采用编写少量代码方案,将多个模型进行简单组装,对外接口与单个模型保持一致。

interface Evaluator<F, R> { ResultValue<R> eval(F resolved); }

此外这里还提供了一系列接口用于外部资源注入,如: 访问特征集的KVStoreSupport,创建子算法的AlgorithmFactoryAware等

public class EvaluatorFactory { publicEvaluator createEvaluator(Config config, String filter, InputStream model) { Evaluator e = createInstance(...); if (einstanceof AlgorithmFactoryAware) { ((AlgorithmFactoryAware) e).setAlgorithmFactory(...); } if (einstanceof KVStoreSupport) { ((KVStoreSupport) e).setStore(...); } ... } }

由于vw模型文件一般较小,可以直接加载使用,而有的模型文件很大,直接使用对gc会有严重影响,导致若干请求超时甚至引起服务恶化,需要考虑使用堆外内存的方式。

特征处理

前面提到,我们需要将特征转换的功能独立出来,达到一套逻辑多处使用,一处调整全部生效的目的;我们从Airbnb提供的Aerosolve项目中得到启发,将所有的特征结构表示为一个FeatureVector,将所有转换逻辑封装成一个个Transform的实现。

根据使用场景,实现对应的Parser, Writer将外部数据和FeatureVector结构之间进行转换,使用aerosolve语法描述整个特征处理的过程。在实现Transform类时,遵循"一个类只做好一件事"的原则。为了提高Transform的使用灵活性,我们引入了表达式的概念,根据配置中'$'的符号标识,根据上下文场景调整转换逻辑,如:

# 气温转换 category_temperature { transform: category # type:raw # fields:[""] keys:[temperature] # 使用默认命名空间中的temperature特征 #outputType: float # 默认将Raw类型转换到Float output:all # 输出到all空间 outputKey: $key # 使用原有特征名,支持 $value, $category或固定值 outputValue: $category # 使用类别作为特征值 # keep:false # 是否保留原有特征数据 categories: { '' : 0 '<10' : 1 '<30' : 2 '>=30': 3 } }

使用上述配置的一个转换的例子效果如下:

raw: {"" :{"temperature": "26"}} # 转换前 floats: {"all": {"temperature":2}} # 转换后

使用list转换器,将一系列的转换配置组装起来,就能做到任意特征转换的描述。通过若干模型的发布积累,目前已有30多个转换器,总结下来有几个比较常用:

  • default 缺省值设置,这个容易理解
  • category 用于归一化处理
  • store 访问外部存储进行特征展开,如: 使用uid获取用户完整数据
  • cross 将FeatureVector内两个特征向量进行交叉运算

为了获取更好的性能和存储空间优化,我们使用了koloboke, trove4里面的类代替Java自带的集合对象,以支持原生的int, double等类型。由于每个Transform类都是经过长期打磨改进,积累下来,有效的保证了特征转换的性能和质量。针对Hive引用特征转换逻辑,我们实现了对应的UDF,开发阶段时使用本地转换配置,上线后使用hdfs或算法平台中的配置。

服务的使用

平台演进初期,我们只负责维护模型文件和特征,由业务系统负责加载模型运算,经历了几个项目后遇到几个棘手的问题。

首先,模型文件一旦交付,算法组没办法了解到其真实的运行状况,无法做到很好的监控;一旦出现问题,也无法进行排查和调试,系统边界模糊,沟通成本高。

所以我们将算法平台直接做了服务化,这样一来业务开发在接入算法时,和使用其他服务没有任何区别,知道传什么参数就行了,而模型的变更、调试、监控则全部由算法组负责,对问题排查和后续的优化提升都更方便,减少沟通成本。

AlgorithmBuilder builder =AlgorithmBuilder.create().setServer(...). setStore(...).setLazyInit(true/fal se).setExecutor(...); Algorithm algorithm = builder.getOrCreate(app,version, filter); interface AlgorithmService { <R>ResultValue<R> eval(Request request); }

为了方便调试我们在请求中增加了debug开关,通过线程绑定RequestContext的方式,可以收集各阶段的数据状态和运算结果一并返回。同时还收集汇总了各个算法服务器上的日志信息,可以在平台管理页面追查某个请求引发的个阶段的运行信息。

总结

算法平台的构建也是一个逐步演进的过程,从特征工程到服务提供是一项浩大复杂的工程,每个人都要根据各自的技术选型和场景进行不断的优化,但"以服务业务为核心不断提高生产力"的宗旨不会改变。本文只是冰山一角,阐述机器学习在实际应用中最后一步时遇到的问题和解决思路,希望能对有同样问题的同学提供参考。

本文参与 腾讯云自媒体分享计划,分享自微信公众号。
原始发表:2017-05-06,如有侵权请联系 cloudcommunity@tencent.com 删除

本文分享自 携程技术中心 微信公众号,前往查看

如有侵权,请联系 cloudcommunity@tencent.com 删除。

本文参与 腾讯云自媒体分享计划  ,欢迎热爱写作的你一起参与!

评论
登录后参与评论
0 条评论
热度
最新
推荐阅读
领券
问题归档专栏文章快讯文章归档关键词归档开发者手册归档开发者手册 Section 归档