diff --git a/README.md b/README.md
index 6405f189..7d27448e 100644
--- a/README.md
+++ b/README.md
@@ -200,7 +200,7 @@ Explore the explanations of your trained model using the DIANNA dashboard (for n
_Dianna dashboard screenshot here_
## Datasets
@@ -260,9 +260,10 @@ And here are links to notebooks showing how we created our models on the benchma
### Text
-| Models | Generation |
-| :---------------------------------------------------- | :---------------------------------------------------------------------------------------------------------------------------------------------------------------------- |
-| [Movie reviews model](https://zenodo.org/record/5910598) | [Stanford sentiment treebank model generation](https://github.com/dianna-ai/dianna-exploration/blob/main/example_data/model_generation/movie_reviews/generate_model.ipynb) |
+| Models | Generation |
+|:---------------------------------------------------------------------|:---------------------------------------------------------------------------------------------------------------------------------------------------------------------------|
+| [Movie reviews model](https://zenodo.org/record/5910598) | [Stanford sentiment treebank model generation](https://github.com/dianna-ai/dianna-exploration/blob/main/example_data/model_generation/movie_reviews/generate_model.ipynb) |
+| [Regalatory statement classifier](https://zenodo.org/record/8200001) | [EU-law regulatory-statement-classification](https://github.com/nature-of-eu-rules/regulatory-statement-classification) |
### Time series
@@ -306,13 +307,13 @@ Also [GradCAM](https://openaccess.thecvf.com/content_ICCV_2017/papers/Selvaraju_
Our goal is that the scientific community embrases XAI as a source for novel and unexplored perspectives on scientific problems.
Here, we offer [tutorials](./tutorials) on specific scientific use-cases of uisng XAI:
-| Use-case (data) \ XAI | [RISE](http://bmvc2018.org/contents/papers/1064.pdf) | [LIME](https://www.kdd.org/kdd2016/papers/files/rfp0573-ribeiroA.pdf) | [KernelSHAP](https://proceedings.neurips.cc/paper/2017/file/8a20a8621978632d76c43dfd28b67767-Paper.pdf) |
-| :----------------------------------------------------------------| :----------------------------------------------------| :---------------------------------------------------------------------| :-------------------------------------------------------------------------------------------------------|
-| Biology (Phytomorphology): Tree Leaves classification (images) | | ✅ | |
-| Astronomy: Fast Radio Burst detection (timeseries) | ✅ | | |
-| Land-atmosphere modeling: Latent heat flux prediction (tabular) | | | ✅ |
-| Social sciences (text) | work in progress | ... |... |
-| Climate | planned | ... | ... |
+| Use-case (data) \ XAI | [RISE](http://bmvc2018.org/contents/papers/1064.pdf) | [LIME](https://www.kdd.org/kdd2016/papers/files/rfp0573-ribeiroA.pdf) | [KernelSHAP](https://proceedings.neurips.cc/paper/2017/file/8a20a8621978632d76c43dfd28b67767-Paper.pdf) |
+|:-------------------------------------------------------------------|:-----------------------------------------------------| :---------------------------------------------------------------------| :-------------------------------------------------------------------------------------------------------|
+| Biology (Phytomorphology): Tree Leaves classification (images) | | ✅ | |
+| Astronomy: Fast Radio Burst detection (timeseries) | ✅ | | |
+| Land-atmosphere modeling: Latent heat flux prediction (tabular) | | | ✅ |
+| Social sciences: EU-law regulatory statement classification (text) | | ✅ | |
+| Climate | planned | ... | ... |
## Reference documentation
diff --git a/dianna/utils/downloader.py b/dianna/utils/downloader.py
index e4a9cef6..ab1a02d3 100644
--- a/dianna/utils/downloader.py
+++ b/dianna/utils/downloader.py
@@ -59,6 +59,10 @@
"doi:10.5281/zenodo.10656613/apertif_frb_dynamic_spectrum_model.onnx",
"sha256:3c87db3c6257d7f251a7bdceb3197d5bb482b8edc19870219fb7ca7c204dd257"
],
+ "inlegal_bert_xgboost_classifier.json": [
+ "doi:10.5281/zenodo.8200001/inlegal_bert_xgboost_classifier.json",
+ "68a672f29aac4a19c404c24f4c5da82a1ce7f704ccce701b0a1073c63730e127"
+ ],
"stemmus_scope_emulator_model_LEtot.onnx": [
"doi:10.5281/zenodo.12623256/stemmus_scope_emulator_model_LEtot.onnx",
"sha256:8c8f34ad5a2c519b1f3c67a4eb0c645c96cac1de166277bfb24e7887c2ce83be"
diff --git a/docs/tutorials/9-lime_text_eulaw.nblink b/docs/tutorials/9-lime_text_eulaw.nblink
new file mode 100644
index 00000000..36a5f475
--- /dev/null
+++ b/docs/tutorials/9-lime_text_eulaw.nblink
@@ -0,0 +1,3 @@
+{
+ "path": "../../tutorials/explainers/LIME/lime_text_eulaw.ipynb"
+}
diff --git a/setup.cfg b/setup.cfg
index c6f808b4..5c30f7c6 100644
--- a/setup.cfg
+++ b/setup.cfg
@@ -112,6 +112,9 @@ notebooks =
torch
torchvision
ipywidgets
+ freetype-py
+ transformers
+ xgboost
[options.entry_points]
console_scripts =
diff --git a/tutorials/README.md b/tutorials/README.md
index 16fd2ac5..9890ce6c 100644
--- a/tutorials/README.md
+++ b/tutorials/README.md
@@ -18,23 +18,23 @@ pip install .[notebooks]
#### Illustrative (Simple)
-|*Data modality*|Dataset|*Task*|Logo|
-|:------------|:------|:---|:----|
-|*Images*|Binary MNIST | Binary digit *classification*| |
-||[Simple Geometric (circles and triangles)](https://doi.org/10.5281/zenodo.5012824)| Binary shape *classificaiton* ||
-||[Imagenet](https://image-net.org/download.php) |$1000$ classes natural images *classificaiton* | |
-|*Text*| [Stanford sentiment treebank](https://nlp.stanford.edu/sentiment/index.html) |Positive or negative movie reviews sentiment *classificaiton* | |
-|*Timeseries* | [Coffee dataset](https://www.timeseriesclassification.com/description.php?Dataset=Coffee) | Binary *classificaiton* of Robusta and Aribica coffee beans| |
-| | [Weather dataset](https://zenodo.org/record/7525955) |Binary *classification* (warm/cold season) of temperature time-series ||
-|*Tabular*| [Penguin dataset](https://www.kaggle.com/code/parulpandey/penguin-dataset-the-new-iris)| $3$ penguin spicies (Adele, Chinstrap, Gentoo) *classificaiton* | | |
-| | [Weather dataset](https://zenodo.org/record/7525955) | Next day sunshine hours prediction (*regression*) | |
+|*Data modality*|Dataset| *Task* |Logo|
+|:------------|:------|:----------------------------------------------------------------------|:----|
+|*Images*|Binary MNIST | Binary digit *classification* | |
+||[Simple Geometric (circles and triangles)](https://doi.org/10.5281/zenodo.5012824)| Binary shape *classificaiton* ||
+||[Imagenet](https://image-net.org/download.php) | $1000$ classes natural images *classificaiton* | |
+|*Text*| [Stanford sentiment treebank](https://nlp.stanford.edu/sentiment/index.html) | Positive or negative movie reviews sentiment *classification* | |
+|*Timeseries* | [Coffee dataset](https://www.timeseriesclassification.com/description.php?Dataset=Coffee) | Binary *classificaiton* of Robusta and Aribica coffee beans | |
+| | [Weather dataset](https://zenodo.org/record/7525955) | Binary *classification* (warm/cold season) of temperature time-series ||
+|*Tabular*| [Penguin dataset](https://www.kaggle.com/code/parulpandey/penguin-dataset-the-new-iris)| $3$ penguin spicies (Adele, Chinstrap, Gentoo) *classificaiton* | | |
+| | [Weather dataset](https://zenodo.org/record/7525955) | Next day sunshine hours prediction (*regression*) | |
#### Scientific use-cases
|*Data modality*|Dataset|*Task*|Logo|
|:------------|:------|:---|:----|
|*Images*|[Simple Scientific (LeafSnap30)](https://zenodo.org/record/5061353/)| $30$ tree species leaves *classification* | |
-|*Text*| | | |
+|*Text*| [EU-law statements](https://zenodo.org/records/8200001) | Regulatory or non-regulatory *classification* | |
|*Timeseries* | Fast Radio Burst (FRB) dataset (not publicly available) | Binary *classificaiton* of Fast Radio Burst (FRB) timeseries data : noise or a real FRB. | |
|*Tabular*| [Land atmosphere dataset](https://zenodo.org/records/12623257)| Prediction of "latent heat flux" (*regression*). The random forest model is used as an [emulator](https://github.com/EcoExtreML/Emulator) to replace the physical model [STEMMUS_SCOPE](https://github.com/EcoExtreML/STEMMUS_SCOPE) to predict global maps of latent heat flux. | |
@@ -59,12 +59,13 @@ To learn more about how we aproach the masking for time-series data, please read
#### Scientific use-cases
-|*Modality* \ Method|RISE|[LIME](https://youtu.be/d6j6bofhj2M)|Kernel[SHAP](https://youtu.be/9haIOplEIGM)|
-|:-----|:---|:---|:---|
-|*Images*| | [](./explainers/LIME/lime_images.ipynb) or [![Open In Colab](https://colab.research.google.com/assets/colab-badge.svg)](https://colab.research.google.com/github/dianna-ai/dianna/blob/main/tutorials/explainers/LIME/lime_images.ipynb) ||
-|*Text* | | | |
-| *Time series*| [](./explainers/RISE/rise_timeseries_frb.ipynb) or [![Open In Colab](https://colab.research.google.com/assets/colab-badge.svg)](https://colab.research.google.com/github/dianna-ai/dianna/blob/main/tutorials/explainers/RISE/rise_timeseries_frb.ipynb) | |
-| *Tabular* | | |[](./explainers/KernelSHAP/kernelshap_tabular_land_atmosphere.ipynb) or [![Open In Colab](https://colab.research.google.com/assets/colab-badge.svg)](https://colab.research.google.com/github/dianna-ai/dianna/blob/main/tutorials/explainers/KernelSHAP/kernelshap_tabular_land_atmosphere.ipynb)|
+| *Modality* \ Method |RISE| [LIME](https://youtu.be/d6j6bofhj2M) |Kernel[SHAP](https://youtu.be/9haIOplEIGM)|
+|:--------------------|:---|:---------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------|:---|
+| *Images* | | [](./explainers/LIME/lime_images.ipynb) or [![Open In Colab](https://colab.research.google.com/assets/colab-badge.svg)](https://colab.research.google.com/github/dianna-ai/dianna/blob/main/tutorials/explainers/LIME/lime_images.ipynb) ||
+| | | [](./explainers/RISE/rise_timeseries_frb.ipynb) or [![Open In Colab](https://colab.research.google.com/assets/colab-badge.svg)](https://colab.research.google.com/github/dianna-ai/dianna/blob/main/tutorials/explainers/RISE/rise_timeseries_frb.ipynb) | |
+| *Text* | | [](./explainers/LIME/lime_text_eulaw.ipynb) or [![Open In Colab](https://colab.research.google.com/assets/colab-badge.svg)](https://colab.research.google.com/github/dianna-ai/dianna/blob/main/tutorials/explainers/LIME/lime_text_eulaw.ipynb) | |
+| *Time series* | [](./explainers/RISE/rise_timeseries_frb.ipynb) or [![Open In Colab](https://colab.research.google.com/assets/colab-badge.svg)](https://colab.research.google.com/github/dianna-ai/dianna/blob/main/tutorials/explainers/RISE/rise_timeseries_frb.ipynb) | |
+| *Tabular* | | |[](./explainers/KernelSHAP/kernelshap_tabular_land_atmosphere.ipynb) or [![Open In Colab](https://colab.research.google.com/assets/colab-badge.svg)](https://colab.research.google.com/github/dianna-ai/dianna/blob/main/tutorials/explainers/KernelSHAP/kernelshap_tabular_land_atmosphere.ipynb)|
### IMPORTANT: Hyperparameters
The XAI methods (explainers) are sensitive to the choice of their hyperparameters! In this [master Thesis](https://staff.fnwi.uva.nl/a.s.z.belloum/MSctheses/MScthesis_Willem_van_der_Spec.pdf), this sensitivity is researched and useful conclusions are drawn.
@@ -85,13 +86,13 @@ Also the main conclusions (🠊) from the thesis (on images and text) about the
🠊 Larger $n_masks$ will return more consistent results at the cost of computation time. If 2 identical runs yield (very) different results, these will likely contain a lot of (or even mostly) noise and a higher value for $n_masks$ should be used instead.
#### LIME
-| Hyperparameter | Default value | (*i*) | (*ts*)| (*ts*)|
-| ------------- | ------------- |--------| -----| -----|
-| $n_{samples}$ | **$5000$** | $1000$ | $10 000$| $500$|
-| *Kernel Width* | **$25$**| default | default| default|
-| $n_{features}$ | **$10$** | $30$ | default| default|
+| Hyperparameter | Default value | (*i*) | (*ts*)| (*ts*)| [](./explainers/LIME/lime_text_eulaw.ipynb) |
+| ------------- | ------------- |--------| -----| -----|-------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------|
+| $n_{samples}$ | **$5000$** | $1000$ | $10 000$| $500$| 2000 |
+| *Kernel Width* | **$25$**| default | default| default| default |
+| $n_{features}$ | **$10$** | $30$ | default| default| 999 |
-🠊 The most crucial parameter is the *Kernel width*: low values cause high sensitivity, however that observaiton was dependant on the evaluaiton metric.
+🠊 The most crucial parameter is the *Kernel width*: low values cause high sensitivity, however that observation was dependent on the evaluation metric.
#### KernelSHAP
| Hyperparameter | Default value | (*i*)| (*i*) | (*tab*) |
diff --git a/tutorials/explainers/LIME/lime_text_eulaw.ipynb b/tutorials/explainers/LIME/lime_text_eulaw.ipynb
new file mode 100644
index 00000000..a4c1780c
--- /dev/null
+++ b/tutorials/explainers/LIME/lime_text_eulaw.ipynb
@@ -0,0 +1,1048 @@
+{
+ "cells": [
+ {
+ "cell_type": "markdown",
+ "id": "african-verse",
+ "metadata": {},
+ "source": [
+ "\n",
+ "\n",
+ "### Interpreting the sentence classification of legal texts using LIME\n",
+ "\n",
+ "LIME (Local Interpretable Model-agnostic Explanations) is an explainable-AI method that aims to create an interpretable model that locally represents the classifier. For more details see the [LIME paper](https://arxiv.org/abs/1602.04938).\n",
+ "\n",
+ "This notebook demonstrates how to use the LIME explainable-AI method in [DIANNA](https://github.com/dianna-ai/dianna) to explain a text classification model created as part of the [Nature of EU Rules project](https://research-software-directory.org/projects/the-nature-of-eu-rules-strict-and-detailed-or-lacking-bite). The model is used to perform binary classification of individual sentences from EU legislation to determine whether they specify a regulation or not (i.e., whether they specify a legal obligation or prohibition that some legal entity should comply with). [Here's an example](https://eur-lex.europa.eu/legal-content/EN/TXT/HTML/?uri=CELEX:32012R1215&qid=1724343987254) of what an EU legislative document looks like.\n",
+ "\n",
+ "##### Regulatory sentence example:\n",
+ "\n",
+ "```Citizens of all Member States shall separate their recyclables before disposing of trash, or else face a fine.```\n",
+ "\n",
+ "##### Non-regulatory (constitutive) sentence example:\n",
+ "\n",
+ "```This Regulation shall apply in civil and commercial matters whatever the nature of the court or tribunal.```\n",
+ "\n",
+ "**Note:** while the occurrence of words like ``shall`` and ``must`` (which are called **deontic** words) are necessary condition for a sentence to be classified as regulatory, they are not a sufficient condition."
+ ]
+ },
+ {
+ "cell_type": "markdown",
+ "id": "fa6d17b0",
+ "metadata": {},
+ "source": [
+ "#### Colab Setup"
+ ]
+ },
+ {
+ "cell_type": "code",
+ "execution_count": 1,
+ "id": "471630ff",
+ "metadata": {
+ "ExecuteTime": {
+ "end_time": "2024-07-10T11:13:59.624761Z",
+ "start_time": "2024-07-10T11:13:59.563650Z"
+ }
+ },
+ "outputs": [],
+ "source": [
+ "running_in_colab = 'google.colab' in str(get_ipython())\n",
+ "if running_in_colab:\n",
+ " # install dianna\n",
+ " !python3 -m pip install dianna[notebooks]"
+ ]
+ },
+ {
+ "cell_type": "markdown",
+ "id": "a5cf6f82-c1c7-4814-ae0f-5a1c0b8578f6",
+ "metadata": {},
+ "source": [
+ "#### 0. Imports and paths"
+ ]
+ },
+ {
+ "cell_type": "code",
+ "execution_count": 2,
+ "id": "34b556d8-5337-44dc-8efe-14d1dff6f011",
+ "metadata": {
+ "ExecuteTime": {
+ "end_time": "2024-07-10T11:13:59.625762Z",
+ "start_time": "2024-07-10T11:13:59.568658Z"
+ }
+ },
+ "outputs": [
+ {
+ "name": "stderr",
+ "output_type": "stream",
+ "text": [
+ "C:\\Users\\ChristiaanMeijer\\anaconda3\\envs\\dianna3112\\Lib\\site-packages\\torchtext\\data\\__init__.py:4: UserWarning: \n",
+ "/!\\ IMPORTANT WARNING ABOUT TORCHTEXT STATUS /!\\ \n",
+ "Torchtext is deprecated and the last released version will be 0.18 (this one). You can silence this warning by calling the following at the beginnign of your scripts: `import torchtext; torchtext.disable_torchtext_deprecation_warning()`\n",
+ " warnings.warn(torchtext._TORCHTEXT_DEPRECATION_MSG)\n"
+ ]
+ }
+ ],
+ "source": [
+ "from torch.utils.data import DataLoader\n",
+ "from typing import Iterable\n",
+ "from tqdm import tqdm\n",
+ "\n",
+ "import numpy as np\n",
+ "import pandas as pd\n",
+ "import torch\n",
+ "import xgboost\n",
+ "\n",
+ "import dianna\n",
+ "from dianna import visualization\n",
+ "from dianna.utils.downloader import download\n",
+ "from dianna.utils.tokenizers import SpacyTokenizer"
+ ]
+ },
+ {
+ "cell_type": "code",
+ "execution_count": 3,
+ "id": "c616916c-78ef-48d0-a744-b25b37b62a3f",
+ "metadata": {
+ "ExecuteTime": {
+ "end_time": "2024-07-10T11:13:59.666220Z",
+ "start_time": "2024-07-10T11:13:59.576245Z"
+ }
+ },
+ "outputs": [],
+ "source": [
+ "class_names = ['constitutive', 'regulatory']\n",
+ "model_path = download('inlegal_bert_xgboost_classifier.json', 'model')"
+ ]
+ },
+ {
+ "cell_type": "markdown",
+ "metadata": {
+ "collapsed": false
+ },
+ "source": [
+ "# 1 - Define test data"
+ ],
+ "id": "156805d0e3de333b"
+ },
+ {
+ "cell_type": "code",
+ "execution_count": 4,
+ "metadata": {
+ "ExecuteTime": {
+ "end_time": "2024-07-10T11:13:59.666220Z",
+ "start_time": "2024-07-10T11:13:59.590692Z"
+ },
+ "collapsed": false
+ },
+ "outputs": [],
+ "source": [
+ "constitutive_statement_0 = \"The purchase, import or transport from Syria of crude oil and petroleum products shall be prohibited.\"\n",
+ "constitutive_statement_1 = \"This Decision shall enter into force on the twentieth day following that of its publication in the Official Journal of the European Union.\"\n",
+ "regulatory_statement_0 = \"Where observations are submitted, or where substantial new evidence is presented, the Council shall review its decision and inform the person or entity concerned accordingly.\"\n",
+ "regulatory_statement_1 = \"The relevant Member State shall inform the other Member States of any authorisation granted under this Article.\"\n",
+ "regulatory_statement_2 = \"Member States shall cooperate, in accordance with their national legislation, with inspections and disposals undertaken pursuant to paragraphs 1 and 2.\""
+ ],
+ "id": "200b1ed56f4ddf5f"
+ },
+ {
+ "cell_type": "markdown",
+ "id": "bad4f5b1-6097-4ef3-98c4-78432ad640b0",
+ "metadata": {
+ "ExecuteTime": {
+ "end_time": "2024-03-21T10:28:34.466985Z",
+ "start_time": "2024-03-21T10:28:34.456937Z"
+ }
+ },
+ "source": [
+ "# 2 - Load and prepare the model\n",
+ "\n",
+ "The model is a combination of a pretrained transformer used as a feature extractor, with an XGBoost model trained on top. The following cells load the model into the variable called `model_runner`."
+ ]
+ },
+ {
+ "cell_type": "code",
+ "execution_count": 5,
+ "metadata": {
+ "ExecuteTime": {
+ "end_time": "2024-07-10T11:13:59.666763Z",
+ "start_time": "2024-07-10T11:13:59.598468Z"
+ },
+ "collapsed": false
+ },
+ "outputs": [],
+ "source": [
+ "from transformers import AutoTokenizer, AutoModel\n",
+ "def create_features(texts: list[str], model_tag=\"law-ai/InLegalBERT\") -> torch.Tensor:\n",
+ " \"\"\"Create features for a list of texts.\"\"\"\n",
+ " max_length = 512\n",
+ " tokenizer = AutoTokenizer.from_pretrained(model_tag)\n",
+ " model = AutoModel.from_pretrained(model_tag)\n",
+ "\n",
+ " def process_batch(batch: Iterable[str]):\n",
+ " cropped_texts = [text[:max_length] for text in batch]\n",
+ " encoded_inputs = tokenizer(cropped_texts, padding='longest', truncation=True, max_length=max_length,\n",
+ " return_tensors=\"pt\")\n",
+ " with torch.no_grad():\n",
+ " outputs = model(**encoded_inputs)\n",
+ " last_hidden_states = outputs.last_hidden_state\n",
+ " sentence_features = last_hidden_states.mean(dim=1)\n",
+ " return sentence_features\n",
+ "\n",
+ " dataloader = DataLoader(texts, batch_size=1) # batch size of 1 was quickest for my development machine\n",
+ " features = [process_batch(batch) for batch in tqdm(dataloader, desc=f'Creating features')]\n",
+ " return np.array(torch.cat(features, dim=0))\n",
+ "\n"
+ ],
+ "id": "e1a16e955d860d89"
+ },
+ {
+ "cell_type": "code",
+ "execution_count": 6,
+ "metadata": {
+ "ExecuteTime": {
+ "end_time": "2024-07-10T11:13:59.672763Z",
+ "start_time": "2024-07-10T11:13:59.606453Z"
+ },
+ "collapsed": false
+ },
+ "outputs": [],
+ "source": [
+ "models={}\n",
+ "def classify_texts(texts: list[str], model_path, return_proba: bool = False):\n",
+ " \"\"\"Classifies every text in a list of texts using the xgboost model stored in model_path.\n",
+ "\n",
+ " The xgboost model will be loaded and used to classify the texts. The texts however will first be processed by a\n",
+ " large language model which will do the feature extraction for every text. The classifications of the\n",
+ " xgboost model will be returned.\n",
+ " For training the xgboost model, see train_legalbert_xgboost.py.\n",
+ "\n",
+ " Parameters\n",
+ " ----------\n",
+ " texts\n",
+ " A list of strings of which each needs to be classified.\n",
+ " model_path\n",
+ " The path to a stored xgboost model\n",
+ " return_proba\n",
+ " return the probabilities of the model\n",
+ "\n",
+ " Returns\n",
+ " -------\n",
+ " List of classifications, one for every text in the list\n",
+ "\n",
+ " \"\"\"\n",
+ " features = create_features(texts)\n",
+ " if model_path not in models:\n",
+ " print(f'Loading model from {model_path}.')\n",
+ " model = xgboost.XGBClassifier()\n",
+ " model.load_model(model_path)\n",
+ " models[model_path] = model\n",
+ "\n",
+ " model = models[model_path]\n",
+ " if return_proba:\n",
+ " return model.predict_proba(features)\n",
+ " return model.predict(features)"
+ ],
+ "id": "9d8346a5f0e78d5d"
+ },
+ {
+ "cell_type": "code",
+ "execution_count": 7,
+ "id": "555842c5-3f82-4f63-93bb-696645d4b447",
+ "metadata": {
+ "ExecuteTime": {
+ "end_time": "2024-07-10T11:14:00.114564Z",
+ "start_time": "2024-07-10T11:13:59.617764Z"
+ }
+ },
+ "outputs": [],
+ "source": [
+ "class StatementClassifier:\n",
+ " def __init__(self):\n",
+ " self.tokenizer = SpacyTokenizer(name='en_core_web_sm')\n",
+ "\n",
+ " def __call__(self, sentences):\n",
+ " # ensure the input has a batch axis\n",
+ " if isinstance(sentences, str):\n",
+ " sentences = [sentences]\n",
+ "\n",
+ " probs = classify_texts(sentences, model_path, return_proba=True)\n",
+ "\n",
+ " return np.transpose([(probs[:, 0]), (1 - probs[:, 0])])\n",
+ "\n",
+ "model_runner = StatementClassifier()"
+ ]
+ },
+ {
+ "cell_type": "markdown",
+ "metadata": {
+ "collapsed": false
+ },
+ "source": [
+ "## Test the model"
+ ],
+ "id": "e6ae5f1011540179"
+ },
+ {
+ "cell_type": "code",
+ "execution_count": 8,
+ "metadata": {
+ "ExecuteTime": {
+ "end_time": "2024-07-10T11:14:01.218604Z",
+ "start_time": "2024-07-10T11:14:00.117563Z"
+ },
+ "collapsed": false
+ },
+ "outputs": [
+ {
+ "name": "stderr",
+ "output_type": "stream",
+ "text": [
+ "Creating features: 100%|██████████| 5/5 [00:01<00:00, 3.63it/s]"
+ ]
+ },
+ {
+ "name": "stdout",
+ "output_type": "stream",
+ "text": [
+ "Loading model from C:\\Users\\ChristiaanMeijer\\AppData\\Local\\dianna\\dianna\\Cache\\inlegal_bert_xgboost_classifier.json.\n"
+ ]
+ },
+ {
+ "name": "stderr",
+ "output_type": "stream",
+ "text": [
+ "\n"
+ ]
+ },
+ {
+ "data": {
+ "text/html": [
+ "
\n",
+ "\n",
+ "
\n",
+ " \n",
+ "
\n",
+ "
\n",
+ "
statement
\n",
+ "
prediction
\n",
+ "
actual
\n",
+ "
\n",
+ " \n",
+ " \n",
+ "
\n",
+ "
0
\n",
+ "
The purchase, import or transport from Syria o...
\n",
+ "
constitutive
\n",
+ "
constitutive
\n",
+ "
\n",
+ "
\n",
+ "
1
\n",
+ "
This Decision shall enter into force on the tw...
\n",
+ "
constitutive
\n",
+ "
constitutive
\n",
+ "
\n",
+ "
\n",
+ "
2
\n",
+ "
Where observations are submitted, or where sub...
\n",
+ "
regulatory
\n",
+ "
regulatory
\n",
+ "
\n",
+ "
\n",
+ "
3
\n",
+ "
The relevant Member State shall inform the oth...
\n",
+ "
regulatory
\n",
+ "
regulatory
\n",
+ "
\n",
+ "
\n",
+ "
4
\n",
+ "
Member States shall cooperate, in accordance w...
\n",
+ "
regulatory
\n",
+ "
regulatory
\n",
+ "
\n",
+ " \n",
+ "
\n",
+ "
"
+ ],
+ "text/plain": [
+ " statement prediction \\\n",
+ "0 The purchase, import or transport from Syria o... constitutive \n",
+ "1 This Decision shall enter into force on the tw... constitutive \n",
+ "2 Where observations are submitted, or where sub... regulatory \n",
+ "3 The relevant Member State shall inform the oth... regulatory \n",
+ "4 Member States shall cooperate, in accordance w... regulatory \n",
+ "\n",
+ " actual \n",
+ "0 constitutive \n",
+ "1 constitutive \n",
+ "2 regulatory \n",
+ "3 regulatory \n",
+ "4 regulatory "
+ ]
+ },
+ "execution_count": 8,
+ "metadata": {},
+ "output_type": "execute_result"
+ }
+ ],
+ "source": [
+ "statements = [constitutive_statement_0, constitutive_statement_1, regulatory_statement_0, regulatory_statement_1,\n",
+ " regulatory_statement_2]\n",
+ "actual_classes = [class_names[c] for c in [0,0,1,1,1]]\n",
+ "model_outputs = model_runner(statements)\n",
+ "predictioned_classes = [class_names[m] for m in np.argmax(model_outputs, axis=1)]\n",
+ "\n",
+ "pd.DataFrame({'statement': statements, 'prediction': predictioned_classes, 'actual': actual_classes})"
+ ],
+ "id": "d4a307a3437d15c"
+ },
+ {
+ "cell_type": "markdown",
+ "metadata": {
+ "collapsed": false
+ },
+ "source": [
+ "# 3 - Explain the model"
+ ],
+ "id": "fe8e935b18285991"
+ },
+ {
+ "cell_type": "markdown",
+ "metadata": {
+ "collapsed": false
+ },
+ "source": [
+ "## Set parameters for DIANNA"
+ ],
+ "id": "62e20ed610533a98"
+ },
+ {
+ "cell_type": "code",
+ "execution_count": 9,
+ "metadata": {
+ "ExecuteTime": {
+ "end_time": "2024-07-10T11:14:01.225117Z",
+ "start_time": "2024-07-10T11:14:01.221604Z"
+ },
+ "collapsed": false
+ },
+ "outputs": [
+ {
+ "name": "stdout",
+ "output_type": "stream",
+ "text": [
+ "label_of_interest is regulatory\n"
+ ]
+ }
+ ],
+ "source": [
+ "label_of_interest = 1\n",
+ "print('label_of_interest is', class_names[label_of_interest])\n",
+ "statement = regulatory_statement_1\n",
+ "num_samples = 2000\n",
+ "num_features = 999 # top n number of words to include in the attribution map\n",
+ "\n",
+ "def run_dianna(input_text):\n",
+ " return dianna.explain_text(model_runner, input_text, model_runner.tokenizer,\n",
+ " 'LIME', labels=[label_of_interest], num_samples=num_samples, num_features=num_features, )[0]"
+ ],
+ "id": "ada613b3b918ee2f"
+ },
+ {
+ "cell_type": "markdown",
+ "metadata": {
+ "collapsed": false
+ },
+ "source": [
+ "## Are the results stable with current parameters?\n",
+ "A crucial hyperparameter is the `num_samples` which is set above. Too few samples results in a noisy explanation. Too many, is computationally expensive. If repeated runs yield (very) different results, the number of samples is too low for the current setting (which includes data, model, sentence length and other xai parameters)."
+ ],
+ "id": "286e22d0c9d8f486"
+ },
+ {
+ "cell_type": "code",
+ "execution_count": 10,
+ "metadata": {
+ "ExecuteTime": {
+ "end_time": "2024-03-21T11:57:43.971896Z",
+ "start_time": "2024-03-21T11:57:43.921805Z"
+ },
+ "collapsed": false
+ },
+ "outputs": [
+ {
+ "name": "stderr",
+ "output_type": "stream",
+ "text": [
+ "Creating features: 100%|██████████| 2000/2000 [02:17<00:00, 14.55it/s]\n",
+ "Creating features: 100%|██████████| 2000/2000 [02:10<00:00, 15.32it/s]\n",
+ "Creating features: 100%|██████████| 2000/2000 [02:11<00:00, 15.16it/s]\n"
+ ]
+ },
+ {
+ "data": {
+ "text/html": [
+ "
\n",
+ "\n",
+ "
\n",
+ " \n",
+ "
\n",
+ "
\n",
+ "
The
\n",
+ "
relevant
\n",
+ "
Member
\n",
+ "
State
\n",
+ "
shall
\n",
+ "
inform
\n",
+ "
the
\n",
+ "
other
\n",
+ "
Member
\n",
+ "
States
\n",
+ "
of
\n",
+ "
any
\n",
+ "
authorisation
\n",
+ "
granted
\n",
+ "
under
\n",
+ "
this
\n",
+ "
Article
\n",
+ "
.
\n",
+ "
\n",
+ " \n",
+ " \n",
+ "
\n",
+ "
count
\n",
+ "
3.000000
\n",
+ "
3.000000
\n",
+ "
3.000000
\n",
+ "
3.000000
\n",
+ "
3.000000
\n",
+ "
3.000000
\n",
+ "
3.000000
\n",
+ "
3.000000
\n",
+ "
3.000000
\n",
+ "
3.000000
\n",
+ "
3.000000
\n",
+ "
3.000000
\n",
+ "
3.000000
\n",
+ "
3.000000
\n",
+ "
3.000000
\n",
+ "
3.000000
\n",
+ "
3.000000
\n",
+ "
3.000000
\n",
+ "
\n",
+ "
\n",
+ "
mean
\n",
+ "
0.066543
\n",
+ "
0.040031
\n",
+ "
0.060046
\n",
+ "
0.105916
\n",
+ "
0.192736
\n",
+ "
0.145457
\n",
+ "
0.078587
\n",
+ "
0.064191
\n",
+ "
0.089121
\n",
+ "
0.142354
\n",
+ "
0.114781
\n",
+ "
0.042535
\n",
+ "
0.065503
\n",
+ "
0.039873
\n",
+ "
0.091866
\n",
+ "
0.080870
\n",
+ "
0.054341
\n",
+ "
0.111533
\n",
+ "
\n",
+ "
\n",
+ "
std
\n",
+ "
0.027477
\n",
+ "
0.007484
\n",
+ "
0.022590
\n",
+ "
0.000665
\n",
+ "
0.008612
\n",
+ "
0.014756
\n",
+ "
0.004525
\n",
+ "
0.004821
\n",
+ "
0.011775
\n",
+ "
0.022780
\n",
+ "
0.011397
\n",
+ "
0.013324
\n",
+ "
0.010638
\n",
+ "
0.009031
\n",
+ "
0.003086
\n",
+ "
0.010070
\n",
+ "
0.020705
\n",
+ "
0.005437
\n",
+ "
\n",
+ "
\n",
+ "
min
\n",
+ "
0.049605
\n",
+ "
0.034720
\n",
+ "
0.038425
\n",
+ "
0.105155
\n",
+ "
0.182833
\n",
+ "
0.128693
\n",
+ "
0.073430
\n",
+ "
0.060816
\n",
+ "
0.075847
\n",
+ "
0.124168
\n",
+ "
0.102572
\n",
+ "
0.028173
\n",
+ "
0.059008
\n",
+ "
0.029511
\n",
+ "
0.088517
\n",
+ "
0.069802
\n",
+ "
0.038332
\n",
+ "
0.108313
\n",
+ "
\n",
+ "
\n",
+ "
25%
\n",
+ "
0.050691
\n",
+ "
0.035751
\n",
+ "
0.048323
\n",
+ "
0.105681
\n",
+ "
0.189868
\n",
+ "
0.139946
\n",
+ "
0.076934
\n",
+ "
0.061430
\n",
+ "
0.084527
\n",
+ "
0.129578
\n",
+ "
0.109601
\n",
+ "
0.036556
\n",
+ "
0.059365
\n",
+ "
0.036776
\n",
+ "
0.090501
\n",
+ "
0.076558
\n",
+ "
0.042649
\n",
+ "
0.108395
\n",
+ "
\n",
+ "
\n",
+ "
50%
\n",
+ "
0.051778
\n",
+ "
0.036782
\n",
+ "
0.058221
\n",
+ "
0.106208
\n",
+ "
0.196902
\n",
+ "
0.151198
\n",
+ "
0.080438
\n",
+ "
0.062044
\n",
+ "
0.093207
\n",
+ "
0.134989
\n",
+ "
0.116630
\n",
+ "
0.044939
\n",
+ "
0.059722
\n",
+ "
0.044042
\n",
+ "
0.092485
\n",
+ "
0.083314
\n",
+ "
0.046967
\n",
+ "
0.108477
\n",
+ "
\n",
+ "
\n",
+ "
75%
\n",
+ "
0.075011
\n",
+ "
0.042686
\n",
+ "
0.070857
\n",
+ "
0.106296
\n",
+ "
0.197688
\n",
+ "
0.153839
\n",
+ "
0.081166
\n",
+ "
0.065878
\n",
+ "
0.095758
\n",
+ "
0.151447
\n",
+ "
0.120885
\n",
+ "
0.049716
\n",
+ "
0.068751
\n",
+ "
0.045054
\n",
+ "
0.093540
\n",
+ "
0.086404
\n",
+ "
0.062345
\n",
+ "
0.113144
\n",
+ "
\n",
+ "
\n",
+ "
max
\n",
+ "
0.098245
\n",
+ "
0.048591
\n",
+ "
0.083493
\n",
+ "
0.106385
\n",
+ "
0.198473
\n",
+ "
0.156479
\n",
+ "
0.081894
\n",
+ "
0.069713
\n",
+ "
0.098308
\n",
+ "
0.167905
\n",
+ "
0.125140
\n",
+ "
0.054494
\n",
+ "
0.077780
\n",
+ "
0.046066
\n",
+ "
0.094595
\n",
+ "
0.089493
\n",
+ "
0.077724
\n",
+ "
0.117810
\n",
+ "
\n",
+ " \n",
+ "
\n",
+ "
"
+ ],
+ "text/plain": [
+ " The relevant Member State shall inform the \\\n",
+ "count 3.000000 3.000000 3.000000 3.000000 3.000000 3.000000 3.000000 \n",
+ "mean 0.066543 0.040031 0.060046 0.105916 0.192736 0.145457 0.078587 \n",
+ "std 0.027477 0.007484 0.022590 0.000665 0.008612 0.014756 0.004525 \n",
+ "min 0.049605 0.034720 0.038425 0.105155 0.182833 0.128693 0.073430 \n",
+ "25% 0.050691 0.035751 0.048323 0.105681 0.189868 0.139946 0.076934 \n",
+ "50% 0.051778 0.036782 0.058221 0.106208 0.196902 0.151198 0.080438 \n",
+ "75% 0.075011 0.042686 0.070857 0.106296 0.197688 0.153839 0.081166 \n",
+ "max 0.098245 0.048591 0.083493 0.106385 0.198473 0.156479 0.081894 \n",
+ "\n",
+ " other Member States of any authorisation \\\n",
+ "count 3.000000 3.000000 3.000000 3.000000 3.000000 3.000000 \n",
+ "mean 0.064191 0.089121 0.142354 0.114781 0.042535 0.065503 \n",
+ "std 0.004821 0.011775 0.022780 0.011397 0.013324 0.010638 \n",
+ "min 0.060816 0.075847 0.124168 0.102572 0.028173 0.059008 \n",
+ "25% 0.061430 0.084527 0.129578 0.109601 0.036556 0.059365 \n",
+ "50% 0.062044 0.093207 0.134989 0.116630 0.044939 0.059722 \n",
+ "75% 0.065878 0.095758 0.151447 0.120885 0.049716 0.068751 \n",
+ "max 0.069713 0.098308 0.167905 0.125140 0.054494 0.077780 \n",
+ "\n",
+ " granted under this Article . \n",
+ "count 3.000000 3.000000 3.000000 3.000000 3.000000 \n",
+ "mean 0.039873 0.091866 0.080870 0.054341 0.111533 \n",
+ "std 0.009031 0.003086 0.010070 0.020705 0.005437 \n",
+ "min 0.029511 0.088517 0.069802 0.038332 0.108313 \n",
+ "25% 0.036776 0.090501 0.076558 0.042649 0.108395 \n",
+ "50% 0.044042 0.092485 0.083314 0.046967 0.108477 \n",
+ "75% 0.045054 0.093540 0.086404 0.062345 0.113144 \n",
+ "max 0.046066 0.094595 0.089493 0.077724 0.117810 "
+ ]
+ },
+ "execution_count": 10,
+ "metadata": {},
+ "output_type": "execute_result"
+ }
+ ],
+ "source": [
+ "explanation_relevances = [run_dianna(statement) for i in range(3)]\n",
+ "sorted_relevances = [sorted(r, key=lambda t : t[1]) for r in explanation_relevances]\n",
+ "\n",
+ "pd.DataFrame([[r[2] for r in sr] for sr in sorted_relevances], columns=[r[0] for r in sorted_relevances[0]]).describe()"
+ ],
+ "id": "a9d3404a5edfe5c6"
+ },
+ {
+ "cell_type": "markdown",
+ "metadata": {
+ "collapsed": false
+ },
+ "source": [
+ "Seems quite stable with 2000 samples in LIME. We can now run DIANNA knowing results will contain mostly signal and not just noise."
+ ],
+ "id": "b9ecfcd2400ffc26"
+ },
+ {
+ "cell_type": "markdown",
+ "metadata": {
+ "collapsed": false
+ },
+ "source": [
+ "## Running the actual explainer\n",
+ "We run the explainer one more time (convenient for whoever skipped the last cell) and show the attributions. This time we run it on a single example statement from the test data. The output table displays attribution scores for each word in the sentence. Each score represents how important or relevant that particular word was for the model to assign it the specified class. The closer the attribution score is to 1 the more important the word is to the classification."
+ ],
+ "id": "847554ae856aa9bc"
+ },
+ {
+ "cell_type": "code",
+ "execution_count": 11,
+ "metadata": {
+ "ExecuteTime": {
+ "end_time": "2024-07-10T10:39:53.877421Z",
+ "start_time": "2024-07-10T10:38:41.095511Z"
+ },
+ "collapsed": false
+ },
+ "outputs": [
+ {
+ "name": "stderr",
+ "output_type": "stream",
+ "text": [
+ "Creating features: 100%|██████████| 2000/2000 [02:28<00:00, 13.44it/s]"
+ ]
+ },
+ {
+ "name": "stdout",
+ "output_type": "stream",
+ "text": [
+ "attributions for class regulatory\n"
+ ]
+ },
+ {
+ "name": "stderr",
+ "output_type": "stream",
+ "text": [
+ "\n"
+ ]
+ },
+ {
+ "data": {
+ "text/html": [
+ "
\n",
+ "\n",
+ "
\n",
+ " \n",
+ "
\n",
+ "
\n",
+ "
0
\n",
+ "
1
\n",
+ "
2
\n",
+ "
\n",
+ " \n",
+ " \n",
+ "
\n",
+ "
0
\n",
+ "
The
\n",
+ "
0
\n",
+ "
0.040727
\n",
+ "
\n",
+ "
\n",
+ "
1
\n",
+ "
relevant
\n",
+ "
1
\n",
+ "
0.063082
\n",
+ "
\n",
+ "
\n",
+ "
2
\n",
+ "
Member
\n",
+ "
2
\n",
+ "
0.065679
\n",
+ "
\n",
+ "
\n",
+ "
3
\n",
+ "
State
\n",
+ "
3
\n",
+ "
0.080698
\n",
+ "
\n",
+ "
\n",
+ "
4
\n",
+ "
shall
\n",
+ "
4
\n",
+ "
0.171471
\n",
+ "
\n",
+ "
\n",
+ "
5
\n",
+ "
inform
\n",
+ "
5
\n",
+ "
0.142825
\n",
+ "
\n",
+ "
\n",
+ "
6
\n",
+ "
the
\n",
+ "
6
\n",
+ "
0.073943
\n",
+ "
\n",
+ "
\n",
+ "
7
\n",
+ "
other
\n",
+ "
7
\n",
+ "
0.078633
\n",
+ "
\n",
+ "
\n",
+ "
8
\n",
+ "
Member
\n",
+ "
8
\n",
+ "
0.070353
\n",
+ "
\n",
+ "
\n",
+ "
9
\n",
+ "
States
\n",
+ "
9
\n",
+ "
0.132782
\n",
+ "
\n",
+ "
\n",
+ "
10
\n",
+ "
of
\n",
+ "
10
\n",
+ "
0.115537
\n",
+ "
\n",
+ "
\n",
+ "
11
\n",
+ "
any
\n",
+ "
11
\n",
+ "
0.059245
\n",
+ "
\n",
+ "
\n",
+ "
12
\n",
+ "
authorisation
\n",
+ "
12
\n",
+ "
0.057643
\n",
+ "
\n",
+ "
\n",
+ "
13
\n",
+ "
granted
\n",
+ "
13
\n",
+ "
0.057985
\n",
+ "
\n",
+ "
\n",
+ "
14
\n",
+ "
under
\n",
+ "
14
\n",
+ "
0.118352
\n",
+ "
\n",
+ "
\n",
+ "
15
\n",
+ "
this
\n",
+ "
15
\n",
+ "
0.085583
\n",
+ "
\n",
+ "
\n",
+ "
16
\n",
+ "
Article
\n",
+ "
16
\n",
+ "
0.062079
\n",
+ "
\n",
+ "
\n",
+ "
17
\n",
+ "
.
\n",
+ "
17
\n",
+ "
0.120736
\n",
+ "
\n",
+ " \n",
+ "
\n",
+ "
"
+ ],
+ "text/plain": [
+ " 0 1 2\n",
+ "0 The 0 0.040727\n",
+ "1 relevant 1 0.063082\n",
+ "2 Member 2 0.065679\n",
+ "3 State 3 0.080698\n",
+ "4 shall 4 0.171471\n",
+ "5 inform 5 0.142825\n",
+ "6 the 6 0.073943\n",
+ "7 other 7 0.078633\n",
+ "8 Member 8 0.070353\n",
+ "9 States 9 0.132782\n",
+ "10 of 10 0.115537\n",
+ "11 any 11 0.059245\n",
+ "12 authorisation 12 0.057643\n",
+ "13 granted 13 0.057985\n",
+ "14 under 14 0.118352\n",
+ "15 this 15 0.085583\n",
+ "16 Article 16 0.062079\n",
+ "17 . 17 0.120736"
+ ]
+ },
+ "execution_count": 11,
+ "metadata": {},
+ "output_type": "execute_result"
+ }
+ ],
+ "source": [
+ "explanation_relevance = run_dianna(statement)\n",
+ "print('attributions for class', class_names[label_of_interest])\n",
+ "pd.DataFrame(explanation_relevance)"
+ ],
+ "id": "b5b2395041aa2577"
+ },
+ {
+ "cell_type": "markdown",
+ "id": "7e177746-3654-4518-9c1c-b7047f922273",
+ "metadata": {
+ "ExecuteTime": {
+ "end_time": "2024-03-21T10:38:43.263330Z",
+ "start_time": "2024-03-21T10:38:41.425881Z"
+ }
+ },
+ "source": [
+ "# 4 - Visualization\n",
+ "DIANNA includes a visualization package, capable of highlighting the relevance of each word in the text for a chosen class. The visualization is in HTML format.\n",
+ "Words in favour of the selected class are highlighted in red, while words against the selected class - in blue.\n",
+ "\n",
+ "Below we see a plot showing which words (colored with higher intensity of red) contributed the most to the classification of the sentence ``The relevant Member State shall inform the other Member States of any authorisation granted under this Article.`` as ``regulatory``. \n",
+ "\n",
+ "We can see that the most important words are: ``shall``, ``inform``, ``States``, ``of`` and ``under``. ``shall`` indicates a necessary deontic word for regulatory sentences which makes sense. ``inform`` is an action word or verb. These actions typically occur in regulatory sentences because such sentences often have to indicate what a particular party should or should not do to successfully comply with a regulation. Therefore it also makes sense that ``inform`` has high attribution. \n",
+ "\n",
+ "Another component often occurring in EU regulatory sentences is a reference to the actual party or legal entity being regulated. In this case the party being regulated is ``Member States`` (all EU Member countries). However we see that ``Member`` does not receive a high attribution score relative to ``States``. It could be that the model prefers ``States of`` as indicating a party to be regulated as in ``United States of America`` (``of`` has a similar attribution score to ``States``). Perhaps the model has learned that ``States of`` is usually a subphrase of an agent of some kind (which is the precondition for being a party to be regulated)."
+ ]
+ },
+ {
+ "cell_type": "code",
+ "execution_count": 12,
+ "id": "0136005d-a22f-43a0-80da-4ec1f283f870",
+ "metadata": {
+ "ExecuteTime": {
+ "end_time": "2024-07-10T10:39:54.163294Z",
+ "start_time": "2024-07-10T10:39:53.872085Z"
+ }
+ },
+ "outputs": [
+ {
+ "data": {
+ "image/png": "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IiIiIiIiIlLFgTIRERERERGRCAfKRERERERERCIcKBMRERERERGJcKBMREREREREJMKBMhEREREREZEIB8pEREREREREIhwoExEREREREYlwoExEREREREQkwoEyERERERERkQgHykREREREREQiHCgTERERERERiXCgTERERERERCTCgTIRERERERGRCAfKRERERERERCIcKBMRERERERGJcKBMREREREREJMKBMhEREREREZEIB8pEREREREREIhwoExEREREREYlwoExEREREREQkwoEyERERERERkQgHykREREREREQiHCgTERERERERiXCgTERERERERCTCgTIRERERERGRCAfKRERERERERCIcKBMRERERERGJcKBMREREREREJMKBMhEREREREZEIB8pEREREREREIhwoExEREREREYlwoExEREREREQkwoEyERERERERkQgHykREREREREQiHCgTERERERERiXCgTERERERERCTCgTIRERERERGRCAfKRERERERERCIcKBMRERERERGJcKBMREREREREJMKBMhEREREREZEIB8pEREREREREIhwoExEREREREYlwoExEREREREQkwoEyERERERERkQgHykREREREREQiHCgTERERERERiXCgTERERERERCTCgTIRERERERGRCAfKRERERERERCIcKBMRERERERGJFMrvAIjov+/9+/dISUnJ7zCISI2BgQGMjIzyOwwiIqIChwNlIspV79+/R+HCjgAe5XcoRKTGzs4O8fHxHCwTERGp4UCZiHKV4kzyI0gk96CnZw49PUBPDyhUCMr/6+kBEonq3+p5NL0vzqPt/bQ8Gb2f9pJKM35fIkkft6YyaMsjjkFTnszqoKDUk0QuA+Ry7S9BAD59yvh9uVx7nrT3teURv69rHvV8mt4X59H2flqejN6XywFZJnVUAOrplSCg5KNHSElJ4UCZiIhIDQfKRJRHzCGRmEMigcZXZoOz7A5uMxq4ZidfXuYpqDFlOlDOaHCXX3lycl1Sac7FBGT8vkSiGPBq+tGkLQ8ovhh1actrewlC3vz0iYiIvkAatqxEREREREREXy8OlImIiIiIiIhEOFAmIiIiIiIiEuFAmYiIiIiIiEiEA2UiIiIiIiIiEQ6UiYiIiIiIiEQ4UCYiIiIiIiIS4XOUiSiPvIIgQOtL/EjYNOI0bculPQ5WLtf+3F9At2c1Z7QOPT3FOmSyjN/P6HnE4hg05dEUo3q+zPJoK2danozqoVAh3eop0+coC0LGzxFO+8K05Ul7X1se8fu65lHPp+l9cR5t76flyeh9uVzRUDJ7hrKu9aRtXZnFkNEPThDwKm9++ERERF8kDpSJKFcJggBTU1O8eVMSMplinx8APnzI37iICDA1NYUgCPkdBhERUYHDgTIR5SqJRII3b97g3r17MDc3z+9wcs2rV69QsmRJlvM/4GsoI/D/ckokkvwOhYiIqMDhQJmI8oS5ufl/etCRhuX87/gaykhERESa8WZeRERERERERCIcKBMRERERERGJcKBMRLnK0NAQo0aNgqGhYX6HkqtYzv+Or6GMwNdTTiIiouyQCLzdJREREREREZESzygTERERERERiXCgTERERERERCTCgTIRERERERGRCAfKRERERERERCIcKBMRERERERGJcKBMRDlu3LhxqF27NoyNjWFpaanTMoIgYOTIkShWrBgKFy4MX19f3LhxI3cD/QzPnz9H27ZtYW5uDktLS3Tu3Blv3rzJcJl69epBIpGovH744Yc8ilg3c+fOhYODA4yMjODh4YETJ05kmD86Ohrly5eHkZERqlSpgm3btuVRpJ8nK+WMjIxM970ZGRnlYbTZc/DgQQQGBsLe3h4SiQQxMTGZLrN//35Ur14dhoaGKFOmDCIjI3M9TiIiooKIA2UiynEpKSlo1aoVevToofMykydPxuzZs7FgwQIcP34cJiYm8Pf3x/v373Mx0uxr27YtLl26hF27dmHLli04ePAgunXrlulyXbt2xcOHD5WvyZMn50G0ulmzZg0GDBiAUaNG4cyZM6hatSr8/f2RmJioMf+RI0cQFhaGzp074+zZswgKCkJQUBAuXryYx5FnTVbLCQDm5uYq39udO3fyMOLsefv2LapWrYq5c+fqlD8+Ph4BAQGoX78+4uLi0K9fP3Tp0gU7d+7M5UiJiIgKIIGIKJcsW7ZMsLCwyDSfXC4X7OzshClTpijTkpKSBENDQ+Gvv/7KxQiz5/LlywIA4eTJk8q07du3CxKJRLh//77W5by9vYW+ffvmQYTZU7NmTaFXr17Kv2UymWBvby9MmDBBY/6QkBAhICBAJc3Dw0Po3r17rsb5ubJaTl3bcUEGQNi4cWOGeQYPHixUqlRJJa1169aCv79/LkZGRERUMPGMMhHlu/j4eDx69Ai+vr7KNAsLC3h4eODo0aP5GJlmR48ehaWlJdzd3ZVpvr6+0NPTw/HjxzNcNioqClZWVqhcuTKGDRuGd+/e5Xa4OklJScHp06dVvgM9PT34+vpq/Q6OHj2qkh8A/P39C+R3liY75QSAN2/eoHTp0ihZsiSaNWuGS5cu5UW4eepL/D6JiIhyS6H8DoCI6NGjRwAAW1tblXRbW1vlewXJo0ePYGNjo5JWqFAhFClSJMN427Rpg9KlS8Pe3h7nz5/HkCFDcO3aNWzYsCG3Q87U06dPIZPJNH4HV69e1bjMo0ePvpjvLE12yuni4oKlS5fi22+/xcuXLzF16lTUrl0bly5dQokSJfIi7Dyh7ft89eoVkpOTUbhw4XyKjIiIKO/xjDIR6WTo0KHpbmik/tI20PhS5HYZu3XrBn9/f1SpUgVt27bFihUrsHHjRty6dSsHS0E5zdPTEx06dICrqyu8vb2xYcMGWFtbY+HChfkdGhEREeUSnlEmIp0MHDgQERERGeZxcnLK1rrt7OwAAI8fP0axYsWU6Y8fP4arq2u21pkdupbRzs4u3Y2fPn36hOfPnyvLogsPDw8AwM2bN+Hs7JzleHOSlZUVpFIpHj9+rJL++PFjrWWys7PLUv6CIDvlVKevr49q1arh5s2buRFivtH2fZqbm/NsMhERfXU4UCYinVhbW8Pa2jpX1u3o6Ag7Ozvs2bNHOTB+9eoVjh8/nqU7Z38uXcvo6emJpKQknD59Gm5ubgCAvXv3Qi6XKwe/uoiLiwMAlYMD+cXAwABubm7Ys2cPgoKCAAByuRx79uxB7969NS7j6emJPXv2oF+/fsq0Xbt2wdPTMw8izp7slFOdTCbDhQsX0KRJk1yMNO95enqme7xXQf8+iYiIcgunXhNRjrt79y7i4uJw9+5dyGQyxMXFIS4uTuU5w+XLl8fGjRsBABKJBP369cPYsWOxadMmXLhwAR06dIC9vb1yMFOQVKhQAY0aNULXrl1x4sQJHD58GL1790ZoaCjs7e0BAPfv30f58uWVz+e9desWfvvtN5w+fRoJCQnYtGkTOnToAC8vL3z77bf5WRylAQMG4I8//sDy5ctx5coV9OjRA2/fvkXHjh0BAB06dMCwYcOU+fv27YsdO3Zg2rRpuHr1KkaPHo1Tp07pPODML1kt56+//orY2Fjcvn0bZ86cQbt27XDnzh106dIlv4qgkzdv3ih/e4Dipnlpv0sAGDZsGDp06KDM/8MPP+D27dsYPHgwrl69innz5mHt2rXo379/foRPRESUv/L7tttE9N8THh4uAEj32rdvnzIPAGHZsmXKv+VyuTBixAjB1tZWMDQ0FBo0aCBcu3Yt74PX0bNnz4SwsDDB1NRUMDc3Fzp27Ci8fv1a+X58fLxKme/evSt4eXkJRYoUEQwNDYUyZcoIgwYNEl6+fJlPJdDs999/F0qVKiUYGBgINWvWFI4dO6Z8z9vbWwgPD1fJv3btWqFcuXKCgYGBUKlSJWHr1q15HHH2ZKWc/fr1U+a1tbUVmjRpIpw5cyYfos6affv2afwdppUtPDxc8Pb2TreMq6urYGBgIDg5Oan8RomIiL4mEkEQhPwZohMREREREREVPJx6TURERERERCTCgTIRERERERGRCAfKRERERERERCIcKBMRERERERGJcKBMREREREREJMKBMhEREREREZEIB8pEREREREREIhwoExEREREREYlwoExEREREREQkwoEyERERERERkQgHykREREREREQi/wPB3vpHbxPSgwAAAABJRU5ErkJggg==",
+ "text/plain": [
+ "