什么是模型部署

一句话概括,模型部署 是将一个调试好的模型应用置于生产或者类生产环境中,其意义在于处理预测新数据,为公司提供数据决策或者对接其他需求部门提供模型支持。我们先来看下面这幅数据科学项目开发流程图。

模型部署的方法

工具

有很多软件包均提供了将模型部署至小型终端设备上的功能。他们大都是将训练好的模型封装成一个对象并储存成自定义格式的文件,然后在相对应的平台或系统中加载调用这个对象,诸编程平台也大都有相应调用接口。

PMML (Predictive Model Markup Language) 是其中最常用的一种,它适合应用在实时、大规模数据量的场景。而在深度学习方面,TensorFlow 社区开发的 Tensorflow Lite 工具能翻译转换 tensorflow 预训练模型至 TensorFlow Lite 文件格式,然后用其他的接口来调用模型文件实现部署。

通过这个链接可以查看更多的有关模型部署相关的内容:https://cran.r-project.org/web/views/ModelDeployment.html

使用pmml包的pmml函数可以将R中的模型对象转变成为pmml对象 使用XML包的saveXML函数,可以将pmml对象保存成为pmml文件

唯一需要注意的是,并不是所有的R模型都能够导出,下图是支持的包和模型:

h20

h2o提供部署方式:MOJO和POJO

MOJO:适用于复杂模型,例如gbm、xgboost、集成模型等,需要依赖模型生成的简单文件。 POJO:适用于分类和回归模型,以Java类的形式调用,不能超过1G,较之mojo的模型快。

服务器

用网络服务做模型部署,将划分出线上与线下两个环境。线下环境是单机环境,用以做一些探索性或者验证性的分析,包含了分析建模的每一个步骤:特征工程、模型选择、模型评价、参数调节等

而线上环境则是生产环境,是把线下调整好参数的模型传至线上对新搜集的数据进行实时反馈。其背后的原理是借助 get&post 方法把特征字段传递给 web 服务器,服务器将会用封装好的模型预测并返回参数数值。

plumber、fiery、opencpu

离线部署

离线部署很简单,有三步

  1. 写好模型脚本.R,或者.python
  2. 把新数据下载下来
  3. 执行 Rscript xx.R 或者 python xx.py。

plumber

https://www.rplumber.io/

https://github.com/rstudio/cheatsheets/blob/main/plumber.pdf

plumber uses special comments to turn R code into API endpoints.

An example :

library(plumber)
#*
#* Echo back the input
#* @param msg The message to echo
#* @get /echo
function(msg="") {
  list(msg = paste0("The message is: '", msg, "'"))
}

#*si plumber comment @decorators define API characteristics @get is HTTP method echo is the location of the endpoint

plumber pipeline

  1. Filters

Filter can forward requests ,throw error ,return a response without forwarding the request.

Filter are defined similarly to endpoints using @filter [name]

  1. Parser

Parsers determine how Plumber parses the incoming request body. By default Plumber parses the request body as JavaScript Object Notation (JSON).

Other parsers, including custom parsers, are identified using the @parser [parser name]

All registered parsers can be viewed with registered_parsers()

  1. endpoint

Endpoints define the R code that is executed in response to request. these endpoints correspond to HTTP methods

1. @get  - request a resource
2. @post - send data in body
3. @put -store/update data 
4. @delete - delete resource
5. @head - no request body
6. @option - describe options 
7. @patch - partial changes
8. @use - use all methods
  1. serializer

serializer deteminde how Plumber returns results to the clinet .By default Plumber serializes R object returned into JavaScript Object Notation

Other serializers, including custom serializers, are identified using the @serializer [serializer name] tag. All registered serializers can be viewed with registered_serializers().

Running Plumber APIS

1.Plumber APIs can be run programmatically from within an R session.

  1. IDE INTEGRATION

Documentation

Plumber APIs automatically generate an OpenAPI specification file. This specification file can be interpreted to generate a dynamic user-interface for the API.

Interact with the API

Once the API is running, it can be interacted with using any HTTP client. Note that using httr requires using a separate R session from the one serving the API.

(resp <- httr::GET("localhost:5762/echo?msg=Hello")) #> Response [http://localhost:5762/echo?msg=Hello] #> Date: 2018-08-07 20:06
#> Status: 200
#> Content-Type: application/json
#> Size: 35 B
httr::content(resp, as = "text")
#> [1] "{\"msg\":[\"The message is: 'Hello'\"]}"

简单的例子

#' 对request提取用户id并做预测
#' @get /predict
function(id){
  getdata = function(id = 1){
    con = dbConnect(MySQL(),user = 'username',password = '123456',dbname = 'spam_mail')
    sqlquery = paste0('SELECT * FROM spam WHERE id = ',id)
    print(sqlquery)
    res = dbSendQuery(con, sqlquery)  
    data = dbFetch(res, n=-1)
    z = unlist(data[,-1])
    # 清空con的查询结果并关闭连接
    dbClearResult(dbListResults(con)[[1]])
    dbDisconnect(con)
    return(t(as.matrix(z)))
  }
  res = xgboost:::predict.xgb.Booster(object = model, newdata = getdata(id))
  res
}

filter 控件则可以用来监听请求并返回请求的信息。比如下面定义的 logger,将会在开启服务后打印出每条请求的发送时间、方法、路径、HTTP 代理等信息。

#' 监听请求
#' @filter logger
function(req){
  model <<- readRDS("model.rds")
  cat(as.character(Sys.time()), "-", 
      req$REQUEST_METHOD, req$PATH_INFO, "-", 
      req$HTTP_USER_AGENT, "@", req$REMOTE_ADDR,"\n")
  plumber::forward()
}

至此我们便完成了一个轻便服务的代码部分,将上面这些内容保存为脚本文件service_plumber.r。接下来在 R 控制台里运行

# 你需切换工作目录至service_plumber.r文件所在位置
pr = plumber::plumb("service_plumber.r")
pr$run(port = 2018)

至此,服务已在设定的端口 2018 启动,接下来我们便可以用浏览器访问 http://localhost:2018/predict?id=1,或者在命令行中运行curl http://localhost:2018/predict?id=1 ,返回编号为 1 的邮件的预测概率。

curl http://localhost:2018/predict?id=1
{"id":["1"],"v":[0.8751]}

一个例子

l <- lm(mpg~cyl+disp+hp+drat,data = mtcars)

save(l,file = "lm.RData")



#* 返回线性回归的结果
#* @param cyl -number of cylinders
#* @param disp -displacement 
#* @param  hp - gross horsepower
#* @param drat -rear axle ratio
#* @get  /predictLm
function(cyl,disp,hp,drat){
  d <- data.frame(cyl=as.numeric(cyl),disp=as.numeric(disp),hp=as.numeric(hp),drat=as.numeric(drat))
  #getwd()
  load("lm.RData")
   
   stats::predict.lm(l,d)
   
  
}

RestRserve

https://github.com/rexyai/RestRserve

一个例子:

library(RestRserve)
app = Application$new()

app$add_get(
  path = "/health", 
  FUN = function(.req, .res) {
    .res$set_body("OK")
  })

app$add_post(
  path = "/addone", 
  FUN = function(.req, .res) {
    result = list(x = .req$body$x + 1L)
    .res$set_content_type("application/json")
    .res$set_body(result)
  })


backend = BackendRserve$new()
backend$start(app, http_port = 8080)

需要注意的是,需要使用Rscript 在中断运行代码

调用API

curl localhost:8080/health
# OK
curl -H "Content-Type: application/json" -d '{"x":10}' localhost:8080/addone
# {"x":11}

更多的资料

一些教程:https://github.com/rexyai/RestRserve/tree/master/inst/examples

一些例子:https://github.com/rexyai/RestRserve/tree/master/inst/examples

HTTP 请求方法

https://www.w3school.com.cn/tags/html_ref_httpmethods.asp

超文本传输协议(Hypertext Transfer Protocol,缩写 HTTP)旨在启用客户端和服务器之间的通信。

HTTP 充当客户端和服务器之间的请求-响应协议。

举例: 客户端(浏览器)向服务器发送 HTTP 请求;然后服务器将响应返回客户端。响应包含有关请求的状态信息,也可能包含所请求的内容。

HTTP 方法

  1. GET
  2. POST
  3. PUT
  4. HEAD
  5. DELETE
  6. PATCH
  7. OPTIONS

最常用的就是GET 和 POST。

GET 方法

GET 用于从指定资源请求数据。

GET 是最常见的 HTTP 方法之一。

请注意,查询字符串(名称/值对)是在 GET 请求的 URL 中发送的:

POST 方法

POST 用于将数据发送到服务器来创建/更新资源。

通过 POST 发送到服务器的数据存储在 HTTP 请求的请求主体中:

PUT 方法

PUT 用于将数据发送到服务器来创建/更新资源。

POST 和 PU T之间的区别在于 PUT 请求是幂等的(idempotent)。也就是说,多次调用相同的 PUT 请求将始终产生相同的结果。相反,重复调用POST请求具有多次创建相同资源的副作用。

HEAD 方法

HEAD 与 GET 几乎相同,但没有响应主体。

换句话说,如果 GET /users 返回用户列表,那么 HEAD /users 将发出相同的请求,但不会返回用户列表。

HEAD 请求对于在实际发出 GET 请求之前(例如在下载大文件或响应正文之前)检查 GET 请求将返回的内容很有用。

DELETE 方法

DELETE 方法删除指定的资源。

OPTIONS 方法

OPTIONS 方法描述目标资源的通信选项。