Create your Explainable Model#
Here you will find the required steps to build your explainable model from your original architecture.
Tip
You can retrieve the model expected input size and target size with the FittedSchema object, with its attribute
input_size and target_size. It includes the batch dimension as well as the first dimension.
For instance, with the previous fitted schema built along with the fitted_train_dataset:
input_size = fitted_train_dataset.fitted_schema.input_size # (1, 2), as it includes the batch dimension
target_size = fitted_train_dataset.fitted_schema.target_size # (1, 3), as it includes the batch dimension
1. Get your Original Pytorch Model#
Let's consider the following basic multi-layers perceptron architecture with an output of size 1 as your original model.
from torch.functional import F
import torch.nn as nn
class SimpleMLP(nn.Module):
def __init__(self, input_size, hidden_size1, hidden_size2, output_size):
super().__init__()
self.fc1 = nn.Linear(input_size, hidden_size1)
self.fc2 = nn.Linear(hidden_size1, hidden_size2)
self.fc3 = nn.Linear(hidden_size2, output_size)
def forward(self, x):
x = F.relu(self.fc1(x))
x = F.relu(self.fc2(x))
x = self.fc3(x)
return x
# Instantiate the model
input_size = 2 # Example input size
hidden_size1 = 64
hidden_size2 = 32
output_size = 3 # Example output size
model = SimpleMLP(input_size, hidden_size1, hidden_size2, output_size)
2. Convert your Original Model to an XpdeepModel#
Here, you need to convert your own model architecture into the explainable architecture.
We want to add a backbone model which will be responsible to project your input data into a better embedding space for your task.
import torch.nn.functional as F
import torch.nn as nn
class BackboneModel(nn.Module):
def __init__(self, input_size: int, hidden_size: int = 64):
super(BackboneModel, self).__init__()
self.fc = nn.Linear(input_size, hidden_size)
def forward(self, x):
x = F.relu(self.fc(x))
return x
Warning
The backbone model is frozen, you should use a pretrained model.
Future Release
The backbone model will be trainable.
👀 Full file preview
from create_schema import fitted_train_dataset
from torch import Tensor, nn
from torch.nn.functional import relu
from xpdeep.model.feature_extraction_output_type import FeatureExtractionOutputType
from xpdeep.model.model_parameters import ModelDecisionGraphParameters
from xpdeep.model.xpdeep_model import XpdeepModel
model_input_size = fitted_train_dataset.fitted_schema.input_size # (1, 2)
model_target_size = fitted_train_dataset.fitted_schema.target_size # (1, 3)
class BackboneModel(nn.Module):
def __init__(self, input_size: int, hidden_size: int = 64) -> None:
super().__init__()
self.fc = nn.Linear(input_size, hidden_size)
def forward(self, x: Tensor) -> Tensor:
"""Forward pass of the backbone model."""
return relu(self.fc(x))
class FeatureExtractionModel(nn.Module):
def __init__(self, input_size: int, hidden_size1: int = 32, output_size: int = 16) -> None:
super().__init__()
self.fc1 = nn.Linear(input_size, hidden_size1)
self.fc2 = nn.Linear(hidden_size1, output_size)
def forward(self, x: Tensor) -> Tensor:
"""Forward pass of the feature extraction model."""
x = relu(self.fc1(x))
return relu(self.fc2(x))
class TaskLearnerModel(nn.Module):
def __init__(self, input_size: int, hidden_size: int = 8, output_size: int = 3) -> None:
super().__init__()
self.fc1 = nn.Linear(input_size, hidden_size)
self.activation = nn.ReLU()
self.output = nn.Linear(hidden_size, output_size)
def forward(self, x: Tensor) -> Tensor:
"""Forward pass of the task learner model."""
x = self.fc1(x)
x = self.activation(x)
return self.output(x)
build_configuration = ModelDecisionGraphParameters(
feature_extraction_output_type=FeatureExtractionOutputType.VECTOR,
)
xpdeep_model = XpdeepModel.from_torch(
example_dataset=fitted_train_dataset,
feature_extraction=FeatureExtractionModel(input_size=64, output_size=16),
task_learner=TaskLearnerModel(input_size=16, output_size=model_target_size[1]),
backbone=BackboneModel(input_size=model_input_size[1], hidden_size=64),
decision_graph_parameters=build_configuration,
)
Now, you need to adapt your original model SimpleMLP into a feature extraction model and a task learner model.
Here please ensure that:
- The backbone model output size is equal to the feature extraction model input size.
- The feature extraction model output size is equal to the task learner model input size.
import torch.nn.functional as F
import torch.nn as nn
class FeatureExtractionModel(nn.Module):
def __init__(self, input_size: int, hidden_size1: int = 32, output_size: int = 16):
super(FeatureExtractionModel, self).__init__()
self.fc1 = nn.Linear(input_size, hidden_size1)
self.fc2 = nn.Linear(hidden_size1, output_size)
def forward(self, x):
x = F.relu(self.fc1(x))
x = F.relu(self.fc2(x))
return x
class TaskLearnerModel(nn.Module):
def __init__(self, input_size: int, hidden_size: int = 8, output_size: int = 3):
super(TaskLearnerModel, self).__init__()
self.fc1 = nn.Linear(input_size, hidden_size)
self.activation = nn.ReLU()
self.output = nn.Linear(hidden_size, output_size)
def forward(self, x):
x = self.fc1(x)
x = self.activation(x)
return self.output(x)
👀 Full file preview
from create_schema import fitted_train_dataset
from torch import Tensor, nn
from torch.nn.functional import relu
from xpdeep.model.feature_extraction_output_type import FeatureExtractionOutputType
from xpdeep.model.model_parameters import ModelDecisionGraphParameters
from xpdeep.model.xpdeep_model import XpdeepModel
model_input_size = fitted_train_dataset.fitted_schema.input_size # (1, 2)
model_target_size = fitted_train_dataset.fitted_schema.target_size # (1, 3)
class BackboneModel(nn.Module):
def __init__(self, input_size: int, hidden_size: int = 64) -> None:
super().__init__()
self.fc = nn.Linear(input_size, hidden_size)
def forward(self, x: Tensor) -> Tensor:
"""Forward pass of the backbone model."""
return relu(self.fc(x))
class FeatureExtractionModel(nn.Module):
def __init__(self, input_size: int, hidden_size1: int = 32, output_size: int = 16) -> None:
super().__init__()
self.fc1 = nn.Linear(input_size, hidden_size1)
self.fc2 = nn.Linear(hidden_size1, output_size)
def forward(self, x: Tensor) -> Tensor:
"""Forward pass of the feature extraction model."""
x = relu(self.fc1(x))
return relu(self.fc2(x))
class TaskLearnerModel(nn.Module):
def __init__(self, input_size: int, hidden_size: int = 8, output_size: int = 3) -> None:
super().__init__()
self.fc1 = nn.Linear(input_size, hidden_size)
self.activation = nn.ReLU()
self.output = nn.Linear(hidden_size, output_size)
def forward(self, x: Tensor) -> Tensor:
"""Forward pass of the task learner model."""
x = self.fc1(x)
x = self.activation(x)
return self.output(x)
build_configuration = ModelDecisionGraphParameters(
feature_extraction_output_type=FeatureExtractionOutputType.VECTOR,
)
xpdeep_model = XpdeepModel.from_torch(
example_dataset=fitted_train_dataset,
feature_extraction=FeatureExtractionModel(input_size=64, output_size=16),
task_learner=TaskLearnerModel(input_size=16, output_size=model_target_size[1]),
backbone=BackboneModel(input_size=model_input_size[1], hidden_size=64),
decision_graph_parameters=build_configuration,
)
3. Explainable Model Specifications#
XpdeepModel hyperparameters are stored under the ModelDecisionGraphParameters configuration class.
Those hyperparameters specify the model characteristics, its graph structure and complexity, as well as a set of its
learning parameters.
Similarly to specifying the parameters of a standard deep model, XpdeepModel hyperparameter values can either be the default values or adjusted according to the dataset complexity and the task at hand.
from xpdeep.model.model_parameters import ModelDecisionGraphParameters
from xpdeep.model.feature_extraction_output_type import FeatureExtractionOutputType
build_configuration = ModelDecisionGraphParameters(
feature_extraction_output_type=FeatureExtractionOutputType.VECTOR,
)
feature_extraction_output_type is an enum describing the output structure of the feature extraction model,
required to build the explainable model. The output structure may be a vector, an image matrix, a video tensor etc. Please
follow the API reference to find the available structures
FeatureExtractionOutputType.
Here, the feature extraction model output structure is a vector of size 16.
Please follow the API reference ModelDecisionGraphParameters
to get more details on each parameter.
👀 Full file preview
from create_schema import fitted_train_dataset
from torch import Tensor, nn
from torch.nn.functional import relu
from xpdeep.model.feature_extraction_output_type import FeatureExtractionOutputType
from xpdeep.model.model_parameters import ModelDecisionGraphParameters
from xpdeep.model.xpdeep_model import XpdeepModel
model_input_size = fitted_train_dataset.fitted_schema.input_size # (1, 2)
model_target_size = fitted_train_dataset.fitted_schema.target_size # (1, 3)
class BackboneModel(nn.Module):
def __init__(self, input_size: int, hidden_size: int = 64) -> None:
super().__init__()
self.fc = nn.Linear(input_size, hidden_size)
def forward(self, x: Tensor) -> Tensor:
"""Forward pass of the backbone model."""
return relu(self.fc(x))
class FeatureExtractionModel(nn.Module):
def __init__(self, input_size: int, hidden_size1: int = 32, output_size: int = 16) -> None:
super().__init__()
self.fc1 = nn.Linear(input_size, hidden_size1)
self.fc2 = nn.Linear(hidden_size1, output_size)
def forward(self, x: Tensor) -> Tensor:
"""Forward pass of the feature extraction model."""
x = relu(self.fc1(x))
return relu(self.fc2(x))
class TaskLearnerModel(nn.Module):
def __init__(self, input_size: int, hidden_size: int = 8, output_size: int = 3) -> None:
super().__init__()
self.fc1 = nn.Linear(input_size, hidden_size)
self.activation = nn.ReLU()
self.output = nn.Linear(hidden_size, output_size)
def forward(self, x: Tensor) -> Tensor:
"""Forward pass of the task learner model."""
x = self.fc1(x)
x = self.activation(x)
return self.output(x)
build_configuration = ModelDecisionGraphParameters(
feature_extraction_output_type=FeatureExtractionOutputType.VECTOR,
)
xpdeep_model = XpdeepModel.from_torch(
example_dataset=fitted_train_dataset,
feature_extraction=FeatureExtractionModel(input_size=64, output_size=16),
task_learner=TaskLearnerModel(input_size=16, output_size=model_target_size[1]),
backbone=BackboneModel(input_size=model_input_size[1], hidden_size=64),
decision_graph_parameters=build_configuration,
)
4. Create the Explainable Model#
Once in possession of your sub-models defined in
section 2, you can finally instantiate the
XpdeepModel.
Note
An additional parameter fitted_schema is required by torch.export to serialize the model.
It encapsulates the input size, without the batch dimension, given to your XpdeepModel.
Therefore, input_size is either the BackboneModel input size (if a BackboneModel was provided), or to the
FeatureExtractionModel input size otherwise. Here, as we have a BackboneModel, the input size is (10,) and is
inferred by the fitted schema.
Assuming you already got your fitted schema from your train dataset, otherwise follow the tutorial here.
from xpdeep.model.xpdeep_model import XpdeepModel
xpdeep_model = XpdeepModel.from_torch(
fitted_schema=fitted_train_dataset.fitted_schema,
feature_extraction=FeatureExtractionModel(input_size=64, output_size=16),
task_learner=TaskLearnerModel(input_size=16, output_size=target_size[0]),
backbone=BackboneModel(input_size=input_size[1], hidden_size=64),
decision_graph_parameters=build_configuration
)
👀 Full file preview
from create_schema import fitted_train_dataset
from torch import Tensor, nn
from torch.nn.functional import relu
from xpdeep.model.feature_extraction_output_type import FeatureExtractionOutputType
from xpdeep.model.model_parameters import ModelDecisionGraphParameters
from xpdeep.model.xpdeep_model import XpdeepModel
model_input_size = fitted_train_dataset.fitted_schema.input_size # (1, 2)
model_target_size = fitted_train_dataset.fitted_schema.target_size # (1, 3)
class BackboneModel(nn.Module):
def __init__(self, input_size: int, hidden_size: int = 64) -> None:
super().__init__()
self.fc = nn.Linear(input_size, hidden_size)
def forward(self, x: Tensor) -> Tensor:
"""Forward pass of the backbone model."""
return relu(self.fc(x))
class FeatureExtractionModel(nn.Module):
def __init__(self, input_size: int, hidden_size1: int = 32, output_size: int = 16) -> None:
super().__init__()
self.fc1 = nn.Linear(input_size, hidden_size1)
self.fc2 = nn.Linear(hidden_size1, output_size)
def forward(self, x: Tensor) -> Tensor:
"""Forward pass of the feature extraction model."""
x = relu(self.fc1(x))
return relu(self.fc2(x))
class TaskLearnerModel(nn.Module):
def __init__(self, input_size: int, hidden_size: int = 8, output_size: int = 3) -> None:
super().__init__()
self.fc1 = nn.Linear(input_size, hidden_size)
self.activation = nn.ReLU()
self.output = nn.Linear(hidden_size, output_size)
def forward(self, x: Tensor) -> Tensor:
"""Forward pass of the task learner model."""
x = self.fc1(x)
x = self.activation(x)
return self.output(x)
build_configuration = ModelDecisionGraphParameters(
feature_extraction_output_type=FeatureExtractionOutputType.VECTOR,
)
xpdeep_model = XpdeepModel.from_torch(
example_dataset=fitted_train_dataset,
feature_extraction=FeatureExtractionModel(input_size=64, output_size=16),
task_learner=TaskLearnerModel(input_size=16, output_size=model_target_size[1]),
backbone=BackboneModel(input_size=model_input_size[1], hidden_size=64),
decision_graph_parameters=build_configuration,
)
With your explainable datasets, you can now train your model !