Já devo ter mais horas de discussão sobre python vs R do que commits no Github. E, apesar de gostar da treta, quando se trata de implantar modelos em produção, a linguagem de programação da solução não deveria um impedimento.
No paper Hidden Technical Debt in Machine Learning Systems da Google, ao ser discutido toda complexidade envolvida em implantar um modelo, o código é apresentado como um parte diminuta frente à toda problemática existente.
E, assim, deve continuar sendo.
Pensando nisso, neste post, vamos entender como por em produção um modelo criado em R utilizando o Sagemaker - ferramenta de ML da AWS.
Sagemaker Endpoints
O Sagemaker consiste em inúmeras APIs que procuram englobar o ciclo de vida de um modelo de Machine Learning. Encontramos soluções que vão desde o treinamento, seleção de modelos e deploy até o monitoramento e recalibração desses modelos.
Neste contexto, o deploy de modelos ocorre através dos Sagemaker Endpoints
, serviço gerenciado da AWS para que permite realizar inferências (predict) de modelos na forma de um rest API. Por gerenciado quero dizer que toda a infraestrutura de rede, atualização das máquinas, monitoramento, até DNS é responsabilidade da AWS.
Caso você utilize o pacote python do Sagemaker
ou o [boto3
] SDK da AWS em python, o processo de criação do endpoint a partir do modelo treinado é transparente a ponto de envolver apenas a seleção do modelo que será deployado e o tamanho da máquina que irá servir o modelo.
Esse processo pode ser tão transparente quanto for desejável, desde que alguns requisitos sejam atendidos.
Por baixo dos panos, um Sagemaker Endpoint
consiste em uma máquina EC2 que executa um container docker expondo um webserver na porta 8080. Neste container estão os artefatos de modelagem (modelos e/ou dados) e códigos para lidar com as requisições ao endpoint e retornar o valor predito pelo modelo. Finalmente, o container deve ter um arquivo executável chamado serve
que levanta o webserver exposto pelo endpoint.
Utilizando as soluções de prateleira do Sagemaker, nos vemos presos a containers próprios da AWS e à linguagem python, mas na minha descrição anterior em nenhum momento comento sobre qualquer de linguagem de programação.
</br>
Preparando seu código para produção
A primeira etapa é criar uma API rest com R através do pacote plumber
. O padrão dos Sagemaker Endpoints
espera uma rota GET chamada /ping para realizar os health checks, e uma rota POST /invocations para obter as predições do modelo.
No código a seguir, exponho um modelo de árvores criado pelo pacote ranger
e salvo no arquivo model.RDS
. É importante notar que o modelo está localizado no diretório /opt/ml/model
e que o arquivo plumber.R
deve estar salvo no diretório /opt/ml
(padrão do Sagemaker).
# plumber.R
library(jsonlite)
library(ranger)
library(plumber)
#' Rota para avaliar a saúde da aplicação
#' @get /ping
function(res) {
res$status <- 200
return('')
}
#' Rota para obter a predição do modelo
#' @param req request
#' @post /invocations
function(req, res, prefix = "/opt/ml/model/") {
res$status <- 200
data <- as.data.frame(jsonlite::fromJSON(req$postBody))
model <- readRDS(paste0(prefix, "model.RDS"))
result <- predict(model, data = data)
jsonlite::toJSON(result$prediction)
}
O código que executará o arquivo plumber.R
deve ser chamar serve
e conter as instruções para execução da API. Dois detalhes devem ser notados: (1) o webserver é exposto na porta 8080 e (2) o arquivo server
é um executável Rscript.
#!/usr/bin/env Rscript
serve <- function(prefix = "/opt/ml/") {
app <- plumber::plumb(paste0(prefix, 'plumber.R'))
app$run(host='0.0.0.0', port=8080)
}
args <- commandArgs()
if (any(grepl('serve', args))) {
serve()}
Esta última condição vem do fato que o container que entregaremos ao Sagemaker Endpoint
deve ser capaz de iniciar o webserver fazendo a seguinte chamada.
docker run -p 8080:8080 <docker-image> serve
Finalmente, encapsulamos toda a solução em uma imagem docker que deve ser armazenada no Dockerhub
ou no AWS ECR
. No arquivo Dockerfile a seguir, é apresentado um exemplo compacto dessa imagem Docker, na qual os pacotes R utilizados são instalados via pacote renv
.
FROM adelmofilho/r-base:4.0.2 #imagem com R instalado
RUN apt-get update
RUN apt-get install -y libsodium-dev #dependencia do plumber
WORKDIR /app
COPY renv.lock renv.lock
RUN R -e "renv::restore()" #instalacao de pacotes R
COPY model.RDS /opt/ml/model/model.RDS
COPY plumber.R /opt/ml/plumber.R
COPY serve /usr/local/bin/serve #criacao do executavel
RUN chmod +x /usr/local/bin/serve
EXPOSE 8080
Em mãos do endereço para sua imagem docker, a criação de um Sagemaker model
passa pela definição de um nome e da imagem docker a que faz referência.
Em seguida, crie a configuração de seu endpoint. Além do nome da configuração, adicione na configuração o modelo criado na etapa anterior.
A última etapa do processo consiste na criação do endpoint em si, que vem com a informação da configuração do endpoint criado na etapa anterior.
Uma vez finalizada a criação, uma url semelhante a https://runtime.sagemaker.sa-east-1.amazonaws.com/endpoints/mlworks-deploypy-Endpoint/invocations
para obter as predições de nosso modelo.
Expondo seus modelos para internet
O Sagemaker Endpoint
, isoladamente, é acessível apenas por usuários autenticados na mesma conta da AWS em que ele foi criado. Por está razão é comum, associar a ele uma função Lambda e um API gateway para tornar seu acesso público pela internet.
Partindo dessa arquitetura, será possível realizar uma chamada curl
tradicional pela internet.
curl -v -d '{"Sepal.Length":1, "Sepal.Width":2, "Petal.Length":3, "Petal.Width":4}' -X POST https://ltchxfsiy2.execute-api.sa-east-1.amazonaws.com/predict
A criação da infraestrutura API gateway/Lambda está fora do escopo desse post, mas no repositório a seguir é disponibilizado toda a infraestrutura como código.