Skip to content

Commit

Permalink
Merge pull request #20 from A3Data/apigunicorn
Browse files Browse the repository at this point in the history
Apigunicorn
  • Loading branch information
neylsoncrepalde authored Oct 20, 2020
2 parents 966685f + bd9493f commit 13fc150
Show file tree
Hide file tree
Showing 15 changed files with 166 additions and 547 deletions.
53 changes: 44 additions & 9 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -40,6 +40,7 @@ This is also our way of reinforcing our position that women should be taking mor

- Anaconda or Miniconda Python (>= 3.6)
- conda (>= 4.8)
- **docker**

Hermione depends on conda to build and manage virtual conda environments. If you don't have it installed, please visit
<a href="https://www.anaconda.com/products/individual" target="_blank">Anaconda website</a> or
Expand All @@ -61,9 +62,11 @@ After installed Hermione:
hermione new project_hermione
```

1. Enter “y” if you want to start with an example code
1. Hit Enter if you want to start with an example code

![](https://cdn-images-1.medium.com/max/800/1*TJoFVA-Nio2O3XvxBN4MUQ.png)
```
Do you want to start with an implemented example (recommended) [y/n]? [y]:
```

3. Hermione already creates a conda virtual environment for the project. Activate it

Expand All @@ -81,9 +84,16 @@ pip install -r requirements.txt

6. After that, a mlflow experiment is created. To verify the experiment in mlflow, type: mlflow ui. The application will go up.

![](https://cdn-images-1.medium.com/max/800/1*DReyAtL9eJ0fiwxaVo3Yfw.png)
```
mlflow ui
```

7. To access the experiment, just enter the path previously provided in your preferred browser. Then it is possible to check the trained models and their metrics.
[2020-10-19 23:23:12 -0300] [15676] [INFO] Starting gunicorn 19.10.0
[2020-10-19 23:23:12 -0300] [15676] [INFO] Listening at: http://127.0.0.1:5000 (15676)
[2020-10-19 23:23:12 -0300] [15676] [INFO] Using worker: sync
[2020-10-19 23:23:12 -0300] [15678] [INFO] Booting worker with pid: 15678

1. To access the experiment, just enter the path previously provided in your preferred browser. Then it is possible to check the trained models and their metrics.

![](https://cdn-images-1.medium.com/max/800/1*c_rDEqERZR6r8JVI3TMTcQ.png)

Expand All @@ -100,17 +110,42 @@ hermione predict
Do you want to create your **project from scratch**? There click [here](tutorial_base.md) to check a tutorial.


## Docker
# Docker

Hermione comes with a default `Dockerfile` that should work fine without editing if you have an autonomous `predict.py` file. It was designed to make batch predictions, not to serve the model with an API (coming soon...). To build correctly, you should be at the top project folder, not inside `src` folder.
Hermione comes with a default `Dockerfile` which implements a Flask + Gunicorn API that serves your ML model. You should take a look at the `api/app.py` module and rewrite `predict_new()` function as you see fit.

You can build the docker image and run it with the following commands:
Also, in the newest version, hermione brings two CLI commands that helps us abstract a little bit the complexity regarding docker commands. To build an image (remember you should have docker installed), you should be in the project's root directory. Than, do:

```bash
hermione build <IMAGE_NAME>
```
docker build -f src/Dockerfile -t myprediction:latest .
docker run --rm myprediction:latest

After you have built you're docker image, run it with:

```bash
hermione run <IMAGE_NAME>
```

[2020-10-20 02:13:20 +0000] [1] [INFO] Starting gunicorn 20.0.4
[2020-10-20 02:13:20 +0000] [1] [INFO] Listening at: http://0.0.0.0:5000 (1)
[2020-10-20 02:13:20 +0000] [1] [INFO] Using worker: sync
[2020-10-20 02:13:20 +0000] [7] [INFO] Booting worker with pid: 7
[2020-10-20 02:13:20 +0000] [8] [INFO] Booting worker with pid: 8
[2020-10-20 02:13:20 +0000] [16] [INFO] Booting worker with pid: 16

**THAT IS IT!** You have a live model up and running. To test your API, hermione provides a `api/myrequests.py` module. *This is not part of the project*; it's a "ready to go" code to make requests to the API. Help yourself!

```bash
cd src/api
python myrequests.py
```

Sending request for model...
Data: {"Pclass": [3, 2, 1], "Sex": ["male", "female", "male"], "Age": [4, 22, 28]}
Response: "[0.24630952 0.996 0.50678968]"

Play a little with the 'fake' data and see how far can the predictions go.


## Documentation
This is the class structure diagram that Hermione relies on:
Expand Down
2 changes: 1 addition & 1 deletion hermione/_version.py
Original file line number Diff line number Diff line change
@@ -1 +1 @@
__version__ = '0.2'
__version__ = '0.3'
39 changes: 33 additions & 6 deletions hermione/cli.py
Original file line number Diff line number Diff line change
Expand Up @@ -30,8 +30,8 @@ def info():
@cli.command()
@click.argument('project_name')
@click.option('-p', '--python-version', 'python_version', default='3.7', show_default=True)
@click.option('-imp', '--implemented', 'implemented', prompt='Do you want to start with an implemented example? [y/n] Default:',
default='n', show_default=True)
@click.option('-imp', '--implemented', 'implemented', prompt='Do you want to start with an implemented example (recommended) [y/n]?',
default='y', show_default=True)
def new(project_name, python_version, implemented):
"""
Create a new hermione project
Expand Down Expand Up @@ -63,14 +63,13 @@ def new(project_name, python_version, implemented):
# Write config file
write_config_file(LOCAL_PATH, project_name)
#write_logging_file(LOCAL_PATH, project_name)
#write_endpoints_file(LOCAL_PATH, project_name)
write_requirements_txt(LOCAL_PATH, project_name, file_source)
write_gitignore(LOCAL_PATH, project_name, file_source)
write_readme_file(LOCAL_PATH, project_name, file_source)
#write_application_file(LOCAL_PATH, project_name)
#write_application_config(LOCAL_PATH, project_name)
write_src_util_file(LOCAL_PATH, project_name, file_source)
#write_wsgi_file(LOCAL_PATH, project_name)
write_wsgi_file(LOCAL_PATH, project_name, file_source)
write_app_file(LOCAL_PATH, project_name, file_source)
write_visualization_file(LOCAL_PATH, project_name, file_source)
write_normalization_file(LOCAL_PATH, project_name, file_source)
write_preprocessing_file(LOCAL_PATH, project_name, file_source)
Expand All @@ -90,6 +89,7 @@ def new(project_name, python_version, implemented):

if implemented in ['yes', 'ye', 'y', 'Yes', 'YES', 'Y']:
write_titanic_data(LOCAL_PATH, project_name, file_source)
write_myrequests_file(LOCAL_PATH, project_name, file_source)


write_test_file(LOCAL_PATH, project_name, file_source)
Expand Down Expand Up @@ -127,4 +127,31 @@ def predict():
else:
print("Making predictions: ")
os.system('python ./predict.py')



@click.argument('image_name')
@click.option('-t', '--tag', 'tag', default='latest', show_default=True)
@cli.command()
def build(image_name, tag):
"""
Build a docker image with given image_name. Only run if you have docker installed.
One should be at the root directory.
"""
if not os.path.exists('src/Dockerfile'):
click.echo("You gotta have an src/Dockerfile file. You must be at the root folder.")
else:
os.system(f'docker build -f src/Dockerfile -t {image_name}:{tag} .')


@click.argument('image_name')
@click.option('-t', '--tag', 'tag', default='latest', show_default=True)
@cli.command()
def run(image_name, tag):
"""
Run a container with given image_name.
Only run if you have docker installed.
"""
if not os.path.exists('src/Dockerfile'):
click.echo("You gotta have an src/Dockerfile file. You must be at the root folder.")
else:
os.system(f'docker run --rm -p 5000:5000 {image_name}:{tag}')
41 changes: 41 additions & 0 deletions hermione/file_text/app.txt
Original file line number Diff line number Diff line change
@@ -0,0 +1,41 @@
from flask import Flask, request, redirect, url_for, flash, jsonify
import numpy as np
import pandas as pd
from joblib import load
import json
import logging

logging.getLogger().setLevel(logging.INFO)

app = Flask(__name__)

def predict_new(X, probs=True):
model = load('model/titanic_model_rf.pkl')
p = model.get_preprocessing()

X = p.clean_data(X)
X = p.categ_encoding(X)

columns = model.get_columns()
for col in columns:
if col not in X.columns:
X[col] = 0
if probs:
return model.predict_proba(X)[:,1]
else:
return model.predict(X)

@app.route('/invocations', methods=['POST'])
def predict():
data = pd.read_json(request.json)
predictions = np.array2string(predict_new(data, probs=True))
return jsonify(predictions)

@app.route('/health', methods=['GET'])
def health_check():
resp = jsonify(success=True)
return resp


if __name__ == "__main__":
app.run(host='0.0.0.0')
91 changes: 0 additions & 91 deletions hermione/file_text/application.txt

This file was deleted.

9 changes: 5 additions & 4 deletions hermione/file_text/dockerfile.txt
Original file line number Diff line number Diff line change
Expand Up @@ -25,15 +25,14 @@ COPY requirements.txt /opt/ml/code/src/requirements.txt
RUN pip3 install --no-cache -r /opt/ml/code/src/requirements.txt

# Copy project files
COPY output/titanic_model_rf.pkl /opt/ml/output/titanic_model_rf.pkl
COPY output/titanic_model_rf.pkl /opt/ml/code/src/api/model/titanic_model_rf.pkl
COPY src/api/ /opt/ml/code/src/api/
COPY src/config/ /opt/ml/code/src/config/
COPY src/ml/ /opt/ml/code/src/ml/
COPY src/util.py /opt/ml/code/src/util.py
COPY src/predict.py /opt/ml/code/src/predict.py

# Change working directory
WORKDIR /opt/ml/code/src
WORKDIR /opt/ml/code/src/api


# Environment variables
Expand All @@ -43,5 +42,7 @@ ENV PYTHONDONTWRITEBYTECODE=1 \

ENV PYTHONPATH="/opt/ml/code/src:${PATH}"

EXPOSE 5000

# Execution command
CMD python3 predict.py
CMD ["gunicorn", "-w", "3", "-b", ":5000", "-t", "360", "--reload", "wsgi:app"]
Loading

0 comments on commit 13fc150

Please sign in to comment.