R và Python đang là 2 ngôn ngữ được các nhà data scientist sử dụng nhiều nhất trong công việc phân tích dữ liệu và xây dựng các mô hình machine learning. Trong bản thân hai ngôn ngữ này luôn có những package được thiết kế cho việc xử lý dữ liệu và phong cách viết code của hai ngôn ngữ này tạo ra sự khác biệt so với ngôn ngữ của các developers như C, C++, Java … trong việc xây dựng mobile app, web…
Do vậy việc kết hợp giữa các data scientists với developers luôn là một bài toán cần giải đáp khi một developers không thể dịch một machine learning từ ngôn ngữ R và Python sang ngôn ngữ họ sử dụng để thiết kế một web hay app.
Từ một thực tế như vậy, machine learning API (Application Program Interface) xuất hiện như một giải pháp để kết nối hai bên mà không cần phải học ngôn ngữ của nhau.
Trước tiên, ta cần nói về API là gì?
Về định nghĩa của API (Application Program Interface) đã có rất nhiều trên googl, đa phần là các định nghĩa nói khá nhiều về kĩ thuật nên những người mới tìm hiểu sẽ khá là khó hiểu (mình là một ví dụ). Tuy nhiên, sau một thời gian nghiên cứu viết machine learning API thì mình rút được ra là: API là một phần mềm được xây dựng để kết nối với các phần mềm khác.
Ví dụ về API: trong các tổ chức tín dụng luôn có hệ thống scoring để đánh giá xem khách hàng trên có được cho vay hay không. Trên màn hình nhập liệu sẽ được thiết kế bởi các developers để cho các Sale đưa các input đầu vào của model. Các input này như sẽ được đưa vào mô hình machine learning thông qua API và output từ mô hình cũng sẽ được đưa ra màn hình hiển thị thông qua API để cho Sale biết được kết quả. Do vậy, các developers và data scientists có thể làm công việc của họ suôn sẻ.
RESTful API là một dạng của API sử dụng thông qua HTTP (URL trên web browser)
Do đây là API cho mô hình machine learning nên việc đầu tiên cần làm đó là xây dựng mô hình dự báo. Ta sẽ xây nhanh một mô hình đơn giản bằng decision tree với 3 biến đầu vào. Data được sử dụng đó chính là Titanic Data nói về tảm họa Titanic trong lịch sử và từ những biến đầu vào được cung cấp ta cần xây dựng mô hình dự báo khả năng sống sót của khách hàng trong thảm họa này. Các bạn có thể đọc bài phân tích về thảm họa này trong article trước đó của mình:
http://rpubs.com/thanhleo92/331246
titanic_data <- read_csv("Data/train.csv")
transform_titantic_data <- function(input_titantic_data) {
ouput_titantic_data <- data.frame(
survived = factor(input_titantic_data$Survived, levels = c(0, 1)),
pclass = factor(input_titantic_data$Pclass, levels = c(1, 2, 3)),
female = tolower(input_titantic_data$Sex) == "female",
age = factor(dplyr::if_else(input_titantic_data$Age < 18, "child", "adult", "unknown"),
levels = c("child", "adult", "unknown"))
)
}
clean_titanic <- transform_titantic_data(titanic_data)
set.seed(42)
training_rows <- sample(1:nrow(clean_titanic), size = floor(0.7*nrow(clean_titanic)))
train_df <- clean_titanic[training_rows, ]
test_df <- clean_titanic[-training_rows, ]
titanic_glm <- glm(survived ~ pclass + female + age,
data = clean_titanic, family = binomial(link = "logit"))
test_predictions <- predict(titanic_glm, newdata = test_df, type = "response") >= 0.5
test_actuals <- test_df$survived == 1
accuracy <- table(test_predictions, test_actuals)
print(accuracy)
print(paste0("Accuracy: ", round(100 * sum(diag(accuracy))/sum(accuracy), 2), "%"))
saveRDS(titanic_glm, file = "model.Rds", compress = TRUE)
model <- titanic_glm
save(model, file = "model.Rdata")
Ta cần save code trên vào một script riêng có tên là: Code.R
Cần save code bên dưới vào một script khác có tên là templete.R
Lưu ý: cần để trống line cuối cùng của Script để việc chạy bước tiếp theo sẽ trở lên dễ dàng hơn
load("model.Rdata")
MODEL_VERSION <- "0.0.1"
VARIABLES <- list(
pclass = "Pclass = 1, 2, 3 (Ticket Class: 1st, 2nd, 3rd)",
sex = "Sex = male or female",
age = "Age = # in years",
gap = "",
survival = "Successful submission will results in a calculated Survival Probability from 0 to 1 (Unlikely to More Likely)"
)
# test API working --------------------------------------------------------
#* @get /healthcheck
health_check <- function() {
result <- data.frame(
"input" = "",
"status" = 200,
"model_version" = MODEL_VERSION
)
return(result)
}
# API landing page --------------------------------------------------------
#* @get /
#* @html
home <- function() {
title <- "Titanic Survival API"
body_intro <- "Welcome to the Titanic Survival API!"
body_model <- paste("We are currently serving model version:", MODEL_VERSION)
body_msg <- paste("To received a prediction on survival probability,",
"submit the following variables to the <b>/survival</b> endpoint:",
sep = "\n")
body_reqs <- paste(VARIABLES, collapse = "<br>")
result <- paste(
"<html>",
"<h1>", title, "</h1>", "<br>",
"<body>",
"<p>", body_intro, "</p>",
"<p>", body_model, "</p>",
"<p>", body_msg, "</p>",
"<p>", body_reqs, "</p>",
"</body>",
"</html>",
collapse = "\n"
)
return(result)
}
# helper functions for predict --------------------------------------------
transform_titantic_data <- function(input_titantic_data) {
ouput_titantic_data <- data.frame(
pclass = factor(input_titantic_data$Pclass, levels = c(1, 2, 3)),
female = tolower(input_titantic_data$Sex) == "female",
age = factor(dplyr::if_else(input_titantic_data$Age < 18, "child", "adult", "unknown"),
levels = c("child", "adult", "unknown"))
)
}
validate_feature_inputs <- function(age, pclass, sex) {
age_valid <- (age >= 0 & age < 200 | is.na(age))
pclass_valid <- (pclass %in% c(1, 2, 3))
sex_valid <- (sex %in% c("male", "female"))
tests <- c("Age must be between 0 and 200 or NA",
"Pclass must be 1, 2, or 3",
"Sex must be either male or female")
test_results <- c(age_valid, pclass_valid, sex_valid)
if(!all(test_results)) {
failed <- which(!test_results)
return(tests[failed])
} else {
return("OK")
}
}
# predict endpoint --------------------------------------------------------
#* @post /survival
#* @get /survival
predict_survival <- function(Age=NA, Pclass=NULL, Sex=NULL) {
age = as.integer(Age)
pclass = as.integer(Pclass)
sex = tolower(Sex)
valid_input <- validate_feature_inputs(age, pclass, sex)
if (valid_input[1] == "OK") {
payload <- data.frame(Age=age, Pclass=pclass, Sex=sex)
clean_data <- transform_titantic_data(payload)
prediction <- predict(model, clean_data, type = "response")
result <- list(
input = list(payload),
reposnse = list("survival_probability" = prediction,
"survival_prediction" = (prediction >= 0.5)
),
status = 200,
model_version = MODEL_VERSION)
} else {
result <- list(
input = list(Age = Age, Pclass = Pclass, Sex = Sex),
response = list(input_error = valid_input),
status = 400,
model_version = MODEL_VERSION)
}
return(result)
}
library(plumber)
serve_model <- plumb("template.R")
serve_model$run(port = 8000)
localhost:8000/survival?Sex=male&Pclass=1&Age=26
Khi sử dụng API ta luôn gặp một vấn đề đó chính là khi cùng một lúc có 100 query đi vào mô hình. Trong khi mô hình chỉ có thể xử lý từng query một, do vậy những lượt query tiếp theo sẽ phải chờ đợt khá lâu để đến lượt. Từ mộ thực tế như vậy, ta cần xây dựng những model chạy song song để phục vụ việc query của các máy khác nhau.
Docker là một giải pháp để xử lý vấn đề trên
https://github.com/thanhleo/Scale-out-machine-learning-using-Docker
docker build -t thanhleo_machinelearning .
docker run --rm -p 8000:8000 thanhleo_machine_learning
localhost:8000/survival?Sex=male&Pclass=1&Age=26
Như vậy các bạn đã hoàn thành việc tạo API và scale mô hình machine learning để sử dụng trong thực tế và trong real time prediction.