diff --git a/dianna/methods/kernelshap_tabular.py b/dianna/methods/kernelshap_tabular.py
new file mode 100644
index 00000000..d2bf47e3
--- /dev/null
+++ b/dianna/methods/kernelshap_tabular.py
@@ -0,0 +1,84 @@
+from typing import List
+from typing import Optional
+from typing import Union
+import numpy as np
+import shap
+from shap import KernelExplainer
+from dianna import utils
+
+
+class KERNELSHAPTabular:
+ """Wrapper around the SHAP Kernel explainer for tabular data."""
+
+ def __init__(
+ self,
+ training_data: np.array,
+ mode: str = "classification",
+ feature_names: List[int] = None,
+ training_data_kmeans: Optional[int] = None,
+ ) -> None:
+ """Initializer of KERNELSHAPTabular.
+
+ Training data must be provided for the explainer to estimate the expected
+ values.
+
+ More information can be found in the API guide:
+ https://github.com/shap/shap/blob/master/shap/explainers/_kernel.py
+
+ Arguments:
+ training_data (np.array): training data, which should be numpy 2d array
+ mode (str, optional): "classification" or "regression"
+ feature_names (list(str), optional): list of names corresponding to the columns
+ in the training data.
+ training_data_kmeans(int, optional): summarize the whole training set with
+ weighted kmeans
+ """
+ if training_data_kmeans:
+ self.training_data = shap.kmeans(training_data, training_data_kmeans)
+ else:
+ self.training_data = training_data
+ self.feature_names = feature_names
+ self.mode = mode
+ self.explainer: KernelExplainer
+
+ def explain(
+ self,
+ model_or_function: Union[str, callable],
+ input_tabular: np.array,
+ link: str = "identity",
+ **kwargs,
+ ) -> np.array:
+ """Run the KernelSHAP explainer.
+
+ Args:
+ model_or_function (callable or str): The function that runs the model to be explained
+ or the path to a ONNX model on disk.
+ input_tabular (np.ndarray): Data to be explained.
+ link (str): A generalized linear model link to connect the feature importance values
+ to the model. Must be either "identity" or "logit".
+ kwargs: These parameters are passed on
+
+ Other keyword arguments: see the documentation for KernelExplainer:
+ https://github.com/shap/shap/blob/master/shap/explainers/_kernel.py
+
+ Returns:
+ explanation: An Explanation object containing the KernelExplainer explanations
+ for each class.
+ """
+ init_instance_kwargs = utils.get_kwargs_applicable_to_function(
+ KernelExplainer, kwargs
+ )
+ self.explainer = KernelExplainer(
+ model_or_function, self.training_data, link, **init_instance_kwargs
+ )
+
+ explain_instance_kwargs = utils.get_kwargs_applicable_to_function(
+ self.explainer.shap_values, kwargs
+ )
+
+ saliency = self.explainer.shap_values(input_tabular, **explain_instance_kwargs)
+
+ if self.mode == 'regression':
+ return saliency[0]
+
+ return saliency
diff --git a/dianna/methods/lime_tabular.py b/dianna/methods/lime_tabular.py
index d72bbc22..59fe5c40 100644
--- a/dianna/methods/lime_tabular.py
+++ b/dianna/methods/lime_tabular.py
@@ -119,11 +119,11 @@ def explain(
**explain_instance_kwargs,
)
- if self.mode == "regression":
+ if self.mode == 'regression':
local_exp = sorted(explanation.local_exp[1])
saliency = [i[1] for i in local_exp]
- elif self.mode == "classification":
+ elif self.mode == 'classification':
# extract scores from lime explainer
saliency = []
for i in range(self.top_labels):
diff --git a/tests/methods/test_shap_tabular.py b/tests/methods/test_shap_tabular.py
new file mode 100644
index 00000000..f2ecc7fe
--- /dev/null
+++ b/tests/methods/test_shap_tabular.py
@@ -0,0 +1,35 @@
+"""Test LIME tabular method."""
+from unittest import TestCase
+import numpy as np
+import dianna
+from dianna.methods.kernelshap_tabular import KERNELSHAPTabular
+from tests.utils import run_model
+
+
+class LIMEOnTabular(TestCase):
+ """Suite of LIME tests for the tabular case."""
+
+ def test_shap_tabular_classification_correct_output_shape(self):
+ """Test whether the output of explainer has the correct shape."""
+ training_data = np.random.random((10, 2))
+ input_data = np.random.random(2)
+ feature_names = ["feature_1", "feature_2"]
+ explainer = KERNELSHAPTabular(training_data,
+ mode ='classification',
+ feature_names=feature_names,)
+ exp = explainer.explain(
+ run_model,
+ input_data,
+ )
+ assert len(exp[0]) == len(feature_names)
+
+ def test_shap_tabular_regression_correct_output_shape(self):
+ """Test whether the output of explainer has the correct length."""
+ training_data = np.random.random((10, 2))
+ input_data = np.random.random(2)
+ feature_names = ["feature_1", "feature_2"]
+ exp = dianna.explain_tabular(run_model, input_tabular=input_data, method='kernelshap',
+ mode ='regression', training_data = training_data,
+ training_data_kmeans = 2, feature_names=feature_names)
+
+ assert len(exp) == len(feature_names)
diff --git a/tutorials/kernelshap_tabular_penguin.ipynb b/tutorials/kernelshap_tabular_penguin.ipynb
new file mode 100644
index 00000000..ffcc5ab7
--- /dev/null
+++ b/tutorials/kernelshap_tabular_penguin.ipynb
@@ -0,0 +1,434 @@
+{
+ "cells": [
+ {
+ "cell_type": "markdown",
+ "metadata": {},
+ "source": [
+ "\n",
+ "\n",
+ "### Model Interpretation using KernelSHAP for penguin dataset classifier\n",
+ "This notebook demonstrates the use of DIANNA with the SHAP Kernel explainer method for tabular data on the penguins dataset."
+ ]
+ },
+ {
+ "cell_type": "markdown",
+ "metadata": {},
+ "source": [
+ "### Colab setup"
+ ]
+ },
+ {
+ "cell_type": "code",
+ "execution_count": 1,
+ "metadata": {},
+ "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]\n",
+ " \n",
+ " # download data used in this demo\n",
+ " import os \n",
+ " base_url = 'https://raw.githubusercontent.com/dianna-ai/dianna/main/tutorials/'\n",
+ " paths_to_download = ['models/penguin_model.onnx']\n",
+ " for path in paths_to_download:\n",
+ " !wget {base_url + path} -P {os.path.dirname(path)}"
+ ]
+ },
+ {
+ "cell_type": "markdown",
+ "metadata": {},
+ "source": [
+ "### Import libraries"
+ ]
+ },
+ {
+ "cell_type": "code",
+ "execution_count": 2,
+ "metadata": {},
+ "outputs": [],
+ "source": [
+ "import dianna\n",
+ "import numpy as np\n",
+ "import pandas as pd\n",
+ "import seaborn as sns\n",
+ "from sklearn.model_selection import train_test_split\n",
+ "from dianna.utils.onnx_runner import SimpleModelRunner\n",
+ "\n",
+ "from numba.core.errors import NumbaDeprecationWarning\n",
+ "import warnings\n",
+ "# silence the Numba deprecation warnings in shap\n",
+ "warnings.simplefilter('ignore', category=NumbaDeprecationWarning)"
+ ]
+ },
+ {
+ "cell_type": "markdown",
+ "metadata": {},
+ "source": [
+ "#### 1 - Loading the data\n",
+ "Load penguins dataset."
+ ]
+ },
+ {
+ "cell_type": "code",
+ "execution_count": 3,
+ "metadata": {},
+ "outputs": [],
+ "source": [
+ "penguins = sns.load_dataset('penguins')"
+ ]
+ },
+ {
+ "cell_type": "markdown",
+ "metadata": {},
+ "source": [
+ "Prepare the data"
+ ]
+ },
+ {
+ "cell_type": "code",
+ "execution_count": 4,
+ "metadata": {},
+ "outputs": [
+ {
+ "data": {
+ "text/html": [
+ "
\n",
+ "\n",
+ "
\n",
+ " \n",
+ " \n",
+ " | \n",
+ " bill_length_mm | \n",
+ " bill_depth_mm | \n",
+ " flipper_length_mm | \n",
+ " body_mass_g | \n",
+ "
\n",
+ " \n",
+ " \n",
+ " \n",
+ " 0 | \n",
+ " 39.1 | \n",
+ " 18.7 | \n",
+ " 181.0 | \n",
+ " 3750.0 | \n",
+ "
\n",
+ " \n",
+ " 1 | \n",
+ " 39.5 | \n",
+ " 17.4 | \n",
+ " 186.0 | \n",
+ " 3800.0 | \n",
+ "
\n",
+ " \n",
+ " 2 | \n",
+ " 40.3 | \n",
+ " 18.0 | \n",
+ " 195.0 | \n",
+ " 3250.0 | \n",
+ "
\n",
+ " \n",
+ " 4 | \n",
+ " 36.7 | \n",
+ " 19.3 | \n",
+ " 193.0 | \n",
+ " 3450.0 | \n",
+ "
\n",
+ " \n",
+ " 5 | \n",
+ " 39.3 | \n",
+ " 20.6 | \n",
+ " 190.0 | \n",
+ " 3650.0 | \n",
+ "
\n",
+ " \n",
+ " ... | \n",
+ " ... | \n",
+ " ... | \n",
+ " ... | \n",
+ " ... | \n",
+ "
\n",
+ " \n",
+ " 338 | \n",
+ " 47.2 | \n",
+ " 13.7 | \n",
+ " 214.0 | \n",
+ " 4925.0 | \n",
+ "
\n",
+ " \n",
+ " 340 | \n",
+ " 46.8 | \n",
+ " 14.3 | \n",
+ " 215.0 | \n",
+ " 4850.0 | \n",
+ "
\n",
+ " \n",
+ " 341 | \n",
+ " 50.4 | \n",
+ " 15.7 | \n",
+ " 222.0 | \n",
+ " 5750.0 | \n",
+ "
\n",
+ " \n",
+ " 342 | \n",
+ " 45.2 | \n",
+ " 14.8 | \n",
+ " 212.0 | \n",
+ " 5200.0 | \n",
+ "
\n",
+ " \n",
+ " 343 | \n",
+ " 49.9 | \n",
+ " 16.1 | \n",
+ " 213.0 | \n",
+ " 5400.0 | \n",
+ "
\n",
+ " \n",
+ "
\n",
+ "
342 rows × 4 columns
\n",
+ "
"
+ ],
+ "text/plain": [
+ " bill_length_mm bill_depth_mm flipper_length_mm body_mass_g\n",
+ "0 39.1 18.7 181.0 3750.0\n",
+ "1 39.5 17.4 186.0 3800.0\n",
+ "2 40.3 18.0 195.0 3250.0\n",
+ "4 36.7 19.3 193.0 3450.0\n",
+ "5 39.3 20.6 190.0 3650.0\n",
+ ".. ... ... ... ...\n",
+ "338 47.2 13.7 214.0 4925.0\n",
+ "340 46.8 14.3 215.0 4850.0\n",
+ "341 50.4 15.7 222.0 5750.0\n",
+ "342 45.2 14.8 212.0 5200.0\n",
+ "343 49.9 16.1 213.0 5400.0\n",
+ "\n",
+ "[342 rows x 4 columns]"
+ ]
+ },
+ "execution_count": 4,
+ "metadata": {},
+ "output_type": "execute_result"
+ }
+ ],
+ "source": [
+ "# Remove categorial columns and NaN values\n",
+ "penguins_filtered = penguins.drop(columns=['island', 'sex']).dropna()\n",
+ "\n",
+ "# Get the species\n",
+ "species = penguins['species'].unique()\n",
+ "\n",
+ "# Extract inputs and target\n",
+ "input_features = penguins_filtered.drop(columns=['species'])\n",
+ "target = pd.get_dummies(penguins_filtered['species'])\n",
+ "\n",
+ "# Let's explore the features of the dataset\n",
+ "input_features"
+ ]
+ },
+ {
+ "cell_type": "markdown",
+ "metadata": {},
+ "source": [
+ "The data-set currently has four features that were used to train the model: bill length, bill depth, flipper length, and body mass. These features were used to classify the different species."
+ ]
+ },
+ {
+ "cell_type": "markdown",
+ "metadata": {},
+ "source": [
+ "Training, validation, and test data split."
+ ]
+ },
+ {
+ "cell_type": "code",
+ "execution_count": 5,
+ "metadata": {},
+ "outputs": [],
+ "source": [
+ "X_train, X_test, y_train, y_test = train_test_split(input_features, target, test_size=0.2,\n",
+ " random_state=0, shuffle=True, stratify=target)"
+ ]
+ },
+ {
+ "cell_type": "markdown",
+ "metadata": {},
+ "source": [
+ "Get an instance to explain."
+ ]
+ },
+ {
+ "cell_type": "code",
+ "execution_count": 6,
+ "metadata": {},
+ "outputs": [],
+ "source": [
+ "# get an instance from test data\n",
+ "data_instance = X_test.iloc[10].to_numpy()"
+ ]
+ },
+ {
+ "cell_type": "markdown",
+ "metadata": {},
+ "source": [
+ "#### 2. Loading ONNX model\n",
+ "DIANNA supports ONNX models. Here we demonstrate the use of KernelSHAP explainer for tabular data with a pre-trained ONNX model, which is a MLP classifier for the penguins dataset.
\n",
+ "\n",
+ "The model is trained following this notebook:
\n",
+ "https://github.com/dianna-ai/dianna-exploration/blob/main/example_data/model_generation/penguin_species/generate_model.ipynb"
+ ]
+ },
+ {
+ "cell_type": "code",
+ "execution_count": 7,
+ "metadata": {},
+ "outputs": [
+ {
+ "data": {
+ "text/plain": [
+ "'Gentoo'"
+ ]
+ },
+ "execution_count": 7,
+ "metadata": {},
+ "output_type": "execute_result"
+ }
+ ],
+ "source": [
+ "# load onnx model and check the prediction with it\n",
+ "model_path = './models/penguin_model.onnx'\n",
+ "loaded_model = SimpleModelRunner(model_path)\n",
+ "predictions = loaded_model(data_instance.reshape(1,-1).astype(np.float32))\n",
+ "species[np.argmax(predictions)]"
+ ]
+ },
+ {
+ "cell_type": "markdown",
+ "metadata": {},
+ "source": [
+ "A runner function is created to prepare data for the ONNX inference session."
+ ]
+ },
+ {
+ "cell_type": "code",
+ "execution_count": 8,
+ "metadata": {},
+ "outputs": [],
+ "source": [
+ "import onnxruntime as ort\n",
+ "\n",
+ "def run_model(data):\n",
+ " # get ONNX predictions\n",
+ " sess = ort.InferenceSession(model_path)\n",
+ " input_name = sess.get_inputs()[0].name\n",
+ " output_name = sess.get_outputs()[0].name\n",
+ "\n",
+ " onnx_input = {input_name: data.astype(np.float32)}\n",
+ " pred_onnx = sess.run([output_name], onnx_input)[0]\n",
+ " \n",
+ " return pred_onnx"
+ ]
+ },
+ {
+ "cell_type": "markdown",
+ "metadata": {},
+ "source": [
+ "#### 3. Applying KernelSHAP with DIANNA\n",
+ "The simplest way to run DIANNA on image data is with `dianna.explain_tabular`.\n",
+ "\n",
+ "DIANNA requires input in numpy format, so the input data is converted into a numpy array.\n",
+ "\n",
+ "Note that the training data is also required since KernelSHAP needs it to generate proper perturbation. But here we can summarize the whole training set with weighted Kmeans to reduce the computational cost. This has been implemented in `shap` and here we just need to set the number of clusters, for instance `training_data_kmeans = 5`."
+ ]
+ },
+ {
+ "cell_type": "code",
+ "execution_count": 9,
+ "metadata": {},
+ "outputs": [
+ {
+ "name": "stderr",
+ "output_type": "stream",
+ "text": [
+ "The default value of `n_init` will change from 10 to 'auto' in 1.4. Set the value of `n_init` explicitly to suppress the warning\n"
+ ]
+ }
+ ],
+ "source": [
+ "explanation = dianna.explain_tabular(run_model, input_tabular=data_instance, method='kernelshap',\n",
+ " mode ='classification', training_data = X_train,\n",
+ " training_data_kmeans = 5, feature_names=input_features.columns)"
+ ]
+ },
+ {
+ "cell_type": "markdown",
+ "metadata": {},
+ "source": [
+ "#### 4. Visualization\n",
+ "The output can be visualized with the DIANNA built-in visualization function. It shows the importance of each feature contributing to the prediction.\n",
+ "\n",
+ "The prediction is \"Gentoo\", so let's visualize the feature importance scores for \"Gentoo\".\n",
+ "\n",
+ "It can be noticed that the body mass feature has the biggest weight in the prediction."
+ ]
+ },
+ {
+ "cell_type": "code",
+ "execution_count": 10,
+ "metadata": {},
+ "outputs": [
+ {
+ "data": {
+ "image/png": "iVBORw0KGgoAAAANSUhEUgAAAqMAAAGwCAYAAACdNWzAAAAAOXRFWHRTb2Z0d2FyZQBNYXRwbG90bGliIHZlcnNpb24zLjcuMSwgaHR0cHM6Ly9tYXRwbG90bGliLm9yZy/bCgiHAAAACXBIWXMAAA9hAAAPYQGoP6dpAAA7FklEQVR4nO3de1xVVf7/8fcR5CL3VAgLOTqooUmijoZ5qyzT8qvWTzMbb13UEi9j41jfNDUrzcnEsXIKy0uT2sU0s7wnjXfNwJxkCAlHS9SZVBDvwPr90deTJ9QAgaXwej4e+/E4e+219/7slXre7dtxGGOMAAAAAAuq2C4AAAAAlRdhFAAAANYQRgEAAGANYRQAAADWEEYBAABgDWEUAAAA1hBGAQAAYI2n7QKAyykoKNCBAwcUEBAgh8NhuxwAAFAExhgdP35ctWrVUpUqlz/3SRjFVe3AgQOKiIiwXQYAACiB/fv368Ybb7xsH8IormoBAQGSfv7DHBgYaLkaAABQFDk5OYqIiHB9j18OYRRXtfOX5gMDAwmjAABcY4pyix0PMAEAAMAawigAAACsIYwCAADAGsIoAAAArCGMAgAAwBrCKAAAAKwhjAIAAMAawigAAACsIYwCAADAGsIoAAAArCGMAgAAwBrCKAAAAKwhjAIAAMAawigAAACsIYwCAADAGsIoAAAArCGMAgAAwBrCKAAAAKwhjAIAAMAawigAAACsIYwCAADAGsIoAAAArCGMAgAAwBrCKAAAAKwhjAIAAMAawigAAACsIYwCAADAGsIoAAAArCGMAgAAwBrCKAAAAKwhjAIAAMAawigAAACsIYwCAADAGsIoAAAArCGMAgAAwBrCKAAAAKwhjAIAAMAawigAAACsIYwCAADAGsIoAAAArCGMAgAAwBrCKAAAAKwhjAIAAMAawigAAACsIYwCAADAmkoVRtu3b68RI0ZccrnT6VRCQoJr3uFwaMmSJZKkvXv3yuFwKCUlpUT7TkpKksPh0LFjx0q0fnGMHz9eTZo0KfP9AAAAXClP2wVcTbZv3y4/Pz/bZRSLw+HQ4sWL1a1bN9ulAAAAFBth9AI1a9a0XQIAAEClUqku00tSXl6e4uPjFRQUpBo1amjs2LEyxkgqfJn+Snz++eeqX7++fH19dfvtt2vv3r2F+mzYsEFt2rSRr6+vIiIiNGzYMJ04ccK13Ol0auLEiXrooYfk5+enG264Qa+//rrbcknq3r27HA6Ha/68d999V06nU0FBQerVq5eOHz9epNrbt2+voUOHasSIEQoJCVFYWJgSExN14sQJDRgwQAEBAYqKitLy5ctd65y/DWHlypWKjY2Vr6+v7rjjDh0+fFjLly9XdHS0AgMD1bt3b508efKS+z5z5oxycnLcJgAAUHFVujA6d+5ceXp6atu2bZo+fbpeffVVzZo1q1T3sX//ft1///3q0qWLUlJS9Nhjj+npp59265ORkaF77rlHDzzwgL755hu9//772rBhg+Lj4936/eUvf9Ett9yi5ORkPf300xo+fLhWr14t6efbCiRp9uzZysrKcs2f3/6SJUu0bNkyLVu2TF9++aUmT55c5GOYO3euatSooW3btmno0KF64okn1KNHD7Vq1Upff/217r77bvXp06dQsBw/frxee+01bdq0Sfv371fPnj2VkJCg+fPn67PPPtOqVas0Y8aMS+530qRJCgoKck0RERFFrhkAAFyDTCXSrl07Ex0dbQoKClxto0ePNtHR0cYYYyIjI820adNcyySZxYsXG2OMyczMNJJMcnLyb+7nmWeeMQ0bNnRrGz16tJFkjh49aowx5tFHHzUDBw5067N+/XpTpUoVc+rUKVc999xzj1ufBx980HTq1OmiNZ43btw4U61aNZOTk+NqGzVqlGnZsuVv1m7Mz+PUunVr13xeXp7x8/Mzffr0cbVlZWUZSWbz5s3GGGPWrVtnJJk1a9a4+kyaNMlIMhkZGa62QYMGmY4dO15y36dPnzbZ2dmuaf/+/UaSyc7OLlLtAADAvuzs7CJ/f1e6M6O33nqrHA6Haz4uLk7p6enKz88vtX2kpqaqZcuWbm1xcXFu8zt37tScOXPk7+/vmjp27KiCggJlZmZecr24uDilpqb+Zg1Op1MBAQGu+fDwcB0+fLjIxxATE+P67OHhoerVq6tx48autrCwMEkqtM0L1wsLC1O1atVUt25dt7bL1eHt7a3AwEC3CQAAVFw8wGRJbm6uBg0apGHDhhVaVrt27SveftWqVd3mHQ6HCgoKrmj9C9vOB/pfb/PXfa60DgAAULFVujC6detWt/ktW7aoXr168vDwKLV9REdHa+nSpYX2c6GmTZtq9+7dioqKuuy2fr3eli1bFB0d7ZqvWrVqqZ7VBQAAKE+V7jL9vn37NHLkSKWlpWnBggWaMWOGhg8fXqr7GDx4sNLT0zVq1CilpaVp/vz5mjNnjluf0aNHa9OmTYqPj1dKSorS09P1ySefFHqAaePGjZoyZYq+++47vf766/rwww/d6nU6nVq7dq0OHjyoo0ePlupxAAAAlLVKF0b79u2rU6dOqUWLFhoyZIiGDx+ugQMHluo+ateurUWLFmnJkiW65ZZb9Le//U0vvfSSW5+YmBh9+eWX+u6779SmTRvFxsbqueeeU61atdz6PfXUU/rqq68UGxurF154Qa+++qo6duzoWj516lStXr1aERERio2NLdXjAAAAKGsOY/7vJZu46jidTo0YMeKyP2Fa0eXk5CgoKEjZ2dk8zAQAwDWiON/fle7MKAAAAK4ehNESGDx4sNsrmS6cBg8ebLu8y9q3b98la/f399e+fftslwgAACoRLtOXwOHDhy/5M5WBgYEKDQ0t54qKLi8v76I/TXqe0+mUp+fV85IFLtMDAHDtKc7399WTOq4hoaGhV3XgvBxPT8/ffJ0UAABAeeEyPQAAAKwhjAIAAMAawigAAACsIYwCAADAGsIoAAAArCGMAgAAwBrCKAAAAKwhjAIAAMAawigAAACsIYwCAADAGsIoAAAArCGMAgAAwBrCKAAAAKwhjAIAAMAawigAAACsIYwCAADAGsIoAAAArCGMAgAAwBrCKAAAAKwhjAIAAMAawigAAACsIYwCAADAGsIoAAAArCGMAgAAwBrCKAAAAKwhjAIAAMAawigAAACsIYwCAADAGsIoAAAArCGMAgAAwBrCKAAAAKwhjAIAAMAawigAAACsIYwCAADAGsIoAAAArCGMAgAAwBrCKAAAAKwhjAIAAMAawigAAACsIYwCAADAGsIoAAAArPG0XQBwTXA4fvlsjL06AACoYDgzCgAAAGsIowAAALCGMAoAAABrCKMAAACwhjAKAAAAawijAAAAsIYwCgAAAGsIowAAALCGMAoAAABrCKMAAACwhjAKAAAAawijAAAAsIYwCgAAAGsIowAAALCGMAoAAABrCKMAAACwhjAKAAAAawijAAAAsIYwCgAAAGsIowAAALCGMAoAAABrCKMAAACwhjAKAAAAawijAAAAsIYwCgAAAGsIowAAALCGMAoAAABrCKMAAACwhjAKAAAAawijAAAAsKZCh9H27dtrxIgR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a9a4+kyaNMlIMhkZGa62QYMGmY4dO15y36dPnzbZ2dmu6fx/m+zs7CLVXlxS0SYAAFB02dnZRf7+tnrPaEnExcUVmk9JSblkH09PTzVv3tx19nTnzp1at26d/P39C207IyND9evXlyQ1a9asxDXu3LlTe/bsUUBAgFv76dOnlZGR4Zpv1KiRPDw8XPPh4eHatWuXJCktLU2enp5q2rSpa3lUVJRCQkKKVIPT6XTbf3h4uNtZ1d8SExPj+uzh4aHq1aurcePGrrawsDBJKrTNC9cLCwtTtWrVVLduXbe2bdu2XXK/3t7e8vb2LnKdAADg2nbNhdErlZubqy5duujll18utCw8PNz12c/P74r20axZM7333nuFll34UFbVqlXdljkcDhUUFJR4vxe60m1fbP0L2xwOhyQV2uav+5TlMQIAgGvfNffS+y1bthSaj46OvmSfvLw87dixw9WnadOm+vbbb+V0OhUVFeU2XUkAvVDTpk2Vnp6u0NDQQvsICgoq0jYaNGigvLw8JScnu9r27Nmjo0ePuvWrWrWq8vPzS6VuAACA8nbNhdEPP/xQ77zzjr777juNGzdO27ZtU3x8vFuf119/XYsXL9a//vUvDRkyREePHtUjjzwiSRoyZIiOHDmihx56SNu3b1dGRoZWrlypAQMGlFqoe/jhh1WjRg117dpV69evV2ZmppKSkjRs2DD98MMPRdrGTTfdpA4dOmjgwIHatm2bkpOTNXDgQPn6+rrOSko/X45fu3atDh48WCioAgAAXO2uuTA6YcIELVy4UDExMZo3b54WLFighg0buvWZPHmyJk+erFtuuUUbNmzQ0qVLVaNGDUlSrVq1tHHjRuXn5+vuu+9W48aNNWLECAUHB6tKldIZjmrVqukf//iHateurfvvv1/R0dF69NFHdfr0aQUGBhZ5O/PmzVNYWJjatm2r7t276/HHH1dAQIB8fHxcfaZOnarVq1crIiJCsbGxpVI/AABAeXEYY4ztIorK4XBo8eLF6tat20WX7927V3Xq1FFycnKF/DnMH374QREREVqzZo3uvPNO2+WUi5ycHAUFBSk7O7tYQb6oLjjJfFnXzt8SAADsK873d6V7gOla8sUXXyg3N1eNGzdWVlaW/vznP8vpdKpt27a2SwMAACgV19xlehvee+89+fv7X3Rq1KhRme333Llz+t///V81atRI3bt3V82aNZWUlFToCfXi2Ldv3yWPxd/fX/v27SvFIwAAALi8a+oyvS3Hjx/XoUOHLrqsatWqioyMLOeKSi4vL8/tZ0l/zel0ytPz6jlhzmV6AACuPVymL2UBAQGFXmB/rfL09FRUVJTtMgAAACRxmR4AAAAWEUYBAABgDWEUAAAA1hBGAQAAYA1hFAAAANYQRgEAAGANYRQAAADWEEYBAABgDWEUAAAA1hBGAQAAYA1hFAAAANYQRgEAAGANYRQAAADWEEYBAABgDWEUAAAA1hBGAQAAYA1hFAAAANYQRgEAAGANYRQAAADWEEYBAABgDWEUAAAA1hBGAQAAYA1hFAAAANYQRgEAAGCNp+0CAJuMsV0BAACVG2dGAQAAYA1hFAAAANYQRgEAAGANYRQAAADWEEYBAABgDWEUAAAA1hBGAQAAYA1hFAAAANYQRgEAAGANYRQAAADWEEYBAABgDWEUAAAA1hBGAQAAYA1hFAAAANYQRgEAAGANYRQAAADWeNouALgmOBy2KwAAoGwYY3X3nBkFAACANYRRAAAAWEMYBQAAgDWEUQAAAFhDGAUAAIA1hFEAAABYQxgFAACANYRRAAAAWEMYBQAAgDWEUQAAAFhDGAUAAIA1hFEAAABYQxgFAACANYRRAAAAWEMYBQAAgDWEUQAAAFhDGAUAAIA1hFEAAABYQxgFAACANYRRAAAAWEMYBQAAgDWEUQAAAFhDGAUAAIA1hFEAAABYQxgFAACANYRRAAAAWEMYBQAAgDWEUQAAAFhDGAUAAIA1hFEAAABYQxi9QPv27TVixIhS3eacOXMUHBxcqtsEAACoKAijAAAAsIYwCgAAAGsIo7+Sl5en+Ph4BQUFqUaNGho7dqyMMZKko0ePqm/fvgoJCVG1atXUqVMnpaenu60/Z84c1a5dW9WqVVP37t31008/uZbt3btXVapU0VdffeW2TkJCgiIjI1VQUHDZ2pKSkuRwOLRy5UrFxsbK19dXd9xxhw4fPqzly5crOjpagYGB6t27t06ePOlab8WKFWrdurWCg4NVvXp13XfffcrIyHAtP3v2rOLj4xUeHi4fHx9FRkZq0qRJkiRjjMaPH6/atWvL29tbtWrV0rBhw4o0lllZWbr33nvl6+urOnXqaP78+XI6nUpISCjS+gAAoOIjjP7K3Llz5enpqW3btmn69Ol69dVXNWvWLElS//799dVXX2np0qXavHmzjDHq3Lmzzp07J0naunWrHn30UcXHxyslJUW33367XnjhBde2nU6nOnTooNmzZ7vtc/bs2erfv7+qVCnaf47x48frtdde06ZNm7R//3717NlTCQkJmj9/vj777DOtWrVKM2bMcPU/ceKERo4cqa+++kpr165VlSpV1L17d1f4/etf/6qlS5fqgw8+UFpamt577z05nU5J0qJFizRt2jS9+eabSk9P15IlS9S4ceMi1dm3b18dOHBASUlJWrRokd566y0dPnz4suucOXNGOTk5bhMAAKjADFzatWtnoqOjTUFBgatt9OjRJjo62nz33XdGktm4caNr2X//+1/j6+trPvjgA2OMMQ899JDp3Lmz2zYffPBBExQU5Jp///33TUhIiDl9+rQxxpgdO3YYh8NhMjMzf7O+devWGUlmzZo1rrZJkyYZSSYjI8PVNmjQINOxY8dLbuc///mPkWR27dpljDFm6NCh5o477nA77vOmTp1q6tevb86ePfub9V0oNTXVSDLbt293taWnpxtJZtq0aZdcb9y4cUZSoSk7O7tY+y91EhMTExMTU8WcykB2drYp6vc3Z0Z/5dZbb5XD4XDNx8XFKT09Xbt375anp6datmzpWla9enU1aNBAqampkqTU1FS35efXv1C3bt3k4eGhxYsXS/r5sv7tt9/uOhNZFDExMa7PYWFhqlatmurWrevWduEZyPT0dD300EOqW7euAgMDXfvat2+fpJ/P+KakpKhBgwYaNmyYVq1a5Vq3R48eOnXqlOrWravHH39cixcvVl5e3m/WmJaWJk9PTzVt2tTVFhUVpZCQkMuu98wzzyg7O9s17d+//zf3BQAArl2E0XLm5eWlvn37avbs2Tp79qzmz5+vRx55pFjbqFq1quuzw+Fwmz/fduH9p126dNGRI0eUmJiorVu3auvWrZJ+vldUkpo2barMzExNnDhRp06dUs+ePfX//t//kyRFREQoLS1Nb7zxhnx9ffXkk0+qbdu2rlsTSpu3t7cCAwPdJgAAUHERRn/lfFA7b8uWLapXr54aNmyovLw8t+U//fST0tLS1LBhQ0lSdHT0Rdf/tccee0xr1qzRG2+8oby8PN1///1lcCTuNY4ZM0Z33nmnoqOjdfTo0UL9AgMD9eCDDyoxMVHvv/++Fi1apCNH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SU1NNePGjatQr3YqzlicOXPGJCcnm+TkZBMeHm7+9Kc/meTkZJOenm7rEEpNccdi8uTJxsvLy3z00Udur645fvy4rUMoNcUdi5deesmsWrXKZGRkmN27d5tXXnnFeHp6msTERFuHUGqKOxa/VpGepi/uWEyYMMGsXLnSZGRkmB07dphevXoZHx8f8+2339o6hFJT3LHYt2+fCQgIMPHx8SYtLc0sW7bMhIaGmhdeeMHWIVhFGAWMMTNmzDC1a9c2Xl5epkWLFmbLli2uZe3atTP9+vVz6//BBx+Y+vXrGy8vL9OoUSPz2WeflXPFZac4Y5GZmWkkFZratWtX/oWXgeKMRWRk5EXHYty4ceVfeBkozlg8++yzJioqyvj4+JiQkBATFxdnFi5caKHqslHcfy8uVJHCqDHFG4sRI0a4+oaFhZnOnTubr7/+2kLVZaO4fy42bdpkWrZsaby9vU3dunXNiy++aPLy8sq56quDwxhjbJ2VBQAAQOXGPaMAAACwhjAKAAAAawijAAAAsIYwCgAAAGsIowAAALCGMAoAAABrCKMAAACwhjAKAAAAawijAAAAsIYwCgBXmf79+6tbt262y7ikvXv3yuFwKCUlxXYpACoAwigAoMjOnj1ru4SrGuMDFB9hFACucu3bt9fQoUM1YsQIhYSEKCwsTImJiTpx4oQGDBiggIAARUVFafny5a51kpKS5HA49NlnnykmJkY+Pj669dZb9c9//tNt24sWLVKjRo3k7e0tp9OpqVOnui13Op2aOHGi+vbtq8DAQA0cOFB16tSRJMXGxsrhcKh9+/aSpO3bt+uuu+5SjRo1FBQUpHbt2unrr792257D4dCsWbPUvXt3VatWTfXq1dPSpUvd+nz77be67777FBgYqICAALVp00YZGRmu5bNmzVJ0dLR8fHx000036Y033rjs+H300Udq3LixfH19Vb16dXXo0EEnTpxwLX/nnXdcYxAeHq74+HjXsn379qlr167y9/dXYGCgevbsqUOHDrmWjx8/Xk2aNNGsWbNUp04d+fj4SJKOHTumxx57TDVr1lRgYKDuuOMO7dy587J1ApUVYRQArgFz585VjRo1tG3bNg0dOlRPPPGEevTooVatWunrr7/W3XffrT59+ujkyZNu640aNUpTp07V9u3bVbNmTXXp0kXnzp2TJO3YsUM9e/ZUr169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",
+ "text/plain": [
+ "