diff --git a/docs/source/user-guide.md b/docs/source/user-guide.md index d20ec334..c59f9492 100644 --- a/docs/source/user-guide.md +++ b/docs/source/user-guide.md @@ -10,21 +10,31 @@ OpenVINO XAI API documentation can be found [here](https://openvinotoolkit.githu Content: -- Architecture -- Explainer -- Basic usage: Auto mode -- White-Box mode -- Black-Box mode -- XAI insertion -- Example scripts +- [OpenVINO™ Explainable AI Toolkit User Guide](#openvino-explainable-ai-toolkit-user-guide) + - [OpenVINO XAI Architecture](#openvino-xai-architecture) + - [Explainer - interface to XAI algorithms](#explainer---interface-to-xai-algorithms) + - [Basic usage: Auto mode](#basic-usage-auto-mode) + - [Running without `preprocess_fn`](#running-without-preprocess_fn) + - [Specifying `preprocess_fn`](#specifying-preprocess_fn) + - [White-Box mode](#white-box-mode) + - [Black-Box mode](#black-box-mode) + - [XAI insertion (white-box usage)](#xai-insertion-white-box-usage) + - [Example scripts](#example-scripts) ## OpenVINO XAI Architecture ![OpenVINO XAI Architecture](_static/ovxai-architecture.svg) +OpenVINO XAI provides the API to explain models, using two types of methods: + +- **White-box** - treats the model as a white box, making inner modifications and adding an extra XAI branch. This results in additional output from the model and relatively fast explanations. +- **Black-box** - treats the model as a black box, working on a wide range of models. However, it requires many more inference runs. + ## Explainer - interface to XAI algorithms +In a nutshell, the explanation call looks like this: + ```python import openvino_xai as xai @@ -39,60 +49,116 @@ explanation = explainer(data, explanation_parameters) ## Basic usage: Auto mode -Under the hood of the auto mode: will try to run white-box mode, if fails => will run black-box mode. +The easiest way to run the explainer is in Auto mode. Under the hood, Auto mode will try to run in `White-Box` mode first. If it fails, it will run in `Black-Box` mode. + +Learn more details about [White-Box](#white-box-mode) and [Black-Box](#black-box-mode) modes below. ![Auto mode process](_static/auto_explain_mode.jpg) -See more details about white_box and black-box modes below. +Generating saliency maps involves model inference. Explainer will perform model inference but to do it, it requires `preprocess_fn` and `postprocess_fn`. + +### Running without `preprocess_fn` + +Here's the example how we can avoid passing `preprocess_fn` by preprocessing data beforehand (like resizing and adding a batch dimension). + +```python +import cv2 +import numpy as np +import openvino.runtime as ov +from openvino.runtime.utils.data_helpers.wrappers import OVDict + +import openvino_xai as xai + + +def postprocess_fn(x: OVDict): + # Implementing our own post-process function based on the model's implementation + # Return "logits" model output + return x["logits"] + +# Create ov.Model +model = ov.Core().read_model("path/to/model.xml") # type: ov.Model + +# Explainer object will prepare and load the model once in the beginning +explainer = xai.Explainer( + model, + task=xai.Task.CLASSIFICATION, + postprocess_fn=postprocess_fn, +) + +# Generate and process saliency maps (as many as required, sequentially) +image = cv2.imread("path/to/image.jpg") + +# Pre-process the image as the model requires (resizing and adding a batch dimension) +preprocessed_image = cv2.resize(src=image, dsize=(224, 224)) +preprocessed_image = np.expand_dims(preprocessed_image, 0) + +# Run explanation +explanation = explainer( + preprocessed_image, + target_explain_labels=[11, 14], # indices or string labels to explain + overlay=True, # False by default + original_input_image=image, # to apply overlay on the original image instead of the preprocessed one that was used for the explainer +) + +# Save saliency maps +explanation.save("output_path", "name") +``` + +### Specifying `preprocess_fn` -Generating saliency maps involves model inference. Explainer will perform model inference. -To infer, `preprocess_fn` and `postprocess_fn` are requested from the user, depending on the usage mode. ```python import cv2 import numpy as np import openvino.runtime as ov +from openvino.runtime.utils.data_helpers.wrappers import OVDict import openvino_xai as xai -from openvino_xai.explainer.explanation_parameters import ExplanationParameters def preprocess_fn(x: np.ndarray) -> np.ndarray: - # Implementing own pre-process function based on model's implementation + # Implementing our own pre-process function based on the model's implementation x = cv2.resize(src=x, dsize=(224, 224)) x = np.expand_dims(x, 0) return x +def postprocess_fn(x: OVDict): + # Implementing our own post-process function based on the model's implementation + # Return "logits" model output + return x["logits"] -# Creating model +# Create ov.Model model = ov.Core().read_model("path/to/model.xml") # type: ov.Model -# Explainer object will prepare and load the model once in the beginning +# The Explainer object will prepare and load the model once in the beginning explainer = xai.Explainer( model, task=xai.Task.CLASSIFICATION, preprocess_fn=preprocess_fn, + postprocess_fn=postprocess_fn, ) # Generate and process saliency maps (as many as required, sequentially) image = cv2.imread("path/to/image.jpg") -explanation_parameters = ExplanationParameters( + +# Run explanation +explanation = explainer( + image, target_explain_labels=[11, 14], # indices or string labels to explain ) -explanation = explainer(image, explanation_parameters) -# Saving saliency maps +# Save saliency maps explanation.save("output_path", "name") ``` ## White-Box mode -White-box mode is a two-step process that includes OV model update and further inference of the updated model. +White-box mode involves two steps: updating the OV model and then running the updated model. -Updated model has additional XAI branch inserted. XAI branch generates saliency maps during model inference. Saliency maps extend the list of model outputs, i.e. saliency maps are generated along with the original model outputs. Depending on the white-box algorithm, computational overhead of inserted XAI branch may vary, but it is usually relatively modest. +The updated model has an extra XAI branch resulting in an additional `saliency_map` output. This XAI branch creates saliency maps during the model's inference. The computational load from the XAI branch varies depending on the white-box algorithm, but it's usually quite small. -`preprocess_fn` is required to be provided by the user for the white-box mode. +You need to pass either `preprocess_fn` or already preprocessed images to run the explainer in white-box mode. ```python import cv2 @@ -100,145 +166,126 @@ import numpy as np import openvino.runtime as ov import openvino_xai as xai -from openvino_xai.explainer.parameters import ExplainMode, ExplanationParameters, TargetExplainGroup, VisualizationParameters -from openvino_xai.inserter.parameters import ClassificationInsertionParameters +from openvino_xai.explainer import ExplainMode def preprocess_fn(x: np.ndarray) -> np.ndarray: - # Implementing own pre-process function based on model's implementation + # Implementing own pre-process function based on the model's implementation x = cv2.resize(src=x, dsize=(224, 224)) x = np.expand_dims(x, 0) return x - -# Creating model +# Create ov.Model model = ov.Core().read_model("path/to/model.xml") # type: ov.Model -# Optional - create insertion parameters -insertion_parameters = ClassificationInsertionParameters( - # target_layer="last_conv_node_name", # target_layer - node after which XAI branch will be inserted - embed_scaling=True, # True by default. If set to True, saliency map scale (0 ~ 255) operation is embedded in the model - explain_method=xai.Method.RECIPROCAM, # ReciproCAM is the default XAI method for CNNs -) - -# Explainer object will prepare and load the model once in the beginning +# The Explainer object will prepare and load the model once at the beginning explainer = xai.Explainer( model, task=xai.Task.CLASSIFICATION, preprocess_fn=preprocess_fn, - explain_mode=ExplainMode.WHITEBOX, - insertion_parameters=insertion_parameters, ) # Generate and process saliency maps (as many as required, sequentially) image = cv2.imread("path/to/image.jpg") -voc_labels = ['aeroplane', 'bicycle', 'bird', 'boat', 'bottle', 'bus', 'car', 'cat', 'chair', 'cow', 'diningtable', - 'dog', 'horse', 'motorbike', 'person', 'pottedplant', 'sheep', 'sofa', 'train', 'tvmonitor'] -explanation_parameters = ExplanationParameters( - target_explain_group=TargetExplainGroup.CUSTOM, - target_explain_labels=[11, 14], # target classes to explain, also ['dog', 'person'] is a valid input + +voc_labels = [ + 'aeroplane', 'bicycle', 'bird', 'boat', 'bottle', 'bus', 'car', 'cat', 'chair', 'cow', 'diningtable', + 'dog', 'horse', 'motorbike', 'person', 'pottedplant', 'sheep', 'sofa', 'train', 'tvmonitor' +] + +# Run explanation +explanation = explainer( + image, + explain_mode=ExplainMode.WHITEBOX, + # target_layer="last_conv_node_name", # target_layer - node after which the XAI branch will be inserted, usually the last convolutional layer in the backbone + embed_scaling=True, # True by default. If set to True, the saliency map scale (0 ~ 255) operation is embedded in the model + explain_method=xai.Method.RECIPROCAM, # ReciproCAM is the default XAI method for CNNs label_names=voc_labels, - visualization_parameters=VisualizationParameters(overlay=True), # by default, saliency map overlay over image + target_explain_labels=[11, 14], # target classes to explain, also ['dog', 'person'] is a valid input, since label_names are provided + overlay=True, # False by default ) -explanation = explainer(image, explanation_parameters) -# Saving saliency maps +# Save saliency maps explanation.save("output_path", "name") ``` ## Black-Box mode -Black-box mode does not update the model (treating model as a black-box). +Black-box mode does not update the model (treating the model as a black box). Black-box approaches are based on the perturbation of the input data and measurement of the model's output change. -The process is repeated many times, which requires hundreds or thousands of forward passes -and introduces significant computational overhead. -`preprocess_fn` and `postprocess_fn` are required to be provided by the user for the black-box mode. +The process is repeated many times, requiring hundreds or thousands of forward passes and introducing **significant computational overhead**. + +`preprocess_fn` (or preprocessed images) and `postprocess_fn` are required to be provided by the user for black-box mode. ```python import cv2 import numpy as np import openvino.runtime as ov -from openvino.runtime.utils.data_helpers.wrappers import OVDict import openvino_xai as xai -from openvino_xai.explainer.explanation_parameters import ExplainMode, ExplanationParameters +from openvino_xai.explainer import ExplainMode def preprocess_fn(x: np.ndarray) -> np.ndarray: - # Implementing own pre-process function based on model's implementation + # Implementing our own pre-process function based on the model's implementation x = cv2.resize(src=x, dsize=(224, 224)) x = np.expand_dims(x, 0) return x - -def postprocess_fn(x: OVDict): - # Implementing own post-process function based on model's implementation - # Output logits - return x["logits"] - - -# Creating model +# Create ov.Model model = ov.Core().read_model("path/to/model.xml") # type: ov.Model -# Explainer object will prepare and load the model once in the beginning +# The Explainer object will prepare and load the model once at the beginning explainer = xai.Explainer( model, task=xai.Task.CLASSIFICATION, preprocess_fn=preprocess_fn, - postprocess_fn=postprocess_fn, - explain_mode=ExplainMode.BLACKBOX, ) # Generate and process saliency maps (as many as required, sequentially) image = cv2.imread("path/to/image.jpg") -explanation_parameters = ExplanationParameters( - target_explain_labels=[11, 14], # indices or string labels to explain -) + +# Run explanation explanation = explainer( image, - explanation_parameters, - num_masks=1000, # kwargs of the RISE algo + explain_mode=ExplainMode.BLACKBOX, + target_explain_labels=[11, 14], # target classes to explain + # target_explain_labels=-1, # explain all classes + overlay=True, # False by default + num_masks=1000, # kwargs for the RISE algorithm ) -# Saving saliency maps +# Save saliency maps explanation.save("output_path", "name") + ``` ## XAI insertion (white-box usage) -As mentioned above, saliency map generation requires model inference. -In the above use cases, OVXAI performs model inference using provided processing functions. -Alternative approach is to use OVXAI just to insert XAI branch into the model and infer it in the original pipeline. +As mentioned above, saliency map generation requires model inference. In the above use cases, OpenVINO XAI performs model inference using provided processing functions. An alternative approach is to use XAI to insert the XAI branch into the model and infer it in the original pipeline. `insert_xai()` API is used for insertion. -Note: original model outputs are not affected and the model should be inferable by the original inference pipeline. +**Note**: The original model outputs are not affected, and the model should be inferable by the original inference pipeline. ```python import openvino.runtime as ov - import openvino_xai as xai -from openvino_xai.inserter.parameters import ClassificationInsertionParameters -# Creating model +# Create an ov.Model model = ov.Core().read_model("path/to/model.xml") # type: ov.Model -# Optional - create insertion parameters -insertion_parameters = ClassificationInsertionParameters( - # target_layer="last_conv_node_name", # target_layer - node after which XAI branch will be inserted - embed_scaling=True, # True by default. If set to True, saliency map scale (0 ~ 255) operation is embedded in the model - explain_method=xai.Method.RECIPROCAM, # ReciproCAM is the default XAI method for CNNs -) - -# Inserting XAI branch into the model graph +# Insert XAI branch into the model graph model_xai = xai.insert_xai( model=model, task=xai.Task.CLASSIFICATION, - insertion_parameters=insertion_parameters, + # target_layer="last_conv_node_name", # target_layer - the node after which the XAI branch will be inserted, usually the last convolutional layer in the backbone + embed_scaling=True, # True by default. If set to True, the saliency map scale (0 ~ 255) operation is embedded in the model + explain_method=xai.Method.RECIPROCAM, # ReciproCAM is the default XAI method for CNNs ) # type: ov.Model # ***** Downstream task: user's code that infers model_xai and picks 'saliency_map' output ***** @@ -247,7 +294,7 @@ model_xai = xai.insert_xai( ## Example scripts -More usage scenarios are available in [examples](../../examples). +More usage scenarios that can be used with your own models and images as arguments are available in [examples](../../examples). ```python # Retrieve models by running tests @@ -256,6 +303,6 @@ pytest tests/test_classification.py # Run a bunch of classification examples # All outputs will be stored in the corresponding output directory -python examples/run_classification.py .data/otx_models/mlc_mobilenetv3_large_voc.xml \ +python examples/run_classification.py .data/otx_models/mlc_mobilenetv3_large_voc.xml tests/assets/cheetah_person.jpg --output output ```