Interactive Web Services with Dash

Building interactive web services using Dash.

Overview

While the standard deployment of a model as a web service is an API that you can call programmatically, it’s often useful to expose models as interactive web applications.

For example, we might want to build an application where there is a UI for specifying different inputs to a model, and the UI reacts to changes made by the user. While Flask can be used to build web pages that react to user input, there are libraries built on top of Flask that provide higher-level abstractions for building web applications with the Python language.

We’ll create a simple Dash application that provides a UI for interacting with a model. The application layout will contain three text boxes, where two are for user inputs and the third shows the output of the model. We’ll create a file called dash_app.py and start by specifying the libraries to import.

Dash

Dash is a Python library written by the Plotly team that enables building interactive web applications with Python. You specify an application layout and a set of callbacks that respond to user input.

If you’ve used Shiny in the past, Dash shares many similarities but is built on Python rather than R. With Dash, you can create simple applications, as we’ll show here, or complex dashboards that interact with machine learning models.

import dash
from dash import html
from dash import dcc
from dash.dependencies import Input, Output
import pandas as pd
import mlflow.sklearn

Layout

Next, we’ll define the layout of our application. We will create a Dash object and then set the layout field to include a title and three text boxes with labels.

We’ll include only 2 of the 10 games from the games dataset to keep the sample short. The last step in the script launches the web service and enables connections from remote machines.

Before writing the callbacks, we can test out the layout of the application by running python3 dash_app.py, which we will run on port 8080. You can browse to your public IP on port 8080 to see the resulting application. The initial application layout is shown in the figure below.

Before any callbacks are added, the result of the Prediction text box will always be 0.

import dash
from dash import html
from dash import dcc
from dash.dependencies import Input, Output
import pandas as pd
import mlflow.sklearn
app = dash.Dash(__name__)
app.layout = html.Div(children=[
html.H1(children='Model UI'),
html.P([
html.Label('Game 1 '),
dcc.Input(value='1', type='text', id='g1'),
]),
html.Div([
html.Label('Game 2 '),
dcc.Input(value='0', type='text', id='g2'),
]),
html.P([
html.Label('Prediction '),
dcc.Input(value='0', type='text', id='pred')
]),
])
if __name__ == '__main__':
app.run_server(host='0.0.0.0', port=8080)
Expected output from the above code
Expected output from the above code

Callbacks

The next step is to add a callback to the application so that the Prediction text box is updated whenever the user changes one of the Game 1 or Game 2 values. To perform this task, we define a callback shown in the snippet below. The callback is defined after the application layout, but before the run_server command. We also load the logistic regression model for the games dataset using MLflow. The callback uses an annotation to define the inputs to the function, the output, and any additional state that needs to be provided.

Due to the way the annotation is defined here, the function will be called whenever the user modifies the value of Game 1 or Game 2, and the value returned by this function will be set as the value of the Prediction text box.

import dash
from dash import html
from dash import dcc
from dash.dependencies import Input, Output
import pandas as pd
import mlflow.sklearn
import flask
model_path = "models/logit_games_v1"
mlflow.sklearn.save_model(model,model_path)
model = mlflow.sklearn.load_model(model_path)
app = dash.Dash(__name__)
@app.callback(
Output(component_id='pred', component_property='value'),
[Input(component_id='g1', component_property='value'),
Input(component_id='g2', component_property='value')]
)
def update_prediction(game1, game2):
new_row = { "G1": float(game1),
"G2": float(game2),
"G3": 0, "G4": 0,
"G5": 0, "G6": 0,
"G7": 0, "G8": 0,
"G9": 0, "G10":0 }
new_x = pd.DataFrame.from_dict(new_row,
orient = "index").transpose()
return str(model.predict_proba(new_x)[0][1])

The updated application, with the callback function included, is shown in the figure below. The prediction value now dynamically changes in response to changes in the other text fields and provides a way of introspecting the model.

Expected output from the above code
Expected output from the above code

Dash is great for building web applications because it eliminates the need to write JavaScript code. It’s also possible to stylize Dash applications using CSS to add some polish to your tools.

Try it out!

  1. Press the button to execute the codes in the previous lesson and load models.
  2. Run the dash_app.py file using the following command python3 dash_app.py.
  3. Open the given link to view the app.
import dash
import dash_html_components as html
import dash_core_components as dcc
from dash.dependencies import Input, Output
import pandas as pd
import mlflow.sklearn

app = dash.Dash(__name__)

app.layout = html.Div(children=[
    html.H1(children='Model UI'),
    html.P([
        html.Label('Game 1 '),
        dcc.Input(value='1', type='text', id='g1'),
    ]),
    html.Div([
        html.Label('Game 2 '),
        dcc.Input(value='0', type='text', id='g2'),
    ]),
    html.P([
        html.Label('Prediction '),
        dcc.Input(value='0', type='text', id='pred')
    ]),
])

model_path = "models/logit_games_v1"
model  = mlflow.sklearn.load_model(model_path)

@app.callback(
    Output(component_id='pred', component_property='value'),
    [Input(component_id='g1', component_property='value'),
     Input(component_id='g2', component_property='value')]
)
def update_prediction(game1, game2):

    new_row = { "G1": float(game1), "G2": float(game2), 
                "G3": 0, "G4": 0, 
                "G5": 0, "G6": 0, 
                "G7": 0, "G8": 0, 
                "G9": 0, "G10":0 }

    new_x = pd.DataFrame.from_dict(new_row, orient = "index").transpose()                
    return str(model.predict_proba(new_x)[0][1])    

if __name__ == '__main__':
    app.run_server(host='0.0.0.0', port=8080)
Dash application