Interactive Web Services with Dash
Building interactive web services using Dash.
We'll cover the following
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 dashfrom dash import htmlfrom dash import dccfrom dash.dependencies import Input, Outputimport pandas as pdimport 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 dashfrom dash import htmlfrom dash import dccfrom dash.dependencies import Input, Outputimport pandas as pdimport mlflow.sklearnapp = 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)
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 dashfrom dash import htmlfrom dash import dccfrom dash.dependencies import Input, Outputimport pandas as pdimport mlflow.sklearnimport flaskmodel_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.
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!
- Press the button to execute the codes in the previous lesson and load models.
- Run the
dash_app.py
file using the following commandpython3 dash_app.py
. - 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)