In [1]:
# 1. magic for inline plot
# 2. magic to print version
# 3. magic so that the notebook will reload external python modules
# 4. magic to enable retina (high resolution) plots
# https://gist.github.com/minrk/3301035
%matplotlib inline
%load_ext watermark
%load_ext autoreload
%autoreload 2
%config InlineBackend.figure_format='retina'
import os
import time
import numpy as np
import pandas as pd
import lightgbm as lgb
import sklearn.metrics as metrics
from sklearn.model_selection import train_test_split
from sklearn.datasets.california_housing import fetch_california_housing
# prevent scientific notations
pd.set_option('display.float_format', lambda x: '%.3f' % x)
%watermark -a 'Ethen' -d -t -v -p numpy,pandas,sklearn,lightgbm
We'll try and keep the data, feature engineering, model training part as short as possible as the main focus of the repo is to build a service on top of the model.
Loads the dataset.
In [2]:
cal_housing = fetch_california_housing()
print('feature names:', cal_housing.feature_names)
print('data shape: ', cal_housing.data.shape)
print('description:')
print(cal_housing.DESCR)
A quick train/test split.
In [3]:
test_size = 0.2
random_state = 123
X_train, X_test, y_train, y_test = train_test_split(
cal_housing.data,
cal_housing.target,
test_size=test_size,
random_state=random_state)
In [4]:
print(cal_housing.feature_names)
Following the LightGBM Python Quickstart to train the model.
In [5]:
dtrain = lgb.Dataset(X_train, y_train,
feature_name=cal_housing.feature_names,
free_raw_data=False)
dtest = lgb.Dataset(X_test, y_test,
feature_name=cal_housing.feature_names,
free_raw_data=False)
dtrain
Out[5]:
In [6]:
params_constraint = {
'nthread': 6,
'seed': 0,
'metric': 'rmse',
'eta': 0.1,
'max_depth': 5
}
evals_result = {}
model = lgb.train(
params_constraint, dtrain,
valid_sets=[dtrain, dtest],
evals_result=evals_result,
num_boost_round=1000,
early_stopping_rounds=10,
verbose_eval=50)
Quick evaluation of our regression model.
In [7]:
def mape_score(y_true, y_score):
"""Mean Absolute Percentage Error (MAPE)."""
mask = y_true != 0
y_true = y_true[mask]
y_score = y_score[mask]
mape = np.abs(y_true - y_score) / y_true
return np.mean(mape)
def compute_score(model, dataset, verbose=True):
"""
Computes the model evaluation score (r2, rmse, mape) for the
input model and dataset.
"""
y_true = dataset.get_label()
y_score = model.predict(dataset.get_data())
r2 = round(metrics.r2_score(y_true, y_score), 3)
rmse = round(np.sqrt(metrics.mean_squared_error(y_true, y_score)), 3)
mape = round(mape_score(y_true, y_score), 3)
if verbose:
print('r2: ', r2)
print('rmse: ', rmse)
print('mape: ', mape)
return r2, rmse, mape
In [8]:
r2, rmse, mape = compute_score(model, dtest)
Saves the trained model under the app
folder.
In [9]:
save_path = os.path.join('app', 'model.txt')
model.save_model(save_path, num_iteration=model.best_iteration)
Ensure the prediction between the model and the saved model matches. Here we pass in the whole test set.
In [10]:
predictions = model.predict(dtest.get_data())
predictions
Out[10]:
In [11]:
model_loaded = lgb.Booster(model_file=save_path)
predictions = model_loaded.predict(dtest.get_data())
predictions
Out[11]:
We can also perform prediction for a single record. The caveat here is that .predict
expects a 2d array, hence for single record prediction, we need to reshape it to 2d first.
In [12]:
row = dtest.get_data()[0].reshape(1, -1)
row
Out[12]:
In [13]:
model.predict(row)
Out[13]:
Before proceeding on to this section, we need to create the service first. Either follow the Docker Container section in the README to host the service locally through a container or power through the Azure Kubernetes Cluster section to host the service on Azure Kubernetes Cluster.
Once we host the service, and can test it using the request
library.
In [14]:
import json
import requests
In [15]:
# data = {
# "MedInc": 0,
# "HouseAge": 0,
# "AveRooms": 0,
# "AveBedrms": 0,
# "Population": 0,
# "AveOccup": 0,
# "Latitude": 0,
# "Longitude": 0
# }
data = {feature_name: value for feature_name, value in zip(cal_housing.feature_names, dtest.get_data()[0])}
data
Out[15]:
Change the url accordingly. And pass our features as a json body.
In [16]:
# e.g. for local deployment
# url = 'http://127.0.0.1:8000/predict'
# e.g. for local docker deployment
# url = 'http://0.0.0.0:80/predict'
# e.g. for azure kubernetes cluster deployment
url = 'http://13.91.195.109:80/predict'
raw_response = requests.post(url, data=json.dumps(data))
raw_response.raise_for_status()
response = json.loads(raw_response.text)
response
Out[16]:
In [17]:
%%timeit
# speed benchmark of the model
model.predict(row)[0]
In [18]:
%%timeit
# speed benchmark of the model hosted as a service
raw_response = requests.post(url, data=json.dumps(data))
raw_response.raise_for_status()
response = json.loads(raw_response.text)
response
We've also implemented the endpoint for supporting batch calls, i.e. to get the scores for multiple records in a single call.
In [19]:
payloads = []
for data in dtest.get_data()[:3]:
payload = {feature_name: value for feature_name, value in zip(cal_housing.feature_names, data)}
payloads.append(payload)
payloads
Out[19]:
In [20]:
url = 'http://13.91.195.109:80/batch/predict'
raw_response = requests.post(url, data=json.dumps(payloads))
raw_response.raise_for_status()
response = json.loads(raw_response.text)
response
Out[20]:
In [21]:
%%timeit
# speed benchmark of the model hosted as a service using the batch endpoint
raw_response = requests.post(url, data=json.dumps(payloads))
raw_response.raise_for_status()
response = json.loads(raw_response.text)
response