How to Easily Deploy Fast.ai Machine Learning Models to DigitalOcean for Production cover image

How to Easily Deploy Fast.ai Machine Learning Models to DigitalOcean for Production

Jian Jye • September 6, 2019

ubuntu digitalocean fastai machine-learning

With Machine Learning being all the rage right now, there are a lot of managed services that you could use to deploy your deep learning models.

However if you already have a DigitalOcean VPS running, why not just use it?

Before we begin...

Source codes and sample model are available on Github.

This tutorial is based on Fast.ai - 2019 Lesson 2 Download notebook.


1. Requirements

To make this work, we will be using the following tools:

What is Supervisor?

Supervisor is a tool that would monitor your processes and if they are terminated, helps you restart them again.

Why do we need Supervisor?

Because we want an easy way to serve our models without setting up WSGI + NGINX, one way is to execute our Flask app directly with Python and restart the process as necessary with Supervisor.

Is there any caveat?

Yup. This works best for low traffic app.

What is Flask?

Flask is a popular micro-framework in Python that helps us easily load our models and start serving requests.


2. Project Stucture

Our project structure would look like this:

myapp
|--model.pkl
|--main.py
|--requirements.txt
|--start.sh
|-tmp

requirements.txt details a list of Python packages that are required for the project to run:

flask
fastai>=1.0
torch
torchvision

main.py houses all the Flask codes needed to start serving traffic to our model. Basically it will take a GET parameter called image, downloads the image locally and predicts it using our model.

import json
import urllib.request

from flask import Flask
from flask import request
from fastai.vision import *

app = Flask(__name__)

@app.route('/')
def handler():
    defaults.device = torch.device('cpu')

    // Load the deep learning model locally
    path = Path('.')
    learner = load_learner(path, 'model.pkl')

    // Download the image based on the GET parameter from URL
    image = request.args['image']
    urllib.request.urlretrieve(image, './tmp/image.jpg')

    // Make Predictions
    img = open_image('./tmp/image.jpg')
    pred_class,pred_idx,outputs = learner.predict(img)

    // Return response
    return json.dumps({
        "predictions": sorted(
            zip(learner.data.classes, map(float, outputs)),
            key=lambda p: p[1],
            reverse=True
        )
    })

tmp is a folder for us to store our downloaded image for our model to make predictions.

model.pkl would be our model file, as created in Jupyter Notebook via the method learn.export(). You should prepare this beforehand while training your desired model.

Finally start.sh is the Supervisor setup script for our VPS that would restart Flask if it was detected to have exited, thus keeping the app alive.

source myappenv/bin/activate
export FLASK_APP=main.py
flask run
# flask run --host=0.0.0.0 --port=8080
deactivate

You can view the source codes directly at Github.


3. Install Dependencies

We are going to start by first creating a virtual environment for our project and activate it. Then, we will install the list of requirements from the requirements.txt file.

$ python3 -m venv myappenv
$ source myappenv/bin/activate
$ pip3 install -r requirements.txt --no-cache-dir

This would create a myappenv folder to install all the local dependencies required for our project.

Because some of the packages required like torch requires a lot of memory, despite the --no-cache-dir flag you might still hit a MemoryError problem. In solve it, either resize your VPS to have more memory or you can try create a larger swap size on the VPS.


4. Test running Flask

Now let's see if our project is set up correctly. Execute the following commands and check if Flask is able to run properly.

$ export FLASK_APP=main.py
$ flask run

You should see the following response:

 * Serving Flask app "main.py"
 * Environment: production
   WARNING: This is a development server. Do not use it in a production deployment.
   Use a production WSGI server instead.
 * Debug mode: off
 * Running on http://127.0.0.1:5000/ (Press CTRL+C to quit)

Yup that was a warning. In our case, we expect the traffic to be internal and low so it should be alright. If you expect otherwise then you may want to read up on WSGI server instead.

Now that we are sure Flask is running fine. Quit it and deactivate the virtual environment.

(Press CTRL+C to quit Flask)
$ deactivate

5. Set up Supervisor

Let's install Supervisor and run it:

$ sudo apt-get install supervisor
$ sudo supervisord

Next, make sure our start.sh is executable:

$ chmod +x start.sh

Create a Supervisor config file for our Flask app:

$ sudo nano /etc/supervisor/conf.d/myapp.conf

Inside myapp.conf, copy and paste the following codes:

[program:myapp]
directory=/home/username/bears/
command=/bin/bash -E -c ./start.sh
process_name=%(program_name)s_%(process_num)02d
autostart=true
autorestart=true
user=username
numprocs=1
redirect_stderr=true
stdout_logfile=/home/username/bears/supervisor.log

And restart Supervisor to load our new configurations:

$ sudo systemctl restart supervisor.service

Verify that Flask is now running via Supervisor:

$ netstat -l | grep 5000

You should a response similar to this:

tcp        0      0 localdomain:5000     0.0.0.0:*               LISTEN

6. Success!

Now our Flask app is running on port 5000 and is ready to serve traffic! You can give it a try by executing the follow curl command:

$ curl http://127.0.0.1:5000/\?image\=http://58.26.9.230/edrive_new/cctv/cctv_image/C165.jpg

You should see this in response:

{"predictions": [["black", 0.9545639753341675], ["grizzly", 0.043770089745521545], ["teddys", 0.0016658934764564037]]}

Awesome! It's now working perfectly and would make predictions based on the input image.

But...

This only works with other web apps that are on the same server because it's only listening for local traffic.


7. Open Up to External Services

To open up to external service, simply uncomment the line in start.sh and replace with an appropriate port value, then restart Supervisor. Make sure your specified port value is not already being used by another service to avoid conflicts.

flask run --host=0.0.0.0 --port=8080

And if you are looking to add HTTPS to the service, you can continue with this guide here.


Closing

That was very easy isn't it? Now you can have a fully working Flask app serving your models on your existing DigitalOcean VPS without paying extra!

Sign up for our newsletter