Build a Flask app for model serving

This post is modeled off a workshop I gave to the Seattle Building Intelligent Applications Meetup.

You can fork the code here Github Repo

What is Flask?

Flask is a web framework for Python. In other words, it’s a way to use python to create websites, web apps and APIs.

Create your Flask Environment

I will be showing how to use flask with python 3 but python 2 should work just as well.

  1. Install virtualenv pip3 install virtualenv

  2. Set up the virtual environment virtualenv -p python3 venv3

  3. Activate the virtual environment source venv3/bin/activate

  4. Install the needed packages pip install -r requirements.txt

Flask App Organization

Flask has some basic requirements when it comes to file and folder structure.

# the most basic flask app
 yourapp/
    |
    | - app.py

Here’s an example of a more complex folder structure you’d see if you were serving a more complete website.

# typical app structure
 yourapp/
    |
    | - app.py
    | - static/
            | - css/
            | - resources/
            | - js/
    | - templates/
            | - index.html
    | - data/

Let’s build a basic flask app

  1. From the root directory, open your app.py file, import flask and create your flask object.

     from flask import Flask
        
     # create the flask object
     app = Flask(__name__)
    
  2. Add your first route. Routes connect web pages to unique python functions. In this example, the root page of the site, yoursite/, should trigger the home() function and return ‘Hello World!’ as the server response to the webpage.

     # routes go here
     @app.route('/')
     def home():
     return 'Hello World!'
    
  3. Now just add some code at the bottom to tell python what to do when the script is run.

     # script initialization
     if __name__ == '__main__':
        app.run(debug=True)
    

    Note: debug=True allows for quicker development since you don’t have to keep restarting your app when you change it.

  4. Run python app.py

  5. Check it out!

    Your Locally Running App

  6. Let’s add another route. Rather than just pure text, let’s return some HTML.

     @app.route('/jazzhands')
     def jazzhands():
         return "<h1>Here's some <i>Pizzazz!</i></h1>"
    
  7. One final route in our basic app. This route will use what’s known as dynamic routing. These allow more flexibility with your urls and the ability to pass variables straight through the url. Variables must be passed in between angled brackets ‘<>’ after the root of the url, in this case /twice/.

     @app.route('/twice/<int:x>') # int says the expected data type
     def twice(x):
         output = 2 * x
         return 'Two times {} is {}'.format(x, output)
    

Now let’s do some Data Science

We will be building a survival classifier using the Titanic Survival Dataset. Our goal is to create an interface for a user to make and view predictions.

The code to read in the data, split it up and train the model has already been written for you. We’re going to focus on how to implement the model and predict with it through the flask web interface.

Note: If you want more info on models for this dataset take a look here. Titanic Dataset Modeling

Install and import additional packages

  1. If you haven’t already done so, make sure you have all the required packages in your virtualenv.

     pip install pandas sklearn scipy
        
     // or 
        
     pip install -r requirements.txt
    
  2. Import the libraries required for modeling. render_template and request are needed for us to get data from the web interface to the flask app and then to present the results in a more visually appealing way than basic text.

     from flask import Flask     # you already should have this 
     from flask import render_template, request
        
     # modeling packages
     import pandas as pd
     import numpy as np
     from sklearn.linear_model import LogisticRegression
     from sklearn.model_selection import train_test_split
     from sklearn.metrics import classification_report
    
  3. Build the model: The following code will read in the titanic_data.csv, clean it up, select the pertinent features, split the data into test and training sets and then train a simple logistic regression model to predict the probability of survival. Paste this code right at the start of the script initialization. The model will be available in the namespace of the flask app.

     # read in data and clean the gender column
     if __name__ == '__main__':
         # build a basic model for titanic survival
         titanic_df = pd.read_csv('data/titanic_data.csv')
         titanic_df['sex_binary'] = titanic_df['sex'].map({'female': 1, 
                                                           'male': 0})
            
         # choose our features and create test and train sets
         features = ['pclass', 'age', 'sibsp', 'parch', 
                     'fare', 'sex_binary', 'survived']
         train_df, test_df = train_test_split(titanic_df)
         train_df = train_df[features].dropna()
         test_df = test_df[features].dropna()
            
         features.remove('survived')
         X_train = train_df[features]
         y_train = train_df['survived']
         X_test = test_df[features]
         y_test = test_df['survived']
            
         # fit the model
         L1_logistic = LogisticRegression(C=1.0, penalty='l1')
         L1_logistic.fit(X_train, y_train)
            
         # check the performance
         target_names = ['Died', 'Survived']
         y_pred = L1_logistic.predict(X_test)
         print(classification_report(y_test, y_pred, 
                                     target_names=target_names))
            
         # start the app
         app.run(debug=True)
    
  4. Rerunning the script now should show us the classification_report from the logistic regression model in the terminal. We haven’t hooked up any flask routes to the model however. Let’s change that.

Rendering HTML templates using Flask

  1. I’ve created a basic HTML template where we can build a user interface for predicting with our amazing model.

     @app.route('/titanic')
     def titanic():
         return render_template('titanic.html')
    
  2. Go to the Titanic Template to see where you will be interfacing with the user.

Hooking it all up

Getting prediction inputs into flask

Our model uses the following variables to predict whether someone would have survived the titanic:

  • Ticket Class (categorical)
  • Age (integer)
  • Number of Siblings & Spouses (integer)
  • Number of Children & Parents (integer)
  • Ticket Fare (numerical)
  • Gender (binary)

In order to hook up the web interface with the model we have to allow the user to input all of the required model parameters. The easiest way to do this is with a simple web form.

  1. In the titanic.html file, add the following code. This creates our webform, labels the inputs so we can grab them later and implements the predict button that sends the form to the /titanic flask route.`
     <!-- The web form goes here -->
     <form action="/titanic" method="post" id="titanic_predict">
          <div>
             <label for="name">Ticket Class: 1, 2, or 3</label>
             <input type="text" id="class" name="predict_class" value=1>
         </div>
         <div>
             <label for="name">Age: 0 - 100 </label>
             <input type="text" id="age" name="predict_age" value=25>
         </div>
         <div>
             <label for="name"># Siblings and Spouses</label>
             <input type="text" id="sibsp" name="predict_sibsp" value=1>
         </div>
         <div>
             <label for="name"># Children and Parents</label>
             <input type="text" id="parch" name="predict_parch" value=0>
         </div>
         <div>
             <label for="name">Ticket Fare: 0 - 500 ($) </label>
             <input type="text" id="fare" name="predict_fare" value=250>
         </div>
         <div>
             <label for="name">Gender: M or F</label>
             <input type="text" id="sex" name="predict_sex" value='F'>
         </div>
     </form>
     <button class="btn" type="submit" form="titanic_predict" value="Submit">Predict</button>
    

    When you go check out your page you should see the web form there for you. Press the predict button though and you’ll get an error saying that method isn’t allowed. Flask routes by default enable the ‘GET’ method but if we want to allow any additional functionality, such as submitting data to the flask server via a webform we’ll need to enable those explicitly.

  2. Update the methods parameter to allow both ‘GET’ and ‘POST’ methods on the /titanic route.

     @app.route('/titanic', methods=['GET','POST'])
     def titanic():
         return render_template('titanic.html')
    
  3. Request the data from the web form inside the flask function.

     def titanic():
         data = {} 
         if request.form:
             # get the form data
             form_data = request.form
             data['form'] = form_data
             predict_class = float(form_data['predict_class'])
             predict_age = float(form_data['predict_age'])
             predict_sibsp = float(form_data['predict_sibsp'])
             predict_parch = float(form_data['predict_parch'])
             predict_fare = float(form_data['predict_fare'])
             predict_sex = form_data['predict_sex']
             print(data)
         return render_template('titanic.html')
    
  4. Test the connection from the web page to the flask function using the predict button to make sure data is being passed from the web form by printing it to the terminal. We will be feeding this data into our model to make our predictions.

Prepare input data and get the prediction

11a. Convert the predict_sex variable from a string into binary (F = 0, M = 1).

11b. Build the numpy array of values input_data to pass into the model. The order DOES matter.

11c. Call the predict_proba() method on the logistic regression model with our input data.

11d. Grab the survival probability from the prediction and put it into the data dictionary to be passed back to the web page.

```python
def titanic():
    data = {} 
    if request.form:
        # get the form data
        form_data = request.form
        data['form'] = form_data
        predict_class = float(form_data['predict_class'])
        predict_age = float(form_data['predict_age'])
        predict_sibsp = float(form_data['predict_sibsp'])
        predict_parch = float(form_data['predict_parch'])
        predict_fare = float(form_data['predict_fare'])
        predict_sex = form_data['predict_sex']
        
        # convert the sex from text to binary
        if predict_sex == 'M':
            sex = 0
        else:
            sex = 1
        input_data = np.array([predict_class, 
                               predict_age, 
                               predict_sibsp, 
                               predict_parch, 
                               predict_fare, 
                               sex])
        
        # get prediction
        prediction = L1_logistic.predict_proba(input_data.reshape(1, -1))
        prediction = prediction[0][1] # probability of survival
        data['prediction'] = '{:.1f}% Chance of Survival'.format(prediction * 100)
    return render_template('titanic.html', data=data)
``` 

Jinja Templating

We are now getting the input data from the form and predicting with it. Now we have to tell the client-side how to display the information. We will be using Jinja templating to control how data from the server is displayed.

  1. Add the following to titanic.html. In Jinja, we access data objects inside double brackets, i.e. ``.

    <div class="col-lg-6">
        <!-- Result presentation goes here -->
            
    </div>
    

Take a look at the /titanic page and you can see that all the bits and pieces are hooked up. Time to do just a bit of UI work. Jinja allows you to use python-like logic to control the HTML that is displayed.

  1. In the titanic.html file, use a simple if statement to prevent displaying anything if a prediction is not present. Then add some HTML to structure what the user will see. Here we want to show the prediction and the input parameters they put into the model.

     <div class="col-lg-6">
        <!-- Result presentation goes here -->
            
    </div>
    

That’s It! (we hope)

Fully Hooked Up Titanic Survival Model

At this point you should hopefully have a fully function flask-based web app that trains a model off real data and presents a simple interface to a user to allow them to make a prediction with the model and see the results.

More Resources


Week 6 / 7: Unsupervised Methods, Recommenders and Break Week

K-Means Clustering Animation

Unsupervised Methods

Well, it’s the end of break week, that’s why there wasn’t a post last Sunday. In the beginning of Week 6, we studied what are known as “unsupervised methods”. These methods involve using a computer to discover patterns in data and are often using in clustering or classifying applications. An example of a specific algorithm named “K-means Clustering” is shown above.

The way K-Means works is you take some set of data and tell the algorithm how many clusters (K) you think might be present. This can be done systematically or explicitly. The computer will then randomly place those K-cluster centers in the data and iteratively try to optimize the classification of the real data until you have the best optimized clusters possible. Optimization, in this case, refers to minimizing the euclidean distance between classified data points and their respective cluster centers as best as possible. Below, I’ve shown the results of K-Means clustering on the famous “Iris” data using K-values from 1 - 4. By eye, we can see that there is most probably 2 or 3 clusters present (humans are very good at pattern recognition) but we can tell the computer any number of clusters we want until the number of clusters is equal to the number of data points, at which point you will have classified each datapoint as it’s own class which is fairly useless.

K = 1 Clusters

1_anim_crop


K = 2 Clusters

2_anim_crop


K = 3 Clusters

3_anim_crop

K = 4 Clusters

4_anim_crop

This was a pretty fun little exercise, and I enjoyed building the different visualizations using both python’s matplotlib and an fantastic command-line tool called ImageMagick (thank you Denis) to make the animations. I’ve made the class file and documentation for my code available on github if anyone is interested. 

Matrices and Recommendation Systems

Apart from unsupervised methods, we again went back to linear algebra to learn a number of matrix factorization and dimensionality reduction techniques. The gist of these methods is that we can use matrix math to discover latent features in data, or to fill in unknown values with good estimates. Ever wonder how Netflix or Spotify recommends items to you? Matrix factorization is what it boils down to. Here’s an great and very accessible article on the topic from the Atlantic: How Netflix Reverse Engineered Hollywood.

Case Study: For this week’s case study, we built a joke recommender using the Jester Dataset. This is a dataset of 150 jokes with about 1.7 million ratings from 60,000 users. Our task was to best estimate the jokes that new users would score highly. My team used a GridSearch to cycle through a number of different parameters to best optimize our recommender system.

Next Week: Big Data Tools


Week 5 - There's MongoDB in my Beautiful Soup

Who comes up with these names???

This week we touched on all sorts of topics. We started off studying data science for business, looking at how to translate different models into profit curves. We then moved onto web scraping (yes, that is the proper terminology). This is one of my favorite parts of data science. Using just a few lines of code, you are able to grab information, automatically from the internet and store them for further use. We used the Beautiful Soup library to scrape thousands of New York Times articles into a MongoDB database. The web scraping part is awesome, I found the MongoDB database pretty confusing, mostly because it’s written in Javascript which is a language I have very little experience with.

Natural Language Processing

After extracting all this text data, we naturally had to come up with ways of doing text analysis; a field called ‘Natural Language Processing (NLP)’. Like everyday, the instructors at Galvanize smashed an insane amount of material into one day. I’m still trying to process it all but basically it boils down to NLP is a difficult field since words in different arrangements or with different punctuation can have different meanings and connotations.

Let's eat Grandma. Let's eat, Grandma.

There are operations you can do to make NLP more successful, dropping suffixes and word simplification (i.e. car, cars, car’s, cars’ $\Rightarrow$ car) is one example. But it is still a developing field with lots of progress to be made. For our assignment, we were asked to analyze how similar various articles from the New York Times were from each other.

Time Series

Time series data broken down. Example of time series data (top) broken down into ‘seasonal’, ‘trend’, and ‘white noise’ components.

Finally, we studied Time Series. I’ll admit that by this point in the week I was pretty much mentally checked out. I liked Time Series though, it was all about decomposing your signal into multiple ‘subsignals’. For example, let’s take temperature. At the scale of a day, the temperature goes up and down with the sun. At the scale of a year, the temperature goes up during summer and down in the winter. And if we were to look at the even larger, multi-year scale, we’d see an overall up-trend, because … global warming. If you have a good model, you should be able to capture the trends and the different cyclical components, leaving behind only white noise. In practice, this is quite difficult.

On the final day of the week we did a day-long analysis using data from a ride-sharing company (think uber or lyft). Using real data, we were able to predict customer ‘churn’ (when a customer leaves a company) and make recommendations for how to retain and build a customer base. My team built a Logistic regression model and Random Forest to capture and analyze these data.

The week ended with a much needed happy hour.

Thanks for hanging in there with me.

Next up: Unsupervised Learning + Capstone Ideas


Week 4 - Trees, Forests, Bagging and Boosting

pride_trees It’s Pride and here is my Random Forest - this isn’t really how it works…

This week we started on what I would consider the more exciting part of data science – machine learning.

By using computers, we are able to process vast quantities of data and discover patterns that would otherwise go unnoticed. Broadly speaking, there are two main categories of machine learning, supervised and unsupervised methods. This week we focused on supervised methods which I will briefly go over here.

Let’s take an entirely made up (and ridiculous) set of data relating ‘eye color’ and ‘sushi preference’ to whether or not a person ‘likes cats’. The ‘likes cats’ will serve as our ‘label’. Using a supervised method you feed the computer a bunch of observations as well as labels for those observations. We can then employ a variety of different algorithms to determine what, if any, features relate to the response variable, ‘Likes Cats.’ By crafting our experimental design and analysis well, we might even be able to determine if some of those features (i.e. eye color), CAUSE someone to like cats. More than that, if our model is good, we take a new person’s eye color and sushi preference, and predict if they’ll like cats or not with some degree of certainty (think targeted ads).

X1 = Eye Color X2 = Favorite Sushi Y = Likes Cats 
Brown California Rolls True
Brown Yellowtail False
Blue California Roll False
Green Cucumber Roll True

Now, from my example, this may seem childish and pointless but imagine you have thousands of predictors variables (X’s) and millions of observations (rows of data). How do you process this? How do you find patterns? Certainly not using an Excel spreadsheet.

This is the type of challenge that biostatisticians are facing when using genetic data to predict cancers and Facebook’s engineers deal with when trying to recognize classify users by their behaviors. These are non-trivial problems to have and machine learning is an essential tool for solving them.

Click here for a beautiful example of machine learning at work!

We learned 8 different algorithms this week. It was definitely an intense week and I won’t bore you by going over all of the nitty gritty. I will however provide links to helpful resources if you are at all interested. Gradient Descent, Stochastic Gradient Descent, Decision Trees, Random Forests, Bagging, Boosting, AdaBoost, and Support Vector Machines.

Please keep reading and ask me about Machine Learning, it’s awesome.