Disease detection and classification in temporal lobe epilepsy: step-wise versus simultaneous AI decision models in a multisite neuroimaging study.
The 4 matches
- [1] § Materials and methods › Artificial neural network › Model training and execution ↔ tutorials/Image_and_Text_Classification_LIME.ipynb, lines 400–451 · score 0.74 · cross entropy loss, Adam optimizer, trained models, epoch, classification, validation
- [2] § Materials and methods › Artificial neural network › Model training and execution ↔ tutorials/Titanic_Basic_Interpret.ipynb, lines 123–151 · score 0.58 · cross entropy loss, Adam, epoch, optimizer, trained
- [3] § Materials and methods › Artificial neural network › Architecture ↔ captum/attr/_core/layer/grad_cam.py, lines 26–58 · score 0.55 · convolutional neural network, convolution layer, channels, activation, dimensional
- [4] § Materials and methods › Artificial neural network › Architecture ↔ captum/attr/_core/guided_grad_cam.py, lines 23–53 · score 0.51 · convolutional neural network, convolution layer, dimensional
Paper
Loaded from Europe PMC by your browser, not stored by OSCR: doi.org · Europe PMC
The paper is loaded when this pane is shown.
The authors' code
Jupyter notebook · 550 lines · 28 KB · BSD-3-Clause · 1 match
- # %% [markdown]
- # # LIME to Inspect Image & Text Classification
- # %% [markdown]
- # This tutorial focuses on showing how to use Captum's implementation of Local Interpretable Model-agnostic Explanations (LIME) to understand neural models. The following content is divided into an image classification section to present our high-level interface `Lime` class and a text classification section for the more customizable low-level interface `LimeBase`.
- # %%
- import torch
- import torch.nn.functional as F
- from captum.attr import visualization as viz
- from captum.attr import Lime, LimeBase
- from captum._utils.models.linear_model import SkLearnLinearRegression, SkLearnLasso
- import os
- import json
- # %% [markdown]
- # ## 1. Image Classification
- # %% [markdown]
- # In this section, we will learn applying Lime to analyze a Resnet trained on ImageNet-1k. For testing data, we use samples from PASCAL VOC 2012 since its segmentation masks can directly serve as semantic "super-pixels" for images.
- # %%
- from torchvision.models import resnet18
- from torchvision.datasets import VOCSegmentation
- import torchvision.transforms as T
- from captum.attr._core.lime import get_exp_kernel_similarity_function
- from PIL import Image
- import matplotlib.pyplot as plt
- # %% [markdown]
- # ### 1.1 Load the model and dataset
- # %% [markdown]
- # We can directly load the pretrained Resnet from torchvision and set it to evaluation mode as our target image classifier to inspect.
- # %%
- resnet = resnet18(pretrained=True)
- resnet = resnet.eval()
- # %% [markdown]
- # This model predicts ImageNet-1k labels for given sample images. To better present the results, we also load the mapping of label index and text.
- # %%
- !wget -P $HOME/.torch/models https://s3.amazonaws.com/deep-learning-models/image-models/imagenet_class_index.json
- # %%
- labels_path = os.getenv('HOME') + '/.torch/models/imagenet_class_index.json'
- with open(labels_path) as json_data:
- idx_to_labels = {idx: label for idx, [_, label] in json.load(json_data).items()}
- # %% [markdown]
- # As mentioned before, we will use PASCAL VOC 2012 as the test data, which is available in torchvision as well. We will load it with `torchvision` transforms which convert both the images and targets, i.e., segmentation masks, to tensors.
- # %%
- voc_ds = VOCSegmentation(
- './VOC',
- year='2012',
- image_set='train',
- download=False,
- transform=T.Compose([
- T.ToTensor(),
- T.Normalize(
- mean=[0.485, 0.456, 0.406],
- std=[0.229, 0.224, 0.225]
- )
- ]),
- target_transform=T.Lambda(
- lambda p: torch.tensor(p.getdata()).view(1, p.size[1], p.size[0])
- )
- )
- # %% [markdown]
- # This dataset provides an additional segmentation mask along with every image. Compared with inspecting each pixel, the segments (or "super-pixels") are semantically more intuitive for humans to perceive. We will discuss more in section 1.3.
- #
- # Let's pick one example to see how the image and corresponding mask look like. Here we choose an image with more than one segment besides the background, so that we can compare each segment's impact on the classification.
- # %%
- sample_idx = 439
- def show_image(ind):
- fig, ax = plt.subplots(1, 2, figsize=[6.4 * 2, 4.8])
- for i, (name, source) in enumerate(zip(['Image', 'Mask'], [voc_ds.images, voc_ds.masks])):
- ax[i].imshow(Image.open(source[ind]));
- ax[i].set_title(f"{name} {ind}")
- ax[i].axis('off')
- show_image(sample_idx)
- # %% [markdown]
- # ### 1.2 Baseline classification
- # %% [markdown]
- # We can check how well our model works with the above example. The original Resnet only gives the logits of labels, so we will add a softmax layer to normalize them into probabilities.
- # %%
- img, seg_mask = voc_ds[sample_idx] # tensors of shape (channel, hight, width)
- outputs = resnet(img.unsqueeze(0))
- output_probs = F.softmax(outputs, dim=1).squeeze(0)
- # %% [markdown]
- # Then we present the top 5 predicted labels to verify the result.
- # %%
- def print_result(probs, topk=1):
- probs, label_indices = torch.topk(probs, topk)
- probs = probs.tolist()
- label_indices = label_indices.tolist()
- for prob, idx in zip(probs, label_indices):
- label = idx_to_labels[str(idx)]
- print(f'{label} ({idx}):', round(prob, 4))
- print_result(output_probs, topk=5)
- # %% [markdown]
- # As we can see, the result is pretty reasonable.
- # %% [markdown]
- # ## 1.3 Inspect the model prediction with Lime
- # %% [markdown]
- # In this section, we will bring in LIME from Captum to analyze how the Resnet made the above prediction based on the sample image.
- #
- # Like many other Captum algorithms, Lime also supports analyzing a number of input features together as a group. This is very useful when dealing with images, where each color channel in each pixel is an input feature. Such a group is also refered as "super-pixel". To define our desired groups over input features, all we need is to provide a feature mask.
- #
- # In case of an image input, the feature mask is a 2D image of the same size, where each pixel in the mask indicates the feature group it belongs to via an integer value. Pixels of the same value define a group.
- #
- # This means we can readily use VOC's segmentation masks as feature masks for Captum! However, while segmentaion numbers range from 0 to 255, Captum prefers consecutive group IDs for efficiency. Therefore, we will also include extra steps to convert mask IDs.
- # %%
- seg_ids = sorted(seg_mask.unique().tolist())
- print('Segmentation IDs:', seg_ids)
- # map segment IDs to feature group IDs
- feature_mask = seg_mask.clone()
- for i, seg_id in enumerate(seg_ids):
- feature_mask[feature_mask == seg_id] = i
- print('Feature mask IDs:', feature_mask.unique().tolist())
- # %% [markdown]
- # It is time to configure our Lime algorithm. Essentially, Lime trains an interpretable surrogate model to simulate the target model's predictions. So, building an appropriate interpretable model is the most critical step in Lime. Fortunately, Captum has provided many of the most common interpretable models to save the efforts. We will demonstrate the usages of Linear Regression and Linear Lasso. Another important factor is the similarity function. Because Lime aims to explain the local behavior of an example, it will reweight the training samples according to their similarity distances. By default, Captum's Lime uses the exponential kernel on top of the cosine distance. We will change to Euclidean distance instead which is more popular in vision.
- # %%
- exp_eucl_distance = get_exp_kernel_similarity_function('euclidean', kernel_width=1000)
- lr_lime = Lime(
- resnet,
- interpretable_model=SkLearnLinearRegression(), # build-in wrapped sklearn Linear Regression
- similarity_func=exp_eucl_distance
- )
- # %% [markdown]
- # Next, we will analyze these groups' influence on the most confident prediction `television`. Every time we call Lime's `attribute` function, an interpretable model is trained around the given input, so unlike many other Captum's attribution algorithms, it is strongly recommended to only provide a single example as input (tensors with first dimension or batch size = 1). There are advanced use cases of passing batched inputs. Interested readers can check the [documentation](https://captum.ai/api/lime.html) for details.
- #
- # In order to train the interpretable model, we need to specify enough training data through the argument `n_samples`. Lime creates the perturbed samples in the form of interpretable representation, i.e., a binary vector indicating the “presence” or “absence” of features. Lime needs to keep calling the target model to get the labels/values for all perturbed samples. This process can be quite time-consuming depending on the complexity of the target model and the number of samples. Setting the `perturbations_per_eval` can batch multiple samples in one forward pass to shorten the process as long as your machine still has capacity. You may also consider turning on the flag `show_progress` to display a progess bar showing how many forward calls are left.
- # %%
- label_idx = output_probs.argmax().unsqueeze(0)
- attrs = lr_lime.attribute(
- img.unsqueeze(0),
- target=label_idx,
- feature_mask=feature_mask.unsqueeze(0),
- n_samples=40,
- perturbations_per_eval=16,
- show_progress=True
- ).squeeze(0)
- print('Attribution range:', attrs.min().item(), 'to', attrs.max().item())
- # %% [markdown]
- # Now, let us use Captum's visualization tool to view the attribution heat map.
- # %%
- def show_attr(attr_map):
- viz.visualize_image_attr(
- attr_map.permute(1, 2, 0).numpy(), # adjust shape to height, width, channels
- method='heat_map',
- sign='all',
- show_colorbar=True
- )
- show_attr(attrs)
- # %% [markdown]
- # The result looks decent: the television segment does demonstrate strongest positive correlation with the prediction, while the chairs have relatively trivial impact and the border slightly shows negative contribution.
- #
- # However, we can further improve this result. One desired characteristic of interpretability is the ease for humans to comprehend. We should help reduce the noisy interference and emphisze the real influential features. In our case, all features more or less show some influences. Adding lasso regularization to the interpretable model can effectively help us filter them. Therefore, let us try Linear Lasso with a fit coefficient `alpha`. For all built-in sklearn wrapper models, you can directly pass any sklearn supported arguments.
- #
- # Moreover, since our example only has 4 segments, there are just 16 possible combinations of interpretable representations in total. So we can exhaust them instead of random sampling. The `Lime` class's argument `perturb_func` allows us to pass a generator function yielding samples. We will create the generator function iterating the combinations and set the `n_samples` to its exact length.
- # %%
- n_interpret_features = len(seg_ids)
- def iter_combinations(*args, **kwargs):
- for i in range(2 ** n_interpret_features):
- yield torch.tensor([int(d) for d in bin(i)[2:].zfill(n_interpret_features)]).unsqueeze(0)
- lasso_lime = Lime(
- resnet,
- interpretable_model=SkLearnLasso(alpha=0.08),
- similarity_func=exp_eucl_distance,
- perturb_func=iter_combinations
- )
- attrs = lasso_lime.attribute(
- img.unsqueeze(0),
- target=label_idx,
- feature_mask=feature_mask.unsqueeze(0),
- n_samples=2 ** n_interpret_features,
- perturbations_per_eval=16,
- show_progress=True
- ).squeeze(0)
- print('Attribution range:', attrs.min().item(), 'to', attrs.max().item())
- show_attr(attrs)
- # %% [markdown]
- # As we can see, the new attribution result removes the chairs and border with the help of Lasso.
- #
- # Another interesting question to explore is if the model also recognizes the chairs in the image. To answer this, we will use the most related label `rocking_chair` from ImageNet as the target, whose label index is `765`. We can check how confident the model feels about the alternative object.
- # %%
- alter_label_idx = 765
- alter_prob = output_probs[alter_label_idx].item()
- print(f'{idx_to_labels[str(alter_label_idx)]} ({alter_label_idx}):', round(alter_prob, 4))
- # %% [markdown]
- # Then, we will redo the attribution with our Lasso Lime.
- # %%
- attrs = lasso_lime.attribute(
- img.unsqueeze(0),
- target=alter_label_idx,
- feature_mask=feature_mask.unsqueeze(0),
- n_samples=2 ** n_interpret_features,
- perturbations_per_eval=16,
- show_progress=True,
- return_input_shape=True,
- ).squeeze(0)
- print('Attribution range:', attrs.min().item(), 'to', attrs.max().item())
- show_attr(attrs)
- # %% [markdown]
- # As shown in the heat map, our ResNet does present the right belief about the chair segment. However, it gets hindered by the television segment in the foreground. This may also explain why the model feels less confident about the chairs than the television.
- # %% [markdown]
- # ### 1.4 Understand the sampling process
- # %% [markdown]
- # We have already learned how to use Captum's Lime. This section will additionally dive into the internal sampling process to give interested readers an overview of what happens underneath. The goal of the sampling process is to collect a set of training data for the surrogate model. Every data point consists of three parts: interpretable input, model predicted label, and similarity weight. We will roughly illustrate how Lime achieves each of them behind the scenes.
- #
- # As we mentioned before, Lime samples data from the interpretable space. By default, Lime uses the presence or absence of the given mask groups as interpretable features. In our example, facing the above image of 4 segments, the interpretable representation is therefore a binary vector of 4 values indicating if each segment is present or absent. This is why we know there are only 16 possible interpretable representations and can exhaust them with our `iter_combinations`. Lime will keep calling its `perturb_func` to get the sample interpretable inputs. Let us simulate this step and give us such an interpretable input.
- # %%
- SAMPLE_INDEX = 13
- pertubed_genertator = iter_combinations()
- for _ in range(SAMPLE_INDEX + 1):
- sample_interp_inp = next(pertubed_genertator)
- print('Perturbed interpretable sample:', sample_interp_inp)
- # %% [markdown]
- # Our input sample `[1, 1, 0, 1]` means the third segment (television) is absent while other three segments stay.
- #
- # In order to find out what the target ImageNet's prediction is for this sample, Lime needs to convert it from interpretable space back to the original example space, i.e., the image space. The transformation takes the original example input and modifies it by setting the features of the absent groups to a baseline value which is `0` by default. The transformation function is called `from_interp_rep_transform` under Lime. We will run it manually here to get the pertubed image input and then visualize what it looks like.
- # %%
- pertubed_img = lasso_lime.from_interp_rep_transform(
- sample_interp_inp,
- img.unsqueeze(0),
- feature_mask=feature_mask.unsqueeze(0),
- baselines=0
- )
- # invert the normalization for render
- invert_norm = T.Normalize(
- mean=[-0.485/0.229, -0.456/0.224, -0.406/0.225],
- std=[1/0.229, 1/0.224, 1/0.225]
- )
- plt.imshow(invert_norm(pertubed_img).squeeze(0).permute(1, 2, 0).numpy())
- plt.axis('off')
- plt.show()
- # %% [markdown]
- # As shown above, compared with the original image, the absent feature, i.e., the television segment, gets masked in the perturbed image, while the other present features stay unchanged. With the perturbed image, Lime is able to find out the model's prediction. Let us still use "television" as our attribution target, so the label of perturbed sample is the value of the model's prediction on "television". Just for curiosity, we can also check how the model's prediction changes with the perturbation.
- # %%
- perturbed_outputs = resnet(pertubed_img).squeeze(0).detach()
- sample_label = perturbed_outputs[label_idx.item()]
- print('Label of the perturbed sample as Television:', sample_label)
- print('\nProbabilities of the perturbed image')
- perturbed_output_probs = F.softmax(perturbed_outputs, dim=0)
- print_result(perturbed_output_probs, topk=5)
- print(f'\ntelevision ({label_idx.item()}):', perturbed_output_probs[label_idx].item())
- # %% [markdown]
- # Reasonably, our ImageNet no longer feel confident about classifying the image as a television.
- #
- # At last, because Lime focuses on the local interpretability, it will calculate the similarity between the perturbed and original images to reweight the loss of this data point. Note the calculation is based on the input space instead of the interpretable space. This step is simply passing the two image tensors into the given `similarity_func` argument which is the exponential kernel of Euclidean distance in our case.
- # %%
- sample_similarity = exp_eucl_distance(img.unsqueeze(0), pertubed_img, None)
- print('Sample similarity:', sample_similarity)
- # %% [markdown]
- # This is basically how Lime create a single training data point of `sample_interp_inp`, `sample_label`, and `sample_similarity`. By repeating this process `n_samples` times, it collects a dataset to train the interpretable model.
- #
- # It is Worth noting that the steps we showed in this section is an example based on our Lime instance configured above. The logic of each step can be customized, especially with `LimeBase` class which will be demonstrated in Section 2.
- # %% [markdown]
- # ## 2. Text Classification
- # %% [markdown]
- # In this section, we will take use of a news subject classification example to demonstrate more customizable functions in Lime. We will train a simple embedding-bag classifier on the AG_NEWS dataset and analyze its understanding of words.
- # %%
- from torch import nn
- from torch.utils.data import DataLoader
- from torch.utils.data.dataset import random_split
- from torchtext.datasets import AG_NEWS
- from torchtext.data.utils import get_tokenizer
- from torchtext.vocab import Vocab
- from collections import Counter
- from IPython.core.display import HTML, display
- # %% [markdown]
- # ### 2.1 Load the data and define the model
- # %% [markdown]
- # `torchtext` has included the AG_NEWS dataset but since it is only split into train & test, we need to further cut a validation set from the original train split. Then we build the vocabulary of the frequent words based on our train split.
- # %%
- ag_ds = list(AG_NEWS(split='train'))
- ag_train, ag_val = ag_ds[:100000], ag_ds[100000:]
- tokenizer = get_tokenizer('basic_english')
- word_counter = Counter()
- for (label, line) in ag_train:
- word_counter.update(tokenizer(line))
- voc = Vocab(word_counter, min_freq=10)
- print('Vocabulary size:', len(voc))
- num_class = len(set(label for label, _ in ag_train))
- print('Num of classes:', num_class)
- # %% [markdown]
- # The model we use is composed of an embedding-bag, which averages the word embeddings as the latent text representation, and a final linear layer, which maps the latent vector to the logits. Unconventially, `pytorch`'s embedding-bag does not assume the first dimension is batch. Instead, it requires a flattened vector of indices with an additional offset tensor to mark the starting position of each example. You can refer to its [documentation](https://pytorch.org/docs/stable/generated/torch.nn.EmbeddingBag.html#embeddingbag) for details.
- # %%
- class EmbeddingBagModel(nn.Module):
- def __init__(self, vocab_size, embed_dim, num_class):
- super().__init__()
- self.embedding = nn.EmbeddingBag(vocab_size, embed_dim)
- self.linear = nn.Linear(embed_dim, num_class)
- def forward(self, inputs, offsets):
- embedded = self.embedding(inputs, offsets)
- return self.linear(embedded)
- # %% [markdown]
- # ### 2.2 Training and Baseline Classification
- # %% [markdown]
- # In order to train our classifier, we need to define a collate function to batch the samples into the tensor fomat required by the embedding-bag and create the interable dataloaders.
- # %%
- BATCH_SIZE = 64
- def collate_batch(batch):
- labels = torch.tensor([label - 1 for label, _ in batch])
- text_list = [tokenizer(line) for _, line in batch]
- # flatten tokens across the whole batch
- text = torch.tensor([voc[t] for tokens in text_list for t in tokens])
- # the offset of each example
- offsets = torch.tensor(
- [0] + [len(tokens) for tokens in text_list][:-1]
- ).cumsum(dim=0)
- return labels, text, offsets
- train_loader = DataLoader(ag_train, batch_size=BATCH_SIZE,
- shuffle=True, collate_fn=collate_batch)
- val_loader = DataLoader(ag_val, batch_size=BATCH_SIZE,
- shuffle=False, collate_fn=collate_batch)
- # %% [markdown]
- # We will then train our embedding-bag model with the common cross-entropy loss and Adam optimizer. Due to the simplicity of this task, 5 epochs should be enough to give us a stable 90% validation accuracy.
- # %%
- EPOCHS = 7
- EMB_SIZE = 64
- CHECKPOINT = './models/embedding_bag_ag_news.pt'
- USE_PRETRAINED = True # change to False if you want to retrain your own model
- def train_model(train_loader, val_loader):
- model = EmbeddingBagModel(len(voc), EMB_SIZE, num_class)
- loss = nn.CrossEntropyLoss()
- optimizer = torch.optim.Adam(model.parameters())
- for epoch in range(1, EPOCHS + 1):
- # training
- model.train()
- total_acc, total_count = 0, 0
- for idx, (label, text, offsets) in enumerate(train_loader):
- optimizer.zero_grad()
- predited_label = model(text, offsets)
- loss(predited_label, label).backward()
- optimizer.step()
- total_acc += (predited_label.argmax(1) == label).sum().item()
- total_count += label.size(0)
- if (idx + 1) % 500 == 0:
- print('epoch {:3d} | {:5d}/{:5d} batches | accuracy {:8.3f}'.format(
- epoch, idx + 1, len(train_loader), total_acc / total_count
- ))
- total_acc, total_count = 0, 0
- # evaluation
- model.eval()
- total_acc, total_count = 0, 0
- with torch.no_grad():
- for label, text, offsets in val_loader:
- predited_label = model(text, offsets)
- total_acc += (predited_label.argmax(1) == label).sum().item()
- total_count += label.size(0)
- print('-' * 59)
- print('end of epoch {:3d} | valid accuracy {:8.3f} '.format(epoch, total_acc / total_count))
- print('-' * 59)
- torch.save(model, CHECKPOINT)
- return model
- eb_model = torch.load(CHECKPOINT, weights_only=False) if USE_PRETRAINED else train_model(train_loader, val_loader)
- # %% [markdown]
- # Now, let us take the following sports news and test how our model performs.
- # %%
- test_label = 2 # {1: World, 2: Sports, 3: Business, 4: Sci/Tec}
- test_line = ('US Men Have Right Touch in Relay Duel Against Australia THENS, Aug. 17 '
- '- So Michael Phelps is not going to match the seven gold medals won by Mark Spitz. '
- 'And it is too early to tell if he will match Aleksandr Dityatin, '
- 'the Soviet gymnast who won eight total medals in 1980.')
- test_labels, test_text, test_offsets = collate_batch([(test_label, test_line)])
- probs = F.softmax(eb_model(test_text, test_offsets), dim=1).squeeze(0)
- print('Prediction probability:', round(probs[test_labels[0]].item(), 4))
- # %% [markdown]
- # Our embedding-bag does successfully identify the above news as sports with pretty high confidence.
- # %% [markdown]
- # ### 2.3 Inspect the model prediction with Lime
- # %% [markdown]
- # Finally, it is time to bring back Lime to inspect how the model makes the prediction. However, we will use the more customizable `LimeBase` class this time which is also the low-level implementation powering the `Lime` class we used before. The `Lime` class is opinionated when creating features from perturbed binary interpretable representations. It can only set the "absence" features to some baseline values while keeping other "presence" features. This is not what we want in this case. For text, the interpretable representation is a binary vector indicating if the word of each position is present or not. The corresponding text input should literally remove the absent words so our embedding-bag can calculate the average embeddings of the left words. Setting them to any baselines will pollute the calculation and moreover, our embedding-bag does not have common baseline tokens like `<padding>` at all. Therefore, we have to use `LimeBase` to customize the conversion logic through the `from_interp_rep_transform` argument.
- #
- # `LimeBase` is not opinionated at all so we have to define every piece manually. Let us talk about them in order:
- # - `forward_func`, the forward function of the model. Notice we cannot pass our model directly since Captum always assumes the first dimension is batch while our embedding-bag requires flattened indices. So we will add the dummy dimension later when calling `attribute` and make a wrapper here to remove the dummy dimension before giving to our model.
- # - `interpretable_model`, the surrogate model. This works the same as we demonstrated in the above image classification example. We also use sklearn linear lasso here.
- # - `similarity_func`, the function calculating the weights for training samples. The most common distance used for texts is the cosine similarity in their latent embedding space. The text inputs are just sequences of token indices, so we have to leverage the trained embedding layer from the model to encode them to their latent vectors. Due to this extra encoding step, we cannot use the util `get_exp_kernel_similarity_function('cosine')` like in the image classification example, which directly calculate the cosine similarity of the given inputs.
- # - `perturb_func`, the function to sample interpretable representations. We present another way to define this argument other than using generator as shown in the above image classification example. Here we directly define a function returning a randomized sample every call. It outputs a binary vector where each token is selected independently and uniformly at random.
- # - `perturb_interpretable_space`, whether perturbed samples are in interpretable space. `LimeBase` also supports sampling in the original input space, but we do not need it in our case.
- # - `from_interp_rep_transform`, the function transforming the perturbed interpretable samples back to the original input space. As explained above, this argument is the main reason for us to use `LimeBase`. We pick the subset of the present tokens from the original text input according to the interpretable representation.
- # - `to_interp_rep_transform`, the opposite of `from_interp_rep_transform`. It is needed only when `perturb_interpretable_space` is set to false.
- # %%
- # remove the batch dimension for the embedding-bag model
- def forward_func(text, offsets):
- return eb_model(text.squeeze(0), offsets)
- # encode text indices into latent representations & calculate cosine similarity
- def exp_embedding_cosine_distance(original_inp, perturbed_inp, _, **kwargs):
- original_emb = eb_model.embedding(original_inp, None)
- perturbed_emb = eb_model.embedding(perturbed_inp, None)
- distance = 1 - F.cosine_similarity(original_emb, perturbed_emb, dim=1)
- return torch.exp(-1 * (distance ** 2) / 2)
- # binary vector where each word is selected independently and uniformly at random
- def bernoulli_perturb(text, **kwargs):
- probs = torch.ones_like(text) * 0.5
- return torch.bernoulli(probs).long()
- # remove absent token based on the intepretable representation sample
- def interp_to_input(interp_sample, original_input, **kwargs):
- return original_input[interp_sample.bool()].view(original_input.size(0), -1)
- lasso_lime_base = LimeBase(
- forward_func,
- interpretable_model=SkLearnLasso(alpha=0.08),
- similarity_func=exp_embedding_cosine_distance,
- perturb_func=bernoulli_perturb,
- perturb_interpretable_space=True,
- from_interp_rep_transform=interp_to_input,
- to_interp_rep_transform=None
- )
- # %% [markdown]
- # The attribution call is the same as the `Lime` class. Just remember to add the dummy batch dimension to the text input and put the offsets in the `additional_forward_args` because it is not a feature for the classification but a metadata for the text input.
- # %%
- attrs = lasso_lime_base.attribute(
- test_text.unsqueeze(0), # add batch dimension for Captum
- target=test_labels,
- additional_forward_args=(test_offsets,),
- n_samples=32000,
- show_progress=True
- ).squeeze(0)
- print('Attribution range:', attrs.min().item(), 'to', attrs.max().item())
- # %% [markdown]
- # At last, let us create a simple visualization to highlight the influential words where green stands for positive correlation and red for negative.
- # %%
- def show_text_attr(attrs):
- rgb = lambda x: '255,0,0' if x < 0 else '0,255,0'
- alpha = lambda x: abs(x) ** 0.5
- token_marks = [
- f'<mark style="background-color:rgba({rgb(attr)},{alpha(attr)})">{token}</mark>'
- for token, attr in zip(tokenizer(test_line), attrs.tolist())
- ]
- display(HTML('<p>' + ' '.join(token_marks) + '</p>'))
- show_text_attr(attrs)
- # %% [markdown]
- # The above visulization should render something like the image below where the model links the "Sports" subject to many reasonable words, like "match" and "medals".
- #
- # 
Image_and_Text_Classification_LIME.ipynb at commit 2a3c3df, under BSD-3-Clause · at the source
Overview
and 49 other authors
Kai M Schubert23, Carolina Ferreira-Atuesta24, Gian Marco Duma25, Raphaël Christin26, Elisa Barbi27, Renzo Guerrini27,28, Theodor Rüber29,30,31,32, Tobias Bauer29,30,31, Benjamin Sinclair33, Jacob Bunyamin33, Merran R Courtney33,34, Meng Law33, Angelo Labate35, Pasquale Striano36,37, Lucy Vivash33,34, Terence J O’Brien33,38, Matteo Lenge27, Luca Saba39, Jonathan K Kleen26, Paolo Bonanni25, Leigh N Sepeta40, Marian Galovic24, Emanuele Bartolini41, Victoria Ives-Deliperi42, Boris C Bernhardt43, Pascal Martin44, Chantal Depondt45, Travis Stoub46, Anna Elisabetta Vaudano47, Stefano Meletti47,48, Ruben Kuzniecky49, Luis Concha50, Anto I Bagić51, Kathryn A Davis19,52, Richard J Staba53, Niels K N Focke54, Heath Pardoe55,56, Patricia C Dugan55, Orrin Devinsky57,58, Daniel L Drane4, Zhiqiang Zhang59, Antonio Gambardella60,61, Alexandra Parashos62, Fernando Cendes63,64, Paul M Thompson9, Sanjay M Sisodiya13,65, Vince D Calhoun66, Leonardo Bonilha67, Carrie R McDonald1,6868 affiliations
- Department of Radiation Medicine and Applied Sciences, University of California, San Diego, La Jolla, CA 92037, USA
- Halıcıoğlu Data Science Institute, University of California, San Diego, La Jolla, CA 92093, USA
- Department of Mathematics and Statistics, San Diego State University, San Diego, CA 92182, USA
- Department of Neurology, Emory University, Atlanta, GA 30322, USA
- Department of Neurosciences, University of California, San Diego, La Jolla, CA 92093, USA
- GSU/GATech/Emory Center for Translational Research in Neuroimaging and Data Science (TReNDS), Atlanta, GA 30303, USA
- Electrical and Computer Engineering, Georgia Institute of Technology, Atlanta, GA 30332, USA
- Center for Translational Research in Neuroimaging and Data Science, Georgia State University, Atlanta, GA 30303, USA
- Imaging Genetics Center, Mark and Mary Stevens Neuroimaging and Informatics Institute, Keck School of Medicine, University of Southern California, Marina del Rey, CA 90292, USA
- Brazilian Institute of Neuroscience and Neurotechnology (BRAINN), Campinas, SP 13083-970, Brazil
- Department of Neurology, University of Campinas (UNICAMP), Campinas, SP 13083-888, Brazil
- Neuroimaging Laboratory, University of Campinas (UNICAMP), Campinas, SP 13083-888, Brazil
- Department of Clinical and Experimental Epilepsy, UCL Queen Square Institute of Neurology, London WC1N 3BG, UK
- Dublin Neurological Institute, Mater Misericordiae University Hospital, Dublin D07 W7XF, Ireland
- School of Medicine, University College Dublin, Dublin D04 V1W8, Ireland
- Centre for Medical Image Computing, Department of Medical Physics and Biomedical Engineering, University College London, London WC1E 6BT, UK
- Department of Neurology, University Medical Center Göttingen, Göttingen 37075, Germany
- Department of Psychiatry and Psychotherapy, University of Cologne, Cologne 50937, Germany
- Department of Neurology, Perelman School of Medicine, University of Pennsylvania, Philadelphia, PA 19104, USA
- Department of Biomedical, Metabolic and Neural Sciences, University of Modena and Reggio Emilia, Baggiovara 41126, Italy
- Department of Radiology, CUB Erasme Hospital, Hôpital Universitaire de Bruxelles, Université Libre de Bruxelles, Brussels 1070, Belgium
- Department of Medical Imaging and Radiation Sciences, Faculty of Medicine and Health Sciences, Université de Sherbrooke, Sherbrooke, QC J1H 5N4, Canada
- Department of Neurology, Clinical Neuroscience Center, University Hospital and University of Zurich, Zurich 8091, Switzerland
- Department of Neurology, University Hospital Zurich, Zurich 8091, Switzerland
- Epilepsy and Clinical Neurophysiology Unit, Scientific Institute IRCCS E. Medea, Conegliano 31015, Italy
- Department of Neurology, University of California SanFrancisco, San Francisco, CA 94158, USA
- Department of Neuroscience and Human Genetics, Meyer Children’s Hospital IRCCS, Florence 50139, Italy
- Department of Neuroscience, Pharmacology and Child Health, University of Florence, Florence 50139, Italy
- Department of Neuroradiology, University Hospital Bonn, Bonn 53127, Germany
- Department of Epileptology, University Hospital Bonn, Bonn 53127, Germany
- German Center for Neurodegenerative Diseases (DZNE), Bonn 53127, Germany
- Center for Medical Data Usability and Translation, University of Bonn, Bonn 53127, Germany
- Department of Neuroscience, School of Translational Medicine, Alfred Health, Monash University, Melbourne, VIC 53127, Australia
- Department of Neurology, Alfred Health, Melbourne, VIC 3004, Australia
- Neurophysiopathology and Movement Disorders Clinic, University of Messina, Messina 98125, Italy
- IRCCS G. Gaslini, Full Member of Epicare, Genova 16147, Italy
- Department of Neurosciences, Rehabilitation, Ophthalmology, Genetics, Maternal and Child Health, University of Genova, Genova 16132, Italy
- Departments of Medicine and Neurology, the Royal Melbourne Hospital, The University of Melbourne, Parkville, VIC 3050, Australia
- Department of Radiology, AOU Cagliari and University of Cagliari, Cagliari 09042, Italy
- Children’s National Hospital (CNH), Washington, DC 20010, USA
- Department of Developmental Neuroscience, IRCCS Foundation Stella Maris, Pisa 56128, Italy
- Department of Psychiatry, Neuroscience Institute, University of Cape Town, Cape Town 7925, South Africa
- Centre of Excellence in Epilepsy at the Neuro and McConnell Brain Imaging Centre, Montreal Neurological Institute, McGill University, Montreal, QC H3A 2B4, Canada
- Department of Neurology and Epileptology, Hertie Institute for Clinical Brain Research, University of Tübingen, Tübingen 72076, Germany
- Department of Neurology, CUB Erasme Hospital, Hôpital Universitaire de Bruxelles, Université Libre de Bruxelles, Brussels 1070, Belgium
- Department of Neurological Sciences, Rush University Medical Center, Chicago, IL 60612, USA
- Department of Biomedical, Metabolic and Neuronal Science, University of Modena and Reggio Emilia, Modena 41125, Italy
- Neurophysiology Unit and Epilepsy Centre, AOU Modena, Modena 41126, Italy
- Department of Neurology, School of Medicine at Hofstra/Northwell, Hempstead, NY 11549, USA
- Institute of Neurobiology, Universidad Nacional Autónoma de México, Querétaro 76230, Mexico
- University of Pittsburgh Comprehensive Epilepsy Center (UPCEC), Department of Neurology, School of Medicine, University of Pittsburgh, Pittsburgh, PA 15213, USA
- Center for Neuroengineering and Therapeutics, University of Pennsylvania, Philadelphia, PA 19104, United States
- Department of Neurology, David Geffen School of Medicine at UCLA, Los Angeles, CA 90095, USA
- Clinic for Neurology, University Medical Center Göttingen, Göttingen 37075, Germany
- Department of Neurology, NYU Grossman School of Medicine, NewYork, NY 10016, USA
- Florey Institute of Neuroscience and Mental Health, Heidelberg, VIC 3084, Australia
- NYU Comprehensive Epilepsy Center, NewYork, NY 10016, USA
- Department of Neurology, Langone School of Medicine, NewYork University, New York, NY 10016, USA
- Department of Radiology, Jinling Hospital, Affiliated Hospital of Medical School, Nanjing University, Nanjing 210002, China
- Institute of Neurology, Magna Græcia University, Catanzaro 88100, Italy
- Neuroscience Research Center, Magna Græcia University, Catanzaro 88100, Italy
- Department of Neurology, Medical University of South Carolina, Charleston, SC 29425, USA
- Department of Neurology, FCM, University of Campinas—UNICAMP, Campinas, SP 13083-888, Brazil
- Brazilian Institute of Neuroscience and Neurotechnology, Campinas, SP 13083-970, Brazil
- Chalfont Centre for Epilepsy, Chalfont St Peter, Bucks SL9 0RJ, UK
- Tri-institutional Center for Translational Research in Neuroimaging and Data Science (TReNDS), Georgia State, Georgia Tech, Emory, Atlanta, GA 30303, USA
- Department of Neurology, University of South Carolina, Columbia, SC 29203, USA
- Department of Psychiatry, University of California, San Diego, La Jolla, CA 92037, USA
Abstract
Diagnostic MRI evaluation of temporal lobe epilepsy (TLE) depends on the subjective visual interpretation of MRI images. These interpretations could be enhanced by quantitative artificial intelligence (AI) support tools. Humans often make sequential and conditional decisions during their radiological interpretations, such as whether an abnormality is present and, if present, characterizing the abnormality. It is not known whether it is superior to train AI to treat every decision separately in a similar step-wise manner or to train a model holistically on all decisions simultaneously. Here, we analysed three large epilepsy MRI datasets [n = 3676, 2320 people with epilepsy and 1356 healthy controls (HC)] to perform two tasks: (i) establish the presence of a TLE pattern on MRI and (ii) determine TLE pattern lateralization. We compared Step-wise models that independently classify TLE versus HC and lateralize patients as left TLE (L-TLE) or right TLE (R-TLE), against a simultaneous model trained to distinguish all three classes in a single step. To do this, 3D volumetric T1-weighted images were input into an EfficientNetV2 model multiple times to ensure reproducibility of results. Class prediction, model classification confidence and saliency maps were output for interpretability. Step-wise models outperformed the Simultaneous model on both tasks (both Ps < 0.001), with an average ∼2.8% accuracy increase for discriminating HC from TLE and an average 12.7% accuracy increase for distinguishing L-TLE from R-TLE. For both the Step-wise and Simultaneous models, important features discriminating TLE from HC included the known TLE limbic pattern involving the hippocampus, parahippocampal cortical regions, cingulate cortex and lateral temporal regions. However, there was less concordance between the Step-wise and Simultaneous models for the L-TLE versus R-TLE task (all Fisher’s Zs > 10.5, Ps < 0.001); the Step-wise model focused less on subcortical regions such as the thalamus and hippocampus and focused more on distributed cortical pathology. Across the two Step-wise models, 95.1% of TLE patients had accurate classifications in either HC versus TLE and/
Reproduced under the paper's license (CC BY), from the paper cited above.
Repository
Its files are read in the Code ↔ Paper reader above, with 4 matches between paragraphs and lines of code.
meta-pytorch/captum
2a3c3dfe660bd23def7de91db87bc2aab5708341, 27 September 2026Availability: 1 check, the latest on 27 September 2026: the link answers
- 27 September 2026: the link answers
274 files
- captum/
__init__.py , Python, 13 lines - captum/
_utils/ , Python, 1 line__init__.py - captum/
_utils/ , Python, 514 linesav.py - captum/
_utils/ , Python, 991 linescommon.py - captum/
_utils/ , Python, 22 linesexceptions.py - captum/
_utils/ , Python, 1,063 linesgradient.py - captum/
_utils/ , Python, 6 linesmodels/ __init__.py - captum/
_utils/ , Python, 24 linesmodels/ linear_model/ __init__.py - captum/
_utils/ , Python, 381 linesmodels/ linear_model/ model.py - captum/
_utils/ , Python, 439 linesmodels/ linear_model/ train.py - captum/
_utils/ , Python, 75 linesmodels/ model.py - captum/
_utils/ , Python, 206 linesprogress.py - captum/
_utils/ , Python, 200 linessample_gradient.py - captum/
_utils/ , Python, 101 linestyping.py - captum/
attr/ , Python, 170 lines__init__.py - captum/
attr/ , Python, 1 line_core/ __init__.py - captum/
attr/ , Python, 479 lines_core/ dataloader_attr.py - captum/
attr/ , Python, 2,012 lines_core/ deep_lift.py - captum/
attr/ , Python, 1,292 lines_core/ feature_ablation.py - captum/
attr/ , Python, 477 lines_core/ feature_permutation.py - captum/
attr/ , Python, 443 lines_core/ gradient_shap.py - captum/
attr/ , Python, 337 lines_core/ guided_backprop_deconvne t.py - captum/
attr/ , Python, 234 lines, 1 match_core/ guided_grad_cam.py - captum/
attr/ , Python, 148 lines_core/ input_x_gradient.py - captum/
attr/ , Python, 415 lines_core/ integrated_gradients.py - captum/
attr/ , Python, 848 lines_core/ kernel_shap.py - captum/
attr/ , Python, 1 line_core/ layer/ __init__.py - captum/
attr/ , Python, 243 lines, 1 match_core/ layer/ grad_cam.py - captum/
attr/ , Python, 327 lines_core/ layer/ internal_influence.py - captum/
attr/ , Python, 145 lines_core/ layer/ layer_activation.py - captum/
attr/ , Python, 416 lines_core/ layer/ layer_conductance.py - captum/
attr/ , Python, 704 lines_core/ layer/ layer_deep_lift.py - captum/
attr/ , Python, 327 lines_core/ layer/ layer_feature_ablation.p y - captum/
attr/ , Python, 258 lines_core/ layer/ layer_feature_permutatio n.py - captum/
attr/ , Python, 490 lines_core/ layer/ layer_gradient_shap.py - captum/
attr/ , Python, 228 lines_core/ layer/ layer_gradient_x_activat ion.py - captum/
attr/ , Python, 628 lines_core/ layer/ layer_integrated_gradien ts.py - captum/
attr/ , Python, 322 lines_core/ layer/ layer_lrp.py - captum/
attr/ , Python, 1,361 lines_core/ lime.py - captum/
attr/ , Python, 1,401 lines_core/ llm_attr.py - captum/
attr/ , Python, 466 lines_core/ lrp.py - captum/
attr/ , Python, 1 line_core/ neuron/ __init__.py - captum/
attr/ , Python, 447 lines_core/ neuron/ neuron_conductance.py - captum/
attr/ , Python, 491 lines_core/ neuron/ neuron_deep_lift.py - captum/
attr/ , Python, 288 lines_core/ neuron/ neuron_feature_ablation. py - captum/
attr/ , Python, 194 lines_core/ neuron/ neuron_gradient.py - captum/
attr/ , Python, 269 lines_core/ neuron/ neuron_gradient_shap.py - captum/
attr/ , Python, 338 lines_core/ neuron/ neuron_guided_backprop_d econvnet.py - captum/
attr/ , Python, 263 lines_core/ neuron/ neuron_integrated_gradie nts.py - captum/
attr/ , Python, 518 lines_core/ noise_tunnel.py - captum/
attr/ , Python, 426 lines_core/ occlusion.py - captum/
attr/ , Python, 250 lines_core/ remote_provider.py - captum/
attr/ , Python, 155 lines_core/ saliency.py - captum/
attr/ , Python, 1,340 lines_core/ shapley_value.py - captum/
attr/ , Python, 1 line_models/ __init__.py - captum/
attr/ , Python, 271 lines_models/ base.py - captum/
attr/ , Python, 1 line_utils/ __init__.py - captum/
attr/ , Python, 147 lines_utils/ approximation_methods.py - captum/
attr/ , Python, 572 lines_utils/ attribution.py - captum/
attr/ , Python, 68 lines_utils/ baselines.py - captum/
attr/ , Python, 252 lines_utils/ batching.py - captum/
attr/ , Python, 108 lines_utils/ class_summarizer.py - captum/
attr/ , Python, 410 lines_utils/ common.py - captum/
attr/ , Python, 22 lines_utils/ custom_modules.py - captum/
attr/ , Python, 86 lines_utils/ input_layer_wrapper.py - captum/
attr/ , Python, 937 lines_utils/ interpretable_input.py - captum/
attr/ , Python, 218 lines_utils/ lrp_rules.py - captum/
attr/ , Python, 318 lines_utils/ stat.py - captum/
attr/ , Python, 258 lines_utils/ summarizer.py - captum/
attr/ , Python, 1,300 lines_utils/ visualization.py - captum/
attr/ , Python, 41 linesvisualization.py - captum/
concept/ , Python, 15 lines__init__.py - captum/
concept/ , Python, 1 line_core/ __init__.py - captum/
concept/ , Python, 243 lines_core/ cav.py - captum/
concept/ , Python, 94 lines_core/ concept.py - captum/
concept/ , Python, 843 lines_core/ tcav.py - captum/
concept/ , Python, 1 line_utils/ __init__.py - captum/
concept/ , Python, 266 lines_utils/ classifier.py - captum/
concept/ , Python, 29 lines_utils/ common.py - captum/
concept/ , Python, 67 lines_utils/ data_iterator.py - captum/
influence/ , Python, 22 lines__init__.py - captum/
influence/ , Python, 1 line_core/ __init__.py - captum/
influence/ , Python, 1,060 lines_core/ arnoldi_influence_functi on.py - captum/
influence/ , Python, 65 lines_core/ influence.py - captum/
influence/ , Python, 1,359 lines_core/ influence_function.py - captum/
influence/ , Python, 318 lines_core/ similarity_influence.py - captum/
influence/ , Python, 1,443 lines_core/ tracincp.py - captum/
influence/ , Python, 1,652 lines_core/ tracincp_fast_rand_proj. py - captum/
influence/ , Python, 1 line_utils/ __init__.py - captum/
influence/ , Python, 1,109 lines_utils/ common.py - captum/
influence/ , Python, 196 lines_utils/ nearest_neighbors.py - captum/
insights/ , Python, 63 lines__init__.py - captum/
log/ , Python, 32 lines__init__.py - captum/
log/ , Python, 75 linesdummy_log.py - captum/
metrics/ , Python, 15 lines__init__.py - captum/
metrics/ , Python, 1 line_core/ __init__.py - captum/
metrics/ , Python, 672 lines_core/ infidelity.py - captum/
metrics/ , Python, 331 lines_core/ sensitivity.py - captum/
metrics/ , Python, 1 line_utils/ __init__.py - captum/
metrics/ , Python, 95 lines_utils/ batching.py - captum/
module/ , Python, 10 lines__init__.py - captum/
module/ , Python, 258 linesbinary_concrete_stochast ic_gates.py - captum/
module/ , Python, 180 linesgaussian_stochastic_gate s.py - captum/
module/ , Python, 232 linesstochastic_gates_base.py - captum/
robust/ , Python, 11 lines__init__.py - captum/
robust/ , Python, 1 line_core/ __init__.py - captum/
robust/ , Python, 226 lines_core/ fgsm.py - captum/
robust/ , Python, 1 line_core/ metrics/ __init__.py - captum/
robust/ , Python, 521 lines_core/ metrics/ attack_comparator.py - captum/
robust/ , Python, 497 lines_core/ metrics/ min_param_perturbation.p y - captum/
robust/ , Python, 50 lines_core/ perturbation.py - captum/
robust/ , Python, 239 lines_core/ pgd.py - captum/
testing/ , Python, 1 lineattr/ helpers/ __init__.py - captum/
testing/ , Python, 48 linesattr/ helpers/ attribution_delta_util.p y - captum/
testing/ , Python, 211 linesattr/ helpers/ conductance_reference.py - captum/
testing/ , Python, 83 linesattr/ helpers/ gen_test_utils.py - captum/
testing/ , Python, 54 linesattr/ helpers/ get_config_util.py - captum/
testing/ , Python, 44 linesattr/ helpers/ neuron_layer_testing_uti l.py - captum/
testing/ , Python, 1,248 linesattr/ helpers/ test_config.py - captum/
testing/ , Python, 18 lineshelpers/ __init__.py - captum/
testing/ , Python, 214 lineshelpers/ basic.py - captum/
testing/ , Python, 812 lineshelpers/ basic_models.py - captum/
testing/ , Python, 113 lineshelpers/ classification_models.py - captum/
testing/ , Python, 62 lineshelpers/ evaluate_linear_model.py - captum/
testing/ , Python, 1 linehelpers/ influence/ __init__.py - captum/
testing/ , Python, 726 lineshelpers/ influence/ common.py - scripts/
build_docs.sh , Shell, 95 lines - scripts/
install_via_pip.sh , Shell, 66 lines - scripts/
parse_sphinx.py , Python, 85 lines - scripts/
parse_tutorials.py , Python, 122 lines - scripts/
publish_site.sh , Shell, 209 lines - scripts/
update_versions_html.py , Python, 79 lines - scripts/
versions.js , JavaScript, 90 lines - sphinx/
source/ , Python, 258 linesconf.py - tests/
__init__.py , Python, 1 line - tests/
attr/ , Python, 1 line__init__.py - tests/
attr/ , Python, 1 linelayer/ __init__.py - tests/
attr/ , Python, 168 lineslayer/ test_grad_cam.py - tests/
attr/ , Python, 206 lineslayer/ test_internal_influence. py - tests/
attr/ , Python, 180 lineslayer/ test_layer_ablation.py - tests/
attr/ , Python, 187 lineslayer/ test_layer_activation.py - tests/
attr/ , Python, 283 lineslayer/ test_layer_conductance.p y - tests/
attr/ , Python, 331 lineslayer/ test_layer_deeplift.py - tests/
attr/ , Python, 39 lineslayer/ test_layer_feature_permu tation.py - tests/
attr/ , Python, 215 lineslayer/ test_layer_gradient_shap .py - tests/
attr/ , Python, 216 lineslayer/ test_layer_gradient_x_ac tivation.py - tests/
attr/ , Python, 445 lineslayer/ test_layer_integrated_gr adients.py - tests/
attr/ , Python, 200 lineslayer/ test_layer_lrp.py - tests/
attr/ , Python, 1 linemodels/ __init__.py - tests/
attr/ , Python, 133 linesmodels/ test_base.py - tests/
attr/ , Python, 1 lineneuron/ __init__.py - tests/
attr/ , Python, 302 linesneuron/ test_neuron_ablation.py - tests/
attr/ , Python, 289 linesneuron/ test_neuron_conductance. py - tests/
attr/ , Python, 230 linesneuron/ test_neuron_deeplift.py - tests/
attr/ , Python, 164 linesneuron/ test_neuron_gradient.py - tests/
attr/ , Python, 102 linesneuron/ test_neuron_gradient_sha p.py - tests/
attr/ , Python, 189 linesneuron/ test_neuron_integrated_g radients.py - tests/
attr/ , Python, 144 linestest_approximation_metho ds.py - tests/
attr/ , Python, 72 linestest_baselines.py - tests/
attr/ , Python, 146 linestest_class_summarizer.py - tests/
attr/ , Python, 71 linestest_common.py - tests/
attr/ , Python, 318 linestest_data_parallel.py - tests/
attr/ , Python, 395 linestest_dataloader_attr.py - tests/
attr/ , Python, 147 linestest_deconvolution.py - tests/
attr/ , Python, 473 linestest_deeplift_basic.py - tests/
attr/ , Python, 493 linestest_deeplift_classifica tion.py - tests/
attr/ , Python, 1,265 linestest_feature_ablation.py - tests/
attr/ , Python, 678 linestest_feature_permutation .py - tests/
attr/ , Python, 274 linestest_gradient_shap.py - tests/
attr/ , Python, 176 linestest_guided_backprop.py - tests/
attr/ , Python, 151 linestest_guided_grad_cam.py - tests/
attr/ , Python, 184 linestest_hook_removal.py - tests/
attr/ , Python, 188 linestest_input_layer_wrapper .py - tests/
attr/ , Python, 140 linestest_input_x_gradient.py - tests/
attr/ , Python, 562 linestest_integrated_gradient s_basic.py - tests/
attr/ , Python, 147 linestest_integrated_gradient s_classification.py - tests/
attr/ , Python, 888 linestest_interpretable_input .py - tests/
attr/ , Python, 231 linestest_jit.py - tests/
attr/ , Python, 584 linestest_kernel_shap.py - tests/
attr/ , Python, 680 linestest_lime.py - tests/
attr/ , Python, 1,442 linestest_llm_attr.py - tests/
attr/ , Python, 275 linestest_llm_attr_hf_compati bility.py - tests/
attr/ , Python, 441 linestest_lrp.py - tests/
attr/ , Python, 416 linestest_occlusion.py - tests/
attr/ , Python, 31 linestest_perturbation_attrib ution.py - tests/
attr/ , Python, 184 linestest_saliency.py - tests/
attr/ , Python, 1,249 linestest_shapley.py - tests/
attr/ , Python, 35 linestest_shapley_cuda.py - tests/
attr/ , Python, 195 linestest_stat.py - tests/
attr/ , Python, 53 linestest_summarizer.py - tests/
attr/ , Python, 244 linestest_targets.py - tests/
attr/ , Python, 112 linestest_utils_batching.py - tests/
attr/ , Python, 79 linestest_visualization_image .py - tests/
attr/ , Python, 35 linestest_visualization_image _display.py - tests/
attr/ , Python, 25 linestest_visualization_text. py - tests/
attr/ , Python, 63 linestest_visualization_zero_ scale.py - tests/
concept/ , Python, 1 line__init__.py - tests/
concept/ , Python, 52 linestest_cav_weights_only.py - tests/
concept/ , Python, 30 linestest_classifier.py - tests/
concept/ , Python, 76 linestest_concept.py - tests/
concept/ , Python, 1,399 linestest_tcav.py - tests/
influence/ , Python, 1 line__init__.py - tests/
influence/ , Python, 1 line_core/ __init__.py - tests/
influence/ , Python, 538 lines_core/ test_arnoldi_influence.p y - tests/
influence/ , Python, 133 lines_core/ test_dataloader.py - tests/
influence/ , Python, 301 lines_core/ test_naive_influence.py - tests/
influence/ , Python, 201 lines_core/ test_similarity_influenc e.py - tests/
influence/ , Python, 160 lines_core/ test_tracin_aggregate_in fluence.py - tests/
influence/ , Python, 386 lines_core/ test_tracin_intermediate _quantities.py - tests/
influence/ , Python, 164 lines_core/ test_tracin_k_most_influ ential.py - tests/
influence/ , Python, 561 lines_core/ test_tracin_regression.p y - tests/
influence/ , Python, 299 lines_core/ test_tracin_self_influen ce.py - tests/
influence/ , Python, 263 lines_core/ test_tracin_show_progres s.py - tests/
influence/ , Python, 129 lines_core/ test_tracin_validation.p y - tests/
influence/ , Python, 316 lines_core/ test_tracin_xor.py - tests/
influence/ , Python, 1 line_utils/ __init__.py - tests/
influence/ , Python, 59 lines_utils/ test_common.py - tests/
metrics/ , Python, 1 line__init__.py - tests/
metrics/ , Python, 516 linestest_infidelity.py - tests/
metrics/ , Python, 358 linestest_sensitivity.py - tests/
module/ , Python, 1 line__init__.py - tests/
module/ , Python, 447 linestest_binary_concrete_sto chastic_gates.py - tests/
module/ , Python, 55 linestest_binary_concrete_sto chastic_gates_cuda.py - tests/
module/ , Python, 428 linestest_gaussian_stochastic _gates.py - tests/
module/ , Python, 48 linestest_gaussian_stochastic _gates_cuda.py - tests/
robust/ , Python, 1 line__init__.py - tests/
robust/ , Python, 223 linestest_FGSM.py - tests/
robust/ , Python, 223 linestest_PGD.py - tests/
robust/ , Python, 226 linestest_attack_comparator.p y - tests/
robust/ , Python, 219 linestest_min_param_perturbat ion.py - tests/
test_insights_retired.py , Python, 51 lines - tests/
test_tutorial_regression , Python, 34 liness.py - tests/
utils/ , Python, 1 line__init__.py - tests/
utils/ , Python, 1 linemodels/ __init__.py - tests/
utils/ , Python, 1 linemodels/ linear_models/ __init__.py - tests/
utils/ , Python, 216 linesmodels/ linear_models/ _test_linear_classifier. py - tests/
utils/ , Python, 536 linestest_av.py - tests/
utils/ , Python, 223 linestest_common.py - tests/
utils/ , Python, 326 linestest_gradient.py - tests/
utils/ , Python, 65 linestest_helpers.py - tests/
utils/ , Python, 395 linestest_jacobian.py - tests/
utils/ , Python, 264 linestest_linear_model.py - tests/
utils/ , Python, 61 linestest_progress.py - tests/
utils/ , Python, 147 linestest_sample_gradient.py - tests/
utils/ , Python, 61 linestest_torch_load_weights_ only.py - tutorials/
Bert_SQUAD_Interpret.ipy , Jupyter, 498 linesnb - tutorials/
Bert_SQUAD_Interpret2.ip , Jupyter, 577 linesynb - tutorials/
CIFAR_Captum_Robustness. , Jupyter, 317 linesipynb - tutorials/
CIFAR_TorchVision_Interp , Jupyter, 250 linesret.ipynb - tutorials/
DLRM_Tutorial.ipynb , Jupyter, 381 lines - tutorials/
Detectron2_Instance_Segm , Jupyter, 135 linesentation_Interpret.ipynb - tutorials/
Distributed_Attribution. , Jupyter, 304 linesipynb - tutorials/
House_Prices_Regression_ , Jupyter, 389 linesInterpret.ipynb - tutorials/
IMDB_TorchText_Interpret , Jupyter, 289 lines.ipynb - tutorials/
Image_and_Text_Classific , Jupyter, 550 lines, 1 matchation_LIME.ipynb - tutorials/
Llama2_LLM_Attribution.i , Jupyter, 267 linespynb - tutorials/
Multimodal_Image_Segment , Jupyter, 465 lines_Attribution.ipynb - tutorials/
Multimodal_VQA_Interpret , Jupyter, 371 lines.ipynb - tutorials/
Resnet_TorchVision_Ablat , Jupyter, 279 linesion.ipynb - tutorials/
Segmentation_Interpret.i , Jupyter, 213 linespynb - tutorials/
TCAV_Image.ipynb , Jupyter, 444 lines - tutorials/
TCAV_NLP.ipynb , Jupyter, 406 lines - tutorials/
Titanic_Basic_Interpret. , Jupyter, 354 lines, 1 matchipynb - tutorials/
TorchVision_Interpret.ip , Jupyter, 274 linesynb - tutorials/
TracInCP_Tutorial.ipynb , Jupyter, 625 lines - website/
core/ , JavaScript, 132 linesFooter.js - website/
core/ , JavaScript, 83 linesTutorial.js - website/
core/ , JavaScript, 108 linesTutorialSidebar.js - website/
pages/ , JavaScript, 302 linesen/ index.js - website/
pages/ , JavaScript, 226 linestutorials/ index.js - website/
siteConfig.js , JavaScript, 101 lines - website/
static/ , JavaScript, 54 linesjs/ code_block_buttons.js - LICENSE, License, 29 lines
- README.md, Text, 441 lines
The paper's code and data availability statement is in the Data section.
Tracing map
Proposed by the machine: these links were found in the paper and verified at the source, without human review. The map will receive a Zenodo DOI once one of the paper's authors has validated it with their ORCID.
What the map holds:
- 1 repository of the authors' code, each at its verified commit, with its license and how the link was found in the paper;
- 272 scripts, each with its path and the digest of its content;
- 4 matches between paragraphs of the paper and lines of the code (method lexical-v1);
- neither the text of the paper nor the code itself.
Its JSON (tracing-map.json) is deposited on Zenodo with its DOI once the map is validated.
Data
No dataset and no data link were found in the paper.
Data availability
Data are available from the corresponding author from the ENIGMA-Epilepsy dataset upon reasonable request and with appropriate data-sharing agreements. Data from ECP and CAPES are available from their respective cohort PIs upon reasonable request. The code used for processing the images, modelling and visualization is noted in their respective sections.
Reproduced under the paper's license (CC BY), from the paper cited above.
Versions
The history of this record: each version stored by the harvester or made by a correction of its authors or of the maintainers of its code, and what changed in its facts. The texts of the paper (its abstract, its availability statements) are not part of it; versions that changed only those are not listed.
Version 1, 27 September 2026: the first record
Recorded: type, language, journal, volume, issue, pages, dates, 69 authors, 5 keywords, 1 funder, 40 references.
Cite
This paper
Kaestner, E., Sawant, J., Arienzo, D., Hasenstab, K. A., Gleichgerrcht, E., Gholipour, T., Abrol, A., Hassanzadeh, R., Thomopoulos, S. I., Yasuda, C. L., Silva, L. S., Alvim, M. K. M., Moloney, P., Altmann, A., Martins Custodio, H., Heide, E.-C., Sinha, N., Ballerini, A., Absil, J., . . . McDonald, C. R. (2026). Disease detection and classification in temporal lobe epilepsy: step-wise versus simultaneous AI decision models in a multisite neuroimaging study. Brain communications, 8(4), fcag253. https://
BibTeX
@article{kaestner2026dis
author = {Kaestner, Erik and Sawant, Jay and Arienzo, Donatello and Hasenstab, Kyle A and Gleichgerrcht, Ezequiel and Gholipour, Taha and Abrol, Anees and Hassanzadeh, Reihaneh and Thomopoulos, Sophia I and Yasuda, Clarissa L and Silva, Lucas Scárdua and Alvim, Marina K M and Moloney, Patrick and Altmann, Andre and Martins Custodio, Helena and Heide, Ev-Christin and Sinha, Nishant and Ballerini, Alice and Absil, Julie and Larivière, Sara and Schubert, Kai M and Ferreira-Atuesta, Carolina and Duma, Gian Marco and Christin, Raphaël and Barbi, Elisa and Guerrini, Renzo and Rüber, Theodor and Bauer, Tobias and Sinclair, Benjamin and Bunyamin, Jacob and Courtney, Merran R and Law, Meng and Labate, Angelo and Striano, Pasquale and Vivash, Lucy and O’Brien, Terence J and Lenge, Matteo and Saba, Luca and Kleen, Jonathan K and Bonanni, Paolo and Sepeta, Leigh N and Galovic, Marian and Bartolini, Emanuele and Ives-Deliperi, Victoria and Bernhardt, Boris C and Martin, Pascal and Depondt, Chantal and Stoub, Travis and Vaudano, Anna Elisabetta and Meletti, Stefano and Kuzniecky, Ruben and Concha, Luis and Bagić, Anto I and Davis, Kathryn A and Staba, Richard J and Focke, Niels K N and Pardoe, Heath and Dugan, Patricia C and Devinsky, Orrin and Drane, Daniel L and Zhang, Zhiqiang and Gambardella, Antonio and Parashos, Alexandra and Cendes, Fernando and Thompson, Paul M and Sisodiya, Sanjay M and Calhoun, Vince D and Bonilha, Leonardo and McDonald, Carrie R},
title = {{Disease detection and classification in temporal lobe epilepsy: step-wise versus simultaneous AI decision models in a multisite neuroimaging study}},
journal = {Brain communications},
year = {2026},
month = jun,
volume = {8},
number = {4},
pages = {fcag253},
publisher = {Oxford University Press},
issn = {2632-1297},
doi = {10.1093/
url = {https://
pmid = {42534493},
pmcid = {PMC13421366}
}
RIS
TY - JOUR
AU - Kaestner, Erik
AU - Sawant, Jay
AU - Arienzo, Donatello
AU - Hasenstab, Kyle A
AU - Gleichgerrcht, Ezequiel
AU - Gholipour, Taha
AU - Abrol, Anees
AU - Hassanzadeh, Reihaneh
AU - Thomopoulos, Sophia I
AU - Yasuda, Clarissa L
AU - Silva, Lucas Scárdua
AU - Alvim, Marina K M
AU - Moloney, Patrick
AU - Altmann, Andre
AU - Martins Custodio, Helena
AU - Heide, Ev-Christin
AU - Sinha, Nishant
AU - Ballerini, Alice
AU - Absil, Julie
AU - Larivière, Sara
AU - Schubert, Kai M
AU - Ferreira-Atuesta, Carolina
AU - Duma, Gian Marco
AU - Christin, Raphaël
AU - Barbi, Elisa
AU - Guerrini, Renzo
AU - Rüber, Theodor
AU - Bauer, Tobias
AU - Sinclair, Benjamin
AU - Bunyamin, Jacob
AU - Courtney, Merran R
AU - Law, Meng
AU - Labate, Angelo
AU - Striano, Pasquale
AU - Vivash, Lucy
AU - O’Brien, Terence J
AU - Lenge, Matteo
AU - Saba, Luca
AU - Kleen, Jonathan K
AU - Bonanni, Paolo
AU - Sepeta, Leigh N
AU - Galovic, Marian
AU - Bartolini, Emanuele
AU - Ives-Deliperi, Victoria
AU - Bernhardt, Boris C
AU - Martin, Pascal
AU - Depondt, Chantal
AU - Stoub, Travis
AU - Vaudano, Anna Elisabetta
AU - Meletti, Stefano
AU - Kuzniecky, Ruben
AU - Concha, Luis
AU - Bagić, Anto I
AU - Davis, Kathryn A
AU - Staba, Richard J
AU - Focke, Niels K N
AU - Pardoe, Heath
AU - Dugan, Patricia C
AU - Devinsky, Orrin
AU - Drane, Daniel L
AU - Zhang, Zhiqiang
AU - Gambardella, Antonio
AU - Parashos, Alexandra
AU - Cendes, Fernando
AU - Thompson, Paul M
AU - Sisodiya, Sanjay M
AU - Calhoun, Vince D
AU - Bonilha, Leonardo
AU - McDonald, Carrie R
TI - Disease detection and classification in temporal lobe epilepsy: step-wise versus simultaneous AI decision models in a multisite neuroimaging study
T2 - Brain communications
J2 - Brain Commun
PY - 2026
DA - 2026/
VL - 8
IS - 4
SP - fcag253
SN - 2632-1297
PB - Oxford University Press
DO - 10.1093/
UR - https://
LA - en
ER -
CSL-JSON
{
"id": "10.1093/
"type": "article-journal",
"title": "Disease detection and classification in temporal lobe epilepsy: step-wise versus simultaneous AI decision models in a multisite neuroimaging study",
"container-title": "Brain communications",
"author": [
{
"family": "Kaestner",
"given": "Erik"
},
{
"family": "Sawant",
"given": "Jay"
},
{
"family": "Arienzo",
"given": "Donatello"
},
{
"family": "Hasenstab",
"given": "Kyle A"
},
{
"family": "Gleichgerrcht",
"given": "Ezequiel"
},
{
"family": "Gholipour",
"given": "Taha"
},
{
"family": "Abrol",
"given": "Anees"
},
{
"family": "Hassanzadeh",
"given": "Reihaneh"
},
{
"family": "Thomopoulos",
"given": "Sophia I"
},
{
"family": "Yasuda",
"given": "Clarissa L"
},
{
"family": "Silva",
"given": "Lucas Scárdua"
},
{
"family": "Alvim",
"given": "Marina K M"
},
{
"family": "Moloney",
"given": "Patrick"
},
{
"family": "Altmann",
"given": "Andre"
},
{
"family": "Martins Custodio",
"given": "Helena"
},
{
"family": "Heide",
"given": "Ev-Christin"
},
{
"family": "Sinha",
"given": "Nishant"
},
{
"family": "Ballerini",
"given": "Alice"
},
{
"family": "Absil",
"given": "Julie"
},
{
"family": "Larivière",
"given": "Sara"
},
{
"family": "Schubert",
"given": "Kai M"
},
{
"family": "Ferreira-Atuesta",
"given": "Carolina"
},
{
"family": "Duma",
"given": "Gian Marco"
},
{
"family": "Christin",
"given": "Raphaël"
},
{
"family": "Barbi",
"given": "Elisa"
},
{
"family": "Guerrini",
"given": "Renzo"
},
{
"family": "Rüber",
"given": "Theodor"
},
{
"family": "Bauer",
"given": "Tobias"
},
{
"family": "Sinclair",
"given": "Benjamin"
},
{
"family": "Bunyamin",
"given": "Jacob"
},
{
"family": "Courtney",
"given": "Merran R"
},
{
"family": "Law",
"given": "Meng"
},
{
"family": "Labate",
"given": "Angelo"
},
{
"family": "Striano",
"given": "Pasquale"
},
{
"family": "Vivash",
"given": "Lucy"
},
{
"family": "O’Brien",
"given": "Terence J"
},
{
"family": "Lenge",
"given": "Matteo"
},
{
"family": "Saba",
"given": "Luca"
},
{
"family": "Kleen",
"given": "Jonathan K"
},
{
"family": "Bonanni",
"given": "Paolo"
},
{
"family": "Sepeta",
"given": "Leigh N"
},
{
"family": "Galovic",
"given": "Marian"
},
{
"family": "Bartolini",
"given": "Emanuele"
},
{
"family": "Ives-Deliperi",
"given": "Victoria"
},
{
"family": "Bernhardt",
"given": "Boris C"
},
{
"family": "Martin",
"given": "Pascal"
},
{
"family": "Depondt",
"given": "Chantal"
},
{
"family": "Stoub",
"given": "Travis"
},
{
"family": "Vaudano",
"given": "Anna Elisabetta"
},
{
"family": "Meletti",
"given": "Stefano"
},
{
"family": "Kuzniecky",
"given": "Ruben"
},
{
"family": "Concha",
"given": "Luis"
},
{
"family": "Bagić",
"given": "Anto I"
},
{
"family": "Davis",
"given": "Kathryn A"
},
{
"family": "Staba",
"given": "Richard J"
},
{
"family": "Focke",
"given": "Niels K N"
},
{
"family": "Pardoe",
"given": "Heath"
},
{
"family": "Dugan",
"given": "Patricia C"
},
{
"family": "Devinsky",
"given": "Orrin"
},
{
"family": "Drane",
"given": "Daniel L"
},
{
"family": "Zhang",
"given": "Zhiqiang"
},
{
"family": "Gambardella",
"given": "Antonio"
},
{
"family": "Parashos",
"given": "Alexandra"
},
{
"family": "Cendes",
"given": "Fernando"
},
{
"family": "Thompson",
"given": "Paul M"
},
{
"family": "Sisodiya",
"given": "Sanjay M"
},
{
"family": "Calhoun",
"given": "Vince D"
},
{
"family": "Bonilha",
"given": "Leonardo"
},
{
"family": "McDonald",
"given": "Carrie R"
}
],
"container-title-short":
"volume": "8",
"issue": "4",
"page": "fcag253",
"DOI": "10.1093/
"PMID": "42534493",
"PMCID": "PMC13421366",
"ISSN": "2632-1297",
"publisher": "Oxford University Press",
"URL": "https://
"language": "en",
"issued": {
"date-parts": [
[
2026,
6,
29
]
]
}
}
The tracing map gets a citation of its own once an author has validated it and it has a DOI.
Similar papers
The papers with a page that share the most with this one: the tools found in their code, their categories, datasets, cited references and authors, the rarest counting most.
- [1] doi:10.1007/s00234-026-04103-8 [code]
- Enhanced detection of subtle cortical abnormalities in focal epilepsy using 7 T MRI surface-based models and graph neural networks.Journal: NeuroradiologyIn common: Pillow, PyTorch, seaborn, 5 other tools, epilepsy, structural MRI / diffusion, 2 references, author Matteo Lenge
- [2] doi:10.1002/ana.78251
- Continuous Versus Short EEG After Ischemic Stroke: What cEEG Adds for Detecting Abnormalities and Predicting Post-Stroke Epilepsy.Journal: Annals of neurologyIn common: epilepsy, clinical / translational, 2 authors
- [3] doi:10.1038/s43856-026-01817-x [code]
- Visual prompt engineering for multimodal and irregularly sampled medical data.Journal: Communications medicineIn common: Hugging Face Transformers, OpenCV, Pillow, 7 other tools, clinical / translational
- [4] doi:10.1038/s41598-026-50791-w [code]
- Hybrid Vi+ECNN framework for advanced ADHD diagnostic accuracy in medical imaging.Journal: Scientific reportsIn common: Hugging Face Transformers, OpenCV, Pillow, 6 other tools, structural MRI / diffusion, clinical / translational
- [5] doi:10.1038/s41593-026-02258-4 [code]
- Laminar organization of cellular microcircuits modulating human interictal epileptiform discharges.Journal: Nature neuroscienceIn common: seaborn, scikit-learn, pandas, 3 other tools, epilepsy, author Jonathan K Kleen
- [6] doi:10.1038/s42003-026-10957-8 [code]
- Brain defence by the extracellular matrix protein Cochlin.Journal: Communications biologyIn common: Hugging Face Transformers, OpenCV, Pillow, 7 other tools
- [7] doi:10.1038/s41467-026-76837-1 [code]
- Drug screen and machine learning predict neuroprotective agents in a preclinical human model of childhood dementia.Journal: Nature communicationsIn common: Hugging Face Transformers, OpenCV, Pillow, 7 other tools
- [8] doi:10.1038/s41467-026-74358-5 [code]
- Brain-inspired spatial intelligence for embodied agents.Journal: Nature communicationsIn common: Hugging Face Transformers, OpenCV, Pillow, 7 other tools
- [9] doi:10.7554/elife.107933 [code]
- Modality-agnostic decoding of vision and language from fMRI.Journal: eLifeIn common: Hugging Face Transformers, OpenCV, Pillow, 7 other tools
- [10] doi:10.1002/hbm.70469 [code]
- VarCoNet: A Variability-Aware Self-Supervised Framework for Functional Connectome Extraction From Resting-State fMRI.Journal: Human brain mappingIn common: Hugging Face Transformers, OpenCV, Pillow, 7 other tools
Contribute
The authors of this paper can claim it, correct its record and validate its tracing map, and the maintainers of its code (its owner, or a public member of its organization) correct what it says of their repository; anyone signed in can ask for its removal. Every request goes to OSCR's own machine, which answers it; your account page follows them.
Sign in with ORCID to claim this paper as one of its authors, correct its record or validate its tracing map: when the paper's metadata lists your ORCID iD, you are recognized at once. Maintainers of its code: sign in with GitHub, then claim the repository on your account page.
Claim this paper
Correct its record
Say what each link of this record is, remove the ones that are not the paper's, add the ones that are missing. The correction becomes a new version of the record, in its Versions section.
Validate its tracing map
You validate the map as this page shows it: 1 repository of the authors' code, each at its verified commit and with its license, 272 scripts, and 4 matches between paragraphs and code (see the Code and Map sections). It then receives a DOI on Zenodo, with you (your ORCID iD) and OSCR as its creators; the code itself is not deposited.
The map's fingerprint: sha256:fd3f64c4cdc9979b…
Add the badge to its README
The badge links the code to this page. Copy one of these into the README of the paper's code: only you decide where it goes, and nothing is changed for you.
Markdown
[, paste the snippet at the top, then “Commit changes…” and, to review it first, “Create a new branch and start a pull request”. You open the pull request; OSCR asks for no permission.
Request its removal
To ask OSCR to remove this record, the copies of its authors' scripts or its tracing map, use the removal request page: signed in, you say who you are, what to remove and why, then review and confirm the request. Published rules decide every request (how).
Discussion, reproductions, activity
Discussion: questions and error reports about this paper and its code, from signed-in readers and its authors. It opens with sign-in.
Reproductions: reports from readers who ran the authors' code: what they reproduced, with which environment, commit and data. It opens with sign-in.
Activity: what happens around this paper: new versions of its record, its map's validation, discussions and reproductions. It opens with sign-in.
