• Berthold

Take action on customer churn: Build & deploy a churn prediction model

Updated: May 25, 2018

In most companies, attracting new customers represents the number one KPI of sales teams to increase revenue. At the same time, it becomes increasingly important to prevent on-boarded customers from turning their back on your business. This so-called customer churn is of particular importance for subscription-based business models, in which the customer lifetime value is growing linearly with time. Running churn retention marketing campaigns also comes at a lower price tag as compared to inbound marketing campaigns targeting new customers.

Hence, understanding the reasons behind churn as well as predict churn risk scores of clients represents very valuable knowledge to a company. Fortunately, this is no rocket science! With the advent of data science and AI one doesn't need much more than customer data and a predictive algorithm. For instance, the time a client spent logged into her account or the number of times he has reached out to a service hotline can serve as a good proxy for churn risk. Training a machine learning algorithm on such customer data, helps to distinguish customers that will likely unsubscribe from a service from customers that are satisfied with the product.

There is a large stack of literature on customer churn and also of recent blog posts

that put it in the context of data science & machine learning. The aim of this article is to provide a practical angle on churn modeling that is setting up a churn prediction model and bringing it to production. In particular the latter point will be of direct practical use, where the deployed model can easily integrate into an existing IT infrastructure and provide churn risk scores via its REST interface.

In short, if you are looking for a data driven & goal oriented way to address customer churn in your company, the following content will provide you with good guidance.

Setting up the churn prediction model and bringing it to production essentially consists of these three steps,

  • Train & validate a prediction model on the user data

  • Creating a model API

  • Dockerize your application and requesting predictions

which we will go trough in the following one by one.

Train and validate a churn prediction model

We use the publicly available telecommunication data set to build a Python based demonstrator model for churn prediction. The data set contains 3333 lines of user data of a telecommunication company with 20 different categories such as the amount of minutes called per day, number of calls to the service center or the state the cutomer is living in. In addition, the data are labeled with an indicator whether each user has churned or not.

As prediction algorithm we here use the gradient boosting classifier (GBM) from the sklearn package. GBM is a competitive, robust out of the box classifier, which usually does well already without parameter tuning.

Most of the code snippets in the following should be self-explanatory, but we try to provide as much background information and instructions as necessary.

As a common first step, we need load all relevant packages into our environment, which are

  • pandas for data handling

  • numpy for math stuff

  • sklearn for modeling and related functions

  • matplotlib for plotting what you have achieved

  • dill for saving what you have achieved

At this point, maybe 99 out of 100 data scientists including myself would immediately start loading in the data and playing around with it. This is, of course, a great way to go when it is about exploring the data and its structure but not necessarily when it is about building a model for production. First, this exploratory approach often results in spaghetti code, which is hard to keep track of for a deployment. Second, you'll likely send raw data from a data base to your prediction API. To generate a prediction those data have to go through the same cleaning steps as the data you used yo build your model on.

The solution to this problem is to work with pipeline instances that include all data cleaning, data processing steps and the trained model instances. The nice part here is that you can easily save your pipeline to a file and include it into a prediction API at a later point, without having to stitch together lines of spaghetti code. Therefore, it is certainly worthwhile to invest in setting up pipeline objects, even though it may sometimes look like an overkill. For this purpose, you can either use the pipeline provided by sklearn, or construct your own pipeline class yourself. We usually do the latter. As you will see, it doesn't take too many lines and you get exactly what you want.

Most of the class methods is self-explanatory. A note to the cleaning method, we here get rid of columns that do not have predictive power to predict churn risks, such as the customer's phone number or account ID. In addition, we extract our target variable that indicates whether a user has churned or not.

Having the pipeline class defined, it is now an easy step to load, clean & process the data as well as to train & evaluate our GBM model. For training our model, we now load the data from a local .csv file but one can easy imagine how to connect to a data base etc.. Besides, we do a train test split using the corresponding sklearn function, which means we separate our data set into two parts: One part to train the model and one to test its performance. The idea behind that step is to make sure that our model does not overfit to the training data and performs poorly on new unseen data. In other words, if our model does well on the test set, we can be confident that it will do well on new data too. But have a look yourself:

Our plug and play GBM model already reaches an excellent accuracy of 96.7 % on the test set, given we haven't performed any parameter tuning or cross-validation folds. We won't do it for this demonstrator model now, but in a real use case scenario a good option were to use GridSearchCV from the scikit-learn library for this purpose. For the sake of completeness, we could, of course, use a different algorithm to set up our churn prediction model. Given the input data shape of 3333x20, random forests or xgbm will certainly do a good job as well. Neural nets likely perform worse on this data set, but could do very well on this problem when there is a lot of input at a available.

Having trained the churn prediction model, we need to save the whole pipeline object to a file that we can later load into our API for making predictions. The dill / pickle module gives us a helping hand here. Again, we want to point out the strength of the object oriented approach via defining a customized pipeline class. We just need to load the pipeline object into our API and can serve predictions with only few lines of code. Without this approach, we would need to recode the entire cleaning & processing steps in the API framework.

Creating a model API

We are now in good shape for the second part of this tutorial: setting up an API that we can send input data to and get back the prediction from our trained model. For this purpose, we will be using the flask environment in python. In general, the working principle of an API is relatively simple in the way that it is not doing much more than routing requests from a user to a specific function defined inside its body called endpoint. But let us take one step after another.

After creating a Flask instance, we can directly load our model by using the dill module. It should be noted that we have to load the class definition of our pipeline class, otherwise python won't be able to reconstruct it when running the API. Hence, you want to save the class definition from above into a separate file that we call classdef .py and load it with the other packages into the API file.

Next we define an API endpoint, which we will be sending requests to via and URL such as "http://[URL]:[PORT]/[ENDPOINT]". For this purpose we use the @app.route command and define the endpoint as /predict. This means that any request "http://[URL]:[PORT]/predict" will be routed to the function body below. This function body itself is relatively lightweight, thanks to our pipeline class we have defined. We basically just retrieve data in JSON format sent via the request, convert it into a data frame and run our cleaning and prediction methods on it (in principle, the data can be sent in any format but the code shown in this demonstrator is specific to JSON). That's it!

With the output we get a little fancy in the way that we do not want a binary output {churn/no churn} but a churn risk score: Let us say we work for the marketing department of a company that wants to run a churn retention campaign by handing out vouchers to customers with a high churn risk score. We can obtain the churn risk score, i.e. the probability for a user to churn, from the GBM model output via our pipeline class. As such a campaign will be very expensive, only users with a very high churn risk are receiving these vouchers. Hence, we will be setting the API output as a response to a request to High churn risk in case the churn probability is equal to or grater than 90 %. Otherwise the output will be Low churn risk.

Last but not least we tell our app to run on a local host and expose port 80. After that we are done with that part and save this entire code to a file called server .py.

Dockerize your application and request predictions

This has already been quite a lot of code and content to digest, but we are almost done! The last piece missing is to wrap our API up in a format that we can deploy and send requests too. A very elegant and, in fact also convenient way to achieve this is to use docker images. You can understand docker images as a lightweight version of a run time with all relevant dependencies stored into one file. This file can be pushed on a server or into a cloud and executed as an independent container. For instance, AWS and Google cloud accept such images.

Put into the context of our churn prediction API, we can store a run time of python together with our python files and all relevant dependencies into one docker image. As soon as the docker image is executed let's say on google cloud, we can send requests in URL format to the endpoint and receive the predictions as the response. In the following, we demonstrate how to create a docker image containing our API with all relevant files, how to execute it locally as well as how to send requests to and receive responses from our API.

To create (and locally run) docker images you need to install docker and pip first (Make sure you are getting pip3). Once done with that step, it takes few lines of code to creat the docker image: First, we create a Dockerfile to let docker know which runtime, dependencies and files should be part of our docker image. This file is simply stored as Dockerfile without suffix. We simply add the entire folder, which should contain the Dockerfile, churn_model.pk, server. py and classdef. py. We tell docker, which port to expose to outside world once the image is executed and the container is up and running. Lastly, we tell docker to execute our API file by using python.

Now we are literally two steps away from having our churn model deployed. The second last step is to tell docker to create a docker image called churn by running

docker build -t churn .

in your terminal. The last step is run the docker image locally by executing docker run -p 4000:80 churn in your terminal. 4000:80 here maps port 4000 on the local host to port 80 of the running docker container.

But one thing is still missing, the gratification for all the hard work you have done: Requesting actual predictions from our API and see it all working nicely.

That is in fact the easiest of all steps. We basically have to define the content of our URL request and feed it with data to be used for making the prediction. For this purpose, we load the dataset again and pick an individual row as sample data. We construct the URL request by using the correct port 4000, the correct endpoint /predict and the sample data in JSON format as content. Of course, we could also send the data in different format such as html or even as a file.

As you can see in the code snippet below, requesting a prediction from our API gives us the expected output, which, for this specific customer, is a....High churn risk. Ouch. We better get our retention campaign going!

And voilá that's really it. After going through this tutorial, you now know how to train a churn prediction model using a gbm classification algorithm, create an API for requesting predictions via its REST interface, wrap it all up for deployment by using docker images and request churn risk scores from the API.

We hope you enjoyed reading this tutorial. All code and relevant files can be found on our GitHub repository. If you have any questions or criticism, please let us know and comment on our post.

© 2017-2018 by DataQuotient GmbH. All rights reserved.