ML App with Flask

You built an ML with TensorFlow or Sci-kit learn and now want it deployed on a website. This article is a quick guide on loading an ML model in Python and using it to make predictions on a web app with Flask. This is based on my image classifier app found on my GitHub Fabio-RibeiroB/image_classifier_app: App to classify happy or sad images (github.com). In this app, the user uploads a file and presses predict. Specifically, the user uploads images for binary classification. It all depends on your model. You can change this code to upload CSV data instead, for example.

I appreciate this article is high-level and lacking detail. It is more than an outline to make it as short-form as possible. To see more, check out my aforementioned repo on GitHub. Anyway, let’s begin.

Save and Load

Let’s say you have a Sequential model that you compiled.

model = Sequential()
....some model
....
model.compile(....)

Now save the model, for example, as a .h5 file. I saved mine in a “models” folder. You can also use pickle to dump and load models as .pkl files.

from tensorflow.keras.models import load_model
import os
model.save(os.path.join('models','model.h5'))

Now load it in your Flask app.

from flask import Flask, render_template, request, redirect, flash, session # useful flask modules
from werkzeug.utils import secure_filename # security
import logging

logging.basicConfig(level=logging.DEBUG)
logging.info('program starting')

from tensorflow.keras.models import load_model
model = load_model('models/model.h5') # loaded model

Static Uploads Folder

We also need a folder where we can upload data for the model. Make a directory called static, and within that, a directory called uploads.

UPLOAD_FOLDER = './static/uploads'
ALLOWED_EXTENSIONS = {'png', 'jgp', 'jpeg'} # change depending on model
app = Flask(__name__)
app.secret_key = b'somesecretkey'
app.config['UPLOAD_FOLDER'] = UPLOAD_FOLDER

def allowed_file(filename):
    """
    Check the uploaded data is correct format
    """"
    return '.' in filename and \
          filename.rsplit('.', 1)[1].lower() in ALLOWED_EXTENSIONS

Routes and Prediction

Now I create the necessary routes in your app that makes a prediction. We will later make a home.html will allow us to start a prediction. The predict.html page shows the results.

@app.route('/')
def home():
    return render_template('home.html')

@app.route('/predict', methods=['POST'])
def predict():
    file = request.files('file')
    # check uploaded file is okay in upload folder
    if file and allowed_file(file.filename):
        filename = secure_filename(file.filename)
        data_path = os.path.join(app.config['UPLOAD_FOLDER'], filename)
   
        file. save(data_path) # save data
        # read data in for example with pandas
        prediction = model.predict(data) # in my example prediction is one number, a probability.

        # remember to delete file after use

        os.remove(data_path)
        return render_template('predict.html', data=prediction)
   
if __name__ == "__main__":
    app.run(debug=True)

The above code saves the valid data in the uploads folder and loads the data. For example, you could load your data into a pandas data frame. The /predict route passes the prediction variable containing a single prediction. The prediction variable is passed to predict.html to render the result on the web page. If your model outputs a lot of predictions, like a CSV of predictions, these lines will need to be modified. You probably want the user to download the predictions as a CSV instead of displaying the results on the screen. In this case, you need a download button in your HTML.

However, continuing my example, we have a home page called home.html with a form for the user to upload data. I simply removed the rest of my HTML tags to declutter the code snippet below. See my repo for the entire HTML file.

In home.html

<form method="POST" action="{{url_for('home')}}"   enctype=multipart/form-data>
    <input type=file name=file>
    <input type=submit value=predict>
</form>

And now for prediction.html. This page will simply output the results with a back button.

{{data}}
<form>
<input type="button" value="Try again" on    click="history.back()">
</form>

Flask run, and the app should be running in local host.