Publish ML Model as API or Web Page: Flask and Digital Ocean

5 minutes read

After building and training your Machine Learning model, the next step is to publish it for others to make predictions. In this tutorial, we will show how to package the model and deploy it as an API or a web page.


Table of Contents

  • The Model We're Working With
  • Creating a Reusable Model
  • Publishing the Model as API with Flask
  • Building the Web App for the Model
  • Prepare the Project for Deployment
  • Deploying the API/Web App on Digital Ocean

The code repository is available, the link is at the bottom of this tutorial.

Let's go!


The Model We're Working With

For this tutorial, we take a Model that predicts a person's salary based on their years of experience and their skill level. We had built this model in the Intro to Machine Learning Course.

However, the actual model doesn't really matter that much. Our goal of this tutorial is to show you the process of publishing any model with input numbers and output results.

Our initial data looked like this:

We have used this data to train our model like this:

model/model.py

import pandas as pd
from sklearn.model_selection import train_test_split
from sklearn.linear_model import LinearRegression
import os
 
df = pd.read_csv('salaries-2023.csv')
 
# Languages we want to use in our model
allowed_languages = ['php', 'js', '.net', 'java']
df = df[df['language'].isin(allowed_languages)]
 
# Fixing the city names
vilnius_names = ['Vilniuj', 'Vilniua', 'VILNIUJE', 'VILNIUS', 'vilnius', 'Vilniuje']
condition = df['city'].isin(vilnius_names)
df.loc[condition, 'city'] = 'Vilnius'
 
kaunas_names = ['KAUNAS', 'kaunas', 'Kaune']
condition = df['city'].isin(kaunas_names)
df.loc[condition, 'city'] = 'Kaunas'
 
# Cities we want to use in our model
allowed_cities = ['Vilnius', 'Kaunas']
df = df[df['city'].isin(allowed_cities)]
 
# Filtering out the salaries that are too high
df = df[df['salary'] <= 6000]
 
one_hot = pd.get_dummies(df['language'], prefix='lang')
df = df.join(one_hot)
df = df.drop('language', axis=1)
 
one_hot = pd.get_dummies(df['city'], prefix='city')
df = df.join(one_hot)
df = df.drop('city', axis=1)
 
x = df.iloc[:, 0:2].values
y = df.iloc[:, 2].values
 
x_train, x_test, y_train, y_test = train_test_split(x, y, test_size=0.2, random_state=0)
 
model = LinearRegression()
model.fit(x_train, y_train)

Then, we were able to predict the salary of a person like this:

# ...
 
# 3 means senior level
# 8 is years of experience
salaries = model.predict([[3, 8]])
print(salaries[0])

This gave us a prediction of 3391.89 - the expected salary of a person with 8 years of experience and a senior level.

Of course, all of this was running locally on our computer. So, the next step was to publish it as an API.


Creating a Reusable Model

Before diving into API development, we have to save our Model into a file, to avoid it being trained on each run.

We will do that with Joblib library.

pip install joblib
import joblib
 
# ...
 
joblib.dump(model, 'model.pkl')

This will create a model.pkl file in our folder. Now, we can load it into other functions or Python files, like this:

# ...
 
model = joblib.load('model.pkl')

Now, let's separate two things into functions:

  • Creating the model
  • Making predictions with that model

model/model.py

import pandas as pd
from sklearn.model_selection import train_test_split
from sklearn.linear_model import LinearRegression
import os
import joblib
 
 
def create_model(current_dir):
df = pd.read_csv(os.path.join(current_dir, 'salaries-2023.csv'))
 
allowed_languages = ['php', 'js', '.net', 'java']
df = df[df['language'].isin(allowed_languages)]
 
vilnius_names = ['Vilniuj', 'Vilniua', 'VILNIUJE', 'VILNIUS', 'vilnius', 'Vilniuje']
condition = df['city'].isin(vilnius_names)
df.loc[condition, 'city'] = 'Vilnius'
 
kaunas_names = ['KAUNAS', 'kaunas', 'Kaune']
condition = df['city'].isin(kaunas_names)
df.loc[condition, 'city'] = 'Kaunas'
 
allowed_cities = ['Vilnius', 'Kaunas']
df = df[df['city'].isin(allowed_cities)]
 
df = df[df['salary'] <= 6000]
 
one_hot = pd.get_dummies(df['language'], prefix='lang')
df = df.join(one_hot)
df = df.drop('language', axis=1)
 
one_hot = pd.get_dummies(df['city'], prefix='city')
df = df.join(one_hot)
df = df.drop('city', axis=1)
 
x = df.iloc[:, 0:2].values
y = df.iloc[:, 2].values
 
x_train, x_test, y_train, y_test = train_test_split(x, y, test_size=0.2, random_state=0)
 
model = LinearRegression()
model.fit(x_train, y_train)
 
joblib.dump(model, os.path.join(current_dir, 'model.pkl'))
 
 
def predict_salary(experience, level):
current_dir = os.path.dirname(__file__)
 
# Check if the model exists. If not - create it
if not os.path.isfile(os.path.join(current_dir, 'model.pkl')):
create_model(current_dir)
 
# Load the saved model
model = joblib.load(os.path.join(current_dir, 'model.pkl'))
# Use the loaded model to make predictions
salaries = model.predict([[int(level), int(experience)]])
 
return salaries[0]

Doing this allows us to have a reusable Model and call it like this, from external Python files:

some_other_file.py

from model.model import predict_salary
 
salary = predict_salary(experience=8, level=3)
print(salary)

The first call will create a Model file if it does not exist and take a little bit longer, while any subsequent calls will be much faster as the Model will be loaded from the file.


Publishing the Model as API

To create our API system, we will use Flask library. Flask is a Python library that allows us to create a web server and define endpoints that can be called from the outside. Let's install it:

pip install flask
pip install waitress

Note: Waitress is a library that allows us to run our Flask app in production. It is not required for development, but it is a good idea to use it in production.

Now we can create our Flask app:

app.py

from flask import Flask
from waitress import serve
 
app = Flask(__name__)
 
@app.route('/')
def homepage():
return "Welcome to the Salary Prediction homepage"
 
if __name__ == '__main__':
serve(app, host="0.0.0.0", port=8080)

Now, we can run our App like this:

flask run

And we should see the following pop-up:

Now, we can open the URL in our browser and see the following:

Of course, this is not API yet, but we already have a web server running with 8 lines of code.

Next, let's work on an actual API endpoint. Here's how we can do that:

from flask import Flask
from flask import Flask, request, jsonify
from model.model import predict_salary
from waitress import serve
 
app = Flask(__name__)
 
@app.route('/')
def homepage():
return "Welcome to the Salary Prediction homepage"
 
@app.route('/api/calculate/salary', methods=['POST'])
def salary_calculation_api():
return jsonify({
"salary": "{:.2f}".format(predict_salary(request.json['experience'], request.json['level']))
})
 
if __name__ == '__main__':
serve(app, host="0.0.0.0", port=8080)

Now we can run our application using flask run and call our API endpoint like this in Postman:

That's it! Our API endpoint is ready!

Next, we will look at a web page built with Flask.


Building the Web App for the Model

Not every application that uses a Model will interact with our client via API.

Sometimes, we must build a simple form where the user can enter the data and get the result. Let's do that with Flask, in the same app.py file:

app.py

from flask import Flask, jsonify
from flask import Flask, render_template, request, jsonify
 
 
@app.route('/api/calculate/salary', methods=['POST'])
def salary_calculation_api():
return jsonify({
"salary": "{:.2f}".format(predict_salary(request.json['experience'], request.json['level']))
})
 
 
@app.route('/calculate/salary', methods=['GET'])
def salary_calculation_get():
# Jinja2 template engine is used to render HTML page
return render_template('form.html')
 
 
@app.route('/calculate/salary', methods=['POST'])
def salary_calculation():
salary = "{:.2f}".format(predict_salary(request.form['experience'], request.form['level']))
return render_template('form.html', salary=salary, experience=request.form['experience'],
level=request.form['level'])
 
 
if __name__ == '__main__':
serve(app, host="0.0.0.0", port=8080)

Here, we did a few things:

  • We have added two routes - the GET route that will return a template (we'll create it in a second) and the POST route that will accept the form data and return the template with the result
  • In our POST route, we use request.form to get the form data and return it back to the template with parameters

Now, we need to create a template that will be used to render our form:

templates/form.html

<!DOCTYPE html>
<html lang="en">
<head>
<title>My Webpage</title>
</head>
<body>
 
{% if salary %}
 
<h1>Predicted Salary</h1>
<p>{{ salary }}</p>
 
{% endif %}
 
<h1>Salary prediction</h1>
 
<form method="POST" action="/calculate/salary">
<label for="experience">Years of experience</label><br/>
<input type="text" id="experience" name="experience" min="1" max="10" step="1" value="{{ experience }}">
 
<br/><br/>
 
<label for="level">Level</label><br/>
<input type="text" id="level" name="level" value="2" min="1" max="3" value="{{ level }}">
<br/><br/>
 
<input type="submit" value="Submit">
</form>
 
</body>
</html>

Once you create this template - we can launch our application with flask run and open the following URL in our browser:

Now we can enter the data and get the result:

That's it! We have created a simple web app that uses our model to predict a person's salary.

The next step is to deploy it to a remote server, for everyone to use!


Prepare Project for Deployment

Before the deployment to production, we need to configure three files in our project:

  • requirements.txt: each project requires different packages, so we have to tell the server what packages we need
  • runtime.txt: what version of Python we are using
  • Procfile: this file will tell the server how to run our application

Let's create the requirements file first:

requirements.txt

flask
pandas
scikit-learn
joblib
waitress

Note: This file can also be created using the pip freeze > requirements.txt command, which will include all the packages installed on your computer. Creating a new virtual environment and installing only the required packages for your project is a good idea.

Next, we need to create a runtime.txt file that will tell the server what version of Python we are using:

runtime.txt

python-3.12.1

Note: This file is optional. If you do not specify the version of Python, the server will use the latest version.

Finally, we need to create a Procfile that will tell the server how to run our application:

Procfile

web: python app.py

Note: This file is required. The server will only know how to run your application if you specify it.

That's it! We have the basics covered and are ready to deploy our application to production.


Deploying the Model and Web App on Digital Ocean

Now that we have a fully working Web App and API, both using our Model, we should deploy it to some server so other people can use it.

For this tutorial, we will use Digital Ocean.

To get started, we need to create a new App in Digital Ocean:

Then we need to select the Source - we will use GitHub:

Then, we need to select the repository and branch:

Since we have /app.py in our root folder, we can leave these fields as Default. You might need a different configuration if you have a different folder structure.

Then we click Next and configure the Server tier:

You can leave it as Default (it will cost $24 per month) or edit the plan and choose a cheaper option. For this tutorial, we will use the default option.

Then we click Next and we should see Environment Variables:

Since we don't have any, press Next. This should give you an overview of your App:

Clicking Next will give you a complete overview of your soon-to-be-created App:

If everything looks good - click Create Resources and wait for the app to be created:

Here, we can go into Build Logs and see the progress:

We need to wait until the app is created and deployed. This can take a few minutes, so be patient. Once it is done - you should see the following:

Now, once this is Deployed, we can go into Settings and see the URL of our app:

Note: This URL in the image will not actually work, we created it just for demo purposes of this tutorial.

Now, we can open this URL in our browser and see the following:

Or we can open the /calculate/salary endpoint and see the following:

And, of course, we can call the /api/calculate/salary endpoint in Postman:

That's it! Your Model is now live as a Web Page or an API! From there, you can expand the functionality and add more features.

You can find all the source code in this GitHub repository.


Alessandro avatar

Hi, here

value="2" min="1" max="3" value="{{ level }}

there are double value="..." items. Is this a typo?

Modestas avatar

Hi, good catch! I missed this while writing, but somehow it did not cause any issues in testing too... :)

Anton Cherednichenko avatar
Anton Cherednichenko

The code repository is available, the link is at th bottom of this tutorial.

Missed the 'e' in 'the' :)

Povilas avatar

Thanks, fixed.