From 0a5efd1e4ec02db1cffca260beb6207341751342 Mon Sep 17 00:00:00 2001 From: jsamantaucd <159275418+jsamantaucd@users.noreply.github.com> Date: Mon, 22 Jul 2024 16:27:09 +0100 Subject: [PATCH 01/15] test commit --- README.md | 1 + 1 file changed, 1 insertion(+) diff --git a/README.md b/README.md index 2761e7ddf02..d09dff0cd89 100644 --- a/README.md +++ b/README.md @@ -1,3 +1,4 @@ +Adding ARIMA [](https://github.com/bentoml/BentoML)
From dcffce9bad12f75caf16803d2dcebc04dc84f1b7 Mon Sep 17 00:00:00 2001 From: jsamanta_ucd Date: Mon, 22 Jul 2024 17:31:24 +0100 Subject: [PATCH 02/15] Update Readme --- README.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/README.md b/README.md index d09dff0cd89..1496256861a 100644 --- a/README.md +++ b/README.md @@ -1,4 +1,4 @@ -Adding ARIMA +Adding ARIMA custom runner [](https://github.com/bentoml/BentoML)
From 5a15c19e2ea2747eeb9a62b5aa8ecf9b062c5201 Mon Sep 17 00:00:00 2001 From: jsamanta_ucd Date: Mon, 22 Jul 2024 18:05:34 +0100 Subject: [PATCH 03/15] Add custom arima model runner --- examples/custom_runner/arima_model/README.md | 0 .../custom_runner/arima_model/bentofile.yaml | 0 .../arima_model/requirements.txt | 0 examples/custom_runner/arima_model/service.py | 48 ++++++++++++++++ examples/custom_runner/arima_model/train.py | 55 +++++++++++++++++++ 5 files changed, 103 insertions(+) create mode 100644 examples/custom_runner/arima_model/README.md create mode 100644 examples/custom_runner/arima_model/bentofile.yaml create mode 100644 examples/custom_runner/arima_model/requirements.txt create mode 100644 examples/custom_runner/arima_model/service.py create mode 100644 examples/custom_runner/arima_model/train.py diff --git a/examples/custom_runner/arima_model/README.md b/examples/custom_runner/arima_model/README.md new file mode 100644 index 00000000000..e69de29bb2d diff --git a/examples/custom_runner/arima_model/bentofile.yaml b/examples/custom_runner/arima_model/bentofile.yaml new file mode 100644 index 00000000000..e69de29bb2d diff --git a/examples/custom_runner/arima_model/requirements.txt b/examples/custom_runner/arima_model/requirements.txt new file mode 100644 index 00000000000..e69de29bb2d diff --git a/examples/custom_runner/arima_model/service.py b/examples/custom_runner/arima_model/service.py new file mode 100644 index 00000000000..d2e833d2827 --- /dev/null +++ b/examples/custom_runner/arima_model/service.py @@ -0,0 +1,48 @@ +import numpy as np +import typing +import bentoml +from bentoml.io import JSON +from bentoml.io import NumpyNdarray +from statsmodels.tsa.arima.model import ARIMA + +arima_model_runner = bentoml.picklable_model.get("arima_forecast_model:latest") +history_data = arima_model_runner.custom_objects["historical_data"] + +# Custom runner for ARIMA model +class ARIMAForecastRunnable(bentoml.Runnable): + SUPPORTED_RESOURCES = ("cpu",) + SUPPORTS_CPU_MULTI_THREADING = False + + def __init__(self): + self.arima = ARIMA + + @bentoml.Runnable.method(batchable=False) + def forecast(self ,test_data): + predictions = [] + history_data = arima_model_runner.custom_objects["historical_data"] + + # Define the ARIMA tuning parameters + model = ARIMA(history_data, order=(5, 1, 0)) + model_fit = model.fit() + output = model_fit.forecast() + y_pred = output[0] + predictions.append(y_pred) + obs = test_data # single test value + history_data.append(obs) + print("New history data: ",history_data) + # Save model with BentoML + bentoml.picklable_model.save_model('arima_forecast_model', + model, + custom_objects= {"historical_data": history_data}, + signatures={"predict": {"batchable": True}}, + ) + return y_pred + + +arima_forecast_custom_runner = bentoml.Runner(ARIMAForecastRunnable) + +svc = bentoml.Service("arima_model_forecast", runners=[arima_forecast_custom_runner]) + +@svc.api(input=NumpyNdarray(dtype="float"), output=JSON()) +def predict(input_data: np.ndarray) -> typing.List[float]: + return arima_forecast_custom_runner.forecast.run(input_data) \ No newline at end of file diff --git a/examples/custom_runner/arima_model/train.py b/examples/custom_runner/arima_model/train.py new file mode 100644 index 00000000000..ad50571dbd2 --- /dev/null +++ b/examples/custom_runner/arima_model/train.py @@ -0,0 +1,55 @@ +# import libraries +import numpy as np +import pandas as pd +import bentoml +import statsmodels.api as sm +from sklearn.metrics import mean_squared_error +from statsmodels.tsa.arima.model import ARIMA + +def main(): + # Load the dataset + data = sm.datasets.sunspots.load_pandas().data + # Prepare dataset + data.index = pd.Index(sm.tsa.datetools.dates_from_range("1700", "2008")) + data.index.freq = data.index.inferred_freq + del data["YEAR"] + + # Split into train and test sets + X = data.values + size = int(len(X) * 0.66) + train, test = X[0:size], X[size:len(X)] + # Create a list of records to train ARIMA + history = [x for x in train] + # Create a list to store the predicted values + predictions = list() + + # Iterate over the test data + for t in range(len(test)): + model = ARIMA(history, order=(5, 1, 0)) + # fit the model and create forecast + model_fit = model.fit() + output = model_fit.forecast() + yhat = output[0] + predictions.append(yhat) + obs = test[t] + history.append(obs) + + y_test = test + y_pred = predictions + + # Calculate root mean squared error + rmse = np.sqrt(mean_squared_error(y_test, y_pred)) + print("Root Mean Squared Error:", rmse) + + # Save model with BentoML + saved_model = bentoml.picklable_model.save_model( + "arima_forecast_model", + model, + custom_objects= {"historical_data": history,}, + signatures={"predict": {"batchable": True}}, + ) + + print(f"Model saved: {saved_model}") + +if __name__ == "__main__": + main() \ No newline at end of file From 326c0b2452fe476914bcb319e4fb768ebf828216 Mon Sep 17 00:00:00 2001 From: jsamanta_ucd Date: Tue, 23 Jul 2024 11:06:24 +0100 Subject: [PATCH 04/15] Update service.py --- examples/custom_runner/arima_model/service.py | 20 +++++++++---------- 1 file changed, 9 insertions(+), 11 deletions(-) diff --git a/examples/custom_runner/arima_model/service.py b/examples/custom_runner/arima_model/service.py index d2e833d2827..2b275d1e519 100644 --- a/examples/custom_runner/arima_model/service.py +++ b/examples/custom_runner/arima_model/service.py @@ -5,21 +5,19 @@ from bentoml.io import NumpyNdarray from statsmodels.tsa.arima.model import ARIMA -arima_model_runner = bentoml.picklable_model.get("arima_forecast_model:latest") -history_data = arima_model_runner.custom_objects["historical_data"] - # Custom runner for ARIMA model class ARIMAForecastRunnable(bentoml.Runnable): SUPPORTED_RESOURCES = ("cpu",) SUPPORTS_CPU_MULTI_THREADING = False def __init__(self): - self.arima = ARIMA + super().__init__() @bentoml.Runnable.method(batchable=False) - def forecast(self ,test_data): + def forecast(self, test_data): predictions = [] - history_data = arima_model_runner.custom_objects["historical_data"] + arima_model = bentoml.picklable_model.get("arima_forecast_model:latest") + history_data = arima_model.custom_objects["historical_data"] # Define the ARIMA tuning parameters model = ARIMA(history_data, order=(5, 1, 0)) @@ -27,9 +25,9 @@ def forecast(self ,test_data): output = model_fit.forecast() y_pred = output[0] predictions.append(y_pred) - obs = test_data # single test value + obs = test_data + # Update history with single test value history_data.append(obs) - print("New history data: ",history_data) # Save model with BentoML bentoml.picklable_model.save_model('arima_forecast_model', model, @@ -39,10 +37,10 @@ def forecast(self ,test_data): return y_pred -arima_forecast_custom_runner = bentoml.Runner(ARIMAForecastRunnable) +arima_model = bentoml.Runner(ARIMAForecastRunnable) -svc = bentoml.Service("arima_model_forecast", runners=[arima_forecast_custom_runner]) +svc = bentoml.Service("arima_model_forecast", runners=[arima_model]) @svc.api(input=NumpyNdarray(dtype="float"), output=JSON()) def predict(input_data: np.ndarray) -> typing.List[float]: - return arima_forecast_custom_runner.forecast.run(input_data) \ No newline at end of file + return arima_model.forecast.run(input_data) \ No newline at end of file From 0899b4c1c956d67a1e08493c9f1a378307fb07ec Mon Sep 17 00:00:00 2001 From: jsamanta_ucd Date: Wed, 24 Jul 2024 12:13:58 +0100 Subject: [PATCH 05/15] Update arima config files --- examples/custom_runner/arima_model/README.md | 53 +++++++++++++++++++ .../custom_runner/arima_model/bentofile.yaml | 5 ++ .../arima_model/requirements.txt | 2 + examples/custom_runner/arima_model/service.py | 6 +-- examples/custom_runner/arima_model/train.py | 1 + 5 files changed, 64 insertions(+), 3 deletions(-) diff --git a/examples/custom_runner/arima_model/README.md b/examples/custom_runner/arima_model/README.md index e69de29bb2d..1409df02ab5 100644 --- a/examples/custom_runner/arima_model/README.md +++ b/examples/custom_runner/arima_model/README.md @@ -0,0 +1,53 @@ +# Serving ARIMA model with BentoML + +This project demonstrates how to use a continuous learning ARIMA model +for a time-series data and use it as a prediction service in BentoML. + +## Requirements + +Install requirements with: + +```bash +pip install -r ./requirements.txt +``` + +## Instruction + +1. Train and save model: + +```bash +python ./train.py +``` + +2. Run the service: + +```bash +bentoml serve service.py:svc +``` + +## Test the endpoint + +Open in browser http://0.0.0.0:3000 to submit input images via the web UI. + +```bash +curl -X 'POST' 'http://0.0.0.0:3000/predict' -H 'accept: application/json' -H 'Content-Type: application/json' -d '[20]' +``` + +Sample result: +``` +14.584079431935686 +``` + +## Build Bento + +Build Bento using the bentofile.yaml which contains all the configurations required: + +```bash +bentoml build -f ./bentofile.yaml +``` + +Once the Bento is built, containerize it as a Docker image for deployment: + +```bash +bentoml containerize arima_forecast_model:latest +``` diff --git a/examples/custom_runner/arima_model/bentofile.yaml b/examples/custom_runner/arima_model/bentofile.yaml index e69de29bb2d..ae0093afdb0 100644 --- a/examples/custom_runner/arima_model/bentofile.yaml +++ b/examples/custom_runner/arima_model/bentofile.yaml @@ -0,0 +1,5 @@ +service: "service.py:svc" +include: +- "service.py" +python: + requirements_txt: ./requirements.txt diff --git a/examples/custom_runner/arima_model/requirements.txt b/examples/custom_runner/arima_model/requirements.txt index e69de29bb2d..9ec1666c910 100644 --- a/examples/custom_runner/arima_model/requirements.txt +++ b/examples/custom_runner/arima_model/requirements.txt @@ -0,0 +1,2 @@ +bentoml>=1.0.0 +statsmodels \ No newline at end of file diff --git a/examples/custom_runner/arima_model/service.py b/examples/custom_runner/arima_model/service.py index 2b275d1e519..dd75429adf2 100644 --- a/examples/custom_runner/arima_model/service.py +++ b/examples/custom_runner/arima_model/service.py @@ -37,10 +37,10 @@ def forecast(self, test_data): return y_pred -arima_model = bentoml.Runner(ARIMAForecastRunnable) +arima_forecast_runner = bentoml.Runner(ARIMAForecastRunnable) -svc = bentoml.Service("arima_model_forecast", runners=[arima_model]) +svc = bentoml.Service("arima_model_forecast", runners=[arima_forecast_runner]) @svc.api(input=NumpyNdarray(dtype="float"), output=JSON()) def predict(input_data: np.ndarray) -> typing.List[float]: - return arima_model.forecast.run(input_data) \ No newline at end of file + return arima_forecast_runner.forecast.run(input_data) \ No newline at end of file diff --git a/examples/custom_runner/arima_model/train.py b/examples/custom_runner/arima_model/train.py index ad50571dbd2..168d82f87fb 100644 --- a/examples/custom_runner/arima_model/train.py +++ b/examples/custom_runner/arima_model/train.py @@ -32,6 +32,7 @@ def main(): yhat = output[0] predictions.append(yhat) obs = test[t] + # update history with test data history.append(obs) y_test = test From e545931e709a679aab6cb9e868216fe0dff93b3c Mon Sep 17 00:00:00 2001 From: jsamanta_ucd Date: Wed, 24 Jul 2024 12:58:47 +0100 Subject: [PATCH 06/15] Update train and service files --- examples/custom_runner/arima_model/service.py | 2 ++ examples/custom_runner/arima_model/train.py | 2 +- 2 files changed, 3 insertions(+), 1 deletion(-) diff --git a/examples/custom_runner/arima_model/service.py b/examples/custom_runner/arima_model/service.py index dd75429adf2..67c3ee452b0 100644 --- a/examples/custom_runner/arima_model/service.py +++ b/examples/custom_runner/arima_model/service.py @@ -5,6 +5,8 @@ from bentoml.io import NumpyNdarray from statsmodels.tsa.arima.model import ARIMA +arima_model = bentoml.picklable_model.get("arima_forecast_model:latest") + # Custom runner for ARIMA model class ARIMAForecastRunnable(bentoml.Runnable): SUPPORTED_RESOURCES = ("cpu",) diff --git a/examples/custom_runner/arima_model/train.py b/examples/custom_runner/arima_model/train.py index 168d82f87fb..bebd5ee1305 100644 --- a/examples/custom_runner/arima_model/train.py +++ b/examples/custom_runner/arima_model/train.py @@ -47,7 +47,7 @@ def main(): "arima_forecast_model", model, custom_objects= {"historical_data": history,}, - signatures={"predict": {"batchable": True}}, + signatures={"predict": {"batchable": True}}, ) print(f"Model saved: {saved_model}") From 606aea5cdd2b5905a92f8168a416a517af7f1ce8 Mon Sep 17 00:00:00 2001 From: jsamanta_ucd Date: Wed, 24 Jul 2024 13:32:18 +0100 Subject: [PATCH 07/15] update service.py --- examples/custom_runner/arima_model/service.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/examples/custom_runner/arima_model/service.py b/examples/custom_runner/arima_model/service.py index 67c3ee452b0..c6061fdf54b 100644 --- a/examples/custom_runner/arima_model/service.py +++ b/examples/custom_runner/arima_model/service.py @@ -41,7 +41,7 @@ def forecast(self, test_data): arima_forecast_runner = bentoml.Runner(ARIMAForecastRunnable) -svc = bentoml.Service("arima_model_forecast", runners=[arima_forecast_runner]) +svc = bentoml.Service("arima_model_forecast", runners=[arima_forecast_runner], models=[arima_model]) @svc.api(input=NumpyNdarray(dtype="float"), output=JSON()) def predict(input_data: np.ndarray) -> typing.List[float]: From e7bec351db69e3f2d1f4c1ffa4b711f49f05f9d9 Mon Sep 17 00:00:00 2001 From: jsamanta_ucd Date: Thu, 25 Jul 2024 12:25:41 +0100 Subject: [PATCH 08/15] update service --- examples/custom_runner/arima_model/service.py | 39 ++++++------------- examples/custom_runner/arima_model/train.py | 1 - 2 files changed, 12 insertions(+), 28 deletions(-) diff --git a/examples/custom_runner/arima_model/service.py b/examples/custom_runner/arima_model/service.py index c6061fdf54b..11b7b428bf7 100644 --- a/examples/custom_runner/arima_model/service.py +++ b/examples/custom_runner/arima_model/service.py @@ -3,9 +3,6 @@ import bentoml from bentoml.io import JSON from bentoml.io import NumpyNdarray -from statsmodels.tsa.arima.model import ARIMA - -arima_model = bentoml.picklable_model.get("arima_forecast_model:latest") # Custom runner for ARIMA model class ARIMAForecastRunnable(bentoml.Runnable): @@ -14,35 +11,23 @@ class ARIMAForecastRunnable(bentoml.Runnable): def __init__(self): super().__init__() + self.model = bentoml.picklable_model.get("arima_forecast_model:latest") @bentoml.Runnable.method(batchable=False) - def forecast(self, test_data): - predictions = [] - arima_model = bentoml.picklable_model.get("arima_forecast_model:latest") - history_data = arima_model.custom_objects["historical_data"] - - # Define the ARIMA tuning parameters - model = ARIMA(history_data, order=(5, 1, 0)) - model_fit = model.fit() - output = model_fit.forecast() - y_pred = output[0] - predictions.append(y_pred) - obs = test_data - # Update history with single test value - history_data.append(obs) - # Save model with BentoML - bentoml.picklable_model.save_model('arima_forecast_model', - model, - custom_objects= {"historical_data": history_data}, - signatures={"predict": {"batchable": True}}, - ) + def forecast(self, values): + # Load trained arima model from bentoml + arima_model = self.model.load_model() + model_fit = arima_model.fit() + y_pred = model_fit.forecast(int(values)) return y_pred arima_forecast_runner = bentoml.Runner(ARIMAForecastRunnable) -svc = bentoml.Service("arima_model_forecast", runners=[arima_forecast_runner], models=[arima_model]) +svc = bentoml.Service("arima_model_forecast", runners=[arima_forecast_runner]) + +@svc.api(input=NumpyNdarray(dtype="int"), output=JSON()) +def predict_forecast(input_data: np.ndarray): + print(input_data) + return arima_forecast_runner.forecast.run(input_data[0]) -@svc.api(input=NumpyNdarray(dtype="float"), output=JSON()) -def predict(input_data: np.ndarray) -> typing.List[float]: - return arima_forecast_runner.forecast.run(input_data) \ No newline at end of file diff --git a/examples/custom_runner/arima_model/train.py b/examples/custom_runner/arima_model/train.py index bebd5ee1305..6e6511959e8 100644 --- a/examples/custom_runner/arima_model/train.py +++ b/examples/custom_runner/arima_model/train.py @@ -46,7 +46,6 @@ def main(): saved_model = bentoml.picklable_model.save_model( "arima_forecast_model", model, - custom_objects= {"historical_data": history,}, signatures={"predict": {"batchable": True}}, ) From 44db8aea95deae0715ac5443fd20858bbf308bf4 Mon Sep 17 00:00:00 2001 From: jsamanta_ucd Date: Thu, 25 Jul 2024 15:54:36 +0100 Subject: [PATCH 09/15] update service.py --- examples/custom_runner/arima_model/service.py | 13 +++++++------ 1 file changed, 7 insertions(+), 6 deletions(-) diff --git a/examples/custom_runner/arima_model/service.py b/examples/custom_runner/arima_model/service.py index 11b7b428bf7..7970748e05f 100644 --- a/examples/custom_runner/arima_model/service.py +++ b/examples/custom_runner/arima_model/service.py @@ -4,6 +4,8 @@ from bentoml.io import JSON from bentoml.io import NumpyNdarray +arima_model = bentoml.picklable_model.get("arima_forecast_model:latest") + # Custom runner for ARIMA model class ARIMAForecastRunnable(bentoml.Runnable): SUPPORTED_RESOURCES = ("cpu",) @@ -11,23 +13,22 @@ class ARIMAForecastRunnable(bentoml.Runnable): def __init__(self): super().__init__() - self.model = bentoml.picklable_model.get("arima_forecast_model:latest") + self.model = arima_model @bentoml.Runnable.method(batchable=False) - def forecast(self, values): + def forecast(self, values_to_forecast): # Load trained arima model from bentoml arima_model = self.model.load_model() model_fit = arima_model.fit() - y_pred = model_fit.forecast(int(values)) - return y_pred + predictions = model_fit.forecast(int(values_to_forecast)) + return predictions arima_forecast_runner = bentoml.Runner(ARIMAForecastRunnable) -svc = bentoml.Service("arima_model_forecast", runners=[arima_forecast_runner]) +svc = bentoml.Service("arima_model_forecast", runners=[arima_forecast_runner], models=[arima_model]) @svc.api(input=NumpyNdarray(dtype="int"), output=JSON()) def predict_forecast(input_data: np.ndarray): - print(input_data) return arima_forecast_runner.forecast.run(input_data[0]) From 9ce3fbee755925aaee14a956e4b5dd1625ce3ceb Mon Sep 17 00:00:00 2001 From: jsamanta_ucd Date: Fri, 26 Jul 2024 15:35:01 +0100 Subject: [PATCH 10/15] update README --- examples/custom_runner/arima_model/README.md | 17 ++++++++++++----- examples/custom_runner/arima_model/service.py | 1 - 2 files changed, 12 insertions(+), 6 deletions(-) diff --git a/examples/custom_runner/arima_model/README.md b/examples/custom_runner/arima_model/README.md index 1409df02ab5..8ae7985e2e1 100644 --- a/examples/custom_runner/arima_model/README.md +++ b/examples/custom_runner/arima_model/README.md @@ -1,7 +1,7 @@ # Serving ARIMA model with BentoML -This project demonstrates how to use a continuous learning ARIMA model -for a time-series data and use it as a prediction service in BentoML. +This project shows how to apply a continuous learning ARIMA model +for time-series data in BentoML to forecasts future values. ## Requirements @@ -27,15 +27,22 @@ bentoml serve service.py:svc ## Test the endpoint -Open in browser http://0.0.0.0:3000 to submit input images via the web UI. +Open in browser http://0.0.0.0:3000 to predict forecast. ```bash -curl -X 'POST' 'http://0.0.0.0:3000/predict' -H 'accept: application/json' -H 'Content-Type: application/json' -d '[20]' +curl -X 'POST' 'http://0.0.0.0:3000/predict' -H 'accept: application/json' -H 'Content-Type: application/json' -d '[5]' ``` Sample result: ``` -14.584079431935686 +[ + 21.32297249948254, + 39.103166807895505, + 51.62030696797619, + 57.742863144656305, + 57.316390331155915 +] + ``` ## Build Bento diff --git a/examples/custom_runner/arima_model/service.py b/examples/custom_runner/arima_model/service.py index 7970748e05f..9eab3f71997 100644 --- a/examples/custom_runner/arima_model/service.py +++ b/examples/custom_runner/arima_model/service.py @@ -23,7 +23,6 @@ def forecast(self, values_to_forecast): predictions = model_fit.forecast(int(values_to_forecast)) return predictions - arima_forecast_runner = bentoml.Runner(ARIMAForecastRunnable) svc = bentoml.Service("arima_model_forecast", runners=[arima_forecast_runner], models=[arima_model]) From 0c8c530886bfc97a4c3f4c5e90240f5d8430e617 Mon Sep 17 00:00:00 2001 From: jsamanta_ucd Date: Tue, 30 Jul 2024 11:41:26 +0100 Subject: [PATCH 11/15] Update readme --- examples/custom_runner/arima_model/README.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/examples/custom_runner/arima_model/README.md b/examples/custom_runner/arima_model/README.md index 8ae7985e2e1..d38fb11ca4c 100644 --- a/examples/custom_runner/arima_model/README.md +++ b/examples/custom_runner/arima_model/README.md @@ -27,7 +27,7 @@ bentoml serve service.py:svc ## Test the endpoint -Open in browser http://0.0.0.0:3000 to predict forecast. +Open in browser http://0.0.0.0:3000 to predict forecast of 5 future values. ```bash curl -X 'POST' 'http://0.0.0.0:3000/predict' -H 'accept: application/json' -H 'Content-Type: application/json' -d '[5]' From 027d2649402156727c64572457e36acef903cbd8 Mon Sep 17 00:00:00 2001 From: jsamanta_ucd Date: Tue, 30 Jul 2024 11:48:13 +0100 Subject: [PATCH 12/15] Update readme --- README.md | 1 - 1 file changed, 1 deletion(-) diff --git a/README.md b/README.md index 1496256861a..2761e7ddf02 100644 --- a/README.md +++ b/README.md @@ -1,4 +1,3 @@ -Adding ARIMA custom runner [](https://github.com/bentoml/BentoML)
From 7f6eb2ff167021eae47b7b3462693175c5985b81 Mon Sep 17 00:00:00 2001 From: "pre-commit-ci[bot]" <66853113+pre-commit-ci[bot]@users.noreply.github.com> Date: Wed, 31 Jul 2024 10:55:30 +0000 Subject: [PATCH 13/15] ci: auto fixes from pre-commit.ci For more information, see https://pre-commit.ci --- examples/custom_runner/arima_model/README.md | 2 +- .../arima_model/requirements.txt | 2 +- examples/custom_runner/arima_model/service.py | 12 ++- examples/custom_runner/arima_model/train.py | 93 ++++++++++--------- 4 files changed, 58 insertions(+), 51 deletions(-) diff --git a/examples/custom_runner/arima_model/README.md b/examples/custom_runner/arima_model/README.md index d38fb11ca4c..e2803aa83ea 100644 --- a/examples/custom_runner/arima_model/README.md +++ b/examples/custom_runner/arima_model/README.md @@ -1,4 +1,4 @@ -# Serving ARIMA model with BentoML +# Serving ARIMA model with BentoML This project shows how to apply a continuous learning ARIMA model for time-series data in BentoML to forecasts future values. diff --git a/examples/custom_runner/arima_model/requirements.txt b/examples/custom_runner/arima_model/requirements.txt index 9ec1666c910..fa64b8fe1f8 100644 --- a/examples/custom_runner/arima_model/requirements.txt +++ b/examples/custom_runner/arima_model/requirements.txt @@ -1,2 +1,2 @@ bentoml>=1.0.0 -statsmodels \ No newline at end of file +statsmodels diff --git a/examples/custom_runner/arima_model/service.py b/examples/custom_runner/arima_model/service.py index 9eab3f71997..ff1e76e5992 100644 --- a/examples/custom_runner/arima_model/service.py +++ b/examples/custom_runner/arima_model/service.py @@ -1,11 +1,12 @@ import numpy as np -import typing + import bentoml from bentoml.io import JSON from bentoml.io import NumpyNdarray arima_model = bentoml.picklable_model.get("arima_forecast_model:latest") + # Custom runner for ARIMA model class ARIMAForecastRunnable(bentoml.Runnable): SUPPORTED_RESOURCES = ("cpu",) @@ -14,7 +15,7 @@ class ARIMAForecastRunnable(bentoml.Runnable): def __init__(self): super().__init__() self.model = arima_model - + @bentoml.Runnable.method(batchable=False) def forecast(self, values_to_forecast): # Load trained arima model from bentoml @@ -23,11 +24,14 @@ def forecast(self, values_to_forecast): predictions = model_fit.forecast(int(values_to_forecast)) return predictions + arima_forecast_runner = bentoml.Runner(ARIMAForecastRunnable) -svc = bentoml.Service("arima_model_forecast", runners=[arima_forecast_runner], models=[arima_model]) +svc = bentoml.Service( + "arima_model_forecast", runners=[arima_forecast_runner], models=[arima_model] +) + @svc.api(input=NumpyNdarray(dtype="int"), output=JSON()) def predict_forecast(input_data: np.ndarray): return arima_forecast_runner.forecast.run(input_data[0]) - diff --git a/examples/custom_runner/arima_model/train.py b/examples/custom_runner/arima_model/train.py index 6e6511959e8..5a70d5d7df8 100644 --- a/examples/custom_runner/arima_model/train.py +++ b/examples/custom_runner/arima_model/train.py @@ -1,55 +1,58 @@ # import libraries import numpy as np import pandas as pd -import bentoml import statsmodels.api as sm from sklearn.metrics import mean_squared_error from statsmodels.tsa.arima.model import ARIMA +import bentoml + + def main(): - # Load the dataset - data = sm.datasets.sunspots.load_pandas().data - # Prepare dataset - data.index = pd.Index(sm.tsa.datetools.dates_from_range("1700", "2008")) - data.index.freq = data.index.inferred_freq - del data["YEAR"] - - # Split into train and test sets - X = data.values - size = int(len(X) * 0.66) - train, test = X[0:size], X[size:len(X)] - # Create a list of records to train ARIMA - history = [x for x in train] - # Create a list to store the predicted values - predictions = list() - - # Iterate over the test data - for t in range(len(test)): - model = ARIMA(history, order=(5, 1, 0)) - # fit the model and create forecast - model_fit = model.fit() - output = model_fit.forecast() - yhat = output[0] - predictions.append(yhat) - obs = test[t] - # update history with test data - history.append(obs) - - y_test = test - y_pred = predictions - - # Calculate root mean squared error - rmse = np.sqrt(mean_squared_error(y_test, y_pred)) - print("Root Mean Squared Error:", rmse) - - # Save model with BentoML - saved_model = bentoml.picklable_model.save_model( - "arima_forecast_model", - model, - signatures={"predict": {"batchable": True}}, - ) - - print(f"Model saved: {saved_model}") + # Load the dataset + data = sm.datasets.sunspots.load_pandas().data + # Prepare dataset + data.index = pd.Index(sm.tsa.datetools.dates_from_range("1700", "2008")) + data.index.freq = data.index.inferred_freq + del data["YEAR"] + + # Split into train and test sets + X = data.values + size = int(len(X) * 0.66) + train, test = X[0:size], X[size : len(X)] + # Create a list of records to train ARIMA + history = [x for x in train] + # Create a list to store the predicted values + predictions = list() + + # Iterate over the test data + for t in range(len(test)): + model = ARIMA(history, order=(5, 1, 0)) + # fit the model and create forecast + model_fit = model.fit() + output = model_fit.forecast() + yhat = output[0] + predictions.append(yhat) + obs = test[t] + # update history with test data + history.append(obs) + + y_test = test + y_pred = predictions + + # Calculate root mean squared error + rmse = np.sqrt(mean_squared_error(y_test, y_pred)) + print("Root Mean Squared Error:", rmse) + + # Save model with BentoML + saved_model = bentoml.picklable_model.save_model( + "arima_forecast_model", + model, + signatures={"predict": {"batchable": True}}, + ) + + print(f"Model saved: {saved_model}") + if __name__ == "__main__": - main() \ No newline at end of file + main() From f3b2703a6400b9d0433f099337b024e2d985f760 Mon Sep 17 00:00:00 2001 From: jsamanta_ucd Date: Wed, 18 Sep 2024 11:44:44 +0100 Subject: [PATCH 14/15] Update arima model with Bentoml version>=1.2 --- .../arima_model => arima-model}/README.md | 4 +- .../bentofile.yaml | 2 +- examples/arima-model/requirements.txt | 3 ++ examples/arima-model/service.py | 31 ++++++++++++++++ .../arima_model => arima-model}/train.py | 1 - .../arima_model/requirements.txt | 2 - examples/custom_runner/arima_model/service.py | 37 ------------------- 7 files changed, 37 insertions(+), 43 deletions(-) rename examples/{custom_runner/arima_model => arima-model}/README.md (91%) rename examples/{custom_runner/arima_model => arima-model}/bentofile.yaml (66%) create mode 100644 examples/arima-model/requirements.txt create mode 100644 examples/arima-model/service.py rename examples/{custom_runner/arima_model => arima-model}/train.py (99%) delete mode 100644 examples/custom_runner/arima_model/requirements.txt delete mode 100644 examples/custom_runner/arima_model/service.py diff --git a/examples/custom_runner/arima_model/README.md b/examples/arima-model/README.md similarity index 91% rename from examples/custom_runner/arima_model/README.md rename to examples/arima-model/README.md index e2803aa83ea..396aa559b5f 100644 --- a/examples/custom_runner/arima_model/README.md +++ b/examples/arima-model/README.md @@ -22,7 +22,7 @@ python ./train.py 2. Run the service: ```bash -bentoml serve service.py:svc +bentoml serve ``` ## Test the endpoint @@ -30,7 +30,7 @@ bentoml serve service.py:svc Open in browser http://0.0.0.0:3000 to predict forecast of 5 future values. ```bash -curl -X 'POST' 'http://0.0.0.0:3000/predict' -H 'accept: application/json' -H 'Content-Type: application/json' -d '[5]' +curl -X 'POST' 'http://0.0.0.0:3000/predict' -H 'accept: application/json' -H 'Content-Type: application/json' -d '{"data": [5]}' ``` Sample result: diff --git a/examples/custom_runner/arima_model/bentofile.yaml b/examples/arima-model/bentofile.yaml similarity index 66% rename from examples/custom_runner/arima_model/bentofile.yaml rename to examples/arima-model/bentofile.yaml index ae0093afdb0..f94c4399eec 100644 --- a/examples/custom_runner/arima_model/bentofile.yaml +++ b/examples/arima-model/bentofile.yaml @@ -1,4 +1,4 @@ -service: "service.py:svc" +service: "service.py:ArimaForecast" include: - "service.py" python: diff --git a/examples/arima-model/requirements.txt b/examples/arima-model/requirements.txt new file mode 100644 index 00000000000..e6899d2190c --- /dev/null +++ b/examples/arima-model/requirements.txt @@ -0,0 +1,3 @@ +bentoml>=1.2.0 +statsmodels +scikit-learn diff --git a/examples/arima-model/service.py b/examples/arima-model/service.py new file mode 100644 index 00000000000..b2515715e8f --- /dev/null +++ b/examples/arima-model/service.py @@ -0,0 +1,31 @@ +import numpy as np +import bentoml + +@bentoml.service( + resources={ + "cpu": "2", + "memory": "2Gi", + }, +) +class ArimaForecast: + """ + A simple ARIMA model to forecast future predictions + """ + + # Load in the class scope to declare the model as a dependency of the service + arima_model = bentoml.picklable_model.get("arima_forecast_model:latest") + + def __init__(self): + """ + Initialize the service by loading the model from the model store + """ + self.model = self.arima_model.load_model() + + @bentoml.api + def forecast(self, data: np.ndarray)-> np.ndarray: + """ + Define API with input as number of forecasts to predict in the future + """ + model_fit = self.model.fit() + predictions = model_fit.forecast(int(data)) + return predictions \ No newline at end of file diff --git a/examples/custom_runner/arima_model/train.py b/examples/arima-model/train.py similarity index 99% rename from examples/custom_runner/arima_model/train.py rename to examples/arima-model/train.py index 5a70d5d7df8..a28128bb472 100644 --- a/examples/custom_runner/arima_model/train.py +++ b/examples/arima-model/train.py @@ -4,7 +4,6 @@ import statsmodels.api as sm from sklearn.metrics import mean_squared_error from statsmodels.tsa.arima.model import ARIMA - import bentoml diff --git a/examples/custom_runner/arima_model/requirements.txt b/examples/custom_runner/arima_model/requirements.txt deleted file mode 100644 index fa64b8fe1f8..00000000000 --- a/examples/custom_runner/arima_model/requirements.txt +++ /dev/null @@ -1,2 +0,0 @@ -bentoml>=1.0.0 -statsmodels diff --git a/examples/custom_runner/arima_model/service.py b/examples/custom_runner/arima_model/service.py deleted file mode 100644 index ff1e76e5992..00000000000 --- a/examples/custom_runner/arima_model/service.py +++ /dev/null @@ -1,37 +0,0 @@ -import numpy as np - -import bentoml -from bentoml.io import JSON -from bentoml.io import NumpyNdarray - -arima_model = bentoml.picklable_model.get("arima_forecast_model:latest") - - -# Custom runner for ARIMA model -class ARIMAForecastRunnable(bentoml.Runnable): - SUPPORTED_RESOURCES = ("cpu",) - SUPPORTS_CPU_MULTI_THREADING = False - - def __init__(self): - super().__init__() - self.model = arima_model - - @bentoml.Runnable.method(batchable=False) - def forecast(self, values_to_forecast): - # Load trained arima model from bentoml - arima_model = self.model.load_model() - model_fit = arima_model.fit() - predictions = model_fit.forecast(int(values_to_forecast)) - return predictions - - -arima_forecast_runner = bentoml.Runner(ARIMAForecastRunnable) - -svc = bentoml.Service( - "arima_model_forecast", runners=[arima_forecast_runner], models=[arima_model] -) - - -@svc.api(input=NumpyNdarray(dtype="int"), output=JSON()) -def predict_forecast(input_data: np.ndarray): - return arima_forecast_runner.forecast.run(input_data[0]) From dc19767141c186ff45925cb30eb95c6e8ca892e3 Mon Sep 17 00:00:00 2001 From: "pre-commit-ci[bot]" <66853113+pre-commit-ci[bot]@users.noreply.github.com> Date: Wed, 18 Sep 2024 10:50:21 +0000 Subject: [PATCH 15/15] ci: auto fixes from pre-commit.ci For more information, see https://pre-commit.ci --- examples/arima-model/service.py | 6 ++++-- examples/arima-model/train.py | 1 + 2 files changed, 5 insertions(+), 2 deletions(-) diff --git a/examples/arima-model/service.py b/examples/arima-model/service.py index b2515715e8f..877f9e65157 100644 --- a/examples/arima-model/service.py +++ b/examples/arima-model/service.py @@ -1,6 +1,8 @@ import numpy as np + import bentoml + @bentoml.service( resources={ "cpu": "2", @@ -22,10 +24,10 @@ def __init__(self): self.model = self.arima_model.load_model() @bentoml.api - def forecast(self, data: np.ndarray)-> np.ndarray: + def forecast(self, data: np.ndarray) -> np.ndarray: """ Define API with input as number of forecasts to predict in the future """ model_fit = self.model.fit() predictions = model_fit.forecast(int(data)) - return predictions \ No newline at end of file + return predictions diff --git a/examples/arima-model/train.py b/examples/arima-model/train.py index a28128bb472..5a70d5d7df8 100644 --- a/examples/arima-model/train.py +++ b/examples/arima-model/train.py @@ -4,6 +4,7 @@ import statsmodels.api as sm from sklearn.metrics import mean_squared_error from statsmodels.tsa.arima.model import ARIMA + import bentoml