-
Notifications
You must be signed in to change notification settings - Fork 18
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
Merge pull request #195 from rstudio/custom-docs
- Loading branch information
Showing
5 changed files
with
205 additions
and
48 deletions.
There are no files selected for viewing
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,199 @@ | ||
# Creating and deploying custom code | ||
|
||
In some cases, you may need to create and deploy custom code as part of your MLOps workflow using vetiver. This could be necessary when you need to: | ||
|
||
- deploy custom models in vetiver | ||
- deploy unsupported models in vetiver | ||
- include custom code in vetiver | ||
- deploy a vetiver model with a custom pipeline | ||
|
||
You may also have custom code in a known framework, such as a column transformer for a scikit-learn model. | ||
|
||
In these cases, extra steps will be required to successfully create and deploy a `VetiverModel` object. | ||
|
||
# Making a custom model | ||
|
||
Vetiver supports basic [scikit-learn](https://scikit-learn.org/), [torch](https://pytorch.org/), [statsmodels](https://www.statsmodels.org/stable/index.html), [xgboost](https://xgboost.readthedocs.io/en/stable/), and [spacy](https://spacy.io/) models. If you need to alter the usage of these models, or deploy a different type of model, you will likely need to create a new model handler. | ||
|
||
To create a model handler, you should create a subclass of vetiver's `BaseHandler` class. This handler should include the following: | ||
|
||
- `model_type`: A static method that declares the type of your model. | ||
- `handler_predict()`: A method that defines how predictions should be made for your model. This method is used at the /predict endpoint in the VetiverAPI. | ||
|
||
Here's an example of a handler for a model of `newmodeltype` type. Once you have defined your handler, you can initialize it with your model and pass it to the `VetiverModel` class. | ||
|
||
```python | ||
from vetiver.handlers.base import BaseHandler | ||
|
||
class CustomHandler(BaseHandler): | ||
def __init__(self, model, prototype_data): | ||
super().__init__(model, prototype_data) | ||
|
||
model_type = staticmethod(lambda: newmodeltype) | ||
pip_name = "scikit-learn" # package's installation name on pip | ||
|
||
def handler_predict(self, input_data, check_prototype: bool): | ||
""" | ||
Your code for making predictions using the custom model | ||
Parameters | ||
---------- | ||
input_data: | ||
Data POSTed to API endpoint | ||
check_prototype: bool | ||
Whether the prototype should be enforced | ||
""" | ||
prediction = model.fancy_new_predict(input_data) | ||
|
||
return prediction | ||
|
||
new_model = CustomHandler(model, prototype_data) | ||
|
||
VetiverModel(new_model, "custom_model") | ||
``` | ||
|
||
If your model is a common type, please consider [submitting a pull request](https://github.com/rstudio/vetiver-python/pulls). | ||
|
||
To deploy custom code, you need to include the necessary source code in your deployment files. If your model or other elements can be imported from a Python package, you can include the relevant packages in a `requirements.txt` file for deployment. However, if you have custom source code in local files, you will need to include those files in the deployment process. | ||
|
||
# Deploying custom elements | ||
|
||
If your `VetiverModel` includes custom source code, you need to include that code in your deployment files to build an API in another location. The example below shows a user-created `FeatureSelector`, which is part of a scikit-learn pipeline. | ||
|
||
```{.python filename="model.py"} | ||
from sklearn.base import BaseEstimator, TransformerMixin | ||
from sklearn.tree import DecisionTreeClassifier | ||
from sklearn.pipeline import Pipeline | ||
# create custom data preprocessing | ||
class FeatureSelector(BaseEstimator, TransformerMixin): | ||
def __init__(self, columns): | ||
self.columns = columns | ||
def fit(self, X, y=None): | ||
return self | ||
def transform(self, X, y=None): | ||
return X[self.columns] | ||
# create model | ||
model = Pipeline(steps=[ | ||
('feature_selector', FeatureSelector(features)), | ||
('decision_tree', DecisionTreeClassifier()) | ||
]) | ||
# create deployable model object | ||
from vetiver import VetiverModel, vetiver_pin_write | ||
v = VetiverModel(model, "selected_decision_tree", protoype_data = X) | ||
# pin model to some location, eg, Posit Connect | ||
import pins | ||
board = pins.board_connect(allow_pickle_read=True) | ||
vetiver_pin_write(board, v) | ||
``` | ||
|
||
::: {.panel-tabset} | ||
## Docker | ||
|
||
To generate files needed to start a Docker container, you can use the command `vetiver.prepare_docker`. | ||
|
||
```{.python} | ||
vetiver.prepare_docker(board, "selected_decision_tree") | ||
``` | ||
|
||
When you run this line, 3 files are generated: a Dockerfile, an `app.py` file, and a `vetiver_requirements.txt`. In the `app.py` file, you'll need to add an import statement that is formatted `from {name of file, excluding .py, that has custom element} import {name of custom element}`. | ||
|
||
```{.python filename="app.py"} | ||
from vetiver import VetiverModel | ||
import vetiver | ||
import pins | ||
from model import FeatureSelector # add this line to import your custom feature engineering | ||
b = pins.board_connect(allow_pickle_read=True) | ||
v = VetiverModel.from_pin(b, 'selected_decision_tree') | ||
vetiver_api = vetiver.VetiverAPI(v) | ||
api = vetiver_api.app | ||
``` | ||
|
||
Add a line to your Dockerfile to copy your source file(s) into your Docker container. The format will be `COPY path/to/your/filename.py /vetiver/app/filename.py`, where the destination is always in the `/vetiver/app/` directory. | ||
|
||
```{.bash filename="Dockerfile"} | ||
# # Generated by the vetiver package; edit with care | ||
# start with python base image | ||
FROM python:3.10 | ||
|
||
# create directory in container for vetiver files | ||
WORKDIR /vetiver | ||
|
||
# copy and install requirements | ||
COPY vetiver_requirements.txt /vetiver/requirements.txt | ||
|
||
# | ||
RUN pip install --no-cache-dir --upgrade -r /vetiver/requirements.txt | ||
|
||
# copy app file | ||
COPY app.py /vetiver/app/app.py | ||
|
||
# ADD THIS LINE to copy model source code | ||
COPY model.py /vetiver/app/model.py | ||
|
||
# expose port | ||
EXPOSE 8080 | ||
|
||
# run vetiver API | ||
CMD ["uvicorn", "app.app:api", "--host", "0.0.0.0", "--port", "8080"] | ||
``` | ||
|
||
## Posit Connect | ||
|
||
To deploy custom code to Posit Connect, you'll first start with the command `vetiver.write_app`. | ||
|
||
```{.python} | ||
vetiver.write_app(board, 'selected_decision_tree') | ||
``` | ||
|
||
This will generate an `app.py` file, where you'll need to add an import statement that is formatted `from {name of file, excluding .py, that has custom element} import {name of custom element}`. | ||
|
||
```{.python filename=="app.py"} | ||
from vetiver import VetiverModel | ||
import vetiver | ||
import pins | ||
from model import FeatureSelector # add this line to import your custom feature engineering | ||
b = pins.board_connect(allow_pickle_read=True) | ||
v = VetiverModel.from_pin(b, 'selected_decision_tree') | ||
vetiver_api = vetiver.VetiverAPI(v) | ||
api = vetiver_api.app | ||
``` | ||
|
||
After editing the `app.py` file, you can deploy it to Posit Connect using the `rsconnect` package. Use the `rsconnect.api.actions.deploy_python_fastapi()` function to deploy the API, specifying the Connect server URL, API key, directory containing the `app.py` and `model.py` files, and the entry point of the API. | ||
|
||
```{.python} | ||
from rsconnect.api.actions import deploy_python_fastapi | ||
import rsconnect | ||
url = "example.connect.com" # your Posit Connect server url | ||
api_key = os.environ(CONNECT_API_KEY) # your Posit Connect API key | ||
connect_server = rsconnect.api.RSConnectServer( | ||
url = url, | ||
api_key = api_key | ||
) | ||
rsconnect.actions.deploy_python_fastapi( | ||
connect_server = connect_server, | ||
directory = "./", # path to the directory containing the app.py and model.py files | ||
entry_point = "app:api" # the API is the app.py file, in a variable named api | ||
) | ||
``` | ||
|
||
::: | ||
|
||
Please note that the above steps are a general guide, and you may need to adapt them to your specific use case and deployment environment. If you have any questions, please consider [opening an issue](https://github.com/rstudio/vetiver-python/issues/new). |
This file was deleted.
Oops, something went wrong.
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters