关键词:r-model-deployment
引言
如果此时你对何谓模型部署仍然一无所知的话,不必有任何焦虑的心情,带你入门正是本文的目标所在。请相信我,这篇介绍将会是十分新手友好的,怀着好奇心和耐心读下去,你也会对模型部署建立起清晰的认识。
模型部署是商业统计建模中极其重要的一部分,然而却往往被人忽视。读完本文,你将了解模型部署的基本概念与用途,学会如何在 R 语言环境中使用网络服务来部署上线一个模型,更多地,你的方法武器库中将会增添几柄利器: opencpu、fiery 及 plumber。话不多说,让我们开始吧!
引入模型部署
什么是模型部署?
一句话概括,模型部署是将一个调试好的模型应用置于生产或者类生产环境中,其意义在于处理预测新数据,为公司提供数据决策或者对接其他需求部门提供模型支持。我们先来看下面这幅数据科学项目开发流程图。
注: 图片来源自 tensorflow 官网 [微软在线文档] (https://cran.r-project.org/web/views/ModelDeployment.html)
位于图片左边区域中的紫色圆圈中的文字,相信对大部分读者都不陌生。我们在学校中习得了很多关于数据科学模型、特征选择技巧及模型评价方法的知识,回想一下,在你过去大多数课程作业或是打比赛的经历中,完成了模型评价也就意味着项目宣告结束。然而在商业应用中,模型部署才标志着一个项目开发的阶段性结束。它的价值体现在两点:
1. 赋予数据科学模型处理新数据的能力,可以是实时的流数据,或者是积累的批数据。
2. 在实际应用场景中完成模型效果与性能的反馈,为模型调整提供依据,实现建模开发流程的闭环。
更多关于模型部署资料:
https://docs.microsoft.com/en-us/azure/machine-learning/team-data-science-process/lifecycle-deployment
模型部署的手段
首先我引述其中几种常用的模型部署的手段,接下来我们将会介绍借助R中的网络服务这种形式来搭建一个线上的模型。
各种工具/工件(artifact)
有很多软件包均提供了将模型部署至小型终端设备上的功能。他们大都是将训练好的模型封装成一个对象并储存成自定义格式的文件,然后在相对应的平台或系统中加载调用这个对象,诸编程平台也大都有相应调用接口。PMML (Predictive Model Markup Language) 是其中最常用的一种,它适合应用在实时、大规模数据量的场景。而在深度学习方面, TensorFlow 社区开发的 Tensorflow Lite 工具(https://www.tensorflow.org/lite/overview)能翻译转换  tensorflow 预训练模型至 TensorFlow Lite 文件格式,然后用其他的接口来调用模型文件实现部署。除此之外还有一些其他的工具,可以参考R官网的任务视图之模型部署部分,这里不再一一赘述。
(https://cran.r-project.org/web/views/ModelDeployment.html)
注: 图片来源自 tensorflow 官网
云/服务器
这种方式通过在服务器上开启一个服务的方式来部署模型,也是本文着力介绍的。主流的编程语言都有处理网络服务的功能,在 Python 中可以用 httpserver、flask、django 等搭建网络框架来部署;而R语言做网络服务的包也不止一个,诸如 plumber,fiery,opencpu等。条条大路通罗马,本文不会对编程语言的选择提供任何建议。
既然是本文重点介绍的方式,不妨多啰嗦两句。
用网络服务做模型部署,将划分出线上与线下两个环境。线下环境是单机环境,用以做一些探索性或者验证性的分析,包含了分析建模的每一个步骤:特征工程、模型选择、模型评价、参数调节等。
而线上环境则是生产环境,是把线下调整好参数的模型传至线上对新搜集的数据进行实时反馈。其背后的原理是借助 get&post 方法把特征字段传递给 web 服务器,服务器将会用封装好的模型预测并返回参数数值。对 get&post 不熟悉的可以参考这个教程。
(教程网址:http://www.w3school.com.cn/tags/html_ref_httpmethods.asp)
离线部署
这种方式相对来说就比较稀松平常了。问离线部署共分几步?答:共分三步。写好模型脚本 xx.R  xx.py ;把累积的新数据下载下来;执行 Rscript xx.R 或者 python xx.py 。完事儿~
闲话流数据
前文说到模型部署的一大价值便在于它赋予了数据科学模型处理新数据的能力,而很多新数据通常是实时采集的流式数据。而在我看来,对于流式数据的陌生也是大部分同学在校期间对模型部署接触不多的一大原因,或者与数据采集环节的遥远导致我们把大部分的精力都投入在如何用模型把面前样本数据中的模式挖掘出来。在这种场景下,数据是固定的,被存放在 .csv 或者各式各样的文件中。
让我们的思路再向前推移一步,用以存放数据的各种数据库或者数据文件是像蓄水池一样逐渐蓄满了被传递进来的数据,而这些数据均是经过一定的技术手段实时采集而来。那么,对于新的观测,一个部署在生产环境中的模型便可以实时地处理预测这些数据:比如互联网金融公司便可根据一个新申请的用户提供的个人信息来预测这名用户的信用评分,垃圾过滤系统也可以获取实时的邮件内容来判断这是否是垃圾邮件。
实战环节
得益于 RStudio 开发的工具包 httpuv,在 R 语言中处理 http 以及 Websocket 请求变成了现实,基于此工具包二次开发的框架 opencpu, fiery 和 plumber 提供了在线模型部署的解决方案,下面我们一一介绍:
场景实例:
我们演示的场景为垃圾邮件拦截,数据来源自 ElemStatLearn 包 spam 数据集。把问题拆解为以下四个步骤来模拟实际生产环境。
1.  根据线下数据训练模型
2.  从线上 MySQL 数据库中获取新数据
3.  将数据传递至部署在 web 服务器上的模型并返回预测值
4.  使用预测值来决策
安装所需要的程序包:
使用线下数据训练模型并保存,留待正式部署模型时使用。
opencpu
参见https://www.opencpu.org/
opencpu包在这三者中是发布最早的,包作者在2013年9月CRAN上发布了第一版。这个包的特点是文档示例详尽(https://www.opencpu.org/api.html),并且提供了方便测试的图形化界面工具。
实现步骤
使用 opencpu 做模型部署遵循以下一般的开发流程:
1.  把要上线的功能写个 R 包
2.  编译安装 R 包
3.  开启 opencpu 的服务器部署模型
4.  连接该端口
STEP1
定义函数逻辑,即从数据库中提取数据并用 xgboost 或者 logistic 模型预测,编写函数 getdata , xgbpred , linearpred 并把函数保存为 PredData.r
此处模拟的情景是从 MySQL 数据库中提取数据,需要在本机上配置 MySQL 或者连接一个远程的 MySQL 数据库,具体的配置过程从略。
STEP 2
创建 R 包 SpamModelOpencpu 并编辑文档,下图是编译好的R包的介绍。
如果你对R包开发不熟悉,可以参考这个教程(https://blog.csdn.net/iccsu/article/details/24237885)或是谢益辉大大在统计之都上的旧文(https://cosx.org/2011/05/write-r-packages-like-a-ninja)。
STEP 3
在 R console里运行单机版的 opencpu 服务器。
输出:
控制台的输出告诉我们服务已经在 http://localhost:5656/ocpu 启动了,接下来你可以用浏览器访问这个 url,或者在控制台中 curl 一下 (我使用 windows 平台下的 git bash ,CMD 中输入的命令可能会有些差异)
返回:
至此就大功告成了,恭喜你得到了一个可用的搭建在本机 web 服务器上的机器学习模型。
fiery
参见https://github.com/thomasp85/fiery
当我们在搭建线上服务时通常会希望请求响应的时间会尽可能快,尽管 opencpu 提供了在 R 环境中做模型部署的一个不错方案,然而与接下来要登场的这两位主角相比,它在响应时间上相形见绌。接下来我所要介绍的是 fiery,包名的来历十分有趣:
Q: Is this a competing framework to Shiny?
A: In a way, yes. Any package that provides functionality for creating web applications in R will be competing for the developers who wish to make web apps. This is of course reinforced by the name of the package, which is a gentle jab at Shiny.
fiery“热情似火”的使用者得以用复杂度换取服务的灵活性,可以在R代码中嵌编辑HTML代码来定制服务,因此相比于用法优雅“bling bling”发光的shiny,高度自由的fiery就像是摇曳的炽烈火苗(纯属个人解读)。闲言说罢,让我们来用fiery实现任务。
实现步骤
编写一个新的R脚本文件,命名为 SpamM.r
STEP 1
首先要开启一个网络服务实例并设置ip地址、端口:
STEP 2
启动服务并同时开启监听,同时加载训练好的储存在 model.rds 文件中的 xgboost 模型。
STEP 3
响应 http 请求,编辑函数定义对访问路径为 hello 或 predict 的请求的操作逻辑。
1.  /hello 页面:介绍页面。
2.  /predict 页面:输入 id,返回预测值的页面。
STEP 4
一切就绪,启动服务。
接下来保存上面的所有编辑,并在控制台中运行该脚本。在 Windows 的 CMD 或者 Mac OS/Linux 的 Shell 中,这并没什么不妥,然而如果你是在 Windows 下使用的 git bash ,就要输入 winpty Rscript SpamM.r 
如果出现以下信息,说明目前一切顺利。
那么下面就试试访问先前定义的 hello 页面,并按照 hello 页面的提示编写一条按照 id 查询的 url。
plumber
包的介绍
plumber是要介绍的最后一个包。在我看来该包的优势有以下几点:
1.  响应速度很快。
2.  学习曲线平缓,即便你是个缺乏 http 请求与 html 语言的知识的小白,这并不妨碍你入门使用这个包,当然如果是深度使用用户,还是要对该包的运行机制有所了解的。
3.  最后一点,该包提供的用 bookdown 编辑的学习文档(https://www.rplumber.io/docs/)方便上手学习,相信很多人对一份简明的开源工具使用文档的必要性深有体会!
案例实现
下面讲一下plumber的实现步骤,并不涉及过多原理。简要来说,plumber 借助 endpoint 与 filter 这两个控件搭建一个可用的 http 服务。
在使用 plumber 实现我们识别垃圾邮件的案例中,我们需要使用 endpoint 来指明路由,后面跟随这个路由对应的R环境中的函数操作。 endpoint 的声明借由在脚本里函数的前面嵌入 @ 来实现,比如下面这段代码我们定义了一个 get 方法,它的路由是 ./predict ,当你访问并向该路由发送请求时,请求中的参数 id 将会被提取出来并被传递至下面的函数。函数同样是定义了从 MySQL 中按照 id 提取数据并加载入模型。
而 filter 控件则可以用来监听请求并返回请求的信息。比如下面定义的 logger,将会在开启服务后打印出每条请求的发送时间、方法、路径、HTTP代理等信息。
至此我们便完成了一个轻便服务的代码部分,将上面这些内容保存为脚本文件
service_plumber.r 。接下来在 R console 里运行。
至此,服务已在设定的端口2018启动,接下来我们便可以用浏览器访问:
http://localhost:2018/predict?id=1,或者在命令行中运行 curl http://localhost:2018/predict?id=1,返回编号为1的邮件的预测概率。
这是在我的电脑上开启服务后打印的两条访问记录。
响应测试
天下武功唯快不破,评价一个服务性能的重要标准就是响应时间,下面我们用 microbenchmark 包来测试下三个部署好的服务的响应速度。
测试结果可能会因机器而异,下面的结果显示 opencpu 搭建的服务响应时间最久,中位时长为1.809s,而另外两个R搭建的服务的响应时长都在200毫秒左右,差异不大。
小结
至此你已经完成了模型部署的入门训练,恭喜!最后考虑到响应测试与上手难度,我个人向你推荐 plumber 这个包。与此同时,一个悲喜交加的消息是 R 社区中今年又发布了一个用并行化来实现的网络服务包 RestReserve: 
https://github.com/dselivanov/RestReserve,其响应速度堪称秒杀 plumber,据说是后者的20倍,留待后话。
参考资料
1.  利用 R 和opencpu 搭建高可用的HTTP服务(刘思喆)
https://www.bjt.name/2017/04/28/opencpu-application.html
2.  plumbeR官方网站
https://www.rplumber.io/
3.  开发 R 程序包之忍者篇(谢益辉)
https://cosx.org/2011/05/write-r-packages-like-a-ninja
作者:周震宇
日期:2018-07-23 
继续阅读
阅读原文