COMTE¶
Counterfactual Explanations for Machine Learning on Multivariate Time Series Data(COMTE) developed by Ates et al. [1] builds counterfactuals in multivariate setting by perturbing the features of a time series with the help of an heuristic algorithm. The code in TSInterpret is based on the authors implementation .
[1] Emre Ates, Burak Aksar, Vitus J. Leung, and Ayse K. Coskun. Counterfactual Explanations for Machine Learning on Multivariate Time Series Data. 2021 International Conference on Applied Artificial Intelligence (ICAPAI), pages 1–8, May 2021. arXiv: 2008.10781. URL: http://arxiv.org/abs/2008.10781 (visited on 2022-03-25), doi:10.1109/ICAPAI49758.2021.9462056.
import sklearn
import pickle
import numpy as np
import torch
from ClassificationModels.CNN_T import ResNetBaseline, UCRDataset
import pandas as pd
from tslearn.datasets import UCR_UEA_datasets
import torch
import warnings
warnings.filterwarnings("ignore")
/home/jacqueline/.local/share/virtualenvs/TSInterpret-x4eqnPOt/lib/python3.9/site-packages/tqdm/auto.py:21: TqdmWarning: IProgress not found. Please update jupyter and ipywidgets. See https://ipywidgets.readthedocs.io/en/stable/user_install.html from .autonotebook import tqdm as notebook_tqdm
Load Data¶
Load Data and reshape the data to fit a 1D-Conv ResNet. Note that the input for a 1D-Conv Resnet hat the shape (batch, features, timesteps).
dataset='Epilepsy'
X_train,y_train, X_test, y_test=UCR_UEA_datasets().load_dataset(dataset)
train_x=np.swapaxes(X_train,1,2)#.reshape(-1,X_train.shape[-1],X_train.shape[-2])
test_x=np.swapaxes(X_test,1,2)#.reshape(-1,X_train.shape[-1],X_train.shape[-2])
train_x=np.nan_to_num(train_x)
test_x=np.nan_to_num(test_x)
train_y = y_train
test_y=y_test
enc1=sklearn.preprocessing.OneHotEncoder(sparse=False).fit(train_y.reshape(-1,1))
f=open(f'../../ClassificationModels/models/{dataset}/OneHotEncoder.pkl','wb')
pickle.dump(enc1,f)
train_y=enc1.transform(train_y.reshape(-1,1))
test_y=enc1.transform(test_y.reshape(-1,1))
enc1.categories_
[array(['EPILEPSY', 'RUNNING', 'SAWING', 'WALKING'], dtype='<U8')]
Model Training / Loading¶
Trains a ResNet and saves the results.
n_pred_classes =train_y.shape[1]
train_dataset = UCRDataset(train_x.astype(np.float64),train_y.astype(np.int64))
test_dataset = UCRDataset(test_x.astype(np.float64),test_y.astype(np.int64))
train_loader = torch.utils.data.DataLoader(train_dataset,batch_size=16,shuffle=True)
test_loader = torch.utils.data.DataLoader(test_dataset,batch_size=1,shuffle=False)
model = ResNetBaseline(in_channels= X_train.shape[-1], num_pred_classes=n_pred_classes)
model.load_state_dict(torch.load(f'../../ClassificationModels/models/{dataset}/ResNet'))
model.eval()
ResNetBaseline( (layers): Sequential( (0): ResNetBlock( (layers): Sequential( (0): ConvBlock( (layers): Sequential( (0): Conv1dSamePadding(3, 64, kernel_size=(8,), stride=(1,)) (1): BatchNorm1d(64, eps=1e-05, momentum=0.1, affine=True, track_running_stats=True) (2): ReLU() ) ) (1): ConvBlock( (layers): Sequential( (0): Conv1dSamePadding(64, 64, kernel_size=(5,), stride=(1,)) (1): BatchNorm1d(64, eps=1e-05, momentum=0.1, affine=True, track_running_stats=True) (2): ReLU() ) ) (2): ConvBlock( (layers): Sequential( (0): Conv1dSamePadding(64, 64, kernel_size=(3,), stride=(1,)) (1): BatchNorm1d(64, eps=1e-05, momentum=0.1, affine=True, track_running_stats=True) (2): ReLU() ) ) ) (residual): Sequential( (0): Conv1dSamePadding(3, 64, kernel_size=(1,), stride=(1,)) (1): BatchNorm1d(64, eps=1e-05, momentum=0.1, affine=True, track_running_stats=True) ) ) (1): ResNetBlock( (layers): Sequential( (0): ConvBlock( (layers): Sequential( (0): Conv1dSamePadding(64, 128, kernel_size=(8,), stride=(1,)) (1): BatchNorm1d(128, eps=1e-05, momentum=0.1, affine=True, track_running_stats=True) (2): ReLU() ) ) (1): ConvBlock( (layers): Sequential( (0): Conv1dSamePadding(128, 128, kernel_size=(5,), stride=(1,)) (1): BatchNorm1d(128, eps=1e-05, momentum=0.1, affine=True, track_running_stats=True) (2): ReLU() ) ) (2): ConvBlock( (layers): Sequential( (0): Conv1dSamePadding(128, 128, kernel_size=(3,), stride=(1,)) (1): BatchNorm1d(128, eps=1e-05, momentum=0.1, affine=True, track_running_stats=True) (2): ReLU() ) ) ) (residual): Sequential( (0): Conv1dSamePadding(64, 128, kernel_size=(1,), stride=(1,)) (1): BatchNorm1d(128, eps=1e-05, momentum=0.1, affine=True, track_running_stats=True) ) ) (2): ResNetBlock( (layers): Sequential( (0): ConvBlock( (layers): Sequential( (0): Conv1dSamePadding(128, 128, kernel_size=(8,), stride=(1,)) (1): BatchNorm1d(128, eps=1e-05, momentum=0.1, affine=True, track_running_stats=True) (2): ReLU() ) ) (1): ConvBlock( (layers): Sequential( (0): Conv1dSamePadding(128, 128, kernel_size=(5,), stride=(1,)) (1): BatchNorm1d(128, eps=1e-05, momentum=0.1, affine=True, track_running_stats=True) (2): ReLU() ) ) (2): ConvBlock( (layers): Sequential( (0): Conv1dSamePadding(128, 128, kernel_size=(3,), stride=(1,)) (1): BatchNorm1d(128, eps=1e-05, momentum=0.1, affine=True, track_running_stats=True) (2): ReLU() ) ) ) ) ) (final): Linear(in_features=128, out_features=4, bias=True) )
Interpretability Algorithm¶
Using a interpretability algorithm consists of 4 steps:
- Load the Interpretability Method
- Instaniate the Method with the desired Parameters
- Call the explain Method
- Plot the results
1. & 2. Loading & Initialization¶
COMTE works on all models returning a probability function. The Initialization takes the following arguments:
`model`: The model to be explaines.
`data`: Tuple of Data and Labels.
`backend`: `PYT`, `SK`, or `TF`.
`mode`: second dimension is either `feat` or `time`.
`method`: Optimization Method either `brut` or `opt`.
item=test_x[1].reshape(1,test_x.shape[1],-1)
shape=item.shape
_item= torch.from_numpy(item).float()
model.eval()
y_pred = torch.nn.functional.softmax(model(_item)).detach().numpy()
y_label= np.argmax(y_pred)
y_label
0
enc1.inverse_transform(np.array([[1,0,0,0]]))
array([['EPILEPSY']], dtype='<U8')
from TSInterpret.InterpretabilityModels.counterfactual.COMTECF import COMTECF
exp_model= COMTECF(model,(train_x,train_y),backend='PYT',mode='feat', method= 'brute')
2023-09-18 14:45:51.081533: I tensorflow/core/platform/cpu_feature_guard.cc:182] This TensorFlow binary is optimized to use available CPU instructions in performance-critical operations. To enable the following instructions: AVX2 FMA, in other operations, rebuild TensorFlow with the appropriate compiler flags. 2023-09-18 14:45:51.757656: W tensorflow/compiler/tf2tensorrt/utils/py_utils.cc:38] TF-TRT Warning: Could not find TensorRT
3. Call the explain method.¶
Prepeare the instance and the predicted label of the instance as parameters for the explain methods.
item
: item to be explained
exp = exp_model.explain(item)
array, label=exp
label
array([3])
enc1.inverse_transform(np.array([[0,0,1,0]]))
array([['SAWING']], dtype='<U8')
item.shape
(1, 3, 206)
4. Visualization¶
All plot function take as input the item to be explained and the returned explanation. As as additonal option a figsize can be given.
i=0
org_label=y_label
cf_label=label[0]
exp=array
exp_model.plot_in_one(item[0],'EPILEPSY',exp[0],'SAWING',save_fig='Ates.png')
Additional Examples, e.g. for the use with LSTM or TF can be found here.