How to Create a Simple Frontend, API, and Model with Python + Vue.js
By: James Salvatore
Tags: Machine Learning, Vuejs, Python3, Data Science, Python

Data Scientists, more often than not, are making models. The gist of the model, from an outsider’s perspective, is that it’s a black box where a whole lot of things (features) go into the model and out pops something like a “yes” or “no” or “how similar to a dog is this picture?” (labels).

How does the data get into the model? This is where things usually get complicated: it can be streaming, it can be batched and then processed, dozens of frameworks and tools exist for exactly this purpose — but for our purposes, we’re going to simply have a user input some data and then we’ll have the model predict on it and return a prediction.

There will be three main parts to this post:

First, we will create a simple model with Scikit-learn in Python 3.x.

Second, we will create an API which can run the model and predict on input data.

Last, we will create a frontend which can hit the API and allow for user input and output.

It’ll look something like this:

This post is meant to be for beginners. In one of these three parts so we will intentionally cut out steps which are not critical in this process. Think of this post as something to whet your appetite, not as a way to learn best practices for this process.

The Model

We will be using Python 3.x (minor version shouldn’t matter; at the time of this writing, 3.7 is the latest version) and the Scikit-learn package along with Pandas and Numpy. Make sure you have these packages installed. If you do not have Python or these packages, check out Anaconda which contains a Python installation as well as a number of packages which are often used in data science.

We will start by using the Iris Flower Dataset, which can be found in a number of places on the web including the UCI Machine Learning Repository.

Once you’ve downloaded the Iris dataset (it will be Data Folder > iris.data in the previous link) we’re doing to load it in and append the columns, then cut it up into features (X values) and label (Y value).

We write the following Python file, given in its entirety below. We make notes below it.

# ./python_code/make_model.py import pandas as pd

import numpy as np

from sklearn.linear_model import LogisticRegression

from sklearn.model_selection import train_test_split

import pickle ## GETTING AND PARSING THE DATA

training_data = pd.read_csv("iris.data") # Appropriately name the columns; see details on the data website.

training_data.columns = [

"sepal_length",

"sepal_width",

"petal_length",

"petal_width",

"class",

] # Split up the columns of the dataframe above into features and labels.

feature_cols = ["sepal_length", "sepal_width", "petal_length", "petal_width"]

label_cols = ["class"] # Sklearn can be picky with how the data is formatted...

X = training_data.loc[:, feature_cols]

y = training_data.loc[:, label_cols].values.ravel() # Make our training and test sets.

X_train, X_test, y_train, y_test = train_test_split(X, y, test_size=0.33) ## TRAINING A MODEL

# We don't use anything fancy, just plain old Logistic Regression!

# We use cross validation here, but you could also use a training-test set. # Instantiate and fit the model:

logreg = LogisticRegression(solver="liblinear", multi_class="ovr")

clf = logreg.fit(X_train, y_train) # See if the model is reasonable.

print("Score: ", clf.score(X_test, y_test)) # Pickle to save the model for use in our API.

pickle.dump(clf, open("./our_model.pkl", "wb"))

A few “gotcha” moments above.

We use ravel on y due to Scikit-Learn requiring a certain format for their variables.

The parameters in LogisticRegression are the defaults for the type of modeling we’d like to do.

are the defaults for the type of modeling we’d like to do. We end by using pickle to make a file which contains our model, which we will use in our API.

That’s all for this basic model.

The API

The API is almost entirely copy-paste from a Quickstart guide. We will use flask-restful as well as flask-cors (which you may need to pip or conda install). The code is below, comments follow.

# ./python_code/api.py import os

import pickle

from flask import Flask

from flask_restful import Resource, Api, reqparse

from flask_cors import CORS

import numpy as np app = Flask(__name__)

CORS(app)

api = Api(app) # Require a parser to parse our POST request.

parser = reqparse.RequestParser()

parser.add_argument("sepal_length")

parser.add_argument("sepal_width")

parser.add_argument("petal_length")

parser.add_argument("petal_width") # Unpickle our model so we can use it!

if os.path.isfile("./our_model.pkl"):

model = pickle.load(open("./our_model.pkl", "rb"))

else:

raise FileNotFoundError class Predict(Resource):

def post(self):

args = parser.parse_args() # Sklearn is VERY PICKY on how you put your values in...

X = (

np.array(

[

args["sepal_length"],

args["sepal_width"],

args["petal_length"],

args["petal_width"]

]

).astype("float").reshape(1, -1)

) _y = model.predict(X)[0]

return {"class": _y} api.add_resource(Predict, "/predict") if __name__ == "__main__":

app.run(debug=True)

A few “gotchas” here as well.

We use CORS to allow us to run our API and hit it with or frontend application later. See CORS.

We are using a POST, so we create a parser above to handle all of the elements we should be POSTing.

We check to see if our pickled model exists, then we load it.

Inside of the POST call, we have to do a large, messy transform for our X value: we first make it into a Numpy array, we cast the types as float, and (due to Scikit-learn being picky about how data comes in) we reshape this array to be an appropriate shape.

value: we first make it into a Numpy array, we cast the types as float, and (due to Scikit-learn being picky about how data comes in) we reshape this array to be an appropriate shape. We predict on our features and return the value of the class (our label).

You can try this if you’d like on the command-line. Note that this is, of course, not production ready (see the debug=True on the last line, for example) but we can run it locally nevertheless.

In one terminal, go into the directory with the api.py file and run

python api.py

This should bring up the api at http://localhost:5000. We can hit this with a curl command like this:

And, if everything goes right, we will see

{

"class": "Iris-virginica"

}

At the end of the POST. Awesome! We just hit our local API. The last thing we should do is to make a tiny interface so we don’t have to keep using the terminal to CURL things…

Making Our Frontend

I’ve chosen Vue.js to do our frontend in since it’s extremely easy to use, extremely easy to work with, and has a ton of things to make the page look nice. If you’ve not used Vue.js, it may be worth reading over the documentation to see what’s going on. Nevertheless, we can get started quite easily and quickly using the Vue CLI 3.

After installing Node and NPM, go over to the Vue CLI page and install the Vue CLI.

I’m going to create a project next to my Python code, but you can create it anywhere you’d like. Do so by running the command:

vue create modelapp

In the directory you want your package to live. Note that you can call this what you’d like, I’m calling it modelapp.

Pick the default present (babel, eslint) and the Vue CLI should start creating your package. Awesome.

After this is done, go into your app directory and run `vue add vuetify` and select “Default”.

You will also need axios. Get it by running

npm install -S axios

inside your project folder.

When that’s all done, go into the package and look around. There’s a lot of scary-looking files in here (if you’ve never seen Vue before!) but you will ultimately be changing one page. If you are proficient in Vue (or want to learn more about it!) feel free to change more things than we’re going to do here.

First, go into the src folder and open App.vue. It should look something like this:



<v-app>

<v-toolbar app>

<v-toolbar-title class="headline text-uppercase">

<span>Vuetify</span>

<span class="font-weight-light">MATERIAL DESIGN</span>

</v-toolbar-title>

<v-spacer></v-spacer>

<v-btn

flat

href="

target="_blank"

>

<span class="mr-2">Latest Release</span>

</v-btn>

</v-toolbar> Vuetify MATERIAL DESIGN https://github.com/vuetifyjs/vuetify/releases/latest target="_blank" Latest Release <v-content>

<HelloWorld/>

</v-content>

</v-app>

</template>

At the top. While this is fine, let’s replace it with something a little more minimal:

<template>

<v-app>

<v-toolbar app>

<v-toolbar-title class="headline text-uppercase">

<span class="font-weight-light">My Cool Model</span>

</v-toolbar-title>

</v-toolbar> <v-content>

<HelloWorld/>

</v-content>

</v-app>

</template>

Keep the <script>…</script> part of the file as-is.

Next, we’re going to go into src/components/HelloWorld.vue. We’re going to delete the whole thing. That’s right. The whole thing. We’ll replace it with the following:

<template>

<v-container>

<v-layout

text-xs-center

wrap

>

<v-flex>

<!-- IMPORTANT PART! --> <form>

<v-text-field

v-model="sepalLength"

label="Sepal Length"

required

></v-text-field>

<v-text-field

v-model="sepalWidth"

label="Sepal Width"

required

></v-text-field>

<v-text-field

v-model="petalLength"

label="Petal Length"

required

></v-text-field>

<v-text-field

v-model="petalWidth"

label="Petal Width"

required

></v-text-field>

<v-btn

</form> @click ="submit">submit @click ="clear">clear <br/>

<br/> <h1 v-if="predictedClass">Predicted Class is: {{ predictedClass }}</h1> <!-- END: IMPORTANT PART! -->

</v-flex>

</v-layout>

</v-container>

</template> <script>

import axios from 'axios'

name: 'HelloWorld',

data: () => ({

sepalLength: '',

sepalWidth: '',

petalLength: '',

petalWidth: '',

predictedClass : ''

}),

methods: {

submit () {

axios.post('

sepal_length: this.sepalLength,

sepal_width: this.sepalWidth,

petal_length: this.petalLength,

petal_width: this.petalWidth

})

.then((response) => {

this.predictedClass = response.data.class

})

},

clear () {

this.sepalLength = ''

this.sepalWidth = ''

this.petalLength = ''

this.petalWidth = ''

}

}

}

</script> export default {name: 'HelloWorld',data: () => ({sepalLength: '',sepalWidth: '',petalLength: '',petalWidth: '',predictedClass : ''}),methods: {submit () {axios.post(' http://127.0.0.1:5000/predict' , {sepal_length: this.sepalLength,sepal_width: this.sepalWidth,petal_length: this.petalLength,petal_width: this.petalWidth}).then((response) => {this.predictedClass = response.data.class})},clear () {this.sepalLength = ''this.sepalWidth = ''this.petalLength = ''this.petalWidth = ''

Note a few things here: while much of this is boiler-plate and standard Vue, the important parts (between the “IMPORTANT PART!” labels) are pretty neat. We use Vuetify Forms here to create a set of forms for our users to use. We use some of the Vuetify buttons to call functions (specified in the “methods” part of the strange-looking inners of `<script>`) which will call the endpoint.

Note above that we have shamelessly copied one of these forms but have taken out the validation (for teaching purposes only; your forms should be validated in real apps!).

Much of this may seem overwhelming if you’re not used to Vue, but I encourage you to mess around (e.g., make a “randomize value” button which places a random float from 1 to 4 into each of the features).

Done!

Once you’ve done this, use your terminal to go into the root directory that your Vue app lives in and run

npm run serve

This should build correctly and tell you to look at something like

localhost:8000

If you go to this address in your web browser of choice, you should see something similar to the gif at the beginning of this post! Awesome!

Make sure that you are also running your API in another terminal window by going to the API directory and running

python api.py

At this point, you should be able to extend this to do things that you want to do — make new models, look at different ways to display them on the front-end, and maybe add parts to the API.

Thanks for reading! Follow the Uptake Tech Blog for more tips, tricks, and tutorials!