Source code for pyreal.transformers.llm
import explingo
from pyreal.explanation_types import NarrativeExplanation
from pyreal.transformers import TransformerBase
[docs]class NarrativeTransformer(TransformerBase):
"""
Transforms explanations to narrative (natural-language) form.
"""
[docs] def __init__(
self,
llm=None,
openai_api_key=None,
num_features=5,
gpt_model_type="gpt-4o",
context_description="",
max_tokens=200,
training_examples=None,
**kwargs,
):
"""
Transforms explanations to narrative (natural-language) form.
Args:
llm (LLM model object): Local LLM object or LLM client object to use to generate \
narratives. One of `llm` or `openai_api_key` must be provided.
openai_api_key (string): OpenAI API key to use
x_orig (DataFrame of shape (n_instances, n_features):
Input to explain
num_features (int):
Number of features to include in the explanation, when relevant.
If None, all features will be included
gpt_model_type (string):
OpenAI model to use to generate the explanation, if passing in an openai api key
context_description (string):
Description of the model's prediction task, in sentence format. This will be
passed to the LLM and may help produce more accurate explanations.
For example: "The model predicts the price of houses."
max_tokens (int):
Maximum number of tokens to use in the explanation
training_examples (dictionary of string:list of tuples):
Few-shot training examples.
Keys are explanation type (currently support: "feature_contributions")
Values are lists of tuples, where the first element is the input to the model
and the second element is the example output.
Use the RealApp train_llm functions to populate this
Returns:
list of strings of length n_instances
Narrative version of feature contribution explanation, one item per instance
"""
self.llm = llm
self.openai_api_key = openai_api_key
if self.llm is None and self.openai_api_key is None:
raise ValueError("Must provide llm or openai_api_key")
self.narrators = {}
self.num_features = num_features
self.llm_model = gpt_model_type
self.context_description = context_description
self.max_tokens = max_tokens
if training_examples is not None:
self.training_examples = training_examples
else:
self.training_examples = {}
if "interpret" not in kwargs:
kwargs["interpret"] = True
if "model" not in kwargs:
kwargs["model"] = False
super().__init__(require_values=True, **kwargs)
def data_transform(self, x):
return x
def fit(self, x, **params):
return self
def set_training_examples(self, explanation_type, training_examples, replace=False):
"""
Set examples of narrative explanations for the request explanation type.
Args:
explanation_type (string):
Type of explanation to set examples for. Currently only "feature_contributions"
is supported.
training_examples (list of tuples):
List of tuples, where the first element is the input to the model
and the second element is the example output.
replace (bool):
If True, replace existing examples. If False, append to existing examples.
"""
if explanation_type not in ["feature_contributions"]:
raise ValueError(
"Invalid training example type %s. Expected one of ['feature_contributions']"
% explanation_type
)
if replace:
self.training_examples[explanation_type] = training_examples
else:
if self.training_examples.get(explanation_type) is None:
self.training_examples[explanation_type] = []
self.training_examples[explanation_type].extend(training_examples)
def transform_explanation_feature_contribution(self, explanation, num_features=None):
if "feature_contributions" not in self.narrators:
self.narrators["feature_contributions"] = explingo.Narrator(
llm=self.llm,
openai_api_key=self.openai_api_key,
gpt_model_name=self.llm_model,
explanation_format="(feature, feature_value, SHAP contribution)",
context=self.context_description,
sample_narratives=self.training_examples.get("feature_contribution"),
)
narrator = self.narrators["feature_contributions"]
if num_features is None:
num_features = self.num_features
narrative_explanations = []
parsed_explanations = self.parse_feature_contribution_explanation_for_llm(
explanation, num_features=num_features
)
for parsed_explanation in parsed_explanations:
narrative_explanations.append(narrator.narrate(parsed_explanation))
return NarrativeExplanation(narrative_explanations)
@staticmethod
def parse_feature_contribution_explanation_for_llm(explanation, num_features=None):
explanations = explanation.get_top_features(num_features=num_features)
parsed_explanations = []
for explanation in explanations:
strings = []
for feature, value, contribution in zip(
explanation[0].index, explanation[1], explanation[0]
):
strings.append(f"({feature}, {value}, {contribution})")
parsed_explanations.append(", ".join(strings))
return parsed_explanations