plumberとshinyを使ってマイクロサービスを作ってみた

こんにちは。データサイエンスチーム 山川です。
この記事はNHN テコラス Advent Calendar 2018の5日目の記事です。

データ分析をする際に、短時間でモデルを構築して動くモノを作りたい、ということがしばしばあります。

インフラを整備して開発環境を構築して、機能設計、開発、テスト、レビュー、デプロイ、、、と本格的なシステム開発を行うのではなく、「とりあえず」的なアウトプットがしたい場合です。

アウトプットとして要望されることが多いのが

  • API
  • Webアプリケーション

ですが、R言語ならいずれも簡単に提供できます。

どんなモデルを作りたいのか

まず、必要なパッケージを読み込みます。

library(tidyverse)
library(plumber)
library(shiny)

Rに組みこまれているcarsというデータセットがあります。

1920年代に観測されたデータで、車の速度(speed)と停止する距離(dist)を示すものです。

speedの単位はマイル/時、distの単位はフィートです。

cars %>% ggplot(aes(speed, dist)) + geom_point() +
  stat_smooth(method = "lm", se = FALSE, colour = "green", size = 1)

と散布図を作ると、そこそこ線形な関係がありそうです。

線形モデルを作って、

library(tidyverse)
carsmodel <- lm(dist~speed, data = datasets::cars)

モデルをRda形式で保存します。

save(carsmodel, file = "carsmodel.Rda")

モデルに対してspeedを指定するとdistの推測値がかえる関数を定義します。

lmpred <- function(var){ predict(object = carsmodel, newdata = data.frame(speed = var)) %>%
  unname()
}

lmpred関数を用いると、

lmpred(10)
>[1] 21.74499

のように推測値が返ってくるわけですが、これをWebアプリケーション化やAPI化するのが、今回やりたいことです。

API化

APIと一口に言ってもさまざまですが、ここではplumberというパッケージを用いてREST APIを作ることにします。

まず、以下のコードをlmpred.Rというファイル名で保存します。

#* @get /lmpred
function(speed = 1) {
  load("carsmodel.Rda")
  dist <- predict(object = carsmodel, newdata = data.frame(speed = as.numeric(speed))) %>%
  unname()
  return(dist)
}

これは、

  • GETメソッドに対応している
  • speedのデフォルト値は1である
  • 都度carsmodel.Rdaに保存されているモデルをロードしている
  • distという変数を与えられたspeedの値から導出している
  • distを関数の帰り値としている

といったことを意味しています。

次に以下のコードをコンソールから実行します。

rapi1 <- plumb("lmpred.R")
rapi1$run(port=8000)

すると、

Starting server to listen on port 8000
Running the swagger UI at http://127.0.0.1:8000/swagger/

とメッセージが表示されます。そこで、改めてブラウザから

http://localhost:8000/lmpred?speed=10

にアクセスすると、distの推測値である21.745が返っていることが確認できます。

もちろんcurlでも確かめられて、

curl -XGET http://localhost:8000/lmpred?speed=10

を実行すると、同じ結果が得られます。

Webサービス化

次に、shinyパッケージを使ってWebサービスを作ります。
最近、RとShinyで作るWebアプリケーションという好著も出たことで、shinyは以前より使いやすくなりました。

shinyの基本的な使い方は、ユーザーインタフェースを提供するui.Rと処理系を提供するserver.Rの二つを組み合わせてWebサービスを作ることです。

まず、以下のコードをui.Rとして保存します(文字コードはUTF-8にしてください。また、うまく動かない場合は日本語をやめてメッセージを半角文字にしてみてください)。

library(shiny)
shinyUI(fluidPage(
  sidebarLayout(
    sidebarPanel(
      numericInput("speed",
    h3("speedを入力してください"),
    value = 1)),
    mainPanel(
      h1("distの予測結果"),
      textOutput("text")
  ))
))

次に、以下のコードをserver.Rとして保存します。

library(shiny)
load("carsmodel.Rda")
shinyServer(function(input, output) {
  output$text <- renderText({
      dist <- predict(object = carsmodel, newdata = data.frame(speed = input$speed)) %>%
        unname()
    return(dist)
  })
})

以上2ファイルをlmpredというフォルダに保存した上で、以下のコードをコンソールから実行します。

runApp("lmpred")

すると、このような画面になり、speedを入力すると、distの予測結果が表示されます。

まとめ

線形回帰モデルというあまりにも単純な例ではありましたが、ひとつのRdaファイルを用いてAPIとWebサービスの二つを作ってみました。複雑なモデルであっても、本質的には同じアプローチでサービス化ができるので、簡単なデプロイをしてみたい方はぜひ参考にしてください。

AWS移行支援キャンペーン

あなたにおすすめの記事