Build a web app to use a Machine Learning model
Contents
# Install the necessary dependencies
import sys
import os
!{sys.executable} -m pip install --quiet pandas scikit-learn numpy matplotlib jupyterlab_myst ipython requests flask
import numpy as np
import pandas as pd
from flask import Flask, request, render_template
import pickle
import sklearn
import requests
from sklearn.preprocessing import LabelEncoder
from sklearn.model_selection import train_test_split
from sklearn.metrics import accuracy_score, classification_report
from sklearn.linear_model import LogisticRegression
12.4. Build a web app to use a Machine Learning model#
In this section, you will train an ML model on a data set thatās out of this world: UFO sightings over the past century, sourced from NUFORCās database.
You will learn:
How to āpickleā a trained model
How to use that model in a Flask app
We will continue our use of notebooks to clean data and train our model, but you can take the process one step further by exploring using a model āin the wildā, so to speak: in a web app.
To do this, you need to build a web app using Flask.
12.4.1. Building an app#
There are several ways to build web apps to consume machine learning models. Your web architecture may influence the way your model is trained. Imagine that you are working in a business where the data science group has trained a model that they want you to use in an app.
12.4.1.1. Considerations#
There are many questions you need to ask:
Is it a web app or a mobile app? If you are building a mobile app or need to use the model in an IoT context, you could use TensorFlow Lite and use the model in an Android or iOS app.
Where will the model reside? In the cloud or locally?
Offline support. Does the app have to work offline?
What technology was used to train the model? The chosen technology may influence the tooling you need to use.
Using TensorFlow. If you are training a model using TensorFlow, for example, that ecosystem provides the ability to convert a TensorFlow model for use in a web app by using TensorFlow.js.
Using PyTorch. If you are building a model using a library such as PyTorch, you have the option to export it in ONNX (Open Neural Network Exchange) format for use in JavaScript web apps that can use the Onnx Runtime. This option will be explored in a future section for a Scikit-learn-trained model.
Using Lobe.ai or Azure Custom Vision. If you are using an ML SaaS (Software as a Service) system such as Lobe.ai or Azure Custom Vision to train a model, this type of software provides ways to export the model for many platforms, including building a bespoke API to be queried in the cloud by your online application.
You also have the opportunity to build an entire Flask web app that would be able to train the model itself in a web browser. This can also be done using TensorFlow.js in a JavaScript context.
For our purposes, since we have been working with Python-based notebooks, letās explore the steps you need to take to export a trained model from such a notebook to a format readable by a Python-built web app.
12.4.2. Tool#
For this task, you need two tools: Flask and Pickle, both of which run on Python.
ā Whatās Flask? Defined as a āmicro-frameworkā by its creators, Flask provides the basic features of web frameworks using Python and a templating engine to build web pages. Take a look at this Learn module to practice building with Flask.
ā
Whatās Pickle? Pickle š„ is a Python module that serializes and de-serializes a Python object structure. When you āpickleā a model, you serialize or flatten its structure for use on the web. Be careful: pickle is not intrinsically secure, so be careful if prompted to āun-pickleā a file. A pickled file has the suffix .pkl
.
12.4.3. Exercise - clean your data#
In this section, youāll use data from 80,000 UFO sightings, gathered by NUFORC (The National UFO Reporting Center). This data has some interesting descriptions of UFO sightings, for example:
Long example description. āA man emerges from a beam of light that shines on a grassy field at night and he runs towards the Texas Instruments parking lotā.
Short example description. āthe lights chased usā.
The ufos.csv
spreadsheet includes columns about the city
, state
and country
where the sighting occurred, the objectās shape
and its latitude
and longitude
.
Create a blank notebook
to continue the steps below:
Import pandas
, matplotlib
, and numpy
as you did in the previous section and import the ufos spreadsheet. You can take a look at a sample data set:
ufos = pd.read_csv(
"https://static-1300131294.cos.ap-shanghai.myqcloud.com/data/ufos.csv"
)
ufos.head()
datetime | city | state | country | shape | duration (seconds) | duration (hours/min) | comments | date posted | latitude | longitude | |
---|---|---|---|---|---|---|---|---|---|---|---|
0 | 10/10/1949 20:30 | san marcos | tx | us | cylinder | 2700.0 | 45 minutes | This event took place in early fall around 194... | 4/27/2004 | 29.883056 | -97.941111 |
1 | 10/10/1949 21:00 | lackland afb | tx | NaN | light | 7200.0 | 1-2 hrs | 1949 Lackland AFB, TX. Lights racing acros... | 12/16/2005 | 29.384210 | -98.581082 |
2 | 10/10/1955 17:00 | chester (uk/england) | NaN | gb | circle | 20.0 | 20 seconds | Green/Orange circular disc over Chester, En... | 1/21/2008 | 53.200000 | -2.916667 |
3 | 10/10/1956 21:00 | edna | tx | us | circle | 20.0 | 1/2 hour | My older brother and twin sister were leaving ... | 1/17/2004 | 28.978333 | -96.645833 |
4 | 10/10/1960 20:00 | kaneohe | hi | us | light | 900.0 | 15 minutes | AS a Marine 1st Lt. flying an FJ4B fighter/att... | 1/22/2004 | 21.418056 | -157.803611 |
Convert the ufos data to a small dataframe with fresh titles. Check the unique values in the Country
field.
ufos = pd.DataFrame(
{
"Seconds": ufos["duration (seconds)"],
"Country": ufos["country"],
"Latitude": ufos["latitude"],
"Longitude": ufos["longitude"],
}
)
ufos.Country.unique()
array(['us', nan, 'gb', 'ca', 'au', 'de'], dtype=object)
Now, you can reduce the amount of data we need to deal with by dropping any null values and only importing sightings between 1-60 seconds:
ufos.dropna(inplace=True)
ufos = ufos[(ufos["Seconds"] >= 1) & (ufos["Seconds"] <= 60)]
ufos.info()
<class 'pandas.core.frame.DataFrame'>
Int64Index: 25863 entries, 2 to 80330
Data columns (total 4 columns):
# Column Non-Null Count Dtype
--- ------ -------------- -----
0 Seconds 25863 non-null float64
1 Country 25863 non-null object
2 Latitude 25863 non-null float64
3 Longitude 25863 non-null float64
dtypes: float64(3), object(1)
memory usage: 1010.3+ KB
Use Scikit-learnās LabelEncoder
library to convert the text values for countries to a number:
ufos["Country"] = LabelEncoder().fit_transform(ufos["Country"])
ufos.head()
Seconds | Country | Latitude | Longitude | |
---|---|---|---|---|
2 | 20.0 | 3 | 53.200000 | -2.916667 |
3 | 20.0 | 4 | 28.978333 | -96.645833 |
14 | 30.0 | 4 | 35.823889 | -80.253611 |
23 | 60.0 | 4 | 45.582778 | -122.352222 |
24 | 3.0 | 3 | 51.783333 | -0.783333 |
12.4.4. Exercise - build your model#
Now you can get ready to train a model by dividing the data into the training and testing group.
Select the three features you want to train on as your X vector, and the y vector will be the Country
. You want to be able to input Seconds
, Latitude
and Longitude
and get a country id to return.
Selected_features = ["Seconds", "Latitude", "Longitude"]
X = ufos[Selected_features].values
y = ufos["Country"].values
X_train, X_test, y_train, y_test = train_test_split(X, y, test_size=0.2, random_state=0)
Train your model using logistic regression:
model = LogisticRegression(solver="sag", max_iter=10000)
model.fit(X_train, y_train)
predictions = model.predict(X_test)
print(classification_report(y_test, predictions))
print("Predicted labels: ", predictions)
print("Accuracy: ", accuracy_score(y_test, predictions))
precision recall f1-score support
0 1.00 1.00 1.00 41
1 0.86 0.31 0.45 250
2 1.00 1.00 1.00 8
3 1.00 1.00 1.00 131
4 0.96 1.00 0.98 4743
accuracy 0.96 5173
macro avg 0.96 0.86 0.89 5173
weighted avg 0.96 0.96 0.96 5173
Predicted labels: [4 4 4 ... 3 4 4]
Accuracy: 0.9640440750048328
The accuracy isnāt bad (around 95%), unsurprisingly, as Country
and Latitude/Longitude
correlate.
The model you created isnāt very revolutionary as you should be able to infer a Country
from its Latitude
and Longitude
, but itās a good exercise to try to train from raw data that you cleaned, exported, and then use this model in a web app.
12.4.5. Exercise - āpickleā your model#
Now, itās time to pickle your model! You can do that in a few lines of code. Once itās pickled, load your pickled model and test it against a sample data array containing values for seconds, latitude and longitude.
# download .pkl file from the cloud
cloud_url = "https://static-1300131294.cos.ap-shanghai.myqcloud.com/data/ufo-model.pkl"
model_filename = "ufo-model.pkl"
response = requests.get(cloud_url)
with open(model_filename, "wb") as local_file:
local_file.write(response.content)
pickle.dump(model, open(model_filename, "wb"))
model = pickle.load(open("ufo-model.pkl", "rb"))
print(model.predict([[50, 44, -12]]))
[3]
The model returns ā3ā, which is the country code for the UK. Wild! š½
12.4.6. Exercise - build a Flask app#
Now you can build a Flask app to call your model and return similar results but in a more visually pleasing way.
Start by creating a folder called /web-app
next to the notebook.ipynb
file where your ufo-model.pkl
file resides.
In that folder create three more folders: /static
, with a folder /css
inside it, and /templates
. You should now have the following files and directories:
web-app/
static/
css/
templates/
notebook.ipynb
../
assets/
pickle/
ufo-model.pkl
ā Refer to the solution folder for a view of the finished app
The first file to create in /web-app
folder is requirements.txt
file. Like package.json
in a JavaScript app, this file lists dependencies required by the app. In requirements.txt
add the lines:
scikit-learn
pandas
numpy
flask
Now, run this file by navigating to /web-app
:
cd web-app
In your terminal type pip install
, to install the libraries listed in requirements.txt
:
pip install -r requirements.txt
Now, youāre ready to create three more files to finish the app:
Create
app.py
in the root.Create
index.html
in/templates
directory.Create
styles.css
in/static/css
directory.
Build out the styles.css file with a few styles:
body {
width: 100%;
height: 100%;
font-family: 'Helvetica';
background: black;
color: #fff;
text-align: center;
letter-spacing: 1.4px;
font-size: 30px;
}
input {
min-width: 150px;
}
.grid {
width: 300px;
border: 1px solid #2d2d2d;
display: grid;
justify-content: center;
margin: 20px auto;
}
.box {
color: #fff;
background: #2d2d2d;
padding: 12px;
display: inline-block;
}
Next, build out the index.html file:
<!DOCTYPE html>
<html>
<head>
<meta charset="UTF-8">
<title>šø UFO Appearance Prediction! š½</title>
<link rel="stylesheet" href="{{ url_for('static', filename='css/styles.css') }}">
</head>
<body>
<div class="grid">
<div class="box">
<p>According to the number of seconds, latitude and longitude, which country is likely to have reported seeing a UFO?</p>
<form action="{{ url_for('predict')}}" method="post">
<input type="number" name="seconds" placeholder="Seconds" required="required" min="0" max="60" />
<input type="text" name="latitude" placeholder="Latitude" required="required" />
<input type="text" name="longitude" placeholder="Longitude" required="required" />
<button type="submit" class="btn">Predict country where the UFO is seen</button>
</form>
<p>{{ prediction_text }}</p>
</div>
</div>
</body>
</html>
Take a look at the templating in this file. Notice the āmustacheā syntax around variables that will be provided by the app, like the prediction text: {{}}
. Thereās also a form that posts a prediction to the /predict
route.
Finally, youāre ready to build the python file that drives the consumption of the model and the display of predictions:
In app.py
add:
app = Flask(__name__)
model = pickle.load(
open(
"ufo-model.pkl",
"rb",
)
)
@app.route("/")
def home():
return render_template("index.html")
@app.route("/predict", methods=["POST"])
def predict():
int_features = [int(x) for x in request.form.values()]
final_features = [np.array(int_features)]
prediction = model.predict(final_features)
output = prediction[0]
countries = ["Australia", "Canada", "Germany", "UK", "US"]
return render_template(
"index.html", prediction_text="Likely country: {}".format(countries[output])
)
if __name__ == "__main__":
app.run(debug=False)
when you add debug=True
while running the web app using Flask, any changes you make to your application will be reflected immediately without the need to restart the server. Beware! Donāt enable this mode in a production app.
If you run python app.py
or python3 app.py
- your web server starts up, locally, and you can fill out a short form to get an answer to your burning question about where UFOs have been sighted!
Before doing that, take a look at the parts of app.py
:
First, dependencies are loaded and the app starts.
Then, the model is imported.
Then, index.html is rendered on the home route.
On the /predict
route, several things happen when the form is posted:
The form variables are gathered and converted to a NumPy array. They are then sent to the model and a prediction is returned.
The Countries that we want to be displayed are re-rendered as readable text from their predicted country code, and that value is sent back to index.html to be rendered in the template.
Using a model this way, with Flask and a pickled model, is relatively straightforward. The hardest thing is to understand what shape the data is that must be sent to the model to get a prediction. That all depends on how the model was trained. This one has three data points to be input in order to get a prediction.
12.4.7. Your turn! š#
12.4.8. Self study#
There are many ways to build a web app to consume ML models. Make a list of the ways you could use JavaScript or Python to build a web app to leverage machine learning. Consider architecture: should the model stay in the app or live in the cloud? If the latter, how would you access it? Draw out an architectural model for an applied ML web solution.
12.4.9. Acknowledgements#
Thanks to Microsoft for creating the open-source course ML-For-Beginners. It inspires the majority of the content in this chapter.