library(iml)
library(ranger) # different random Forest package!
library(EcoData)
library(cito)
set.seed(123)
load("sdm.RData")
X = scale(sdm_env$train[,2:20])
Y = Y_train[,4] # we analyze the fourth species
df = data.frame(Presence = Y, X)8 Explainable AI
The goal of explainable AI (xAI, aka interpretable machine learning) is to explain why a fitted machine learning model makes certain predictions. A typical example is to understand how important different variables are for predictions. The incentives for doing so range from a better technical understanding of the models, through understanding which data is important for improving predictions, to questions of fairness and discrimination (e.g. to understand if an algorithm uses skin color to make a decision).
8.1 A Practical Example
We will fit a random forest and use the iml package for xAI, see https://christophm.github.io/interpretable-ml-book/.
Meaning of the bioclim variables:
| Bioclim variable | Meaning |
|---|---|
| bio1 | Annual Mean Temperature |
| bio2 | Mean Diurnal Range (Mean of monthly (max temp - min temp)) |
| bio3 | Isothermality (BIO2/BIO7) (×100) |
| bio4 | Temperature Seasonality (standard deviation ×100) |
| bio5 | Max Temperature of Warmest Month |
| bio6 | Min Temperature of Coldest Month |
| bio7 | Temperature Annual Range (BIO5-BIO6) |
| bio8 | Mean Temperature of Wettest Quarter |
| bio9 | Mean Temperature of Driest Quarter |
| bio10 | Mean Temperature of Warmest Quarter |
| bio11 | Mean Temperature of Coldest Quarter |
| bio12 | Annual Precipitation |
| bio13 | Precipitation of Wettest Month |
| bio14 | Precipitation of Driest Month |
| bio15 | Precipitation Seasonality (Coefficient of Variation) |
| bio16 | Precipitation of Wettest Quarter |
| bio17 | Precipitation of Driest Quarter |
| bio18 | Precipitation of Warmest Quarter |
| bio19 | Precipitation of Coldest Quarter |
rf = ranger(as.factor(Presence) ~ ., data = df, probability = TRUE)The cito package has quite extensive xAI functionalities. However, ranger, like most other machine learning packages, has no extensive xAI functionalities. Thus, to do xAI with ranger, we have to use a generic xAI package that can handle almost all machine learning models.
When we want to use such a generic package, we first have to create a predictor object that holds the model and the data. The iml package uses R6 classes, which means new objects can be created by calling Predictor$new(). (Do not worry if you do not know what R6 classes are, just use the command.)
To make the xAI tools available to many different packages/algorithms, the iml package expects the ML-algorithm-specific predict method to be wrapped in a generic predict function in the form of function(model, newdata) predict(model, newdata), and the function wrapper should return a vector of predictions:
predict_wrapper = function(model, newdata) predict(model, data=newdata)$predictions[,2]
predictor = Predictor$new(rf, data = df[,-1], y = df[,1], predict.function = predict_wrapper)
predictor$task = "classif" # set task to classification
# "Predictor" is an object generator.8.2 Feature/Permutation Importance
Feature importance should not be confused with random forest variable importance, although they are related. It tells us how important each variable is for prediction, can be computed for all machine learning models, and is based on a permutation approach (see the book):
imp = FeatureImp$new(predictor, loss = "ce")
plot(imp)bio9 (Mean Temperature of Driest Quarter) is the most important variable.
8.3 Partial Dependencies
Partial dependencies are similar to allEffects plots for normal regressions. The idea is to visualize “marginal effects” of predictors (with the “feature” argument we specify the variable we want to visualize):
eff = FeatureEffect$new(predictor, feature = "Bio14", method = "pdp",
grid.size = 10)
plot(eff)One disadvantage of partial dependencies is that they are sensitive to correlated predictors. Accumulated local effects can be used to account for correlation between predictors.
8.4 Accumulated Local Effects
Accumulated local effects (ALE) are basically partial dependence plots but try to correct for correlations between predictors.
ale = FeatureEffect$new(predictor, feature = "Bio14", method = "ale")
ale$plot()If there is no collinearity, you shouldn’t see much difference between partial dependencies and ALE plots.
8.5 Friedman’s H-statistic
The H-statistic can be used to find interactions between predictors. However, again, keep in mind that the H-statistic is sensitive to correlation between predictors:
interact = Interaction$new(predictor, "Bio14",grid.size = 5L)
plot(interact)8.6 Global Explainer - Simplifying the Machine Learning Model
Another idea is to simplify the machine learning model with a simpler model such as a decision tree. We create predictions with the machine learning model for a lot of different input values and then we fit a decision tree on these predictions. We can then interpret the simpler model.
library(partykit)
tree = TreeSurrogate$new(predictor, maxdepth = 2)
plot(tree$tree)8.7 Local Explainer - LIME Explaining Single Instances (observations)
The global approach is to simplify the entire machine-learning black-box model via a simpler model, which is then interpretable.
However, sometimes we are only interested in understanding how single predictions are generated. The LIME (Local Interpretable Model-agnostic Explanations) approach explores the feature space around one observation and, based on this, fits a simpler model locally (e.g. a linear model):
lime.explain = LocalModel$new(predictor, x.interest = df[1,-1])
lime.explain$results
plot(lime.explain)8.8 Local Explainer - Shapley
The Shapley method computes the so-called Shapley value, feature contributions for single predictions, and is based on an approach from cooperative game theory. The idea is that each feature value of the instance is a “player” in a game, where the prediction is the reward. The Shapley value tells us how to fairly distribute the reward among the features.
shapley = Shapley$new(predictor, x.interest = df[1,-1])
shapley$plot()8.9 Uncertainties - the bootstrap
Standard xAI methods do not provide reliable uncertainties on the fitted curves. If you want uncertainties or p-values, the most common method is the bootstrap.
In a bootstrap, instead of splitting up the data into test / validation, we sample from the data with replacement and fit the models repeatedly. The idea is to get an estimate of the variability we would expect if we created another dataset of the same size.
k = 10 # bootstrap samples
n = nrow(df)
error = rep(NA, k)
for(i in 1:k){
bootSample = sample.int(n, n, replace = TRUE)
rf = ranger(as.factor(Presence) ~ ., data = df[bootSample,], probability = TRUE)
error[i] = rf$prediction.error
}
hist(error, main = "uncertainty of in-sample error")Note that the distinction between bootstrap and validation / cross-validation is as follows:
- Validation / cross-validation estimates out-of-sample predictive error
- Bootstrap estimates uncertainty / confidence interval on all model outputs (could be prediction and inference).
8.10 Exercises
8.10.0.1 xAI in cito
Dataset can be downloaded from the submission server or from dropbox
Data preparation
library(iml)
library(cito)
library(EcoData)
load("sdm.RData")
X = scale(sdm_env$train[,2:20])
Y = Y_train[,4] # we analyze the fourth species
df = data.frame(Presence = as.factor(Y), X)
# we will subsample data (absences) to reduce runtime
data_sub = df[sample.int(nrow(df), 500),]cito includes several xAI methods directly out of the box
model = dnn(Presence~., data = data_sub, batchsize = 200L,loss = "binomial", verbose = FALSE, lr = 0.15, epochs = 300)Try the following commands:
summary(model, n_permute = 10)PDP(model)ALE(model)
Moreover, try to refit the model with the option bootstrap = 5. This may take a short while. Observe how the xAI outputs change.
Also, try out other species!
model = dnn(Presence~., data = data_sub, batchsize = 200L, bootstrap = 5L, loss = "binomial", verbose = FALSE, lr = 0.15, epochs = 300)summary(model, n_permute = 10L)PDP(model)
ALE(model)8.10.0.2 Interpret your models
Use the iml package to interpret the models you built for the challenges, and calculate:
- Feature importance
- Accumulated local effect plots
- Partial dependency plots
Here is a predict_wrapper for the ranger package
predict_wrapper = function(model, newdata) predict(model, data=newdata)$predictions[,2]