A Guide To Interpretable Machine Learning— Part 1 | by Abhijit Roy | Towards Data Science

Machine learning models are becoming popular at a huge rate currently. They are now being used to solve various problems in various fields. At this point of increasing popularity and consequently, importance in our daily lives, there is a silent hazard, that is also very important to take care of now: the interpretability of the machine learning model.
In recent times, we have seen how image processing is used in security sectors and health sectors, regression, and classification models are used in stock markets and investment sectors. If we observe closely, we are depending on machine learning to make essential decisions that will affect us for sure. So, the question arises, why should we trust models?
In other words, why should we depend on a decision taken by an intelligence algorithm without even knowing how it operate? Well, you can say that there are various algorithms like decision trees and linear regression which are very easy to understand and are interpretable. But, in the current world, researchers and data scientists are constantly trying to solve challenging problems, which can’t be solved by interpretable models efficiently and thus, need very complicated algorithms or customized neural network-based ones. If we look around us, we will find that most instances, use complex and black box models and it is very hard for us to answer the question “How is the algorithm doing it?”
It proves to be a valid question. Often while designing a model, data scientists focus on a few questions or a few regulations on some features, that they expect the model to learn and use for prediction. For example, I may feed the population to predict, “Whether to set up a shop in the region or not”.The reason being that I may think that if the population is high my sales will be high, so there will be a very high correlation between these two, so my model will learn this feature and the feature’s importance will be high. I train my model, it gives me high accuracy but it may turn out that it didn’t learn the way it was planned to learn. So, we need to be sure about these things in several cases to increase the accuracy and be sure of our predictions.
I will illustrate a classic example, in an image classification of a “Siberian Husky” and a “Wolf”, a model was designed. It gave very high accuracy. When the model was analyzed, it was discovered that the model never classified the husky and the wolf. It classified “Snow” or “No Snow”. The snow was predicted as husky and no snow as a wolf. Most husky images had snow the accuracy was very good.
So, from the above examples we can understand, how important is it to actually interpret our models.
Numerous methods to deal with the interpretation of machine learning models have evolved over the years. Different methods use different logic and theories to interpret the hard to understand “black box” algorithms like neural networks, Xgboost, and random forests. Two of the most important and popular methods are LIME and SHAP. They are used to interpret local or observation-wise predictions. In other words, they give us a clear intuition of how a given feature affects a prediction in our model and how important is that feature for our model.
We will talk about LIME in detail and briefly on SHAP in this article. We will try to explain using the famous Titanic Dataset available here.
The sinking of the Titanic is one of the most infamous shipwrecks in history.
On April 15, 1912, during her maiden voyage, the widely considered “unsinkable” RMS Titanic sank after colliding with an iceberg. Unfortunately, there weren’t enough lifeboats for everyone on board, resulting in the death of 1502 out of 2224 passengers and crew. While there was some element of luck involved in surviving, it seems some groups of people were more likely to survive than others.
In this challenge, we ask you to build a predictive model that answers the question: “what sorts of people were more likely to survive?” using passenger data (ie name, age, gender, socio-economic class, etc).
Now lets first talk about the dataset a bit and then move to our objective.
The dataset looks like this, it has 12 columns. “Survived” is our target column and others are feature columns. Now, the feature columns will need some preprocessing.
Dataset extraction snippet:
import pandas as pddf1=pd.read_csv('train.csv')df2=pd.read_csv('test.csv')df=pd.concat([df1,df2],axis=0)
We will drop PassengerID as it won’t have any correlation. Next, we will take the “Name” column, and based on their title will assign them, class, like “Mrs”, will be class 1, “Mr” class 2, and so on. We can do this using the following snippet.
i=0name=[]while i<len(df):    #print(df.iloc[i]['Name'].type)        if "Mrs." in df.iloc[i]['Name']:        name.append(str(1))        print("a")    elif 'Mr.' in df.iloc[i]['Name']:        name.append(str(2))        print("b")    elif "Miss." in df.iloc[i]['Name']:        name.append(str(3))        print("c")    elif "Master." in df.iloc[i]["Name"]:        name.append(str(4))        print("d")    else:        name.append(str(5))    i+=1df=df.drop(['Name'],axis=1)df['name']=name
Now, we go for the SibSp, here also we will create a feature having two classes, having siblings denoted by one and not having siblings denoted by 0, and include it in the feature set dropping the original feature. One thing we need to take care of is most of these features have NaN’s so we need to fill the NULL values first to avoid errors. Code snippet:
i=0siblings=[]df.fillna(0)while i<len(df):    if df.iloc[i]['SibSp']>0:        siblings.append(str(1))    else:        siblings.append(str(0))            i+=1df=df.drop(["SibSp"],axis=1)df["Siblings"]=siblings
Next, we do the same for the Parch feature, we design two classes, having parent represented by 1 and not having parents represented by 0 and use the column as a feature.
i=0parent=[]df.fillna(0)while i<len(df):    if df.iloc[i]['Parch']>0:        parent.append(str(1))    else:        parent.append(str(0))            i+=1df=df.drop(["Parch"],axis=1)df["Parent"]=parent
We go for the “age” feature, there are also a few NaNs which we will fill using the mean value of the age column.
df['Age'].fillna(df['Age'].mean())
We will then create a new feature column having 5 classes to classify and remove the variable nature of the age. I have used the given class boundaries:
Age<15: Class 1
15≤Age<30: Class 2
30≤Age<45: Class 3
45≤Age<60: Class 4
Age≥60: Class 5
“Age” below 15 is classified as 1 and so on.
Code:
i=0age=[]while i<len(df):    if df.iloc[i]['Age']<15:        age.append(str(1))    elif df.iloc[i]['Age']<30:        age.append(str(2))    elif df.iloc[i]['Age']<45:        age.append(str(3))    elif df.iloc[i]['Age']<60:        age.append(str(4))    else:        age.append(str(5))    i+=1df['age']=agedf=df.drop(['Age'],axis=1)
For the “Fare” feature, we will do the same as the “age” feature. We will fill NaN with mean and classify them into some fixed buckets. Here are my estimations or bucket boundaries.
Fare<15: Class 1
15≤Fare<55: Class 2
55≤Fare<120: Class 3
120≤Fare<190: Class 4
Fare≥190: Class 5
Next, if we check the “Tickets” column, we will see a few special tickets with letters, and others are numeric. I have labeled the completely numeric ones 1 else 0. Snippet:
i=0ticket=[]while i<len(df):    if df.iloc[i]['Ticket'].isnumeric():        ticket.append(str(1))    else:        ticket.append(str(0))    i+=1df['ticket']=ticketdf=df.drop(['Ticket'],axis=1)
Next, we do something similar to the cabin feature. Most of the entries in the “Cabin” feature are empty. We label the empty ones as 0 and others as 1.
z=0df['Cabin'].fillna("NA")cabin=[]while z<len(df):    print(df.iloc[z]['Cabin'])    if 'NA' in str(df.iloc[z]['Cabin']):        cabin.append(str(0))    else:        cabin.append(str(1))    z+=1
Now, our preprocessing is complete. After the preprocessing our data looks like this.
We will now drop the Survived column and apply encoding on the categorical features. After application, we will obtain a Feature set X, of 31 columns i.e 31 features and a target set Y, of 1 column, Survived.
Index(['Survived', 'Pclass_1', 'Pclass_2', 'Pclass_3', 'Sex_female',       'Sex_male', 'Embarked_C', 'Embarked_Q', 'Embarked_S', 'name_1',       'name_2', 'name_3', 'name_4', 'name_5', 'Siblings_0', 'Siblings_1',       'Parent_0', 'Parent_1', 'age_1', 'age_2', 'age_3', 'age_4', 'age_5',       'fare_1', 'fare_2', 'fare_3', 'fare_4', 'fare_5', 'ticket_0',       'ticket_1', 'cabin_0', 'cabin_1'],      dtype='object')
These are our 31 features.
We split our dataset in X_train, Y_train, X_test, and Y_test.
from sklearn.model_selection import train_test_splitX_train,X_test,Y_train,Y_test=train_test_split(X,Y,test_size=0.3, random_state=42)
Thus we have processed our dataset and are ready for operations.
We go back to our application.
LIME stands for Linear Model Agnostic Explanation. It is used for local interpretation. It is used to analyze and interpret the underlying Black box model’s decision and its prediction based on a particular observation, say a particular row of the training or testing dataset. Now, the question arises how does it do that? As we have noticed earlier, there are several models that are very easy to interpret. LIME uses these interpretable models to predict the black box models.
LIME uses models like Decision Tree and Logistic Regression to create a clone of the predictions given by the Black box model. It takes in a single observation and creates a set of observations by changing the feature values by some amount. Thus, after trying all combinations, LIME obtains a totally new feature set. This feature set is then passed to the black-box model and the corresponding predictions are obtained. Now, these predictions become the Y_training or target set and the Feature set becomes X_training set for the interpretable models.
Now, LIME has several models like Decision Trees, Linear Regression, Lasso Regression, and Logistic regression. The target interpretable model is the one that produces minimum Loss among all the interpretable models. This is due to the fact, that minimum loss function implies the model that had maximum accuracy that is the model that cloned the black-box model the best. Now as the interpretable model behaves like the Black-box model, interpreting it will give the results for the Black box model.
This is the interpreting formula of LIME. Here the explanation of the observation x is shown by the one of the models f, or g, depending upon which will produce the minimum loss function and the sigma(g) depicts the complexity of the interpreting model kept as low as possible. Now the variation of data produced by LIME also gives the interpreting model liberty to get the boundaries of a feature, like if the value of a feature is less than the value x it is class A else it is class B.
There are a few disadvantages also. The problem with LIME is that it is a local algorithm, so its explanation is not true for the entire dataset.
Some of the best interpreting Algorithms are Decision Trees and Logistic regression. We will talk about these two algorithms here.
Decision Trees: It is used for non-linear datasets. Now, here the tree takes in a dataset and keeps on splitting the datasets based on the boundary values of different features. It splits the dataset multiple times and creates several data subsets. The final subsets are the leaf nodes. They are classified classes. The internal nodes are the splitting nodes. Training helps the tree to judge the boundary values of every feature and their impact on the decision path.
So, each node represents a feature and a value corresponding to that feature that acts as a boundary. Now, every Decision tree wants to minimize the Gini Coefficient. The Gini Coefficient is the difference between the actual value and the predicted value. So, after each node, if we try to judge how much the coefficient decreases compared to the parent, we will gain a clear insight into the weight of the node. The weight of the node depicts the feature’s importance.
Logistic Regression: It is a classification based on a linear regression algorithm. This does not work for Non-linear models. In linear regression, we use an equation to represent an n-dimensional hyperplane to predict the values.
Y=b0+b1x1+b2x2+..........+bnxn
Here b0 is the intercept and the b{i} for a feature ‘i’ is the coefficient or the weight of the feature in the regression. It produces interpolation values using linear regression.
In classification, we predict probabilities so we squeeze the value between 0 and 1 using a logistic function.
which looks like this,
So, we obtain probability as
Now, we deal with the ratio of the probability of an event happening and the probability of the event, not happening. P(y=1)/P(y=0). This ratio is called the odds.
This is our equation. Now here p(y=1)/p(y=0) =n means that the chance of the event occurring is n times then the chance of the event not occurring.
Now if we increase the value of the feature j (xj) by 1, then the equation becomes,
which results in,
So, if we increase a feature by 1 unit its effect on the odds will be exponential of its weight. So, its contribution is a function of its weight. So we can easily get the feature contributions using the weights in logistic regression.
In the next portion, we will see applications and practical implementations of interpretable models. We will also see the SHAP application.