Building a Text Classifier using Python and Docker — Part 3: Building a Web-Service using Docker and Flask

ML-Unleashed
4 min readAug 10, 2016

--

This is part three of this series about reusing a pre-built text classifier and exposing it as an HTTP web service. If you haven’t read Part 1 and Part 2, you can catch up there or directly look at the source code on Github.

Picking up from where we left in part two, we create a new file called service.py and fill it up with the necessary code to provide an HTTP web service for text classification:

Let’s start with some imports:

from flask import Flask, request, jsonify

Next, we’ll instantiate a helper class, which will provide us with the functions necessary to start up the web service using Flask:

app = Flask(__name__)

Then we load the serialized classification model from Part 2 back into our runtime environment. We also copy our little helper function to make predictions from the model. Remember, that scikit-learn works on integers indices for the class (that’s why we need the targets vector which map it to a meaningful string) and that prediction are made in batches. We only need one prediction at a time. Our helper function shortens the necessary code to a simple call of “predict”.

from sklearn.externals import joblib
pipeline = joblib.load(‘models/pipeline.pickle’)
targets = joblib.load(‘models/targets.pickle’)
def predict(text):
idx = pipeline.predict([text])[0]
return targets[idx]

For the final and last part, we expose the endpoint as ‘/’ with a parameter called ‘text’, which expects a textual representation of what a user might try to classify:

@app.route(‘/’)
def index():
text = request.args.get(‘text’)
result = {‘prediction’ : predict(text)}
return jsonify(result)

We then save everything and pack up the service using the following Dockerfile (copy and paste the following snippet):

FROM jupyter/datascience-notebookRUN pip install flaskCOPY models/* /home/jovyan/work/models/
COPY service.py /home/jovyan/work
WORKDIR /home/jovyan/work
ENV FLASK_APP=service.py
ENV FLASK_DEBUG=0
CMD [“flask”, “run”, “ — host=0.0.0.0”]

In order to build the image, we execute the Docker build command (remember to replace <your_docker_id>)…

docker build -t <your_docker_id>/text-classifier-service .

…followed by a docker run to finally start the service in a Docker container:

docker run -it --rm -p 5000:5000 <your_docker_id>/text-classifier-service* Serving Flask app "service"
* Running on http://0.0.0.0:5000/ (Press CTRL+C to quit)

Head over the following URL:

http://<IP>:5000/?text=Windows%20is%20a%20operating%20system

{
"prediction": "comp.os.ms-windows.misc"
}

Where <IP> denotes the IP address of the server running the Docker Engine and the text to be classified as a named parameter called ‘text’.

You should now see the following output in your browser:

{
"prediction": "comp.os.ms-windows.misc"
}

Wrap-up

That’s it. We have successfully built a text-classifier and exposed it over an HTTP interface. As our service is completely stateless, we can easily run multiple instances of our image on a distributed Docker platform of your choice. Popular choices include:

  • Docker Engine deployed on a cloud instance (AWS/DigitalOcean)
  • Docker Cloud
  • Docker Swarm
  • Google Container Engine (GCE)
  • Red Hat Open Shift
  • AWS Elastic Beanstalk

For the sake of brevity we omitted several concerns regarding a production-ready deployment, here are some pointers regard those directions:

  • Continuous Integration
    One option is to add a CI-System (e.g. Jenkins) to our codebase. Then it’s possible to build the images, deploy it on a test or integration system and run scripted integrations tests against the system.
  • Image Size vs. Runtime Consistency
    Both our development environment and the web service run using the same base Docker image (jupyter/datascience-notebook). This gives us confidence, that our model will behave the same during development and production. But this comes at a cost. Our web service is packed with a lot of libraries and tools which are not required to run it. Best practices suggest, that we reduce production images to the barest minimum. We could write another Dockerfile that strips down all dependencies to a bare minimum (and in turn, would also reduce the size of our Docker image). Here we trade ease of development for image size.
  • URL length limitations
    Although HTTP does not have a limitation regarding the size of the URL (which is directly related to the text we send to our classifier) a practical limit is usually around 2000 characters, depending on the webserver implementation (for a deeper discussion on this topic see here). Therefore we should rewrite our service to make use of HTTP-POST requests instead of GET-requests.

References

Still curious?

Hi, I’m Matt. I’m a software engineer at heart with a strong passion and interest in machine learning and data science.

I used to be ranked in the top 100 data scientists on Kaggle.com, the world’s largest data science community. I have a great interest and pleasure in building intelligent software solutions.

If you have an interesting machine learning problem to solve, feel free to contact me.

Visit my Kaggle profile at
https://www.kaggle.com/mattvonrohr

Visit my Linked profile at
https://www.linkedin.com/in/mattvonrohr

--

--

ML-Unleashed

The Pragmatic Data Scientist — Software Engineer at heart, Machine Learning Engineer by passion.