Creating the REST API
With Plumber it’s really easy to create a simple API in our case we define a function that takes a pars variable to be a string of coma separated values. Using the same model back-end you could define different functions depending on the desired input. For example you could modify the input to take batch CSVs from a cloud environment.
In this case using plumber could not be easier. The next code chunk would be our REST controller. We save this file as ./R/REST_controller.R
library(mlr)
library(stringr)
model <- readRDS("./output/model_best.rds") # To change when pushed from other machine
#* @get /is_fraud
get_predict_fraud <- function(pars){
names <- model$features
input <- as.numeric(stringr::str_split_fixed(pars,",",30))
new_data <- data.frame(t(input))
names(new_data) <- names
predict(model,newdata = new_data)$data
}
That the function, note the comment using #* @get to mark the function and it’s location.
Next we define the main server file
library(plumber)
r <- plumb("./R/REST_controller.R")
r$run(port=80, host="0.0.0.0")
Host 0.0.0.0 means that the controller will run on your localhost port 80. Now you can make calls to it within you network or expose it to the rest of the internet.
Deploying as a micro-service
If you want to share that from another computer other than you laptop you might want to have your RESTful API running on a server in a controlled environment. For that saving the entire process in a docker container will allow you to make sure it runs the same in every computer and that you can easily “spin up” copies of your API to meet increased demand. That is specially useful when using Elastic Computing solutions.
Other than installing docker in your system there is not much you need to create the container.
This is a dockerfile, it contains instruction to create a container based on rocker version 3.5.1, install the necessary packages and libraries to enable the model to run and then copy the files from this folder inside the virtual image. You should save it as dockerfile in the root of your project.
# start from the rocker/r-ver:3.5.0 image
FROM rocker/r-ver:3.5.1
# install the linux libraries needed for plumber
RUN apt-get update -qq && apt-get install -y \
libssl-dev \
libcurl4-gnutls-dev \
libxml2-dev
# install plumber and needed libraries
RUN R -e "install.packages('plumber')"
RUN R -e "install.packages('XML')"
RUN R -e "install.packages('stringr')"
RUN R -e "install.packages('mlr')"
RUN R -e "install.packages('xgboost')"
#RUN install2.r -e plumber XML mlr stringr xgboost
# copy everything from the current directory into the container
COPY / /
# open port 80 to traffic
EXPOSE 80
# when the container starts, start the main.R script
ENTRYPOINT ["Rscript", "main.R"]
Building the image is done with a simple command, in this case we name the image plumbr-demo
docker build -t plumbr-demo .
Once built, it can take a bit, you can spin an instance of it (or many!). This maps port 80 on the container to port 80 on your machine.
docker run --rm -p 80:80 plumber-demo
Following this steps you can train a model, create an API based on it and bundle it in a virtual container in very simple steps. You could set up a continuous deliver where you retrain the model with new data every day and redeploy the model.
I hope you have enjoyed this simple two part tutorial, do not doubt in contacting me if you have any questions.
LS0tCnRpdGxlOiAiUGFydCAyOiBEZXBsb3lpbmcgYSBtb2RlbCIKYXV0aG9yOiAiRmVybmFuZG8gTXVub3oiCm91dHB1dDogaHRtbF9ub3RlYm9vawotLS0KCmBgYHtyIHNldHVwLCBpbmNsdWRlID0gRkFMU0V9CmtuaXRyOjpvcHRzX2NodW5rJHNldChldmFsID0gRkFMU0UsIGNhY2hlID0gVFJVRSkKYGBgCgojIyBDcmVhdGluZyB0aGUgUkVTVCBBUEkKCldpdGggUGx1bWJlciBpdCdzIHJlYWxseSBlYXN5IHRvIGNyZWF0ZSBhIHNpbXBsZSBBUEkgaW4gb3VyIGNhc2Ugd2UgZGVmaW5lIGEgZnVuY3Rpb24gdGhhdCB0YWtlcyBhIGBwYXJzYCB2YXJpYWJsZSB0byBiZSBhIHN0cmluZyBvZiBjb21hIHNlcGFyYXRlZCB2YWx1ZXMuIFVzaW5nIHRoZSBzYW1lIG1vZGVsIGJhY2stZW5kIHlvdSBjb3VsZCBkZWZpbmUgZGlmZmVyZW50IGZ1bmN0aW9ucyBkZXBlbmRpbmcgb24gdGhlIGRlc2lyZWQgaW5wdXQuIEZvciBleGFtcGxlIHlvdSBjb3VsZCBtb2RpZnkgdGhlIGlucHV0IHRvIHRha2UgYmF0Y2ggQ1NWcyBmcm9tIGEgY2xvdWQgZW52aXJvbm1lbnQuIAoKSW4gdGhpcyBjYXNlIHVzaW5nIHBsdW1iZXIgY291bGQgbm90IGJlIGVhc2llci4gVGhlIG5leHQgY29kZSBjaHVuayB3b3VsZCBiZSBvdXIgUkVTVCBjb250cm9sbGVyLiBXZSBzYXZlIHRoaXMgZmlsZSBhcyBgLi9SL1JFU1RfY29udHJvbGxlci5SYAoKYGBge3J9CmxpYnJhcnkobWxyKQpsaWJyYXJ5KHN0cmluZ3IpCgoKbW9kZWwgPC0gcmVhZFJEUygiLi9vdXRwdXQvbW9kZWxfYmVzdC5yZHMiKSAjIFRvIGNoYW5nZSB3aGVuIHB1c2hlZCBmcm9tIG90aGVyIG1hY2hpbmUKCiMqIEBnZXQgL2lzX2ZyYXVkCmdldF9wcmVkaWN0X2ZyYXVkIDwtIGZ1bmN0aW9uKHBhcnMpewogIAogIG5hbWVzIDwtIG1vZGVsJGZlYXR1cmVzCiAgaW5wdXQgPC0gYXMubnVtZXJpYyhzdHJpbmdyOjpzdHJfc3BsaXRfZml4ZWQocGFycywiLCIsMzApKQogIG5ld19kYXRhIDwtIGRhdGEuZnJhbWUodChpbnB1dCkpCiAgbmFtZXMobmV3X2RhdGEpIDwtIG5hbWVzCiAgCiAgcHJlZGljdChtb2RlbCxuZXdkYXRhID0gbmV3X2RhdGEpJGRhdGEKICAKICAKfQpgYGAKClRoYXQgdGhlIGZ1bmN0aW9uLCBub3RlIHRoZSBjb21tZW50IHVzaW5nIGAjKiBAZ2V0YCB0byBtYXJrIHRoZSBmdW5jdGlvbiBhbmQgaXQncyBsb2NhdGlvbi4KCk5leHQgd2UgZGVmaW5lIHRoZSBtYWluIHNlcnZlciBmaWxlCgpgYGB7cn0KbGlicmFyeShwbHVtYmVyKQpyIDwtIHBsdW1iKCIuL1IvUkVTVF9jb250cm9sbGVyLlIiKQpyJHJ1bihwb3J0PTgwLCBob3N0PSIwLjAuMC4wIikKYGBgCgpIb3N0IGAwLjAuMC4wYCBtZWFucyB0aGF0IHRoZSBjb250cm9sbGVyIHdpbGwgcnVuIG9uIHlvdXIgbG9jYWxob3N0IHBvcnQgODAuIE5vdyB5b3UgY2FuIG1ha2UgY2FsbHMgdG8gaXQgd2l0aGluIHlvdSBuZXR3b3JrIG9yIGV4cG9zZSBpdCB0byB0aGUgcmVzdCBvZiB0aGUgaW50ZXJuZXQuIAoKIyMgRGVwbG95aW5nIGFzIGEgbWljcm8tc2VydmljZQoKSWYgeW91IHdhbnQgdG8gc2hhcmUgdGhhdCBmcm9tIGFub3RoZXIgY29tcHV0ZXIgb3RoZXIgdGhhbiB5b3UgbGFwdG9wIHlvdSBtaWdodCB3YW50IHRvIGhhdmUgeW91ciBSRVNUZnVsIEFQSSBydW5uaW5nIG9uIGEgc2VydmVyIGluIGEgY29udHJvbGxlZCBlbnZpcm9ubWVudC4gRm9yIHRoYXQgc2F2aW5nIHRoZSBlbnRpcmUgcHJvY2VzcyBpbiBhIGRvY2tlciBjb250YWluZXIgd2lsbCBhbGxvdyB5b3UgdG8gbWFrZSBzdXJlIGl0IHJ1bnMgdGhlIHNhbWUgaW4gZXZlcnkgY29tcHV0ZXIgYW5kIHRoYXQgeW91IGNhbiBlYXNpbHkgInNwaW4gdXAiIGNvcGllcyBvZiB5b3VyIEFQSSB0byBtZWV0IGluY3JlYXNlZCBkZW1hbmQuIFRoYXQgaXMgc3BlY2lhbGx5IHVzZWZ1bCB3aGVuIHVzaW5nIEVsYXN0aWMgQ29tcHV0aW5nIHNvbHV0aW9ucy4KCk90aGVyIHRoYW4gaW5zdGFsbGluZyBkb2NrZXIgaW4geW91ciBzeXN0ZW0gdGhlcmUgaXMgbm90IG11Y2ggeW91IG5lZWQgdG8gY3JlYXRlIHRoZSBjb250YWluZXIuCgpUaGlzIGlzIGEgZG9ja2VyZmlsZSwgaXQgY29udGFpbnMgaW5zdHJ1Y3Rpb24gdG8gY3JlYXRlIGEgY29udGFpbmVyIGJhc2VkIG9uIHJvY2tlciB2ZXJzaW9uIDMuNS4xLCBpbnN0YWxsIHRoZSBuZWNlc3NhcnkgcGFja2FnZXMgYW5kIGxpYnJhcmllcyB0byBlbmFibGUgdGhlIG1vZGVsIHRvIHJ1biBhbmQgdGhlbiBjb3B5IHRoZSBmaWxlcyBmcm9tIHRoaXMgZm9sZGVyIGluc2lkZSB0aGUgdmlydHVhbCBpbWFnZS4gWW91IHNob3VsZCBzYXZlIGl0IGFzIGBkb2NrZXJmaWxlYCBpbiB0aGUgcm9vdCBvZiB5b3VyIHByb2plY3QuCgpgYGB7YmFzaH0KIyBzdGFydCBmcm9tIHRoZSByb2NrZXIvci12ZXI6My41LjAgaW1hZ2UKRlJPTSByb2NrZXIvci12ZXI6My41LjEKCiMgaW5zdGFsbCB0aGUgbGludXggbGlicmFyaWVzIG5lZWRlZCBmb3IgcGx1bWJlcgpSVU4gYXB0LWdldCB1cGRhdGUgLXFxICYmIGFwdC1nZXQgaW5zdGFsbCAteSBcCiAgbGlic3NsLWRldiBcCiAgbGliY3VybDQtZ251dGxzLWRldiBcCiAgbGlieG1sMi1kZXYKCiMgaW5zdGFsbCBwbHVtYmVyIGFuZCBuZWVkZWQgbGlicmFyaWVzClJVTiBSIC1lICJpbnN0YWxsLnBhY2thZ2VzKCdwbHVtYmVyJykiClJVTiBSIC1lICJpbnN0YWxsLnBhY2thZ2VzKCdYTUwnKSIKUlVOIFIgLWUgImluc3RhbGwucGFja2FnZXMoJ3N0cmluZ3InKSIKUlVOIFIgLWUgImluc3RhbGwucGFja2FnZXMoJ21scicpIgpSVU4gUiAtZSAiaW5zdGFsbC5wYWNrYWdlcygneGdib29zdCcpIgojUlVOIGluc3RhbGwyLnIgLWUgIHBsdW1iZXIgWE1MIG1sciBzdHJpbmdyIHhnYm9vc3QKCiMgY29weSBldmVyeXRoaW5nIGZyb20gdGhlIGN1cnJlbnQgZGlyZWN0b3J5IGludG8gdGhlIGNvbnRhaW5lcgpDT1BZIC8gLwoKIyBvcGVuIHBvcnQgODAgdG8gdHJhZmZpYwpFWFBPU0UgODAKCiMgd2hlbiB0aGUgY29udGFpbmVyIHN0YXJ0cywgc3RhcnQgdGhlIG1haW4uUiBzY3JpcHQKRU5UUllQT0lOVCBbIlJzY3JpcHQiLCAibWFpbi5SIl0KYGBgCgpCdWlsZGluZyB0aGUgaW1hZ2UgaXMgZG9uZSB3aXRoIGEgc2ltcGxlIGNvbW1hbmQsICBpbiB0aGlzIGNhc2Ugd2UgbmFtZSB0aGUgaW1hZ2UgYHBsdW1ici1kZW1vYAoKYGBge2Jhc2h9CmRvY2tlciBidWlsZCAtdCBwbHVtYnItZGVtbyAuIApgYGAKCgpPbmNlIGJ1aWx0LCBpdCBjYW4gdGFrZSBhIGJpdCwgeW91IGNhbiBzcGluIGFuIGluc3RhbmNlIG9mIGl0IChvciBtYW55ISkuIFRoaXMgbWFwcyBwb3J0IDgwIG9uIHRoZSBjb250YWluZXIgdG8gcG9ydCA4MCBvbiB5b3VyIG1hY2hpbmUuCgpgYGB7YmFzaH0KZG9ja2VyIHJ1biAtLXJtIC1wIDgwOjgwIHBsdW1iZXItZGVtbwpgYGAKCkZvbGxvd2luZyB0aGlzIHN0ZXBzIHlvdSBjYW4gdHJhaW4gYSBtb2RlbCwgY3JlYXRlIGFuIEFQSSBiYXNlZCBvbiBpdCBhbmQgYnVuZGxlIGl0IGluIGEgdmlydHVhbCBjb250YWluZXIgaW4gdmVyeSBzaW1wbGUgc3RlcHMuIFlvdSBjb3VsZCBzZXQgdXAgYSBjb250aW51b3VzIGRlbGl2ZXIgd2hlcmUgeW91IHJldHJhaW4gdGhlIG1vZGVsIHdpdGggbmV3IGRhdGEgZXZlcnkgZGF5IGFuZCByZWRlcGxveSB0aGUgbW9kZWwuCgoKSSBob3BlIHlvdSBoYXZlIGVuam95ZWQgdGhpcyBzaW1wbGUgdHdvIHBhcnQgdHV0b3JpYWwsIGRvIG5vdCBkb3VidCBpbiBjb250YWN0aW5nIG1lIGlmIHlvdSBoYXZlIGFueSBxdWVzdGlvbnMu